Repository: GuillaumeGomez/sysinfo (language: rust)

Issue:
Selectively refreshing disks
When running `Disks::new_with_refreshed_list()` on android devices, I often see warnings (from SELinux?) along the lines of:

```
type=1400 audit(0.0:339351): avc:  denied  { search } for  name="block" dev="tmpfs" ino=12
```

I've done some spelunking, and it appears they are a side-effect of [this `canonicalize` call](https://github.com/GuillaumeGomez/sysinfo/blob/master/src/unix/linux/disk.rs#L157), on certain disks' mount points.

(N.b. the dev="tmpfs" in the warning doesn't refer to the type of the filesystem being refreshed. I'm not using `linux-tmpfs`, so they're filtered out. In debugging, I've seen the warning for "ext4" and "fuse" filesystems.)

I know these warnings are harmless! ...but I've had customers who were alarmed about them, so I'd prefer to avoid generating them. Given my use-case (I'm only reporting disk usage over time for *one* disk), I'd love some sort of mechanism for *selectively* refreshing `Disks`. There are lots of options.

My first suggestion would be a separate `Disks` constructor (`new_with_list()` or `new_with_unrefreshed_list()`?) that loads the *list* of disks from /proc/mounts, but defers the *stat collection* until a later `refresh`. On linux, at least, I think the separation makes sense, and it gives complete control over *what is refreshed* to the library user, without adding much complexity to the interface. On other platforms, I haven't looked, but it seems like the worst-case would simply be that `new_with_list()` calls `new_with_refreshed_list()` on platforms where there's no distinction between listing and refreshing.

I'd be happy to do the work. If my proposal sounds reasonable, I'll submit a PR next week and we can hash out the specifics. If not, I'd be open to other ways of solving this... just let me know :)


Discussion / hints:
Seems a bit weird to want to have disks but not their path or even their kind. I think the best course would be to instead detect when the `canonicalize` call is actually needed and only call it if so.
> Seems a bit weird to want to have disks but not their path or even their kind.

I was thinking those *would* be present in the `new_with_list` return. Only the information obtained from `statvfs` would be deferred.

> I think the best course would be to instead detect when the canonicalize call is actually needed and only call it if so.

Hummm. Canonicalize (on linux) IIUC is just `realpath`. How would one decide if it were necessary? (I think you'd end up reimplementing almost all of `realpath`, right?)
The kind is retrieved from the function using `canonicalize`, so both functions would get a call to it.

> Hummm. Canonicalize (on linux) IIUC is just `realpath`. How would one decide if it were necessary? (I think you'd end up reimplementing almost all of `realpath`, right?)

Mostly checking if it's a link, if not, checking if it doesn't start with `/` or contains `..` (still not great to check that ourselves).

Any clue why `statvfs` is emitting this warning?
> The kind is retrieved from the function using canonicalize, so both functions would get a call to it.

Ah, I see. I was thinking about `vfstype`, not `DiskKind`. You're right that `DiskKind` requires the `canonicalize`.

So in my suggestion, I guess the populated fields would be `device_name`, `file_system` and `mount_point`; the other fields would require a `refresh`.

> Any clue why statvfs is emitting this warning?

I believe it happens during the `canonicalize`, not the `statvfs`. And it's coming from SELinux or apparmor or some similar system for permissions above and beyond POSIX. The fact that it emits these audit warnings *in addition to failing the syscall* is super annoying, at least in this instance :/
I think `DiskKind` is part of the fundamental information for a `Disk`, so I'm not super open to the idea of not having it by default.

However the `RefreshKind` approach for `Disks` sounds pretty good, just not sure there are enough different fields to make it relevant currently.
I'm not sure what you mean about `RefreshKind`, can you elaborate?
Sure. Like for [`refresh_processes_specifics`](https://docs.rs/sysinfo/latest/sysinfo/struct.System.html#method.refresh_processes_specifics): you can pick what you want to refresh with `ProcessRefreshKind`.
Ahh, I see. That's perfect! I'd be happy to extend that pattern over `Disks`. So I think I would:
 * add a `DiskRefreshKind`
 * add a `Disks::new_with_specifics()` constructor
 * add a `Disks::refresh_specifics()` refresher
 * add a `Disk::refresh_specifics()` refresher
 * make (at least) the linux impl respect the specific refresh kind(s)

... sound about right? That would work for me :)
Yes but that wouldn't solve your problem since I still want `DiskKind` to be retrieved for all `Disk` unconditionally. ^^'
What if you always get `DiskKind` unless you specifically ask *not* to get it?
That could work. Only question, what value do you put into `DiskKind::Unknown`? It's supposed to mean something to the OS.
I'm not sure. I don't use the `DiskKind`, personally. The idiomatic Rust thing would be to make it `Option<DiskKind>`, but I'm guessing that's exactly what you want to avoid. Could we add another enum variant (`DiskKind::NeedsReload`)?
Indeed, I don't want an `Option`. I'm still wondering if we're not trying to go around a technical issue that could be fixed instead...
FWIW: even if it weren't for the annoying warnings, I would still use this functionality, since I'm currently loading way more information than I actually need :)
We can do it in two passes then: a PR to implement the selective refresh and another where we can debate what's best for `DiskKind`.
Look for a PR early next week!
Thanks in advance!

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index a60eb441c..2a9fc5935 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -103,7 +103,7 @@ ntapi = { version = "0.4", optional = true }
 windows = { version = ">=0.54, <=0.57", optional = true }
 
 [target.'cfg(not(any(target_os = "unknown", target_arch = "wasm32")))'.dependencies]
-libc = "^0.2.165"
+libc = "^0.2.164"
 
 [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
 core-foundation-sys = "0.8.7"
@@ -115,6 +115,7 @@ tempfile = "3.9"
 serde_json = "1.0" # Used in documentation tests.
 bstr = "1.9.0"
 tempfile = "3.9"
+itertools = "0.13.0"
 
 [[example]]
 name = "simple"
diff --git a/src/common/disk.rs b/src/common/disk.rs
index 1fe004f11..90d2f307a 100644
--- a/src/common/disk.rs
+++ b/src/common/disk.rs
@@ -4,6 +4,7 @@ use std::ffi::OsStr;
 use std::fmt;
 use std::path::Path;
 
+use crate::common::impl_get_set::impl_get_set;
 use crate::DiskUsage;
 
 /// Struct containing a disk information.
@@ -133,7 +134,9 @@ impl Disk {
         self.inner.is_read_only()
     }
 
-    /// Updates the disk' information.
+    /// Updates the disk' information with everything loaded.
+    ///
+    /// Equivalent to <code>[Disk::refresh_specifics]\([DiskRefreshKind::everything]\())</code>.
     ///
     /// ```no_run
     /// use sysinfo::Disks;
@@ -144,7 +147,21 @@ impl Disk {
     /// }
     /// ```
     pub fn refresh(&mut self) -> bool {
-        self.inner.refresh()
+        self.refresh_specifics(DiskRefreshKind::everything())
+    }
+
+    /// Updates the disk's information corresponding to the given [`DiskRefreshKind`].
+    ///
+    /// ```no_run
+    /// use sysinfo::{Disks, DiskRefreshKind};
+    ///
+    /// let mut disks = Disks::new_with_refreshed_list();
+    /// for disk in disks.list_mut() {
+    ///     disk.refresh_specifics(DiskRefreshKind::new());
+    /// }
+    /// ```
+    pub fn refresh_specifics(&mut self, refreshes: DiskRefreshKind) -> bool {
+        self.inner.refresh_specifics(refreshes)
     }
 
     /// Returns number of bytes read and written by the disk
@@ -244,7 +261,8 @@ impl Disks {
     }
 
     /// Creates a new [`Disks`][crate::Disks] type with the disk list loaded.
-    /// It is a combination of [`Disks::new`] and [`Disks::refresh_list`].
+    ///
+    /// Equivalent to <code>[Disks::new_with_refreshed_list_specifics]\([DiskRefreshKind::everything]\())</code>.
     ///
     /// ```no_run
     /// use sysinfo::Disks;
@@ -255,8 +273,24 @@ impl Disks {
     /// }
     /// ```
     pub fn new_with_refreshed_list() -> Self {
+        Self::new_with_refreshed_list_specifics(DiskRefreshKind::everything())
+    }
+
+    /// Creates a new [`Disks`][crate::Disks] type with the disk list loaded
+    /// and refreshed according to the given [`DiskRefreshKind`]. It is a combination of
+    /// [`Disks::new`] and [`Disks::refresh_list_specifics`].
+    ///
+    /// ```no_run
+    /// use sysinfo::{Disks, DiskRefreshKind};
+    ///
+    /// let mut disks = Disks::new_with_refreshed_list_specifics(DiskRefreshKind::new());
+    /// for disk in disks.list() {
+    ///     println!("{disk:?}");
+    /// }
+    /// ```
+    pub fn new_with_refreshed_list_specifics(refreshes: DiskRefreshKind) -> Self {
         let mut disks = Self::new();
-        disks.refresh_list();
+        disks.refresh_list_specifics(refreshes);
         disks
     }
 
@@ -291,6 +325,13 @@ impl Disks {
 
     /// Refreshes the listed disks' information.
     ///
+    /// Equivalent to <code>[Disks::refresh_specifics]\([DiskRefreshKind::everything]\())</code>.
+    pub fn refresh(&mut self) {
+        self.refresh_specifics(DiskRefreshKind::everything());
+    }
+
+    /// Refreshes the listed disks' information according to the given [`DiskRefreshKind`].
+    ///
     /// ⚠️ If a disk is added or removed, this method won't take it into account. Use
     /// [`Disks::refresh_list`] instead.
     ///
@@ -304,30 +345,45 @@ impl Disks {
     /// // We wait some time...?
     /// disks.refresh();
     /// ```
-    pub fn refresh(&mut self) {
-        self.inner.refresh();
+    pub fn refresh_specifics(&mut self, refreshes: DiskRefreshKind) {
+        self.inner.refresh_specifics(refreshes);
     }
 
     /// The disk list will be emptied then completely recomputed.
     ///
+    /// Equivalent to <code>[Disks::refresh_list_specifics]\([DiskRefreshKind::everything]\())</code>.
+    ///
+    /// ```no_run
+    /// use sysinfo::Disks;
+    ///
+    /// let mut disks = Disks::new();
+    /// disks.refresh_list();
+    /// ```
+    pub fn refresh_list(&mut self) {
+        self.refresh_list_specifics(DiskRefreshKind::everything());
+    }
+
+    /// The disk list will be emptied then completely recomputed according to the given
+    /// [`DiskRefreshKind`].
+    ///
     /// ## Linux
     ///
     /// ⚠️ On Linux, the [NFS](https://en.wikipedia.org/wiki/Network_File_System) file
     /// systems are ignored and the information of a mounted NFS **cannot** be obtained
-    /// via [`Disks::refresh_list`]. This is due to the fact that I/O function
-    /// `statvfs` used by [`Disks::refresh_list`] is blocking and
+    /// via [`Disks::refresh_list_specifics`]. This is due to the fact that I/O function
+    /// `statvfs` used by [`Disks::refresh_list_specifics`] is blocking and
     /// [may hang](https://github.com/GuillaumeGomez/sysinfo/pull/876) in some cases,
     /// requiring to call `systemctl stop` to terminate the NFS service from the remote
     /// server in some cases.
     ///
     /// ```no_run
-    /// use sysinfo::Disks;
+    /// use sysinfo::{Disks, DiskRefreshKind};
     ///
     /// let mut disks = Disks::new();
-    /// disks.refresh_list();
+    /// disks.refresh_list_specifics(DiskRefreshKind::new());
     /// ```
-    pub fn refresh_list(&mut self) {
-        self.inner.refresh_list();
+    pub fn refresh_list_specifics(&mut self, refreshes: DiskRefreshKind) {
+        self.inner.refresh_list_specifics(refreshes);
     }
 }
 
@@ -376,3 +432,61 @@ impl fmt::Display for DiskKind {
         })
     }
 }
+
+/// Used to determine what you want to refresh specifically on the [`Disk`] type.
+///
+/// ```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);
+/// }
+/// ```
+#[derive(Clone, Copy, Debug, Default)]
+pub struct DiskRefreshKind {
+    kind: bool,
+    details: bool,
+    io_usage: bool,
+}
+
+impl DiskRefreshKind {
+    /// Creates a new `DiskRefreshKind` with every refresh set to false.
+    ///
+    /// ```
+    /// use sysinfo::DiskRefreshKind;
+    ///
+    /// let r = DiskRefreshKind::new();
+    ///
+    /// assert_eq!(r.kind(), false);
+    /// assert_eq!(r.details(), false);
+    /// assert_eq!(r.io_usage(), false);
+    /// ```
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Creates a new `DiskRefreshKind` with every refresh set to true.
+    ///
+    /// ```
+    /// use sysinfo::DiskRefreshKind;
+    ///
+    /// let r = DiskRefreshKind::everything();
+    ///
+    /// assert_eq!(r.kind(), true);
+    /// assert_eq!(r.details(), true);
+    /// assert_eq!(r.io_usage(), true);
+    /// ```
+    pub fn everything() -> Self {
+        Self {
+            kind: true,
+            details: 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, io_usage, with_io_usage, without_io_usage);
+}
diff --git a/src/common/impl_get_set.rs b/src/common/impl_get_set.rs
new file mode 100644
index 000000000..2df3616db
--- /dev/null
+++ b/src/common/impl_get_set.rs
@@ -0,0 +1,173 @@
+// Take a look at the license at the top of the repository in the LICENSE file.
+
+macro_rules! impl_get_set {
+    ($ty_name:ident, $name:ident, $with:ident, $without:ident $(, $extra_doc:literal)? $(,)?) => {
+        #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.")]
+        $(#[doc = concat!("
+", $extra_doc, "
+")])?
+        #[doc = concat!("
+```
+use sysinfo::", stringify!($ty_name), ";
+
+let r = ", stringify!($ty_name), "::new();
+
+let r = r.with_", stringify!($name), "();
+assert_eq!(r.", stringify!($name), "(), true);
+
+let r = r.without_", stringify!($name), "();
+assert_eq!(r.", stringify!($name), "(), false);
+```")]
+        pub fn $name(&self) -> bool {
+            self.$name
+        }
+
+        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `true`.
+
+```
+use sysinfo::", stringify!($ty_name), ";
+
+let r = ", stringify!($ty_name), "::new();
+
+let r = r.with_", stringify!($name), "();
+assert_eq!(r.", stringify!($name), "(), true);
+```")]
+        #[must_use]
+        pub fn $with(mut self) -> Self {
+            self.$name = true;
+            self
+        }
+
+        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `false`.
+
+```
+use sysinfo::", stringify!($ty_name), ";
+
+let r = ", stringify!($ty_name), "::everything();
+assert_eq!(r.", stringify!($name), "(), true);
+
+let r = r.without_", stringify!($name), "();
+assert_eq!(r.", stringify!($name), "(), false);
+```")]
+        #[must_use]
+        pub fn $without(mut self) -> Self {
+            self.$name = false;
+            self
+        }
+    };
+
+    // To handle `UpdateKind`.
+    ($ty_name:ident, $name:ident, $with:ident, $without:ident, UpdateKind $(, $extra_doc:literal)? $(,)?) => {
+        #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.")]
+        $(#[doc = concat!("
+", $extra_doc, "
+")])?
+        #[doc = concat!("
+```
+use sysinfo::{", stringify!($ty_name), ", UpdateKind};
+
+let r = ", stringify!($ty_name), "::new();
+assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
+
+let r = r.with_", stringify!($name), "(UpdateKind::OnlyIfNotSet);
+assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet);
+
+let r = r.without_", stringify!($name), "();
+assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
+```")]
+        pub fn $name(&self) -> UpdateKind {
+            self.$name
+        }
+
+        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind.
+
+```
+use sysinfo::{", stringify!($ty_name), ", UpdateKind};
+
+let r = ", stringify!($ty_name), "::new();
+assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
+
+let r = r.with_", stringify!($name), "(UpdateKind::OnlyIfNotSet);
+assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet);
+```")]
+        #[must_use]
+        pub fn $with(mut self, kind: UpdateKind) -> Self {
+            self.$name = kind;
+            self
+        }
+
+        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `UpdateKind::Never`.
+
+```
+use sysinfo::{", stringify!($ty_name), ", UpdateKind};
+
+let r = ", stringify!($ty_name), "::everything();
+assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet);
+
+let r = r.without_", stringify!($name), "();
+assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
+```")]
+        #[must_use]
+        pub fn $without(mut self) -> Self {
+            self.$name = UpdateKind::Never;
+            self
+        }
+    };
+
+    // To handle `*RefreshKind`.
+    ($ty_name:ident, $name:ident, $with:ident, $without:ident, $typ:ty $(,)?) => {
+        #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.
+
+```
+use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "};
+
+let r = ", stringify!($ty_name), "::new();
+assert_eq!(r.", stringify!($name), "().is_some(), false);
+
+let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything());
+assert_eq!(r.", stringify!($name), "().is_some(), true);
+
+let r = r.without_", stringify!($name), "();
+assert_eq!(r.", stringify!($name), "().is_some(), false);
+```")]
+        pub fn $name(&self) -> Option<$typ> {
+            self.$name
+        }
+
+        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `Some(...)`.
+
+```
+use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "};
+
+let r = ", stringify!($ty_name), "::new();
+assert_eq!(r.", stringify!($name), "().is_some(), false);
+
+let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything());
+assert_eq!(r.", stringify!($name), "().is_some(), true);
+```")]
+        #[must_use]
+        pub fn $with(mut self, kind: $typ) -> Self {
+            self.$name = Some(kind);
+            self
+        }
+
+        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `None`.
+
+```
+use sysinfo::", stringify!($ty_name), ";
+
+let r = ", stringify!($ty_name), "::everything();
+assert_eq!(r.", stringify!($name), "().is_some(), true);
+
+let r = r.without_", stringify!($name), "();
+assert_eq!(r.", stringify!($name), "().is_some(), false);
+```")]
+        #[must_use]
+        pub fn $without(mut self) -> Self {
+            self.$name = None;
+            self
+        }
+    };
+}
+
+pub(crate) use impl_get_set;
diff --git a/src/common/mod.rs b/src/common/mod.rs
index cbbf52131..b59ba48f3 100644
--- a/src/common/mod.rs
+++ b/src/common/mod.rs
@@ -4,6 +4,8 @@
 pub(crate) mod component;
 #[cfg(feature = "disk")]
 pub(crate) mod disk;
+#[cfg(any(feature = "system", feature = "disk"))]
+pub(crate) mod impl_get_set;
 #[cfg(feature = "network")]
 pub(crate) mod network;
 #[cfg(feature = "system")]
diff --git a/src/common/system.rs b/src/common/system.rs
index 71c71e5b1..73fd41078 100644
--- a/src/common/system.rs
+++ b/src/common/system.rs
@@ -6,6 +6,7 @@ use std::fmt;
 use std::path::Path;
 use std::str::FromStr;
 
+use crate::common::impl_get_set::impl_get_set;
 use crate::common::DiskUsage;
 use crate::{CpuInner, Gid, ProcessInner, SystemInner, Uid};
 
@@ -1686,178 +1687,6 @@ cfg_if! {
     }
 }
 
-macro_rules! impl_get_set {
-    ($ty_name:ident, $name:ident, $with:ident, $without:ident $(, $extra_doc:literal)? $(,)?) => {
-        #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.")]
-        $(#[doc = concat!("
-", $extra_doc, "
-")])?
-        #[doc = concat!("
-```
-use sysinfo::", stringify!($ty_name), ";
-
-let r = ", stringify!($ty_name), "::new();
-assert_eq!(r.", stringify!($name), "(), false);
-
-let r = r.with_", stringify!($name), "();
-assert_eq!(r.", stringify!($name), "(), true);
-
-let r = r.without_", stringify!($name), "();
-assert_eq!(r.", stringify!($name), "(), false);
-```")]
-        pub fn $name(&self) -> bool {
-            self.$name
-        }
-
-        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `true`.
-
-```
-use sysinfo::", stringify!($ty_name), ";
-
-let r = ", stringify!($ty_name), "::new();
-assert_eq!(r.", stringify!($name), "(), false);
-
-let r = r.with_", stringify!($name), "();
-assert_eq!(r.", stringify!($name), "(), true);
-```")]
-        #[must_use]
-        pub fn $with(mut self) -> Self {
-            self.$name = true;
-            self
-        }
-
-        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `false`.
-
-```
-use sysinfo::", stringify!($ty_name), ";
-
-let r = ", stringify!($ty_name), "::everything();
-assert_eq!(r.", stringify!($name), "(), true);
-
-let r = r.without_", stringify!($name), "();
-assert_eq!(r.", stringify!($name), "(), false);
-```")]
-        #[must_use]
-        pub fn $without(mut self) -> Self {
-            self.$name = false;
-            self
-        }
-    };
-
-    // To handle `UpdateKind`.
-    ($ty_name:ident, $name:ident, $with:ident, $without:ident, UpdateKind $(, $extra_doc:literal)? $(,)?) => {
-        #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.")]
-        $(#[doc = concat!("
-", $extra_doc, "
-")])?
-        #[doc = concat!("
-```
-use sysinfo::{", stringify!($ty_name), ", UpdateKind};
-
-let r = ", stringify!($ty_name), "::new();
-assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
-
-let r = r.with_", stringify!($name), "(UpdateKind::OnlyIfNotSet);
-assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet);
-
-let r = r.without_", stringify!($name), "();
-assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
-```")]
-        pub fn $name(&self) -> UpdateKind {
-            self.$name
-        }
-
-        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind.
-
-```
-use sysinfo::{", stringify!($ty_name), ", UpdateKind};
-
-let r = ", stringify!($ty_name), "::new();
-assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
-
-let r = r.with_", stringify!($name), "(UpdateKind::OnlyIfNotSet);
-assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet);
-```")]
-        #[must_use]
-        pub fn $with(mut self, kind: UpdateKind) -> Self {
-            self.$name = kind;
-            self
-        }
-
-        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `UpdateKind::Never`.
-
-```
-use sysinfo::{", stringify!($ty_name), ", UpdateKind};
-
-let r = ", stringify!($ty_name), "::everything();
-assert_eq!(r.", stringify!($name), "(), UpdateKind::OnlyIfNotSet);
-
-let r = r.without_", stringify!($name), "();
-assert_eq!(r.", stringify!($name), "(), UpdateKind::Never);
-```")]
-        #[must_use]
-        pub fn $without(mut self) -> Self {
-            self.$name = UpdateKind::Never;
-            self
-        }
-    };
-
-    // To handle `*RefreshKind`.
-    ($ty_name:ident, $name:ident, $with:ident, $without:ident, $typ:ty $(,)?) => {
-        #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.
-
-```
-use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "};
-
-let r = ", stringify!($ty_name), "::new();
-assert_eq!(r.", stringify!($name), "().is_some(), false);
-
-let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything());
-assert_eq!(r.", stringify!($name), "().is_some(), true);
-
-let r = r.without_", stringify!($name), "();
-assert_eq!(r.", stringify!($name), "().is_some(), false);
-```")]
-        pub fn $name(&self) -> Option<$typ> {
-            self.$name
-        }
-
-        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `Some(...)`.
-
-```
-use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "};
-
-let r = ", stringify!($ty_name), "::new();
-assert_eq!(r.", stringify!($name), "().is_some(), false);
-
-let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything());
-assert_eq!(r.", stringify!($name), "().is_some(), true);
-```")]
-        #[must_use]
-        pub fn $with(mut self, kind: $typ) -> Self {
-            self.$name = Some(kind);
-            self
-        }
-
-        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `None`.
-
-```
-use sysinfo::", stringify!($ty_name), ";
-
-let r = ", stringify!($ty_name), "::everything();
-assert_eq!(r.", stringify!($name), "().is_some(), true);
-
-let r = r.without_", stringify!($name), "();
-assert_eq!(r.", stringify!($name), "().is_some(), false);
-```")]
-        #[must_use]
-        pub fn $without(mut self) -> Self {
-            self.$name = None;
-            self
-        }
-    };
-}
-
 /// This enum allows you to specify when you want the related information to be updated.
 ///
 /// For example if you only want the [`Process::exe()`] information to be refreshed only if it's not
diff --git a/src/lib.rs b/src/lib.rs
index 1a1e411c3..c04c3be1a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -71,7 +71,7 @@ cfg_if! {
 #[cfg(feature = "component")]
 pub use crate::common::component::{Component, Components};
 #[cfg(feature = "disk")]
-pub use crate::common::disk::{Disk, DiskKind, Disks};
+pub use crate::common::disk::{Disk, DiskKind, DiskRefreshKind, Disks};
 #[cfg(feature = "network")]
 pub use crate::common::network::{IpNetwork, MacAddr, NetworkData, Networks};
 #[cfg(feature = "system")]
diff --git a/src/unix/apple/disk.rs b/src/unix/apple/disk.rs
index 2ac36ac17..e2f5e28cf 100644
--- a/src/unix/apple/disk.rs
+++ b/src/unix/apple/disk.rs
@@ -7,7 +7,7 @@ use crate::{
     },
     DiskUsage,
 };
-use crate::{Disk, DiskKind};
+use crate::{Disk, DiskKind, DiskRefreshKind};
 
 use core_foundation_sys::array::CFArrayCreate;
 use core_foundation_sys::base::kCFAllocatorDefault;
@@ -72,44 +72,70 @@ impl DiskInner {
         self.is_read_only
     }
 
-    pub(crate) fn refresh(&mut self) -> bool {
-        #[cfg(target_os = "macos")]
-        let Some((read_bytes, written_bytes)) = self
-            .bsd_name
-            .as_ref()
-            .and_then(|name| crate::sys::inner::disk::get_disk_io(name))
-        else {
-            sysinfo_debug!("Failed to update disk i/o stats");
-            return false;
-        };
-        #[cfg(not(target_os = "macos"))]
-        let (read_bytes, written_bytes) = (0, 0);
+    pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) -> bool {
+        if refresh_kind.kind() && self.type_ == DiskKind::Unknown(-1) {
+            let type_ = {
+                #[cfg(target_os = "macos")]
+                {
+                    self.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
+            };
 
-        self.old_read_bytes = self.read_bytes;
-        self.old_written_bytes = self.written_bytes;
-        self.read_bytes = read_bytes;
-        self.written_bytes = written_bytes;
+            self.type_ = type_;
+        }
 
-        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 => {
-                        sysinfo_debug!("Failed to get disk properties");
-                        false
+        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");
+                }
+            }
+        }
+
+        if refresh_kind.details() {
+            unsafe {
+                if let Some(requested_properties) = build_requested_properties(&[
+                    ffi::kCFURLVolumeTotalCapacityKey,
+                    ffi::kCFURLVolumeAvailableCapacityKey,
+                    ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey,
+                ]) {
+                    match get_disk_properties(&self.volume_url, &requested_properties) {
+                        Some(disk_props) => {
+                            self.total_space = get_int_value(
+                                disk_props.inner(),
+                                DictKey::Extern(ffi::kCFURLVolumeTotalCapacityKey),
+                            )
+                            .unwrap_or_default()
+                                as u64;
+                            self.available_space = get_available_volume_space(&disk_props);
+                        }
+                        None => {
+                            sysinfo_debug!("Failed to get disk properties");
+                        }
                     }
+                } else {
+                    sysinfo_debug!("failed to create volume key list, skipping refresh");
                 }
-            } else {
-                sysinfo_debug!("failed to create volume key list, skipping refresh");
-                false
             }
         }
+
+        true
     }
 
     pub(crate) fn usage(&self) -> DiskUsage {
@@ -129,19 +155,19 @@ impl crate::DisksInner {
         }
     }
 
-    pub(crate) fn refresh_list(&mut self) {
+    pub(crate) fn refresh_list_specifics(&mut self, 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);
+                get_list(&mut self.disks, refresh_kind);
             })
         }
     }
 
-    pub(crate) fn refresh(&mut self) {
+    pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) {
         for disk in self.list_mut() {
-            disk.refresh();
+            disk.refresh_specifics(refresh_kind);
         }
     }
 
@@ -154,7 +180,7 @@ impl crate::DisksInner {
     }
 }
 
-unsafe fn get_list(container: &mut Vec<Disk>) {
+unsafe fn get_list(container: &mut Vec<Disk>, refresh_kind: DiskRefreshKind) {
     container.clear();
 
     let raw_disks = {
@@ -252,7 +278,7 @@ unsafe fn get_list(container: &mut Vec<Disk>) {
             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) {
+        if let Some(disk) = new_disk(mount_point, volume_url, c_disk, &prop_dict, refresh_kind) {
             container.push(disk);
         }
     }
@@ -398,6 +424,7 @@ unsafe fn new_disk(
     volume_url: RetainedCFURL,
     c_disk: libc::statfs,
     disk_props: &RetainedCFDictionary,
+    refresh_kind: DiskRefreshKind,
 ) -> Option<Disk> {
     let bsd_name = get_bsd_name(&c_disk);
 
@@ -406,21 +433,33 @@ unsafe fn new_disk(
     // 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.
 
-    #[cfg(target_os = "macos")]
-    let type_ = 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"))]
-    let type_ = DiskKind::SSD;
+    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
+    } else {
+        DiskKind::Unknown(-1)
+    };
 
-    #[cfg(target_os = "macos")]
-    let (read_bytes, written_bytes) = bsd_name
-        .as_ref()
-        .and_then(|name| crate::sys::inner::disk::get_disk_io(name))
-        .unwrap_or_default();
-    #[cfg(not(target_os = "macos"))]
-    let (read_bytes, written_bytes) = (0, 0);
+    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()
+        }
+        #[cfg(not(target_os = "macos"))]
+        (0, 0)
+    } else {
+        (0, 0)
+    };
 
     // Note: Since we requested these properties from the system, we don't expect
     // these property retrievals to fail.
@@ -431,7 +470,7 @@ unsafe fn new_disk(
     )
     .map(OsString::from)?;
 
-    let is_removable = {
+    let is_removable = if refresh_kind.details() {
         let ejectable = get_bool_value(
             disk_props.inner(),
             DictKey::Extern(ffi::kCFURLVolumeIsEjectableKey),
@@ -459,14 +498,25 @@ unsafe fn new_disk(
 
             !internal
         }
+    } else {
+        false
     };
 
-    let total_space = get_int_value(
-        disk_props.inner(),
-        DictKey::Extern(ffi::kCFURLVolumeTotalCapacityKey),
-    )? as u64;
+    let total_space = if refresh_kind.details() {
+        get_int_value(
+            disk_props.inner(),
+            DictKey::Extern(ffi::kCFURLVolumeTotalCapacityKey),
+        )
+        .unwrap_or_default() as u64
+    } else {
+        0
+    };
 
-    let available_space = get_available_volume_space(disk_props);
+    let available_space = if refresh_kind.details() {
+        get_available_volume_space(disk_props)
+    } else {
+        0
+    };
 
     let file_system = {
         let len = c_disk
@@ -482,7 +532,7 @@ unsafe fn new_disk(
         )
     };
 
-    let is_read_only = (c_disk.f_flags & libc::MNT_RDONLY as u32) != 0;
+    let is_read_only = refresh_kind.details() && (c_disk.f_flags & libc::MNT_RDONLY as u32) != 0;
 
     Some(Disk {
         inner: DiskInner {
diff --git a/src/unix/freebsd/disk.rs b/src/unix/freebsd/disk.rs
index dff7cafec..b273ce937 100644
--- a/src/unix/freebsd/disk.rs
+++ b/src/unix/freebsd/disk.rs
@@ -16,7 +16,7 @@ use super::ffi::{
     DEVSTAT_WRITE,
 };
 use super::utils::{c_buf_to_utf8_str, get_sys_value_str_by_name};
-use crate::{Disk, DiskKind, DiskUsage};
+use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage};
 
 #[derive(Debug)]
 pub(crate) struct DiskInner {
@@ -69,8 +69,8 @@ impl DiskInner {
         self.is_read_only
     }
 
-    pub(crate) fn refresh(&mut self) -> bool {
-        refresh_disk(self)
+    pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) -> bool {
+        refresh_disk(self, refresh_kind)
     }
 
     pub(crate) fn usage(&self) -> DiskUsage {
@@ -90,10 +90,8 @@ impl crate::DisksInner {
         }
     }
 
-    pub(crate) fn refresh_list(&mut self) {
-        unsafe {
-            get_all_list(&mut self.disks, true);
-        }
+    pub(crate) fn refresh_list_specifics(&mut self, refresh_kind: DiskRefreshKind) {
+        unsafe { get_all_list(&mut self.disks, true, refresh_kind) }
     }
 
     pub(crate) fn list(&self) -> &[Disk] {
@@ -104,9 +102,9 @@ impl crate::DisksInner {
         &mut self.disks
     }
 
-    pub(crate) fn refresh(&mut self) {
+    pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) {
         unsafe {
-            get_all_list(&mut self.disks, false);
+            get_all_list(&mut self.disks, false, refresh_kind);
         }
     }
 }
@@ -164,19 +162,27 @@ impl GetValues for DiskInner {
     }
 }
 
-fn refresh_disk(disk: &mut DiskInner) -> bool {
-    unsafe {
-        let mut vfs: libc::statvfs = std::mem::zeroed();
-        if libc::statvfs(disk.c_mount_point.as_ptr() as *const _, &mut vfs as *mut _) < 0 {
-            return false;
+fn refresh_disk(disk: &mut DiskInner, refresh_kind: DiskRefreshKind) -> bool {
+    if refresh_kind.details() {
+        unsafe {
+            let mut vfs: libc::statvfs = std::mem::zeroed();
+            if libc::statvfs(disk.c_mount_point.as_ptr() as *const _, &mut vfs as *mut _) < 0 {
+                sysinfo_debug!("statvfs failed");
+            } else {
+                let block_size: u64 = vfs.f_frsize as _;
+                disk.total_space = vfs.f_blocks.saturating_mul(block_size);
+                disk.available_space = vfs.f_favail.saturating_mul(block_size);
+            }
         }
-        let block_size: u64 = vfs.f_frsize as _;
+    }
 
-        disk.total_space = vfs.f_blocks.saturating_mul(block_size);
-        disk.available_space = vfs.f_favail.saturating_mul(block_size);
-        refresh_disk_io(&mut [disk]);
-        true
+    if refresh_kind.io_usage() {
+        unsafe {
+            refresh_disk_io(&mut [disk]);
+        }
     }
+
+    true
 }
 
 unsafe fn initialize_geom() -> Result<(), ()> {
@@ -291,7 +297,11 @@ fn get_disks_mapping() -> HashMap<String, String> {
     disk_mapping
 }
 
-pub unsafe fn get_all_list(container: &mut Vec<Disk>, add_new_disks: bool) {
+pub unsafe fn get_all_list(
+    container: &mut Vec<Disk>,
+    add_new_disks: bool,
+    refresh_kind: DiskRefreshKind,
+) {
     let mut fs_infos: *mut libc::statfs = null_mut();
 
     let count = libc::getmntinfo(&mut fs_infos, libc::MNT_WAIT);
@@ -349,15 +359,21 @@ pub unsafe fn get_all_list(container: &mut Vec<Disk>, add_new_disks: bool) {
             OsString::from(mount_point)
         };
 
-        if libc::statvfs(fs_info.f_mntonname.as_ptr(), &mut vfs) != 0 {
-            continue;
-        }
-
-        let f_frsize: u64 = vfs.f_frsize as _;
-
-        let is_read_only = (vfs.f_flag & libc::ST_RDONLY) != 0;
-        let total_space = vfs.f_blocks.saturating_mul(f_frsize);
-        let available_space = vfs.f_favail.saturating_mul(f_frsize);
+        let (is_read_only, total_space, available_space) = if refresh_kind.details() {
+            if libc::statvfs(fs_info.f_mntonname.as_ptr(), &mut vfs) != 0 {
+                (false, 0, 0)
+            } else {
+                let f_frsize: u64 = vfs.f_frsize as _;
+
+                (
+                    ((vfs.f_flag & libc::ST_RDONLY) != 0),
+                    vfs.f_blocks.saturating_mul(f_frsize),
+                    vfs.f_favail.saturating_mul(f_frsize),
+                )
+            }
+        } else {
+            (false, 0, 0)
+        };
 
         if let Some(disk) = container.iter_mut().find(|d| d.inner.name == name) {
             disk.inner.updated = true;
@@ -367,8 +383,12 @@ pub unsafe fn get_all_list(container: &mut Vec<Disk>, add_new_disks: bool) {
             let dev_mount_point = c_buf_to_utf8_str(&fs_info.f_mntfromname).unwrap_or("");
 
             // USB keys and CDs are removable.
-            let is_removable = [b"USB", b"usb"].iter().any(|b| *b == &fs_type[..])
-                || fs_type.starts_with(b"/dev/cd");
+            let is_removable = if refresh_kind.details() {
+                [b"USB", b"usb"].iter().any(|b| *b == &fs_type[..])
+                    || fs_type.starts_with(b"/dev/cd")
+            } else {
+                false
+            };
 
             container.push(Disk {
                 inner: DiskInner {
@@ -376,8 +396,8 @@ pub unsafe fn get_all_list(container: &mut Vec<Disk>, add_new_disks: bool) {
                     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: vfs.f_blocks.saturating_mul(f_frsize),
-                    available_space: vfs.f_favail.saturating_mul(f_frsize),
+                    total_space,
+                    available_space,
                     file_system: OsString::from_vec(fs_type),
                     is_removable,
                     is_read_only,
@@ -404,7 +424,9 @@ pub unsafe fn get_all_list(container: &mut Vec<Disk>, add_new_disks: bool) {
             c.inner.updated = false;
         }
     }
-    refresh_disk_io(container.as_mut_slice());
+    if refresh_kind.io_usage() {
+        refresh_disk_io(container.as_mut_slice());
+    }
 }
 
 // struct DevInfoWrapper {
diff --git a/src/unix/linux/disk.rs b/src/unix/linux/disk.rs
index 8d1d41fe9..f07b56a96 100644
--- a/src/unix/linux/disk.rs
+++ b/src/unix/linux/disk.rs
@@ -1,7 +1,7 @@
 // Take a look at the license at the top of the repository in the LICENSE file.
 
 use crate::sys::utils::{get_all_utf8_data, to_cpath};
-use crate::{Disk, DiskKind, DiskUsage};
+use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage};
 
 use libc::statvfs;
 use std::collections::HashMap;
@@ -37,7 +37,7 @@ macro_rules! cast {
 pub(crate) struct DiskInner {
     type_: DiskKind,
     device_name: OsString,
-    actual_device_name: String,
+    actual_device_name: Option<String>,
     file_system: OsString,
     mount_point: PathBuf,
     total_space: u64,
@@ -83,39 +83,52 @@ impl DiskInner {
         self.is_read_only
     }
 
-    pub(crate) fn refresh(&mut self) -> bool {
-        self.efficient_refresh(&disk_stats())
+    pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) -> bool {
+        self.efficient_refresh(refresh_kind, &disk_stats(&refresh_kind))
     }
 
-    fn efficient_refresh(&mut self, procfs_disk_stats: &HashMap<String, DiskStat>) -> bool {
-        let Some((read_bytes, written_bytes)) =
-            procfs_disk_stats.get(&self.actual_device_name).map(|stat| {
-                (
-                    stat.sectors_read * SECTOR_SIZE,
-                    stat.sectors_written * SECTOR_SIZE,
-                )
-            })
-        else {
-            sysinfo_debug!("Failed to update disk i/o stats");
-            return false;
-        };
+    fn efficient_refresh(
+        &mut self,
+        refresh_kind: DiskRefreshKind,
+        procfs_disk_stats: &HashMap<String, DiskStat>,
+    ) -> bool {
+        if refresh_kind.kind() && self.type_ == DiskKind::Unknown(-1) {
+            self.type_ = find_type_for_device_name(&self.device_name);
+        }
 
-        self.old_read_bytes = self.read_bytes;
-        self.old_written_bytes = self.written_bytes;
-        self.read_bytes = read_bytes;
-        self.written_bytes = written_bytes;
-
-        unsafe {
-            let mut stat: statvfs = mem::zeroed();
-            let mount_point_cpath = to_cpath(&self.mount_point);
-            if retry_eintr!(statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat)) == 0 {
-                let tmp = cast!(stat.f_bsize).saturating_mul(cast!(stat.f_bavail));
-                self.available_space = cast!(tmp);
-                true
+        if refresh_kind.io_usage() {
+            if self.actual_device_name.is_none() {
+                self.actual_device_name = Some(get_actual_device_name(&self.device_name));
+            }
+            if let Some((read_bytes, written_bytes)) = procfs_disk_stats
+                .get(self.actual_device_name.as_ref().unwrap())
+                .map(|stat| {
+                    (
+                        stat.sectors_read * SECTOR_SIZE,
+                        stat.sectors_written * SECTOR_SIZE,
+                    )
+                })
+            {
+                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 {
-                false
+                sysinfo_debug!("Failed to update disk i/o stats");
+            }
+        }
+
+        if refresh_kind.details() {
+            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;
             }
         }
+
+        true
     }
 
     pub(crate) fn usage(&self) -> DiskUsage {
@@ -135,17 +148,19 @@ impl crate::DisksInner {
         }
     }
 
-    pub(crate) fn refresh_list(&mut self) {
+    pub(crate) fn refresh_list_specifics(&mut self, refresh_kind: DiskRefreshKind) {
         get_all_list(
             &mut self.disks,
             &get_all_utf8_data("/proc/mounts", 16_385).unwrap_or_default(),
+            refresh_kind,
         )
     }
 
-    pub(crate) fn refresh(&mut self) {
-        let procfs_disk_stats = disk_stats();
+    pub(crate) fn refresh_specifics(&mut self, refresh_kind: DiskRefreshKind) {
+        let procfs_disk_stats = disk_stats(&refresh_kind);
         for disk in self.list_mut() {
-            disk.inner.efficient_refresh(&procfs_disk_stats);
+            disk.inner
+                .efficient_refresh(refresh_kind, &procfs_disk_stats);
         }
     }
 
@@ -177,38 +192,53 @@ fn get_actual_device_name(device: &OsStr) -> String {
         .unwrap_or_default()
 }
 
+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 bsize = cast!(stat.f_bsize);
+        let blocks = cast!(stat.f_blocks);
+        let bavail = cast!(stat.f_bavail);
+        let total = bsize.saturating_mul(blocks);
+        if total == 0 {
+            return None;
+        }
+        let available = bsize.saturating_mul(bavail);
+        let is_read_only = (stat.f_flag & libc::ST_RDONLY) != 0;
+
+        Some((total, available, is_read_only))
+    } else {
+        None
+    }
+}
+
 fn new_disk(
     device_name: &OsStr,
     mount_point: &Path,
     file_system: &OsStr,
     removable_entries: &[PathBuf],
     procfs_disk_stats: &HashMap<String, DiskStat>,
-) -> Option<Disk> {
-    let mount_point_cpath = to_cpath(mount_point);
-    let type_ = find_type_for_device_name(device_name);
-    let mut total = 0;
-    let mut available = 0;
-    let mut is_read_only = false;
-    unsafe {
-        let mut stat: statvfs = mem::zeroed();
-        if retry_eintr!(statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat)) == 0 {
-            let bsize = cast!(stat.f_bsize);
-            let blocks = cast!(stat.f_blocks);
-            let bavail = cast!(stat.f_bavail);
-            total = bsize.saturating_mul(blocks);
-            available = bsize.saturating_mul(bavail);
-            is_read_only = (stat.f_flag & libc::ST_RDONLY) != 0;
-        }
-        if total == 0 {
-            return None;
-        }
-        let mount_point = mount_point.to_owned();
-        let is_removable = removable_entries
+    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 = 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| {
@@ -218,24 +248,27 @@ fn new_disk(
                 )
             })
             .unwrap_or_default();
+        (Some(actual_device_name), read_bytes, written_bytes)
+    } else {
+        (None, 0, 0)
+    };
 
-        Some(Disk {
-            inner: DiskInner {
-                type_,
-                device_name: device_name.to_owned(),
-                actual_device_name,
-                file_system: file_system.to_owned(),
-                mount_point,
-                total_space: cast!(total),
-                available_space: cast!(available),
-                is_removable,
-                is_read_only,
-                old_read_bytes: 0,
-                old_written_bytes: 0,
-                read_bytes,
-                written_bytes,
-            },
-        })
+    Disk {
+        inner: DiskInner {
+            type_,
+            device_name: device_name.to_owned(),
+            actual_device_name,
+            file_system: file_system.to_owned(),
+            mount_point: mount_point.to_owned(),
+            total_space,
+            available_space,
+            is_removable,
+            is_read_only,
+            old_read_bytes: 0,
+            old_written_bytes: 0,
+            read_bytes,
+            written_bytes,
+        },
     }
 }
 
@@ -310,7 +343,7 @@ fn find_type_for_device_name(device_name: &OsStr) -> DiskKind {
     }
 }
 
-fn get_all_list(container: &mut Vec<Disk>, content: &str) {
+fn get_all_list(container: &mut Vec<Disk>, content: &str, refresh_kind: DiskRefreshKind) {
     container.clear();
     // The goal of this array is to list all removable devices (the ones whose name starts with
     // "usb-").
@@ -331,7 +364,7 @@ fn get_all_list(container: &mut Vec<Disk>, content: &str) {
         _ => Vec::new(),
     };
 
-    let procfs_disk_stats = disk_stats();
+    let procfs_disk_stats = disk_stats(&refresh_kind);
 
     for disk in content
         .lines()
@@ -378,13 +411,14 @@ fn get_all_list(container: &mut Vec<Disk>, content: &str) {
                (fs_file.starts_with("/run") && !fs_file.starts_with("/run/media")) ||
                fs_spec.starts_with("sunrpc"))
         })
-        .filter_map(|(fs_spec, fs_file, fs_vfstype)| {
+        .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,
             )
         })
     {
@@ -444,14 +478,18 @@ impl DiskStat {
     }
 }
 
-fn disk_stats() -> HashMap<String, DiskStat> {
-    let path = "/proc/diskstats";
-    match fs::read_to_string(path) {
-        Ok(content) => disk_stats_inner(&content),
-        Err(_error) => {
-            sysinfo_debug!("failed to read {path:?}: {_error:?}");
-            HashMap::new()
+fn disk_stats(refresh_kind: &DiskRefreshKind) -> HashMap<String, DiskStat> {
+    if refresh_kind.io_usage() {
+        let path = "/proc/diskstats";
+        match fs::read_to_string(path) {
+            Ok(content) => disk_stats_inner(&content),
+            Err(_error) => {
+                sysinfo_debug!("failed to read {path:?}: {_error:?}");
+                HashMap::new()
+            }
         }
+    } else {
+        Default::default()
     }
 }
 
diff --git a/src/unknown/disk.rs b/src/unknown/disk.rs
index 7ab253b47..7a4b97d54 100644
--- a/src/unknown/disk.rs
+++ b/src/unknown/disk.rs
@@ -1,6 +1,6 @@
 // Take a look at the license at the top of the repository in the LICENSE file.
 
-use crate::{Disk, DiskKind, DiskUsage};
+use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage};
 
 use std::{ffi::OsStr, path::Path};
 
@@ -39,7 +39,7 @@ impl DiskInner {
         false
     }
 
-    pub(crate) fn refresh(&mut self) -> bool {
+    pub(crate) fn refresh_specifics(&mut self, _refreshes: DiskRefreshKind) -> bool {
         true
     }
 
@@ -65,11 +65,11 @@ impl DisksInner {
         self.disks
     }
 
-    pub(crate) fn refresh_list(&mut self) {
+    pub(crate) fn refresh_list_specifics(&mut self, _refreshes: DiskRefreshKind) {
         // Does nothing.
     }
 
-    pub(crate) fn refresh(&mut self) {
+    pub(crate) fn refresh_specifics(&mut self, _refreshes: DiskRefreshKind) {
         // Does nothing.
     }
 
diff --git a/src/windows/disk.rs b/src/windows/disk.rs
index 5d635c396..f9efc6dfb 100644
--- a/src/windows/disk.rs
+++ b/src/windows/disk.rs
@@ -1,7 +1,7 @@
 // Take a look at the license at the top of the repository in the LICENSE file.
 
 use crate::sys::utils::HandleWrapper;
-use crate::{Disk, DiskKind, DiskUsage};
+use crate::{Disk, DiskKind, DiskRefreshKind, DiskUsage};
 
 use std::ffi::{c_void, OsStr, OsString};
 use std::mem::size_of;
@@ -167,25 +167,28 @@ impl DiskInner {
         self.is_read_only
     }
 
-    pub(crate) fn refresh(&mut self) -> bool {
-        let Some((read_bytes, written_bytes)) = get_disk_io(&self.device_path, None) else {
-            sysinfo_debug!("Failed to update disk i/o stats");
-            return false;
-        };
+    pub(crate) fn refresh_specifics(&mut self, refreshes: DiskRefreshKind) -> bool {
+        if refreshes.kind() && self.type_ == DiskKind::Unknown(-1) {
+            self.type_ = get_disk_kind(&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;
-
-        if self.total_space != 0 {
-            unsafe {
-                let mut tmp = 0;
-                let lpdirectoryname = PCWSTR::from_raw(self.mount_point.as_ptr());
-                if GetDiskFreeSpaceExW(lpdirectoryname, None, None, Some(&mut tmp)).is_ok() {
-                    self.available_space = tmp;
-                    return true;
-                }
+        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.details() {
+            if let Some((total_space, available_space)) =
+                unsafe { get_drive_size(&self.mount_point) }
+            {
+                self.total_space = total_space;
+                self.available_space = available_space;
             }
         }
         false
@@ -220,15 +223,15 @@ impl DisksInner {
         self.disks
     }
 
-    pub(crate) fn refresh_list(&mut self) {
+    pub(crate) fn refresh_list_specifics(&mut self, refreshes: DiskRefreshKind) {
         unsafe {
-            self.disks = get_list();
+            self.disks = get_list(refreshes);
         }
     }
 
-    pub(crate) fn refresh(&mut self) {
+    pub(crate) fn refresh_specifics(&mut self, refreshes: DiskRefreshKind) {
         for disk in self.list_mut() {
-            disk.refresh();
+            disk.refresh_specifics(refreshes);
         }
     }
 
@@ -259,7 +262,7 @@ unsafe fn get_drive_size(mount_point: &[u16]) -> Option<(u64, u64)> {
     }
 }
 
-pub(crate) unsafe fn get_list() -> Vec<Disk> {
+pub(crate) unsafe fn get_list(refreshes: DiskRefreshKind) -> Vec<Disk> {
     #[cfg(feature = "multithread")]
     use rayon::iter::ParallelIterator;
 
@@ -268,7 +271,7 @@ pub(crate) unsafe fn get_list() -> Vec<Disk> {
             let raw_volume_name = PCWSTR::from_raw(volume_name.as_ptr());
             let drive_type = GetDriveTypeW(raw_volume_name);
 
-            let is_removable = drive_type == DRIVE_REMOVABLE;
+            let is_removable = refreshes.details() && drive_type == DRIVE_REMOVABLE;
 
             if drive_type != DRIVE_FIXED && drive_type != DRIVE_REMOVABLE {
                 return Vec::new();
@@ -292,7 +295,7 @@ pub(crate) unsafe fn get_list() -> Vec<Disk> {
                 );
                 return Vec::new();
             }
-            let is_read_only = (flags & FILE_READ_ONLY_VOLUME) != 0;
+            let is_read_only = refreshes.details() && (flags & FILE_READ_ONLY_VOLUME) != 0;
 
             let mount_paths = get_volume_path_names_for_volume_name(&volume_name[..]);
             if mount_paths.is_empty() {
@@ -305,51 +308,25 @@ pub(crate) unsafe fn get_list() -> Vec<Disk> {
                 .copied()
                 .chain([0])
                 .collect::<Vec<_>>();
-            let Some(handle) = HandleWrapper::new_from_file(&device_path[..], Default::default())
-            else {
-                return Vec::new();
-            };
-            let Some((total_space, available_space)) = get_drive_size(&mount_paths[0][..]) else {
-                return Vec::new();
-            };
-            if total_space == 0 {
-                sysinfo_debug!("total_space == 0");
-                return Vec::new();
-            }
-            let spq_trim = STORAGE_PROPERTY_QUERY {
-                PropertyId: StorageDeviceSeekPenaltyProperty,
-                QueryType: PropertyStandardQuery,
-                AdditionalParameters: [0],
+            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 mut result: DEVICE_SEEK_PENALTY_DESCRIPTOR = std::mem::zeroed();
-
-            let mut dw_size = 0;
-            let device_io_control = DeviceIoControl(
-                handle.0,
-                IOCTL_STORAGE_QUERY_PROPERTY,
-                Some(&spq_trim as *const STORAGE_PROPERTY_QUERY as *const c_void),
-                size_of::<STORAGE_PROPERTY_QUERY>() as u32,
-                Some(&mut result as *mut DEVICE_SEEK_PENALTY_DESCRIPTOR as *mut c_void),
-                size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as u32,
-                Some(&mut dw_size),
-                None,
-            )
-            .is_ok();
-            let type_ = if !device_io_control
-                || dw_size != size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as u32
-            {
-                DiskKind::Unknown(-1)
+
+            let type_ = if refreshes.kind() {
+                get_disk_kind(&device_path, handle.as_ref())
             } else {
-                let is_hdd = result.IncursSeekPenalty.as_bool();
-                if is_hdd {
-                    DiskKind::HDD
-                } else {
-                    DiskKind::SSD
-                }
+                DiskKind::Unknown(-1)
             };
 
-            let (read_bytes, written_bytes) =
-                get_disk_io(&device_path, Some(handle)).unwrap_or_default();
+            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);
@@ -383,6 +360,63 @@ fn os_string_from_zero_terminated(name: &[u16]) -> OsString {
     OsString::from_wide(&name[..len])
 }
 
+fn get_disk_kind(device_path: &[u16], borrowed_handle: Option<&HandleWrapper>) -> DiskKind {
+    let binding = (
+        borrowed_handle,
+        if borrowed_handle.is_none() {
+            unsafe { HandleWrapper::new_from_file(device_path, Default::default()) }
+        } else {
+            None
+        },
+    );
+    let handle = match binding {
+        (Some(handle), _) => handle,
+        (_, Some(ref handle)) => handle,
+        (None, None) => return DiskKind::Unknown(-1),
+    };
+
+    if handle.is_invalid() {
+        sysinfo_debug!(
+            "Expected handle to {:?} to be valid",
+            String::from_utf16_lossy(device_path)
+        );
+        return DiskKind::Unknown(-1);
+    }
+
+    let spq_trim = STORAGE_PROPERTY_QUERY {
+        PropertyId: StorageDeviceSeekPenaltyProperty,
+        QueryType: PropertyStandardQuery,
+        AdditionalParameters: [0],
+    };
+    let mut result: DEVICE_SEEK_PENALTY_DESCRIPTOR = unsafe { std::mem::zeroed() };
+
+    let mut dw_size = 0;
+    let device_io_control = unsafe {
+        DeviceIoControl(
+            handle.0,
+            IOCTL_STORAGE_QUERY_PROPERTY,
+            Some(&spq_trim as *const STORAGE_PROPERTY_QUERY as *const c_void),
+            size_of::<STORAGE_PROPERTY_QUERY>() as u32,
+            Some(&mut result as *mut DEVICE_SEEK_PENALTY_DESCRIPTOR as *mut c_void),
+            size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as u32,
+            Some(&mut dw_size),
+            None,
+        )
+        .is_ok()
+    };
+
+    if !device_io_control || dw_size != size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as u32 {
+        DiskKind::Unknown(-1)
+    } else {
+        let is_hdd = result.IncursSeekPenalty.as_bool();
+        if is_hdd {
+            DiskKind::HDD
+        } else {
+            DiskKind::SSD
+        }
+    }
+}
+
 /// 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<HandleWrapper>) -> Option<(u64, u64)> {
     let handle =
@@ -390,7 +424,7 @@ fn get_disk_io(device_path: &[u16], handle: Option<HandleWrapper>) -> Option<(u6
 
     if handle.is_invalid() {
         sysinfo_debug!(
-            "Expected handle to '{:?}' to be valid",
+            "Expected handle to {:?} to be valid",
             String::from_utf16_lossy(device_path)
         );
         return None;


Test patch:
diff --git a/tests/disk.rs b/tests/disk.rs
index 4210fcac7..2fbab2ba3 100644
--- a/tests/disk.rs
+++ b/tests/disk.rs
@@ -1,34 +1,158 @@
 // Take a look at the license at the top of the repository in the LICENSE file.
 
+#[cfg(all(feature = "system", feature = "disk"))]
+fn should_skip() -> bool {
+    if !sysinfo::IS_SUPPORTED_SYSTEM {
+        return true;
+    }
+
+    let s = sysinfo::System::new_all();
+
+    // If we don't have any physical core present, it's very likely that we're inside a VM...
+    s.physical_core_count().unwrap_or_default() == 0
+}
+
 #[test]
 #[cfg(all(feature = "system", feature = "disk"))]
 fn test_disks() {
-    if sysinfo::IS_SUPPORTED_SYSTEM {
-        let s = sysinfo::System::new_all();
-        // If we don't have any physical core present, it's very likely that we're inside a VM...
-        if s.physical_core_count().unwrap_or_default() > 0 {
-            let mut disks = sysinfo::Disks::new();
-            assert!(disks.list().is_empty());
-            disks.refresh_list();
-            assert!(!disks.list().is_empty());
+    if should_skip() {
+        return;
+    }
+
+    let mut disks = sysinfo::Disks::new();
+    assert!(disks.list().is_empty());
+    disks.refresh_list();
+    assert!(!disks.list().is_empty());
+}
+
+#[test]
+#[cfg(all(feature = "system", feature = "disk"))]
+fn test_disk_refresh_kind() {
+    use itertools::Itertools;
+
+    use sysinfo::{DiskKind, DiskRefreshKind, Disks};
+
+    if should_skip() {
+        return;
+    }
+
+    for fs in [
+        DiskRefreshKind::with_kind,
+        DiskRefreshKind::without_kind,
+        DiskRefreshKind::with_details,
+        DiskRefreshKind::without_details,
+        DiskRefreshKind::with_io_usage,
+        DiskRefreshKind::without_io_usage,
+    ]
+    .iter()
+    .powerset()
+    {
+        let mut refreshes = DiskRefreshKind::new();
+        for f in fs {
+            refreshes = f(refreshes);
         }
+
+        let assertions = |name: &'static str, disks: &Disks| {
+            if refreshes.kind() {
+                // This would ideally assert that *all* are refreshed, but we settle for a weaker
+                // assertion because failures can't be distinguished from "not refreshed" values.
+                #[cfg(not(any(target_os = "freebsd", target_os = "windows")))]
+                assert!(
+                    disks
+                        .iter()
+                        .any(|disk| disk.kind() != DiskKind::Unknown(-1)),
+                    "{name}: disk.kind should be refreshed"
+                );
+            } else {
+                assert!(
+                    disks
+                        .iter()
+                        .all(|disk| disk.kind() == DiskKind::Unknown(-1)),
+                    "{name}: disk.kind should not be refreshed"
+                );
+            }
+
+            if refreshes.details() {
+                // 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!(
+                    disks
+                        .iter()
+                        .any(|disk| disk.available_space() != Default::default()),
+                    "{name}: disk.available_space should be refreshed"
+                );
+                assert!(
+                    disks
+                        .iter()
+                        .any(|disk| disk.total_space() != Default::default()),
+                    "{name}: disk.total_space should be refreshed"
+                );
+                // We can't assert anything about booleans, since false is indistinguishable from
+                // not-refreshed
+            } else {
+                assert!(
+                    disks
+                        .iter()
+                        .all(|disk| disk.available_space() == Default::default()),
+                    "{name}: disk.available_space should not be refreshed"
+                );
+                assert!(
+                    disks
+                        .iter()
+                        .all(|disk| disk.total_space() == Default::default()),
+                    "{name}: disk.total_space should not be refreshed"
+                );
+                assert!(
+                    disks
+                        .iter()
+                        .all(|disk| disk.is_read_only() == <bool as Default>::default()),
+                    "{name}: disk.is_read_only should not be refreshed"
+                );
+                assert!(
+                    disks
+                        .iter()
+                        .all(|disk| disk.is_removable() == <bool as Default>::default()),
+                    "{name}: disk.is_removable should not be refreshed"
+                );
+            }
+
+            if refreshes.io_usage() {
+                // This 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!(
+                    disks.iter().any(|disk| disk.usage() != Default::default()),
+                    "{name}: disk.usage should be refreshed"
+                );
+            } else {
+                assert!(
+                    disks.iter().all(|disk| disk.usage() == Default::default()),
+                    "{name}: disk.usage should not be refreshed"
+                );
+            }
+        };
+
+        // load and refresh with the desired details should work
+        let disks = Disks::new_with_refreshed_list_specifics(refreshes);
+        assertions("full", &disks);
+
+        // load with minimal `DiskRefreshKind`, then refresh for added detail should also work!
+        let mut disks = Disks::new_with_refreshed_list_specifics(DiskRefreshKind::new());
+        disks.refresh_specifics(refreshes);
+        assertions("incremental", &disks);
     }
 }
 
 #[test]
-#[cfg(feature = "disk")]
+#[cfg(all(feature = "system", feature = "disk"))]
 fn test_disks_usage() {
     use std::fs::{remove_file, File};
     use std::io::Write;
     use std::path::{Path, PathBuf};
     use std::thread::sleep;
 
-    use sysinfo::{CpuRefreshKind, Disks, RefreshKind, System};
-
-    let s = System::new_with_specifics(RefreshKind::new().with_cpu(CpuRefreshKind::new()));
+    use sysinfo::Disks;
 
-    // Skip the tests on unsupported platforms and on systems with no physical cores (likely a VM)
-    if !sysinfo::IS_SUPPORTED_SYSTEM || s.physical_core_count().unwrap_or_default() == 0 {
+    if should_skip() {
         return;
     }