[PATCH] shell script error management in ash (set of 6 patches)

Harald van Dijk harald at gigawatt.nl
Sun Aug 22 00:23:27 UTC 2021


On 22/08/2021 00:31, Roberto A. Foglietta wrote:
> Il giorno sab 21 ago 2021 alle ore 23:33 Harald van Dijk 
> <harald at gigawatt.nl <mailto:harald at gigawatt.nl>> ha scritto:
> 
>     Hi again,
> 
>     Another bug: the exit status is not preserved.
> 
>     Consider
> 
>         busybox ash -c 'trap "echo ERR" ERR; false; echo $?'
> 
>     This prints ERR, and then 0, because the echo command completed
>     successfully. It is supposed to print ERR, and then 1, because like
>     EXIT
>     actions, ERR actions are supposed to preserve the exit status of the
>     last command before the action was invoked.
> 
> 
> Patch 11 and testsuite attached.
> In the test suite two cases are evaluated exit status =0 !0

I think that's probably correct but suboptimal. You're saving exitstatus 
using the savestatus global variable, but in that block, you already 
know that exitstatus is always going to be equal to the local status 
variable: the only time that exitstatus != status is if the earlier 
exitstatus = status; assignment is bypassed, but the only way that can 
get bypassed is if evaltree(NULL, flags) is called, in which case status 
will equal 0 so the block never gets entered. You should be able to 
repeat the earlier exitstatus = status; assignment without saving it in 
a new variable.

>     I am also somewhat concerned that there may be cases where the global
>     variables "doingtrap" and "recursive" are set, but evalstring() exits
>     via raise_exception() and they are never cleared, preventing future ERR
>     handlers from running. I do not currently have a test case where it
>     matters, but am not convinced there are no test cases where it matters.
> 
> 
> line 13.320 in evalstring
> 
> exception_handler = savehandler;
> if (ex)
> longjmp(exception_handler->loc, ex);
> 
> do you think that this might resolve the issue?
> 
> exception_handler = savehandler;
> if (ex) {
> doingtrap = 0; recursive = 0;
> longjmp(exception_handler->loc, ex);
> }

I don't think that's right. evalstring() may end up calling evalstring() 
again recursively, and in that case you wouldn't want to clear doingtrap 
or recursive.

> Obviously recursive should become a global variable.
> When an exception is raised, what is going to happen? Exit?
> In case of exit then it is not a problem, IMHO.

That's why I haven't been able to create a test case yet where it goes 
wrong: in *almost* all cases where an exception is raised, the shell 
will terminate. The shell won't immediately terminate, EXIT handlers run 
first, but if that runs in the context of an ERR handler, leaving 
doingtrap and recursive alone is the right thing to do, as ERR handlers 
shouldn't run again.

But only almost all cases, not all cases. One exception, and I am not 
sure if it is the only one, is that the 'command' builtin can catch and 
handle exceptions that come from errors raised by special built-ins. 
Normally they cause the shell to terminate, but the 'command' built-in 
makes such errors non-fatal.

If it is possible for a special built-in within an ERR handler to raise 
an exception, and for that exception to be caught and handled by an 
outer 'command' builtin, it should be possible to run more ERR handlers 
after that.

> Thank you, -R


More information about the busybox mailing list