Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/trunk]: src/bin/sh Enhance the trap command to make it possible to do wh...



details:   https://anonhg.NetBSD.org/src/rev/a16eeb311a2a
branches:  trunk
changeset: 823760:a16eeb311a2a
user:      kre <kre%NetBSD.org@localhost>
date:      Sun May 07 15:01:18 2017 +0000

description:
Enhance the trap command to make it possible to do what POSIX wants
(even if no shell in existence, that I am aware of, does that).

That is, POSIX says ... [of the trap command with no args]

        The shell shall format the output, including the proper use of
        quoting, so that it is suitable for re-input to the shell as commands
        that achieve the same trapping results. For example:

        save_traps=$(trap)

        ...

        eval "$save_traps"

It is obvious what the intent is there.  But no shell makes it work.

An example using bash (as the NetBSD shell, still does not do the save_traps=
stuff correctly - but that is a problem for a different time and place...)

Given this script

        printf 'At start: '; trap
        printf '\n'

        traps=$(trap)
        trap 'echo hello' INT
        printf 'inside  : '; trap
        printf '\n'
        eval "${traps}"

        printf 'At end  : '; trap
        printf '\n'

One would expect that (assuming no traps are set at the start, and
there aren't) that the first trap will print nothing, then the inside
trap will show the trap that was set, and then when we get to the
end everything will be back to nothing again.

But:

At start:
inside  : trap -- 'echo hello' SIGINT

At end  : trap -- 'echo hello' SIGINT

And of course. when you think about it, it is obvious why this happens.
The first "trap" command prints nothing ... nothing has changed when we
get to the "traps=$(trap)" command ... that trap command also prints
nothing.  So this does traps=''.  When we do eval "${traps}" we are
doing eval "", and it is hardly surprising that this accomplishes nothing!

Now we cannot rationally change the "trap" command without args to
behave in a way that would make it useful for the posix purpose (and
here, what they're aiming for is good, it should be possible to
accomplish that objective) so is there some other way?

I think I have seen some shell (but I do not remember which one) that
actually has "trap -" that resets all traps to the default, so with that,
if we changed the 'eval "${traps}"' line to 'trap -; eval "${traps}"'
then things would actually work - kind of - that version has race conditions,
so is not really safe to use (it will work, most of the time...)

But, both ksh93 and bash have a -p arg to "trap" that allows information
about the current trap status of named signals to be reported.  Unfortunately
they don't do quite the same thing, but that's not important right now,
either would be usable, and they are, but it is a lot of effort, not
nearly as simple as the posix example.

First, while "trap -p" (with no signals specified) works, it works just
the same (in both bash and ksh93, aside from output format) as "trap".
That is, that is useless.   But we can to

        trap_int=$(trap -p int)
        trap_hup=$(trap -p hup)
        ...

and then reset them all, one by one, later...

(bash syntax)
        test -n "${trap_int}" && eval "${trap_int}" || trap - int
        test -n "${trap_hup}" && eval "${trap_hup}" || trap - hup
(ksh93 syntax)
        trap "${trap_int:-}" int
        trap "${trap_hup:-}" hup

the test (for bash) and  variable with default for ksh93, is needed
because they both still print nothing if the signal action is the default.

So, this modification attempts to fix all of that...

1) we add trap -p, but make it always output something for every signal
   listed (all of the signals if none are given) even if the signal
   action is the default.

