diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2009-06-09 18:40:52 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2009-06-09 18:40:52 +0200 |
commit | 9d617c44d2b1135d14b7dafd01a1d3992293f4d9 (patch) | |
tree | a763d31365f18cb2bb984d252bc849f03c45625a | |
parent | 2634bf366b298a827d043566656f8696f4dc153c (diff) | |
download | busybox-w32-9d617c44d2b1135d14b7dafd01a1d3992293f4d9.tar.gz busybox-w32-9d617c44d2b1135d14b7dafd01a1d3992293f4d9.tar.bz2 busybox-w32-9d617c44d2b1135d14b7dafd01a1d3992293f4d9.zip |
hush: specially handle [[ - suppress globbing & multiword expansion
It's a bashism, but is surprisingly easy to do and costs very little code.
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | shell/hush.c | 132 | ||||
-rw-r--r-- | shell/hush_test/hush-bugs/export_exp.right | 7 | ||||
-rwxr-xr-x | shell/hush_test/hush-bugs/export_exp.tests | 19 |
3 files changed, 138 insertions, 20 deletions
diff --git a/shell/hush.c b/shell/hush.c index 9ecb60293..c9962e33b 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -296,12 +296,40 @@ struct command { | |||
296 | pid_t pid; /* 0 if exited */ | 296 | pid_t pid; /* 0 if exited */ |
297 | int assignment_cnt; /* how many argv[i] are assignments? */ | 297 | int assignment_cnt; /* how many argv[i] are assignments? */ |
298 | smallint is_stopped; /* is the command currently running? */ | 298 | smallint is_stopped; /* is the command currently running? */ |
299 | smallint grp_type; /* GRP_xxx */ | 299 | smallint cmd_type; /* CMD_xxx */ |
300 | #define GRP_NORMAL 0 | 300 | #define CMD_NORMAL 0 |
301 | #define GRP_SUBSHELL 1 | 301 | #define CMD_SUBSHELL 1 |
302 | /* used for "[[ EXPR ]]" */ | ||
303 | #define CMD_SINGLEWORD_NOGLOB 2 | ||
304 | /* used for "export noglob=* glob* a=`echo a b`" */ | ||
305 | /* #define CMD_SINGLEWORD_NOGLOB_COND 3 */ | ||
306 | // It is hard to implement correctly, adds significant amounts of tricky code, | ||
307 | // and all this only useful for really obscure export statements | ||
308 | // almost nobody would use anyway. #ifdef CMD_SINGLEWORD_NOGLOB_COND | ||
309 | // guard the code which implement it, but I have doubts it works | ||
310 | // in all cases (especially with mixed globbed/non-globbed arguments) | ||
302 | #if ENABLE_HUSH_FUNCTIONS | 311 | #if ENABLE_HUSH_FUNCTIONS |
303 | # define GRP_FUNCTION 2 | 312 | # define CMD_FUNCDEF 4 |
304 | #endif | 313 | #endif |
314 | //TODO | ||
315 | //#define CMD_ARITH - let EXPR, ((EXPR)) | ||
316 | //let EXPR [EXPR...] | ||
317 | // Each EXPR is an arithmetic expression to be evaluated (ARITHMETIC EVALUATION) | ||
318 | // If the last arg evaluates to 0, let returns 1; 0 is returned otherwise. | ||
319 | // NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used) | ||
320 | //((EXPR)) | ||
321 | // The EXPR is evaluated according to ARITHMETIC EVALUATION. | ||
322 | // This is exactly equivalent to let "expression". | ||
323 | //[[ EXPR ]] | ||
324 | // Basically, word splitting and pathname expansion should NOT be performed | ||
325 | // Examples: | ||
326 | // no word splitting: a="a b"; [[ $a = "a b" ]]; echo $? should print "0" | ||
327 | // no pathname expansion: [[ /bin/m* = "/bin/m*" ]]; echo $? should print "0" | ||
328 | // Additional operators: | ||
329 | // || and && should work as -o and -a | ||
330 | // =~ regexp match | ||
331 | // Apart from the above, [[ expr ]] should work as [ expr ] | ||
332 | |||
305 | /* if non-NULL, this "command" is { list }, ( list ), or a compound statement */ | 333 | /* if non-NULL, this "command" is { list }, ( list ), or a compound statement */ |
306 | struct pipe *group; | 334 | struct pipe *group; |
307 | #if !BB_MMU | 335 | #if !BB_MMU |
@@ -310,11 +338,11 @@ struct command { | |||
310 | #if ENABLE_HUSH_FUNCTIONS | 338 | #if ENABLE_HUSH_FUNCTIONS |
311 | struct function *child_func; | 339 | struct function *child_func; |
312 | /* This field is used to prevent a bug here: | 340 | /* This field is used to prevent a bug here: |
313 | * while...do f1() {a;}; f1; f1 {b;}; f1; done | 341 | * while...do f1() {a;}; f1; f1() {b;}; f1; done |
314 | * When we execute "f1() {a;}" cmd, we create new function and clear | 342 | * When we execute "f1() {a;}" cmd, we create new function and clear |
315 | * cmd->group, cmd->group_as_string, cmd->argv[0]. | 343 | * cmd->group, cmd->group_as_string, cmd->argv[0]. |
316 | * when we execute "f1 {b;}", we notice that f1 exists, | 344 | * When we execute "f1() {b;}", we notice that f1 exists, |
317 | * and that it's "parent cmd" struct is still "alive", | 345 | * and that its "parent cmd" struct is still "alive", |
318 | * we put those fields back into cmd->xxx | 346 | * we put those fields back into cmd->xxx |
319 | * (struct function has ->parent_cmd ptr to facilitate that). | 347 | * (struct function has ->parent_cmd ptr to facilitate that). |
320 | * When we loop back, we can execute "f1() {a;}" again and set f1 correctly. | 348 | * When we loop back, we can execute "f1() {a;}" again and set f1 correctly. |
@@ -2440,7 +2468,7 @@ static char **expand_variables(char **argv, int or_mask) | |||
2440 | n = 0; | 2468 | n = 0; |
2441 | v = argv; | 2469 | v = argv; |
2442 | while (*v) { | 2470 | while (*v) { |
2443 | n = expand_vars_to_list(&output, n, *v, (char)or_mask); | 2471 | n = expand_vars_to_list(&output, n, *v, (unsigned char)or_mask); |
2444 | v++; | 2472 | v++; |
2445 | } | 2473 | } |
2446 | debug_print_list("expand_variables", &output, n); | 2474 | debug_print_list("expand_variables", &output, n); |
@@ -2456,6 +2484,46 @@ static char **expand_strvec_to_strvec(char **argv) | |||
2456 | return expand_variables(argv, 0x100); | 2484 | return expand_variables(argv, 0x100); |
2457 | } | 2485 | } |
2458 | 2486 | ||
2487 | static char **expand_strvec_to_strvec_singleword_noglob(char **argv) | ||
2488 | { | ||
2489 | return expand_variables(argv, 0x80); | ||
2490 | } | ||
2491 | |||
2492 | #ifdef CMD_SINGLEWORD_NOGLOB_COND | ||
2493 | static char **expand_strvec_to_strvec_singleword_noglob_cond(char **argv) | ||
2494 | { | ||
2495 | int n; | ||
2496 | char **list; | ||
2497 | char **v; | ||
2498 | o_string output = NULL_O_STRING; | ||
2499 | |||
2500 | n = 0; | ||
2501 | v = argv; | ||
2502 | while (*v) { | ||
2503 | int is_var = is_well_formed_var_name(*v, '='); | ||
2504 | /* is_var * 0x80: singleword expansion for vars */ | ||
2505 | n = expand_vars_to_list(&output, n, *v, is_var * 0x80); | ||
2506 | |||
2507 | /* Subtle! expand_vars_to_list did not glob last word yet. | ||
2508 | * It does this only when fed with further data. | ||
2509 | * Therefore we set globbing flags AFTER it, not before: | ||
2510 | */ | ||
2511 | |||
2512 | /* if it is not recognizably abc=...; then: */ | ||
2513 | output.o_escape = !is_var; /* protect against globbing for "$var" */ | ||
2514 | /* (unquoted $var will temporarily switch it off) */ | ||
2515 | output.o_glob = !is_var; /* and indeed do globbing */ | ||
2516 | v++; | ||
2517 | } | ||
2518 | debug_print_list("expand_cond", &output, n); | ||
2519 | |||
2520 | /* output.data (malloced in one block) gets returned in "list" */ | ||
2521 | list = o_finalize_list(&output, n); | ||
2522 | debug_print_strings("expand_cond[1]", list); | ||
2523 | return list; | ||
2524 | } | ||
2525 | #endif | ||
2526 | |||
2459 | /* Used for expansion of right hand of assignments */ | 2527 | /* Used for expansion of right hand of assignments */ |
2460 | /* NB: should NOT do globbing! "export v=/bin/c*; env | grep ^v=" outputs | 2528 | /* NB: should NOT do globbing! "export v=/bin/c*; env | grep ^v=" outputs |
2461 | * "v=/bin/c*" */ | 2529 | * "v=/bin/c*" */ |
@@ -2465,7 +2533,7 @@ static char *expand_string_to_string(const char *str) | |||
2465 | 2533 | ||
2466 | argv[0] = (char*)str; | 2534 | argv[0] = (char*)str; |
2467 | argv[1] = NULL; | 2535 | argv[1] = NULL; |
2468 | list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */ | 2536 | list = expand_variables(argv, 0x80); /* 0x80: singleword expansion */ |
2469 | if (HUSH_DEBUG) | 2537 | if (HUSH_DEBUG) |
2470 | if (!list[0] || list[1]) | 2538 | if (!list[0] || list[1]) |
2471 | bb_error_msg_and_die("BUG in varexp2"); | 2539 | bb_error_msg_and_die("BUG in varexp2"); |
@@ -2875,8 +2943,8 @@ static void free_pipe(struct pipe *pi) | |||
2875 | } | 2943 | } |
2876 | /* not "else if": on syntax error, we may have both! */ | 2944 | /* not "else if": on syntax error, we may have both! */ |
2877 | if (command->group) { | 2945 | if (command->group) { |
2878 | debug_printf_clean(" begin group (grp_type:%d)\n", | 2946 | debug_printf_clean(" begin group (cmd_type:%d)\n", |
2879 | command->grp_type); | 2947 | command->cmd_type); |
2880 | free_pipe_list(command->group); | 2948 | free_pipe_list(command->group); |
2881 | debug_printf_clean(" end group\n"); | 2949 | debug_printf_clean(" end group\n"); |
2882 | command->group = NULL; | 2950 | command->group = NULL; |
@@ -3688,7 +3756,7 @@ static int run_pipe(struct pipe *pi) | |||
3688 | 3756 | ||
3689 | if (pi->num_cmds != 1 | 3757 | if (pi->num_cmds != 1 |
3690 | || pi->followup == PIPE_BG | 3758 | || pi->followup == PIPE_BG |
3691 | || command->grp_type == GRP_SUBSHELL | 3759 | || command->cmd_type == CMD_SUBSHELL |
3692 | ) { | 3760 | ) { |
3693 | goto must_fork; | 3761 | goto must_fork; |
3694 | } | 3762 | } |
@@ -3700,7 +3768,7 @@ static int run_pipe(struct pipe *pi) | |||
3700 | 3768 | ||
3701 | if (command->group) { | 3769 | if (command->group) { |
3702 | #if ENABLE_HUSH_FUNCTIONS | 3770 | #if ENABLE_HUSH_FUNCTIONS |
3703 | if (command->grp_type == GRP_FUNCTION) { | 3771 | if (command->cmd_type == CMD_FUNCDEF) { |
3704 | /* "executing" func () { list } */ | 3772 | /* "executing" func () { list } */ |
3705 | struct function *funcp; | 3773 | struct function *funcp; |
3706 | 3774 | ||
@@ -3770,7 +3838,18 @@ static int run_pipe(struct pipe *pi) | |||
3770 | } | 3838 | } |
3771 | 3839 | ||
3772 | /* Expand the rest into (possibly) many strings each */ | 3840 | /* Expand the rest into (possibly) many strings each */ |
3773 | argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); | 3841 | if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) { |
3842 | argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt); | ||
3843 | } | ||
3844 | #ifdef CMD_SINGLEWORD_NOGLOB_COND | ||
3845 | else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB_COND) { | ||
3846 | argv_expanded = expand_strvec_to_strvec_singleword_noglob_cond(argv + command->assignment_cnt); | ||
3847 | |||
3848 | } | ||
3849 | #endif | ||
3850 | else { | ||
3851 | argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); | ||
3852 | } | ||
3774 | 3853 | ||
3775 | x = find_builtin(argv_expanded[0]); | 3854 | x = find_builtin(argv_expanded[0]); |
3776 | #if ENABLE_HUSH_FUNCTIONS | 3855 | #if ENABLE_HUSH_FUNCTIONS |
@@ -4012,7 +4091,7 @@ static void debug_print_tree(struct pipe *pi, int lvl) | |||
4012 | [RES_XXXX ] = "XXXX" , | 4091 | [RES_XXXX ] = "XXXX" , |
4013 | [RES_SNTX ] = "SNTX" , | 4092 | [RES_SNTX ] = "SNTX" , |
4014 | }; | 4093 | }; |
4015 | static const char *const GRPTYPE[] = { | 4094 | static const char *const CMDTYPE[] = { |
4016 | "{}", | 4095 | "{}", |
4017 | "()", | 4096 | "()", |
4018 | #if ENABLE_HUSH_FUNCTIONS | 4097 | #if ENABLE_HUSH_FUNCTIONS |
@@ -4036,7 +4115,7 @@ static void debug_print_tree(struct pipe *pi, int lvl) | |||
4036 | command->assignment_cnt); | 4115 | command->assignment_cnt); |
4037 | if (command->group) { | 4116 | if (command->group) { |
4038 | fprintf(stderr, " group %s: (argv=%p)\n", | 4117 | fprintf(stderr, " group %s: (argv=%p)\n", |
4039 | GRPTYPE[command->grp_type], | 4118 | CMDTYPE[command->cmd_type], |
4040 | argv); | 4119 | argv); |
4041 | debug_print_tree(command->group, lvl+1); | 4120 | debug_print_tree(command->group, lvl+1); |
4042 | prn++; | 4121 | prn++; |
@@ -4649,7 +4728,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx) | |||
4649 | debug_printf_parse("pop stack %p\n", ctx->stack); | 4728 | debug_printf_parse("pop stack %p\n", ctx->stack); |
4650 | old = ctx->stack; | 4729 | old = ctx->stack; |
4651 | old->command->group = ctx->list_head; | 4730 | old->command->group = ctx->list_head; |
4652 | old->command->grp_type = GRP_NORMAL; | 4731 | old->command->cmd_type = CMD_NORMAL; |
4653 | #if !BB_MMU | 4732 | #if !BB_MMU |
4654 | o_addstr(&old->as_string, ctx->as_string.data); | 4733 | o_addstr(&old->as_string, ctx->as_string.data); |
4655 | o_free_unsafe(&ctx->as_string); | 4734 | o_free_unsafe(&ctx->as_string); |
@@ -4745,6 +4824,19 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
4745 | (ctx->ctx_res_w == RES_SNTX)); | 4824 | (ctx->ctx_res_w == RES_SNTX)); |
4746 | return (ctx->ctx_res_w == RES_SNTX); | 4825 | return (ctx->ctx_res_w == RES_SNTX); |
4747 | } | 4826 | } |
4827 | # ifdef CMD_SINGLEWORD_NOGLOB_COND | ||
4828 | if (strcmp(word->data, "export") == 0 | ||
4829 | # if ENABLE_HUSH_LOCAL | ||
4830 | || strcmp(word->data, "local") == 0 | ||
4831 | # endif | ||
4832 | ) { | ||
4833 | command->cmd_type = CMD_SINGLEWORD_NOGLOB_COND; | ||
4834 | } else | ||
4835 | # endif | ||
4836 | if (strcmp(word->data, "[[") == 0) { | ||
4837 | command->cmd_type = CMD_SINGLEWORD_NOGLOB; | ||
4838 | } | ||
4839 | /* fall through */ | ||
4748 | } | 4840 | } |
4749 | #endif | 4841 | #endif |
4750 | if (command->group) { | 4842 | if (command->group) { |
@@ -5189,7 +5281,7 @@ static int parse_group(o_string *dest, struct parse_context *ctx, | |||
5189 | return 1; | 5281 | return 1; |
5190 | } | 5282 | } |
5191 | nommu_addchr(&ctx->as_string, ch); | 5283 | nommu_addchr(&ctx->as_string, ch); |
5192 | command->grp_type = GRP_FUNCTION; | 5284 | command->cmd_type = CMD_FUNCDEF; |
5193 | goto skip; | 5285 | goto skip; |
5194 | } | 5286 | } |
5195 | #endif | 5287 | #endif |
@@ -5209,7 +5301,7 @@ static int parse_group(o_string *dest, struct parse_context *ctx, | |||
5209 | endch = '}'; | 5301 | endch = '}'; |
5210 | if (ch == '(') { | 5302 | if (ch == '(') { |
5211 | endch = ')'; | 5303 | endch = ')'; |
5212 | command->grp_type = GRP_SUBSHELL; | 5304 | command->cmd_type = CMD_SUBSHELL; |
5213 | } else { | 5305 | } else { |
5214 | /* bash does not allow "{echo...", requires whitespace */ | 5306 | /* bash does not allow "{echo...", requires whitespace */ |
5215 | ch = i_getch(input); | 5307 | ch = i_getch(input); |
diff --git a/shell/hush_test/hush-bugs/export_exp.right b/shell/hush_test/hush-bugs/export_exp.right new file mode 100644 index 000000000..17a2e93f7 --- /dev/null +++ b/shell/hush_test/hush-bugs/export_exp.right | |||
@@ -0,0 +1,7 @@ | |||
1 | aa0 bb0 | ||
2 | a=aa0 b=bb0 | ||
3 | aa1 bb1 | ||
4 | a=aa1 b=bb1 | ||
5 | zzz=zzz | ||
6 | zz=* | ||
7 | Done | ||
diff --git a/shell/hush_test/hush-bugs/export_exp.tests b/shell/hush_test/hush-bugs/export_exp.tests new file mode 100755 index 000000000..91f57aa2c --- /dev/null +++ b/shell/hush_test/hush-bugs/export_exp.tests | |||
@@ -0,0 +1,19 @@ | |||
1 | v="a=aa0 b=bb0" | ||
2 | # only 1st arg should be expanded in multiple words | ||
3 | export $v c=$v | ||
4 | echo $a $b | ||
5 | echo $c | ||
6 | |||
7 | # only 1st arg should be expanded in multiple words | ||
8 | export `echo a=aa1 b=bb1` c=`echo a=aa1 b=bb1` | ||
9 | echo $a $b | ||
10 | echo $c | ||
11 | |||
12 | >zz=zz | ||
13 | >zzz=zzz | ||
14 | # only 1st arg should be globbed | ||
15 | export zzz* zz=* | ||
16 | env | grep ^zz | sort | ||
17 | rm -rf zz=zz zzz=zzz | ||
18 | |||
19 | echo Done | ||