From: Thomas Weißschuh Date: Sat, 24 Dec 2022 04:27:04 +0000 (+0000) Subject: waitpid: add new command X-Git-Tag: v2.39-rc1~203^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=550d32c49e13c219f4e7f895241e0bdcd6fa55ec;p=thirdparty%2Futil-linux.git waitpid: add new command 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. --- diff --git a/bash-completion/waitpid b/bash-completion/waitpid new file mode 100644 index 0000000000..762c30d711 --- /dev/null +++ b/bash-completion/waitpid @@ -0,0 +1 @@ +complete -F _pids waitpid diff --git a/configure.ac b/configure.ac index c7f0bf972e..06843d2ab8 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/include/c.h b/include/c.h index cbc99669f1..0663774d22 100644 --- a/include/c.h +++ b/include/c.h @@ -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 */ diff --git a/include/strutils.h b/include/strutils.h index 9cf924d7e3..34822e72bb 100644 --- a/include/strutils.h +++ b/include/strutils.h @@ -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, diff --git a/meson.build b/meson.build index c1ff7605d1..e30064e67b 100644 --- a/meson.build +++ b/meson.build @@ -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() diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am index 74bc2d1db0..71548c9f9e 100644 --- a/misc-utils/Makemodule.am +++ b/misc-utils/Makemodule.am @@ -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 diff --git a/misc-utils/meson.build b/misc-utils/meson.build index b600970497..7d21d02c15 100644 --- a/misc-utils/meson.build +++ b/misc-utils/meson.build @@ -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 index 0000000000..864610fd36 --- /dev/null +++ b/misc-utils/waitpid.1.adoc @@ -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 index 0000000000..f9bd62634d --- /dev/null +++ b/misc-utils/waitpid.c @@ -0,0 +1,168 @@ +/* + * waitpid(1) - wait for process termination + * + * Copyright (C) 2022 Thomas Weißschuh + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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); +} diff --git a/tests/commands.sh b/tests/commands.sh index ef9e74d238..c5ea4e8b95 100644 --- a/tests/commands.sh +++ b/tests/commands.sh @@ -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 index 0000000000..79d035658a --- /dev/null +++ b/tests/expected/misc/waitpid @@ -0,0 +1,4 @@ +3 +2 +1 +4 diff --git a/tests/ts/misc/waitpid b/tests/ts/misc/waitpid new file mode 100755 index 0000000000..033bd40719 --- /dev/null +++ b/tests/ts/misc/waitpid @@ -0,0 +1,37 @@ +#!/bin/bash + +# Copyright (C) 2022 Thomas Weißschuh +# +# 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