diff options
author | Mateusz Piotrowski <0mp@FreeBSD.org> | 2023-02-01 15:24:59 +0000 |
---|---|---|
committer | Mateusz Piotrowski <0mp@FreeBSD.org> | 2023-02-02 17:34:35 +0000 |
commit | e7ab133648a168c4bf7c11da840663c5581771d8 (patch) | |
tree | 0110d5cfb8bbe1cc60a3493361b90eac7deff1b9 /bin/timeout | |
parent | 0f031350f32b8760e0843a6476d67aa21116103b (diff) | |
download | src-e7ab133648a168c4bf7c11da840663c5581771d8.tar.gz src-e7ab133648a168c4bf7c11da840663c5581771d8.zip |
timeout: Move from /usr/bin to /bin
timeout(1) is used by /etc/rc.d/zfskeys. Unfortunately, having
timeout(1) installed in /usr/bin causes problems when /usr is an
encrypted ZFS partition.
Implementing timeout(1) in sh(1) is not trivial. A more elegant solution
is to move timeout(1) to /bin so that it is available to early services
in the boot process.
PR: 265221
Reviewed by: allanjude, des, imp
Approved by: allanjude, des, imp
Reported by: Ivan <r4@sovserv.ru>
Fixes: 33ff39796ffe Add zfskeys rc.d script for auto-loading encryption keys
MFC after: 1 week
Relnotes: yes
Sponsored by: Modirum MDPay
Sponsored by: Klara Inc.
Differential Revision: https://reviews.freebsd.org/D38344
Diffstat (limited to 'bin/timeout')
-rw-r--r-- | bin/timeout/Makefile | 12 | ||||
-rw-r--r-- | bin/timeout/Makefile.depend | 17 | ||||
-rw-r--r-- | bin/timeout/tests/Makefile | 5 | ||||
-rw-r--r-- | bin/timeout/tests/Makefile.depend | 11 | ||||
-rw-r--r-- | bin/timeout/tests/timeout_test.sh | 215 | ||||
-rw-r--r-- | bin/timeout/timeout.1 | 206 | ||||
-rw-r--r-- | bin/timeout/timeout.c | 361 |
7 files changed, 827 insertions, 0 deletions
diff --git a/bin/timeout/Makefile b/bin/timeout/Makefile new file mode 100644 index 000000000000..2eb88d6827fa --- /dev/null +++ b/bin/timeout/Makefile @@ -0,0 +1,12 @@ +# $FreeBSD$ + +.include <src.opts.mk> + +PROG= timeout + +SYMLINKS= ../..${BINDIR}/timeout /usr/bin/timeout + +HAS_TESTS= +SUBDIR.${MK_TESTS}+= tests + +.include <bsd.prog.mk> diff --git a/bin/timeout/Makefile.depend b/bin/timeout/Makefile.depend new file mode 100644 index 000000000000..6cfaab1c3644 --- /dev/null +++ b/bin/timeout/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/timeout/tests/Makefile b/bin/timeout/tests/Makefile new file mode 100644 index 000000000000..691a27df20b1 --- /dev/null +++ b/bin/timeout/tests/Makefile @@ -0,0 +1,5 @@ +# $FreeBSD$ + +ATF_TESTS_SH= timeout_test + +.include <bsd.test.mk> diff --git a/bin/timeout/tests/Makefile.depend b/bin/timeout/tests/Makefile.depend new file mode 100644 index 000000000000..f80275d86ab1 --- /dev/null +++ b/bin/timeout/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/timeout/tests/timeout_test.sh b/bin/timeout/tests/timeout_test.sh new file mode 100644 index 000000000000..ffddfcafb4bb --- /dev/null +++ b/bin/timeout/tests/timeout_test.sh @@ -0,0 +1,215 @@ +# $FreeBSD$ + +atf_test_case nominal +nominal_head() +{ + atf_set "descr" "Basic tests on timeout(1) utility" +} + +nominal_body() +{ + atf_check \ + -o empty \ + -e empty \ + -s exit:0 \ + timeout 5 true +} + +atf_test_case time_unit +time_unit_head() +{ + atf_set "descr" "Test parsing the default time unit" +} + +time_unit_body() +{ + atf_check \ + -o empty \ + -e empty \ + -s exit:0 \ + timeout 1d true + + atf_check \ + -o empty \ + -e empty \ + -s exit:0 \ + timeout 1h true + + atf_check \ + -o empty \ + -e empty \ + -s exit:0 \ + timeout 1m true + + atf_check \ + -o empty \ + -e empty \ + -s exit:0 \ + timeout 1s true +} + +atf_test_case no_timeout +no_timeout_head() +{ + atf_set "descr" "Test disabled timeout" +} + +no_timeout_body() +{ + atf_check \ + -o empty \ + -e empty \ + -s exit:0 \ + timeout 0 true +} + +atf_test_case exit_numbers +exit_numbers_head() +{ + atf_set "descr" "Test exit numbers" +} + +exit_numbers_body() +{ + atf_check \ + -o empty \ + -e empty \ + -s exit:2 \ + -x timeout 5 sh -c \'exit 2\' + + atf_check \ + -o empty \ + -e empty \ + -s exit:124 \ + timeout .1 sleep 1 + + # With preserv status exit should be 128 + TERM aka 143 + atf_check \ + -o empty \ + -e empty \ + -s exit:143 \ + timeout --preserve-status .1 sleep 10 + + atf_check \ + -o empty \ + -e empty \ + -s exit:124 \ + timeout -s1 -k1 .1 sleep 10 + + atf_check \ + -o empty \ + -e empty \ + -s exit:0 \ + -x sh -c 'trap "" CHLD; exec timeout 10 true' +} + +atf_test_case with_a_child +with_a_child_head() +{ + atf_set "descr" "When starting with a child (coreutils bug#9098)" +} + +with_a_child_body() +{ + out=$(sleep .1 & exec timeout .5 sh -c 'sleep 2; echo foo') + status=$? + test "$out" = "" && test $status = 124 || atf_fail + +} + +atf_test_case invalid_timeout +invalid_timeout_head() +{ + atf_set "descr" "Invalid timeout" +} + +invalid_timeout_body() +{ + atf_check \ + -o empty \ + -e inline:"timeout: invalid duration\n" \ + -s exit:125 \ + timeout invalid sleep 0 + + atf_check \ + -o empty \ + -e inline:"timeout: invalid duration\n" \ + -s exit:125 \ + timeout --kill-after=invalid 1 sleep 0 + + atf_check \ + -o empty \ + -e inline:"timeout: invalid duration\n" \ + -s exit:125 \ + timeout 42D sleep 0 + + atf_check \ + -o empty \ + -e inline:"timeout: invalid duration\n" \ + -s exit:125 \ + timeout 999999999999999999999999999999999999999999999999999999999999d sleep 0 + + atf_check \ + -o empty \ + -e inline:"timeout: invalid duration\n" \ + -s exit:125 \ + timeout 2.34e+5d sleep 0 +} + +atf_test_case invalid_signal +invalid_signal_head() +{ + atf_set "descr" "Invalid signal" +} + +invalid_signal_body() +{ + atf_check \ + -o empty \ + -e inline:"timeout: invalid signal\n" \ + -s exit:125 \ + timeout --signal=invalid 1 sleep 0 +} + +atf_test_case invalid_command +invalid_command_head() +{ + atf_set "descr" "Invalid command" +} + +invalid_command_body() +{ + atf_check \ + -o empty \ + -e inline:"timeout: exec(.): Permission denied\n" \ + -s exit:126 \ + timeout 10 . +} + +atf_test_case no_such_command +no_such_command_head() +{ + atf_set "descr" "No such command" +} + +no_such_command_body() +{ + atf_check \ + -o empty \ + -e inline:"timeout: exec(enoexists): No such file or directory\n" \ + -s exit:127 \ + timeout 10 enoexists +} + +atf_init_test_cases() +{ + atf_add_test_case nominal + atf_add_test_case time_unit + atf_add_test_case no_timeout + atf_add_test_case exit_numbers + atf_add_test_case with_a_child + atf_add_test_case invalid_timeout + atf_add_test_case invalid_signal + atf_add_test_case invalid_command + atf_add_test_case no_such_command +} diff --git a/bin/timeout/timeout.1 b/bin/timeout/timeout.1 new file mode 100644 index 000000000000..27f022c48636 --- /dev/null +++ b/bin/timeout/timeout.1 @@ -0,0 +1,206 @@ +.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD +.\" +.\" Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org> +.\" 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 AUTHOR 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 AUTHOR 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 October 21, 2021 +.Dt TIMEOUT 1 +.Os +.Sh NAME +.Nm timeout +.Nd run a command with a time limit +.Sh SYNOPSIS +.Nm +.Op Fl -signal Ar sig | Fl s Ar sig +.Op Fl -preserve-status +.Op Fl -kill-after Ar time | Fl k Ar time +.Op Fl -foreground +.Ar duration +.Ar command +.Op Ar args ... +.Sh DESCRIPTION +.Nm +starts the +.Ar command +with its +.Ar args . +If the +.Ar command +is still running after +.Ar duration , +it is killed. +By default, +.Dv SIGTERM +is sent. +The special +.Ar duration , +zero, signifies no limit. +Therefore a signal is never sent if +.Ar duration +is 0. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl -preserve-status +Exit with the same status as +.Ar command , +even if it times out and is killed. +.It Fl -foreground +Do not propagate timeout to the children of +.Ar command . +.It Fl s Ar sig , Fl -signal Ar sig +Specify the signal to send on timeout. +By default, +.Dv SIGTERM +is sent. +.It Fl k Ar time , Fl -kill-after Ar time +Send a +.Dv SIGKILL +signal if +.Ar command +is still running after +.Ar time +after the first signal was sent. +.El +.Sh DURATION FORMAT +.Ar duration +and +.Ar time +are non-negative integer or real (decimal) numbers, with an optional +unit-specifying suffix. +Values without an explicit unit are interpreted as seconds. +.Pp +Supported unit symbols are: +.Bl -tag -width indent -compact +.It Cm s +seconds +.It Cm m +minutes +.It Cm h +hours +.It Cm d +days +.El +.Sh EXIT STATUS +If the timeout was not reached, the exit status of +.Ar command +is returned. +.Pp +If the timeout was reached and +.Fl -preserve-status +is set, the exit status of +.Ar command +is returned. +If +.Fl -preserve-status +is not set, an exit status of 124 is returned. +.Pp +If +.Ar command +exits after receiving a signal, the exit status returned is the signal number +plus 128. +.Pp +If +.Ar command +refers to a non-existing program, the exit status returned is 127. +.Pp +If +.Ar command +is an otherwise invalid program, the exit status returned is 126. +.Pp +If an invalid parameter is passed to +.Fl s +or +.Fl k , +the exit status returned is 125. +.Sh EXAMPLES +Run +.Xr sleep 1 +with a time limit of 4 seconds. +Since the command completes in 2 seconds, the exit status is 0: +.Bd -literal -offset indent +$ timeout 4 sleep 2 +$ echo $? +0 +.Ed +.Pp +Run +.Xr sleep 1 +for 4 seconds and terminate process after 2 seconds. +124 is returned since no +.Fl -preserve-status +is used: +.Bd -literal -offset indent +$ timeout 2 sleep 4 +$ echo $? +124 +.Ed +.Pp +Same as above but preserving status. +Exit status is 128 + signal number (15 for +.Va SIGTERM ) : +.Bd -literal -offset indent +$ timeout --preserve-status 2 sleep 4 +$ echo $? +143 +.Ed +.Pp +Same as above but sending +.Va SIGALRM +(signal number 14) instead of +.Va SIGTERM : +.Bd -literal -offset indent +$ timeout --preserve-status -s SIGALRM 2 sleep 4 +$ echo $? +142 +.Ed +.Pp +Try to +.Xr fetch 1 +the PDF version of the +.Fx +Handbook. +Send a +.Va SIGTERM +signal after 1 minute and send a +.Va SIGKILL +signal 5 seconds later if the process refuses to stop: +.Bd -literal -offset indent +$ timeout -k 5s 1m fetch \\ +> https://download.freebsd.org/ftp/doc/en/books/handbook/book.pdf +.Ed +.Sh SEE ALSO +.Xr kill 1 , +.Xr signal 3 +.Sh HISTORY +The +.Nm +command first appeared in +.Fx 10.3 . +.Sh AUTHORS +.An Baptiste Daroussin Aq Mt bapt@FreeBSD.org +and +.An Vsevolod Stakhov Aq Mt vsevolod@FreeBSD.org diff --git a/bin/timeout/timeout.c b/bin/timeout/timeout.c new file mode 100644 index 000000000000..d6b82373c0a1 --- /dev/null +++ b/bin/timeout/timeout.c @@ -0,0 +1,361 @@ +/*- + * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org> + * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org> + * 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 + * in this position and unchanged. + * 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 AUTHOR(S) ``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 AUTHOR(S) 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/procctl.h> +#include <sys/time.h> +#include <sys/wait.h> + +#include <err.h> +#include <errno.h> +#include <getopt.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define EXIT_TIMEOUT 124 + +static sig_atomic_t sig_chld = 0; +static sig_atomic_t sig_term = 0; +static sig_atomic_t sig_alrm = 0; +static sig_atomic_t sig_ign = 0; + +static void +usage(void) +{ + + fprintf(stderr, "Usage: %s [--signal sig | -s sig] [--preserve-status]" + " [--kill-after time | -k time] [--foreground] <duration> <command>" + " <arg ...>\n", getprogname()); + + exit(EXIT_FAILURE); +} + +static double +parse_duration(const char *duration) +{ + double ret; + char *end; + + ret = strtod(duration, &end); + if (ret == 0 && end == duration) + errx(125, "invalid duration"); + + if (end == NULL || *end == '\0') + return (ret); + + if (end != NULL && *(end + 1) != '\0') + errx(125, "invalid duration"); + + switch (*end) { + case 's': + break; + case 'm': + ret *= 60; + break; + case 'h': + ret *= 60 * 60; + break; + case 'd': + ret *= 60 * 60 * 24; + break; + default: + errx(125, "invalid duration"); + } + + if (ret < 0 || ret >= 100000000UL) + errx(125, "invalid duration"); + + return (ret); +} + +static int +parse_signal(const char *str) +{ + int sig, i; + const char *errstr; + + sig = strtonum(str, 1, sys_nsig - 1, &errstr); + + if (errstr == NULL) + return (sig); + if (strncasecmp(str, "SIG", 3) == 0) + str += 3; + + for (i = 1; i < sys_nsig; i++) { + if (strcasecmp(str, sys_signame[i]) == 0) + return (i); + } + + errx(125, "invalid signal"); +} + +static void +sig_handler(int signo) +{ + if (sig_ign != 0 && signo == sig_ign) { + sig_ign = 0; + return; + } + + switch (signo) { + case 0: + case SIGINT: + case SIGHUP: + case SIGQUIT: + case SIGTERM: + sig_term = signo; + break; + case SIGCHLD: + sig_chld = 1; + break; + case SIGALRM: + sig_alrm = 1; + break; + } +} + +static void +set_interval(double iv) +{ + struct itimerval tim; + + memset(&tim, 0, sizeof(tim)); + tim.it_value.tv_sec = (time_t)iv; + iv -= (time_t)iv; + tim.it_value.tv_usec = (suseconds_t)(iv * 1000000UL); + + if (setitimer(ITIMER_REAL, &tim, NULL) == -1) + err(EXIT_FAILURE, "setitimer()"); +} + +int +main(int argc, char **argv) +{ + int ch; + unsigned long i; + int foreground, preserve; + int error, pstat, status; + int killsig = SIGTERM; + pid_t pid, cpid; + double first_kill; + double second_kill; + bool timedout = false; + bool do_second_kill = false; + bool child_done = false; + struct sigaction signals; + struct procctl_reaper_status info; + struct procctl_reaper_kill killemall; + int signums[] = { + -1, + SIGTERM, + SIGINT, + SIGHUP, + SIGCHLD, + SIGALRM, + SIGQUIT, + }; + + foreground = preserve = 0; + second_kill = 0; + + const struct option longopts[] = { + { "preserve-status", no_argument, &preserve, 1 }, + { "foreground", no_argument, &foreground, 1 }, + { "kill-after", required_argument, NULL, 'k'}, + { "signal", required_argument, NULL, 's'}, + { "help", no_argument, NULL, 'h'}, + { NULL, 0, NULL, 0 } + }; + + while ((ch = getopt_long(argc, argv, "+k:s:h", longopts, NULL)) != -1) { + switch (ch) { + case 'k': + do_second_kill = true; + second_kill = parse_duration(optarg); + break; + case 's': + killsig = parse_signal(optarg); + break; + case 0: + break; + case 'h': + default: + usage(); + break; + } + } + + argc -= optind; + argv += optind; + + if (argc < 2) + usage(); + + first_kill = parse_duration(argv[0]); + argc--; + argv++; + + if (!foreground) { + /* Acquire a reaper */ + if (procctl(P_PID, getpid(), PROC_REAP_ACQUIRE, NULL) == -1) + err(EXIT_FAILURE, "Fail to acquire the reaper"); + } + + memset(&signals, 0, sizeof(signals)); + sigemptyset(&signals.sa_mask); + + if (killsig != SIGKILL && killsig != SIGSTOP) + signums[0] = killsig; + + for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) + sigaddset(&signals.sa_mask, signums[i]); + + signals.sa_handler = sig_handler; + signals.sa_flags = SA_RESTART; + + for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) + if (signums[i] != -1 && signums[i] != 0 && + sigaction(signums[i], &signals, NULL) == -1) + err(EXIT_FAILURE, "sigaction()"); + + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + + pid = fork(); + if (pid == -1) + err(EXIT_FAILURE, "fork()"); + else if (pid == 0) { + /* child process */ + signal(SIGTTIN, SIG_DFL); + signal(SIGTTOU, SIG_DFL); + + error = execvp(argv[0], argv); + if (error == -1) { + if (errno == ENOENT) + err(127, "exec(%s)", argv[0]); + else + err(126, "exec(%s)", argv[0]); + } + } + + if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1) + err(EXIT_FAILURE, "sigprocmask()"); + + /* parent continues here */ + set_interval(first_kill); + + for (;;) { + sigemptyset(&signals.sa_mask); + sigsuspend(&signals.sa_mask); + + if (sig_chld) { + sig_chld = 0; + + while ((cpid = waitpid(-1, &status, WNOHANG)) != 0) { + if (cpid < 0) { + if (errno == EINTR) + continue; + else + break; + } else if (cpid == pid) { + pstat = status; + child_done = true; + } + } + if (child_done) { + if (foreground) { + break; + } else { + procctl(P_PID, getpid(), + PROC_REAP_STATUS, &info); + if (info.rs_children == 0) + break; + } + } + } else if (sig_alrm) { + sig_alrm = 0; + + timedout = true; + if (!foreground) { + killemall.rk_sig = killsig; + killemall.rk_flags = 0; + procctl(P_PID, getpid(), PROC_REAP_KILL, + &killemall); + } else + kill(pid, killsig); + + if (do_second_kill) { + set_interval(second_kill); + second_kill = 0; + sig_ign = killsig; + killsig = SIGKILL; + } else + break; + + } else if (sig_term) { + if (!foreground) { + killemall.rk_sig = sig_term; + killemall.rk_flags = 0; + procctl(P_PID, getpid(), PROC_REAP_KILL, + &killemall); + } else + kill(pid, sig_term); + + if (do_second_kill) { + set_interval(second_kill); + second_kill = 0; + sig_ign = killsig; + killsig = SIGKILL; + } else + break; + } + } + + while (!child_done && wait(&pstat) == -1) { + if (errno != EINTR) + err(EXIT_FAILURE, "waitpid()"); + } + + if (!foreground) + procctl(P_PID, getpid(), PROC_REAP_RELEASE, NULL); + + if (WEXITSTATUS(pstat)) + pstat = WEXITSTATUS(pstat); + else if (WIFSIGNALED(pstat)) + pstat = 128 + WTERMSIG(pstat); + + if (timedout && !preserve) + pstat = EXIT_TIMEOUT; + + return (pstat); +} |