Repository: GuillaumeGomez/sysinfo
Pull request #1159 (2023-11-29 00:22:40)

Add `UpdateKind` enum
Fixes https://github.com/GuillaumeGomez/sysinfo/issues/840.


Patch:
diff --git a/src/common.rs b/src/common.rs
index be3359d..f86de6f 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -1342,6 +1342,65 @@ assert_eq!(r.", stringify!($name), "(), false);
         }
     };
 
+    // 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.
 
@@ -1361,7 +1420,7 @@ assert_eq!(r.", stringify!($name), "().is_some(), false);
             self.$name
         }
 
-        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `true`.
+        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `Some(...)`.
 
 ```
 use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "};
@@ -1378,7 +1437,7 @@ assert_eq!(r.", stringify!($name), "().is_some(), true);
             self
         }
 
-        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `false`.
+        #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `None`.
 
 ```
 use sysinfo::", stringify!($ty_name), ";
@@ -1397,6 +1456,42 @@ assert_eq!(r.", stringify!($name), "().is_some(), false);
     };
 }
 
+/// 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
+/// already set:
+///
+/// ```no_run
+/// use sysinfo::{ProcessRefreshKind, System, UpdateKind};
+///
+/// let mut system = System::new();
+/// system.refresh_processes_specifics(
+///     ProcessRefreshKind::new().with_exe(UpdateKind::OnlyIfNotSet),
+/// );
+/// ```
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub enum UpdateKind {
+    /// Never update the related information.
+    #[default]
+    Never,
+    /// Always update the related information.
+    Always,
+    /// Only update the related information if it was not already set at least once.
+    OnlyIfNotSet,
+}
+
+impl UpdateKind {
+    /// If `self` is `OnlyIfNotSet`, `f` is called and its returned value is returned.
+    #[allow(dead_code)] // Needed for unsupported targets.
+    pub(crate) fn needs_update(self, f: impl Fn() -> bool) -> bool {
+        match self {
+            Self::Never => false,
+            Self::Always => true,
+            Self::OnlyIfNotSet => f(),
+        }
+    }
+}
+
 /// Used to determine what you want to refresh specifically on the [`Process`] type.
 ///
 /// When all refresh are ruled out, a [`Process`] will still retrieve the following information:
@@ -1428,51 +1523,52 @@ assert_eq!(r.", stringify!($name), "().is_some(), false);
 pub struct ProcessRefreshKind {
     cpu: bool,
     disk_usage: bool,
-    user: bool,
     memory: bool,
-    cwd: bool,
-    root: bool,
-    environ: bool,
-    cmd: bool,
-    exe: bool,
+    user: UpdateKind,
+    cwd: UpdateKind,
+    root: UpdateKind,
+    environ: UpdateKind,
+    cmd: UpdateKind,
+    exe: UpdateKind,
 }
 
 impl ProcessRefreshKind {
     /// Creates a new `ProcessRefreshKind` with every refresh set to `false`.
     ///
     /// ```
-    /// use sysinfo::ProcessRefreshKind;
+    /// use sysinfo::{ProcessRefreshKind, UpdateKind};
     ///
     /// let r = ProcessRefreshKind::new();
     ///
     /// assert_eq!(r.cpu(), false);
-    /// assert_eq!(r.disk_usage(), false);
+    /// assert_eq!(r.user(), UpdateKind::Never);
     /// ```
     pub fn new() -> Self {
         Self::default()
     }
 
-    /// Creates a new `ProcessRefreshKind` with every refresh set to `true`.
+    /// Creates a new `ProcessRefreshKind` with every refresh set to `true` or
+    /// `UpdateKind::OnlyIfNotSet`.
     ///
     /// ```
-    /// use sysinfo::ProcessRefreshKind;
+    /// use sysinfo::{ProcessRefreshKind, UpdateKind};
     ///
     /// let r = ProcessRefreshKind::everything();
     ///
     /// assert_eq!(r.cpu(), true);
-    /// assert_eq!(r.disk_usage(), true);
+    /// assert_eq!(r.user(), UpdateKind::OnlyIfNotSet);
     /// ```
     pub fn everything() -> Self {
         Self {
             cpu: true,
             disk_usage: true,
-            user: true,
             memory: true,
-            cwd: true,
-            root: true,
-            environ: true,
-            cmd: true,
-            exe: true,
+            user: UpdateKind::OnlyIfNotSet,
+            cwd: UpdateKind::OnlyIfNotSet,
+            root: UpdateKind::OnlyIfNotSet,
+            environ: UpdateKind::OnlyIfNotSet,
+            cmd: UpdateKind::OnlyIfNotSet,
+            exe: UpdateKind::OnlyIfNotSet,
         }
     }
 
@@ -1488,6 +1584,7 @@ impl ProcessRefreshKind {
         user,
         with_user,
         without_user,
+        UpdateKind,
         "\
 It will retrieve the following information:
 
@@ -1497,11 +1594,23 @@ It will retrieve the following information:
  * user effective ID (if available on the platform)"
     );
     impl_get_set!(ProcessRefreshKind, memory, with_memory, without_memory);
-    impl_get_set!(ProcessRefreshKind, cwd, with_cwd, without_cwd);
-    impl_get_set!(ProcessRefreshKind, root, with_root, without_root);
-    impl_get_set!(ProcessRefreshKind, environ, with_environ, without_environ);
-    impl_get_set!(ProcessRefreshKind, cmd, with_cmd, without_cmd);
-    impl_get_set!(ProcessRefreshKind, exe, with_exe, without_exe);
+    impl_get_set!(ProcessRefreshKind, cwd, with_cwd, without_cwd, UpdateKind);
+    impl_get_set!(
+        ProcessRefreshKind,
+        root,
+        with_root,
+        without_root,
+        UpdateKind
+    );
+    impl_get_set!(
+        ProcessRefreshKind,
+        environ,
+        with_environ,
+        without_environ,
+        UpdateKind
+    );
+    impl_get_set!(ProcessRefreshKind, cmd, with_cmd, without_cmd, UpdateKind);
+    impl_get_set!(ProcessRefreshKind, exe, with_exe, without_exe, UpdateKind);
 }
 
 /// Used to determine what you want to refresh specifically on the [`Cpu`] type.
