diff options
-rw-r--r-- | shell/hush.c | 108 | ||||
-rw-r--r-- | shell/hush_test/hush-vars/var_serial.right | 5 | ||||
-rwxr-xr-x | shell/hush_test/hush-vars/var_serial.tests | 22 |
3 files changed, 107 insertions, 28 deletions
diff --git a/shell/hush.c b/shell/hush.c index c67aebdd7..c5a8ea617 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -221,7 +221,8 @@ | |||
221 | //config: default y | 221 | //config: default y |
222 | //config: depends on HUSH | 222 | //config: depends on HUSH |
223 | //config: help | 223 | //config: help |
224 | //config: This instructs hush to print commands before execution. Adds ~300 bytes. | 224 | //config: This instructs hush to print commands before execution. |
225 | //config: Adds ~300 bytes. | ||
225 | //config: | 226 | //config: |
226 | 227 | ||
227 | //usage:#define hush_trivial_usage NOUSAGE_STR | 228 | //usage:#define hush_trivial_usage NOUSAGE_STR |
@@ -664,7 +665,7 @@ struct globals { | |||
664 | smallint n_mode; | 665 | smallint n_mode; |
665 | #if ENABLE_HUSH_MODE_X | 666 | #if ENABLE_HUSH_MODE_X |
666 | smallint x_mode; | 667 | smallint x_mode; |
667 | # define G_x_mode G.x_mode | 668 | # define G_x_mode (G.x_mode) |
668 | #else | 669 | #else |
669 | # define G_x_mode 0 | 670 | # define G_x_mode 0 |
670 | #endif | 671 | #endif |
@@ -688,6 +689,7 @@ struct globals { | |||
688 | const char *cwd; | 689 | const char *cwd; |
689 | struct variable *top_var; /* = &G.shell_ver (set in main()) */ | 690 | struct variable *top_var; /* = &G.shell_ver (set in main()) */ |
690 | struct variable shell_ver; | 691 | struct variable shell_ver; |
692 | char **expanded_assignments; | ||
691 | #if ENABLE_HUSH_FUNCTIONS | 693 | #if ENABLE_HUSH_FUNCTIONS |
692 | struct function *top_func; | 694 | struct function *top_func; |
693 | # if ENABLE_HUSH_LOCAL | 695 | # if ENABLE_HUSH_LOCAL |
@@ -1459,9 +1461,23 @@ static struct variable *get_local_var(const char *name) | |||
1459 | 1461 | ||
1460 | static const char* FAST_FUNC get_local_var_value(const char *name) | 1462 | static const char* FAST_FUNC get_local_var_value(const char *name) |
1461 | { | 1463 | { |
1462 | struct variable **pp = get_ptr_to_local_var(name); | 1464 | struct variable **vpp; |
1463 | if (pp) | 1465 | |
1464 | return strchr((*pp)->varstr, '=') + 1; | 1466 | if (G.expanded_assignments) { |
1467 | char **cpp = G.expanded_assignments; | ||
1468 | int len = strlen(name); | ||
1469 | while (*cpp) { | ||
1470 | char *cp = *cpp; | ||
1471 | if (strncmp(cp, name, len) == 0 && cp[len] == '=') | ||
1472 | return cp + len + 1; | ||
1473 | cpp++; | ||
1474 | } | ||
1475 | } | ||
1476 | |||
1477 | vpp = get_ptr_to_local_var(name); | ||
1478 | if (vpp) | ||
1479 | return strchr((*vpp)->varstr, '=') + 1; | ||
1480 | |||
1465 | if (strcmp(name, "PPID") == 0) | 1481 | if (strcmp(name, "PPID") == 0) |
1466 | return utoa(G.root_ppid); | 1482 | return utoa(G.root_ppid); |
1467 | // bash compat: UID? EUID? | 1483 | // bash compat: UID? EUID? |
@@ -3090,11 +3106,14 @@ static char* expand_strvec_to_string(char **argv) | |||
3090 | static char **expand_assignments(char **argv, int count) | 3106 | static char **expand_assignments(char **argv, int count) |
3091 | { | 3107 | { |
3092 | int i; | 3108 | int i; |
3093 | char **p = NULL; | 3109 | char **p; |
3110 | |||
3111 | G.expanded_assignments = p = NULL; | ||
3094 | /* Expand assignments into one string each */ | 3112 | /* Expand assignments into one string each */ |
3095 | for (i = 0; i < count; i++) { | 3113 | for (i = 0; i < count; i++) { |
3096 | p = add_string_to_strings(p, expand_string_to_string(argv[i])); | 3114 | G.expanded_assignments = p = add_string_to_strings(p, expand_string_to_string(argv[i])); |
3097 | } | 3115 | } |
3116 | G.expanded_assignments = NULL; | ||
3098 | return p; | 3117 | return p; |
3099 | } | 3118 | } |
3100 | 3119 | ||
@@ -4316,6 +4335,27 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe) | |||
4316 | * backgrounded: cmd & { list } & | 4335 | * backgrounded: cmd & { list } & |
4317 | * subshell: ( list ) [&] | 4336 | * subshell: ( list ) [&] |
4318 | */ | 4337 | */ |
4338 | #if !ENABLE_HUSH_MODE_X | ||
4339 | #define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, char argv_expanded) \ | ||
4340 | redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel) | ||
4341 | #endif | ||
4342 | static int redirect_and_varexp_helper(char ***new_env_p, struct variable **old_vars_p, struct command *command, int squirrel[3], char **argv_expanded) | ||
4343 | { | ||
4344 | /* setup_redirects acts on file descriptors, not FILEs. | ||
4345 | * This is perfect for work that comes after exec(). | ||
4346 | * Is it really safe for inline use? Experimentally, | ||
4347 | * things seem to work. */ | ||
4348 | int rcode = setup_redirects(command, squirrel); | ||
4349 | if (rcode == 0) { | ||
4350 | char **new_env = expand_assignments(command->argv, command->assignment_cnt); | ||
4351 | *new_env_p = new_env; | ||
4352 | dump_cmd_in_x_mode(new_env); | ||
4353 | dump_cmd_in_x_mode(argv_expanded); | ||
4354 | if (old_vars_p) | ||
4355 | *old_vars_p = set_vars_and_save_old(new_env); | ||
4356 | } | ||
4357 | return rcode; | ||
4358 | } | ||
4319 | static NOINLINE int run_pipe(struct pipe *pi) | 4359 | static NOINLINE int run_pipe(struct pipe *pi) |
4320 | { | 4360 | { |
4321 | static const char *const null_ptr = NULL; | 4361 | static const char *const null_ptr = NULL; |
@@ -4325,7 +4365,6 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
4325 | struct command *command; | 4365 | struct command *command; |
4326 | char **argv_expanded; | 4366 | char **argv_expanded; |
4327 | char **argv; | 4367 | char **argv; |
4328 | char *p; | ||
4329 | /* it is not always needed, but we aim to smaller code */ | 4368 | /* it is not always needed, but we aim to smaller code */ |
4330 | int squirrel[] = { -1, -1, -1 }; | 4369 | int squirrel[] = { -1, -1, -1 }; |
4331 | int rcode; | 4370 | int rcode; |
@@ -4402,19 +4441,45 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
4402 | if (argv[command->assignment_cnt] == NULL) { | 4441 | if (argv[command->assignment_cnt] == NULL) { |
4403 | /* Assignments, but no command */ | 4442 | /* Assignments, but no command */ |
4404 | /* Ensure redirects take effect (that is, create files). | 4443 | /* Ensure redirects take effect (that is, create files). |
4405 | * Try "a=t >file": */ | 4444 | * Try "a=t >file" */ |
4445 | #if 0 /* A few cases in testsuite fail with this code. FIXME */ | ||
4446 | rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, squirrel, /*argv_expanded:*/ NULL); | ||
4447 | /* Set shell variables */ | ||
4448 | if (new_env) { | ||
4449 | argv = new_env; | ||
4450 | while (*argv) { | ||
4451 | set_local_var(*argv, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); | ||
4452 | /* Do we need to flag set_local_var() errors? | ||
4453 | * "assignment to readonly var" and "putenv error" | ||
4454 | */ | ||
4455 | argv++; | ||
4456 | } | ||
4457 | } | ||
4458 | /* Redirect error sets $? to 1. Otherwise, | ||
4459 | * if evaluating assignment value set $?, retain it. | ||
4460 | * Try "false; q=`exit 2`; echo $?" - should print 2: */ | ||
4461 | if (rcode == 0) | ||
4462 | rcode = G.last_exitcode; | ||
4463 | /* Exit, _skipping_ variable restoring code: */ | ||
4464 | goto clean_up_and_ret0; | ||
4465 | |||
4466 | #else /* Older, bigger, but more correct code */ | ||
4467 | |||
4406 | rcode = setup_redirects(command, squirrel); | 4468 | rcode = setup_redirects(command, squirrel); |
4407 | restore_redirects(squirrel); | 4469 | restore_redirects(squirrel); |
4408 | /* Set shell variables */ | 4470 | /* Set shell variables */ |
4409 | if (G_x_mode) | 4471 | if (G_x_mode) |
4410 | bb_putchar_stderr('+'); | 4472 | bb_putchar_stderr('+'); |
4411 | while (*argv) { | 4473 | while (*argv) { |
4412 | p = expand_string_to_string(*argv); | 4474 | char *p = expand_string_to_string(*argv); |
4413 | if (G_x_mode) | 4475 | if (G_x_mode) |
4414 | fprintf(stderr, " %s", p); | 4476 | fprintf(stderr, " %s", p); |
4415 | debug_printf_exec("set shell var:'%s'->'%s'\n", | 4477 | debug_printf_exec("set shell var:'%s'->'%s'\n", |
4416 | *argv, p); | 4478 | *argv, p); |
4417 | set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); | 4479 | set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); |
4480 | /* Do we need to flag set_local_var() errors? | ||
4481 | * "assignment to readonly var" and "putenv error" | ||
4482 | */ | ||
4418 | argv++; | 4483 | argv++; |
4419 | } | 4484 | } |
4420 | if (G_x_mode) | 4485 | if (G_x_mode) |
@@ -4424,13 +4489,11 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
4424 | * Try "false; q=`exit 2`; echo $?" - should print 2: */ | 4489 | * Try "false; q=`exit 2`; echo $?" - should print 2: */ |
4425 | if (rcode == 0) | 4490 | if (rcode == 0) |
4426 | rcode = G.last_exitcode; | 4491 | rcode = G.last_exitcode; |
4427 | /* Do we need to flag set_local_var() errors? | ||
4428 | * "assignment to readonly var" and "putenv error" | ||
4429 | */ | ||
4430 | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) | 4492 | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) |
4431 | debug_leave(); | 4493 | debug_leave(); |
4432 | debug_printf_exec("run_pipe: return %d\n", rcode); | 4494 | debug_printf_exec("run_pipe: return %d\n", rcode); |
4433 | return rcode; | 4495 | return rcode; |
4496 | #endif | ||
4434 | } | 4497 | } |
4435 | 4498 | ||
4436 | /* Expand the rest into (possibly) many strings each */ | 4499 | /* Expand the rest into (possibly) many strings each */ |
@@ -4471,16 +4534,8 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
4471 | goto clean_up_and_ret1; | 4534 | goto clean_up_and_ret1; |
4472 | } | 4535 | } |
4473 | } | 4536 | } |
4474 | /* setup_redirects acts on file descriptors, not FILEs. | 4537 | rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); |
4475 | * This is perfect for work that comes after exec(). | ||
4476 | * Is it really safe for inline use? Experimentally, | ||
4477 | * things seem to work. */ | ||
4478 | rcode = setup_redirects(command, squirrel); | ||
4479 | if (rcode == 0) { | 4538 | if (rcode == 0) { |
4480 | new_env = expand_assignments(argv, command->assignment_cnt); | ||
4481 | dump_cmd_in_x_mode(new_env); | ||
4482 | dump_cmd_in_x_mode(argv_expanded); | ||
4483 | old_vars = set_vars_and_save_old(new_env); | ||
4484 | if (!funcp) { | 4539 | if (!funcp) { |
4485 | debug_printf_exec(": builtin '%s' '%s'...\n", | 4540 | debug_printf_exec(": builtin '%s' '%s'...\n", |
4486 | x->b_cmd, argv_expanded[1]); | 4541 | x->b_cmd, argv_expanded[1]); |
@@ -4504,9 +4559,10 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
4504 | #endif | 4559 | #endif |
4505 | } | 4560 | } |
4506 | clean_up_and_ret: | 4561 | clean_up_and_ret: |
4507 | restore_redirects(squirrel); | ||
4508 | unset_vars(new_env); | 4562 | unset_vars(new_env); |
4509 | add_vars(old_vars); | 4563 | add_vars(old_vars); |
4564 | /* clean_up_and_ret0: */ | ||
4565 | restore_redirects(squirrel); | ||
4510 | clean_up_and_ret1: | 4566 | clean_up_and_ret1: |
4511 | free(argv_expanded); | 4567 | free(argv_expanded); |
4512 | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) | 4568 | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) |
@@ -4518,12 +4574,8 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
4518 | if (ENABLE_FEATURE_SH_STANDALONE) { | 4574 | if (ENABLE_FEATURE_SH_STANDALONE) { |
4519 | int n = find_applet_by_name(argv_expanded[0]); | 4575 | int n = find_applet_by_name(argv_expanded[0]); |
4520 | if (n >= 0 && APPLET_IS_NOFORK(n)) { | 4576 | if (n >= 0 && APPLET_IS_NOFORK(n)) { |
4521 | rcode = setup_redirects(command, squirrel); | 4577 | rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); |
4522 | if (rcode == 0) { | 4578 | if (rcode == 0) { |
4523 | new_env = expand_assignments(argv, command->assignment_cnt); | ||
4524 | dump_cmd_in_x_mode(new_env); | ||
4525 | dump_cmd_in_x_mode(argv_expanded); | ||
4526 | old_vars = set_vars_and_save_old(new_env); | ||
4527 | debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", | 4579 | debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", |
4528 | argv_expanded[0], argv_expanded[1]); | 4580 | argv_expanded[0], argv_expanded[1]); |
4529 | rcode = run_nofork_applet(n, argv_expanded); | 4581 | rcode = run_nofork_applet(n, argv_expanded); |
diff --git a/shell/hush_test/hush-vars/var_serial.right b/shell/hush_test/hush-vars/var_serial.right new file mode 100644 index 000000000..42aa33057 --- /dev/null +++ b/shell/hush_test/hush-vars/var_serial.right | |||
@@ -0,0 +1,5 @@ | |||
1 | Assignments only: c=a | ||
2 | Assignments and a command: c=a | ||
3 | Assignments and a builtin: c=a | ||
4 | Assignments and a function: c=a | ||
5 | Done | ||
diff --git a/shell/hush_test/hush-vars/var_serial.tests b/shell/hush_test/hush-vars/var_serial.tests new file mode 100755 index 000000000..6b4a4cdf7 --- /dev/null +++ b/shell/hush_test/hush-vars/var_serial.tests | |||
@@ -0,0 +1,22 @@ | |||
1 | a=a | ||
2 | |||
3 | b=b | ||
4 | c=c | ||
5 | # Second assignment depends on the first: | ||
6 | b=$a c=$b | ||
7 | echo Assignments only: c=$c | ||
8 | |||
9 | b=b | ||
10 | c=c | ||
11 | b=$a c=$b "$THIS_SH" -c 'echo Assignments and a command: c=$c' | ||
12 | |||
13 | b=b | ||
14 | c=c | ||
15 | b=$a c=$b eval 'echo Assignments and a builtin: c=$c' | ||
16 | |||
17 | b=b | ||
18 | c=c | ||
19 | f() { echo Assignments and a function: c=$c; } | ||
20 | b=$a c=$b f | ||
21 | |||
22 | echo Done | ||