hush: add support for "set -e"

function                                             old     new   delta
run_list                                             978    1046     +68
o_opt_strings                                         24      32      +8
reset_traps_to_defaults                              136     142      +6
pick_sighandler                                       57      60      +3
packed_usage                                       31772   31770      -2
hush_main                                            983     961     -22
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 4/2 up/down: 85/-24)             Total: 61 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2017-07-14 13:36:48 +02:00
parent 75e90b1548
commit 9fda609a60
7 changed files with 96 additions and 21 deletions

View File

@ -49,7 +49,6 @@
* [un]alias, command, fc, getopts, newgrp, readonly, times
* make complex ${var%...} constructs support optional
* make here documents optional
* set -e (some ash testsuite entries use it, want to adopt those)
*
* Bash compat TODO:
* redirection of stdout+stderr: &> and >&
@ -286,7 +285,7 @@
* therefore we don't show them either.
*/
//usage:#define hush_trivial_usage
//usage: "[-nxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
//usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
//usage:#define hush_full_usage "\n\n"
//usage: "Unix shell interpreter"
@ -747,6 +746,7 @@ struct function {
static const char o_opt_strings[] ALIGN1 =
"pipefail\0"
"noexec\0"
"errexit\0"
#if ENABLE_HUSH_MODE_X
"xtrace\0"
#endif
@ -754,6 +754,7 @@ static const char o_opt_strings[] ALIGN1 =
enum {
OPT_O_PIPEFAIL,
OPT_O_NOEXEC,
OPT_O_ERREXIT,
#if ENABLE_HUSH_MODE_X
OPT_O_XTRACE,
#endif
@ -810,6 +811,25 @@ struct globals {
#else
# define G_saved_tty_pgrp 0
#endif
/* How deeply are we in context where "set -e" is ignored */
int errexit_depth;
/* "set -e" rules (do we follow them correctly?):
* Exit if pipe, list, or compound command exits with a non-zero status.
* Shell does not exit if failed command is part of condition in
* if/while, part of && or || list except the last command, any command
* in a pipe but the last, or if the command's return value is being
* inverted with !. If a compound command other than a subshell returns a
* non-zero status because a command failed while -e was being ignored, the
* shell does not exit. A trap on ERR, if set, is executed before the shell
* exits [ERR is a bashism].
*
* If a compound command or function executes in a context where -e is
* ignored, none of the commands executed within are affected by the -e
* setting. If a compound command or function sets -e while executing in a
* context where -e is ignored, that setting does not have any effect until
* the compound command or the command containing the function call completes.
*/
char o_opt[NUM_OPT_O];
#if ENABLE_HUSH_MODE_X
# define G_x_mode (G.o_opt[OPT_O_XTRACE])
@ -5159,7 +5179,7 @@ static struct pipe *parse_stream(char **pstring,
* and it will match } earlier (not here). */
syntax_error_unexpected_ch(ch);
G.last_exitcode = 2;
goto parse_error1;
goto parse_error2;
default:
if (HUSH_DEBUG)
bb_error_msg_and_die("BUG: unexpected %c\n", ch);
@ -5168,7 +5188,7 @@ static struct pipe *parse_stream(char **pstring,
parse_error:
G.last_exitcode = 1;
parse_error1:
parse_error2:
{
struct parse_context *pctx;
IF_HAS_KEYWORDS(struct parse_context *p2;)
@ -8021,6 +8041,7 @@ static int run_list(struct pipe *pi)
/* Go through list of pipes, (maybe) executing them. */
for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
int r;
int sv_errexit_depth;
if (G.flag_SIGINT)
break;
@ -8030,6 +8051,13 @@ static int run_list(struct pipe *pi)
IF_HAS_KEYWORDS(rword = pi->res_word;)
debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n",
rword, cond_code, last_rword);
sv_errexit_depth = G.errexit_depth;
if (IF_HAS_KEYWORDS(rword == RES_IF || rword == RES_ELIF ||)
pi->followup != PIPE_SEQ
) {
G.errexit_depth++;
}
#if ENABLE_HUSH_LOOPS
if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR)
&& loop_top == NULL /* avoid bumping G.depth_of_loop twice */
@ -8243,6 +8271,14 @@ static int run_list(struct pipe *pi)
check_and_run_traps();
}
/* Handle "set -e" */
if (rcode != 0 && G.o_opt[OPT_O_ERREXIT]) {
debug_printf_exec("ERREXIT:1 errexit_depth:%d\n", G.errexit_depth);
if (G.errexit_depth == 0)
hush_exit(rcode);
}
G.errexit_depth = sv_errexit_depth;
/* Analyze how result affects subsequent commands */
#if ENABLE_HUSH_IF
if (rword == RES_IF || rword == RES_ELIF)
@ -8422,22 +8458,9 @@ static int set_mode(int state, char mode, const char *o_opt)
G.o_opt[idx] = state;
break;
}
/* TODO: set -e
Exit if pipe, list, or compound command exits with a non-zero status.
Shell does not exit if failed command is part of condition in
if/while, part of && or || list except the last command, any command
in a pipe but the last, or if the command's return value is being
inverted with !. If a compound command other than a subshell returns a
non-zero status because a command failed while -e was being ignored, the
shell does not exit. A trap on ERR, if set, is executed before the shell
exits [ERR is a bashism].
If a compound command or function executes in a context where -e is
ignored, none of the commands executed within are affected by the -e
setting. If a compound command or function sets -e while executing in a
context where -e is ignored, that setting does not have any effect until
the compound command or the command containing the function call completes.
*/
case 'e':
G.o_opt[OPT_O_ERREXIT] = state;
break;
default:
return EXIT_FAILURE;
}
@ -8564,7 +8587,7 @@ int hush_main(int argc, char **argv)
flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0;
builtin_argc = 0;
while (1) {
opt = getopt(argc, argv, "+c:xinsl"
opt = getopt(argc, argv, "+c:exinsl"
#if !BB_MMU
"<:$:R:V:"
# if ENABLE_HUSH_FUNCTIONS
@ -8682,6 +8705,7 @@ int hush_main(int argc, char **argv)
#endif
case 'n':
case 'x':
case 'e':
if (set_mode(1, opt, NULL) == 0) /* no error */
break;
default:

View File

@ -0,0 +1 @@
OK

View File

@ -0,0 +1,5 @@
set -e
(true)
echo OK
(false)
echo FAIL

View File

@ -0,0 +1,3 @@
Removing traps
End of exit_func
Done: 0

View File

@ -0,0 +1,18 @@
"$THIS_SH" -c '
exit_func() {
echo "Removing traps"
trap - EXIT TERM INT
echo "End of exit_func"
}
set -e
trap exit_func EXIT TERM INT
sleep 2
exit 77
' &
sleep 1
# BUG: ash kills -PGRP, but in non-interactive shell we do not create pgrps!
# In this case, bash kills by PID, not PGRP.
kill -TERM %1
wait
echo Done: $?

View File

@ -0,0 +1,3 @@
Removing traps
End of exit_func
Done: 0

View File

@ -0,0 +1,21 @@
# Note: the inner script is a test which checks for a different bug
# (ordering between INT handler and exit on "set -e"),
# but so far I did not figure out how to simulate it non-interactively.
"$THIS_SH" -c '
exit_func() {
echo "Removing traps"
trap - EXIT TERM INT
echo "End of exit_func"
}
set -e
trap exit_func EXIT TERM INT
sleep 2
exit 77
' &
child=$!
sleep 1
kill -TERM $child
wait
echo Done: $?