hush: make
authorDenis Vlasenko <vda.linux@googlemail.com>
Thu, 2 Apr 2009 16:31:29 +0000 (16:31 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Thu, 2 Apr 2009 16:31:29 +0000 (16:31 -0000)
 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

index 3725191d849db6fe3430e0c6d9735cbefeae1169..64b6e87e832390ae5bcdb0a887316968e704186f 100644 (file)
@@ -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 '+': { /* <SPECIAL_VAR_SYMBOL>(cmd<SPECIAL_VAR_SYMBOL> */
+               case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */
                        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 <SPECIAL_VAR_SYMBOL> */
                        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 <SPECIAL_VAR_SYMBOL>) */
+
                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("\\<eof>");
+                       debug_printf_parse("parse_stream_dquoted return 1: \\<eof>\n");
+                       return 1;
+               }
+               /* bash:
+                * "The backslash retains its special meaning [in "..."]
+                * only when followed by one of the following characters:
+                * $, `, ", \, or <newline>.  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 <newline> 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: \\<eof>\n");
                                return 1;
                        }
-                       /* bash:
-                        * "The backslash retains its special meaning [in "..."]
-                        * only when followed by one of the following characters:
-                        * $, `, ", \, or <newline>.  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);