From owner-svn-src-all@FreeBSD.ORG Wed Jul 16 09:55:37 2014 Return-Path: Delivered-To: svn-src-all@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) (using TLSv1 with cipher ADH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id A86AC474; Wed, 16 Jul 2014 09:55:37 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 9471E24BB; Wed, 16 Jul 2014 09:55:37 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.8/8.14.8) with ESMTP id s6G9tb5s084062; Wed, 16 Jul 2014 09:55:37 GMT (envelope-from bapt@svn.freebsd.org) Received: (from bapt@localhost) by svn.freebsd.org (8.14.8/8.14.8/Submit) id s6G9taro084054; Wed, 16 Jul 2014 09:55:36 GMT (envelope-from bapt@svn.freebsd.org) Message-Id: <201407160955.s6G9taro084054@svn.freebsd.org> From: Baptiste Daroussin Date: Wed, 16 Jul 2014 09:55:36 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r268745 - in head/usr.bin: . timeout X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-all@freebsd.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: "SVN commit messages for the entire src tree \(except for " user" and " projects" \)" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 16 Jul 2014 09:55:37 -0000 Author: bapt Date: Wed Jul 16 09:55:36 2014 New Revision: 268745 URL: http://svnweb.freebsd.org/changeset/base/268745 Log: New BSDL timeout(1) utility compatible with GNU timeout it fully passes the GNU timeout regression tests, it is written in a mostly portable way (only signal parsing is relying on non portable structures) Phabric: D377 Added: head/usr.bin/timeout/ head/usr.bin/timeout/Makefile (contents, props changed) head/usr.bin/timeout/timeout.1 (contents, props changed) head/usr.bin/timeout/timeout.c (contents, props changed) Modified: head/usr.bin/Makefile Modified: head/usr.bin/Makefile ============================================================================== --- head/usr.bin/Makefile Wed Jul 16 08:59:44 2014 (r268744) +++ head/usr.bin/Makefile Wed Jul 16 09:55:36 2014 (r268745) @@ -163,6 +163,7 @@ SUBDIR= alias \ ${_tests} \ tftp \ time \ + timeout \ tip \ top \ touch \ Added: head/usr.bin/timeout/Makefile ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.bin/timeout/Makefile Wed Jul 16 09:55:36 2014 (r268745) @@ -0,0 +1,5 @@ +# $FreeBSD$ + +PROG= timeout + +.include Added: head/usr.bin/timeout/timeout.1 ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.bin/timeout/timeout.1 Wed Jul 16 09:55:36 2014 (r268745) @@ -0,0 +1,70 @@ +.\" Copyright (c) 2014 Baptiste Daroussin +.\" 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 July 16, 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 +and kills if it is still runs after +.Ar duration . +.Bl -tag -width "-k time, --kill-after time" +.It Fl -preserve-status +Always exist 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 +Speficy the signal to send on timeout by default +.Ar SIGTERM . +.It Fl k Ar time , Fl -kill-after Ar time +Send a second kill if the +.Ar command +is still running after +.Ar time +seconds after the first signal was sent +.Sh SEE ALSO +.Xr signal 3 , +.Xr kill 1 Added: head/usr.bin/timeout/timeout.c ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/usr.bin/timeout/timeout.c Wed Jul 16 09:55:36 2014 (r268745) @@ -0,0 +1,336 @@ +/*- + * Copyright (c) 2014 Baptiste Daroussin + * Copyright (c) 2014 Vsevolod Stakhov + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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] " + " \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 *err; + + sig = strtonum(str, 0, sys_nsig, &err); + + if (err == 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; + int killedwith; + 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; + + 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); + + sigemptyset(&signals.sa_mask); + + for (;;) { + killedwith = killsig; + 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); +}