Repository: GuillaumeGomez/sysinfo (language: rust)

Issue:
macos process.root() is returning the PATH env var
**Describe the bug**
On MacOS in nushell, I'm writing a small process information command to inspect nushell memory on demand. The code is working great on Windows but on MacOS, process.root() is returning the system PATH instead of the root folder where the executable is. I'm not really understanding why it's happening. Any ideas?

Here's a snippet of how I'm getting the information. I know there's a lot of nushell code here but hopefully it's self explanatory.
```rust
        let span = Span::unknown();
        // get the nushell process id
        let pid = Pid::from(std::process::id() as usize);
        // only refresh the process and memory information
        let rk = RefreshKind::new()
            .with_processes(
                ProcessRefreshKind::new()
                    .without_cpu()
                    .without_disk_usage()
                    .without_user(),
            )
            .with_memory();
        // only get information requested
        let system = System::new_with_specifics(rk);
        // get the process information for the nushell pid
        let pinfo = system.process(pid);

        if let Some(p) = pinfo {
            Ok(Value::record(
                record! {
                    "pid" => Value::int(p.pid().as_u32() as i64, span),
                    "ppid" => Value::int(p.parent().unwrap_or(0.into()).as_u32() as i64, span),
                    "process" => {
                        Value::record(
                            record! {
                                "memory" => Value::filesize(p.memory() as i64, span),
                                "virtual_memory" => Value::filesize(p.virtual_memory() as i64, span),
                                "status" => Value::string(p.status().to_string(), span),
                                // This root is returning the contents of the env var PATH on MacOS
                                // On Windows, it returns the root path for the pid's executable
                                "root" => Value::string(p.root().to_string_lossy().to_string(), span),
                                "cwd" => Value::string(p.cwd().to_string_lossy().to_string(), span),
                                "exe_path" => Value::string(p.exe().to_string_lossy().to_string(), span),
                                "command" => Value::string(p.cmd().join(" "), span),
                                "name" => Value::string(p.name().to_string(), span),
```




Discussion / hints:
What is:

> the system PATH

? Shod me the path you expect and the one you get, might be better to understand for me.
Sorry for not being clearer. The system PATH is probably mischaracterized but it's supposed to mean the environment variable named PATH on unix type systems.

