From 4e8199b572f2035b7749cba276ece3a26630d23e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:18:21 +0200 Subject: Adding upstream version 1.67.1+dfsg1. Signed-off-by: Daniel Baumann --- vendor/sysinfo/src/apple/disk.rs | 351 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 340 insertions(+), 11 deletions(-) (limited to 'vendor/sysinfo/src/apple/disk.rs') diff --git a/vendor/sysinfo/src/apple/disk.rs b/vendor/sysinfo/src/apple/disk.rs index 9f0b4a3a3..d866d5bba 100644 --- a/vendor/sysinfo/src/apple/disk.rs +++ b/vendor/sysinfo/src/apple/disk.rs @@ -1,15 +1,23 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use crate::utils::to_cpath; +use crate::sys::{ + ffi, + utils::{self, CFReleaser}, +}; use crate::{DiskExt, DiskType}; -#[cfg(target_os = "macos")] -pub(crate) use crate::sys::inner::disk::*; +use core_foundation_sys::array::CFArrayCreate; +use core_foundation_sys::base::kCFAllocatorDefault; +use core_foundation_sys::dictionary::{CFDictionaryGetValueIfPresent, CFDictionaryRef}; +use core_foundation_sys::number::{kCFBooleanTrue, CFBooleanRef, CFNumberGetValue}; +use core_foundation_sys::string::{self as cfs, CFStringRef}; -use libc::statfs; -use std::ffi::{OsStr, OsString}; -use std::mem; +use libc::c_void; + +use std::ffi::{CStr, OsStr, OsString}; +use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; +use std::ptr; #[doc = include_str!("../../md_doc/disk.md")] pub struct Disk { @@ -17,6 +25,7 @@ pub struct Disk { pub(crate) name: OsString, pub(crate) file_system: Vec, pub(crate) mount_point: PathBuf, + volume_url: RetainedCFURL, pub(crate) total_space: u64, pub(crate) available_space: u64, pub(crate) is_removable: bool, @@ -53,14 +62,334 @@ impl DiskExt for Disk { fn refresh(&mut self) -> bool { unsafe { - let mut stat: statfs = mem::zeroed(); - let mount_point_cpath = to_cpath(&self.mount_point); - if statfs(mount_point_cpath.as_ptr() as *const i8, &mut stat) == 0 { - self.available_space = u64::from(stat.f_bsize).saturating_mul(stat.f_bavail); - true + if let Some(requested_properties) = build_requested_properties(&[ + ffi::kCFURLVolumeAvailableCapacityKey, + ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey, + ]) { + match get_disk_properties(&self.volume_url, &requested_properties) { + Some(disk_props) => { + self.available_space = get_available_volume_space(&disk_props); + true + } + None => false, + } } else { + sysinfo_debug!("failed to create volume key list, skipping refresh"); false } } } } + +pub(super) unsafe fn get_disks() -> Vec { + 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::() 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; +type RetainedCFDictionary = CFReleaser; +type RetainedCFURL = CFReleaser; + +unsafe fn build_requested_properties(properties: &[CFStringRef]) -> Option { + 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 { + 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 Option>( + dict: CFDictionaryRef, + key: DictKey, + callback: F, +) -> Option { + #[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 { + 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 { + get_dict_value(dict, key, |v| Some(v as CFBooleanRef == kCFBooleanTrue)) +} + +unsafe fn get_int_value(dict: CFDictionaryRef, key: DictKey) -> Option { + 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 { + // 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, + }) +} -- cgit v1.2.3