Repository: uutils/procps
Pull request #173 (2024-08-09 08:21:35)

pwdx: use `sysinfo` crate
This PR uses the `sysinfo` crate to get the working directories of processes in a platform-independent way.


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4d34551..26e5bd2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -69,12 +69,20 @@ jobs:
 
     - name: rust toolchain ~ install
       uses: dtolnay/rust-toolchain@nightly
+    - name: set RUSTFLAGS
+      shell: bash
+      run: |
+        if [ "$RUNNER_OS" == "Windows" ]; then
+          # -Clink-dead-code conflicts with sysinfo crate and leads to STATUS_DLL_NOT_FOUND error, so we don't use it on Windows
+          echo "RUSTFLAGS=-Zprofile -Ccodegen-units=1 -Copt-level=0 -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" >> $GITHUB_ENV
+        else
+          echo "RUSTFLAGS=-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" >> $GITHUB_ENV
+        fi
     - name: Test
       run: cargo test --no-fail-fast
       env:
         CARGO_INCREMENTAL: "0"
         RUSTC_WRAPPER: ""
-        RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
         RUSTDOCFLAGS: "-Cpanic=abort"
     - name: "`grcov` ~ install"
       id: build_grcov
diff --git a/Cargo.lock b/Cargo.lock
index 7b5e79d..090093a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1065,6 +1065,7 @@ name = "uu_pwdx"
 version = "0.0.1"
 dependencies = [
  "clap",
+ "sysinfo",
  "uucore",
 ]
 
diff --git a/src/uu/pwdx/Cargo.toml b/src/uu/pwdx/Cargo.toml
index 86144ce..8c9a50d 100644
--- a/src/uu/pwdx/Cargo.toml
+++ b/src/uu/pwdx/Cargo.toml
@@ -14,6 +14,7 @@ categories = ["command-line-utilities"]
 [dependencies]
 uucore = { workspace = true }
 clap = { workspace = true }
+sysinfo = { workspace = true }
 
 [lib]
 path = "src/pwdx.rs"
diff --git a/src/uu/pwdx/src/pwdx.rs b/src/uu/pwdx/src/pwdx.rs
index 1003ca0..9e7ac06 100644
--- a/src/uu/pwdx/src/pwdx.rs
+++ b/src/uu/pwdx/src/pwdx.rs
@@ -5,10 +5,8 @@
 
 use clap::{crate_version, Arg, Command};
 use std::env;
-use std::fs;
-use std::path::Path;
-use std::process;
-use uucore::error::{UResult, USimpleError};
+use sysinfo::{Pid, System};
+use uucore::error::{set_exit_code, UResult, USimpleError};
 use uucore::{format_usage, help_about, help_usage};
 
 const ABOUT: &str = help_about!("pwdx.md");
@@ -19,6 +17,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     let matches = uu_app().try_get_matches_from(args)?;
 
     let pids = matches.get_many::<String>("pid").unwrap();
+    let sys = System::new_all();
 
     for pid_str in pids {
         let pid = match pid_str.parse::<i32>() {
@@ -32,13 +31,17 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
             Ok(pid) => pid,
         };
 
-        let cwd_link = format!("/proc/{}/cwd", pid);
-
-        match fs::read_link(Path::new(&cwd_link)) {
-            Ok(path) => println!("{}: {}", pid, path.display()),
-            Err(e) => {
-                eprintln!("pwdx: failed to read link for PID {}: {}", pid, e);
-                process::exit(1);
+        match sys.process(Pid::from(pid as usize)) {
+            Some(process) => match process.cwd() {
+                Some(cwd) => println!("{pid}: {}", cwd.display()),
+                None => {
+                    set_exit_code(1);
+                    eprintln!("{pid}: Permission denied");
+                }
+            },
+            None => {
+                set_exit_code(1);
+                eprintln!("{pid}: No such process");
             }
         }
     }


Test patch:
diff --git a/tests/by-util/test_pwdx.rs b/tests/by-util/test_pwdx.rs
index 8e92d29..83d7270 100644
--- a/tests/by-util/test_pwdx.rs
+++ b/tests/by-util/test_pwdx.rs
@@ -3,10 +3,8 @@
 // For the full copyright and license information, please view the LICENSE
 // file that was distributed with this source code.
 
-#[cfg(target_os = "linux")]
 use std::process;
 
-#[cfg(target_os = "linux")]
 use regex::Regex;
 
 use crate::common::util::TestScenario;
@@ -17,7 +15,6 @@ fn test_no_args() {
 }
 
 #[test]
-#[cfg(target_os = "linux")]
 fn test_valid_pid() {
     let pid = process::id();
 
@@ -28,7 +25,6 @@ fn test_valid_pid() {
 }
 
 #[test]
-#[cfg(target_os = "linux")]
 fn test_multiple_valid_pids() {
     let pid = process::id();
 
@@ -40,6 +36,40 @@ fn test_multiple_valid_pids() {
         .stdout_matches(&Regex::new(&format!("(?m)^{pid}: .+$")).unwrap());
 }
 
+#[test]
+fn test_non_existing_pid() {
+    let non_existing_pid = "999999";
+
+    new_ucmd!()
+        .arg(non_existing_pid)
+        .fails()
+        .code_is(1)
+        .no_stdout()
+        .stderr_is(format!("{non_existing_pid}: No such process\n"));
+}
+
+#[test]
+fn test_non_existing_and_existing_pid() {
+    let pid = process::id();
+    let non_existing_pid = "999999";
+
+    new_ucmd!()
+        .arg(non_existing_pid)
+        .arg(pid.to_string())
+        .fails()
+        .code_is(1)
+        .stdout_matches(&Regex::new(&format!("^{pid}: .+\n$")).unwrap())
+        .stderr_is(format!("{non_existing_pid}: No such process\n"));
+
+    new_ucmd!()
+        .arg(pid.to_string())
+        .arg(non_existing_pid)
+        .fails()
+        .code_is(1)
+        .stdout_matches(&Regex::new(&format!("^{pid}: .+\n$")).unwrap())
+        .stderr_is(format!("{non_existing_pid}: No such process\n"));
+}
+
 #[test]
 fn test_invalid_pid() {
     for invalid_pid in ["0", "invalid"] {
@@ -53,7 +83,6 @@ fn test_invalid_pid() {
 }
 
 #[test]
-#[cfg(target_os = "linux")]
 fn test_invalid_and_valid_pid() {
     let pid = process::id();
     let invalid_pid = "invalid";