NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

bin/49053: Import timeout(1) from FreeBSD



>Number:         49053
>Category:       bin
>Synopsis:       Import timeout(1) from FreeBSD
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    bin-bug-people
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Thu Jul 31 01:25:00 +0000 2014
>Originator:     Kamil Rytarowski
>Release:        NetBSD-current 6.99.47
>Organization:
>Environment:
NetBSD 6.99.47 NetBSD 6.99.47 amd64
>Description:
Please add timeout(1) from FreeBSD.

timeout starts the command with its args. If command is still running after 
duration, it is killed.

A timeout utility appeared in a development branch of FreeBSD 11. FreeBSD work 
is compatible with GNU timeout(1) by Padraig Brady, from GNU Coreutils 8.21. 
The timeout(1) utility first appeared in GNU Coreutils 7.0.

This has been discussed at 
http://mail-index.netbsd.org/netbsd-users/2014/07/27/msg015044.html
>How-To-Repeat:
N/A
>Fix:
I'm attaching two patches against src/usr.bin (timeout(1) shall reside in 
/usr/bin).

[PATCH 1/2] Import timeout from FreeBSD r.268745
[PATCH 2/2] Adapt timeout(1) for NetBSD


From 4c42ce105f5cc1ad9c8e84abea32224996793012 Mon Sep 17 00:00:00 2001
From: Kamil Rytarowski <n54%gmx.com@localhost>
Date: Thu, 31 Jul 2014 00:33:24 +0200
Subject: [PATCH 1/2] Import timeout from FreeBSD r.268745

---
 timeout/Makefile  |   5 +
 timeout/timeout.1 | 114 +++++++++++++++++++
 timeout/timeout.c | 333 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 452 insertions(+)
 create mode 100644 timeout/Makefile
 create mode 100644 timeout/timeout.1
 create mode 100644 timeout/timeout.c

