From c8384e682c1cfb3b2dc797e0f8a3cbaaccf7a3da Mon Sep 17 00:00:00 2001
From: "Alex Xu (Hello71)" <alex_y_xu@yahoo.ca>
Date: Sun, 23 Feb 2020 22:02:59 -0500
Subject: [PATCH] pgrep: add pwait

---
 .gitignore   |   1 +
 Makefile.am  |  10 +++++
 configure.ac |  16 ++++++++
 pgrep.1      |  39 ++++++++++++------
 pgrep.c      | 112 +++++++++++++++++++++++++++++++++++++++++----------
 pwait.1      |   1 +
 6 files changed, 145 insertions(+), 34 deletions(-)
 create mode 100644 pwait.1

diff --git a/.gitignore b/.gitignore
index 933b66b2..17d78f6e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,6 +37,7 @@ pgrep
 pidof
 pkill
 pmap
+pwait
 procps-ng-*.tar.xz
 proc/.depend
 proc/libprocps.la
diff --git a/Makefile.am b/Makefile.am
index 13564dac..815023b2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -54,6 +54,9 @@ bin_PROGRAMS = \
 	uptime \
 	vmstat \
 	w
+if BUILD_PWAIT
+bin_PROGRAMS += pwait
+endif
 else
 usrbin_exec_PROGRAMS += \
 	ps/pscommand \
@@ -88,6 +91,10 @@ dist_man_MANS += \
 	sysctl.8 \
 	sysctl.conf.5 \
 	ps/ps.1
+
+if BUILD_PWAIT
+dist_man_MANS += pwait.1
+endif
 endif
 
 EXTRA_DIST = \
@@ -201,6 +208,9 @@ free_SOURCES = free.c lib/strutils.c lib/fileutils.c
 pgrep_SOURCES = pgrep.c lib/fileutils.c lib/signals.c
 pkill_SOURCES = pgrep.c lib/fileutils.c lib/signals.c
 pmap_SOURCES = pmap.c lib/fileutils.c
+if BUILD_PWAIT
+pwait_SOURCES = pgrep.c lib/fileutils.c lib/nsutils.c
+endif
 if !CYGWIN
 pwdx_SOURCES = pwdx.c lib/fileutils.c
 pwdx_LDADD= $(CYGWINFLAGS)
