diff options
Diffstat (limited to 'shell/hush.c')
-rw-r--r-- | shell/hush.c | 4084 |
1 files changed, 2042 insertions, 2042 deletions
diff --git a/shell/hush.c b/shell/hush.c index 1bed72159..2afe12fea 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2515,20 +2515,1864 @@ 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 + 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); + } + command->redirects = NULL; + } + 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; + + for (pi = head; pi; pi = next) { +#if HAS_KEYWORDS + debug_printf_clean(" pipe reserved word %d\n", pi->res_word); +#endif + free_pipe(pi); + debug_printf_clean("pipe followup code %d\n", pi->followup); + next = pi->next; + /*pi->next = NULL;*/ + free(pi); + } +} + + +/*** Parsing routines ***/ + +static struct pipe *new_pipe(void) +{ + 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; +} + +/* 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; + + 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); + } + + /* 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 */ +} + +static void done_pipe(struct parse_context *ctx, pipe_style type) +{ + int not_null; + + 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 + + /* 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); + } + 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]; + */ + done_command(ctx); +} + +/* 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; + + 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 + */ +static int reserved_word(o_string *word, struct parse_context *ctx) +{ +# 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; + + 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; + } + 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); + } + + 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); + } + return 1; +} +#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) +{ + 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; + } + + 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; + } + +#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); + } + +#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); + } +#endif + + o_reset_to_empty_unquoted(word); + + debug_printf_parse("done_word return 0\n"); + return 0; +} + + +/* 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; + + 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; +} + +/* 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); + } + } + + 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 +#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); + } +} + +/* 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) +{ + struct pipe *pi = ctx->list_head; + + while (pi && heredoc_cnt) { + int i; + struct command *cmd = pi->cmds; + + 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; + + 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 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); + + +#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) +{ + /* 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; + + 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; + } + 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; + } +#endif + +#if ENABLE_HUSH_FUNCTIONS + 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); + } + + { +#if BB_MMU +# define as_string NULL +#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); +#endif +#undef as_string + } + debug_printf_parse("parse_group return 0\n"); + return 0; + /* command remains "open", available for possible redirects */ +} + +#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 (1) { + int ch = i_getch(input); + if (ch == EOF) { + syntax_error_unterm_ch('\''); + /*xfunc_die(); - redundant */ + } + if (ch == '\'') + return; + o_addchr(dest, ch); + } +} +/* "...\"...`..`...." - do we need to handle "...$(..)..." too? */ +static void add_till_double_quote(o_string *dest, struct in_str *input) +{ + 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); + } + o_addchr(dest, ch); + if (ch == '`') { + add_till_backquote(dest, input); + o_addchr(dest, ch); + continue; + } + //if (ch == '$') ... + } +} +/* 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 */ + } + 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); + } +} +/* 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); + + 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; + } + } + 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; + } + } + return ch; +} +#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 parse_dollar(as_string, dest, input) \ + parse_dollar(dest, input) +#define as_string NULL +#endif +static int parse_dollar(o_string *as_string, + o_string *dest, + struct in_str *input) +{ + int ch = i_peek(input); /* first character after the $ */ + unsigned char quote_mask = dest->o_escape ? 0x80 : 0; + + 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); + + ch = i_getch(input); /* eat '{' */ + nommu_addchr(as_string, ch); + + 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; + + /* 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... */ + + while (1) { + unsigned pos; + + o_addchr(dest, ch); + debug_printf_parse(": '%c'\n", ch); + + ch = i_getch(input); + nommu_addchr(as_string, ch); + if (ch == '}') + break; + + 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; + + /* 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); + } + + 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; + } +#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, ')'); + } + 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 + 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, '$'); + } + 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 dquote_end) +{ + int ch; + int next; + + 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(); + } + /* 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 +} + +/* + * 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 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; + + 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; + + 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; + + 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; */ + } + + 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); + + next = '\0'; + if (ch != '\n') + next = i_peek(input); + + 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; + } + continue; + } + + 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 */ + } + + 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 + 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; + + /* 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; + } +#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; + } + 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 + 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; + } + + /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ + + 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; + + 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; + } +#endif + case ';': +#if ENABLE_HUSH_CASE + case_semi: +#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; + 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)" */ +#endif + 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 + } + 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 + case '{': + if (parse_group(&dest, &ctx, input, ch) != 0) { + goto parse_error; + } + 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) */ + + 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; + } + /* Discard cached input, force prompt */ + input->p = NULL; + IF_HUSH_INTERACTIVE(input->promptme = 1;) + goto reset; + } +} + + +/*** Execution routines ***/ + +/* 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); /* expand_strvec_to_strvec() takes a list of strings, expands * all variable references within and returns a pointer to @@ -3463,6 +5307,194 @@ static void re_execute_shell(char ***to_free, const char *s, #endif /* !BB_MMU */ +static int run_and_free_list(struct pipe *pi); + +/* 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) +{ + /* 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; + } +} + +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, ';'); +} + +#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 + 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 + } + + /* 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]); +} + +/* Return code is exit status of the process that is run. */ +static int process_command_subs(o_string *dest, const char *s) +{ + FILE *fp; + struct in_str pipe_str; + pid_t pid; + int status, ch, eol_cnt; + + 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); + } + + 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; @@ -3622,101 +5654,6 @@ static void restore_redirects(int squirrel[]) } } - -static void free_pipe_list(struct pipe *head); - -/* 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 -#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); - } - command->redirects = NULL; - } - 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; - - for (pi = head; pi; pi = next) { -#if HAS_KEYWORDS - debug_printf_clean(" pipe reserved word %d\n", pi->res_word); -#endif - free_pipe(pi); - debug_printf_clean("pipe followup code %d\n", pi->followup); - next = pi->next; - /*pi->next = NULL;*/ - free(pi); - } -} - - -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 parse_and_run_string(const char *s); - - static char *find_in_path(const char *arg) { char *ret = NULL; @@ -4482,7 +6419,11 @@ static int checkjobs_and_fg_shell(struct pipe* fg_pipe) #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 redirect_and_varexp_helper(char ***new_env_p, struct variable **old_vars_p, struct command *command, int squirrel[3], char **argv_expanded) +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(). @@ -5291,1947 +7232,6 @@ static int run_and_free_list(struct pipe *pi) } -static struct pipe *new_pipe(void) -{ - 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; -} - -/* 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; - - 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); - } - - /* 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 */ -} - -static void done_pipe(struct parse_context *ctx, pipe_style type) -{ - int not_null; - - 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 - - /* 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); - } - 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]; - */ - done_command(ctx); -} - -/* 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; - - 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 - */ -static int reserved_word(o_string *word, struct parse_context *ctx) -{ -# 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; - - 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; - } - 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); - } - - 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); - } - return 1; -} -#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) -{ - 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; - } - - 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; - } - -#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); - } - -#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); - } -#endif - - o_reset_to_empty_unquoted(word); - - debug_printf_parse("done_word return 0\n"); - return 0; -} - - -/* 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; - - 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; -} - -/* 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); - } - } - - 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 -#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); - } -} - -/* 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) -{ - struct pipe *pi = ctx->list_head; - - while (pi && heredoc_cnt) { - int i; - struct command *cmd = pi->cmds; - - 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; - - 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; -} - - -#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 - 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')) - { - 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 - } - - /* 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]); -} - -/* Return code is exit status of the process that is run. */ -static int process_command_subs(o_string *dest, const char *s) -{ - FILE *fp; - struct in_str pipe_str; - pid_t pid; - int status, ch, eol_cnt; - - 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); - } - - 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 */ - -#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) -{ - /* 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; - - 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; - } - 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; - } -#endif - -#if ENABLE_HUSH_FUNCTIONS - 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); - } - - { -#if BB_MMU -# define as_string NULL -#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); -#endif -#undef as_string - } - debug_printf_parse("parse_group return 0\n"); - return 0; - /* command remains "open", available for possible redirects */ -} - -#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 (1) { - int ch = i_getch(input); - if (ch == EOF) { - syntax_error_unterm_ch('\''); - /*xfunc_die(); - redundant */ - } - if (ch == '\'') - return; - o_addchr(dest, ch); - } -} -/* "...\"...`..`...." - do we need to handle "...$(..)..." too? */ -static void add_till_double_quote(o_string *dest, struct in_str *input) -{ - 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); - } - o_addchr(dest, ch); - if (ch == '`') { - add_till_backquote(dest, input); - o_addchr(dest, ch); - continue; - } - //if (ch == '$') ... - } -} -/* 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 */ - } - 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); - } -} -/* 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); - - 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; - } - } - 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; - } - } - return ch; -} -#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 parse_dollar(as_string, dest, input) \ - parse_dollar(dest, input) -#define as_string NULL -#endif -static int parse_dollar(o_string *as_string, - o_string *dest, - struct in_str *input) -{ - int ch = i_peek(input); /* first character after the $ */ - unsigned char quote_mask = dest->o_escape ? 0x80 : 0; - - 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); - - ch = i_getch(input); /* eat '{' */ - nommu_addchr(as_string, ch); - - 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; - - /* 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... */ - - while (1) { - unsigned pos; - - o_addchr(dest, ch); - debug_printf_parse(": '%c'\n", ch); - - ch = i_getch(input); - nommu_addchr(as_string, ch); - if (ch == '}') - break; - - 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; - - /* 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); - } - - 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; - } -#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, ')'); - } - 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 - 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, '$'); - } - 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; - - 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(); - } - /* 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 -} - -/* - * 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 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; - - 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; - - 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; - - 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; */ - } - - 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); - - next = '\0'; - if (ch != '\n') - next = i_peek(input); - - 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; - } - continue; - } - - 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 */ - } - - 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 - 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; - - /* 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; - } -#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; - } - 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 - 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; - } - - /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ - - 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; - - 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; - } -#endif - case ';': -#if ENABLE_HUSH_CASE - case_semi: -#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; - 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)" */ -#endif - 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 - } - 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 - case '{': - if (parse_group(&dest, &ctx, input, ch) != 0) { - goto parse_error; - } - 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) */ - - 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; - } - /* Discard cached input, force prompt */ - input->p = NULL; - IF_HUSH_INTERACTIVE(input->promptme = 1;) - goto reset; - } -} - -/* 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) -{ - /* 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; - } -} - -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) { |