diff options
Diffstat (limited to 'bin/pwait')
-rw-r--r-- | bin/pwait/Makefile | 11 | ||||
-rw-r--r-- | bin/pwait/Makefile.depend | 17 | ||||
-rw-r--r-- | bin/pwait/pwait.1 | 101 | ||||
-rw-r--r-- | bin/pwait/pwait.c | 195 | ||||
-rw-r--r-- | bin/pwait/tests/Makefile | 5 | ||||
-rw-r--r-- | bin/pwait/tests/Makefile.depend | 11 | ||||
-rw-r--r-- | bin/pwait/tests/pwait_test.sh | 242 |
7 files changed, 582 insertions, 0 deletions
diff --git a/bin/pwait/Makefile b/bin/pwait/Makefile new file mode 100644 index 000000000000..210ae3cdb918 --- /dev/null +++ b/bin/pwait/Makefile @@ -0,0 +1,11 @@ +# $FreeBSD$ + +.include <src.opts.mk> + +PACKAGE=runtime +PROG= pwait + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +.include <bsd.prog.mk> diff --git a/bin/pwait/Makefile.depend b/bin/pwait/Makefile.depend new file mode 100644 index 000000000000..6cfaab1c3644 --- /dev/null +++ b/bin/pwait/Makefile.depend @@ -0,0 +1,17 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + gnu/lib/csu \ + include \ + include/xlocale \ + lib/${CSU_DIR} \ + lib/libc \ + lib/libcompiler_rt \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/pwait/pwait.1 b/bin/pwait/pwait.1 new file mode 100644 index 000000000000..b31700065aca --- /dev/null +++ b/bin/pwait/pwait.1 @@ -0,0 +1,101 @@ +.\" +.\" Copyright (c) 2004-2009, Jilles Tjoelker +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with +.\" or without modification, are permitted provided that the +.\" following conditions are met: +.\" +.\" 1. Redistributions of source code must retain the above +.\" copyright notice, this list of conditions and the +.\" following disclaimer. +.\" 2. Redistributions in binary form must reproduce the +.\" above copyright notice, this list of conditions and +.\" the following disclaimer in the documentation and/or +.\" other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +.\" CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +.\" WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +.\" PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +.\" COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +.\" PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +.\" USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +.\" CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +.\" NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +.\" USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +.\" OF SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd March 7, 2017 +.Dt PWAIT 1 +.Os +.Sh NAME +.Nm pwait +.Nd wait for processes to terminate +.Sh SYNOPSIS +.Nm +.Op Fl t Ar duration +.Op Fl v +.Ar pid +\&... +.Sh DESCRIPTION +The +.Nm +utility will wait until each of the given processes has terminated. +.Pp +The following option is available: +.Bl -tag -width indent +.It Fl t Ar duration +If any process is still running after +.Ar duration , +.Nm +will exit. +The +.Ar duration +value can be integer or decimal numbers. +Values without unit symbols are interpreted as seconds. +.Pp +Supported unit symbols are: +.Bl -tag -width indent -compact +.It s +seconds +.It m +minutes +.It h +hours +.El +.It Fl v +Print the exit status when each process terminates. +.El +.Sh EXIT STATUS +The +.Nm +utility exits 0 on success, and >0 if an error occurs. +.Pp +If the +.Fl t +flag is specified and a timeout occurs, the exit status will be 124. +.Pp +Invalid pids elicit a warning message but are otherwise ignored. +.Sh SEE ALSO +.Xr kill 1 , +.Xr pkill 1 , +.Xr ps 1 , +.Xr wait 1 , +.Xr kqueue 2 +.Sh NOTES +.Nm +is not a substitute for the +.Xr wait 1 +builtin +as it will not clean up any zombies or state in the parent process. +.Sh HISTORY +A +.Nm +command first appeared in SunOS 5.8. diff --git a/bin/pwait/pwait.c b/bin/pwait/pwait.c new file mode 100644 index 000000000000..76989c919371 --- /dev/null +++ b/bin/pwait/pwait.c @@ -0,0 +1,195 @@ +/*- + * Copyright (c) 2004-2009, Jilles Tjoelker + * All rights reserved. + * + * Redistribution and use in source and binary forms, with + * or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * 2. Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/event.h> +#include <sys/time.h> +#include <sys/wait.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +static void +usage(void) +{ + + fprintf(stderr, "usage: pwait [-t timeout] [-v] pid ...\n"); + exit(EX_USAGE); +} + +/* + * pwait - wait for processes to terminate + */ +int +main(int argc, char *argv[]) +{ + struct itimerval itv; + int kq; + struct kevent *e; + int tflag, verbose; + int opt, nleft, n, i, duplicate, status; + long pid; + char *s, *end; + double timeout; + + tflag = verbose = 0; + memset(&itv, 0, sizeof(itv)); + while ((opt = getopt(argc, argv, "t:v")) != -1) { + switch (opt) { + case 't': + tflag = 1; + errno = 0; + timeout = strtod(optarg, &end); + if (end == optarg || errno == ERANGE || + timeout < 0) + errx(EX_DATAERR, "timeout value"); + switch(*end) { + case 0: + case 's': + break; + case 'h': + timeout *= 60; + /* FALLTHROUGH */ + case 'm': + timeout *= 60; + break; + default: + errx(EX_DATAERR, "timeout unit"); + } + if (timeout > 100000000L) + errx(EX_DATAERR, "timeout value"); + itv.it_value.tv_sec = (time_t)timeout; + timeout -= (time_t)timeout; + itv.it_value.tv_usec = + (suseconds_t)(timeout * 1000000UL); + break; + case 'v': + verbose = 1; + break; + default: + usage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (argc == 0) + usage(); + + kq = kqueue(); + if (kq == -1) + err(1, "kqueue"); + + e = malloc((argc + tflag) * sizeof(struct kevent)); + if (e == NULL) + err(1, "malloc"); + nleft = 0; + for (n = 0; n < argc; n++) { + s = argv[n]; + if (!strncmp(s, "/proc/", 6)) /* Undocumented Solaris compat */ + s += 6; + errno = 0; + pid = strtol(s, &end, 10); + if (pid < 0 || *end != '\0' || errno != 0) { + warnx("%s: bad process id", s); + continue; + } + duplicate = 0; + for (i = 0; i < nleft; i++) + if (e[i].ident == (uintptr_t)pid) + duplicate = 1; + if (!duplicate) { + EV_SET(e + nleft, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, + 0, NULL); + if (kevent(kq, e + nleft, 1, NULL, 0, NULL) == -1) + warn("%ld", pid); + else + nleft++; + } + } + + if (tflag) { + /* + * Explicitly detect SIGALRM so that an exit status of 124 + * can be returned rather than 142. + */ + EV_SET(e + nleft, SIGALRM, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL); + if (kevent(kq, e + nleft, 1, NULL, 0, NULL) == -1) + err(EX_OSERR, "kevent"); + /* Ignore SIGALRM to not interrupt kevent(2). */ + signal(SIGALRM, SIG_IGN); + if (setitimer(ITIMER_REAL, &itv, NULL) == -1) + err(EX_OSERR, "setitimer"); + } + while (nleft > 0) { + n = kevent(kq, NULL, 0, e, nleft + tflag, NULL); + if (n == -1) + err(1, "kevent"); + for (i = 0; i < n; i++) { + if (e[i].filter == EVFILT_SIGNAL) { + if (verbose) + printf("timeout\n"); + return (124); + } + if (verbose) { + status = e[i].data; + if (WIFEXITED(status)) + printf("%ld: exited with status %d.\n", + (long)e[i].ident, + WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + printf("%ld: killed by signal %d.\n", + (long)e[i].ident, + WTERMSIG(status)); + else + printf("%ld: terminated.\n", + (long)e[i].ident); + } + --nleft; + } + } + + exit(EX_OK); +} diff --git a/bin/pwait/tests/Makefile b/bin/pwait/tests/Makefile new file mode 100644 index 000000000000..db05b1f1051c --- /dev/null +++ b/bin/pwait/tests/Makefile @@ -0,0 +1,5 @@ +# $FreeBSD$ + +ATF_TESTS_SH= pwait_test + +.include <bsd.test.mk> diff --git a/bin/pwait/tests/Makefile.depend b/bin/pwait/tests/Makefile.depend new file mode 100644 index 000000000000..f80275d86ab1 --- /dev/null +++ b/bin/pwait/tests/Makefile.depend @@ -0,0 +1,11 @@ +# $FreeBSD$ +# Autogenerated - do NOT edit! + +DIRDEPS = \ + + +.include <dirdeps.mk> + +.if ${DEP_RELDIR} == ${_DEP_RELDIR} +# local dependencies - needed for -jN in clean tree +.endif diff --git a/bin/pwait/tests/pwait_test.sh b/bin/pwait/tests/pwait_test.sh new file mode 100644 index 000000000000..0e22c94114ba --- /dev/null +++ b/bin/pwait/tests/pwait_test.sh @@ -0,0 +1,242 @@ +# $FreeBSD$ + +atf_test_case basic +basic_head() +{ + atf_set "descr" "Basic tests on pwait(1) utility" +} + +basic_body() +{ + sleep 1 & + p1=$! + + sleep 5 & + p5=$! + + sleep 10 & + p10=$! + + atf_check \ + -o empty \ + -e empty \ + -s exit:0 \ + timeout --preserve-status 15 pwait $p1 $p5 $p10 + + atf_check \ + -o empty \ + -e inline:"kill: $p1: No such process\n" \ + -s exit:1 \ + kill -0 $p1 + + atf_check \ + -o empty \ + -e inline:"kill: $p5: No such process\n" \ + -s exit:1 \ + kill -0 $p5 + + atf_check \ + -o empty \ + -e inline:"kill: $p10: No such process\n" \ + -s exit:1 \ + kill -0 $p10 + +} + +basic_cleanup() +{ + kill $p1 $p5 $p10 >/dev/null 2>&1 + wait $p1 $p5 $p10 >/dev/null 2>&1 +} + +atf_test_case time_unit +time_unit_head() +{ + atf_set "descr" "Test parsing the timeout unit and value" +} + +time_unit_body() +{ + init=1 + + atf_check \ + -o empty \ + -e inline:"pwait: timeout unit\n" \ + -s exit:65 \ + timeout --preserve-status 2 pwait -t 1d $init + + atf_check \ + -o empty \ + -e inline:"pwait: timeout unit\n" \ + -s exit:65 \ + timeout --preserve-status 2 pwait -t 1d $init + + atf_check \ + -o empty \ + -e inline:"pwait: timeout value\n" \ + -s exit:65 \ + timeout --preserve-status 2 pwait -t -1 $init + + atf_check \ + -o empty \ + -e inline:"pwait: timeout value\n" \ + -s exit:65 \ + timeout --preserve-status 2 pwait -t 100000001 $init + + # These long duration cases are expected to timeout from the + # timeout utility rather than pwait -t. + atf_check \ + -o empty \ + -e empty \ + -s exit:143 \ + timeout --preserve-status 2 pwait -t 100000000 $init + + atf_check \ + -o empty \ + -e empty \ + -s exit:143 \ + timeout --preserve-status 2 pwait -t 1h $init + + atf_check \ + -o empty \ + -e empty \ + -s exit:143 \ + timeout --preserve-status 2 pwait -t 1.5h $init + + atf_check \ + -o empty \ + -e empty \ + -s exit:143 \ + timeout --preserve-status 2 pwait -t 1m $init + + atf_check \ + -o empty \ + -e empty \ + -s exit:143 \ + timeout --preserve-status 2 pwait -t 1.5m $init + + atf_check \ + -o empty \ + -e empty \ + -s exit:143 \ + timeout --preserve-status 2 pwait -t 0 $init + + # The rest are fast enough that pwait -t is expected to trigger + # the timeout. + atf_check \ + -o empty \ + -e empty \ + -s exit:124 \ + timeout --preserve-status 2 pwait -t 1s $init + + atf_check \ + -o empty \ + -e empty \ + -s exit:124 \ + timeout --preserve-status 2 pwait -t 1.5s $init + + atf_check \ + -o empty \ + -e empty \ + -s exit:124 \ + timeout --preserve-status 2 pwait -t 1 $init + + atf_check \ + -o empty \ + -e empty \ + -s exit:124 \ + timeout --preserve-status 2 pwait -t 1.5 $init + + atf_check \ + -o empty \ + -e empty \ + -s exit:124 \ + timeout --preserve-status 2 pwait -t 0.5 $init +} + +atf_test_case timeout_trigger_timeout +timeout_trigger_timeout_head() +{ + atf_set "descr" "Test that exceeding the timeout is detected" +} + +timeout_trigger_timeout_body() +{ + sleep 10 & + p10=$! + + atf_check \ + -o empty \ + -e empty \ + -s exit:124 \ + timeout --preserve-status 6.5 pwait -t 5 $p10 +} + +timeout_trigger_timeout_cleanup() +{ + kill $p10 >/dev/null 2>&1 + wait $p10 >/dev/null 2>&1 +} + +atf_test_case timeout_no_timeout +timeout_no_timeout_head() +{ + atf_set "descr" "Test that not exceeding the timeout continues to wait" +} + +timeout_no_timeout_body() +{ + sleep 10 & + p10=$! + + atf_check \ + -o empty \ + -e empty \ + -s exit:0 \ + timeout --preserve-status 11.5 pwait -t 12 $p10 +} + +timeout_no_timeout_cleanup() +{ + kill $p10 >/dev/null 2>&1 + wait $p10 >/dev/null 2>&1 +} + +atf_test_case timeout_many +timeout_many_head() +{ + atf_set "descr" "Test timeout on many processes" +} + +timeout_many_body() +{ + sleep 1 & + p1=$! + + sleep 5 & + p5=$! + + sleep 10 & + p10=$! + + atf_check \ + -o empty \ + -e empty \ + -s exit:124 \ + timeout --preserve-status 7.5 pwait -t 6 $p1 $p5 $p10 +} + +timeout_many_cleanup() +{ + kill $p1 $p5 $p10 >/dev/null 2>&1 + wait $p1 $p5 $p10 >/dev/null 2>&1 +} + +atf_init_test_cases() +{ + atf_add_test_case basic + atf_add_test_case time_unit + atf_add_test_case timeout_trigger_timeout + atf_add_test_case timeout_no_timeout + atf_add_test_case timeout_many +} |