diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2017-07-14 13:36:48 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2017-07-14 13:36:48 +0200 |
commit | 9fda609a60506f4c1f73f0034cafd2b3434f4df3 (patch) | |
tree | d3ea98fe9f47577162a884ec6cd1a6b233e72bc4 | |
parent | 75e90b15482184db83f03c67b53d4220888c6c9d (diff) | |
download | busybox-w32-9fda609a60506f4c1f73f0034cafd2b3434f4df3.tar.gz busybox-w32-9fda609a60506f4c1f73f0034cafd2b3434f4df3.tar.bz2 busybox-w32-9fda609a60506f4c1f73f0034cafd2b3434f4df3.zip |
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>
-rw-r--r-- | shell/hush.c | 66 | ||||
-rw-r--r-- | shell/hush_test/hush-misc/errexit1.right | 1 | ||||
-rwxr-xr-x | shell/hush_test/hush-misc/errexit1.tests | 5 | ||||
-rw-r--r-- | shell/hush_test/hush-signals/signal8.right | 3 | ||||
-rwxr-xr-x | shell/hush_test/hush-signals/signal8.tests | 18 | ||||
-rw-r--r-- | shell/hush_test/hush-signals/signal9.right | 3 | ||||
-rwxr-xr-x | shell/hush_test/hush-signals/signal9.tests | 21 |
7 files changed, 96 insertions, 21 deletions
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 @@ | |||
49 | * [un]alias, command, fc, getopts, newgrp, readonly, times | 49 | * [un]alias, command, fc, getopts, newgrp, readonly, times |
50 | * make complex ${var%...} constructs support optional | 50 | * make complex ${var%...} constructs support optional |
51 | * make here documents optional | 51 | * make here documents optional |
52 | * set -e (some ash testsuite entries use it, want to adopt those) | ||
53 | * | 52 | * |
54 | * Bash compat TODO: | 53 | * Bash compat TODO: |
55 | * redirection of stdout+stderr: &> and >& | 54 | * redirection of stdout+stderr: &> and >& |
@@ -286,7 +285,7 @@ | |||
286 | * therefore we don't show them either. | 285 | * therefore we don't show them either. |
287 | */ | 286 | */ |
288 | //usage:#define hush_trivial_usage | 287 | //usage:#define hush_trivial_usage |
289 | //usage: "[-nxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" | 288 | //usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" |
290 | //usage:#define hush_full_usage "\n\n" | 289 | //usage:#define hush_full_usage "\n\n" |
291 | //usage: "Unix shell interpreter" | 290 | //usage: "Unix shell interpreter" |
292 | 291 | ||
@@ -747,6 +746,7 @@ struct function { | |||
747 | static const char o_opt_strings[] ALIGN1 = | 746 | static const char o_opt_strings[] ALIGN1 = |
748 | "pipefail\0" | 747 | "pipefail\0" |
749 | "noexec\0" | 748 | "noexec\0" |
749 | "errexit\0" | ||
750 | #if ENABLE_HUSH_MODE_X | 750 | #if ENABLE_HUSH_MODE_X |
751 | "xtrace\0" | 751 | "xtrace\0" |
752 | #endif | 752 | #endif |
@@ -754,6 +754,7 @@ static const char o_opt_strings[] ALIGN1 = | |||
754 | enum { | 754 | enum { |
755 | OPT_O_PIPEFAIL, | 755 | OPT_O_PIPEFAIL, |
756 | OPT_O_NOEXEC, | 756 | OPT_O_NOEXEC, |
757 | OPT_O_ERREXIT, | ||
757 | #if ENABLE_HUSH_MODE_X | 758 | #if ENABLE_HUSH_MODE_X |
758 | OPT_O_XTRACE, | 759 | OPT_O_XTRACE, |
759 | #endif | 760 | #endif |
@@ -810,6 +811,25 @@ struct globals { | |||
810 | #else | 811 | #else |
811 | # define G_saved_tty_pgrp 0 | 812 | # define G_saved_tty_pgrp 0 |
812 | #endif | 813 | #endif |
814 | /* How deeply are we in context where "set -e" is ignored */ | ||
815 | int errexit_depth; | ||
816 | /* "set -e" rules (do we follow them correctly?): | ||
817 | * Exit if pipe, list, or compound command exits with a non-zero status. | ||
818 | * Shell does not exit if failed command is part of condition in | ||
819 | * if/while, part of && or || list except the last command, any command | ||
820 | * in a pipe but the last, or if the command's return value is being | ||
821 | * inverted with !. If a compound command other than a subshell returns a | ||
822 | * non-zero status because a command failed while -e was being ignored, the | ||
823 | * shell does not exit. A trap on ERR, if set, is executed before the shell | ||
824 | * exits [ERR is a bashism]. | ||
825 | * | ||
826 | * If a compound command or function executes in a context where -e is | ||
827 | * ignored, none of the commands executed within are affected by the -e | ||
828 | * setting. If a compound command or function sets -e while executing in a | ||
829 | * context where -e is ignored, that setting does not have any effect until | ||
830 | * the compound command or the command containing the function call completes. | ||
831 | */ | ||
832 | |||
813 | char o_opt[NUM_OPT_O]; | 833 | char o_opt[NUM_OPT_O]; |
814 | #if ENABLE_HUSH_MODE_X | 834 | #if ENABLE_HUSH_MODE_X |
815 | # define G_x_mode (G.o_opt[OPT_O_XTRACE]) | 835 | # define G_x_mode (G.o_opt[OPT_O_XTRACE]) |
@@ -5159,7 +5179,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5159 | * and it will match } earlier (not here). */ | 5179 | * and it will match } earlier (not here). */ |
5160 | syntax_error_unexpected_ch(ch); | 5180 | syntax_error_unexpected_ch(ch); |
5161 | G.last_exitcode = 2; | 5181 | G.last_exitcode = 2; |
5162 | goto parse_error1; | 5182 | goto parse_error2; |
5163 | default: | 5183 | default: |
5164 | if (HUSH_DEBUG) | 5184 | if (HUSH_DEBUG) |
5165 | bb_error_msg_and_die("BUG: unexpected %c\n", ch); | 5185 | bb_error_msg_and_die("BUG: unexpected %c\n", ch); |
@@ -5168,7 +5188,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5168 | 5188 | ||
5169 | parse_error: | 5189 | parse_error: |
5170 | G.last_exitcode = 1; | 5190 | G.last_exitcode = 1; |
5171 | parse_error1: | 5191 | parse_error2: |
5172 | { | 5192 | { |
5173 | struct parse_context *pctx; | 5193 | struct parse_context *pctx; |
5174 | IF_HAS_KEYWORDS(struct parse_context *p2;) | 5194 | IF_HAS_KEYWORDS(struct parse_context *p2;) |
@@ -8021,6 +8041,7 @@ static int run_list(struct pipe *pi) | |||
8021 | /* Go through list of pipes, (maybe) executing them. */ | 8041 | /* Go through list of pipes, (maybe) executing them. */ |
8022 | for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) { | 8042 | for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) { |
8023 | int r; | 8043 | int r; |
8044 | int sv_errexit_depth; | ||
8024 | 8045 | ||
8025 | if (G.flag_SIGINT) | 8046 | if (G.flag_SIGINT) |
8026 | break; | 8047 | break; |
@@ -8030,6 +8051,13 @@ static int run_list(struct pipe *pi) | |||
8030 | IF_HAS_KEYWORDS(rword = pi->res_word;) | 8051 | IF_HAS_KEYWORDS(rword = pi->res_word;) |
8031 | debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n", | 8052 | debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n", |
8032 | rword, cond_code, last_rword); | 8053 | rword, cond_code, last_rword); |
8054 | |||
8055 | sv_errexit_depth = G.errexit_depth; | ||
8056 | if (IF_HAS_KEYWORDS(rword == RES_IF || rword == RES_ELIF ||) | ||
8057 | pi->followup != PIPE_SEQ | ||
8058 | ) { | ||
8059 | G.errexit_depth++; | ||
8060 | } | ||
8033 | #if ENABLE_HUSH_LOOPS | 8061 | #if ENABLE_HUSH_LOOPS |
8034 | if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) | 8062 | if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) |
8035 | && loop_top == NULL /* avoid bumping G.depth_of_loop twice */ | 8063 | && loop_top == NULL /* avoid bumping G.depth_of_loop twice */ |
@@ -8243,6 +8271,14 @@ static int run_list(struct pipe *pi) | |||
8243 | check_and_run_traps(); | 8271 | check_and_run_traps(); |
8244 | } | 8272 | } |
8245 | 8273 | ||
8274 | /* Handle "set -e" */ | ||
8275 | if (rcode != 0 && G.o_opt[OPT_O_ERREXIT]) { | ||
8276 | debug_printf_exec("ERREXIT:1 errexit_depth:%d\n", G.errexit_depth); | ||
8277 | if (G.errexit_depth == 0) | ||
8278 | hush_exit(rcode); | ||
8279 | } | ||
8280 | G.errexit_depth = sv_errexit_depth; | ||
8281 | |||
8246 | /* Analyze how result affects subsequent commands */ | 8282 | /* Analyze how result affects subsequent commands */ |
8247 | #if ENABLE_HUSH_IF | 8283 | #if ENABLE_HUSH_IF |
8248 | if (rword == RES_IF || rword == RES_ELIF) | 8284 | if (rword == RES_IF || rword == RES_ELIF) |
@@ -8422,22 +8458,9 @@ static int set_mode(int state, char mode, const char *o_opt) | |||
8422 | G.o_opt[idx] = state; | 8458 | G.o_opt[idx] = state; |
8423 | break; | 8459 | break; |
8424 | } | 8460 | } |
8425 | /* TODO: set -e | 8461 | case 'e': |
8426 | Exit if pipe, list, or compound command exits with a non-zero status. | 8462 | G.o_opt[OPT_O_ERREXIT] = state; |
8427 | Shell does not exit if failed command is part of condition in | 8463 | break; |
8428 | if/while, part of && or || list except the last command, any command | ||
8429 | in a pipe but the last, or if the command's return value is being | ||
8430 | inverted with !. If a compound command other than a subshell returns a | ||
8431 | non-zero status because a command failed while -e was being ignored, the | ||
8432 | shell does not exit. A trap on ERR, if set, is executed before the shell | ||
8433 | exits [ERR is a bashism]. | ||
8434 | |||
8435 | If a compound command or function executes in a context where -e is | ||
8436 | ignored, none of the commands executed within are affected by the -e | ||
8437 | setting. If a compound command or function sets -e while executing in a | ||
8438 | context where -e is ignored, that setting does not have any effect until | ||
8439 | the compound command or the command containing the function call completes. | ||
8440 | */ | ||
8441 | default: | 8464 | default: |
8442 | return EXIT_FAILURE; | 8465 | return EXIT_FAILURE; |
8443 | } | 8466 | } |
@@ -8564,7 +8587,7 @@ int hush_main(int argc, char **argv) | |||
8564 | flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0; | 8587 | flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0; |
8565 | builtin_argc = 0; | 8588 | builtin_argc = 0; |
8566 | while (1) { | 8589 | while (1) { |
8567 | opt = getopt(argc, argv, "+c:xinsl" | 8590 | opt = getopt(argc, argv, "+c:exinsl" |
8568 | #if !BB_MMU | 8591 | #if !BB_MMU |
8569 | "<:$:R:V:" | 8592 | "<:$:R:V:" |
8570 | # if ENABLE_HUSH_FUNCTIONS | 8593 | # if ENABLE_HUSH_FUNCTIONS |
@@ -8682,6 +8705,7 @@ int hush_main(int argc, char **argv) | |||
8682 | #endif | 8705 | #endif |
8683 | case 'n': | 8706 | case 'n': |
8684 | case 'x': | 8707 | case 'x': |
8708 | case 'e': | ||
8685 | if (set_mode(1, opt, NULL) == 0) /* no error */ | 8709 | if (set_mode(1, opt, NULL) == 0) /* no error */ |
8686 | break; | 8710 | break; |
8687 | default: | 8711 | 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 @@ | |||
1 | set -e | ||
2 | (true) | ||
3 | echo OK | ||
4 | (false) | ||
5 | 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 @@ | |||
1 | Removing traps | ||
2 | End of exit_func | ||
3 | 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 @@ | |||
1 | "$THIS_SH" -c ' | ||
2 | exit_func() { | ||
3 | echo "Removing traps" | ||
4 | trap - EXIT TERM INT | ||
5 | echo "End of exit_func" | ||
6 | } | ||
7 | set -e | ||
8 | trap exit_func EXIT TERM INT | ||
9 | sleep 2 | ||
10 | exit 77 | ||
11 | ' & | ||
12 | |||
13 | sleep 1 | ||
14 | # BUG: ash kills -PGRP, but in non-interactive shell we do not create pgrps! | ||
15 | # In this case, bash kills by PID, not PGRP. | ||
16 | kill -TERM %1 | ||
17 | wait | ||
18 | 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 @@ | |||
1 | Removing traps | ||
2 | End of exit_func | ||
3 | 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 @@ | |||
1 | # Note: the inner script is a test which checks for a different bug | ||
2 | # (ordering between INT handler and exit on "set -e"), | ||
3 | # but so far I did not figure out how to simulate it non-interactively. | ||
4 | |||
5 | "$THIS_SH" -c ' | ||
6 | exit_func() { | ||
7 | echo "Removing traps" | ||
8 | trap - EXIT TERM INT | ||
9 | echo "End of exit_func" | ||
10 | } | ||
11 | set -e | ||
12 | trap exit_func EXIT TERM INT | ||
13 | sleep 2 | ||
14 | exit 77 | ||
15 | ' & | ||
16 | |||
17 | child=$! | ||
18 | sleep 1 | ||
19 | kill -TERM $child | ||
20 | wait | ||
21 | echo Done: $? | ||