summaryrefslogtreecommitdiffstats
path: root/vendor/sysinfo/src/apple/disk.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/sysinfo/src/apple/disk.rs')
-rw-r--r--vendor/sysinfo/src/apple/disk.rs351
1 files changed, 340 insertions, 11 deletions
diff --git a/vendor/sysinfo/src/apple/disk.rs b/vendor/sysinfo/src/apple/disk.rs
index 9f0b4a3a3..d866d5bba 100644
--- a/vendor/sysinfo/src/apple/disk.rs
+++ b/vendor/sysinfo/src/apple/disk.rs
@@ -1,15 +1,23 @@
// Take a look at the license at the top of the repository in the LICENSE file.
-use crate::utils::to_cpath;
+use crate::sys::{
+ ffi,
+ utils::{self, CFReleaser},
+};
use crate::{DiskExt, DiskType};
-#[cfg(target_os = "macos")]
-pub(crate) use crate::sys::inner::disk::*;
+use core_foundation_sys::array::CFArrayCreate;
+use core_foundation_sys::base::kCFAllocatorDefault;
+use core_foundation_sys::dictionary::{CFDictionaryGetValueIfPresent, CFDictionaryRef};
+use core_foundation_sys::number::{kCFBooleanTrue, CFBooleanRef, CFNumberGetValue};
+use core_foundation_sys::string::{self as cfs, CFStringRef};
-use libc::statfs;
-use std::ffi::{OsStr, OsString};
-use std::mem;
+use libc::c_void;
+
+use std::ffi::{CStr, OsStr, OsString};
+use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
+use std::ptr;
#[doc = include_str!("../../md_doc/disk.md")]
pub struct Disk {
@@ -17,6 +25,7 @@ pub struct Disk {
pub(crate) name: OsString,
pub(crate) file_system: Vec<u8>,
pub(crate) mount_point: PathBuf,
+ volume_url: RetainedCFURL,
pub(crate) total_space: u64,
pub(crate) available_space: u64,
pub(crate) is_removable: bool,
@@ -53,14 +62,334 @@ impl DiskExt for Disk {
fn refresh(&mut self) -> bool {
unsafe {
- let mut stat: statfs = mem::zeroed();
- let mount_point_cpath = to_cpath(&self.mount_point);
- if statfs(mount_point_cpath.as_ptr() as *const i8, &mut stat) == 0 {
- self.available_space = u64::from(stat.f_bsize).saturating_mul(stat.f_bavail);
- true
+ if let Some(requested_properties) = build_requested_properties(&[
+ ffi::kCFURLVolumeAvailableCapacityKey,
+ ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey,
+ ]) {
+ match get_disk_properties(&self.volume_url, &requested_properties) {
+ Some(disk_props) => {
+ self.available_space = get_available_volume_space(&disk_props);
+ true
+ }
+ None => false,
+ }
} else {
+ sysinfo_debug!("failed to create volume key list, skipping refresh");
false
}
}
}
}
+
+pub(super) unsafe fn get_disks() -> Vec<Disk> {
+ let raw_disks = {
+ let count = libc::getfsstat(ptr::null_mut(), 0, libc::MNT_NOWAIT);
+ if count < 1 {
+ return Vec::new();
+ }
+ let bufsize = count * std::mem::size_of::<libc::statfs>() as libc::c_int;
+ let mut disks = Vec::with_capacity(count as _);
+ let count = libc::getfsstat(disks.as_mut_ptr(), bufsize, libc::MNT_NOWAIT);
+
+ if count < 1 {
+ return Vec::new();
+ }
+
+ disks.set_len(count as usize);
+
+ disks
+ };
+
+ // Create a list of properties about the disk that we want to fetch.
+ let requested_properties = match build_requested_properties(&[
+ ffi::kCFURLVolumeIsEjectableKey,
+ ffi::kCFURLVolumeIsRemovableKey,
+ ffi::kCFURLVolumeIsInternalKey,
+ ffi::kCFURLVolumeTotalCapacityKey,
+ ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey,
+ ffi::kCFURLVolumeAvailableCapacityKey,
+ ffi::kCFURLVolumeNameKey,
+ ffi::kCFURLVolumeIsBrowsableKey,
+ ffi::kCFURLVolumeIsLocalKey,
+ ]) {
+ Some(properties) => properties,
+ None => {
+ sysinfo_debug!("failed to create volume key list");
+ return Vec::new();
+ }
+ };
+
+ let mut disks = Vec::with_capacity(raw_disks.len());
+ for c_disk in raw_disks {
+ let volume_url = match CFReleaser::new(
+ core_foundation_sys::url::CFURLCreateFromFileSystemRepresentation(
+ kCFAllocatorDefault,
+ c_disk.f_mntonname.as_ptr() as *const _,
+ c_disk.f_mntonname.len() as _,
+ false as _,
+ ),
+ ) {
+ Some(url) => url,
+ None => {
+ sysinfo_debug!("getfsstat returned incompatible paths");
+ continue;
+ }
+ };
+
+ let prop_dict = match get_disk_properties(&volume_url, &requested_properties) {
+ Some(props) => props,
+ None => continue,
+ };
+
+ // Future note: There is a difference between `kCFURLVolumeIsBrowsableKey` and the
+ // `kCFURLEnumeratorSkipInvisibles` option of `CFURLEnumeratorOptions`. Specifically,
+ // the first one considers the writable `Data`(`/System/Volumes/Data`) partition to be
+ // browsable, while it is classified as "invisible" by CoreFoundation's volume emumerator.
+ let browsable = get_bool_value(
+ prop_dict.inner(),
+ DictKey::Extern(ffi::kCFURLVolumeIsBrowsableKey),
+ )
+ .unwrap_or_default();
+
+ // Do not return invisible "disks". Most of the time, these are APFS snapshots, hidden
+ // system volumes, etc. Browsable is defined to be visible in the system's UI like Finder,
+ // disk utility, system information, etc.
+ //
+ // To avoid seemingly duplicating many disks and creating an inaccurate view of the system's resources,
+ // these are skipped entirely.
+ if !browsable {
+ continue;
+ }
+
+ let local_only = get_bool_value(
+ prop_dict.inner(),
+ DictKey::Extern(ffi::kCFURLVolumeIsLocalKey),
+ )
+ .unwrap_or(true);
+
+ // Skip any drive that is not locally attached to the system.
+ //
+ // This includes items like SMB mounts, and matches the other platform's behavior.
+ if !local_only {
+ continue;
+ }
+
+ let mount_point = PathBuf::from(OsStr::from_bytes(
+ CStr::from_ptr(c_disk.f_mntonname.as_ptr()).to_bytes(),
+ ));
+
+ disks.extend(new_disk(mount_point, volume_url, c_disk, &prop_dict))
+ }
+
+ disks
+}
+
+type RetainedCFArray = CFReleaser<core_foundation_sys::array::__CFArray>;
+type RetainedCFDictionary = CFReleaser<core_foundation_sys::dictionary::__CFDictionary>;
+type RetainedCFURL = CFReleaser<core_foundation_sys::url::__CFURL>;
+
+unsafe fn build_requested_properties(properties: &[CFStringRef]) -> Option<RetainedCFArray> {
+ CFReleaser::new(CFArrayCreate(
+ ptr::null_mut(),
+ properties.as_ptr() as *const *const c_void,
+ properties.len() as _,
+ &core_foundation_sys::array::kCFTypeArrayCallBacks,
+ ))
+}
+
+fn get_disk_properties(
+ volume_url: &RetainedCFURL,
+ requested_properties: &RetainedCFArray,
+) -> Option<RetainedCFDictionary> {
+ CFReleaser::new(unsafe {
+ ffi::CFURLCopyResourcePropertiesForKeys(
+ volume_url.inner(),
+ requested_properties.inner(),
+ ptr::null_mut(),
+ )
+ })
+}
+
+fn get_available_volume_space(disk_props: &RetainedCFDictionary) -> u64 {
+ // We prefer `AvailableCapacityForImportantUsage` over `AvailableCapacity` because
+ // it takes more of the system's properties into account, like the trash, system-managed caches,
+ // etc. It generally also returns higher values too, because of the above, so it's a more accurate
+ // representation of what the system _could_ still use.
+ unsafe {
+ get_int_value(
+ disk_props.inner(),
+ DictKey::Extern(ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey),
+ )
+ .filter(|bytes| *bytes != 0)
+ .or_else(|| {
+ get_int_value(
+ disk_props.inner(),
+ DictKey::Extern(ffi::kCFURLVolumeAvailableCapacityKey),
+ )
+ })
+ }
+ .unwrap_or_default() as u64
+}
+
+pub(super) enum DictKey {
+ Extern(CFStringRef),
+ #[cfg(target_os = "macos")]
+ Defined(&'static str),
+}
+
+unsafe fn get_dict_value<T, F: FnOnce(*const c_void) -> Option<T>>(
+ dict: CFDictionaryRef,
+ key: DictKey,
+ callback: F,
+) -> Option<T> {
+ #[cfg(target_os = "macos")]
+ let _defined;
+ let key = match key {
+ DictKey::Extern(val) => val,
+ #[cfg(target_os = "macos")]
+ DictKey::Defined(val) => {
+ _defined = CFReleaser::new(cfs::CFStringCreateWithBytesNoCopy(
+ kCFAllocatorDefault,
+ val.as_ptr(),
+ val.len() as _,
+ cfs::kCFStringEncodingUTF8,
+ false as _,
+ core_foundation_sys::base::kCFAllocatorNull,
+ ))?;
+
+ _defined.inner()
+ }
+ };
+
+ let mut value = std::ptr::null();
+ if CFDictionaryGetValueIfPresent(dict, key.cast(), &mut value) != 0 {
+ callback(value)
+ } else {
+ None
+ }
+}
+
+pub(super) unsafe fn get_str_value(dict: CFDictionaryRef, key: DictKey) -> Option<String> {
+ get_dict_value(dict, key, |v| {
+ let v = v as cfs::CFStringRef;
+
+ let len_utf16 = cfs::CFStringGetLength(v) as usize;
+ let len_bytes = len_utf16 * 2; // Two bytes per UTF-16 codepoint.
+
+ let v_ptr = cfs::CFStringGetCStringPtr(v, cfs::kCFStringEncodingUTF8);
+ if v_ptr.is_null() {
+ // Fallback on CFStringGetString to read the underlying bytes from the CFString.
+ let mut buf = vec![0; len_bytes];
+ let success = cfs::CFStringGetCString(
+ v,
+ buf.as_mut_ptr(),
+ len_bytes as _,
+ cfs::kCFStringEncodingUTF8,
+ );
+
+ if success != 0 {
+ utils::vec_to_rust(buf)
+ } else {
+ None
+ }
+ } else {
+ utils::cstr_to_rust_with_size(v_ptr, Some(len_bytes))
+ }
+ })
+}
+
+unsafe fn get_bool_value(dict: CFDictionaryRef, key: DictKey) -> Option<bool> {
+ get_dict_value(dict, key, |v| Some(v as CFBooleanRef == kCFBooleanTrue))
+}
+
+unsafe fn get_int_value(dict: CFDictionaryRef, key: DictKey) -> Option<i64> {
+ get_dict_value(dict, key, |v| {
+ let mut val: i64 = 0;
+ if CFNumberGetValue(
+ v.cast(),
+ core_foundation_sys::number::kCFNumberSInt64Type,
+ &mut val as *mut i64 as *mut c_void,
+ ) {
+ Some(val)
+ } else {
+ None
+ }
+ })
+}
+
+unsafe fn new_disk(
+ mount_point: PathBuf,
+ volume_url: RetainedCFURL,
+ c_disk: libc::statfs,
+ disk_props: &RetainedCFDictionary,
+) -> Option<Disk> {
+ // IOKit is not available on any but the most recent (16+) iOS and iPadOS versions.
+ // Due to this, we can't query the medium type. All iOS devices use flash-based storage
+ // so we just assume the disk type is an SSD until Rust has a way to conditionally link to
+ // IOKit in more recent deployment versions.
+ #[cfg(target_os = "macos")]
+ let type_ = crate::sys::inner::disk::get_disk_type(&c_disk).unwrap_or(DiskType::Unknown(-1));
+ #[cfg(not(target_os = "macos"))]
+ let type_ = DiskType::SSD;
+
+ // Note: Since we requested these properties from the system, we don't expect
+ // these property retrievals to fail.
+
+ let name = get_str_value(
+ disk_props.inner(),
+ DictKey::Extern(ffi::kCFURLVolumeNameKey),
+ )
+ .map(OsString::from)?;
+
+ let is_removable = {
+ let ejectable = get_bool_value(
+ disk_props.inner(),
+ DictKey::Extern(ffi::kCFURLVolumeIsEjectableKey),
+ )
+ .unwrap_or_default();
+
+ let removable = get_bool_value(
+ disk_props.inner(),
+ DictKey::Extern(ffi::kCFURLVolumeIsRemovableKey),
+ )
+ .unwrap_or_default();
+
+ let is_removable = ejectable || removable;
+
+ if is_removable {
+ is_removable
+ } else {
+ // If neither `ejectable` or `removable` return `true`, fallback to checking
+ // if the disk is attached to the internal system.
+ let internal = get_bool_value(
+ disk_props.inner(),
+ DictKey::Extern(ffi::kCFURLVolumeIsInternalKey),
+ )
+ .unwrap_or_default();
+
+ !internal
+ }
+ };
+
+ let total_space = get_int_value(
+ disk_props.inner(),
+ DictKey::Extern(ffi::kCFURLVolumeTotalCapacityKey),
+ )? as u64;
+
+ let available_space = get_available_volume_space(disk_props);
+
+ let file_system = IntoIterator::into_iter(c_disk.f_fstypename)
+ .filter_map(|b| if b != 0 { Some(b as u8) } else { None })
+ .collect();
+
+ Some(Disk {
+ type_,
+ name,
+ file_system,
+ mount_point,
+ volume_url,
+ total_space,
+ available_space,
+ is_removable,
+ })
+}