2) choose the bash output format for trap -p, over the ksh93 format,
   even though the simpler usage just above makes the ksh93 form seem
   better.   But it isn't.  Consider:

        ksh93$ trap -p int hup
        echo hello

   One of the two traps has "echo hello" as its action, the other is
   still at the default, but which?

   From bash...
        bash$ trap -p int hup
        trap -- 'echo hello' SIGINT

   And now we know!  Given the bash 'trap -p' format, the following function
   produces ksh93 format output (for use with named signals only) instead...

        ksh93_trap_p() {
                for _ARG_ do
                        _TRAP_=$(trap -p "${_ARG_}") || return 1
                        eval set -- "${_TRAP_}"
                        printf '%s' "$3${3:+
        }"
                done
                return 0
        }

  [ It needs to be entered without the indentation, that '}"' line has to be
    at the margin.   If the shell running that has local vars (bash does) then
    _ARG_ and _TRAP_ should be made local. ]

  So the bash format was chosen (except we do not include the "SIG" on the
  signal names.  That's irrelevant.)

  If no traps are set, "trap -p" will say (on NetBSD of course)...

trap -- - EXIT HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS
trap -- - PIPE ALRM TERM URG STOP TSTP CONT CHLD TTIN TTOU IO XCPU XFSZ
trap -- - VTALRM PROF WINCH INFO USR1 USR2 PWR RT0 RT1 RT2 RT3 RT4 RT5
trap -- - RT6 RT7 RT8 RT9 RT10 RT11 RT12 RT13 RT14 RT15 RT16 RT17 RT18
trap -- - RT19 RT20 RT21 RT22 RT23 RT24 RT25 RT26 RT27 RT28 RT29 RT30

  Obviously if traps are set, the relevant signal names will be removed from
  that list, and additional lines added for the trapped signals.

  With args, the signals names are listed, one line each, whatever
  the status of the trap for that signal is:

$ trap -p HUP INT QUIT
trap -- - HUP
trap -- 'echo interrupted' INT
trap -- - QUIT

3) we add "trap -" to reset all traps to default.   (It is easy, and seems
   useful.)

4) While here, lots of generic cleanup.   In particular, get rid of the
   NSIG+1 nonsense, and anything that ever believes a signo == NSIG
   is in any way rational.   Before there was a bunch of confusion,
   as we need all the signals for traps, plus one more for the EXIT
   trap, which looks like we then need NSIG+1.  But EXIT is 0, NSIG
   includes signals from 0..NSIG-1 but there is no signal 0, EXIT
   uses that slot, so we do not need to add and extra one, NSIG is
   enough.   (To see the effect of this, use a /bin/sh from before
   this fix, and compare the output from

        trap '' 64
   and  trap '' 65

   both invalid signal numbers.

   Then try just "trap" and watch your shell drop core...)

   Eventually NSIG needs to go away completely (from user apps), it
   is not POSIX, it isn't really useful (unless we make lots of
   assumptions about how signals are numbered, which are not guaranteed,
   so even if apps, like this sh, work on NetBSD, they're not portable,)
   and it isn't necessary (or will not be, soon.)

   But that is for another day...

5) As is kind of obvious above, when listing "all" traps, list all the
   ones still at their defaults, and all the ignored signals, on as
   few lines as possible (it could all be on one line - technically it
   would work as well, but it would have made this cvs log message
   really ugly...)   Signals with a non-null action still get listed
   one to a line (even if several do have the exact same action.)

6) Man page updates as well.

After this change, the following script:

        printf 'At start: '; trap
        printf '\n'

        trap -p >/tmp/out.$$
        trap 'echo hello' INT
        printf 'inside  : '; trap
        printf '\n'
        . /tmp/out.$$; rm /tmp/out.$$

        printf 'At end  : '; trap
        printf '\n'

which is just the example from above,
using "trap -p" instead of just "trap" to save the traps,
and modified to a form that will work with the NetBSD shell today
produces:

At start:
inside  : trap -- 'echo hello' INT

At end  :

[Do I get a prize for longest commit log message of the year?]

diffstat:

 bin/sh/sh.1   |   76 +++++++++++++++++++++++++++----
 bin/sh/trap.c |  136 ++++++++++++++++++++++++++++++++++++++++++++++++---------
 2 files changed, 179 insertions(+), 33 deletions(-)

diffs (truncated from 429 to 300 lines):

diff -r 49e05378e6ce -r a16eeb311a2a bin/sh/sh.1
--- a/bin/sh/sh.1       Sun May 07 14:20:50 2017 +0000
+++ b/bin/sh/sh.1       Sun May 07 15:01:18 2017 +0000
@@ -1,4 +1,4 @@
-.\"    $NetBSD: sh.1,v 1.135 2017/05/04 04:37:51 kre Exp $
+.\"    $NetBSD: sh.1,v 1.136 2017/05/07 15:01:18 kre Exp $
 .\" Copyright (c) 1991, 1993
 .\"    The Regents of the University of California.  All rights reserved.
 .\"
@@ -31,7 +31,7 @@
 .\"
 .\"    @(#)sh.1        8.6 (Berkeley) 5/4/95
 .\"
-.Dd May 4, 2017
+.Dd May 7, 2017
 .Dt SH 1
 .ds flags abCEeFfhnuvxIimpqV
 .Os
