From 4496c94091f8a6bc95fc1c0868eba632fae99ba3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?P=C3=A1draig=20Brady?= Date: Wed, 6 Jul 2011 23:17:10 +0100 Subject: [PATCH] timeout: add --foreground to support interactive commands Or more accurately, commands not started from the shell prompt, that are interactive, or need to receive Ctrl-C etc. from the terminal. * doc/coreutils.texi (timeout invocation): Document --foreground. * src/timeout.c (main): Set the foreground flag and don't create a separate group. (cleanup): Only send a signal directly to the monitored command when the foreground flag is set. (usage): Describe --foreground. * tests/misc/timeout-group: Add a new test. * tests/Makefile.am: Reference new test. NEWS: Mention the new option. Reported by Shay Shimony Analysis by Alan Curry Fix suggested by Paul Eggert --- NEWS | 4 ++++ doc/coreutils.texi | 17 ++++++++++++++ src/timeout.c | 36 +++++++++++++++++++++++------- tests/Makefile.am | 1 + tests/misc/timeout-group | 58 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 8 deletions(-) create mode 100755 tests/misc/timeout-group diff --git a/NEWS b/NEWS index 32dab7a..a4b11ed 100644 --- a/NEWS +++ b/NEWS @@ -33,6 +33,10 @@ GNU coreutils NEWS -*- outline -*- tool exit non-zero for any invalid input line, rather than just warning. This also affects sha1sum, sha224sum, sha384sum and sha512sum. + timeout accepts a new --foreground option, to support commands not started + directly from a shell prompt, where the command is interactive or needs to + receive signals initiated from the terminal. + ** Improvements shuf outputs small subsets of large permutations much more efficiently. diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 1ab8a92..424446c 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -15658,6 +15658,23 @@ The program accepts the following options. Also see @ref{Common options}. Options must precede operands. @table @samp +@itemx --foreground +@opindex --foreground +Don't create a separate background program group, so that +the managed @var{command} can use the foreground TTY normally. +This is needed to support timing out commands not started +directly from an interactive shell, in two situations. +@enumerate +@item +@var{command} is interactive and needs to read from the terminal for example +@item +the user wants to support sending signals directly to @var{command} +from the terminal (like Ctrl-C for example) +@end enumerate + +Note in this mode of operation, any children of @var{command} +will not be timed out. + @item -k @var{duration} @itemx --kill-after=@var{duration} @opindex -k diff --git a/src/timeout.c b/src/timeout.c index a686225..8f0980b 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -67,11 +67,19 @@ static int term_signal = SIGTERM; /* same default as kill command. */ static int monitored_pid; static int sigs_to_ignore[NSIG]; /* so monitor can ignore sigs it resends. */ static unsigned long kill_after; +static bool foreground; /* whether to use another program group. */ + +/* for long options with no corresponding short option, use enum */ +enum +{ + FOREGROUND_OPTION = CHAR_MAX + 1 +}; static struct option const long_options[] = { {"kill-after", required_argument, NULL, 'k'}, {"signal", required_argument, NULL, 's'}, + {"foreground", no_argument, NULL, FOREGROUND_OPTION}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} @@ -96,6 +104,8 @@ cleanup (int sig) } if (monitored_pid) { + int where = foreground ? monitored_pid : 0; + if (sigs_to_ignore[sig]) { sigs_to_ignore[sig] = 0; @@ -108,9 +118,10 @@ cleanup (int sig) alarm (kill_after); kill_after = 0; /* Don't let later signals reset kill alarm. */ } - send_sig (0, sig); + + send_sig (where, sig); if (sig != SIGKILL && sig != SIGCONT) - send_sig (0, SIGCONT); + send_sig (where, SIGCONT); } else /* we're the child or the child is not exec'd yet. */ _exit (128 + sig); @@ -134,13 +145,17 @@ Start COMMAND, and kill it if still running after DURATION.\n\ Mandatory arguments to long options are mandatory for short options too.\n\ "), stdout); fputs (_("\ + --foreground\n\ + When not running timeout directly from a shell prompt,\n\ + allow COMMAND to read from the TTY and receive TTY signals.\n\ + In this mode, children of COMMAND will not be timed out.\n\ -k, --kill-after=DURATION\n\ - also send a KILL signal if COMMAND is still running\n\ - this long after the initial signal was sent.\n\ + also send a KILL signal if COMMAND is still running\n\ + this long after the initial signal was sent.\n\ -s, --signal=SIGNAL\n\ - specify the signal to be sent on timeout.\n\ - SIGNAL may be a name like `HUP' or a number.\n\ - See `kill -l` for a list of signals\n"), stdout); + specify the signal to be sent on timeout.\n\ + SIGNAL may be a name like `HUP' or a number.\n\ + See `kill -l` for a list of signals\n"), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); @@ -266,6 +281,10 @@ main (int argc, char **argv) usage (EXIT_CANCELED); break; + case FOREGROUND_OPTION: + foreground = true; + break; + case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); @@ -287,7 +306,8 @@ main (int argc, char **argv) Note we don't just put the child in a separate group as then we would need to worry about foreground and background groups and propagating signals between them. */ - setpgid (0, 0); + if (!foreground) + setpgid (0, 0); /* Setup handlers before fork() so that we handle any signals caused by child, without races. */ diff --git a/tests/Makefile.am b/tests/Makefile.am index f8fbd38..ebd1b11 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -277,6 +277,7 @@ TESTS = \ misc/tee-dash \ misc/test-diag \ misc/timeout \ + misc/timeout-group \ misc/timeout-parameters \ misc/tr \ misc/tr-case-class \ diff --git a/tests/misc/timeout-group b/tests/misc/timeout-group new file mode 100755 index 0000000..0c3caa0 --- /dev/null +++ b/tests/misc/timeout-group @@ -0,0 +1,58 @@ +#!/bin/sh +# test program group handling + +# Copyright (C) 2011 Free Software Foundation, Inc. + +# 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 3 of the License, or +# (at your option) any later version. + +# This program 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. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +. "${srcdir=.}/init.sh"; path_prepend_ ../src +print_ver_ timeout + +# construct a program group hierarchy as follows: +# timeout-group - foreground group +# group.sh - separate group +# timeout.cmd - same group as group.sh +# +# We then send a SIGINT to the "separate group" +# to simulate what happens when a Ctrl-C +# is sent to the foreground group. + +setsid true || skip_ "setsid required to control groups" + +cat > timeout.cmd <<\EOF +#!/bin/sh +trap 'touch int.received; exit' INT +touch timeout.running +sleep 10 +EOF +chmod a+x timeout.cmd + +cat > group.sh <<\EOF +#!/bin/sh +timeout --foreground 5 ./timeout.cmd& +wait +EOF +chmod a+x group.sh + +# Start above script in its own group. +# We could use timeout for this, but that assumes an implementation. +setsid ./group.sh & +until test -e timeout.running; do sleep .1; done +# Simulate a Ctrl-C to the group to test timely exit +# Note dash doesn't support signalling groups (a leading -) +env kill -INT -- -$! +wait +test -e int.received || fail=1 + +Exit $fail -- 2.7.4