[Toybox] Confused by bash trap handler return value.
Rob Landley
rob at landley.net
Tue Feb 11 15:41:35 PST 2025
On 2/11/25 08:34, Chet Ramey wrote:
> On 2/10/25 7:25 PM, Rob Landley wrote:
>> I don't understand how to reconcile the behavior of all three of these
>> in debian's bash 5.2.15(1):
>
> Trap actions have no "return value." POSIX says:
>
> "The value of "$?" after the trap action completes shall be the value it
> had before the trap action was executed."
Ok, I can do that. Thanks.
The question is how to TEST it...
>> 3) The previous statement returns 1 and the trap handler returns 1, so
>> the next statement sees zero...?
>
> The AND-OR list returns 0, since the trap command succeeds, the kill
> command runs, and kill returns 0 if it sends at least one signal
> successfully. That is documented:
>
> "kill returns true if at least one signal was successfully sent,
> or false if an error occurs or an invalid option is encountered."
/bin/kill from procps is returning 1 if an error occurred (because a
"PID does not exist" error occurs), bash kill is returning 0 because at
least one signal was successfully sent (despite an error having
occurred). Their behavior differs because you can read the above either
way. (You're reading the or as a short circuit operator, they're
probably reading the "at least one" as meaning "kill -s USR1" should
return error and "return false if an error occurs" as a mandate.)
I suppose I can implement a 0 return code for the kill builtin and a 1
return code if it's called via the $PATH, but... ew?
My problem is for the test suite I want the command to send a signal but
return nonzero, and it's sort of atomic. The signal should reliably be
delivered by the linux kernel before the command returns (because the
command made a syscall, signals are checked for at syscall return or
when the scheduler runs, and the shell is wait()ing for the child
process so won't advance until after the command sending a signal to its
parent process exits... which is a syscall that checks for the signal
upon return), so the shell should reliably notice the pending signal
before running the next shell command and execute the trap handler at a
specific location, giving me deterministic output. Thus the /bin/kill
behavior is useful to me to create a test around (because 987654321
should never be a valid PID, but it already sent the signal to $$
because it parses the command line arguments in order), and the kill
builtin behavior is not useful to me.
Any sort of split up "kill; false" means the signal would happen before
the false, and thus I can't set up the non-success return code to test
after the signal handler. Doing a trap "" USR1; kill -s USR1; false;
trap "thingy" USR1 to ignore the signal around a critical section A)
isn't really testing the same thing, B) again stomps the return code
(although maybe trap "thingy" USR1 POTATO would...?).
The next alternative to making a test for this is some sort of
"backgrounding and sleep" nonsense, which tends to end badly on loaded
systems (such as android's test servers: the amount of time a sleep
actually TAKES varies hugely). Although I also need to test what "read"
does when we get a signal...
I'm trying not to need to run additional binaries for my test suite, it
makes testing stuff in cross compiled environments a lot more
complicated. (Right now the tests are a series of shell scripts you run
against provided command line utilities. A compiler existing on the
system is not required, nor are any binaries shipped with the test suite
other than small test data files for commands like file/blkid/tar/gzip
to examine, none of them are expected to be runnable on the current
architecture...) And yes I wrote an "expect" implementation in bash.
I can test it by hand on my dev box, but this is a regression test suite
meant to run after an arbitrary build (and ideally compare results
against debian and/or busybox host commands)...
Rob
More information about the Toybox
mailing list