From 2f1d394214c968181e9ab320f2c66f905f8352cf Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Thu, 2 Apr 2009 16:31:29 +0000 Subject: hush: make a=55; echo $(($a + 1)) $((1 + $((2)) + `echo $a`)) work as expected function old new delta handle_dollar - 667 +667 parse_stream_dquoted - 316 +316 expand_variables 2124 2272 +148 is_assignment 134 215 +81 parse_stream 2038 1240 -798 ------------------------------------------------------------------------------ (add/remove: 2/0 grow/shrink: 2/1 up/down: 1212/-798) Total: 414 bytes --- shell/hush.c | 220 ++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 164 insertions(+), 56 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 3725191d8..64b6e87e8 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -1510,7 +1510,7 @@ static void debug_print_list(const char *prefix, o_string *o, int n) } if (n) { const char *p = o->data + (int)list[n - 1] + string_start; - fprintf(stderr, " total_sz:%ld\n", (p + strlen(p) + 1) - o->data); + fprintf(stderr, " total_sz:%ld\n", (long)((p + strlen(p) + 1) - o->data)); } } #else @@ -1644,6 +1644,14 @@ static char **o_finalize_list(o_string *o, int n) } +/* Expansion can recurse */ +#if ENABLE_HUSH_TICK +static int process_command_subs(o_string *dest, + struct in_str *input, const char *subst_end); +#endif +static char *expand_string_to_string(const char *str); +static int parse_stream_dquoted(o_string *dest, struct in_str *input, int dquote_end); + /* expand_strvec_to_strvec() takes a list of strings, expands * all variable references within and returns a pointer to * a list of expanded strings, possibly with larger number @@ -1678,11 +1686,6 @@ static int expand_on_ifs(o_string *output, int n, const char *str) return n; } -#if ENABLE_HUSH_TICK -static int process_command_subs(o_string *dest, - struct in_str *input, const char *subst_end); -#endif - /* Expand all variable references in given string, adding words to list[] * at n, n+1,... positions. Return updated n (so that list[n] is next one * to be filled). This routine is extremely tricky: has to deal with @@ -1709,6 +1712,9 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) { #if ENABLE_HUSH_TICK o_string subst_result = NULL_O_STRING; +#endif +#if ENABLE_SH_MATH_SUPPORT + char arith_buf[sizeof(arith_t)*3 + 2]; #endif o_addstr(output, arg, p - arg); debug_print_list("expand_vars_to_list[1]", output, n); @@ -1720,6 +1726,7 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) * expand to nothing (not even an empty string) */ if ((first_ch & 0x7f) != '@') ored_ch |= first_ch; + val = NULL; switch (first_ch & 0x7f) { /* Highest bit in first_ch indicates that var is double-quoted */ @@ -1803,19 +1810,52 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) } #endif #if ENABLE_SH_MATH_SUPPORT - case '+': { /* (cmd */ + case '+': { /* +cmd */ arith_eval_hooks_t hooks; arith_t res; - char buf[30]; int errcode; + char *exp_str; - *p = '\0'; - ++arg; + arg++; /* skip '+' */ + *p = '\0'; /* replace trailing */ debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch); + + /* Optional: skip expansion if expr is simple ("a + 3", "i++" etc) */ + exp_str = arg; + while (1) { + unsigned char c = *exp_str++; + if (c == '\0') { + exp_str = NULL; + goto skip_expand; + } + if (isdigit(c)) + continue; + if (strchr(" \t+-*/%_", c) != NULL) + continue; + c |= 0x20; /* tolower */ + if (c >= 'a' && c <= 'z') + continue; + break; + } + /* We need to expand. Example: "echo $(($a + 1)) $((1 + $((2)) ))" */ + { + struct in_str input; + o_string dest = NULL_O_STRING; + + setup_string_in_str(&input, arg); + parse_stream_dquoted(&dest, &input, EOF); + //bb_error_msg("'%s' -> '%s'", arg, dest.data); + exp_str = expand_string_to_string(dest.data); + //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); + o_free(&dest); + } + skip_expand: hooks.lookupvar = lookup_param; hooks.setvar = arith_set_local_var; hooks.endofname = endofname; - res = arith(arg, &errcode, &hooks); + res = arith(exp_str ? exp_str : arg, &errcode, &hooks); + free(exp_str); + if (errcode < 0) { switch (errcode) { case -3: maybe_die("arith", "exponent less than 0"); break; @@ -1824,9 +1864,9 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) default: maybe_die("arith", "syntax error"); break; } } - sprintf(buf, arith_t_fmt, res); - o_addstrauto(output, buf); debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res); + sprintf(arith_buf, arith_t_fmt, res); + val = arith_buf; break; } #endif @@ -1918,13 +1958,14 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) } else { /* quoted $VAR, val will be appended below */ debug_printf_expand("quoted '%s', output->o_quote:%d\n", val, output->o_quote); } - } - } + } /* default: */ + } /* switch (char after ) */ + if (val) { o_addQstr(output, val, strlen(val)); } /* Do the check to avoid writing to a const string */ - if (p && *p != SPECIAL_VAR_SYMBOL) + if (*p != SPECIAL_VAR_SYMBOL) *p = SPECIAL_VAR_SYMBOL; #if ENABLE_HUSH_TICK @@ -3756,11 +3797,11 @@ static int process_command_subs(o_string *dest, } debug_printf("done reading from pipe, pclose()ing\n"); - /* This is the step that waits for the child. Should be pretty - * safe, since we just read an EOF from its stdout. We could try - * to do better, by using waitpid, and keeping track of background jobs - * at the same time. That would be a lot of work, and contrary - * to the KISS philosophy of this program. */ + /* Note: we got EOF, and we just close the read end of the pipe. + * We do not wait for the `cmd` child to terminate. bash and ash do. + * Try this: + * echo `echo Hi; exec 1>&-; sleep 2` + */ retcode = fclose(p); free_pipe_list(inner.list_head, /* indent: */ 0); debug_printf("closed FILE from child, retcode=%d\n", retcode); @@ -4056,7 +4097,7 @@ static int handle_dollar(o_string *dest, struct in_str *input) if (i_peek(input) == '(') { i_getch(input); o_addchr(dest, SPECIAL_VAR_SYMBOL); - o_addchr(dest, quote_mask | '+'); + o_addchr(dest, /*quote_mask |*/ '+'); add_till_closing_paren(dest, input, true); o_addchr(dest, SPECIAL_VAR_SYMBOL); break; @@ -4093,6 +4134,86 @@ static int handle_dollar(o_string *dest, struct in_str *input) return 0; } +static int parse_stream_dquoted(o_string *dest, struct in_str *input, int dquote_end) +{ + int ch, m; + int next; + + again: + ch = i_getch(input); + if (ch == dquote_end) { /* may be only '"' or EOF */ + dest->nonnull = 1; + if (dest->o_assignment == NOT_ASSIGNMENT) + dest->o_quote ^= 1; + debug_printf_parse("parse_stream_dquoted return 0\n"); + return 0; + } + if (ch == EOF) { + syntax("unterminated \""); + debug_printf_parse("parse_stream_dquoted return 1: unterminated \"\n"); + return 1; + } + next = '\0'; + m = G.charmap[ch]; + if (ch != '\n') { + next = i_peek(input); + } + debug_printf_parse(": ch=%c (%d) m=%d quote=%d\n", + ch, ch, m, dest->o_quote); + if (m != CHAR_SPECIAL) { + o_addQchr(dest, ch); + if ((dest->o_assignment == MAYBE_ASSIGNMENT + || dest->o_assignment == WORD_IS_KEYWORD) + && ch == '=' + && is_assignment(dest->data) + ) { + dest->o_assignment = DEFINITELY_ASSIGNMENT; + } + goto again; + } + if (ch == '\\') { + if (next == EOF) { + syntax("\\"); + debug_printf_parse("parse_stream_dquoted return 1: \\\n"); + return 1; + } + /* bash: + * "The backslash retains its special meaning [in "..."] + * only when followed by one of the following characters: + * $, `, ", \, or . A double quote may be quoted + * within double quotes by preceding it with a backslash. + * If enabled, history expansion will be performed unless + * an ! appearing in double quotes is escaped using + * a backslash. The backslash preceding the ! is not removed." + */ + if (strchr("$`\"\\", next) != NULL) { + o_addqchr(dest, i_getch(input)); + } else { + o_addqchr(dest, '\\'); + } + goto again; + } + if (ch == '$') { + if (handle_dollar(dest, input) != 0) { + debug_printf_parse("parse_stream_dquoted return 1: handle_dollar returned non-0\n"); + return 1; + } + goto again; + } +#if ENABLE_HUSH_TICK + if (ch == '`') { + //int pos = dest->length; + o_addchr(dest, SPECIAL_VAR_SYMBOL); + o_addchr(dest, 0x80 | '`'); + add_till_backquote(dest, input); + o_addchr(dest, SPECIAL_VAR_SYMBOL); + //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos); + /* fall through */ + } +#endif + goto again; +} + /* Scan input, call done_word() whenever full IFS delimited word was seen. * Call done_pipe if '\n' was seen (and end_trigger != NULL). * Return code is 0 if end_trigger char is met, @@ -4104,7 +4225,7 @@ static int parse_stream(o_string *dest, struct parse_context *ctx, int ch, m; int redir_fd; redir_type redir_style; - int shadow_quote = dest->o_quote; + int is_in_dquote; int next; /* Only double-quote state is handled in the state variable dest->o_quote. @@ -4113,7 +4234,14 @@ static int parse_stream(o_string *dest, struct parse_context *ctx, debug_printf_parse("parse_stream entered, end_trigger='%s' dest->o_assignment:%d\n", end_trigger, dest->o_assignment); + is_in_dquote = dest->o_quote; while (1) { + if (is_in_dquote) { + if (parse_stream_dquoted(dest, input, '"')) + return 1; /* propagate parse error */ + /* If we're here, we reached closing '"' */ + is_in_dquote = 0; + } m = CHAR_IFS; next = '\0'; ch = i_getch(input); @@ -4125,14 +4253,7 @@ static int parse_stream(o_string *dest, struct parse_context *ctx, } debug_printf_parse(": ch=%c (%d) m=%d quote=%d\n", ch, ch, m, dest->o_quote); - if (m == CHAR_ORDINARY - || (m != CHAR_SPECIAL && shadow_quote) - ) { - if (ch == EOF) { - syntax("unterminated \""); - debug_printf_parse("parse_stream return 1: unterminated \"\n"); - return 1; - } + if (m == CHAR_ORDINARY) { o_addQchr(dest, ch); if ((dest->o_assignment == MAYBE_ASSIGNMENT || dest->o_assignment == WORD_IS_KEYWORD) @@ -4143,6 +4264,8 @@ static int parse_stream(o_string *dest, struct parse_context *ctx, } continue; } + /* m is SPECIAL ($,`), IFS, or ORDINARY_IF_QUOTED (*,#) + */ if (m == CHAR_IFS) { if (done_word(dest, ctx)) { debug_printf_parse("parse_stream return 1: done_word!=0\n"); @@ -4152,7 +4275,7 @@ static int parse_stream(o_string *dest, struct parse_context *ctx, break; /* If we aren't performing a substitution, treat * a newline as a command separator. - * [why we don't handle it exactly like ';'? --vda] */ + * [why don't we handle it exactly like ';'? --vda] */ if (end_trigger && ch == '\n') { #if ENABLE_HUSH_CASE /* "case ... in word) ..." - @@ -4168,7 +4291,7 @@ static int parse_stream(o_string *dest, struct parse_context *ctx, } } if (end_trigger) { - if (!shadow_quote && strchr(end_trigger, ch)) { + if (strchr(end_trigger, ch)) { /* Special case: (...word) makes last word terminate, * as if ';' is seen */ if (ch == ')') { @@ -4188,15 +4311,17 @@ static int parse_stream(o_string *dest, struct parse_context *ctx, if (m == CHAR_IFS) continue; + /* m is SPECIAL (e.g. $,`) or ORDINARY_IF_QUOTED (*,#) */ + if (dest->o_assignment == MAYBE_ASSIGNMENT) { /* ch is a special char and thus this word - * cannot be an assignment: */ + * cannot be an assignment */ dest->o_assignment = NOT_ASSIGNMENT; } switch (ch) { case '#': - if (dest->length == 0 && !shadow_quote) { + if (dest->length == 0) { while (1) { ch = i_peek(input); if (ch == EOF || ch == '\n') @@ -4213,25 +4338,8 @@ static int parse_stream(o_string *dest, struct parse_context *ctx, debug_printf_parse("parse_stream return 1: \\\n"); return 1; } - /* bash: - * "The backslash retains its special meaning [in "..."] - * only when followed by one of the following characters: - * $, `, ", \, or . A double quote may be quoted - * within double quotes by preceding it with a backslash. - * If enabled, history expansion will be performed unless - * an ! appearing in double quotes is escaped using - * a backslash. The backslash preceding the ! is not removed." - */ - if (shadow_quote) { //NOT SURE dest->o_quote) { - if (strchr("$`\"\\", next) != NULL) { - o_addqchr(dest, i_getch(input)); - } else { - o_addqchr(dest, '\\'); - } - } else { - o_addchr(dest, '\\'); - o_addchr(dest, i_getch(input)); - } + o_addchr(dest, '\\'); + o_addchr(dest, i_getch(input)); break; case '$': if (handle_dollar(dest, input) != 0) { @@ -4258,7 +4366,7 @@ static int parse_stream(o_string *dest, struct parse_context *ctx, break; case '"': dest->nonnull = 1; - shadow_quote ^= 1; /* invert */ + is_in_dquote ^= 1; /* invert */ if (dest->o_assignment == NOT_ASSIGNMENT) dest->o_quote ^= 1; break; @@ -4266,7 +4374,7 @@ static int parse_stream(o_string *dest, struct parse_context *ctx, case '`': { //int pos = dest->length; o_addchr(dest, SPECIAL_VAR_SYMBOL); - o_addchr(dest, shadow_quote /*or dest->o_quote??*/ ? 0x80 | '`' : '`'); + o_addchr(dest, '`'); add_till_backquote(dest, input); o_addchr(dest, SPECIAL_VAR_SYMBOL); //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos); -- cgit v1.2.3