diff options
author | Denis Vlasenko <vda.linux@googlemail.com> | 2008-07-28 23:07:06 +0000 |
---|---|---|
committer | Denis Vlasenko <vda.linux@googlemail.com> | 2008-07-28 23:07:06 +0000 |
commit | 6a2d40f239566e886ef76542a75662cee9380a0e (patch) | |
tree | f2e26f50cae7ddc60112f51174c4d994a9954fea /shell | |
parent | bcb25537d02b50ce26678defcf4f39d0c89f5b3b (diff) | |
download | busybox-w32-6a2d40f239566e886ef76542a75662cee9380a0e.tar.gz busybox-w32-6a2d40f239566e886ef76542a75662cee9380a0e.tar.bz2 busybox-w32-6a2d40f239566e886ef76542a75662cee9380a0e.zip |
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
Diffstat (limited to 'shell')
-rw-r--r-- | shell/hush.c | 99 | ||||
-rw-r--r-- | shell/hush_test/hush-misc/break1.right | 2 | ||||
-rwxr-xr-x | shell/hush_test/hush-misc/break1.tests | 3 | ||||
-rw-r--r-- | shell/hush_test/hush-misc/break2.right | 3 | ||||
-rwxr-xr-x | shell/hush_test/hush-misc/break2.tests | 6 | ||||
-rw-r--r-- | shell/hush_test/hush-misc/break3.right | 2 | ||||
-rwxr-xr-x | shell/hush_test/hush-misc/break3.tests | 2 |
7 files changed, 82 insertions, 35 deletions
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 { | |||
440 | smalluint last_return_code; | 440 | smalluint last_return_code; |
441 | char **global_argv; | 441 | char **global_argv; |
442 | int global_argc; | 442 | int global_argc; |
443 | unsigned depth_break_continue; | ||
443 | pid_t last_bg_pid; | 444 | pid_t last_bg_pid; |
444 | const char *ifs; | 445 | const char *ifs; |
445 | const char *cwd; | 446 | const char *cwd; |
@@ -487,7 +488,8 @@ enum { run_list_level = 0 }; | |||
487 | #define global_argc (G.global_argc ) | 488 | #define global_argc (G.global_argc ) |
488 | #define last_return_code (G.last_return_code) | 489 | #define last_return_code (G.last_return_code) |
489 | #define ifs (G.ifs ) | 490 | #define ifs (G.ifs ) |
490 | #define flag_break_continue (G.flag_break_continue) | 491 | #define flag_break_continue (G.flag_break_continue ) |
492 | #define depth_break_continue (G.depth_break_continue) | ||
491 | #define fake_mode (G.fake_mode ) | 493 | #define fake_mode (G.fake_mode ) |
492 | #define cwd (G.cwd ) | 494 | #define cwd (G.cwd ) |
493 | #define last_bg_pid (G.last_bg_pid ) | 495 | #define last_bg_pid (G.last_bg_pid ) |
@@ -552,11 +554,11 @@ static int free_pipe(struct pipe *pi, int indent); | |||
552 | static int setup_redirects(struct child_prog *prog, int squirrel[]); | 554 | static int setup_redirects(struct child_prog *prog, int squirrel[]); |
553 | static int run_list(struct pipe *pi); | 555 | static int run_list(struct pipe *pi); |
554 | #if BB_MMU | 556 | #if BB_MMU |
555 | #define pseudo_exec_argv(ptrs2free, argv) pseudo_exec_argv(argv) | 557 | #define pseudo_exec_argv(ptrs2free, argv, argv_expanded) pseudo_exec_argv(argv, argv_expanded) |
556 | #define pseudo_exec(ptrs2free, child) pseudo_exec(child) | 558 | #define pseudo_exec(ptrs2free, child, argv_expanded) pseudo_exec(child, argv_expanded) |
557 | #endif | 559 | #endif |
558 | static void pseudo_exec_argv(char **ptrs2free, char **argv) NORETURN; | 560 | static void pseudo_exec_argv(char **ptrs2free, char **argv, char **argv_expanded) NORETURN; |
559 | static void pseudo_exec(char **ptrs2free, struct child_prog *child) NORETURN; | 561 | static void pseudo_exec(char **ptrs2free, struct child_prog *child, char **argv_expanded) NORETURN; |
560 | static int run_pipe(struct pipe *pi); | 562 | static int run_pipe(struct pipe *pi); |
561 | /* data structure manipulation: */ | 563 | /* data structure manipulation: */ |
562 | static int setup_redirect(struct p_context *ctx, int fd, redir_type style, struct in_str *input); | 564 | 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[]) | |||
1410 | * XXX no exit() here. If you don't exec, use _exit instead. | 1412 | * XXX no exit() here. If you don't exec, use _exit instead. |
1411 | * The at_exit handlers apparently confuse the calling process, | 1413 | * The at_exit handlers apparently confuse the calling process, |
1412 | * in particular stdin handling. Not sure why? -- because of vfork! (vda) */ | 1414 | * in particular stdin handling. Not sure why? -- because of vfork! (vda) */ |
1413 | static void pseudo_exec_argv(char **ptrs2free, char **argv) | 1415 | static void pseudo_exec_argv(char **ptrs2free, char **argv, char **argv_expanded) |
1414 | { | 1416 | { |
1415 | int i, rcode; | 1417 | int i, rcode; |
1416 | char *p; | 1418 | char *p; |
@@ -1432,10 +1434,14 @@ static void pseudo_exec_argv(char **ptrs2free, char **argv) | |||
1432 | if (!argv[0]) | 1434 | if (!argv[0]) |
1433 | _exit(EXIT_SUCCESS); | 1435 | _exit(EXIT_SUCCESS); |
1434 | 1436 | ||
1435 | argv = expand_strvec_to_strvec(argv); | 1437 | if (argv_expanded) { |
1438 | argv = argv_expanded; | ||
1439 | } else { | ||
1440 | argv = expand_strvec_to_strvec(argv); | ||
1436 | #if !BB_MMU | 1441 | #if !BB_MMU |
1437 | *ptrs2free++ = (char*) argv; | 1442 | *ptrs2free++ = (char*) argv; |
1438 | #endif | 1443 | #endif |
1444 | } | ||
1439 | 1445 | ||
1440 | /* | 1446 | /* |
1441 | * Check if the command matches any of the builtins. | 1447 | * Check if the command matches any of the builtins. |
@@ -1479,10 +1485,10 @@ static void pseudo_exec_argv(char **ptrs2free, char **argv) | |||
1479 | 1485 | ||
1480 | /* Called after [v]fork() in run_pipe() | 1486 | /* Called after [v]fork() in run_pipe() |
1481 | */ | 1487 | */ |
1482 | static void pseudo_exec(char **ptrs2free, struct child_prog *child) | 1488 | static void pseudo_exec(char **ptrs2free, struct child_prog *child, char **argv_expanded) |
1483 | { | 1489 | { |
1484 | if (child->argv) | 1490 | if (child->argv) |
1485 | pseudo_exec_argv(ptrs2free, child->argv); | 1491 | pseudo_exec_argv(ptrs2free, child->argv, argv_expanded); |
1486 | 1492 | ||
1487 | if (child->group) { | 1493 | if (child->group) { |
1488 | #if !BB_MMU | 1494 | #if !BB_MMU |
@@ -1760,14 +1766,16 @@ static int run_pipe(struct pipe *pi) | |||
1760 | int nextin; | 1766 | int nextin; |
1761 | int pipefds[2]; /* pipefds[0] is for reading */ | 1767 | int pipefds[2]; /* pipefds[0] is for reading */ |
1762 | struct child_prog *child; | 1768 | struct child_prog *child; |
1769 | char **argv_expanded = NULL; | ||
1770 | char **argv; | ||
1763 | const struct built_in_command *x; | 1771 | const struct built_in_command *x; |
1764 | char *p; | 1772 | char *p; |
1765 | /* it is not always needed, but we aim to smaller code */ | 1773 | /* it is not always needed, but we aim to smaller code */ |
1766 | int squirrel[] = { -1, -1, -1 }; | 1774 | int squirrel[] = { -1, -1, -1 }; |
1767 | int rcode; | 1775 | int rcode; |
1768 | const int single_fg = (pi->num_progs == 1 && pi->followup != PIPE_BG); | 1776 | const int single_and_fg = (pi->num_progs == 1 && pi->followup != PIPE_BG); |
1769 | 1777 | ||
1770 | debug_printf_exec("run_pipe start: single_fg=%d\n", single_fg); | 1778 | debug_printf_exec("run_pipe start: single_and_fg=%d\n", single_and_fg); |
1771 | 1779 | ||
1772 | #if ENABLE_HUSH_JOB | 1780 | #if ENABLE_HUSH_JOB |
1773 | pi->pgrp = -1; | 1781 | pi->pgrp = -1; |
@@ -1780,7 +1788,7 @@ static int run_pipe(struct pipe *pi) | |||
1780 | * pseudo_exec. "echo foo | read bar" doesn't work on bash, either. | 1788 | * pseudo_exec. "echo foo | read bar" doesn't work on bash, either. |
1781 | */ | 1789 | */ |
1782 | child = &(pi->progs[0]); | 1790 | child = &(pi->progs[0]); |
1783 | if (single_fg && child->group && child->subshell == 0) { | 1791 | if (single_and_fg && child->group && child->subshell == 0) { |
1784 | debug_printf("non-subshell grouping\n"); | 1792 | debug_printf("non-subshell grouping\n"); |
1785 | setup_redirects(child, squirrel); | 1793 | setup_redirects(child, squirrel); |
1786 | debug_printf_exec(": run_list\n"); | 1794 | debug_printf_exec(": run_list\n"); |
@@ -1791,41 +1799,45 @@ static int run_pipe(struct pipe *pi) | |||
1791 | return rcode; | 1799 | return rcode; |
1792 | } | 1800 | } |
1793 | 1801 | ||
1794 | if (single_fg && child->argv != NULL) { | 1802 | argv = child->argv; |
1795 | char **argv_expanded; | ||
1796 | char **argv = child->argv; | ||
1797 | 1803 | ||
1804 | if (single_and_fg && argv != NULL) { | ||
1798 | for (i = 0; is_assignment(argv[i]); i++) | 1805 | for (i = 0; is_assignment(argv[i]); i++) |
1799 | continue; | 1806 | continue; |
1800 | if (i != 0 && argv[i] == NULL) { | 1807 | if (i != 0 && argv[i] == NULL) { |
1801 | /* assignments, but no command: set the local environment */ | 1808 | /* assignments, but no command: set local environment */ |
1802 | for (i = 0; argv[i] != NULL; i++) { | 1809 | for (i = 0; argv[i] != NULL; i++) { |
1803 | debug_printf("local environment set: %s\n", argv[i]); | 1810 | debug_printf("local environment set: %s\n", argv[i]); |
1804 | p = expand_string_to_string(argv[i]); | 1811 | p = expand_string_to_string(argv[i]); |
1805 | set_local_var(p, 0); | 1812 | set_local_var(p, 0); |
1806 | } | 1813 | } |
1807 | return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ | 1814 | return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ |
1808 | } | 1815 | } |
1816 | |||
1817 | /* Expand assignments into one string each */ | ||
1809 | for (i = 0; is_assignment(argv[i]); i++) { | 1818 | for (i = 0; is_assignment(argv[i]); i++) { |
1810 | p = expand_string_to_string(argv[i]); | 1819 | p = expand_string_to_string(argv[i]); |
1811 | putenv(p); | 1820 | putenv(p); |
1812 | //FIXME: do we leak p?! | 1821 | //FIXME: do we leak p?! |
1813 | } | 1822 | } |
1823 | |||
1824 | /* Expand the rest into (possibly) many strings each */ | ||
1825 | argv_expanded = expand_strvec_to_strvec(argv + i); | ||
1826 | |||
1814 | for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) { | 1827 | for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) { |
1815 | if (strcmp(argv[i], x->cmd) == 0) { | 1828 | if (strcmp(argv_expanded[0], x->cmd) == 0) { |
1816 | if (x->function == builtin_exec && argv[i+1] == NULL) { | 1829 | if (x->function == builtin_exec && argv_expanded[1] == NULL) { |
1817 | debug_printf("magic exec\n"); | 1830 | debug_printf("magic exec\n"); |
1818 | setup_redirects(child, NULL); | 1831 | setup_redirects(child, NULL); |
1819 | return EXIT_SUCCESS; | 1832 | return EXIT_SUCCESS; |
1820 | } | 1833 | } |
1821 | debug_printf("builtin inline %s\n", argv[0]); | 1834 | debug_printf("builtin inline %s\n", argv_expanded[0]); |
1822 | /* XXX setup_redirects acts on file descriptors, not FILEs. | 1835 | /* XXX setup_redirects acts on file descriptors, not FILEs. |
1823 | * This is perfect for work that comes after exec(). | 1836 | * This is perfect for work that comes after exec(). |
1824 | * Is it really safe for inline use? Experimentally, | 1837 | * Is it really safe for inline use? Experimentally, |
1825 | * things seem to work with glibc. */ | 1838 | * things seem to work with glibc. */ |
1826 | setup_redirects(child, squirrel); | 1839 | setup_redirects(child, squirrel); |
1827 | debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv[i+1]); | 1840 | debug_printf_exec(": builtin '%s' '%s'...\n", x->cmd, argv_expanded[1]); |
1828 | argv_expanded = expand_strvec_to_strvec(argv + i); | ||
1829 | rcode = x->function(argv_expanded) & 0xff; | 1841 | rcode = x->function(argv_expanded) & 0xff; |
1830 | free(argv_expanded); | 1842 | free(argv_expanded); |
1831 | restore_redirects(squirrel); | 1843 | restore_redirects(squirrel); |
@@ -1836,12 +1848,10 @@ static int run_pipe(struct pipe *pi) | |||
1836 | } | 1848 | } |
1837 | #if ENABLE_FEATURE_SH_STANDALONE | 1849 | #if ENABLE_FEATURE_SH_STANDALONE |
1838 | { | 1850 | { |
1839 | int a = find_applet_by_name(argv[i]); | 1851 | int a = find_applet_by_name(argv_expanded[0]); |
1840 | if (a >= 0 && APPLET_IS_NOFORK(a)) { | 1852 | if (a >= 0 && APPLET_IS_NOFORK(a)) { |
1841 | setup_redirects(child, squirrel); | 1853 | setup_redirects(child, squirrel); |
1842 | save_nofork_data(&nofork_save); | 1854 | save_nofork_data(&nofork_save); |
1843 | argv_expanded = argv + i; | ||
1844 | argv_expanded = expand_strvec_to_strvec(argv + i); | ||
1845 | debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); | 1855 | debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); |
1846 | rcode = run_nofork_applet_prime(&nofork_save, a, argv_expanded); | 1856 | rcode = run_nofork_applet_prime(&nofork_save, a, argv_expanded); |
1847 | free(argv_expanded); | 1857 | free(argv_expanded); |
@@ -1854,6 +1864,10 @@ static int run_pipe(struct pipe *pi) | |||
1854 | #endif | 1864 | #endif |
1855 | } | 1865 | } |
1856 | 1866 | ||
1867 | /* NB: argv_expanded may already be created, and that | ||
1868 | * might include `cmd` runs! Do not rerun it! We *must* | ||
1869 | * use argv_expanded if it's non-NULL */ | ||
1870 | |||
1857 | /* Disable job control signals for shell (parent) and | 1871 | /* Disable job control signals for shell (parent) and |
1858 | * for initial child code after fork */ | 1872 | * for initial child code after fork */ |
1859 | set_jobctrl_sighandler(SIG_IGN); | 1873 | set_jobctrl_sighandler(SIG_IGN); |
@@ -1914,8 +1928,10 @@ static int run_pipe(struct pipe *pi) | |||
1914 | set_jobctrl_sighandler(SIG_DFL); | 1928 | set_jobctrl_sighandler(SIG_DFL); |
1915 | set_misc_sighandler(SIG_DFL); | 1929 | set_misc_sighandler(SIG_DFL); |
1916 | signal(SIGCHLD, SIG_DFL); | 1930 | signal(SIGCHLD, SIG_DFL); |
1917 | pseudo_exec(ptrs2free, child); /* does not return */ | 1931 | pseudo_exec(ptrs2free, child, argv_expanded); /* does not return */ |
1918 | } | 1932 | } |
1933 | free(argv_expanded); | ||
1934 | argv_expanded = NULL; | ||
1919 | #if !BB_MMU | 1935 | #if !BB_MMU |
1920 | free_strings(ptrs2free); | 1936 | free_strings(ptrs2free); |
1921 | #endif | 1937 | #endif |
@@ -2236,18 +2252,22 @@ static int run_list(struct pipe *pi) | |||
2236 | /* we only ran a builtin: rcode is already known | 2252 | /* we only ran a builtin: rcode is already known |
2237 | * and we don't need to wait for anything. */ | 2253 | * and we don't need to wait for anything. */ |
2238 | /* was it "break" or "continue"? */ | 2254 | /* was it "break" or "continue"? */ |
2255 | |||
2239 | if (flag_break_continue) { | 2256 | if (flag_break_continue) { |
2240 | smallint fbc = flag_break_continue; | 2257 | smallint fbc = flag_break_continue; |
2241 | /* we might fall into outer *loop*, | 2258 | /* we might fall into outer *loop*, |
2242 | * don't want to break it too */ | 2259 | * don't want to break it too */ |
2243 | flag_break_continue = 0; | ||
2244 | if (loop_top) { | 2260 | if (loop_top) { |
2245 | if (fbc == BC_BREAK) | 2261 | depth_break_continue--; |
2262 | if (depth_break_continue == 0) | ||
2263 | flag_break_continue = 0; | ||
2264 | if (depth_break_continue != 0 || fbc == BC_BREAK) | ||
2246 | goto check_jobs_and_break; | 2265 | goto check_jobs_and_break; |
2247 | /* "continue": simulate end of loop */ | 2266 | /* "continue": simulate end of loop */ |
2248 | rword = RES_DONE; | 2267 | rword = RES_DONE; |
2249 | continue; | 2268 | continue; |
2250 | } | 2269 | } |
2270 | flag_break_continue = 0; | ||
2251 | bb_error_msg("break/continue: only meaningful in a loop"); | 2271 | bb_error_msg("break/continue: only meaningful in a loop"); |
2252 | /* bash compat: exit code is still 0 */ | 2272 | /* bash compat: exit code is still 0 */ |
2253 | } | 2273 | } |
@@ -4265,7 +4285,7 @@ static int builtin_exec(char **argv) | |||
4265 | char **ptrs2free = alloc_ptrs(argv); | 4285 | char **ptrs2free = alloc_ptrs(argv); |
4266 | #endif | 4286 | #endif |
4267 | // FIXME: if exec fails, bash does NOT exit! We do... | 4287 | // FIXME: if exec fails, bash does NOT exit! We do... |
4268 | pseudo_exec_argv(ptrs2free, argv + 1); | 4288 | pseudo_exec_argv(ptrs2free, argv + 1, NULL); |
4269 | /* never returns */ | 4289 | /* never returns */ |
4270 | } | 4290 | } |
4271 | } | 4291 | } |
@@ -4512,14 +4532,23 @@ static int builtin_unset(char **argv) | |||
4512 | return EXIT_SUCCESS; | 4532 | return EXIT_SUCCESS; |
4513 | } | 4533 | } |
4514 | 4534 | ||
4515 | static int builtin_break(char **argv UNUSED_PARAM) | 4535 | static int builtin_break(char **argv) |
4516 | { | 4536 | { |
4517 | flag_break_continue = BC_BREAK; | 4537 | flag_break_continue++; /* BC_BREAK = 1 */ |
4538 | depth_break_continue = 1; | ||
4539 | if (argv[1]) { | ||
4540 | depth_break_continue = bb_strtou(argv[1], NULL, 10); | ||
4541 | if (errno || !depth_break_continue || argv[2]) { | ||
4542 | bb_error_msg("bad arguments"); | ||
4543 | flag_break_continue = BC_BREAK; | ||
4544 | depth_break_continue = UINT_MAX; | ||
4545 | } | ||
4546 | } | ||
4518 | return EXIT_SUCCESS; | 4547 | return EXIT_SUCCESS; |
4519 | } | 4548 | } |
4520 | 4549 | ||
4521 | static int builtin_continue(char **argv UNUSED_PARAM) | 4550 | static int builtin_continue(char **argv) |
4522 | { | 4551 | { |
4523 | flag_break_continue = BC_CONTINUE; | 4552 | flag_break_continue++; /* BC_CONTINUE = 2 = 1+1 */ |
4524 | return EXIT_SUCCESS; | 4553 | return builtin_break(argv); |
4525 | } | 4554 | } |
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 @@ | |||
1 | A | ||
2 | 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 @@ | |||
1 | while true; do echo A; break; echo B; done | ||
2 | echo OK:$? | ||
3 | |||
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 @@ | |||
1 | A | ||
2 | AA | ||
3 | 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 @@ | |||
1 | while true; do | ||
2 | echo A | ||
3 | while true; do echo AA; break 2; echo BB; done | ||
4 | echo B | ||
5 | done | ||
6 | 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 @@ | |||
1 | A | ||
2 | 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 @@ | |||
1 | v=break; while true; do echo A; $v; echo B; break; echo C; done | ||
2 | echo OK:$? | ||