diff --git a/timeout/Makefile b/timeout/Makefile
new file mode 100644
index 0000000..9efc91d
--- /dev/null
+++ b/timeout/Makefile
@@ -0,0 +1,5 @@
+# $FreeBSD: head/usr.bin/timeout/Makefile 268745 2014-07-16 09:55:36Z bapt $
+
+PROG=  timeout
+
+.include <bsd.prog.mk>
diff --git a/timeout/timeout.1 b/timeout/timeout.1
new file mode 100644
index 0000000..14dfdb3
--- /dev/null
+++ b/timeout/timeout.1
@@ -0,0 +1,114 @@
+.\" Copyright (c) 2014 Baptiste Daroussin <bapt%FreeBSD.org@localhost>
+.\" 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: head/usr.bin/timeout/timeout.1 268861 2014-07-18 22:56:59Z bapt $
+.\"
+.Dd July 19, 2014
+.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
+.Ao Ar duration Ac
+.Ao Ar command Ac
+.Ao Ar args ... Ac
+.Sh DESCRIPTION
+.Nm
+starts the
+.Ar command
+with its
+.Ar args.
+If
+.Ar command
+is still running after
+.Ar duration ,
+it is killed.
+By default,
+.Ar SIGTERM.
+is sent.
+.Bl -tag -width "-k time, --kill-after time"
+.It Fl -preserve-status
+Always exits with the same status as
+.Ar command
+even if it times out.
+.It Fl -foreground
+Do not propagate timeout to the
+.Ar command
+children.
+.It Fl s Ar sig , Fl -signal Ar sig
+Specify the signal to send on timeout.
+By default,
+.Ar SIGTERM .
+is sent.
+.It Fl k Ar time , Fl -kill-after Ar time
+Send a second kill 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
+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
+.It 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.
+.Sh SEE ALSO
+.Xr kill 1 ,
+.Xr signal 3
diff --git a/timeout/timeout.c b/timeout/timeout.c
new file mode 100644
index 0000000..784bce3
--- /dev/null
+++ b/timeout/timeout.c
@@ -0,0 +1,333 @@
+/*-
+ * Copyright (c) 2014 Baptiste Daroussin <bapt%FreeBSD.org@localhost>
+ * Copyright (c) 2014 Vsevolod Stakhov <vsevolod%FreeBSD.org@localhost>
+ * 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: head/usr.bin/timeout/timeout.c 268763 2014-07-16 13:52:05Z 
bapt $");
+
+#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 <sysexits.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(EX_USAGE);
+}
+
+static double
+parse_duration(const char *duration)
+{
+       double ret;
+       char *end;
+
+       ret = strtod(duration, &end);
+       if (ret == 0 && end == duration)
+               errx(EXIT_FAILURE, "invalid duration");
+
+       if (end == NULL || *end == '\0')
+               return (ret);
+
+       if (end != NULL && *(end + 1) != '\0')
+               errx(EX_USAGE, "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(EX_USAGE, "invalid duration");
+       }
+
+       if (ret < 0 || ret >= 100000000UL)
+               errx(EX_USAGE, "invalid duration");
+
+       return (ret);
+}
+
+static int
+parse_signal(const char *str)
+{
+       int sig, i;
+       const char *errstr;
+
+       sig = strtonum(str, 0, sys_nsig, &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(EX_USAGE, "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(EX_OSERR, "setitimer()");
+}
+
+int
+main(int argc, char **argv)
+{
+       int ch;
+       unsigned long i;
+       int foreground, preserve;
+       int error, pstat, status;
+       int killsig = SIGTERM;
+       pid_t pgid, pid, cpid;
+       double first_kill;
+       double second_kill;
+       bool timedout = false;
+       bool do_second_kill = false;
+       struct sigaction signals;
+       int signums[] = {
+               -1,
+               SIGTERM,
+               SIGINT,
+               SIGHUP,
+               SIGCHLD,
+               SIGALRM,
+               SIGQUIT,
+       };
+
+       foreground = preserve = 0;
+       second_kill = 0;
+       cpid = -1;
+       pgid = -1;
+
+       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) {
+               pgid = setpgid(0,0);
+
+               if (pgid == -1)
+                       err(EX_OSERR, "setpgid()");
+       }
+
+       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(EX_OSERR, "sigaction()");
+
+       signal(SIGTTIN, SIG_IGN);
+       signal(SIGTTOU, SIG_IGN);
+
+       pid = fork();
+       if (pid == -1)
+               err(EX_OSERR, "fork()");
+       else if (pid == 0) {
+               /* child process */
+               signal(SIGTTIN, SIG_DFL);
+               signal(SIGTTOU, SIG_DFL);
+
+               error = execvp(argv[0], argv);
+               if (error == -1)
+                       err(EX_UNAVAILABLE, "exec()");
+       }
+
+       if (sigprocmask(SIG_BLOCK, &signals.sa_mask, NULL) == -1)
+               err(EX_OSERR, "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 = wait(&status)) < 0) && errno == EINTR)
+                               continue;
+
+                       if (cpid == pid) {
+                               pstat = status;
+                               break;
+                       }
+               } else if (sig_alrm) {
+                       sig_alrm = 0;
+
+                       timedout = true;
+                       if (!foreground)
+                               killpg(pgid, killsig);
+                       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)
+                               killpg(pgid, killsig);
+                       else
+                               kill(pid, sig_term);
+
+                       if (do_second_kill) {
+                               set_interval(second_kill);
+                               second_kill = 0;
+                               sig_ign = killsig;
+                               killsig = SIGKILL;
+                       } else
+                               break;
+               }
+       }
+
+       while (cpid != pid  && wait(&pstat) == -1) {
+               if (errno != EINTR)
+                       err(EX_OSERR, "waitpid()");
+       }
+
+       if (WEXITSTATUS(pstat))
+               pstat = WEXITSTATUS(pstat);
+       else if(WIFSIGNALED(pstat))
+               pstat = 128 + WTERMSIG(pstat);
+
+       if (timedout && !preserve)
+               pstat = EXIT_TIMEOUT;
+
+       return (pstat);
+}
-- 
1.9.4





From 449374f43006a7bf373db37659345ca1cb8ad3a3 Mon Sep 17 00:00:00 2001
From: Kamil Rytarowski <n54%gmx.com@localhost>
Date: Thu, 31 Jul 2014 03:05:02 +0200
Subject: [PATCH 2/2] Adapt timeout(1) for NetBSD

---
 Makefile          |  4 ++--
 timeout/Makefile  |  1 +
 timeout/timeout.1 | 11 +++++++++++
 timeout/timeout.c | 43 +++++++++++++++++++++++++++++++++----------
 4 files changed, 47 insertions(+), 12 deletions(-)

diff --git a/Makefile b/Makefile
index ba4a534..e589e78 100644
--- a/Makefile
+++ b/Makefile
@@ -26,8 +26,8 @@ SUBDIR= apply asa at audio audiocfg \
        rup ruptime rusers rwall rwho \
        script sdiff sdpquery sed seq shar shlock \
        showmount shuffle sockstat sort spell split stat su systat \
-       tabs tail talk tcopy tee telnet tftp tic time tip touch tpfmt tput \
-       tr true tset tsort tty ul uname unexpand unifdef \
+       tabs tail talk tcopy tee telnet tftp tic time timeout tip touch tpfmt \
+       tput tr true tset tsort tty ul uname unexpand unifdef \
        uniq units unvis unzip usbhidaction usbhidctl users utoppya \
        uudecode uuencode uuidgen vacation vgrind videoctl vis \
        vmstat vndcompress w \
