diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2018-04-05 00:51:55 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2018-04-05 00:51:55 +0200 |
commit | d358b0b65dae83d52e511a126757e2aa7b1881b2 (patch) | |
tree | 6ff6f7c14a399c0e5ff659b69080822a0e84cc83 | |
parent | 332e4115c978094b3630cde62a7154a4fcd564d8 (diff) | |
download | busybox-w32-d358b0b65dae83d52e511a126757e2aa7b1881b2.tar.gz busybox-w32-d358b0b65dae83d52e511a126757e2aa7b1881b2.tar.bz2 busybox-w32-d358b0b65dae83d52e511a126757e2aa7b1881b2.zip |
hush: fix a bug where we don't properly handle f() { a=A; b=B; }; a= f
function old new delta
unset_local_var 20 274 +254
leave_var_nest_level - 98 +98
set_vars_and_save_old 128 164 +36
enter_var_nest_level - 32 +32
builtin_local 46 50 +4
pseudo_exec_argv 554 544 -10
redirect_and_varexp_helper 77 64 -13
run_pipe 1890 1641 -249
unset_local_var_len 267 - -267
------------------------------------------------------------------------------
(add/remove: 2/1 grow/shrink: 3/3 up/down: 424/-539) Total: -115 bytes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | shell/hush.c | 153 | ||||
-rw-r--r-- | shell/hush_test/hush-vars/var_nested1.right | 3 | ||||
-rwxr-xr-x | shell/hush_test/hush-vars/var_nested1.tests | 16 | ||||
-rw-r--r-- | shell/hush_test/hush-vars/var_nested2.right | 1 | ||||
-rwxr-xr-x | shell/hush_test/hush-vars/var_nested2.tests | 2 |
5 files changed, 93 insertions, 82 deletions
diff --git a/shell/hush.c b/shell/hush.c index 9fb9b5f32..00d86b4e4 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -475,7 +475,6 @@ static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER; | |||
475 | */ | 475 | */ |
476 | #if !BB_MMU | 476 | #if !BB_MMU |
477 | typedef struct nommu_save_t { | 477 | typedef struct nommu_save_t { |
478 | char **new_env; | ||
479 | struct variable *old_vars; | 478 | struct variable *old_vars; |
480 | char **argv; | 479 | char **argv; |
481 | char **argv_from_re_execing; | 480 | char **argv_from_re_execing; |
@@ -2176,10 +2175,16 @@ static int set_local_var(char *str, unsigned flags) | |||
2176 | *eq_sign = '='; | 2175 | *eq_sign = '='; |
2177 | } | 2176 | } |
2178 | if (cur->var_nest_level < local_lvl) { | 2177 | if (cur->var_nest_level < local_lvl) { |
2179 | /* New variable is declared as local, | 2178 | /* New variable is local ("local VAR=VAL" or |
2179 | * "VAR=VAL cmd") | ||
2180 | * and existing one is global, or local | 2180 | * and existing one is global, or local |
2181 | * from enclosing function. | 2181 | * on a lower level that new one. |
2182 | * Remove and save old one: */ | 2182 | * Remove and save old one: |
2183 | */ | ||
2184 | debug_printf_env("shadowing %s'%s'/%u by '%s'/%u\n", | ||
2185 | cur->flg_export ? "exported " : "", | ||
2186 | cur->varstr, cur->var_nest_level, str, local_lvl | ||
2187 | ); | ||
2183 | *cur_pp = cur->next; | 2188 | *cur_pp = cur->next; |
2184 | cur->next = *G.shadowed_vars_pp; | 2189 | cur->next = *G.shadowed_vars_pp; |
2185 | *G.shadowed_vars_pp = cur; | 2190 | *G.shadowed_vars_pp = cur; |
@@ -2197,6 +2202,7 @@ static int set_local_var(char *str, unsigned flags) | |||
2197 | } | 2202 | } |
2198 | 2203 | ||
2199 | if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) { | 2204 | if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) { |
2205 | debug_printf_env("assignement '%s' does not change anything\n", str); | ||
2200 | free_and_exp: | 2206 | free_and_exp: |
2201 | free(str); | 2207 | free(str); |
2202 | goto exp; | 2208 | goto exp; |
@@ -2204,6 +2210,7 @@ static int set_local_var(char *str, unsigned flags) | |||
2204 | if (cur->max_len != 0) { | 2210 | if (cur->max_len != 0) { |
2205 | if (cur->max_len >= strlen(str)) { | 2211 | if (cur->max_len >= strlen(str)) { |
2206 | /* This one is from startup env, reuse space */ | 2212 | /* This one is from startup env, reuse space */ |
2213 | debug_printf_env("reusing startup env for '%s'\n", str); | ||
2207 | strcpy(cur->varstr, str); | 2214 | strcpy(cur->varstr, str); |
2208 | goto free_and_exp; | 2215 | goto free_and_exp; |
2209 | } | 2216 | } |
@@ -2221,7 +2228,7 @@ static int set_local_var(char *str, unsigned flags) | |||
2221 | goto set_str_and_exp; | 2228 | goto set_str_and_exp; |
2222 | } | 2229 | } |
2223 | 2230 | ||
2224 | /* Not found - create new variable struct */ | 2231 | /* Not found or shadowed - create new variable struct */ |
2225 | cur = xzalloc(sizeof(*cur)); | 2232 | cur = xzalloc(sizeof(*cur)); |
2226 | cur->var_nest_level = local_lvl; | 2233 | cur->var_nest_level = local_lvl; |
2227 | cur->next = *cur_pp; | 2234 | cur->next = *cur_pp; |
@@ -2250,7 +2257,7 @@ static int set_local_var(char *str, unsigned flags) | |||
2250 | /* unsetenv was already done */ | 2257 | /* unsetenv was already done */ |
2251 | } else { | 2258 | } else { |
2252 | int i; | 2259 | int i; |
2253 | debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr); | 2260 | debug_printf_env("%s: putenv '%s'/%u\n", __func__, cur->varstr, cur->var_nest_level); |
2254 | i = putenv(cur->varstr); | 2261 | i = putenv(cur->varstr); |
2255 | /* only now we can free old exported malloced string */ | 2262 | /* only now we can free old exported malloced string */ |
2256 | free(free_me); | 2263 | free(free_me); |
@@ -2313,21 +2320,6 @@ static int unset_local_var(const char *name) | |||
2313 | } | 2320 | } |
2314 | #endif | 2321 | #endif |
2315 | 2322 | ||
2316 | static void unset_vars(char **strings) | ||
2317 | { | ||
2318 | char **v; | ||
2319 | |||
2320 | if (!strings) | ||
2321 | return; | ||
2322 | v = strings; | ||
2323 | while (*v) { | ||
2324 | const char *eq = strchrnul(*v, '='); | ||
2325 | unset_local_var_len(*v, (int)(eq - *v)); | ||
2326 | v++; | ||
2327 | } | ||
2328 | free(strings); | ||
2329 | } | ||
2330 | |||
2331 | #if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS | 2323 | #if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS |
2332 | static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) | 2324 | static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) |
2333 | { | 2325 | { |
@@ -2349,15 +2341,20 @@ static void add_vars(struct variable *var) | |||
2349 | var->next = G.top_var; | 2341 | var->next = G.top_var; |
2350 | G.top_var = var; | 2342 | G.top_var = var; |
2351 | if (var->flg_export) { | 2343 | if (var->flg_export) { |
2352 | debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr); | 2344 | debug_printf_env("%s: restoring exported '%s'/%u\n", __func__, var->varstr, var->var_nest_level); |
2353 | putenv(var->varstr); | 2345 | putenv(var->varstr); |
2354 | } else { | 2346 | } else { |
2355 | debug_printf_env("%s: restoring variable '%s'\n", __func__, var->varstr); | 2347 | debug_printf_env("%s: restoring variable '%s'/%u\n", __func__, var->varstr, var->var_nest_level); |
2356 | } | 2348 | } |
2357 | var = next; | 2349 | var = next; |
2358 | } | 2350 | } |
2359 | } | 2351 | } |
2360 | 2352 | ||
2353 | /* We put strings[i] into variable table and possibly putenv them. | ||
2354 | * If variable is read only, we can free the strings[i] | ||
2355 | * which attempts to overwrite it. | ||
2356 | * The strings[] vector itself is freed. | ||
2357 | */ | ||
2361 | static struct variable *set_vars_and_save_old(char **strings) | 2358 | static struct variable *set_vars_and_save_old(char **strings) |
2362 | { | 2359 | { |
2363 | char **s; | 2360 | char **s; |
@@ -2365,6 +2362,7 @@ static struct variable *set_vars_and_save_old(char **strings) | |||
2365 | 2362 | ||
2366 | if (!strings) | 2363 | if (!strings) |
2367 | return old; | 2364 | return old; |
2365 | |||
2368 | s = strings; | 2366 | s = strings; |
2369 | while (*s) { | 2367 | while (*s) { |
2370 | struct variable *var_p; | 2368 | struct variable *var_p; |
@@ -2398,11 +2396,15 @@ static struct variable *set_vars_and_save_old(char **strings) | |||
2398 | var_p->next = old; | 2396 | var_p->next = old; |
2399 | old = var_p; | 2397 | old = var_p; |
2400 | } | 2398 | } |
2401 | set_local_var(*s, SETFLAG_EXPORT); | 2399 | //bb_error_msg("G.var_nest_level:%d", G.var_nest_level); |
2400 | set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT); | ||
2401 | } else if (HUSH_DEBUG) { | ||
2402 | bb_error_msg_and_die("BUG in varexp4"); | ||
2402 | } | 2403 | } |
2403 | s++; | 2404 | s++; |
2404 | next: ; | 2405 | next: ; |
2405 | } | 2406 | } |
2407 | free(strings); | ||
2406 | return old; | 2408 | return old; |
2407 | } | 2409 | } |
2408 | 2410 | ||
@@ -6339,7 +6341,7 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv) | |||
6339 | #endif | 6341 | #endif |
6340 | 6342 | ||
6341 | /* Used for expansion of right hand of assignments, | 6343 | /* Used for expansion of right hand of assignments, |
6342 | * $((...)), heredocs, variable espansion parts. | 6344 | * $((...)), heredocs, variable expansion parts. |
6343 | * | 6345 | * |
6344 | * NB: should NOT do globbing! | 6346 | * NB: should NOT do globbing! |
6345 | * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" | 6347 | * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" |
@@ -7385,6 +7387,7 @@ static void exec_function(char ***to_free, | |||
7385 | static void enter_var_nest_level(void) | 7387 | static void enter_var_nest_level(void) |
7386 | { | 7388 | { |
7387 | G.var_nest_level++; | 7389 | G.var_nest_level++; |
7390 | debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); | ||
7388 | 7391 | ||
7389 | /* Try: f() { echo -n .; f; }; f | 7392 | /* Try: f() { echo -n .; f; }; f |
7390 | * struct variable::var_nest_level is uint16_t, | 7393 | * struct variable::var_nest_level is uint16_t, |
@@ -7408,17 +7411,22 @@ static void leave_var_nest_level(void) | |||
7408 | continue; | 7411 | continue; |
7409 | } | 7412 | } |
7410 | /* Unexport */ | 7413 | /* Unexport */ |
7411 | if (cur->flg_export) | 7414 | if (cur->flg_export) { |
7415 | debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); | ||
7412 | bb_unsetenv(cur->varstr); | 7416 | bb_unsetenv(cur->varstr); |
7417 | } | ||
7413 | /* Remove from global list */ | 7418 | /* Remove from global list */ |
7414 | *cur_pp = cur->next; | 7419 | *cur_pp = cur->next; |
7415 | /* Free */ | 7420 | /* Free */ |
7416 | if (!cur->max_len) | 7421 | if (!cur->max_len) { |
7422 | debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); | ||
7417 | free(cur->varstr); | 7423 | free(cur->varstr); |
7424 | } | ||
7418 | free(cur); | 7425 | free(cur); |
7419 | } | 7426 | } |
7420 | 7427 | ||
7421 | G.var_nest_level--; | 7428 | G.var_nest_level--; |
7429 | debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); | ||
7422 | if (HUSH_DEBUG && (int)G.var_nest_level < 0) | 7430 | if (HUSH_DEBUG && (int)G.var_nest_level < 0) |
7423 | bb_error_msg_and_die("BUG: nesting underflow"); | 7431 | bb_error_msg_and_die("BUG: nesting underflow"); |
7424 | } | 7432 | } |
@@ -7431,11 +7439,12 @@ static int run_function(const struct function *funcp, char **argv) | |||
7431 | 7439 | ||
7432 | save_and_replace_G_args(&sv, argv); | 7440 | save_and_replace_G_args(&sv, argv); |
7433 | 7441 | ||
7434 | /* "we are in function, ok to use return" */ | 7442 | /* "We are in function, ok to use return" */ |
7435 | sv_flg = G_flag_return_in_progress; | 7443 | sv_flg = G_flag_return_in_progress; |
7436 | G_flag_return_in_progress = -1; | 7444 | G_flag_return_in_progress = -1; |
7437 | 7445 | ||
7438 | enter_var_nest_level(); | 7446 | /* Make "local" variables properly shadow previous ones */ |
7447 | IF_HUSH_LOCAL(enter_var_nest_level();) | ||
7439 | IF_HUSH_LOCAL(G.func_nest_level++;) | 7448 | IF_HUSH_LOCAL(G.func_nest_level++;) |
7440 | 7449 | ||
7441 | /* On MMU, funcp->body is always non-NULL */ | 7450 | /* On MMU, funcp->body is always non-NULL */ |
@@ -7451,7 +7460,7 @@ static int run_function(const struct function *funcp, char **argv) | |||
7451 | } | 7460 | } |
7452 | 7461 | ||
7453 | IF_HUSH_LOCAL(G.func_nest_level--;) | 7462 | IF_HUSH_LOCAL(G.func_nest_level--;) |
7454 | leave_var_nest_level(); | 7463 | IF_HUSH_LOCAL(leave_var_nest_level();) |
7455 | 7464 | ||
7456 | G_flag_return_in_progress = sv_flg; | 7465 | G_flag_return_in_progress = sv_flg; |
7457 | 7466 | ||
@@ -7608,11 +7617,9 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, | |||
7608 | 7617 | ||
7609 | #if BB_MMU | 7618 | #if BB_MMU |
7610 | set_vars_and_save_old(new_env); | 7619 | set_vars_and_save_old(new_env); |
7611 | free(new_env); /* optional */ | 7620 | /* we can destroy set_vars_and_save_old's return value, |
7612 | /* we can also destroy set_vars_and_save_old's return value, | ||
7613 | * to save memory */ | 7621 | * to save memory */ |
7614 | #else | 7622 | #else |
7615 | nommu_save->new_env = new_env; | ||
7616 | nommu_save->old_vars = set_vars_and_save_old(new_env); | 7623 | nommu_save->old_vars = set_vars_and_save_old(new_env); |
7617 | #endif | 7624 | #endif |
7618 | 7625 | ||
@@ -8174,10 +8181,10 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe) | |||
8174 | * subshell: ( list ) [&] | 8181 | * subshell: ( list ) [&] |
8175 | */ | 8182 | */ |
8176 | #if !ENABLE_HUSH_MODE_X | 8183 | #if !ENABLE_HUSH_MODE_X |
8177 | #define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, argv_expanded) \ | 8184 | #define redirect_and_varexp_helper(old_vars_p, command, squirrel, argv_expanded) \ |
8178 | redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel) | 8185 | redirect_and_varexp_helper(old_vars_p, command, squirrel) |
8179 | #endif | 8186 | #endif |
8180 | static int redirect_and_varexp_helper(char ***new_env_p, | 8187 | static int redirect_and_varexp_helper( |
8181 | struct variable **old_vars_p, | 8188 | struct variable **old_vars_p, |
8182 | struct command *command, | 8189 | struct command *command, |
8183 | struct squirrel **sqp, | 8190 | struct squirrel **sqp, |
@@ -8190,11 +8197,10 @@ static int redirect_and_varexp_helper(char ***new_env_p, | |||
8190 | int rcode = setup_redirects(command, sqp); | 8197 | int rcode = setup_redirects(command, sqp); |
8191 | if (rcode == 0) { | 8198 | if (rcode == 0) { |
8192 | char **new_env = expand_assignments(command->argv, command->assignment_cnt); | 8199 | char **new_env = expand_assignments(command->argv, command->assignment_cnt); |
8193 | *new_env_p = new_env; | ||
8194 | dump_cmd_in_x_mode(new_env); | 8200 | dump_cmd_in_x_mode(new_env); |
8195 | dump_cmd_in_x_mode(argv_expanded); | 8201 | dump_cmd_in_x_mode(argv_expanded); |
8196 | if (old_vars_p) | 8202 | /* this takes ownership of new_env[i] elements, and frees new_env: */ |
8197 | *old_vars_p = set_vars_and_save_old(new_env); | 8203 | *old_vars_p = set_vars_and_save_old(new_env); |
8198 | } | 8204 | } |
8199 | return rcode; | 8205 | return rcode; |
8200 | } | 8206 | } |
@@ -8284,13 +8290,9 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8284 | argv = command->argv ? command->argv : (char **) &null_ptr; | 8290 | argv = command->argv ? command->argv : (char **) &null_ptr; |
8285 | { | 8291 | { |
8286 | const struct built_in_command *x; | 8292 | const struct built_in_command *x; |
8287 | #if ENABLE_HUSH_FUNCTIONS | 8293 | IF_HUSH_FUNCTIONS(const struct function *funcp;) |
8288 | const struct function *funcp; | 8294 | IF_NOT_HUSH_FUNCTIONS(enum { funcp = 0 };) |
8289 | #else | 8295 | struct variable *old_vars; |
8290 | enum { funcp = 0 }; | ||
8291 | #endif | ||
8292 | char **new_env = NULL; | ||
8293 | struct variable *old_vars = NULL; | ||
8294 | 8296 | ||
8295 | #if ENABLE_HUSH_LINENO_VAR | 8297 | #if ENABLE_HUSH_LINENO_VAR |
8296 | if (G.lineno_var) | 8298 | if (G.lineno_var) |
@@ -8303,28 +8305,6 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8303 | * Try "a=t >file" | 8305 | * Try "a=t >file" |
8304 | */ | 8306 | */ |
8305 | G.expand_exitcode = 0; | 8307 | G.expand_exitcode = 0; |
8306 | #if 0 /* A few cases in testsuite fail with this code. FIXME */ | ||
8307 | rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, &squirrel, /*argv_expanded:*/ NULL); | ||
8308 | /* Set shell variables */ | ||
8309 | if (new_env) { | ||
8310 | argv = new_env; | ||
8311 | while (*argv) { | ||
8312 | if (set_local_var(*argv, /*flag:*/ 0)) { | ||
8313 | /* assignment to readonly var / putenv error? */ | ||
8314 | rcode = 1; | ||
8315 | } | ||
8316 | argv++; | ||
8317 | } | ||
8318 | } | ||
8319 | /* Redirect error sets $? to 1. Otherwise, | ||
8320 | * if evaluating assignment value set $?, retain it. | ||
8321 | * Try "false; q=`exit 2`; echo $?" - should print 2: */ | ||
8322 | if (rcode == 0) | ||
8323 | rcode = G.expand_exitcode; | ||
8324 | /* Exit, _skipping_ variable restoring code: */ | ||
8325 | goto clean_up_and_ret0; | ||
8326 | |||
8327 | #else /* Older, bigger, but more correct code */ | ||
8328 | 8308 | ||
8329 | rcode = setup_redirects(command, &squirrel); | 8309 | rcode = setup_redirects(command, &squirrel); |
8330 | restore_redirects(squirrel); | 8310 | restore_redirects(squirrel); |
@@ -8358,7 +8338,6 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8358 | debug_leave(); | 8338 | debug_leave(); |
8359 | debug_printf_exec("run_pipe: return %d\n", rcode); | 8339 | debug_printf_exec("run_pipe: return %d\n", rcode); |
8360 | return rcode; | 8340 | return rcode; |
8361 | #endif | ||
8362 | } | 8341 | } |
8363 | 8342 | ||
8364 | /* Expand the rest into (possibly) many strings each */ | 8343 | /* Expand the rest into (possibly) many strings each */ |
@@ -8372,6 +8351,7 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8372 | } | 8351 | } |
8373 | 8352 | ||
8374 | /* if someone gives us an empty string: `cmd with empty output` */ | 8353 | /* if someone gives us an empty string: `cmd with empty output` */ |
8354 | //TODO: what about: var=EXPR `` >FILE ? will var be set? Will FILE be created? | ||
8375 | if (!argv_expanded[0]) { | 8355 | if (!argv_expanded[0]) { |
8376 | free(argv_expanded); | 8356 | free(argv_expanded); |
8377 | debug_leave(); | 8357 | debug_leave(); |
@@ -8394,7 +8374,14 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8394 | goto clean_up_and_ret1; | 8374 | goto clean_up_and_ret1; |
8395 | } | 8375 | } |
8396 | } | 8376 | } |
8397 | rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); | 8377 | |
8378 | /* Without bumping var nesting level, this leaks | ||
8379 | * exported $a: | ||
8380 | * a=b true; env | grep ^a= | ||
8381 | */ | ||
8382 | enter_var_nest_level(); | ||
8383 | rcode = redirect_and_varexp_helper(&old_vars, command, &squirrel, argv_expanded); | ||
8384 | |||
8398 | if (rcode == 0) { | 8385 | if (rcode == 0) { |
8399 | if (!funcp) { | 8386 | if (!funcp) { |
8400 | debug_printf_exec(": builtin '%s' '%s'...\n", | 8387 | debug_printf_exec(": builtin '%s' '%s'...\n", |
@@ -8405,24 +8392,23 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8405 | } | 8392 | } |
8406 | #if ENABLE_HUSH_FUNCTIONS | 8393 | #if ENABLE_HUSH_FUNCTIONS |
8407 | else { | 8394 | else { |
8408 | # if ENABLE_HUSH_LOCAL | 8395 | struct variable **sv = G.shadowed_vars_pp; |
8409 | struct variable **sv; | 8396 | /* Make "local" builtins in the called function |
8410 | sv = G.shadowed_vars_pp; | 8397 | * stash shadowed variables in old_vars list |
8398 | */ | ||
8411 | G.shadowed_vars_pp = &old_vars; | 8399 | G.shadowed_vars_pp = &old_vars; |
8412 | # endif | 8400 | |
8413 | debug_printf_exec(": function '%s' '%s'...\n", | 8401 | debug_printf_exec(": function '%s' '%s'...\n", |
8414 | funcp->name, argv_expanded[1]); | 8402 | funcp->name, argv_expanded[1]); |
8415 | rcode = run_function(funcp, argv_expanded) & 0xff; | 8403 | rcode = run_function(funcp, argv_expanded) & 0xff; |
8416 | # if ENABLE_HUSH_LOCAL | 8404 | |
8417 | G.shadowed_vars_pp = sv; | 8405 | G.shadowed_vars_pp = sv; |
8418 | # endif | ||
8419 | } | 8406 | } |
8420 | #endif | 8407 | #endif |
8421 | } | 8408 | } |
8422 | clean_up_and_ret: | 8409 | clean_up_and_ret: |
8423 | unset_vars(new_env); | 8410 | leave_var_nest_level(); |
8424 | add_vars(old_vars); | 8411 | add_vars(old_vars); |
8425 | /* clean_up_and_ret0: */ | ||
8426 | restore_redirects(squirrel); | 8412 | restore_redirects(squirrel); |
8427 | /* | 8413 | /* |
8428 | * Try "usleep 99999999" + ^C + "echo $?" | 8414 | * Try "usleep 99999999" + ^C + "echo $?" |
@@ -8453,7 +8439,8 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8453 | if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) { | 8439 | if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) { |
8454 | int n = find_applet_by_name(argv_expanded[0]); | 8440 | int n = find_applet_by_name(argv_expanded[0]); |
8455 | if (n >= 0 && APPLET_IS_NOFORK(n)) { | 8441 | if (n >= 0 && APPLET_IS_NOFORK(n)) { |
8456 | rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); | 8442 | enter_var_nest_level(); |
8443 | rcode = redirect_and_varexp_helper(&old_vars, command, &squirrel, argv_expanded); | ||
8457 | if (rcode == 0) { | 8444 | if (rcode == 0) { |
8458 | debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", | 8445 | debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", |
8459 | argv_expanded[0], argv_expanded[1]); | 8446 | argv_expanded[0], argv_expanded[1]); |
@@ -8487,7 +8474,6 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8487 | struct fd_pair pipefds; | 8474 | struct fd_pair pipefds; |
8488 | #if !BB_MMU | 8475 | #if !BB_MMU |
8489 | volatile nommu_save_t nommu_save; | 8476 | volatile nommu_save_t nommu_save; |
8490 | nommu_save.new_env = NULL; | ||
8491 | nommu_save.old_vars = NULL; | 8477 | nommu_save.old_vars = NULL; |
8492 | nommu_save.argv = NULL; | 8478 | nommu_save.argv = NULL; |
8493 | nommu_save.argv_from_re_execing = NULL; | 8479 | nommu_save.argv_from_re_execing = NULL; |
@@ -8580,7 +8566,6 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8580 | /* Clean up after vforked child */ | 8566 | /* Clean up after vforked child */ |
8581 | free(nommu_save.argv); | 8567 | free(nommu_save.argv); |
8582 | free(nommu_save.argv_from_re_execing); | 8568 | free(nommu_save.argv_from_re_execing); |
8583 | unset_vars(nommu_save.new_env); | ||
8584 | add_vars(nommu_save.old_vars); | 8569 | add_vars(nommu_save.old_vars); |
8585 | #endif | 8570 | #endif |
8586 | free(argv_expanded); | 8571 | free(argv_expanded); |
@@ -10066,7 +10051,11 @@ static int FAST_FUNC builtin_local(char **argv) | |||
10066 | return EXIT_FAILURE; /* bash compat */ | 10051 | return EXIT_FAILURE; /* bash compat */ |
10067 | } | 10052 | } |
10068 | argv++; | 10053 | argv++; |
10069 | return helper_export_local(argv, G.var_nest_level << SETFLAG_VARLVL_SHIFT); | 10054 | /* Since all builtins run in a nested variable level, |
10055 | * need to use level - 1 here. Or else the variable will be removed at once | ||
10056 | * after builtin returns. | ||
10057 | */ | ||
10058 | return helper_export_local(argv, (G.var_nest_level - 1) << SETFLAG_VARLVL_SHIFT); | ||
10070 | } | 10059 | } |
10071 | #endif | 10060 | #endif |
10072 | 10061 | ||
diff --git a/shell/hush_test/hush-vars/var_nested1.right b/shell/hush_test/hush-vars/var_nested1.right new file mode 100644 index 000000000..f4bc5f4b6 --- /dev/null +++ b/shell/hush_test/hush-vars/var_nested1.right | |||
@@ -0,0 +1,3 @@ | |||
1 | Expected:AB Actual:AB | ||
2 | Expected:Ab Actual:Ab | ||
3 | Expected:ab Actual:ab | ||
diff --git a/shell/hush_test/hush-vars/var_nested1.tests b/shell/hush_test/hush-vars/var_nested1.tests new file mode 100755 index 000000000..59e4a14fa --- /dev/null +++ b/shell/hush_test/hush-vars/var_nested1.tests | |||
@@ -0,0 +1,16 @@ | |||
1 | f() { a=A; b=B; } | ||
2 | |||
3 | a=a | ||
4 | b=b | ||
5 | f | ||
6 | echo Expected:AB Actual:$a$b | ||
7 | |||
8 | a=a | ||
9 | b=b | ||
10 | b= f | ||
11 | echo Expected:Ab Actual:$a$b | ||
12 | |||
13 | a=a | ||
14 | b=b | ||
15 | a= b= f | ||
16 | echo Expected:ab Actual:$a$b | ||
diff --git a/shell/hush_test/hush-vars/var_nested2.right b/shell/hush_test/hush-vars/var_nested2.right new file mode 100644 index 000000000..c930d971c --- /dev/null +++ b/shell/hush_test/hush-vars/var_nested2.right | |||
@@ -0,0 +1 @@ | |||
aB | |||
diff --git a/shell/hush_test/hush-vars/var_nested2.tests b/shell/hush_test/hush-vars/var_nested2.tests new file mode 100755 index 000000000..e8865861e --- /dev/null +++ b/shell/hush_test/hush-vars/var_nested2.tests | |||
@@ -0,0 +1,2 @@ | |||
1 | # the bug was easier to trigger in one-liner form | ||
2 | a=a; b=b; f() { a=A; b=B; }; a= f; echo $a$b | ||