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:
parent
75e90b1548
commit
9fda609a60
66
shell/hush.c
66
shell/hush.c
@ -49,7 +49,6 @@
|
|||||||
* [un]alias, command, fc, getopts, newgrp, readonly, times
|
* [un]alias, command, fc, getopts, newgrp, readonly, times
|
||||||
* make complex ${var%...} constructs support optional
|
* make complex ${var%...} constructs support optional
|
||||||
* make here documents optional
|
* make here documents optional
|
||||||
* set -e (some ash testsuite entries use it, want to adopt those)
|
|
||||||
*
|
*
|
||||||
* Bash compat TODO:
|
* Bash compat TODO:
|
||||||
* redirection of stdout+stderr: &> and >&
|
* redirection of stdout+stderr: &> and >&
|
||||||
@ -286,7 +285,7 @@
|
|||||||
* therefore we don't show them either.
|
* therefore we don't show them either.
|
||||||
*/
|
*/
|
||||||
//usage:#define hush_trivial_usage
|
//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:#define hush_full_usage "\n\n"
|
||||||
//usage: "Unix shell interpreter"
|
//usage: "Unix shell interpreter"
|
||||||
|
|
||||||
@ -747,6 +746,7 @@ struct function {
|
|||||||
static const char o_opt_strings[] ALIGN1 =
|
static const char o_opt_strings[] ALIGN1 =
|
||||||
"pipefail\0"
|
"pipefail\0"
|
||||||
"noexec\0"
|
"noexec\0"
|
||||||
|
"errexit\0"
|
||||||
#if ENABLE_HUSH_MODE_X
|
#if ENABLE_HUSH_MODE_X
|
||||||
"xtrace\0"
|
"xtrace\0"
|
||||||
#endif
|
#endif
|
||||||
@ -754,6 +754,7 @@ static const char o_opt_strings[] ALIGN1 =
|
|||||||
enum {
|
enum {
|
||||||
OPT_O_PIPEFAIL,
|
OPT_O_PIPEFAIL,
|
||||||
OPT_O_NOEXEC,
|
OPT_O_NOEXEC,
|
||||||
|
OPT_O_ERREXIT,
|
||||||
#if ENABLE_HUSH_MODE_X
|
#if ENABLE_HUSH_MODE_X
|
||||||
OPT_O_XTRACE,
|
OPT_O_XTRACE,
|
||||||
#endif
|
#endif
|
||||||
@ -810,6 +811,25 @@ struct globals {
|
|||||||
#else
|
#else
|
||||||
# define G_saved_tty_pgrp 0
|
# define G_saved_tty_pgrp 0
|
||||||
#endif
|
#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];
|
char o_opt[NUM_OPT_O];
|
||||||
#if ENABLE_HUSH_MODE_X
|
#if ENABLE_HUSH_MODE_X
|
||||||
# define G_x_mode (G.o_opt[OPT_O_XTRACE])
|
# 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). */
|
* and it will match } earlier (not here). */
|
||||||
syntax_error_unexpected_ch(ch);
|
syntax_error_unexpected_ch(ch);
|
||||||
G.last_exitcode = 2;
|
G.last_exitcode = 2;
|
||||||
goto parse_error1;
|
goto parse_error2;
|
||||||
default:
|
default:
|
||||||
if (HUSH_DEBUG)
|
if (HUSH_DEBUG)
|
||||||
bb_error_msg_and_die("BUG: unexpected %c\n", ch);
|
bb_error_msg_and_die("BUG: unexpected %c\n", ch);
|
||||||
@ -5168,7 +5188,7 @@ static struct pipe *parse_stream(char **pstring,
|
|||||||
|
|
||||||
parse_error:
|
parse_error:
|
||||||
G.last_exitcode = 1;
|
G.last_exitcode = 1;
|
||||||
parse_error1:
|
parse_error2:
|
||||||
{
|
{
|
||||||
struct parse_context *pctx;
|
struct parse_context *pctx;
|
||||||
IF_HAS_KEYWORDS(struct parse_context *p2;)
|
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. */
|
/* Go through list of pipes, (maybe) executing them. */
|
||||||
for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
|
for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
|
||||||
int r;
|
int r;
|
||||||
|
int sv_errexit_depth;
|
||||||
|
|
||||||
if (G.flag_SIGINT)
|
if (G.flag_SIGINT)
|
||||||
break;
|
break;
|
||||||
@ -8030,6 +8051,13 @@ static int run_list(struct pipe *pi)
|
|||||||
IF_HAS_KEYWORDS(rword = pi->res_word;)
|
IF_HAS_KEYWORDS(rword = pi->res_word;)
|
||||||
debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n",
|
debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n",
|
||||||
rword, cond_code, last_rword);
|
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 ENABLE_HUSH_LOOPS
|
||||||
if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR)
|
if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR)
|
||||||
&& loop_top == NULL /* avoid bumping G.depth_of_loop twice */
|
&& 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();
|
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 */
|
/* Analyze how result affects subsequent commands */
|
||||||
#if ENABLE_HUSH_IF
|
#if ENABLE_HUSH_IF
|
||||||
if (rword == RES_IF || rword == RES_ELIF)
|
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;
|
G.o_opt[idx] = state;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* TODO: set -e
|
case 'e':
|
||||||
Exit if pipe, list, or compound command exits with a non-zero status.
|
G.o_opt[OPT_O_ERREXIT] = state;
|
||||||
Shell does not exit if failed command is part of condition in
|
break;
|
||||||
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.
|
|
||||||
*/
|
|
||||||
default:
|
default:
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
@ -8564,7 +8587,7 @@ int hush_main(int argc, char **argv)
|
|||||||
flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0;
|
flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0;
|
||||||
builtin_argc = 0;
|
builtin_argc = 0;
|
||||||
while (1) {
|
while (1) {
|
||||||
opt = getopt(argc, argv, "+c:xinsl"
|
opt = getopt(argc, argv, "+c:exinsl"
|
||||||
#if !BB_MMU
|
#if !BB_MMU
|
||||||
"<:$:R:V:"
|
"<:$:R:V:"
|
||||||
# if ENABLE_HUSH_FUNCTIONS
|
# if ENABLE_HUSH_FUNCTIONS
|
||||||
@ -8682,6 +8705,7 @@ int hush_main(int argc, char **argv)
|
|||||||
#endif
|
#endif
|
||||||
case 'n':
|
case 'n':
|
||||||
case 'x':
|
case 'x':
|
||||||
|
case 'e':
|
||||||
if (set_mode(1, opt, NULL) == 0) /* no error */
|
if (set_mode(1, opt, NULL) == 0) /* no error */
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
1
shell/hush_test/hush-misc/errexit1.right
Normal file
1
shell/hush_test/hush-misc/errexit1.right
Normal file
@ -0,0 +1 @@
|
|||||||
|
OK
|
5
shell/hush_test/hush-misc/errexit1.tests
Executable file
5
shell/hush_test/hush-misc/errexit1.tests
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
set -e
|
||||||
|
(true)
|
||||||
|
echo OK
|
||||||
|
(false)
|
||||||
|
echo FAIL
|
3
shell/hush_test/hush-signals/signal8.right
Normal file
3
shell/hush_test/hush-signals/signal8.right
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Removing traps
|
||||||
|
End of exit_func
|
||||||
|
Done: 0
|
18
shell/hush_test/hush-signals/signal8.tests
Executable file
18
shell/hush_test/hush-signals/signal8.tests
Executable 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: $?
|
3
shell/hush_test/hush-signals/signal9.right
Normal file
3
shell/hush_test/hush-signals/signal9.right
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Removing traps
|
||||||
|
End of exit_func
|
||||||
|
Done: 0
|
21
shell/hush_test/hush-signals/signal9.tests
Executable file
21
shell/hush_test/hush-signals/signal9.tests
Executable 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: $?
|
Loading…
x
Reference in New Issue
Block a user