From: Denys Vlasenko Date: Fri, 14 Jul 2017 11:36:48 +0000 (+0200) Subject: hush: add support for "set -e" X-Git-Tag: 1_28_0~408 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=9fda609a60506f4c1f73f0034cafd2b3434f4df3;p=oweals%2Fbusybox.git 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 --- diff --git a/shell/hush.c b/shell/hush.c index 89cd47d8f..553c8e64a 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -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: diff --git a/shell/hush_test/hush-misc/errexit1.right b/shell/hush_test/hush-misc/errexit1.right new file mode 100644 index 000000000..d86bac9de --- /dev/null +++ b/shell/hush_test/hush-misc/errexit1.right @@ -0,0 +1 @@ +OK diff --git a/shell/hush_test/hush-misc/errexit1.tests b/shell/hush_test/hush-misc/errexit1.tests new file mode 100755 index 000000000..7b4a15634 --- /dev/null +++ b/shell/hush_test/hush-misc/errexit1.tests @@ -0,0 +1,5 @@ +set -e +(true) +echo OK +(false) +echo FAIL diff --git a/shell/hush_test/hush-signals/signal8.right b/shell/hush_test/hush-signals/signal8.right new file mode 100644 index 000000000..39572f30e --- /dev/null +++ b/shell/hush_test/hush-signals/signal8.right @@ -0,0 +1,3 @@ +Removing traps +End of exit_func +Done: 0 diff --git a/shell/hush_test/hush-signals/signal8.tests b/shell/hush_test/hush-signals/signal8.tests new file mode 100755 index 000000000..731af7477 --- /dev/null +++ b/shell/hush_test/hush-signals/signal8.tests @@ -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: $? diff --git a/shell/hush_test/hush-signals/signal9.right b/shell/hush_test/hush-signals/signal9.right new file mode 100644 index 000000000..39572f30e --- /dev/null +++ b/shell/hush_test/hush-signals/signal9.right @@ -0,0 +1,3 @@ +Removing traps +End of exit_func +Done: 0 diff --git a/shell/hush_test/hush-signals/signal9.tests b/shell/hush_test/hush-signals/signal9.tests new file mode 100755 index 000000000..18e71012b --- /dev/null +++ b/shell/hush_test/hush-signals/signal9.tests @@ -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: $?