struct child_prog {
pid_t pid; /* 0 if exited */
+ int assignment_cnt; /* how many argv[i] are assignments? */
smallint is_stopped; /* is the program currently running? */
smallint subshell; /* flag, non-zero if group must be forked */
+ struct pipe *group; /* if non-NULL, this "prog" is {} group,
+ * subshell, or a compound statement */
char **argv; /* program name and arguments */
- struct pipe *group; /* if non-NULL, first in group or subshell */
struct redir_struct *redirects; /* I/O redirections */
};
/* argv vector may contain variable references (^Cvar^C, ^C0^C etc)
MAYBE_ASSIGNMENT = 0,
DEFINITELY_ASSIGNMENT = 1,
NOT_ASSIGNMENT = 2,
+ WORD_IS_KEYWORD = 3, /* not assigment, but next word may be: "if v=xyz cmd;" */
};
/* Used for initialization: o_string foo = NULL_O_STRING; */
#define NULL_O_STRING { NULL }
pid_t last_bg_pid;
#if ENABLE_HUSH_JOB
int run_list_level;
-// pid_t saved_task_pgrp;
pid_t saved_tty_pgrp;
int last_jobid;
struct pipe *job_list;
static int setup_redirects(struct child_prog *prog, int squirrel[]);
static int run_list(struct pipe *pi);
#if BB_MMU
-#define pseudo_exec_argv(ptrs2free, argv, argv_expanded) pseudo_exec_argv(argv, argv_expanded)
-#define pseudo_exec(ptrs2free, child, argv_expanded) pseudo_exec(child, argv_expanded)
+#define pseudo_exec_argv(ptrs2free, argv, assignment_cnt, argv_expanded) \
+ pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
+#define pseudo_exec(ptrs2free, child, argv_expanded) \
+ pseudo_exec(child, argv_expanded)
#endif
-static void pseudo_exec_argv(char **ptrs2free, char **argv, char **argv_expanded) NORETURN;
+static void pseudo_exec_argv(char **ptrs2free, char **argv, int assignment_cnt, char **argv_expanded) NORETURN;
static void pseudo_exec(char **ptrs2free, struct child_prog *child, char **argv_expanded) NORETURN;
static int run_pipe(struct pipe *pi);
/* data structure manipulation: */
* XXX no exit() here. If you don't exec, use _exit instead.
* The at_exit handlers apparently confuse the calling process,
* in particular stdin handling. Not sure why? -- because of vfork! (vda) */
-static void pseudo_exec_argv(char **ptrs2free, char **argv, char **argv_expanded)
+static void pseudo_exec_argv(char **ptrs2free, char **argv, int assignment_cnt, char **argv_expanded)
{
int i, rcode;
char *p;
const struct built_in_command *x;
- for (i = 0; is_assignment(argv[i]); i++) {
+ for (i = 0; i < assignment_cnt; i++) {
debug_printf_exec("pid %d environment modification: %s\n",
getpid(), argv[i]);
p = expand_string_to_string(argv[i]);
static void pseudo_exec(char **ptrs2free, struct child_prog *child, char **argv_expanded)
{
if (child->argv)
- pseudo_exec_argv(ptrs2free, child->argv, argv_expanded);
+ pseudo_exec_argv(ptrs2free, child->argv, child->assignment_cnt, argv_expanded);
if (child->group) {
#if !BB_MMU
p = getpgid(0); /* pgid of our process */
debug_printf_jobs("fg'ing ourself: getpgid(0)=%d\n", (int)p);
tcsetpgrp(G.interactive_fd, p);
-// if (tcsetpgrp(G.interactive_fd, p) && errno != ENOTTY)
-// bb_perror_msg("tcsetpgrp-4a");
return rcode;
}
#endif
argv = child->argv;
if (single_and_fg && argv != NULL) {
- for (i = 0; is_assignment(argv[i]); i++)
- continue;
+ i = child->assignment_cnt;
if (i != 0 && argv[i] == NULL) {
/* assignments, but no command: set local environment */
for (i = 0; argv[i] != NULL; i++) {
}
/* Expand assignments into one string each */
- for (i = 0; is_assignment(argv[i]); i++) {
+ for (i = 0; i < child->assignment_cnt; i++) {
p = expand_string_to_string(argv[i]);
putenv(p);
//FIXME: do we leak p?!
struct child_prog *child = &pi->progs[prn];
char **argv = child->argv;
- fprintf(stderr, "%*s prog %d", lvl*2, "", prn);
+ fprintf(stderr, "%*s prog %d assignment_cnt:%d", lvl*2, "", prn, child->assignment_cnt);
if (child->group) {
fprintf(stderr, " group %s: (argv=%p)\n",
(child->subshell ? "()" : "{}"),
vals = (char**)encoded_dollar_at_argv;
if (pi->next->res_word == RES_IN) {
/* if no variable values after "in" we skip "for" */
- if (!pi->next->progs->argv)
+ if (!pi->next->progs[0].argv)
break;
- vals = pi->next->progs->argv;
+ vals = pi->next->progs[0].argv;
} /* else: "for var; do..." -> assume "$@" list */
/* create list of variable values */
debug_print_strings("for_list made from", vals);
for_list = expand_strvec_to_strvec(vals);
for_lcur = for_list;
debug_print_strings("for_list", for_list);
- for_varname = pi->progs->argv[0];
- pi->progs->argv[0] = NULL;
+ for_varname = pi->progs[0].argv[0];
+ pi->progs[0].argv[0] = NULL;
}
- free(pi->progs->argv[0]);
+ free(pi->progs[0].argv[0]);
if (!*for_lcur) {
/* "for" loop is over, clean up */
free(for_list);
for_list = NULL;
for_lcur = NULL;
- pi->progs->argv[0] = for_varname;
+ pi->progs[0].argv[0] = for_varname;
break;
}
/* insert next value from for_lcur */
//TODO: does it need escaping?
- pi->progs->argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++);
+ pi->progs[0].argv[0] = xasprintf("%s=%s", for_varname, *for_lcur++);
+ pi->progs[0].assignment_cnt = 1;
}
if (rword == RES_IN) /* "for v IN list;..." - "in" has no cmds anyway */
continue;
* case, function, and select are obnoxious, save those for later.
*/
#if HAS_KEYWORDS
-static int reserved_word(const o_string *word, struct p_context *ctx)
+static int reserved_word(o_string *word, struct p_context *ctx)
{
struct reserved_combo {
- char literal[7];
+ char literal[6];
unsigned char res;
+ unsigned char assignment_flag;
int flag;
};
enum {
*/
static const struct reserved_combo reserved_list[] = {
#if ENABLE_HUSH_IF
- { "!", RES_NONE, 0 },
- { "if", RES_IF, FLAG_THEN | FLAG_START },
- { "then", RES_THEN, FLAG_ELIF | FLAG_ELSE | FLAG_FI },
- { "elif", RES_ELIF, FLAG_THEN },
- { "else", RES_ELSE, FLAG_FI },
- { "fi", RES_FI, FLAG_END },
+ { "!", 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, FLAG_IN | FLAG_DO | FLAG_START },
- { "while", RES_WHILE, FLAG_DO | FLAG_START },
- { "until", RES_UNTIL, FLAG_DO | FLAG_START },
- { "in", RES_IN, FLAG_DO },
- { "do", RES_DO, FLAG_DONE },
- { "done", RES_DONE, FLAG_END },
+ { "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, FLAG_MATCH | FLAG_START },
- { "esac", RES_ESAC, FLAG_END },
+ { "case", RES_CASE, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_START },
+ { "esac", RES_ESAC, NOT_ASSIGNMENT , FLAG_END },
#endif
};
#if ENABLE_HUSH_CASE
static const struct reserved_combo reserved_match = {
- "" /* "match" */, RES_MATCH, FLAG_MATCH | FLAG_ESAC
+ "", RES_MATCH, NOT_ASSIGNMENT , FLAG_MATCH | FLAG_ESAC
};
#endif
const struct reserved_combo *r;
*ctx = *old; /* physical copy */
free(old);
}
+ word->o_assignment = r->assignment_flag;
return 1;
}
return 0;
struct child_prog *child = ctx->child;
debug_printf_parse("done_word entered: '%s' %p\n", word->data, child);
+ if (word->length == 0 && word->nonnull == 0) {
+ debug_printf_parse("done_word return 0: true null, ignored\n");
+ return 0;
+ }
/* 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) {
+ if (word->o_assignment != DEFINITELY_ASSIGNMENT
+ && word->o_assignment != WORD_IS_KEYWORD
+ ) {
word->o_assignment = NOT_ASSIGNMENT;
} else {
+ if (word->o_assignment == DEFINITELY_ASSIGNMENT)
+ child->assignment_cnt++;
word->o_assignment = MAYBE_ASSIGNMENT;
}
- if (word->length == 0 && word->nonnull == 0) {
- 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" */
debug_printf_parse(": checking '%s' for reserved-ness\n", word->data);
if (reserved_word(word, ctx)) {
o_reset(word);
- word->o_assignment = NOT_ASSIGNMENT;
debug_printf_parse("done_word return %d\n", (ctx->ctx_res_w == RES_SNTX));
return (ctx->ctx_res_w == RES_SNTX);
}
o_addchr(dest, '\n');
eol_cnt--;
}
-// /* Even unquoted `echo '\'` results in two backslashes
-// * (which are converted into one by globbing later) */
-// if (!dest->o_quote && ch == '\\') {
-// o_addchr(dest, ch);
-// }
o_addQchr(dest, ch);
}
static int parse_group(o_string *dest, struct p_context *ctx,
struct in_str *input, int ch)
{
+ /* NB: parse_group may create and use its own o_string,
+ * without any code changes. It just so happens that code is smaller
+ * if we (ab)use caller's one. */
int rcode;
const char *endch = NULL;
struct p_context sub;
* 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_quote. */
- debug_printf_parse("parse_stream entered, end_trigger='%s'\n", end_trigger);
+ debug_printf_parse("parse_stream entered, end_trigger='%s' dest->o_assignment:%d\n", end_trigger, dest->o_assignment);
while (1) {
m = CHAR_IFS;
return 1;
}
o_addQchr(dest, ch);
- if (dest->o_assignment == MAYBE_ASSIGNMENT
+ if ((dest->o_assignment == MAYBE_ASSIGNMENT
+ || dest->o_assignment == WORD_IS_KEYWORD)
&& ch == '='
&& is_assignment(dest->data)
) {
}
#endif
done_pipe(ctx, PIPE_SEQ);
+ dest->o_assignment = MAYBE_ASSIGNMENT;
}
}
if (end_trigger) {
done_word(dest, ctx);
//err chk?
done_pipe(ctx, PIPE_SEQ);
+ dest->o_assignment = MAYBE_ASSIGNMENT;
}
if (!HAS_KEYWORDS
IF_HAS_KEYWORDS(|| (ctx->ctx_res_w == RES_NONE && ctx->old_flag == 0))
}
}
#endif
+ new_cmd:
+ /* We just finished a cmd. New one may start
+ * with an assignment */
+ dest->o_assignment = MAYBE_ASSIGNMENT;
break;
case '&':
done_word(dest, ctx);
} else {
done_pipe(ctx, PIPE_BG);
}
- break;
+ goto new_cmd;
case '|':
done_word(dest, ctx);
#if ENABLE_HUSH_CASE
if (ctx->ctx_res_w == RES_MATCH)
break; /* we are in case's "word | word)" */
#endif
- if (next == '|') {
+ if (next == '|') { /* || */
i_getch(input);
done_pipe(ctx, PIPE_OR);
} else {
* "echo foo 2| cat" yields "foo 2". */
done_command(ctx);
}
- break;
+ goto new_cmd;
case '(':
#if ENABLE_HUSH_CASE
/* "case... in [(]word)..." - skip '(' */
debug_printf_parse("parse_stream return 1: parse_group returned non-0\n");
return 1;
}
- break;
+ goto new_cmd;
case ')':
#if ENABLE_HUSH_CASE
if (ctx->ctx_res_w == RES_MATCH)
/* We will stop & execute after each ';' or '\n'.
* Example: "sleep 9999; echo TEST" + ctrl-C:
* TEST should be printed */
+ temp.o_assignment = MAYBE_ASSIGNMENT;
rcode = parse_stream(&temp, &ctx, inp, ";\n");
#if HAS_KEYWORDS
if (rcode != 1 && ctx.old_flag != 0) {
{
pid_t shell_pgrp;
-// G.saved_task_pgrp =
shell_pgrp = getpgrp();
-// debug_printf_jobs("saved_task_pgrp=%d\n", G.saved_task_pgrp);
close_on_exec_on(G.interactive_fd);
/* If we were ran as 'hush &',
char **ptrs2free = alloc_ptrs(argv);
#endif
// FIXME: if exec fails, bash does NOT exit! We do...
- pseudo_exec_argv(ptrs2free, argv + 1, NULL);
+ pseudo_exec_argv(ptrs2free, argv + 1, 0, NULL);
/* never returns */
}
}