mirror of
https://github.com/sheumann/hush.git
synced 2024-12-22 14:30:31 +00:00
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:
parent
bcb25537d0
commit
6a2d40f239
99
shell/hush.c
99
shell/hush.c
@ -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;
|
||||||
@ -487,7 +488,8 @@ enum { run_list_level = 0 };
|
|||||||
#define global_argc (G.global_argc )
|
#define global_argc (G.global_argc )
|
||||||
#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);
|
||||||
|
|
||||||
argv = expand_strvec_to_strvec(argv);
|
if (argv_expanded) {
|
||||||
|
argv = argv_expanded;
|
||||||
|
} else {
|
||||||
|
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,41 +1799,45 @@ 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]);
|
||||||
set_local_var(p, 0);
|
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++) {
|
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;
|
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;
|
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);
|
||||||
}
|
}
|
||||||
|
2
shell/hush_test/hush-misc/break1.right
Normal file
2
shell/hush_test/hush-misc/break1.right
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
A
|
||||||
|
OK:0
|
3
shell/hush_test/hush-misc/break1.tests
Executable file
3
shell/hush_test/hush-misc/break1.tests
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
while true; do echo A; break; echo B; done
|
||||||
|
echo OK:$?
|
||||||
|
|
3
shell/hush_test/hush-misc/break2.right
Normal file
3
shell/hush_test/hush-misc/break2.right
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
A
|
||||||
|
AA
|
||||||
|
OK:0
|
6
shell/hush_test/hush-misc/break2.tests
Executable file
6
shell/hush_test/hush-misc/break2.tests
Executable 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:$?
|
2
shell/hush_test/hush-misc/break3.right
Normal file
2
shell/hush_test/hush-misc/break3.right
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
A
|
||||||
|
OK:0
|
2
shell/hush_test/hush-misc/break3.tests
Executable file
2
shell/hush_test/hush-misc/break3.tests
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
v=break; while true; do echo A; $v; echo B; break; echo C; done
|
||||||
|
echo OK:$?
|
Loading…
Reference in New Issue
Block a user