]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
waitpid: add new command
authorThomas Weißschuh <thomas@t-8ch.de>
Sat, 24 Dec 2022 04:27:04 +0000 (04:27 +0000)
committerThomas Weißschuh <thomas@t-8ch.de>
Wed, 4 Jan 2023 01:18:33 +0000 (01:18 +0000)
This command implements waiting for the exit of multiple processes.
Especially it allows to wait for process that are not children of the
current process.

In contrast to wait(1P) it does not allow to retrieve the processes exit
codes.

12 files changed:
bash-completion/waitpid [new file with mode: 0644]
configure.ac
include/c.h
include/strutils.h
meson.build
misc-utils/Makemodule.am
misc-utils/meson.build
misc-utils/waitpid.1.adoc [new file with mode: 0644]
misc-utils/waitpid.c [new file with mode: 0644]
tests/commands.sh
tests/expected/misc/waitpid [new file with mode: 0644]
tests/ts/misc/waitpid [new file with mode: 0755]

diff --git a/bash-completion/waitpid b/bash-completion/waitpid
new file mode 100644 (file)
index 0000000..762c30d
--- /dev/null
@@ -0,0 +1 @@
+complete -F _pids waitpid
index c7f0bf972e69db89fffe8e71c03fe49090014d9f..06843d2ab85c8b7a4c228d11d34f2b9fa961f0d1 100644 (file)
@@ -1839,6 +1839,10 @@ UL_BUILD_INIT([fadvise], [check])
 UL_REQUIRES_LINUX([fadvise])
 AM_CONDITIONAL([BUILD_FADVISE], [test "x$build_fadvise" = xyes])
 
+UL_BUILD_INIT([waitpid], [check])
+UL_REQUIRES_LINUX([waitpid])
+AM_CONDITIONAL([BUILD_WAITPID], [test "x$build_waitpid" = xyes])
+
 UL_BUILD_INIT([getopt], [yes])
 AM_CONDITIONAL([BUILD_GETOPT], [test "x$build_getopt" = xyes])
 
index cbc99669f18f62769309c6a0f51703adf6e2fc39..0663774d22092dc0df40492f34801284ade8ac82 100644 (file)
@@ -526,4 +526,6 @@ static inline void print_features(const char **features, const char *prefix)
 # define MAP_ANONYMOUS  (MAP_ANON)
 #endif
 
+#define SINT_MAX(t) (((size_t) 1 << (sizeof(t) * 8 - 1)) - 1)
+
 #endif /* UTIL_LINUX_C_H */
index 9cf924d7e39726b87518031fca68fa600736f514..34822e72bb1addd3fe29d7bd7368b446b2ef4ce3 100644 (file)
@@ -43,6 +43,7 @@ extern double strtod_or_err(const char *str, const char *errmesg);
 extern long double strtold_or_err(const char *str, const char *errmesg);
 
 #define strtol_or_err(_s, _e)  (long) str2num_or_err(_s, 10, _e, LONG_MIN, LONG_MAX)