diff --git a/timeout/Makefile b/timeout/Makefile
index 9efc91d..f96189f 100644
--- a/timeout/Makefile
+++ b/timeout/Makefile
@@ -1,3 +1,4 @@
+# $NetBSD: $
 # $FreeBSD: head/usr.bin/timeout/Makefile 268745 2014-07-16 09:55:36Z bapt $
 
 PROG=  timeout
diff --git a/timeout/timeout.1 b/timeout/timeout.1
index 14dfdb3..a2da5b1 100644
--- a/timeout/timeout.1
+++ b/timeout/timeout.1
@@ -1,3 +1,5 @@
+.\"    $NetBSD: $
+.\"
 .\" Copyright (c) 2014 Baptiste Daroussin <bapt%FreeBSD.org@localhost>
 .\" All rights reserved.
 .\"
@@ -112,3 +114,12 @@ exits after receiving a signal, the exit status returned 
is the signal number pl
 .Sh SEE ALSO
 .Xr kill 1 ,
 .Xr signal 3
+.Sh HISTORY
+A
+.Nm
+utility appeared in a development branch of FreeBSD 11 and was imported into 
NetBSD 7.0.
+FreeBSD work is compatible with GNU
+.Xr timeout 1
+by Padraig Brady, from GNU Coreutils 8.21. The
+.Xr timeout 1
+utility first appeared in GNU Coreutils 7.0.
diff --git a/timeout/timeout.c b/timeout/timeout.c
index 784bce3..7a580b5 100644
--- a/timeout/timeout.c
+++ b/timeout/timeout.c
@@ -1,3 +1,5 @@
+/* $NetBSD: $ */
+
 /*-
  * Copyright (c) 2014 Baptiste Daroussin <bapt%FreeBSD.org@localhost>
  * Copyright (c) 2014 Vsevolod Stakhov <vsevolod%FreeBSD.org@localhost>
@@ -26,7 +28,13 @@
  */
 
 #include <sys/cdefs.h>
+#if !defined(lint)
+#if 0
 __FBSDID("$FreeBSD: head/usr.bin/timeout/timeout.c 268763 2014-07-16 13:52:05Z 
bapt $");
+#else
+__RCSID("$NetBSD: $");
+#endif
+#endif /* not lint */
 
 #include <sys/time.h>
 #include <sys/wait.h>
@@ -34,6 +42,7 @@ __FBSDID("$FreeBSD: head/usr.bin/timeout/timeout.c 268763 
2014-07-16 13:52:05Z b
 #include <err.h>
 #include <errno.h>
 #include <getopt.h>
+#include <limits.h>
 #include <signal.h>
 #include <stdbool.h>
 #include <stdio.h>
@@ -49,7 +58,7 @@ static sig_atomic_t sig_term = 0;
 static sig_atomic_t sig_alrm = 0;
 static sig_atomic_t sig_ign = 0;
 
-static void
+static void __dead
 usage(void)
 {
 
@@ -102,20 +111,32 @@ static int
 parse_signal(const char *str)
 {
        int sig, i;
-       const char *errstr;
-
-       sig = strtonum(str, 0, sys_nsig, &errstr);
+       char *ep;
 
-       if (errstr == NULL)
-               return (sig);
-       if (strncasecmp(str, "SIG", 3) == 0)
+       if (strncasecmp(str, "SIG", 3) == 0) {
                str += 3;
 
-       for (i = 1; i < sys_nsig; i++) {
-               if (strcasecmp(str, sys_signame[i]) == 0)
-                       return (i);
+               for (i = 1; i < sys_nsig; i++) {
+                       if (strcasecmp(str, sys_signame[i]) == 0)
+                               return (i);
+               }
+
+               goto err;
        }
 
+       errno = 0;
+       sig = strtol(str, &ep, 10);
+
+       if (str[0] == '\0' || *ep != '\0')
+               goto err;
+       if (errno == ERANGE && (sig == INT_MAX || sig == INT_MIN))
+               goto err;
+       if (sig >= sys_nsig || sig < 0)
+               goto err;
+
+       return (sig);
+
+err:
        errx(EX_USAGE, "invalid signal");
 }
 
@@ -182,6 +203,8 @@ main(int argc, char **argv)
                SIGQUIT,
        };
 
+       setprogname(argv[0]);
+
        foreground = preserve = 0;
        second_kill = 0;
        cpid = -1;
-- 
1.9.4




Home | Main Index | Thread Index | Old Index