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 | ||
