Subject: bug#34488: Add sort --limit, or document workarounds for sort|head error messages

From: Assaf Gordon <[email-masked]>
Date: Mon, 25 Feb 2019 18:15:03 -0700

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 <[email-masked]>
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 <[email-masked]>
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 <[email-masked]>
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