From eb0de05d682b28fcf7465358ea31cf8574c1221b Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 9 Apr 2018 17:54:07 +0200 Subject: hush: fix func_return2.tests on NOMMU function old new delta hush_main 1714 1718 +4 Signed-off-by: Denys Vlasenko --- shell/hush.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index d5ea3b21f..3a4b5d894 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9393,6 +9393,13 @@ int hush_main(int argc, char **argv) # if ENABLE_HUSH_LOOPS optarg++; G.depth_of_loop = bb_strtou(optarg, &optarg, 16); +# endif +# if ENABLE_HUSH_FUNCTIONS + /* nommu uses re-exec trick for "... | func | ...", + * should allow "return". + * This accidentally allows returns in subshells. + */ + G_flag_return_in_progress = -1; # endif break; } -- cgit v1.2.3-55-g6feb From 9db344a0f4ed5f6f893940b734215d05e48320e9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 9 Apr 2018 19:05:11 +0200 Subject: hush: fix var_leaks.tests and var_preserved.tests on NOMMU function old new delta remove_nested_vars - 77 +77 run_pipe 1756 1786 +30 pseudo_exec_argv 376 379 +3 leave_var_nest_level 98 32 -66 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 2/1 up/down: 110/-66) Total: 44 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 108 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 59 insertions(+), 49 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 3a4b5d894..885561389 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2266,6 +2266,7 @@ static int set_local_var(char *str, unsigned flags) } /* Not found or shadowed - create new variable struct */ + debug_printf_env("%s: alloc new var '%s'/%u\n", __func__, str, local_lvl); cur = xzalloc(sizeof(*cur)); cur->var_nest_level = local_lvl; cur->next = *cur_pp; @@ -2420,7 +2421,7 @@ static void set_vars_and_save_old(char **strings) * global linked list. */ } - //bb_error_msg("G.var_nest_level:%d", G.var_nest_level); + debug_printf_env("%s: env override '%s'/%u\n", __func__, *s, G.var_nest_level); set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT); } else if (HUSH_DEBUG) { bb_error_msg_and_die("BUG in varexp4"); @@ -7358,6 +7359,58 @@ static void unset_func(const char *name) } # endif +static void remove_nested_vars(void) +{ + struct variable *cur; + struct variable **cur_pp; + + cur_pp = &G.top_var; + while ((cur = *cur_pp) != NULL) { + if (cur->var_nest_level <= G.var_nest_level) { + cur_pp = &cur->next; + continue; + } + /* Unexport */ + if (cur->flg_export) { + debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); + bb_unsetenv(cur->varstr); + } + /* Remove from global list */ + *cur_pp = cur->next; + /* Free */ + if (!cur->max_len) { + debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); + free(cur->varstr); + } + free(cur); + } +} + +static void enter_var_nest_level(void) +{ + G.var_nest_level++; + debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); + + /* Try: f() { echo -n .; f; }; f + * struct variable::var_nest_level is uint16_t, + * thus limiting recursion to < 2^16. + * In any case, with 8 Mbyte stack SEGV happens + * not too long after 2^16 recursions anyway. + */ + if (G.var_nest_level > 0xff00) + bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); +} + +static void leave_var_nest_level(void) +{ + G.var_nest_level--; + debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); + if (HUSH_DEBUG && (int)G.var_nest_level < 0) + bb_error_msg_and_die("BUG: nesting underflow"); + + remove_nested_vars(); +} + # if BB_MMU #define exec_function(to_free, funcp, argv) \ exec_function(funcp, argv) @@ -7392,7 +7445,7 @@ static void exec_function(char ***to_free, /* "we are in a function, ok to use return" */ G_flag_return_in_progress = -1; - G.var_nest_level++; + enter_var_nest_level(); IF_HUSH_LOCAL(G.func_nest_level++;) /* On MMU, funcp->body is always non-NULL */ @@ -7412,53 +7465,6 @@ static void exec_function(char ***to_free, # endif } -static void enter_var_nest_level(void) -{ - G.var_nest_level++; - debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); - - /* Try: f() { echo -n .; f; }; f - * struct variable::var_nest_level is uint16_t, - * thus limiting recursion to < 2^16. - * In any case, with 8 Mbyte stack SEGV happens - * not too long after 2^16 recursions anyway. - */ - if (G.var_nest_level > 0xff00) - bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); -} - -static void leave_var_nest_level(void) -{ - struct variable *cur; - struct variable **cur_pp; - - cur_pp = &G.top_var; - while ((cur = *cur_pp) != NULL) { - if (cur->var_nest_level < G.var_nest_level) { - cur_pp = &cur->next; - continue; - } - /* Unexport */ - if (cur->flg_export) { - debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); - bb_unsetenv(cur->varstr); - } - /* Remove from global list */ - *cur_pp = cur->next; - /* Free */ - if (!cur->max_len) { - debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); - free(cur->varstr); - } - free(cur); - } - - G.var_nest_level--; - debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); - if (HUSH_DEBUG && (int)G.var_nest_level < 0) - bb_error_msg_and_die("BUG: nesting underflow"); -} - static int run_function(const struct function *funcp, char **argv) { int rc; @@ -7648,6 +7654,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, G.shadowed_vars_pp = NULL; /* "don't save, free them instead" */ #else G.shadowed_vars_pp = &nommu_save->old_vars; + G.var_nest_level++; #endif set_vars_and_save_old(new_env); G.shadowed_vars_pp = sv_shadowed; @@ -8522,6 +8529,7 @@ static NOINLINE int run_pipe(struct pipe *pi) while (cmd_no < pi->num_cmds) { struct fd_pair pipefds; #if !BB_MMU + int sv_var_nest_level = G.var_nest_level; volatile nommu_save_t nommu_save; nommu_save.old_vars = NULL; nommu_save.argv = NULL; @@ -8615,6 +8623,8 @@ static NOINLINE int run_pipe(struct pipe *pi) /* Clean up after vforked child */ free(nommu_save.argv); free(nommu_save.argv_from_re_execing); + G.var_nest_level = sv_var_nest_level; + remove_nested_vars(); add_vars(nommu_save.old_vars); #endif free(argv_expanded); -- cgit v1.2.3-55-g6feb From 57b7efb0d5b16fe9d2c19b45fd240fe552bb5c36 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 01:20:26 +0200 Subject: ash: trivial code shrink function old new delta parse_command 1677 1674 -3 Signed-off-by: Denys Vlasenko --- shell/ash.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 24958c0fc..303542197 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -11662,7 +11662,8 @@ simplecmd(void) *vpp = NULL; *rpp = NULL; n = stzalloc(sizeof(struct ncmd)); - n->type = NCMD; + if (NCMD != 0) + n->type = NCMD; n->ncmd.linno = savelinno; n->ncmd.args = args; n->ncmd.assign = vars; -- cgit v1.2.3-55-g6feb From e93031e6dced47e8f5a86408b4aa3f89aef647c7 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 01:23:19 +0200 Subject: ash: if "[[" bashism is not supported, do not handle it anywhere Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 303542197..45c747dbc 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -11611,10 +11611,12 @@ simplecmd(void) case TLP: function_flag = 0; break; +# if BASH_TEST2 case TWORD: if (strcmp("[[", wordtext) == 0) goto do_func; /* fall through */ +# endif default: raise_error_unexpected_syntax(-1); } -- cgit v1.2.3-55-g6feb From 09b7a7ec0ea5ef602ff543dad1a90b6174a4f1c8 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 03:22:10 +0200 Subject: hush: put "current word" structure into parsing context function old new delta done_word 790 767 -23 parse_stream 3018 2919 -99 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-122) Total: -122 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 242 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 120 insertions(+), 122 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 885561389..0c57803f1 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -522,7 +522,6 @@ typedef struct o_string { * possibly empty one: word"", wo''rd etc. */ smallint has_quoted_part; smallint has_empty_slot; - smallint o_assignment; /* 0:maybe, 1:yes, 2:no */ } o_string; enum { EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ @@ -531,13 +530,6 @@ enum { * by prepending \ to *, ?, [, \ */ EXP_FLAG_ESC_GLOB_CHARS = 0x1, }; -enum { - MAYBE_ASSIGNMENT = 0, - DEFINITELY_ASSIGNMENT = 1, - NOT_ASSIGNMENT = 2, - /* Not an assignment, but next word may be: "if v=xyz cmd;" */ - WORD_IS_KEYWORD = 3, -}; /* Used for initialization: o_string foo = NULL_O_STRING; */ #define NULL_O_STRING { NULL } @@ -694,9 +686,11 @@ struct parse_context { struct command *command; /* last redirect in command->redirects list */ struct redir_struct *pending_redirect; + o_string word; #if !BB_MMU o_string as_string; #endif + smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */ #if HAS_KEYWORDS smallint ctx_res_w; smallint ctx_inverted; /* "! cmd | cmd" */ @@ -717,6 +711,13 @@ struct parse_context { struct parse_context *stack; #endif }; +enum { + MAYBE_ASSIGNMENT = 0, + DEFINITELY_ASSIGNMENT = 1, + NOT_ASSIGNMENT = 2, + /* Not an assignment, but next word may be: "if v=xyz cmd;" */ + WORD_IS_KEYWORD = 3, +}; /* On program start, environ points to initial environment. * putenv adds new pointers into it, unsetenv removes them. @@ -3671,6 +3672,8 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) static void initialize_context(struct parse_context *ctx) { memset(ctx, 0, sizeof(*ctx)); + if (MAYBE_ASSIGNMENT != 0) + ctx->is_assignment = MAYBE_ASSIGNMENT; ctx->pipe = ctx->list_head = new_pipe(); /* Create the memory for command, roughly: * ctx->pipe->cmds = new struct command; @@ -3752,7 +3755,7 @@ static const struct reserved_combo* match_reserved_word(o_string *word) } /* Return NULL: not a keyword, else: keyword */ -static const struct reserved_combo* reserved_word(o_string *word, struct parse_context *ctx) +static const struct reserved_combo* reserved_word(struct parse_context *ctx) { # if ENABLE_HUSH_CASE static const struct reserved_combo reserved_match = { @@ -3761,9 +3764,9 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c # endif const struct reserved_combo *r; - if (word->has_quoted_part) + if (ctx->word.has_quoted_part) return 0; - r = match_reserved_word(word); + r = match_reserved_word(&ctx->word); if (!r) return r; /* NULL */ @@ -3790,7 +3793,7 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c initialize_context(ctx); ctx->stack = old; } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { - syntax_error_at(word->data); + syntax_error_at(ctx->word.data); ctx->ctx_res_w = RES_SNTX; return r; } else { @@ -3803,8 +3806,8 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c ctx->ctx_res_w = r->res; ctx->old_flag = r->flag; - word->o_assignment = r->assignment_flag; - debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); + ctx->is_assignment = r->assignment_flag; + debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]); if (ctx->old_flag & FLAG_END) { struct parse_context *old; @@ -3850,12 +3853,12 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c * Normal return is 0. Syntax errors return 1. * Note: on return, word is reset, but not o_free'd! */ -static int done_word(o_string *word, struct parse_context *ctx) +static int done_word(struct parse_context *ctx) { struct command *command = ctx->command; - debug_printf_parse("done_word entered: '%s' %p\n", word->data, command); - if (word->length == 0 && !word->has_quoted_part) { + debug_printf_parse("done_word entered: '%s' %p\n", ctx->word.data, command); + if (ctx->word.length == 0 && !ctx->word.has_quoted_part) { debug_printf_parse("done_word return 0: true null, ignored\n"); return 0; } @@ -3885,7 +3888,7 @@ static int done_word(o_string *word, struct parse_context *ctx) // <pending_redirect->rd_filename = xstrdup(word->data); + ctx->pending_redirect->rd_filename = xstrdup(ctx->word.data); /* Cater for >\file case: * >\a creates file a; >\\a, >"\a", >"\\a" create file \a * Same with heredocs: @@ -3894,17 +3897,17 @@ static int done_word(o_string *word, struct parse_context *ctx) if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) { unbackslash(ctx->pending_redirect->rd_filename); /* Is it <<"HEREDOC"? */ - if (word->has_quoted_part) { + if (ctx->word.has_quoted_part) { ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED; } } - debug_printf_parse("word stored in rd_filename: '%s'\n", word->data); + debug_printf_parse("word stored in rd_filename: '%s'\n", ctx->word.data); ctx->pending_redirect = NULL; } else { #if HAS_KEYWORDS # if ENABLE_HUSH_CASE if (ctx->ctx_dsemicolon - && strcmp(word->data, "esac") != 0 /* not "... pattern) cmd;; esac" */ + && strcmp(ctx->word.data, "esac") != 0 /* not "... pattern) cmd;; esac" */ ) { /* already done when ctx_dsemicolon was set to 1: */ /* ctx->ctx_res_w = RES_MATCH; */ @@ -3921,7 +3924,7 @@ static int done_word(o_string *word, struct parse_context *ctx) # endif ) { const struct reserved_combo *reserved; - reserved = reserved_word(word, ctx); + reserved = reserved_word(ctx); debug_printf_parse("checking for reserved-ness: %d\n", !!reserved); if (reserved) { # if ENABLE_HUSH_LINENO_VAR @@ -3940,7 +3943,7 @@ static int done_word(o_string *word, struct parse_context *ctx) done_pipe(ctx, PIPE_SEQ); } # endif - o_reset_to_empty_unquoted(word); + o_reset_to_empty_unquoted(&ctx->word); debug_printf_parse("done_word return %d\n", (ctx->ctx_res_w == RES_SNTX)); return (ctx->ctx_res_w == RES_SNTX); @@ -3948,7 +3951,7 @@ static int done_word(o_string *word, struct parse_context *ctx) # if defined(CMD_SINGLEWORD_NOGLOB) if (0 # if BASH_TEST2 - || strcmp(word->data, "[[") == 0 + || strcmp(ctx->word.data, "[[") == 0 # endif /* In bash, local/export/readonly are special, args * are assignments and therefore expansion of them @@ -3965,9 +3968,9 @@ static int done_word(o_string *word, struct parse_context *ctx) * $ "export" i=`echo 'aaa bbb'`; echo "$i" * aaa */ - IF_HUSH_LOCAL( || strcmp(word->data, "local") == 0) - IF_HUSH_EXPORT( || strcmp(word->data, "export") == 0) - IF_HUSH_READONLY( || strcmp(word->data, "readonly") == 0) + IF_HUSH_LOCAL( || strcmp(ctx->word.data, "local") == 0) + IF_HUSH_EXPORT( || strcmp(ctx->word.data, "export") == 0) + IF_HUSH_READONLY(|| strcmp(ctx->word.data, "readonly") == 0) ) { command->cmd_type = CMD_SINGLEWORD_NOGLOB; } @@ -3978,7 +3981,7 @@ static int done_word(o_string *word, struct parse_context *ctx) if (command->group) { /* "{ echo foo; } echo bar" - bad */ - syntax_error_at(word->data); + syntax_error_at(ctx->word.data); debug_printf_parse("done_word return 1: syntax error, " "groups and arglists don't mix\n"); return 1; @@ -3986,26 +3989,26 @@ static int done_word(o_string *word, struct parse_context *ctx) /* If this word wasn't an assignment, next ones definitely * can't be assignments. Even if they look like ones. */ - if (word->o_assignment != DEFINITELY_ASSIGNMENT - && word->o_assignment != WORD_IS_KEYWORD + if (ctx->is_assignment != DEFINITELY_ASSIGNMENT + && ctx->is_assignment != WORD_IS_KEYWORD ) { - word->o_assignment = NOT_ASSIGNMENT; + ctx->is_assignment = NOT_ASSIGNMENT; } else { - if (word->o_assignment == DEFINITELY_ASSIGNMENT) { + if (ctx->is_assignment == DEFINITELY_ASSIGNMENT) { command->assignment_cnt++; debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt); } - debug_printf_parse("word->o_assignment was:'%s'\n", assignment_flag[word->o_assignment]); - word->o_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("ctx->is_assignment was:'%s'\n", assignment_flag[ctx->is_assignment]); + ctx->is_assignment = MAYBE_ASSIGNMENT; } - debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); - command->argv = add_string_to_strings(command->argv, xstrdup(word->data)); + debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]); + command->argv = add_string_to_strings(command->argv, xstrdup(ctx->word.data)); debug_print_strings("word appended to argv", command->argv); } #if ENABLE_HUSH_LOOPS if (ctx->ctx_res_w == RES_FOR) { - if (word->has_quoted_part + if (ctx->word.has_quoted_part || !is_well_formed_var_name(command->argv[0], '\0') ) { /* bash says just "not a valid identifier" */ @@ -4026,7 +4029,7 @@ static int done_word(o_string *word, struct parse_context *ctx) } #endif - o_reset_to_empty_unquoted(word); + o_reset_to_empty_unquoted(&ctx->word); debug_printf_parse("done_word return 0\n"); return 0; @@ -4310,14 +4313,10 @@ static struct pipe *parse_stream(char **pstring, int end_trigger); -#if !ENABLE_HUSH_FUNCTIONS -#define parse_group(dest, ctx, input, ch) \ - parse_group(ctx, input, ch) -#endif -static int parse_group(o_string *dest, struct parse_context *ctx, +static int parse_group(struct parse_context *ctx, struct in_str *input, int ch) { - /* dest contains characters seen prior to ( or {. + /* ctx->word contains characters seen prior to ( or {. * Typically it's empty, but for function defs, * it contains function name (without '()'). */ #if BB_MMU @@ -4331,9 +4330,9 @@ static int parse_group(o_string *dest, struct parse_context *ctx, debug_printf_parse("parse_group entered\n"); #if ENABLE_HUSH_FUNCTIONS - if (ch == '(' && !dest->has_quoted_part) { - if (dest->length) - if (done_word(dest, ctx)) + if (ch == '(' && !ctx->word.has_quoted_part) { + if (ctx->word.length) + if (done_word(ctx)) return 1; if (!command->argv) goto skip; /* (... */ @@ -4365,8 +4364,8 @@ static int parse_group(o_string *dest, struct parse_context *ctx, #if 0 /* Prevented by caller */ if (command->argv /* word [word]{... */ - || dest->length /* word{... */ - || dest->has_quoted_part /* ""{... */ + || ctx->word.length /* word{... */ + || ctx->word.has_quoted_part /* ""{... */ ) { syntax_error(NULL); debug_printf_parse("parse_group return 1: " @@ -4972,29 +4971,28 @@ static struct pipe *parse_stream(char **pstring, int end_trigger) { struct parse_context ctx; - o_string dest = NULL_O_STRING; int heredoc_cnt; /* Single-quote triggers a bypass of the main loop until its mate is - * found. When recursing, quote state is passed in via dest->o_expflags. + * found. When recursing, quote state is passed in via ctx.word.o_expflags. */ debug_printf_parse("parse_stream entered, end_trigger='%c'\n", end_trigger ? end_trigger : 'X'); debug_enter(); - /* If very first arg is "" or '', dest.data may end up NULL. - * Preventing this: */ - o_addchr(&dest, '\0'); - dest.length = 0; + initialize_context(&ctx); + + /* If very first arg is "" or '', ctx.word.data may end up NULL. + * Preventing this: + */ + o_addchr(&ctx.word, '\0'); + ctx.word.length = 0; /* We used to separate words on $IFS here. This was wrong. * $IFS is used only for word splitting when $var is expanded, * here we should use blank chars as separators, not $IFS */ - if (MAYBE_ASSIGNMENT != 0) - dest.o_assignment = MAYBE_ASSIGNMENT; - initialize_context(&ctx); heredoc_cnt = 0; while (1) { const char *is_blank; @@ -5006,7 +5004,7 @@ static struct pipe *parse_stream(char **pstring, ch = i_getch(input); debug_printf_parse(": ch=%c (%d) escape=%d\n", - ch, ch, !!(dest.o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); + ch, ch, !!(ctx.word.o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); if (ch == EOF) { struct pipe *pi; @@ -5023,10 +5021,10 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; } - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } - o_free(&dest); + o_free(&ctx.word); done_pipe(&ctx, PIPE_SEQ); pi = ctx.list_head; /* If we got nothing... */ @@ -5066,8 +5064,8 @@ static struct pipe *parse_stream(char **pstring, SPECIAL_VAR_SYMBOL_STR; /* Are { and } special here? */ if (ctx.command->argv /* word [word]{... - non-special */ - || dest.length /* word{... - non-special */ - || dest.has_quoted_part /* ""{... - non-special */ + || ctx.word.length /* word{... - non-special */ + || ctx.word.has_quoted_part /* ""{... - non-special */ || (next != ';' /* }; - special */ && next != ')' /* }) - special */ && next != '(' /* {( - special */ @@ -5084,14 +5082,14 @@ static struct pipe *parse_stream(char **pstring, if (!is_special && !is_blank) { /* ordinary char */ ordinary_char: - o_addQchr(&dest, ch); - if ((dest.o_assignment == MAYBE_ASSIGNMENT - || dest.o_assignment == WORD_IS_KEYWORD) + o_addQchr(&ctx.word, ch); + if ((ctx.is_assignment == MAYBE_ASSIGNMENT + || ctx.is_assignment == WORD_IS_KEYWORD) && ch == '=' - && is_well_formed_var_name(dest.data, '=') + && is_well_formed_var_name(ctx.word.data, '=') ) { - dest.o_assignment = DEFINITELY_ASSIGNMENT; - debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); + ctx.is_assignment = DEFINITELY_ASSIGNMENT; + debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); } continue; } @@ -5113,7 +5111,7 @@ static struct pipe *parse_stream(char **pstring, } /* ch == last eaten whitespace char */ #endif - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } if (ch == '\n') { @@ -5123,7 +5121,7 @@ static struct pipe *parse_stream(char **pstring, * "case ... in word) ..." */ if (IS_NULL_CMD(ctx.command) - && dest.length == 0 && !dest.has_quoted_part + && ctx.word.length == 0 && !ctx.word.has_quoted_part ) { /* This newline can be ignored. But... * Without check #1, interactive shell @@ -5158,8 +5156,8 @@ static struct pipe *parse_stream(char **pstring, } heredoc_cnt = 0; } - dest.o_assignment = MAYBE_ASSIGNMENT; - debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); + ctx.is_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); ch = ';'; /* note: if (is_blank) continue; * will still trigger for us */ @@ -5171,8 +5169,8 @@ static struct pipe *parse_stream(char **pstring, * Pathological example: { ""}; } should exec "}" cmd */ if (ch == '}') { - if (dest.length != 0 /* word} */ - || dest.has_quoted_part /* ""} */ + if (ctx.word.length != 0 /* word} */ + || ctx.word.has_quoted_part /* ""} */ ) { goto ordinary_char; } @@ -5201,7 +5199,7 @@ static struct pipe *parse_stream(char **pstring, #if ENABLE_HUSH_CASE && (ch != ')' || ctx.ctx_res_w != RES_MATCH - || (!dest.has_quoted_part && strcmp(dest.data, "esac") == 0) + || (!ctx.word.has_quoted_part && strcmp(ctx.word.data, "esac") == 0) ) #endif ) { @@ -5218,17 +5216,17 @@ static struct pipe *parse_stream(char **pstring, syntax_error_unterm_str("here document"); goto parse_error; } - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } done_pipe(&ctx, PIPE_SEQ); - dest.o_assignment = MAYBE_ASSIGNMENT; - debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); + ctx.is_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); /* Do we sit outside of any if's, loops or case's? */ if (!HAS_KEYWORDS IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0)) ) { - o_free(&dest); + o_free(&ctx.word); #if !BB_MMU debug_printf_parse("as_string2 '%s'\n", ctx.as_string.data); if (pstring) @@ -5257,8 +5255,8 @@ static struct pipe *parse_stream(char **pstring, * an assignment. a=1 2>z b=2: b=2 is still assignment */ switch (ch) { case '>': - redir_fd = redirect_opt_num(&dest); - if (done_word(&dest, &ctx)) { + redir_fd = redirect_opt_num(&ctx.word); + if (done_word(&ctx)) { goto parse_error; } redir_style = REDIRECT_OVERWRITE; @@ -5279,8 +5277,8 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; continue; /* back to top of while (1) */ case '<': - redir_fd = redirect_opt_num(&dest); - if (done_word(&dest, &ctx)) { + redir_fd = redirect_opt_num(&ctx.word); + if (done_word(&ctx)) { goto parse_error; } redir_style = REDIRECT_INPUT; @@ -5307,7 +5305,7 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; continue; /* back to top of while (1) */ case '#': - if (dest.length == 0 && !dest.has_quoted_part) { + if (ctx.word.length == 0 && !ctx.word.has_quoted_part) { /* skip "#comment" */ /* note: we do not add it to &ctx.as_string */ /* TODO: in bash: @@ -5342,14 +5340,14 @@ static struct pipe *parse_stream(char **pstring, break; } - if (dest.o_assignment == MAYBE_ASSIGNMENT + if (ctx.is_assignment == MAYBE_ASSIGNMENT /* check that we are not in word in "a=1 2>word b=1": */ && !ctx.pending_redirect ) { /* ch is a special char and thus this word * cannot be an assignment */ - dest.o_assignment = NOT_ASSIGNMENT; - debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); + ctx.is_assignment = NOT_ASSIGNMENT; + debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); } /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ @@ -5357,12 +5355,12 @@ static struct pipe *parse_stream(char **pstring, switch (ch) { case SPECIAL_VAR_SYMBOL: /* Convert raw ^C to corresponding special variable reference */ - o_addchr(&dest, SPECIAL_VAR_SYMBOL); - o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS); + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); /* fall through */ case '#': /* non-comment #: "echo a#b" etc */ - o_addchr(&dest, ch); + o_addchr(&ctx.word, ch); break; case '\\': if (next == EOF) { @@ -5371,29 +5369,29 @@ static struct pipe *parse_stream(char **pstring, } ch = i_getch(input); /* note: ch != '\n' (that case does not reach this place) */ - o_addchr(&dest, '\\'); + o_addchr(&ctx.word, '\\'); /*nommu_addchr(&ctx.as_string, '\\'); - already done */ - o_addchr(&dest, ch); + o_addchr(&ctx.word, ch); nommu_addchr(&ctx.as_string, ch); /* Example: echo Hello \2>file * we need to know that word 2 is quoted */ - dest.has_quoted_part = 1; + ctx.word.has_quoted_part = 1; break; case '$': - if (!parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0)) { + if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) { debug_printf_parse("parse_stream parse error: " "parse_dollar returned 0 (error)\n"); goto parse_error; } break; case '\'': - dest.has_quoted_part = 1; + ctx.word.has_quoted_part = 1; if (next == '\'' && !ctx.pending_redirect) { insert_empty_quoted_str_marker: nommu_addchr(&ctx.as_string, next); i_getch(input); /* eat second ' */ - o_addchr(&dest, SPECIAL_VAR_SYMBOL); - o_addchr(&dest, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); } else { while (1) { ch = i_getch(input); @@ -5406,38 +5404,38 @@ static struct pipe *parse_stream(char **pstring, break; if (ch == SPECIAL_VAR_SYMBOL) { /* Convert raw ^C to corresponding special variable reference */ - o_addchr(&dest, SPECIAL_VAR_SYMBOL); - o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS); + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); } - o_addqchr(&dest, ch); + o_addqchr(&ctx.word, ch); } } break; case '"': - dest.has_quoted_part = 1; + ctx.word.has_quoted_part = 1; if (next == '"' && !ctx.pending_redirect) goto insert_empty_quoted_str_marker; - if (dest.o_assignment == NOT_ASSIGNMENT) - dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; - if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1)) + if (ctx.is_assignment == NOT_ASSIGNMENT) + ctx.word.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; + if (!encode_string(&ctx.as_string, &ctx.word, input, '"', /*process_bkslash:*/ 1)) goto parse_error; - dest.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; + ctx.word.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; break; #if ENABLE_HUSH_TICK case '`': { USE_FOR_NOMMU(unsigned pos;) - o_addchr(&dest, SPECIAL_VAR_SYMBOL); - o_addchr(&dest, '`'); - USE_FOR_NOMMU(pos = dest.length;) - if (!add_till_backquote(&dest, input, /*in_dquote:*/ 0)) + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, '`'); + USE_FOR_NOMMU(pos = ctx.word.length;) + if (!add_till_backquote(&ctx.word, input, /*in_dquote:*/ 0)) goto parse_error; # if !BB_MMU - o_addstr(&ctx.as_string, dest.data + pos); + o_addstr(&ctx.as_string, ctx.word.data + pos); o_addchr(&ctx.as_string, '`'); # endif - o_addchr(&dest, SPECIAL_VAR_SYMBOL); - //debug_printf_subst("SUBST RES3 '%s'\n", dest.data + pos); + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + //debug_printf_subst("SUBST RES3 '%s'\n", ctx.word.data + pos); break; } #endif @@ -5445,7 +5443,7 @@ static struct pipe *parse_stream(char **pstring, #if ENABLE_HUSH_CASE case_semi: #endif - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } done_pipe(&ctx, PIPE_SEQ); @@ -5468,11 +5466,11 @@ static struct pipe *parse_stream(char **pstring, new_cmd: /* We just finished a cmd. New one may start * with an assignment */ - dest.o_assignment = MAYBE_ASSIGNMENT; - debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); + ctx.is_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); break; case '&': - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } if (next == '\\') @@ -5486,7 +5484,7 @@ static struct pipe *parse_stream(char **pstring, } goto new_cmd; case '|': - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } #if ENABLE_HUSH_CASE @@ -5511,14 +5509,14 @@ static struct pipe *parse_stream(char **pstring, /* "case... in [(]word)..." - skip '(' */ if (ctx.ctx_res_w == RES_MATCH && ctx.command->argv == NULL /* not (word|(... */ - && dest.length == 0 /* not word(... */ - && dest.has_quoted_part == 0 /* not ""(... */ + && ctx.word.length == 0 /* not word(... */ + && ctx.word.has_quoted_part == 0 /* not ""(... */ ) { continue; } #endif case '{': - if (parse_group(&dest, &ctx, input, ch) != 0) { + if (parse_group(&ctx, input, ch) != 0) { goto parse_error; } goto new_cmd; @@ -5575,7 +5573,7 @@ static struct pipe *parse_stream(char **pstring, IF_HAS_KEYWORDS(pctx = p2;) } while (HAS_KEYWORDS && pctx); - o_free(&dest); + o_free(&ctx.word); #if !BB_MMU if (pstring) *pstring = NULL; -- cgit v1.2.3-55-g6feb From 1c57269b5d1891aef5093e7a5824f1adfbb33847 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 13:09:26 +0200 Subject: hush: simplify \ code, part 1 function old new delta parse_stream 2919 2787 -132 Signed-off-by: Denys Vlasenko --- shell/hush.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 0c57803f1..94ab45053 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4913,6 +4913,9 @@ static int encode_string(o_string *as_string, ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); if (process_bkslash && ch == '\\') { if (next == EOF) { +// TODO: what if in interactive shell a file with +// echo "unterminated string\ +// is sourced? syntax_error("\\"); xfunc_die(); } @@ -5051,12 +5054,14 @@ static struct pipe *parse_stream(char **pstring, next = '\0'; if (ch != '\n') { - next = i_peek(input); - /* Can't use i_peek_and_eat_bkslash_nl(input) here: + /* Do not break this case: * echo '\ * ' - * will break. + * and + * echo z\\ */ + next = (ch == '\'' || ch == '\\') ? i_peek(input) : i_peek_and_eat_bkslash_nl(input); +/// } is_special = "{}<>;&|()#'" /* special outside of "str" */ @@ -5260,8 +5265,6 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; } redir_style = REDIRECT_OVERWRITE; - if (next == '\\') - next = i_peek_and_eat_bkslash_nl(input); if (next == '>') { redir_style = REDIRECT_APPEND; ch = i_getch(input); @@ -5282,8 +5285,6 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; } redir_style = REDIRECT_INPUT; - if (next == '\\') - next = i_peek_and_eat_bkslash_nl(input); if (next == '<') { redir_style = REDIRECT_HEREDOC; heredoc_cnt++; @@ -5327,6 +5328,7 @@ static struct pipe *parse_stream(char **pstring, continue; /* back to top of while (1) */ } break; +#if 0 /* looks like we never reach this code */ case '\\': if (next == '\n') { /* It's "\" */ @@ -5338,6 +5340,7 @@ static struct pipe *parse_stream(char **pstring, continue; /* back to top of while (1) */ } break; +#endif } if (ctx.is_assignment == MAYBE_ASSIGNMENT @@ -5364,6 +5367,7 @@ static struct pipe *parse_stream(char **pstring, break; case '\\': if (next == EOF) { +//TODO: in ". FILE" containing "cmd\" (no newline) bash ignores last "\" syntax_error("\\"); xfunc_die(); } @@ -5473,8 +5477,6 @@ static struct pipe *parse_stream(char **pstring, if (done_word(&ctx)) { goto parse_error; } - if (next == '\\') - next = i_peek_and_eat_bkslash_nl(input); if (next == '&') { ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); @@ -5491,8 +5493,6 @@ static struct pipe *parse_stream(char **pstring, if (ctx.ctx_res_w == RES_MATCH) break; /* we are in case's "word | word)" */ #endif - if (next == '\\') - next = i_peek_and_eat_bkslash_nl(input); if (next == '|') { /* || */ ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); -- cgit v1.2.3-55-g6feb From e8b1bc0481828d84cea2862eab0ad13a73b0caca Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 13:13:10 +0200 Subject: hush: simplify \ code, part 2 function old new delta parse_stream 2787 2780 -7 Signed-off-by: Denys Vlasenko --- shell/hush.c | 60 ++++++++++++++++++++++++++++++------------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 94ab45053..3c6718648 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5364,7 +5364,7 @@ static struct pipe *parse_stream(char **pstring, case '#': /* non-comment #: "echo a#b" etc */ o_addchr(&ctx.word, ch); - break; + continue; /* get next char */ case '\\': if (next == EOF) { //TODO: in ". FILE" containing "cmd\" (no newline) bash ignores last "\" @@ -5380,51 +5380,51 @@ static struct pipe *parse_stream(char **pstring, /* Example: echo Hello \2>file * we need to know that word 2 is quoted */ ctx.word.has_quoted_part = 1; - break; + continue; /* get next char */ case '$': if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) { debug_printf_parse("parse_stream parse error: " "parse_dollar returned 0 (error)\n"); goto parse_error; } - break; + continue; /* get next char */ case '\'': ctx.word.has_quoted_part = 1; - if (next == '\'' && !ctx.pending_redirect) { + if (next == '\'' && !ctx.pending_redirect) + goto insert_empty_quoted_str_marker; + while (1) { + ch = i_getch(input); + if (ch == EOF) { + syntax_error_unterm_ch('\''); + goto parse_error; + } + nommu_addchr(&ctx.as_string, ch); + if (ch == '\'') + break; + if (ch == SPECIAL_VAR_SYMBOL) { + /* Convert raw ^C to corresponding special variable reference */ + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); + } + o_addqchr(&ctx.word, ch); + } + continue; /* get next char */ + case '"': + ctx.word.has_quoted_part = 1; + if (next == '"' && !ctx.pending_redirect) { insert_empty_quoted_str_marker: nommu_addchr(&ctx.as_string, next); i_getch(input); /* eat second ' */ o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); - } else { - while (1) { - ch = i_getch(input); - if (ch == EOF) { - syntax_error_unterm_ch('\''); - goto parse_error; - } - nommu_addchr(&ctx.as_string, ch); - if (ch == '\'') - break; - if (ch == SPECIAL_VAR_SYMBOL) { - /* Convert raw ^C to corresponding special variable reference */ - o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); - o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); - } - o_addqchr(&ctx.word, ch); - } + continue; /* get next char */ } - break; - case '"': - ctx.word.has_quoted_part = 1; - if (next == '"' && !ctx.pending_redirect) - goto insert_empty_quoted_str_marker; if (ctx.is_assignment == NOT_ASSIGNMENT) ctx.word.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; if (!encode_string(&ctx.as_string, &ctx.word, input, '"', /*process_bkslash:*/ 1)) goto parse_error; ctx.word.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; - break; + continue; /* get next char */ #if ENABLE_HUSH_TICK case '`': { USE_FOR_NOMMU(unsigned pos;) @@ -5440,7 +5440,7 @@ static struct pipe *parse_stream(char **pstring, # endif o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); //debug_printf_subst("SUBST RES3 '%s'\n", ctx.word.data + pos); - break; + continue; /* get next char */ } #endif case ';': @@ -5472,7 +5472,7 @@ static struct pipe *parse_stream(char **pstring, * with an assignment */ ctx.is_assignment = MAYBE_ASSIGNMENT; debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); - break; + continue; /* get next char */ case '&': if (done_word(&ctx)) { goto parse_error; @@ -5512,7 +5512,7 @@ static struct pipe *parse_stream(char **pstring, && ctx.word.length == 0 /* not word(... */ && ctx.word.has_quoted_part == 0 /* not ""(... */ ) { - continue; + continue; /* get next char */ } #endif case '{': -- cgit v1.2.3-55-g6feb From 92a930b4e8dce1b8d884a83d7f38bb139ab8317f Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 14:20:48 +0200 Subject: hush: simplify \ code, part 3 function old new delta parse_stream 2780 2762 -18 Signed-off-by: Denys Vlasenko --- shell/hush.c | 100 +++++++++++++++++++++++++++-------------------------------- 1 file changed, 45 insertions(+), 55 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 3c6718648..6cd85cc4d 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5052,19 +5052,43 @@ static struct pipe *parse_stream(char **pstring, } nommu_addchr(&ctx.as_string, ch); + if (ch == '\'') { + ctx.word.has_quoted_part = 1; + next = i_getch(input); + if (next == '\'' && !ctx.pending_redirect) + goto insert_empty_quoted_str_marker; + + ch = next; + while (1) { + if (ch == EOF) { + syntax_error_unterm_ch('\''); + goto parse_error; + } + nommu_addchr(&ctx.as_string, ch); + if (ch == '\'') + break; + if (ch == SPECIAL_VAR_SYMBOL) { + /* Convert raw ^C to corresponding special variable reference */ + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); + } + o_addqchr(&ctx.word, ch); + ch = i_getch(input); + } + continue; /* get next char */ + } + next = '\0'; - if (ch != '\n') { - /* Do not break this case: - * echo '\ - * ' - * and - * echo z\\ + if (ch != '\n' && ch != '\\') { + /* Not on '\': do not break the case of "echo z\\": + * on 2nd '\', i_peek_and_eat_bkslash_nl() + * would stop and try to read next line, + * not letting the command to execute. */ - next = (ch == '\'' || ch == '\\') ? i_peek(input) : i_peek_and_eat_bkslash_nl(input); -/// + next = i_peek_and_eat_bkslash_nl(input); } - is_special = "{}<>;&|()#'" /* special outside of "str" */ + is_special = "{}<>;&|()#" /* special outside of "str" */ "\\$\"" IF_HUSH_TICK("`") /* always special */ SPECIAL_VAR_SYMBOL_STR; /* Are { and } special here? */ @@ -5252,7 +5276,7 @@ static struct pipe *parse_stream(char **pstring, return ctx.list_head; } } - skip_end_trigger: + if (is_blank) continue; @@ -5278,7 +5302,7 @@ static struct pipe *parse_stream(char **pstring, #endif if (parse_redirect(&ctx, redir_fd, redir_style, input)) goto parse_error; - continue; /* back to top of while (1) */ + continue; /* get next char */ case '<': redir_fd = redirect_opt_num(&ctx.word); if (done_word(&ctx)) { @@ -5304,7 +5328,7 @@ static struct pipe *parse_stream(char **pstring, #endif if (parse_redirect(&ctx, redir_fd, redir_style, input)) goto parse_error; - continue; /* back to top of while (1) */ + continue; /* get next char */ case '#': if (ctx.word.length == 0 && !ctx.word.has_quoted_part) { /* skip "#comment" */ @@ -5325,23 +5349,11 @@ static struct pipe *parse_stream(char **pstring, if (ch == EOF) break; } - continue; /* back to top of while (1) */ - } - break; -#if 0 /* looks like we never reach this code */ - case '\\': - if (next == '\n') { - /* It's "\" */ -#if !BB_MMU - /* Remove trailing '\' from ctx.as_string */ - ctx.as_string.data[--ctx.as_string.length] = '\0'; -#endif - ch = i_getch(input); /* eat it */ - continue; /* back to top of while (1) */ + continue; /* get next char */ } break; -#endif } + skip_end_trigger: if (ctx.is_assignment == MAYBE_ASSIGNMENT /* check that we are not in word in "a=1 2>word b=1": */ @@ -5366,20 +5378,19 @@ static struct pipe *parse_stream(char **pstring, o_addchr(&ctx.word, ch); continue; /* get next char */ case '\\': - if (next == EOF) { + /*nommu_addchr(&ctx.as_string, '\\'); - already done */ + o_addchr(&ctx.word, '\\'); + ch = i_getch(input); + if (ch == EOF) { //TODO: in ". FILE" containing "cmd\" (no newline) bash ignores last "\" syntax_error("\\"); xfunc_die(); } - ch = i_getch(input); - /* note: ch != '\n' (that case does not reach this place) */ - o_addchr(&ctx.word, '\\'); - /*nommu_addchr(&ctx.as_string, '\\'); - already done */ - o_addchr(&ctx.word, ch); - nommu_addchr(&ctx.as_string, ch); /* Example: echo Hello \2>file * we need to know that word 2 is quoted */ ctx.word.has_quoted_part = 1; + nommu_addchr(&ctx.as_string, ch); + o_addchr(&ctx.word, ch); continue; /* get next char */ case '$': if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) { @@ -5388,33 +5399,12 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; } continue; /* get next char */ - case '\'': - ctx.word.has_quoted_part = 1; - if (next == '\'' && !ctx.pending_redirect) - goto insert_empty_quoted_str_marker; - while (1) { - ch = i_getch(input); - if (ch == EOF) { - syntax_error_unterm_ch('\''); - goto parse_error; - } - nommu_addchr(&ctx.as_string, ch); - if (ch == '\'') - break; - if (ch == SPECIAL_VAR_SYMBOL) { - /* Convert raw ^C to corresponding special variable reference */ - o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); - o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); - } - o_addqchr(&ctx.word, ch); - } - continue; /* get next char */ case '"': ctx.word.has_quoted_part = 1; if (next == '"' && !ctx.pending_redirect) { + i_getch(input); /* eat second " */ insert_empty_quoted_str_marker: nommu_addchr(&ctx.as_string, next); - i_getch(input); /* eat second ' */ o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); continue; /* get next char */ -- cgit v1.2.3-55-g6feb From bcf56114fa7f037cea579cdc8d17ac1a5dab93a3 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 14:40:23 +0200 Subject: hush: fix eval 'echo ok\' function old new delta parse_stream 2762 2753 -9 Signed-off-by: Denys Vlasenko --- shell/hush.c | 14 +++++++++----- shell/hush_test/hush-parsing/bkslash_eof1.right | 1 + shell/hush_test/hush-parsing/bkslash_eof1.tests | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 shell/hush_test/hush-parsing/bkslash_eof1.right create mode 100755 shell/hush_test/hush-parsing/bkslash_eof1.tests (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 6cd85cc4d..5df8d4744 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5379,15 +5379,19 @@ static struct pipe *parse_stream(char **pstring, continue; /* get next char */ case '\\': /*nommu_addchr(&ctx.as_string, '\\'); - already done */ - o_addchr(&ctx.word, '\\'); ch = i_getch(input); if (ch == EOF) { -//TODO: in ". FILE" containing "cmd\" (no newline) bash ignores last "\" - syntax_error("\\"); - xfunc_die(); + /* Ignore this '\'. Testcase: eval 'echo Ok\' */ +#if !BB_MMU + /* Remove trailing '\' from ctx.as_string */ + ctx.as_string.data[--ctx.as_string.length] = '\0'; +#endif + continue; /* get next char */ } + o_addchr(&ctx.word, '\\'); /* Example: echo Hello \2>file - * we need to know that word 2 is quoted */ + * we need to know that word 2 is quoted + */ ctx.word.has_quoted_part = 1; nommu_addchr(&ctx.as_string, ch); o_addchr(&ctx.word, ch); diff --git a/shell/hush_test/hush-parsing/bkslash_eof1.right b/shell/hush_test/hush-parsing/bkslash_eof1.right new file mode 100644 index 000000000..9766475a4 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof1.right @@ -0,0 +1 @@ +ok diff --git a/shell/hush_test/hush-parsing/bkslash_eof1.tests b/shell/hush_test/hush-parsing/bkslash_eof1.tests new file mode 100755 index 000000000..97629cb13 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof1.tests @@ -0,0 +1 @@ +eval 'echo ok\' -- cgit v1.2.3-55-g6feb From 4709df0f152c477c191f83e18bfecabb2fb2c1f9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 14:49:01 +0200 Subject: hush: fix handling of \ in double-quoted strings function old new delta encode_string 268 250 -18 Signed-off-by: Denys Vlasenko --- shell/hush.c | 11 ++++++----- shell/hush_test/hush-parsing/bkslash_eof2.right | 2 ++ shell/hush_test/hush-parsing/bkslash_eof2.tests | 4 ++++ 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 shell/hush_test/hush-parsing/bkslash_eof2.right create mode 100755 shell/hush_test/hush-parsing/bkslash_eof2.tests (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 5df8d4744..98ba96e0c 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4913,11 +4913,12 @@ static int encode_string(o_string *as_string, ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); if (process_bkslash && ch == '\\') { if (next == EOF) { -// TODO: what if in interactive shell a file with -// echo "unterminated string\ -// is sourced? - syntax_error("\\"); - xfunc_die(); + /* Testcase: in interactive shell a file with + * echo "unterminated string\ + * is sourced. + */ + syntax_error_unterm_ch('"'); + return 0; /* error */ } /* bash: * "The backslash retains its special meaning [in "..."] diff --git a/shell/hush_test/hush-parsing/bkslash_eof2.right b/shell/hush_test/hush-parsing/bkslash_eof2.right new file mode 100644 index 000000000..8be75727f --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof2.right @@ -0,0 +1,2 @@ +hush: syntax error: unterminated " +One:1 diff --git a/shell/hush_test/hush-parsing/bkslash_eof2.tests b/shell/hush_test/hush-parsing/bkslash_eof2.tests new file mode 100755 index 000000000..da1f08db6 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof2.tests @@ -0,0 +1,4 @@ +printf 'echo "unterminated string\\' >test.tmp.sh +. ./test.tmp.sh +echo One:$? +rm -f test.tmp.sh -- cgit v1.2.3-55-g6feb From 3632cb15f16a7596a68dccfd66a2ad9496bf9fd9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 15:25:41 +0200 Subject: shell: add comments about [[, no code changes Signed-off-by: Denys Vlasenko --- coreutils/test.c | 4 ++++ shell/ash.c | 15 ++++++++++++++- shell/hush.c | 12 ++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) (limited to 'shell') diff --git a/coreutils/test.c b/coreutils/test.c index 824ce3b5a..ddb66ddce 100644 --- a/coreutils/test.c +++ b/coreutils/test.c @@ -313,6 +313,9 @@ static const struct operator_t ops_table[] = { { /* "-L" */ FILSYM , UNOP }, { /* "-S" */ FILSOCK , UNOP }, { /* "=" */ STREQ , BINOP }, + /* "==" is bashism, http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html + * lists only "=" as comparison operator. + */ { /* "==" */ STREQ , BINOP }, { /* "!=" */ STRNE , BINOP }, { /* "<" */ STRLT , BINOP }, @@ -357,6 +360,7 @@ static const char ops_texts[] ALIGN1 = "-L" "\0" "-S" "\0" "=" "\0" + /* "==" is bashism */ "==" "\0" "!=" "\0" "<" "\0" diff --git a/shell/ash.c b/shell/ash.c index 45c747dbc..713219b6e 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -197,7 +197,20 @@ #define IF_BASH_PATTERN_SUBST IF_ASH_BASH_COMPAT #define BASH_SUBSTR ENABLE_ASH_BASH_COMPAT #define IF_BASH_SUBSTR IF_ASH_BASH_COMPAT -/* [[ EXPR ]] */ +/* BASH_TEST2: [[ EXPR ]] + * Status of [[ support: + * We replace && and || with -a and -o + * TODO: + * singleword+noglob expansion: + * v='a b'; [[ $v = 'a b' ]]; echo 0:$? + * [[ /bin/* ]]; echo 0:$? + * -a/-o are not AND/OR ops! (they are just strings) + * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) + * = is glob match operator, not equality operator: STR = GLOB + * (in GLOB, quoting is significant on char-by-char basis: a*cd"*") + * == same as = + * add =~ regex match operator: STR =~ REGEX + */ #define BASH_TEST2 (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST) #define BASH_SOURCE ENABLE_ASH_BASH_COMPAT #define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT diff --git a/shell/hush.c b/shell/hush.c index 98ba96e0c..3afb70cb0 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -79,6 +79,18 @@ * Some builtins mandated by standards: * newgrp [GRP]: not a builtin in bash but a suid binary * which spawns a new shell with new group ID + * + * Status of [[ support: + * [[ args ]] are CMD_SINGLEWORD_NOGLOB: + * v='a b'; [[ $v = 'a b' ]]; echo 0:$? + * [[ /bin/* ]]; echo 0:$? + * TODO: + * &&/|| are AND/OR ops, -a/-o are not + * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) + * = is glob match operator, not equality operator: STR = GLOB + * (in GLOB, quoting is significant on char-by-char basis: a*cd"*") + * == same as = + * add =~ regex match operator: STR =~ REGEX */ //config:config HUSH //config: bool "hush (64 kb)" -- cgit v1.2.3-55-g6feb From 89e9d5534d0e8879803ed9dbb25dff3989c31202 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 01:15:33 +0200 Subject: hush: do not drop backslash from eval 'echo ok\' newer bash does not drop it, most other shells too function old new delta unbackslash 39 57 +18 parse_stream 2753 2751 -2 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 18/-2) Total: 16 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 +- shell/ash_test/ash-parsing/bkslash_eof1.right | 1 + shell/ash_test/ash-parsing/bkslash_eof1.tests | 1 + shell/ash_test/ash-redir/redir_exec1.right | 2 +- shell/hush.c | 22 +++++++++++++++++----- shell/hush_test/hush-parsing/bkslash_eof1.right | 2 +- 6 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 shell/ash_test/ash-parsing/bkslash_eof1.right create mode 100755 shell/ash_test/ash-parsing/bkslash_eof1.tests (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 713219b6e..6f8bc9042 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -203,7 +203,7 @@ * TODO: * singleword+noglob expansion: * v='a b'; [[ $v = 'a b' ]]; echo 0:$? - * [[ /bin/* ]]; echo 0:$? + * [[ /bin/n* ]]; echo 0:$? * -a/-o are not AND/OR ops! (they are just strings) * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) * = is glob match operator, not equality operator: STR = GLOB diff --git a/shell/ash_test/ash-parsing/bkslash_eof1.right b/shell/ash_test/ash-parsing/bkslash_eof1.right new file mode 100644 index 000000000..6c6df0b0c --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_eof1.right @@ -0,0 +1 @@ +ok\ diff --git a/shell/ash_test/ash-parsing/bkslash_eof1.tests b/shell/ash_test/ash-parsing/bkslash_eof1.tests new file mode 100755 index 000000000..97629cb13 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_eof1.tests @@ -0,0 +1 @@ +eval 'echo ok\' diff --git a/shell/ash_test/ash-redir/redir_exec1.right b/shell/ash_test/ash-redir/redir_exec1.right index d4393d10c..c98455bf5 100644 --- a/shell/ash_test/ash-redir/redir_exec1.right +++ b/shell/ash_test/ash-redir/redir_exec1.right @@ -1,2 +1,2 @@ -redir_exec1.tests: line 1: can't create /cant/be/created: nonexistent directory +./redir_exec1.tests: line 1: can't create /cant/be/created: nonexistent directory First diff --git a/shell/hush.c b/shell/hush.c index 3afb70cb0..523fc1a31 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -83,7 +83,7 @@ * Status of [[ support: * [[ args ]] are CMD_SINGLEWORD_NOGLOB: * v='a b'; [[ $v = 'a b' ]]; echo 0:$? - * [[ /bin/* ]]; echo 0:$? + * [[ /bin/n* ]]; echo 0:$? * TODO: * &&/|| are AND/OR ops, -a/-o are not * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) @@ -1426,8 +1426,19 @@ static char *unbackslash(char *src) { char *dst = src = strchrnul(src, '\\'); while (1) { - if (*src == '\\') + if (*src == '\\') { src++; + if (*src != '\0') { + /* \x -> x */ + *dst++ = *src++; + continue; + } + /* else: "\". Do not delete this backslash. + * Testcase: eval 'echo ok\' + */ + *dst++ = '\\'; + /* fallthrough */ + } if ((*dst++ = *src++) == '\0') break; } @@ -5392,16 +5403,17 @@ static struct pipe *parse_stream(char **pstring, continue; /* get next char */ case '\\': /*nommu_addchr(&ctx.as_string, '\\'); - already done */ + o_addchr(&ctx.word, '\\'); ch = i_getch(input); if (ch == EOF) { - /* Ignore this '\'. Testcase: eval 'echo Ok\' */ -#if !BB_MMU + /* Testcase: eval 'echo Ok\' */ + +#if 0 /* bash-4.3.43 was removing backslash, but 4.4.19 retains it, most other shells too */ /* Remove trailing '\' from ctx.as_string */ ctx.as_string.data[--ctx.as_string.length] = '\0'; #endif continue; /* get next char */ } - o_addchr(&ctx.word, '\\'); /* Example: echo Hello \2>file * we need to know that word 2 is quoted */ diff --git a/shell/hush_test/hush-parsing/bkslash_eof1.right b/shell/hush_test/hush-parsing/bkslash_eof1.right index 9766475a4..6c6df0b0c 100644 --- a/shell/hush_test/hush-parsing/bkslash_eof1.right +++ b/shell/hush_test/hush-parsing/bkslash_eof1.right @@ -1 +1 @@ -ok +ok\ -- cgit v1.2.3-55-g6feb From 0403bedccc17c8ea3059523e32ea615e5df4bc26 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 01:33:54 +0200 Subject: hush: optimize parse_stream() Since we check for '\' anyway when we determine whether we can look ahead, we can just check for *and handle* it there. function old new delta parse_stream 2751 2740 -11 Signed-off-by: Denys Vlasenko --- shell/hush.c | 59 +++++++++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 28 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 523fc1a31..735fbef27 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5076,6 +5076,14 @@ static struct pipe *parse_stream(char **pstring, } nommu_addchr(&ctx.as_string, ch); + /* Handle "'" and "\" first, as they won't play nice with + * i_peek_and_eat_bkslash_nl() anyway: + * echo z\\ + * and + * echo '\ + * ' + * would break. + */ if (ch == '\'') { ctx.word.has_quoted_part = 1; next = i_getch(input); @@ -5101,19 +5109,34 @@ static struct pipe *parse_stream(char **pstring, } continue; /* get next char */ } + if (ch == '\\') { + /*nommu_addchr(&ctx.as_string, '\\'); - already done */ + o_addchr(&ctx.word, '\\'); + ch = i_getch(input); + if (ch == EOF) { + /* Testcase: eval 'echo Ok\' */ - next = '\0'; - if (ch != '\n' && ch != '\\') { - /* Not on '\': do not break the case of "echo z\\": - * on 2nd '\', i_peek_and_eat_bkslash_nl() - * would stop and try to read next line, - * not letting the command to execute. +#if 0 /* bash-4.3.43 was removing backslash, but 4.4.19 retains it, most other shells too */ + /* Remove trailing '\' from ctx.as_string */ + ctx.as_string.data[--ctx.as_string.length] = '\0'; +#endif + continue; /* get next char */ + } + /* Example: echo Hello \2>file + * we need to know that word 2 is quoted */ - next = i_peek_and_eat_bkslash_nl(input); + ctx.word.has_quoted_part = 1; + nommu_addchr(&ctx.as_string, ch); + o_addchr(&ctx.word, ch); + continue; /* get next char */ } + next = '\0'; + if (ch != '\n') + next = i_peek_and_eat_bkslash_nl(input); + is_special = "{}<>;&|()#" /* special outside of "str" */ - "\\$\"" IF_HUSH_TICK("`") /* always special */ + "$\"" IF_HUSH_TICK("`") /* always special */ SPECIAL_VAR_SYMBOL_STR; /* Are { and } special here? */ if (ctx.command->argv /* word [word]{... - non-special */ @@ -5401,26 +5424,6 @@ static struct pipe *parse_stream(char **pstring, /* non-comment #: "echo a#b" etc */ o_addchr(&ctx.word, ch); continue; /* get next char */ - case '\\': - /*nommu_addchr(&ctx.as_string, '\\'); - already done */ - o_addchr(&ctx.word, '\\'); - ch = i_getch(input); - if (ch == EOF) { - /* Testcase: eval 'echo Ok\' */ - -#if 0 /* bash-4.3.43 was removing backslash, but 4.4.19 retains it, most other shells too */ - /* Remove trailing '\' from ctx.as_string */ - ctx.as_string.data[--ctx.as_string.length] = '\0'; -#endif - continue; /* get next char */ - } - /* Example: echo Hello \2>file - * we need to know that word 2 is quoted - */ - ctx.word.has_quoted_part = 1; - nommu_addchr(&ctx.as_string, ch); - o_addchr(&ctx.word, ch); - continue; /* get next char */ case '$': if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) { debug_printf_parse("parse_stream parse error: " -- cgit v1.2.3-55-g6feb From 680c3016a2dddc3edb4d79868a728e899638e2c4 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 12:39:18 +0200 Subject: ash: parser: Allow newlines within parameter substitution Upstream commit: Date: Thu, 22 Mar 2018 21:41:24 +0800 parser: Allow newlines within parameter substitution On Fri, Mar 16, 2018 at 11:27:22AM +0800, Herbert Xu wrote: > On Thu, Mar 15, 2018 at 10:49:15PM +0100, Harald van Dijk wrote: > > > > Okay, it can be trivially modified to something that does work in other > > shells (even if it were actually executed), but gets rejected at parse time > > by dash: > > > > if false; then > > : ${$+ > > } > > fi > > That's just a bug in dash's parser with ${} in general, because > it bombs out without the if clause too: > > : ${$+ > } This patch fixes the parsing of newlines with parameter substitution. Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 5 ++++- shell/ash_test/ash-vars/param_expand_alt2.right | 4 ++++ shell/ash_test/ash-vars/param_expand_alt2.tests | 7 +++++++ shell/hush_test/hush-vars/param_expand_alt2.right | 4 ++++ shell/hush_test/hush-vars/param_expand_alt2.tests | 7 +++++++ 5 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 shell/ash_test/ash-vars/param_expand_alt2.right create mode 100755 shell/ash_test/ash-vars/param_expand_alt2.tests create mode 100644 shell/hush_test/hush-vars/param_expand_alt2.right create mode 100755 shell/hush_test/hush-vars/param_expand_alt2.tests (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 6f8bc9042..40ca82d0b 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -11999,8 +11999,11 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ switch (SIT(c, synstack->syntax)) { case CNL: /* '\n' */ - if (synstack->syntax == BASESYNTAX) + if (synstack->syntax == BASESYNTAX + && !synstack->varnest + ) { goto endword; /* exit outer loop */ + } USTPUTC(c, out); nlprompt(); c = pgetc(); diff --git a/shell/ash_test/ash-vars/param_expand_alt2.right b/shell/ash_test/ash-vars/param_expand_alt2.right new file mode 100644 index 000000000..fef5889ca --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_alt2.right @@ -0,0 +1,4 @@ +Unquoted: H H +Quoted: H +H +Ok:0 diff --git a/shell/ash_test/ash-vars/param_expand_alt2.tests b/shell/ash_test/ash-vars/param_expand_alt2.tests new file mode 100755 index 000000000..d8abf4c3b --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_alt2.tests @@ -0,0 +1,7 @@ +echo Unquoted: H${$+ +}H + +echo Quoted: "H${$+ +}H" + +echo Ok:$? diff --git a/shell/hush_test/hush-vars/param_expand_alt2.right b/shell/hush_test/hush-vars/param_expand_alt2.right new file mode 100644 index 000000000..fef5889ca --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_alt2.right @@ -0,0 +1,4 @@ +Unquoted: H H +Quoted: H +H +Ok:0 diff --git a/shell/hush_test/hush-vars/param_expand_alt2.tests b/shell/hush_test/hush-vars/param_expand_alt2.tests new file mode 100755 index 000000000..d8abf4c3b --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_alt2.tests @@ -0,0 +1,7 @@ +echo Unquoted: H${$+ +}H + +echo Quoted: "H${$+ +}H" + +echo Ok:$? -- cgit v1.2.3-55-g6feb From 34179956f96370f5a53e73073d984d62135cd037 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 13:47:59 +0200 Subject: hush: fix "$v" expansion in case patterns when v='[a]' function old new delta run_list 1053 1063 +10 setup_redirects 311 320 +9 encode_then_expand_string 135 142 +7 run_pipe 1784 1789 +5 expand_assignments 81 86 +5 expand_string_to_string 124 125 +1 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 6/0 up/down: 37/0) Total: 37 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-quoting/case_glob1.right | 1 + shell/ash_test/ash-quoting/case_glob1.tests | 8 +++++ shell/hush.c | 50 ++++++++++++++++++--------- shell/hush_test/hush-quoting/case_glob1.right | 1 + shell/hush_test/hush-quoting/case_glob1.tests | 8 +++++ 5 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 shell/ash_test/ash-quoting/case_glob1.right create mode 100755 shell/ash_test/ash-quoting/case_glob1.tests create mode 100644 shell/hush_test/hush-quoting/case_glob1.right create mode 100755 shell/hush_test/hush-quoting/case_glob1.tests (limited to 'shell') diff --git a/shell/ash_test/ash-quoting/case_glob1.right b/shell/ash_test/ash-quoting/case_glob1.right new file mode 100644 index 000000000..b4785957b --- /dev/null +++ b/shell/ash_test/ash-quoting/case_glob1.right @@ -0,0 +1 @@ +s diff --git a/shell/ash_test/ash-quoting/case_glob1.tests b/shell/ash_test/ash-quoting/case_glob1.tests new file mode 100755 index 000000000..8dbbc0fb1 --- /dev/null +++ b/shell/ash_test/ash-quoting/case_glob1.tests @@ -0,0 +1,8 @@ +g='[3](a)(b)(c)' +s='[3](a)(b)(c)' +case $g in +"$s") echo s + ;; +*) echo "*" + ;; +esac diff --git a/shell/hush.c b/shell/hush.c index 735fbef27..248364be2 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5610,11 +5610,10 @@ static struct pipe *parse_stream(char **pstring, /* Expansion can recurse, need forward decls: */ #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE -/* only ${var/pattern/repl} (its pattern part) needs additional mode */ -#define expand_string_to_string(str, do_unbackslash) \ +#define expand_string_to_string(str, EXP_flags, do_unbackslash) \ expand_string_to_string(str) #endif -static char *expand_string_to_string(const char *str, int do_unbackslash); +static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash); #if ENABLE_HUSH_TICK static int process_command_subs(o_string *dest, const char *s); #endif @@ -5760,7 +5759,10 @@ static char *encode_then_expand_string(const char *str, int process_bkslash, int encode_string(NULL, &dest, &input, EOF, process_bkslash); //TODO: error check (encode_string returns 0 on error)? //bb_error_msg("'%s' -> '%s'", str, dest.data); - exp_str = expand_string_to_string(dest.data, /*unbackslash:*/ do_unbackslash); + exp_str = expand_string_to_string(dest.data, + do_unbackslash ? EXP_FLAG_ESC_GLOB_CHARS : 0, + do_unbackslash + ); //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); o_free_unsafe(&dest); return exp_str; @@ -6393,10 +6395,11 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv) * NB: should NOT do globbing! * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" */ -static char *expand_string_to_string(const char *str, int do_unbackslash) +static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash) { #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE const int do_unbackslash = 1; + const int EXP_flags = EXP_FLAG_ESC_GLOB_CHARS; #endif char *argv[2], **list; @@ -6413,10 +6416,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash) argv[0] = (char*)str; argv[1] = NULL; - list = expand_variables(argv, do_unbackslash - ? EXP_FLAG_ESC_GLOB_CHARS | EXP_FLAG_SINGLEWORD - : EXP_FLAG_SINGLEWORD - ); + list = expand_variables(argv, EXP_flags | EXP_FLAG_SINGLEWORD); if (HUSH_DEBUG) if (!list[0] || list[1]) bb_error_msg_and_die("BUG in varexp2"); @@ -6460,7 +6460,13 @@ static char **expand_assignments(char **argv, int count) G.expanded_assignments = p = NULL; /* Expand assignments into one string each */ for (i = 0; i < count; i++) { - G.expanded_assignments = p = add_string_to_strings(p, expand_string_to_string(argv[i], /*unbackslash:*/ 1)); + p = add_string_to_strings(p, + expand_string_to_string(argv[i], + EXP_FLAG_ESC_GLOB_CHARS, + /*unbackslash:*/ 1 + ) + ); + G.expanded_assignments = p; } G.expanded_assignments = NULL; return p; @@ -7172,7 +7178,8 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) continue; } mode = redir_table[redir->rd_type].mode; - p = expand_string_to_string(redir->rd_filename, /*unbackslash:*/ 1); + p = expand_string_to_string(redir->rd_filename, + EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1); newfd = open_or_warn(p, mode); free(p); if (newfd < 0) { @@ -8370,7 +8377,10 @@ static NOINLINE int run_pipe(struct pipe *pi) bb_putchar_stderr('+'); i = 0; while (i < command->assignment_cnt) { - char *p = expand_string_to_string(argv[i], /*unbackslash:*/ 1); + char *p = expand_string_to_string(argv[i], + EXP_FLAG_ESC_GLOB_CHARS, + /*unbackslash:*/ 1 + ); if (G_x_mode) fprintf(stderr, " %s", p); debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); @@ -8865,7 +8875,8 @@ static int run_list(struct pipe *pi) #if ENABLE_HUSH_CASE if (rword == RES_CASE) { debug_printf_exec("CASE cond_code:%d\n", cond_code); - case_word = expand_string_to_string(pi->cmds->argv[0], 1); + case_word = expand_string_to_string(pi->cmds->argv[0], + EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1); debug_printf_exec("CASE word1:'%s'\n", case_word); //unbackslash(case_word); //debug_printf_exec("CASE word2:'%s'\n", case_word); @@ -8880,12 +8891,19 @@ static int run_list(struct pipe *pi) /* all prev words didn't match, does this one match? */ argv = pi->cmds->argv; while (*argv) { - char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 0); + char *pattern; + debug_printf_exec("expand_string_to_string('%s')\n", *argv); + pattern = expand_string_to_string(*argv, + EXP_FLAG_ESC_GLOB_CHARS, + /*unbackslash:*/ 0 + ); /* TODO: which FNM_xxx flags to use? */ cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); - debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", pattern, case_word, cond_code); + debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", + pattern, case_word, cond_code); free(pattern); - if (cond_code == 0) { /* match! we will execute this branch */ + if (cond_code == 0) { + /* match! we will execute this branch */ free(case_word); case_word = NULL; /* make future "word)" stop */ break; diff --git a/shell/hush_test/hush-quoting/case_glob1.right b/shell/hush_test/hush-quoting/case_glob1.right new file mode 100644 index 000000000..b4785957b --- /dev/null +++ b/shell/hush_test/hush-quoting/case_glob1.right @@ -0,0 +1 @@ +s diff --git a/shell/hush_test/hush-quoting/case_glob1.tests b/shell/hush_test/hush-quoting/case_glob1.tests new file mode 100755 index 000000000..8dbbc0fb1 --- /dev/null +++ b/shell/hush_test/hush-quoting/case_glob1.tests @@ -0,0 +1,8 @@ +g='[3](a)(b)(c)' +s='[3](a)(b)(c)' +case $g in +"$s") echo s + ;; +*) echo "*" + ;; +esac -- cgit v1.2.3-55-g6feb From 9678636911b39a7adf9b51d5b625cf4dc7e4ac81 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 16:02:58 +0200 Subject: hush: IFS fixes $ IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done |x| $ IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done |x| || function old new delta run_pipe 1789 1870 +81 expand_on_ifs 310 361 +51 pseudo_exec_argv 588 591 +3 builtin_local 50 53 +3 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 4/0 up/down: 138/0) Total: 138 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-vars/var_wordsplit_ifs4.right | 5 +++ shell/ash_test/ash-vars/var_wordsplit_ifs4.tests | 4 +++ shell/hush.c | 38 ++++++++++++++++++++-- shell/hush_test/hush-vars/var_wordsplit_ifs4.right | 5 +++ shell/hush_test/hush-vars/var_wordsplit_ifs4.tests | 4 +++ 5 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 shell/ash_test/ash-vars/var_wordsplit_ifs4.right create mode 100755 shell/ash_test/ash-vars/var_wordsplit_ifs4.tests create mode 100644 shell/hush_test/hush-vars/var_wordsplit_ifs4.right create mode 100755 shell/hush_test/hush-vars/var_wordsplit_ifs4.tests (limited to 'shell') diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs4.right b/shell/ash_test/ash-vars/var_wordsplit_ifs4.right new file mode 100644 index 000000000..c27284c31 --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs4.right @@ -0,0 +1,5 @@ +|x| +Ok1:0 +|x| +|| +Ok2:0 diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests b/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests new file mode 100755 index 000000000..638bfbb28 --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests @@ -0,0 +1,4 @@ +IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done +echo Ok1:$? +IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done +echo Ok2:$? diff --git a/shell/hush.c b/shell/hush.c index 248364be2..8e95a26a6 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -930,6 +930,7 @@ struct globals { unsigned getopt_count; #endif const char *ifs; + char *ifs_whitespace; /* = G.ifs or malloced */ const char *cwd; struct variable *top_var; char **expanded_assignments; @@ -5696,10 +5697,20 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha /* We know str here points to at least one IFS char */ last_is_ifs = 1; - str += strspn(str, G.ifs); /* skip IFS chars */ + str += strspn(str, G.ifs_whitespace); /* skip IFS whitespace chars */ if (!*str) /* EOL - do not finalize word */ break; + if (G.ifs_whitespace != G.ifs /* usually false ($IFS is usually all whitespace), */ + && strchr(G.ifs, *str) /* the second check would fail */ + ) { + /* This is a non-whitespace $IFS char */ + /* Skip it and IFS whitespace chars, start new word */ + str++; + str += strspn(str, G.ifs_whitespace); + goto new_word; + } + /* Start new word... but not always! */ /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */ if (output->has_quoted_part @@ -5710,6 +5721,7 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha */ || (n > 0 && output->data[output->length - 1]) ) { + new_word: o_addchr(output, '\0'); debug_print_list("expand_on_ifs", output, n); n = o_save_ptr(output, n); @@ -8283,9 +8295,31 @@ static NOINLINE int run_pipe(struct pipe *pi) /* Testcase: set -- q w e; (IFS='' echo "$*"; IFS=''; echo "$*"); echo "$*" * Result should be 3 lines: q w e, qwe, q w e */ + if (G.ifs_whitespace != G.ifs) + free(G.ifs_whitespace); G.ifs = get_local_var_value("IFS"); - if (!G.ifs) + if (G.ifs) { + char *p; + G.ifs_whitespace = (char*)G.ifs; + p = skip_whitespace(G.ifs); + if (*p) { + /* Not all $IFS is whitespace */ + char *d; + int len = p - G.ifs; + p = skip_non_whitespace(p); + G.ifs_whitespace = xmalloc(len + strlen(p) + 1); /* can overestimate */ + d = mempcpy(G.ifs_whitespace, G.ifs, len); + while (*p) { + if (isspace(*p)) + *d++ = *p; + p++; + } + *d = '\0'; + } + } else { G.ifs = defifs; + G.ifs_whitespace = (char*)G.ifs; + } IF_HUSH_JOB(pi->pgrp = -1;) pi->stopped_cmds = 0; diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs4.right b/shell/hush_test/hush-vars/var_wordsplit_ifs4.right new file mode 100644 index 000000000..c27284c31 --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs4.right @@ -0,0 +1,5 @@ +|x| +Ok1:0 +|x| +|| +Ok2:0 diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests b/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests new file mode 100755 index 000000000..638bfbb28 --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests @@ -0,0 +1,4 @@ +IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done +echo Ok1:$? +IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done +echo Ok2:$? -- cgit v1.2.3-55-g6feb From 44257ad5d0790a846423c9ef69a50049366b4578 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 17:18:34 +0200 Subject: hush: fix IFS handling in read $ echo "X:Y:" | (IFS=": " read x y; echo "|$x|$y|") |X|Y| $ echo "X:Y : " | (IFS=": " read x y; echo "|$x|$y|") |X|Y| function old new delta shell_builtin_read 1320 1426 +106 Signed-off-by: Denys Vlasenko --- shell/hush_test/hush-read/read_ifs2.right | 9 ++++++++ shell/hush_test/hush-read/read_ifs2.tests | 9 ++++++++ shell/shell_common.c | 37 ++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 shell/hush_test/hush-read/read_ifs2.right create mode 100755 shell/hush_test/hush-read/read_ifs2.tests (limited to 'shell') diff --git a/shell/hush_test/hush-read/read_ifs2.right b/shell/hush_test/hush-read/read_ifs2.right new file mode 100644 index 000000000..797137dae --- /dev/null +++ b/shell/hush_test/hush-read/read_ifs2.right @@ -0,0 +1,9 @@ +|X|Y:Z:| +|X|Y:Z| +|X|Y| +|X|Y| +|X|| +|X|| +||| +Whitespace should be trimmed too: +|X|Y| diff --git a/shell/hush_test/hush-read/read_ifs2.tests b/shell/hush_test/hush-read/read_ifs2.tests new file mode 100755 index 000000000..f01a68978 --- /dev/null +++ b/shell/hush_test/hush-read/read_ifs2.tests @@ -0,0 +1,9 @@ +echo "X:Y:Z:" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:Y:Z" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:Y:" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:Y" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:" | (IFS=": " read x y; echo "|$x|$y|") +echo "X" | (IFS=": " read x y; echo "|$x|$y|") +echo "" | (IFS=": " read x y; echo "|$x|$y|") +echo Whitespace should be trimmed too: +echo "X:Y : " | (IFS=": " read x y; echo "|$x|$y|") diff --git a/shell/shell_common.c b/shell/shell_common.c index 9e58ee4fe..0a07296f3 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -274,9 +274,44 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), if (argv[0]) { /* Remove trailing space $IFS chars */ - while (--bufpos >= 0 && isspace(buffer[bufpos]) && strchr(ifs, buffer[bufpos]) != NULL) + while (--bufpos >= 0 + && isspace(buffer[bufpos]) + && strchr(ifs, buffer[bufpos]) != NULL + ) { continue; + } buffer[bufpos + 1] = '\0'; + + /* Last variable takes the entire remainder with delimiters + * (sans trailing whitespace $IFS), + * but ***only "if there are fewer vars than fields"(c)***! + * The "X:Y:" case below: there are two fields, + * and therefore last delimiter (:) is eaten: + * IFS=": " + * echo "X:Y:Z:" | (read x y; echo "|$x|$y|") # |X|Y:Z:| + * echo "X:Y:Z" | (read x y; echo "|$x|$y|") # |X|Y:Z| + * echo "X:Y:" | (read x y; echo "|$x|$y|") # |X|Y|, not |X|Y:| + * echo "X:Y : " | (read x y; echo "|$x|$y|") # |X|Y| + */ + if (bufpos >= 0 + && strchr(ifs, buffer[bufpos]) != NULL + ) { + /* There _is_ a non-whitespace IFS char */ + /* Skip whitespace IFS char before it */ + while (--bufpos >= 0 + && isspace(buffer[bufpos]) + && strchr(ifs, buffer[bufpos]) != NULL + ) { + continue; + } + /* Are there $IFS chars? */ + if (strcspn(buffer, ifs) >= ++bufpos) { + /* No: last var takes one field, not more */ + /* So, drop trailing IFS delims */ + buffer[bufpos] = '\0'; + } + } + /* Use the remainder as a value for the next variable */ setvar(*argv, buffer); /* Set the rest to "" */ -- cgit v1.2.3-55-g6feb From f693b606b732437bb1265c2ec883d93127f3f38e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 20:00:43 +0200 Subject: hush: fix recent breakage from parse_stream() changes function old new delta parse_stream 3808 3821 +13 Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-parsing/bkslash_newline3.right | 1 + shell/ash_test/ash-parsing/bkslash_newline3.tests | 4 ++ shell/hush.c | 44 +++++++++++----------- .../hush_test/hush-parsing/bkslash_newline3.right | 1 + .../hush_test/hush-parsing/bkslash_newline3.tests | 4 ++ 5 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 shell/ash_test/ash-parsing/bkslash_newline3.right create mode 100755 shell/ash_test/ash-parsing/bkslash_newline3.tests create mode 100644 shell/hush_test/hush-parsing/bkslash_newline3.right create mode 100755 shell/hush_test/hush-parsing/bkslash_newline3.tests (limited to 'shell') diff --git a/shell/ash_test/ash-parsing/bkslash_newline3.right b/shell/ash_test/ash-parsing/bkslash_newline3.right new file mode 100644 index 000000000..e635074e5 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_newline3.right @@ -0,0 +1 @@ +a:[a] diff --git a/shell/ash_test/ash-parsing/bkslash_newline3.tests b/shell/ash_test/ash-parsing/bkslash_newline3.tests new file mode 100755 index 000000000..2accd4395 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_newline3.tests @@ -0,0 +1,4 @@ +for s in \ +a; do + echo "a:[$s]" +done diff --git a/shell/hush.c b/shell/hush.c index 8e95a26a6..c77700175 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5075,7 +5075,6 @@ static struct pipe *parse_stream(char **pstring, debug_printf_parse("parse_stream return %p\n", pi); return pi; } - nommu_addchr(&ctx.as_string, ch); /* Handle "'" and "\" first, as they won't play nice with * i_peek_and_eat_bkslash_nl() anyway: @@ -5085,6 +5084,28 @@ static struct pipe *parse_stream(char **pstring, * ' * would break. */ + if (ch == '\\') { + ch = i_getch(input); + if (ch == '\n') + continue; /* drop \, get next char */ + nommu_addchr(&ctx.as_string, '\\'); + o_addchr(&ctx.word, '\\'); + if (ch == EOF) { + /* Testcase: eval 'echo Ok\' */ + /* bash-4.3.43 was removing backslash, + * but 4.4.19 retains it, most other shells too + */ + continue; /* get next char */ + } + /* Example: echo Hello \2>file + * we need to know that word 2 is quoted + */ + ctx.word.has_quoted_part = 1; + nommu_addchr(&ctx.as_string, ch); + o_addchr(&ctx.word, ch); + continue; /* get next char */ + } + nommu_addchr(&ctx.as_string, ch); if (ch == '\'') { ctx.word.has_quoted_part = 1; next = i_getch(input); @@ -5110,27 +5131,6 @@ static struct pipe *parse_stream(char **pstring, } continue; /* get next char */ } - if (ch == '\\') { - /*nommu_addchr(&ctx.as_string, '\\'); - already done */ - o_addchr(&ctx.word, '\\'); - ch = i_getch(input); - if (ch == EOF) { - /* Testcase: eval 'echo Ok\' */ - -#if 0 /* bash-4.3.43 was removing backslash, but 4.4.19 retains it, most other shells too */ - /* Remove trailing '\' from ctx.as_string */ - ctx.as_string.data[--ctx.as_string.length] = '\0'; -#endif - continue; /* get next char */ - } - /* Example: echo Hello \2>file - * we need to know that word 2 is quoted - */ - ctx.word.has_quoted_part = 1; - nommu_addchr(&ctx.as_string, ch); - o_addchr(&ctx.word, ch); - continue; /* get next char */ - } next = '\0'; if (ch != '\n') diff --git a/shell/hush_test/hush-parsing/bkslash_newline3.right b/shell/hush_test/hush-parsing/bkslash_newline3.right new file mode 100644 index 000000000..e635074e5 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_newline3.right @@ -0,0 +1 @@ +a:[a] diff --git a/shell/hush_test/hush-parsing/bkslash_newline3.tests b/shell/hush_test/hush-parsing/bkslash_newline3.tests new file mode 100755 index 000000000..2accd4395 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_newline3.tests @@ -0,0 +1,4 @@ +for s in \ +a; do + echo "a:[$s]" +done -- cgit v1.2.3-55-g6feb From 46158dc833ca48a63a2547bb26eee133aa19dccf Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 20:24:58 +0200 Subject: shell: add 6856 $IFS tests to testsuites Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-z_slow/many_ifs.right | 1 + shell/ash_test/ash-z_slow/many_ifs.tests | 257 +++++++++++++++++++++++++++++ shell/hush_test/hush-z_slow/many_ifs.right | 1 + shell/hush_test/hush-z_slow/many_ifs.tests | 257 +++++++++++++++++++++++++++++ 4 files changed, 516 insertions(+) create mode 100644 shell/ash_test/ash-z_slow/many_ifs.right create mode 100755 shell/ash_test/ash-z_slow/many_ifs.tests create mode 100644 shell/hush_test/hush-z_slow/many_ifs.right create mode 100755 shell/hush_test/hush-z_slow/many_ifs.tests (limited to 'shell') diff --git a/shell/ash_test/ash-z_slow/many_ifs.right b/shell/ash_test/ash-z_slow/many_ifs.right new file mode 100644 index 000000000..f3bdccc6c --- /dev/null +++ b/shell/ash_test/ash-z_slow/many_ifs.right @@ -0,0 +1 @@ +# tests 6856 passed 6856 failed 0 diff --git a/shell/ash_test/ash-z_slow/many_ifs.tests b/shell/ash_test/ash-z_slow/many_ifs.tests new file mode 100755 index 000000000..1f5b1b3a6 --- /dev/null +++ b/shell/ash_test/ash-z_slow/many_ifs.tests @@ -0,0 +1,257 @@ +# Usage: $SHELL ifs.sh +# +# This script generates 6856 tests for the set(1) and read(1) +# builtins w.r.t. IFS whitespace and non-whitespace characters. +# Each failed test produces one line on the standard output that +# contains the test along with the expected and actual results. +# The last output line contains the test result counts. ordered>0 +# are the number of tests where IFS=": " produced different results +# than IFS=" :". If a test fails the same way for IFS=": " and +# IFS=" :" then the second output line is suppressed. + +TESTS=6856 + +ksh_read=0 +echo 1 | read ksh_read +ksh_arith=0 +eval '((ksh_arith+=1))' 2>/dev/null + +failed=0 +ordered=0 +passed=0 + +split() +{ + i=$1 s=$2 r=$3 S='' R='' + for ifs in ': ' ' :' + do IFS=$ifs + set x $i + shift + IFS=' ' + g="[$#]" + while : + do case $# in + 0) break ;; + esac + g="$g($1)" + shift + done + case $g in + "$s") case $ksh_arith in + 1) ((passed+=1)) ;; + *) passed=`expr $passed + 1` ;; + esac + case $S in + '') S=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + "$S") case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + ;; + *) case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + case $s in + "$S") ;; + ?0*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#]\" # expected \"$s\" got \"$g\"" ;; + ?1*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)\" # expected \"$s\" got \"$g\"" ;; + ?2*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)\" # expected \"$s\" got \"$g\"" ;; + ?3*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)(\$3)\" # expected \"$s\" got \"$g\"" ;; + *) echo TEST ERROR i="'$i'" s="'$s'" ;; + esac + case $S in + '') S=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + esac + case $ksh_read in + 1) echo "$i" | IFS=$ifs read x y; g="($x)($y)" ;; + *) g=`export ifs; echo "$i" | ( IFS=$ifs; read x y; echo "($x)($y)" )` ;; + esac + case $g in + "$r") case $ksh_arith in + 1) ((passed+=1)) ;; + *) passed=`expr $passed + 1` ;; + esac + case $R in + '') R=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + "$R") case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + ;; + *) case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + case $r in + "$R") ;; + *) echo "echo \"$i\" | ( IFS=\"$ifs\" read x y; echo \"(\$x)(\$y)\" ) # expected \"$r\" got \"$g\"" ;; + esac + case $R in + '') R=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + esac + done +} + +for str in \ + '-' \ + 'a' \ + '- -' \ + '- a' \ + 'a -' \ + 'a b' \ + '- - -' \ + '- - a' \ + '- a -' \ + '- a b' \ + 'a - -' \ + 'a - b' \ + 'a b -' \ + 'a b c' \ + +do + IFS=' ' + set x $str + + shift + case $# in + 0) continue ;; + esac + + f1=$1 + case $f1 in + '-') f1='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f1$d1 in + '') split "$d0$f1$d1" "[0]" "()()" ;; + ' ') ;; + *) split "$d0$f1$d1" "[1]($f1)" "($f1)()" ;; + esac + done + done + continue + ;; + esac + f2=$1 + case $f2 in + '-') f2='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in ' ' ':' ' :' ': ' ' : ' + do + case ' ' in + $f1$d1|$d1$f2) continue ;; + esac + for d2 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f2$d2 in + '') split "$d0$f1$d1$f2$d2" "[1]($f1)" "($f1)()" ;; + ' ') ;; + *) split "$d0$f1$d1$f2$d2" "[2]($f1)($f2)" "($f1)($f2)" ;; + esac + done + done + done + continue + ;; + esac + f3=$1 + case $f3 in + '-') f3='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in ':' ' :' ': ' ' : ' + do + case ' ' in + $f1$d1|$d1$f2) continue ;; + esac + for d2 in ' ' ':' ' :' ': ' ' : ' + do + case $f2$d2 in + ' ') continue ;; + esac + case ' ' in + $f2$d2|$d2$f3) continue ;; + esac + for d3 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f3$d3 in + '') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;; + ' ') ;; + *) x=$f2$d2$f3$d3 + x=${x# } #was x=${x#' '} hush needs fixing for this to work + x=${x% } #was x=${x%' '} + split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)" + ;; + esac + done + done + done + done + continue + ;; + esac +done +case $ksh_arith in +1) ((tests=passed+failed)) ;; +*) tests=`expr $passed + $failed` ;; +esac +case $ordered in +0) ordered="" ;; +*) ordered=" ordered $ordered" ;; +esac +case $tests in +$TESTS) fatal="" ;; +*) fatal=" -- fundamental IFS error -- $TESTS tests expected" +esac +echo "# tests $tests passed $passed failed $failed$ordered$fatal" diff --git a/shell/hush_test/hush-z_slow/many_ifs.right b/shell/hush_test/hush-z_slow/many_ifs.right new file mode 100644 index 000000000..f3bdccc6c --- /dev/null +++ b/shell/hush_test/hush-z_slow/many_ifs.right @@ -0,0 +1 @@ +# tests 6856 passed 6856 failed 0 diff --git a/shell/hush_test/hush-z_slow/many_ifs.tests b/shell/hush_test/hush-z_slow/many_ifs.tests new file mode 100755 index 000000000..1f5b1b3a6 --- /dev/null +++ b/shell/hush_test/hush-z_slow/many_ifs.tests @@ -0,0 +1,257 @@ +# Usage: $SHELL ifs.sh +# +# This script generates 6856 tests for the set(1) and read(1) +# builtins w.r.t. IFS whitespace and non-whitespace characters. +# Each failed test produces one line on the standard output that +# contains the test along with the expected and actual results. +# The last output line contains the test result counts. ordered>0 +# are the number of tests where IFS=": " produced different results +# than IFS=" :". If a test fails the same way for IFS=": " and +# IFS=" :" then the second output line is suppressed. + +TESTS=6856 + +ksh_read=0 +echo 1 | read ksh_read +ksh_arith=0 +eval '((ksh_arith+=1))' 2>/dev/null + +failed=0 +ordered=0 +passed=0 + +split() +{ + i=$1 s=$2 r=$3 S='' R='' + for ifs in ': ' ' :' + do IFS=$ifs + set x $i + shift + IFS=' ' + g="[$#]" + while : + do case $# in + 0) break ;; + esac + g="$g($1)" + shift + done + case $g in + "$s") case $ksh_arith in + 1) ((passed+=1)) ;; + *) passed=`expr $passed + 1` ;; + esac + case $S in + '') S=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + "$S") case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + ;; + *) case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + case $s in + "$S") ;; + ?0*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#]\" # expected \"$s\" got \"$g\"" ;; + ?1*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)\" # expected \"$s\" got \"$g\"" ;; + ?2*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)\" # expected \"$s\" got \"$g\"" ;; + ?3*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)(\$3)\" # expected \"$s\" got \"$g\"" ;; + *) echo TEST ERROR i="'$i'" s="'$s'" ;; + esac + case $S in + '') S=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + esac + case $ksh_read in + 1) echo "$i" | IFS=$ifs read x y; g="($x)($y)" ;; + *) g=`export ifs; echo "$i" | ( IFS=$ifs; read x y; echo "($x)($y)" )` ;; + esac + case $g in + "$r") case $ksh_arith in + 1) ((passed+=1)) ;; + *) passed=`expr $passed + 1` ;; + esac + case $R in + '') R=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + "$R") case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + ;; + *) case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + case $r in + "$R") ;; + *) echo "echo \"$i\" | ( IFS=\"$ifs\" read x y; echo \"(\$x)(\$y)\" ) # expected \"$r\" got \"$g\"" ;; + esac + case $R in + '') R=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + esac + done +} + +for str in \ + '-' \ + 'a' \ + '- -' \ + '- a' \ + 'a -' \ + 'a b' \ + '- - -' \ + '- - a' \ + '- a -' \ + '- a b' \ + 'a - -' \ + 'a - b' \ + 'a b -' \ + 'a b c' \ + +do + IFS=' ' + set x $str + + shift + case $# in + 0) continue ;; + esac + + f1=$1 + case $f1 in + '-') f1='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f1$d1 in + '') split "$d0$f1$d1" "[0]" "()()" ;; + ' ') ;; + *) split "$d0$f1$d1" "[1]($f1)" "($f1)()" ;; + esac + done + done + continue + ;; + esac + f2=$1 + case $f2 in + '-') f2='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in ' ' ':' ' :' ': ' ' : ' + do + case ' ' in + $f1$d1|$d1$f2) continue ;; + esac + for d2 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f2$d2 in + '') split "$d0$f1$d1$f2$d2" "[1]($f1)" "($f1)()" ;; + ' ') ;; + *) split "$d0$f1$d1$f2$d2" "[2]($f1)($f2)" "($f1)($f2)" ;; + esac + done + done + done + continue + ;; + esac + f3=$1 + case $f3 in + '-') f3='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in ':' ' :' ': ' ' : ' + do + case ' ' in + $f1$d1|$d1$f2) continue ;; + esac + for d2 in ' ' ':' ' :' ': ' ' : ' + do + case $f2$d2 in + ' ') continue ;; + esac + case ' ' in + $f2$d2|$d2$f3) continue ;; + esac + for d3 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f3$d3 in + '') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;; + ' ') ;; + *) x=$f2$d2$f3$d3 + x=${x# } #was x=${x#' '} hush needs fixing for this to work + x=${x% } #was x=${x%' '} + split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)" + ;; + esac + done + done + done + done + continue + ;; + esac +done +case $ksh_arith in +1) ((tests=passed+failed)) ;; +*) tests=`expr $passed + $failed` ;; +esac +case $ordered in +0) ordered="" ;; +*) ordered=" ordered $ordered" ;; +esac +case $tests in +$TESTS) fatal="" ;; +*) fatal=" -- fundamental IFS error -- $TESTS tests expected" +esac +echo "# tests $tests passed $passed failed $failed$ordered$fatal" -- cgit v1.2.3-55-g6feb From d5f5045b43bd7a55402dfcecde511a2c207d07b7 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 14 Apr 2018 14:50:47 +0200 Subject: ash: expand: Fix buffer overflow in expandmeta Upstream commit: Date: Sun, 25 Mar 2018 16:38:00 +0800 expand: Fix buffer overflow in expandmeta The native version of expandmeta allocates a buffer that may be overrun for two reasons. First of all the size is 1 byte too small but this is normally hidden because the minimum size is rounded up to 2048 bytes. Secondly, if the directory level is deep enough, any buffer can be overrun. This patch fixes both problems by calling realloc when necessary. Signed-off-by: Herbert Xu function old new delta expmeta 517 635 +118 expandarg 990 996 +6 mklocal 288 290 +2 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/0 up/down: 126/0) Total: 126 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 67 ++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 25 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 40ca82d0b..051cc671f 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -7575,9 +7575,16 @@ expandmeta(struct strlist *str /*, int flag*/) /* * Do metacharacter (i.e. *, ?, [...]) expansion. */ +typedef struct exp_t { + char *dir; + unsigned dir_max; +} exp_t; static void -expmeta(char *expdir, char *enddir, char *name) +expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) { +#define expdir exp->dir +#define expdir_max exp->dir_max + char *enddir = expdir + expdir_len; char *p; const char *cp; char *start; @@ -7620,15 +7627,15 @@ expmeta(char *expdir, char *enddir, char *name) } } if (metaflag == 0) { /* we've reached the end of the file name */ - if (enddir != expdir) - metaflag++; + if (!expdir_len) + return; p = name; do { if (*p == '\\') p++; *enddir++ = *p; } while (*p++); - if (metaflag == 0 || lstat(expdir, &statb) >= 0) + if (lstat(expdir, &statb) == 0) addfname(expdir); return; } @@ -7641,19 +7648,14 @@ expmeta(char *expdir, char *enddir, char *name) *enddir++ = *p++; } while (p < start); } - if (enddir == expdir) { + *enddir = '\0'; + cp = expdir; + expdir_len = enddir - cp; + if (!expdir_len) cp = "."; - } else if (enddir == expdir + 1 && *expdir == '/') { - cp = "/"; - } else { - cp = expdir; - enddir[-1] = '\0'; - } dirp = opendir(cp); if (dirp == NULL) return; - if (enddir != expdir) - enddir[-1] = '/'; if (*endname == 0) { atend = 1; } else { @@ -7661,6 +7663,7 @@ expmeta(char *expdir, char *enddir, char *name) *endname = '\0'; endname += esc + 1; } + name_len -= endname - name; matchdot = 0; p = start; if (*p == '\\') @@ -7675,16 +7678,30 @@ expmeta(char *expdir, char *enddir, char *name) strcpy(enddir, dp->d_name); addfname(expdir); } else { - for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';) - continue; - p[-1] = '/'; - expmeta(expdir, p, endname); + unsigned offset; + unsigned len; + + p = stpcpy(enddir, dp->d_name); + *p = '/'; + + offset = p - expdir + 1; + len = offset + name_len + NAME_MAX; + if (len > expdir_max) { + len += PATH_MAX; + expdir = ckrealloc(expdir, len); + expdir_max = len; + } + + expmeta(exp, endname, name_len, offset); + enddir = expdir + expdir_len; } } } closedir(dirp); if (!atend) endname[-esc - 1] = esc ? '\\' : '/'; +#undef expdir +#undef expdir_max } static struct strlist * @@ -7757,10 +7774,11 @@ expandmeta(struct strlist *str /*, int flag*/) /* TODO - EXP_REDIR */ while (str) { - char *expdir; + exp_t exp; struct strlist **savelastp; struct strlist *sp; char *p; + unsigned len; if (fflag) goto nometa; @@ -7770,13 +7788,12 @@ expandmeta(struct strlist *str /*, int flag*/) INT_OFF; p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); - { - int i = strlen(str->text); -//BUGGY estimation of how long expanded name can be - expdir = ckmalloc(i < 2048 ? 2048 : i+1); - } - expmeta(expdir, expdir, p); - free(expdir); + len = strlen(p); + exp.dir_max = len + PATH_MAX; + exp.dir = ckmalloc(exp.dir_max); + + expmeta(&exp, p, len, 0); + free(exp.dir); if (p != str->text) free(p); INT_ON; -- cgit v1.2.3-55-g6feb