@@ -2056,9 +2056,12 @@
 positional parameters (
 .Dq $# )
 before the shift.
+.It trap Ar action signal ...
+.It trap \-
 .It trap Oo Fl l Oc
-.It trap Ar action signal ...
+.It trap Oo Fl p Oc signal ...
 .It trap Ar N signal ...
+.Pp
 Cause the shell to parse and execute action when any of the specified
 signals are received.
 The signals are specified by signal number or as the name of the signal.
@@ -2068,13 +2071,15 @@
 .Li 0
 or its equivalent, EXIT,
 the action is executed when the shell exits.
+The
 .Ar action
-may be null, which cause the specified signals to be ignored.
+may be a null (empty) string,
+which causes the specified signals to be ignored.
 With
 .Ar action
 set to
 .Sq -
-the specified signals are set to their default action.
+the specified signals are set to their default actions.
 If the first
 .Ar signal
 is specified in its numeric form, then
@@ -2085,6 +2090,9 @@
 form should not be relied upon, use the explicit
 .Sq -
 action.
+If no signals are specified with an action of
+.Sq - ,
+all signals are reset.
 .Pp
 When the shell forks off a subshell, it resets trapped (but not ignored)
 signals to the default action.
@@ -2095,21 +2103,28 @@
 On interactive shells, the
 .Ic trap
 command will catch or reset signals ignored on entry.
+.Pp
 Issuing
 .Ic trap
 with option
 .Ar -l
 will print a list of valid signal names.
 .Ic trap
-without any arguments cause it to write a list of signals and their
-associated action to the standard output in a format that is suitable
-as an input to the shell that achieves the same trapping results.
+without any arguments causes it to write a list of signals and their
+associated non-default actions to the standard output in a format
+that is suitable as an input to the shell that achieves the same
+trapping results.
+With the
+.Ar -p
+flag, trap prints the same information for the signals specified,
+or if none are given, for all signals, including those where the
+action is the default.
 .Pp
 Examples:
 .Pp
 .Dl trap
 .Pp
-List trapped signals and their corresponding action.
+List trapped signals and their corresponding actions.
 .Pp
 .Dl trap -l
 .Pp
@@ -2129,11 +2144,30 @@
 .Pp
 Run the
 .Dq HUP
-command upon receiving signal INT.
+command, or function, upon receiving signal INT.
 .Pp
 .Dl trap 1 2
 .Pp
 Reset the actions for signals 1 (HUP) and 2 (INT) to their defaults.
+.Pp
+.Bd -literal -offset indent
+traps=$(trap -p)
+   # more commands ...
+trap 'action' SIG
+   # more commands ...
+eval "$traps"
+.Ed
+.Pp
+Save the trap status, execute commands, changing some traps,
+and then reset all traps to their values at the start of the sequence.
+The
+.Fl p
+option is required in the first command here,
+or any signals that were previously
+untrapped (in their default states) 
+and which were altered during the intermediate code,
+would not be reset by the final
+.Dq eval .
 .It type Op Ar name ...
 Interpret each name as a command and print the resolution of the command
 search.
@@ -2461,7 +2495,10 @@
 .Nm
 command appeared in
 .At v1 .
-It was, however, unmaintainable so we wrote this one.
+It was replaced in
+.At v7
+with a version that introduced the basis of the current syntax.
+That was, however, unmaintainable so we wrote this one.
 .Sh BUGS
 Setuid shell scripts should be avoided at all costs, as they are a
 significant security risk.
@@ -2473,4 +2510,21 @@
 to ensure that the filename is still valid after the input line has been
 processed.
 .Pp
+The
+.\" markup?
+trap
+command cannot usefully be used, yet, within a command substitution,
+to obtain the current trap values,
+as all command substitutions are currently executed within a
+subshell environment,
+and in sub-shells all non-ignored, non-default, traps are reset.
+As a workaround, it is possible to redirect ooutput from
+.Dq trap
+or
+.Dq trap -p
+to a file, and then read the file later using the
+.Dq \&.
+command.
+.Pp
 Many, many, more.
+(But less than there were...)
diff -r 49e05378e6ce -r a16eeb311a2a bin/sh/trap.c
--- a/bin/sh/trap.c     Sun May 07 14:20:50 2017 +0000
+++ b/bin/sh/trap.c     Sun May 07 15:01:18 2017 +0000
@@ -1,4 +1,4 @@
-/*     $NetBSD: trap.c,v 1.39 2017/04/29 15:12:21 kre Exp $    */
+/*     $NetBSD: trap.c,v 1.40 2017/05/07 15:01:18 kre Exp $    */
 
 /*-
  * Copyright (c) 1991, 1993
@@ -37,13 +37,14 @@
 #if 0
 static char sccsid[] = "@(#)trap.c     8.5 (Berkeley) 6/5/95";
 #else
-__RCSID("$NetBSD: trap.c,v 1.39 2017/04/29 15:12:21 kre Exp $");
+__RCSID("$NetBSD: trap.c,v 1.40 2017/05/07 15:01:18 kre Exp $");
 #endif
 #endif /* not lint */
 
 #include <signal.h>
 #include <unistd.h>
 #include <stdlib.h>
+#include <stdio.h>
 
 #include "shell.h"
 #include "main.h"
@@ -75,12 +76,13 @@
 #define S_RESET 5              /* temporary - to reset a hard ignored sig */
 
 
-char *trap[NSIG+1];            /* trap handler commands */
+char *trap[NSIG];              /* trap handler commands */
 MKINIT char sigmode[NSIG];     /* current value of signal */
 static volatile char gotsig[NSIG];/* indicates specified signal received */
 volatile int pendingsigs;      /* indicates some signal received */
 
 static int getsigaction(int, sig_t *);
+STATIC const char *trap_signame(int);
 
 /*
  * return the signal number described by `p' (as a number or a name)
@@ -108,6 +110,25 @@
 }
 
 /*
+ * return the name of a signal used by the "trap" command
+ */
+STATIC const char *
+trap_signame(int signo)
+{
+       static char nbuf[12];
+       const char *p = NULL;
+
+       if (signo == 0)
+               return "EXIT";
+       if (signo > 0 && signo < NSIG)
+               p = sys_signame[signo];
+       if (p != NULL)
+               return p;
+       (void)snprintf(nbuf, sizeof nbuf, "%d", signo);
+       return nbuf;
+}
+
+/*
  * Print a list of valid signal names
  */
 static void
@@ -118,7 +139,7 @@
        out1str("EXIT ");
 
        for (n = 1; n < NSIG; n++) {
-               out1fmt("%s", sys_signame[n]);
+               out1fmt("%s", trap_signame(n));
                if ((n == NSIG/2) ||  n == (NSIG - 1))
                        out1str("\n");
                else
@@ -137,6 +158,7 @@
        char **ap;
        int signo;
        int errs = 0;
+       int printonly = 0;
 
        ap = argv + 1;
 
@@ -144,6 +166,24 @@
                printsignals();
                return 0;
        }
+       if (argc == 2 && strcmp(*ap, "-") == 0) {
+               for (signo = 0; signo < NSIG; signo++) {
+                       if (trap[signo] == NULL)
+                               continue;
+                       INTOFF;
+                       ckfree(trap[signo]);
+                       trap[signo] = NULL;
+                       if (signo != 0)
+                               setsignal(signo, 0);
+                       INTON;
+               }
+               return 0;
+       }
+       if (argc >= 2 && strcmp(*ap, "-p") == 0) {
+               printonly = 1;
+               ap++;
+               argc--;
+       }
 
        if (argc > 1 && strcmp(*ap, "--") == 0) {
                argc--;
@@ -151,19 +191,58 @@
        }
 
        if (argc <= 1) {
-               for (signo = 0 ; signo <= NSIG ; signo++)
-                       if (trap[signo] != NULL) {
-                               out1fmt("trap -- ");
+               int count;
+
+               if (printonly) {
+                       for (count = 0, signo = 0 ; signo < NSIG ; signo++)
+                               if (trap[signo] == NULL) {
+                                       if (count == 0)
+                                               out1str("trap -- -");
+                                       out1fmt(" %s", trap_signame(signo));
+                                       /* oh! unlucky 13 */
+                                       if (++count >= 13) {
+                                               out1str("\n");
+                                               count = 0;
+                                       }
+                               }
+                       if (count)
+                               out1str("\n");
+               }
+
+               for (count = 0, signo = 0 ; signo < NSIG ; signo++)
+                       if (trap[signo] != NULL && trap[signo][0] == '\0') {
+                               if (count == 0)
+                                       out1str("trap -- ''");
+                               out1fmt(" %s", trap_signame(signo));
+                               /*
+                                * the prefix is 10 bytes, with 4 byte
+                                * signal names (common) we have room in



Home | Main Index | Thread Index | Old Index