>From 3542f1762c9f14e2275fe5e61d5d7f6275b420a9 Mon Sep 17 00:00:00 2001
From: Assaf Gordon
Date: Fri, 15 Feb 2019 12:31:48 -0700
Subject: [PATCH] env: new options -p/--default-signal=SIG/--ignore-signal=SIG
New options to set signal handlers to default (SIG_DFL) or ignore
(SIG_IGN) This is useful to overcome POSIX limitation that shell must
not override inherited signal state, e.g. the second 'trap' here is
a no-op:
trap '' PIPE && sh -c 'trap - PIPE ; seq inf | head -n1'
Instead use:
trap '' PIPE && sh -c 'env -p seq inf | head -n1'
Similarly, the following will prevent CTRL-C from terminating the
program:
env --ignore-signal=INT seq inf > /dev/null
See https://bugs.gnu.org/34488#8 .
* NEWS: Mention new options.
* doc/coreutils.texi (env invocation): Document new options.
* man/env.x: Add example of --default-signal=SIG usage.
* src/env.c (signals): New global variable.
(shortopts,longopts): Add new options.
(usage): Print new options.
(parse_signal_params): Parse comma-separated list of signals, store in
signals variable.
(reset_signal_handlers): Set each signal to SIG_DFL/SIG_IGN.
(main): Process new options.
* src/local.mk (src_env_SOURCES): Add operand2sig.c.
* tests/misc/env-signal-handler.sh: New test.
* tests/local.mk (all_tests): Add new test.
---
NEWS | 3 +
doc/coreutils.texi | 43 ++++++++++++
man/env.x | 35 ++++++++++
src/env.c | 127 +++++++++++++++++++++++++++++++++-
src/local.mk | 1 +
tests/local.mk | 1 +
tests/misc/env-signal-handler.sh | 146 +++++++++++++++++++++++++++++++++++++++
7 files changed, 355 insertions(+), 1 deletion(-)
create mode 100755 tests/misc/env-signal-handler.sh
diff --git a/NEWS b/NEWS
index fdde47593..5a8e8a3de 100644
--- a/NEWS
+++ b/NEWS
@@ -67,6 +67,9 @@ GNU coreutils NEWS -*- outline -*-
test now supports the '-N FILE' unary operator (like e.g. bash) to check
whether FILE exists and has been modified since it was last read.
+ env now supports '--default-singal[=SIG]' and '--ignore-signal[=SIG]'
+ options to set signal handlers before executing a program.
+
** New commands
basenc is added to complement existing base64,base32 commands,
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index be35de490..57b209e07 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -17227,6 +17227,49 @@ chroot /chroot env --chdir=/srv true
env --chdir=/build FOO=bar timeout 5 true
@end example
address@hidden address@hidden
+Reset signal @var{sig} to its default signal handler. Without @var{sig} all
+known signals are reset to their defaults. Multiple signals can be
+comma-separated. The following command runs @command{seq} with SIGINT and
+SIGPIPE set to their default (which is to terminate the program):
+
address@hidden
+env --default-signal=PIPE,INT seq 1000 | head -n1
address@hidden example
+
+In the following example:
+
address@hidden
+trap '' PIPE && sh -c 'trap - PIPE ; seq inf | head -n1'
address@hidden example
+
+The first trap command sets SIGPIPE to ignore. The second trap command
+ostensibly sets it back to its default, but POSIX mandates that the shell
+must not change inherited state of the signal - so it is a no-op.
+
+Using @option{--default-signal=PIPE} (or its shortcut @option{-p}) can be
+used to force the signal to its default behavior:
+
address@hidden
+trap '' PIPE && sh -c "env -p seq inf | head -n1'
address@hidden example
+
+
address@hidden address@hidden
+Ignore signal @var{sig} when running a program. Without @var{sig} all
+known signals are set to ignore. Multiple signals can be
+comma-separated. The following command runs @command{seq} with SIGINT set
+to be ignored - pressing @kbd{Ctrl-C} will not terminate it:
+
address@hidden
+env --ignore-signal=INT seq inf > /dev/null
address@hidden example
+
+
address@hidden -p
+Equivalent to @option{--default-signal=PIPE} - sets SIGPIPE to its default
+behavior (terminate a program upon SIGPIPE).
+
@item -v
@itemx --debug
@opindex -v
diff --git a/man/env.x b/man/env.x
index 8eea79655..5e0ef975e 100644
--- a/man/env.x
+++ b/man/env.x
@@ -37,3 +37,38 @@ parameter the script will likely fail with:
.RE
.PP
See the full documentation for more details.
+.PP
+.SS "\-\-default-signal[=SIG]" to 'untrap' a singal
+This option allows setting a signal handler to its default
+action. This is useful to reset a signal after setting it
+to 'ignore' using the shell's trap command.
+
+In the following example:
+.PP
+.RS
+.nf
+trap '' PIPE && sh \-c 'trap \- PIPE ; seq inf | head \-n1'
+.fi
+.RE
+.PP
+The first
+.B trap
+command sets SIGPIPE to ignore.
+The second
+.B trap
+command ostensibly sets it back to its default, but POSIX mandates that the
+shell must not change inherited state of the signal - so it is a no-op.
+.PP
+Using
+.B \-\-default-signal=PIPE
+(or its shortcut
+.B \-p
+)
+can be used to force the signal to its default behavior:
+.PP
+.RS
+.nf
+trap '' PIPE && sh \-c "env \-p seq inf | head \-n1'
+.fi
+.RE
+.PP
diff --git a/src/env.c b/src/env.c
index 3a1a3869e..6c20ac40c 100644
--- a/src/env.c
+++ b/src/env.c
@@ -21,12 +21,15 @@
#include
#include
#include
+#include
#include
#include "system.h"
#include "die.h"
#include "error.h"
+#include "operand2sig.h"
#include "quote.h"
+#include "sig2str.h"
/* The official name of this program (e.g., no 'g' prefix). */
#define PROGRAM_NAME "env"
@@ -48,7 +51,27 @@ static bool dev_debug;
static char *varname;
static size_t vnlen;
-static char const shortopts[] = "+C:iS:u:v0 \t";
+/* Possible actions on each signal. */
+enum SIGNAL_MODE {
+ UNCHANGED = 0,
+ DEFAULT, /* Set to default handler (SIG_DFL). */
+ DEFAULT_NOERR, /* ditto, but ignore sigaction(2) errors. */
+ IGNORE, /* Set to ignore (SIG_IGN). */
+ IGNORE_NOERR /* ditto, but ignore sigaction(2) errors. */
+};
+static enum SIGNAL_MODE signals[SIGNUM_BOUND];
+
+
+
+static char const shortopts[] = "+C:ipS:u:v0 \t";
+
+/* For long options that have no equivalent short option, use a
+ non-character as a pseudo short option, starting with CHAR_MAX + 1. */
+enum
+{
+ DEFAULT_SIGNAL_OPTION = CHAR_MAX + 1,
+ IGNORE_SIGNAL_OPTION
+};
static struct option const longopts[] =
{
@@ -56,6 +79,8 @@ static struct option const longopts[] =
{"null", no_argument, NULL, '0'},
{"unset", required_argument, NULL, 'u'},
{"chdir", required_argument, NULL, 'C'},
+ {"default-signal", optional_argument, NULL, DEFAULT_SIGNAL_OPTION},
+ {"ignore-signal", optional_argument, NULL, IGNORE_SIGNAL_OPTION},
{"debug", no_argument, NULL, 'v'},
{"split-string", required_argument, NULL, 'S'},
{GETOPT_HELP_OPTION_DECL},
@@ -88,8 +113,23 @@ Set each NAME to VALUE in the environment and run COMMAND.\n\
-C, --chdir=DIR change working directory to DIR\n\
"), stdout);
fputs (_("\
+ --default-signal[=SIG] reset signal SIG to its default signal handler.\n\
+ without SIG, all known signals are included.\n\
+ multiple signals can be comma-separated.\n\
+"), stdout);
+ fputs (_("\
+ --ignore-signal[=SIG] set signal SIG to be IGNORED.\n\
+ without SIG, all known signals are included.\n\
+ multiple signals can be comma-separated.\n\
+"), stdout);
+ fputs (_("\
-S, --split-string=S process and split S into separate arguments;\n\
used to pass multiple arguments on shebang lines\n\
+"), stdout);
+ fputs (_("\
+ -p same as --default-signal=PIPE\n\
+"), stdout);
+ fputs (_("\
-v, --debug print verbose information for each processing step\n\
"), stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
@@ -525,6 +565,79 @@ parse_split_string (const char* str, int /*out*/ *orig_optind,
*orig_optind = 0; /* tell getopt to restart from first argument */
}
+static void
+parse_signal_params (const char* optarg, bool set_default)
+{
+ char signame[SIG2STR_MAX];
+ char *opt_sig;
+ char *optarg_writable;
+
+ if (!optarg)
+ {
+ /* without an argument, reset all signals.
+ Some signals cannot be set to ignore or default (e.g., SIGKILL,
+ SIGSTOP on most OSes, and SIGCONT on AIX.) - so ignore errors. */
+ for (int i = 0 ; i < SIGNUM_BOUND; ++i)
+ if (sig2str (i, signame) == 0)
+ signals[i] = set_default ? DEFAULT_NOERR : IGNORE_NOERR;
+ return;
+ }
+
+ optarg_writable = xstrdup (optarg);
+
+ opt_sig = strtok (optarg_writable, ",");
+ while (opt_sig)
+ {
+ int signum = operand2sig (opt_sig, signame);
+ if (signum < 0)
+ usage (exit_failure);
+
+ signals[signum] = set_default ? DEFAULT : IGNORE;
+
+ opt_sig = strtok (NULL, ",");
+ }
+
+ free (optarg_writable);
+}
+
+static void
+reset_signal_handlers (void)
+{
+ for (int i=0; i.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ seq
+print_ver_ timeout
+print_ver_ test
+trap_sigpipe_or_skip_
+
+# Paraphrasing http://bugs.gnu.org/34488#8:
+# POSIX requires that sh started with an inherited ignored SIGPIPE must
+# silently ignore all attempts from within the shell to restore SIGPIPE
+# handling to child processes of the shell:
+#
+# $ (trap '' PIPE; bash -c 'trap - PIPE; seq inf | head -n1')
+# 1
+# seq: write error: Broken pipe
+#
+# With 'env --default-signal=PIPE', the signal handler can be reset to its
+# default.
+
+# Baseline Test - default signal handler
+# --------------------------------------
+# Ensure this results in a "broken pipe" error (the first 'trap'
+# sets SIGPIPE to ignore, and the second 'trap' becomes a no-op instead
+# of resetting SIGPIPE to its default). Upon a SIGPIPE 'seq' will not be
+# terminated, instead its write(2) call will return an error.
+(trap '' PIPE; sh -c 'trap - PIPE; seq 999999 2>err1t | head -n1 > out1')
+
+# The exact broken pipe message depends on the operating system, just ensure
+# there was a 'write error' message in stderr:
+sed 's/^\(seq: write error:\) .*/\1/' err1t > err1 || framework_failure_
+
+printf "1\n" > exp-out || framework_failure_
+printf "seq: write error:\n" > exp-err1 || framework_failure_
+
+compare exp-out out1 || framework_failure_
+compare exp-err1 err1 || framework_failure_
+
+
+# env test - default signal handler
+# ---------------------------------
+# With env resetting the signal handler to its defaults, there should be no
+# error message (because the default SIGPIPE action is to terminate the
+# 'seq' program):
+(trap '' PIPE;
+ env --default-signal=PIPE \
+ sh -c 'trap - PIPE; seq 999999 2>err2 | head -n1 > out2')
+
+compare exp-out out2 || fail=1
+compare /dev/null err2 || fail=1
+
+# env test - default signal handler (2)
+# -------------------------------------
+# Repeat the previous test, using "-p" (shortcut for --default-signal=PIPE):
+(trap '' PIPE;
+ env -p \
+ sh -c 'trap - PIPE; seq 999999 2>err3 | head -n1 > out3')
+
+compare exp-out out3 || fail=1
+compare /dev/null err3 || fail=1
+
+# env test - default signal handler (3)
+# -------------------------------------
+# Repeat the previous test, using --default-signal with no signal names,
+# i.e., all signals.
+(trap '' PIPE;
+ env --default-signal \
+ sh -c 'trap - PIPE; seq 999999 2>err4 | head -n1 > out4')
+
+compare exp-out out4 || fail=1
+compare /dev/null err4 || fail=1
+
+
+
+
+
+
+# Baseline test - ignore signal handler
+# -------------------------------------
+# Kill 'seq' after 1 second with SIGINT - it should terminate (as SIGINT's
+# default action is to terminate a program).
+# (The first 'env' is just to ensure timeout is not the shell's built-in.)
+env timeout --verbose --kill-after=1 --signal=INT 1 \
+ seq inf > /dev/null 2>err5
+
+printf "timeout: sending signal INT to command 'seq'\n" > exp-err5 \
+ || framework_failure_
+
+compare exp-err5 err5 || fail=1
+
+
+# env test - ignore signal handler
+# --------------------------------
+# Use env to silence (ignore) SIGINT - "seq" should continue running
+# after timeout sends SIGINT, and be killed after 1 second using SIGKILL.
+
+cat>exp-err6 < /dev/null 2>err6t
+
+# check only the first two lines from stderr, which are printed by timeout.
+# (operating systems might add more messages, like "killed").
+sed -n '1,2p' err6t > err6 || framework_failure_
+
+compare exp-err6 err6 || fail=1
+
+
+# env test - ignore signal handler (2)
+# ------------------------------------
+# Repeat the previous test with "--ignore-signals" and no signal names,
+# i.e., all signals.
+
+env timeout --verbose --kill-after=1 --signal=INT 1 \
+ env --ignore-signal \
+ seq inf > /dev/null 2>err7t
+
+# check only the first two lines from stderr, which are printed by timeout.
+# (operating systems might add more messages, like "killed").
+sed -n '1,2p' err7t > err7 || framework_failure_
+
+compare exp-err6 err7 || fail=1
+
+
+
+Exit $fail
--
2.11.0