From 6a2d40f239566e886ef76542a75662cee9380a0e Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Mon, 28 Jul 2008 23:07:06 +0000 Subject: [PATCH] hush: support "break N" and "continue N" fix non-detection of builtins and applets in "v=break; ...; $v; ..." case add testsuite entries for the above function old new delta builtin_break 12 93 +81 run_list 1948 1971 +23 builtin_continue 12 21 +9 pseudo_exec_argv 132 138 +6 builtin_exec 23 25 +2 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 5/0 up/down: 121/0) Total: 121 bytes --- shell/hush.c | 99 +++++++++++++++++--------- shell/hush_test/hush-misc/break1.right | 2 + shell/hush_test/hush-misc/break1.tests | 3 + shell/hush_test/hush-misc/break2.right | 3 + shell/hush_test/hush-misc/break2.tests | 6 ++ shell/hush_test/hush-misc/break3.right | 2 + shell/hush_test/hush-misc/break3.tests | 2 + 7 files changed, 82 insertions(+), 35 deletions(-) create mode 100644 shell/hush_test/hush-misc/break1.right create mode 100755 shell/hush_test/hush-misc/break1.tests create mode 100644 shell/hush_test/hush-misc/break2.right create mode 100755 shell/hush_test/hush-misc/break2.tests create mode 100644 shell/hush_test/hush-misc/break3.right create mode 100755 shell/hush_test/hush-misc/break3.tests diff --git a/shell/hush.c b/shell/hush.c index 21cb36564..a74fe4730 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -440,6 +440,7 @@ struct globals { smalluint last_return_code; char **global_argv; int global_argc; + unsigned depth_break_continue; pid_t last_bg_pid; const char *ifs; const char *cwd; @@ -487,7 +488,8 @@ enum { run_list_level = 0 }; #define global_argc (G.global_argc ) #define last_return_code (G.last_return_code) #define ifs (G.ifs ) -#define flag_break_continue (G.flag_break_continue) +#define flag_break_continue (G.flag_break_continue ) +#define depth_break_continue (G.depth_break_continue) #define fake_mode (G.fake_mode ) #define cwd (G.cwd ) #define last_bg_pid (G.last_bg_pid ) @@ -552,11 +554,11 @@ static int free_pipe(struct pipe *pi, int indent); static int setup_redirects(struct child_prog *prog, int squirrel[]); static int run_list(struct pipe *pi); #if BB_MMU -#define pseudo_exec_argv(ptrs2free, argv) pseudo_exec_argv(argv) -#define pseudo_exec(ptrs2free, child) pseudo_exec(child) +#define pseudo_exec_argv(ptrs2free, argv, argv_expanded) pseudo_exec_argv(argv, argv_expanded) +#define pseudo_exec(ptrs2free, child, argv_expanded) pseudo_exec(child, argv_expanded) #endif -static void pseudo_exec_argv(char **ptrs2free, char **argv) NORETURN; -static void pseudo_exec(char **ptrs2free, struct child_prog *child) NORETURN; +static void pseudo_exec_argv(char **ptrs2free, char **argv, char **argv_expanded) NORETURN; +static void pseudo_exec(char **ptrs2free, struct child_prog *child, char **argv_expanded) NORETURN; static int run_pipe(struct pipe *pi); /* data structure manipulation: */ static int setup_redirect(struct p_context *ctx, int fd, redir_type style, struct in_str *input); @@ -1410,7 +1412,7 @@ static void restore_redirects(int squirrel[]) * XXX no exit() here. If you don't exec, use _exit instead. * The at_exit handlers apparently confuse the calling process, * in particular stdin handling. Not sure why? -- because of vfork! (vda) */ -static void pseudo_exec_argv(char **ptrs2free, char **argv) +static void pseudo_exec_argv(char **ptrs2free, char **argv, char **argv_expanded) { int i, rcode; char *p; @@ -1432,10 +1434,14 @@ static void pseudo_exec_argv(char **ptrs2free, char **argv) if (!argv[0]) _exit(EXIT_SUCCESS); - argv = expand_strvec_to_strvec(argv); + if (argv_expanded) { + argv = argv_expanded; + } else { + argv = expand_strvec_to_strvec(argv); #if !BB_MMU - *ptrs2free++ = (char*) argv; + *ptrs2free++ = (char*) argv; #endif + } /* * Check if the command matches any of the builtins. @@ -1479,10 +1485,10 @@ static void pseudo_exec_argv(char **ptrs2free, char **argv) /* Called after [v]fork() in run_pipe() */ -static void pseudo_exec(char **ptrs2free, struct child_prog *child) +static void pseudo_exec(char **ptrs2free, struct child_prog *child, char **argv_expanded) { if (child->argv) - pseudo_exec_argv(ptrs2free, child->argv); + pseudo_exec_argv(ptrs2free, child->argv, argv_expanded); if (child->group) { #if !BB_MMU @@ -1760,14 +1766,16 @@ static int run_pipe(struct pipe *pi) int nextin; int pipefds[2]; /* pipefds[0] is for reading */ struct child_prog *child; + char **argv_expanded = NULL; + char **argv; const struct built_in_command *x; char *p; /* it is not always needed, but we aim to smaller code */ int squirrel[] = { -1, -1, -1 }; int rcode; - const int single_fg = (pi->num_progs == 1 && pi->followup != PIPE_BG); + const int single_and_fg = (pi->num_progs == 1 && pi->followup != PIPE_BG); - debug_printf_exec("run_pipe start: single_fg=%d\n", single_fg); + debug_printf_exec("run_pipe start: single_and_fg=%d\n", single_and_fg); #if ENABLE_HUSH_JOB pi->pgrp = -1; @@ -1780,7 +1788,7 @@ static int run_pipe(struct pipe *pi) * pseudo_exec. "echo foo | read bar" doesn't work on bash, either. */ child = &(pi->progs[0]); - if (single_fg && child->group && child->subshell == 0) { + if (single_and_fg && child->group && child->subshell == 0) { debug_printf("non-subshell grouping\n"); setup_redirects(child, squirrel); debug_printf_exec(": run_list\n"); @@ -1791,41 +1799,45 @@ static int run_pipe(struct pipe *pi) return rcode; } - if (single_fg && child->argv != NULL) { - char **argv_expanded; - char **argv = child->argv; + argv = child->argv; + if (single_and_fg && argv != NULL) { for (i = 0; is_assignment(argv[i]); i++) continue; if (i != 0 && argv[i] == NULL) { - /* assignments, but no command: set the local environment */ + /* assignments, but no command: set local environment */ for (i = 0; argv[i] != NULL; i++) { debug_printf("local environment set: %s\n", argv[i]); p = expand_string_to_string(argv[i]); set_local_var(p, 0); } - return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ + return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ } + + /* Expand assignments into one string each */ for (i = 0; is_assignment(argv[i]); i++) { p = expand_string_to_string(argv[i]); putenv(p); //FIXME: do we leak p?! } + + /* Expand the rest into (possibly) many strings each */ + argv_expanded = expand_strvec_to_strvec(argv + i); + for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) { - if (strcmp(argv[i], x->cmd) == 0) { - if (x->function == builtin_exec && argv[i+1] == NULL) { + if (strcmp(argv_expanded[0], x->cmd) == 0) { + if (x->function == builtin_exec && argv_expanded[1] == NULL) { debug_printf("magic exec\n"); setup_redirects(child, NULL); return EXIT_SUCCESS; } - debug_printf("builtin inline %s\n", argv[0]); + debug_printf("builtin inline %s\n", argv_expanded[0]); /* XXX setup_redirects acts on file descriptors, not FILEs. * This is perfect for work that comes after exec(). * Is it really safe for inline use? Experimentally, * things seem to work with glibc. */ setup_redirects(child, squirrel); - debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv[i+1]); - argv_expanded = expand_strvec_to_strvec(argv + i); + debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv_expanded[1]); rcode = x->function(argv_expanded) & 0xff; free(argv_expanded); restore_redirects(squirrel); @@ -1836,12 +1848,10 @@ static int run_pipe(struct pipe *pi) } #if ENABLE_FEATURE_SH_STANDALONE { - int a = find_applet_by_name(argv[i]); + int a = find_applet_by_name(argv_expanded[0]); if (a >= 0 && APPLET_IS_NOFORK(a)) { setup_redirects(child, squirrel); save_nofork_data(&nofork_save); - argv_expanded = argv + i; - argv_expanded = expand_strvec_to_strvec(argv + i); debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); rcode = run_nofork_applet_prime(&nofork_save, a, argv_expanded); free(argv_expanded); @@ -1854,6 +1864,10 @@ static int run_pipe(struct pipe *pi) #endif } + /* NB: argv_expanded may already be created, and that + * might include `cmd` runs! Do not rerun it! We *must* + * use argv_expanded if it's non-NULL */ + /* Disable job control signals for shell (parent) and * for initial child code after fork */ set_jobctrl_sighandler(SIG_IGN); @@ -1914,8 +1928,10 @@ static int run_pipe(struct pipe *pi) set_jobctrl_sighandler(SIG_DFL); set_misc_sighandler(SIG_DFL); signal(SIGCHLD, SIG_DFL); - pseudo_exec(ptrs2free, child); /* does not return */ + pseudo_exec(ptrs2free, child, argv_expanded); /* does not return */ } + free(argv_expanded); + argv_expanded = NULL; #if !BB_MMU free_strings(ptrs2free); #endif @@ -2236,18 +2252,22 @@ static int run_list(struct pipe *pi) /* we only ran a builtin: rcode is already known * and we don't need to wait for anything. */ /* was it "break" or "continue"? */ + if (flag_break_continue) { smallint fbc = flag_break_continue; /* we might fall into outer *loop*, * don't want to break it too */ - flag_break_continue = 0; if (loop_top) { - if (fbc == BC_BREAK) + depth_break_continue--; + if (depth_break_continue == 0) + flag_break_continue = 0; + if (depth_break_continue != 0 || fbc == BC_BREAK) goto check_jobs_and_break; /* "continue": simulate end of loop */ rword = RES_DONE; continue; } + flag_break_continue = 0; bb_error_msg("break/continue: only meaningful in a loop"); /* bash compat: exit code is still 0 */ } @@ -4265,7 +4285,7 @@ static int builtin_exec(char **argv) char **ptrs2free = alloc_ptrs(argv); #endif // FIXME: if exec fails, bash does NOT exit! We do... - pseudo_exec_argv(ptrs2free, argv + 1); + pseudo_exec_argv(ptrs2free, argv + 1, NULL); /* never returns */ } } @@ -4512,14 +4532,23 @@ static int builtin_unset(char **argv) return EXIT_SUCCESS; } -static int builtin_break(char **argv UNUSED_PARAM) +static int builtin_break(char **argv) { - flag_break_continue = BC_BREAK; + flag_break_continue++; /* BC_BREAK = 1 */ + depth_break_continue = 1; + if (argv[1]) { + depth_break_continue = bb_strtou(argv[1], NULL, 10); + if (errno || !depth_break_continue || argv[2]) { + bb_error_msg("bad arguments"); + flag_break_continue = BC_BREAK; + depth_break_continue = UINT_MAX; + } + } return EXIT_SUCCESS; } -static int builtin_continue(char **argv UNUSED_PARAM) +static int builtin_continue(char **argv) { - flag_break_continue = BC_CONTINUE; - return EXIT_SUCCESS; + flag_break_continue++; /* BC_CONTINUE = 2 = 1+1 */ + return builtin_break(argv); } diff --git a/shell/hush_test/hush-misc/break1.right b/shell/hush_test/hush-misc/break1.right new file mode 100644 index 000000000..04a4b1757 --- /dev/null +++ b/shell/hush_test/hush-misc/break1.right @@ -0,0 +1,2 @@ +A +OK:0 diff --git a/shell/hush_test/hush-misc/break1.tests b/shell/hush_test/hush-misc/break1.tests new file mode 100755 index 000000000..912f149c1 --- /dev/null +++ b/shell/hush_test/hush-misc/break1.tests @@ -0,0 +1,3 @@ +while true; do echo A; break; echo B; done +echo OK:$? + diff --git a/shell/hush_test/hush-misc/break2.right b/shell/hush_test/hush-misc/break2.right new file mode 100644 index 000000000..8a15cb95f --- /dev/null +++ b/shell/hush_test/hush-misc/break2.right @@ -0,0 +1,3 @@ +A +AA +OK:0 diff --git a/shell/hush_test/hush-misc/break2.tests b/shell/hush_test/hush-misc/break2.tests new file mode 100755 index 000000000..7da9faf34 --- /dev/null +++ b/shell/hush_test/hush-misc/break2.tests @@ -0,0 +1,6 @@ +while true; do + echo A + while true; do echo AA; break 2; echo BB; done + echo B +done +echo OK:$? diff --git a/shell/hush_test/hush-misc/break3.right b/shell/hush_test/hush-misc/break3.right new file mode 100644 index 000000000..04a4b1757 --- /dev/null +++ b/shell/hush_test/hush-misc/break3.right @@ -0,0 +1,2 @@ +A +OK:0 diff --git a/shell/hush_test/hush-misc/break3.tests b/shell/hush_test/hush-misc/break3.tests new file mode 100755 index 000000000..d138dcac5 --- /dev/null +++ b/shell/hush_test/hush-misc/break3.tests @@ -0,0 +1,2 @@ +v=break; while true; do echo A; $v; echo B; break; echo C; done +echo OK:$?