In this image you can see the path I get for `root()` in the middle under `status` and to the right of the green text `root`.
![image](https://github.com/GuillaumeGomez/sysinfo/assets/343840/fb98deef-f724-42f9-b73c-06027700cb43)
The path I expect in this image is `target/debug`
Ok, so it's reading the `PATH` environment variable. Interesting.

Fix patch:
diff --git a/src/unix/apple/macos/process.rs b/src/unix/apple/macos/process.rs
index cad8c05d8..b69004950 100644
--- a/src/unix/apple/macos/process.rs
+++ b/src/unix/apple/macos/process.rs
@@ -1,6 +1,5 @@
 // Take a look at the license at the top of the repository in the LICENSE file.
 
-use std::ffi::CStr;
 use std::mem::{self, MaybeUninit};
 use std::ops::Deref;
 use std::path::{Path, PathBuf};
@@ -13,6 +12,7 @@ use crate::{DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Sig
 
 use crate::sys::process::ThreadStatus;
 use crate::sys::system::Wrap;
+use crate::unix::utils::cstr_to_rust_with_size;
 
 pub(crate) struct ProcessInner {
     pub(crate) name: String,
@@ -48,7 +48,13 @@ pub(crate) struct ProcessInner {
 }
 
 impl ProcessInner {
-    pub(crate) fn new_empty(pid: Pid, exe: PathBuf, name: String, cwd: PathBuf) -> Self {
+    pub(crate) fn new_empty(
+        pid: Pid,
+        exe: PathBuf,
+        name: String,
+        cwd: PathBuf,
+        root: PathBuf,
+    ) -> Self {
         Self {
             name,
             pid,
@@ -57,7 +63,7 @@ impl ProcessInner {
             environ: Vec::new(),
             exe,
             cwd,
-            root: PathBuf::new(),
+            root,
             memory: 0,
             virtual_memory: 0,
             cpu_usage: 0.,
@@ -79,7 +85,14 @@ impl ProcessInner {
         }
     }
 
-    pub(crate) fn new(pid: Pid, parent: Option<Pid>, start_time: u64, run_time: u64) -> Self {
+    pub(crate) fn new(
+        pid: Pid,
+        parent: Option<Pid>,
+        start_time: u64,
+        run_time: u64,
+        cwd: PathBuf,
+        root: PathBuf,
+    ) -> Self {
         Self {
             name: String::new(),
             pid,
@@ -87,8 +100,8 @@ impl ProcessInner {
             cmd: Vec::new(),
             environ: Vec::new(),
             exe: PathBuf::new(),
-            cwd: PathBuf::new(),
-            root: PathBuf::new(),
+            cwd,
+            root,
             memory: 0,
             virtual_memory: 0,
             cpu_usage: 0.,
@@ -319,17 +332,6 @@ fn check_if_pid_is_alive(pid: Pid, check_if_alive: bool) -> bool {
     }
 }
 
-#[inline]
-fn do_not_get_env_path(_: &str, _: &mut PathBuf, _: &mut bool) {}
-
-#[inline]
-fn do_get_env_path(env: &str, root: &mut PathBuf, check: &mut bool) {
-    if *check && env.starts_with("PATH=") {
-        *check = false;
-        *root = Path::new(&env[5..]).to_path_buf();
-    }
-}
-
 unsafe fn get_bsd_info(pid: Pid) -> Option<libc::proc_bsdinfo> {
     let mut info = mem::zeroed::<libc::proc_bsdinfo>();
 
@@ -347,6 +349,18 @@ unsafe fn get_bsd_info(pid: Pid) -> Option<libc::proc_bsdinfo> {
     }
 }
 
+unsafe fn convert_node_path_info(node: &libc::vnode_info_path) -> PathBuf {
+    if node.vip_vi.vi_stat.vst_dev == 0 {
+        return PathBuf::new();
+    }
+    cstr_to_rust_with_size(
+        node.vip_path.as_ptr() as _,
+        Some(node.vip_path.len() * node.vip_path[0].len()),
+    )
+    .map(PathBuf::from)
+    .unwrap_or_default()
+}
+
 unsafe fn create_new_process(
     pid: Pid,
     mut size: size_t,
@@ -362,15 +376,13 @@ unsafe fn create_new_process(
         &mut vnodepathinfo as *mut _ as *mut _,
         mem::size_of::<libc::proc_vnodepathinfo>() as _,
     );
-    let cwd = if result > 0 {
-        let buffer = vnodepathinfo.pvi_cdir.vip_path;
-        let buffer = CStr::from_ptr(buffer.as_ptr() as _);
-        buffer
-            .to_str()
-            .map(PathBuf::from)
-            .unwrap_or_else(|_| PathBuf::new())
+    let (cwd, root) = if result > 0 {
+        (
+            convert_node_path_info(&vnodepathinfo.pvi_cdir),
+            convert_node_path_info(&vnodepathinfo.pvi_rdir),
+        )
     } else {
-        PathBuf::new()
+        (PathBuf::new(), PathBuf::new())
     };
 
     let info = match info {
@@ -392,7 +404,7 @@ unsafe fn create_new_process(
                         .unwrap_or("")
                         .to_owned();
                     return Ok(Some(Process {
-                        inner: ProcessInner::new_empty(pid, exe, name, cwd),
+                        inner: ProcessInner::new_empty(pid, exe, name, cwd, root),
                     }));
                 }
                 _ => {}
@@ -488,57 +500,33 @@ unsafe fn create_new_process(
         }
 
         #[inline]
-        unsafe fn get_environ<F: Fn(&str, &mut PathBuf, &mut bool)>(
-            ptr: *mut u8,
-            mut cp: *mut u8,
-            size: size_t,
-            mut root: PathBuf,
-            callback: F,
-        ) -> (Vec<String>, PathBuf) {
+        unsafe fn get_environ(ptr: *mut u8, mut cp: *mut u8, size: size_t) -> Vec<String> {
             let mut environ = Vec::with_capacity(10);
             let mut start = cp;
-            let mut check = true;
             while cp < ptr.add(size) {
                 if *cp == 0 {
                     if cp == start {
                         break;
                     }
                     let e = get_unchecked_str(cp, start);
-                    callback(&e, &mut root, &mut check);
                     environ.push(e);
                     start = cp.offset(1);
                 }
                 cp = cp.offset(1);
             }
-            (environ, root)
+            environ
         }
 
-        let (environ, root) = if exe.is_absolute() {
-            if let Some(parent_path) = exe.parent() {
-                get_environ(
-                    ptr,
-                    cp,
-                    size,
-                    parent_path.to_path_buf(),
-                    do_not_get_env_path,
-                )
-            } else {
-                get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path)
-            }
-        } else {
-            get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path)
-        };
-        let mut p = ProcessInner::new(pid, parent, start_time, run_time);
+        let environ = get_environ(ptr, cp, size);
+        let mut p = ProcessInner::new(pid, parent, start_time, run_time, cwd, root);
 
         p.exe = exe;
         p.name = name;
-        p.cwd = cwd;
         p.cmd = parse_command_line(&cmd);
         p.environ = environ;
-        p.root = root;
         p
     } else {
-        ProcessInner::new(pid, parent, start_time, run_time)
+        ProcessInner::new(pid, parent, start_time, run_time, cwd, root)
     };
 
     let task_info = get_task_info(pid);
@@ -699,19 +687,3 @@ fn parse_command_line<T: Deref<Target = str> + Borrow<str>>(cmd: &[T]) -> Vec<St
     }
     command
 }
-
-#[cfg(test)]
-mod test {
-    use super::*;
-
-    #[test]
-    fn test_get_path() {
-        let mut path = PathBuf::new();
-        let mut check = true;
-
-        do_get_env_path("PATH=tadam", &mut path, &mut check);
-
-        assert!(!check);
-        assert_eq!(path, PathBuf::from("tadam"));
-    }
-}
diff --git a/src/unix/utils.rs b/src/unix/utils.rs
index 66f2e5d73..6c5bb36b1 100644
--- a/src/unix/utils.rs
+++ b/src/unix/utils.rs
@@ -10,9 +10,9 @@ pub(crate) fn cstr_to_rust_with_size(c: *const c_char, size: Option<usize>) -> O
     if c.is_null() {
         return None;
     }
-    let mut s = match size {
-        Some(len) => Vec::with_capacity(len),
-        None => Vec::new(),
+    let (mut s, max) = match size {
+        Some(len) => (Vec::with_capacity(len), len as isize),
+        None => (Vec::new(), isize::MAX),
     };
     let mut i = 0;
     unsafe {
@@ -23,6 +23,9 @@ pub(crate) fn cstr_to_rust_with_size(c: *const c_char, size: Option<usize>) -> O
             }
             s.push(value);
             i += 1;
+            if i >= max {
+                break;
+            }
         }
         String::from_utf8(s).ok()
     }


Test patch: