[PATCH] ps: add options -G, -U and -p

Chris Ever chirsz-ever at outlook.com
Sat Aug 30 18:22:51 UTC 2025


This patch implements the -G, -U, and -p options for ps according to POSIX.

Why -g and -u are not implemented: In POSIX they are XSI. On BSD 
platforms, -g
are not generally supported,
and -u have different meanings than in POSIX.

Why -t is not implemented: Because there are subtle differences between
different implementations, and these
implementations conflict with the XSI scheme described by POSIX.

References:

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/ps.html
https://man.freebsd.org/cgi/man.cgi?ps(1)
https://man.netbsd.org/ps.1
https://man.openbsd.org/ps.1
https://man7.org/linux/man-pages/man1/ps.1.html

---
 From 3161a935a19285503316814154ce39a80579cbc9 Mon Sep 17 00:00:00 2001
From: chirsz-ever <chirsz-ever at outlook.com>
Date: Sun, 31 Aug 2025 00:26:54 +0800
Subject: [PATCH] ps: add options -G, -U and -p

---
  include/libbb.h |   6 ++-
  libbb/procps.c  |   4 +-
  procps/ps.c     | 133 +++++++++++++++++++++++++++++++++++++++++++++++-
  3 files changed, 138 insertions(+), 5 deletions(-)

diff --git a/include/libbb.h b/include/libbb.h
index 4b3319824..c00ef41c9 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -2105,9 +2105,11 @@ typedef struct procps_status_t {
      unsigned sid;
      unsigned uid;
      unsigned gid;
-#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
+#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS || ENABLE_FEATURE_PS_FILTERS
      unsigned ruid;
      unsigned rgid;
+#endif
+#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
      int niceness;
  #endif
      unsigned tty_major,tty_minor;
@@ -2159,7 +2161,7 @@ enum {
      PSSCAN_START_TIME = 1 << 18,
      PSSCAN_CPU      = (1 << 19) * ENABLE_FEATURE_TOP_SMP_PROCESS,
      PSSCAN_NICE     = (1 << 20) * ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS,
-    PSSCAN_RUIDGID  = (1 << 21) * ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS,
+    PSSCAN_RUIDGID  = (1 << 21) * (ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
|| ENABLE_FEATURE_PS_FILTERS),
      PSSCAN_TASKS    = (1 << 22) * ENABLE_FEATURE_SHOW_THREADS,
  };
  //procps_status_t* alloc_procps_scan(void) FAST_FUNC;
diff --git a/libbb/procps.c b/libbb/procps.c
index de640d29e..5ed21adc7 100644
--- a/libbb/procps.c
+++ b/libbb/procps.c
@@ -474,7 +474,7 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t*
sp, int flags)
          if (flags & PSSCAN_SMAPS)
              procps_read_smaps(pid, sp);
  #endif /* TOPMEM */
-#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
+#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS || ENABLE_FEATURE_PS_FILTERS
          if (flags & PSSCAN_RUIDGID) {
              FILE *file;

@@ -496,7 +496,7 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t*
sp, int flags)
                  fclose(file);
              }
          }
-#endif /* PS_ADDITIONAL_COLUMNS */
+#endif /* PS_ADDITIONAL_COLUMNS || PS_FILTERS */
          if (flags & PSSCAN_EXE) {
              strcpy(filename_tail, "exe");
              free(sp->exe);
diff --git a/procps/ps.c b/procps/ps.c
index 5b521aebd..48c8ca34d 100644
--- a/procps/ps.c
+++ b/procps/ps.c
@@ -48,6 +48,10 @@
  //config:    bool "Enable -o rgroup, -o ruser, -o nice specifiers"
  //config:    default y
  //config:    depends on (PS || MINIPS) && DESKTOP
+//config:config FEATURE_PS_FILTERS
+//config:    bool "Enable filter options (-G -U -p)"
+//config:    default y
+//config:    depends on (PS || MINIPS) && DESKTOP

  //                 APPLET_NOEXEC:name    main location suid_type     help
  //applet:IF_PS(    APPLET_NOEXEC(ps,     ps,  BB_DIR_BIN, 
BB_SUID_DROP, ps))
@@ -62,10 +66,23 @@
  //usage:       "[-o COL1,COL2=HEADER]" IF_FEATURE_SHOW_THREADS(" [-T]")
  //usage:#define ps_full_usage "\n\n"
  //usage:       "Show list of processes\n"
+//usage:    IF_FEATURE_PS_FILTERS(
+//usage:     "\n    -G group[,group...]    Select rows by real group
ids or names"
+//usage:    )
  //usage:     "\n    -o COL1,COL2=HEADER    Select columns for display"
+//usage:    IF_FEATURE_PS_FILTERS(
+//usage:     "\n    -p pid[,pid...]        Select rows by process
ids"
+//usage:    )
  //usage:    IF_FEATURE_SHOW_THREADS(
  //usage:     "\n    -T            Show threads"
  //usage:    )
+//usage:    IF_FEATURE_PS_FILTERS(
+//usage:     "\n    -U user[,user...]    Select rows by real user
ids or names"
+//usage:    )
+//usage:    IF_FEATURE_PS_FILTERS(
+//usage:     "\n\n-G, -U and -p accept comma or space as separator."
+//usage:     "\n-G and -U accept either names or numeric ids."
+//usage:    )
  //usage:
  //usage:#else /* !ENABLE_DESKTOP */
  //usage:
@@ -541,6 +558,104 @@ static void format_process(const procps_status_t *ps)
      printf("%.*s\n", terminal_width, buffer);
  }

+#if ENABLE_FEATURE_PS_FILTERS
+/* store id to llist_t::data */
+static llist_t *opt_G, *opt_G_items;
+static llist_t *opt_U, *opt_U_items;
+static llist_t *opt_p, *opt_p_items;
+
+static void parse_comma_space_list(char *opt, llist_t **items, void
*(*parser)(const char *))
+{
+    char *p = opt;
+    while (*p) {
+        char *start = p;
+        while (*p && *p != ',' && !isspace(*p)) {
+            ++p;
+        }
+        if (*p) {
+            *p++ = '\0';
+            while (isspace(*p)) {
+                ++p;
+            }
+        }
+        if (*start)
+            llist_add_to(items, parser(start));
+    }
+}
+
+static void* parse_rgid(const char *str)
+{
+    char *endptr;
+    unsigned long rgid = strtoul(str, &endptr, 0);
+    if (*endptr != '\0') { /* try as login name */
+        /* die if unknown group */
+        rgid = xgetgrnam(str)->gr_gid;
+    }
+    if (rgid >= (gid_t)-1L)
+        bb_error_msg_and_die("group ID out of range: %lu",
rgid);
+    return (void*)(uintptr_t)rgid;
+}
+
+static void* parse_ruid(const char *str)
+{
+    char *endptr;
+    unsigned long ruid = strtoul(str, &endptr, 0);
+    if (*endptr != '\0') { /* try as login name */
+        /* die if unknown user */
+        ruid = xgetpwnam(str)->pw_uid;
+    }
+    if (ruid >= (uid_t)-1L)
+        bb_error_msg_and_die("user ID out of range: %lu", ruid);
+    return (void*)(uintptr_t)ruid;
+}
+
+static void* parse_pid(const char *str)
+{
+    char *endptr;
+    unsigned long val = strtoul(str, &endptr, 0);
+    if (*endptr != '\0')
+        bb_error_msg_and_die("bad pid: '%s'", str);
+    if (val >= (unsigned)-1L)
+        bb_error_msg_and_die("pid out of range: %lu", val);
+    return (void*)val;
+}
+
+static void parse_filter_options(void)
+{
+    while (opt_G)
+        parse_comma_space_list(llist_pop(&opt_G), &opt_G_items,
parse_rgid);
+    while (opt_U)
+        parse_comma_space_list(llist_pop(&opt_U), &opt_U_items,
parse_ruid);
+    while (opt_p)
+        parse_comma_space_list(llist_pop(&opt_p), &opt_p_items,
parse_pid);
+}
+
+static int is_in_list_num(const llist_t* items, unsigned num)
+{
+    while (items) {
+        if (items->data == (void*)(uintptr_t)num)
+            return TRUE;
+        items = items->link;
+    }
+    return FALSE;
+}
+
+static int is_filtered_process(const procps_status_t *p)
+{
+    /* if no filters, show all processes */
+    if (!opt_G_items && !opt_U_items && !opt_p_items)
+        return TRUE;
+    /* if any filter exists, the process should match at least one filter
*/
+    if (opt_G_items && is_in_list_num(opt_G_items, p->rgid))
+        return TRUE;
+    if (opt_U_items && is_in_list_num(opt_U_items, p->ruid))
+        return TRUE;
+    if (opt_p_items && is_in_list_num(opt_p_items, p->pid))
+        return TRUE;
+    return FALSE;
+}
+#endif
+
  #if ENABLE_SELINUX
  # define SELINUX_O_PREFIX "label,"
  # define DEFAULT_O_STR    (SELINUX_O_PREFIX "pid,user"
IF_FEATURE_PS_TIME(",time") ",args")
@@ -567,6 +682,10 @@ int ps_main(int argc UNUSED_PARAM, char **argv)
          OPT_f = (1 << 6),
          OPT_l = (1 << 7),
          OPT_T = (1 << 8) * ENABLE_FEATURE_SHOW_THREADS,
+        OPT_G = (1 << 8) * (1 << ENABLE_FEATURE_SHOW_THREADS) *
ENABLE_FEATURE_PS_FILTERS,
+        OPT_U = (1 << 9) * (1 << ENABLE_FEATURE_SHOW_THREADS) *
ENABLE_FEATURE_PS_FILTERS,
+        OPT_p = (1 << 10) * (1 << ENABLE_FEATURE_SHOW_THREADS) *
ENABLE_FEATURE_PS_FILTERS,
+        OPT_t = (1 << 11) * (1 << ENABLE_FEATURE_SHOW_THREADS) *
ENABLE_FEATURE_PS_FILTERS,
      };

      INIT_G();
@@ -595,7 +714,8 @@ int ps_main(int argc UNUSED_PARAM, char **argv)
  #if ENABLE_SELINUX || ENABLE_FEATURE_SHOW_THREADS
      opt =
  #endif
-        getopt32(argv, "Zo:*aAdefl"IF_FEATURE_SHOW_THREADS("T"),
&opt_o);
+        getopt32(argv,
"Zo:*aAdefl"IF_FEATURE_SHOW_THREADS("T")IF_FEATURE_PS_FILTERS("G:*U:*p:*"),
+            &opt_o, &opt_G, &opt_U, &opt_p);

      if (opt_o) {
          do {
@@ -620,6 +740,13 @@ int ps_main(int argc UNUSED_PARAM, char **argv)
      if (opt & OPT_T)
          need_flags |= PSSCAN_TASKS;
  #endif
+#if ENABLE_FEATURE_PS_FILTERS
+    if (opt & (OPT_G | OPT_U))
+        need_flags |= PSSCAN_RUIDGID;
+    if (opt & OPT_p)
+        need_flags |= PSSCAN_PID;
+    parse_filter_options();
+#endif

      /* Was INT_MAX, but some libc's go belly up with printf("%.*s")
       * and such large widths */
@@ -634,6 +761,10 @@ int ps_main(int argc UNUSED_PARAM, char **argv)

      p = NULL;
      while ((p = procps_scan(p, need_flags)) != NULL) {
+#if ENABLE_FEATURE_PS_FILTERS
+        if (!is_filtered_process(p))
+            continue;
+#endif
          format_process(p);
      }

-- 
2.50.1




More information about the busybox mailing list