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:
parent
240c255d8b
commit
b6e6556b31
494
shell/hush.c
494
shell/hush.c
@ -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)
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
bash 3.2 fails this
|
bash 3.2 fails this
|
||||||
hush: syntax error
|
hush: syntax error: ! ! command
|
||||||
|
Loading…
Reference in New Issue
Block a user