hush: improve parse_stream: does not require parsing context struct;

cleans up on syntax errors (we used to leak memory in this case);
 much simplified interface to the rest of hush.

function                                             old     new   delta
parse_stream                                        1204    1447    +243
done_word                                            658     669     +11
static_get                                            22      28      +6
builtin_source                                        84      89      +5
parse_and_run_file                                    27      30      +3
parse_and_run_string                                  31      27      -4
builtin_eval                                          55      50      -5
hush_main                                            991     985      -6
free_pipe_list                                        39      31      -8
free_pipe                                            210     189     -21
expand_variables                                    2242    2199     -43
parse_and_run_stream                                 289     153    -136
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 5/7 up/down: 268/-223)           Total: 45 bytes
This commit is contained in:
Denis Vlasenko 2009-04-03 16:49:04 +00:00
parent 240c255d8b
commit b6e6556b31
2 changed files with 267 additions and 229 deletions

View File

@ -217,24 +217,24 @@ void xxfree(void *ptr);
void *xxmalloc(int lineno, size_t size) void *xxmalloc(int lineno, size_t size)
{ {
void *ptr = xmalloc((size + 0xff) & ~0xff); void *ptr = xmalloc((size + 0xff) & ~0xff);
fprintf(stderr, "line %d: malloc %p\n", lineno, ptr); fdprintf(2, "line %d: malloc %p\n", lineno, ptr);
return ptr; return ptr;
} }
void *xxrealloc(int lineno, void *ptr, size_t size) void *xxrealloc(int lineno, void *ptr, size_t size)
{ {
ptr = xrealloc(ptr, (size + 0xff) & ~0xff); ptr = xrealloc(ptr, (size + 0xff) & ~0xff);
fprintf(stderr, "line %d: realloc %p\n", lineno, ptr); fdprintf(2, "line %d: realloc %p\n", lineno, ptr);
return ptr; return ptr;
} }
char *xxstrdup(int lineno, const char *str) char *xxstrdup(int lineno, const char *str)
{ {
char *ptr = xstrdup(str); char *ptr = xstrdup(str);
fprintf(stderr, "line %d: strdup %p\n", lineno, ptr); fdprintf(2, "line %d: strdup %p\n", lineno, ptr);
return ptr; return ptr;
} }
void xxfree(void *ptr) void xxfree(void *ptr)
{ {
fprintf(stderr, "free %p\n", ptr); fdprintf(2, "free %p\n", ptr);
free(ptr); free(ptr);
} }
#define xmalloc(s) xxmalloc(__LINE__, s) #define xmalloc(s) xxmalloc(__LINE__, s)
@ -244,12 +244,13 @@ void xxfree(void *ptr)
#endif #endif
#define ERR_PTR ((void*)(long)1)
static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="HUSH_VER_STR; static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="HUSH_VER_STR;
#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" #define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n"
#define SPECIAL_VAR_SYMBOL 3 #define SPECIAL_VAR_SYMBOL 3
#define PARSEFLAG_EXIT_FROM_LOOP 1
typedef enum redir_type { typedef enum redir_type {
REDIRECT_INPUT = 1, REDIRECT_INPUT = 1,
@ -1209,8 +1210,10 @@ static void arith_set_local_var(const char *name, const char *val, int flags)
static int static_get(struct in_str *i) static int static_get(struct in_str *i)
{ {
int ch = *i->p++; int ch = *i->p++;
if (ch == '\0') return EOF; if (ch != '\0')
return ch; return ch;
i->p--;
return EOF;
} }
static int static_peek(struct in_str *i) static int static_peek(struct in_str *i)
@ -2147,18 +2150,18 @@ static void restore_redirects(int squirrel[])
#define free_pipe_list(head, indent) free_pipe_list(head) #define free_pipe_list(head, indent) free_pipe_list(head)
#define free_pipe(pi, indent) free_pipe(pi) #define free_pipe(pi, indent) free_pipe(pi)
#endif #endif
static int free_pipe_list(struct pipe *head, int indent); static void free_pipe_list(struct pipe *head, int indent);
/* return code is the exit status of the pipe */ /* return code is the exit status of the pipe */
static int free_pipe(struct pipe *pi, int indent) static void free_pipe(struct pipe *pi, int indent)
{ {
char **p; char **p;
struct command *command; struct command *command;
struct redir_struct *r, *rnext; struct redir_struct *r, *rnext;
int a, i, ret_code = 0; int a, i;
if (pi->stopped_cmds > 0) if (pi->stopped_cmds > 0)
return ret_code; return;
debug_printf_clean("%s run pipe: (pid %d)\n", indenter(indent), getpid()); debug_printf_clean("%s run pipe: (pid %d)\n", indenter(indent), getpid());
for (i = 0; i < pi->num_cmds; i++) { for (i = 0; i < pi->num_cmds; i++) {
command = &pi->cmds[i]; command = &pi->cmds[i];
@ -2169,12 +2172,13 @@ static int free_pipe(struct pipe *pi, int indent)
} }
free_strings(command->argv); free_strings(command->argv);
command->argv = NULL; command->argv = NULL;
} else if (command->group) { }
/* not "else if": on syntax error, we may have both! */
if (command->group) {
debug_printf_clean("%s begin group (grp_type:%d)\n", indenter(indent), command->grp_type); debug_printf_clean("%s begin group (grp_type:%d)\n", indenter(indent), command->grp_type);
ret_code = free_pipe_list(command->group, indent+3); free_pipe_list(command->group, indent+3);
debug_printf_clean("%s end group\n", indenter(indent)); debug_printf_clean("%s end group\n", indenter(indent));
} else { command->group = NULL;
debug_printf_clean("%s (nil)\n", indenter(indent));
} }
for (r = command->redirects; r; r = rnext) { for (r = command->redirects; r; r = rnext) {
debug_printf_clean("%s redirect %d%s", indenter(indent), r->fd, redir_table[r->rd_type].descrip); debug_printf_clean("%s redirect %d%s", indenter(indent), r->fd, redir_table[r->rd_type].descrip);
@ -2199,25 +2203,22 @@ static int free_pipe(struct pipe *pi, int indent)
free(pi->cmdtext); free(pi->cmdtext);
pi->cmdtext = NULL; pi->cmdtext = NULL;
#endif #endif
return ret_code;
} }
static int free_pipe_list(struct pipe *head, int indent) static void free_pipe_list(struct pipe *head, int indent)
{ {
int rcode = 0; /* if list has no members */
struct pipe *pi, *next; struct pipe *pi, *next;
for (pi = head; pi; pi = next) { for (pi = head; pi; pi = next) {
#if HAS_KEYWORDS #if HAS_KEYWORDS
debug_printf_clean("%s pipe reserved mode %d\n", indenter(indent), pi->res_word); debug_printf_clean("%s pipe reserved mode %d\n", indenter(indent), pi->res_word);
#endif #endif
rcode = free_pipe(pi, indent); free_pipe(pi, indent);
debug_printf_clean("%s pipe followup code %d\n", indenter(indent), pi->followup); debug_printf_clean("%s pipe followup code %d\n", indenter(indent), pi->followup);
next = pi->next; next = pi->next;
/*pi->next = NULL;*/ /*pi->next = NULL;*/
free(pi); free(pi);
} }
return rcode;
} }
@ -3412,7 +3413,6 @@ static void done_pipe(struct parse_context *ctx, pipe_style type)
new_p = new_pipe(); new_p = new_pipe();
ctx->pipe->next = new_p; ctx->pipe->next = new_p;
ctx->pipe = new_p; ctx->pipe = new_p;
ctx->command = NULL; /* needed! */
/* RES_THEN, RES_DO etc are "sticky" - /* RES_THEN, RES_DO etc are "sticky" -
* they remain set for commands inside if/while. * they remain set for commands inside if/while.
* This is used to control execution. * This is used to control execution.
@ -3428,6 +3428,7 @@ static void done_pipe(struct parse_context *ctx, pipe_style type)
if (ctx->ctx_res_w == RES_MATCH) if (ctx->ctx_res_w == RES_MATCH)
ctx->ctx_res_w = RES_CASEI; ctx->ctx_res_w = RES_CASEI;
#endif #endif
ctx->command = NULL; /* trick done_command below */
/* Create the memory for command, roughly: /* Create the memory for command, roughly:
* ctx->pipe->cmds = new struct command; * ctx->pipe->cmds = new struct command;
* ctx->command = &ctx->pipe->cmds[0]; * ctx->command = &ctx->pipe->cmds[0];
@ -3542,21 +3543,21 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
#endif #endif
if (r->flag == 0) { /* '!' */ if (r->flag == 0) { /* '!' */
if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */ if (ctx->ctx_inverted) { /* bash doesn't accept '! ! true' */
syntax(NULL); syntax("! ! command");
IF_HAS_KEYWORDS(ctx->ctx_res_w = RES_SNTX;) IF_HAS_KEYWORDS(ctx->ctx_res_w = RES_SNTX;)
} }
ctx->ctx_inverted = 1; ctx->ctx_inverted = 1;
return 1; return 1;
} }
if (r->flag & FLAG_START) { if (r->flag & FLAG_START) {
struct parse_context *new; struct parse_context *old;
debug_printf("push stack\n"); old = xmalloc(sizeof(*old));
new = xmalloc(sizeof(*new)); debug_printf_parse("push stack %p\n", old);
*new = *ctx; /* physical copy */ *old = *ctx; /* physical copy */
initialize_context(ctx); initialize_context(ctx);
ctx->stack = new; ctx->stack = old;
} else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) {
syntax(NULL); syntax(word->data);
ctx->ctx_res_w = RES_SNTX; ctx->ctx_res_w = RES_SNTX;
return 1; return 1;
} }
@ -3564,8 +3565,8 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
ctx->old_flag = r->flag; ctx->old_flag = r->flag;
if (ctx->old_flag & FLAG_END) { if (ctx->old_flag & FLAG_END) {
struct parse_context *old; struct parse_context *old;
debug_printf("pop stack\n");
done_pipe(ctx, PIPE_SEQ); done_pipe(ctx, PIPE_SEQ);
debug_printf_parse("pop stack %p\n", ctx->stack);
old = ctx->stack; old = ctx->stack;
old->command->group = ctx->list_head; old->command->group = ctx->list_head;
old->command->grp_type = GRP_NORMAL; old->command->grp_type = GRP_NORMAL;
@ -3577,10 +3578,10 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
} }
#endif #endif
//TODO: many, many callers don't check error from done_word()
/* Word is complete, look at it and update parsing context. /* Word is complete, look at it and update parsing context.
* Normal return is 0. Syntax errors return 1. */ * 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) static int done_word(o_string *word, struct parse_context *ctx)
{ {
struct command *command = ctx->command; struct command *command = ctx->command;
@ -3610,10 +3611,14 @@ static int done_word(o_string *word, struct parse_context *ctx)
debug_printf("word stored in rd_filename: '%s'\n", word->data); debug_printf("word stored in rd_filename: '%s'\n", word->data);
} else { } else {
/* "{ echo foo; } echo bar" - bad */ /* "{ echo foo; } echo bar" - bad */
/* NB: bash allows e.g. "if true; then { echo foo; } fi". TODO? */ /* NB: bash allows e.g.:
* if true; then { echo foo; } fi
* while if false; then false; fi do break; done
* TODO? */
if (command->group) { if (command->group) {
syntax(NULL); syntax(word->data);
debug_printf_parse("done_word return 1: syntax error, groups and arglists don't mix\n"); debug_printf_parse("done_word return 1: syntax error, "
"groups and arglists don't mix\n");
return 1; return 1;
} }
#if HAS_KEYWORDS #if HAS_KEYWORDS
@ -3716,8 +3721,7 @@ static int redirect_opt_num(o_string *o)
return num; return num;
} }
static int parse_stream(o_string *dest, struct parse_context *ctx, static struct pipe *parse_stream(struct in_str *input, int end_trigger);
struct in_str *input0, int end_trigger);
#if ENABLE_HUSH_TICK #if ENABLE_HUSH_TICK
static FILE *generate_stream_from_list(struct pipe *head) static FILE *generate_stream_from_list(struct pipe *head)
@ -3767,24 +3771,25 @@ static int process_command_subs(o_string *dest,
struct in_str *input) struct in_str *input)
{ {
int retcode, ch, eol_cnt; int retcode, ch, eol_cnt;
o_string result = NULL_O_STRING; struct pipe *pipe_list;
struct parse_context inner;
FILE *p; FILE *p;
struct in_str pipe_str; struct in_str pipe_str;
/* Recursion to generate command */ /* Recursion to generate command */
retcode = parse_stream(&result, &inner, input, '\0'); pipe_list = parse_stream(input, '\0');
if (retcode != 0) if (pipe_list == NULL)
return retcode; /* syntax error or EOF */ return 0; /* EOF: empty `cmd`: ``, ` ` etc */
o_free(&result); if (pipe_list == ERR_PTR)
return 1; /* parse error. can this really happen? */
p = generate_stream_from_list(inner.list_head); p = generate_stream_from_list(pipe_list);
free_pipe_list(pipe_list, /* indent: */ 0);
if (p == NULL) if (p == NULL)
return 1; return 1;
close_on_exec_on(fileno(p)); close_on_exec_on(fileno(p));
setup_file_in_str(&pipe_str, p);
/* Now send results of command back into original context */ /* Now send results of command back into original context */
setup_file_in_str(&pipe_str, p);
eol_cnt = 0; eol_cnt = 0;
while ((ch = i_getch(&pipe_str)) != EOF) { while ((ch = i_getch(&pipe_str)) != EOF) {
if (ch == '\n') { if (ch == '\n') {
@ -3805,7 +3810,6 @@ static int process_command_subs(o_string *dest,
* echo `echo Hi; exec 1>&-; sleep 2` * echo `echo Hi; exec 1>&-; sleep 2`
*/ */
retcode = fclose(p); retcode = fclose(p);
free_pipe_list(inner.list_head, /* indent: */ 0);
debug_printf("closed FILE from child, retcode=%d\n", retcode); debug_printf("closed FILE from child, retcode=%d\n", retcode);
return retcode; return retcode;
} }
@ -3817,9 +3821,8 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
/* dest contains characters seen prior to ( or {. /* dest contains characters seen prior to ( or {.
* Typically it's empty, but for function defs, * Typically it's empty, but for function defs,
* it contains function name (without '()'). */ * it contains function name (without '()'). */
int rcode; struct pipe *pipe_list;
int endch; int endch;
struct parse_context sub;
struct command *command = ctx->command; struct command *command = ctx->command;
debug_printf_parse("parse_group entered\n"); debug_printf_parse("parse_group entered\n");
@ -3845,12 +3848,16 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
endch = ')'; endch = ')';
command->grp_type = GRP_SUBSHELL; command->grp_type = GRP_SUBSHELL;
} }
rcode = parse_stream(dest, &sub, input, endch); pipe_list = parse_stream(input, endch);
if (rcode == 0) { /* empty ()/{} or parse error? */
command->group = sub.list_head; if (!pipe_list || pipe_list == ERR_PTR) {
syntax(NULL);
debug_printf_parse("parse_group return 1: parse_stream returned %p\n", pipe_list);
return 1;
} }
debug_printf_parse("parse_group return %d\n", rcode); command->group = pipe_list;
return rcode; debug_printf_parse("parse_group return 0\n");
return 0;
/* command remains "open", available for possible redirects */ /* command remains "open", available for possible redirects */
} }
@ -4211,17 +4218,18 @@ static int parse_stream_dquoted(o_string *dest, struct in_str *input, int dquote
goto again; goto again;
} }
/* Initalize ctx (i.e. caller does not need to do that). /*
* Scan input, call done_word() whenever full IFS delimited word was seen. * Scan input until EOF or end_trigger char.
* Call done_pipe if '\n' was seen (and end_trigger != NULL). * Return a list of pipes to execute, or NULL on EOF
* Return code is 0 if end_trigger char is met, * or if end_trigger character is met.
* -1 on EOF (but if end_trigger == NULL then return 0), * On syntax error, exit is shell is not interactive,
* 1 for syntax error * reset parsing machinery and start parsing anew,
* Net result is a list of pipes in ctx->list_head. * or return ERR_PTR.
*/ */
static int parse_stream(o_string *dest, struct parse_context *ctx, static struct pipe *parse_stream(struct in_str *input, int end_trigger)
struct in_str *input, int end_trigger)
{ {
struct parse_context ctx;
o_string dest = NULL_O_STRING;
int ch, m; int ch, m;
int redir_fd; int redir_fd;
redir_type redir_style; redir_type redir_style;
@ -4232,18 +4240,22 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
* A single-quote triggers a bypass of the main loop until its mate is * 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. */ * found. When recursing, quote state is passed in via dest->o_escape. */
debug_printf_parse("parse_stream entered, end_trigger='%c' " debug_printf_parse("parse_stream entered, end_trigger='%c'\n",
"dest->o_assignment:%d\n", end_trigger ? : 'X' end_trigger ? : 'X');
, dest->o_assignment);
dest->o_assignment = MAYBE_ASSIGNMENT; reset:
initialize_context(ctx); #if ENABLE_HUSH_INTERACTIVE
is_in_dquote = dest->o_escape; input->promptmode = 0; /* PS1 */
#endif
/* dest.o_assignment = MAYBE_ASSIGNMENT; - already is */
initialize_context(&ctx);
is_in_dquote = 0;
while (1) { while (1) {
if (is_in_dquote) { if (is_in_dquote) {
if (parse_stream_dquoted(dest, input, '"')) if (parse_stream_dquoted(&dest, input, '"')) {
return 1; /* propagate parse error */ goto parse_error;
/* If we're here, we reached closing '"' */ }
/* We reached closing '"' */
is_in_dquote = 0; is_in_dquote = 0;
} }
m = CHAR_IFS; m = CHAR_IFS;
@ -4256,15 +4268,15 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
} }
} }
debug_printf_parse(": ch=%c (%d) m=%d escape=%d\n", debug_printf_parse(": ch=%c (%d) m=%d escape=%d\n",
ch, ch, m, dest->o_escape); ch, ch, m, dest.o_escape);
if (m == CHAR_ORDINARY) { if (m == CHAR_ORDINARY) {
o_addQchr(dest, ch); o_addQchr(&dest, ch);
if ((dest->o_assignment == MAYBE_ASSIGNMENT if ((dest.o_assignment == MAYBE_ASSIGNMENT
|| dest->o_assignment == WORD_IS_KEYWORD) || dest.o_assignment == WORD_IS_KEYWORD)
&& ch == '=' && ch == '='
&& is_assignment(dest->data) && is_assignment(dest.data)
) { ) {
dest->o_assignment = DEFINITELY_ASSIGNMENT; dest.o_assignment = DEFINITELY_ASSIGNMENT;
} }
continue; continue;
} }
@ -4272,25 +4284,40 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
/* m is SPECIAL ($,`), IFS, or ORDINARY_IF_QUOTED (*,#) */ /* m is SPECIAL ($,`), IFS, or ORDINARY_IF_QUOTED (*,#) */
if (m == CHAR_IFS) { if (m == CHAR_IFS) {
if (ch == EOF) if (ch == EOF) {
goto ret_EOF; struct pipe *pi;
if (done_word(dest, ctx)) { if (done_word(&dest, &ctx)) {
debug_printf_parse("parse_stream return 1: done_word!=0\n"); goto parse_error;
return 1; }
o_free(&dest);
done_pipe(&ctx, PIPE_SEQ);
/* If we got nothing... */
pi = ctx.list_head;
if (pi->num_cmds == 0
&& pi->res_word == RES_NONE
) {
free_pipe_list(pi, 0);
pi = NULL;
}
debug_printf_parse("parse_stream return %p\n", pi);
return pi;
}
if (done_word(&dest, &ctx)) {
goto parse_error;
} }
if (ch == '\n') { if (ch == '\n') {
#if ENABLE_HUSH_CASE #if ENABLE_HUSH_CASE
/* "case ... in <newline> word) ..." - /* "case ... in <newline> word) ..." -
* newlines are ignored (but ';' wouldn't be) */ * newlines are ignored (but ';' wouldn't be) */
if (ctx->command->argv == NULL if (ctx.command->argv == NULL
&& ctx->ctx_res_w == RES_MATCH && ctx.ctx_res_w == RES_MATCH
) { ) {
continue; continue;
} }
#endif #endif
/* Treat newline as a command separator. */ /* Treat newline as a command separator. */
done_pipe(ctx, PIPE_SEQ); done_pipe(&ctx, PIPE_SEQ);
dest->o_assignment = MAYBE_ASSIGNMENT; dest.o_assignment = MAYBE_ASSIGNMENT;
ch = ';'; ch = ';';
/* note: if (m == CHAR_IFS) continue; /* note: if (m == CHAR_IFS) continue;
* will still trigger for us */ * will still trigger for us */
@ -4298,15 +4325,20 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
} }
if (end_trigger && end_trigger == ch) { if (end_trigger && end_trigger == ch) {
//TODO: disallow "{ cmd }" without semicolon //TODO: disallow "{ cmd }" without semicolon
done_word(dest, ctx); if (done_word(&dest, &ctx)) {
done_pipe(ctx, PIPE_SEQ); goto parse_error;
dest->o_assignment = MAYBE_ASSIGNMENT; }
done_pipe(&ctx, PIPE_SEQ);
dest.o_assignment = MAYBE_ASSIGNMENT;
/* Do we sit outside of any if's, loops or case's? */ /* Do we sit outside of any if's, loops or case's? */
if (!HAS_KEYWORDS if (!HAS_KEYWORDS
IF_HAS_KEYWORDS(|| (ctx->ctx_res_w == RES_NONE && ctx->old_flag == 0)) IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0))
) { ) {
debug_printf_parse("parse_stream return 0: end_trigger char found\n"); debug_printf_parse("parse_stream return %p: "
return 0; "end_trigger char found\n",
ctx.list_head);
o_free(&dest);
return ctx.list_head;
} }
} }
if (m == CHAR_IFS) if (m == CHAR_IFS)
@ -4314,15 +4346,15 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
/* m is SPECIAL (e.g. $,`) or ORDINARY_IF_QUOTED (*,#) */ /* m is SPECIAL (e.g. $,`) or ORDINARY_IF_QUOTED (*,#) */
if (dest->o_assignment == MAYBE_ASSIGNMENT) { if (dest.o_assignment == MAYBE_ASSIGNMENT) {
/* ch is a special char and thus this word /* ch is a special char and thus this word
* cannot be an assignment */ * cannot be an assignment */
dest->o_assignment = NOT_ASSIGNMENT; dest.o_assignment = NOT_ASSIGNMENT;
} }
switch (ch) { switch (ch) {
case '#': case '#':
if (dest->length == 0) { if (dest.length == 0) {
while (1) { while (1) {
ch = i_peek(input); ch = i_peek(input);
if (ch == EOF || ch == '\n') if (ch == EOF || ch == '\n')
@ -4330,61 +4362,61 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
i_getch(input); i_getch(input);
} }
} else { } else {
o_addQchr(dest, ch); o_addQchr(&dest, ch);
} }
break; break;
case '\\': case '\\':
if (next == EOF) { if (next == EOF) {
syntax("\\<eof>"); syntax("\\<eof>");
debug_printf_parse("parse_stream return 1: \\<eof>\n"); goto parse_error;
return 1;
} }
o_addchr(dest, '\\'); o_addchr(&dest, '\\');
o_addchr(dest, i_getch(input)); o_addchr(&dest, i_getch(input));
break; break;
case '$': case '$':
if (handle_dollar(dest, input) != 0) { if (handle_dollar(&dest, input) != 0) {
debug_printf_parse("parse_stream return 1: handle_dollar returned non-0\n"); debug_printf_parse("parse_stream parse error: handle_dollar returned non-0\n");
return 1; goto parse_error;
} }
break; break;
case '\'': case '\'':
dest->nonnull = 1; dest.nonnull = 1;
while (1) { while (1) {
ch = i_getch(input); ch = i_getch(input);
if (ch == EOF) { if (ch == EOF) {
syntax("unterminated '"); syntax("unterminated '");
debug_printf_parse("parse_stream return 1: unterminated '\n"); goto parse_error;
return 1;
} }
if (ch == '\'') if (ch == '\'')
break; break;
if (dest->o_assignment == NOT_ASSIGNMENT) if (dest.o_assignment == NOT_ASSIGNMENT)
o_addqchr(dest, ch); o_addqchr(&dest, ch);
else else
o_addchr(dest, ch); o_addchr(&dest, ch);
} }
break; break;
case '"': case '"':
dest->nonnull = 1; dest.nonnull = 1;
is_in_dquote ^= 1; /* invert */ is_in_dquote ^= 1; /* invert */
if (dest->o_assignment == NOT_ASSIGNMENT) if (dest.o_assignment == NOT_ASSIGNMENT)
dest->o_escape ^= 1; dest.o_escape ^= 1;
break; break;
#if ENABLE_HUSH_TICK #if ENABLE_HUSH_TICK
case '`': { case '`': {
//int pos = dest->length; //int pos = dest.length;
o_addchr(dest, SPECIAL_VAR_SYMBOL); o_addchr(&dest, SPECIAL_VAR_SYMBOL);
o_addchr(dest, '`'); o_addchr(&dest, '`');
add_till_backquote(dest, input); add_till_backquote(&dest, input);
o_addchr(dest, SPECIAL_VAR_SYMBOL); o_addchr(&dest, SPECIAL_VAR_SYMBOL);
//debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos); //debug_printf_subst("SUBST RES3 '%s'\n", dest.data + pos);
break; break;
} }
#endif #endif
case '>': case '>':
redir_fd = redirect_opt_num(dest); redir_fd = redirect_opt_num(&dest);
done_word(dest, ctx); if (done_word(&dest, &ctx)) {
goto parse_error;
}
redir_style = REDIRECT_OVERWRITE; redir_style = REDIRECT_OVERWRITE;
if (next == '>') { if (next == '>') {
redir_style = REDIRECT_APPEND; redir_style = REDIRECT_APPEND;
@ -4393,15 +4425,16 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
#if 0 #if 0
else if (next == '(') { else if (next == '(') {
syntax(">(process) not supported"); syntax(">(process) not supported");
debug_printf_parse("parse_stream return 1: >(process) not supported\n"); goto parse_error;
return 1;
} }
#endif #endif
setup_redirect(ctx, redir_fd, redir_style, input); setup_redirect(&ctx, redir_fd, redir_style, input);
break; break;
case '<': case '<':
redir_fd = redirect_opt_num(dest); redir_fd = redirect_opt_num(&dest);
done_word(dest, ctx); if (done_word(&dest, &ctx)) {
goto parse_error;
}
redir_style = REDIRECT_INPUT; redir_style = REDIRECT_INPUT;
if (next == '<') { if (next == '<') {
redir_style = REDIRECT_HEREIS; redir_style = REDIRECT_HEREIS;
@ -4413,18 +4446,19 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
#if 0 #if 0
else if (next == '(') { else if (next == '(') {
syntax("<(process) not supported"); syntax("<(process) not supported");
debug_printf_parse("parse_stream return 1: <(process) not supported\n"); goto parse_error;
return 1;
} }
#endif #endif
setup_redirect(ctx, redir_fd, redir_style, input); setup_redirect(&ctx, redir_fd, redir_style, input);
break; break;
case ';': case ';':
#if ENABLE_HUSH_CASE #if ENABLE_HUSH_CASE
case_semi: case_semi:
#endif #endif
done_word(dest, ctx); if (done_word(&dest, &ctx)) {
done_pipe(ctx, PIPE_SEQ); goto parse_error;
}
done_pipe(&ctx, PIPE_SEQ);
#if ENABLE_HUSH_CASE #if ENABLE_HUSH_CASE
/* Eat multiple semicolons, detect /* Eat multiple semicolons, detect
* whether it means something special */ * whether it means something special */
@ -4433,9 +4467,9 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
if (ch != ';') if (ch != ';')
break; break;
i_getch(input); i_getch(input);
if (ctx->ctx_res_w == RES_CASEI) { if (ctx.ctx_res_w == RES_CASEI) {
ctx->ctx_dsemicolon = 1; ctx.ctx_dsemicolon = 1;
ctx->ctx_res_w = RES_MATCH; ctx.ctx_res_w = RES_MATCH;
break; break;
} }
} }
@ -4443,51 +4477,55 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
new_cmd: new_cmd:
/* We just finished a cmd. New one may start /* We just finished a cmd. New one may start
* with an assignment */ * with an assignment */
dest->o_assignment = MAYBE_ASSIGNMENT; dest.o_assignment = MAYBE_ASSIGNMENT;
break; break;
case '&': case '&':
done_word(dest, ctx); if (done_word(&dest, &ctx)) {
goto parse_error;
}
if (next == '&') { if (next == '&') {
i_getch(input); i_getch(input);
done_pipe(ctx, PIPE_AND); done_pipe(&ctx, PIPE_AND);
} else { } else {
done_pipe(ctx, PIPE_BG); done_pipe(&ctx, PIPE_BG);
} }
goto new_cmd; goto new_cmd;
case '|': case '|':
done_word(dest, ctx); if (done_word(&dest, &ctx)) {
goto parse_error;
}
#if ENABLE_HUSH_CASE #if ENABLE_HUSH_CASE
if (ctx->ctx_res_w == RES_MATCH) if (ctx.ctx_res_w == RES_MATCH)
break; /* we are in case's "word | word)" */ break; /* we are in case's "word | word)" */
#endif #endif
if (next == '|') { /* || */ if (next == '|') { /* || */
i_getch(input); i_getch(input);
done_pipe(ctx, PIPE_OR); done_pipe(&ctx, PIPE_OR);
} else { } else {
/* we could pick up a file descriptor choice here /* we could pick up a file descriptor choice here
* with redirect_opt_num(), but bash doesn't do it. * with redirect_opt_num(), but bash doesn't do it.
* "echo foo 2| cat" yields "foo 2". */ * "echo foo 2| cat" yields "foo 2". */
done_command(ctx); done_command(&ctx);
} }
goto new_cmd; goto new_cmd;
case '(': case '(':
#if ENABLE_HUSH_CASE #if ENABLE_HUSH_CASE
/* "case... in [(]word)..." - skip '(' */ /* "case... in [(]word)..." - skip '(' */
if (ctx->ctx_res_w == RES_MATCH if (ctx.ctx_res_w == RES_MATCH
&& ctx->command->argv == NULL /* not (word|(... */ && ctx.command->argv == NULL /* not (word|(... */
&& dest->length == 0 /* not word(... */ && dest.length == 0 /* not word(... */
&& dest->nonnull == 0 /* not ""(... */ && dest.nonnull == 0 /* not ""(... */
) { ) {
continue; continue;
} }
#endif #endif
#if ENABLE_HUSH_FUNCTIONS #if ENABLE_HUSH_FUNCTIONS
if (dest->length != 0 /* not just () but word() */ if (dest.length != 0 /* not just () but word() */
&& dest->nonnull == 0 /* not a"b"c() */ && dest.nonnull == 0 /* not a"b"c() */
&& ctx->command->argv == NULL /* it's the first word */ && ctx.command->argv == NULL /* it's the first word */
//TODO: "func ( ) {...}" - note spaces - is valid format too in bash //TODO: "func ( ) {...}" - note spaces - is valid format too in bash
&& i_peek(input) == ')' && i_peek(input) == ')'
&& !match_reserved_word(dest) && !match_reserved_word(&dest)
) { ) {
bb_error_msg("seems like a function definition"); bb_error_msg("seems like a function definition");
i_getch(input); i_getch(input);
@ -4497,21 +4535,19 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
} while (ch == ' ' || ch == '\n'); } while (ch == ' ' || ch == '\n');
if (ch != '{') { if (ch != '{') {
syntax("was expecting {"); syntax("was expecting {");
debug_printf_parse("parse_stream return 1\n"); goto parse_error;
return 1;
} }
ch = 'F'; /* magic value */ ch = 'F'; /* magic value */
} }
#endif #endif
case '{': case '{':
if (parse_group(dest, ctx, input, ch) != 0) { if (parse_group(&dest, &ctx, input, ch) != 0) {
debug_printf_parse("parse_stream return 1: parse_group returned non-0\n"); goto parse_error;
return 1;
} }
goto new_cmd; goto new_cmd;
case ')': case ')':
#if ENABLE_HUSH_CASE #if ENABLE_HUSH_CASE
if (ctx->ctx_res_w == RES_MATCH) if (ctx.ctx_res_w == RES_MATCH)
goto case_semi; goto case_semi;
#endif #endif
case '}': case '}':
@ -4519,23 +4555,51 @@ static int parse_stream(o_string *dest, struct parse_context *ctx,
* if we see {, we call parse_group(..., end_trigger='}') * if we see {, we call parse_group(..., end_trigger='}')
* and it will match } earlier (not here). */ * and it will match } earlier (not here). */
syntax("unexpected } or )"); syntax("unexpected } or )");
debug_printf_parse("parse_stream return 1: unexpected '}'\n"); goto parse_error;
return 1;
default: default:
if (HUSH_DEBUG) if (HUSH_DEBUG)
bb_error_msg_and_die("BUG: unexpected %c\n", ch); bb_error_msg_and_die("BUG: unexpected %c\n", ch);
} }
} /* while (1) */ } /* while (1) */
/* Non-error returns */ parse_error:
ret_EOF: {
debug_printf_parse("parse_stream return %d\n", -(end_trigger != NULL)); struct parse_context *pctx, *p2;
done_word(dest, ctx);
done_pipe(ctx, PIPE_SEQ); /* Clean up allocated tree.
if (end_trigger) { * Samples for finding leaks on syntax error recovery path.
return -1; /* EOF found while expecting end_trigger */ * Execute them from interactive shell and watch pmap `pidof hush`.
* while if false; then false; fi do break; done (bash accepts it)
* while if false; then false; fi; do break; fi
*/
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, 0);
debug_printf_clean("freed list %p\n", pctx->list_head);
p2 = pctx->stack;
if (pctx != &ctx) {
free(pctx);
}
pctx = p2;
} while (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 != ';')
return ERR_PTR;
/* Discard cached input, force prompt */
input->p = NULL;
input->promptme = 1;
goto reset;
} }
return 0;
} }
static void set_in_charmap(const char *set, int code) static void set_in_charmap(const char *set, int code)
@ -4565,71 +4629,41 @@ static void update_charmap(void)
set_in_charmap(G.ifs, CHAR_IFS); /* are ordinary if quoted */ set_in_charmap(G.ifs, CHAR_IFS); /* are ordinary if quoted */
} }
/* Most recursion does not come through here, the exception is /* Execiting from string: eval, sh -c '...'
* from builtin_source() and builtin_eval() */ * or from file: /etc/profile, . file, sh <script>, sh (intereactive)
static int parse_and_run_stream(struct in_str *inp, int parse_flag) * 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)
{ {
struct parse_context ctx; while (1) {
o_string temp = NULL_O_STRING; struct pipe *pipe_list;
int rcode;
do {
update_charmap(); update_charmap();
#if ENABLE_HUSH_INTERACTIVE
inp->promptmode = 0; /* PS1 */ pipe_list = parse_stream(inp, end_trigger);
#endif if (!pipe_list) /* EOF */
/* We will stop & execute after each ';' or '\n'. break;
* Example: "sleep 9999; echo TEST" + ctrl-C: debug_print_tree(pipe_list, 0);
* TEST should be printed */ debug_printf_exec("parse_and_run_stream: run_and_free_list\n");
temp.o_assignment = MAYBE_ASSIGNMENT; run_and_free_list(pipe_list);
rcode = parse_stream(&temp, &ctx, inp, ';'); /* Loop on syntax errors, return on EOF: */
debug_printf_parse("rcode %d ctx.old_flag %x\n", rcode, ctx.old_flag); }
#if HAS_KEYWORDS
if (rcode != 1 && ctx.old_flag != 0) {
syntax(NULL);
}
#endif
if (rcode != 1 IF_HAS_KEYWORDS(&& ctx.old_flag == 0)) {
debug_print_tree(ctx.list_head, 0);
debug_printf_exec("parse_and_run_stream: run_and_free_list\n");
run_and_free_list(ctx.list_head);
} else {
/* We arrive here also if rcode == 1 (error in parse_stream) */
#if HAS_KEYWORDS
if (ctx.old_flag != 0) {
free(ctx.stack);
o_reset(&temp);
}
#endif
/*temp.nonnull = 0; - o_free does it below */
/*temp.o_escape = 0; - o_free does it below */
free_pipe_list(ctx.list_head, /* indent: */ 0);
/* Discard all unprocessed line input, force prompt on */
inp->p = NULL;
#if ENABLE_HUSH_INTERACTIVE
inp->promptme = 1;
#endif
}
o_free(&temp);
/* loop on syntax errors, return on EOF: */
} while (rcode != -1 && !(parse_flag & PARSEFLAG_EXIT_FROM_LOOP));
return 0;
} }
static int parse_and_run_string(const char *s, int parse_flag) static void parse_and_run_string(const char *s)
{ {
struct in_str input; struct in_str input;
setup_string_in_str(&input, s); setup_string_in_str(&input, s);
return parse_and_run_stream(&input, parse_flag); parse_and_run_stream(&input, '\0');
} }
static int parse_and_run_file(FILE *f) static void parse_and_run_file(FILE *f)
{ {
int rcode;
struct in_str input; struct in_str input;
setup_file_in_str(&input, f); setup_file_in_str(&input, f);
rcode = parse_and_run_stream(&input, 0 /* parse_flag */); parse_and_run_stream(&input, ';');
return rcode;
} }
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
@ -4753,7 +4787,7 @@ int hush_main(int argc, char **argv)
optind--; optind--;
} /* else -c 'script' PAR0 PAR1: $0 is PAR0 */ } /* else -c 'script' PAR0 PAR1: $0 is PAR0 */
G.global_argc = argc - optind; G.global_argc = argc - optind;
opt = parse_and_run_string(optarg, 0 /* parse_flag */); parse_and_run_string(optarg);
goto final_return; goto final_return;
case 'i': case 'i':
/* Well, we cannot just declare interactiveness, /* Well, we cannot just declare interactiveness,
@ -4851,14 +4885,14 @@ int hush_main(int argc, char **argv)
#endif #endif
if (argv[optind] == NULL) { if (argv[optind] == NULL) {
opt = parse_and_run_file(stdin); parse_and_run_file(stdin);
} else { } else {
debug_printf("\nrunning script '%s'\n", argv[optind]); debug_printf("\nrunning script '%s'\n", argv[optind]);
G.global_argv = argv + optind; G.global_argv = argv + optind;
G.global_argc = argc - optind; G.global_argc = argc - optind;
input = xfopen_for_read(argv[optind]); input = xfopen_for_read(argv[optind]);
fcntl(fileno(input), F_SETFD, FD_CLOEXEC); fcntl(fileno(input), F_SETFD, FD_CLOEXEC);
opt = parse_and_run_file(input); parse_and_run_file(input);
} }
final_return: final_return:
@ -4876,7 +4910,7 @@ int hush_main(int argc, char **argv)
free(tmp); free(tmp);
} }
#endif #endif
hush_exit(opt ? opt : G.last_return_code); hush_exit(G.last_return_code);
} }
@ -5000,7 +5034,11 @@ static int builtin_eval(char **argv)
if (argv[1]) { if (argv[1]) {
char *str = expand_strvec_to_string(argv + 1); char *str = expand_strvec_to_string(argv + 1);
parse_and_run_string(str, PARSEFLAG_EXIT_FROM_LOOP); /* bash:
* eval "echo Hi; done" ("done" is syntax error):
* "echo Hi" will not execute too.
*/
parse_and_run_string(str);
free(str); free(str);
rcode = G.last_return_code; rcode = G.last_return_code;
} }
@ -5011,8 +5049,9 @@ static int builtin_cd(char **argv)
{ {
const char *newdir; const char *newdir;
if (argv[1] == NULL) { if (argv[1] == NULL) {
// bash does nothing (exitcode 0) if HOME is ""; if it's unset, /* bash does nothing (exitcode 0) if HOME is ""; if it's unset,
// bash says "bash: cd: HOME not set" and does nothing (exitcode 1) * bash says "bash: cd: HOME not set" and does nothing (exitcode 1)
*/
newdir = getenv("HOME") ? : "/"; newdir = getenv("HOME") ? : "/";
} else } else
newdir = argv[1]; newdir = argv[1];
@ -5309,7 +5348,6 @@ static int builtin_shift(char **argv)
static int builtin_source(char **argv) static int builtin_source(char **argv)
{ {
FILE *input; FILE *input;
int status;
if (argv[1] == NULL) if (argv[1] == NULL)
return EXIT_FAILURE; return EXIT_FAILURE;
@ -5326,9 +5364,9 @@ static int builtin_source(char **argv)
/* XXX argv and argc are broken; need to save old G.global_argv /* XXX argv and argc are broken; need to save old G.global_argv
* (pointer only is OK!) on this stack frame, * (pointer only is OK!) on this stack frame,
* set G.global_argv=argv+1, recurse, and restore. */ * set G.global_argv=argv+1, recurse, and restore. */
status = parse_and_run_file(input); parse_and_run_file(input);
fclose(input); fclose(input);
return status; return G.last_return_code;
} }
static int builtin_umask(char **argv) static int builtin_umask(char **argv)

View File

@ -1,2 +1,2 @@
bash 3.2 fails this bash 3.2 fails this
hush: syntax error hush: syntax error: ! ! command