Repository: uutils/procps
Pull request #144 (2024-07-25 13:01:45)

pgrep: implement -P/--parent


Patch:
diff --git a/src/uu/pgrep/src/pgrep.rs b/src/uu/pgrep/src/pgrep.rs
index b89b4e8..cd972c5 100644
--- a/src/uu/pgrep/src/pgrep.rs
+++ b/src/uu/pgrep/src/pgrep.rs
@@ -28,6 +28,7 @@ struct Settings {
     newest: bool,
     oldest: bool,
     older: Option<u64>,
+    parent: Option<Vec<u64>>,
     runstates: Option<String>,
     terminal: Option<HashSet<TerminalType>>,
 }
@@ -60,6 +61,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
         inverse: matches.get_flag("inverse"),
         newest: matches.get_flag("newest"),
         oldest: matches.get_flag("oldest"),
+        parent: matches
+            .get_many::<u64>("parent")
+            .map(|parents| parents.copied().collect()),
         runstates: matches.get_one::<String>("runstates").cloned(),
         older: matches.get_one::<u64>("older").copied(),
         terminal: matches.get_many::<String>("terminal").map(|ttys| {
@@ -73,6 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
         && !settings.oldest
         && settings.runstates.is_none()
         && settings.older.is_none()
+        && settings.parent.is_none()
         && settings.terminal.is_none())
         && pattern.is_empty()
     {
@@ -209,7 +214,20 @@ fn collect_matched_pids(settings: &Settings) -> Vec<ProcessInformation> {
             let arg_older = settings.older.unwrap_or(0);
             let older_matched = pid.start_time().unwrap() >= arg_older;
 
-            if (run_state_matched && pattern_matched && tty_matched && older_matched)
+            // the PPID is the fourth field in /proc/<PID>/stat
+            // (https://www.kernel.org/doc/html/latest/filesystems/proc.html#id10)
+            let stat = pid.stat();
+            let ppid = stat.get(3);
+            let parent_matched = match (&settings.parent, ppid) {
+                (Some(parents), Some(ppid)) => parents.contains(&ppid.parse::<u64>().unwrap()),
+                _ => true,
+            };
+
+            if (run_state_matched
+                && pattern_matched
+                && tty_matched
+                && older_matched
+                && parent_matched)
                 ^ settings.inverse
             {
                 tmp_vec.push(pid)
@@ -285,7 +303,9 @@ pub fn uu_app() -> Command {
             arg!(-o     --oldest                "select least recently started"),
             arg!(-O     --older <seconds>       "select where older than seconds")
                 .value_parser(clap::value_parser!(u64)),
-            // arg!(-P     --parent <PPID>         "match only child processes of the given parent"),
+            arg!(-P     --parent <PPID>         "match only child processes of the given parent")
+                .value_delimiter(',')
+                .value_parser(clap::value_parser!(u64)),
             // arg!(-s     --session <SID>         "match session IDs"),
             arg!(-t     --terminal <tty>        "match by controlling terminal")
                 .value_delimiter(','),
diff --git a/src/uu/pgrep/src/process.rs b/src/uu/pgrep/src/process.rs
index ad55ba2..bb5eb46 100644
--- a/src/uu/pgrep/src/process.rs
+++ b/src/uu/pgrep/src/process.rs
@@ -260,7 +260,7 @@ impl ProcessInformation {
     }
 
     /// Collect information from `/proc/<pid>/stat` file
-    fn stat(&mut self) -> Rc<Vec<String>> {
+    pub fn stat(&mut self) -> Rc<Vec<String>> {
         if let Some(c) = &self.cached_stat {
             return Rc::clone(c);
         }


Test patch:
diff --git a/tests/by-util/test_pgrep.rs b/tests/by-util/test_pgrep.rs
index 1aba87d..bad57b9 100644
--- a/tests/by-util/test_pgrep.rs
+++ b/tests/by-util/test_pgrep.rs
@@ -304,3 +304,34 @@ fn test_runstates_invalid_runstate() {
         .code_is(1)
         .no_output();
 }
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_parent() {
+    for arg in ["-P", "--parent"] {
+        new_ucmd!()
+            .arg(arg)
+            .arg("0")
+            .succeeds()
+            .stdout_matches(&Regex::new(MULTIPLE_PIDS).unwrap());
+    }
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_parent_multiple_parents() {
+    new_ucmd!()
+        .arg("--parent=0,1")
+        .succeeds()
+        .stdout_matches(&Regex::new(MULTIPLE_PIDS).unwrap());
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_parent_non_matching_parent() {
+    new_ucmd!()
+        .arg("--parent=10000000")
+        .fails()
+        .code_is(1)
+        .no_output();
+}