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
This commit is contained in:
Denis Vlasenko 2008-07-28 23:07:06 +00:00
parent bcb25537d0
commit 6a2d40f239
7 changed files with 82 additions and 35 deletions

View File

@ -440,6 +440,7 @@ struct globals {
smalluint last_return_code; smalluint last_return_code;
char **global_argv; char **global_argv;
int global_argc; int global_argc;
unsigned depth_break_continue;
pid_t last_bg_pid; pid_t last_bg_pid;
const char *ifs; const char *ifs;
const char *cwd; const char *cwd;
@ -488,6 +489,7 @@ enum { run_list_level = 0 };
#define last_return_code (G.last_return_code) #define last_return_code (G.last_return_code)
#define ifs (G.ifs ) #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 fake_mode (G.fake_mode )
#define cwd (G.cwd ) #define cwd (G.cwd )
#define last_bg_pid (G.last_bg_pid ) #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 setup_redirects(struct child_prog *prog, int squirrel[]);
static int run_list(struct pipe *pi); static int run_list(struct pipe *pi);
#if BB_MMU #if BB_MMU
#define pseudo_exec_argv(ptrs2free, argv) pseudo_exec_argv(argv) #define pseudo_exec_argv(ptrs2free, argv, argv_expanded) pseudo_exec_argv(argv, argv_expanded)
#define pseudo_exec(ptrs2free, child) pseudo_exec(child) #define pseudo_exec(ptrs2free, child, argv_expanded) pseudo_exec(child, argv_expanded)
#endif #endif
static void pseudo_exec_argv(char **ptrs2free, char **argv) NORETURN; static void pseudo_exec_argv(char **ptrs2free, char **argv, char **argv_expanded) NORETURN;
static void pseudo_exec(char **ptrs2free, struct child_prog *child) NORETURN; static void pseudo_exec(char **ptrs2free, struct child_prog *child, char **argv_expanded) NORETURN;
static int run_pipe(struct pipe *pi); static int run_pipe(struct pipe *pi);
/* data structure manipulation: */ /* data structure manipulation: */
static int setup_redirect(struct p_context *ctx, int fd, redir_type style, struct in_str *input); 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. * XXX no exit() here. If you don't exec, use _exit instead.
* The at_exit handlers apparently confuse the calling process, * The at_exit handlers apparently confuse the calling process,
* in particular stdin handling. Not sure why? -- because of vfork! (vda) */ * 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; int i, rcode;
char *p; char *p;
@ -1432,10 +1434,14 @@ static void pseudo_exec_argv(char **ptrs2free, char **argv)
if (!argv[0]) if (!argv[0])
_exit(EXIT_SUCCESS); _exit(EXIT_SUCCESS);
if (argv_expanded) {
argv = argv_expanded;
} else {
argv = expand_strvec_to_strvec(argv); argv = expand_strvec_to_strvec(argv);
#if !BB_MMU #if !BB_MMU
*ptrs2free++ = (char*) argv; *ptrs2free++ = (char*) argv;
#endif #endif
}
/* /*
* Check if the command matches any of the builtins. * 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() /* 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) if (child->argv)
pseudo_exec_argv(ptrs2free, child->argv); pseudo_exec_argv(ptrs2free, child->argv, argv_expanded);
if (child->group) { if (child->group) {
#if !BB_MMU #if !BB_MMU
@ -1760,14 +1766,16 @@ static int run_pipe(struct pipe *pi)
int nextin; int nextin;
int pipefds[2]; /* pipefds[0] is for reading */ int pipefds[2]; /* pipefds[0] is for reading */
struct child_prog *child; struct child_prog *child;
char **argv_expanded = NULL;
char **argv;
const struct built_in_command *x; const struct built_in_command *x;
char *p; char *p;
/* it is not always needed, but we aim to smaller code */ /* it is not always needed, but we aim to smaller code */
int squirrel[] = { -1, -1, -1 }; int squirrel[] = { -1, -1, -1 };
int rcode; 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 #if ENABLE_HUSH_JOB
pi->pgrp = -1; 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. * pseudo_exec. "echo foo | read bar" doesn't work on bash, either.
*/ */
child = &(pi->progs[0]); 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"); debug_printf("non-subshell grouping\n");
setup_redirects(child, squirrel); setup_redirects(child, squirrel);
debug_printf_exec(": run_list\n"); debug_printf_exec(": run_list\n");
@ -1791,14 +1799,13 @@ static int run_pipe(struct pipe *pi)
return rcode; return rcode;
} }
if (single_fg && child->argv != NULL) { argv = child->argv;
char **argv_expanded;
char **argv = child->argv;
if (single_and_fg && argv != NULL) {
for (i = 0; is_assignment(argv[i]); i++) for (i = 0; is_assignment(argv[i]); i++)
continue; continue;
if (i != 0 && argv[i] == NULL) { 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++) { for (i = 0; argv[i] != NULL; i++) {
debug_printf("local environment set: %s\n", argv[i]); debug_printf("local environment set: %s\n", argv[i]);
p = expand_string_to_string(argv[i]); p = expand_string_to_string(argv[i]);
@ -1806,26 +1813,31 @@ static int run_pipe(struct pipe *pi)
} }
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++) { for (i = 0; is_assignment(argv[i]); i++) {
p = expand_string_to_string(argv[i]); p = expand_string_to_string(argv[i]);
putenv(p); putenv(p);
//FIXME: do we leak 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++) { for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
if (strcmp(argv[i], x->cmd) == 0) { if (strcmp(argv_expanded[0], x->cmd) == 0) {
if (x->function == builtin_exec && argv[i+1] == NULL) { if (x->function == builtin_exec && argv_expanded[1] == NULL) {
debug_printf("magic exec\n"); debug_printf("magic exec\n");
setup_redirects(child, NULL); setup_redirects(child, NULL);
return EXIT_SUCCESS; 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. /* XXX setup_redirects acts on file descriptors, not FILEs.
* This is perfect for work that comes after exec(). * This is perfect for work that comes after exec().
* Is it really safe for inline use? Experimentally, * Is it really safe for inline use? Experimentally,
* things seem to work with glibc. */ * things seem to work with glibc. */
setup_redirects(child, squirrel); setup_redirects(child, squirrel);
debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv[i+1]); debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv_expanded[1]);
argv_expanded = expand_strvec_to_strvec(argv + i);
rcode = x->function(argv_expanded) & 0xff; rcode = x->function(argv_expanded) & 0xff;
free(argv_expanded); free(argv_expanded);
restore_redirects(squirrel); restore_redirects(squirrel);
@ -1836,12 +1848,10 @@ static int run_pipe(struct pipe *pi)
} }
#if ENABLE_FEATURE_SH_STANDALONE #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)) { if (a >= 0 && APPLET_IS_NOFORK(a)) {
setup_redirects(child, squirrel); setup_redirects(child, squirrel);
save_nofork_data(&nofork_save); 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]); 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); rcode = run_nofork_applet_prime(&nofork_save, a, argv_expanded);
free(argv_expanded); free(argv_expanded);
@ -1854,6 +1864,10 @@ static int run_pipe(struct pipe *pi)
#endif #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 /* Disable job control signals for shell (parent) and
* for initial child code after fork */ * for initial child code after fork */
set_jobctrl_sighandler(SIG_IGN); set_jobctrl_sighandler(SIG_IGN);
@ -1914,8 +1928,10 @@ static int run_pipe(struct pipe *pi)
set_jobctrl_sighandler(SIG_DFL); set_jobctrl_sighandler(SIG_DFL);
set_misc_sighandler(SIG_DFL); set_misc_sighandler(SIG_DFL);
signal(SIGCHLD, 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 #if !BB_MMU
free_strings(ptrs2free); free_strings(ptrs2free);
#endif #endif
@ -2236,18 +2252,22 @@ static int run_list(struct pipe *pi)
/* we only ran a builtin: rcode is already known /* we only ran a builtin: rcode is already known
* and we don't need to wait for anything. */ * and we don't need to wait for anything. */
/* was it "break" or "continue"? */ /* was it "break" or "continue"? */
if (flag_break_continue) { if (flag_break_continue) {
smallint fbc = flag_break_continue; smallint fbc = flag_break_continue;
/* we might fall into outer *loop*, /* we might fall into outer *loop*,
* don't want to break it too */ * don't want to break it too */
flag_break_continue = 0;
if (loop_top) { 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; goto check_jobs_and_break;
/* "continue": simulate end of loop */ /* "continue": simulate end of loop */
rword = RES_DONE; rword = RES_DONE;
continue; continue;
} }
flag_break_continue = 0;
bb_error_msg("break/continue: only meaningful in a loop"); bb_error_msg("break/continue: only meaningful in a loop");
/* bash compat: exit code is still 0 */ /* bash compat: exit code is still 0 */
} }
@ -4265,7 +4285,7 @@ static int builtin_exec(char **argv)
char **ptrs2free = alloc_ptrs(argv); char **ptrs2free = alloc_ptrs(argv);
#endif #endif
// FIXME: if exec fails, bash does NOT exit! We do... // 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 */ /* never returns */
} }
} }
@ -4512,14 +4532,23 @@ static int builtin_unset(char **argv)
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
static int builtin_break(char **argv UNUSED_PARAM) static int builtin_break(char **argv)
{ {
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; flag_break_continue = BC_BREAK;
depth_break_continue = UINT_MAX;
}
}
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
static int builtin_continue(char **argv UNUSED_PARAM) static int builtin_continue(char **argv)
{ {
flag_break_continue = BC_CONTINUE; flag_break_continue++; /* BC_CONTINUE = 2 = 1+1 */
return EXIT_SUCCESS; return builtin_break(argv);
} }

View File

@ -0,0 +1,2 @@
A
OK:0

View File

@ -0,0 +1,3 @@
while true; do echo A; break; echo B; done
echo OK:$?

View File

@ -0,0 +1,3 @@
A
AA
OK:0

View File

@ -0,0 +1,6 @@
while true; do
echo A
while true; do echo AA; break 2; echo BB; done
echo B
done
echo OK:$?

View File

@ -0,0 +1,2 @@
A
OK:0

View File

@ -0,0 +1,2 @@
v=break; while true; do echo A; $v; echo B; break; echo C; done
echo OK:$?