diff --git a/src/lib.rs b/src/lib.rs
index f5e253f..0ba4218 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -53,7 +53,7 @@ cfg_if::cfg_if! {
 pub use crate::common::{
     get_current_pid, CGroupLimits, Component, Components, Cpu, CpuRefreshKind, Disk, DiskKind,
     DiskUsage, Disks, Gid, Group, LoadAvg, MacAddr, NetworkData, Networks, Pid, Process,
-    ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, System, Uid, User, Users,
+    ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, System, Uid, UpdateKind, User, Users,
 };
 
 pub(crate) use crate::sys::{
@@ -327,7 +327,8 @@ mod test {
 
             // And now check that our `get_user_by_id` method works.
             let s = System::new_with_specifics(
-                RefreshKind::new().with_processes(ProcessRefreshKind::new().with_user()),
+                RefreshKind::new()
+                    .with_processes(ProcessRefreshKind::new().with_user(UpdateKind::Always)),
             );
             assert!(s
                 .processes()
@@ -343,7 +344,8 @@ mod test {
         // If `getent` doesn't find them, we can assume it's a dark secret from the linux land.
         if IS_SUPPORTED && cfg!(not(target_os = "linux")) {
             let s = System::new_with_specifics(
-                RefreshKind::new().with_processes(ProcessRefreshKind::new().with_user()),
+                RefreshKind::new()
+                    .with_processes(ProcessRefreshKind::new().with_user(UpdateKind::Always)),
             );
             let users = Users::new_with_refreshed_list();
 
diff --git a/src/unix/apple/macos/process.rs b/src/unix/apple/macos/process.rs
index f7fe1df..b1135af 100644
--- a/src/unix/apple/macos/process.rs
+++ b/src/unix/apple/macos/process.rs
@@ -388,7 +388,10 @@ unsafe fn get_exe_and_name_backup(
     process: &mut ProcessInner,
     refresh_kind: ProcessRefreshKind,
 ) -> bool {
-    if !refresh_kind.exe() && !process.name.is_empty() {
+    let exe_needs_update = refresh_kind
+        .exe()
+        .needs_update(|| process.exe.as_os_str().is_empty());
+    if !process.name.is_empty() && !exe_needs_update {
         return false;
     }
     let mut buffer: Vec<u8> = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _);
@@ -408,7 +411,7 @@ unsafe fn get_exe_and_name_backup(
                     .unwrap_or("")
                     .to_owned();
             }
-            if refresh_kind.exe() {
+            if exe_needs_update {
                 process.exe = exe;
             }
             true
@@ -429,7 +432,13 @@ unsafe fn convert_node_path_info(node: &libc::vnode_info_path) -> Option<PathBuf
 }
 
 unsafe fn get_cwd_root(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) {
-    if !refresh_kind.cwd() && !refresh_kind.root() {
+    let cwd_needs_update = refresh_kind
+        .cwd()
+        .needs_update(|| process.cwd.as_os_str().is_empty());
+    let root_needs_update = refresh_kind
+        .root()
+        .needs_update(|| process.root.as_os_str().is_empty());
+    if !cwd_needs_update && !root_needs_update {
         return;
     }
     let mut vnodepathinfo = mem::zeroed::<libc::proc_vnodepathinfo>();
@@ -444,12 +453,12 @@ unsafe fn get_cwd_root(process: &mut ProcessInner, refresh_kind: ProcessRefreshK
         sysinfo_debug!("Failed to retrieve cwd and root for {}", process.pid.0);
         return;
     }
-    if refresh_kind.cwd() {
+    if cwd_needs_update {
         if let Some(cwd) = convert_node_path_info(&vnodepathinfo.pvi_cdir) {
             process.cwd = cwd;
         }
     }
-    if refresh_kind.root() {
+    if root_needs_update {
         if let Some(root) = convert_node_path_info(&vnodepathinfo.pvi_rdir) {
             process.root = root;
         }
@@ -548,16 +557,23 @@ unsafe fn get_process_infos(process: &mut ProcessInner, refresh_kind: ProcessRef
             .to_owned();
     }
 
-    if refresh_kind.exe() {
+    if refresh_kind
+        .exe()
+        .needs_update(|| process.exe.as_os_str().is_empty())
+    {
         process.exe = exe;
     }
 
-    if !refresh_kind.environ() && !refresh_kind.cmd() {
+    let environ_needs_update = refresh_kind
+        .environ()
+        .needs_update(|| process.environ.is_empty());
+    let cmd_needs_update = refresh_kind.cmd().needs_update(|| process.cmd.is_empty());
+    if !environ_needs_update && !cmd_needs_update {
         // Nothing else to be done!
         return true;
     }
-    let proc_args = get_arguments(&mut process.cmd, proc_args, n_args, refresh_kind.cmd());
-    if refresh_kind.environ() {
+    let proc_args = get_arguments(&mut process.cmd, proc_args, n_args, cmd_needs_update);
+    if environ_needs_update {
         get_environ(&mut process.environ, proc_args);
     }
     true
diff --git a/src/unix/freebsd/process.rs b/src/unix/freebsd/process.rs
index 42289dc..6617f7e 100644
--- a/src/unix/freebsd/process.rs
+++ b/src/unix/freebsd/process.rs
@@ -298,7 +298,10 @@ pub(crate) unsafe fn get_process_data(
 }
 
 pub(crate) unsafe fn get_exe(exe: &mut PathBuf, pid: crate::Pid, refresh_kind: ProcessRefreshKind) {
-    if refresh_kind.exe() && exe.as_os_str().is_empty() {
+    if refresh_kind
+        .exe()
+        .needs_update(|| exe.as_os_str().is_empty())
+    {
         let mut buffer = [0; libc::PATH_MAX as usize + 1];
 
         *exe = get_sys_value_str(
diff --git a/src/unix/freebsd/system.rs b/src/unix/freebsd/system.rs
index 373fb13..3cc5d2d 100644
--- a/src/unix/freebsd/system.rs
+++ b/src/unix/freebsd/system.rs
@@ -318,7 +318,10 @@ unsafe fn add_missing_proc_info(
     {
         let kd = system_info.kd.as_ptr();
         let proc_inner = &mut proc_.inner;
-        if refresh_kind.cmd() || proc_inner.name.is_empty() {
+        let cmd_needs_update = refresh_kind
+            .cmd()
+            .needs_update(|| proc_inner.cmd.is_empty());
+        if proc_inner.name.is_empty() || cmd_needs_update {
             let cmd = from_cstr_array(libc::kvm_getargv(kd, kproc, 0) as _);
 
             if !cmd.is_empty() {
@@ -327,10 +330,10 @@ unsafe fn add_missing_proc_info(
                 if let Some(name) = p.file_name().and_then(|s| s.to_str()) {
                     proc_inner.name = name.to_owned();
                 }
-            }
 
-            if refresh_kind.cmd() {
-                proc_inner.cmd = cmd;
+                if cmd_needs_update {
+                    proc_inner.cmd = cmd;
+                }
             }
         }
         get_exe(&mut proc_inner.exe, proc_inner.pid, refresh_kind);
@@ -341,7 +344,10 @@ unsafe fn add_missing_proc_info(
             // possible.
             proc_inner.name = c_buf_to_string(&kproc.ki_comm).unwrap_or_default();
         }
-        if refresh_kind.environ() {
+        if refresh_kind
+            .environ()
+            .needs_update(|| proc_inner.environ.is_empty())
+        {
             proc_inner.environ = from_cstr_array(libc::kvm_getenvv(kd, kproc, 0) as _);
         }
     }
@@ -593,10 +599,16 @@ impl SystemInfo {
         refresh_kind: ProcessRefreshKind,
     ) {
         let mut done = 0;
-        if refresh_kind.cwd() {
+        let cwd_needs_update = refresh_kind
+            .cwd()
+            .needs_update(|| proc_.cwd().as_os_str().is_empty());
+        let root_needs_update = refresh_kind
+            .root()
+            .needs_update(|| proc_.root().as_os_str().is_empty());
+        if cwd_needs_update {
             done += 1;
         }
-        if refresh_kind.root() {
+        if root_needs_update {
             done += 1;
         }
         if done == 0 {
@@ -618,14 +630,14 @@ impl SystemInfo {
             {
                 let tmp = &*entry;
                 if tmp.fs_uflags & libc::PS_FST_UFLAG_CDIR != 0 {
-                    if refresh_kind.cwd() && !tmp.fs_path.is_null() {
+                    if cwd_needs_update && !tmp.fs_path.is_null() {
                         if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() {
                             proc_.cwd = PathBuf::from(p);
                             done -= 1;
                         }
                     }
                 } else if tmp.fs_uflags & libc::PS_FST_UFLAG_RDIR != 0 {
-                    if refresh_kind.root() && !tmp.fs_path.is_null() {
+                    if root_needs_update && !tmp.fs_path.is_null() {
                         if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() {
                             proc_.root = PathBuf::from(p);
                             done -= 1;
diff --git a/src/unix/linux/process.rs b/src/unix/linux/process.rs
index 0ae4de8..2f5fd50 100644
--- a/src/unix/linux/process.rs
+++ b/src/unix/linux/process.rs
@@ -345,7 +345,15 @@ fn get_status(p: &mut ProcessInner, part: &str) {
         .unwrap_or_else(|| ProcessStatus::Unknown(0));
 }
 
-fn refresh_user_group_ids(p: &mut ProcessInner, path: &mut PathHandler) {
+fn refresh_user_group_ids(
+    p: &mut ProcessInner,
+    path: &mut PathHandler,
+    refresh_kind: ProcessRefreshKind,
+) {
+    if !refresh_kind.user().needs_update(|| p.user_id.is_none()) {
+        return;
+    }
+
     if let Some(((user_id, effective_user_id), (group_id, effective_group_id))) =
         get_uid_and_gid(path.join("status"))
     {
@@ -368,12 +376,12 @@ fn update_proc_info(
     info: &SystemInfo,
 ) {
     get_status(p, parts[2]);
+    refresh_user_group_ids(p, proc_path, refresh_kind);
 
-    if refresh_kind.user() && p.user_id.is_none() {
-        refresh_user_group_ids(p, proc_path);
-    }
-
-    if refresh_kind.exe() && p.exe.as_os_str().is_empty() {
+    if refresh_kind
+        .exe()
+        .needs_update(|| p.exe.as_os_str().is_empty())
+    {
         match proc_path.join("exe").read_link() {
             Ok(exe_path) => p.exe = exe_path,
             Err(_error) => {
@@ -385,16 +393,22 @@ fn update_proc_info(
         }
     }
 
-    if refresh_kind.cmd() && p.cmd.is_empty() {
+    if refresh_kind.cmd().needs_update(|| p.cmd.is_empty()) {
         p.cmd = copy_from_file(proc_path.join("cmdline"));
     }
-    if refresh_kind.environ() {
+    if refresh_kind.environ().needs_update(|| p.environ.is_empty()) {
         p.environ = copy_from_file(proc_path.join("environ"));
     }
-    if refresh_kind.cwd() {
+    if refresh_kind
+        .cwd()
+        .needs_update(|| p.cwd.as_os_str().is_empty())
+    {
         p.cwd = realpath(proc_path.join("cwd"));
     }
-    if refresh_kind.root() {
+    if refresh_kind
+        .root()
+        .needs_update(|| p.root.as_os_str().is_empty())
+    {
         p.root = realpath(proc_path.join("root"));
     }
 
@@ -513,9 +527,7 @@ pub(crate) fn _get_process_data(
                 info,
             );
 
-            if refresh_kind.user() && entry.user_id.is_none() {
-                refresh_user_group_ids(entry, &mut proc_path);
-            }
+            refresh_user_group_ids(entry, &mut proc_path, refresh_kind);
             return Ok((None, pid));
         }
         parts
diff --git a/src/windows/process.rs b/src/windows/process.rs
index 9ea4f8e..ea962f6 100644
--- a/src/windows/process.rs
+++ b/src/windows/process.rs
@@ -88,10 +88,7 @@ fn get_process_handler(pid: Pid) -> Option<HandleWrapper> {
         })
 }
 
-unsafe fn get_process_user_id(
-    handle: &HandleWrapper,
-    refresh_kind: ProcessRefreshKind,
-) -> Option<Uid> {
+unsafe fn get_process_user_id(process: &mut ProcessInner, refresh_kind: ProcessRefreshKind) {
     struct HeapWrap<T>(*mut T);
 
     impl<T> HeapWrap<T> {
@@ -118,39 +115,57 @@ unsafe fn get_process_user_id(
         }
     }
 
-    if !refresh_kind.user() {
-        return None;
+    let handle = match &process.handle {
+        // We get back the pointer so we don't need to clone the wrapping `Arc`.
+        Some(handle) => ***handle,
+        None => return,
+    };
+
+    if !refresh_kind
+        .user()
+        .needs_update(|| process.user_id.is_none())
+    {
+        return;
     }
 
     let mut token = Default::default();
 
-    if OpenProcessToken(**handle, TOKEN_QUERY, &mut token).is_err() {
+    if OpenProcessToken(handle, TOKEN_QUERY, &mut token).is_err() {
         sysinfo_debug!("OpenProcessToken failed");
-        return None;
+        return;
     }
 
-    let token = HandleWrapper::new(token)?;
+    let token = match HandleWrapper::new(token) {
+        Some(token) => token,
+        None => return,
+    };
 
     let mut size = 0;
 
     if let Err(err) = GetTokenInformation(*token, TokenUser, None, 0, &mut size) {
         if err.code() != ERROR_INSUFFICIENT_BUFFER.to_hresult() {
             sysinfo_debug!("GetTokenInformation failed, error: {:?}", err);
-            return None;
+            return;
         }
     }
 
-    let ptu: HeapWrap<TOKEN_USER> = HeapWrap::new(size)?;
+    let ptu: HeapWrap<TOKEN_USER> = match HeapWrap::new(size) {
+        Some(ptu) => ptu,
+        None => return,
+    };
 
     if let Err(_err) = GetTokenInformation(*token, TokenUser, Some(ptu.0.cast()), size, &mut size) {
         sysinfo_debug!(
             "GetTokenInformation failed (returned {_err:?}), error: {:?}",
             io::Error::last_os_error()
         );
-        return None;
+        return;
     }
 
-    Sid::from_psid((*ptu.0).User.Sid).map(Uid)
+    // We force this check to prevent overwritting a `Some` with a `None`.
+    if let Some(uid) = Sid::from_psid((*ptu.0).User.Sid).map(Uid) {
+        process.user_id = Some(uid);
+    }
 }
 
 struct HandleWrapper(HANDLE);
@@ -357,7 +372,7 @@ impl ProcessInner {
             let info = info.assume_init();
 
             let name = get_process_name(pid).unwrap_or_default();
-            let exe = if refresh_kind.exe() {
+            let exe = if refresh_kind.exe().needs_update(|| true) {
                 get_exe(&process_handler)
             } else {
                 PathBuf::new()
@@ -368,13 +383,12 @@ impl ProcessInner {
             } else {
                 None
             };
-            let user_id = get_process_user_id(&process_handler, refresh_kind);
             let mut p = Self {
                 handle: Some(Arc::new(process_handler)),
                 name,
                 pid,
                 parent,
-                user_id,
+                user_id: None,
                 cmd: Vec::new(),
                 environ: Vec::new(),
                 exe,
@@ -393,6 +407,7 @@ impl ProcessInner {
                 read_bytes: 0,
                 written_bytes: 0,
             };
+            get_process_user_id(&mut p, refresh_kind);
             get_process_params(&mut p, refresh_kind);
             Some(p)
         }
@@ -409,18 +424,17 @@ impl ProcessInner {
     ) -> Self {
         if let Some(handle) = get_process_handler(pid) {
             unsafe {
-                let exe = if refresh_kind.exe() {
+                let exe = if refresh_kind.exe().needs_update(|| true) {
                     get_exe(&handle)
                 } else {
                     PathBuf::new()
                 };
                 let (start_time, run_time) = get_start_and_run_time(*handle, now);
-                let user_id = get_process_user_id(&handle, refresh_kind);
                 let mut p = Self {
                     handle: Some(Arc::new(handle)),
                     name,
                     pid,
-                    user_id,
+                    user_id: None,
                     parent,
                     cmd: Vec::new(),
                     environ: Vec::new(),
@@ -441,11 +455,12 @@ impl ProcessInner {
                     written_bytes: 0,
                 };
 
+                get_process_user_id(&mut p, refresh_kind);
                 get_process_params(&mut p, refresh_kind);
                 p
             }
         } else {
-            let exe = if refresh_kind.exe() {
+            let exe = if refresh_kind.exe().needs_update(|| true) {
                 get_executable_path(pid)
             } else {
                 PathBuf::new()
@@ -493,9 +508,13 @@ impl ProcessInner {
             update_memory(self);
         }
         unsafe {
+            get_process_user_id(self, refresh_kind);
             get_process_params(self, refresh_kind);
         }
-        if refresh_kind.exe() {
+        if refresh_kind
+            .exe()
+            .needs_update(|| self.exe.as_os_str().is_empty())
+        {
             unsafe {
                 self.exe = match self.handle.as_ref() {
                     Some(handle) => get_exe(handle),
@@ -654,7 +673,10 @@ unsafe fn get_process_times(handle: HANDLE) -> u64 {
 
 // On Windows, the root folder is always the current drive. So we get it from its `cwd`.
 fn update_root(refresh_kind: ProcessRefreshKind, cwd: &Path, root: &mut PathBuf) {
-    if !refresh_kind.root() {
+    if !refresh_kind
+        .root()
+        .needs_update(|| root.as_os_str().is_empty())
+    {
         return;
     }
     if cwd.as_os_str().is_empty() || !cwd.has_root() {
@@ -842,7 +864,16 @@ unsafe fn get_process_params(process: &mut ProcessInner, refresh_kind: ProcessRe
         sysinfo_debug!("Non 64 bit targets are not supported");
         return;
     }
-    if !(refresh_kind.cmd() || refresh_kind.environ() || refresh_kind.cwd() || refresh_kind.root())
+    if !(refresh_kind.cmd().needs_update(|| process.cmd.is_empty())
+        || refresh_kind
+            .environ()
+            .needs_update(|| process.environ.is_empty())
+        || refresh_kind
+            .cwd()
+            .needs_update(|| process.cwd.as_os_str().is_empty())
+        || refresh_kind
+            .root()
+            .needs_update(|| process.root.as_os_str().is_empty()))
     {
         return;
     }
@@ -975,7 +1006,13 @@ fn get_cwd_and_root<T: RtlUserProcessParameters>(
     cwd: &mut PathBuf,
     root: &mut PathBuf,
 ) {
-    if !refresh_kind.cwd() && !refresh_kind.root() {
+    let cwd_needs_update = refresh_kind
+        .cwd()
+        .needs_update(|| cwd.as_os_str().is_empty());
+    let root_needs_update = refresh_kind
+        .root()
+        .needs_update(|| root.as_os_str().is_empty());
+    if !cwd_needs_update && !root_needs_update {
         return;
     }
     match params.get_cwd(handle) {
@@ -983,7 +1020,7 @@ fn get_cwd_and_root<T: RtlUserProcessParameters>(
             let tmp_cwd = PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice()));
             // Should always be called after we refreshed `cwd`.
             update_root(refresh_kind, &tmp_cwd, root);
-            if refresh_kind.cwd() {
+            if cwd_needs_update {
                 *cwd = tmp_cwd;
             }
         },
@@ -1033,7 +1070,7 @@ fn get_cmd_line<T: RtlUserProcessParameters>(
     refresh_kind: ProcessRefreshKind,
     cmd_line: &mut Vec<String>,
 ) {
-    if !refresh_kind.cmd() {
+    if !refresh_kind.cmd().needs_update(|| cmd_line.is_empty()) {
         return;
     }
     if *WINDOWS_8_1_OR_NEWER {
@@ -1049,7 +1086,7 @@ fn get_proc_env<T: RtlUserProcessParameters>(
     refresh_kind: ProcessRefreshKind,
     environ: &mut Vec<String>,
 ) {
-    if !refresh_kind.environ() {
+    if !refresh_kind.environ().needs_update(|| environ.is_empty()) {
         return;
     }
     match params.get_environ(handle) {


Test patch:
diff --git a/tests/process.rs b/tests/process.rs
index c7baf24..6cb5ac2 100644
--- a/tests/process.rs
+++ b/tests/process.rs
@@ -1,6 +1,6 @@
 // Take a look at the license at the top of the repository in the LICENSE file.
 
-use sysinfo::{Pid, ProcessRefreshKind, System};
+use sysinfo::{Pid, ProcessRefreshKind, System, UpdateKind};
 
 #[test]
 fn test_process() {
@@ -17,7 +17,7 @@ fn test_process() {
         .all(|p| p.exe().as_os_str().is_empty()));
 
     let mut s = System::new();
-    s.refresh_processes_specifics(ProcessRefreshKind::new().with_exe());
+    s.refresh_processes_specifics(ProcessRefreshKind::new().with_exe(UpdateKind::Always));
     assert!(s
         .processes()
         .values()
@@ -48,7 +48,7 @@ fn test_cwd() {
     let pid = Pid::from_u32(p.id() as _);
     std::thread::sleep(std::time::Duration::from_secs(1));
     let mut s = System::new();
-    s.refresh_processes_specifics(ProcessRefreshKind::new().with_cwd());
+    s.refresh_processes_specifics(ProcessRefreshKind::new().with_cwd(UpdateKind::Always));
     p.kill().expect("Unable to kill process.");
 
     let processes = s.processes();
@@ -85,8 +85,8 @@ fn test_cmd() {
     std::thread::sleep(std::time::Duration::from_millis(500));
     let mut s = System::new();
     assert!(s.processes().is_empty());
-    s.refresh_processes_specifics(ProcessRefreshKind::new().with_cmd());
-    p.kill().expect("Unable to kill process.");
+    s.refresh_processes_specifics(ProcessRefreshKind::new().with_cmd(UpdateKind::Always));
+    p.kill().expect("Unable to kill process");
     assert!(!s.processes().is_empty());
     if let Some(process) = s.process(Pid::from_u32(p.id() as _)) {
         if cfg!(target_os = "windows") {
@@ -160,7 +160,7 @@ fn test_environ() {
     let pid = Pid::from_u32(p.id() as _);
     let mut s = System::new();
 
-    s.refresh_processes_specifics(ProcessRefreshKind::new().with_environ());
+    s.refresh_processes_specifics(ProcessRefreshKind::new().with_environ(UpdateKind::Always));
 
     let processes = s.processes();
     let proc_ = processes.get(&pid);
@@ -716,7 +716,7 @@ fn test_process_specific_refresh() {
                     concat!("failed 0 check check for ", stringify!($name)),
                 );
             }
-            s.refresh_process_specifics(pid, ProcessRefreshKind::new().$method());
+            s.refresh_process_specifics(pid, ProcessRefreshKind::new().$method(UpdateKind::Always));
             {
                 let p = s.process(pid).unwrap();
                 assert_ne!(