Hello, Thanks for all comments.
On 2019-02-24 11:33 a.m., Paul Eggert wrote:
Thanks for doing all that. Although Pádraig is not enthusiastic about a shortcut like -p, I'm a bit warmer to it, as it's an important special case to fix a wart in POSIX. No big deal either way.
For now I kept "-p", can be removed later of course. The first patch includes Pádraig's recent suggestions (slightly modified).
The documentation should mention that SIGCHLD is special [...]
The documentation should say what happens if mutually-contradictory options are specified, [...]
The documentation should echo this suggestion in <http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html>:
I've added those, and I welcome all improvements suggestion to grammar/phrasing/etc.
> There should be options --block-signal[=SIG], --unblock-signal[=SIG], > and --setmask-signal[=SIG] that affect the signal mask, which is also > inherited by the child. These can be implemented via pthread_sigmask. The second patch adds these new options (separated to ease review). As for documentation - I'm not sure what to add beyond the basic option description. When should these be used? A third small patch adds "env ---list-signal-actions" and "env --list-blocked-signals" - to ease diagnostics. Might be worth adding for completeness (e.g., for users who need to somehow know if SIGPIPE is being ignored by the shell or not): $ ( trap '' PIPE && src/env --list-signal-actions ) PIPE (13): ignore Comments very welcomed, - assaf
>From 02cba657e2f63c05f859daf18a7d1032fdc32c6f Mon Sep 17 00:00:00 2001 From: Assaf Gordon <assafgor...@gmail.com> Date: Fri, 15 Feb 2019 12:31:48 -0700 Subject: [PATCH 1/3] 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 | 58 ++++++++++++++++ man/env.x | 69 ++++++++++++++++++ src/env.c | 138 +++++++++++++++++++++++++++++++++++- src/local.mk | 1 + tests/local.mk | 1 + tests/misc/env-signal-handler.sh | 146 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 415 insertions(+), 1 deletion(-) create mode 100755 tests/misc/env-signal-handler.sh diff --git a/NEWS b/NEWS index e73cb52b8..ddbbaf138 100644 --- a/NEWS +++ b/NEWS @@ -81,6 +81,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 eb1848882..c2c202b28 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -17246,6 +17246,64 @@ chroot /chroot env --chdir=/srv true env --chdir=/build FOO=bar timeout 5 true @end example +@item --default-signal[=@var{sig}] +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): + +@example +env --default-signal=PIPE,INT seq 1000 | head -n1 +@end example + +In the following example: + +@example +trap '' PIPE && sh -c 'trap - PIPE ; seq inf | head -n1' +@end 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: + +@example +trap '' PIPE && sh -c "env -p seq inf | head -n1' +@end example + + +@item --ignore-signal[=@var{sig}] +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: + +@example +env --ignore-signal=INT seq inf > /dev/null +@end example + +@samp{SIGCHLD} is special, in that @option{--ignore-signal=CHLD} might have +no effect (POSIX says it's unspecified). + +Most operating systems do not allow ignoring @samp{SIGKILL}, @samp{SIGSTOP} +(and possibly other signals). Attempting to ignore these signals will fail. + +Multiple (and contradictory) @option{--default-signal=SIG} and +@option{--ignore-signal=SIG} options are processed left-to-right, +with the latter taking precedence. In the follwing example, @samp{SIGPIPE} is +set to default while @samp{SIGINT} is ignored: + +@example +env --default-signal=INT,PIPE --ignore-signal=INT +@end example + + +@item -p +Equivalent to @option{--default-signal=PIPE} - sets SIGPIPE to its default +behavior (terminate a program upon @samp{SIGPIPE}). + @item -v @itemx --debug @opindex -v diff --git a/man/env.x b/man/env.x index 8eea79655..09c7f0f3a 100644 --- a/man/env.x +++ b/man/env.x @@ -37,3 +37,72 @@ 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 +.B SIGCHLD +is special in that +.B \-\-ignore\-signal=CHLD +might have no effect (POSIX says it's unspecified). +.PP +Most operating systems do not allow ignoring +.B SIGKILL, SIGSTOP +(and possibly other signals). +Attempting to ignore these signals will fail. +.PP +Multiple (and contradictory) +.B \-\-default\-signal=SIG +and +.B \-\-ignore\-signal=SIG +options are processed left-to-right, with the latter taking precedence. +In the follwing example, SIGPIPE is set to default while SIGINT is ignored: +.RS +.nf +env \-\-default\-signal=INT,PIPE \-\-ignore\-signal=INT +.fi +.RE + +[NOTES] +POSIX's exec(2) pages says: +.RS +"many existing applications wrongly assume that they start with certain +signals set to the default action and/or unblocked.... Therefore, it is best +not to block or ignore signals across execs without explicit reason to do so, +and especially not to block signals across execs of arbitrary (not closely +cooperating) programs." +.RE + +[SEE ALSO] +sigaction(2), signal(7) diff --git a/src/env.c b/src/env.c index 3a1a3869e..bd9b68560 100644 --- a/src/env.c +++ b/src/env.c @@ -21,12 +21,15 @@ #include <sys/types.h> #include <getopt.h> #include <c-ctype.h> +#include <signal.h> #include <assert.h> #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,19 @@ 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] set signal SIG to its default action.\n\ +"), stdout); + fputs (_("\ + --ignore-signal[=SIG] set signal SIG to be ignored.\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); @@ -98,6 +134,13 @@ Set each NAME to VALUE in the environment and run COMMAND.\n\ \n\ A mere - implies -i. If no COMMAND, print the resulting environment.\n\ "), stdout); + fputs (_("\ +\n\ +SIG may be a signal name like 'PIPE', or a signal number like '13'.\n\ +Without SIG, all known signals are included. Multiple signals can be\n\ +comma-separated.\n\ +\n\ +"), stdout); emit_ancillary_info (PROGRAM_NAME); } exit (status); @@ -525,6 +568,87 @@ parse_split_string (const char* str, int /*out*/ *orig_optind, *orig_optind = 0; /* tell getopt to restart from first argument */ } +static void +parse_signal_action_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 = 1 ; 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); + /* operand2sig accepts signal 0 (EXIT) - but we reject it. */ + if (signum == 0) + error (0, 0, _("%s: invalid signal"), quote (opt_sig)); + 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 = 1; i < SIGNUM_BOUND; i++) + { + struct sigaction act; + + if (signals[i] == UNCHANGED) + continue; + + bool ignore_errors = (signals[i] == DEFAULT_NOERR + || signals[i] == IGNORE_NOERR); + + bool set_to_default = (signals[i] == DEFAULT + || signals[i] == DEFAULT_NOERR); + + int sig_err = sigaction (i, NULL, &act); + + if (sig_err && !ignore_errors) + die (EXIT_CANCELED, errno, + _("failed to get signal action for signal %d"), i); + + if (! sig_err) + { + act.sa_handler = set_to_default ? SIG_DFL : SIG_IGN; + + if ((sig_err = sigaction (i, &act, NULL)) && !ignore_errors) + die (EXIT_CANCELED, errno, + _("failed to set signal action for signal %d"), i); + } + + if (dev_debug) + { + char signame[SIG2STR_MAX]; + sig2str (i, signame); + devmsg ("Reset signal %s (%d) to %s%s\n", + signame, i, + set_to_default ? "DEFAULT" : "IGNORE", + sig_err ? " (failure ignored)" : ""); + } + } +} + int main (int argc, char **argv) { @@ -558,6 +682,16 @@ main (int argc, char **argv) case '0': opt_nul_terminate_output = true; break; + case 'p': + /* alias to --default-signal=PIPE */ + signals[SIGPIPE] = DEFAULT; + break; + case DEFAULT_SIGNAL_OPTION: + parse_signal_action_params (optarg, true); + break; + case IGNORE_SIGNAL_OPTION: + parse_signal_action_params (optarg, false); + break; case 'C': newdir = optarg; break; @@ -633,6 +767,8 @@ main (int argc, char **argv) return EXIT_SUCCESS; } + reset_signal_handlers (); + if (newdir) { devmsg ("chdir: %s\n", quoteaf (newdir)); diff --git a/src/local.mk b/src/local.mk index ba219d530..a69d40521 100644 --- a/src/local.mk +++ b/src/local.mk @@ -356,6 +356,7 @@ src_coreutils_SOURCES = src/coreutils.c src_cp_SOURCES = src/cp.c $(copy_sources) $(selinux_sources) src_dir_SOURCES = src/ls.c src/ls-dir.c +src_env_SOURCES = src/env.c src/operand2sig.c src_vdir_SOURCES = src/ls.c src/ls-vdir.c src_id_SOURCES = src/id.c src/group-list.c src_groups_SOURCES = src/groups.c src/group-list.c diff --git a/tests/local.mk b/tests/local.mk index bcb61edc7..d67e4e80c 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -240,6 +240,7 @@ all_tests = \ tests/fmt/goal-option.sh \ tests/misc/echo.sh \ tests/misc/env.sh \ + tests/misc/env-signal-handler.sh \ tests/misc/ptx.pl \ tests/misc/test.pl \ tests/misc/seq.pl \ diff --git a/tests/misc/env-signal-handler.sh b/tests/misc/env-signal-handler.sh new file mode 100755 index 000000000..b5315499e --- /dev/null +++ b/tests/misc/env-signal-handler.sh @@ -0,0 +1,146 @@ +#!/bin/sh +# Test env --default-signal=PIPE feature. + +# Copyright (C) 2019 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 <https://www.gnu.org/licenses/>. + +. "${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 <<EOF +timeout: sending signal INT to command 'env' +timeout: sending signal KILL to command 'env' +EOF + +env timeout --verbose --kill-after=1 --signal=INT 1 \ + env --ignore-signal=INT \ + seq inf > /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
>From deb0d5a64eef13bdffe9a84f98fc6a5b18805262 Mon Sep 17 00:00:00 2001 From: Assaf Gordon <assafgor...@gmail.com> Date: Mon, 25 Feb 2019 15:37:47 -0700 Subject: [PATCH 2/3] env: add --{block,unblock,setmask}-signal=SIG options Suggested by Paul Eggert in http://bugs.gnu.org/34488#71 . * NEWS: Mention new options. * doc/coreutils.texi (env invocation): Document new options. * src/env.c (block_signals, unblock_signals): New global variables. (longopts, usage): Add new options. (parse_block_signal_params): Parse command-line options. (set_signal_proc_mask): Call sigprocmask to block/unblock signals. (main): Handle new command-line options, call set_signal_proc_mask. * man/env.x (SEE ALSO): Mention sigprocmask. --- NEWS | 3 ++ doc/coreutils.texi | 10 +++++ man/env.x | 2 +- src/env.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 125 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index ddbbaf138..cb8aa107f 100644 --- a/NEWS +++ b/NEWS @@ -84,6 +84,9 @@ GNU coreutils NEWS -*- outline -*- env now supports '--default-singal[=SIG]' and '--ignore-signal[=SIG]' options to set signal handlers before executing a program. + env now supports '--{block,unblock,setmask}-singal[=SIG]' to block/unblock + signal delivery 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 c2c202b28..8f5212a72 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -17299,6 +17299,16 @@ set to default while @samp{SIGINT} is ignored: env --default-signal=INT,PIPE --ignore-signal=INT @end example +@item --block-signal[=@var{sig}] +Block signal @var{sig} from being delivered. + +@item --unblock-signal[=@var{sig}] +Unblock signal @var{sig}. + +@item --setmask-signal[=@var{sig}] +Set list of blocked signals to @var{sig}. All other signals will be unblocked. + + @item -p Equivalent to @option{--default-signal=PIPE} - sets SIGPIPE to its default diff --git a/man/env.x b/man/env.x index 09c7f0f3a..b787fe3a7 100644 --- a/man/env.x +++ b/man/env.x @@ -105,4 +105,4 @@ cooperating) programs." .RE [SEE ALSO] -sigaction(2), signal(7) +sigaction(2), sigprocmask(2), signal(7) diff --git a/src/env.c b/src/env.c index bd9b68560..4cae7d47f 100644 --- a/src/env.c +++ b/src/env.c @@ -61,6 +61,11 @@ enum SIGNAL_MODE { }; static enum SIGNAL_MODE signals[SIGNUM_BOUND]; +/* Set of signals to block. */ +static sigset_t block_signals; + +/* Set of signals to unblock. */ +static sigset_t unblock_signals; static char const shortopts[] = "+C:ipS:u:v0 \t"; @@ -70,7 +75,10 @@ static char const shortopts[] = "+C:ipS:u:v0 \t"; enum { DEFAULT_SIGNAL_OPTION = CHAR_MAX + 1, - IGNORE_SIGNAL_OPTION + IGNORE_SIGNAL_OPTION, + BLOCK_SIGNAL_OPTION, + UNBLOCK_SIGNAL_OPTION, + SETMASK_SIGNAL_OPTION }; static struct option const longopts[] = @@ -81,6 +89,9 @@ static struct option const longopts[] = {"chdir", required_argument, NULL, 'C'}, {"default-signal", optional_argument, NULL, DEFAULT_SIGNAL_OPTION}, {"ignore-signal", optional_argument, NULL, IGNORE_SIGNAL_OPTION}, + {"block-signal", optional_argument, NULL, BLOCK_SIGNAL_OPTION}, + {"unblock-signal", optional_argument, NULL, UNBLOCK_SIGNAL_OPTION}, + {"setmask-signal", optional_argument, NULL, SETMASK_SIGNAL_OPTION}, {"debug", no_argument, NULL, 'v'}, {"split-string", required_argument, NULL, 'S'}, {GETOPT_HELP_OPTION_DECL}, @@ -110,6 +121,15 @@ Set each NAME to VALUE in the environment and run COMMAND.\n\ -u, --unset=NAME remove variable from the environment\n\ "), stdout); fputs (_("\ + --block-signal[=SIG] block signal SIG.\n\ +"), stdout); + fputs (_("\ + --unblock-signal[=SIG] unblock signal SIG.\n\ +"), stdout); + fputs (_("\ + --setmask-signal[=SIG] set blocked signal(s) mask to SIG.\n\ +"), stdout); + fputs (_("\ -C, --chdir=DIR change working directory to DIR\n\ "), stdout); fputs (_("\ @@ -649,6 +669,84 @@ reset_signal_handlers (void) } } +static void +parse_block_signal_params (const char* optarg, bool block) +{ + char signame[SIG2STR_MAX]; + char *opt_sig; + char *optarg_writable; + + if (! optarg) + { + /* without an argument, reset all signals. */ + sigfillset (block ? &block_signals : &unblock_signals); + sigemptyset (block ? &unblock_signals : &block_signals); + return; + } + + optarg_writable = xstrdup (optarg); + + opt_sig = strtok (optarg_writable, ","); + while (opt_sig) + { + int signum = operand2sig (opt_sig, signame); + /* operand2sig accepts signal 0 (EXIT) - but we reject it. */ + if (signum == 0) + error (0, 0, _("%s: invalid signal"), quote (opt_sig)); + if (signum <= 0) + usage (exit_failure); + + sigaddset (block ? &block_signals : &unblock_signals, signum); + sigdelset (block ? &unblock_signals : &block_signals, signum); + + opt_sig = strtok (NULL, ","); + } + + free (optarg_writable); +} + +static void +set_signal_proc_mask (void) +{ + /* Get the existing signal mask */ + sigset_t set; + const char *debug_act; + + sigemptyset (&set); + + if (sigprocmask (0, NULL, &set)) + die (EXIT_CANCELED, errno, _("failed to get signal process mask")); + + for (int i = 1; i < SIGNUM_BOUND; i++) + { + if (sigismember (&block_signals, i)) + { + sigaddset (&set, i); + debug_act = "BLOCK"; + } + else if (sigismember (&unblock_signals, i)) + { + sigdelset (&set, i); + debug_act = "UNBLOCK"; + } + else + { + debug_act = NULL; + } + + if (dev_debug && debug_act) + { + char signame[SIG2STR_MAX]; + sig2str (i, signame); + devmsg ("signal %s (%d) procmask set to %s\n", + signame, i, debug_act); + } + } + + if (sigprocmask (SIG_SETMASK, &set, NULL)) + die (EXIT_CANCELED, errno, _("failed to set signal process mask")); +} + int main (int argc, char **argv) { @@ -692,6 +790,17 @@ main (int argc, char **argv) case IGNORE_SIGNAL_OPTION: parse_signal_action_params (optarg, false); break; + case BLOCK_SIGNAL_OPTION: + parse_block_signal_params (optarg, true); + break; + case UNBLOCK_SIGNAL_OPTION: + parse_block_signal_params (optarg, false); + break; + case SETMASK_SIGNAL_OPTION: + sigfillset (&unblock_signals); + sigemptyset (&block_signals); + parse_block_signal_params (optarg, true); + break; case 'C': newdir = optarg; break; @@ -768,6 +877,7 @@ main (int argc, char **argv) } reset_signal_handlers (); + set_signal_proc_mask (); if (newdir) { -- 2.11.0
>From 150c30aec340d6ce00afa1251e6962c012449407 Mon Sep 17 00:00:00 2001 From: Assaf Gordon <assafgor...@gmail.com> Date: Mon, 25 Feb 2019 18:03:19 -0700 Subject: [PATCH 3/3] env: add --list-blocked-signals/--list-signal-actions options Example: $ ( trap '' PIPE && env --list-signal-actions ) PIPE (13): ignore * NEWS: Mention new options. * doc/coreutils.texi (env invocation): Mention new options. * src/env.c (longopts,usage): Add new options. (list_signal_actions, list_blocked_signals): New functions. (main): Handle new options. --- NEWS | 3 +++ doc/coreutils.texi | 8 ++++++ src/env.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index cb8aa107f..3cef6066e 100644 --- a/NEWS +++ b/NEWS @@ -87,6 +87,9 @@ GNU coreutils NEWS -*- outline -*- env now supports '--{block,unblock,setmask}-singal[=SIG]' to block/unblock signal delivery before executing a program. + env now supports '--list-signal-actions' and '--list-blocked-signals' + options. + ** New commands basenc is added to complement existing base64,base32 commands, diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 8f5212a72..b63161803 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -17309,6 +17309,14 @@ Unblock signal @var{sig}. Set list of blocked signals to @var{sig}. All other signals will be unblocked. +@item --list-signal-actions +List non-default actions for all signals and exit. +If all signals are set to their default action, nothing is printed. + +@item --list-blocked-signals +List blocked (masked) signals and exit. +If no signals are blocked, nothing is printed. + @item -p Equivalent to @option{--default-signal=PIPE} - sets SIGPIPE to its default diff --git a/src/env.c b/src/env.c index 4cae7d47f..43856200f 100644 --- a/src/env.c +++ b/src/env.c @@ -78,7 +78,9 @@ enum IGNORE_SIGNAL_OPTION, BLOCK_SIGNAL_OPTION, UNBLOCK_SIGNAL_OPTION, - SETMASK_SIGNAL_OPTION + SETMASK_SIGNAL_OPTION, + LIST_SIGNAL_ACTIONS_OPTION, + LIST_BLOCKED_SIGNALS_OPTION, }; static struct option const longopts[] = @@ -89,9 +91,11 @@ static struct option const longopts[] = {"chdir", required_argument, NULL, 'C'}, {"default-signal", optional_argument, NULL, DEFAULT_SIGNAL_OPTION}, {"ignore-signal", optional_argument, NULL, IGNORE_SIGNAL_OPTION}, + {"list-signal-actions", no_argument, NULL, LIST_SIGNAL_ACTIONS_OPTION}, {"block-signal", optional_argument, NULL, BLOCK_SIGNAL_OPTION}, {"unblock-signal", optional_argument, NULL, UNBLOCK_SIGNAL_OPTION}, {"setmask-signal", optional_argument, NULL, SETMASK_SIGNAL_OPTION}, + {"list-blocked-signals", no_argument, NULL, LIST_BLOCKED_SIGNALS_OPTION}, {"debug", no_argument, NULL, 'v'}, {"split-string", required_argument, NULL, 'S'}, {GETOPT_HELP_OPTION_DECL}, @@ -143,6 +147,15 @@ Set each NAME to VALUE in the environment and run COMMAND.\n\ used to pass multiple arguments on shebang lines\n\ "), stdout); fputs (_("\ + --list-signal-actions list non-default actions for signals and exit.\n\ + if all signals use their default actions,\n\ + nothing is printed.\n\ +"), stdout); + fputs (_("\ + --list-blocked-signals list blocked (masked) signals and exit.\n\ + if no signals are blocked, nothing is printed.\n\ +"), stdout); + fputs (_("\ -p same as --default-signal=PIPE\n\ "), stdout); fputs (_("\ @@ -670,6 +683,35 @@ reset_signal_handlers (void) } static void +list_signal_actions (void) +{ + char signame[SIG2STR_MAX]; + const char* action; + + for (int i = 1; i < SIGNUM_BOUND; i++) + { + struct sigaction act; + if (sigaction (i, NULL, &act)) + continue; + + if (act.sa_flags & SA_SIGINFO) + action = "sig-action"; + else if (act.sa_handler == SIG_DFL) + continue; /* no need to print defaults. */ + else if (act.sa_handler == SIG_IGN) + action = "ignore"; + else + action = "sig-handler"; + + sig2str (i, signame); + printf ("%-10s (%2d): %s\n", signame, i, action); + } + + exit (EXIT_SUCCESS); +} + + +static void parse_block_signal_params (const char* optarg, bool block) { char signame[SIG2STR_MAX]; @@ -747,6 +789,29 @@ set_signal_proc_mask (void) die (EXIT_CANCELED, errno, _("failed to set signal process mask")); } +static void +list_blocked_signals (void) +{ + sigset_t set; + char signame[SIG2STR_MAX]; + + sigemptyset (&set); + if (sigprocmask (0, NULL, &set)) + die (EXIT_CANCELED, errno, _("failed to get signal process mask")); + + for (int i = 1; i < SIGNUM_BOUND; i++) + { + if (!sigismember (&set, i)) + continue; + + sig2str (i, signame); + printf ("%-10s (%2d)\n", signame, i); + } + + exit (EXIT_SUCCESS); +} + + int main (int argc, char **argv) { @@ -790,6 +855,9 @@ main (int argc, char **argv) case IGNORE_SIGNAL_OPTION: parse_signal_action_params (optarg, false); break; + case LIST_SIGNAL_ACTIONS_OPTION: + list_signal_actions (); + break; case BLOCK_SIGNAL_OPTION: parse_block_signal_params (optarg, true); break; @@ -801,6 +869,9 @@ main (int argc, char **argv) sigemptyset (&block_signals); parse_block_signal_params (optarg, true); break; + case LIST_BLOCKED_SIGNALS_OPTION: + list_blocked_signals (); + break; case 'C': newdir = optarg; break; -- 2.11.0