help-bash
[Top][All Lists]
Advanced

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

Re: [Help-bash] too paranoid?


From: Bob Proulx
Subject: Re: [Help-bash] too paranoid?
Date: Sat, 12 Mar 2016 20:16:03 -0700
User-agent: Mutt/1.5.24 (2015-08-30)

Stephane Chazelas wrote:
> 2016-03-10 17:48:37 -0700, Bob Proulx:
> [...]
> > unset tmpfile
> > cleanup() {
> >   test -n "$tmpfile" && rm -f "$tmpfile"
> > }
> > trap "cleanup" EXIT
> > trap "cleanup; trap - HUP; kill -HUP $$" HUP
> > trap "cleanup; trap - INT; kill -INT $$" INT
> [...]
> 
> Note that if sh is based on bash (GNU systems, OS/X...), pdksh
> (some BSDs) or ksh93 (Solaris 11), (not dash nor zsh nor yash)
> cleanup will be run twice upon a signal (once for the signal
> handler, another time for the EXIT trap).

Cleanup routines like this should always be idempotent.  Running them
multiple times should not do any harm.  You have pointed out that in
the above rm might get called twice.  I actually knew that.  I agree
with you that it is technically incorrect.  However in practice this
is unlikely to really be a problem which is why I hadn't worried about
it before.  However as you point out it is technically incorrect and
therefore should be handled.  Fortunely it is simple to unset tmpfile
at the end and I do that in my improvement below.

I guess I can make the cleanup a little more tight by unsetting
tmpfile as the action happens.  Then it won't call rm twice.  Thank
you for poking me about this as it allows me to improve my code.

  unset tmpfile
  cleanup() {
    test -n "$tmpfile" && rm -f "$tmpfile" && unset tmpfile
  }
  trap "cleanup" EXIT

The difference between shells will mean that some shells trap signals
and call the EXIT trap and some do not.  It is that reason that I add
the calls to HUP, INT, QUIT, and TERM.  Those are the four common
signals that are often sent to processes.  A shell like bash and ksh93
and so forth already traps those signals (and others) and calls the
EXIT trap appropriately but other shells, notably dash, do not.
Therefore for general portability I trap on both so that it will also
work with /bin/dash as /bin/sh.

  trap "cleanup; trap - HUP; kill -HUP $$" HUP
  trap "cleanup; trap - INT; kill -INT $$" INT
  trap "cleanup; trap - QUIT; kill -QUIT $$" QUIT
  trap "cleanup; trap - TERM; kill -TERM $$" TERM

Alternatively to handling 'unset tmpfile' I guess I could also reset
the EXIT trap too in the signal handling traps.  Since cleanup has
already been called.

  trap "cleanup; trap - EXIT; trap - HUP; kill -HUP $$" HUP
  trap "cleanup; trap - EXIT; trap - INT; kill -INT $$" INT
  trap "cleanup; trap - EXIT; trap - QUIT; kill -QUIT $$" QUIT
  trap "cleanup; trap - EXIT; trap - TERM; kill -TERM $$" TERM

That will also avoid calling cleanup twice.  At the expense of a
little more always required boilerplate.

> > trap "cleanup; trap - INT; kill -INT $$" INT
>
> Depending on the version of pdksh/mksh, the "kill" within the
> trap may not work at all, or if it works, the exit status of the
> script may be that of the clean-up function.

Sorry but you have lost me.  Why won't the kill work on these other
shells?

With the exception of shells older than those that know about signal
names in addition to signal numbers that should be portable to quite
old shells.  Really old shells will need the signal numbers to be used
instead of the signal names because it won't know about INT but
instead knows only about 2.  But the readability of INT over 2 wins if
that extreme level of portability isn't needed as in the original
question.

> For portability, it may be better as (untested):
> 
> unset tmpfile killedby
> cleanup() {
>   ret=$?
>   if [ -n "$tmpfile" ]; then
>     rm -f "$tmpfile"
>     unset tmpfile
>   fi
>   if [ -n "$killedby" ]; then
>     sig=$killedby
>     unset killedby
>     trap - "$sig"
>     kill -s "$sig" "$$"
>     ret=$((128 + $(kill -l "$sig")))
>   fi
>   exit "$ret"
> }
> trap cleanup EXIT
> for sig in INT TERM QUIT HUP; do
>   trap "killedby=$sig; cleanup" "$sig"
> done

In the above code it looks like you are expecting the signal handler
to continue after the kill.  That won't happen.  Do you have an
example of a shell which continues after having received the signal?

If the code did actually continue then it is trying to return a signal
value encoded as 128 plus the signal number.  Sorry but that is
incorrect.  Worse it will be confusing when observing the return value
with the shell afterward by looking at $?.  It will appear to be the
same number due to the way the shell encodes the value for $? and this
will hide the fact that it isn't returning the correct exit code.
That is a very bad thing.

As a matter of style (ignoring the multiline if versus single line) I
would prefer to pass in the signal name as $1 into the function.  Also
there is no reason to unset killedby in the above since the process
exits immediately.

> Note that doing a "kill $$" instead of exit(128+signum) is only
> marginally useful.

Sorry but no.  In order to handle signals correctly the process *must*
kill itself with a signal.  There is no other correct handling.

> It is possibly useful for SIGINT and SIGQUIT if your system has
> shells that implement the "wait and cooperative exit" way of
> handling death by keyboard interrupt.

> But note that shells like mksh will anyway do a exit(128+signum)
> when they receive a signal.

Since the signal handler is set to the default value before sending
the signal to itself the process is not the one handling the signal.
The default action for most signals is to terminate the process.  See
signal(7) for the full list.  As long as the process resets the
disposition of the signal to the default (SIG_DFL) then the default
action occurs.  The default action is to terminate the program and the
kernel will set the exit status appropriately for processes terminated
by a signal.

You mentioned mksh and so I tested mksh version 52c on my Debian
system using #!/bin/mksh.  It does not do exit(128+signum) but returns
the proper signal.  I don't see any problem.  Which means that mksh is
resetting the signal handler to the default value properly.

Perhaps in a different version mksh had problematic behavior?

Bob



reply via email to

[Prev in Thread] Current Thread [Next in Thread]