diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 03:59:35 +0000 |
commit | d1b2d29528b7794b41e66fc2136e395a02f8529b (patch) | |
tree | a4a17504b260206dec3cf55b2dca82929a348ac2 /vendor/sysinfo-0.26.7/src/apple | |
parent | Releasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.tar.xz rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.zip |
Merging upstream version 1.73.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/sysinfo-0.26.7/src/apple')
23 files changed, 3990 insertions, 0 deletions
diff --git a/vendor/sysinfo-0.26.7/src/apple/app_store/component.rs b/vendor/sysinfo-0.26.7/src/apple/app_store/component.rs new file mode 100644 index 000000000..914fc9406 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/app_store/component.rs @@ -0,0 +1,26 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::ComponentExt; + +#[doc = include_str!("../../../md_doc/component.md")] +pub struct Component {} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + 0.0 + } + + fn max(&self) -> f32 { + 0.0 + } + + fn critical(&self) -> Option<f32> { + None + } + + fn label(&self) -> &str { + "" + } + + fn refresh(&mut self) {} +} diff --git a/vendor/sysinfo-0.26.7/src/apple/app_store/mod.rs b/vendor/sysinfo-0.26.7/src/apple/app_store/mod.rs new file mode 100644 index 000000000..0df24cabf --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/app_store/mod.rs @@ -0,0 +1,4 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub mod component; +pub mod process; diff --git a/vendor/sysinfo-0.26.7/src/apple/app_store/process.rs b/vendor/sysinfo-0.26.7/src/apple/app_store/process.rs new file mode 100644 index 000000000..7c43697a6 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/app_store/process.rs @@ -0,0 +1,84 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::path::Path; + +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessStatus, Signal, Uid}; + +#[doc = include_str!("../../../md_doc/process.md")] +pub struct Process; + +impl ProcessExt for Process { + fn kill_with(&self, _signal: Signal) -> Option<bool> { + None + } + + fn name(&self) -> &str { + "" + } + + fn cmd(&self) -> &[String] { + &[] + } + + fn exe(&self) -> &Path { + Path::new("/") + } + + fn pid(&self) -> Pid { + Pid(0) + } + + fn environ(&self) -> &[String] { + &[] + } + + fn cwd(&self) -> &Path { + Path::new("/") + } + + fn root(&self) -> &Path { + Path::new("/") + } + + fn memory(&self) -> u64 { + 0 + } + + fn virtual_memory(&self) -> u64 { + 0 + } + + fn parent(&self) -> Option<Pid> { + None + } + + fn status(&self) -> ProcessStatus { + ProcessStatus::Unknown(0) + } + + fn start_time(&self) -> u64 { + 0 + } + + fn run_time(&self) -> u64 { + 0 + } + + fn cpu_usage(&self) -> f32 { + 0.0 + } + + fn disk_usage(&self) -> DiskUsage { + DiskUsage::default() + } + + fn user_id(&self) -> Option<&Uid> { + None + } + + fn group_id(&self) -> Option<Gid> { + None + } + + fn wait(&self) {} +} diff --git a/vendor/sysinfo-0.26.7/src/apple/component.rs b/vendor/sysinfo-0.26.7/src/apple/component.rs new file mode 100644 index 000000000..7c4196e72 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/component.rs @@ -0,0 +1,3 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub use crate::sys::inner::component::*; diff --git a/vendor/sysinfo-0.26.7/src/apple/cpu.rs b/vendor/sysinfo-0.26.7/src/apple/cpu.rs new file mode 100644 index 000000000..e613bdd2c --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/cpu.rs @@ -0,0 +1,331 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::system::get_sys_value; + +use crate::{CpuExt, CpuRefreshKind}; + +use libc::{c_char, host_processor_info, mach_task_self}; +use std::mem; +use std::ops::Deref; +use std::sync::Arc; + +pub(crate) struct UnsafePtr<T>(*mut T); + +unsafe impl<T> Send for UnsafePtr<T> {} +unsafe impl<T> Sync for UnsafePtr<T> {} + +impl<T> Deref for UnsafePtr<T> { + type Target = *mut T; + + fn deref(&self) -> &*mut T { + &self.0 + } +} + +pub(crate) struct CpuData { + pub cpu_info: UnsafePtr<i32>, + pub num_cpu_info: u32, +} + +impl CpuData { + pub fn new(cpu_info: *mut i32, num_cpu_info: u32) -> CpuData { + CpuData { + cpu_info: UnsafePtr(cpu_info), + num_cpu_info, + } + } +} + +impl Drop for CpuData { + fn drop(&mut self) { + if !self.cpu_info.0.is_null() { + let prev_cpu_info_size = std::mem::size_of::<i32>() as u32 * self.num_cpu_info; + unsafe { + libc::vm_deallocate( + mach_task_self(), + self.cpu_info.0 as _, + prev_cpu_info_size as _, + ); + } + self.cpu_info.0 = std::ptr::null_mut(); + } + } +} + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu { + name: String, + cpu_usage: f32, + cpu_data: Arc<CpuData>, + frequency: u64, + vendor_id: String, + brand: String, +} + +impl Cpu { + pub(crate) fn new( + name: String, + cpu_data: Arc<CpuData>, + frequency: u64, + vendor_id: String, + brand: String, + ) -> Cpu { + Cpu { + name, + cpu_usage: 0f32, + cpu_data, + frequency, + vendor_id, + brand, + } + } + + pub(crate) fn set_cpu_usage(&mut self, cpu_usage: f32) { + self.cpu_usage = cpu_usage; + } + + pub(crate) fn update(&mut self, cpu_usage: f32, cpu_data: Arc<CpuData>) { + self.cpu_usage = cpu_usage; + self.cpu_data = cpu_data; + } + + pub(crate) fn data(&self) -> Arc<CpuData> { + Arc::clone(&self.cpu_data) + } + + pub(crate) fn set_frequency(&mut self, frequency: u64) { + self.frequency = frequency; + } +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn name(&self) -> &str { + &self.name + } + + /// Returns the CPU frequency in MHz. + fn frequency(&self) -> u64 { + self.frequency + } + + fn vendor_id(&self) -> &str { + &self.vendor_id + } + + fn brand(&self) -> &str { + &self.brand + } +} + +pub(crate) fn get_cpu_frequency() -> u64 { + let mut speed: u64 = 0; + let mut len = std::mem::size_of::<u64>(); + unsafe { + libc::sysctlbyname( + b"hw.cpufrequency\0".as_ptr() as *const c_char, + &mut speed as *mut _ as _, + &mut len, + std::ptr::null_mut(), + 0, + ); + speed / 1_000_000 + } +} + +#[inline] +fn get_in_use(cpu_info: *mut i32, offset: isize) -> i32 { + unsafe { + *cpu_info.offset(offset + libc::CPU_STATE_USER as isize) + + *cpu_info.offset(offset + libc::CPU_STATE_SYSTEM as isize) + + *cpu_info.offset(offset + libc::CPU_STATE_NICE as isize) + } +} + +#[inline] +fn get_idle(cpu_info: *mut i32, offset: isize) -> i32 { + unsafe { *cpu_info.offset(offset + libc::CPU_STATE_IDLE as isize) } +} + +pub(crate) fn compute_usage_of_cpu(proc_: &Cpu, cpu_info: *mut i32, offset: isize) -> f32 { + let old_cpu_info = proc_.data().cpu_info.0; + let in_use; + let total; + + // In case we are initializing cpus, there is no "old value" yet. + if old_cpu_info == cpu_info { + in_use = get_in_use(cpu_info, offset); + total = in_use + get_idle(cpu_info, offset); + } else { + in_use = get_in_use(cpu_info, offset) - get_in_use(old_cpu_info, offset); + total = in_use + (get_idle(cpu_info, offset) - get_idle(old_cpu_info, offset)); + } + in_use as f32 / total as f32 * 100. +} + +pub(crate) fn update_cpu_usage<F: FnOnce(Arc<CpuData>, *mut i32) -> (f32, usize)>( + port: libc::mach_port_t, + global_cpu: &mut Cpu, + f: F, +) { + let mut num_cpu_u = 0u32; + let mut cpu_info: *mut i32 = std::ptr::null_mut(); + let mut num_cpu_info = 0u32; + + let mut total_cpu_usage = 0f32; + + unsafe { + if host_processor_info( + port, + libc::PROCESSOR_CPU_LOAD_INFO, + &mut num_cpu_u as *mut u32, + &mut cpu_info as *mut *mut i32, + &mut num_cpu_info as *mut u32, + ) == libc::KERN_SUCCESS + { + let (total_percentage, len) = + f(Arc::new(CpuData::new(cpu_info, num_cpu_info)), cpu_info); + total_cpu_usage = total_percentage / len as f32; + } + global_cpu.set_cpu_usage(total_cpu_usage); + } +} + +pub(crate) fn init_cpus( + port: libc::mach_port_t, + cpus: &mut Vec<Cpu>, + global_cpu: &mut Cpu, + refresh_kind: CpuRefreshKind, +) { + let mut num_cpu = 0; + let mut mib = [0, 0]; + + let (vendor_id, brand) = get_vendor_id_and_brand(); + let frequency = if refresh_kind.frequency() { + get_cpu_frequency() + } else { + 0 + }; + + unsafe { + if !get_sys_value( + libc::CTL_HW as _, + libc::HW_NCPU as _, + mem::size_of::<u32>(), + &mut num_cpu as *mut _ as *mut _, + &mut mib, + ) { + num_cpu = 1; + } + } + update_cpu_usage(port, global_cpu, |proc_data, cpu_info| { + let mut percentage = 0f32; + let mut offset = 0; + for i in 0..num_cpu { + let mut p = Cpu::new( + format!("{}", i + 1), + Arc::clone(&proc_data), + frequency, + vendor_id.clone(), + brand.clone(), + ); + if refresh_kind.cpu_usage() { + let cpu_usage = compute_usage_of_cpu(&p, cpu_info, offset); + p.set_cpu_usage(cpu_usage); + percentage += p.cpu_usage(); + } + cpus.push(p); + + offset += libc::CPU_STATE_MAX as isize; + } + (percentage, cpus.len()) + }); + + // We didn't set them above to avoid cloning them unnecessarily. + global_cpu.brand = brand; + global_cpu.vendor_id = vendor_id; + global_cpu.frequency = frequency; +} + +fn get_sysctl_str(s: &[u8]) -> String { + let mut len = 0; + + unsafe { + libc::sysctlbyname( + s.as_ptr() as *const c_char, + std::ptr::null_mut(), + &mut len, + std::ptr::null_mut(), + 0, + ); + if len < 1 { + return String::new(); + } + + let mut buf = Vec::with_capacity(len); + libc::sysctlbyname( + s.as_ptr() as *const c_char, + buf.as_mut_ptr() as _, + &mut len, + std::ptr::null_mut(), + 0, + ); + if len > 0 { + buf.set_len(len); + while buf.last() == Some(&b'\0') { + buf.pop(); + } + String::from_utf8(buf).unwrap_or_else(|_| String::new()) + } else { + String::new() + } + } +} + +pub(crate) fn get_vendor_id_and_brand() -> (String, String) { + // On apple M1, `sysctl machdep.cpu.vendor` returns "", so fallback to "Apple" if the result + // is empty. + let mut vendor = get_sysctl_str(b"machdep.cpu.vendor\0"); + if vendor.is_empty() { + vendor = "Apple".to_string(); + } + + (vendor, get_sysctl_str(b"machdep.cpu.brand_string\0")) +} + +#[cfg(test)] +mod test { + use crate::*; + use std::process::Command; + + #[test] + fn check_vendor_and_brand() { + let child = Command::new("sysctl") + .arg("-a") + .output() + .expect("Failed to start command..."); + + assert!(child.status.success()); + let stdout = String::from_utf8(child.stdout).expect("Not valid UTF8"); + + let sys = System::new_with_specifics( + crate::RefreshKind::new().with_cpu(CpuRefreshKind::new().with_cpu_usage()), + ); + let cpus = sys.cpus(); + assert!(!cpus.is_empty(), "no CPU found"); + if let Some(line) = stdout.lines().find(|l| l.contains("machdep.cpu.vendor")) { + let sysctl_value = line.split(':').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(':').nth(1).unwrap(); + assert_eq!(cpus[0].brand(), sysctl_value.trim()); + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/disk.rs b/vendor/sysinfo-0.26.7/src/apple/disk.rs new file mode 100644 index 000000000..d866d5bba --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/disk.rs @@ -0,0 +1,395 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::{ + ffi, + utils::{self, CFReleaser}, +}; +use crate::{DiskExt, DiskType}; + +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::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 { + pub(crate) type_: DiskType, + 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, +} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + self.type_ + } + + fn name(&self) -> &OsStr { + &self.name + } + + fn file_system(&self) -> &[u8] { + &self.file_system + } + + fn mount_point(&self) -> &Path { + &self.mount_point + } + + fn total_space(&self) -> u64 { + self.total_space + } + + fn available_space(&self) -> u64 { + self.available_space + } + + fn is_removable(&self) -> bool { + self.is_removable + } + + fn refresh(&mut self) -> bool { + unsafe { + 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-0.26.7/src/apple/ffi.rs b/vendor/sysinfo-0.26.7/src/apple/ffi.rs new file mode 100644 index 000000000..72822202f --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/ffi.rs @@ -0,0 +1,38 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +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::*; + +#[link(name = "CoreFoundation", kind = "framework")] +extern "C" { + pub fn CFURLCopyResourcePropertiesForKeys( + url: CFURLRef, + keys: CFArrayRef, + error: *mut CFErrorRef, + ) -> CFDictionaryRef; + + 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)] +#[repr(C)] +pub struct Val_t { + pub key: [i8; 5], + pub data_size: u32, + pub data_type: [i8; 5], // UInt32Char_t + pub bytes: [i8; 32], // SMCBytes_t +} diff --git a/vendor/sysinfo-0.26.7/src/apple/ios.rs b/vendor/sysinfo-0.26.7/src/apple/ios.rs new file mode 100644 index 000000000..0393c5ec6 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/ios.rs @@ -0,0 +1,5 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub mod ffi {} +pub use crate::sys::app_store::component; +pub use crate::sys::app_store::process; diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/component/arm.rs b/vendor/sysinfo-0.26.7/src/apple/macos/component/arm.rs new file mode 100644 index 000000000..328ffebfa --- /dev/null +++ b/vendor/sysinfo-0.26.7/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-0.26.7/src/apple/macos/component/mod.rs b/vendor/sysinfo-0.26.7/src/apple/macos/component/mod.rs new file mode 100644 index 000000000..50b359e61 --- /dev/null +++ b/vendor/sysinfo-0.26.7/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-0.26.7/src/apple/macos/component/x86.rs b/vendor/sysinfo-0.26.7/src/apple/macos/component/x86.rs new file mode 100644 index 000000000..415f90455 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/component/x86.rs @@ -0,0 +1,326 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::{ffi, macos::utils::IOReleaser}; +use crate::ComponentExt; + +use libc::{c_char, c_int, c_void}; + +use std::mem; + +const COMPONENTS_TEMPERATURE_IDS: &[(&str, &[i8])] = &[ + ("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'C' as i8]), // PECI CPU "TCXC" + ("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'c' as i8]), // PECI CPU "TCXc" + ( + "CPU Proximity", + &['T' as i8, 'C' as i8, '0' as i8, 'P' as i8], + ), // CPU Proximity (heat spreader) "TC0P" + ("GPU", &['T' as i8, 'G' as i8, '0' as i8, 'P' as i8]), // GPU "TG0P" + ("Battery", &['T' as i8, 'B' as i8, '0' as i8, 'T' as i8]), // Battery "TB0T" +]; + +pub(crate) struct ComponentFFI { + input_structure: ffi::KeyData_t, + val: ffi::Val_t, + /// 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], connection: ffi::io_connect_t) -> Option<ComponentFFI> { + unsafe { + get_key_size(connection, key) + .ok() + .map(|(input_structure, val)| ComponentFFI { + input_structure, + val, + connection, + }) + } + } + + 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")] +pub struct Component { + temperature: f32, + max: f32, + critical: Option<f32>, + label: String, + ffi_part: ComponentFFI, +} + +impl Component { + /// Creates a new `Component` with the given information. + pub(crate) fn new( + label: String, + max: Option<f32>, + critical: Option<f32>, + key: &[i8], + connection: ffi::io_connect_t, + ) -> Option<Component> { + let ffi_part = ComponentFFI::new(key, connection)?; + ffi_part.temperature().map(|temperature| Component { + temperature, + label, + max: max.unwrap_or(temperature), + critical, + ffi_part, + }) + } +} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature + } + + fn max(&self) -> f32 { + self.max + } + + fn critical(&self) -> Option<f32> { + self.critical + } + + fn label(&self) -> &str { + &self.label + } + + fn refresh(&mut self) { + if let Some(temp) = self.ffi_part.temperature() { + self.temperature = temp; + if self.temperature > self.max { + self.max = self.temperature; + } + } + } +} + +unsafe fn perform_call( + conn: ffi::io_connect_t, + index: c_int, + input_structure: *const ffi::KeyData_t, + output_structure: *mut ffi::KeyData_t, +) -> i32 { + let mut structure_output_size = mem::size_of::<ffi::KeyData_t>(); + + ffi::IOConnectCallStructMethod( + conn, + index as u32, + input_structure, + mem::size_of::<ffi::KeyData_t>(), + output_structure, + &mut structure_output_size, + ) +} + +// Adapted from https://github.com/lavoiesl/osx-cpu-temp/blob/master/smc.c#L28 +#[inline] +fn strtoul(s: &[i8]) -> u32 { + unsafe { + ((*s.get_unchecked(0) as u32) << (3u32 << 3)) + + ((*s.get_unchecked(1) as u32) << (2u32 << 3)) + + ((*s.get_unchecked(2) as u32) << (1u32 << 3)) + + (*s.get_unchecked(3) as u32) + } +} + +#[inline] +unsafe fn ultostr(s: *mut c_char, val: u32) { + *s.offset(0) = ((val >> 24) % 128) as i8; + *s.offset(1) = ((val >> 16) % 128) as i8; + *s.offset(2) = ((val >> 8) % 128) as i8; + *s.offset(3) = (val % 128) as i8; + *s.offset(4) = 0; +} + +unsafe fn get_key_size( + con: ffi::io_connect_t, + key: &[i8], +) -> Result<(ffi::KeyData_t, ffi::Val_t), i32> { + let mut input_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>(); + let mut output_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>(); + let mut val: ffi::Val_t = mem::zeroed::<ffi::Val_t>(); + + input_structure.key = strtoul(key); + input_structure.data8 = ffi::SMC_CMD_READ_KEYINFO; + + let result = perform_call( + con, + ffi::KERNEL_INDEX_SMC, + &input_structure, + &mut output_structure, + ); + if result != ffi::KIO_RETURN_SUCCESS { + return Err(result); + } + + val.data_size = output_structure.key_info.data_size; + ultostr( + val.data_type.as_mut_ptr(), + output_structure.key_info.data_type, + ); + input_structure.key_info.data_size = val.data_size; + input_structure.data8 = ffi::SMC_CMD_READ_BYTES; + Ok((input_structure, val)) +} + +unsafe fn read_key( + con: ffi::io_connect_t, + input_structure: &ffi::KeyData_t, + mut val: ffi::Val_t, +) -> Result<ffi::Val_t, i32> { + let mut output_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>(); + + match perform_call( + con, + ffi::KERNEL_INDEX_SMC, + input_structure, + &mut output_structure, + ) { + ffi::KIO_RETURN_SUCCESS => { + libc::memcpy( + val.bytes.as_mut_ptr() as *mut c_void, + output_structure.bytes.as_mut_ptr() as *mut c_void, + mem::size_of::<[u8; 32]>(), + ); + Ok(val) + } + result => Err(result), + } +} + +fn get_temperature_inner( + con: ffi::io_connect_t, + input_structure: &ffi::KeyData_t, + original_val: &ffi::Val_t, +) -> Option<f32> { + unsafe { + if let Ok(val) = read_key(con, input_structure, (*original_val).clone()) { + if val.data_size > 0 + && libc::strcmp(val.data_type.as_ptr(), b"sp78\0".as_ptr() as *const i8) == 0 + { + // convert sp78 value to temperature + let x = (i32::from(val.bytes[0]) << 6) + (i32::from(val.bytes[1]) >> 2); + return Some(x as f32 / 64f32); + } + } + } + None +} + +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-0.26.7/src/apple/macos/disk.rs b/vendor/sysinfo-0.26.7/src/apple/macos/disk.rs new file mode 100644 index 000000000..3a4372a2f --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/disk.rs @@ -0,0 +1,126 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +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}; +use core_foundation_sys::string as cfs; + +use std::ffi::CStr; + +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, + ))? + }; + + // 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()) }; + + if matching.is_null() { + return None; + } + + 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; + } + + // 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) }; + + 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; + } + + current_service_entry = match IOReleaser::new(parent_entry) { + Some(service) => service, + // There were no more parents left + None => break, + }; + + 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), + ) + }; + + 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); + } + } + } + } + + None +} diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/ffi.rs b/vendor/sysinfo-0.26.7/src/apple/macos/ffi.rs new file mode 100644 index 000000000..0b9c82cfa --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/ffi.rs @@ -0,0 +1,291 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +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, kern_return_t}; + +// Note: IOKit is only available on MacOS up until very recent iOS versions: https://developer.apple.com/documentation/iokit + +#[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; + +pub type IOOptionBits = u32; + +#[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( + 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) -> kern_return_t; + + pub fn IORegistryEntryCreateCFProperty( + entry: io_registry_entry_t, + key: CFStringRef, + allocator: CFAllocatorRef, + 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 fn IOBSDNameMatching( + mainPort: mach_port_t, + options: u32, + bsdName: *const c_char, + ) -> CFMutableDictionaryRef; + + // 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(all( + not(feature = "apple-sandbox"), + any(target_arch = "x86", target_arch = "x86_64") +))] +mod io_service { + 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_connect_t = io_object_t; + + #[allow(non_camel_case_types)] + 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)] + pub struct KeyData_vers_t { + pub major: u8, + pub minor: u8, + pub build: u8, + pub reserved: [u8; 1], + pub release: u16, + } + + #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] + #[repr(C)] + pub struct KeyData_pLimitData_t { + pub version: u16, + pub length: u16, + pub cpu_plimit: u32, + pub gpu_plimit: u32, + pub mem_plimit: u32, + } + + #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] + #[repr(C)] + pub struct KeyData_keyInfo_t { + pub data_size: u32, + pub data_type: u32, + pub data_attributes: u8, + } + + #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] + #[repr(C)] + pub struct KeyData_t { + pub key: u32, + pub vers: KeyData_vers_t, + pub p_limit_data: KeyData_pLimitData_t, + pub key_info: KeyData_keyInfo_t, + pub result: u8, + pub status: u8, + pub data8: u8, + pub data32: u32, + pub bytes: [i8; 32], // SMCBytes_t + } + + #[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; +} + +#[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-0.26.7/src/apple/macos/mod.rs b/vendor/sysinfo-0.26.7/src/apple/macos/mod.rs new file mode 100644 index 000000000..856c5931d --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/mod.rs @@ -0,0 +1,20 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +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-0.26.7/src/apple/macos/process.rs b/vendor/sysinfo-0.26.7/src/apple/macos/process.rs new file mode 100644 index 000000000..fff9c1f71 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/process.rs @@ -0,0 +1,688 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::ffi::CStr; +use std::mem::{self, MaybeUninit}; +use std::ops::Deref; +use std::path::{Path, PathBuf}; + +use std::borrow::Borrow; + +use libc::{c_int, c_void, kill, size_t}; + +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; + +use crate::sys::process::ThreadStatus; +use crate::sys::system::Wrap; + +#[doc = include_str!("../../../md_doc/process.md")] +pub struct Process { + pub(crate) name: String, + pub(crate) cmd: Vec<String>, + pub(crate) exe: PathBuf, + pid: Pid, + parent: Option<Pid>, + pub(crate) environ: Vec<String>, + cwd: PathBuf, + pub(crate) root: PathBuf, + pub(crate) memory: u64, + pub(crate) virtual_memory: u64, + old_utime: u64, + old_stime: u64, + start_time: u64, + run_time: u64, + pub(crate) updated: bool, + cpu_usage: f32, + user_id: Option<Uid>, + group_id: Option<Gid>, + pub(crate) process_status: ProcessStatus, + /// Status of process (running, stopped, waiting, etc). `None` means `sysinfo` doesn't have + /// enough rights to get this information. + /// + /// This is very likely this one that you want instead of `process_status`. + pub status: Option<ThreadStatus>, + pub(crate) old_read_bytes: u64, + pub(crate) old_written_bytes: u64, + pub(crate) read_bytes: u64, + pub(crate) written_bytes: u64, +} + +impl Process { + pub(crate) fn new_empty(pid: Pid, exe: PathBuf, name: String, cwd: PathBuf) -> Process { + Process { + name, + pid, + parent: None, + cmd: Vec::new(), + environ: Vec::new(), + exe, + cwd, + root: PathBuf::new(), + memory: 0, + virtual_memory: 0, + cpu_usage: 0., + old_utime: 0, + old_stime: 0, + updated: true, + start_time: 0, + run_time: 0, + user_id: None, + group_id: None, + process_status: ProcessStatus::Unknown(0), + status: None, + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + } + } + + pub(crate) fn new(pid: Pid, parent: Option<Pid>, start_time: u64, run_time: u64) -> Process { + Process { + name: String::new(), + pid, + parent, + cmd: Vec::new(), + environ: Vec::new(), + exe: PathBuf::new(), + cwd: PathBuf::new(), + root: PathBuf::new(), + memory: 0, + virtual_memory: 0, + cpu_usage: 0., + old_utime: 0, + old_stime: 0, + updated: true, + start_time, + run_time, + user_id: None, + group_id: None, + process_status: ProcessStatus::Unknown(0), + status: None, + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + } + } +} + +impl ProcessExt for Process { + fn kill_with(&self, signal: Signal) -> Option<bool> { + let c_signal = crate::sys::system::convert_signal(signal)?; + unsafe { Some(kill(self.pid.0, c_signal) == 0) } + } + + fn name(&self) -> &str { + &self.name + } + + fn cmd(&self) -> &[String] { + &self.cmd + } + + fn exe(&self) -> &Path { + self.exe.as_path() + } + + fn pid(&self) -> Pid { + self.pid + } + + fn environ(&self) -> &[String] { + &self.environ + } + + fn cwd(&self) -> &Path { + self.cwd.as_path() + } + + fn root(&self) -> &Path { + self.root.as_path() + } + + fn memory(&self) -> u64 { + self.memory + } + + fn virtual_memory(&self) -> u64 { + self.virtual_memory + } + + fn parent(&self) -> Option<Pid> { + self.parent + } + + fn status(&self) -> ProcessStatus { + self.process_status + } + + fn start_time(&self) -> u64 { + self.start_time + } + + fn run_time(&self) -> u64 { + self.run_time + } + + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn disk_usage(&self) -> DiskUsage { + DiskUsage { + read_bytes: self.read_bytes - self.old_read_bytes, + total_read_bytes: self.read_bytes, + written_bytes: self.written_bytes - self.old_written_bytes, + total_written_bytes: self.written_bytes, + } + } + + fn user_id(&self) -> Option<&Uid> { + self.user_id.as_ref() + } + + fn group_id(&self) -> Option<Gid> { + self.group_id + } + + 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. +pub(crate) fn compute_cpu_usage( + p: &mut Process, + task_info: libc::proc_taskinfo, + system_time: u64, + user_time: u64, + time_interval: Option<f64>, +) { + if let Some(time_interval) = time_interval { + let total_existing_time = p.old_stime.saturating_add(p.old_utime); + if time_interval > 0.000001 && total_existing_time > 0 { + let total_current_time = task_info + .pti_total_system + .saturating_add(task_info.pti_total_user); + + let total_time_diff = total_current_time.saturating_sub(total_existing_time); + if total_time_diff > 0 { + p.cpu_usage = (total_time_diff as f64 / time_interval * 100.) as f32; + } + } else { + p.cpu_usage = 0.; + } + p.old_stime = task_info.pti_total_system; + p.old_utime = task_info.pti_total_user; + } else { + unsafe { + // This is the "backup way" of CPU computation. + let time = libc::mach_absolute_time(); + let task_time = user_time + .saturating_add(system_time) + .saturating_add(task_info.pti_total_user) + .saturating_add(task_info.pti_total_system); + + let system_time_delta = if task_time < p.old_utime { + task_time + } else { + task_time.saturating_sub(p.old_utime) + }; + let time_delta = if time < p.old_stime { + time + } else { + time.saturating_sub(p.old_stime) + }; + p.old_utime = task_time; + p.old_stime = time; + p.cpu_usage = if time_delta == 0 { + 0f32 + } else { + (system_time_delta as f64 * 100f64 / time_delta as f64) as f32 + }; + } + } +} + +/*pub fn set_time(p: &mut Process, utime: u64, stime: u64) { + p.old_utime = p.utime; + p.old_stime = p.stime; + p.utime = utime; + p.stime = stime; + p.updated = true; +}*/ + +unsafe fn get_task_info(pid: Pid) -> libc::proc_taskinfo { + let mut task_info = mem::zeroed::<libc::proc_taskinfo>(); + // If it doesn't work, we just don't have memory information for this process + // so it's "fine". + libc::proc_pidinfo( + pid.0, + libc::PROC_PIDTASKINFO, + 0, + &mut task_info as *mut libc::proc_taskinfo as *mut c_void, + mem::size_of::<libc::proc_taskinfo>() as _, + ); + task_info +} + +#[inline] +fn check_if_pid_is_alive(pid: Pid, 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] +fn do_not_get_env_path(_: &str, _: &mut PathBuf, _: &mut bool) {} + +#[inline] +fn do_get_env_path(env: &str, root: &mut PathBuf, check: &mut bool) { + if *check && env.starts_with("PATH=") { + *check = false; + *root = Path::new(&env[5..]).to_path_buf(); + } +} + +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, + now: u64, + refresh_kind: ProcessRefreshKind, + info: Option<libc::proc_bsdinfo>, +) -> Result<Option<Process>, ()> { + 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 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, + buffer.as_mut_ptr() as *mut _, + libc::PROC_PIDPATHINFO_MAXSIZE as _, + ) { + x if x > 0 => { + buffer.set_len(x as _); + let tmp = String::from_utf8_unchecked(buffer); + let exe = PathBuf::from(tmp); + let name = exe + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .to_owned(); + return Ok(Some(Process::new_empty(pid, exe, name, cwd))); + } + _ => {} + } + return Err(()); + } + }; + let parent = match info.pbi_ppid as i32 { + 0 => None, + p => Some(Pid(p)), + }; + + let 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 start_time = info.pbi_start_tvsec; + let run_time = now.saturating_sub(start_time); + + let mut p = if cp < ptr.add(size) { + while cp < ptr.add(size) && *cp != 0 { + cp = cp.offset(1); + } + let exe = Path::new(get_unchecked_str(cp, start).as_str()).to_path_buf(); + let name = exe + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .to_owned(); + while cp < ptr.add(size) && *cp == 0 { + cp = cp.offset(1); + } + start = cp; + let mut c = 0; + let mut cmd = Vec::with_capacity(n_args as usize); + while c < n_args && cp < ptr.add(size) { + if *cp == 0 { + c += 1; + cmd.push(get_unchecked_str(cp, start)); + start = cp.offset(1); + } + cp = cp.offset(1); + } + + #[inline] + unsafe fn get_environ<F: Fn(&str, &mut PathBuf, &mut bool)>( + ptr: *mut u8, + mut cp: *mut u8, + size: size_t, + mut root: PathBuf, + callback: F, + ) -> (Vec<String>, PathBuf) { + let mut environ = Vec::with_capacity(10); + let mut start = cp; + let mut check = true; + while cp < ptr.add(size) { + if *cp == 0 { + if cp == start { + break; + } + let e = get_unchecked_str(cp, start); + callback(&e, &mut root, &mut check); + environ.push(e); + start = cp.offset(1); + } + cp = cp.offset(1); + } + (environ, root) + } + + let (environ, root) = if exe.is_absolute() { + if let Some(parent_path) = exe.parent() { + get_environ( + ptr, + cp, + size, + parent_path.to_path_buf(), + do_not_get_env_path, + ) + } else { + get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) + } + } else { + get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) + }; + let mut p = Process::new(pid, parent, start_time, run_time); + + p.exe = exe; + p.name = name; + p.cwd = cwd; + p.cmd = parse_command_line(&cmd); + p.environ = environ; + p.root = root; + p + } else { + Process::new(pid, parent, start_time, run_time) + }; + + let task_info = get_task_info(pid); + + p.memory = task_info.pti_resident_size; + p.virtual_memory = task_info.pti_virtual_size; + + 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 { + 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 { + // 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(()); + } + }; + 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; + p.virtual_memory = task_info.pti_virtual_size; + if refresh_kind.disk_usage() { + update_proc_disk_activity(p); + } + p.updated = true; + return Ok(None); + } + create_new_process(pid, size, now, refresh_kind, get_bsd_info(pid)) + } +} + +fn update_proc_disk_activity(p: &mut Process) { + p.old_read_bytes = p.read_bytes; + p.old_written_bytes = p.written_bytes; + + let mut pidrusage = MaybeUninit::<libc::rusage_info_v2>::uninit(); + + unsafe { + let retval = libc::proc_pid_rusage( + p.pid().0 as _, + libc::RUSAGE_INFO_V2, + pidrusage.as_mut_ptr() as _, + ); + + if retval < 0 { + sysinfo_debug!("proc_pid_rusage failed: {:?}", retval); + } else { + let pidrusage = pidrusage.assume_init(); + p.read_bytes = pidrusage.ri_diskio_bytesread; + p.written_bytes = pidrusage.ri_diskio_byteswritten; + } + } +} + +#[allow(unknown_lints)] +#[allow(clippy::uninit_vec)] +pub(crate) fn get_proc_list() -> Option<Vec<Pid>> { + unsafe { + let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); + if count < 1 { + return None; + } + let mut pids: Vec<Pid> = Vec::with_capacity(count as usize); + pids.set_len(count as usize); + let count = count * mem::size_of::<Pid>() as i32; + let x = libc::proc_listallpids(pids.as_mut_ptr() as *mut c_void, count); + + if x < 1 || x as usize >= pids.len() { + None + } else { + pids.set_len(x as usize); + Some(pids) + } + } +} + +unsafe fn get_unchecked_str(cp: *mut u8, start: *mut u8) -> String { + let len = cp as usize - start as usize; + let part = Vec::from_raw_parts(start, len, len); + let tmp = String::from_utf8_unchecked(part.clone()); + mem::forget(part); + tmp +} + +fn parse_command_line<T: Deref<Target = str> + Borrow<str>>(cmd: &[T]) -> Vec<String> { + let mut x = 0; + let mut command = Vec::with_capacity(cmd.len()); + while x < cmd.len() { + let mut y = x; + if cmd[y].starts_with('\'') || cmd[y].starts_with('"') { + let c = if cmd[y].starts_with('\'') { '\'' } else { '"' }; + while y < cmd.len() && !cmd[y].ends_with(c) { + y += 1; + } + command.push(cmd[x..y].join(" ")); + x = y; + } else { + command.push(cmd[x].to_owned()); + } + x += 1; + } + command +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_get_path() { + let mut path = PathBuf::new(); + let mut check = true; + + do_get_env_path("PATH=tadam", &mut path, &mut check); + + assert!(!check); + assert_eq!(path, PathBuf::from("tadam")); + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/system.rs b/vendor/sysinfo-0.26.7/src/apple/macos/system.rs new file mode 100644 index 000000000..949532234 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/macos/system.rs @@ -0,0 +1,136 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#[allow(deprecated)] +use libc::{mach_timebase_info, mach_timebase_info_data_t}; + +use libc::{ + host_processor_info, mach_port_t, munmap, natural_t, processor_cpu_load_info, + processor_cpu_load_info_t, sysconf, vm_page_size, PROCESSOR_CPU_LOAD_INFO, _SC_CLK_TCK, +}; +use std::ptr::null_mut; + +unsafe fn free_cpu_load_info(cpu_load: &mut processor_cpu_load_info_t) { + if !cpu_load.is_null() { + munmap(*cpu_load as _, vm_page_size); + *cpu_load = null_mut(); + } +} + +pub(crate) struct SystemTimeInfo { + timebase_to_ns: f64, + clock_per_sec: f64, + old_cpu_load: processor_cpu_load_info_t, + old_cpu_count: natural_t, +} + +unsafe impl Send for SystemTimeInfo {} +unsafe impl Sync for SystemTimeInfo {} + +impl SystemTimeInfo { + #[allow(deprecated)] // Everything related to mach_timebase_info_data_t + pub fn new(port: mach_port_t) -> Option<Self> { + unsafe { + let clock_ticks_per_sec = sysconf(_SC_CLK_TCK); + + // FIXME: Maybe check errno here? Problem is that if errno is not 0 before this call, + // we will get an error which isn't related... + // if let Some(er) = std::io::Error::last_os_error().raw_os_error() { + // if err != 0 { + // println!("==> {:?}", er); + // sysinfo_debug!("Failed to get _SC_CLK_TCK value, using old CPU tick measure system"); + // return None; + // } + // } + + let mut info = mach_timebase_info_data_t { numer: 0, denom: 0 }; + if mach_timebase_info(&mut info) != libc::KERN_SUCCESS { + sysinfo_debug!("mach_timebase_info failed, using default value of 1"); + info.numer = 1; + info.denom = 1; + } + + let mut old_cpu_load = null_mut(); + let old_cpu_count = match Self::update_ticks(port, &mut old_cpu_load) { + Some(c) => c, + None => { + sysinfo_debug!("host_processor_info failed, using old CPU tick measure system"); + return None; + } + }; + + let nano_per_seconds = 1_000_000_000.; + sysinfo_debug!(""); + Some(Self { + timebase_to_ns: info.numer as f64 / info.denom as f64, + clock_per_sec: nano_per_seconds / clock_ticks_per_sec as f64, + old_cpu_load, + old_cpu_count, + }) + } + } + + fn update_ticks( + port: mach_port_t, + cpu_load: &mut processor_cpu_load_info_t, + ) -> Option<natural_t> { + let mut info_size = std::mem::size_of::<processor_cpu_load_info_t>() as _; + let mut cpu_count = 0; + + unsafe { + free_cpu_load_info(cpu_load); + + if host_processor_info( + port, + PROCESSOR_CPU_LOAD_INFO, + &mut cpu_count, + cpu_load as *mut _ as *mut _, + &mut info_size, + ) != 0 + { + sysinfo_debug!("host_processor_info failed, not updating CPU ticks usage..."); + None + } else if cpu_count < 1 || cpu_load.is_null() { + None + } else { + Some(cpu_count) + } + } + } + + pub fn get_time_interval(&mut self, port: mach_port_t) -> f64 { + let mut total = 0; + let mut new_cpu_load = null_mut(); + + let new_cpu_count = match Self::update_ticks(port, &mut new_cpu_load) { + Some(c) => c, + None => return 0., + }; + let cpu_count = std::cmp::min(self.old_cpu_count, new_cpu_count); + unsafe { + for i in 0..cpu_count { + let new_load: &processor_cpu_load_info = &*new_cpu_load.offset(i as _); + let old_load: &processor_cpu_load_info = &*self.old_cpu_load.offset(i as _); + for (new, old) in new_load.cpu_ticks.iter().zip(old_load.cpu_ticks.iter()) { + if new > old { + total += new - old; + } + } + } + + free_cpu_load_info(&mut self.old_cpu_load); + self.old_cpu_load = new_cpu_load; + self.old_cpu_count = new_cpu_count; + + // Now we convert the ticks to nanoseconds: + total as f64 / self.timebase_to_ns * self.clock_per_sec / cpu_count as f64 + } + } +} + +impl Drop for SystemTimeInfo { + fn drop(&mut self) { + unsafe { + free_cpu_load_info(&mut self.old_cpu_load); + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/macos/utils.rs b/vendor/sysinfo-0.26.7/src/apple/macos/utils.rs new file mode 100644 index 000000000..ff870db55 --- /dev/null +++ b/vendor/sysinfo-0.26.7/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-0.26.7/src/apple/mod.rs b/vendor/sysinfo-0.26.7/src/apple/mod.rs new file mode 100644 index 000000000..fd6fdec2c --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/mod.rs @@ -0,0 +1,32 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#[cfg(target_os = "macos")] +pub(crate) mod macos; + +#[cfg(target_os = "macos")] +pub(crate) use self::macos as inner; + +#[cfg(target_os = "ios")] +pub(crate) mod ios; +#[cfg(target_os = "ios")] +pub(crate) use self::ios as inner; + +#[cfg(any(target_os = "ios", feature = "apple-sandbox"))] +pub(crate) mod app_store; + +pub mod component; +pub mod cpu; +pub mod disk; +mod ffi; +pub mod network; +pub mod process; +pub mod system; +pub mod users; +mod utils; + +pub use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo-0.26.7/src/apple/network.rs b/vendor/sysinfo-0.26.7/src/apple/network.rs new file mode 100644 index 000000000..3c4918155 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/network.rs @@ -0,0 +1,240 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use libc::{self, c_char, if_msghdr2, CTL_NET, NET_RT_IFLIST2, PF_ROUTE, RTM_IFINFO2}; + +use std::collections::{hash_map, HashMap}; +use std::ptr::null_mut; + +use crate::{NetworkExt, NetworksExt, NetworksIter}; + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $new_val; + }}; +} + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +impl Networks { + pub(crate) fn new() -> Self { + Networks { + interfaces: HashMap::new(), + } + } + + #[allow(unknown_lints)] + #[allow(clippy::cast_ptr_alignment)] + #[allow(clippy::uninit_vec)] + fn update_networks(&mut self) { + let mib = &mut [CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0]; + let mut len = 0; + unsafe { + if libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + null_mut(), + &mut len, + null_mut(), + 0, + ) < 0 + { + // TODO: might be nice to put an error in here... + return; + } + let mut buf = Vec::with_capacity(len); + buf.set_len(len); + if libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + buf.as_mut_ptr(), + &mut len, + null_mut(), + 0, + ) < 0 + { + // TODO: might be nice to put an error in here... + return; + } + let buf = buf.as_ptr() as *const c_char; + let lim = buf.add(len); + let mut next = buf; + while next < lim { + let ifm = next as *const libc::if_msghdr; + next = next.offset((*ifm).ifm_msglen as isize); + if (*ifm).ifm_type == RTM_IFINFO2 as u8 { + // The interface (line description) name stored at ifname will be returned in + // the default coded character set identifier (CCSID) currently in effect for + // the job. If this is not a single byte CCSID, then storage greater than + // IFNAMSIZ (16) bytes may be needed. 22 bytes is large enough for all CCSIDs. + let mut name = vec![0u8; libc::IFNAMSIZ + 6]; + + let if2m: *const if_msghdr2 = ifm as *const if_msghdr2; + let pname = + libc::if_indextoname((*if2m).ifm_index as _, name.as_mut_ptr() as _); + if pname.is_null() { + continue; + } + name.set_len(libc::strlen(pname)); + let name = String::from_utf8_unchecked(name); + match self.interfaces.entry(name) { + hash_map::Entry::Occupied(mut e) => { + let mut interface = e.get_mut(); + old_and_new!( + interface, + current_out, + old_out, + (*if2m).ifm_data.ifi_obytes + ); + old_and_new!( + interface, + current_in, + old_in, + (*if2m).ifm_data.ifi_ibytes + ); + old_and_new!( + interface, + packets_in, + old_packets_in, + (*if2m).ifm_data.ifi_ipackets + ); + old_and_new!( + interface, + packets_out, + old_packets_out, + (*if2m).ifm_data.ifi_opackets + ); + old_and_new!( + interface, + errors_in, + old_errors_in, + (*if2m).ifm_data.ifi_ierrors + ); + old_and_new!( + interface, + errors_out, + old_errors_out, + (*if2m).ifm_data.ifi_oerrors + ); + interface.updated = true; + } + hash_map::Entry::Vacant(e) => { + let current_in = (*if2m).ifm_data.ifi_ibytes; + let current_out = (*if2m).ifm_data.ifi_obytes; + let packets_in = (*if2m).ifm_data.ifi_ipackets; + let packets_out = (*if2m).ifm_data.ifi_opackets; + let errors_in = (*if2m).ifm_data.ifi_ierrors; + let errors_out = (*if2m).ifm_data.ifi_oerrors; + + e.insert(NetworkData { + current_in, + old_in: current_in, + current_out, + old_out: current_out, + packets_in, + old_packets_in: packets_in, + packets_out, + old_packets_out: packets_out, + errors_in, + old_errors_in: errors_in, + errors_out, + old_errors_out: errors_out, + updated: true, + }); + } + } + } + } + } + } +} + +impl NetworksExt for Networks { + #[allow(clippy::needless_lifetimes)] + fn iter<'a>(&'a self) -> NetworksIter<'a> { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) { + for (_, data) in self.interfaces.iter_mut() { + data.updated = false; + } + self.update_networks(); + self.interfaces.retain(|_, data| data.updated); + } + + fn refresh(&mut self) { + self.update_networks(); + } +} + +#[doc = include_str!("../../md_doc/network_data.md")] +#[derive(PartialEq, Eq)] +pub struct NetworkData { + current_in: u64, + old_in: u64, + current_out: u64, + old_out: u64, + packets_in: u64, + old_packets_in: u64, + packets_out: u64, + old_packets_out: u64, + errors_in: u64, + old_errors_in: u64, + errors_out: u64, + old_errors_out: u64, + updated: bool, +} + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + self.current_in.saturating_sub(self.old_in) + } + + fn total_received(&self) -> u64 { + self.current_in + } + + fn transmitted(&self) -> u64 { + self.current_out.saturating_sub(self.old_out) + } + + fn total_transmitted(&self) -> u64 { + self.current_out + } + + fn packets_received(&self) -> u64 { + self.packets_in.saturating_sub(self.old_packets_in) + } + + fn total_packets_received(&self) -> u64 { + self.packets_in + } + + fn packets_transmitted(&self) -> u64 { + self.packets_out.saturating_sub(self.old_packets_out) + } + + fn total_packets_transmitted(&self) -> u64 { + self.packets_out + } + + fn errors_on_received(&self) -> u64 { + self.errors_in.saturating_sub(self.old_errors_in) + } + + fn total_errors_on_received(&self) -> u64 { + self.errors_in + } + + fn errors_on_transmitted(&self) -> u64 { + self.errors_out.saturating_sub(self.old_errors_out) + } + + fn total_errors_on_transmitted(&self) -> u64 { + self.errors_out + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/process.rs b/vendor/sysinfo-0.26.7/src/apple/process.rs new file mode 100644 index 000000000..e0f005bdc --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/process.rs @@ -0,0 +1,83 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::fmt; + +pub use crate::sys::inner::process::*; +use crate::ProcessStatus; + +#[doc(hidden)] +impl From<u32> for ProcessStatus { + fn from(status: u32) -> ProcessStatus { + match status { + 1 => ProcessStatus::Idle, + 2 => ProcessStatus::Run, + 3 => ProcessStatus::Sleep, + 4 => ProcessStatus::Stop, + 5 => ProcessStatus::Zombie, + x => ProcessStatus::Unknown(x), + } + } +} + +impl fmt::Display for ProcessStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + ProcessStatus::Idle => "Idle", + ProcessStatus::Run => "Runnable", + ProcessStatus::Sleep => "Sleeping", + ProcessStatus::Stop => "Stopped", + ProcessStatus::Zombie => "Zombie", + _ => "Unknown", + }) + } +} + +/// Enum describing the different status of a thread. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ThreadStatus { + /// Thread is running normally. + Running, + /// Thread is stopped. + Stopped, + /// Thread is waiting normally. + Waiting, + /// Thread is in an uninterruptible wait + Uninterruptible, + /// Thread is halted at a clean point. + Halted, + /// Unknown. + Unknown(i32), +} + +impl From<i32> for ThreadStatus { + fn from(status: i32) -> ThreadStatus { + match status { + 1 => ThreadStatus::Running, + 2 => ThreadStatus::Stopped, + 3 => ThreadStatus::Waiting, + 4 => ThreadStatus::Uninterruptible, + 5 => ThreadStatus::Halted, + x => ThreadStatus::Unknown(x), + } + } +} + +impl ThreadStatus { + /// Used to display `ThreadStatus`. + pub fn to_string(&self) -> &str { + match *self { + ThreadStatus::Running => "Running", + ThreadStatus::Stopped => "Stopped", + ThreadStatus::Waiting => "Waiting", + ThreadStatus::Uninterruptible => "Uninterruptible", + ThreadStatus::Halted => "Halted", + ThreadStatus::Unknown(_) => "Unknown", + } + } +} + +impl fmt::Display for ThreadStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/system.rs b/vendor/sysinfo-0.26.7/src/apple/system.rs new file mode 100644 index 000000000..12abbd23b --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/system.rs @@ -0,0 +1,691 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::component::Component; +use crate::sys::cpu::*; +use crate::sys::disk::*; +use crate::sys::network::Networks; +use crate::sys::process::*; + +use crate::{ + CpuExt, CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, +}; + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +use crate::ProcessExt; + +use std::cell::UnsafeCell; +use std::collections::HashMap; +use std::mem; +use std::sync::Arc; +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +use std::time::SystemTime; + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +use libc::size_t; + +use libc::{ + c_char, c_int, c_void, host_statistics64, mach_port_t, sysconf, sysctl, sysctlbyname, timeval, + vm_statistics64, _SC_PAGESIZE, +}; + +#[cfg(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, + Signal::Hangup => libc::SIGHUP, + Signal::Interrupt => libc::SIGINT, + Signal::Quit => libc::SIGQUIT, + Signal::Illegal => libc::SIGILL, + Signal::Trap => libc::SIGTRAP, + Signal::Abort => libc::SIGABRT, + Signal::IOT => libc::SIGIOT, + Signal::Bus => libc::SIGBUS, + Signal::FloatingPointException => libc::SIGFPE, + Signal::Kill => libc::SIGKILL, + Signal::User1 => libc::SIGUSR1, + Signal::Segv => libc::SIGSEGV, + Signal::User2 => libc::SIGUSR2, + Signal::Pipe => libc::SIGPIPE, + Signal::Alarm => libc::SIGALRM, + Signal::Term => libc::SIGTERM, + Signal::Child => libc::SIGCHLD, + Signal::Continue => libc::SIGCONT, + Signal::Stop => libc::SIGSTOP, + Signal::TSTP => libc::SIGTSTP, + Signal::TTIN => libc::SIGTTIN, + Signal::TTOU => libc::SIGTTOU, + Signal::Urgent => libc::SIGURG, + Signal::XCPU => libc::SIGXCPU, + Signal::XFSZ => libc::SIGXFSZ, + Signal::VirtualAlarm => libc::SIGVTALRM, + Signal::Profiling => libc::SIGPROF, + Signal::Winch => libc::SIGWINCH, + Signal::IO => libc::SIGIO, + // SIGPOLL doesn't exist on apple targets but since it's an equivalent of SIGIO on unix, + // we simply use the SIGIO constant. + Signal::Poll => libc::SIGIO, + Signal::Sys => libc::SIGSYS, + _ => None, +} +#[cfg(any(target_os = "ios", feature = "apple-sandbox"))] +declare_signals! { + c_int, + _ => None, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + process_list: HashMap<Pid, Process>, + mem_total: u64, + mem_free: u64, + mem_available: u64, + swap_total: u64, + swap_free: u64, + global_cpu: Cpu, + cpus: Vec<Cpu>, + page_size_kb: u64, + #[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, + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + clock_info: Option<crate::sys::macos::system::SystemTimeInfo>, + got_cpu_frequency: bool, +} + +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> {} + +fn boot_time() -> u64 { + let mut boot_time = timeval { + tv_sec: 0, + tv_usec: 0, + }; + let mut len = std::mem::size_of::<timeval>(); + let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME]; + + unsafe { + if sysctl( + mib.as_mut_ptr(), + mib.len() as _, + &mut boot_time as *mut timeval as *mut _, + &mut len, + std::ptr::null_mut(), + 0, + ) < 0 + { + 0 + } else { + boot_time.tv_sec as _ + } + } +} + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +fn get_now() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|n| n.as_secs()) + .unwrap_or(0) +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = true; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + fn new_with_specifics(refreshes: RefreshKind) -> System { + unsafe { + let port = libc::mach_host_self(); + + let mut s = System { + process_list: HashMap::with_capacity(200), + mem_total: 0, + mem_free: 0, + mem_available: 0, + swap_total: 0, + swap_free: 0, + global_cpu: Cpu::new( + "0".to_owned(), + Arc::new(CpuData::new(std::ptr::null_mut(), 0)), + 0, + String::new(), + String::new(), + ), + cpus: Vec::new(), + page_size_kb: sysconf(_SC_PAGESIZE) as _, + #[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(all(target_os = "macos", not(feature = "apple-sandbox")))] + clock_info: crate::sys::macos::system::SystemTimeInfo::new(port), + got_cpu_frequency: false, + }; + s.refresh_specifics(refreshes); + s + } + } + + fn refresh_memory(&mut self) { + let mut mib = [0, 0]; + + unsafe { + // get system values + // get swap info + let mut xs: libc::xsw_usage = mem::zeroed::<libc::xsw_usage>(); + if get_sys_value( + libc::CTL_VM as _, + libc::VM_SWAPUSAGE as _, + mem::size_of::<libc::xsw_usage>(), + &mut xs as *mut _ as *mut c_void, + &mut mib, + ) { + self.swap_total = xs.xsu_total; + self.swap_free = xs.xsu_avail; + } + // get ram info + if self.mem_total < 1 { + get_sys_value( + libc::CTL_HW as _, + libc::HW_MEMSIZE as _, + mem::size_of::<u64>(), + &mut self.mem_total as *mut u64 as *mut c_void, + &mut mib, + ); + } + let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _; + let mut stat = mem::zeroed::<vm_statistics64>(); + if host_statistics64( + self.port, + libc::HOST_VM_INFO64, + &mut stat as *mut vm_statistics64 as *mut _, + &mut count, + ) == libc::KERN_SUCCESS + { + // From the apple documentation: + // + // /* + // * NB: speculative pages are already accounted for in "free_count", + // * so "speculative_count" is the number of "free" pages that are + // * used to hold data that was read speculatively from disk but + // * haven't actually been used by anyone so far. + // */ + self.mem_available = self.mem_total.saturating_sub( + u64::from(stat.active_count) + .saturating_add(u64::from(stat.inactive_count)) + .saturating_add(u64::from(stat.wire_count)) + .saturating_add(u64::from(stat.speculative_count)) + .saturating_sub(u64::from(stat.purgeable_count)) + .saturating_mul(self.page_size_kb), + ); + self.mem_free = u64::from(stat.free_count).saturating_mul(self.page_size_kb); + } + } + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn refresh_components_list(&mut self) {} + + #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] + fn refresh_components_list(&mut self) { + self.components.refresh(); + } + + fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { + let cpus = &mut self.cpus; + if cpus.is_empty() { + init_cpus(self.port, cpus, &mut self.global_cpu, refresh_kind); + self.got_cpu_frequency = refresh_kind.frequency(); + return; + } + if refresh_kind.frequency() && !self.got_cpu_frequency { + let frequency = get_cpu_frequency(); + for proc_ in cpus.iter_mut() { + proc_.set_frequency(frequency); + } + self.got_cpu_frequency = true; + } + if refresh_kind.cpu_usage() { + update_cpu_usage(self.port, &mut self.global_cpu, |proc_data, cpu_info| { + let mut percentage = 0f32; + let mut offset = 0; + for proc_ in cpus.iter_mut() { + let cpu_usage = compute_usage_of_cpu(proc_, cpu_info, offset); + proc_.update(cpu_usage, Arc::clone(&proc_data)); + percentage += proc_.cpu_usage(); + + offset += libc::CPU_STATE_MAX as isize; + } + (percentage, cpus.len()) + }); + } + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn refresh_processes_specifics(&mut self, _refresh_kind: ProcessRefreshKind) {} + + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { + use crate::utils::into_iter; + + unsafe { + let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); + if count < 1 { + return; + } + } + if let Some(pids) = get_proc_list() { + let now = get_now(); + let arg_max = get_arg_max(); + let port = self.port; + let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port)); + let entries: Vec<Process> = { + let wrap = &Wrap(UnsafeCell::new(&mut self.process_list)); + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + into_iter(pids) + .flat_map(|pid| { + match update_process( + wrap, + pid, + arg_max as size_t, + time_interval, + now, + refresh_kind, + false, + ) { + Ok(x) => x, + _ => None, + } + }) + .collect() + }; + entries.into_iter().for_each(|entry| { + self.process_list.insert(entry.pid(), entry); + }); + self.process_list + .retain(|_, proc_| std::mem::replace(&mut proc_.updated, false)); + } + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn refresh_process_specifics(&mut self, _pid: Pid, _refresh_kind: ProcessRefreshKind) -> bool { + false + } + + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { + let now = get_now(); + let arg_max = get_arg_max(); + let port = self.port; + let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port)); + match { + let wrap = Wrap(UnsafeCell::new(&mut self.process_list)); + update_process( + &wrap, + pid, + arg_max as size_t, + time_interval, + now, + refresh_kind, + true, + ) + } { + Ok(Some(p)) => { + self.process_list.insert(p.pid(), p); + true + } + Ok(_) => true, + Err(_) => false, + } + } + + fn refresh_disks_list(&mut self) { + self.disks = unsafe { get_disks() }; + } + + fn refresh_users_list(&mut self) { + self.users = crate::apple::users::get_users_list(); + } + + // COMMON PART + // + // Need to be moved into a "common" file to avoid duplication. + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.process_list + } + + fn process(&self, pid: Pid) -> Option<&Process> { + self.process_list.get(&pid) + } + + fn global_cpu_info(&self) -> &Cpu { + &self.global_cpu + } + + fn cpus(&self) -> &[Cpu] { + &self.cpus + } + + fn physical_core_count(&self) -> Option<usize> { + let mut physical_core_count = 0; + + unsafe { + if get_sys_value_by_name( + b"hw.physicalcpu\0", + &mut mem::size_of::<u32>(), + &mut physical_core_count as *mut usize as *mut c_void, + ) { + Some(physical_core_count) + } else { + None + } + } + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn total_memory(&self) -> u64 { + self.mem_total + } + + fn free_memory(&self) -> u64 { + self.mem_free + } + + fn available_memory(&self) -> u64 { + self.mem_available + } + + fn used_memory(&self) -> u64 { + self.mem_total - self.mem_free + } + + fn total_swap(&self) -> u64 { + self.swap_total + } + + fn free_swap(&self) -> u64 { + self.swap_free + } + + // TODO: need to be checked + fn used_swap(&self) -> u64 { + self.swap_total - self.swap_free + } + + #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] + fn components(&self) -> &[Component] { + &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.inner + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn components_mut(&mut self) -> &mut [Component] { + &mut [] + } + + fn disks(&self) -> &[Disk] { + &self.disks + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &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()); + + libc::difftime(csec, self.boot_time as _) as u64 + } + } + + fn load_average(&self) -> LoadAvg { + let mut loads = vec![0f64; 3]; + + unsafe { + libc::getloadavg(loads.as_mut_ptr(), 3); + LoadAvg { + one: loads[0], + five: loads[1], + fifteen: loads[2], + } + } + } + + fn users(&self) -> &[User] { + &self.users + } + + fn boot_time(&self) -> u64 { + self.boot_time + } + + fn name(&self) -> Option<String> { + get_system_info(libc::KERN_OSTYPE, Some("Darwin")) + } + + fn long_os_version(&self) -> Option<String> { + #[cfg(target_os = "macos")] + let friendly_name = match self.os_version().unwrap_or_default() { + f_n if f_n.starts_with("10.16") + | f_n.starts_with("11.0") + | f_n.starts_with("11.1") + | f_n.starts_with("11.2") => + { + "Big Sur" + } + f_n if f_n.starts_with("10.15") => "Catalina", + f_n if f_n.starts_with("10.14") => "Mojave", + f_n if f_n.starts_with("10.13") => "High Sierra", + f_n if f_n.starts_with("10.12") => "Sierra", + f_n if f_n.starts_with("10.11") => "El Capitan", + f_n if f_n.starts_with("10.10") => "Yosemite", + f_n if f_n.starts_with("10.9") => "Mavericks", + f_n if f_n.starts_with("10.8") => "Mountain Lion", + f_n if f_n.starts_with("10.7") => "Lion", + f_n if f_n.starts_with("10.6") => "Snow Leopard", + f_n if f_n.starts_with("10.5") => "Leopard", + f_n if f_n.starts_with("10.4") => "Tiger", + f_n if f_n.starts_with("10.3") => "Panther", + f_n if f_n.starts_with("10.2") => "Jaguar", + f_n if f_n.starts_with("10.1") => "Puma", + f_n if f_n.starts_with("10.0") => "Cheetah", + _ => "", + }; + + #[cfg(target_os = "macos")] + let long_name = Some(format!( + "MacOS {} {}", + self.os_version().unwrap_or_default(), + friendly_name + )); + + #[cfg(target_os = "ios")] + let long_name = Some(format!("iOS {}", self.os_version().unwrap_or_default())); + + long_name + } + + fn host_name(&self) -> Option<String> { + get_system_info(libc::KERN_HOSTNAME, None) + } + + fn kernel_version(&self) -> Option<String> { + get_system_info(libc::KERN_OSRELEASE, None) + } + + fn os_version(&self) -> Option<String> { + unsafe { + // get the size for the buffer first + let mut size = 0; + if get_sys_value_by_name(b"kern.osproductversion\0", &mut size, std::ptr::null_mut()) + && size > 0 + { + // now create a buffer with the size and get the real value + let mut buf = vec![0_u8; size as _]; + + if get_sys_value_by_name( + b"kern.osproductversion\0", + &mut size, + buf.as_mut_ptr() as *mut c_void, + ) { + if let Some(pos) = buf.iter().position(|x| *x == 0) { + // Shrink buffer to terminate the null bytes + buf.resize(pos, 0); + } + + String::from_utf8(buf).ok() + } else { + // getting the system value failed + None + } + } else { + // getting the system value failed, or did not return a buffer size + None + } + } + } + + fn distribution_id(&self) -> String { + std::env::consts::OS.to_owned() + } +} + +impl Default for System { + fn default() -> System { + System::new() + } +} + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +fn get_arg_max() -> usize { + let mut mib = [libc::CTL_KERN, libc::KERN_ARGMAX]; + let mut arg_max = 0i32; + let mut size = mem::size_of::<c_int>(); + unsafe { + if sysctl( + mib.as_mut_ptr(), + mib.len() as _, + (&mut arg_max) as *mut i32 as *mut c_void, + &mut size, + std::ptr::null_mut(), + 0, + ) == -1 + { + 4096 // We default to this value + } else { + arg_max as usize + } + } +} + +pub(crate) unsafe fn get_sys_value( + high: u32, + low: u32, + mut len: usize, + value: *mut c_void, + mib: &mut [i32; 2], +) -> bool { + mib[0] = high as i32; + mib[1] = low as i32; + sysctl( + mib.as_mut_ptr(), + mib.len() as _, + value, + &mut len as *mut usize, + std::ptr::null_mut(), + 0, + ) == 0 +} + +unsafe fn get_sys_value_by_name(name: &[u8], len: &mut usize, value: *mut c_void) -> bool { + sysctlbyname( + name.as_ptr() as *const c_char, + value, + len, + std::ptr::null_mut(), + 0, + ) == 0 +} + +fn get_system_info(value: c_int, default: Option<&str>) -> Option<String> { + let mut mib: [c_int; 2] = [libc::CTL_KERN, value]; + let mut size = 0; + + unsafe { + // Call first to get size + sysctl( + mib.as_mut_ptr(), + mib.len() as _, + std::ptr::null_mut(), + &mut size, + std::ptr::null_mut(), + 0, + ); + + // exit early if we did not update the size + if size == 0 { + default.map(|s| s.to_owned()) + } else { + // set the buffer to the correct size + let mut buf = vec![0_u8; size as _]; + + if sysctl( + mib.as_mut_ptr(), + mib.len() as _, + buf.as_mut_ptr() as _, + &mut size, + std::ptr::null_mut(), + 0, + ) == -1 + { + // If command fails return default + default.map(|s| s.to_owned()) + } else { + if let Some(pos) = buf.iter().position(|x| *x == 0) { + // Shrink buffer to terminate the null bytes + buf.resize(pos, 0); + } + + String::from_utf8(buf).ok() + } + } + } +} diff --git a/vendor/sysinfo-0.26.7/src/apple/users.rs b/vendor/sysinfo-0.26.7/src/apple/users.rs new file mode 100644 index 000000000..690aceee1 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/users.rs @@ -0,0 +1,178 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + common::{Gid, Uid}, + User, +}; + +use crate::sys::utils; +use libc::{c_char, endpwent, getgrgid, getgrouplist, getpwent, gid_t, setpwent, strlen}; + +fn get_user_groups(name: *const c_char, group_id: gid_t) -> Vec<String> { + let mut add = 0; + + loop { + let mut nb_groups = 256 + add; + let mut groups = Vec::with_capacity(nb_groups as _); + unsafe { + if getgrouplist(name, group_id as _, groups.as_mut_ptr(), &mut nb_groups) == -1 { + add += 100; + continue; + } + groups.set_len(nb_groups as _); + return groups + .into_iter() + .filter_map(|g| { + let group = getgrgid(g as _); + if group.is_null() { + return None; + } + utils::cstr_to_rust((*group).gr_name) + }) + .collect(); + } + } +} + +fn endswith(s1: *const c_char, s2: &[u8]) -> bool { + if s1.is_null() { + return false; + } + unsafe { + let mut len = strlen(s1) as isize - 1; + let mut i = s2.len() as isize - 1; + while len >= 0 && i >= 0 && *s1.offset(len) == s2[i as usize] as _ { + i -= 1; + len -= 1; + } + i == -1 + } +} + +fn users_list<F>(filter: F) -> Vec<User> +where + F: Fn(*const c_char, u32) -> bool, +{ + let mut users = Vec::new(); + + unsafe { + setpwent(); + loop { + let pw = getpwent(); + if pw.is_null() { + break; + } + + if !filter((*pw).pw_shell, (*pw).pw_uid) { + // This is not a "real" or "local" user. + continue; + } + + let groups = get_user_groups((*pw).pw_name, (*pw).pw_gid); + let uid = (*pw).pw_uid; + let gid = (*pw).pw_gid; + if let Some(name) = utils::cstr_to_rust((*pw).pw_name) { + users.push(User { + uid: Uid(uid), + gid: Gid(gid), + name, + groups, + }); + } + } + endpwent(); + } + users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap()); + users.dedup_by(|a, b| a.name == b.name); + users +} + +pub(crate) fn get_users_list() -> Vec<User> { + users_list(|shell, uid| { + !endswith(shell, b"/false") && !endswith(shell, b"/uucico") && uid < 65536 + }) +} + +// This was the OSX-based solution. It provides enough information, but what a mess! +// pub fn get_users_list() -> Vec<User> { +// let mut users = Vec::new(); +// let node_name = b"/Local/Default\0"; + +// unsafe { +// let node_name = ffi::CFStringCreateWithCStringNoCopy( +// std::ptr::null_mut(), +// node_name.as_ptr() as *const c_char, +// ffi::kCFStringEncodingMacRoman, +// ffi::kCFAllocatorNull as *mut c_void, +// ); +// let node_ref = ffi::ODNodeCreateWithName( +// ffi::kCFAllocatorDefault, +// ffi::kODSessionDefault, +// node_name, +// std::ptr::null_mut(), +// ); +// let query = ffi::ODQueryCreateWithNode( +// ffi::kCFAllocatorDefault, +// node_ref, +// ffi::kODRecordTypeUsers as _, // kODRecordTypeGroups +// std::ptr::null(), +// 0, +// std::ptr::null(), +// std::ptr::null(), +// 0, +// std::ptr::null_mut(), +// ); +// if query.is_null() { +// return users; +// } +// let results = ffi::ODQueryCopyResults( +// query, +// false as _, +// std::ptr::null_mut(), +// ); +// let len = ffi::CFArrayGetCount(results); +// for i in 0..len { +// let name = match get_user_name(ffi::CFArrayGetValueAtIndex(results, i)) { +// Some(n) => n, +// None => continue, +// }; +// let groups = get_user_groups(&name); +// users.push(User { name }); +// } + +// ffi::CFRelease(results as *const c_void); +// ffi::CFRelease(query as *const c_void); +// ffi::CFRelease(node_ref as *const c_void); +// ffi::CFRelease(node_name as *const c_void); +// } +// users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap()); +// return users; +// } + +// fn get_user_name(result: *const c_void) -> Option<String> { +// let user_name = ffi::ODRecordGetRecordName(result as _); +// let ptr = ffi::CFStringGetCharactersPtr(user_name); +// String::from_utf16(&if ptr.is_null() { +// let len = ffi::CFStringGetLength(user_name); // It returns the len in UTF-16 code pairs. +// if len == 0 { +// continue; +// } +// let mut v = Vec::with_capacity(len as _); +// for x in 0..len { +// v.push(ffi::CFStringGetCharacterAtIndex(user_name, x)); +// } +// v +// } else { +// let mut v: Vec<u16> = Vec::new(); +// let mut x = 0; +// loop { +// let letter = *ptr.offset(x); +// if letter == 0 { +// break; +// } +// v.push(letter); +// x += 1; +// } +// v +// }.ok() +// } diff --git a/vendor/sysinfo-0.26.7/src/apple/utils.rs b/vendor/sysinfo-0.26.7/src/apple/utils.rs new file mode 100644 index 000000000..408c02c31 --- /dev/null +++ b/vendor/sysinfo-0.26.7/src/apple/utils.rs @@ -0,0 +1,71 @@ +// 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) +} + +pub(crate) fn cstr_to_rust_with_size(c: *const c_char, size: Option<usize>) -> Option<String> { + if c.is_null() { + return None; + } + let mut s = match size { + Some(len) => Vec::with_capacity(len), + None => Vec::new(), + }; + let mut i = 0; + unsafe { + loop { + let value = *c.offset(i) as u8; + if value == 0 { + break; + } + s.push(value); + i += 1; + } + String::from_utf8(s).ok() + } +} + +pub(crate) fn vec_to_rust(buf: Vec<i8>) -> Option<String> { + String::from_utf8( + buf.into_iter() + .flat_map(|b| if b > 0 { Some(b as u8) } else { None }) + .collect(), + ) + .ok() +} |