hush: support "break N" and "continue N"
authorDenis Vlasenko <vda.linux@googlemail.com>
Mon, 28 Jul 2008 23:07:06 +0000 (23:07 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Mon, 28 Jul 2008 23:07:06 +0000 (23:07 -0000)
 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
shell/hush_test/hush-misc/break1.right [new file with mode: 0644]
shell/hush_test/hush-misc/break1.tests [new file with mode: 0755]
shell/hush_test/hush-misc/break2.right [new file with mode: 0644]
shell/hush_test/hush-misc/break2.tests [new file with mode: 0755]
shell/hush_test/hush-misc/break3.right [new file with mode: 0644]
shell/hush_test/hush-misc/break3.tests [new file with mode: 0755]

index 21cb365647bf003d3cc3eaaf5b59793af1f8fe6f..a74fe473045b9a3ca09d9ec5b89e1e6657f4ca94 100644 (file)
@@ -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 (file)
index 0000000..04a4b17
--- /dev/null
@@ -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 (executable)
index 0000000..912f149
--- /dev/null
@@ -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 (file)
index 0000000..8a15cb9
--- /dev/null
@@ -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 (executable)
index 0000000..7da9faf
--- /dev/null
@@ -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 (file)
index 0000000..04a4b17
--- /dev/null
@@ -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 (executable)
index 0000000..d138dca
--- /dev/null
@@ -0,0 +1,2 @@
+v=break; while true; do echo A; $v; echo B; break; echo C; done
+echo OK:$?