diff --git a/.gitignore b/.gitignore index 9d5313b6..da595d33 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ pgrep pidof pkill pmap +pwait procps-ng-*.tar.xz proc/.depend proc/libprocps.la diff --git a/Makefile.am b/Makefile.am index bcc7318d..4a005778 100644 --- a/Makefile.am +++ b/Makefile.am @@ -49,6 +49,9 @@ bin_PROGRAMS = \ uptime \ vmstat \ w +if BUILD_PWAIT +bin_PROGRAMS += pwait +endif else usrbin_exec_PROGRAMS += \ ps/pscommand \ @@ -81,6 +84,10 @@ dist_man_MANS += \ sysctl.8 \ sysctl.conf.5 \ ps/ps.1 + +if BUILD_PWAIT +dist_man_MANS += pwait.1 +endif endif EXTRA_DIST = \ @@ -192,6 +199,9 @@ free_SOURCES = free.c lib/strutils.c lib/fileutils.c pgrep_SOURCES = pgrep.c lib/fileutils.c lib/nsutils.c pkill_SOURCES = pgrep.c lib/fileutils.c lib/nsutils.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 e5ed2aa8..f29fc064 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 @@ -125,6 +126,21 @@ AC_TRY_COMPILE([#include ], 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 +#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 a1810f0d..4f8907b8 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. @@ -241,9 +255,10 @@ the output of /proc/\fIpid\fP/stat. Use the \fB\-f\fR option to match against t complete command line, /proc/\fIpid\fP/cmdline. .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 4b64c1a1..205a1db6 100644 --- a/pgrep.c +++ b/pgrep.c @@ -38,6 +38,11 @@ #include #include +#if defined(ENABLE_PWAIT) && !defined(HAVE_PIDFD_OPEN) +#include +#include +#endif + /* EXIT_SUCCESS is 0 */ /* EXIT_FAILURE is 1 */ #define EXIT_USAGE 2 @@ -60,7 +65,13 @@ (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; @@ -112,17 +123,24 @@ static int __attribute__ ((__noreturn__)) usage(int opt) fputs(USAGE_HEADER, fp); fprintf(fp, _(" %s [options] \n"), program_invocation_short_name); fputs(USAGE_OPTIONS, fp); - if (i_am_pkill == 0) { + switch (prog_mode) { + case PGREP: fputs(_(" -d, --delimiter 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(_(" -, --signal signal to send (either number or name)\n"), fp); fputs(_(" -q, --queue integer value to be sent with the signal\n"), fp); fputs(_(" -e, --echo display what is killed\n"), fp); + break; +#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); @@ -669,11 +687,8 @@ static struct el * select_procs (int *num) xerrx(EXIT_FAILURE, _("internal error")); } - // pkill does not need subtasks! - // this control is still done at - // argparse time, but a further - // control is free - if (opt_threads && !i_am_pkill) { + // pkill and pwait don't support -w, but this is checked in getopt + if (opt_threads) { while (readtask(ptp, &task, &subtask)){ // don't add redundant tasks if (task.XXXID == subtask.XXXID) @@ -722,6 +737,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] = ""; @@ -766,16 +788,21 @@ 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 */ + prog_mode = PGREP; strcat (opts, "lad:vw"); } @@ -974,6 +1001,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; @@ -986,25 +1021,8 @@ int main (int argc, char **argv) parse_opts (argc, argv); procs = select_procs (&num); - if (i_am_pkill) { - int i; - int kill_count = 0; - for (i = 0; i < num; i++) { - if (execute_kill (procs[i].num, opt_signal) != -1) { - if (opt_echo) - printf(_("%s killed (pid %lu)\n"), procs[i].str, procs[i].num); - kill_count++; - continue; - } - if (errno==ESRCH) - /* gone now, which is OK */ - continue; - xwarn(_("killing pid %ld failed"), procs[i].num); - } - if (opt_count) - fprintf(stdout, "%d\n", num); - return !kill_count; - } else { + switch (prog_mode) { + case PGREP: if (opt_count) { fprintf(stdout, "%d\n", num); } else { @@ -1013,6 +1031,59 @@ int main (int argc, char **argv) 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) + printf(_("%s killed (pid %lu)\n"), procs[i].str, procs[i].num); + kill_count++; + continue; + } + if (errno==ESRCH) + /* gone now, which is OK */ + continue; + xwarn(_("killing pid %ld failed"), procs[i].num); + } + if (opt_count) + fprintf(stdout, "%d\n", num); + return !kill_count; + +#ifdef ENABLE_PWAIT + case PWAIT: + if (opt_count) + fprintf(stdout, "%d\n", 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