diff --git a/configure.ac b/configure.ac
index b8e4938b..f9375d40 100644
--- a/configure.ac
+++ b/configure.ac
@@ -10,6 +10,7 @@ AM_INIT_AUTOMAKE([foreign 1.11 subdir-objects -Wall -Wno-portability tar-pax no-
 AM_SILENT_RULES([yes])
 AC_CONFIG_SRCDIR([free.c])
 AC_CONFIG_HEADERS([config.h])
+AC_LANG([C])
 
 # Checks for programs.
 AC_USE_SYSTEM_EXTENSIONS
@@ -120,6 +121,21 @@ AC_TRY_COMPILE([#include <errno.h>],
 		AC_MSG_RESULT(yes),
 		AC_MSG_RESULT(no))
 
+AC_CHECK_FUNC([pidfd_open], [enable_pwait=yes], [
+  AC_MSG_CHECKING([for __NR_pidfd_open])
+  AC_COMPILE_IFELSE([AC_LANG_SOURCE([
+#include <sys/syscall.h>
+#ifndef __NR_pidfd_open
+#error __NR_pidfd_open not defined
+#endif
+    ])], [enable_pwait=yes], [enable_pwait=no])
+  AC_MSG_RESULT([$enable_pwait])
+])
+if test "$enable_pwait" = yes; then
+  AC_DEFINE([ENABLE_PWAIT], [1], [Enable pwait])
+fi
+AM_CONDITIONAL([BUILD_PWAIT], [test x$enable_pwait = xyes])
+
 dnl watch8bit must be before the AC_ARG_WITH set as it sets up ncurses
 AC_SUBST([WITH_WATCH8BIT])
 AC_ARG_ENABLE([watch8bit],
diff --git a/pgrep.1 b/pgrep.1
index 6de06b58..cc833f28 100644
--- a/pgrep.1
+++ b/pgrep.1
@@ -9,13 +9,16 @@
 .\"
 .TH PGREP "1" "2020-06-04" "procps-ng" "User Commands"
 .SH NAME
-pgrep, pkill \- look up or signal processes based on name and other attributes
+pgrep, pkill, pwait \- look up, signal, or wait for processes based on name and other attributes
 .SH SYNOPSIS
 .B pgrep
 [options] pattern
 .br
 .B pkill
 [options] pattern
+.br
+.B pwait
+[options] pattern
 .SH DESCRIPTION
 .B pgrep
 looks through the currently running processes and lists the process IDs which
@@ -41,6 +44,9 @@ OR
 will send the specified signal (by default
 .BR SIGTERM )
 to each process instead of listing them on stdout.
+.PP
+.B pwait
+will wait for each process instead of listing them on stdout.
 .SH OPTIONS
 .TP
 \fB\-\fR\fIsignal\fP
@@ -54,7 +60,9 @@ only.)
 \fB\-c\fR, \fB\-\-count\fR
 Suppress normal output; instead print a count of matching processes.  When
 count does not match anything, e.g. returns zero, the command will return
-non-zero value.
+non-zero value. Note that for pkill and pwait, the count is the number of
+matching processes, not the processes that were successfully signaled or waited
+for.
 .TP
 \fB\-d\fR, \fB\-\-delimiter\fR \fIdelimiter\fP
 Sets the string used to delimit each process ID in the output (by default a
@@ -77,9 +85,10 @@ is set, the full command line is used.
 \fB\-g\fR, \fB\-\-pgroup\fR \fIpgrp\fP,...
 Only match processes in the process group IDs listed.  Process group 0 is
 translated into
-.BR pgrep 's
+.BR pgrep 's,
+.BR pkill 's,
 or
-.BR pkill 's
+.BR pwait 's
 own process group.
 .TP
 \fB\-G\fR, \fB\-\-group\fR \fIgid\fP,...
@@ -114,9 +123,10 @@ Only match processes whose parent process ID is listed.
 \fB\-s\fR, \fB\-\-session\fR \fIsid\fP,...
 Only match processes whose process session ID is listed.  Session ID 0
 is translated into
-.BR pgrep 's
+.BR pgrep 's,
+.BR pkill 's,
 or
-.BR pkill 's
+.BR pwait 's
 own session ID.
 .TP
 \fB\-t\fR, \fB\-\-terminal\fR \fIterm\fP,...
@@ -134,6 +144,8 @@ symbolical value may be used.
 \fB\-v\fR, \fB\-\-inverse\fR\fR
 Negates the matching.  This option is usually used in
 .BR pgrep 's
+or
+.BR pwait 's
 context.  In
 .BR pkill 's
 context the short option is disabled to avoid accidental usage of the option.
@@ -141,6 +153,8 @@ context the short option is disabled to avoid accidental usage of the option.
 \fB\-w\fR, \fB\-\-lightweight\fR\fR
 Shows all thread ids instead of pids in
 .BR pgrep 's
+or
+.BR pwait 's
 context.  In
 .BR pkill 's
 context this option is disabled.
@@ -152,8 +166,8 @@ match the
 .IR pattern .
 .TP
 \fB\-F\fR, \fB\-\-pidfile\fR \fIfile\fR
-Read \fIPID\fRs from \fIfile\fR.  This option is perhaps more useful for
-.B pkill
+Read \fIPID\fRs from \fIfile\fR.  This option is more useful for
+.BR pkill or pwait
 than
 .BR pgrep .
 .TP
@@ -223,8 +237,8 @@ $ renice +4 $(pgrep chrome)
 .PD 0
 .TP
 0
-One or more processes matched the criteria. For pkill the process must also
-have been successfully signalled.
+One or more processes matched the criteria. For pkill and pwait, one or more
+processes must also have been successfully signalled or waited for.
 .TP
 1
 No processes matched or none of them could be signalled.
@@ -242,9 +256,10 @@ complete command line, /proc/\fIpid\fP/cmdline. Threads may not have the
 same process name as the parent process but will have the same command line.
 .PP
 The running
-.B pgrep
+.BR pgrep ,
+.BR pkill ,
 or
-.B pkill
+.B pwait
 process will never report itself as a
 match.
 .SH BUGS
diff --git a/pgrep.c b/pgrep.c
index 899d49cc..574a5556 100644
--- a/pgrep.c
+++ b/pgrep.c
@@ -38,6 +38,11 @@
 #include <stdbool.h>
 #include <time.h>
 
+#if defined(ENABLE_PWAIT) && !defined(HAVE_PIDFD_OPEN)
+#include <sys/epoll.h>
+#include <sys/syscall.h>
+#endif
+
 /* EXIT_SUCCESS is 0 */
 /* EXIT_FAILURE is 1 */
 #define EXIT_USAGE 2
@@ -80,7 +85,13 @@ enum rel_items {
 	(x) = (x) * 5 / 4 + 4; \
 } while (0)
 
-static int i_am_pkill = 0;
+static enum {
+    PGREP = 0,
+    PKILL,
+#ifdef ENABLE_PWAIT
+    PWAIT,
+#endif
+} prog_mode;
 
 struct el {
     long    num;
@@ -132,17 +143,23 @@ static int __attribute__ ((__noreturn__)) usage(int opt)
     fputs(USAGE_HEADER, fp);
     fprintf(fp, _(" %s [options] <pattern>\n"), program_invocation_short_name);
     fputs(USAGE_OPTIONS, fp);
-    if (i_am_pkill == 0) {
+    switch (prog_mode) {
+    case PGREP:
         fputs(_(" -d, --delimiter <string>  specify output delimiter\n"),fp);
         fputs(_(" -l, --list-name           list PID and process name\n"),fp);
         fputs(_(" -a, --list-full           list PID and full command line\n"),fp);
         fputs(_(" -v, --inverse             negates the matching\n"),fp);
         fputs(_(" -w, --lightweight         list all TID\n"), fp);
-    }
-    if (i_am_pkill == 1) {
+        break;
+    case PKILL:
         fputs(_(" -<sig>, --signal <sig>    signal to send (either number or name)\n"), fp);
         fputs(_(" -q, --queue <value>       integer value to be sent with the signal\n"), fp);
         fputs(_(" -e, --echo                display what is killed\n"), fp);
+#ifdef ENABLE_PWAIT
+    case PWAIT:
+        fputs(_(" -e, --echo                display PIDs before waiting\n"), fp);
+        break;
+#endif
     }
     fputs(_(" -c, --count               count of matching processes\n"), fp);
     fputs(_(" -f, --full                use full process name to match\n"), fp);
@@ -676,6 +693,13 @@ static int signal_option(int *argc, char **argv)
     return -1;
 }
 
+#if defined(ENABLE_PWAIT) && !defined(HAVE_PIDFD_OPEN)
+static int pidfd_open (pid_t pid, unsigned int flags)
+{
+	return syscall(__NR_pidfd_open, pid, flags);
+}
+#endif
+
 static void parse_opts (int argc, char **argv)
 {
     char opts[64] = "";
@@ -720,17 +744,22 @@ static void parse_opts (int argc, char **argv)
         {NULL, 0, NULL, 0}
     };
 
-    if (strstr (program_invocation_short_name, "pkill")) {
+#ifdef ENABLE_PWAIT
+    if (strcmp (program_invocation_short_name, "pwait") == 0) {
+        prog_mode = PWAIT;
+        strcat (opts, "e");
+    } else
+#endif
+    if (strcmp (program_invocation_short_name, "pkill") == 0) {
         int sig;
-        i_am_pkill = 1;
+        prog_mode = PKILL;
         sig = signal_option(&argc, argv);
         if (-1 < sig)
             opt_signal = sig;
-        /* These options are for pkill only */
 	strcat (opts, "eq:");
     } else {
-        /* These options are for pgrep only */
         strcat (opts, "lad:vw");
+        prog_mode = PGREP;
     }
 
     strcat (opts, "LF:cfinoxP:O:g:s:u:U:G:t:r:?Vh");
@@ -868,9 +897,6 @@ static void parse_opts (int argc, char **argv)
         case NS_OPTION:
             opt_ns_pid = atoi(optarg);
             if (opt_ns_pid == 0)
-                usage ('?');
-            ++criteria_count;
-            break;
 		case 'r': /* match by runstate */
 			opt_runstates = xstrdup (optarg);
 			++criteria_count;
@@ -929,6 +955,14 @@ int main (int argc, char **argv)
 {
     struct el *procs;
     int num;
+    int i;
+    int kill_count = 0;
+#ifdef ENABLE_PWAIT
+    int poll_count = 0;
+    int wait_count = 0;
+    int epollfd = epoll_create(1);
+    struct epoll_event ev, events[32];
+#endif
 
 #ifdef HAVE_PROGRAM_INVOCATION_NAME
     program_invocation_name = program_invocation_short_name;
@@ -941,9 +975,18 @@ int main (int argc, char **argv)
     parse_opts (argc, argv);
 
     procs = select_procs (&num);
-    if (i_am_pkill) {
-        int i;
-        int kill_count = 0;
+    switch (prog_mode) {
+    case PGREP:
+        if (opt_count) {
+            fprintf(stdout, "%d\n", num);
+        } else {
+            if (opt_long || opt_longlong)
+                output_strlist (procs,num);
+            else
+                output_numlist (procs,num);
+        }
+        return !num;
+    case PKILL:
         for (i = 0; i < num; i++) {
             if (execute_kill (procs[i].num, opt_signal) != -1) {
                 if (opt_echo)
@@ -959,15 +1002,40 @@ int main (int argc, char **argv)
         if (opt_count)
             fprintf(stdout, "%d\n", num);
         return !kill_count;
-    } else {
-        if (opt_count) {
+#ifdef ENABLE_PWAIT
+    case PWAIT:
+        if (opt_count)
             fprintf(stdout, "%d\n", num);
-        } else {
-            if (opt_long || opt_longlong)
-                output_strlist (procs,num);
-            else
-                output_numlist (procs,num);
+
+        for (i = 0; i < num; i++) {
+            if (opt_echo)
+                printf(_("waiting for %s (pid %lu)\n"), procs[i].str, procs[i].num);
+            int pidfd = pidfd_open(procs[i].num, 0);
+            if (pidfd == -1) {
+                /* ignore ESRCH, same as pkill */
+                if (errno != ESRCH)
+                    xwarn(_("opening pid %ld failed"), procs[i].num);
+                continue;
+            }
+            ev.events = EPOLLIN | EPOLLET;
+            ev.data.fd = pidfd;
+            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, pidfd, &ev) != -1)
+                poll_count++;
+        }
+
+        while (wait_count < poll_count) {
+            int ew = epoll_wait(epollfd, events, sizeof(events)/sizeof(events[0]), -1);
+            if (ew == -1) {
+                if (errno == EINTR)
+                    continue;
+                xwarn(_("epoll_wait failed"));
+            }
+            wait_count += ew;
         }
+
+        return !wait_count;
+#endif
     }
-    return !num; /* exit(EXIT_SUCCESS) if match, otherwise exit(EXIT_FAILURE) */
+    /* Not sure if it is possible to get here */
+    return -1;
 }
diff --git a/pwait.1 b/pwait.1
new file mode 100644
index 00000000..0e94b52c
--- /dev/null
+++ b/pwait.1
@@ -0,0 +1 @@
+.so man1/pgrep.1
-- 
2.40.0