From 9d617c44d2b1135d14b7dafd01a1d3992293f4d9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 9 Jun 2009 18:40:52 +0200 Subject: [PATCH] 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 --- shell/hush.c | 132 +++++++++++++++++---- shell/hush_test/hush-bugs/export_exp.right | 7 ++ shell/hush_test/hush-bugs/export_exp.tests | 19 +++ 3 files changed, 138 insertions(+), 20 deletions(-) create mode 100644 shell/hush_test/hush-bugs/export_exp.right create mode 100755 shell/hush_test/hush-bugs/export_exp.tests 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 { pid_t pid; /* 0 if exited */ int assignment_cnt; /* how many argv[i] are assignments? */ smallint is_stopped; /* is the command currently running? */ - smallint grp_type; /* GRP_xxx */ -#define GRP_NORMAL 0 -#define GRP_SUBSHELL 1 + smallint cmd_type; /* CMD_xxx */ +#define CMD_NORMAL 0 +#define CMD_SUBSHELL 1 +/* used for "[[ EXPR ]]" */ +#define CMD_SINGLEWORD_NOGLOB 2 +/* used for "export noglob=* glob* a=`echo a b`" */ +/* #define CMD_SINGLEWORD_NOGLOB_COND 3 */ +// It is hard to implement correctly, adds significant amounts of tricky code, +// and all this only useful for really obscure export statements +// almost nobody would use anyway. #ifdef CMD_SINGLEWORD_NOGLOB_COND +// guard the code which implement it, but I have doubts it works +// in all cases (especially with mixed globbed/non-globbed arguments) #if ENABLE_HUSH_FUNCTIONS -# define GRP_FUNCTION 2 -#endif +# define CMD_FUNCDEF 4 +#endif +//TODO +//#define CMD_ARITH - let EXPR, ((EXPR)) +//let EXPR [EXPR...] +// Each EXPR is an arithmetic expression to be evaluated (ARITHMETIC EVALUATION) +// If the last arg evaluates to 0, let returns 1; 0 is returned otherwise. +// NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used) +//((EXPR)) +// The EXPR is evaluated according to ARITHMETIC EVALUATION. +// This is exactly equivalent to let "expression". +//[[ EXPR ]] +// Basically, word splitting and pathname expansion should NOT be performed +// Examples: +// no word splitting: a="a b"; [[ $a = "a b" ]]; echo $? should print "0" +// no pathname expansion: [[ /bin/m* = "/bin/m*" ]]; echo $? should print "0" +// Additional operators: +// || and && should work as -o and -a +// =~ regexp match +// Apart from the above, [[ expr ]] should work as [ expr ] + /* if non-NULL, this "command" is { list }, ( list ), or a compound statement */ struct pipe *group; #if !BB_MMU @@ -310,11 +338,11 @@ struct command { #if ENABLE_HUSH_FUNCTIONS struct function *child_func; /* This field is used to prevent a bug here: - * while...do f1() {a;}; f1; f1 {b;}; f1; done + * while...do f1() {a;}; f1; f1() {b;}; f1; done * When we execute "f1() {a;}" cmd, we create new function and clear * cmd->group, cmd->group_as_string, cmd->argv[0]. - * when we execute "f1 {b;}", we notice that f1 exists, - * and that it's "parent cmd" struct is still "alive", + * When we execute "f1() {b;}", we notice that f1 exists, + * and that its "parent cmd" struct is still "alive", * we put those fields back into cmd->xxx * (struct function has ->parent_cmd ptr to facilitate that). * 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) n = 0; v = argv; while (*v) { - n = expand_vars_to_list(&output, n, *v, (char)or_mask); + n = expand_vars_to_list(&output, n, *v, (unsigned char)or_mask); v++; } debug_print_list("expand_variables", &output, n); @@ -2456,6 +2484,46 @@ static char **expand_strvec_to_strvec(char **argv) return expand_variables(argv, 0x100); } +static char **expand_strvec_to_strvec_singleword_noglob(char **argv) +{ + return expand_variables(argv, 0x80); +} + +#ifdef CMD_SINGLEWORD_NOGLOB_COND +static char **expand_strvec_to_strvec_singleword_noglob_cond(char **argv) +{ + int n; + char **list; + char **v; + o_string output = NULL_O_STRING; + + n = 0; + v = argv; + while (*v) { + int is_var = is_well_formed_var_name(*v, '='); + /* is_var * 0x80: singleword expansion for vars */ + n = expand_vars_to_list(&output, n, *v, is_var * 0x80); + + /* Subtle! expand_vars_to_list did not glob last word yet. + * It does this only when fed with further data. + * Therefore we set globbing flags AFTER it, not before: + */ + + /* if it is not recognizably abc=...; then: */ + output.o_escape = !is_var; /* protect against globbing for "$var" */ + /* (unquoted $var will temporarily switch it off) */ + output.o_glob = !is_var; /* and indeed do globbing */ + v++; + } + debug_print_list("expand_cond", &output, n); + + /* output.data (malloced in one block) gets returned in "list" */ + list = o_finalize_list(&output, n); + debug_print_strings("expand_cond[1]", list); + return list; +} +#endif + /* Used for expansion of right hand of assignments */ /* NB: should NOT do globbing! "export v=/bin/c*; env | grep ^v=" outputs * "v=/bin/c*" */ @@ -2465,7 +2533,7 @@ static char *expand_string_to_string(const char *str) argv[0] = (char*)str; argv[1] = NULL; - list = expand_variables(argv, 0x80); /* 0x80: make one-element expansion */ + list = expand_variables(argv, 0x80); /* 0x80: singleword expansion */ if (HUSH_DEBUG) if (!list[0] || list[1]) bb_error_msg_and_die("BUG in varexp2"); @@ -2875,8 +2943,8 @@ static void free_pipe(struct pipe *pi) } /* not "else if": on syntax error, we may have both! */ if (command->group) { - debug_printf_clean(" begin group (grp_type:%d)\n", - command->grp_type); + debug_printf_clean(" begin group (cmd_type:%d)\n", + command->cmd_type); free_pipe_list(command->group); debug_printf_clean(" end group\n"); command->group = NULL; @@ -3688,7 +3756,7 @@ static int run_pipe(struct pipe *pi) if (pi->num_cmds != 1 || pi->followup == PIPE_BG - || command->grp_type == GRP_SUBSHELL + || command->cmd_type == CMD_SUBSHELL ) { goto must_fork; } @@ -3700,7 +3768,7 @@ static int run_pipe(struct pipe *pi) if (command->group) { #if ENABLE_HUSH_FUNCTIONS - if (command->grp_type == GRP_FUNCTION) { + if (command->cmd_type == CMD_FUNCDEF) { /* "executing" func () { list } */ struct function *funcp; @@ -3770,7 +3838,18 @@ static int run_pipe(struct pipe *pi) } /* Expand the rest into (possibly) many strings each */ - argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); + if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) { + argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt); + } +#ifdef CMD_SINGLEWORD_NOGLOB_COND + else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB_COND) { + argv_expanded = expand_strvec_to_strvec_singleword_noglob_cond(argv + command->assignment_cnt); + + } +#endif + else { + argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); + } x = find_builtin(argv_expanded[0]); #if ENABLE_HUSH_FUNCTIONS @@ -4012,7 +4091,7 @@ static void debug_print_tree(struct pipe *pi, int lvl) [RES_XXXX ] = "XXXX" , [RES_SNTX ] = "SNTX" , }; - static const char *const GRPTYPE[] = { + static const char *const CMDTYPE[] = { "{}", "()", #if ENABLE_HUSH_FUNCTIONS @@ -4036,7 +4115,7 @@ static void debug_print_tree(struct pipe *pi, int lvl) command->assignment_cnt); if (command->group) { fprintf(stderr, " group %s: (argv=%p)\n", - GRPTYPE[command->grp_type], + CMDTYPE[command->cmd_type], argv); debug_print_tree(command->group, lvl+1); prn++; @@ -4649,7 +4728,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx) debug_printf_parse("pop stack %p\n", ctx->stack); old = ctx->stack; old->command->group = ctx->list_head; - old->command->grp_type = GRP_NORMAL; + old->command->cmd_type = CMD_NORMAL; #if !BB_MMU o_addstr(&old->as_string, ctx->as_string.data); o_free_unsafe(&ctx->as_string); @@ -4745,6 +4824,19 @@ static int done_word(o_string *word, struct parse_context *ctx) (ctx->ctx_res_w == RES_SNTX)); return (ctx->ctx_res_w == RES_SNTX); } +# ifdef CMD_SINGLEWORD_NOGLOB_COND + if (strcmp(word->data, "export") == 0 +# if ENABLE_HUSH_LOCAL + || strcmp(word->data, "local") == 0 +# endif + ) { + command->cmd_type = CMD_SINGLEWORD_NOGLOB_COND; + } else +# endif + if (strcmp(word->data, "[[") == 0) { + command->cmd_type = CMD_SINGLEWORD_NOGLOB; + } + /* fall through */ } #endif if (command->group) { @@ -5189,7 +5281,7 @@ static int parse_group(o_string *dest, struct parse_context *ctx, return 1; } nommu_addchr(&ctx->as_string, ch); - command->grp_type = GRP_FUNCTION; + command->cmd_type = CMD_FUNCDEF; goto skip; } #endif @@ -5209,7 +5301,7 @@ static int parse_group(o_string *dest, struct parse_context *ctx, endch = '}'; if (ch == '(') { endch = ')'; - command->grp_type = GRP_SUBSHELL; + command->cmd_type = CMD_SUBSHELL; } else { /* bash does not allow "{echo...", requires whitespace */ 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 @@ +aa0 bb0 +a=aa0 b=bb0 +aa1 bb1 +a=aa1 b=bb1 +zzz=zzz +zz=* +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 @@ +v="a=aa0 b=bb0" +# only 1st arg should be expanded in multiple words +export $v c=$v +echo $a $b +echo $c + +# only 1st arg should be expanded in multiple words +export `echo a=aa1 b=bb1` c=`echo a=aa1 b=bb1` +echo $a $b +echo $c + +>zz=zz +>zzz=zzz +# only 1st arg should be globbed +export zzz* zz=* +env | grep ^zz | sort +rm -rf zz=zz zzz=zzz + +echo Done -- 2.25.1