diff --git a/examples/simple.rs b/examples/simple.rs index efd003f3c..cd10074ee 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -158,7 +158,7 @@ fn interpret_input( "help" => print_help(), "refresh_disks" => { writeln!(&mut io::stdout(), "Refreshing disk list..."); - disks.refresh_list(); + disks.refresh_list(true); writeln!(&mut io::stdout(), "Done."); } "refresh_users" => { diff --git a/src/c_interface.rs b/src/c_interface.rs index c1d007f98..ecdee1706 100644 --- a/src/c_interface.rs +++ b/src/c_interface.rs @@ -156,7 +156,7 @@ pub extern "C" fn sysinfo_disks_refresh_list(disks: CDisks) { let mut disks: Box = Box::from_raw(disks as *mut Disks); { let disks: &mut Disks = disks.borrow_mut(); - disks.refresh_list(); + disks.refresh_list(true); } let _ = Box::into_raw(disks); } diff --git a/src/common/disk.rs b/src/common/disk.rs index 90d2f307a..cf570e3e7 100644 --- a/src/common/disk.rs +++ b/src/common/disk.rs @@ -249,7 +249,7 @@ impl Disks { /// use sysinfo::Disks; /// /// let mut disks = Disks::new(); - /// disks.refresh_list(); + /// disks.refresh_list(false); /// for disk in disks.list() { /// println!("{disk:?}"); /// } @@ -290,7 +290,7 @@ impl Disks { /// ``` pub fn new_with_refreshed_list_specifics(refreshes: DiskRefreshKind) -> Self { let mut disks = Self::new(); - disks.refresh_list_specifics(refreshes); + disks.refresh_list_specifics(false, refreshes); disks } @@ -357,10 +357,10 @@ impl Disks { /// use sysinfo::Disks; /// /// let mut disks = Disks::new(); - /// disks.refresh_list(); + /// disks.refresh_list(true); /// ``` - pub fn refresh_list(&mut self) { - self.refresh_list_specifics(DiskRefreshKind::everything()); + pub fn refresh_list(&mut self, remove_not_listed_disks: bool) { + self.refresh_list_specifics(remove_not_listed_disks, DiskRefreshKind::everything()); } /// The disk list will be emptied then completely recomputed according to the given @@ -380,10 +380,15 @@ impl Disks { /// use sysinfo::{Disks, DiskRefreshKind}; /// /// let mut disks = Disks::new(); - /// disks.refresh_list_specifics(DiskRefreshKind::new()); + /// disks.refresh_list_specifics(true, DiskRefreshKind::new()); /// ``` - pub fn refresh_list_specifics(&mut self, refreshes: DiskRefreshKind) { - self.inner.refresh_list_specifics(refreshes); + pub fn refresh_list_specifics( + &mut self, + remove_not_listed_disks: bool, + refreshes: DiskRefreshKind, + ) { + self.inner + .refresh_list_specifics(remove_not_listed_disks, refreshes); } } @@ -435,19 +440,23 @@ impl fmt::Display for DiskKind { /// Used to determine what you want to refresh specifically on the [`Disk`] type. /// +/// * `kind` is about refreshing the [`Disk::kind`] information. +/// * `storage` is about refreshing the [`Disk::available_space`] and [`Disk::total_space`] information. +/// * `io_usage` is about refreshing the [`Disk::usage`] information. +/// /// ```no_run /// use sysinfo::{Disks, DiskRefreshKind}; /// /// let mut disks = Disks::new_with_refreshed_list_specifics(DiskRefreshKind::everything()); /// /// for disk in disks.list() { -/// assert_eq!(disk.total_space(), 0); +/// assert!(disk.total_space() != 0); /// } /// ``` #[derive(Clone, Copy, Debug, Default)] pub struct DiskRefreshKind { kind: bool, - details: bool, + storage: bool, io_usage: bool, } @@ -460,7 +469,7 @@ impl DiskRefreshKind { /// let r = DiskRefreshKind::new(); /// /// assert_eq!(r.kind(), false); - /// assert_eq!(r.details(), false); + /// assert_eq!(r.storage(), false); /// assert_eq!(r.io_usage(), false); /// ``` pub fn new() -> Self { @@ -475,18 +484,18 @@ impl DiskRefreshKind { /// let r = DiskRefreshKind::everything(); /// /// assert_eq!(r.kind(), true); - /// assert_eq!(r.details(), true); + /// assert_eq!(r.storage(), true); /// assert_eq!(r.io_usage(), true); /// ``` pub fn everything() -> Self { Self { kind: true, - details: true, + storage: true, io_usage: true, } } impl_get_set!(DiskRefreshKind, kind, with_kind, without_kind); - impl_get_set!(DiskRefreshKind, details, with_details, without_details,); + impl_get_set!(DiskRefreshKind, storage, with_storage, without_storage); impl_get_set!(DiskRefreshKind, io_usage, with_io_usage, without_io_usage); } diff --git a/src/unix/apple/disk.rs b/src/unix/apple/disk.rs index 712f9f06b..812f3637a 100644 --- a/src/unix/apple/disk.rs +++ b/src/unix/apple/disk.rs @@ -37,6 +37,7 @@ pub(crate) struct DiskInner { pub(crate) old_read_bytes: u64, pub(crate) written_bytes: u64, pub(crate) read_bytes: u64, + updated: bool, } impl DiskInner { @@ -73,46 +74,10 @@ impl DiskInner { } pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) -> bool { - if refresh_kind.kind() && self.type_ == DiskKind::Unknown(-1) { - #[cfg(target_os = "macos")] - { - match self - .bsd_name - .as_ref() - .and_then(|name| crate::sys::inner::disk::get_disk_type(name)) - { - Some(type_) => self.type_ = type_, - None => { - sysinfo_debug!("Failed to retrieve `DiskKind`"); - } - } - } - #[cfg(not(target_os = "macos"))] - { - self.type_ = DiskKind::SSD; - } - } - - if refresh_kind.io_usage() { - #[cfg(target_os = "macos")] - match self - .bsd_name - .as_ref() - .and_then(|name| crate::sys::inner::disk::get_disk_io(name)) - { - Some((read_bytes, written_bytes)) => { - self.old_read_bytes = self.read_bytes; - self.old_written_bytes = self.written_bytes; - self.read_bytes = read_bytes; - self.written_bytes = written_bytes; - } - None => { - sysinfo_debug!("Failed to update disk i/o stats"); - } - } - } + self.refresh_kind(refresh_kind); + self.refresh_io(refresh_kind); - if refresh_kind.details() { + if refresh_kind.storage() { unsafe { if let Some(requested_properties) = build_requested_properties(&[ ffi::kCFURLVolumeTotalCapacityKey, @@ -158,6 +123,52 @@ impl DiskInner { total_written_bytes: self.written_bytes, } } + + fn refresh_kind(&mut self, refresh_kind: DiskRefreshKind) { + if refresh_kind.kind() && self.type_ == DiskKind::Unknown(-1) { + #[cfg(target_os = "macos")] + { + match self + .bsd_name + .as_ref() + .and_then(|name| crate::sys::inner::disk::get_disk_type(name)) + { + Some(type_) => self.type_ = type_, + None => { + sysinfo_debug!("Failed to retrieve `DiskKind`"); + } + } + } + #[cfg(not(target_os = "macos"))] + { + self.type_ = DiskKind::SSD; + } + } + } + + #[cfg(target_os = "macos")] + fn refresh_io(&mut self, refresh_kind: DiskRefreshKind) { + if refresh_kind.io_usage() { + match self + .bsd_name + .as_ref() + .and_then(|name| crate::sys::inner::disk::get_disk_io(name)) + { + Some((read_bytes, written_bytes)) => { + self.old_read_bytes = self.read_bytes; + self.old_written_bytes = self.written_bytes; + self.read_bytes = read_bytes; + self.written_bytes = written_bytes; + } + None => { + sysinfo_debug!("Failed to update disk i/o stats"); + } + } + } + } + + #[cfg(not(target_os = "macos"))] + fn refresh_io(&mut self, _refresh_kind: DiskRefreshKind) {} } impl crate::DisksInner { @@ -167,12 +178,16 @@ impl crate::DisksInner { } } - pub(crate) fn refresh_list_specifics(&mut self, refresh_kind: DiskRefreshKind) { + pub(crate) fn refresh_list_specifics( + &mut self, + remove_not_listed_disks: bool, + refresh_kind: DiskRefreshKind, + ) { unsafe { // SAFETY: We don't keep any Objective-C objects around because we // don't make any direct Objective-C calls in this code. with_autorelease(|| { - get_list(&mut self.disks, refresh_kind); + get_list(&mut self.disks, remove_not_listed_disks, refresh_kind); }) } } @@ -192,9 +207,11 @@ impl crate::DisksInner { } } -unsafe fn get_list(container: &mut Vec, refresh_kind: DiskRefreshKind) { - container.clear(); - +unsafe fn get_list( + container: &mut Vec, + remove_not_listed_disks: bool, + refresh_kind: DiskRefreshKind, +) { let raw_disks = { let count = libc::getfsstat(ptr::null_mut(), 0, libc::MNT_NOWAIT); if count < 1 { @@ -213,18 +230,26 @@ unsafe fn get_list(container: &mut Vec, refresh_kind: DiskRefreshKind) { disks }; + // Currently we query maximum 9 properties. + let mut properties = Vec::with_capacity(9); + // "mandatory" information + properties.push(ffi::kCFURLVolumeNameKey); + properties.push(ffi::kCFURLVolumeIsBrowsableKey); + properties.push(ffi::kCFURLVolumeIsLocalKey); + + // is_removable + properties.push(ffi::kCFURLVolumeIsEjectableKey); + properties.push(ffi::kCFURLVolumeIsRemovableKey); + properties.push(ffi::kCFURLVolumeIsInternalKey); + + if refresh_kind.storage() { + properties.push(ffi::kCFURLVolumeTotalCapacityKey); + properties.push(ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey); + properties.push(ffi::kCFURLVolumeAvailableCapacityKey); + } + // 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, - ]) { + let requested_properties = match build_requested_properties(&properties) { Some(properties) => properties, None => { sysinfo_debug!("failed to create volume key list"); @@ -290,10 +315,34 @@ unsafe fn get_list(container: &mut Vec, refresh_kind: DiskRefreshKind) { CStr::from_ptr(c_disk.f_mntonname.as_ptr()).to_bytes(), )); - if let Some(disk) = new_disk(mount_point, volume_url, c_disk, &prop_dict, refresh_kind) { + let disk = container + .iter_mut() + .find(|d| d.inner.mount_point == mount_point); + if let Some(disk) = new_disk( + disk, + mount_point, + volume_url, + c_disk, + &prop_dict, + refresh_kind, + ) { container.push(disk); } } + + if remove_not_listed_disks { + container.retain_mut(|disk| { + if !disk.inner.updated { + return false; + } + disk.inner.updated = false; + true + }); + } else { + for c in container.iter_mut() { + c.inner.updated = false; + } + } } type RetainedCFArray = CFReleaser; @@ -431,46 +480,37 @@ pub(super) unsafe fn get_int_value(dict: CFDictionaryRef, key: DictKey) -> Optio } unsafe fn new_disk( + disk: Option<&mut Disk>, mount_point: PathBuf, volume_url: RetainedCFURL, c_disk: libc::statfs, disk_props: &RetainedCFDictionary, refresh_kind: DiskRefreshKind, ) -> Option { - let bsd_name = get_bsd_name(&c_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 and disk i/o stats. All iOS devices use flash-based storage - // so we just assume the disk type is an SSD and set disk i/o stats to 0 until Rust has a way to conditionally link to - // IOKit in more recent deployment versions. - - let type_ = if refresh_kind.kind() { - #[cfg(target_os = "macos")] - { - bsd_name - .as_ref() - .and_then(|name| crate::sys::inner::disk::get_disk_type(name)) - .unwrap_or(DiskKind::Unknown(-1)) - } - #[cfg(not(target_os = "macos"))] - DiskKind::SSD + let (total_space, available_space) = if refresh_kind.storage() { + ( + get_int_value( + disk_props.inner(), + DictKey::Extern(ffi::kCFURLVolumeTotalCapacityKey), + ), + get_available_volume_space(disk_props), + ) } else { - DiskKind::Unknown(-1) + (None, None) }; - let (read_bytes, written_bytes) = if refresh_kind.io_usage() { - #[cfg(target_os = "macos")] - { - bsd_name - .as_ref() - .and_then(|name| crate::sys::inner::disk::get_disk_io(name)) - .unwrap_or_default() + if let Some(disk) = disk { + let disk = &mut disk.inner; + if let Some(total_space) = total_space { + disk.total_space = total_space; } - #[cfg(not(target_os = "macos"))] - (0, 0) - } else { - (0, 0) - }; + if let Some(available_space) = available_space { + disk.available_space = available_space; + } + disk.refresh_io(refresh_kind); + disk.updated = true; + return None; + } // Note: Since we requested these properties from the system, we don't expect // these property retrievals to fail. @@ -481,51 +521,6 @@ unsafe fn new_disk( ) .map(OsString::from)?; - let is_removable = if refresh_kind.details() { - 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 - } - } else { - false - }; - - let (total_space, available_space) = if refresh_kind.details() { - ( - get_int_value( - disk_props.inner(), - DictKey::Extern(ffi::kCFURLVolumeTotalCapacityKey), - ) - .unwrap_or(0), - get_available_volume_space(disk_props).unwrap_or(0), - ) - } else { - (0, 0) - }; - let file_system = { let len = c_disk .f_fstypename @@ -540,26 +535,63 @@ unsafe fn new_disk( ) }; - let is_read_only = refresh_kind.details() && (c_disk.f_flags & libc::MNT_RDONLY as u32) != 0; + let bsd_name = get_bsd_name(&c_disk); - Some(Disk { - inner: DiskInner { - type_, - name, - bsd_name, - file_system, - mount_point, - volume_url, - total_space, - available_space, - is_removable, - is_read_only, - read_bytes, - written_bytes, - old_read_bytes: 0, - old_written_bytes: 0, - }, - }) + // 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 and disk i/o stats. All iOS devices use flash-based storage + // so we just assume the disk type is an SSD and set disk i/o stats to 0 until Rust has a way to conditionally link to + // IOKit in more recent deployment versions. + + let ejectable = get_bool_value( + disk_props.inner(), + DictKey::Extern(ffi::kCFURLVolumeIsEjectableKey), + ) + .unwrap_or(false); + + let removable = get_bool_value( + disk_props.inner(), + DictKey::Extern(ffi::kCFURLVolumeIsRemovableKey), + ) + .unwrap_or(false); + + let is_removable = if ejectable || removable { + true + } 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 is_read_only = (c_disk.f_flags & libc::MNT_RDONLY as u32) != 0; + + let mut disk = DiskInner { + type_: DiskKind::Unknown(-1), + name, + bsd_name, + file_system, + mount_point, + volume_url, + total_space: total_space.unwrap_or(0), + available_space: available_space.unwrap_or(0), + is_removable, + is_read_only, + read_bytes: 0, + written_bytes: 0, + old_read_bytes: 0, + old_written_bytes: 0, + updated: true, + }; + + disk.refresh_kind(refresh_kind); + disk.refresh_io(refresh_kind); + + Some(Disk { inner: disk }) } /// Calls the provided closure in the context of a new autorelease pool that is drained @@ -599,7 +631,7 @@ fn get_bsd_name(disk: &libc::statfs) -> Option> { // Removes `/dev/` from the value. unsafe { CStr::from_ptr(disk.f_mntfromname.as_ptr()) - .to_bytes() + .to_bytes_with_nul() .strip_prefix(b"/dev/") .map(|slice| slice.to_vec()) .or_else(|| { diff --git a/src/unix/freebsd/disk.rs b/src/unix/freebsd/disk.rs index 6958b9b6b..026fb9745 100644 --- a/src/unix/freebsd/disk.rs +++ b/src/unix/freebsd/disk.rs @@ -38,6 +38,7 @@ pub(crate) struct DiskInner { impl DiskInner { pub(crate) fn kind(&self) -> DiskKind { + // Currently don't know how to retrieve this information on FreeBSD. DiskKind::Unknown(-1) } @@ -90,8 +91,12 @@ impl crate::DisksInner { } } - pub(crate) fn refresh_list_specifics(&mut self, refresh_kind: DiskRefreshKind) { - unsafe { get_all_list(&mut self.disks, true, refresh_kind) } + pub(crate) fn refresh_list_specifics( + &mut self, + remove_not_listed_disks: bool, + refresh_kind: DiskRefreshKind, + ) { + unsafe { get_all_list(&mut self.disks, true, remove_not_listed_disks, refresh_kind) } } pub(crate) fn list(&self) -> &[Disk] { @@ -104,7 +109,7 @@ impl crate::DisksInner { pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) { unsafe { - get_all_list(&mut self.disks, false, refresh_kind); + get_all_list(&mut self.disks, false, false, refresh_kind); } } } @@ -162,11 +167,11 @@ impl GetValues for DiskInner { } } -/// Returns `(total_space, available_space)`. +/// Returns `(total_space, available_space, is_read_only)`. unsafe fn get_statvfs( c_mount_point: &[libc::c_char], vfs: &mut libc::statvfs, -) -> Option<(u64, u64)> { +) -> Option<(u64, u64, bool)> { if libc::statvfs(c_mount_point.as_ptr() as *const _, vfs as *mut _) < 0 { sysinfo_debug!("statvfs failed"); None @@ -175,18 +180,21 @@ unsafe fn get_statvfs( Some(( vfs.f_blocks.saturating_mul(block_size), vfs.f_favail.saturating_mul(block_size), + (vfs.f_flag & libc::ST_RDONLY) != 0, )) } } fn refresh_disk(disk: &mut DiskInner, refresh_kind: DiskRefreshKind) -> bool { - if refresh_kind.details() { + if refresh_kind.storage() { unsafe { let mut vfs: libc::statvfs = std::mem::zeroed(); - if let Some((total_space, available_space)) = get_statvfs(&disk.c_mount_point, &mut vfs) + if let Some((total_space, available_space, is_read_only)) = + get_statvfs(&disk.c_mount_point, &mut vfs) { disk.total_space = total_space; disk.available_space = available_space; + disk.is_read_only = is_read_only; } } } @@ -315,6 +323,7 @@ fn get_disks_mapping() -> HashMap { pub unsafe fn get_all_list( container: &mut Vec, add_new_disks: bool, + remove_not_listed_disks: bool, refresh_kind: DiskRefreshKind, ) { let mut fs_infos: *mut libc::statfs = null_mut(); @@ -374,59 +383,44 @@ pub unsafe fn get_all_list( OsString::from(mount_point) }; - let (is_read_only, total_space, available_space) = if refresh_kind.details() { - if let Some((total_space, available_space)) = - get_statvfs(&fs_info.f_mntonname, &mut vfs) - { - ( - ((vfs.f_flag & libc::ST_RDONLY) != 0), - available_space, - total_space, - ) - } else { - (false, 0, 0) - } - } else { - (false, 0, 0) - }; - if let Some(disk) = container.iter_mut().find(|d| d.inner.name == name) { + // I/O usage is updated for all disks at once at the end. + refresh_disk(&mut disk.inner, refresh_kind.without_io_usage()); disk.inner.updated = true; - disk.inner.total_space = total_space; - disk.inner.available_space = available_space; } else if add_new_disks { let dev_mount_point = c_buf_to_utf8_str(&fs_info.f_mntfromname).unwrap_or(""); // USB keys and CDs are removable. - let is_removable = if refresh_kind.details() { + let is_removable = if refresh_kind.storage() { [b"USB", b"usb"].iter().any(|b| *b == &fs_type[..]) || fs_type.starts_with(b"/dev/cd") } else { false }; - container.push(Disk { - inner: DiskInner { - name, - c_mount_point: fs_info.f_mntonname.to_vec(), - mount_point: PathBuf::from(mount_point), - dev_id: disk_mapping.get(dev_mount_point).map(ToString::to_string), - total_space, - available_space, - file_system: OsString::from_vec(fs_type), - is_removable, - is_read_only, - read_bytes: 0, - old_read_bytes: 0, - written_bytes: 0, - old_written_bytes: 0, - updated: true, - }, - }); + let mut disk = DiskInner { + name, + c_mount_point: fs_info.f_mntonname.to_vec(), + mount_point: PathBuf::from(mount_point), + dev_id: disk_mapping.get(dev_mount_point).map(ToString::to_string), + total_space: 0, + available_space: 0, + file_system: OsString::from_vec(fs_type), + is_removable, + is_read_only: false, + read_bytes: 0, + old_read_bytes: 0, + written_bytes: 0, + old_written_bytes: 0, + updated: true, + }; + // I/O usage is updated for all disks at once at the end. + refresh_disk(&mut disk, refresh_kind.without_io_usage()); + container.push(Disk { inner: disk }); } } - if add_new_disks { + if remove_not_listed_disks { container.retain_mut(|disk| { if !disk.inner.updated { return false; diff --git a/src/unix/linux/disk.rs b/src/unix/linux/disk.rs index 83c2c7d8e..2dfb227d8 100644 --- a/src/unix/linux/disk.rs +++ b/src/unix/linux/disk.rs @@ -7,7 +7,7 @@ use libc::statvfs; use std::collections::HashMap; use std::ffi::{OsStr, OsString}; use std::fs; -use std::mem; +use std::mem::MaybeUninit; use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -48,6 +48,7 @@ pub(crate) struct DiskInner { old_read_bytes: u64, written_bytes: u64, read_bytes: u64, + updated: bool, } impl DiskInner { @@ -84,18 +85,15 @@ impl DiskInner { } pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) -> bool { - self.efficient_refresh(refresh_kind, &disk_stats(&refresh_kind)) + self.efficient_refresh(refresh_kind, &disk_stats(&refresh_kind), false) } fn efficient_refresh( &mut self, refresh_kind: DiskRefreshKind, procfs_disk_stats: &HashMap, + first: bool, ) -> bool { - if refresh_kind.kind() && self.type_ == DiskKind::Unknown(-1) { - self.type_ = find_type_for_device_name(&self.device_name); - } - if refresh_kind.io_usage() { if self.actual_device_name.is_none() { self.actual_device_name = Some(get_actual_device_name(&self.device_name)); @@ -114,13 +112,19 @@ impl DiskInner { } } - if refresh_kind.details() { + if refresh_kind.kind() && self.type_ == DiskKind::Unknown(-1) { + self.type_ = find_type_for_device_name(&self.device_name); + } + + if refresh_kind.storage() { if let Some((total_space, available_space, is_read_only)) = unsafe { load_statvfs_values(&self.mount_point) } { self.total_space = total_space; self.available_space = available_space; - self.is_read_only = is_read_only; + if first { + self.is_read_only = is_read_only; + } } } @@ -144,9 +148,14 @@ impl crate::DisksInner { } } - pub(crate) fn refresh_list_specifics(&mut self, refresh_kind: DiskRefreshKind) { + pub(crate) fn refresh_list_specifics( + &mut self, + remove_not_listed_disks: bool, + refresh_kind: DiskRefreshKind, + ) { get_all_list( &mut self.disks, + remove_not_listed_disks, &get_all_utf8_data("/proc/mounts", 16_385).unwrap_or_default(), refresh_kind, ) @@ -156,7 +165,7 @@ impl crate::DisksInner { let procfs_disk_stats = disk_stats(&refresh_kind); for disk in self.list_mut() { disk.inner - .efficient_refresh(refresh_kind, &procfs_disk_stats); + .efficient_refresh(refresh_kind, &procfs_disk_stats, false); } } @@ -190,8 +199,14 @@ fn get_actual_device_name(device: &OsStr) -> String { unsafe fn load_statvfs_values(mount_point: &Path) -> Option<(u64, u64, bool)> { let mount_point_cpath = to_cpath(mount_point); - let mut stat: statvfs = mem::zeroed(); - if retry_eintr!(statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat)) == 0 { + let mut stat: MaybeUninit = MaybeUninit::uninit(); + if retry_eintr!(statvfs( + mount_point_cpath.as_ptr() as *const _, + stat.as_mut_ptr() + )) == 0 + { + let stat = stat.assume_init(); + let bsize = cast!(stat.f_bsize); let blocks = cast!(stat.f_blocks); let bavail = cast!(stat.f_bavail); @@ -216,56 +231,31 @@ fn new_disk( procfs_disk_stats: &HashMap, refresh_kind: DiskRefreshKind, ) -> Disk { - let type_ = if refresh_kind.kind() { - find_type_for_device_name(device_name) - } else { - DiskKind::Unknown(-1) - }; - - let (total_space, available_space, is_read_only) = if refresh_kind.details() { - unsafe { load_statvfs_values(mount_point).unwrap_or((0, 0, false)) } - } else { - (0, 0, false) - }; + let is_removable = removable_entries + .iter() + .any(|e| e.as_os_str() == device_name); - let is_removable = refresh_kind.details() - && removable_entries - .iter() - .any(|e| e.as_os_str() == device_name); - - let (actual_device_name, read_bytes, written_bytes) = if refresh_kind.io_usage() { - let actual_device_name = get_actual_device_name(device_name); - let (read_bytes, written_bytes) = procfs_disk_stats - .get(&actual_device_name) - .map(|stat| { - ( - stat.sectors_read * SECTOR_SIZE, - stat.sectors_written * SECTOR_SIZE, - ) - }) - .unwrap_or_default(); - (Some(actual_device_name), read_bytes, written_bytes) - } else { - (None, 0, 0) - }; - - Disk { + let mut disk = Disk { inner: DiskInner { - type_, + type_: DiskKind::Unknown(-1), device_name: device_name.to_owned(), - actual_device_name, + actual_device_name: None, file_system: file_system.to_owned(), mount_point: mount_point.to_owned(), - total_space, - available_space, + total_space: 0, + available_space: 0, is_removable, - is_read_only, + is_read_only: false, old_read_bytes: 0, old_written_bytes: 0, - read_bytes, - written_bytes, + read_bytes: 0, + written_bytes: 0, + updated: true, }, - } + }; + disk.inner + .efficient_refresh(refresh_kind, procfs_disk_stats, true); + disk } #[allow(clippy::manual_range_contains)] @@ -339,8 +329,12 @@ fn find_type_for_device_name(device_name: &OsStr) -> DiskKind { } } -fn get_all_list(container: &mut Vec, content: &str, refresh_kind: DiskRefreshKind) { - container.clear(); +fn get_all_list( + container: &mut Vec, + remove_not_listed_disks: bool, + content: &str, + refresh_kind: DiskRefreshKind, +) { // The goal of this array is to list all removable devices (the ones whose name starts with // "usb-"). let removable_entries = match fs::read_dir("/dev/disk/by-id/") { @@ -362,7 +356,7 @@ fn get_all_list(container: &mut Vec, content: &str, refresh_kind: DiskRefr let procfs_disk_stats = disk_stats(&refresh_kind); - for disk in content + for (fs_spec, fs_file, fs_vfstype) in content .lines() .map(|line| { let line = line.trim_start(); @@ -407,18 +401,39 @@ fn get_all_list(container: &mut Vec, content: &str, refresh_kind: DiskRefr (fs_file.starts_with("/run") && !fs_file.starts_with("/run/media")) || fs_spec.starts_with("sunrpc")) }) - .map(|(fs_spec, fs_file, fs_vfstype)| { - new_disk( - fs_spec.as_ref(), - Path::new(&fs_file), - fs_vfstype.as_ref(), - &removable_entries, - &procfs_disk_stats, - refresh_kind, - ) - }) { - container.push(disk); + let mount_point = Path::new(&fs_file); + if let Some(disk) = container + .iter_mut() + .find(|d| d.inner.mount_point == mount_point) + { + disk.inner + .efficient_refresh(refresh_kind, &procfs_disk_stats, false); + disk.inner.updated = true; + continue; + } + container.push(new_disk( + fs_spec.as_ref(), + mount_point, + fs_vfstype.as_ref(), + &removable_entries, + &procfs_disk_stats, + refresh_kind, + )); + } + + if remove_not_listed_disks { + container.retain_mut(|disk| { + if !disk.inner.updated { + return false; + } + disk.inner.updated = false; + true + }); + } else { + for c in container.iter_mut() { + c.inner.updated = false; + } } } diff --git a/src/unknown/disk.rs b/src/unknown/disk.rs index 7a4b97d54..99bc6a634 100644 --- a/src/unknown/disk.rs +++ b/src/unknown/disk.rs @@ -65,7 +65,11 @@ impl DisksInner { self.disks } - pub(crate) fn refresh_list_specifics(&mut self, _refreshes: DiskRefreshKind) { + pub(crate) fn refresh_list_specifics( + &mut self, + _remove_not_listed_disks: bool, + _refreshes: DiskRefreshKind, + ) { // Does nothing. } diff --git a/src/windows/disk.rs b/src/windows/disk.rs index 13a74b488..be6a3cf0f 100644 --- a/src/windows/disk.rs +++ b/src/windows/disk.rs @@ -132,6 +132,7 @@ pub(crate) struct DiskInner { old_read_bytes: u64, written_bytes: u64, read_bytes: u64, + updated: bool, } impl DiskInner { @@ -168,30 +169,36 @@ impl DiskInner { } pub(crate) fn refresh_specifics(&mut self, refreshes: DiskRefreshKind) -> bool { - if refreshes.kind() && self.type_ == DiskKind::Unknown(-1) { - self.type_ = unsafe { get_disk_kind(&self.device_path, None) }; - } + if refreshes.kind() || refreshes.io_usage() { + if let Some(handle) = + HandleWrapper::new_from_file(&self.device_path, Default::default()) + { + if refreshes.kind() && self.type_ == DiskKind::Unknown(-1) { + self.type_ = get_disk_kind(&self.device_path, &handle); + } - if refreshes.io_usage() { - if let Some((read_bytes, written_bytes)) = get_disk_io(&self.device_path, None) { - self.old_read_bytes = self.read_bytes; - self.old_written_bytes = self.written_bytes; - self.read_bytes = read_bytes; - self.written_bytes = written_bytes; - } else { - sysinfo_debug!("Failed to update disk i/o stats"); + if refreshes.io_usage() { + if let Some((read_bytes, written_bytes)) = + get_disk_io(&self.device_path, handle) + { + self.old_read_bytes = self.read_bytes; + self.old_written_bytes = self.written_bytes; + self.read_bytes = read_bytes; + self.written_bytes = written_bytes; + } else { + sysinfo_debug!("Failed to update disk i/o stats"); + } + } } } - if refreshes.details() { - if let Some((total_space, available_space)) = - unsafe { get_drive_size(&self.mount_point) } - { + if refreshes.storage() { + if let Some((total_space, available_space)) = get_drive_size(&self.mount_point) { self.total_space = total_space; self.available_space = available_space; } } - false + true } pub(crate) fn usage(&self) -> DiskUsage { @@ -223,9 +230,13 @@ impl DisksInner { self.disks } - pub(crate) fn refresh_list_specifics(&mut self, refreshes: DiskRefreshKind) { + pub(crate) fn refresh_list_specifics( + &mut self, + remove_not_listed_disks: bool, + refreshes: DiskRefreshKind, + ) { unsafe { - self.disks = get_list(refreshes); + get_list(&mut self.disks, remove_not_listed_disks, refreshes); } } @@ -262,20 +273,25 @@ unsafe fn get_drive_size(mount_point: &[u16]) -> Option<(u64, u64)> { } } -pub(crate) unsafe fn get_list(refreshes: DiskRefreshKind) -> Vec { +pub(crate) unsafe fn get_list( + disks: &mut Vec, + remove_not_listed_disks: bool, + refreshes: DiskRefreshKind, +) { #[cfg(feature = "multithread")] use rayon::iter::ParallelIterator; - crate::utils::into_iter(get_volume_guid_paths()) - .flat_map(|volume_name| { + for disk in crate::utils::into_iter(get_volume_guid_paths()) + .map(|volume_name| { let raw_volume_name = PCWSTR::from_raw(volume_name.as_ptr()); let drive_type = GetDriveTypeW(raw_volume_name); - let is_removable = refreshes.details() && drive_type == DRIVE_REMOVABLE; - if drive_type != DRIVE_FIXED && drive_type != DRIVE_REMOVABLE { return Vec::new(); } + + let is_removable = drive_type == DRIVE_REMOVABLE; + let mut name = [0u16; MAX_PATH as usize + 1]; let mut file_system = [0u16; 32]; let mut flags = 0; @@ -295,7 +311,7 @@ pub(crate) unsafe fn get_list(refreshes: DiskRefreshKind) -> Vec { ); return Vec::new(); } - let is_read_only = refreshes.details() && (flags & FILE_READ_ONLY_VOLUME) != 0; + let is_read_only = (flags & FILE_READ_ONLY_VOLUME) != 0; let mount_paths = get_volume_path_names_for_volume_name(&volume_name[..]); if mount_paths.is_empty() { @@ -308,51 +324,53 @@ pub(crate) unsafe fn get_list(refreshes: DiskRefreshKind) -> Vec { .copied() .chain([0]) .collect::>(); - let handle = HandleWrapper::new_from_file(&device_path[..], Default::default()); - - let (total_space, available_space) = if refreshes.details() { - get_drive_size(&mount_paths[0][..]).unwrap_or_default() - } else { - (0, 0) - }; - - let type_ = if refreshes.kind() { - get_disk_kind(&device_path, handle.as_ref()) - } else { - DiskKind::Unknown(-1) - }; - - let (read_bytes, written_bytes) = if refreshes.io_usage() { - get_disk_io(&device_path, handle).unwrap_or_default() - } else { - (0, 0) - }; let name = os_string_from_zero_terminated(&name); let file_system = os_string_from_zero_terminated(&file_system); - mount_paths - .into_iter() - .map(move |mount_path| Disk { - inner: DiskInner { - type_, - name: name.clone(), - file_system: file_system.clone(), - s_mount_point: OsString::from_wide(&mount_path[..mount_path.len() - 1]), - mount_point: mount_path, - total_space, - available_space, - is_removable, - is_read_only, - device_path: device_path.clone(), - old_read_bytes: 0, - old_written_bytes: 0, - read_bytes, - written_bytes, - }, - }) - .collect::>() + mount_paths.into_iter().filter_map(move |mount_path| { + if let Some(disk) = disks.iter_mut().find(|d| d.inner.mount_point == mount_path) { + disk.refresh_specifics(refreshes); + return None; + } + let mut disk = DiskInner { + type_: DiskKind::Unknown(-1), + name: name.clone(), + file_system: file_system.clone(), + s_mount_point: OsString::from_wide(&mount_path[..mount_path.len() - 1]), + mount_point: mount_path, + total_space: 0, + available_space: 0, + is_removable, + is_read_only, + device_path: device_path.clone(), + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + updated: true, + }; + disk.refresh_specifics(refreshes); + Some(Disk { inner: disk }) + }) }) - .collect::>() + .flatten() + { + disks.push(disk); + } + + if remove_not_listed_disks { + disks.retain_mut(|disk| { + if !disk.inner.updated { + return false; + } + disk.inner.updated = false; + true + }); + } else { + for c in disks.iter_mut() { + c.inner.updated = false; + } + } } fn os_string_from_zero_terminated(name: &[u16]) -> OsString { @@ -360,7 +378,7 @@ fn os_string_from_zero_terminated(name: &[u16]) -> OsString { OsString::from_wide(&name[..len]) } -unsafe fn get_disk_kind(device_path: &[u16], borrowed_handle: Option<&HandleWrapper>) -> DiskKind { +unsafe fn get_disk_kind(device_path: &[u16], borrowed_handle: &HandleWrapper) -> DiskKind { let handle_data; let borrowed_handle = if borrowed_handle.is_none() { handle_data = HandleWrapper::new_from_file(device_path, Default::default()); @@ -416,10 +434,7 @@ unsafe fn get_disk_kind(device_path: &[u16], borrowed_handle: Option<&HandleWrap } /// Returns a tuple consisting of the total number of bytes read and written by the volume with the specified device path -fn get_disk_io(device_path: &[u16], handle: Option) -> Option<(u64, u64)> { - let handle = - handle.or(unsafe { HandleWrapper::new_from_file(device_path, Default::default()) })?; - +fn get_disk_io(device_path: &[u16], handle: HandleWrapper) -> Option<(u64, u64)> { if handle.is_invalid() { sysinfo_debug!( "Expected handle to {:?} to be valid", diff --git a/tests/disk.rs b/tests/disk.rs index 2fbab2ba3..0ec0f75da 100644 --- a/tests/disk.rs +++ b/tests/disk.rs @@ -21,7 +21,7 @@ fn test_disks() { let mut disks = sysinfo::Disks::new(); assert!(disks.list().is_empty()); - disks.refresh_list(); + disks.refresh_list(false); assert!(!disks.list().is_empty()); } @@ -39,8 +39,8 @@ fn test_disk_refresh_kind() { for fs in [ DiskRefreshKind::with_kind, DiskRefreshKind::without_kind, - DiskRefreshKind::with_details, - DiskRefreshKind::without_details, + DiskRefreshKind::with_storage, + DiskRefreshKind::without_storage, DiskRefreshKind::with_io_usage, DiskRefreshKind::without_io_usage, ] @@ -72,7 +72,7 @@ fn test_disk_refresh_kind() { ); } - if refreshes.details() { + if refreshes.storage() { // These would ideally assert that *all* are refreshed, but we settle for a weaker // assertion because failures can't be distinguished from "not refreshed" values. assert!( @@ -102,18 +102,6 @@ fn test_disk_refresh_kind() { .all(|disk| disk.total_space() == Default::default()), "{name}: disk.total_space should not be refreshed" ); - assert!( - disks - .iter() - .all(|disk| disk.is_read_only() == ::default()), - "{name}: disk.is_read_only should not be refreshed" - ); - assert!( - disks - .iter() - .all(|disk| disk.is_removable() == ::default()), - "{name}: disk.is_removable should not be refreshed" - ); } if refreshes.io_usage() {