hush: big reordering: move parser functions to the beginning. no code changes
No binary (build failed)
—
(log yok)
| @@ -2515,4722 +2515,4722 @@ static char **o_finalize_list(o_string *o, int n) | |
| return list; | |
| } | |
| + | static void free_pipe_list(struct pipe *head); |
| - | /* Expansion can recurse */ |
| - | #if ENABLE_HUSH_TICK |
| - | static int process_command_subs(o_string *dest, const char *s); |
| + | /* Return code is the exit status of the pipe */ |
| + | static void free_pipe(struct pipe *pi) |
| + | { |
| + | char **p; |
| + | struct command *command; |
| + | struct redir_struct *r, *rnext; |
| + | int a, i; |
| + | |
| + | if (pi->stopped_cmds > 0) /* why? */ |
| + | return; |
| + | debug_printf_clean("run pipe: (pid %d)\n", getpid()); |
| + | for (i = 0; i < pi->num_cmds; i++) { |
| + | command = &pi->cmds[i]; |
| + | debug_printf_clean(" command %d:\n", i); |
| + | if (command->argv) { |
| + | for (a = 0, p = command->argv; *p; a++, p++) { |
| + | debug_printf_clean(" argv[%d] = %s\n", a, *p); |
| + | } |
| + | free_strings(command->argv); |
| + | command->argv = NULL; |
| + | } |
| + | /* not "else if": on syntax error, we may have both! */ |
| + | if (command->group) { |
| + | debug_printf_clean(" begin group (cmd_type:%d)\n", |
| + | command->cmd_type); |
| + | free_pipe_list(command->group); |
| + | debug_printf_clean(" end group\n"); |
| + | command->group = NULL; |
| + | } |
| + | /* else is crucial here. |
| + | * If group != NULL, child_func is meaningless */ |
| + | #if ENABLE_HUSH_FUNCTIONS |
| + | else if (command->child_func) { |
| + | debug_printf_exec("cmd %p releases child func at %p\n", command, command->child_func); |
| + | command->child_func->parent_cmd = NULL; |
| + | } |
| #endif | |
| - | static char *expand_string_to_string(const char *str); |
| - | #if BB_MMU |
| - | #define parse_stream_dquoted(as_string, dest, input, dquote_end) \ |
| - | parse_stream_dquoted(dest, input, dquote_end) |
| + | #if !BB_MMU |
| + | free(command->group_as_string); |
| + | command->group_as_string = NULL; |
| #endif | |
| - | static int parse_stream_dquoted(o_string *as_string, |
| - | 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 |
| - | * of strings. (Think VAR="a b"; echo $VAR). |
| - | * This new list is allocated as a single malloc block. |
| - | * NULL-terminated list of char* pointers is at the beginning of it, |
| - | * followed by strings themself. |
| - | * Caller can deallocate entire list by single free(list). */ |
| - | |
| - | /* Store given string, finalizing the word and starting new one whenever |
| - | * we encounter IFS char(s). This is used for expanding variable values. |
| - | * End-of-string does NOT finalize word: think about 'echo -$VAR-' */ |
| - | static int expand_on_ifs(o_string *output, int n, const char *str) |
| - | { |
| - | while (1) { |
| - | int word_len = strcspn(str, G.ifs); |
| - | if (word_len) { |
| - | if (output->o_escape || !output->o_glob) |
| - | o_addQblock(output, str, word_len); |
| - | else /* protect backslashes against globbing up :) */ |
| - | o_addblock_duplicate_backslash(output, str, word_len); |
| - | str += word_len; |
| + | for (r = command->redirects; r; r = rnext) { |
| + | debug_printf_clean(" redirect %d%s", |
| + | r->rd_fd, redir_table[r->rd_type].descrip); |
| + | /* guard against the case >$FOO, where foo is unset or blank */ |
| + | if (r->rd_filename) { |
| + | debug_printf_clean(" fname:'%s'\n", r->rd_filename); |
| + | free(r->rd_filename); |
| + | r->rd_filename = NULL; |
| + | } |
| + | debug_printf_clean(" rd_dup:%d\n", r->rd_dup); |
| + | rnext = r->next; |
| + | free(r); |
| } | |
| - | if (!*str) /* EOL - do not finalize word */ |
| - | break; |
| - | o_addchr(output, '\0'); |
| - | debug_print_list("expand_on_ifs", output, n); |
| - | n = o_save_ptr(output, n); |
| - | str += strspn(str, G.ifs); /* skip ifs chars */ |
| + | command->redirects = NULL; |
| } | |
| - | debug_print_list("expand_on_ifs[1]", output, n); |
| - | return n; |
| + | free(pi->cmds); /* children are an array, they get freed all at once */ |
| + | pi->cmds = NULL; |
| + | #if ENABLE_HUSH_JOB |
| + | free(pi->cmdtext); |
| + | pi->cmdtext = NULL; |
| + | #endif |
| } | |
| - | /* Helper to expand $((...)) and heredoc body. These act as if |
| - | * they are in double quotes, with the exception that they are not :). |
| - | * Just the rules are similar: "expand only $var and `cmd`" |
| - | * |
| - | * Returns malloced string. |
| - | * As an optimization, we return NULL if expansion is not needed. |
| - | */ |
| - | static char *expand_pseudo_dquoted(const char *str) |
| + | static void free_pipe_list(struct pipe *head) |
| { | |
| - | char *exp_str; |
| - | struct in_str input; |
| - | o_string dest = NULL_O_STRING; |
| + | struct pipe *pi, *next; |
| - | if (!strchr(str, '$') |
| - | #if ENABLE_HUSH_TICK |
| - | && !strchr(str, '`') |
| + | for (pi = head; pi; pi = next) { |
| + | #if HAS_KEYWORDS |
| + | debug_printf_clean(" pipe reserved word %d\n", pi->res_word); |
| #endif | |
| - | ) { |
| - | return NULL; |
| + | free_pipe(pi); |
| + | debug_printf_clean("pipe followup code %d\n", pi->followup); |
| + | next = pi->next; |
| + | /*pi->next = NULL;*/ |
| + | free(pi); |
| } | |
| - | |
| - | /* We need to expand. Example: |
| - | * echo $(($a + `echo 1`)) $((1 + $((2)) )) |
| - | */ |
| - | setup_string_in_str(&input, str); |
| - | parse_stream_dquoted(NULL, &dest, &input, EOF); |
| - | //bb_error_msg("'%s' -> '%s'", str, dest.data); |
| - | exp_str = expand_string_to_string(dest.data); |
| - | //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); |
| - | o_free_unsafe(&dest); |
| - | return exp_str; |
| } | |
| - | #if ENABLE_SH_MATH_SUPPORT |
| - | static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p) |
| - | { |
| - | arith_eval_hooks_t hooks; |
| - | arith_t res; |
| - | char *exp_str; |
| - | hooks.lookupvar = get_local_var_value; |
| - | hooks.setvar = set_local_var_from_halves; |
| - | hooks.endofname = endofname; |
| - | exp_str = expand_pseudo_dquoted(arg); |
| - | res = arith(exp_str ? exp_str : arg, errcode_p, &hooks); |
| - | free(exp_str); |
| - | return res; |
| - | } |
| - | #endif |
| + | /*** Parsing routines ***/ |
| - | #if ENABLE_HUSH_BASH_COMPAT |
| - | /* ${var/[/]pattern[/repl]} helpers */ |
| - | static char *strstr_pattern(char *val, const char *pattern, int *size) |
| + | static struct pipe *new_pipe(void) |
| { | |
| - | while (1) { |
| - | char *end = scan_and_match(val, pattern, SCAN_MOVE_FROM_RIGHT + SCAN_MATCH_LEFT_HALF); |
| - | debug_printf_varexp("val:'%s' pattern:'%s' end:'%s'\n", val, pattern, end); |
| - | if (end) { |
| - | *size = end - val; |
| - | return val; |
| - | } |
| - | if (*val == '\0') |
| - | return NULL; |
| - | /* Optimization: if "*pat" did not match the start of "string", |
| - | * we know that "tring", "ring" etc will not match too: |
| - | */ |
| - | if (pattern[0] == '*') |
| - | return NULL; |
| - | val++; |
| - | } |
| + | struct pipe *pi; |
| + | pi = xzalloc(sizeof(struct pipe)); |
| + | /*pi->followup = 0; - deliberately invalid value */ |
| + | /*pi->res_word = RES_NONE; - RES_NONE is 0 anyway */ |
| + | return pi; |
| } | |
| - | static char *replace_pattern(char *val, const char *pattern, const char *repl, char exp_op) |
| - | { |
| - | char *result = NULL; |
| - | unsigned res_len = 0; |
| - | unsigned repl_len = strlen(repl); |
| - | |
| - | while (1) { |
| - | int size; |
| - | char *s = strstr_pattern(val, pattern, &size); |
| - | if (!s) |
| - | break; |
| - | result = xrealloc(result, res_len + (s - val) + repl_len + 1); |
| - | memcpy(result + res_len, val, s - val); |
| - | res_len += s - val; |
| - | strcpy(result + res_len, repl); |
| - | res_len += repl_len; |
| - | debug_printf_varexp("val:'%s' s:'%s' result:'%s'\n", val, s, result); |
| + | /* Command (member of a pipe) is complete, or we start a new pipe |
| + | * if ctx->command is NULL. |
| + | * No errors possible here. |
| + | */ |
| + | static int done_command(struct parse_context *ctx) |
| + | { |
| + | /* The command is really already in the pipe structure, so |
| + | * advance the pipe counter and make a new, null command. */ |
| + | struct pipe *pi = ctx->pipe; |
| + | struct command *command = ctx->command; |
| - | val = s + size; |
| - | if (exp_op == '/') |
| - | break; |
| - | } |
| - | if (val[0] && result) { |
| - | result = xrealloc(result, res_len + strlen(val) + 1); |
| - | strcpy(result + res_len, val); |
| - | debug_printf_varexp("val:'%s' result:'%s'\n", val, result); |
| + | if (command) { |
| + | if (IS_NULL_CMD(command)) { |
| + | debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds); |
| + | goto clear_and_ret; |
| + | } |
| + | pi->num_cmds++; |
| + | debug_printf_parse("done_command: ++num_cmds=%d\n", pi->num_cmds); |
| + | //debug_print_tree(ctx->list_head, 20); |
| + | } else { |
| + | debug_printf_parse("done_command: initializing, num_cmds=%d\n", pi->num_cmds); |
| } | |
| - | debug_printf_varexp("result:'%s'\n", result); |
| - | return result; |
| + | |
| + | /* Only real trickiness here is that the uncommitted |
| + | * command structure is not counted in pi->num_cmds. */ |
| + | pi->cmds = xrealloc(pi->cmds, sizeof(*pi->cmds) * (pi->num_cmds+1)); |
| + | ctx->command = command = &pi->cmds[pi->num_cmds]; |
| + | clear_and_ret: |
| + | memset(command, 0, sizeof(*command)); |
| + | return pi->num_cmds; /* used only for 0/nonzero check */ |
| } | |
| - | #endif |
| - | /* Helper: |
| - | * Handles <SPECIAL_VAR_SYMBOL>varname...<SPECIAL_VAR_SYMBOL> construct. |
| - | */ |
| - | static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, char **pp, char first_ch) |
| + | static void done_pipe(struct parse_context *ctx, pipe_style type) |
| { | |
| - | const char *val = NULL; |
| - | char *to_be_freed = NULL; |
| - | char *p = *pp; |
| - | char *var; |
| - | char first_char; |
| - | char exp_op; |
| - | char exp_save = exp_save; /* for compiler */ |
| - | char *exp_saveptr; /* points to expansion operator */ |
| - | char *exp_word = exp_word; /* for compiler */ |
| - | |
| - | var = arg; |
| - | *p = '\0'; |
| - | exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL; |
| - | first_char = arg[0] = first_ch & 0x7f; |
| - | exp_op = 0; |
| + | int not_null; |
| - | if (first_char == '#' && arg[1] && !exp_saveptr) { |
| - | /* handle length expansion ${#var} */ |
| - | var++; |
| - | exp_op = 'L'; |
| - | } else { |
| - | /* maybe handle parameter expansion */ |
| - | if (exp_saveptr /* if 2nd char is one of expansion operators */ |
| - | && strchr(NUMERIC_SPECVARS_STR, first_char) /* 1st char is special variable */ |
| - | ) { |
| - | /* ${?:0}, ${#[:]%0} etc */ |
| - | exp_saveptr = var + 1; |
| - | } else { |
| - | /* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */ |
| - | exp_saveptr = var+1 + strcspn(var+1, VAR_ENCODED_SUBST_OPS); |
| - | } |
| - | exp_op = exp_save = *exp_saveptr; |
| - | if (exp_op) { |
| - | exp_word = exp_saveptr + 1; |
| - | if (exp_op == ':') { |
| - | exp_op = *exp_word++; |
| - | if (ENABLE_HUSH_BASH_COMPAT |
| - | && (exp_op == '\0' || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op)) |
| - | ) { |
| - | /* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */ |
| - | exp_op = ':'; |
| - | exp_word--; |
| - | } |
| - | } |
| - | *exp_saveptr = '\0'; |
| - | } /* else: it's not an expansion op, but bare ${var} */ |
| - | } |
| - | |
| - | /* lookup the variable in question */ |
| - | if (isdigit(var[0])) { |
| - | /* parse_dollar() should have vetted var for us */ |
| - | int n = xatoi_positive(var); |
| - | if (n < G.global_argc) |
| - | val = G.global_argv[n]; |
| - | /* else val remains NULL: $N with too big N */ |
| - | } else { |
| - | switch (var[0]) { |
| - | case '$': /* pid */ |
| - | val = utoa(G.root_pid); |
| - | break; |
| - | case '!': /* bg pid */ |
| - | val = G.last_bg_pid ? utoa(G.last_bg_pid) : ""; |
| - | break; |
| - | case '?': /* exitcode */ |
| - | val = utoa(G.last_exitcode); |
| - | break; |
| - | case '#': /* argc */ |
| - | val = utoa(G.global_argc ? G.global_argc-1 : 0); |
| - | break; |
| - | default: |
| - | val = get_local_var_value(var); |
| - | } |
| - | } |
| - | |
| - | /* Handle any expansions */ |
| - | if (exp_op == 'L') { |
| - | debug_printf_expand("expand: length(%s)=", val); |
| - | val = utoa(val ? strlen(val) : 0); |
| - | debug_printf_expand("%s\n", val); |
| - | } else if (exp_op) { |
| - | if (exp_op == '%' || exp_op == '#') { |
| - | /* Standard-mandated substring removal ops: |
| - | * ${parameter%word} - remove smallest suffix pattern |
| - | * ${parameter%%word} - remove largest suffix pattern |
| - | * ${parameter#word} - remove smallest prefix pattern |
| - | * ${parameter##word} - remove largest prefix pattern |
| - | * |
| - | * Word is expanded to produce a glob pattern. |
| - | * Then var's value is matched to it and matching part removed. |
| - | */ |
| - | if (val && val[0]) { |
| - | char *exp_exp_word; |
| - | char *loc; |
| - | unsigned scan_flags = pick_scan(exp_op, *exp_word); |
| - | if (exp_op == *exp_word) /* ## or %% */ |
| - | exp_word++; |
| - | //TODO: avoid xstrdup unless needed |
| - | // (see HACK ALERT below) |
| - | val = to_be_freed = xstrdup(val); |
| - | exp_exp_word = expand_pseudo_dquoted(exp_word); |
| - | if (exp_exp_word) |
| - | exp_word = exp_exp_word; |
| - | loc = scan_and_match(to_be_freed, exp_word, scan_flags); |
| - | //bb_error_msg("op:%c str:'%s' pat:'%s' res:'%s'", |
| - | // exp_op, to_be_freed, exp_word, loc); |
| - | free(exp_exp_word); |
| - | if (loc) { /* match was found */ |
| - | if (scan_flags & SCAN_MATCH_LEFT_HALF) /* #[#] */ |
| - | val = loc; |
| - | else /* %[%] */ |
| - | *loc = '\0'; |
| - | } |
| - | } |
| - | } |
| - | #if ENABLE_HUSH_BASH_COMPAT |
| - | else if (exp_op == '/' || exp_op == '\\') { |
| - | /* Empty variable always gives nothing: */ |
| - | // "v=''; echo ${v/*/w}" prints "" |
| - | if (val && val[0]) { |
| - | /* It's ${var/[/]pattern[/repl]} thing */ |
| - | char *pattern, *repl, *t; |
| - | pattern = expand_pseudo_dquoted(exp_word); |
| - | if (!pattern) |
| - | pattern = xstrdup(exp_word); |
| - | debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern); |
| - | *p++ = SPECIAL_VAR_SYMBOL; |
| - | exp_word = p; |
| - | p = strchr(p, SPECIAL_VAR_SYMBOL); |
| - | *p = '\0'; |
| - | repl = expand_pseudo_dquoted(exp_word); |
| - | debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl); |
| - | /* HACK ALERT. We depend here on the fact that |
| - | * G.global_argv and results of utoa and get_local_var_value |
| - | * are actually in writable memory: |
| - | * replace_pattern momentarily stores NULs there. */ |
| - | t = (char*)val; |
| - | to_be_freed = replace_pattern(t, |
| - | pattern, |
| - | (repl ? repl : exp_word), |
| - | exp_op); |
| - | if (to_be_freed) /* at least one replace happened */ |
| - | val = to_be_freed; |
| - | free(pattern); |
| - | free(repl); |
| - | } |
| - | } |
| - | #endif |
| - | else if (exp_op == ':') { |
| - | #if ENABLE_HUSH_BASH_COMPAT && ENABLE_SH_MATH_SUPPORT |
| - | /* It's ${var:N[:M]} bashism. |
| - | * Note that in encoded form it has TWO parts: |
| - | * var:N<SPECIAL_VAR_SYMBOL>M<SPECIAL_VAR_SYMBOL> |
| - | */ |
| - | arith_t beg, len; |
| - | int errcode = 0; |
| - | |
| - | beg = expand_and_evaluate_arith(exp_word, &errcode); |
| - | debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg); |
| - | *p++ = SPECIAL_VAR_SYMBOL; |
| - | exp_word = p; |
| - | p = strchr(p, SPECIAL_VAR_SYMBOL); |
| - | *p = '\0'; |
| - | len = expand_and_evaluate_arith(exp_word, &errcode); |
| - | debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len); |
| - | |
| - | if (errcode >= 0 && len >= 0) { /* bash compat: len < 0 is illegal */ |
| - | if (beg < 0) /* bash compat */ |
| - | beg = 0; |
| - | debug_printf_varexp("from val:'%s'\n", val); |
| - | if (len == 0 || !val || beg >= strlen(val)) |
| - | val = ""; |
| - | else { |
| - | /* Paranoia. What if user entered 9999999999999 |
| - | * which fits in arith_t but not int? */ |
| - | if (len >= INT_MAX) |
| - | len = INT_MAX; |
| - | val = to_be_freed = xstrndup(val + beg, len); |
| - | } |
| - | debug_printf_varexp("val:'%s'\n", val); |
| - | } else |
| - | #endif |
| - | { |
| - | die_if_script("malformed ${%s:...}", var); |
| - | val = ""; |
| - | } |
| - | } else { /* one of "-=+?" */ |
| - | /* Standard-mandated substitution ops: |
| - | * ${var?word} - indicate error if unset |
| - | * If var is unset, word (or a message indicating it is unset |
| - | * if word is null) is written to standard error |
| - | * and the shell exits with a non-zero exit status. |
| - | * Otherwise, the value of var is substituted. |
| - | * ${var-word} - use default value |
| - | * If var is unset, word is substituted. |
| - | * ${var=word} - assign and use default value |
| - | * If var is unset, word is assigned to var. |
| - | * In all cases, final value of var is substituted. |
| - | * ${var+word} - use alternative value |
| - | * If var is unset, null is substituted. |
| - | * Otherwise, word is substituted. |
| - | * |
| - | * Word is subjected to tilde expansion, parameter expansion, |
| - | * command substitution, and arithmetic expansion. |
| - | * If word is not needed, it is not expanded. |
| - | * |
| - | * Colon forms (${var:-word}, ${var:=word} etc) do the same, |
| - | * but also treat null var as if it is unset. |
| - | */ |
| - | int use_word = (!val || ((exp_save == ':') && !val[0])); |
| - | if (exp_op == '+') |
| - | use_word = !use_word; |
| - | debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, |
| - | (exp_save == ':') ? "true" : "false", use_word); |
| - | if (use_word) { |
| - | to_be_freed = expand_pseudo_dquoted(exp_word); |
| - | if (to_be_freed) |
| - | exp_word = to_be_freed; |
| - | if (exp_op == '?') { |
| - | /* mimic bash message */ |
| - | die_if_script("%s: %s", |
| - | var, |
| - | exp_word[0] ? exp_word : "parameter null or not set" |
| - | ); |
| - | //TODO: how interactive bash aborts expansion mid-command? |
| - | } else { |
| - | val = exp_word; |
| - | } |
| - | |
| - | if (exp_op == '=') { |
| - | /* ${var=[word]} or ${var:=[word]} */ |
| - | if (isdigit(var[0]) || var[0] == '#') { |
| - | /* mimic bash message */ |
| - | die_if_script("$%s: cannot assign in this way", var); |
| - | val = NULL; |
| - | } else { |
| - | char *new_var = xasprintf("%s=%s", var, val); |
| - | set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); |
| - | } |
| - | } |
| - | } |
| - | } /* one of "-=+?" */ |
| - | |
| - | *exp_saveptr = exp_save; |
| - | } /* if (exp_op) */ |
| - | |
| - | arg[0] = first_ch; |
| - | |
| - | *pp = p; |
| - | *to_be_freed_pp = to_be_freed; |
| - | return val; |
| - | } |
| - | |
| - | /* 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 |
| - | * variables/parameters with whitespace, $* and $@, and constructs like |
| - | * 'echo -$*-'. If you play here, you must run testsuite afterwards! */ |
| - | static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) |
| - | { |
| - | /* or_mask is either 0 (normal case) or 0x80 - |
| - | * expansion of right-hand side of assignment == 1-element expand. |
| - | * It will also do no globbing, and thus we must not backslash-quote! |
| - | */ |
| - | char ored_ch; |
| - | char *p; |
| - | |
| - | ored_ch = 0; |
| - | |
| - | debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask); |
| - | debug_print_list("expand_vars_to_list", output, n); |
| - | n = o_save_ptr(output, n); |
| - | debug_print_list("expand_vars_to_list[0]", output, n); |
| - | |
| - | while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) { |
| - | char first_ch; |
| - | int i; |
| - | char *to_be_freed = NULL; |
| - | const char *val = 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]; |
| + | debug_printf_parse("done_pipe entered, followup %d\n", type); |
| + | /* Close previous command */ |
| + | not_null = done_command(ctx); |
| + | ctx->pipe->followup = type; |
| + | #if HAS_KEYWORDS |
| + | ctx->pipe->pi_inverted = ctx->ctx_inverted; |
| + | ctx->ctx_inverted = 0; |
| + | ctx->pipe->res_word = ctx->ctx_res_w; |
| #endif | |
| - | o_addblock(output, arg, p - arg); |
| - | debug_print_list("expand_vars_to_list[1]", output, n); |
| - | arg = ++p; |
| - | p = strchr(p, SPECIAL_VAR_SYMBOL); |
| - | first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */ |
| - | /* "$@" is special. Even if quoted, it can still |
| - | * expand to nothing (not even an empty string) */ |
| - | if ((first_ch & 0x7f) != '@') |
| - | ored_ch |= first_ch; |
| - | |
| - | switch (first_ch & 0x7f) { |
| - | /* Highest bit in first_ch indicates that var is double-quoted */ |
| - | case '*': |
| - | case '@': |
| - | i = 1; |
| - | if (!G.global_argv[i]) |
| - | break; |
| - | ored_ch |= first_ch; /* do it for "$@" _now_, when we know it's not empty */ |
| - | if (!(first_ch & 0x80)) { /* unquoted $* or $@ */ |
| - | smallint sv = output->o_escape; |
| - | /* unquoted var's contents should be globbed, so don't escape */ |
| - | output->o_escape = 0; |
| - | while (G.global_argv[i]) { |
| - | n = expand_on_ifs(output, n, G.global_argv[i]); |
| - | debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1); |
| - | if (G.global_argv[i++][0] && G.global_argv[i]) { |
| - | /* this argv[] is not empty and not last: |
| - | * put terminating NUL, start new word */ |
| - | o_addchr(output, '\0'); |
| - | debug_print_list("expand_vars_to_list[2]", output, n); |
| - | n = o_save_ptr(output, n); |
| - | debug_print_list("expand_vars_to_list[3]", output, n); |
| - | } |
| - | } |
| - | output->o_escape = sv; |
| - | } else |
| - | /* If or_mask is nonzero, we handle assignment 'a=....$@.....' |
| - | * and in this case should treat it like '$*' - see 'else...' below */ |
| - | if (first_ch == ('@'|0x80) && !or_mask) { /* quoted $@ */ |
| - | while (1) { |
| - | o_addQstr(output, G.global_argv[i]); |
| - | if (++i >= G.global_argc) |
| - | break; |
| - | o_addchr(output, '\0'); |
| - | debug_print_list("expand_vars_to_list[4]", output, n); |
| - | n = o_save_ptr(output, n); |
| - | } |
| - | } else { /* quoted $*: add as one word */ |
| - | while (1) { |
| - | o_addQstr(output, G.global_argv[i]); |
| - | if (!G.global_argv[++i]) |
| - | break; |
| - | if (G.ifs[0]) |
| - | o_addchr(output, G.ifs[0]); |
| - | } |
| - | } |
| - | break; |
| - | case SPECIAL_VAR_SYMBOL: /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */ |
| - | /* "Empty variable", used to make "" etc to not disappear */ |
| - | arg++; |
| - | ored_ch = 0x80; |
| - | break; |
| - | #if ENABLE_HUSH_TICK |
| - | case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */ |
| - | *p = '\0'; |
| - | arg++; |
| - | /* Can't just stuff it into output o_string, |
| - | * expanded result may need to be globbed |
| - | * and $IFS-splitted */ |
| - | debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch); |
| - | G.last_exitcode = process_command_subs(&subst_result, arg); |
| - | debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); |
| - | val = subst_result.data; |
| - | goto store_val; |
| + | /* Without this check, even just <enter> on command line generates |
| + | * tree of three NOPs (!). Which is harmless but annoying. |
| + | * IOW: it is safe to do it unconditionally. */ |
| + | if (not_null |
| + | #if ENABLE_HUSH_IF |
| + | || ctx->ctx_res_w == RES_FI |
| #endif | |
| - | #if ENABLE_SH_MATH_SUPPORT |
| - | case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */ |
| - | arith_t res; |
| - | int errcode; |
| - | |
| - | arg++; /* skip '+' */ |
| - | *p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */ |
| - | debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch); |
| - | res = expand_and_evaluate_arith(arg, &errcode); |
| - | |
| - | if (errcode < 0) { |
| - | const char *msg = "error in arithmetic"; |
| - | switch (errcode) { |
| - | case -3: |
| - | msg = "exponent less than 0"; |
| - | break; |
| - | case -2: |
| - | msg = "divide by 0"; |
| - | break; |
| - | case -5: |
| - | msg = "expression recursion loop detected"; |
| - | break; |
| - | } |
| - | die_if_script(msg); |
| - | } |
| - | debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res); |
| - | sprintf(arith_buf, arith_t_fmt, res); |
| - | val = arith_buf; |
| - | break; |
| - | } |
| + | #if ENABLE_HUSH_LOOPS |
| + | || ctx->ctx_res_w == RES_DONE |
| + | || ctx->ctx_res_w == RES_FOR |
| + | || ctx->ctx_res_w == RES_IN |
| #endif | |
| - | default: |
| - | val = expand_one_var(&to_be_freed, arg, &p, first_ch); |
| - | IF_HUSH_TICK(store_val:) |
| - | if (!(first_ch & 0x80)) { /* unquoted $VAR */ |
| - | debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, output->o_escape); |
| - | if (val && val[0]) { |
| - | /* unquoted var's contents should be globbed, so don't escape */ |
| - | smallint sv = output->o_escape; |
| - | output->o_escape = 0; |
| - | n = expand_on_ifs(output, n, val); |
| - | val = NULL; |
| - | output->o_escape = sv; |
| - | } |
| - | } else { /* quoted $VAR, val will be appended below */ |
| - | debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, output->o_escape); |
| - | } |
| - | break; |
| - | |
| - | } /* switch (char after <SPECIAL_VAR_SYMBOL>) */ |
| - | |
| - | if (val && val[0]) { |
| - | o_addQstr(output, val); |
| - | } |
| - | free(to_be_freed); |
| - | /* Do the check to avoid writing to a const string */ |
| - | if (*p != SPECIAL_VAR_SYMBOL) |
| - | *p = SPECIAL_VAR_SYMBOL; |
| - | |
| - | #if ENABLE_HUSH_TICK |
| - | o_free(&subst_result); |
| + | #if ENABLE_HUSH_CASE |
| + | || ctx->ctx_res_w == RES_ESAC |
| #endif | |
| - | arg = ++p; |
| - | } /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */ |
| - | |
| - | if (arg[0]) { |
| - | debug_print_list("expand_vars_to_list[a]", output, n); |
| - | /* this part is literal, and it was already pre-quoted |
| - | * if needed (much earlier), do not use o_addQstr here! */ |
| - | o_addstr_with_NUL(output, arg); |
| - | debug_print_list("expand_vars_to_list[b]", output, n); |
| - | } else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */ |
| - | && !(ored_ch & 0x80) /* and all vars were not quoted. */ |
| ) { | |
| - | n--; |
| - | /* allow to reuse list[n] later without re-growth */ |
| - | output->has_empty_slot = 1; |
| - | } else { |
| - | o_addchr(output, '\0'); |
| - | } |
| - | |
| - | return n; |
| - | } |
| - | |
| - | enum { |
| - | EXPVAR_FLAG_GLOB = 0x200, |
| - | EXPVAR_FLAG_ESCAPE_VARS = 0x100, |
| - | EXPVAR_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ |
| - | }; |
| - | static char **expand_variables(char **argv, unsigned or_mask) |
| - | { |
| - | int n; |
| - | char **list; |
| - | char **v; |
| - | o_string output = NULL_O_STRING; |
| - | |
| - | /* protect against globbing for "$var"? */ |
| - | /* (unquoted $var will temporarily switch it off) */ |
| - | output.o_escape = 1 & (or_mask / EXPVAR_FLAG_ESCAPE_VARS); |
| - | output.o_glob = 1 & (or_mask / EXPVAR_FLAG_GLOB); |
| - | |
| - | n = 0; |
| - | v = argv; |
| - | while (*v) { |
| - | n = expand_vars_to_list(&output, n, *v, (unsigned char)or_mask); |
| - | v++; |
| - | } |
| - | debug_print_list("expand_variables", &output, n); |
| - | |
| - | /* output.data (malloced in one block) gets returned in "list" */ |
| - | list = o_finalize_list(&output, n); |
| - | debug_print_strings("expand_variables[1]", list); |
| - | return list; |
| - | } |
| - | |
| - | static char **expand_strvec_to_strvec(char **argv) |
| - | { |
| - | return expand_variables(argv, EXPVAR_FLAG_GLOB | EXPVAR_FLAG_ESCAPE_VARS); |
| - | } |
| - | |
| - | #if ENABLE_HUSH_BASH_COMPAT |
| - | static char **expand_strvec_to_strvec_singleword_noglob(char **argv) |
| - | { |
| - | return expand_variables(argv, EXPVAR_FLAG_SINGLEWORD); |
| - | } |
| + | struct pipe *new_p; |
| + | debug_printf_parse("done_pipe: adding new pipe: " |
| + | "not_null:%d ctx->ctx_res_w:%d\n", |
| + | not_null, ctx->ctx_res_w); |
| + | new_p = new_pipe(); |
| + | ctx->pipe->next = new_p; |
| + | ctx->pipe = new_p; |
| + | /* RES_THEN, RES_DO etc are "sticky" - |
| + | * they remain set for pipes inside if/while. |
| + | * This is used to control execution. |
| + | * RES_FOR and RES_IN are NOT sticky (needed to support |
| + | * cases where variable or value happens to match a keyword): |
| + | */ |
| + | #if ENABLE_HUSH_LOOPS |
| + | if (ctx->ctx_res_w == RES_FOR |
| + | || ctx->ctx_res_w == RES_IN) |
| + | ctx->ctx_res_w = RES_NONE; |
| #endif | |
| - | |
| - | #ifdef CMD_SINGLEWORD_NOGLOB_COND |
| - | static char **expand_strvec_to_strvec_singleword_noglob_cond(char **argv) |
| - | { |
| - | int n; |
| - | char **list; |
| - | char **v; |
| - | o_string output = NULL_O_STRING; |
| - | |
| - | n = 0; |
| - | v = argv; |
| - | while (*v) { |
| - | int is_var = is_well_formed_var_name(*v, '='); |
| - | /* is_var * 0x80: singleword expansion for vars */ |
| - | n = expand_vars_to_list(&output, n, *v, is_var * 0x80); |
| - | |
| - | /* Subtle! expand_vars_to_list did not glob last word yet. |
| - | * It does this only when fed with further data. |
| - | * Therefore we set globbing flags AFTER it, not before: |
| + | #if ENABLE_HUSH_CASE |
| + | if (ctx->ctx_res_w == RES_MATCH) |
| + | ctx->ctx_res_w = RES_CASE_BODY; |
| + | if (ctx->ctx_res_w == RES_CASE) |
| + | ctx->ctx_res_w = RES_CASE_IN; |
| + | #endif |
| + | ctx->command = NULL; /* trick done_command below */ |
| + | /* Create the memory for command, roughly: |
| + | * ctx->pipe->cmds = new struct command; |
| + | * ctx->command = &ctx->pipe->cmds[0]; |
| */ | |
| - | |
| - | /* if it is not recognizably abc=...; then: */ |
| - | output.o_escape = !is_var; /* protect against globbing for "$var" */ |
| - | /* (unquoted $var will temporarily switch it off) */ |
| - | output.o_glob = !is_var; /* and indeed do globbing */ |
| - | v++; |
| + | done_command(ctx); |
| + | //debug_print_tree(ctx->list_head, 10); |
| } | |
| - | debug_print_list("expand_cond", &output, n); |
| - | |
| - | /* output.data (malloced in one block) gets returned in "list" */ |
| - | list = o_finalize_list(&output, n); |
| - | debug_print_strings("expand_cond[1]", list); |
| - | return list; |
| + | debug_printf_parse("done_pipe return\n"); |
| } | |
| - | #endif |
| - | /* Used for expansion of right hand of assignments */ |
| - | /* NB: should NOT do globbing! |
| - | * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" */ |
| - | static char *expand_string_to_string(const char *str) |
| + | static void initialize_context(struct parse_context *ctx) |
| { | |
| - | char *argv[2], **list; |
| - | |
| - | /* This is generally an optimization, but it also |
| - | * handles "", which otherwise trips over !list[0] check below. |
| - | * (is this ever happens that we actually get str="" here?) |
| + | memset(ctx, 0, sizeof(*ctx)); |
| + | ctx->pipe = ctx->list_head = new_pipe(); |
| + | /* Create the memory for command, roughly: |
| + | * ctx->pipe->cmds = new struct command; |
| + | * ctx->command = &ctx->pipe->cmds[0]; |
| */ | |
| - | if (!strchr(str, SPECIAL_VAR_SYMBOL) && !strchr(str, '\\')) { |
| - | //TODO: Can use on strings with \ too, just unbackslash() them? |
| - | debug_printf_expand("string_to_string(fast)='%s'\n", str); |
| - | return xstrdup(str); |
| - | } |
| - | |
| - | argv[0] = (char*)str; |
| - | argv[1] = NULL; |
| - | list = expand_variables(argv, EXPVAR_FLAG_ESCAPE_VARS | EXPVAR_FLAG_SINGLEWORD); |
| - | if (HUSH_DEBUG) |
| - | if (!list[0] || list[1]) |
| - | bb_error_msg_and_die("BUG in varexp2"); |
| - | /* actually, just move string 2*sizeof(char*) bytes back */ |
| - | overlapping_strcpy((char*)list, list[0]); |
| - | unbackslash((char*)list); |
| - | debug_printf_expand("string_to_string='%s'\n", (char*)list); |
| - | return (char*)list; |
| + | done_command(ctx); |
| } | |
| - | /* Used for "eval" builtin */ |
| - | static char* expand_strvec_to_string(char **argv) |
| - | { |
| - | char **list; |
| - | |
| - | list = expand_variables(argv, EXPVAR_FLAG_SINGLEWORD); |
| - | /* Convert all NULs to spaces */ |
| - | if (list[0]) { |
| - | int n = 1; |
| - | while (list[n]) { |
| - | if (HUSH_DEBUG) |
| - | if (list[n-1] + strlen(list[n-1]) + 1 != list[n]) |
| - | bb_error_msg_and_die("BUG in varexp3"); |
| - | /* bash uses ' ' regardless of $IFS contents */ |
| - | list[n][-1] = ' '; |
| - | n++; |
| - | } |
| - | } |
| - | overlapping_strcpy((char*)list, list[0]); |
| - | debug_printf_expand("strvec_to_string='%s'\n", (char*)list); |
| - | return (char*)list; |
| - | } |
| + | /* If a reserved word is found and processed, parse context is modified |
| + | * and 1 is returned. |
| + | */ |
| + | #if HAS_KEYWORDS |
| + | struct reserved_combo { |
| + | char literal[6]; |
| + | unsigned char res; |
| + | unsigned char assignment_flag; |
| + | int flag; |
| + | }; |
| + | enum { |
| + | FLAG_END = (1 << RES_NONE ), |
| + | # if ENABLE_HUSH_IF |
| + | FLAG_IF = (1 << RES_IF ), |
| + | FLAG_THEN = (1 << RES_THEN ), |
| + | FLAG_ELIF = (1 << RES_ELIF ), |
| + | FLAG_ELSE = (1 << RES_ELSE ), |
| + | FLAG_FI = (1 << RES_FI ), |
| + | # endif |
| + | # if ENABLE_HUSH_LOOPS |
| + | FLAG_FOR = (1 << RES_FOR ), |
| + | FLAG_WHILE = (1 << RES_WHILE), |
| + | FLAG_UNTIL = (1 << RES_UNTIL), |
| + | FLAG_DO = (1 << RES_DO ), |
| + | FLAG_DONE = (1 << RES_DONE ), |
| + | FLAG_IN = (1 << RES_IN ), |
| + | # endif |
| + | # if ENABLE_HUSH_CASE |
| + | FLAG_MATCH = (1 << RES_MATCH), |
| + | FLAG_ESAC = (1 << RES_ESAC ), |
| + | # endif |
| + | FLAG_START = (1 << RES_XXXX ), |
| + | }; |
| - | static char **expand_assignments(char **argv, int count) |
| + | static const struct reserved_combo* match_reserved_word(o_string *word) |
| { | |
| - | int i; |
| - | char **p; |
| + | /* Mostly a list of accepted follow-up reserved words. |
| + | * FLAG_END means we are done with the sequence, and are ready |
| + | * to turn the compound list into a command. |
| + | * FLAG_START means the word must start a new compound list. |
| + | */ |
| + | static const struct reserved_combo reserved_list[] = { |
| + | # if ENABLE_HUSH_IF |
| + | { "!", RES_NONE, NOT_ASSIGNMENT , 0 }, |
| + | { "if", RES_IF, WORD_IS_KEYWORD, FLAG_THEN | FLAG_START }, |
| + | { "then", RES_THEN, WORD_IS_KEYWORD, FLAG_ELIF | FLAG_ELSE | FLAG_FI }, |
| + | { "elif", RES_ELIF, WORD_IS_KEYWORD, FLAG_THEN }, |
| + | { "else", RES_ELSE, WORD_IS_KEYWORD, FLAG_FI }, |
| + | { "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END }, |
| + | # endif |
| + | # if ENABLE_HUSH_LOOPS |
| + | { "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START }, |
| + | { "while", RES_WHILE, WORD_IS_KEYWORD, FLAG_DO | FLAG_START }, |
| + | { "until", RES_UNTIL, WORD_IS_KEYWORD, FLAG_DO | FLAG_START }, |
| + | { "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO }, |
| + | { "do", RES_DO, WORD_IS_KEYWORD, FLAG_DONE }, |
| + | { "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END }, |
| + | # endif |
| + | # if ENABLE_HUSH_CASE |
| + | { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START }, |
| + | { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END }, |
| + | # endif |
| + | }; |
| + | const struct reserved_combo *r; |
| - | 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])); |
| + | for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) { |
| + | if (strcmp(word->data, r->literal) == 0) |
| + | return r; |
| } | |
| - | G.expanded_assignments = NULL; |
| - | return p; |
| + | return NULL; |
| } | |
| - | |
| - | |
| - | #if BB_MMU |
| - | /* never called */ |
| - | void re_execute_shell(char ***to_free, const char *s, |
| - | char *g_argv0, char **g_argv, |
| - | char **builtin_argv) NORETURN; |
| - | |
| - | static void reset_traps_to_defaults(void) |
| + | /* Return 0: not a keyword, 1: keyword |
| + | */ |
| + | static int reserved_word(o_string *word, struct parse_context *ctx) |
| { | |
| - | /* This function is always called in a child shell |
| - | * after fork (not vfork, NOMMU doesn't use this function). |
| - | */ |
| - | unsigned sig; |
| - | unsigned mask; |
| + | # if ENABLE_HUSH_CASE |
| + | static const struct reserved_combo reserved_match = { |
| + | "", RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC |
| + | }; |
| + | # endif |
| + | const struct reserved_combo *r; |
| - | /* Child shells are not interactive. |
| - | * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling. |
| - | * Testcase: (while :; do :; done) + ^Z should background. |
| - | * Same goes for SIGTERM, SIGHUP, SIGINT. |
| - | */ |
| - | if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS)) |
| - | return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */ |
| + | if (word->has_quoted_part) |
| + | return 0; |
| + | r = match_reserved_word(word); |
| + | if (!r) |
| + | return 0; |
| - | /* Switching off SPECIAL_INTERACTIVE_SIGS. |
| - | * Stupid. It can be done with *single* &= op, but we can't use |
| - | * the fact that G.blocked_set is implemented as a bitmask |
| - | * in libc... */ |
| - | mask = (SPECIAL_INTERACTIVE_SIGS >> 1); |
| - | sig = 1; |
| - | while (1) { |
| - | if (mask & 1) { |
| - | /* Careful. Only if no trap or trap is not "" */ |
| - | if (!G.traps || !G.traps[sig] || G.traps[sig][0]) |
| - | sigdelset(&G.blocked_set, sig); |
| + | debug_printf("found reserved word %s, res %d\n", r->literal, r->res); |
| + | # if ENABLE_HUSH_CASE |
| + | if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE_IN) { |
| + | /* "case word IN ..." - IN part starts first MATCH part */ |
| + | r = &reserved_match; |
| + | } else |
| + | # endif |
| + | if (r->flag == 0) { /* '!' */ |
| + | if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */ |
| + | syntax_error("! ! command"); |
| + | ctx->ctx_res_w = RES_SNTX; |
| } | |
| - | mask >>= 1; |
| - | if (!mask) |
| - | break; |
| - | sig++; |
| + | ctx->ctx_inverted = 1; |
| + | return 1; |
| + | } |
| + | if (r->flag & FLAG_START) { |
| + | struct parse_context *old; |
| + | |
| + | old = xmalloc(sizeof(*old)); |
| + | debug_printf_parse("push stack %p\n", old); |
| + | *old = *ctx; /* physical copy */ |
| + | 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); |
| + | ctx->ctx_res_w = RES_SNTX; |
| + | return 1; |
| + | } else { |
| + | /* "{...} fi" is ok. "{...} if" is not |
| + | * Example: |
| + | * if { echo foo; } then { echo bar; } fi */ |
| + | if (ctx->command->group) |
| + | done_pipe(ctx, PIPE_SEQ); |
| } | |
| - | /* Our homegrown sig mask is saner to work with :) */ |
| - | G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS; |
| - | /* Resetting all traps to default except empty ones */ |
| - | mask = G.non_DFL_mask; |
| - | if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) { |
| - | if (!G.traps[sig] || !G.traps[sig][0]) |
| - | continue; |
| - | free(G.traps[sig]); |
| - | G.traps[sig] = NULL; |
| - | /* There is no signal for 0 (EXIT) */ |
| - | if (sig == 0) |
| - | continue; |
| - | /* There was a trap handler, we just removed it. |
| - | * But if sig still has non-DFL handling, |
| - | * we should not unblock the sig. */ |
| - | if (mask & 1) |
| - | continue; |
| - | sigdelset(&G.blocked_set, sig); |
| + | ctx->ctx_res_w = r->res; |
| + | ctx->old_flag = r->flag; |
| + | word->o_assignment = r->assignment_flag; |
| + | |
| + | if (ctx->old_flag & FLAG_END) { |
| + | struct parse_context *old; |
| + | |
| + | done_pipe(ctx, PIPE_SEQ); |
| + | debug_printf_parse("pop stack %p\n", ctx->stack); |
| + | old = ctx->stack; |
| + | old->command->group = ctx->list_head; |
| + | old->command->cmd_type = CMD_NORMAL; |
| + | # if !BB_MMU |
| + | o_addstr(&old->as_string, ctx->as_string.data); |
| + | o_free_unsafe(&ctx->as_string); |
| + | old->command->group_as_string = xstrdup(old->as_string.data); |
| + | debug_printf_parse("pop, remembering as:'%s'\n", |
| + | old->command->group_as_string); |
| + | # endif |
| + | *ctx = *old; /* physical copy */ |
| + | free(old); |
| } | |
| - | sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); |
| + | return 1; |
| } | |
| + | #endif /* HAS_KEYWORDS */ |
| - | #else /* !BB_MMU */ |
| - | |
| - | static void re_execute_shell(char ***to_free, const char *s, |
| - | char *g_argv0, char **g_argv, |
| - | char **builtin_argv) NORETURN; |
| - | static void re_execute_shell(char ***to_free, const char *s, |
| - | char *g_argv0, char **g_argv, |
| - | char **builtin_argv) |
| + | /* Word is complete, look at it and update parsing context. |
| + | * 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) |
| { | |
| - | # define NOMMU_HACK_FMT ("-$%x:%x:%x:%x:%x:%llx" IF_HUSH_LOOPS(":%x")) |
| - | /* delims + 2 * (number of bytes in printed hex numbers) */ |
| - | char param_buf[sizeof(NOMMU_HACK_FMT) + 2 * (sizeof(int)*6 + sizeof(long long)*1)]; |
| - | char *heredoc_argv[4]; |
| - | struct variable *cur; |
| - | # if ENABLE_HUSH_FUNCTIONS |
| - | struct function *funcp; |
| - | # endif |
| - | char **argv, **pp; |
| - | unsigned cnt; |
| - | unsigned long long empty_trap_mask; |
| + | struct command *command = ctx->command; |
| - | if (!g_argv0) { /* heredoc */ |
| - | argv = heredoc_argv; |
| - | argv[0] = (char *) G.argv0_for_re_execing; |
| - | argv[1] = (char *) "-<"; |
| - | argv[2] = (char *) s; |
| - | argv[3] = NULL; |
| - | pp = &argv[3]; /* used as pointer to empty environment */ |
| - | goto do_exec; |
| + | 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 return 0: true null, ignored\n"); |
| + | return 0; |
| } | |
| - | cnt = 0; |
| - | pp = builtin_argv; |
| - | if (pp) while (*pp++) |
| - | cnt++; |
| + | if (ctx->pending_redirect) { |
| + | /* We do not glob in e.g. >*.tmp case. bash seems to glob here |
| + | * only if run as "bash", not "sh" */ |
| + | /* http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html |
| + | * "2.7 Redirection |
| + | * ...the word that follows the redirection operator |
| + | * shall be subjected to tilde expansion, parameter expansion, |
| + | * command substitution, arithmetic expansion, and quote |
| + | * removal. Pathname expansion shall not be performed |
| + | * on the word by a non-interactive shell; an interactive |
| + | * shell may perform it, but shall do so only when |
| + | * the expansion would result in one word." |
| + | */ |
| + | ctx->pending_redirect->rd_filename = xstrdup(word->data); |
| + | /* Cater for >\file case: |
| + | * >\a creates file a; >\\a, >"\a", >"\\a" create file \a |
| + | * Same with heredocs: |
| + | * for <<\H delim is H; <<\\H, <<"\H", <<"\\H" - \H |
| + | */ |
| + | if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) { |
| + | unbackslash(ctx->pending_redirect->rd_filename); |
| + | /* Is it <<"HEREDOC"? */ |
| + | if (word->has_quoted_part) { |
| + | ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED; |
| + | } |
| + | } |
| + | debug_printf_parse("word stored in rd_filename: '%s'\n", word->data); |
| + | ctx->pending_redirect = NULL; |
| + | } else { |
| + | /* 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 |
| + | ) { |
| + | word->o_assignment = NOT_ASSIGNMENT; |
| + | } else { |
| + | if (word->o_assignment == DEFINITELY_ASSIGNMENT) |
| + | command->assignment_cnt++; |
| + | word->o_assignment = MAYBE_ASSIGNMENT; |
| + | } |
| - | empty_trap_mask = 0; |
| - | if (G.traps) { |
| - | int sig; |
| - | for (sig = 1; sig < NSIG; sig++) { |
| - | if (G.traps[sig] && !G.traps[sig][0]) |
| - | empty_trap_mask |= 1LL << sig; |
| + | #if HAS_KEYWORDS |
| + | # if ENABLE_HUSH_CASE |
| + | if (ctx->ctx_dsemicolon |
| + | && strcmp(word->data, "esac") != 0 /* not "... pattern) cmd;; esac" */ |
| + | ) { |
| + | /* already done when ctx_dsemicolon was set to 1: */ |
| + | /* ctx->ctx_res_w = RES_MATCH; */ |
| + | ctx->ctx_dsemicolon = 0; |
| + | } else |
| + | # endif |
| + | if (!command->argv /* if it's the first word... */ |
| + | # if ENABLE_HUSH_LOOPS |
| + | && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */ |
| + | && ctx->ctx_res_w != RES_IN |
| + | # endif |
| + | # if ENABLE_HUSH_CASE |
| + | && ctx->ctx_res_w != RES_CASE |
| + | # endif |
| + | ) { |
| + | debug_printf_parse("checking '%s' for reserved-ness\n", word->data); |
| + | if (reserved_word(word, ctx)) { |
| + | o_reset_to_empty_unquoted(word); |
| + | debug_printf_parse("done_word return %d\n", |
| + | (ctx->ctx_res_w == RES_SNTX)); |
| + | return (ctx->ctx_res_w == RES_SNTX); |
| + | } |
| + | # ifdef CMD_SINGLEWORD_NOGLOB_COND |
| + | if (strcmp(word->data, "export") == 0 |
| + | # if ENABLE_HUSH_LOCAL |
| + | || strcmp(word->data, "local") == 0 |
| + | # endif |
| + | ) { |
| + | command->cmd_type = CMD_SINGLEWORD_NOGLOB_COND; |
| + | } else |
| + | # endif |
| + | # if ENABLE_HUSH_BASH_COMPAT |
| + | if (strcmp(word->data, "[[") == 0) { |
| + | command->cmd_type = CMD_SINGLEWORD_NOGLOB; |
| + | } |
| + | /* fall through */ |
| + | # endif |
| + | } |
| + | #endif |
| + | if (command->group) { |
| + | /* "{ echo foo; } echo bar" - bad */ |
| + | syntax_error_at(word->data); |
| + | debug_printf_parse("done_word return 1: syntax error, " |
| + | "groups and arglists don't mix\n"); |
| + | return 1; |
| } | |
| + | if (word->has_quoted_part |
| + | /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */ |
| + | && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL) |
| + | /* (otherwise it's known to be not empty and is already safe) */ |
| + | ) { |
| + | /* exclude "$@" - it can expand to no word despite "" */ |
| + | char *p = word->data; |
| + | while (p[0] == SPECIAL_VAR_SYMBOL |
| + | && (p[1] & 0x7f) == '@' |
| + | && p[2] == SPECIAL_VAR_SYMBOL |
| + | ) { |
| + | p += 3; |
| + | } |
| + | if (p == word->data || p[0] != '\0') { |
| + | /* saw no "$@", or not only "$@" but some |
| + | * real text is there too */ |
| + | /* insert "empty variable" reference, this makes |
| + | * e.g. "", $empty"" etc to not disappear */ |
| + | o_addchr(word, SPECIAL_VAR_SYMBOL); |
| + | o_addchr(word, SPECIAL_VAR_SYMBOL); |
| + | } |
| + | } |
| + | command->argv = add_string_to_strings(command->argv, xstrdup(word->data)); |
| + | debug_print_strings("word appended to argv", command->argv); |
| } | |
| - | sprintf(param_buf, NOMMU_HACK_FMT |
| - | , (unsigned) G.root_pid |
| - | , (unsigned) G.root_ppid |
| - | , (unsigned) G.last_bg_pid |
| - | , (unsigned) G.last_exitcode |
| - | , cnt |
| - | , empty_trap_mask |
| - | IF_HUSH_LOOPS(, G.depth_of_loop) |
| - | ); |
| - | # undef NOMMU_HACK_FMT |
| - | /* 1:hush 2:-$<pid>:<pid>:<exitcode>:<etc...> <vars...> <funcs...> |
| - | * 3:-c 4:<cmd> 5:<arg0> <argN...> 6:NULL |
| - | */ |
| - | cnt += 6; |
| - | for (cur = G.top_var; cur; cur = cur->next) { |
| - | if (!cur->flg_export || cur->flg_read_only) |
| - | cnt += 2; |
| - | } |
| - | # if ENABLE_HUSH_FUNCTIONS |
| - | for (funcp = G.top_func; funcp; funcp = funcp->next) |
| - | cnt += 3; |
| - | # endif |
| - | pp = g_argv; |
| - | while (*pp++) |
| - | cnt++; |
| - | *to_free = argv = pp = xzalloc(sizeof(argv[0]) * cnt); |
| - | *pp++ = (char *) G.argv0_for_re_execing; |
| - | *pp++ = param_buf; |
| - | for (cur = G.top_var; cur; cur = cur->next) { |
| - | if (strcmp(cur->varstr, hush_version_str) == 0) |
| - | continue; |
| - | if (cur->flg_read_only) { |
| - | *pp++ = (char *) "-R"; |
| - | *pp++ = cur->varstr; |
| - | } else if (!cur->flg_export) { |
| - | *pp++ = (char *) "-V"; |
| - | *pp++ = cur->varstr; |
| + | #if ENABLE_HUSH_LOOPS |
| + | if (ctx->ctx_res_w == RES_FOR) { |
| + | if (word->has_quoted_part |
| + | || !is_well_formed_var_name(command->argv[0], '\0') |
| + | ) { |
| + | /* bash says just "not a valid identifier" */ |
| + | syntax_error("not a valid identifier in for"); |
| + | return 1; |
| } | |
| + | /* Force FOR to have just one word (variable name) */ |
| + | /* NB: basically, this makes hush see "for v in ..." |
| + | * syntax as if it is "for v; in ...". FOR and IN become |
| + | * two pipe structs in parse tree. */ |
| + | done_pipe(ctx, PIPE_SEQ); |
| } | |
| - | # if ENABLE_HUSH_FUNCTIONS |
| - | for (funcp = G.top_func; funcp; funcp = funcp->next) { |
| - | *pp++ = (char *) "-F"; |
| - | *pp++ = funcp->name; |
| - | *pp++ = funcp->body_as_string; |
| - | } |
| - | # endif |
| - | /* We can pass activated traps here. Say, -Tnn:trap_string |
| - | * |
| - | * However, POSIX says that subshells reset signals with traps |
| - | * to SIG_DFL. |
| - | * I tested bash-3.2 and it not only does that with true subshells |
| - | * of the form ( list ), but with any forked children shells. |
| - | * I set trap "echo W" WINCH; and then tried: |
| - | * |
| - | * { echo 1; sleep 20; echo 2; } & |
| - | * while true; do echo 1; sleep 20; echo 2; break; done & |
| - | * true | { echo 1; sleep 20; echo 2; } | cat |
| - | * |
| - | * In all these cases sending SIGWINCH to the child shell |
| - | * did not run the trap. If I add trap "echo V" WINCH; |
| - | * _inside_ group (just before echo 1), it works. |
| - | * |
| - | * I conclude it means we don't need to pass active traps here. |
| - | * Even if we would use signal handlers instead of signal masking |
| - | * in order to implement trap handling, |
| - | * exec syscall below resets signals to SIG_DFL for us. |
| - | */ |
| - | *pp++ = (char *) "-c"; |
| - | *pp++ = (char *) s; |
| - | if (builtin_argv) { |
| - | while (*++builtin_argv) |
| - | *pp++ = *builtin_argv; |
| - | *pp++ = (char *) ""; |
| + | #endif |
| + | #if ENABLE_HUSH_CASE |
| + | /* Force CASE to have just one word */ |
| + | if (ctx->ctx_res_w == RES_CASE) { |
| + | done_pipe(ctx, PIPE_SEQ); |
| } | |
| - | *pp++ = g_argv0; |
| - | while (*g_argv) |
| - | *pp++ = *g_argv++; |
| - | /* *pp = NULL; - is already there */ |
| - | pp = environ; |
| + | #endif |
| - | do_exec: |
| - | debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s); |
| - | sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); |
| - | execve(bb_busybox_exec_path, argv, pp); |
| - | /* Fallback. Useful for init=/bin/hush usage etc */ |
| - | if (argv[0][0] == '/') |
| - | execve(argv[0], argv, pp); |
| - | xfunc_error_retval = 127; |
| - | bb_error_msg_and_die("can't re-execute the shell"); |
| + | o_reset_to_empty_unquoted(word); |
| + | |
| + | debug_printf_parse("done_word return 0\n"); |
| + | return 0; |
| } | |
| - | #endif /* !BB_MMU */ |
| - | static void setup_heredoc(struct redir_struct *redir) |
| - | { |
| - | struct fd_pair pair; |
| - | pid_t pid; |
| - | int len, written; |
| - | /* the _body_ of heredoc (misleading field name) */ |
| - | const char *heredoc = redir->rd_filename; |
| - | char *expanded; |
| - | #if !BB_MMU |
| - | char **to_free; |
| + | /* Peek ahead in the input to find out if we have a "&n" construct, |
| + | * as in "2>&1", that represents duplicating a file descriptor. |
| + | * Return: |
| + | * REDIRFD_CLOSE if >&- "close fd" construct is seen, |
| + | * REDIRFD_SYNTAX_ERR if syntax error, |
| + | * REDIRFD_TO_FILE if no & was seen, |
| + | * or the number found. |
| + | */ |
| + | #if BB_MMU |
| + | #define parse_redir_right_fd(as_string, input) \ |
| + | parse_redir_right_fd(input) |
| #endif | |
| + | static int parse_redir_right_fd(o_string *as_string, struct in_str *input) |
| + | { |
| + | int ch, d, ok; |
| - | expanded = NULL; |
| - | if (!(redir->rd_dup & HEREDOC_QUOTED)) { |
| - | expanded = expand_pseudo_dquoted(heredoc); |
| - | if (expanded) |
| - | heredoc = expanded; |
| + | ch = i_peek(input); |
| + | if (ch != '&') |
| + | return REDIRFD_TO_FILE; |
| + | |
| + | ch = i_getch(input); /* get the & */ |
| + | nommu_addchr(as_string, ch); |
| + | ch = i_peek(input); |
| + | if (ch == '-') { |
| + | ch = i_getch(input); |
| + | nommu_addchr(as_string, ch); |
| + | return REDIRFD_CLOSE; |
| } | |
| - | len = strlen(heredoc); |
| + | d = 0; |
| + | ok = 0; |
| + | while (ch != EOF && isdigit(ch)) { |
| + | d = d*10 + (ch-'0'); |
| + | ok = 1; |
| + | ch = i_getch(input); |
| + | nommu_addchr(as_string, ch); |
| + | ch = i_peek(input); |
| + | } |
| + | if (ok) return d; |
| - | close(redir->rd_fd); /* often saves dup2+close in xmove_fd */ |
| - | xpiped_pair(pair); |
| - | xmove_fd(pair.rd, redir->rd_fd); |
| + | //TODO: this is the place to catch ">&file" bashism (redirect both fd 1 and 2) |
| - | /* Try writing without forking. Newer kernels have |
| - | * dynamically growing pipes. Must use non-blocking write! */ |
| - | ndelay_on(pair.wr); |
| - | while (1) { |
| - | written = write(pair.wr, heredoc, len); |
| - | if (written <= 0) |
| - | break; |
| - | len -= written; |
| - | if (len == 0) { |
| - | close(pair.wr); |
| - | free(expanded); |
| - | return; |
| + | bb_error_msg("ambiguous redirect"); |
| + | return REDIRFD_SYNTAX_ERR; |
| + | } |
| + | |
| + | /* Return code is 0 normal, 1 if a syntax error is detected |
| + | */ |
| + | static int parse_redirect(struct parse_context *ctx, |
| + | int fd, |
| + | redir_type style, |
| + | struct in_str *input) |
| + | { |
| + | struct command *command = ctx->command; |
| + | struct redir_struct *redir; |
| + | struct redir_struct **redirp; |
| + | int dup_num; |
| + | |
| + | dup_num = REDIRFD_TO_FILE; |
| + | if (style != REDIRECT_HEREDOC) { |
| + | /* Check for a '>&1' type redirect */ |
| + | dup_num = parse_redir_right_fd(&ctx->as_string, input); |
| + | if (dup_num == REDIRFD_SYNTAX_ERR) |
| + | return 1; |
| + | } else { |
| + | int ch = i_peek(input); |
| + | dup_num = (ch == '-'); /* HEREDOC_SKIPTABS bit is 1 */ |
| + | if (dup_num) { /* <<-... */ |
| + | ch = i_getch(input); |
| + | nommu_addchr(&ctx->as_string, ch); |
| + | ch = i_peek(input); |
| } | |
| - | heredoc += written; |
| } | |
| - | ndelay_off(pair.wr); |
| - | /* Okay, pipe buffer was not big enough */ |
| - | /* Note: we must not create a stray child (bastard? :) |
| - | * for the unsuspecting parent process. Child creates a grandchild |
| - | * and exits before parent execs the process which consumes heredoc |
| - | * (that exec happens after we return from this function) */ |
| - | #if !BB_MMU |
| - | to_free = NULL; |
| - | #endif |
| - | pid = xvfork(); |
| - | if (pid == 0) { |
| - | /* child */ |
| - | disable_restore_tty_pgrp_on_exit(); |
| - | pid = BB_MMU ? xfork() : xvfork(); |
| - | if (pid != 0) |
| - | _exit(0); |
| - | /* grandchild */ |
| - | close(redir->rd_fd); /* read side of the pipe */ |
| + | if (style == REDIRECT_OVERWRITE && dup_num == REDIRFD_TO_FILE) { |
| + | int ch = i_peek(input); |
| + | if (ch == '|') { |
| + | /* >|FILE redirect ("clobbering" >). |
| + | * Since we do not support "set -o noclobber" yet, |
| + | * >| and > are the same for now. Just eat |. |
| + | */ |
| + | ch = i_getch(input); |
| + | nommu_addchr(&ctx->as_string, ch); |
| + | } |
| + | } |
| + | |
| + | /* Create a new redir_struct and append it to the linked list */ |
| + | redirp = &command->redirects; |
| + | while ((redir = *redirp) != NULL) { |
| + | redirp = &(redir->next); |
| + | } |
| + | *redirp = redir = xzalloc(sizeof(*redir)); |
| + | /* redir->next = NULL; */ |
| + | /* redir->rd_filename = NULL; */ |
| + | redir->rd_type = style; |
| + | redir->rd_fd = (fd == -1) ? redir_table[style].default_fd : fd; |
| + | |
| + | debug_printf_parse("redirect type %d %s\n", redir->rd_fd, |
| + | redir_table[style].descrip); |
| + | |
| + | redir->rd_dup = dup_num; |
| + | if (style != REDIRECT_HEREDOC && dup_num != REDIRFD_TO_FILE) { |
| + | /* Erik had a check here that the file descriptor in question |
| + | * is legit; I postpone that to "run time" |
| + | * A "-" representation of "close me" shows up as a -3 here */ |
| + | debug_printf_parse("duplicating redirect '%d>&%d'\n", |
| + | redir->rd_fd, redir->rd_dup); |
| + | } else { |
| + | /* Set ctx->pending_redirect, so we know what to do at the |
| + | * end of the next parsed word. */ |
| + | ctx->pending_redirect = redir; |
| + | } |
| + | return 0; |
| + | } |
| + | |
| + | /* If a redirect is immediately preceded by a number, that number is |
| + | * supposed to tell which file descriptor to redirect. This routine |
| + | * looks for such preceding numbers. In an ideal world this routine |
| + | * needs to handle all the following classes of redirects... |
| + | * echo 2>foo # redirects fd 2 to file "foo", nothing passed to echo |
| + | * echo 49>foo # redirects fd 49 to file "foo", nothing passed to echo |
| + | * echo -2>foo # redirects fd 1 to file "foo", "-2" passed to echo |
| + | * echo 49x>foo # redirects fd 1 to file "foo", "49x" passed to echo |
| + | * |
| + | * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html |
| + | * "2.7 Redirection |
| + | * ... If n is quoted, the number shall not be recognized as part of |
| + | * the redirection expression. For example: |
| + | * echo \2>a |
| + | * writes the character 2 into file a" |
| + | * We are getting it right by setting ->has_quoted_part on any \<char> |
| + | * |
| + | * A -1 return means no valid number was found, |
| + | * the caller should use the appropriate default for this redirection. |
| + | */ |
| + | static int redirect_opt_num(o_string *o) |
| + | { |
| + | int num; |
| + | |
| + | if (o->data == NULL) |
| + | return -1; |
| + | num = bb_strtou(o->data, NULL, 10); |
| + | if (errno || num < 0) |
| + | return -1; |
| + | o_reset_to_empty_unquoted(o); |
| + | return num; |
| + | } |
| + | |
| #if BB_MMU | |
| - | full_write(pair.wr, heredoc, len); /* may loop or block */ |
| - | _exit(0); |
| - | #else |
| - | /* Delegate blocking writes to another process */ |
| - | xmove_fd(pair.wr, STDOUT_FILENO); |
| - | re_execute_shell(&to_free, heredoc, NULL, NULL, NULL); |
| + | #define fetch_till_str(as_string, input, word, skip_tabs) \ |
| + | fetch_till_str(input, word, skip_tabs) |
| #endif | |
| + | static char *fetch_till_str(o_string *as_string, |
| + | struct in_str *input, |
| + | const char *word, |
| + | int skip_tabs) |
| + | { |
| + | o_string heredoc = NULL_O_STRING; |
| + | int past_EOL = 0; |
| + | int ch; |
| + | |
| + | goto jump_in; |
| + | while (1) { |
| + | ch = i_getch(input); |
| + | nommu_addchr(as_string, ch); |
| + | if (ch == '\n') { |
| + | if (strcmp(heredoc.data + past_EOL, word) == 0) { |
| + | heredoc.data[past_EOL] = '\0'; |
| + | debug_printf_parse("parsed heredoc '%s'\n", heredoc.data); |
| + | return heredoc.data; |
| + | } |
| + | do { |
| + | o_addchr(&heredoc, ch); |
| + | past_EOL = heredoc.length; |
| + | jump_in: |
| + | do { |
| + | ch = i_getch(input); |
| + | nommu_addchr(as_string, ch); |
| + | } while (skip_tabs && ch == '\t'); |
| + | } while (ch == '\n'); |
| + | } |
| + | if (ch == EOF) { |
| + | o_free_unsafe(&heredoc); |
| + | return NULL; |
| + | } |
| + | o_addchr(&heredoc, ch); |
| + | nommu_addchr(as_string, ch); |
| } | |
| - | /* parent */ |
| - | #if ENABLE_HUSH_FAST |
| - | G.count_SIGCHLD++; |
| - | //bb_error_msg("[%d] fork in setup_heredoc: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); |
| - | #endif |
| - | enable_restore_tty_pgrp_on_exit(); |
| - | #if !BB_MMU |
| - | free(to_free); |
| - | #endif |
| - | close(pair.wr); |
| - | free(expanded); |
| - | wait(NULL); /* wait till child has died */ |
| } | |
| - | /* squirrel != NULL means we squirrel away copies of stdin, stdout, |
| - | * and stderr if they are redirected. */ |
| - | static int setup_redirects(struct command *prog, int squirrel[]) |
| + | /* Look at entire parse tree for not-yet-loaded REDIRECT_HEREDOCs |
| + | * and load them all. There should be exactly heredoc_cnt of them. |
| + | */ |
| + | static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_str *input) |
| { | |
| - | int openfd, mode; |
| - | struct redir_struct *redir; |
| + | struct pipe *pi = ctx->list_head; |
| - | for (redir = prog->redirects; redir; redir = redir->next) { |
| - | if (redir->rd_type == REDIRECT_HEREDOC2) { |
| - | /* rd_fd<<HERE case */ |
| - | if (squirrel && redir->rd_fd < 3 |
| - | && squirrel[redir->rd_fd] < 0 |
| - | ) { |
| - | squirrel[redir->rd_fd] = dup(redir->rd_fd); |
| - | } |
| - | /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ |
| - | * of the heredoc */ |
| - | debug_printf_parse("set heredoc '%s'\n", |
| - | redir->rd_filename); |
| - | setup_heredoc(redir); |
| - | continue; |
| - | } |
| + | while (pi && heredoc_cnt) { |
| + | int i; |
| + | struct command *cmd = pi->cmds; |
| - | if (redir->rd_dup == REDIRFD_TO_FILE) { |
| - | /* rd_fd<*>file case (<*> is <,>,>>,<>) */ |
| - | char *p; |
| - | if (redir->rd_filename == NULL) { |
| - | /* Something went wrong in the parse. |
| - | * Pretend it didn't happen */ |
| - | bb_error_msg("bug in redirect parse"); |
| - | continue; |
| - | } |
| - | mode = redir_table[redir->rd_type].mode; |
| - | p = expand_string_to_string(redir->rd_filename); |
| - | openfd = open_or_warn(p, mode); |
| - | free(p); |
| - | if (openfd < 0) { |
| - | /* this could get lost if stderr has been redirected, but |
| - | * bash and ash both lose it as well (though zsh doesn't!) */ |
| - | //what the above comment tries to say? |
| - | return 1; |
| - | } |
| - | } else { |
| - | /* rd_fd<*>rd_dup or rd_fd<*>- cases */ |
| - | openfd = redir->rd_dup; |
| - | } |
| + | debug_printf_parse("fetch_heredocs: num_cmds:%d cmd argv0:'%s'\n", |
| + | pi->num_cmds, |
| + | cmd->argv ? cmd->argv[0] : "NONE"); |
| + | for (i = 0; i < pi->num_cmds; i++) { |
| + | struct redir_struct *redir = cmd->redirects; |
| - | if (openfd != redir->rd_fd) { |
| - | if (squirrel && redir->rd_fd < 3 |
| - | && squirrel[redir->rd_fd] < 0 |
| - | ) { |
| - | squirrel[redir->rd_fd] = dup(redir->rd_fd); |
| - | } |
| - | if (openfd == REDIRFD_CLOSE) { |
| - | /* "n>-" means "close me" */ |
| - | close(redir->rd_fd); |
| - | } else { |
| - | xdup2(openfd, redir->rd_fd); |
| - | if (redir->rd_dup == REDIRFD_TO_FILE) |
| - | close(openfd); |
| + | debug_printf_parse("fetch_heredocs: %d cmd argv0:'%s'\n", |
| + | i, cmd->argv ? cmd->argv[0] : "NONE"); |
| + | while (redir) { |
| + | if (redir->rd_type == REDIRECT_HEREDOC) { |
| + | char *p; |
| + | |
| + | redir->rd_type = REDIRECT_HEREDOC2; |
| + | /* redir->rd_dup is (ab)used to indicate <<- */ |
| + | p = fetch_till_str(&ctx->as_string, input, |
| + | redir->rd_filename, redir->rd_dup & HEREDOC_SKIPTABS); |
| + | if (!p) { |
| + | syntax_error("unexpected EOF in here document"); |
| + | return 1; |
| + | } |
| + | free(redir->rd_filename); |
| + | redir->rd_filename = p; |
| + | heredoc_cnt--; |
| + | } |
| + | redir = redir->next; |
| } | |
| + | cmd++; |
| } | |
| + | pi = pi->next; |
| } | |
| + | #if 0 |
| + | /* Should be 0. If it isn't, it's a parse error */ |
| + | if (heredoc_cnt) |
| + | bb_error_msg_and_die("heredoc BUG 2"); |
| + | #endif |
| return 0; | |
| } | |
| - | static void restore_redirects(int squirrel[]) |
| - | { |
| - | int i, fd; |
| - | for (i = 0; i < 3; i++) { |
| - | fd = squirrel[i]; |
| - | if (fd != -1) { |
| - | /* We simply die on error */ |
| - | xmove_fd(fd, i); |
| - | } |
| - | } |
| - | } |
| + | static int run_list(struct pipe *pi); |
| + | #if BB_MMU |
| + | #define parse_stream(pstring, input, end_trigger) \ |
| + | parse_stream(input, end_trigger) |
| + | #endif |
| + | static struct pipe *parse_stream(char **pstring, |
| + | struct in_str *input, |
| + | int end_trigger); |
| - | static void free_pipe_list(struct pipe *head); |
| - | /* Return code is the exit status of the pipe */ |
| - | static void free_pipe(struct pipe *pi) |
| + | #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, |
| + | struct in_str *input, int ch) |
| { | |
| - | char **p; |
| - | struct command *command; |
| - | struct redir_struct *r, *rnext; |
| - | int a, i; |
| + | /* dest contains characters seen prior to ( or {. |
| + | * Typically it's empty, but for function defs, |
| + | * it contains function name (without '()'). */ |
| + | struct pipe *pipe_list; |
| + | int endch; |
| + | struct command *command = ctx->command; |
| - | if (pi->stopped_cmds > 0) /* why? */ |
| - | return; |
| - | debug_printf_clean("run pipe: (pid %d)\n", getpid()); |
| - | for (i = 0; i < pi->num_cmds; i++) { |
| - | command = &pi->cmds[i]; |
| - | debug_printf_clean(" command %d:\n", i); |
| - | if (command->argv) { |
| - | for (a = 0, p = command->argv; *p; a++, p++) { |
| - | debug_printf_clean(" argv[%d] = %s\n", a, *p); |
| - | } |
| - | free_strings(command->argv); |
| - | command->argv = NULL; |
| - | } |
| - | /* not "else if": on syntax error, we may have both! */ |
| - | if (command->group) { |
| - | debug_printf_clean(" begin group (cmd_type:%d)\n", |
| - | command->cmd_type); |
| - | free_pipe_list(command->group); |
| - | debug_printf_clean(" end group\n"); |
| - | command->group = NULL; |
| - | } |
| - | /* else is crucial here. |
| - | * If group != NULL, child_func is meaningless */ |
| + | debug_printf_parse("parse_group entered\n"); |
| #if ENABLE_HUSH_FUNCTIONS | |
| - | else if (command->child_func) { |
| - | debug_printf_exec("cmd %p releases child func at %p\n", command, command->child_func); |
| - | command->child_func->parent_cmd = NULL; |
| + | if (ch == '(' && !dest->has_quoted_part) { |
| + | if (dest->length) |
| + | if (done_word(dest, ctx)) |
| + | return 1; |
| + | if (!command->argv) |
| + | goto skip; /* (... */ |
| + | if (command->argv[1]) { /* word word ... (... */ |
| + | syntax_error_unexpected_ch('('); |
| + | return 1; |
| } | |
| - | #endif |
| - | #if !BB_MMU |
| - | free(command->group_as_string); |
| - | command->group_as_string = NULL; |
| - | #endif |
| - | for (r = command->redirects; r; r = rnext) { |
| - | debug_printf_clean(" redirect %d%s", |
| - | r->rd_fd, redir_table[r->rd_type].descrip); |
| - | /* guard against the case >$FOO, where foo is unset or blank */ |
| - | if (r->rd_filename) { |
| - | debug_printf_clean(" fname:'%s'\n", r->rd_filename); |
| - | free(r->rd_filename); |
| - | r->rd_filename = NULL; |
| - | } |
| - | debug_printf_clean(" rd_dup:%d\n", r->rd_dup); |
| - | rnext = r->next; |
| - | free(r); |
| + | /* it is "word(..." or "word (..." */ |
| + | do |
| + | ch = i_getch(input); |
| + | while (ch == ' ' || ch == '\t'); |
| + | if (ch != ')') { |
| + | syntax_error_unexpected_ch(ch); |
| + | return 1; |
| + | } |
| + | nommu_addchr(&ctx->as_string, ch); |
| + | do |
| + | ch = i_getch(input); |
| + | while (ch == ' ' || ch == '\t' || ch == '\n'); |
| + | if (ch != '{') { |
| + | syntax_error_unexpected_ch(ch); |
| + | return 1; |
| } | |
| - | command->redirects = NULL; |
| + | nommu_addchr(&ctx->as_string, ch); |
| + | command->cmd_type = CMD_FUNCDEF; |
| + | goto skip; |
| } | |
| - | free(pi->cmds); /* children are an array, they get freed all at once */ |
| - | pi->cmds = NULL; |
| - | #if ENABLE_HUSH_JOB |
| - | free(pi->cmdtext); |
| - | pi->cmdtext = NULL; |
| #endif | |
| - | } |
| - | static void free_pipe_list(struct pipe *head) |
| - | { |
| - | struct pipe *pi, *next; |
| + | #if 0 /* Prevented by caller */ |
| + | if (command->argv /* word [word]{... */ |
| + | || dest->length /* word{... */ |
| + | || dest->has_quoted_part /* ""{... */ |
| + | ) { |
| + | syntax_error(NULL); |
| + | debug_printf_parse("parse_group return 1: " |
| + | "syntax error, groups and arglists don't mix\n"); |
| + | return 1; |
| + | } |
| + | #endif |
| - | for (pi = head; pi; pi = next) { |
| - | #if HAS_KEYWORDS |
| - | debug_printf_clean(" pipe reserved word %d\n", pi->res_word); |
| + | #if ENABLE_HUSH_FUNCTIONS |
| + | skip: |
| #endif | |
| - | free_pipe(pi); |
| - | debug_printf_clean("pipe followup code %d\n", pi->followup); |
| - | next = pi->next; |
| - | /*pi->next = NULL;*/ |
| - | free(pi); |
| + | endch = '}'; |
| + | if (ch == '(') { |
| + | endch = ')'; |
| + | command->cmd_type = CMD_SUBSHELL; |
| + | } else { |
| + | /* bash does not allow "{echo...", requires whitespace */ |
| + | ch = i_getch(input); |
| + | if (ch != ' ' && ch != '\t' && ch != '\n') { |
| + | syntax_error_unexpected_ch(ch); |
| + | return 1; |
| + | } |
| + | nommu_addchr(&ctx->as_string, ch); |
| } | |
| - | } |
| - | |
| - | static int run_list(struct pipe *pi); |
| + | { |
| #if BB_MMU | |
| - | #define parse_stream(pstring, input, end_trigger) \ |
| - | parse_stream(input, end_trigger) |
| + | # define as_string NULL |
| + | #else |
| + | char *as_string = NULL; |
| #endif | |
| - | static struct pipe *parse_stream(char **pstring, |
| - | struct in_str *input, |
| - | int end_trigger); |
| - | static void parse_and_run_string(const char *s); |
| - | |
| - | |
| - | static char *find_in_path(const char *arg) |
| - | { |
| - | char *ret = NULL; |
| - | const char *PATH = get_local_var_value("PATH"); |
| - | |
| - | if (!PATH) |
| - | return NULL; |
| - | |
| - | while (1) { |
| - | const char *end = strchrnul(PATH, ':'); |
| - | int sz = end - PATH; /* must be int! */ |
| - | |
| - | free(ret); |
| - | if (sz != 0) { |
| - | ret = xasprintf("%.*s/%s", sz, PATH, arg); |
| - | } else { |
| - | /* We have xxx::yyyy in $PATH, |
| - | * it means "use current dir" */ |
| - | ret = xstrdup(arg); |
| - | } |
| - | if (access(ret, F_OK) == 0) |
| - | break; |
| - | |
| - | if (*end == '\0') { |
| - | free(ret); |
| - | return NULL; |
| + | pipe_list = parse_stream(&as_string, input, endch); |
| + | #if !BB_MMU |
| + | if (as_string) |
| + | o_addstr(&ctx->as_string, as_string); |
| + | #endif |
| + | /* empty ()/{} or parse error? */ |
| + | if (!pipe_list || pipe_list == ERR_PTR) { |
| + | /* parse_stream already emitted error msg */ |
| + | if (!BB_MMU) |
| + | free(as_string); |
| + | debug_printf_parse("parse_group return 1: " |
| + | "parse_stream returned %p\n", pipe_list); |
| + | return 1; |
| } | |
| - | PATH = end + 1; |
| + | command->group = pipe_list; |
| + | #if !BB_MMU |
| + | as_string[strlen(as_string) - 1] = '\0'; /* plink ')' or '}' */ |
| + | command->group_as_string = as_string; |
| + | debug_printf_parse("end of group, remembering as:'%s'\n", |
| + | command->group_as_string); |
| + | #endif |
| + | #undef as_string |
| } | |
| - | |
| - | return ret; |
| + | debug_printf_parse("parse_group return 0\n"); |
| + | return 0; |
| + | /* command remains "open", available for possible redirects */ |
| } | |
| - | static const struct built_in_command* find_builtin_helper(const char *name, |
| - | const struct built_in_command *x, |
| - | const struct built_in_command *end) |
| + | #if ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_DOLLAR_OPS |
| + | /* Subroutines for copying $(...) and `...` things */ |
| + | static void add_till_backquote(o_string *dest, struct in_str *input); |
| + | /* '...' */ |
| + | static void add_till_single_quote(o_string *dest, struct in_str *input) |
| { | |
| - | while (x != end) { |
| - | if (strcmp(name, x->b_cmd) != 0) { |
| - | x++; |
| - | continue; |
| + | while (1) { |
| + | int ch = i_getch(input); |
| + | if (ch == EOF) { |
| + | syntax_error_unterm_ch('\''); |
| + | /*xfunc_die(); - redundant */ |
| } | |
| - | debug_printf_exec("found builtin '%s'\n", name); |
| - | return x; |
| + | if (ch == '\'') |
| + | return; |
| + | o_addchr(dest, ch); |
| } | |
| - | return NULL; |
| - | } |
| - | static const struct built_in_command* find_builtin1(const char *name) |
| - | { |
| - | return find_builtin_helper(name, bltins1, &bltins1[ARRAY_SIZE(bltins1)]); |
| - | } |
| - | static const struct built_in_command* find_builtin(const char *name) |
| - | { |
| - | const struct built_in_command *x = find_builtin1(name); |
| - | if (x) |
| - | return x; |
| - | return find_builtin_helper(name, bltins2, &bltins2[ARRAY_SIZE(bltins2)]); |
| } | |
| - | |
| - | #if ENABLE_HUSH_FUNCTIONS |
| - | static struct function **find_function_slot(const char *name) |
| + | /* "...\"...`..`...." - do we need to handle "...$(..)..." too? */ |
| + | static void add_till_double_quote(o_string *dest, struct in_str *input) |
| { | |
| - | struct function **funcpp = &G.top_func; |
| - | while (*funcpp) { |
| - | if (strcmp(name, (*funcpp)->name) == 0) { |
| - | break; |
| + | while (1) { |
| + | int ch = i_getch(input); |
| + | if (ch == EOF) { |
| + | syntax_error_unterm_ch('"'); |
| + | /*xfunc_die(); - redundant */ |
| } | |
| - | funcpp = &(*funcpp)->next; |
| - | } |
| - | return funcpp; |
| - | } |
| - | |
| - | static const struct function *find_function(const char *name) |
| - | { |
| - | const struct function *funcp = *find_function_slot(name); |
| - | if (funcp) |
| - | debug_printf_exec("found function '%s'\n", name); |
| - | return funcp; |
| - | } |
| - | |
| - | /* Note: takes ownership on name ptr */ |
| - | static struct function *new_function(char *name) |
| - | { |
| - | struct function **funcpp = find_function_slot(name); |
| - | struct function *funcp = *funcpp; |
| - | |
| - | if (funcp != NULL) { |
| - | struct command *cmd = funcp->parent_cmd; |
| - | debug_printf_exec("func %p parent_cmd %p\n", funcp, cmd); |
| - | if (!cmd) { |
| - | debug_printf_exec("freeing & replacing function '%s'\n", funcp->name); |
| - | free(funcp->name); |
| - | /* Note: if !funcp->body, do not free body_as_string! |
| - | * This is a special case of "-F name body" function: |
| - | * body_as_string was not malloced! */ |
| - | if (funcp->body) { |
| - | free_pipe_list(funcp->body); |
| - | # if !BB_MMU |
| - | free(funcp->body_as_string); |
| - | # endif |
| - | } |
| - | } else { |
| - | debug_printf_exec("reinserting in tree & replacing function '%s'\n", funcp->name); |
| - | cmd->argv[0] = funcp->name; |
| - | cmd->group = funcp->body; |
| - | # if !BB_MMU |
| - | cmd->group_as_string = funcp->body_as_string; |
| - | # endif |
| + | if (ch == '"') |
| + | return; |
| + | if (ch == '\\') { /* \x. Copy both chars. */ |
| + | o_addchr(dest, ch); |
| + | ch = i_getch(input); |
| } | |
| - | } else { |
| - | debug_printf_exec("remembering new function '%s'\n", name); |
| - | funcp = *funcpp = xzalloc(sizeof(*funcp)); |
| - | /*funcp->next = NULL;*/ |
| + | o_addchr(dest, ch); |
| + | if (ch == '`') { |
| + | add_till_backquote(dest, input); |
| + | o_addchr(dest, ch); |
| + | continue; |
| + | } |
| + | //if (ch == '$') ... |
| } | |
| - | |
| - | funcp->name = name; |
| - | return funcp; |
| } | |
| - | |
| - | static void unset_func(const char *name) |
| - | { |
| - | struct function **funcpp = find_function_slot(name); |
| - | struct function *funcp = *funcpp; |
| - | |
| - | if (funcp != NULL) { |
| - | debug_printf_exec("freeing function '%s'\n", funcp->name); |
| - | *funcpp = funcp->next; |
| - | /* funcp is unlinked now, deleting it. |
| - | * Note: if !funcp->body, the function was created by |
| - | * "-F name body", do not free ->body_as_string |
| - | * and ->name as they were not malloced. */ |
| - | if (funcp->body) { |
| - | free_pipe_list(funcp->body); |
| - | free(funcp->name); |
| - | # if !BB_MMU |
| - | free(funcp->body_as_string); |
| - | # endif |
| + | /* Process `cmd` - copy contents until "`" is seen. Complicated by |
| + | * \` quoting. |
| + | * "Within the backquoted style of command substitution, backslash |
| + | * shall retain its literal meaning, except when followed by: '$', '`', or '\'. |
| + | * The search for the matching backquote shall be satisfied by the first |
| + | * backquote found without a preceding backslash; during this search, |
| + | * if a non-escaped backquote is encountered within a shell comment, |
| + | * a here-document, an embedded command substitution of the $(command) |
| + | * form, or a quoted string, undefined results occur. A single-quoted |
| + | * or double-quoted string that begins, but does not end, within the |
| + | * "`...`" sequence produces undefined results." |
| + | * Example Output |
| + | * echo `echo '\'TEST\`echo ZZ\`BEST` \TESTZZBEST |
| + | */ |
| + | static void add_till_backquote(o_string *dest, struct in_str *input) |
| + | { |
| + | while (1) { |
| + | int ch = i_getch(input); |
| + | if (ch == EOF) { |
| + | syntax_error_unterm_ch('`'); |
| + | /*xfunc_die(); - redundant */ |
| } | |
| - | free(funcp); |
| + | if (ch == '`') |
| + | return; |
| + | if (ch == '\\') { |
| + | /* \x. Copy both chars unless it is \` */ |
| + | int ch2 = i_getch(input); |
| + | if (ch2 == EOF) { |
| + | syntax_error_unterm_ch('`'); |
| + | /*xfunc_die(); - redundant */ |
| + | } |
| + | if (ch2 != '`' && ch2 != '$' && ch2 != '\\') |
| + | o_addchr(dest, ch); |
| + | ch = ch2; |
| + | } |
| + | o_addchr(dest, ch); |
| } | |
| } | |
| - | |
| - | # if BB_MMU |
| - | #define exec_function(to_free, funcp, argv) \ |
| - | exec_function(funcp, argv) |
| - | # endif |
| - | static void exec_function(char ***to_free, |
| - | const struct function *funcp, |
| - | char **argv) NORETURN; |
| - | static void exec_function(char ***to_free, |
| - | const struct function *funcp, |
| - | char **argv) |
| - | { |
| - | # if BB_MMU |
| - | int n = 1; |
| - | |
| - | argv[0] = G.global_argv[0]; |
| - | G.global_argv = argv; |
| - | while (*++argv) |
| - | n++; |
| - | G.global_argc = n; |
| - | /* On MMU, funcp->body is always non-NULL */ |
| - | n = run_list(funcp->body); |
| - | fflush_all(); |
| - | _exit(n); |
| - | # else |
| - | re_execute_shell(to_free, |
| - | funcp->body_as_string, |
| - | G.global_argv[0], |
| - | argv + 1, |
| - | NULL); |
| - | # endif |
| - | } |
| - | |
| - | static int run_function(const struct function *funcp, char **argv) |
| + | /* Process $(cmd) - copy contents until ")" is seen. Complicated by |
| + | * quoting and nested ()s. |
| + | * "With the $(command) style of command substitution, all characters |
| + | * following the open parenthesis to the matching closing parenthesis |
| + | * constitute the command. Any valid shell script can be used for command, |
| + | * except a script consisting solely of redirections which produces |
| + | * unspecified results." |
| + | * Example Output |
| + | * echo $(echo '(TEST)' BEST) (TEST) BEST |
| + | * echo $(echo 'TEST)' BEST) TEST) BEST |
| + | * echo $(echo \(\(TEST\) BEST) ((TEST) BEST |
| + | * |
| + | * Also adapted to eat ${var%...} and $((...)) constructs, since ... part |
| + | * can contain arbitrary constructs, just like $(cmd). |
| + | * In bash compat mode, it needs to also be able to stop on ':' or '/' |
| + | * for ${var:N[:M]} and ${var/P[/R]} parsing. |
| + | */ |
| + | #define DOUBLE_CLOSE_CHAR_FLAG 0x80 |
| + | static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsigned end_ch) |
| { | |
| - | int rc; |
| - | save_arg_t sv; |
| - | smallint sv_flg; |
| - | |
| - | save_and_replace_G_args(&sv, argv); |
| - | |
| - | /* "we are in function, ok to use return" */ |
| - | sv_flg = G.flag_return_in_progress; |
| - | G.flag_return_in_progress = -1; |
| - | # if ENABLE_HUSH_LOCAL |
| - | G.func_nest_level++; |
| - | # endif |
| - | |
| - | /* On MMU, funcp->body is always non-NULL */ |
| - | # if !BB_MMU |
| - | if (!funcp->body) { |
| - | /* Function defined by -F */ |
| - | parse_and_run_string(funcp->body_as_string); |
| - | rc = G.last_exitcode; |
| - | } else |
| + | int ch; |
| + | char dbl = end_ch & DOUBLE_CLOSE_CHAR_FLAG; |
| + | # if ENABLE_HUSH_BASH_COMPAT |
| + | char end_char2 = end_ch >> 8; |
| # endif | |
| - | { |
| - | rc = run_list(funcp->body); |
| - | } |
| - | |
| - | # if ENABLE_HUSH_LOCAL |
| - | { |
| - | struct variable *var; |
| - | struct variable **var_pp; |
| + | end_ch &= (DOUBLE_CLOSE_CHAR_FLAG - 1); |
| - | var_pp = &G.top_var; |
| - | while ((var = *var_pp) != NULL) { |
| - | if (var->func_nest_level < G.func_nest_level) { |
| - | var_pp = &var->next; |
| - | continue; |
| + | while (1) { |
| + | ch = i_getch(input); |
| + | if (ch == EOF) { |
| + | syntax_error_unterm_ch(end_ch); |
| + | /*xfunc_die(); - redundant */ |
| + | } |
| + | if (ch == end_ch IF_HUSH_BASH_COMPAT( || ch == end_char2)) { |
| + | if (!dbl) |
| + | break; |
| + | /* we look for closing )) of $((EXPR)) */ |
| + | if (i_peek(input) == end_ch) { |
| + | i_getch(input); /* eat second ')' */ |
| + | break; |
| } | |
| - | /* Unexport */ |
| - | if (var->flg_export) |
| - | bb_unsetenv(var->varstr); |
| - | /* Remove from global list */ |
| - | *var_pp = var->next; |
| - | /* Free */ |
| - | if (!var->max_len) |
| - | free(var->varstr); |
| - | free(var); |
| } | |
| - | G.func_nest_level--; |
| + | o_addchr(dest, ch); |
| + | if (ch == '(' || ch == '{') { |
| + | ch = (ch == '(' ? ')' : '}'); |
| + | add_till_closing_bracket(dest, input, ch); |
| + | o_addchr(dest, ch); |
| + | continue; |
| + | } |
| + | if (ch == '\'') { |
| + | add_till_single_quote(dest, input); |
| + | o_addchr(dest, ch); |
| + | continue; |
| + | } |
| + | if (ch == '"') { |
| + | add_till_double_quote(dest, input); |
| + | o_addchr(dest, ch); |
| + | continue; |
| + | } |
| + | if (ch == '`') { |
| + | add_till_backquote(dest, input); |
| + | o_addchr(dest, ch); |
| + | continue; |
| + | } |
| + | if (ch == '\\') { |
| + | /* \x. Copy verbatim. Important for \(, \) */ |
| + | ch = i_getch(input); |
| + | if (ch == EOF) { |
| + | syntax_error_unterm_ch(')'); |
| + | /*xfunc_die(); - redundant */ |
| + | } |
| + | o_addchr(dest, ch); |
| + | continue; |
| + | } |
| } | |
| - | # endif |
| - | G.flag_return_in_progress = sv_flg; |
| - | |
| - | restore_G_args(&sv, argv); |
| - | |
| - | return rc; |
| + | return ch; |
| } | |
| - | #endif /* ENABLE_HUSH_FUNCTIONS */ |
| - | |
| + | #endif /* ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_DOLLAR_OPS */ |
| + | /* Return code: 0 for OK, 1 for syntax error */ |
| #if BB_MMU | |
| - | #define exec_builtin(to_free, x, argv) \ |
| - | exec_builtin(x, argv) |
| - | #else |
| - | #define exec_builtin(to_free, x, argv) \ |
| - | exec_builtin(to_free, argv) |
| - | #endif |
| - | static void exec_builtin(char ***to_free, |
| - | const struct built_in_command *x, |
| - | char **argv) NORETURN; |
| - | static void exec_builtin(char ***to_free, |
| - | const struct built_in_command *x, |
| - | char **argv) |
| - | { |
| - | #if BB_MMU |
| - | int rcode = x->b_function(argv); |
| - | fflush_all(); |
| - | _exit(rcode); |
| - | #else |
| - | /* On NOMMU, we must never block! |
| - | * Example: { sleep 99 | read line; } & echo Ok |
| - | */ |
| - | re_execute_shell(to_free, |
| - | argv[0], |
| - | G.global_argv[0], |
| - | G.global_argv + 1, |
| - | argv); |
| + | #define parse_dollar(as_string, dest, input) \ |
| + | parse_dollar(dest, input) |
| + | #define as_string NULL |
| #endif | |
| - | } |
| - | |
| - | |
| - | static void execvp_or_die(char **argv) NORETURN; |
| - | static void execvp_or_die(char **argv) |
| + | static int parse_dollar(o_string *as_string, |
| + | o_string *dest, |
| + | struct in_str *input) |
| { | |
| - | debug_printf_exec("execing '%s'\n", argv[0]); |
| - | sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); |
| - | execvp(argv[0], argv); |
| - | bb_perror_msg("can't execute '%s'", argv[0]); |
| - | _exit(127); /* bash compat */ |
| - | } |
| + | int ch = i_peek(input); /* first character after the $ */ |
| + | unsigned char quote_mask = dest->o_escape ? 0x80 : 0; |
| - | #if ENABLE_HUSH_MODE_X |
| - | static void dump_cmd_in_x_mode(char **argv) |
| - | { |
| - | if (G_x_mode && argv) { |
| - | /* We want to output the line in one write op */ |
| - | char *buf, *p; |
| - | int len; |
| - | int n; |
| + | debug_printf_parse("parse_dollar entered: ch='%c'\n", ch); |
| + | if (isalpha(ch)) { |
| + | ch = i_getch(input); |
| + | nommu_addchr(as_string, ch); |
| + | make_var: |
| + | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| + | while (1) { |
| + | debug_printf_parse(": '%c'\n", ch); |
| + | o_addchr(dest, ch | quote_mask); |
| + | quote_mask = 0; |
| + | ch = i_peek(input); |
| + | if (!isalnum(ch) && ch != '_') |
| + | break; |
| + | ch = i_getch(input); |
| + | nommu_addchr(as_string, ch); |
| + | } |
| + | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| + | } else if (isdigit(ch)) { |
| + | make_one_char_var: |
| + | ch = i_getch(input); |
| + | nommu_addchr(as_string, ch); |
| + | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| + | debug_printf_parse(": '%c'\n", ch); |
| + | o_addchr(dest, ch | quote_mask); |
| + | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| + | } else switch (ch) { |
| + | case '$': /* pid */ |
| + | case '!': /* last bg pid */ |
| + | case '?': /* last exit code */ |
| + | case '#': /* number of args */ |
| + | case '*': /* args */ |
| + | case '@': /* args */ |
| + | goto make_one_char_var; |
| + | case '{': { |
| + | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| - | len = 3; |
| - | n = 0; |
| - | while (argv[n]) |
| - | len += strlen(argv[n++]) + 1; |
| - | buf = xmalloc(len); |
| - | buf[0] = '+'; |
| - | p = buf + 1; |
| - | n = 0; |
| - | while (argv[n]) |
| - | p += sprintf(p, " %s", argv[n++]); |
| - | *p++ = '\n'; |
| - | *p = '\0'; |
| - | fputs(buf, stderr); |
| - | free(buf); |
| - | } |
| - | } |
| - | #else |
| - | # define dump_cmd_in_x_mode(argv) ((void)0) |
| - | #endif |
| + | ch = i_getch(input); /* eat '{' */ |
| + | nommu_addchr(as_string, ch); |
| - | #if BB_MMU |
| - | #define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \ |
| - | pseudo_exec_argv(argv, assignment_cnt, argv_expanded) |
| - | #define pseudo_exec(nommu_save, command, argv_expanded) \ |
| - | pseudo_exec(command, argv_expanded) |
| - | #endif |
| + | ch = i_getch(input); /* first char after '{' */ |
| + | nommu_addchr(as_string, ch); |
| + | /* It should be ${?}, or ${#var}, |
| + | * or even ${?+subst} - operator acting on a special variable, |
| + | * or the beginning of variable name. |
| + | */ |
| + | if (!strchr(_SPECIAL_VARS_STR, ch) && !isalnum(ch)) { /* not one of those */ |
| + | bad_dollar_syntax: |
| + | syntax_error_unterm_str("${name}"); |
| + | debug_printf_parse("parse_dollar return 1: unterminated ${name}\n"); |
| + | return 1; |
| + | } |
| + | ch |= quote_mask; |
| - | /* Called after [v]fork() in run_pipe, or from builtin_exec. |
| - | * Never returns. |
| - | * Don't exit() here. If you don't exec, use _exit instead. |
| - | * The at_exit handlers apparently confuse the calling process, |
| - | * in particular stdin handling. Not sure why? -- because of vfork! (vda) */ |
| - | static void pseudo_exec_argv(nommu_save_t *nommu_save, |
| - | char **argv, int assignment_cnt, |
| - | char **argv_expanded) NORETURN; |
| - | static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, |
| - | char **argv, int assignment_cnt, |
| - | char **argv_expanded) |
| - | { |
| - | char **new_env; |
| + | /* It's possible to just call add_till_closing_bracket() at this point. |
| + | * However, this regresses some of our testsuite cases |
| + | * which check invalid constructs like ${%}. |
| + | * Oh well... let's check that the var name part is fine... */ |
| - | new_env = expand_assignments(argv, assignment_cnt); |
| - | dump_cmd_in_x_mode(new_env); |
| + | while (1) { |
| + | unsigned pos; |
| - | if (!argv[assignment_cnt]) { |
| - | /* Case when we are here: ... | var=val | ... |
| - | * (note that we do not exit early, i.e., do not optimize out |
| - | * expand_assignments(): think about ... | var=`sleep 1` | ... |
| - | */ |
| - | free_strings(new_env); |
| - | _exit(EXIT_SUCCESS); |
| - | } |
| + | o_addchr(dest, ch); |
| + | debug_printf_parse(": '%c'\n", ch); |
| - | #if BB_MMU |
| - | set_vars_and_save_old(new_env); |
| - | free(new_env); /* optional */ |
| - | /* we can also destroy set_vars_and_save_old's return value, |
| - | * to save memory */ |
| - | #else |
| - | nommu_save->new_env = new_env; |
| - | nommu_save->old_vars = set_vars_and_save_old(new_env); |
| - | #endif |
| + | ch = i_getch(input); |
| + | nommu_addchr(as_string, ch); |
| + | if (ch == '}') |
| + | break; |
| - | if (argv_expanded) { |
| - | argv = argv_expanded; |
| - | } else { |
| - | argv = expand_strvec_to_strvec(argv + assignment_cnt); |
| - | #if !BB_MMU |
| - | nommu_save->argv = argv; |
| - | #endif |
| - | } |
| - | dump_cmd_in_x_mode(argv); |
| + | if (!isalnum(ch) && ch != '_') { |
| + | unsigned end_ch; |
| + | unsigned char last_ch; |
| + | /* handle parameter expansions |
| + | * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 |
| + | */ |
| + | if (!strchr(VAR_SUBST_OPS, ch)) /* ${var<bad_char>... */ |
| + | goto bad_dollar_syntax; |
| - | #if ENABLE_FEATURE_SH_STANDALONE || BB_MMU |
| - | if (strchr(argv[0], '/') != NULL) |
| - | goto skip; |
| + | /* Eat everything until closing '}' (or ':') */ |
| + | end_ch = '}'; |
| + | if (ENABLE_HUSH_BASH_COMPAT |
| + | && ch == ':' |
| + | && !strchr(MINUS_PLUS_EQUAL_QUESTION, i_peek(input)) |
| + | ) { |
| + | /* It's ${var:N[:M]} thing */ |
| + | end_ch = '}' * 0x100 + ':'; |
| + | } |
| + | if (ENABLE_HUSH_BASH_COMPAT |
| + | && ch == '/' |
| + | ) { |
| + | /* It's ${var/[/]pattern[/repl]} thing */ |
| + | if (i_peek(input) == '/') { /* ${var//pattern[/repl]}? */ |
| + | i_getch(input); |
| + | nommu_addchr(as_string, '/'); |
| + | ch = '\\'; |
| + | } |
| + | end_ch = '}' * 0x100 + '/'; |
| + | } |
| + | o_addchr(dest, ch); |
| + | again: |
| + | if (!BB_MMU) |
| + | pos = dest->length; |
| + | #if ENABLE_HUSH_DOLLAR_OPS |
| + | last_ch = add_till_closing_bracket(dest, input, end_ch); |
| + | #else |
| + | #error Simple code to only allow ${var} is not implemented |
| #endif | |
| + | if (as_string) { |
| + | o_addstr(as_string, dest->data + pos); |
| + | o_addchr(as_string, last_ch); |
| + | } |
| - | /* Check if the command matches any of the builtins. |
| - | * Depending on context, this might be redundant. But it's |
| - | * easier to waste a few CPU cycles than it is to figure out |
| - | * if this is one of those cases. |
| - | */ |
| - | { |
| - | /* On NOMMU, it is more expensive to re-execute shell |
| - | * just in order to run echo or test builtin. |
| - | * It's better to skip it here and run corresponding |
| - | * non-builtin later. */ |
| - | const struct built_in_command *x; |
| - | x = BB_MMU ? find_builtin(argv[0]) : find_builtin1(argv[0]); |
| - | if (x) { |
| - | exec_builtin(&nommu_save->argv_from_re_execing, x, argv); |
| - | } |
| - | } |
| - | #if ENABLE_HUSH_FUNCTIONS |
| - | /* Check if the command matches any functions */ |
| - | { |
| - | const struct function *funcp = find_function(argv[0]); |
| - | if (funcp) { |
| - | exec_function(&nommu_save->argv_from_re_execing, funcp, argv); |
| + | if (ENABLE_HUSH_BASH_COMPAT && (end_ch & 0xff00)) { |
| + | /* close the first block: */ |
| + | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| + | /* while parsing N from ${var:N[:M]} |
| + | * or pattern from ${var/[/]pattern[/repl]} */ |
| + | if ((end_ch & 0xff) == last_ch) { |
| + | /* got ':' or '/'- parse the rest */ |
| + | end_ch = '}'; |
| + | goto again; |
| + | } |
| + | /* got '}' */ |
| + | if (end_ch == '}' * 0x100 + ':') { |
| + | /* it's ${var:N} - emulate :999999999 */ |
| + | o_addstr(dest, "999999999"); |
| + | } /* else: it's ${var/[/]pattern} */ |
| + | } |
| + | break; |
| + | } |
| } | |
| + | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| + | break; |
| } | |
| - | #endif |
| + | #if ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK |
| + | case '(': { |
| + | unsigned pos; |
| - | #if ENABLE_FEATURE_SH_STANDALONE |
| - | /* Check if the command matches any busybox applets */ |
| - | { |
| - | int a = find_applet_by_name(argv[0]); |
| - | if (a >= 0) { |
| - | # if BB_MMU /* see above why on NOMMU it is not allowed */ |
| - | if (APPLET_IS_NOEXEC(a)) { |
| - | debug_printf_exec("running applet '%s'\n", argv[0]); |
| - | run_applet_no_and_exit(a, argv); |
| + | ch = i_getch(input); |
| + | nommu_addchr(as_string, ch); |
| + | # if ENABLE_SH_MATH_SUPPORT |
| + | if (i_peek(input) == '(') { |
| + | ch = i_getch(input); |
| + | nommu_addchr(as_string, ch); |
| + | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| + | o_addchr(dest, /*quote_mask |*/ '+'); |
| + | if (!BB_MMU) |
| + | pos = dest->length; |
| + | add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG); |
| + | if (as_string) { |
| + | o_addstr(as_string, dest->data + pos); |
| + | o_addchr(as_string, ')'); |
| + | o_addchr(as_string, ')'); |
| } | |
| - | # endif |
| - | /* Re-exec ourselves */ |
| - | debug_printf_exec("re-execing applet '%s'\n", argv[0]); |
| - | sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); |
| - | execv(bb_busybox_exec_path, argv); |
| - | /* If they called chroot or otherwise made the binary no longer |
| - | * executable, fall through */ |
| + | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| + | break; |
| } | |
| + | # endif |
| + | # if ENABLE_HUSH_TICK |
| + | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| + | o_addchr(dest, quote_mask | '`'); |
| + | if (!BB_MMU) |
| + | pos = dest->length; |
| + | add_till_closing_bracket(dest, input, ')'); |
| + | if (as_string) { |
| + | o_addstr(as_string, dest->data + pos); |
| + | o_addchr(as_string, ')'); |
| + | } |
| + | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| + | # endif |
| + | break; |
| } | |
| #endif | |
| - | |
| - | #if ENABLE_FEATURE_SH_STANDALONE || BB_MMU |
| - | skip: |
| - | #endif |
| - | execvp_or_die(argv); |
| - | } |
| - | |
| - | /* Called after [v]fork() in run_pipe |
| - | */ |
| - | static void pseudo_exec(nommu_save_t *nommu_save, |
| - | struct command *command, |
| - | char **argv_expanded) NORETURN; |
| - | static void pseudo_exec(nommu_save_t *nommu_save, |
| - | struct command *command, |
| - | char **argv_expanded) |
| - | { |
| - | if (command->argv) { |
| - | pseudo_exec_argv(nommu_save, command->argv, |
| - | command->assignment_cnt, argv_expanded); |
| - | } |
| - | |
| - | if (command->group) { |
| - | /* Cases when we are here: |
| - | * ( list ) |
| - | * { list } & |
| - | * ... | ( list ) | ... |
| - | * ... | { list } | ... |
| - | */ |
| - | #if BB_MMU |
| - | int rcode; |
| - | debug_printf_exec("pseudo_exec: run_list\n"); |
| - | reset_traps_to_defaults(); |
| - | rcode = run_list(command->group); |
| - | /* OK to leak memory by not calling free_pipe_list, |
| - | * since this process is about to exit */ |
| - | _exit(rcode); |
| - | #else |
| - | re_execute_shell(&nommu_save->argv_from_re_execing, |
| - | command->group_as_string, |
| - | G.global_argv[0], |
| - | G.global_argv + 1, |
| - | NULL); |
| - | #endif |
| + | case '_': |
| + | ch = i_getch(input); |
| + | nommu_addchr(as_string, ch); |
| + | ch = i_peek(input); |
| + | if (isalnum(ch)) { /* it's $_name or $_123 */ |
| + | ch = '_'; |
| + | goto make_var; |
| + | } |
| + | /* else: it's $_ */ |
| + | /* TODO: $_ and $-: */ |
| + | /* $_ Shell or shell script name; or last argument of last command |
| + | * (if last command wasn't a pipe; if it was, bash sets $_ to ""); |
| + | * but in command's env, set to full pathname used to invoke it */ |
| + | /* $- Option flags set by set builtin or shell options (-i etc) */ |
| + | default: |
| + | o_addQchr(dest, '$'); |
| } | |
| - | |
| - | /* Case when we are here: ... | >file */ |
| - | debug_printf_exec("pseudo_exec'ed null command\n"); |
| - | _exit(EXIT_SUCCESS); |
| + | debug_printf_parse("parse_dollar return 0\n"); |
| + | return 0; |
| + | #undef as_string |
| } | |
| - | #if ENABLE_HUSH_JOB |
| - | static const char *get_cmdtext(struct pipe *pi) |
| + | #if BB_MMU |
| + | #define parse_stream_dquoted(as_string, dest, input, dquote_end) \ |
| + | parse_stream_dquoted(dest, input, dquote_end) |
| + | #define as_string NULL |
| + | #endif |
| + | static int parse_stream_dquoted(o_string *as_string, |
| + | o_string *dest, |
| + | struct in_str *input, |
| + | int dquote_end) |
| { | |
| - | char **argv; |
| - | char *p; |
| - | int len; |
| + | int ch; |
| + | int next; |
| - | /* This is subtle. ->cmdtext is created only on first backgrounding. |
| - | * (Think "cat, <ctrl-z>, fg, <ctrl-z>, fg, <ctrl-z>...." here...) |
| - | * On subsequent bg argv is trashed, but we won't use it */ |
| - | if (pi->cmdtext) |
| - | return pi->cmdtext; |
| - | argv = pi->cmds[0].argv; |
| - | if (!argv || !argv[0]) { |
| - | pi->cmdtext = xzalloc(1); |
| - | return pi->cmdtext; |
| + | again: |
| + | ch = i_getch(input); |
| + | if (ch != EOF) |
| + | nommu_addchr(as_string, ch); |
| + | if (ch == dquote_end) { /* may be only '"' or EOF */ |
| + | if (dest->o_assignment == NOT_ASSIGNMENT) |
| + | dest->o_escape ^= 1; |
| + | debug_printf_parse("parse_stream_dquoted return 0\n"); |
| + | return 0; |
| } | |
| - | |
| - | len = 0; |
| - | do { |
| - | len += strlen(*argv) + 1; |
| - | } while (*++argv); |
| - | p = xmalloc(len); |
| - | pi->cmdtext = p; |
| - | argv = pi->cmds[0].argv; |
| - | do { |
| - | len = strlen(*argv); |
| - | memcpy(p, *argv, len); |
| - | p += len; |
| - | *p++ = ' '; |
| - | } while (*++argv); |
| - | p[-1] = '\0'; |
| - | return pi->cmdtext; |
| - | } |
| - | |
| - | static void insert_bg_job(struct pipe *pi) |
| - | { |
| - | struct pipe *job, **jobp; |
| - | int i; |
| - | |
| - | /* Linear search for the ID of the job to use */ |
| - | pi->jobid = 1; |
| - | for (job = G.job_list; job; job = job->next) |
| - | if (job->jobid >= pi->jobid) |
| - | pi->jobid = job->jobid + 1; |
| - | |
| - | /* Add job to the list of running jobs */ |
| - | jobp = &G.job_list; |
| - | while ((job = *jobp) != NULL) |
| - | jobp = &job->next; |
| - | job = *jobp = xmalloc(sizeof(*job)); |
| - | |
| - | *job = *pi; /* physical copy */ |
| - | job->next = NULL; |
| - | job->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds); |
| - | /* Cannot copy entire pi->cmds[] vector! This causes double frees */ |
| - | for (i = 0; i < pi->num_cmds; i++) { |
| - | job->cmds[i].pid = pi->cmds[i].pid; |
| - | /* all other fields are not used and stay zero */ |
| + | /* note: can't move it above ch == dquote_end check! */ |
| + | if (ch == EOF) { |
| + | syntax_error_unterm_ch('"'); |
| + | /*xfunc_die(); - redundant */ |
| } | |
| - | job->cmdtext = xstrdup(get_cmdtext(pi)); |
| - | |
| - | if (G_interactive_fd) |
| - | printf("[%d] %d %s\n", job->jobid, job->cmds[0].pid, job->cmdtext); |
| - | G.last_jobid = job->jobid; |
| + | next = '\0'; |
| + | if (ch != '\n') { |
| + | next = i_peek(input); |
| + | } |
| + | debug_printf_parse("\" ch=%c (%d) escape=%d\n", |
| + | ch, ch, dest->o_escape); |
| + | if (ch == '\\') { |
| + | if (next == EOF) { |
| + | syntax_error("\\<eof>"); |
| + | xfunc_die(); |
| + | } |
| + | /* 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 (strchr("$`\"\\\n", next) != NULL) { |
| + | ch = i_getch(input); |
| + | if (ch != '\n') { |
| + | o_addqchr(dest, ch); |
| + | nommu_addchr(as_string, ch); |
| + | } |
| + | } else { |
| + | o_addqchr(dest, '\\'); |
| + | nommu_addchr(as_string, '\\'); |
| + | } |
| + | goto again; |
| + | } |
| + | if (ch == '$') { |
| + | if (parse_dollar(as_string, dest, input) != 0) { |
| + | debug_printf_parse("parse_stream_dquoted return 1: " |
| + | "parse_dollar returned non-0\n"); |
| + | return 1; |
| + | } |
| + | goto again; |
| + | } |
| + | #if ENABLE_HUSH_TICK |
| + | if (ch == '`') { |
| + | //unsigned 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); |
| + | goto again; |
| + | } |
| + | #endif |
| + | o_addQchr(dest, ch); |
| + | if (ch == '=' |
| + | && (dest->o_assignment == MAYBE_ASSIGNMENT |
| + | || dest->o_assignment == WORD_IS_KEYWORD) |
| + | && is_well_formed_var_name(dest->data, '=') |
| + | ) { |
| + | dest->o_assignment = DEFINITELY_ASSIGNMENT; |
| + | } |
| + | goto again; |
| + | #undef as_string |
| } | |
| - | static void remove_bg_job(struct pipe *pi) |
| + | /* |
| + | * Scan input until EOF or end_trigger char. |
| + | * Return a list of pipes to execute, or NULL on EOF |
| + | * or if end_trigger character is met. |
| + | * On syntax error, exit is shell is not interactive, |
| + | * reset parsing machinery and start parsing anew, |
| + | * or return ERR_PTR. |
| + | */ |
| + | static struct pipe *parse_stream(char **pstring, |
| + | struct in_str *input, |
| + | int end_trigger) |
| { | |
| - | struct pipe *prev_pipe; |
| + | struct parse_context ctx; |
| + | o_string dest = NULL_O_STRING; |
| + | int is_in_dquote; |
| + | int heredoc_cnt; |
| - | if (pi == G.job_list) { |
| - | G.job_list = pi->next; |
| - | } else { |
| - | prev_pipe = G.job_list; |
| - | while (prev_pipe->next != pi) |
| - | prev_pipe = prev_pipe->next; |
| - | prev_pipe->next = pi->next; |
| - | } |
| - | if (G.job_list) |
| - | G.last_jobid = G.job_list->jobid; |
| - | else |
| - | G.last_jobid = 0; |
| - | } |
| + | /* Double-quote state is handled in the state variable is_in_dquote. |
| + | * A single-quote triggers a bypass of the main loop until its mate is |
| + | * found. When recursing, quote state is passed in via dest->o_escape. |
| + | */ |
| + | debug_printf_parse("parse_stream entered, end_trigger='%c'\n", |
| + | end_trigger ? end_trigger : 'X'); |
| + | debug_enter(); |
| - | /* Remove a backgrounded job */ |
| - | static void delete_finished_bg_job(struct pipe *pi) |
| - | { |
| - | remove_bg_job(pi); |
| - | pi->stopped_cmds = 0; |
| - | free_pipe(pi); |
| - | free(pi); |
| - | } |
| - | #endif /* JOB */ |
| + | /* If very first arg is "" or '', dest.data may end up NULL. |
| + | * Preventing this: */ |
| + | o_addchr(&dest, '\0'); |
| + | dest.length = 0; |
| - | /* Check to see if any processes have exited -- if they |
| - | * have, figure out why and see if a job has completed */ |
| - | static int checkjobs(struct pipe* fg_pipe) |
| - | { |
| - | int attributes; |
| - | int status; |
| - | #if ENABLE_HUSH_JOB |
| - | struct pipe *pi; |
| + | G.ifs = get_local_var_value("IFS"); |
| + | if (G.ifs == NULL) |
| + | G.ifs = defifs; |
| + | |
| + | reset: |
| + | #if ENABLE_HUSH_INTERACTIVE |
| + | input->promptmode = 0; /* PS1 */ |
| #endif | |
| - | pid_t childpid; |
| - | int rcode = 0; |
| + | /* dest.o_assignment = MAYBE_ASSIGNMENT; - already is */ |
| + | initialize_context(&ctx); |
| + | is_in_dquote = 0; |
| + | heredoc_cnt = 0; |
| + | while (1) { |
| + | const char *is_ifs; |
| + | const char *is_special; |
| + | int ch; |
| + | int next; |
| + | int redir_fd; |
| + | redir_type redir_style; |
| - | debug_printf_jobs("checkjobs %p\n", fg_pipe); |
| + | if (is_in_dquote) { |
| + | /* dest.has_quoted_part = 1; - already is (see below) */ |
| + | if (parse_stream_dquoted(&ctx.as_string, &dest, input, '"')) { |
| + | goto parse_error; |
| + | } |
| + | /* We reached closing '"' */ |
| + | is_in_dquote = 0; |
| + | } |
| + | ch = i_getch(input); |
| + | debug_printf_parse(": ch=%c (%d) escape=%d\n", |
| + | ch, ch, dest.o_escape); |
| + | if (ch == EOF) { |
| + | struct pipe *pi; |
| - | attributes = WUNTRACED; |
| - | if (fg_pipe == NULL) |
| - | attributes |= WNOHANG; |
| + | if (heredoc_cnt) { |
| + | syntax_error_unterm_str("here document"); |
| + | goto parse_error; |
| + | } |
| + | /* end_trigger == '}' case errors out earlier, |
| + | * checking only ')' */ |
| + | if (end_trigger == ')') { |
| + | syntax_error_unterm_ch('('); /* exits */ |
| + | /* goto parse_error; */ |
| + | } |
| - | errno = 0; |
| - | #if ENABLE_HUSH_FAST |
| - | if (G.handled_SIGCHLD == G.count_SIGCHLD) { |
| - | //bb_error_msg("[%d] checkjobs: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d children?:%d fg_pipe:%p", |
| - | //getpid(), G.count_SIGCHLD, G.handled_SIGCHLD, G.we_have_children, fg_pipe); |
| - | /* There was neither fork nor SIGCHLD since last waitpid */ |
| - | /* Avoid doing waitpid syscall if possible */ |
| - | if (!G.we_have_children) { |
| - | errno = ECHILD; |
| - | return -1; |
| - | } |
| - | if (fg_pipe == NULL) { /* is WNOHANG set? */ |
| - | /* We have children, but they did not exit |
| - | * or stop yet (we saw no SIGCHLD) */ |
| - | return 0; |
| - | } |
| - | /* else: !WNOHANG, waitpid will block, can't short-circuit */ |
| - | } |
| + | if (done_word(&dest, &ctx)) { |
| + | goto parse_error; |
| + | } |
| + | o_free(&dest); |
| + | done_pipe(&ctx, PIPE_SEQ); |
| + | pi = ctx.list_head; |
| + | /* If we got nothing... */ |
| + | /* (this makes bare "&" cmd a no-op. |
| + | * bash says: "syntax error near unexpected token '&'") */ |
| + | if (pi->num_cmds == 0 |
| + | IF_HAS_KEYWORDS( && pi->res_word == RES_NONE) |
| + | ) { |
| + | free_pipe_list(pi); |
| + | pi = NULL; |
| + | } |
| + | #if !BB_MMU |
| + | debug_printf_parse("as_string '%s'\n", ctx.as_string.data); |
| + | if (pstring) |
| + | *pstring = ctx.as_string.data; |
| + | else |
| + | o_free_unsafe(&ctx.as_string); |
| #endif | |
| + | debug_leave(); |
| + | debug_printf_parse("parse_stream return %p\n", pi); |
| + | return pi; |
| + | } |
| + | nommu_addchr(&ctx.as_string, ch); |
| - | /* Do we do this right? |
| - | * bash-3.00# sleep 20 | false |
| - | * <ctrl-Z pressed> |
| - | * [3]+ Stopped sleep 20 | false |
| - | * bash-3.00# echo $? |
| - | * 1 <========== bg pipe is not fully done, but exitcode is already known! |
| - | * [hush 1.14.0: yes we do it right] |
| - | */ |
| - | wait_more: |
| - | while (1) { |
| - | int i; |
| - | int dead; |
| + | next = '\0'; |
| + | if (ch != '\n') |
| + | next = i_peek(input); |
| - | #if ENABLE_HUSH_FAST |
| - | i = G.count_SIGCHLD; |
| - | #endif |
| - | childpid = waitpid(-1, &status, attributes); |
| - | if (childpid <= 0) { |
| - | if (childpid && errno != ECHILD) |
| - | bb_perror_msg("waitpid"); |
| - | #if ENABLE_HUSH_FAST |
| - | else { /* Until next SIGCHLD, waitpid's are useless */ |
| - | G.we_have_children = (childpid == 0); |
| - | G.handled_SIGCHLD = i; |
| - | //bb_error_msg("[%d] checkjobs: waitpid returned <= 0, G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); |
| + | is_special = "{}<>;&|()#'" /* special outside of "str" */ |
| + | "\\$\"" IF_HUSH_TICK("`"); /* always special */ |
| + | /* Are { and } special here? */ |
| + | if (ctx.command->argv /* word [word]{... - non-special */ |
| + | || dest.length /* word{... - non-special */ |
| + | || dest.has_quoted_part /* ""{... - non-special */ |
| + | || (next != ';' /* }; - special */ |
| + | && next != ')' /* }) - special */ |
| + | && next != '&' /* }& and }&& ... - special */ |
| + | && next != '|' /* }|| ... - special */ |
| + | && !strchr(G.ifs, next) /* {word - non-special */ |
| + | ) |
| + | ) { |
| + | /* They are not special, skip "{}" */ |
| + | is_special += 2; |
| + | } |
| + | is_special = strchr(is_special, ch); |
| + | is_ifs = strchr(G.ifs, ch); |
| + | |
| + | if (!is_special && !is_ifs) { /* ordinary char */ |
| + | ordinary_char: |
| + | o_addQchr(&dest, ch); |
| + | if ((dest.o_assignment == MAYBE_ASSIGNMENT |
| + | || dest.o_assignment == WORD_IS_KEYWORD) |
| + | && ch == '=' |
| + | && is_well_formed_var_name(dest.data, '=') |
| + | ) { |
| + | dest.o_assignment = DEFINITELY_ASSIGNMENT; |
| } | |
| - | #endif |
| - | break; |
| + | continue; |
| } | |
| - | dead = WIFEXITED(status) || WIFSIGNALED(status); |
| - | #if DEBUG_JOBS |
| - | if (WIFSTOPPED(status)) |
| - | debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n", |
| - | childpid, WSTOPSIG(status), WEXITSTATUS(status)); |
| - | if (WIFSIGNALED(status)) |
| - | debug_printf_jobs("pid %d killed by sig %d (exitcode %d)\n", |
| - | childpid, WTERMSIG(status), WEXITSTATUS(status)); |
| - | if (WIFEXITED(status)) |
| - | debug_printf_jobs("pid %d exited, exitcode %d\n", |
| - | childpid, WEXITSTATUS(status)); |
| - | #endif |
| - | /* Were we asked to wait for fg pipe? */ |
| - | if (fg_pipe) { |
| - | for (i = 0; i < fg_pipe->num_cmds; i++) { |
| - | debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid); |
| - | if (fg_pipe->cmds[i].pid != childpid) |
| + | if (is_ifs) { |
| + | if (done_word(&dest, &ctx)) { |
| + | goto parse_error; |
| + | } |
| + | if (ch == '\n') { |
| + | #if ENABLE_HUSH_CASE |
| + | /* "case ... in <newline> word) ..." - |
| + | * newlines are ignored (but ';' wouldn't be) */ |
| + | if (ctx.command->argv == NULL |
| + | && ctx.ctx_res_w == RES_MATCH |
| + | ) { |
| continue; | |
| - | if (dead) { |
| - | fg_pipe->cmds[i].pid = 0; |
| - | fg_pipe->alive_cmds--; |
| - | if (i == fg_pipe->num_cmds - 1) { |
| - | /* last process gives overall exitstatus */ |
| - | rcode = WEXITSTATUS(status); |
| - | /* bash prints killer signal's name for *last* |
| - | * process in pipe (prints just newline for SIGINT). |
| - | * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT) |
| - | */ |
| - | if (WIFSIGNALED(status)) { |
| - | int sig = WTERMSIG(status); |
| - | printf("%s\n", sig == SIGINT ? "" : get_signame(sig)); |
| - | /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here? |
| - | * Maybe we need to use sig | 128? */ |
| - | rcode = sig + 128; |
| - | } |
| - | IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;) |
| + | } |
| + | #endif |
| + | /* Treat newline as a command separator. */ |
| + | done_pipe(&ctx, PIPE_SEQ); |
| + | debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt); |
| + | if (heredoc_cnt) { |
| + | if (fetch_heredocs(heredoc_cnt, &ctx, input)) { |
| + | goto parse_error; |
| } | |
| - | } else { |
| - | fg_pipe->cmds[i].is_stopped = 1; |
| - | fg_pipe->stopped_cmds++; |
| + | heredoc_cnt = 0; |
| } | |
| - | debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n", |
| - | fg_pipe->alive_cmds, fg_pipe->stopped_cmds); |
| - | if (fg_pipe->alive_cmds - fg_pipe->stopped_cmds <= 0) { |
| - | /* All processes in fg pipe have exited or stopped */ |
| - | /* Note: *non-interactive* bash does not continue if all processes in fg pipe |
| - | * are stopped. Testcase: "cat | cat" in a script (not on command line!) |
| - | * and "killall -STOP cat" */ |
| - | if (G_interactive_fd) { |
| - | #if ENABLE_HUSH_JOB |
| - | if (fg_pipe->alive_cmds) |
| - | insert_bg_job(fg_pipe); |
| + | dest.o_assignment = MAYBE_ASSIGNMENT; |
| + | ch = ';'; |
| + | /* note: if (is_ifs) continue; |
| + | * will still trigger for us */ |
| + | } |
| + | } |
| + | |
| + | /* "cmd}" or "cmd }..." without semicolon or &: |
| + | * } is an ordinary char in this case, even inside { cmd; } |
| + | * Pathological example: { ""}; } should exec "}" cmd |
| + | */ |
| + | if (ch == '}') { |
| + | if (!IS_NULL_CMD(ctx.command) /* cmd } */ |
| + | || dest.length != 0 /* word} */ |
| + | || dest.has_quoted_part /* ""} */ |
| + | ) { |
| + | goto ordinary_char; |
| + | } |
| + | if (!IS_NULL_PIPE(ctx.pipe)) /* cmd | } */ |
| + | goto skip_end_trigger; |
| + | /* else: } does terminate a group */ |
| + | } |
| + | |
| + | if (end_trigger && end_trigger == ch |
| + | && (ch != ';' || heredoc_cnt == 0) |
| + | #if ENABLE_HUSH_CASE |
| + | && (ch != ')' |
| + | || ctx.ctx_res_w != RES_MATCH |
| + | || (!dest.has_quoted_part && strcmp(dest.data, "esac") == 0) |
| + | ) |
| + | #endif |
| + | ) { |
| + | if (heredoc_cnt) { |
| + | /* This is technically valid: |
| + | * { cat <<HERE; }; echo Ok |
| + | * heredoc |
| + | * heredoc |
| + | * HERE |
| + | * but we don't support this. |
| + | * We require heredoc to be in enclosing {}/(), |
| + | * if any. |
| + | */ |
| + | syntax_error_unterm_str("here document"); |
| + | goto parse_error; |
| + | } |
| + | if (done_word(&dest, &ctx)) { |
| + | goto parse_error; |
| + | } |
| + | done_pipe(&ctx, PIPE_SEQ); |
| + | dest.o_assignment = MAYBE_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); |
| + | #if !BB_MMU |
| + | debug_printf_parse("as_string '%s'\n", ctx.as_string.data); |
| + | if (pstring) |
| + | *pstring = ctx.as_string.data; |
| + | else |
| + | o_free_unsafe(&ctx.as_string); |
| #endif | |
| - | return rcode; |
| - | } |
| - | if (!fg_pipe->alive_cmds) |
| - | return rcode; |
| - | } |
| - | /* There are still running processes in the fg pipe */ |
| - | goto wait_more; /* do waitpid again */ |
| + | debug_leave(); |
| + | debug_printf_parse("parse_stream return %p: " |
| + | "end_trigger char found\n", |
| + | ctx.list_head); |
| + | return ctx.list_head; |
| } | |
| - | /* it wasnt fg_pipe, look for process in bg pipes */ |
| } | |
| + | skip_end_trigger: |
| + | if (is_ifs) |
| + | continue; |
| - | #if ENABLE_HUSH_JOB |
| - | /* We asked to wait for bg or orphaned children */ |
| - | /* No need to remember exitcode in this case */ |
| - | for (pi = G.job_list; pi; pi = pi->next) { |
| - | for (i = 0; i < pi->num_cmds; i++) { |
| - | if (pi->cmds[i].pid == childpid) |
| - | goto found_pi_and_prognum; |
| + | /* Catch <, > before deciding whether this word is |
| + | * 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)) { |
| + | goto parse_error; |
| } | |
| - | } |
| - | /* Happens when shell is used as init process (init=/bin/sh) */ |
| - | debug_printf("checkjobs: pid %d was not in our list!\n", childpid); |
| - | continue; /* do waitpid again */ |
| - | |
| - | found_pi_and_prognum: |
| - | if (dead) { |
| - | /* child exited */ |
| - | pi->cmds[i].pid = 0; |
| - | pi->alive_cmds--; |
| - | if (!pi->alive_cmds) { |
| - | if (G_interactive_fd) |
| - | printf(JOB_STATUS_FORMAT, pi->jobid, |
| - | "Done", pi->cmdtext); |
| - | delete_finished_bg_job(pi); |
| + | redir_style = REDIRECT_OVERWRITE; |
| + | if (next == '>') { |
| + | redir_style = REDIRECT_APPEND; |
| + | ch = i_getch(input); |
| + | nommu_addchr(&ctx.as_string, ch); |
| + | } |
| + | #if 0 |
| + | else if (next == '(') { |
| + | syntax_error(">(process) not supported"); |
| + | goto parse_error; |
| } | |
| - | } else { |
| - | /* child stopped */ |
| - | pi->cmds[i].is_stopped = 1; |
| - | pi->stopped_cmds++; |
| - | } |
| - | #endif |
| - | } /* while (waitpid succeeds)... */ |
| - | |
| - | return rcode; |
| - | } |
| - | |
| - | #if ENABLE_HUSH_JOB |
| - | static int checkjobs_and_fg_shell(struct pipe* fg_pipe) |
| - | { |
| - | pid_t p; |
| - | int rcode = checkjobs(fg_pipe); |
| - | if (G_saved_tty_pgrp) { |
| - | /* Job finished, move the shell to the foreground */ |
| - | p = getpgrp(); /* our process group id */ |
| - | debug_printf_jobs("fg'ing ourself: getpgrp()=%d\n", (int)p); |
| - | tcsetpgrp(G_interactive_fd, p); |
| - | } |
| - | return rcode; |
| - | } |
| #endif | |
| - | |
| - | /* Start all the jobs, but don't wait for anything to finish. |
| - | * See checkjobs(). |
| - | * |
| - | * Return code is normally -1, when the caller has to wait for children |
| - | * to finish to determine the exit status of the pipe. If the pipe |
| - | * is a simple builtin command, however, the action is done by the |
| - | * time run_pipe returns, and the exit code is provided as the |
| - | * return value. |
| - | * |
| - | * Returns -1 only if started some children. IOW: we have to |
| - | * mask out retvals of builtins etc with 0xff! |
| - | * |
| - | * The only case when we do not need to [v]fork is when the pipe |
| - | * is single, non-backgrounded, non-subshell command. Examples: |
| - | * cmd ; ... { list } ; ... |
| - | * cmd && ... { list } && ... |
| - | * cmd || ... { list } || ... |
| - | * If it is, then we can run cmd as a builtin, NOFORK [do we do this?], |
| - | * or (if SH_STANDALONE) an applet, and we can run the { list } |
| - | * with run_list. If it isn't one of these, we fork and exec cmd. |
| - | * |
| - | * Cases when we must fork: |
| - | * non-single: cmd | cmd |
| - | * backgrounded: cmd & { list } & |
| - | * subshell: ( list ) [&] |
| - | */ |
| - | #if !ENABLE_HUSH_MODE_X |
| - | #define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, char argv_expanded) \ |
| - | redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel) |
| + | if (parse_redirect(&ctx, redir_fd, redir_style, input)) |
| + | goto parse_error; |
| + | continue; /* back to top of while (1) */ |
| + | case '<': |
| + | redir_fd = redirect_opt_num(&dest); |
| + | if (done_word(&dest, &ctx)) { |
| + | goto parse_error; |
| + | } |
| + | redir_style = REDIRECT_INPUT; |
| + | if (next == '<') { |
| + | redir_style = REDIRECT_HEREDOC; |
| + | heredoc_cnt++; |
| + | debug_printf_parse("++heredoc_cnt=%d\n", heredoc_cnt); |
| + | ch = i_getch(input); |
| + | nommu_addchr(&ctx.as_string, ch); |
| + | } else if (next == '>') { |
| + | redir_style = REDIRECT_IO; |
| + | ch = i_getch(input); |
| + | nommu_addchr(&ctx.as_string, ch); |
| + | } |
| + | #if 0 |
| + | else if (next == '(') { |
| + | syntax_error("<(process) not supported"); |
| + | goto parse_error; |
| + | } |
| #endif | |
| - | static int redirect_and_varexp_helper(char ***new_env_p, struct variable **old_vars_p, struct command *command, int squirrel[3], char **argv_expanded) |
| - | { |
| - | /* setup_redirects acts on file descriptors, not FILEs. |
| - | * This is perfect for work that comes after exec(). |
| - | * Is it really safe for inline use? Experimentally, |
| - | * things seem to work. */ |
| - | int rcode = setup_redirects(command, squirrel); |
| - | if (rcode == 0) { |
| - | char **new_env = expand_assignments(command->argv, command->assignment_cnt); |
| - | *new_env_p = new_env; |
| - | dump_cmd_in_x_mode(new_env); |
| - | dump_cmd_in_x_mode(argv_expanded); |
| - | if (old_vars_p) |
| - | *old_vars_p = set_vars_and_save_old(new_env); |
| - | } |
| - | return rcode; |
| - | } |
| - | static NOINLINE int run_pipe(struct pipe *pi) |
| - | { |
| - | static const char *const null_ptr = NULL; |
| - | |
| - | int cmd_no; |
| - | int next_infd; |
| - | struct command *command; |
| - | char **argv_expanded; |
| - | char **argv; |
| - | /* it is not always needed, but we aim to smaller code */ |
| - | int squirrel[] = { -1, -1, -1 }; |
| - | int rcode; |
| - | |
| - | debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds); |
| - | debug_enter(); |
| - | |
| - | IF_HUSH_JOB(pi->pgrp = -1;) |
| - | pi->stopped_cmds = 0; |
| - | command = &pi->cmds[0]; |
| - | argv_expanded = NULL; |
| - | |
| - | if (pi->num_cmds != 1 |
| - | || pi->followup == PIPE_BG |
| - | || command->cmd_type == CMD_SUBSHELL |
| - | ) { |
| - | goto must_fork; |
| - | } |
| + | if (parse_redirect(&ctx, redir_fd, redir_style, input)) |
| + | goto parse_error; |
| + | continue; /* back to top of while (1) */ |
| + | } |
| - | pi->alive_cmds = 1; |
| + | if (dest.o_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_exec(": group:%p argv:'%s'\n", |
| - | command->group, command->argv ? command->argv[0] : "NONE"); |
| + | /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ |
| - | if (command->group) { |
| - | #if ENABLE_HUSH_FUNCTIONS |
| - | if (command->cmd_type == CMD_FUNCDEF) { |
| - | /* "executing" func () { list } */ |
| - | struct function *funcp; |
| + | switch (ch) { |
| + | case '#': |
| + | if (dest.length == 0) { |
| + | while (1) { |
| + | ch = i_peek(input); |
| + | if (ch == EOF || ch == '\n') |
| + | break; |
| + | i_getch(input); |
| + | /* note: we do not add it to &ctx.as_string */ |
| + | } |
| + | nommu_addchr(&ctx.as_string, '\n'); |
| + | } else { |
| + | o_addQchr(&dest, ch); |
| + | } |
| + | break; |
| + | case '\\': |
| + | if (next == EOF) { |
| + | syntax_error("\\<eof>"); |
| + | xfunc_die(); |
| + | } |
| + | ch = i_getch(input); |
| + | if (ch != '\n') { |
| + | o_addchr(&dest, '\\'); |
| + | /*nommu_addchr(&ctx.as_string, '\\'); - already done */ |
| + | o_addchr(&dest, 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; |
| + | } |
| + | #if !BB_MMU |
| + | else { |
| + | /* It's "\<newline>". Remove trailing '\' from ctx.as_string */ |
| + | ctx.as_string.data[--ctx.as_string.length] = '\0'; |
| + | } |
| + | #endif |
| + | break; |
| + | case '$': |
| + | if (parse_dollar(&ctx.as_string, &dest, input) != 0) { |
| + | debug_printf_parse("parse_stream parse error: " |
| + | "parse_dollar returned non-0\n"); |
| + | goto parse_error; |
| + | } |
| + | break; |
| + | case '\'': |
| + | dest.has_quoted_part = 1; |
| + | while (1) { |
| + | ch = i_getch(input); |
| + | if (ch == EOF) { |
| + | syntax_error_unterm_ch('\''); |
| + | /*xfunc_die(); - redundant */ |
| + | } |
| + | nommu_addchr(&ctx.as_string, ch); |
| + | if (ch == '\'') |
| + | break; |
| + | o_addqchr(&dest, ch); |
| + | } |
| + | break; |
| + | case '"': |
| + | dest.has_quoted_part = 1; |
| + | is_in_dquote ^= 1; /* invert */ |
| + | if (dest.o_assignment == NOT_ASSIGNMENT) |
| + | dest.o_escape ^= 1; |
| + | break; |
| + | #if ENABLE_HUSH_TICK |
| + | case '`': { |
| + | unsigned pos; |
| - | funcp = new_function(command->argv[0]); |
| - | /* funcp->name is already set to argv[0] */ |
| - | funcp->body = command->group; |
| + | o_addchr(&dest, SPECIAL_VAR_SYMBOL); |
| + | o_addchr(&dest, '`'); |
| + | pos = dest.length; |
| + | add_till_backquote(&dest, input); |
| # if !BB_MMU | |
| - | funcp->body_as_string = command->group_as_string; |
| - | command->group_as_string = NULL; |
| + | o_addstr(&ctx.as_string, dest.data + pos); |
| + | o_addchr(&ctx.as_string, '`'); |
| # endif | |
| - | command->group = NULL; |
| - | command->argv[0] = NULL; |
| - | debug_printf_exec("cmd %p has child func at %p\n", command, funcp); |
| - | funcp->parent_cmd = command; |
| - | command->child_func = funcp; |
| - | |
| - | debug_printf_exec("run_pipe: return EXIT_SUCCESS\n"); |
| - | debug_leave(); |
| - | return EXIT_SUCCESS; |
| + | o_addchr(&dest, SPECIAL_VAR_SYMBOL); |
| + | //debug_printf_subst("SUBST RES3 '%s'\n", dest.data + pos); |
| + | break; |
| } | |
| #endif | |
| - | /* { list } */ |
| - | debug_printf("non-subshell group\n"); |
| - | rcode = 1; /* exitcode if redir failed */ |
| - | if (setup_redirects(command, squirrel) == 0) { |
| - | debug_printf_exec(": run_list\n"); |
| - | rcode = run_list(command->group) & 0xff; |
| - | } |
| - | restore_redirects(squirrel); |
| - | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) |
| - | debug_leave(); |
| - | debug_printf_exec("run_pipe: return %d\n", rcode); |
| - | return rcode; |
| - | } |
| - | |
| - | argv = command->argv ? command->argv : (char **) &null_ptr; |
| - | { |
| - | const struct built_in_command *x; |
| - | #if ENABLE_HUSH_FUNCTIONS |
| - | const struct function *funcp; |
| - | #else |
| - | enum { funcp = 0 }; |
| + | case ';': |
| + | #if ENABLE_HUSH_CASE |
| + | case_semi: |
| #endif | |
| - | char **new_env = NULL; |
| - | struct variable *old_vars = NULL; |
| - | |
| - | if (argv[command->assignment_cnt] == NULL) { |
| - | /* Assignments, but no command */ |
| - | /* Ensure redirects take effect (that is, create files). |
| - | * Try "a=t >file" */ |
| - | #if 0 /* A few cases in testsuite fail with this code. FIXME */ |
| - | rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, squirrel, /*argv_expanded:*/ NULL); |
| - | /* Set shell variables */ |
| - | if (new_env) { |
| - | argv = new_env; |
| - | while (*argv) { |
| - | set_local_var(*argv, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); |
| - | /* Do we need to flag set_local_var() errors? |
| - | * "assignment to readonly var" and "putenv error" |
| - | */ |
| - | argv++; |
| - | } |
| + | if (done_word(&dest, &ctx)) { |
| + | goto parse_error; |
| } | |
| - | /* Redirect error sets $? to 1. Otherwise, |
| - | * if evaluating assignment value set $?, retain it. |
| - | * Try "false; q=`exit 2`; echo $?" - should print 2: */ |
| - | if (rcode == 0) |
| - | rcode = G.last_exitcode; |
| - | /* Exit, _skipping_ variable restoring code: */ |
| - | goto clean_up_and_ret0; |
| - | |
| - | #else /* Older, bigger, but more correct code */ |
| - | |
| - | rcode = setup_redirects(command, squirrel); |
| - | restore_redirects(squirrel); |
| - | /* Set shell variables */ |
| - | if (G_x_mode) |
| - | bb_putchar_stderr('+'); |
| - | while (*argv) { |
| - | char *p = expand_string_to_string(*argv); |
| - | if (G_x_mode) |
| - | fprintf(stderr, " %s", p); |
| - | debug_printf_exec("set shell var:'%s'->'%s'\n", |
| - | *argv, p); |
| - | set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); |
| - | /* Do we need to flag set_local_var() errors? |
| - | * "assignment to readonly var" and "putenv error" |
| - | */ |
| - | argv++; |
| + | done_pipe(&ctx, PIPE_SEQ); |
| + | #if ENABLE_HUSH_CASE |
| + | /* Eat multiple semicolons, detect |
| + | * whether it means something special */ |
| + | while (1) { |
| + | ch = i_peek(input); |
| + | if (ch != ';') |
| + | break; |
| + | ch = i_getch(input); |
| + | nommu_addchr(&ctx.as_string, ch); |
| + | if (ctx.ctx_res_w == RES_CASE_BODY) { |
| + | ctx.ctx_dsemicolon = 1; |
| + | ctx.ctx_res_w = RES_MATCH; |
| + | break; |
| + | } |
| } | |
| - | if (G_x_mode) |
| - | bb_putchar_stderr('\n'); |
| - | /* Redirect error sets $? to 1. Otherwise, |
| - | * if evaluating assignment value set $?, retain it. |
| - | * Try "false; q=`exit 2`; echo $?" - should print 2: */ |
| - | if (rcode == 0) |
| - | rcode = G.last_exitcode; |
| - | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) |
| - | debug_leave(); |
| - | debug_printf_exec("run_pipe: return %d\n", rcode); |
| - | return rcode; |
| #endif | |
| - | } |
| - | |
| - | /* Expand the rest into (possibly) many strings each */ |
| - | if (0) {} |
| - | #if ENABLE_HUSH_BASH_COMPAT |
| - | else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) { |
| - | argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt); |
| - | } |
| + | new_cmd: |
| + | /* We just finished a cmd. New one may start |
| + | * with an assignment */ |
| + | dest.o_assignment = MAYBE_ASSIGNMENT; |
| + | break; |
| + | case '&': |
| + | if (done_word(&dest, &ctx)) { |
| + | goto parse_error; |
| + | } |
| + | if (next == '&') { |
| + | ch = i_getch(input); |
| + | nommu_addchr(&ctx.as_string, ch); |
| + | done_pipe(&ctx, PIPE_AND); |
| + | } else { |
| + | done_pipe(&ctx, PIPE_BG); |
| + | } |
| + | goto new_cmd; |
| + | case '|': |
| + | if (done_word(&dest, &ctx)) { |
| + | goto parse_error; |
| + | } |
| + | #if ENABLE_HUSH_CASE |
| + | if (ctx.ctx_res_w == RES_MATCH) |
| + | break; /* we are in case's "word | word)" */ |
| #endif | |
| - | #ifdef CMD_SINGLEWORD_NOGLOB_COND |
| - | else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB_COND) { |
| - | argv_expanded = expand_strvec_to_strvec_singleword_noglob_cond(argv + command->assignment_cnt); |
| - | } |
| + | if (next == '|') { /* || */ |
| + | ch = i_getch(input); |
| + | nommu_addchr(&ctx.as_string, ch); |
| + | done_pipe(&ctx, PIPE_OR); |
| + | } else { |
| + | /* we could pick up a file descriptor choice here |
| + | * with redirect_opt_num(), but bash doesn't do it. |
| + | * "echo foo 2| cat" yields "foo 2". */ |
| + | done_command(&ctx); |
| + | #if !BB_MMU |
| + | o_reset_to_empty_unquoted(&ctx.as_string); |
| #endif | |
| - | else { |
| - | argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); |
| - | } |
| - | |
| - | /* if someone gives us an empty string: `cmd with empty output` */ |
| - | if (!argv_expanded[0]) { |
| - | free(argv_expanded); |
| - | debug_leave(); |
| - | return G.last_exitcode; |
| - | } |
| - | |
| - | x = find_builtin(argv_expanded[0]); |
| - | #if ENABLE_HUSH_FUNCTIONS |
| - | funcp = NULL; |
| - | if (!x) |
| - | funcp = find_function(argv_expanded[0]); |
| + | } |
| + | goto new_cmd; |
| + | case '(': |
| + | #if ENABLE_HUSH_CASE |
| + | /* "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 ""(... */ |
| + | ) { |
| + | continue; |
| + | } |
| #endif | |
| - | if (x || funcp) { |
| - | if (!funcp) { |
| - | if (x->b_function == builtin_exec && argv_expanded[1] == NULL) { |
| - | debug_printf("exec with redirects only\n"); |
| - | rcode = setup_redirects(command, NULL); |
| - | goto clean_up_and_ret1; |
| - | } |
| + | case '{': |
| + | if (parse_group(&dest, &ctx, input, ch) != 0) { |
| + | goto parse_error; |
| } | |
| - | rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); |
| - | if (rcode == 0) { |
| - | if (!funcp) { |
| - | debug_printf_exec(": builtin '%s' '%s'...\n", |
| - | x->b_cmd, argv_expanded[1]); |
| - | rcode = x->b_function(argv_expanded) & 0xff; |
| - | fflush_all(); |
| - | } |
| - | #if ENABLE_HUSH_FUNCTIONS |
| - | else { |
| - | # if ENABLE_HUSH_LOCAL |
| - | struct variable **sv; |
| - | sv = G.shadowed_vars_pp; |
| - | G.shadowed_vars_pp = &old_vars; |
| - | # endif |
| - | debug_printf_exec(": function '%s' '%s'...\n", |
| - | funcp->name, argv_expanded[1]); |
| - | rcode = run_function(funcp, argv_expanded) & 0xff; |
| - | # if ENABLE_HUSH_LOCAL |
| - | G.shadowed_vars_pp = sv; |
| - | # endif |
| - | } |
| + | goto new_cmd; |
| + | case ')': |
| + | #if ENABLE_HUSH_CASE |
| + | if (ctx.ctx_res_w == RES_MATCH) |
| + | goto case_semi; |
| #endif | |
| - | } |
| - | clean_up_and_ret: |
| - | unset_vars(new_env); |
| - | add_vars(old_vars); |
| - | /* clean_up_and_ret0: */ |
| - | restore_redirects(squirrel); |
| - | clean_up_and_ret1: |
| - | free(argv_expanded); |
| - | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) |
| - | debug_leave(); |
| - | debug_printf_exec("run_pipe return %d\n", rcode); |
| - | return rcode; |
| + | case '}': |
| + | /* proper use of this character is caught by end_trigger: |
| + | * if we see {, we call parse_group(..., end_trigger='}') |
| + | * and it will match } earlier (not here). */ |
| + | syntax_error_unexpected_ch(ch); |
| + | goto parse_error; |
| + | default: |
| + | if (HUSH_DEBUG) |
| + | bb_error_msg_and_die("BUG: unexpected %c\n", ch); |
| } | |
| + | } /* while (1) */ |
| - | if (ENABLE_FEATURE_SH_STANDALONE) { |
| - | int n = find_applet_by_name(argv_expanded[0]); |
| - | if (n >= 0 && APPLET_IS_NOFORK(n)) { |
| - | rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); |
| - | if (rcode == 0) { |
| - | debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", |
| - | argv_expanded[0], argv_expanded[1]); |
| - | rcode = run_nofork_applet(n, argv_expanded); |
| - | } |
| - | goto clean_up_and_ret; |
| + | parse_error: |
| + | { |
| + | struct parse_context *pctx; |
| + | IF_HAS_KEYWORDS(struct parse_context *p2;) |
| + | |
| + | /* Clean up allocated tree. |
| + | * Sample for finding leaks on syntax error recovery path. |
| + | * Run it from interactive shell, watch pmap `pidof hush`. |
| + | * while if false; then false; fi; do break; fi |
| + | * Samples to catch leaks at execution: |
| + | * while if (true | {true;}); then echo ok; fi; do break; done |
| + | * while if (true | {true;}); then echo ok; fi; do (if echo ok; break; then :; fi) | cat; break; done |
| + | */ |
| + | pctx = &ctx; |
| + | do { |
| + | /* Update pipe/command counts, |
| + | * otherwise freeing may miss some */ |
| + | done_pipe(pctx, PIPE_SEQ); |
| + | debug_printf_clean("freeing list %p from ctx %p\n", |
| + | pctx->list_head, pctx); |
| + | debug_print_tree(pctx->list_head, 0); |
| + | free_pipe_list(pctx->list_head); |
| + | debug_printf_clean("freed list %p\n", pctx->list_head); |
| + | #if !BB_MMU |
| + | o_free_unsafe(&pctx->as_string); |
| + | #endif |
| + | IF_HAS_KEYWORDS(p2 = pctx->stack;) |
| + | if (pctx != &ctx) { |
| + | free(pctx); |
| } | |
| + | IF_HAS_KEYWORDS(pctx = p2;) |
| + | } while (HAS_KEYWORDS && pctx); |
| + | /* Free text, clear all dest fields */ |
| + | o_free(&dest); |
| + | /* If we are not in top-level parse, we return, |
| + | * our caller will propagate error. |
| + | */ |
| + | if (end_trigger != ';') { |
| + | #if !BB_MMU |
| + | if (pstring) |
| + | *pstring = NULL; |
| + | #endif |
| + | debug_leave(); |
| + | return ERR_PTR; |
| } | |
| - | /* It is neither builtin nor applet. We must fork. */ |
| + | /* Discard cached input, force prompt */ |
| + | input->p = NULL; |
| + | IF_HUSH_INTERACTIVE(input->promptme = 1;) |
| + | goto reset; |
| } | |
| + | } |
| - | must_fork: |
| - | /* NB: argv_expanded may already be created, and that |
| - | * might include `cmd` runs! Do not rerun it! We *must* |
| - | * use argv_expanded if it's non-NULL */ |
| - | /* Going to fork a child per each pipe member */ |
| - | pi->alive_cmds = 0; |
| - | next_infd = 0; |
| + | /*** Execution routines ***/ |
| - | cmd_no = 0; |
| - | while (cmd_no < pi->num_cmds) { |
| - | struct fd_pair pipefds; |
| - | #if !BB_MMU |
| - | volatile nommu_save_t nommu_save; |
| - | nommu_save.new_env = NULL; |
| - | nommu_save.old_vars = NULL; |
| - | nommu_save.argv = NULL; |
| - | nommu_save.argv_from_re_execing = NULL; |
| - | #endif |
| - | command = &pi->cmds[cmd_no]; |
| - | cmd_no++; |
| - | if (command->argv) { |
| - | debug_printf_exec(": pipe member '%s' '%s'...\n", |
| - | command->argv[0], command->argv[1]); |
| - | } else { |
| - | debug_printf_exec(": pipe member with no argv\n"); |
| - | } |
| + | /* Expansion can recurse, need forward decls: */ |
| + | static char *expand_string_to_string(const char *str); |
| + | static int process_command_subs(o_string *dest, const char *s); |
| - | /* pipes are inserted between pairs of commands */ |
| - | pipefds.rd = 0; |
| - | pipefds.wr = 1; |
| - | if (cmd_no < pi->num_cmds) |
| - | xpiped_pair(pipefds); |
| + | /* 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 |
| + | * of strings. (Think VAR="a b"; echo $VAR). |
| + | * This new list is allocated as a single malloc block. |
| + | * NULL-terminated list of char* pointers is at the beginning of it, |
| + | * followed by strings themself. |
| + | * Caller can deallocate entire list by single free(list). */ |
| - | command->pid = BB_MMU ? fork() : vfork(); |
| - | if (!command->pid) { /* child */ |
| - | #if ENABLE_HUSH_JOB |
| - | disable_restore_tty_pgrp_on_exit(); |
| - | CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ |
| + | /* Store given string, finalizing the word and starting new one whenever |
| + | * we encounter IFS char(s). This is used for expanding variable values. |
| + | * End-of-string does NOT finalize word: think about 'echo -$VAR-' */ |
| + | static int expand_on_ifs(o_string *output, int n, const char *str) |
| + | { |
| + | while (1) { |
| + | int word_len = strcspn(str, G.ifs); |
| + | if (word_len) { |
| + | if (output->o_escape || !output->o_glob) |
| + | o_addQblock(output, str, word_len); |
| + | else /* protect backslashes against globbing up :) */ |
| + | o_addblock_duplicate_backslash(output, str, word_len); |
| + | str += word_len; |
| + | } |
| + | if (!*str) /* EOL - do not finalize word */ |
| + | break; |
| + | o_addchr(output, '\0'); |
| + | debug_print_list("expand_on_ifs", output, n); |
| + | n = o_save_ptr(output, n); |
| + | str += strspn(str, G.ifs); /* skip ifs chars */ |
| + | } |
| + | debug_print_list("expand_on_ifs[1]", output, n); |
| + | return n; |
| + | } |
| - | /* Every child adds itself to new process group |
| - | * with pgid == pid_of_first_child_in_pipe */ |
| - | if (G.run_list_level == 1 && G_interactive_fd) { |
| - | pid_t pgrp; |
| - | pgrp = pi->pgrp; |
| - | if (pgrp < 0) /* true for 1st process only */ |
| - | pgrp = getpid(); |
| - | if (setpgid(0, pgrp) == 0 |
| - | && pi->followup != PIPE_BG |
| - | && G_saved_tty_pgrp /* we have ctty */ |
| - | ) { |
| - | /* We do it in *every* child, not just first, |
| - | * to avoid races */ |
| - | tcsetpgrp(G_interactive_fd, pgrp); |
| - | } |
| - | } |
| + | /* Helper to expand $((...)) and heredoc body. These act as if |
| + | * they are in double quotes, with the exception that they are not :). |
| + | * Just the rules are similar: "expand only $var and `cmd`" |
| + | * |
| + | * Returns malloced string. |
| + | * As an optimization, we return NULL if expansion is not needed. |
| + | */ |
| + | static char *expand_pseudo_dquoted(const char *str) |
| + | { |
| + | char *exp_str; |
| + | struct in_str input; |
| + | o_string dest = NULL_O_STRING; |
| + | |
| + | if (!strchr(str, '$') |
| + | #if ENABLE_HUSH_TICK |
| + | && !strchr(str, '`') |
| #endif | |
| - | if (pi->alive_cmds == 0 && pi->followup == PIPE_BG) { |
| - | /* 1st cmd in backgrounded pipe |
| - | * should have its stdin /dev/null'ed */ |
| - | close(0); |
| - | if (open(bb_dev_null, O_RDONLY)) |
| - | xopen("/", O_RDONLY); |
| - | } else { |
| - | xmove_fd(next_infd, 0); |
| - | } |
| - | xmove_fd(pipefds.wr, 1); |
| - | if (pipefds.rd > 1) |
| - | close(pipefds.rd); |
| - | /* Like bash, explicit redirects override pipes, |
| - | * and the pipe fd is available for dup'ing. */ |
| - | if (setup_redirects(command, NULL)) |
| - | _exit(1); |
| + | ) { |
| + | return NULL; |
| + | } |
| - | /* Restore default handlers just prior to exec */ |
| - | /*signal(SIGCHLD, SIG_DFL); - so far we don't have any handlers */ |
| + | /* We need to expand. Example: |
| + | * echo $(($a + `echo 1`)) $((1 + $((2)) )) |
| + | */ |
| + | setup_string_in_str(&input, str); |
| + | parse_stream_dquoted(NULL, &dest, &input, EOF); |
| + | //bb_error_msg("'%s' -> '%s'", str, dest.data); |
| + | exp_str = expand_string_to_string(dest.data); |
| + | //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); |
| + | o_free_unsafe(&dest); |
| + | return exp_str; |
| + | } |
| - | /* Stores to nommu_save list of env vars putenv'ed |
| - | * (NOMMU, on MMU we don't need that) */ |
| - | /* cast away volatility... */ |
| - | pseudo_exec((nommu_save_t*) &nommu_save, command, argv_expanded); |
| - | /* pseudo_exec() does not return */ |
| - | } |
| + | #if ENABLE_SH_MATH_SUPPORT |
| + | static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p) |
| + | { |
| + | arith_eval_hooks_t hooks; |
| + | arith_t res; |
| + | char *exp_str; |
| - | /* parent or error */ |
| - | #if ENABLE_HUSH_FAST |
| - | G.count_SIGCHLD++; |
| - | //bb_error_msg("[%d] fork in run_pipe: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); |
| - | #endif |
| - | enable_restore_tty_pgrp_on_exit(); |
| - | #if !BB_MMU |
| - | /* Clean up after vforked child */ |
| - | free(nommu_save.argv); |
| - | free(nommu_save.argv_from_re_execing); |
| - | unset_vars(nommu_save.new_env); |
| - | add_vars(nommu_save.old_vars); |
| - | #endif |
| - | free(argv_expanded); |
| - | argv_expanded = NULL; |
| - | if (command->pid < 0) { /* [v]fork failed */ |
| - | /* Clearly indicate, was it fork or vfork */ |
| - | bb_perror_msg(BB_MMU ? "vfork"+1 : "vfork"); |
| - | } else { |
| - | pi->alive_cmds++; |
| - | #if ENABLE_HUSH_JOB |
| - | /* Second and next children need to know pid of first one */ |
| - | if (pi->pgrp < 0) |
| - | pi->pgrp = command->pid; |
| + | hooks.lookupvar = get_local_var_value; |
| + | hooks.setvar = set_local_var_from_halves; |
| + | hooks.endofname = endofname; |
| + | exp_str = expand_pseudo_dquoted(arg); |
| + | res = arith(exp_str ? exp_str : arg, errcode_p, &hooks); |
| + | free(exp_str); |
| + | return res; |
| + | } |
| #endif | |
| - | } |
| - | if (cmd_no > 1) |
| - | close(next_infd); |
| - | if (cmd_no < pi->num_cmds) |
| - | close(pipefds.wr); |
| - | /* Pass read (output) pipe end to next iteration */ |
| - | next_infd = pipefds.rd; |
| + | #if ENABLE_HUSH_BASH_COMPAT |
| + | /* ${var/[/]pattern[/repl]} helpers */ |
| + | static char *strstr_pattern(char *val, const char *pattern, int *size) |
| + | { |
| + | while (1) { |
| + | char *end = scan_and_match(val, pattern, SCAN_MOVE_FROM_RIGHT + SCAN_MATCH_LEFT_HALF); |
| + | debug_printf_varexp("val:'%s' pattern:'%s' end:'%s'\n", val, pattern, end); |
| + | if (end) { |
| + | *size = end - val; |
| + | return val; |
| + | } |
| + | if (*val == '\0') |
| + | return NULL; |
| + | /* Optimization: if "*pat" did not match the start of "string", |
| + | * we know that "tring", "ring" etc will not match too: |
| + | */ |
| + | if (pattern[0] == '*') |
| + | return NULL; |
| + | val++; |
| } | |
| + | } |
| + | static char *replace_pattern(char *val, const char *pattern, const char *repl, char exp_op) |
| + | { |
| + | char *result = NULL; |
| + | unsigned res_len = 0; |
| + | unsigned repl_len = strlen(repl); |
| - | if (!pi->alive_cmds) { |
| - | debug_leave(); |
| - | debug_printf_exec("run_pipe return 1 (all forks failed, no children)\n"); |
| - | return 1; |
| - | } |
| + | while (1) { |
| + | int size; |
| + | char *s = strstr_pattern(val, pattern, &size); |
| + | if (!s) |
| + | break; |
| - | debug_leave(); |
| - | debug_printf_exec("run_pipe return -1 (%u children started)\n", pi->alive_cmds); |
| - | return -1; |
| + | result = xrealloc(result, res_len + (s - val) + repl_len + 1); |
| + | memcpy(result + res_len, val, s - val); |
| + | res_len += s - val; |
| + | strcpy(result + res_len, repl); |
| + | res_len += repl_len; |
| + | debug_printf_varexp("val:'%s' s:'%s' result:'%s'\n", val, s, result); |
| + | |
| + | val = s + size; |
| + | if (exp_op == '/') |
| + | break; |
| + | } |
| + | if (val[0] && result) { |
| + | result = xrealloc(result, res_len + strlen(val) + 1); |
| + | strcpy(result + res_len, val); |
| + | debug_printf_varexp("val:'%s' result:'%s'\n", val, result); |
| + | } |
| + | debug_printf_varexp("result:'%s'\n", result); |
| + | return result; |
| } | |
| + | #endif |
| - | #ifndef debug_print_tree |
| - | static void debug_print_tree(struct pipe *pi, int lvl) |
| + | /* Helper: |
| + | * Handles <SPECIAL_VAR_SYMBOL>varname...<SPECIAL_VAR_SYMBOL> construct. |
| + | */ |
| + | static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, char **pp, char first_ch) |
| { | |
| - | static const char *const PIPE[] = { |
| - | [PIPE_SEQ] = "SEQ", |
| - | [PIPE_AND] = "AND", |
| - | [PIPE_OR ] = "OR" , |
| - | [PIPE_BG ] = "BG" , |
| - | }; |
| - | static const char *RES[] = { |
| - | [RES_NONE ] = "NONE" , |
| - | # if ENABLE_HUSH_IF |
| - | [RES_IF ] = "IF" , |
| - | [RES_THEN ] = "THEN" , |
| - | [RES_ELIF ] = "ELIF" , |
| - | [RES_ELSE ] = "ELSE" , |
| - | [RES_FI ] = "FI" , |
| - | # endif |
| - | # if ENABLE_HUSH_LOOPS |
| - | [RES_FOR ] = "FOR" , |
| - | [RES_WHILE] = "WHILE", |
| - | [RES_UNTIL] = "UNTIL", |
| - | [RES_DO ] = "DO" , |
| - | [RES_DONE ] = "DONE" , |
| - | # endif |
| - | # if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE |
| - | [RES_IN ] = "IN" , |
| - | # endif |
| - | # if ENABLE_HUSH_CASE |
| - | [RES_CASE ] = "CASE" , |
| - | [RES_CASE_IN ] = "CASE_IN" , |
| - | [RES_MATCH] = "MATCH", |
| - | [RES_CASE_BODY] = "CASE_BODY", |
| - | [RES_ESAC ] = "ESAC" , |
| - | # endif |
| - | [RES_XXXX ] = "XXXX" , |
| - | [RES_SNTX ] = "SNTX" , |
| - | }; |
| - | static const char *const CMDTYPE[] = { |
| - | "{}", |
| - | "()", |
| - | "[noglob]", |
| - | # if ENABLE_HUSH_FUNCTIONS |
| - | "func()", |
| - | # endif |
| - | }; |
| + | const char *val = NULL; |
| + | char *to_be_freed = NULL; |
| + | char *p = *pp; |
| + | char *var; |
| + | char first_char; |
| + | char exp_op; |
| + | char exp_save = exp_save; /* for compiler */ |
| + | char *exp_saveptr; /* points to expansion operator */ |
| + | char *exp_word = exp_word; /* for compiler */ |
| - | int pin, prn; |
| + | var = arg; |
| + | *p = '\0'; |
| + | exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL; |
| + | first_char = arg[0] = first_ch & 0x7f; |
| + | exp_op = 0; |
| - | pin = 0; |
| - | while (pi) { |
| - | fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "", |
| - | pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]); |
| - | prn = 0; |
| - | while (prn < pi->num_cmds) { |
| - | struct command *command = &pi->cmds[prn]; |
| - | char **argv = command->argv; |
| + | if (first_char == '#' && arg[1] && !exp_saveptr) { |
| + | /* handle length expansion ${#var} */ |
| + | var++; |
| + | exp_op = 'L'; |
| + | } else { |
| + | /* maybe handle parameter expansion */ |
| + | if (exp_saveptr /* if 2nd char is one of expansion operators */ |
| + | && strchr(NUMERIC_SPECVARS_STR, first_char) /* 1st char is special variable */ |
| + | ) { |
| + | /* ${?:0}, ${#[:]%0} etc */ |
| + | exp_saveptr = var + 1; |
| + | } else { |
| + | /* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */ |
| + | exp_saveptr = var+1 + strcspn(var+1, VAR_ENCODED_SUBST_OPS); |
| + | } |
| + | exp_op = exp_save = *exp_saveptr; |
| + | if (exp_op) { |
| + | exp_word = exp_saveptr + 1; |
| + | if (exp_op == ':') { |
| + | exp_op = *exp_word++; |
| + | if (ENABLE_HUSH_BASH_COMPAT |
| + | && (exp_op == '\0' || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op)) |
| + | ) { |
| + | /* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */ |
| + | exp_op = ':'; |
| + | exp_word--; |
| + | } |
| + | } |
| + | *exp_saveptr = '\0'; |
| + | } /* else: it's not an expansion op, but bare ${var} */ |
| + | } |
| - | fprintf(stderr, "%*s cmd %d assignment_cnt:%d", |
| - | lvl*2, "", prn, |
| - | command->assignment_cnt); |
| - | if (command->group) { |
| - | fprintf(stderr, " group %s: (argv=%p)%s%s\n", |
| - | CMDTYPE[command->cmd_type], |
| - | argv |
| - | # if !BB_MMU |
| - | , " group_as_string:", command->group_as_string |
| - | # else |
| - | , "", "" |
| - | # endif |
| - | ); |
| - | debug_print_tree(command->group, lvl+1); |
| - | prn++; |
| - | continue; |
| + | /* lookup the variable in question */ |
| + | if (isdigit(var[0])) { |
| + | /* parse_dollar() should have vetted var for us */ |
| + | int n = xatoi_positive(var); |
| + | if (n < G.global_argc) |
| + | val = G.global_argv[n]; |
| + | /* else val remains NULL: $N with too big N */ |
| + | } else { |
| + | switch (var[0]) { |
| + | case '$': /* pid */ |
| + | val = utoa(G.root_pid); |
| + | break; |
| + | case '!': /* bg pid */ |
| + | val = G.last_bg_pid ? utoa(G.last_bg_pid) : ""; |
| + | break; |
| + | case '?': /* exitcode */ |
| + | val = utoa(G.last_exitcode); |
| + | break; |
| + | case '#': /* argc */ |
| + | val = utoa(G.global_argc ? G.global_argc-1 : 0); |
| + | break; |
| + | default: |
| + | val = get_local_var_value(var); |
| + | } |
| + | } |
| + | |
| + | /* Handle any expansions */ |
| + | if (exp_op == 'L') { |
| + | debug_printf_expand("expand: length(%s)=", val); |
| + | val = utoa(val ? strlen(val) : 0); |
| + | debug_printf_expand("%s\n", val); |
| + | } else if (exp_op) { |
| + | if (exp_op == '%' || exp_op == '#') { |
| + | /* Standard-mandated substring removal ops: |
| + | * ${parameter%word} - remove smallest suffix pattern |
| + | * ${parameter%%word} - remove largest suffix pattern |
| + | * ${parameter#word} - remove smallest prefix pattern |
| + | * ${parameter##word} - remove largest prefix pattern |
| + | * |
| + | * Word is expanded to produce a glob pattern. |
| + | * Then var's value is matched to it and matching part removed. |
| + | */ |
| + | if (val && val[0]) { |
| + | char *exp_exp_word; |
| + | char *loc; |
| + | unsigned scan_flags = pick_scan(exp_op, *exp_word); |
| + | if (exp_op == *exp_word) /* ## or %% */ |
| + | exp_word++; |
| + | //TODO: avoid xstrdup unless needed |
| + | // (see HACK ALERT below) |
| + | val = to_be_freed = xstrdup(val); |
| + | exp_exp_word = expand_pseudo_dquoted(exp_word); |
| + | if (exp_exp_word) |
| + | exp_word = exp_exp_word; |
| + | loc = scan_and_match(to_be_freed, exp_word, scan_flags); |
| + | //bb_error_msg("op:%c str:'%s' pat:'%s' res:'%s'", |
| + | // exp_op, to_be_freed, exp_word, loc); |
| + | free(exp_exp_word); |
| + | if (loc) { /* match was found */ |
| + | if (scan_flags & SCAN_MATCH_LEFT_HALF) /* #[#] */ |
| + | val = loc; |
| + | else /* %[%] */ |
| + | *loc = '\0'; |
| + | } |
| } | |
| - | if (argv) while (*argv) { |
| - | fprintf(stderr, " '%s'", *argv); |
| - | argv++; |
| + | } |
| + | #if ENABLE_HUSH_BASH_COMPAT |
| + | else if (exp_op == '/' || exp_op == '\\') { |
| + | /* Empty variable always gives nothing: */ |
| + | // "v=''; echo ${v/*/w}" prints "" |
| + | if (val && val[0]) { |
| + | /* It's ${var/[/]pattern[/repl]} thing */ |
| + | char *pattern, *repl, *t; |
| + | pattern = expand_pseudo_dquoted(exp_word); |
| + | if (!pattern) |
| + | pattern = xstrdup(exp_word); |
| + | debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern); |
| + | *p++ = SPECIAL_VAR_SYMBOL; |
| + | exp_word = p; |
| + | p = strchr(p, SPECIAL_VAR_SYMBOL); |
| + | *p = '\0'; |
| + | repl = expand_pseudo_dquoted(exp_word); |
| + | debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl); |
| + | /* HACK ALERT. We depend here on the fact that |
| + | * G.global_argv and results of utoa and get_local_var_value |
| + | * are actually in writable memory: |
| + | * replace_pattern momentarily stores NULs there. */ |
| + | t = (char*)val; |
| + | to_be_freed = replace_pattern(t, |
| + | pattern, |
| + | (repl ? repl : exp_word), |
| + | exp_op); |
| + | if (to_be_freed) /* at least one replace happened */ |
| + | val = to_be_freed; |
| + | free(pattern); |
| + | free(repl); |
| } | |
| - | fprintf(stderr, "\n"); |
| - | prn++; |
| } | |
| - | pi = pi->next; |
| - | pin++; |
| - | } |
| - | } |
| - | #endif /* debug_print_tree */ |
| - | |
| - | /* NB: called by pseudo_exec, and therefore must not modify any |
| - | * global data until exec/_exit (we can be a child after vfork!) */ |
| - | static int run_list(struct pipe *pi) |
| - | { |
| - | #if ENABLE_HUSH_CASE |
| - | char *case_word = NULL; |
| - | #endif |
| - | #if ENABLE_HUSH_LOOPS |
| - | struct pipe *loop_top = NULL; |
| - | char **for_lcur = NULL; |
| - | char **for_list = NULL; |
| - | #endif |
| - | smallint last_followup; |
| - | smalluint rcode; |
| - | #if ENABLE_HUSH_IF || ENABLE_HUSH_CASE |
| - | smalluint cond_code = 0; |
| - | #else |
| - | enum { cond_code = 0 }; |
| - | #endif |
| - | #if HAS_KEYWORDS |
| - | smallint rword; /* enum reserved_style */ |
| - | smallint last_rword; /* ditto */ |
| #endif | |
| + | else if (exp_op == ':') { |
| + | #if ENABLE_HUSH_BASH_COMPAT && ENABLE_SH_MATH_SUPPORT |
| + | /* It's ${var:N[:M]} bashism. |
| + | * Note that in encoded form it has TWO parts: |
| + | * var:N<SPECIAL_VAR_SYMBOL>M<SPECIAL_VAR_SYMBOL> |
| + | */ |
| + | arith_t beg, len; |
| + | int errcode = 0; |
| - | debug_printf_exec("run_list start lvl %d\n", G.run_list_level); |
| - | debug_enter(); |
| + | beg = expand_and_evaluate_arith(exp_word, &errcode); |
| + | debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg); |
| + | *p++ = SPECIAL_VAR_SYMBOL; |
| + | exp_word = p; |
| + | p = strchr(p, SPECIAL_VAR_SYMBOL); |
| + | *p = '\0'; |
| + | len = expand_and_evaluate_arith(exp_word, &errcode); |
| + | debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len); |
| - | #if ENABLE_HUSH_LOOPS |
| - | /* Check syntax for "for" */ |
| - | for (struct pipe *cpipe = pi; cpipe; cpipe = cpipe->next) { |
| - | if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN) |
| - | continue; |
| - | /* current word is FOR or IN (BOLD in comments below) */ |
| - | if (cpipe->next == NULL) { |
| - | syntax_error("malformed for"); |
| - | debug_leave(); |
| - | debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level); |
| - | return 1; |
| - | } |
| - | /* "FOR v; do ..." and "for v IN a b; do..." are ok */ |
| - | if (cpipe->next->res_word == RES_DO) |
| - | continue; |
| - | /* next word is not "do". It must be "in" then ("FOR v in ...") */ |
| - | if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */ |
| - | || cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */ |
| - | ) { |
| - | syntax_error("malformed for"); |
| - | debug_leave(); |
| - | debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level); |
| - | return 1; |
| - | } |
| - | } |
| + | if (errcode >= 0 && len >= 0) { /* bash compat: len < 0 is illegal */ |
| + | if (beg < 0) /* bash compat */ |
| + | beg = 0; |
| + | debug_printf_varexp("from val:'%s'\n", val); |
| + | if (len == 0 || !val || beg >= strlen(val)) |
| + | val = ""; |
| + | else { |
| + | /* Paranoia. What if user entered 9999999999999 |
| + | * which fits in arith_t but not int? */ |
| + | if (len >= INT_MAX) |
| + | len = INT_MAX; |
| + | val = to_be_freed = xstrndup(val + beg, len); |
| + | } |
| + | debug_printf_varexp("val:'%s'\n", val); |
| + | } else |
| #endif | |
| + | { |
| + | die_if_script("malformed ${%s:...}", var); |
| + | val = ""; |
| + | } |
| + | } else { /* one of "-=+?" */ |
| + | /* Standard-mandated substitution ops: |
| + | * ${var?word} - indicate error if unset |
| + | * If var is unset, word (or a message indicating it is unset |
| + | * if word is null) is written to standard error |
| + | * and the shell exits with a non-zero exit status. |
| + | * Otherwise, the value of var is substituted. |
| + | * ${var-word} - use default value |
| + | * If var is unset, word is substituted. |
| + | * ${var=word} - assign and use default value |
| + | * If var is unset, word is assigned to var. |
| + | * In all cases, final value of var is substituted. |
| + | * ${var+word} - use alternative value |
| + | * If var is unset, null is substituted. |
| + | * Otherwise, word is substituted. |
| + | * |
| + | * Word is subjected to tilde expansion, parameter expansion, |
| + | * command substitution, and arithmetic expansion. |
| + | * If word is not needed, it is not expanded. |
| + | * |
| + | * Colon forms (${var:-word}, ${var:=word} etc) do the same, |
| + | * but also treat null var as if it is unset. |
| + | */ |
| + | int use_word = (!val || ((exp_save == ':') && !val[0])); |
| + | if (exp_op == '+') |
| + | use_word = !use_word; |
| + | debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, |
| + | (exp_save == ':') ? "true" : "false", use_word); |
| + | if (use_word) { |
| + | to_be_freed = expand_pseudo_dquoted(exp_word); |
| + | if (to_be_freed) |
| + | exp_word = to_be_freed; |
| + | if (exp_op == '?') { |
| + | /* mimic bash message */ |
| + | die_if_script("%s: %s", |
| + | var, |
| + | exp_word[0] ? exp_word : "parameter null or not set" |
| + | ); |
| + | //TODO: how interactive bash aborts expansion mid-command? |
| + | } else { |
| + | val = exp_word; |
| + | } |
| - | /* Past this point, all code paths should jump to ret: label |
| - | * in order to return, no direct "return" statements please. |
| - | * This helps to ensure that no memory is leaked. */ |
| + | if (exp_op == '=') { |
| + | /* ${var=[word]} or ${var:=[word]} */ |
| + | if (isdigit(var[0]) || var[0] == '#') { |
| + | /* mimic bash message */ |
| + | die_if_script("$%s: cannot assign in this way", var); |
| + | val = NULL; |
| + | } else { |
| + | char *new_var = xasprintf("%s=%s", var, val); |
| + | set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); |
| + | } |
| + | } |
| + | } |
| + | } /* one of "-=+?" */ |
| - | #if ENABLE_HUSH_JOB |
| - | G.run_list_level++; |
| - | #endif |
| + | *exp_saveptr = exp_save; |
| + | } /* if (exp_op) */ |
| - | #if HAS_KEYWORDS |
| - | rword = RES_NONE; |
| - | last_rword = RES_XXXX; |
| - | #endif |
| - | last_followup = PIPE_SEQ; |
| - | rcode = G.last_exitcode; |
| + | arg[0] = first_ch; |
| - | /* Go through list of pipes, (maybe) executing them. */ |
| - | for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) { |
| - | if (G.flag_SIGINT) |
| - | break; |
| + | *pp = p; |
| + | *to_be_freed_pp = to_be_freed; |
| + | return val; |
| + | } |
| - | IF_HAS_KEYWORDS(rword = pi->res_word;) |
| - | debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n", |
| - | rword, cond_code, last_rword); |
| - | #if ENABLE_HUSH_LOOPS |
| - | if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) |
| - | && loop_top == NULL /* avoid bumping G.depth_of_loop twice */ |
| - | ) { |
| - | /* start of a loop: remember where loop starts */ |
| - | loop_top = pi; |
| - | G.depth_of_loop++; |
| - | } |
| - | #endif |
| - | /* Still in the same "if...", "then..." or "do..." branch? */ |
| - | if (IF_HAS_KEYWORDS(rword == last_rword &&) 1) { |
| - | if ((rcode == 0 && last_followup == PIPE_OR) |
| - | || (rcode != 0 && last_followup == PIPE_AND) |
| - | ) { |
| - | /* It is "<true> || CMD" or "<false> && CMD" |
| - | * and we should not execute CMD */ |
| - | debug_printf_exec("skipped cmd because of || or &&\n"); |
| - | last_followup = pi->followup; |
| - | continue; |
| - | } |
| - | } |
| - | last_followup = pi->followup; |
| - | IF_HAS_KEYWORDS(last_rword = rword;) |
| - | #if ENABLE_HUSH_IF |
| - | if (cond_code) { |
| - | if (rword == RES_THEN) { |
| - | /* if false; then ... fi has exitcode 0! */ |
| - | G.last_exitcode = rcode = EXIT_SUCCESS; |
| - | /* "if <false> THEN cmd": skip cmd */ |
| - | continue; |
| - | } |
| - | } else { |
| - | if (rword == RES_ELSE || rword == RES_ELIF) { |
| - | /* "if <true> then ... ELSE/ELIF cmd": |
| - | * skip cmd and all following ones */ |
| - | break; |
| - | } |
| - | } |
| - | #endif |
| - | #if ENABLE_HUSH_LOOPS |
| - | if (rword == RES_FOR) { /* && pi->num_cmds - always == 1 */ |
| - | if (!for_lcur) { |
| - | /* first loop through for */ |
| + | /* 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 |
| + | * variables/parameters with whitespace, $* and $@, and constructs like |
| + | * 'echo -$*-'. If you play here, you must run testsuite afterwards! */ |
| + | static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask) |
| + | { |
| + | /* or_mask is either 0 (normal case) or 0x80 - |
| + | * expansion of right-hand side of assignment == 1-element expand. |
| + | * It will also do no globbing, and thus we must not backslash-quote! |
| + | */ |
| + | char ored_ch; |
| + | char *p; |
| - | static const char encoded_dollar_at[] ALIGN1 = { |
| - | SPECIAL_VAR_SYMBOL, '@' | 0x80, SPECIAL_VAR_SYMBOL, '\0' |
| - | }; /* encoded representation of "$@" */ |
| - | static const char *const encoded_dollar_at_argv[] = { |
| - | encoded_dollar_at, NULL |
| - | }; /* argv list with one element: "$@" */ |
| - | char **vals; |
| + | ored_ch = 0; |
| - | vals = (char**)encoded_dollar_at_argv; |
| - | if (pi->next->res_word == RES_IN) { |
| - | /* if no variable values after "in" we skip "for" */ |
| - | if (!pi->next->cmds[0].argv) { |
| - | G.last_exitcode = rcode = EXIT_SUCCESS; |
| - | debug_printf_exec(": null FOR: exitcode EXIT_SUCCESS\n"); |
| - | break; |
| - | } |
| - | vals = pi->next->cmds[0].argv; |
| - | } /* else: "for var; do..." -> assume "$@" list */ |
| - | /* create list of variable values */ |
| - | debug_print_strings("for_list made from", vals); |
| - | for_list = expand_strvec_to_strvec(vals); |
| - | for_lcur = for_list; |
| - | debug_print_strings("for_list", for_list); |
| - | } |
| - | if (!*for_lcur) { |
| - | /* "for" loop is over, clean up */ |
| - | free(for_list); |
| - | for_list = NULL; |
| - | for_lcur = NULL; |
| - | break; |
| - | } |
| - | /* Insert next value from for_lcur */ |
| - | /* note: *for_lcur already has quotes removed, $var expanded, etc */ |
| - | set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); |
| - | continue; |
| - | } |
| - | if (rword == RES_IN) { |
| - | continue; /* "for v IN list;..." - "in" has no cmds anyway */ |
| - | } |
| - | if (rword == RES_DONE) { |
| - | continue; /* "done" has no cmds too */ |
| - | } |
| + | debug_printf_expand("expand_vars_to_list: arg:'%s' or_mask:%x\n", arg, or_mask); |
| + | debug_print_list("expand_vars_to_list", output, n); |
| + | n = o_save_ptr(output, n); |
| + | debug_print_list("expand_vars_to_list[0]", output, n); |
| + | |
| + | while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) { |
| + | char first_ch; |
| + | int i; |
| + | char *to_be_freed = NULL; |
| + | const char *val = NULL; |
| + | #if ENABLE_HUSH_TICK |
| + | o_string subst_result = NULL_O_STRING; |
| #endif | |
| - | #if ENABLE_HUSH_CASE |
| - | if (rword == RES_CASE) { |
| - | case_word = expand_strvec_to_string(pi->cmds->argv); |
| - | continue; |
| - | } |
| - | if (rword == RES_MATCH) { |
| - | char **argv; |
| + | #if ENABLE_SH_MATH_SUPPORT |
| + | char arith_buf[sizeof(arith_t)*3 + 2]; |
| + | #endif |
| + | o_addblock(output, arg, p - arg); |
| + | debug_print_list("expand_vars_to_list[1]", output, n); |
| + | arg = ++p; |
| + | p = strchr(p, SPECIAL_VAR_SYMBOL); |
| - | if (!case_word) /* "case ... matched_word) ... WORD)": we executed selected branch, stop */ |
| + | first_ch = arg[0] | or_mask; /* forced to "quoted" if or_mask = 0x80 */ |
| + | /* "$@" is special. Even if quoted, it can still |
| + | * expand to nothing (not even an empty string) */ |
| + | if ((first_ch & 0x7f) != '@') |
| + | ored_ch |= first_ch; |
| + | |
| + | switch (first_ch & 0x7f) { |
| + | /* Highest bit in first_ch indicates that var is double-quoted */ |
| + | case '*': |
| + | case '@': |
| + | i = 1; |
| + | if (!G.global_argv[i]) |
| break; | |
| - | /* all prev words didn't match, does this one match? */ |
| - | argv = pi->cmds->argv; |
| - | while (*argv) { |
| - | char *pattern = expand_string_to_string(*argv); |
| - | /* TODO: which FNM_xxx flags to use? */ |
| - | cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); |
| - | free(pattern); |
| - | if (cond_code == 0) { /* match! we will execute this branch */ |
| - | free(case_word); /* make future "word)" stop */ |
| - | case_word = NULL; |
| - | break; |
| + | ored_ch |= first_ch; /* do it for "$@" _now_, when we know it's not empty */ |
| + | if (!(first_ch & 0x80)) { /* unquoted $* or $@ */ |
| + | smallint sv = output->o_escape; |
| + | /* unquoted var's contents should be globbed, so don't escape */ |
| + | output->o_escape = 0; |
| + | while (G.global_argv[i]) { |
| + | n = expand_on_ifs(output, n, G.global_argv[i]); |
| + | debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1); |
| + | if (G.global_argv[i++][0] && G.global_argv[i]) { |
| + | /* this argv[] is not empty and not last: |
| + | * put terminating NUL, start new word */ |
| + | o_addchr(output, '\0'); |
| + | debug_print_list("expand_vars_to_list[2]", output, n); |
| + | n = o_save_ptr(output, n); |
| + | debug_print_list("expand_vars_to_list[3]", output, n); |
| + | } |
| + | } |
| + | output->o_escape = sv; |
| + | } else |
| + | /* If or_mask is nonzero, we handle assignment 'a=....$@.....' |
| + | * and in this case should treat it like '$*' - see 'else...' below */ |
| + | if (first_ch == ('@'|0x80) && !or_mask) { /* quoted $@ */ |
| + | while (1) { |
| + | o_addQstr(output, G.global_argv[i]); |
| + | if (++i >= G.global_argc) |
| + | break; |
| + | o_addchr(output, '\0'); |
| + | debug_print_list("expand_vars_to_list[4]", output, n); |
| + | n = o_save_ptr(output, n); |
| + | } |
| + | } else { /* quoted $*: add as one word */ |
| + | while (1) { |
| + | o_addQstr(output, G.global_argv[i]); |
| + | if (!G.global_argv[++i]) |
| + | break; |
| + | if (G.ifs[0]) |
| + | o_addchr(output, G.ifs[0]); |
| } | |
| - | argv++; |
| } | |
| - | continue; |
| - | } |
| - | if (rword == RES_CASE_BODY) { /* inside of a case branch */ |
| - | if (cond_code != 0) |
| - | continue; /* not matched yet, skip this pipe */ |
| - | } |
| + | break; |
| + | case SPECIAL_VAR_SYMBOL: /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_SYMBOL> */ |
| + | /* "Empty variable", used to make "" etc to not disappear */ |
| + | arg++; |
| + | ored_ch = 0x80; |
| + | break; |
| + | #if ENABLE_HUSH_TICK |
| + | case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */ |
| + | *p = '\0'; |
| + | arg++; |
| + | /* Can't just stuff it into output o_string, |
| + | * expanded result may need to be globbed |
| + | * and $IFS-splitted */ |
| + | debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch); |
| + | G.last_exitcode = process_command_subs(&subst_result, arg); |
| + | debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); |
| + | val = subst_result.data; |
| + | goto store_val; |
| #endif | |
| - | /* Just pressing <enter> in shell should check for jobs. |
| - | * OTOH, in non-interactive shell this is useless |
| - | * and only leads to extra job checks */ |
| - | if (pi->num_cmds == 0) { |
| - | if (G_interactive_fd) |
| - | goto check_jobs_and_continue; |
| - | continue; |
| - | } |
| + | #if ENABLE_SH_MATH_SUPPORT |
| + | case '+': { /* <SPECIAL_VAR_SYMBOL>+cmd<SPECIAL_VAR_SYMBOL> */ |
| + | arith_t res; |
| + | int errcode; |
| - | /* After analyzing all keywords and conditions, we decided |
| - | * to execute this pipe. NB: have to do checkjobs(NULL) |
| - | * after run_pipe to collect any background children, |
| - | * even if list execution is to be stopped. */ |
| - | debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds); |
| - | { |
| - | int r; |
| - | #if ENABLE_HUSH_LOOPS |
| - | G.flag_break_continue = 0; |
| - | #endif |
| - | rcode = r = run_pipe(pi); /* NB: rcode is a smallint */ |
| - | if (r != -1) { |
| - | /* We ran a builtin, function, or group. |
| - | * rcode is already known |
| - | * and we don't need to wait for anything. */ |
| - | G.last_exitcode = rcode; |
| - | debug_printf_exec(": builtin/func exitcode %d\n", rcode); |
| - | check_and_run_traps(0); |
| - | #if ENABLE_HUSH_LOOPS |
| - | /* Was it "break" or "continue"? */ |
| - | if (G.flag_break_continue) { |
| - | smallint fbc = G.flag_break_continue; |
| - | /* We might fall into outer *loop*, |
| - | * don't want to break it too */ |
| - | if (loop_top) { |
| - | G.depth_break_continue--; |
| - | if (G.depth_break_continue == 0) |
| - | G.flag_break_continue = 0; |
| - | /* else: e.g. "continue 2" should *break* once, *then* continue */ |
| - | } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */ |
| - | if (G.depth_break_continue != 0 || fbc == BC_BREAK) |
| - | goto check_jobs_and_break; |
| - | /* "continue": simulate end of loop */ |
| - | rword = RES_DONE; |
| - | continue; |
| - | } |
| - | #endif |
| - | #if ENABLE_HUSH_FUNCTIONS |
| - | if (G.flag_return_in_progress == 1) { |
| - | /* same as "goto check_jobs_and_break" */ |
| - | checkjobs(NULL); |
| + | arg++; /* skip '+' */ |
| + | *p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */ |
| + | debug_printf_subst("ARITH '%s' first_ch %x\n", arg, first_ch); |
| + | res = expand_and_evaluate_arith(arg, &errcode); |
| + | |
| + | if (errcode < 0) { |
| + | const char *msg = "error in arithmetic"; |
| + | switch (errcode) { |
| + | case -3: |
| + | msg = "exponent less than 0"; |
| + | break; |
| + | case -2: |
| + | msg = "divide by 0"; |
| + | break; |
| + | case -5: |
| + | msg = "expression recursion loop detected"; |
| break; | |
| } | |
| - | #endif |
| - | } else if (pi->followup == PIPE_BG) { |
| - | /* What does bash do with attempts to background builtins? */ |
| - | /* even bash 3.2 doesn't do that well with nested bg: |
| - | * try "{ { sleep 10; echo DEEP; } & echo HERE; } &". |
| - | * I'm NOT treating inner &'s as jobs */ |
| - | check_and_run_traps(0); |
| - | #if ENABLE_HUSH_JOB |
| - | if (G.run_list_level == 1) |
| - | insert_bg_job(pi); |
| - | #endif |
| - | /* Last command's pid goes to $! */ |
| - | G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; |
| - | G.last_exitcode = rcode = EXIT_SUCCESS; |
| - | debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); |
| - | } else { |
| - | #if ENABLE_HUSH_JOB |
| - | if (G.run_list_level == 1 && G_interactive_fd) { |
| - | /* Waits for completion, then fg's main shell */ |
| - | rcode = checkjobs_and_fg_shell(pi); |
| - | debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode); |
| - | check_and_run_traps(0); |
| - | } else |
| - | #endif |
| - | { /* This one just waits for completion */ |
| - | rcode = checkjobs(pi); |
| - | debug_printf_exec(": checkjobs exitcode %d\n", rcode); |
| - | check_and_run_traps(0); |
| - | } |
| - | G.last_exitcode = rcode; |
| + | die_if_script(msg); |
| } | |
| + | debug_printf_subst("ARITH RES '"arith_t_fmt"'\n", res); |
| + | sprintf(arith_buf, arith_t_fmt, res); |
| + | val = arith_buf; |
| + | break; |
| } | |
| - | |
| - | /* Analyze how result affects subsequent commands */ |
| - | #if ENABLE_HUSH_IF |
| - | if (rword == RES_IF || rword == RES_ELIF) |
| - | cond_code = rcode; |
| #endif | |
| - | #if ENABLE_HUSH_LOOPS |
| - | /* Beware of "while false; true; do ..."! */ |
| - | if (pi->next && pi->next->res_word == RES_DO) { |
| - | if (rword == RES_WHILE) { |
| - | if (rcode) { |
| - | /* "while false; do...done" - exitcode 0 */ |
| - | G.last_exitcode = rcode = EXIT_SUCCESS; |
| - | debug_printf_exec(": while expr is false: breaking (exitcode:EXIT_SUCCESS)\n"); |
| - | goto check_jobs_and_break; |
| - | } |
| - | } |
| - | if (rword == RES_UNTIL) { |
| - | if (!rcode) { |
| - | debug_printf_exec(": until expr is true: breaking\n"); |
| - | check_jobs_and_break: |
| - | checkjobs(NULL); |
| - | break; |
| + | default: |
| + | val = expand_one_var(&to_be_freed, arg, &p, first_ch); |
| + | IF_HUSH_TICK(store_val:) |
| + | if (!(first_ch & 0x80)) { /* unquoted $VAR */ |
| + | debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, output->o_escape); |
| + | if (val && val[0]) { |
| + | /* unquoted var's contents should be globbed, so don't escape */ |
| + | smallint sv = output->o_escape; |
| + | output->o_escape = 0; |
| + | n = expand_on_ifs(output, n, val); |
| + | val = NULL; |
| + | output->o_escape = sv; |
| } | |
| + | } else { /* quoted $VAR, val will be appended below */ |
| + | debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, output->o_escape); |
| } | |
| + | break; |
| + | |
| + | } /* switch (char after <SPECIAL_VAR_SYMBOL>) */ |
| + | |
| + | if (val && val[0]) { |
| + | o_addQstr(output, val); |
| } | |
| + | free(to_be_freed); |
| + | /* Do the check to avoid writing to a const string */ |
| + | if (*p != SPECIAL_VAR_SYMBOL) |
| + | *p = SPECIAL_VAR_SYMBOL; |
| + | |
| + | #if ENABLE_HUSH_TICK |
| + | o_free(&subst_result); |
| #endif | |
| + | arg = ++p; |
| + | } /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */ |
| - | check_jobs_and_continue: |
| - | checkjobs(NULL); |
| - | } /* for (pi) */ |
| + | if (arg[0]) { |
| + | debug_print_list("expand_vars_to_list[a]", output, n); |
| + | /* this part is literal, and it was already pre-quoted |
| + | * if needed (much earlier), do not use o_addQstr here! */ |
| + | o_addstr_with_NUL(output, arg); |
| + | debug_print_list("expand_vars_to_list[b]", output, n); |
| + | } else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */ |
| + | && !(ored_ch & 0x80) /* and all vars were not quoted. */ |
| + | ) { |
| + | n--; |
| + | /* allow to reuse list[n] later without re-growth */ |
| + | output->has_empty_slot = 1; |
| + | } else { |
| + | o_addchr(output, '\0'); |
| + | } |
| - | #if ENABLE_HUSH_JOB |
| - | G.run_list_level--; |
| - | #endif |
| - | #if ENABLE_HUSH_LOOPS |
| - | if (loop_top) |
| - | G.depth_of_loop--; |
| - | free(for_list); |
| - | #endif |
| - | #if ENABLE_HUSH_CASE |
| - | free(case_word); |
| + | return n; |
| + | } |
| + | |
| + | enum { |
| + | EXPVAR_FLAG_GLOB = 0x200, |
| + | EXPVAR_FLAG_ESCAPE_VARS = 0x100, |
| + | EXPVAR_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ |
| + | }; |
| + | static char **expand_variables(char **argv, unsigned or_mask) |
| + | { |
| + | int n; |
| + | char **list; |
| + | char **v; |
| + | o_string output = NULL_O_STRING; |
| + | |
| + | /* protect against globbing for "$var"? */ |
| + | /* (unquoted $var will temporarily switch it off) */ |
| + | output.o_escape = 1 & (or_mask / EXPVAR_FLAG_ESCAPE_VARS); |
| + | output.o_glob = 1 & (or_mask / EXPVAR_FLAG_GLOB); |
| + | |
| + | n = 0; |
| + | v = argv; |
| + | while (*v) { |
| + | n = expand_vars_to_list(&output, n, *v, (unsigned char)or_mask); |
| + | v++; |
| + | } |
| + | debug_print_list("expand_variables", &output, n); |
| + | |
| + | /* output.data (malloced in one block) gets returned in "list" */ |
| + | list = o_finalize_list(&output, n); |
| + | debug_print_strings("expand_variables[1]", list); |
| + | return list; |
| + | } |
| + | |
| + | static char **expand_strvec_to_strvec(char **argv) |
| + | { |
| + | return expand_variables(argv, EXPVAR_FLAG_GLOB | EXPVAR_FLAG_ESCAPE_VARS); |
| + | } |
| + | |
| + | #if ENABLE_HUSH_BASH_COMPAT |
| + | static char **expand_strvec_to_strvec_singleword_noglob(char **argv) |
| + | { |
| + | return expand_variables(argv, EXPVAR_FLAG_SINGLEWORD); |
| + | } |
| #endif | |
| - | debug_leave(); |
| - | debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode); |
| - | return rcode; |
| + | |
| + | #ifdef CMD_SINGLEWORD_NOGLOB_COND |
| + | static char **expand_strvec_to_strvec_singleword_noglob_cond(char **argv) |
| + | { |
| + | int n; |
| + | char **list; |
| + | char **v; |
| + | o_string output = NULL_O_STRING; |
| + | |
| + | n = 0; |
| + | v = argv; |
| + | while (*v) { |
| + | int is_var = is_well_formed_var_name(*v, '='); |
| + | /* is_var * 0x80: singleword expansion for vars */ |
| + | n = expand_vars_to_list(&output, n, *v, is_var * 0x80); |
| + | |
| + | /* Subtle! expand_vars_to_list did not glob last word yet. |
| + | * It does this only when fed with further data. |
| + | * Therefore we set globbing flags AFTER it, not before: |
| + | */ |
| + | |
| + | /* if it is not recognizably abc=...; then: */ |
| + | output.o_escape = !is_var; /* protect against globbing for "$var" */ |
| + | /* (unquoted $var will temporarily switch it off) */ |
| + | output.o_glob = !is_var; /* and indeed do globbing */ |
| + | v++; |
| + | } |
| + | debug_print_list("expand_cond", &output, n); |
| + | |
| + | /* output.data (malloced in one block) gets returned in "list" */ |
| + | list = o_finalize_list(&output, n); |
| + | debug_print_strings("expand_cond[1]", list); |
| + | return list; |
| } | |
| + | #endif |
| - | /* Select which version we will use */ |
| - | static int run_and_free_list(struct pipe *pi) |
| + | /* Used for expansion of right hand of assignments */ |
| + | /* 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 rcode = 0; |
| - | debug_printf_exec("run_and_free_list entered\n"); |
| - | if (!G.n_mode) { |
| - | debug_printf_exec(": run_list: 1st pipe with %d cmds\n", pi->num_cmds); |
| - | rcode = run_list(pi); |
| + | char *argv[2], **list; |
| + | |
| + | /* This is generally an optimization, but it also |
| + | * handles "", which otherwise trips over !list[0] check below. |
| + | * (is this ever happens that we actually get str="" here?) |
| + | */ |
| + | if (!strchr(str, SPECIAL_VAR_SYMBOL) && !strchr(str, '\\')) { |
| + | //TODO: Can use on strings with \ too, just unbackslash() them? |
| + | debug_printf_expand("string_to_string(fast)='%s'\n", str); |
| + | return xstrdup(str); |
| } | |
| - | /* free_pipe_list has the side effect of clearing memory. |
| - | * In the long run that function can be merged with run_list, |
| - | * but doing that now would hobble the debugging effort. */ |
| - | free_pipe_list(pi); |
| - | debug_printf_exec("run_and_free_list return %d\n", rcode); |
| - | return rcode; |
| + | |
| + | argv[0] = (char*)str; |
| + | argv[1] = NULL; |
| + | list = expand_variables(argv, EXPVAR_FLAG_ESCAPE_VARS | EXPVAR_FLAG_SINGLEWORD); |
| + | if (HUSH_DEBUG) |
| + | if (!list[0] || list[1]) |
| + | bb_error_msg_and_die("BUG in varexp2"); |
| + | /* actually, just move string 2*sizeof(char*) bytes back */ |
| + | overlapping_strcpy((char*)list, list[0]); |
| + | unbackslash((char*)list); |
| + | debug_printf_expand("string_to_string='%s'\n", (char*)list); |
| + | return (char*)list; |
| } | |
| + | /* Used for "eval" builtin */ |
| + | static char* expand_strvec_to_string(char **argv) |
| + | { |
| + | char **list; |
| - | static struct pipe *new_pipe(void) |
| + | list = expand_variables(argv, EXPVAR_FLAG_SINGLEWORD); |
| + | /* Convert all NULs to spaces */ |
| + | if (list[0]) { |
| + | int n = 1; |
| + | while (list[n]) { |
| + | if (HUSH_DEBUG) |
| + | if (list[n-1] + strlen(list[n-1]) + 1 != list[n]) |
| + | bb_error_msg_and_die("BUG in varexp3"); |
| + | /* bash uses ' ' regardless of $IFS contents */ |
| + | list[n][-1] = ' '; |
| + | n++; |
| + | } |
| + | } |
| + | overlapping_strcpy((char*)list, list[0]); |
| + | debug_printf_expand("strvec_to_string='%s'\n", (char*)list); |
| + | return (char*)list; |
| + | } |
| + | |
| + | static char **expand_assignments(char **argv, int count) |
| { | |
| - | struct pipe *pi; |
| - | pi = xzalloc(sizeof(struct pipe)); |
| - | /*pi->followup = 0; - deliberately invalid value */ |
| - | /*pi->res_word = RES_NONE; - RES_NONE is 0 anyway */ |
| - | return pi; |
| + | int i; |
| + | char **p; |
| + | |
| + | 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])); |
| + | } |
| + | G.expanded_assignments = NULL; |
| + | return p; |
| } | |
| - | /* Command (member of a pipe) is complete, or we start a new pipe |
| - | * if ctx->command is NULL. |
| - | * No errors possible here. |
| - | */ |
| - | static int done_command(struct parse_context *ctx) |
| + | |
| + | #if BB_MMU |
| + | /* never called */ |
| + | void re_execute_shell(char ***to_free, const char *s, |
| + | char *g_argv0, char **g_argv, |
| + | char **builtin_argv) NORETURN; |
| + | |
| + | static void reset_traps_to_defaults(void) |
| { | |
| - | /* The command is really already in the pipe structure, so |
| - | * advance the pipe counter and make a new, null command. */ |
| - | struct pipe *pi = ctx->pipe; |
| - | struct command *command = ctx->command; |
| + | /* This function is always called in a child shell |
| + | * after fork (not vfork, NOMMU doesn't use this function). |
| + | */ |
| + | unsigned sig; |
| + | unsigned mask; |
| - | if (command) { |
| - | if (IS_NULL_CMD(command)) { |
| - | debug_printf_parse("done_command: skipping null cmd, num_cmds=%d\n", pi->num_cmds); |
| - | goto clear_and_ret; |
| + | /* Child shells are not interactive. |
| + | * SIGTTIN/SIGTTOU/SIGTSTP should not have special handling. |
| + | * Testcase: (while :; do :; done) + ^Z should background. |
| + | * Same goes for SIGTERM, SIGHUP, SIGINT. |
| + | */ |
| + | if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS)) |
| + | return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */ |
| + | |
| + | /* Switching off SPECIAL_INTERACTIVE_SIGS. |
| + | * Stupid. It can be done with *single* &= op, but we can't use |
| + | * the fact that G.blocked_set is implemented as a bitmask |
| + | * in libc... */ |
| + | mask = (SPECIAL_INTERACTIVE_SIGS >> 1); |
| + | sig = 1; |
| + | while (1) { |
| + | if (mask & 1) { |
| + | /* Careful. Only if no trap or trap is not "" */ |
| + | if (!G.traps || !G.traps[sig] || G.traps[sig][0]) |
| + | sigdelset(&G.blocked_set, sig); |
| } | |
| - | pi->num_cmds++; |
| - | debug_printf_parse("done_command: ++num_cmds=%d\n", pi->num_cmds); |
| - | //debug_print_tree(ctx->list_head, 20); |
| - | } else { |
| - | debug_printf_parse("done_command: initializing, num_cmds=%d\n", pi->num_cmds); |
| + | mask >>= 1; |
| + | if (!mask) |
| + | break; |
| + | sig++; |
| } | |
| + | /* Our homegrown sig mask is saner to work with :) */ |
| + | G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS; |
| - | /* Only real trickiness here is that the uncommitted |
| - | * command structure is not counted in pi->num_cmds. */ |
| - | pi->cmds = xrealloc(pi->cmds, sizeof(*pi->cmds) * (pi->num_cmds+1)); |
| - | ctx->command = command = &pi->cmds[pi->num_cmds]; |
| - | clear_and_ret: |
| - | memset(command, 0, sizeof(*command)); |
| - | return pi->num_cmds; /* used only for 0/nonzero check */ |
| + | /* Resetting all traps to default except empty ones */ |
| + | mask = G.non_DFL_mask; |
| + | if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) { |
| + | if (!G.traps[sig] || !G.traps[sig][0]) |
| + | continue; |
| + | free(G.traps[sig]); |
| + | G.traps[sig] = NULL; |
| + | /* There is no signal for 0 (EXIT) */ |
| + | if (sig == 0) |
| + | continue; |
| + | /* There was a trap handler, we just removed it. |
| + | * But if sig still has non-DFL handling, |
| + | * we should not unblock the sig. */ |
| + | if (mask & 1) |
| + | continue; |
| + | sigdelset(&G.blocked_set, sig); |
| + | } |
| + | sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); |
| } | |
| - | static void done_pipe(struct parse_context *ctx, pipe_style type) |
| + | #else /* !BB_MMU */ |
| + | |
| + | static void re_execute_shell(char ***to_free, const char *s, |
| + | char *g_argv0, char **g_argv, |
| + | char **builtin_argv) NORETURN; |
| + | static void re_execute_shell(char ***to_free, const char *s, |
| + | char *g_argv0, char **g_argv, |
| + | char **builtin_argv) |
| { | |
| - | int not_null; |
| + | # define NOMMU_HACK_FMT ("-$%x:%x:%x:%x:%x:%llx" IF_HUSH_LOOPS(":%x")) |
| + | /* delims + 2 * (number of bytes in printed hex numbers) */ |
| + | char param_buf[sizeof(NOMMU_HACK_FMT) + 2 * (sizeof(int)*6 + sizeof(long long)*1)]; |
| + | char *heredoc_argv[4]; |
| + | struct variable *cur; |
| + | # if ENABLE_HUSH_FUNCTIONS |
| + | struct function *funcp; |
| + | # endif |
| + | char **argv, **pp; |
| + | unsigned cnt; |
| + | unsigned long long empty_trap_mask; |
| - | debug_printf_parse("done_pipe entered, followup %d\n", type); |
| - | /* Close previous command */ |
| - | not_null = done_command(ctx); |
| - | ctx->pipe->followup = type; |
| - | #if HAS_KEYWORDS |
| - | ctx->pipe->pi_inverted = ctx->ctx_inverted; |
| - | ctx->ctx_inverted = 0; |
| - | ctx->pipe->res_word = ctx->ctx_res_w; |
| - | #endif |
| + | if (!g_argv0) { /* heredoc */ |
| + | argv = heredoc_argv; |
| + | argv[0] = (char *) G.argv0_for_re_execing; |
| + | argv[1] = (char *) "-<"; |
| + | argv[2] = (char *) s; |
| + | argv[3] = NULL; |
| + | pp = &argv[3]; /* used as pointer to empty environment */ |
| + | goto do_exec; |
| + | } |
| - | /* Without this check, even just <enter> on command line generates |
| - | * tree of three NOPs (!). Which is harmless but annoying. |
| - | * IOW: it is safe to do it unconditionally. */ |
| - | if (not_null |
| - | #if ENABLE_HUSH_IF |
| - | || ctx->ctx_res_w == RES_FI |
| - | #endif |
| - | #if ENABLE_HUSH_LOOPS |
| - | || ctx->ctx_res_w == RES_DONE |
| - | || ctx->ctx_res_w == RES_FOR |
| - | || ctx->ctx_res_w == RES_IN |
| - | #endif |
| - | #if ENABLE_HUSH_CASE |
| - | || ctx->ctx_res_w == RES_ESAC |
| - | #endif |
| - | ) { |
| - | struct pipe *new_p; |
| - | debug_printf_parse("done_pipe: adding new pipe: " |
| - | "not_null:%d ctx->ctx_res_w:%d\n", |
| - | not_null, ctx->ctx_res_w); |
| - | new_p = new_pipe(); |
| - | ctx->pipe->next = new_p; |
| - | ctx->pipe = new_p; |
| - | /* RES_THEN, RES_DO etc are "sticky" - |
| - | * they remain set for pipes inside if/while. |
| - | * This is used to control execution. |
| - | * RES_FOR and RES_IN are NOT sticky (needed to support |
| - | * cases where variable or value happens to match a keyword): |
| - | */ |
| - | #if ENABLE_HUSH_LOOPS |
| - | if (ctx->ctx_res_w == RES_FOR |
| - | || ctx->ctx_res_w == RES_IN) |
| - | ctx->ctx_res_w = RES_NONE; |
| - | #endif |
| - | #if ENABLE_HUSH_CASE |
| - | if (ctx->ctx_res_w == RES_MATCH) |
| - | ctx->ctx_res_w = RES_CASE_BODY; |
| - | if (ctx->ctx_res_w == RES_CASE) |
| - | ctx->ctx_res_w = RES_CASE_IN; |
| - | #endif |
| - | ctx->command = NULL; /* trick done_command below */ |
| - | /* Create the memory for command, roughly: |
| - | * ctx->pipe->cmds = new struct command; |
| - | * ctx->command = &ctx->pipe->cmds[0]; |
| - | */ |
| - | done_command(ctx); |
| - | //debug_print_tree(ctx->list_head, 10); |
| + | cnt = 0; |
| + | pp = builtin_argv; |
| + | if (pp) while (*pp++) |
| + | cnt++; |
| + | |
| + | empty_trap_mask = 0; |
| + | if (G.traps) { |
| + | int sig; |
| + | for (sig = 1; sig < NSIG; sig++) { |
| + | if (G.traps[sig] && !G.traps[sig][0]) |
| + | empty_trap_mask |= 1LL << sig; |
| + | } |
| } | |
| - | debug_printf_parse("done_pipe return\n"); |
| - | } |
| - | static void initialize_context(struct parse_context *ctx) |
| - | { |
| - | memset(ctx, 0, sizeof(*ctx)); |
| - | ctx->pipe = ctx->list_head = new_pipe(); |
| - | /* Create the memory for command, roughly: |
| - | * ctx->pipe->cmds = new struct command; |
| - | * ctx->command = &ctx->pipe->cmds[0]; |
| + | sprintf(param_buf, NOMMU_HACK_FMT |
| + | , (unsigned) G.root_pid |
| + | , (unsigned) G.root_ppid |
| + | , (unsigned) G.last_bg_pid |
| + | , (unsigned) G.last_exitcode |
| + | , cnt |
| + | , empty_trap_mask |
| + | IF_HUSH_LOOPS(, G.depth_of_loop) |
| + | ); |
| + | # undef NOMMU_HACK_FMT |
| + | /* 1:hush 2:-$<pid>:<pid>:<exitcode>:<etc...> <vars...> <funcs...> |
| + | * 3:-c 4:<cmd> 5:<arg0> <argN...> 6:NULL |
| */ | |
| - | done_command(ctx); |
| + | cnt += 6; |
| + | for (cur = G.top_var; cur; cur = cur->next) { |
| + | if (!cur->flg_export || cur->flg_read_only) |
| + | cnt += 2; |
| + | } |
| + | # if ENABLE_HUSH_FUNCTIONS |
| + | for (funcp = G.top_func; funcp; funcp = funcp->next) |
| + | cnt += 3; |
| + | # endif |
| + | pp = g_argv; |
| + | while (*pp++) |
| + | cnt++; |
| + | *to_free = argv = pp = xzalloc(sizeof(argv[0]) * cnt); |
| + | *pp++ = (char *) G.argv0_for_re_execing; |
| + | *pp++ = param_buf; |
| + | for (cur = G.top_var; cur; cur = cur->next) { |
| + | if (strcmp(cur->varstr, hush_version_str) == 0) |
| + | continue; |
| + | if (cur->flg_read_only) { |
| + | *pp++ = (char *) "-R"; |
| + | *pp++ = cur->varstr; |
| + | } else if (!cur->flg_export) { |
| + | *pp++ = (char *) "-V"; |
| + | *pp++ = cur->varstr; |
| + | } |
| + | } |
| + | # if ENABLE_HUSH_FUNCTIONS |
| + | for (funcp = G.top_func; funcp; funcp = funcp->next) { |
| + | *pp++ = (char *) "-F"; |
| + | *pp++ = funcp->name; |
| + | *pp++ = funcp->body_as_string; |
| + | } |
| + | # endif |
| + | /* We can pass activated traps here. Say, -Tnn:trap_string |
| + | * |
| + | * However, POSIX says that subshells reset signals with traps |
| + | * to SIG_DFL. |
| + | * I tested bash-3.2 and it not only does that with true subshells |
| + | * of the form ( list ), but with any forked children shells. |
| + | * I set trap "echo W" WINCH; and then tried: |
| + | * |
| + | * { echo 1; sleep 20; echo 2; } & |
| + | * while true; do echo 1; sleep 20; echo 2; break; done & |
| + | * true | { echo 1; sleep 20; echo 2; } | cat |
| + | * |
| + | * In all these cases sending SIGWINCH to the child shell |
| + | * did not run the trap. If I add trap "echo V" WINCH; |
| + | * _inside_ group (just before echo 1), it works. |
| + | * |
| + | * I conclude it means we don't need to pass active traps here. |
| + | * Even if we would use signal handlers instead of signal masking |
| + | * in order to implement trap handling, |
| + | * exec syscall below resets signals to SIG_DFL for us. |
| + | */ |
| + | *pp++ = (char *) "-c"; |
| + | *pp++ = (char *) s; |
| + | if (builtin_argv) { |
| + | while (*++builtin_argv) |
| + | *pp++ = *builtin_argv; |
| + | *pp++ = (char *) ""; |
| + | } |
| + | *pp++ = g_argv0; |
| + | while (*g_argv) |
| + | *pp++ = *g_argv++; |
| + | /* *pp = NULL; - is already there */ |
| + | pp = environ; |
| + | |
| + | do_exec: |
| + | debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s); |
| + | sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); |
| + | execve(bb_busybox_exec_path, argv, pp); |
| + | /* Fallback. Useful for init=/bin/hush usage etc */ |
| + | if (argv[0][0] == '/') |
| + | execve(argv[0], argv, pp); |
| + | xfunc_error_retval = 127; |
| + | bb_error_msg_and_die("can't re-execute the shell"); |
| } | |
| + | #endif /* !BB_MMU */ |
| - | /* If a reserved word is found and processed, parse context is modified |
| - | * and 1 is returned. |
| - | */ |
| - | #if HAS_KEYWORDS |
| - | struct reserved_combo { |
| - | char literal[6]; |
| - | unsigned char res; |
| - | unsigned char assignment_flag; |
| - | int flag; |
| - | }; |
| - | enum { |
| - | FLAG_END = (1 << RES_NONE ), |
| - | # if ENABLE_HUSH_IF |
| - | FLAG_IF = (1 << RES_IF ), |
| - | FLAG_THEN = (1 << RES_THEN ), |
| - | FLAG_ELIF = (1 << RES_ELIF ), |
| - | FLAG_ELSE = (1 << RES_ELSE ), |
| - | FLAG_FI = (1 << RES_FI ), |
| - | # endif |
| - | # if ENABLE_HUSH_LOOPS |
| - | FLAG_FOR = (1 << RES_FOR ), |
| - | FLAG_WHILE = (1 << RES_WHILE), |
| - | FLAG_UNTIL = (1 << RES_UNTIL), |
| - | FLAG_DO = (1 << RES_DO ), |
| - | FLAG_DONE = (1 << RES_DONE ), |
| - | FLAG_IN = (1 << RES_IN ), |
| - | # endif |
| - | # if ENABLE_HUSH_CASE |
| - | FLAG_MATCH = (1 << RES_MATCH), |
| - | FLAG_ESAC = (1 << RES_ESAC ), |
| - | # endif |
| - | FLAG_START = (1 << RES_XXXX ), |
| - | }; |
| - | static const struct reserved_combo* match_reserved_word(o_string *word) |
| - | { |
| - | /* Mostly a list of accepted follow-up reserved words. |
| - | * FLAG_END means we are done with the sequence, and are ready |
| - | * to turn the compound list into a command. |
| - | * FLAG_START means the word must start a new compound list. |
| - | */ |
| - | static const struct reserved_combo reserved_list[] = { |
| - | # if ENABLE_HUSH_IF |
| - | { "!", RES_NONE, NOT_ASSIGNMENT , 0 }, |
| - | { "if", RES_IF, WORD_IS_KEYWORD, FLAG_THEN | FLAG_START }, |
| - | { "then", RES_THEN, WORD_IS_KEYWORD, FLAG_ELIF | FLAG_ELSE | FLAG_FI }, |
| - | { "elif", RES_ELIF, WORD_IS_KEYWORD, FLAG_THEN }, |
| - | { "else", RES_ELSE, WORD_IS_KEYWORD, FLAG_FI }, |
| - | { "fi", RES_FI, NOT_ASSIGNMENT , FLAG_END }, |
| - | # endif |
| - | # if ENABLE_HUSH_LOOPS |
| - | { "for", RES_FOR, NOT_ASSIGNMENT , FLAG_IN | FLAG_DO | FLAG_START }, |
| - | { "while", RES_WHILE, WORD_IS_KEYWORD, FLAG_DO | FLAG_START }, |
| - | { "until", RES_UNTIL, WORD_IS_KEYWORD, FLAG_DO | FLAG_START }, |
| - | { "in", RES_IN, NOT_ASSIGNMENT , FLAG_DO }, |
| - | { "do", RES_DO, WORD_IS_KEYWORD, FLAG_DONE }, |
| - | { "done", RES_DONE, NOT_ASSIGNMENT , FLAG_END }, |
| - | # endif |
| - | # if ENABLE_HUSH_CASE |
| - | { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START }, |
| - | { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END }, |
| - | # endif |
| - | }; |
| - | const struct reserved_combo *r; |
| + | static int run_and_free_list(struct pipe *pi); |
| - | for (r = reserved_list; r < reserved_list + ARRAY_SIZE(reserved_list); r++) { |
| - | if (strcmp(word->data, r->literal) == 0) |
| - | return r; |
| - | } |
| - | return NULL; |
| - | } |
| - | /* Return 0: not a keyword, 1: keyword |
| + | /* Executing from string: eval, sh -c '...' |
| + | * or from file: /etc/profile, . file, sh <script>, sh (intereactive) |
| + | * end_trigger controls how often we stop parsing |
| + | * NUL: parse all, execute, return |
| + | * ';': parse till ';' or newline, execute, repeat till EOF |
| */ | |
| - | static int reserved_word(o_string *word, struct parse_context *ctx) |
| + | static void parse_and_run_stream(struct in_str *inp, int end_trigger) |
| { | |
| - | # if ENABLE_HUSH_CASE |
| - | static const struct reserved_combo reserved_match = { |
| - | "", RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC |
| - | }; |
| - | # endif |
| - | const struct reserved_combo *r; |
| - | |
| - | if (word->has_quoted_part) |
| - | return 0; |
| - | r = match_reserved_word(word); |
| - | if (!r) |
| - | return 0; |
| + | /* Why we need empty flag? |
| + | * An obscure corner case "false; ``; echo $?": |
| + | * empty command in `` should still set $? to 0. |
| + | * But we can't just set $? to 0 at the start, |
| + | * this breaks "false; echo `echo $?`" case. |
| + | */ |
| + | bool empty = 1; |
| + | while (1) { |
| + | struct pipe *pipe_list; |
| - | debug_printf("found reserved word %s, res %d\n", r->literal, r->res); |
| - | # if ENABLE_HUSH_CASE |
| - | if (r->res == RES_IN && ctx->ctx_res_w == RES_CASE_IN) { |
| - | /* "case word IN ..." - IN part starts first MATCH part */ |
| - | r = &reserved_match; |
| - | } else |
| - | # endif |
| - | if (r->flag == 0) { /* '!' */ |
| - | if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */ |
| - | syntax_error("! ! command"); |
| - | ctx->ctx_res_w = RES_SNTX; |
| + | pipe_list = parse_stream(NULL, inp, end_trigger); |
| + | if (!pipe_list) { /* EOF */ |
| + | if (empty) |
| + | G.last_exitcode = 0; |
| + | break; |
| } | |
| - | ctx->ctx_inverted = 1; |
| - | return 1; |
| - | } |
| - | if (r->flag & FLAG_START) { |
| - | struct parse_context *old; |
| - | |
| - | old = xmalloc(sizeof(*old)); |
| - | debug_printf_parse("push stack %p\n", old); |
| - | *old = *ctx; /* physical copy */ |
| - | 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); |
| - | ctx->ctx_res_w = RES_SNTX; |
| - | return 1; |
| - | } else { |
| - | /* "{...} fi" is ok. "{...} if" is not |
| - | * Example: |
| - | * if { echo foo; } then { echo bar; } fi */ |
| - | if (ctx->command->group) |
| - | done_pipe(ctx, PIPE_SEQ); |
| + | debug_print_tree(pipe_list, 0); |
| + | debug_printf_exec("parse_and_run_stream: run_and_free_list\n"); |
| + | run_and_free_list(pipe_list); |
| + | empty = 0; |
| } | |
| + | } |
| - | ctx->ctx_res_w = r->res; |
| - | ctx->old_flag = r->flag; |
| - | word->o_assignment = r->assignment_flag; |
| + | static void parse_and_run_string(const char *s) |
| + | { |
| + | struct in_str input; |
| + | setup_string_in_str(&input, s); |
| + | parse_and_run_stream(&input, '\0'); |
| + | } |
| - | if (ctx->old_flag & FLAG_END) { |
| - | struct parse_context *old; |
| + | static void parse_and_run_file(FILE *f) |
| + | { |
| + | struct in_str input; |
| + | setup_file_in_str(&input, f); |
| + | parse_and_run_stream(&input, ';'); |
| + | } |
| - | done_pipe(ctx, PIPE_SEQ); |
| - | debug_printf_parse("pop stack %p\n", ctx->stack); |
| - | old = ctx->stack; |
| - | old->command->group = ctx->list_head; |
| - | old->command->cmd_type = CMD_NORMAL; |
| + | #if ENABLE_HUSH_TICK |
| + | static FILE *generate_stream_from_string(const char *s, pid_t *pid_p) |
| + | { |
| + | pid_t pid; |
| + | int channel[2]; |
| # if !BB_MMU | |
| - | o_addstr(&old->as_string, ctx->as_string.data); |
| - | o_free_unsafe(&ctx->as_string); |
| - | old->command->group_as_string = xstrdup(old->as_string.data); |
| - | debug_printf_parse("pop, remembering as:'%s'\n", |
| - | old->command->group_as_string); |
| + | char **to_free = NULL; |
| + | # endif |
| + | |
| + | xpipe(channel); |
| + | pid = BB_MMU ? xfork() : xvfork(); |
| + | if (pid == 0) { /* child */ |
| + | disable_restore_tty_pgrp_on_exit(); |
| + | /* Process substitution is not considered to be usual |
| + | * 'command execution'. |
| + | * SUSv3 says ctrl-Z should be ignored, ctrl-C should not. |
| + | */ |
| + | bb_signals(0 |
| + | + (1 << SIGTSTP) |
| + | + (1 << SIGTTIN) |
| + | + (1 << SIGTTOU) |
| + | , SIG_IGN); |
| + | CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ |
| + | close(channel[0]); /* NB: close _first_, then move fd! */ |
| + | xmove_fd(channel[1], 1); |
| + | /* Prevent it from trying to handle ctrl-z etc */ |
| + | IF_HUSH_JOB(G.run_list_level = 1;) |
| + | /* Awful hack for `trap` or $(trap). |
| + | * |
| + | * http://www.opengroup.org/onlinepubs/009695399/utilities/trap.html |
| + | * contains an example where "trap" is executed in a subshell: |
| + | * |
| + | * save_traps=$(trap) |
| + | * ... |
| + | * eval "$save_traps" |
| + | * |
| + | * Standard does not say that "trap" in subshell shall print |
| + | * parent shell's traps. It only says that its output |
| + | * must have suitable form, but then, in the above example |
| + | * (which is not supposed to be normative), it implies that. |
| + | * |
| + | * bash (and probably other shell) does implement it |
| + | * (traps are reset to defaults, but "trap" still shows them), |
| + | * but as a result, "trap" logic is hopelessly messed up: |
| + | * |
| + | * # trap |
| + | * trap -- 'echo Ho' SIGWINCH <--- we have a handler |
| + | * # (trap) <--- trap is in subshell - no output (correct, traps are reset) |
| + | * # true | trap <--- trap is in subshell - no output (ditto) |
| + | * # echo `true | trap` <--- in subshell - output (but traps are reset!) |
| + | * trap -- 'echo Ho' SIGWINCH |
| + | * # echo `(trap)` <--- in subshell in subshell - output |
| + | * trap -- 'echo Ho' SIGWINCH |
| + | * # echo `true | (trap)` <--- in subshell in subshell in subshell - output! |
| + | * trap -- 'echo Ho' SIGWINCH |
| + | * |
| + | * The rules when to forget and when to not forget traps |
| + | * get really complex and nonsensical. |
| + | * |
| + | * Our solution: ONLY bare $(trap) or `trap` is special. |
| + | */ |
| + | s = skip_whitespace(s); |
| + | if (strncmp(s, "trap", 4) == 0 |
| + | && skip_whitespace(s + 4)[0] == '\0' |
| + | ) { |
| + | static const char *const argv[] = { NULL, NULL }; |
| + | builtin_trap((char**)argv); |
| + | exit(0); /* not _exit() - we need to fflush */ |
| + | } |
| + | # if BB_MMU |
| + | reset_traps_to_defaults(); |
| + | parse_and_run_string(s); |
| + | _exit(G.last_exitcode); |
| + | # else |
| + | /* We re-execute after vfork on NOMMU. This makes this script safe: |
| + | * yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG |
| + | * huge=`cat BIG` # was blocking here forever |
| + | * echo OK |
| + | */ |
| + | re_execute_shell(&to_free, |
| + | s, |
| + | G.global_argv[0], |
| + | G.global_argv + 1, |
| + | NULL); |
| # endif | |
| - | *ctx = *old; /* physical copy */ |
| - | free(old); |
| } | |
| - | return 1; |
| + | |
| + | /* parent */ |
| + | *pid_p = pid; |
| + | # if ENABLE_HUSH_FAST |
| + | G.count_SIGCHLD++; |
| + | //bb_error_msg("[%d] fork in generate_stream_from_string:" |
| + | // " G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", |
| + | // getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); |
| + | # endif |
| + | enable_restore_tty_pgrp_on_exit(); |
| + | # if !BB_MMU |
| + | free(to_free); |
| + | # endif |
| + | close(channel[1]); |
| + | close_on_exec_on(channel[0]); |
| + | return xfdopen_for_read(channel[0]); |
| } | |
| - | #endif /* HAS_KEYWORDS */ |
| - | /* Word is complete, look at it and update parsing context. |
| - | * 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) |
| + | /* Return code is exit status of the process that is run. */ |
| + | static int process_command_subs(o_string *dest, const char *s) |
| { | |
| - | 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 return 0: true null, ignored\n"); |
| - | return 0; |
| - | } |
| + | FILE *fp; |
| + | struct in_str pipe_str; |
| + | pid_t pid; |
| + | int status, ch, eol_cnt; |
| - | if (ctx->pending_redirect) { |
| - | /* We do not glob in e.g. >*.tmp case. bash seems to glob here |
| - | * only if run as "bash", not "sh" */ |
| - | /* http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html |
| - | * "2.7 Redirection |
| - | * ...the word that follows the redirection operator |
| - | * shall be subjected to tilde expansion, parameter expansion, |
| - | * command substitution, arithmetic expansion, and quote |
| - | * removal. Pathname expansion shall not be performed |
| - | * on the word by a non-interactive shell; an interactive |
| - | * shell may perform it, but shall do so only when |
| - | * the expansion would result in one word." |
| - | */ |
| - | ctx->pending_redirect->rd_filename = xstrdup(word->data); |
| - | /* Cater for >\file case: |
| - | * >\a creates file a; >\\a, >"\a", >"\\a" create file \a |
| - | * Same with heredocs: |
| - | * for <<\H delim is H; <<\\H, <<"\H", <<"\\H" - \H |
| - | */ |
| - | if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) { |
| - | unbackslash(ctx->pending_redirect->rd_filename); |
| - | /* Is it <<"HEREDOC"? */ |
| - | if (word->has_quoted_part) { |
| - | ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED; |
| - | } |
| - | } |
| - | debug_printf_parse("word stored in rd_filename: '%s'\n", word->data); |
| - | ctx->pending_redirect = NULL; |
| - | } else { |
| - | /* 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 |
| - | ) { |
| - | word->o_assignment = NOT_ASSIGNMENT; |
| - | } else { |
| - | if (word->o_assignment == DEFINITELY_ASSIGNMENT) |
| - | command->assignment_cnt++; |
| - | word->o_assignment = MAYBE_ASSIGNMENT; |
| - | } |
| + | fp = generate_stream_from_string(s, &pid); |
| - | #if HAS_KEYWORDS |
| - | # if ENABLE_HUSH_CASE |
| - | if (ctx->ctx_dsemicolon |
| - | && strcmp(word->data, "esac") != 0 /* not "... pattern) cmd;; esac" */ |
| - | ) { |
| - | /* already done when ctx_dsemicolon was set to 1: */ |
| - | /* ctx->ctx_res_w = RES_MATCH; */ |
| - | ctx->ctx_dsemicolon = 0; |
| - | } else |
| - | # endif |
| - | if (!command->argv /* if it's the first word... */ |
| - | # if ENABLE_HUSH_LOOPS |
| - | && ctx->ctx_res_w != RES_FOR /* ...not after FOR or IN */ |
| - | && ctx->ctx_res_w != RES_IN |
| - | # endif |
| - | # if ENABLE_HUSH_CASE |
| - | && ctx->ctx_res_w != RES_CASE |
| - | # endif |
| - | ) { |
| - | debug_printf_parse("checking '%s' for reserved-ness\n", word->data); |
| - | if (reserved_word(word, ctx)) { |
| - | o_reset_to_empty_unquoted(word); |
| - | debug_printf_parse("done_word return %d\n", |
| - | (ctx->ctx_res_w == RES_SNTX)); |
| - | return (ctx->ctx_res_w == RES_SNTX); |
| - | } |
| - | # ifdef CMD_SINGLEWORD_NOGLOB_COND |
| - | if (strcmp(word->data, "export") == 0 |
| - | # if ENABLE_HUSH_LOCAL |
| - | || strcmp(word->data, "local") == 0 |
| - | # endif |
| - | ) { |
| - | command->cmd_type = CMD_SINGLEWORD_NOGLOB_COND; |
| - | } else |
| - | # endif |
| - | # if ENABLE_HUSH_BASH_COMPAT |
| - | if (strcmp(word->data, "[[") == 0) { |
| - | command->cmd_type = CMD_SINGLEWORD_NOGLOB; |
| - | } |
| - | /* fall through */ |
| - | # endif |
| - | } |
| - | #endif |
| - | if (command->group) { |
| - | /* "{ echo foo; } echo bar" - bad */ |
| - | syntax_error_at(word->data); |
| - | debug_printf_parse("done_word return 1: syntax error, " |
| - | "groups and arglists don't mix\n"); |
| - | return 1; |
| + | /* Now send results of command back into original context */ |
| + | setup_file_in_str(&pipe_str, fp); |
| + | eol_cnt = 0; |
| + | while ((ch = i_getch(&pipe_str)) != EOF) { |
| + | if (ch == '\n') { |
| + | eol_cnt++; |
| + | continue; |
| } | |
| - | if (word->has_quoted_part |
| - | /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */ |
| - | && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL) |
| - | /* (otherwise it's known to be not empty and is already safe) */ |
| - | ) { |
| - | /* exclude "$@" - it can expand to no word despite "" */ |
| - | char *p = word->data; |
| - | while (p[0] == SPECIAL_VAR_SYMBOL |
| - | && (p[1] & 0x7f) == '@' |
| - | && p[2] == SPECIAL_VAR_SYMBOL |
| - | ) { |
| - | p += 3; |
| - | } |
| - | if (p == word->data || p[0] != '\0') { |
| - | /* saw no "$@", or not only "$@" but some |
| - | * real text is there too */ |
| - | /* insert "empty variable" reference, this makes |
| - | * e.g. "", $empty"" etc to not disappear */ |
| - | o_addchr(word, SPECIAL_VAR_SYMBOL); |
| - | o_addchr(word, SPECIAL_VAR_SYMBOL); |
| - | } |
| + | while (eol_cnt) { |
| + | o_addchr(dest, '\n'); |
| + | eol_cnt--; |
| } | |
| - | command->argv = add_string_to_strings(command->argv, xstrdup(word->data)); |
| - | debug_print_strings("word appended to argv", command->argv); |
| + | o_addQchr(dest, ch); |
| } | |
| - | #if ENABLE_HUSH_LOOPS |
| - | if (ctx->ctx_res_w == RES_FOR) { |
| - | if (word->has_quoted_part |
| - | || !is_well_formed_var_name(command->argv[0], '\0') |
| - | ) { |
| - | /* bash says just "not a valid identifier" */ |
| - | syntax_error("not a valid identifier in for"); |
| - | return 1; |
| - | } |
| - | /* Force FOR to have just one word (variable name) */ |
| - | /* NB: basically, this makes hush see "for v in ..." |
| - | * syntax as if it is "for v; in ...". FOR and IN become |
| - | * two pipe structs in parse tree. */ |
| - | done_pipe(ctx, PIPE_SEQ); |
| - | } |
| - | #endif |
| - | #if ENABLE_HUSH_CASE |
| - | /* Force CASE to have just one word */ |
| - | if (ctx->ctx_res_w == RES_CASE) { |
| - | done_pipe(ctx, PIPE_SEQ); |
| - | } |
| + | debug_printf("done reading from `cmd` pipe, closing it\n"); |
| + | fclose(fp); |
| + | /* We need to extract exitcode. Test case |
| + | * "true; echo `sleep 1; false` $?" |
| + | * should print 1 */ |
| + | safe_waitpid(pid, &status, 0); |
| + | debug_printf("child exited. returning its exitcode:%d\n", WEXITSTATUS(status)); |
| + | return WEXITSTATUS(status); |
| + | } |
| + | #endif /* ENABLE_HUSH_TICK */ |
| + | |
| + | |
| + | static void setup_heredoc(struct redir_struct *redir) |
| + | { |
| + | struct fd_pair pair; |
| + | pid_t pid; |
| + | int len, written; |
| + | /* the _body_ of heredoc (misleading field name) */ |
| + | const char *heredoc = redir->rd_filename; |
| + | char *expanded; |
| + | #if !BB_MMU |
| + | char **to_free; |
| #endif | |
| - | o_reset_to_empty_unquoted(word); |
| + | expanded = NULL; |
| + | if (!(redir->rd_dup & HEREDOC_QUOTED)) { |
| + | expanded = expand_pseudo_dquoted(heredoc); |
| + | if (expanded) |
| + | heredoc = expanded; |
| + | } |
| + | len = strlen(heredoc); |
| - | debug_printf_parse("done_word return 0\n"); |
| - | return 0; |
| - | } |
| + | close(redir->rd_fd); /* often saves dup2+close in xmove_fd */ |
| + | xpiped_pair(pair); |
| + | xmove_fd(pair.rd, redir->rd_fd); |
| + | /* Try writing without forking. Newer kernels have |
| + | * dynamically growing pipes. Must use non-blocking write! */ |
| + | ndelay_on(pair.wr); |
| + | while (1) { |
| + | written = write(pair.wr, heredoc, len); |
| + | if (written <= 0) |
| + | break; |
| + | len -= written; |
| + | if (len == 0) { |
| + | close(pair.wr); |
| + | free(expanded); |
| + | return; |
| + | } |
| + | heredoc += written; |
| + | } |
| + | ndelay_off(pair.wr); |
| - | /* Peek ahead in the input to find out if we have a "&n" construct, |
| - | * as in "2>&1", that represents duplicating a file descriptor. |
| - | * Return: |
| - | * REDIRFD_CLOSE if >&- "close fd" construct is seen, |
| - | * REDIRFD_SYNTAX_ERR if syntax error, |
| - | * REDIRFD_TO_FILE if no & was seen, |
| - | * or the number found. |
| - | */ |
| + | /* Okay, pipe buffer was not big enough */ |
| + | /* Note: we must not create a stray child (bastard? :) |
| + | * for the unsuspecting parent process. Child creates a grandchild |
| + | * and exits before parent execs the process which consumes heredoc |
| + | * (that exec happens after we return from this function) */ |
| + | #if !BB_MMU |
| + | to_free = NULL; |
| + | #endif |
| + | pid = xvfork(); |
| + | if (pid == 0) { |
| + | /* child */ |
| + | disable_restore_tty_pgrp_on_exit(); |
| + | pid = BB_MMU ? xfork() : xvfork(); |
| + | if (pid != 0) |
| + | _exit(0); |
| + | /* grandchild */ |
| + | close(redir->rd_fd); /* read side of the pipe */ |
| #if BB_MMU | |
| - | #define parse_redir_right_fd(as_string, input) \ |
| - | parse_redir_right_fd(input) |
| + | full_write(pair.wr, heredoc, len); /* may loop or block */ |
| + | _exit(0); |
| + | #else |
| + | /* Delegate blocking writes to another process */ |
| + | xmove_fd(pair.wr, STDOUT_FILENO); |
| + | re_execute_shell(&to_free, heredoc, NULL, NULL, NULL); |
| #endif | |
| - | static int parse_redir_right_fd(o_string *as_string, struct in_str *input) |
| - | { |
| - | int ch, d, ok; |
| - | |
| - | ch = i_peek(input); |
| - | if (ch != '&') |
| - | return REDIRFD_TO_FILE; |
| - | |
| - | ch = i_getch(input); /* get the & */ |
| - | nommu_addchr(as_string, ch); |
| - | ch = i_peek(input); |
| - | if (ch == '-') { |
| - | ch = i_getch(input); |
| - | nommu_addchr(as_string, ch); |
| - | return REDIRFD_CLOSE; |
| - | } |
| - | d = 0; |
| - | ok = 0; |
| - | while (ch != EOF && isdigit(ch)) { |
| - | d = d*10 + (ch-'0'); |
| - | ok = 1; |
| - | ch = i_getch(input); |
| - | nommu_addchr(as_string, ch); |
| - | ch = i_peek(input); |
| } | |
| - | if (ok) return d; |
| - | |
| - | //TODO: this is the place to catch ">&file" bashism (redirect both fd 1 and 2) |
| - | |
| - | bb_error_msg("ambiguous redirect"); |
| - | return REDIRFD_SYNTAX_ERR; |
| + | /* parent */ |
| + | #if ENABLE_HUSH_FAST |
| + | G.count_SIGCHLD++; |
| + | //bb_error_msg("[%d] fork in setup_heredoc: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); |
| + | #endif |
| + | enable_restore_tty_pgrp_on_exit(); |
| + | #if !BB_MMU |
| + | free(to_free); |
| + | #endif |
| + | close(pair.wr); |
| + | free(expanded); |
| + | wait(NULL); /* wait till child has died */ |
| } | |
| - | /* Return code is 0 normal, 1 if a syntax error is detected |
| - | */ |
| - | static int parse_redirect(struct parse_context *ctx, |
| - | int fd, |
| - | redir_type style, |
| - | struct in_str *input) |
| + | /* squirrel != NULL means we squirrel away copies of stdin, stdout, |
| + | * and stderr if they are redirected. */ |
| + | static int setup_redirects(struct command *prog, int squirrel[]) |
| { | |
| - | struct command *command = ctx->command; |
| + | int openfd, mode; |
| struct redir_struct *redir; | |
| - | struct redir_struct **redirp; |
| - | int dup_num; |
| - | dup_num = REDIRFD_TO_FILE; |
| - | if (style != REDIRECT_HEREDOC) { |
| - | /* Check for a '>&1' type redirect */ |
| - | dup_num = parse_redir_right_fd(&ctx->as_string, input); |
| - | if (dup_num == REDIRFD_SYNTAX_ERR) |
| - | return 1; |
| - | } else { |
| - | int ch = i_peek(input); |
| - | dup_num = (ch == '-'); /* HEREDOC_SKIPTABS bit is 1 */ |
| - | if (dup_num) { /* <<-... */ |
| - | ch = i_getch(input); |
| - | nommu_addchr(&ctx->as_string, ch); |
| - | ch = i_peek(input); |
| + | for (redir = prog->redirects; redir; redir = redir->next) { |
| + | if (redir->rd_type == REDIRECT_HEREDOC2) { |
| + | /* rd_fd<<HERE case */ |
| + | if (squirrel && redir->rd_fd < 3 |
| + | && squirrel[redir->rd_fd] < 0 |
| + | ) { |
| + | squirrel[redir->rd_fd] = dup(redir->rd_fd); |
| + | } |
| + | /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ |
| + | * of the heredoc */ |
| + | debug_printf_parse("set heredoc '%s'\n", |
| + | redir->rd_filename); |
| + | setup_heredoc(redir); |
| + | continue; |
| } | |
| - | } |
| - | if (style == REDIRECT_OVERWRITE && dup_num == REDIRFD_TO_FILE) { |
| - | int ch = i_peek(input); |
| - | if (ch == '|') { |
| - | /* >|FILE redirect ("clobbering" >). |
| - | * Since we do not support "set -o noclobber" yet, |
| - | * >| and > are the same for now. Just eat |. |
| - | */ |
| - | ch = i_getch(input); |
| - | nommu_addchr(&ctx->as_string, ch); |
| + | if (redir->rd_dup == REDIRFD_TO_FILE) { |
| + | /* rd_fd<*>file case (<*> is <,>,>>,<>) */ |
| + | char *p; |
| + | if (redir->rd_filename == NULL) { |
| + | /* Something went wrong in the parse. |
| + | * Pretend it didn't happen */ |
| + | bb_error_msg("bug in redirect parse"); |
| + | continue; |
| + | } |
| + | mode = redir_table[redir->rd_type].mode; |
| + | p = expand_string_to_string(redir->rd_filename); |
| + | openfd = open_or_warn(p, mode); |
| + | free(p); |
| + | if (openfd < 0) { |
| + | /* this could get lost if stderr has been redirected, but |
| + | * bash and ash both lose it as well (though zsh doesn't!) */ |
| + | //what the above comment tries to say? |
| + | return 1; |
| + | } |
| + | } else { |
| + | /* rd_fd<*>rd_dup or rd_fd<*>- cases */ |
| + | openfd = redir->rd_dup; |
| } | |
| - | } |
| - | /* Create a new redir_struct and append it to the linked list */ |
| - | redirp = &command->redirects; |
| - | while ((redir = *redirp) != NULL) { |
| - | redirp = &(redir->next); |
| - | } |
| - | *redirp = redir = xzalloc(sizeof(*redir)); |
| - | /* redir->next = NULL; */ |
| - | /* redir->rd_filename = NULL; */ |
| - | redir->rd_type = style; |
| - | redir->rd_fd = (fd == -1) ? redir_table[style].default_fd : fd; |
| - | |
| - | debug_printf_parse("redirect type %d %s\n", redir->rd_fd, |
| - | redir_table[style].descrip); |
| - | |
| - | redir->rd_dup = dup_num; |
| - | if (style != REDIRECT_HEREDOC && dup_num != REDIRFD_TO_FILE) { |
| - | /* Erik had a check here that the file descriptor in question |
| - | * is legit; I postpone that to "run time" |
| - | * A "-" representation of "close me" shows up as a -3 here */ |
| - | debug_printf_parse("duplicating redirect '%d>&%d'\n", |
| - | redir->rd_fd, redir->rd_dup); |
| - | } else { |
| - | /* Set ctx->pending_redirect, so we know what to do at the |
| - | * end of the next parsed word. */ |
| - | ctx->pending_redirect = redir; |
| + | if (openfd != redir->rd_fd) { |
| + | if (squirrel && redir->rd_fd < 3 |
| + | && squirrel[redir->rd_fd] < 0 |
| + | ) { |
| + | squirrel[redir->rd_fd] = dup(redir->rd_fd); |
| + | } |
| + | if (openfd == REDIRFD_CLOSE) { |
| + | /* "n>-" means "close me" */ |
| + | close(redir->rd_fd); |
| + | } else { |
| + | xdup2(openfd, redir->rd_fd); |
| + | if (redir->rd_dup == REDIRFD_TO_FILE) |
| + | close(openfd); |
| + | } |
| + | } |
| } | |
| return 0; | |
| } | |
| - | /* If a redirect is immediately preceded by a number, that number is |
| - | * supposed to tell which file descriptor to redirect. This routine |
| - | * looks for such preceding numbers. In an ideal world this routine |
| - | * needs to handle all the following classes of redirects... |
| - | * echo 2>foo # redirects fd 2 to file "foo", nothing passed to echo |
| - | * echo 49>foo # redirects fd 49 to file "foo", nothing passed to echo |
| - | * echo -2>foo # redirects fd 1 to file "foo", "-2" passed to echo |
| - | * echo 49x>foo # redirects fd 1 to file "foo", "49x" passed to echo |
| - | * |
| - | * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html |
| - | * "2.7 Redirection |
| - | * ... If n is quoted, the number shall not be recognized as part of |
| - | * the redirection expression. For example: |
| - | * echo \2>a |
| - | * writes the character 2 into file a" |
| - | * We are getting it right by setting ->has_quoted_part on any \<char> |
| - | * |
| - | * A -1 return means no valid number was found, |
| - | * the caller should use the appropriate default for this redirection. |
| - | */ |
| - | static int redirect_opt_num(o_string *o) |
| + | static void restore_redirects(int squirrel[]) |
| { | |
| - | int num; |
| - | |
| - | if (o->data == NULL) |
| - | return -1; |
| - | num = bb_strtou(o->data, NULL, 10); |
| - | if (errno || num < 0) |
| - | return -1; |
| - | o_reset_to_empty_unquoted(o); |
| - | return num; |
| + | int i, fd; |
| + | for (i = 0; i < 3; i++) { |
| + | fd = squirrel[i]; |
| + | if (fd != -1) { |
| + | /* We simply die on error */ |
| + | xmove_fd(fd, i); |
| + | } |
| + | } |
| } | |
| - | #if BB_MMU |
| - | #define fetch_till_str(as_string, input, word, skip_tabs) \ |
| - | fetch_till_str(input, word, skip_tabs) |
| - | #endif |
| - | static char *fetch_till_str(o_string *as_string, |
| - | struct in_str *input, |
| - | const char *word, |
| - | int skip_tabs) |
| + | static char *find_in_path(const char *arg) |
| { | |
| - | o_string heredoc = NULL_O_STRING; |
| - | int past_EOL = 0; |
| - | int ch; |
| + | char *ret = NULL; |
| + | const char *PATH = get_local_var_value("PATH"); |
| + | |
| + | if (!PATH) |
| + | return NULL; |
| - | goto jump_in; |
| while (1) { | |
| - | ch = i_getch(input); |
| - | nommu_addchr(as_string, ch); |
| - | if (ch == '\n') { |
| - | if (strcmp(heredoc.data + past_EOL, word) == 0) { |
| - | heredoc.data[past_EOL] = '\0'; |
| - | debug_printf_parse("parsed heredoc '%s'\n", heredoc.data); |
| - | return heredoc.data; |
| - | } |
| - | do { |
| - | o_addchr(&heredoc, ch); |
| - | past_EOL = heredoc.length; |
| - | jump_in: |
| - | do { |
| - | ch = i_getch(input); |
| - | nommu_addchr(as_string, ch); |
| - | } while (skip_tabs && ch == '\t'); |
| - | } while (ch == '\n'); |
| + | const char *end = strchrnul(PATH, ':'); |
| + | int sz = end - PATH; /* must be int! */ |
| + | |
| + | free(ret); |
| + | if (sz != 0) { |
| + | ret = xasprintf("%.*s/%s", sz, PATH, arg); |
| + | } else { |
| + | /* We have xxx::yyyy in $PATH, |
| + | * it means "use current dir" */ |
| + | ret = xstrdup(arg); |
| } | |
| - | if (ch == EOF) { |
| - | o_free_unsafe(&heredoc); |
| + | if (access(ret, F_OK) == 0) |
| + | break; |
| + | |
| + | if (*end == '\0') { |
| + | free(ret); |
| return NULL; | |
| } | |
| - | o_addchr(&heredoc, ch); |
| - | nommu_addchr(as_string, ch); |
| + | PATH = end + 1; |
| } | |
| + | |
| + | return ret; |
| } | |
| - | /* Look at entire parse tree for not-yet-loaded REDIRECT_HEREDOCs |
| - | * and load them all. There should be exactly heredoc_cnt of them. |
| - | */ |
| - | static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_str *input) |
| + | static const struct built_in_command* find_builtin_helper(const char *name, |
| + | const struct built_in_command *x, |
| + | const struct built_in_command *end) |
| { | |
| - | struct pipe *pi = ctx->list_head; |
| + | while (x != end) { |
| + | if (strcmp(name, x->b_cmd) != 0) { |
| + | x++; |
| + | continue; |
| + | } |
| + | debug_printf_exec("found builtin '%s'\n", name); |
| + | return x; |
| + | } |
| + | return NULL; |
| + | } |
| + | static const struct built_in_command* find_builtin1(const char *name) |
| + | { |
| + | return find_builtin_helper(name, bltins1, &bltins1[ARRAY_SIZE(bltins1)]); |
| + | } |
| + | static const struct built_in_command* find_builtin(const char *name) |
| + | { |
| + | const struct built_in_command *x = find_builtin1(name); |
| + | if (x) |
| + | return x; |
| + | return find_builtin_helper(name, bltins2, &bltins2[ARRAY_SIZE(bltins2)]); |
| + | } |
| - | while (pi && heredoc_cnt) { |
| - | int i; |
| - | struct command *cmd = pi->cmds; |
| + | #if ENABLE_HUSH_FUNCTIONS |
| + | static struct function **find_function_slot(const char *name) |
| + | { |
| + | struct function **funcpp = &G.top_func; |
| + | while (*funcpp) { |
| + | if (strcmp(name, (*funcpp)->name) == 0) { |
| + | break; |
| + | } |
| + | funcpp = &(*funcpp)->next; |
| + | } |
| + | return funcpp; |
| + | } |
| - | debug_printf_parse("fetch_heredocs: num_cmds:%d cmd argv0:'%s'\n", |
| - | pi->num_cmds, |
| - | cmd->argv ? cmd->argv[0] : "NONE"); |
| - | for (i = 0; i < pi->num_cmds; i++) { |
| - | struct redir_struct *redir = cmd->redirects; |
| + | static const struct function *find_function(const char *name) |
| + | { |
| + | const struct function *funcp = *find_function_slot(name); |
| + | if (funcp) |
| + | debug_printf_exec("found function '%s'\n", name); |
| + | return funcp; |
| + | } |
| - | debug_printf_parse("fetch_heredocs: %d cmd argv0:'%s'\n", |
| - | i, cmd->argv ? cmd->argv[0] : "NONE"); |
| - | while (redir) { |
| - | if (redir->rd_type == REDIRECT_HEREDOC) { |
| - | char *p; |
| + | /* Note: takes ownership on name ptr */ |
| + | static struct function *new_function(char *name) |
| + | { |
| + | struct function **funcpp = find_function_slot(name); |
| + | struct function *funcp = *funcpp; |
| - | redir->rd_type = REDIRECT_HEREDOC2; |
| - | /* redir->rd_dup is (ab)used to indicate <<- */ |
| - | p = fetch_till_str(&ctx->as_string, input, |
| - | redir->rd_filename, redir->rd_dup & HEREDOC_SKIPTABS); |
| - | if (!p) { |
| - | syntax_error("unexpected EOF in here document"); |
| - | return 1; |
| - | } |
| - | free(redir->rd_filename); |
| - | redir->rd_filename = p; |
| - | heredoc_cnt--; |
| - | } |
| - | redir = redir->next; |
| + | if (funcp != NULL) { |
| + | struct command *cmd = funcp->parent_cmd; |
| + | debug_printf_exec("func %p parent_cmd %p\n", funcp, cmd); |
| + | if (!cmd) { |
| + | debug_printf_exec("freeing & replacing function '%s'\n", funcp->name); |
| + | free(funcp->name); |
| + | /* Note: if !funcp->body, do not free body_as_string! |
| + | * This is a special case of "-F name body" function: |
| + | * body_as_string was not malloced! */ |
| + | if (funcp->body) { |
| + | free_pipe_list(funcp->body); |
| + | # if !BB_MMU |
| + | free(funcp->body_as_string); |
| + | # endif |
| } | |
| - | cmd++; |
| + | } else { |
| + | debug_printf_exec("reinserting in tree & replacing function '%s'\n", funcp->name); |
| + | cmd->argv[0] = funcp->name; |
| + | cmd->group = funcp->body; |
| + | # if !BB_MMU |
| + | cmd->group_as_string = funcp->body_as_string; |
| + | # endif |
| } | |
| - | pi = pi->next; |
| + | } else { |
| + | debug_printf_exec("remembering new function '%s'\n", name); |
| + | funcp = *funcpp = xzalloc(sizeof(*funcp)); |
| + | /*funcp->next = NULL;*/ |
| } | |
| - | #if 0 |
| - | /* Should be 0. If it isn't, it's a parse error */ |
| - | if (heredoc_cnt) |
| - | bb_error_msg_and_die("heredoc BUG 2"); |
| - | #endif |
| - | return 0; |
| - | } |
| + | funcp->name = name; |
| + | return funcp; |
| + | } |
| - | #if ENABLE_HUSH_TICK |
| - | static FILE *generate_stream_from_string(const char *s, pid_t *pid_p) |
| + | static void unset_func(const char *name) |
| { | |
| - | pid_t pid; |
| - | int channel[2]; |
| + | struct function **funcpp = find_function_slot(name); |
| + | struct function *funcp = *funcpp; |
| + | |
| + | if (funcp != NULL) { |
| + | debug_printf_exec("freeing function '%s'\n", funcp->name); |
| + | *funcpp = funcp->next; |
| + | /* funcp is unlinked now, deleting it. |
| + | * Note: if !funcp->body, the function was created by |
| + | * "-F name body", do not free ->body_as_string |
| + | * and ->name as they were not malloced. */ |
| + | if (funcp->body) { |
| + | free_pipe_list(funcp->body); |
| + | free(funcp->name); |
| # if !BB_MMU | |
| - | char **to_free = NULL; |
| + | free(funcp->body_as_string); |
| # endif | |
| - | |
| - | xpipe(channel); |
| - | pid = BB_MMU ? xfork() : xvfork(); |
| - | if (pid == 0) { /* child */ |
| - | disable_restore_tty_pgrp_on_exit(); |
| - | /* Process substitution is not considered to be usual |
| - | * 'command execution'. |
| - | * SUSv3 says ctrl-Z should be ignored, ctrl-C should not. |
| - | */ |
| - | bb_signals(0 |
| - | + (1 << SIGTSTP) |
| - | + (1 << SIGTTIN) |
| - | + (1 << SIGTTOU) |
| - | , SIG_IGN); |
| - | CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ |
| - | close(channel[0]); /* NB: close _first_, then move fd! */ |
| - | xmove_fd(channel[1], 1); |
| - | /* Prevent it from trying to handle ctrl-z etc */ |
| - | IF_HUSH_JOB(G.run_list_level = 1;) |
| - | /* Awful hack for `trap` or $(trap). |
| - | * |
| - | * http://www.opengroup.org/onlinepubs/009695399/utilities/trap.html |
| - | * contains an example where "trap" is executed in a subshell: |
| - | * |
| - | * save_traps=$(trap) |
| - | * ... |
| - | * eval "$save_traps" |
| - | * |
| - | * Standard does not say that "trap" in subshell shall print |
| - | * parent shell's traps. It only says that its output |
| - | * must have suitable form, but then, in the above example |
| - | * (which is not supposed to be normative), it implies that. |
| - | * |
| - | * bash (and probably other shell) does implement it |
| - | * (traps are reset to defaults, but "trap" still shows them), |
| - | * but as a result, "trap" logic is hopelessly messed up: |
| - | * |
| - | * # trap |
| - | * trap -- 'echo Ho' SIGWINCH <--- we have a handler |
| - | * # (trap) <--- trap is in subshell - no output (correct, traps are reset) |
| - | * # true | trap <--- trap is in subshell - no output (ditto) |
| - | * # echo `true | trap` <--- in subshell - output (but traps are reset!) |
| - | * trap -- 'echo Ho' SIGWINCH |
| - | * # echo `(trap)` <--- in subshell in subshell - output |
| - | * trap -- 'echo Ho' SIGWINCH |
| - | * # echo `true | (trap)` <--- in subshell in subshell in subshell - output! |
| - | * trap -- 'echo Ho' SIGWINCH |
| - | * |
| - | * The rules when to forget and when to not forget traps |
| - | * get really complex and nonsensical. |
| - | * |
| - | * Our solution: ONLY bare $(trap) or `trap` is special. |
| - | */ |
| - | s = skip_whitespace(s); |
| - | if (strncmp(s, "trap", 4) == 0 && (*skip_whitespace(s + 4) == '\0')) |
| - | { |
| - | static const char *const argv[] = { NULL, NULL }; |
| - | builtin_trap((char**)argv); |
| - | exit(0); /* not _exit() - we need to fflush */ |
| } | |
| + | free(funcp); |
| + | } |
| + | } |
| + | |
| # if BB_MMU | |
| - | reset_traps_to_defaults(); |
| - | parse_and_run_string(s); |
| - | _exit(G.last_exitcode); |
| + | #define exec_function(to_free, funcp, argv) \ |
| + | exec_function(funcp, argv) |
| + | # endif |
| + | static void exec_function(char ***to_free, |
| + | const struct function *funcp, |
| + | char **argv) NORETURN; |
| + | static void exec_function(char ***to_free, |
| + | const struct function *funcp, |
| + | char **argv) |
| + | { |
| + | # if BB_MMU |
| + | int n = 1; |
| + | |
| + | argv[0] = G.global_argv[0]; |
| + | G.global_argv = argv; |
| + | while (*++argv) |
| + | n++; |
| + | G.global_argc = n; |
| + | /* On MMU, funcp->body is always non-NULL */ |
| + | n = run_list(funcp->body); |
| + | fflush_all(); |
| + | _exit(n); |
| # else | |
| - | /* We re-execute after vfork on NOMMU. This makes this script safe: |
| - | * yes "0123456789012345678901234567890" | dd bs=32 count=64k >BIG |
| - | * huge=`cat BIG` # was blocking here forever |
| - | * echo OK |
| - | */ |
| - | re_execute_shell(&to_free, |
| - | s, |
| - | G.global_argv[0], |
| - | G.global_argv + 1, |
| - | NULL); |
| + | re_execute_shell(to_free, |
| + | funcp->body_as_string, |
| + | G.global_argv[0], |
| + | argv + 1, |
| + | NULL); |
| # endif | |
| - | } |
| + | } |
| + | |
| + | static int run_function(const struct function *funcp, char **argv) |
| + | { |
| + | int rc; |
| + | save_arg_t sv; |
| + | smallint sv_flg; |
| + | |
| + | save_and_replace_G_args(&sv, argv); |
| - | /* parent */ |
| - | *pid_p = pid; |
| - | # if ENABLE_HUSH_FAST |
| - | G.count_SIGCHLD++; |
| - | //bb_error_msg("[%d] fork in generate_stream_from_string:" |
| - | // " G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", |
| - | // getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); |
| + | /* "we are in function, ok to use return" */ |
| + | sv_flg = G.flag_return_in_progress; |
| + | G.flag_return_in_progress = -1; |
| + | # if ENABLE_HUSH_LOCAL |
| + | G.func_nest_level++; |
| # endif | |
| - | enable_restore_tty_pgrp_on_exit(); |
| + | |
| + | /* On MMU, funcp->body is always non-NULL */ |
| # if !BB_MMU | |
| - | free(to_free); |
| + | if (!funcp->body) { |
| + | /* Function defined by -F */ |
| + | parse_and_run_string(funcp->body_as_string); |
| + | rc = G.last_exitcode; |
| + | } else |
| # endif | |
| - | close(channel[1]); |
| - | close_on_exec_on(channel[0]); |
| - | return xfdopen_for_read(channel[0]); |
| + | { |
| + | rc = run_list(funcp->body); |
| + | } |
| + | |
| + | # if ENABLE_HUSH_LOCAL |
| + | { |
| + | struct variable *var; |
| + | struct variable **var_pp; |
| + | |
| + | var_pp = &G.top_var; |
| + | while ((var = *var_pp) != NULL) { |
| + | if (var->func_nest_level < G.func_nest_level) { |
| + | var_pp = &var->next; |
| + | continue; |
| + | } |
| + | /* Unexport */ |
| + | if (var->flg_export) |
| + | bb_unsetenv(var->varstr); |
| + | /* Remove from global list */ |
| + | *var_pp = var->next; |
| + | /* Free */ |
| + | if (!var->max_len) |
| + | free(var->varstr); |
| + | free(var); |
| + | } |
| + | G.func_nest_level--; |
| + | } |
| + | # endif |
| + | G.flag_return_in_progress = sv_flg; |
| + | |
| + | restore_G_args(&sv, argv); |
| + | |
| + | return rc; |
| } | |
| + | #endif /* ENABLE_HUSH_FUNCTIONS */ |
| - | /* Return code is exit status of the process that is run. */ |
| - | static int process_command_subs(o_string *dest, const char *s) |
| + | |
| + | #if BB_MMU |
| + | #define exec_builtin(to_free, x, argv) \ |
| + | exec_builtin(x, argv) |
| + | #else |
| + | #define exec_builtin(to_free, x, argv) \ |
| + | exec_builtin(to_free, argv) |
| + | #endif |
| + | static void exec_builtin(char ***to_free, |
| + | const struct built_in_command *x, |
| + | char **argv) NORETURN; |
| + | static void exec_builtin(char ***to_free, |
| + | const struct built_in_command *x, |
| + | char **argv) |
| { | |
| - | FILE *fp; |
| - | struct in_str pipe_str; |
| - | pid_t pid; |
| - | int status, ch, eol_cnt; |
| + | #if BB_MMU |
| + | int rcode = x->b_function(argv); |
| + | fflush_all(); |
| + | _exit(rcode); |
| + | #else |
| + | /* On NOMMU, we must never block! |
| + | * Example: { sleep 99 | read line; } & echo Ok |
| + | */ |
| + | re_execute_shell(to_free, |
| + | argv[0], |
| + | G.global_argv[0], |
| + | G.global_argv + 1, |
| + | argv); |
| + | #endif |
| + | } |
| - | fp = generate_stream_from_string(s, &pid); |
| - | /* Now send results of command back into original context */ |
| - | setup_file_in_str(&pipe_str, fp); |
| - | eol_cnt = 0; |
| - | while ((ch = i_getch(&pipe_str)) != EOF) { |
| - | if (ch == '\n') { |
| - | eol_cnt++; |
| - | continue; |
| - | } |
| - | while (eol_cnt) { |
| - | o_addchr(dest, '\n'); |
| - | eol_cnt--; |
| - | } |
| - | o_addQchr(dest, ch); |
| - | } |
| + | static void execvp_or_die(char **argv) NORETURN; |
| + | static void execvp_or_die(char **argv) |
| + | { |
| + | debug_printf_exec("execing '%s'\n", argv[0]); |
| + | sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); |
| + | execvp(argv[0], argv); |
| + | bb_perror_msg("can't execute '%s'", argv[0]); |
| + | _exit(127); /* bash compat */ |
| + | } |
| - | debug_printf("done reading from `cmd` pipe, closing it\n"); |
| - | fclose(fp); |
| - | /* We need to extract exitcode. Test case |
| - | * "true; echo `sleep 1; false` $?" |
| - | * should print 1 */ |
| - | safe_waitpid(pid, &status, 0); |
| - | debug_printf("child exited. returning its exitcode:%d\n", WEXITSTATUS(status)); |
| - | return WEXITSTATUS(status); |
| + | #if ENABLE_HUSH_MODE_X |
| + | static void dump_cmd_in_x_mode(char **argv) |
| + | { |
| + | if (G_x_mode && argv) { |
| + | /* We want to output the line in one write op */ |
| + | char *buf, *p; |
| + | int len; |
| + | int n; |
| + | |
| + | len = 3; |
| + | n = 0; |
| + | while (argv[n]) |
| + | len += strlen(argv[n++]) + 1; |
| + | buf = xmalloc(len); |
| + | buf[0] = '+'; |
| + | p = buf + 1; |
| + | n = 0; |
| + | while (argv[n]) |
| + | p += sprintf(p, " %s", argv[n++]); |
| + | *p++ = '\n'; |
| + | *p = '\0'; |
| + | fputs(buf, stderr); |
| + | free(buf); |
| + | } |
| } | |
| - | #endif /* ENABLE_HUSH_TICK */ |
| + | #else |
| + | # define dump_cmd_in_x_mode(argv) ((void)0) |
| + | #endif |
| - | #if !ENABLE_HUSH_FUNCTIONS |
| - | #define parse_group(dest, ctx, input, ch) \ |
| - | parse_group(ctx, input, ch) |
| + | #if BB_MMU |
| + | #define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \ |
| + | pseudo_exec_argv(argv, assignment_cnt, argv_expanded) |
| + | #define pseudo_exec(nommu_save, command, argv_expanded) \ |
| + | pseudo_exec(command, argv_expanded) |
| #endif | |
| - | static int parse_group(o_string *dest, struct parse_context *ctx, |
| - | struct in_str *input, int ch) |
| + | |
| + | /* Called after [v]fork() in run_pipe, or from builtin_exec. |
| + | * Never returns. |
| + | * Don't exit() here. If you don't exec, use _exit instead. |
| + | * The at_exit handlers apparently confuse the calling process, |
| + | * in particular stdin handling. Not sure why? -- because of vfork! (vda) */ |
| + | static void pseudo_exec_argv(nommu_save_t *nommu_save, |
| + | char **argv, int assignment_cnt, |
| + | char **argv_expanded) NORETURN; |
| + | static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, |
| + | char **argv, int assignment_cnt, |
| + | char **argv_expanded) |
| { | |
| - | /* dest contains characters seen prior to ( or {. |
| - | * Typically it's empty, but for function defs, |
| - | * it contains function name (without '()'). */ |
| - | struct pipe *pipe_list; |
| - | int endch; |
| - | struct command *command = ctx->command; |
| + | char **new_env; |
| - | 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)) |
| - | return 1; |
| - | if (!command->argv) |
| - | goto skip; /* (... */ |
| - | if (command->argv[1]) { /* word word ... (... */ |
| - | syntax_error_unexpected_ch('('); |
| - | return 1; |
| - | } |
| - | /* it is "word(..." or "word (..." */ |
| - | do |
| - | ch = i_getch(input); |
| - | while (ch == ' ' || ch == '\t'); |
| - | if (ch != ')') { |
| - | syntax_error_unexpected_ch(ch); |
| - | return 1; |
| - | } |
| - | nommu_addchr(&ctx->as_string, ch); |
| - | do |
| - | ch = i_getch(input); |
| - | while (ch == ' ' || ch == '\t' || ch == '\n'); |
| - | if (ch != '{') { |
| - | syntax_error_unexpected_ch(ch); |
| - | return 1; |
| + | new_env = expand_assignments(argv, assignment_cnt); |
| + | dump_cmd_in_x_mode(new_env); |
| + | |
| + | if (!argv[assignment_cnt]) { |
| + | /* Case when we are here: ... | var=val | ... |
| + | * (note that we do not exit early, i.e., do not optimize out |
| + | * expand_assignments(): think about ... | var=`sleep 1` | ... |
| + | */ |
| + | free_strings(new_env); |
| + | _exit(EXIT_SUCCESS); |
| + | } |
| + | |
| + | #if BB_MMU |
| + | set_vars_and_save_old(new_env); |
| + | free(new_env); /* optional */ |
| + | /* we can also destroy set_vars_and_save_old's return value, |
| + | * to save memory */ |
| + | #else |
| + | nommu_save->new_env = new_env; |
| + | nommu_save->old_vars = set_vars_and_save_old(new_env); |
| + | #endif |
| + | |
| + | if (argv_expanded) { |
| + | argv = argv_expanded; |
| + | } else { |
| + | argv = expand_strvec_to_strvec(argv + assignment_cnt); |
| + | #if !BB_MMU |
| + | nommu_save->argv = argv; |
| + | #endif |
| + | } |
| + | dump_cmd_in_x_mode(argv); |
| + | |
| + | #if ENABLE_FEATURE_SH_STANDALONE || BB_MMU |
| + | if (strchr(argv[0], '/') != NULL) |
| + | goto skip; |
| + | #endif |
| + | |
| + | /* Check if the command matches any of the builtins. |
| + | * Depending on context, this might be redundant. But it's |
| + | * easier to waste a few CPU cycles than it is to figure out |
| + | * if this is one of those cases. |
| + | */ |
| + | { |
| + | /* On NOMMU, it is more expensive to re-execute shell |
| + | * just in order to run echo or test builtin. |
| + | * It's better to skip it here and run corresponding |
| + | * non-builtin later. */ |
| + | const struct built_in_command *x; |
| + | x = BB_MMU ? find_builtin(argv[0]) : find_builtin1(argv[0]); |
| + | if (x) { |
| + | exec_builtin(&nommu_save->argv_from_re_execing, x, argv); |
| + | } |
| + | } |
| + | #if ENABLE_HUSH_FUNCTIONS |
| + | /* Check if the command matches any functions */ |
| + | { |
| + | const struct function *funcp = find_function(argv[0]); |
| + | if (funcp) { |
| + | exec_function(&nommu_save->argv_from_re_execing, funcp, argv); |
| } | |
| - | nommu_addchr(&ctx->as_string, ch); |
| - | command->cmd_type = CMD_FUNCDEF; |
| - | goto skip; |
| } | |
| #endif | |
| - | #if 0 /* Prevented by caller */ |
| - | if (command->argv /* word [word]{... */ |
| - | || dest->length /* word{... */ |
| - | || dest->has_quoted_part /* ""{... */ |
| - | ) { |
| - | syntax_error(NULL); |
| - | debug_printf_parse("parse_group return 1: " |
| - | "syntax error, groups and arglists don't mix\n"); |
| - | return 1; |
| + | #if ENABLE_FEATURE_SH_STANDALONE |
| + | /* Check if the command matches any busybox applets */ |
| + | { |
| + | int a = find_applet_by_name(argv[0]); |
| + | if (a >= 0) { |
| + | # if BB_MMU /* see above why on NOMMU it is not allowed */ |
| + | if (APPLET_IS_NOEXEC(a)) { |
| + | debug_printf_exec("running applet '%s'\n", argv[0]); |
| + | run_applet_no_and_exit(a, argv); |
| + | } |
| + | # endif |
| + | /* Re-exec ourselves */ |
| + | debug_printf_exec("re-execing applet '%s'\n", argv[0]); |
| + | sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); |
| + | execv(bb_busybox_exec_path, argv); |
| + | /* If they called chroot or otherwise made the binary no longer |
| + | * executable, fall through */ |
| + | } |
| } | |
| #endif | |
| - | #if ENABLE_HUSH_FUNCTIONS |
| + | #if ENABLE_FEATURE_SH_STANDALONE || BB_MMU |
| skip: | |
| #endif | |
| - | endch = '}'; |
| - | if (ch == '(') { |
| - | endch = ')'; |
| - | command->cmd_type = CMD_SUBSHELL; |
| - | } else { |
| - | /* bash does not allow "{echo...", requires whitespace */ |
| - | ch = i_getch(input); |
| - | if (ch != ' ' && ch != '\t' && ch != '\n') { |
| - | syntax_error_unexpected_ch(ch); |
| - | return 1; |
| - | } |
| - | nommu_addchr(&ctx->as_string, ch); |
| + | execvp_or_die(argv); |
| + | } |
| + | |
| + | /* Called after [v]fork() in run_pipe |
| + | */ |
| + | static void pseudo_exec(nommu_save_t *nommu_save, |
| + | struct command *command, |
| + | char **argv_expanded) NORETURN; |
| + | static void pseudo_exec(nommu_save_t *nommu_save, |
| + | struct command *command, |
| + | char **argv_expanded) |
| + | { |
| + | if (command->argv) { |
| + | pseudo_exec_argv(nommu_save, command->argv, |
| + | command->assignment_cnt, argv_expanded); |
| } | |
| - | { |
| + | if (command->group) { |
| + | /* Cases when we are here: |
| + | * ( list ) |
| + | * { list } & |
| + | * ... | ( list ) | ... |
| + | * ... | { list } | ... |
| + | */ |
| #if BB_MMU | |
| - | # define as_string NULL |
| + | int rcode; |
| + | debug_printf_exec("pseudo_exec: run_list\n"); |
| + | reset_traps_to_defaults(); |
| + | rcode = run_list(command->group); |
| + | /* OK to leak memory by not calling free_pipe_list, |
| + | * since this process is about to exit */ |
| + | _exit(rcode); |
| #else | |
| - | char *as_string = NULL; |
| - | #endif |
| - | pipe_list = parse_stream(&as_string, input, endch); |
| - | #if !BB_MMU |
| - | if (as_string) |
| - | o_addstr(&ctx->as_string, as_string); |
| - | #endif |
| - | /* empty ()/{} or parse error? */ |
| - | if (!pipe_list || pipe_list == ERR_PTR) { |
| - | /* parse_stream already emitted error msg */ |
| - | if (!BB_MMU) |
| - | free(as_string); |
| - | debug_printf_parse("parse_group return 1: " |
| - | "parse_stream returned %p\n", pipe_list); |
| - | return 1; |
| - | } |
| - | command->group = pipe_list; |
| - | #if !BB_MMU |
| - | as_string[strlen(as_string) - 1] = '\0'; /* plink ')' or '}' */ |
| - | command->group_as_string = as_string; |
| - | debug_printf_parse("end of group, remembering as:'%s'\n", |
| - | command->group_as_string); |
| + | re_execute_shell(&nommu_save->argv_from_re_execing, |
| + | command->group_as_string, |
| + | G.global_argv[0], |
| + | G.global_argv + 1, |
| + | NULL); |
| #endif | |
| - | #undef as_string |
| } | |
| - | debug_printf_parse("parse_group return 0\n"); |
| - | return 0; |
| - | /* command remains "open", available for possible redirects */ |
| + | |
| + | /* Case when we are here: ... | >file */ |
| + | debug_printf_exec("pseudo_exec'ed null command\n"); |
| + | _exit(EXIT_SUCCESS); |
| } | |
| - | #if ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_DOLLAR_OPS |
| - | /* Subroutines for copying $(...) and `...` things */ |
| - | static void add_till_backquote(o_string *dest, struct in_str *input); |
| - | /* '...' */ |
| - | static void add_till_single_quote(o_string *dest, struct in_str *input) |
| + | #if ENABLE_HUSH_JOB |
| + | static const char *get_cmdtext(struct pipe *pi) |
| { | |
| - | while (1) { |
| - | int ch = i_getch(input); |
| - | if (ch == EOF) { |
| - | syntax_error_unterm_ch('\''); |
| - | /*xfunc_die(); - redundant */ |
| - | } |
| - | if (ch == '\'') |
| - | return; |
| - | o_addchr(dest, ch); |
| + | char **argv; |
| + | char *p; |
| + | int len; |
| + | |
| + | /* This is subtle. ->cmdtext is created only on first backgrounding. |
| + | * (Think "cat, <ctrl-z>, fg, <ctrl-z>, fg, <ctrl-z>...." here...) |
| + | * On subsequent bg argv is trashed, but we won't use it */ |
| + | if (pi->cmdtext) |
| + | return pi->cmdtext; |
| + | argv = pi->cmds[0].argv; |
| + | if (!argv || !argv[0]) { |
| + | pi->cmdtext = xzalloc(1); |
| + | return pi->cmdtext; |
| } | |
| + | |
| + | len = 0; |
| + | do { |
| + | len += strlen(*argv) + 1; |
| + | } while (*++argv); |
| + | p = xmalloc(len); |
| + | pi->cmdtext = p; |
| + | argv = pi->cmds[0].argv; |
| + | do { |
| + | len = strlen(*argv); |
| + | memcpy(p, *argv, len); |
| + | p += len; |
| + | *p++ = ' '; |
| + | } while (*++argv); |
| + | p[-1] = '\0'; |
| + | return pi->cmdtext; |
| } | |
| - | /* "...\"...`..`...." - do we need to handle "...$(..)..." too? */ |
| - | static void add_till_double_quote(o_string *dest, struct in_str *input) |
| + | |
| + | static void insert_bg_job(struct pipe *pi) |
| { | |
| - | while (1) { |
| - | int ch = i_getch(input); |
| - | if (ch == EOF) { |
| - | syntax_error_unterm_ch('"'); |
| - | /*xfunc_die(); - redundant */ |
| - | } |
| - | if (ch == '"') |
| - | return; |
| - | if (ch == '\\') { /* \x. Copy both chars. */ |
| - | o_addchr(dest, ch); |
| - | ch = i_getch(input); |
| + | struct pipe *job, **jobp; |
| + | int i; |
| + | |
| + | /* Linear search for the ID of the job to use */ |
| + | pi->jobid = 1; |
| + | for (job = G.job_list; job; job = job->next) |
| + | if (job->jobid >= pi->jobid) |
| + | pi->jobid = job->jobid + 1; |
| + | |
| + | /* Add job to the list of running jobs */ |
| + | jobp = &G.job_list; |
| + | while ((job = *jobp) != NULL) |
| + | jobp = &job->next; |
| + | job = *jobp = xmalloc(sizeof(*job)); |
| + | |
| + | *job = *pi; /* physical copy */ |
| + | job->next = NULL; |
| + | job->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds); |
| + | /* Cannot copy entire pi->cmds[] vector! This causes double frees */ |
| + | for (i = 0; i < pi->num_cmds; i++) { |
| + | job->cmds[i].pid = pi->cmds[i].pid; |
| + | /* all other fields are not used and stay zero */ |
| + | } |
| + | job->cmdtext = xstrdup(get_cmdtext(pi)); |
| + | |
| + | if (G_interactive_fd) |
| + | printf("[%d] %d %s\n", job->jobid, job->cmds[0].pid, job->cmdtext); |
| + | G.last_jobid = job->jobid; |
| + | } |
| + | |
| + | static void remove_bg_job(struct pipe *pi) |
| + | { |
| + | struct pipe *prev_pipe; |
| + | |
| + | if (pi == G.job_list) { |
| + | G.job_list = pi->next; |
| + | } else { |
| + | prev_pipe = G.job_list; |
| + | while (prev_pipe->next != pi) |
| + | prev_pipe = prev_pipe->next; |
| + | prev_pipe->next = pi->next; |
| + | } |
| + | if (G.job_list) |
| + | G.last_jobid = G.job_list->jobid; |
| + | else |
| + | G.last_jobid = 0; |
| + | } |
| + | |
| + | /* Remove a backgrounded job */ |
| + | static void delete_finished_bg_job(struct pipe *pi) |
| + | { |
| + | remove_bg_job(pi); |
| + | pi->stopped_cmds = 0; |
| + | free_pipe(pi); |
| + | free(pi); |
| + | } |
| + | #endif /* JOB */ |
| + | |
| + | /* Check to see if any processes have exited -- if they |
| + | * have, figure out why and see if a job has completed */ |
| + | static int checkjobs(struct pipe* fg_pipe) |
| + | { |
| + | int attributes; |
| + | int status; |
| + | #if ENABLE_HUSH_JOB |
| + | struct pipe *pi; |
| + | #endif |
| + | pid_t childpid; |
| + | int rcode = 0; |
| + | |
| + | debug_printf_jobs("checkjobs %p\n", fg_pipe); |
| + | |
| + | attributes = WUNTRACED; |
| + | if (fg_pipe == NULL) |
| + | attributes |= WNOHANG; |
| + | |
| + | errno = 0; |
| + | #if ENABLE_HUSH_FAST |
| + | if (G.handled_SIGCHLD == G.count_SIGCHLD) { |
| + | //bb_error_msg("[%d] checkjobs: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d children?:%d fg_pipe:%p", |
| + | //getpid(), G.count_SIGCHLD, G.handled_SIGCHLD, G.we_have_children, fg_pipe); |
| + | /* There was neither fork nor SIGCHLD since last waitpid */ |
| + | /* Avoid doing waitpid syscall if possible */ |
| + | if (!G.we_have_children) { |
| + | errno = ECHILD; |
| + | return -1; |
| } | |
| - | o_addchr(dest, ch); |
| - | if (ch == '`') { |
| - | add_till_backquote(dest, input); |
| - | o_addchr(dest, ch); |
| - | continue; |
| + | if (fg_pipe == NULL) { /* is WNOHANG set? */ |
| + | /* We have children, but they did not exit |
| + | * or stop yet (we saw no SIGCHLD) */ |
| + | return 0; |
| } | |
| - | //if (ch == '$') ... |
| + | /* else: !WNOHANG, waitpid will block, can't short-circuit */ |
| } | |
| - | } |
| - | /* Process `cmd` - copy contents until "`" is seen. Complicated by |
| - | * \` quoting. |
| - | * "Within the backquoted style of command substitution, backslash |
| - | * shall retain its literal meaning, except when followed by: '$', '`', or '\'. |
| - | * The search for the matching backquote shall be satisfied by the first |
| - | * backquote found without a preceding backslash; during this search, |
| - | * if a non-escaped backquote is encountered within a shell comment, |
| - | * a here-document, an embedded command substitution of the $(command) |
| - | * form, or a quoted string, undefined results occur. A single-quoted |
| - | * or double-quoted string that begins, but does not end, within the |
| - | * "`...`" sequence produces undefined results." |
| - | * Example Output |
| - | * echo `echo '\'TEST\`echo ZZ\`BEST` \TESTZZBEST |
| + | #endif |
| + | |
| + | /* Do we do this right? |
| + | * bash-3.00# sleep 20 | false |
| + | * <ctrl-Z pressed> |
| + | * [3]+ Stopped sleep 20 | false |
| + | * bash-3.00# echo $? |
| + | * 1 <========== bg pipe is not fully done, but exitcode is already known! |
| + | * [hush 1.14.0: yes we do it right] |
| */ | |
| - | static void add_till_backquote(o_string *dest, struct in_str *input) |
| - | { |
| + | wait_more: |
| while (1) { | |
| - | int ch = i_getch(input); |
| - | if (ch == EOF) { |
| - | syntax_error_unterm_ch('`'); |
| - | /*xfunc_die(); - redundant */ |
| - | } |
| - | if (ch == '`') |
| - | return; |
| - | if (ch == '\\') { |
| - | /* \x. Copy both chars unless it is \` */ |
| - | int ch2 = i_getch(input); |
| - | if (ch2 == EOF) { |
| - | syntax_error_unterm_ch('`'); |
| - | /*xfunc_die(); - redundant */ |
| + | int i; |
| + | int dead; |
| + | |
| + | #if ENABLE_HUSH_FAST |
| + | i = G.count_SIGCHLD; |
| + | #endif |
| + | childpid = waitpid(-1, &status, attributes); |
| + | if (childpid <= 0) { |
| + | if (childpid && errno != ECHILD) |
| + | bb_perror_msg("waitpid"); |
| + | #if ENABLE_HUSH_FAST |
| + | else { /* Until next SIGCHLD, waitpid's are useless */ |
| + | G.we_have_children = (childpid == 0); |
| + | G.handled_SIGCHLD = i; |
| + | //bb_error_msg("[%d] checkjobs: waitpid returned <= 0, G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); |
| } | |
| - | if (ch2 != '`' && ch2 != '$' && ch2 != '\\') |
| - | o_addchr(dest, ch); |
| - | ch = ch2; |
| + | #endif |
| + | break; |
| } | |
| - | o_addchr(dest, ch); |
| - | } |
| - | } |
| - | /* Process $(cmd) - copy contents until ")" is seen. Complicated by |
| - | * quoting and nested ()s. |
| - | * "With the $(command) style of command substitution, all characters |
| - | * following the open parenthesis to the matching closing parenthesis |
| - | * constitute the command. Any valid shell script can be used for command, |
| - | * except a script consisting solely of redirections which produces |
| - | * unspecified results." |
| - | * Example Output |
| - | * echo $(echo '(TEST)' BEST) (TEST) BEST |
| - | * echo $(echo 'TEST)' BEST) TEST) BEST |
| - | * echo $(echo \(\(TEST\) BEST) ((TEST) BEST |
| - | * |
| - | * Also adapted to eat ${var%...} and $((...)) constructs, since ... part |
| - | * can contain arbitrary constructs, just like $(cmd). |
| - | * In bash compat mode, it needs to also be able to stop on ':' or '/' |
| - | * for ${var:N[:M]} and ${var/P[/R]} parsing. |
| - | */ |
| - | #define DOUBLE_CLOSE_CHAR_FLAG 0x80 |
| - | static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsigned end_ch) |
| - | { |
| - | int ch; |
| - | char dbl = end_ch & DOUBLE_CLOSE_CHAR_FLAG; |
| - | # if ENABLE_HUSH_BASH_COMPAT |
| - | char end_char2 = end_ch >> 8; |
| - | # endif |
| - | end_ch &= (DOUBLE_CLOSE_CHAR_FLAG - 1); |
| + | dead = WIFEXITED(status) || WIFSIGNALED(status); |
| - | while (1) { |
| - | ch = i_getch(input); |
| - | if (ch == EOF) { |
| - | syntax_error_unterm_ch(end_ch); |
| - | /*xfunc_die(); - redundant */ |
| - | } |
| - | if (ch == end_ch IF_HUSH_BASH_COMPAT( || ch == end_char2)) { |
| - | if (!dbl) |
| - | break; |
| - | /* we look for closing )) of $((EXPR)) */ |
| - | if (i_peek(input) == end_ch) { |
| - | i_getch(input); /* eat second ')' */ |
| - | break; |
| + | #if DEBUG_JOBS |
| + | if (WIFSTOPPED(status)) |
| + | debug_printf_jobs("pid %d stopped by sig %d (exitcode %d)\n", |
| + | childpid, WSTOPSIG(status), WEXITSTATUS(status)); |
| + | if (WIFSIGNALED(status)) |
| + | debug_printf_jobs("pid %d killed by sig %d (exitcode %d)\n", |
| + | childpid, WTERMSIG(status), WEXITSTATUS(status)); |
| + | if (WIFEXITED(status)) |
| + | debug_printf_jobs("pid %d exited, exitcode %d\n", |
| + | childpid, WEXITSTATUS(status)); |
| + | #endif |
| + | /* Were we asked to wait for fg pipe? */ |
| + | if (fg_pipe) { |
| + | for (i = 0; i < fg_pipe->num_cmds; i++) { |
| + | debug_printf_jobs("check pid %d\n", fg_pipe->cmds[i].pid); |
| + | if (fg_pipe->cmds[i].pid != childpid) |
| + | continue; |
| + | if (dead) { |
| + | fg_pipe->cmds[i].pid = 0; |
| + | fg_pipe->alive_cmds--; |
| + | if (i == fg_pipe->num_cmds - 1) { |
| + | /* last process gives overall exitstatus */ |
| + | rcode = WEXITSTATUS(status); |
| + | /* bash prints killer signal's name for *last* |
| + | * process in pipe (prints just newline for SIGINT). |
| + | * Mimic this. Example: "sleep 5" + (^\ or kill -QUIT) |
| + | */ |
| + | if (WIFSIGNALED(status)) { |
| + | int sig = WTERMSIG(status); |
| + | printf("%s\n", sig == SIGINT ? "" : get_signame(sig)); |
| + | /* TODO: MIPS has 128 sigs (1..128), what if sig==128 here? |
| + | * Maybe we need to use sig | 128? */ |
| + | rcode = sig + 128; |
| + | } |
| + | IF_HAS_KEYWORDS(if (fg_pipe->pi_inverted) rcode = !rcode;) |
| + | } |
| + | } else { |
| + | fg_pipe->cmds[i].is_stopped = 1; |
| + | fg_pipe->stopped_cmds++; |
| + | } |
| + | debug_printf_jobs("fg_pipe: alive_cmds %d stopped_cmds %d\n", |
| + | fg_pipe->alive_cmds, fg_pipe->stopped_cmds); |
| + | if (fg_pipe->alive_cmds - fg_pipe->stopped_cmds <= 0) { |
| + | /* All processes in fg pipe have exited or stopped */ |
| + | /* Note: *non-interactive* bash does not continue if all processes in fg pipe |
| + | * are stopped. Testcase: "cat | cat" in a script (not on command line!) |
| + | * and "killall -STOP cat" */ |
| + | if (G_interactive_fd) { |
| + | #if ENABLE_HUSH_JOB |
| + | if (fg_pipe->alive_cmds) |
| + | insert_bg_job(fg_pipe); |
| + | #endif |
| + | return rcode; |
| + | } |
| + | if (!fg_pipe->alive_cmds) |
| + | return rcode; |
| + | } |
| + | /* There are still running processes in the fg pipe */ |
| + | goto wait_more; /* do waitpid again */ |
| } | |
| + | /* it wasnt fg_pipe, look for process in bg pipes */ |
| } | |
| - | o_addchr(dest, ch); |
| - | if (ch == '(' || ch == '{') { |
| - | ch = (ch == '(' ? ')' : '}'); |
| - | add_till_closing_bracket(dest, input, ch); |
| - | o_addchr(dest, ch); |
| - | continue; |
| - | } |
| - | if (ch == '\'') { |
| - | add_till_single_quote(dest, input); |
| - | o_addchr(dest, ch); |
| - | continue; |
| - | } |
| - | if (ch == '"') { |
| - | add_till_double_quote(dest, input); |
| - | o_addchr(dest, ch); |
| - | continue; |
| - | } |
| - | if (ch == '`') { |
| - | add_till_backquote(dest, input); |
| - | o_addchr(dest, ch); |
| - | continue; |
| + | |
| + | #if ENABLE_HUSH_JOB |
| + | /* We asked to wait for bg or orphaned children */ |
| + | /* No need to remember exitcode in this case */ |
| + | for (pi = G.job_list; pi; pi = pi->next) { |
| + | for (i = 0; i < pi->num_cmds; i++) { |
| + | if (pi->cmds[i].pid == childpid) |
| + | goto found_pi_and_prognum; |
| + | } |
| } | |
| - | if (ch == '\\') { |
| - | /* \x. Copy verbatim. Important for \(, \) */ |
| - | ch = i_getch(input); |
| - | if (ch == EOF) { |
| - | syntax_error_unterm_ch(')'); |
| - | /*xfunc_die(); - redundant */ |
| + | /* Happens when shell is used as init process (init=/bin/sh) */ |
| + | debug_printf("checkjobs: pid %d was not in our list!\n", childpid); |
| + | continue; /* do waitpid again */ |
| + | |
| + | found_pi_and_prognum: |
| + | if (dead) { |
| + | /* child exited */ |
| + | pi->cmds[i].pid = 0; |
| + | pi->alive_cmds--; |
| + | if (!pi->alive_cmds) { |
| + | if (G_interactive_fd) |
| + | printf(JOB_STATUS_FORMAT, pi->jobid, |
| + | "Done", pi->cmdtext); |
| + | delete_finished_bg_job(pi); |
| } | |
| - | o_addchr(dest, ch); |
| - | continue; |
| + | } else { |
| + | /* child stopped */ |
| + | pi->cmds[i].is_stopped = 1; |
| + | pi->stopped_cmds++; |
| } | |
| + | #endif |
| + | } /* while (waitpid succeeds)... */ |
| + | |
| + | return rcode; |
| + | } |
| + | |
| + | #if ENABLE_HUSH_JOB |
| + | static int checkjobs_and_fg_shell(struct pipe* fg_pipe) |
| + | { |
| + | pid_t p; |
| + | int rcode = checkjobs(fg_pipe); |
| + | if (G_saved_tty_pgrp) { |
| + | /* Job finished, move the shell to the foreground */ |
| + | p = getpgrp(); /* our process group id */ |
| + | debug_printf_jobs("fg'ing ourself: getpgrp()=%d\n", (int)p); |
| + | tcsetpgrp(G_interactive_fd, p); |
| } | |
| - | return ch; |
| + | return rcode; |
| } | |
| - | #endif /* ENABLE_HUSH_TICK || ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_DOLLAR_OPS */ |
| + | #endif |
| - | /* Return code: 0 for OK, 1 for syntax error */ |
| - | #if BB_MMU |
| - | #define parse_dollar(as_string, dest, input) \ |
| - | parse_dollar(dest, input) |
| - | #define as_string NULL |
| + | /* Start all the jobs, but don't wait for anything to finish. |
| + | * See checkjobs(). |
| + | * |
| + | * Return code is normally -1, when the caller has to wait for children |
| + | * to finish to determine the exit status of the pipe. If the pipe |
| + | * is a simple builtin command, however, the action is done by the |
| + | * time run_pipe returns, and the exit code is provided as the |
| + | * return value. |
| + | * |
| + | * Returns -1 only if started some children. IOW: we have to |
| + | * mask out retvals of builtins etc with 0xff! |
| + | * |
| + | * The only case when we do not need to [v]fork is when the pipe |
| + | * is single, non-backgrounded, non-subshell command. Examples: |
| + | * cmd ; ... { list } ; ... |
| + | * cmd && ... { list } && ... |
| + | * cmd || ... { list } || ... |
| + | * If it is, then we can run cmd as a builtin, NOFORK [do we do this?], |
| + | * or (if SH_STANDALONE) an applet, and we can run the { list } |
| + | * with run_list. If it isn't one of these, we fork and exec cmd. |
| + | * |
| + | * Cases when we must fork: |
| + | * non-single: cmd | cmd |
| + | * backgrounded: cmd & { list } & |
| + | * subshell: ( list ) [&] |
| + | */ |
| + | #if !ENABLE_HUSH_MODE_X |
| + | #define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, char argv_expanded) \ |
| + | redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel) |
| #endif | |
| - | static int parse_dollar(o_string *as_string, |
| - | o_string *dest, |
| - | struct in_str *input) |
| + | static int redirect_and_varexp_helper(char ***new_env_p, |
| + | struct variable **old_vars_p, |
| + | struct command *command, |
| + | int squirrel[3], |
| + | char **argv_expanded) |
| { | |
| - | int ch = i_peek(input); /* first character after the $ */ |
| - | unsigned char quote_mask = dest->o_escape ? 0x80 : 0; |
| + | /* setup_redirects acts on file descriptors, not FILEs. |
| + | * This is perfect for work that comes after exec(). |
| + | * Is it really safe for inline use? Experimentally, |
| + | * things seem to work. */ |
| + | int rcode = setup_redirects(command, squirrel); |
| + | if (rcode == 0) { |
| + | char **new_env = expand_assignments(command->argv, command->assignment_cnt); |
| + | *new_env_p = new_env; |
| + | dump_cmd_in_x_mode(new_env); |
| + | dump_cmd_in_x_mode(argv_expanded); |
| + | if (old_vars_p) |
| + | *old_vars_p = set_vars_and_save_old(new_env); |
| + | } |
| + | return rcode; |
| + | } |
| + | static NOINLINE int run_pipe(struct pipe *pi) |
| + | { |
| + | static const char *const null_ptr = NULL; |
| - | debug_printf_parse("parse_dollar entered: ch='%c'\n", ch); |
| - | if (isalpha(ch)) { |
| - | ch = i_getch(input); |
| - | nommu_addchr(as_string, ch); |
| - | make_var: |
| - | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| - | while (1) { |
| - | debug_printf_parse(": '%c'\n", ch); |
| - | o_addchr(dest, ch | quote_mask); |
| - | quote_mask = 0; |
| - | ch = i_peek(input); |
| - | if (!isalnum(ch) && ch != '_') |
| - | break; |
| - | ch = i_getch(input); |
| - | nommu_addchr(as_string, ch); |
| - | } |
| - | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| - | } else if (isdigit(ch)) { |
| - | make_one_char_var: |
| - | ch = i_getch(input); |
| - | nommu_addchr(as_string, ch); |
| - | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| - | debug_printf_parse(": '%c'\n", ch); |
| - | o_addchr(dest, ch | quote_mask); |
| - | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| - | } else switch (ch) { |
| - | case '$': /* pid */ |
| - | case '!': /* last bg pid */ |
| - | case '?': /* last exit code */ |
| - | case '#': /* number of args */ |
| - | case '*': /* args */ |
| - | case '@': /* args */ |
| - | goto make_one_char_var; |
| - | case '{': { |
| - | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| + | int cmd_no; |
| + | int next_infd; |
| + | struct command *command; |
| + | char **argv_expanded; |
| + | char **argv; |
| + | /* it is not always needed, but we aim to smaller code */ |
| + | int squirrel[] = { -1, -1, -1 }; |
| + | int rcode; |
| - | ch = i_getch(input); /* eat '{' */ |
| - | nommu_addchr(as_string, ch); |
| + | debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds); |
| + | debug_enter(); |
| - | ch = i_getch(input); /* first char after '{' */ |
| - | nommu_addchr(as_string, ch); |
| - | /* It should be ${?}, or ${#var}, |
| - | * or even ${?+subst} - operator acting on a special variable, |
| - | * or the beginning of variable name. |
| - | */ |
| - | if (!strchr(_SPECIAL_VARS_STR, ch) && !isalnum(ch)) { /* not one of those */ |
| - | bad_dollar_syntax: |
| - | syntax_error_unterm_str("${name}"); |
| - | debug_printf_parse("parse_dollar return 1: unterminated ${name}\n"); |
| - | return 1; |
| + | IF_HUSH_JOB(pi->pgrp = -1;) |
| + | pi->stopped_cmds = 0; |
| + | command = &pi->cmds[0]; |
| + | argv_expanded = NULL; |
| + | |
| + | if (pi->num_cmds != 1 |
| + | || pi->followup == PIPE_BG |
| + | || command->cmd_type == CMD_SUBSHELL |
| + | ) { |
| + | goto must_fork; |
| + | } |
| + | |
| + | pi->alive_cmds = 1; |
| + | |
| + | debug_printf_exec(": group:%p argv:'%s'\n", |
| + | command->group, command->argv ? command->argv[0] : "NONE"); |
| + | |
| + | if (command->group) { |
| + | #if ENABLE_HUSH_FUNCTIONS |
| + | if (command->cmd_type == CMD_FUNCDEF) { |
| + | /* "executing" func () { list } */ |
| + | struct function *funcp; |
| + | |
| + | funcp = new_function(command->argv[0]); |
| + | /* funcp->name is already set to argv[0] */ |
| + | funcp->body = command->group; |
| + | # if !BB_MMU |
| + | funcp->body_as_string = command->group_as_string; |
| + | command->group_as_string = NULL; |
| + | # endif |
| + | command->group = NULL; |
| + | command->argv[0] = NULL; |
| + | debug_printf_exec("cmd %p has child func at %p\n", command, funcp); |
| + | funcp->parent_cmd = command; |
| + | command->child_func = funcp; |
| + | |
| + | debug_printf_exec("run_pipe: return EXIT_SUCCESS\n"); |
| + | debug_leave(); |
| + | return EXIT_SUCCESS; |
| } | |
| - | ch |= quote_mask; |
| + | #endif |
| + | /* { list } */ |
| + | debug_printf("non-subshell group\n"); |
| + | rcode = 1; /* exitcode if redir failed */ |
| + | if (setup_redirects(command, squirrel) == 0) { |
| + | debug_printf_exec(": run_list\n"); |
| + | rcode = run_list(command->group) & 0xff; |
| + | } |
| + | restore_redirects(squirrel); |
| + | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) |
| + | debug_leave(); |
| + | debug_printf_exec("run_pipe: return %d\n", rcode); |
| + | return rcode; |
| + | } |
| - | /* It's possible to just call add_till_closing_bracket() at this point. |
| - | * However, this regresses some of our testsuite cases |
| - | * which check invalid constructs like ${%}. |
| - | * Oh well... let's check that the var name part is fine... */ |
| + | argv = command->argv ? command->argv : (char **) &null_ptr; |
| + | { |
| + | const struct built_in_command *x; |
| + | #if ENABLE_HUSH_FUNCTIONS |
| + | const struct function *funcp; |
| + | #else |
| + | enum { funcp = 0 }; |
| + | #endif |
| + | char **new_env = NULL; |
| + | struct variable *old_vars = NULL; |
| - | while (1) { |
| - | unsigned pos; |
| + | if (argv[command->assignment_cnt] == NULL) { |
| + | /* Assignments, but no command */ |
| + | /* Ensure redirects take effect (that is, create files). |
| + | * Try "a=t >file" */ |
| + | #if 0 /* A few cases in testsuite fail with this code. FIXME */ |
| + | rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, squirrel, /*argv_expanded:*/ NULL); |
| + | /* Set shell variables */ |
| + | if (new_env) { |
| + | argv = new_env; |
| + | while (*argv) { |
| + | set_local_var(*argv, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); |
| + | /* Do we need to flag set_local_var() errors? |
| + | * "assignment to readonly var" and "putenv error" |
| + | */ |
| + | argv++; |
| + | } |
| + | } |
| + | /* Redirect error sets $? to 1. Otherwise, |
| + | * if evaluating assignment value set $?, retain it. |
| + | * Try "false; q=`exit 2`; echo $?" - should print 2: */ |
| + | if (rcode == 0) |
| + | rcode = G.last_exitcode; |
| + | /* Exit, _skipping_ variable restoring code: */ |
| + | goto clean_up_and_ret0; |
| - | o_addchr(dest, ch); |
| - | debug_printf_parse(": '%c'\n", ch); |
| + | #else /* Older, bigger, but more correct code */ |
| - | ch = i_getch(input); |
| - | nommu_addchr(as_string, ch); |
| - | if (ch == '}') |
| - | break; |
| + | rcode = setup_redirects(command, squirrel); |
| + | restore_redirects(squirrel); |
| + | /* Set shell variables */ |
| + | if (G_x_mode) |
| + | bb_putchar_stderr('+'); |
| + | while (*argv) { |
| + | char *p = expand_string_to_string(*argv); |
| + | if (G_x_mode) |
| + | fprintf(stderr, " %s", p); |
| + | debug_printf_exec("set shell var:'%s'->'%s'\n", |
| + | *argv, p); |
| + | set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); |
| + | /* Do we need to flag set_local_var() errors? |
| + | * "assignment to readonly var" and "putenv error" |
| + | */ |
| + | argv++; |
| + | } |
| + | if (G_x_mode) |
| + | bb_putchar_stderr('\n'); |
| + | /* Redirect error sets $? to 1. Otherwise, |
| + | * if evaluating assignment value set $?, retain it. |
| + | * Try "false; q=`exit 2`; echo $?" - should print 2: */ |
| + | if (rcode == 0) |
| + | rcode = G.last_exitcode; |
| + | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) |
| + | debug_leave(); |
| + | debug_printf_exec("run_pipe: return %d\n", rcode); |
| + | return rcode; |
| + | #endif |
| + | } |
| + | |
| + | /* Expand the rest into (possibly) many strings each */ |
| + | if (0) {} |
| + | #if ENABLE_HUSH_BASH_COMPAT |
| + | else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) { |
| + | argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt); |
| + | } |
| + | #endif |
| + | #ifdef CMD_SINGLEWORD_NOGLOB_COND |
| + | else if (command->cmd_type == CMD_SINGLEWORD_NOGLOB_COND) { |
| + | argv_expanded = expand_strvec_to_strvec_singleword_noglob_cond(argv + command->assignment_cnt); |
| + | } |
| + | #endif |
| + | else { |
| + | argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); |
| + | } |
| - | if (!isalnum(ch) && ch != '_') { |
| - | unsigned end_ch; |
| - | unsigned char last_ch; |
| - | /* handle parameter expansions |
| - | * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 |
| - | */ |
| - | if (!strchr(VAR_SUBST_OPS, ch)) /* ${var<bad_char>... */ |
| - | goto bad_dollar_syntax; |
| + | /* if someone gives us an empty string: `cmd with empty output` */ |
| + | if (!argv_expanded[0]) { |
| + | free(argv_expanded); |
| + | debug_leave(); |
| + | return G.last_exitcode; |
| + | } |
| - | /* Eat everything until closing '}' (or ':') */ |
| - | end_ch = '}'; |
| - | if (ENABLE_HUSH_BASH_COMPAT |
| - | && ch == ':' |
| - | && !strchr(MINUS_PLUS_EQUAL_QUESTION, i_peek(input)) |
| - | ) { |
| - | /* It's ${var:N[:M]} thing */ |
| - | end_ch = '}' * 0x100 + ':'; |
| - | } |
| - | if (ENABLE_HUSH_BASH_COMPAT |
| - | && ch == '/' |
| - | ) { |
| - | /* It's ${var/[/]pattern[/repl]} thing */ |
| - | if (i_peek(input) == '/') { /* ${var//pattern[/repl]}? */ |
| - | i_getch(input); |
| - | nommu_addchr(as_string, '/'); |
| - | ch = '\\'; |
| - | } |
| - | end_ch = '}' * 0x100 + '/'; |
| - | } |
| - | o_addchr(dest, ch); |
| - | again: |
| - | if (!BB_MMU) |
| - | pos = dest->length; |
| - | #if ENABLE_HUSH_DOLLAR_OPS |
| - | last_ch = add_till_closing_bracket(dest, input, end_ch); |
| - | #else |
| - | #error Simple code to only allow ${var} is not implemented |
| + | x = find_builtin(argv_expanded[0]); |
| + | #if ENABLE_HUSH_FUNCTIONS |
| + | funcp = NULL; |
| + | if (!x) |
| + | funcp = find_function(argv_expanded[0]); |
| #endif | |
| - | if (as_string) { |
| - | o_addstr(as_string, dest->data + pos); |
| - | o_addchr(as_string, last_ch); |
| + | if (x || funcp) { |
| + | if (!funcp) { |
| + | if (x->b_function == builtin_exec && argv_expanded[1] == NULL) { |
| + | debug_printf("exec with redirects only\n"); |
| + | rcode = setup_redirects(command, NULL); |
| + | goto clean_up_and_ret1; |
| } | |
| - | |
| - | if (ENABLE_HUSH_BASH_COMPAT && (end_ch & 0xff00)) { |
| - | /* close the first block: */ |
| - | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| - | /* while parsing N from ${var:N[:M]} |
| - | * or pattern from ${var/[/]pattern[/repl]} */ |
| - | if ((end_ch & 0xff) == last_ch) { |
| - | /* got ':' or '/'- parse the rest */ |
| - | end_ch = '}'; |
| - | goto again; |
| - | } |
| - | /* got '}' */ |
| - | if (end_ch == '}' * 0x100 + ':') { |
| - | /* it's ${var:N} - emulate :999999999 */ |
| - | o_addstr(dest, "999999999"); |
| - | } /* else: it's ${var/[/]pattern} */ |
| + | } |
| + | rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); |
| + | if (rcode == 0) { |
| + | if (!funcp) { |
| + | debug_printf_exec(": builtin '%s' '%s'...\n", |
| + | x->b_cmd, argv_expanded[1]); |
| + | rcode = x->b_function(argv_expanded) & 0xff; |
| + | fflush_all(); |
| } | |
| - | break; |
| + | #if ENABLE_HUSH_FUNCTIONS |
| + | else { |
| + | # if ENABLE_HUSH_LOCAL |
| + | struct variable **sv; |
| + | sv = G.shadowed_vars_pp; |
| + | G.shadowed_vars_pp = &old_vars; |
| + | # endif |
| + | debug_printf_exec(": function '%s' '%s'...\n", |
| + | funcp->name, argv_expanded[1]); |
| + | rcode = run_function(funcp, argv_expanded) & 0xff; |
| + | # if ENABLE_HUSH_LOCAL |
| + | G.shadowed_vars_pp = sv; |
| + | # endif |
| + | } |
| + | #endif |
| } | |
| + | clean_up_and_ret: |
| + | unset_vars(new_env); |
| + | add_vars(old_vars); |
| + | /* clean_up_and_ret0: */ |
| + | restore_redirects(squirrel); |
| + | clean_up_and_ret1: |
| + | free(argv_expanded); |
| + | IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) |
| + | debug_leave(); |
| + | debug_printf_exec("run_pipe return %d\n", rcode); |
| + | return rcode; |
| } | |
| - | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| - | break; |
| - | } |
| - | #if ENABLE_SH_MATH_SUPPORT || ENABLE_HUSH_TICK |
| - | case '(': { |
| - | unsigned pos; |
| - | ch = i_getch(input); |
| - | nommu_addchr(as_string, ch); |
| - | # if ENABLE_SH_MATH_SUPPORT |
| - | if (i_peek(input) == '(') { |
| - | ch = i_getch(input); |
| - | nommu_addchr(as_string, ch); |
| - | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| - | o_addchr(dest, /*quote_mask |*/ '+'); |
| - | if (!BB_MMU) |
| - | pos = dest->length; |
| - | add_till_closing_bracket(dest, input, ')' | DOUBLE_CLOSE_CHAR_FLAG); |
| - | if (as_string) { |
| - | o_addstr(as_string, dest->data + pos); |
| - | o_addchr(as_string, ')'); |
| - | o_addchr(as_string, ')'); |
| + | if (ENABLE_FEATURE_SH_STANDALONE) { |
| + | int n = find_applet_by_name(argv_expanded[0]); |
| + | if (n >= 0 && APPLET_IS_NOFORK(n)) { |
| + | rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); |
| + | if (rcode == 0) { |
| + | debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", |
| + | argv_expanded[0], argv_expanded[1]); |
| + | rcode = run_nofork_applet(n, argv_expanded); |
| + | } |
| + | goto clean_up_and_ret; |
| } | |
| - | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| - | break; |
| - | } |
| - | # endif |
| - | # if ENABLE_HUSH_TICK |
| - | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| - | o_addchr(dest, quote_mask | '`'); |
| - | if (!BB_MMU) |
| - | pos = dest->length; |
| - | add_till_closing_bracket(dest, input, ')'); |
| - | if (as_string) { |
| - | o_addstr(as_string, dest->data + pos); |
| - | o_addchr(as_string, ')'); |
| } | |
| - | o_addchr(dest, SPECIAL_VAR_SYMBOL); |
| - | # endif |
| - | break; |
| + | /* It is neither builtin nor applet. We must fork. */ |
| } | |
| + | |
| + | must_fork: |
| + | /* NB: argv_expanded may already be created, and that |
| + | * might include `cmd` runs! Do not rerun it! We *must* |
| + | * use argv_expanded if it's non-NULL */ |
| + | |
| + | /* Going to fork a child per each pipe member */ |
| + | pi->alive_cmds = 0; |
| + | next_infd = 0; |
| + | |
| + | cmd_no = 0; |
| + | while (cmd_no < pi->num_cmds) { |
| + | struct fd_pair pipefds; |
| + | #if !BB_MMU |
| + | volatile nommu_save_t nommu_save; |
| + | nommu_save.new_env = NULL; |
| + | nommu_save.old_vars = NULL; |
| + | nommu_save.argv = NULL; |
| + | nommu_save.argv_from_re_execing = NULL; |
| #endif | |
| - | case '_': |
| - | ch = i_getch(input); |
| - | nommu_addchr(as_string, ch); |
| - | ch = i_peek(input); |
| - | if (isalnum(ch)) { /* it's $_name or $_123 */ |
| - | ch = '_'; |
| - | goto make_var; |
| + | command = &pi->cmds[cmd_no]; |
| + | cmd_no++; |
| + | if (command->argv) { |
| + | debug_printf_exec(": pipe member '%s' '%s'...\n", |
| + | command->argv[0], command->argv[1]); |
| + | } else { |
| + | debug_printf_exec(": pipe member with no argv\n"); |
| } | |
| - | /* else: it's $_ */ |
| - | /* TODO: $_ and $-: */ |
| - | /* $_ Shell or shell script name; or last argument of last command |
| - | * (if last command wasn't a pipe; if it was, bash sets $_ to ""); |
| - | * but in command's env, set to full pathname used to invoke it */ |
| - | /* $- Option flags set by set builtin or shell options (-i etc) */ |
| - | default: |
| - | o_addQchr(dest, '$'); |
| - | } |
| - | debug_printf_parse("parse_dollar return 0\n"); |
| - | return 0; |
| - | #undef as_string |
| - | } |
| - | #if BB_MMU |
| - | #define parse_stream_dquoted(as_string, dest, input, dquote_end) \ |
| - | parse_stream_dquoted(dest, input, dquote_end) |
| - | #define as_string NULL |
| - | #endif |
| - | static int parse_stream_dquoted(o_string *as_string, |
| - | o_string *dest, |
| - | struct in_str *input, |
| - | int dquote_end) |
| - | { |
| - | int ch; |
| - | int next; |
| + | /* pipes are inserted between pairs of commands */ |
| + | pipefds.rd = 0; |
| + | pipefds.wr = 1; |
| + | if (cmd_no < pi->num_cmds) |
| + | xpiped_pair(pipefds); |
| + | |
| + | command->pid = BB_MMU ? fork() : vfork(); |
| + | if (!command->pid) { /* child */ |
| + | #if ENABLE_HUSH_JOB |
| + | disable_restore_tty_pgrp_on_exit(); |
| + | CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ |
| + | |
| + | /* Every child adds itself to new process group |
| + | * with pgid == pid_of_first_child_in_pipe */ |
| + | if (G.run_list_level == 1 && G_interactive_fd) { |
| + | pid_t pgrp; |
| + | pgrp = pi->pgrp; |
| + | if (pgrp < 0) /* true for 1st process only */ |
| + | pgrp = getpid(); |
| + | if (setpgid(0, pgrp) == 0 |
| + | && pi->followup != PIPE_BG |
| + | && G_saved_tty_pgrp /* we have ctty */ |
| + | ) { |
| + | /* We do it in *every* child, not just first, |
| + | * to avoid races */ |
| + | tcsetpgrp(G_interactive_fd, pgrp); |
| + | } |
| + | } |
| + | #endif |
| + | if (pi->alive_cmds == 0 && pi->followup == PIPE_BG) { |
| + | /* 1st cmd in backgrounded pipe |
| + | * should have its stdin /dev/null'ed */ |
| + | close(0); |
| + | if (open(bb_dev_null, O_RDONLY)) |
| + | xopen("/", O_RDONLY); |
| + | } else { |
| + | xmove_fd(next_infd, 0); |
| + | } |
| + | xmove_fd(pipefds.wr, 1); |
| + | if (pipefds.rd > 1) |
| + | close(pipefds.rd); |
| + | /* Like bash, explicit redirects override pipes, |
| + | * and the pipe fd is available for dup'ing. */ |
| + | if (setup_redirects(command, NULL)) |
| + | _exit(1); |
| + | |
| + | /* Restore default handlers just prior to exec */ |
| + | /*signal(SIGCHLD, SIG_DFL); - so far we don't have any handlers */ |
| - | again: |
| - | ch = i_getch(input); |
| - | if (ch != EOF) |
| - | nommu_addchr(as_string, ch); |
| - | if (ch == dquote_end) { /* may be only '"' or EOF */ |
| - | if (dest->o_assignment == NOT_ASSIGNMENT) |
| - | dest->o_escape ^= 1; |
| - | debug_printf_parse("parse_stream_dquoted return 0\n"); |
| - | return 0; |
| - | } |
| - | /* note: can't move it above ch == dquote_end check! */ |
| - | if (ch == EOF) { |
| - | syntax_error_unterm_ch('"'); |
| - | /*xfunc_die(); - redundant */ |
| - | } |
| - | next = '\0'; |
| - | if (ch != '\n') { |
| - | next = i_peek(input); |
| - | } |
| - | debug_printf_parse("\" ch=%c (%d) escape=%d\n", |
| - | ch, ch, dest->o_escape); |
| - | if (ch == '\\') { |
| - | if (next == EOF) { |
| - | syntax_error("\\<eof>"); |
| - | xfunc_die(); |
| + | /* Stores to nommu_save list of env vars putenv'ed |
| + | * (NOMMU, on MMU we don't need that) */ |
| + | /* cast away volatility... */ |
| + | pseudo_exec((nommu_save_t*) &nommu_save, command, argv_expanded); |
| + | /* pseudo_exec() does not return */ |
| } | |
| - | /* 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 (strchr("$`\"\\\n", next) != NULL) { |
| - | ch = i_getch(input); |
| - | if (ch != '\n') { |
| - | o_addqchr(dest, ch); |
| - | nommu_addchr(as_string, ch); |
| - | } |
| + | |
| + | /* parent or error */ |
| + | #if ENABLE_HUSH_FAST |
| + | G.count_SIGCHLD++; |
| + | //bb_error_msg("[%d] fork in run_pipe: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); |
| + | #endif |
| + | enable_restore_tty_pgrp_on_exit(); |
| + | #if !BB_MMU |
| + | /* Clean up after vforked child */ |
| + | free(nommu_save.argv); |
| + | free(nommu_save.argv_from_re_execing); |
| + | unset_vars(nommu_save.new_env); |
| + | add_vars(nommu_save.old_vars); |
| + | #endif |
| + | free(argv_expanded); |
| + | argv_expanded = NULL; |
| + | if (command->pid < 0) { /* [v]fork failed */ |
| + | /* Clearly indicate, was it fork or vfork */ |
| + | bb_perror_msg(BB_MMU ? "vfork"+1 : "vfork"); |
| } else { | |
| - | o_addqchr(dest, '\\'); |
| - | nommu_addchr(as_string, '\\'); |
| - | } |
| - | goto again; |
| - | } |
| - | if (ch == '$') { |
| - | if (parse_dollar(as_string, dest, input) != 0) { |
| - | debug_printf_parse("parse_stream_dquoted return 1: " |
| - | "parse_dollar returned non-0\n"); |
| - | return 1; |
| + | pi->alive_cmds++; |
| + | #if ENABLE_HUSH_JOB |
| + | /* Second and next children need to know pid of first one */ |
| + | if (pi->pgrp < 0) |
| + | pi->pgrp = command->pid; |
| + | #endif |
| } | |
| - | goto again; |
| - | } |
| - | #if ENABLE_HUSH_TICK |
| - | if (ch == '`') { |
| - | //unsigned 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); |
| - | goto again; |
| + | |
| + | if (cmd_no > 1) |
| + | close(next_infd); |
| + | if (cmd_no < pi->num_cmds) |
| + | close(pipefds.wr); |
| + | /* Pass read (output) pipe end to next iteration */ |
| + | next_infd = pipefds.rd; |
| } | |
| - | #endif |
| - | o_addQchr(dest, ch); |
| - | if (ch == '=' |
| - | && (dest->o_assignment == MAYBE_ASSIGNMENT |
| - | || dest->o_assignment == WORD_IS_KEYWORD) |
| - | && is_well_formed_var_name(dest->data, '=') |
| - | ) { |
| - | dest->o_assignment = DEFINITELY_ASSIGNMENT; |
| + | |
| + | if (!pi->alive_cmds) { |
| + | debug_leave(); |
| + | debug_printf_exec("run_pipe return 1 (all forks failed, no children)\n"); |
| + | return 1; |
| } | |
| - | goto again; |
| - | #undef as_string |
| + | |
| + | debug_leave(); |
| + | debug_printf_exec("run_pipe return -1 (%u children started)\n", pi->alive_cmds); |
| + | return -1; |
| } | |
| - | /* |
| - | * Scan input until EOF or end_trigger char. |
| - | * Return a list of pipes to execute, or NULL on EOF |
| - | * or if end_trigger character is met. |
| - | * On syntax error, exit is shell is not interactive, |
| - | * reset parsing machinery and start parsing anew, |
| - | * or return ERR_PTR. |
| - | */ |
| - | static struct pipe *parse_stream(char **pstring, |
| - | struct in_str *input, |
| - | int end_trigger) |
| + | #ifndef debug_print_tree |
| + | static void debug_print_tree(struct pipe *pi, int lvl) |
| { | |
| - | struct parse_context ctx; |
| - | o_string dest = NULL_O_STRING; |
| - | int is_in_dquote; |
| - | int heredoc_cnt; |
| - | |
| - | /* Double-quote state is handled in the state variable is_in_dquote. |
| - | * A single-quote triggers a bypass of the main loop until its mate is |
| - | * found. When recursing, quote state is passed in via dest->o_escape. |
| - | */ |
| - | 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; |
| - | |
| - | G.ifs = get_local_var_value("IFS"); |
| - | if (G.ifs == NULL) |
| - | G.ifs = defifs; |
| + | static const char *const PIPE[] = { |
| + | [PIPE_SEQ] = "SEQ", |
| + | [PIPE_AND] = "AND", |
| + | [PIPE_OR ] = "OR" , |
| + | [PIPE_BG ] = "BG" , |
| + | }; |
| + | static const char *RES[] = { |
| + | [RES_NONE ] = "NONE" , |
| + | # if ENABLE_HUSH_IF |
| + | [RES_IF ] = "IF" , |
| + | [RES_THEN ] = "THEN" , |
| + | [RES_ELIF ] = "ELIF" , |
| + | [RES_ELSE ] = "ELSE" , |
| + | [RES_FI ] = "FI" , |
| + | # endif |
| + | # if ENABLE_HUSH_LOOPS |
| + | [RES_FOR ] = "FOR" , |
| + | [RES_WHILE] = "WHILE", |
| + | [RES_UNTIL] = "UNTIL", |
| + | [RES_DO ] = "DO" , |
| + | [RES_DONE ] = "DONE" , |
| + | # endif |
| + | # if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE |
| + | [RES_IN ] = "IN" , |
| + | # endif |
| + | # if ENABLE_HUSH_CASE |
| + | [RES_CASE ] = "CASE" , |
| + | [RES_CASE_IN ] = "CASE_IN" , |
| + | [RES_MATCH] = "MATCH", |
| + | [RES_CASE_BODY] = "CASE_BODY", |
| + | [RES_ESAC ] = "ESAC" , |
| + | # endif |
| + | [RES_XXXX ] = "XXXX" , |
| + | [RES_SNTX ] = "SNTX" , |
| + | }; |
| + | static const char *const CMDTYPE[] = { |
| + | "{}", |
| + | "()", |
| + | "[noglob]", |
| + | # if ENABLE_HUSH_FUNCTIONS |
| + | "func()", |
| + | # endif |
| + | }; |
| - | reset: |
| - | #if ENABLE_HUSH_INTERACTIVE |
| - | input->promptmode = 0; /* PS1 */ |
| - | #endif |
| - | /* dest.o_assignment = MAYBE_ASSIGNMENT; - already is */ |
| - | initialize_context(&ctx); |
| - | is_in_dquote = 0; |
| - | heredoc_cnt = 0; |
| - | while (1) { |
| - | const char *is_ifs; |
| - | const char *is_special; |
| - | int ch; |
| - | int next; |
| - | int redir_fd; |
| - | redir_type redir_style; |
| + | int pin, prn; |
| - | if (is_in_dquote) { |
| - | /* dest.has_quoted_part = 1; - already is (see below) */ |
| - | if (parse_stream_dquoted(&ctx.as_string, &dest, input, '"')) { |
| - | goto parse_error; |
| - | } |
| - | /* We reached closing '"' */ |
| - | is_in_dquote = 0; |
| - | } |
| - | ch = i_getch(input); |
| - | debug_printf_parse(": ch=%c (%d) escape=%d\n", |
| - | ch, ch, dest.o_escape); |
| - | if (ch == EOF) { |
| - | struct pipe *pi; |
| + | pin = 0; |
| + | while (pi) { |
| + | fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "", |
| + | pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]); |
| + | prn = 0; |
| + | while (prn < pi->num_cmds) { |
| + | struct command *command = &pi->cmds[prn]; |
| + | char **argv = command->argv; |
| - | if (heredoc_cnt) { |
| - | syntax_error_unterm_str("here document"); |
| - | goto parse_error; |
| + | fprintf(stderr, "%*s cmd %d assignment_cnt:%d", |
| + | lvl*2, "", prn, |
| + | command->assignment_cnt); |
| + | if (command->group) { |
| + | fprintf(stderr, " group %s: (argv=%p)%s%s\n", |
| + | CMDTYPE[command->cmd_type], |
| + | argv |
| + | # if !BB_MMU |
| + | , " group_as_string:", command->group_as_string |
| + | # else |
| + | , "", "" |
| + | # endif |
| + | ); |
| + | debug_print_tree(command->group, lvl+1); |
| + | prn++; |
| + | continue; |
| } | |
| - | /* end_trigger == '}' case errors out earlier, |
| - | * checking only ')' */ |
| - | if (end_trigger == ')') { |
| - | syntax_error_unterm_ch('('); /* exits */ |
| - | /* goto parse_error; */ |
| + | if (argv) while (*argv) { |
| + | fprintf(stderr, " '%s'", *argv); |
| + | argv++; |
| } | |
| + | fprintf(stderr, "\n"); |
| + | prn++; |
| + | } |
| + | pi = pi->next; |
| + | pin++; |
| + | } |
| + | } |
| + | #endif /* debug_print_tree */ |
| - | if (done_word(&dest, &ctx)) { |
| - | goto parse_error; |
| - | } |
| - | o_free(&dest); |
| - | done_pipe(&ctx, PIPE_SEQ); |
| - | pi = ctx.list_head; |
| - | /* If we got nothing... */ |
| - | /* (this makes bare "&" cmd a no-op. |
| - | * bash says: "syntax error near unexpected token '&'") */ |
| - | if (pi->num_cmds == 0 |
| - | IF_HAS_KEYWORDS( && pi->res_word == RES_NONE) |
| - | ) { |
| - | free_pipe_list(pi); |
| - | pi = NULL; |
| - | } |
| - | #if !BB_MMU |
| - | debug_printf_parse("as_string '%s'\n", ctx.as_string.data); |
| - | if (pstring) |
| - | *pstring = ctx.as_string.data; |
| - | else |
| - | o_free_unsafe(&ctx.as_string); |
| + | /* NB: called by pseudo_exec, and therefore must not modify any |
| + | * global data until exec/_exit (we can be a child after vfork!) */ |
| + | static int run_list(struct pipe *pi) |
| + | { |
| + | #if ENABLE_HUSH_CASE |
| + | char *case_word = NULL; |
| + | #endif |
| + | #if ENABLE_HUSH_LOOPS |
| + | struct pipe *loop_top = NULL; |
| + | char **for_lcur = NULL; |
| + | char **for_list = NULL; |
| + | #endif |
| + | smallint last_followup; |
| + | smalluint rcode; |
| + | #if ENABLE_HUSH_IF || ENABLE_HUSH_CASE |
| + | smalluint cond_code = 0; |
| + | #else |
| + | enum { cond_code = 0 }; |
| + | #endif |
| + | #if HAS_KEYWORDS |
| + | smallint rword; /* enum reserved_style */ |
| + | smallint last_rword; /* ditto */ |
| #endif | |
| - | debug_leave(); |
| - | debug_printf_parse("parse_stream return %p\n", pi); |
| - | return pi; |
| - | } |
| - | nommu_addchr(&ctx.as_string, ch); |
| - | next = '\0'; |
| - | if (ch != '\n') |
| - | next = i_peek(input); |
| + | debug_printf_exec("run_list start lvl %d\n", G.run_list_level); |
| + | debug_enter(); |
| - | is_special = "{}<>;&|()#'" /* special outside of "str" */ |
| - | "\\$\"" IF_HUSH_TICK("`"); /* always special */ |
| - | /* Are { and } special here? */ |
| - | if (ctx.command->argv /* word [word]{... - non-special */ |
| - | || dest.length /* word{... - non-special */ |
| - | || dest.has_quoted_part /* ""{... - non-special */ |
| - | || (next != ';' /* }; - special */ |
| - | && next != ')' /* }) - special */ |
| - | && next != '&' /* }& and }&& ... - special */ |
| - | && next != '|' /* }|| ... - special */ |
| - | && !strchr(G.ifs, next) /* {word - non-special */ |
| - | ) |
| - | ) { |
| - | /* They are not special, skip "{}" */ |
| - | is_special += 2; |
| + | #if ENABLE_HUSH_LOOPS |
| + | /* Check syntax for "for" */ |
| + | for (struct pipe *cpipe = pi; cpipe; cpipe = cpipe->next) { |
| + | if (cpipe->res_word != RES_FOR && cpipe->res_word != RES_IN) |
| + | continue; |
| + | /* current word is FOR or IN (BOLD in comments below) */ |
| + | if (cpipe->next == NULL) { |
| + | syntax_error("malformed for"); |
| + | debug_leave(); |
| + | debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level); |
| + | return 1; |
| } | |
| - | is_special = strchr(is_special, ch); |
| - | is_ifs = strchr(G.ifs, ch); |
| - | |
| - | if (!is_special && !is_ifs) { /* ordinary char */ |
| - | ordinary_char: |
| - | o_addQchr(&dest, ch); |
| - | if ((dest.o_assignment == MAYBE_ASSIGNMENT |
| - | || dest.o_assignment == WORD_IS_KEYWORD) |
| - | && ch == '=' |
| - | && is_well_formed_var_name(dest.data, '=') |
| - | ) { |
| - | dest.o_assignment = DEFINITELY_ASSIGNMENT; |
| - | } |
| + | /* "FOR v; do ..." and "for v IN a b; do..." are ok */ |
| + | if (cpipe->next->res_word == RES_DO) |
| continue; | |
| + | /* next word is not "do". It must be "in" then ("FOR v in ...") */ |
| + | if (cpipe->res_word == RES_IN /* "for v IN a b; not_do..."? */ |
| + | || cpipe->next->res_word != RES_IN /* FOR v not_do_and_not_in..."? */ |
| + | ) { |
| + | syntax_error("malformed for"); |
| + | debug_leave(); |
| + | debug_printf_exec("run_list lvl %d return 1\n", G.run_list_level); |
| + | return 1; |
| } | |
| - | |
| - | if (is_ifs) { |
| - | if (done_word(&dest, &ctx)) { |
| - | goto parse_error; |
| - | } |
| - | if (ch == '\n') { |
| - | #if ENABLE_HUSH_CASE |
| - | /* "case ... in <newline> word) ..." - |
| - | * newlines are ignored (but ';' wouldn't be) */ |
| - | if (ctx.command->argv == NULL |
| - | && ctx.ctx_res_w == RES_MATCH |
| - | ) { |
| - | continue; |
| - | } |
| + | } |
| #endif | |
| - | /* Treat newline as a command separator. */ |
| - | done_pipe(&ctx, PIPE_SEQ); |
| - | debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt); |
| - | if (heredoc_cnt) { |
| - | if (fetch_heredocs(heredoc_cnt, &ctx, input)) { |
| - | goto parse_error; |
| - | } |
| - | heredoc_cnt = 0; |
| - | } |
| - | dest.o_assignment = MAYBE_ASSIGNMENT; |
| - | ch = ';'; |
| - | /* note: if (is_ifs) continue; |
| - | * will still trigger for us */ |
| - | } |
| - | } |
| - | /* "cmd}" or "cmd }..." without semicolon or &: |
| - | * } is an ordinary char in this case, even inside { cmd; } |
| - | * Pathological example: { ""}; } should exec "}" cmd |
| - | */ |
| - | if (ch == '}') { |
| - | if (!IS_NULL_CMD(ctx.command) /* cmd } */ |
| - | || dest.length != 0 /* word} */ |
| - | || dest.has_quoted_part /* ""} */ |
| - | ) { |
| - | goto ordinary_char; |
| - | } |
| - | if (!IS_NULL_PIPE(ctx.pipe)) /* cmd | } */ |
| - | goto skip_end_trigger; |
| - | /* else: } does terminate a group */ |
| - | } |
| + | /* Past this point, all code paths should jump to ret: label |
| + | * in order to return, no direct "return" statements please. |
| + | * This helps to ensure that no memory is leaked. */ |
| - | if (end_trigger && end_trigger == ch |
| - | && (ch != ';' || heredoc_cnt == 0) |
| - | #if ENABLE_HUSH_CASE |
| - | && (ch != ')' |
| - | || ctx.ctx_res_w != RES_MATCH |
| - | || (!dest.has_quoted_part && strcmp(dest.data, "esac") == 0) |
| - | ) |
| + | #if ENABLE_HUSH_JOB |
| + | G.run_list_level++; |
| #endif | |
| - | ) { |
| - | if (heredoc_cnt) { |
| - | /* This is technically valid: |
| - | * { cat <<HERE; }; echo Ok |
| - | * heredoc |
| - | * heredoc |
| - | * HERE |
| - | * but we don't support this. |
| - | * We require heredoc to be in enclosing {}/(), |
| - | * if any. |
| - | */ |
| - | syntax_error_unterm_str("here document"); |
| - | goto parse_error; |
| - | } |
| - | if (done_word(&dest, &ctx)) { |
| - | goto parse_error; |
| - | } |
| - | done_pipe(&ctx, PIPE_SEQ); |
| - | dest.o_assignment = MAYBE_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); |
| - | #if !BB_MMU |
| - | debug_printf_parse("as_string '%s'\n", ctx.as_string.data); |
| - | if (pstring) |
| - | *pstring = ctx.as_string.data; |
| - | else |
| - | o_free_unsafe(&ctx.as_string); |
| + | |
| + | #if HAS_KEYWORDS |
| + | rword = RES_NONE; |
| + | last_rword = RES_XXXX; |
| #endif | |
| - | debug_leave(); |
| - | debug_printf_parse("parse_stream return %p: " |
| - | "end_trigger char found\n", |
| - | ctx.list_head); |
| - | return ctx.list_head; |
| - | } |
| - | } |
| - | skip_end_trigger: |
| - | if (is_ifs) |
| - | continue; |
| + | last_followup = PIPE_SEQ; |
| + | rcode = G.last_exitcode; |
| - | /* Catch <, > before deciding whether this word is |
| - | * 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)) { |
| - | goto parse_error; |
| - | } |
| - | redir_style = REDIRECT_OVERWRITE; |
| - | if (next == '>') { |
| - | redir_style = REDIRECT_APPEND; |
| - | ch = i_getch(input); |
| - | nommu_addchr(&ctx.as_string, ch); |
| - | } |
| - | #if 0 |
| - | else if (next == '(') { |
| - | syntax_error(">(process) not supported"); |
| - | goto parse_error; |
| - | } |
| + | /* Go through list of pipes, (maybe) executing them. */ |
| + | for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) { |
| + | if (G.flag_SIGINT) |
| + | break; |
| + | |
| + | IF_HAS_KEYWORDS(rword = pi->res_word;) |
| + | debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n", |
| + | rword, cond_code, last_rword); |
| + | #if ENABLE_HUSH_LOOPS |
| + | if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) |
| + | && loop_top == NULL /* avoid bumping G.depth_of_loop twice */ |
| + | ) { |
| + | /* start of a loop: remember where loop starts */ |
| + | loop_top = pi; |
| + | G.depth_of_loop++; |
| + | } |
| #endif | |
| - | if (parse_redirect(&ctx, redir_fd, redir_style, input)) |
| - | goto parse_error; |
| - | continue; /* back to top of while (1) */ |
| - | case '<': |
| - | redir_fd = redirect_opt_num(&dest); |
| - | if (done_word(&dest, &ctx)) { |
| - | goto parse_error; |
| + | /* Still in the same "if...", "then..." or "do..." branch? */ |
| + | if (IF_HAS_KEYWORDS(rword == last_rword &&) 1) { |
| + | if ((rcode == 0 && last_followup == PIPE_OR) |
| + | || (rcode != 0 && last_followup == PIPE_AND) |
| + | ) { |
| + | /* It is "<true> || CMD" or "<false> && CMD" |
| + | * and we should not execute CMD */ |
| + | debug_printf_exec("skipped cmd because of || or &&\n"); |
| + | last_followup = pi->followup; |
| + | continue; |
| } | |
| - | redir_style = REDIRECT_INPUT; |
| - | if (next == '<') { |
| - | redir_style = REDIRECT_HEREDOC; |
| - | heredoc_cnt++; |
| - | debug_printf_parse("++heredoc_cnt=%d\n", heredoc_cnt); |
| - | ch = i_getch(input); |
| - | nommu_addchr(&ctx.as_string, ch); |
| - | } else if (next == '>') { |
| - | redir_style = REDIRECT_IO; |
| - | ch = i_getch(input); |
| - | nommu_addchr(&ctx.as_string, ch); |
| + | } |
| + | last_followup = pi->followup; |
| + | IF_HAS_KEYWORDS(last_rword = rword;) |
| + | #if ENABLE_HUSH_IF |
| + | if (cond_code) { |
| + | if (rword == RES_THEN) { |
| + | /* if false; then ... fi has exitcode 0! */ |
| + | G.last_exitcode = rcode = EXIT_SUCCESS; |
| + | /* "if <false> THEN cmd": skip cmd */ |
| + | continue; |
| } | |
| - | #if 0 |
| - | else if (next == '(') { |
| - | syntax_error("<(process) not supported"); |
| - | goto parse_error; |
| + | } else { |
| + | if (rword == RES_ELSE || rword == RES_ELIF) { |
| + | /* "if <true> then ... ELSE/ELIF cmd": |
| + | * skip cmd and all following ones */ |
| + | break; |
| } | |
| - | #endif |
| - | if (parse_redirect(&ctx, redir_fd, redir_style, input)) |
| - | goto parse_error; |
| - | continue; /* back to top of while (1) */ |
| - | } |
| - | |
| - | if (dest.o_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; |
| } | |
| + | #endif |
| + | #if ENABLE_HUSH_LOOPS |
| + | if (rword == RES_FOR) { /* && pi->num_cmds - always == 1 */ |
| + | if (!for_lcur) { |
| + | /* first loop through for */ |
| - | /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ |
| + | static const char encoded_dollar_at[] ALIGN1 = { |
| + | SPECIAL_VAR_SYMBOL, '@' | 0x80, SPECIAL_VAR_SYMBOL, '\0' |
| + | }; /* encoded representation of "$@" */ |
| + | static const char *const encoded_dollar_at_argv[] = { |
| + | encoded_dollar_at, NULL |
| + | }; /* argv list with one element: "$@" */ |
| + | char **vals; |
| - | switch (ch) { |
| - | case '#': |
| - | if (dest.length == 0) { |
| - | while (1) { |
| - | ch = i_peek(input); |
| - | if (ch == EOF || ch == '\n') |
| + | vals = (char**)encoded_dollar_at_argv; |
| + | if (pi->next->res_word == RES_IN) { |
| + | /* if no variable values after "in" we skip "for" */ |
| + | if (!pi->next->cmds[0].argv) { |
| + | G.last_exitcode = rcode = EXIT_SUCCESS; |
| + | debug_printf_exec(": null FOR: exitcode EXIT_SUCCESS\n"); |
| break; | |
| - | i_getch(input); |
| - | /* note: we do not add it to &ctx.as_string */ |
| - | } |
| - | nommu_addchr(&ctx.as_string, '\n'); |
| - | } else { |
| - | o_addQchr(&dest, ch); |
| - | } |
| - | break; |
| - | case '\\': |
| - | if (next == EOF) { |
| - | syntax_error("\\<eof>"); |
| - | xfunc_die(); |
| - | } |
| - | ch = i_getch(input); |
| - | if (ch != '\n') { |
| - | o_addchr(&dest, '\\'); |
| - | /*nommu_addchr(&ctx.as_string, '\\'); - already done */ |
| - | o_addchr(&dest, 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; |
| + | } |
| + | vals = pi->next->cmds[0].argv; |
| + | } /* else: "for var; do..." -> assume "$@" list */ |
| + | /* create list of variable values */ |
| + | debug_print_strings("for_list made from", vals); |
| + | for_list = expand_strvec_to_strvec(vals); |
| + | for_lcur = for_list; |
| + | debug_print_strings("for_list", for_list); |
| } | |
| - | #if !BB_MMU |
| - | else { |
| - | /* It's "\<newline>". Remove trailing '\' from ctx.as_string */ |
| - | ctx.as_string.data[--ctx.as_string.length] = '\0'; |
| + | if (!*for_lcur) { |
| + | /* "for" loop is over, clean up */ |
| + | free(for_list); |
| + | for_list = NULL; |
| + | for_lcur = NULL; |
| + | break; |
| } | |
| + | /* Insert next value from for_lcur */ |
| + | /* note: *for_lcur already has quotes removed, $var expanded, etc */ |
| + | set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); |
| + | continue; |
| + | } |
| + | if (rword == RES_IN) { |
| + | continue; /* "for v IN list;..." - "in" has no cmds anyway */ |
| + | } |
| + | if (rword == RES_DONE) { |
| + | continue; /* "done" has no cmds too */ |
| + | } |
| #endif | |
| - | break; |
| - | case '$': |
| - | if (parse_dollar(&ctx.as_string, &dest, input) != 0) { |
| - | debug_printf_parse("parse_stream parse error: " |
| - | "parse_dollar returned non-0\n"); |
| - | goto parse_error; |
| - | } |
| - | break; |
| - | case '\'': |
| - | dest.has_quoted_part = 1; |
| - | while (1) { |
| - | ch = i_getch(input); |
| - | if (ch == EOF) { |
| - | syntax_error_unterm_ch('\''); |
| - | /*xfunc_die(); - redundant */ |
| - | } |
| - | nommu_addchr(&ctx.as_string, ch); |
| - | if (ch == '\'') |
| + | #if ENABLE_HUSH_CASE |
| + | if (rword == RES_CASE) { |
| + | case_word = expand_strvec_to_string(pi->cmds->argv); |
| + | continue; |
| + | } |
| + | if (rword == RES_MATCH) { |
| + | char **argv; |
| + | |
| + | if (!case_word) /* "case ... matched_word) ... WORD)": we executed selected branch, stop */ |
| + | break; |
| + | /* all prev words didn't match, does this one match? */ |
| + | argv = pi->cmds->argv; |
| + | while (*argv) { |
| + | char *pattern = expand_string_to_string(*argv); |
| + | /* TODO: which FNM_xxx flags to use? */ |
| + | cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); |
| + | free(pattern); |
| + | if (cond_code == 0) { /* match! we will execute this branch */ |
| + | free(case_word); /* make future "word)" stop */ |
| + | case_word = NULL; |
| break; | |
| - | o_addqchr(&dest, ch); |
| + | } |
| + | argv++; |
| } | |
| - | break; |
| - | case '"': |
| - | dest.has_quoted_part = 1; |
| - | is_in_dquote ^= 1; /* invert */ |
| - | if (dest.o_assignment == NOT_ASSIGNMENT) |
| - | dest.o_escape ^= 1; |
| - | break; |
| - | #if ENABLE_HUSH_TICK |
| - | case '`': { |
| - | unsigned pos; |
| - | |
| - | o_addchr(&dest, SPECIAL_VAR_SYMBOL); |
| - | o_addchr(&dest, '`'); |
| - | pos = dest.length; |
| - | add_till_backquote(&dest, input); |
| - | # if !BB_MMU |
| - | o_addstr(&ctx.as_string, dest.data + pos); |
| - | o_addchr(&ctx.as_string, '`'); |
| - | # endif |
| - | o_addchr(&dest, SPECIAL_VAR_SYMBOL); |
| - | //debug_printf_subst("SUBST RES3 '%s'\n", dest.data + pos); |
| - | break; |
| + | continue; |
| + | } |
| + | if (rword == RES_CASE_BODY) { /* inside of a case branch */ |
| + | if (cond_code != 0) |
| + | continue; /* not matched yet, skip this pipe */ |
| } | |
| #endif | |
| - | case ';': |
| - | #if ENABLE_HUSH_CASE |
| - | case_semi: |
| + | /* Just pressing <enter> in shell should check for jobs. |
| + | * OTOH, in non-interactive shell this is useless |
| + | * and only leads to extra job checks */ |
| + | if (pi->num_cmds == 0) { |
| + | if (G_interactive_fd) |
| + | goto check_jobs_and_continue; |
| + | continue; |
| + | } |
| + | |
| + | /* After analyzing all keywords and conditions, we decided |
| + | * to execute this pipe. NB: have to do checkjobs(NULL) |
| + | * after run_pipe to collect any background children, |
| + | * even if list execution is to be stopped. */ |
| + | debug_printf_exec(": run_pipe with %d members\n", pi->num_cmds); |
| + | { |
| + | int r; |
| + | #if ENABLE_HUSH_LOOPS |
| + | G.flag_break_continue = 0; |
| + | #endif |
| + | rcode = r = run_pipe(pi); /* NB: rcode is a smallint */ |
| + | if (r != -1) { |
| + | /* We ran a builtin, function, or group. |
| + | * rcode is already known |
| + | * and we don't need to wait for anything. */ |
| + | G.last_exitcode = rcode; |
| + | debug_printf_exec(": builtin/func exitcode %d\n", rcode); |
| + | check_and_run_traps(0); |
| + | #if ENABLE_HUSH_LOOPS |
| + | /* Was it "break" or "continue"? */ |
| + | if (G.flag_break_continue) { |
| + | smallint fbc = G.flag_break_continue; |
| + | /* We might fall into outer *loop*, |
| + | * don't want to break it too */ |
| + | if (loop_top) { |
| + | G.depth_break_continue--; |
| + | if (G.depth_break_continue == 0) |
| + | G.flag_break_continue = 0; |
| + | /* else: e.g. "continue 2" should *break* once, *then* continue */ |
| + | } /* else: "while... do... { we are here (innermost list is not a loop!) };...done" */ |
| + | if (G.depth_break_continue != 0 || fbc == BC_BREAK) |
| + | goto check_jobs_and_break; |
| + | /* "continue": simulate end of loop */ |
| + | rword = RES_DONE; |
| + | continue; |
| + | } |
| #endif | |
| - | if (done_word(&dest, &ctx)) { |
| - | goto parse_error; |
| - | } |
| - | done_pipe(&ctx, PIPE_SEQ); |
| - | #if ENABLE_HUSH_CASE |
| - | /* Eat multiple semicolons, detect |
| - | * whether it means something special */ |
| - | while (1) { |
| - | ch = i_peek(input); |
| - | if (ch != ';') |
| - | break; |
| - | ch = i_getch(input); |
| - | nommu_addchr(&ctx.as_string, ch); |
| - | if (ctx.ctx_res_w == RES_CASE_BODY) { |
| - | ctx.ctx_dsemicolon = 1; |
| - | ctx.ctx_res_w = RES_MATCH; |
| + | #if ENABLE_HUSH_FUNCTIONS |
| + | if (G.flag_return_in_progress == 1) { |
| + | /* same as "goto check_jobs_and_break" */ |
| + | checkjobs(NULL); |
| break; | |
| } | |
| - | } |
| #endif | |
| - | new_cmd: |
| - | /* We just finished a cmd. New one may start |
| - | * with an assignment */ |
| - | dest.o_assignment = MAYBE_ASSIGNMENT; |
| - | break; |
| - | case '&': |
| - | if (done_word(&dest, &ctx)) { |
| - | goto parse_error; |
| - | } |
| - | if (next == '&') { |
| - | ch = i_getch(input); |
| - | nommu_addchr(&ctx.as_string, ch); |
| - | done_pipe(&ctx, PIPE_AND); |
| - | } else { |
| - | done_pipe(&ctx, PIPE_BG); |
| - | } |
| - | goto new_cmd; |
| - | case '|': |
| - | if (done_word(&dest, &ctx)) { |
| - | goto parse_error; |
| - | } |
| - | #if ENABLE_HUSH_CASE |
| - | if (ctx.ctx_res_w == RES_MATCH) |
| - | break; /* we are in case's "word | word)" */ |
| + | } else if (pi->followup == PIPE_BG) { |
| + | /* What does bash do with attempts to background builtins? */ |
| + | /* even bash 3.2 doesn't do that well with nested bg: |
| + | * try "{ { sleep 10; echo DEEP; } & echo HERE; } &". |
| + | * I'm NOT treating inner &'s as jobs */ |
| + | check_and_run_traps(0); |
| + | #if ENABLE_HUSH_JOB |
| + | if (G.run_list_level == 1) |
| + | insert_bg_job(pi); |
| #endif | |
| - | if (next == '|') { /* || */ |
| - | ch = i_getch(input); |
| - | nommu_addchr(&ctx.as_string, ch); |
| - | done_pipe(&ctx, PIPE_OR); |
| + | /* Last command's pid goes to $! */ |
| + | G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; |
| + | G.last_exitcode = rcode = EXIT_SUCCESS; |
| + | debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); |
| } else { | |
| - | /* we could pick up a file descriptor choice here |
| - | * with redirect_opt_num(), but bash doesn't do it. |
| - | * "echo foo 2| cat" yields "foo 2". */ |
| - | done_command(&ctx); |
| - | #if !BB_MMU |
| - | o_reset_to_empty_unquoted(&ctx.as_string); |
| + | #if ENABLE_HUSH_JOB |
| + | if (G.run_list_level == 1 && G_interactive_fd) { |
| + | /* Waits for completion, then fg's main shell */ |
| + | rcode = checkjobs_and_fg_shell(pi); |
| + | debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode); |
| + | check_and_run_traps(0); |
| + | } else |
| #endif | |
| + | { /* This one just waits for completion */ |
| + | rcode = checkjobs(pi); |
| + | debug_printf_exec(": checkjobs exitcode %d\n", rcode); |
| + | check_and_run_traps(0); |
| + | } |
| + | G.last_exitcode = rcode; |
| } | |
| - | goto new_cmd; |
| - | case '(': |
| - | #if ENABLE_HUSH_CASE |
| - | /* "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 ""(... */ |
| - | ) { |
| - | continue; |
| - | } |
| + | } |
| + | |
| + | /* Analyze how result affects subsequent commands */ |
| + | #if ENABLE_HUSH_IF |
| + | if (rword == RES_IF || rword == RES_ELIF) |
| + | cond_code = rcode; |
| #endif | |
| - | case '{': |
| - | if (parse_group(&dest, &ctx, input, ch) != 0) { |
| - | goto parse_error; |
| + | #if ENABLE_HUSH_LOOPS |
| + | /* Beware of "while false; true; do ..."! */ |
| + | if (pi->next && pi->next->res_word == RES_DO) { |
| + | if (rword == RES_WHILE) { |
| + | if (rcode) { |
| + | /* "while false; do...done" - exitcode 0 */ |
| + | G.last_exitcode = rcode = EXIT_SUCCESS; |
| + | debug_printf_exec(": while expr is false: breaking (exitcode:EXIT_SUCCESS)\n"); |
| + | goto check_jobs_and_break; |
| + | } |
| + | } |
| + | if (rword == RES_UNTIL) { |
| + | if (!rcode) { |
| + | debug_printf_exec(": until expr is true: breaking\n"); |
| + | check_jobs_and_break: |
| + | checkjobs(NULL); |
| + | break; |
| + | } |
| } | |
| - | goto new_cmd; |
| - | case ')': |
| - | #if ENABLE_HUSH_CASE |
| - | if (ctx.ctx_res_w == RES_MATCH) |
| - | goto case_semi; |
| - | #endif |
| - | case '}': |
| - | /* proper use of this character is caught by end_trigger: |
| - | * if we see {, we call parse_group(..., end_trigger='}') |
| - | * and it will match } earlier (not here). */ |
| - | syntax_error_unexpected_ch(ch); |
| - | goto parse_error; |
| - | default: |
| - | if (HUSH_DEBUG) |
| - | bb_error_msg_and_die("BUG: unexpected %c\n", ch); |
| } | |
| - | } /* while (1) */ |
| + | #endif |
| - | parse_error: |
| - | { |
| - | struct parse_context *pctx; |
| - | IF_HAS_KEYWORDS(struct parse_context *p2;) |
| + | check_jobs_and_continue: |
| + | checkjobs(NULL); |
| + | } /* for (pi) */ |
| - | /* Clean up allocated tree. |
| - | * Sample for finding leaks on syntax error recovery path. |
| - | * Run it from interactive shell, watch pmap `pidof hush`. |
| - | * while if false; then false; fi; do break; fi |
| - | * Samples to catch leaks at execution: |
| - | * while if (true | {true;}); then echo ok; fi; do break; done |
| - | * while if (true | {true;}); then echo ok; fi; do (if echo ok; break; then :; fi) | cat; break; done |
| - | */ |
| - | pctx = &ctx; |
| - | do { |
| - | /* Update pipe/command counts, |
| - | * otherwise freeing may miss some */ |
| - | done_pipe(pctx, PIPE_SEQ); |
| - | debug_printf_clean("freeing list %p from ctx %p\n", |
| - | pctx->list_head, pctx); |
| - | debug_print_tree(pctx->list_head, 0); |
| - | free_pipe_list(pctx->list_head); |
| - | debug_printf_clean("freed list %p\n", pctx->list_head); |
| - | #if !BB_MMU |
| - | o_free_unsafe(&pctx->as_string); |
| + | #if ENABLE_HUSH_JOB |
| + | G.run_list_level--; |
| #endif | |
| - | IF_HAS_KEYWORDS(p2 = pctx->stack;) |
| - | if (pctx != &ctx) { |
| - | free(pctx); |
| - | } |
| - | IF_HAS_KEYWORDS(pctx = p2;) |
| - | } while (HAS_KEYWORDS && pctx); |
| - | /* Free text, clear all dest fields */ |
| - | o_free(&dest); |
| - | /* If we are not in top-level parse, we return, |
| - | * our caller will propagate error. |
| - | */ |
| - | if (end_trigger != ';') { |
| - | #if !BB_MMU |
| - | if (pstring) |
| - | *pstring = NULL; |
| + | #if ENABLE_HUSH_LOOPS |
| + | if (loop_top) |
| + | G.depth_of_loop--; |
| + | free(for_list); |
| #endif | |
| - | debug_leave(); |
| - | return ERR_PTR; |
| - | } |
| - | /* Discard cached input, force prompt */ |
| - | input->p = NULL; |
| - | IF_HUSH_INTERACTIVE(input->promptme = 1;) |
| - | goto reset; |
| - | } |
| + | #if ENABLE_HUSH_CASE |
| + | free(case_word); |
| + | #endif |
| + | debug_leave(); |
| + | debug_printf_exec("run_list lvl %d return %d\n", G.run_list_level + 1, rcode); |
| + | return rcode; |
| } | |
| - | /* Executing from string: eval, sh -c '...' |
| - | * or from file: /etc/profile, . file, sh <script>, sh (intereactive) |
| - | * end_trigger controls how often we stop parsing |
| - | * NUL: parse all, execute, return |
| - | * ';': parse till ';' or newline, execute, repeat till EOF |
| - | */ |
| - | static void parse_and_run_stream(struct in_str *inp, int end_trigger) |
| + | /* Select which version we will use */ |
| + | static int run_and_free_list(struct pipe *pi) |
| { | |
| - | /* Why we need empty flag? |
| - | * An obscure corner case "false; ``; echo $?": |
| - | * empty command in `` should still set $? to 0. |
| - | * But we can't just set $? to 0 at the start, |
| - | * this breaks "false; echo `echo $?`" case. |
| - | */ |
| - | bool empty = 1; |
| - | while (1) { |
| - | struct pipe *pipe_list; |
| - | |
| - | pipe_list = parse_stream(NULL, inp, end_trigger); |
| - | if (!pipe_list) { /* EOF */ |
| - | if (empty) |
| - | G.last_exitcode = 0; |
| - | break; |
| - | } |
| - | debug_print_tree(pipe_list, 0); |
| - | debug_printf_exec("parse_and_run_stream: run_and_free_list\n"); |
| - | run_and_free_list(pipe_list); |
| - | empty = 0; |
| + | int rcode = 0; |
| + | debug_printf_exec("run_and_free_list entered\n"); |
| + | if (!G.n_mode) { |
| + | debug_printf_exec(": run_list: 1st pipe with %d cmds\n", pi->num_cmds); |
| + | rcode = run_list(pi); |
| } | |
| + | /* free_pipe_list has the side effect of clearing memory. |
| + | * In the long run that function can be merged with run_list, |
| + | * but doing that now would hobble the debugging effort. */ |
| + | free_pipe_list(pi); |
| + | debug_printf_exec("run_and_free_list return %d\n", rcode); |
| + | return rcode; |
| } | |
| - | static void parse_and_run_string(const char *s) |
| - | { |
| - | struct in_str input; |
| - | setup_string_in_str(&input, s); |
| - | parse_and_run_stream(&input, '\0'); |
| - | } |
| - | |
| - | static void parse_and_run_file(FILE *f) |
| - | { |
| - | struct in_str input; |
| - | setup_file_in_str(&input, f); |
| - | parse_and_run_stream(&input, ';'); |
| - | } |
| /* Called a few times only (or even once if "sh -c") */ | |
| static void init_sigmasks(void) | |