+#define strtopid_or_err(_s, _e)        (pid_t) str2num_or_err(_s, 10, _e, 1, SINT_MAX(pid_t))
 #define strtoul_or_err(_s, _e) (unsigned long) str2unum_or_err(_s, 10, _e, ULONG_MAX)
 
 extern void strtotimeval_or_err(const char *str, struct timeval *tv,
index c1ff7605d1027ae4b707b615790128e97ecd849e..e30064e67b28b59d2d0ff18079b33c23bea48acc 100644 (file)
@@ -2802,6 +2802,19 @@ if not is_disabler(exe)
   bashcompletions += ['fadvise']
 endif
 
+exe = executable(
+  'waitpid',
+  waitpid_sources,
+  include_directories : includes,
+  link_with : [lib_common],
+  install_dir : usrbin_exec_dir,
+  install : true)
+if not is_disabler(exe)
+  exes += exe
+  manadocs += ['misc-utils/waitpid.1.adoc']
+  bashcompletions += ['waitpid']
+endif
+
 ############################################################
 
 opt = not get_option('build-schedutils').disabled()
index 74bc2d1db0f72237f326bbdabe9c0507e0bdfd1f..71548c9f9ef2d88a8959b8b0b3b7392adb3b1dd4 100644 (file)
@@ -288,3 +288,12 @@ fadvise_SOURCES = misc-utils/fadvise.c
 fadvise_LDADD = $(LDADD) libcommon.la
 fadvise_CFLAGS = $(AM_CFLAGS)
 endif
+
+if BUILD_WAITPID
+usrbin_exec_PROGRAMS += waitpid
+MANPAGES += misc-utils/waitpid.1
+dist_noinst_DATA += misc-utils/waitpid.1.adoc
+waitpid_SOURCES = misc-utils/waitpid.c
+waitpid_LDADD = $(LDADD) libcommon.la
+waitpid_CFLAGS = $(AM_CFLAGS)
+endif
index b6009704970aa1b7446e40379a368f9b7da3572f..7d21d02c15abee3837834fbf4ba1005d5e082614 100644 (file)
@@ -161,3 +161,7 @@ pipesz_sources = files(
 fadvise_sources = files(
   'fadvise.c',
 )
+
+waitpid_sources = files(
+  'waitpid.c',
+)
diff --git a/misc-utils/waitpid.1.adoc b/misc-utils/waitpid.1.adoc
new file mode 100644 (file)
index 0000000..864610f
--- /dev/null
@@ -0,0 +1,56 @@
+//po4a: entry man manual
+= waitpid(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: waitpid
+
+== NAME
+
+waitpid - utility to wait for arbitrary processes
+
+== SYNOPSIS
+
+*waitpid* [-v] pid...
+
+== DESCRIPTION
+
+*waitpid* is a simple command to wait for arbitrary non-child processes.
+
+It exits after all processes whose PIDs have been passed as arguments have
+exited.
+
+== OPTIONS
+
+*-v*, *--verbose*::
+Be more verbose.
+
+include::man-common/help-version.adoc[]
+
+== EXIT STATUS
+
+*waitpid* has the following exit status values:
+
+*0*::
+success
+*1*::
+unspecified failure
+*2*::
+system does not provide necessary functionality
+
+== AUTHORS
+
+mailto:thomas@t-8ch.de[Thomas Weißschuh]
+
+== SEE ALSO
+
+*waitpid*(2) *wait*(1P)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/misc-utils/waitpid.c b/misc-utils/waitpid.c
new file mode 100644 (file)
index 0000000..f9bd626
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * waitpid(1) - wait for process termination
+ *
+ * Copyright (C) 2022 Thomas Weißschuh <thomas@t-8ch.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <sys/epoll.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <getopt.h>
+
+#include "pidfd-utils.h"
+#include "c.h"
+#include "nls.h"
+#include "xalloc.h"
+#include "strutils.h"
+#include "exitcodes.h"
+
+#define err_nosys(exitcode, ...) \
+       err(errno == ENOSYS ? EXIT_NOTSUPP : exitcode, __VA_ARGS__)
+
+static bool verbose = false;
+
+static pid_t *parse_pids(size_t n_strings, char * const *strings)
+{
+       pid_t *pids = xcalloc(n_strings, sizeof(*pids));
+
+       for (size_t i = 0; i < n_strings; i++)
+               pids[i] = strtopid_or_err(strings[i], _("failed to parse pid"));
+
+       return pids;
+}
+
+static int *open_pidfds(size_t n_pids, pid_t *pids)
+{
+       int *pidfds = xcalloc(n_pids, sizeof(*pidfds));
+
+       for (size_t i = 0; i < n_pids; i++) {
+               pidfds[i] = pidfd_open(pids[i], 0);
+               if (pidfds[i] == -1)
+                       err_nosys(EXIT_FAILURE, _("could not open pid %u"), pids[i]);
+       }
+
+       return pidfds;
+}
+
+static void add_listeners(int epll, size_t n_pids, int * const pidfds)
+{
+       for (size_t i = 0; i < n_pids; i++) {
+               struct epoll_event evt = {
+                       .events = EPOLLIN,
+                       .data = { .u64 = i },
+               };
+               if (epoll_ctl(epll, EPOLL_CTL_ADD, pidfds[i], &evt))
+                       err_nosys(EXIT_FAILURE, _("could not add listener"));
+       }
+}
+
+static void wait_for_exits(int epll, size_t n_pids, pid_t * const pids, int * const pidfds)
+{
+       while (n_pids) {
+               struct epoll_event evt;
+               int ret, fd;
+
+               ret = epoll_wait(epll, &evt, 1, -1);
+               if (ret == -1) {
+                       if (errno == EINTR)
+                               continue;
+                       else
+                               err_nosys(EXIT_FAILURE, _("failure during wait"));
+               }
+               if (verbose)
+                       printf(_("PID %d finished\n"), pids[evt.data.u64]);
+               assert((size_t) ret <= n_pids);
+               fd = pidfds[evt.data.u64];
+               epoll_ctl(epll, EPOLL_CTL_DEL, fd, NULL);
+               n_pids -= ret;
+       }
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+       FILE *out = stdout;
+
+       fputs(USAGE_HEADER, out);
+       fprintf(out, _(" %s [options] pid...\n"), program_invocation_short_name);
+
+       fputs(USAGE_OPTIONS, out);
+       fputs(_(" -v, --verbose         be more verbose\n"), out);
+
+       fputs(USAGE_SEPARATOR, out);
+       fprintf(out, USAGE_HELP_OPTIONS(23));
+
+       fprintf(out, USAGE_MAN_TAIL("waitpid(1)"));
+
+       exit(EXIT_SUCCESS);
+}
+
+static int parse_options(int argc, char **argv)
+{
+       int c;
+       static const struct option longopts[] = {
+               { "verbose", no_argument, NULL, 'v' },
+               { "version", no_argument, NULL, 'V' },
+               { "help",    no_argument, NULL, 'h' },
+               { NULL, 0, NULL, 0 },
+       };
+
+       while ((c = getopt_long (argc, argv, "vVh", longopts, NULL)) != -1) {
+               switch (c) {
+               case 'v':
+                       verbose = true;
+                       break;
+               case 'V':
+                       print_version(EXIT_SUCCESS);
+               case 'h':
+                       usage();
+               default:
+                       errtryhelp(EXIT_FAILURE);
+               }
+       }
+
+       return optind;
+}
+
+int main(int argc, char **argv)
+{
+       int pid_idx, epoll;
+       size_t n_pids;
+       int *pidfds;
+
+       setlocale(LC_ALL, "");
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+
+       pid_idx = parse_options(argc, argv);
+       n_pids = argc - pid_idx;
+       if (!n_pids)
+               return EXIT_FAILURE;
+
+       pid_t *pids = parse_pids(argc - pid_idx, argv + pid_idx);
+
+       pidfds = open_pidfds(n_pids, pids);
+       epoll = epoll_create(n_pids);
+       if (epoll == -1)
+               err_nosys(EXIT_FAILURE, _("could not create epoll"));
+
+       add_listeners(epoll, n_pids, pidfds);
+       wait_for_exits(epoll, n_pids, pids, pidfds);
+}
index ef9e74d238197f17954b2510f8bc66578b2d6fa7..c5ea4e8b955af7b5cf03dfb3dc50015122429d49 100644 (file)
@@ -114,6 +114,7 @@ TS_CMD_UTMPDUMP=${TS_CMD_UTMPDUMP-"${ts_commandsdir}utmpdump"}
 TS_CMD_UUIDD=${TS_CMD_UUIDD-"${ts_commandsdir}uuidd"}
 TS_CMD_UUIDGEN=${TS_CMD_UUIDGEN-"${ts_commandsdir}uuidgen"}
 TS_CMD_UUIDPARSE=${TS_CMD_UUIDPARSE-"${ts_commandsdir}uuidparse"}
+TS_CMD_WAITPID=${TS_CMD_WAITPID-"${ts_commandsdir}waitpid"}
 TS_CMD_WHEREIS=${TS_CMD_WHEREIS-"${ts_commandsdir}whereis"}
 TS_CMD_WIPEFS=${TS_CMD_WIPEFS-"${ts_commandsdir}wipefs"}
 TS_CMD_CHRT=${TS_CMD_CHRT-"${ts_commandsdir}chrt"}
diff --git a/tests/expected/misc/waitpid b/tests/expected/misc/waitpid
new file mode 100644 (file)
index 0000000..79d0356
--- /dev/null
@@ -0,0 +1,4 @@
+3
+2
+1
+4
diff --git a/tests/ts/misc/waitpid b/tests/ts/misc/waitpid
new file mode 100755 (executable)
index 0000000..033bd40
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+# Copyright (C) 2022 Thomas Weißschuh <thomas@t-8ch.de>
+#
+# This file is part of util-linux.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This file is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="waitpid"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_WAITPID"
+
+(sleep 0.2; echo 1 >> "$TS_OUTPUT") &
+BG1="$!"
+
+(sleep 0.1; echo 2 >> "$TS_OUTPUT") &
+BG2="$!"
+
+echo 3 >> "$TS_OUTPUT"
+"$TS_CMD_WAITPID" "$BG1" "$BG2"
+ts_skip_exitcode_not_supported
+
+echo 4 >> "$TS_OUTPUT"
+
+ts_finalize