hush: fix improper handling of newline and hash chars in few corner cases

Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com>
This commit is contained in:
Denys Vlasenko 2010-11-22 17:58:14 +01:00
parent 1e23f32453
commit 7b4c0fd5f4
7 changed files with 160 additions and 123 deletions

View File

@ -543,7 +543,6 @@ struct command {
#define IS_NULL_CMD(cmd) \
(!(cmd)->group && !(cmd)->argv && !(cmd)->redirects)
struct pipe {
struct pipe *next;
int num_cmds; /* total number of commands in pipe */
@ -2622,6 +2621,94 @@ static void free_pipe_list(struct pipe *pi)
/*** Parsing routines ***/
#ifndef debug_print_tree
static void debug_print_tree(struct pipe *pi, int lvl)
{
static const char *const PIPE[] = {
[PIPE_SEQ] = "SEQ",
[PIPE_AND] = "AND",
[PIPE_OR ] = "OR" ,
[PIPE_BG ] = "BG" ,
};
static const char *RES[] = {
[RES_NONE ] = "NONE" ,
# if ENABLE_HUSH_IF
[RES_IF ] = "IF" ,
[RES_THEN ] = "THEN" ,
[RES_ELIF ] = "ELIF" ,
[RES_ELSE ] = "ELSE" ,
[RES_FI ] = "FI" ,
# endif
# if ENABLE_HUSH_LOOPS
[RES_FOR ] = "FOR" ,
[RES_WHILE] = "WHILE",
[RES_UNTIL] = "UNTIL",
[RES_DO ] = "DO" ,
[RES_DONE ] = "DONE" ,
# endif
# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
[RES_IN ] = "IN" ,
# endif
# if ENABLE_HUSH_CASE
[RES_CASE ] = "CASE" ,
[RES_CASE_IN ] = "CASE_IN" ,
[RES_MATCH] = "MATCH",
[RES_CASE_BODY] = "CASE_BODY",
[RES_ESAC ] = "ESAC" ,
# endif
[RES_XXXX ] = "XXXX" ,
[RES_SNTX ] = "SNTX" ,
};
static const char *const CMDTYPE[] = {
"{}",
"()",
"[noglob]",
# if ENABLE_HUSH_FUNCTIONS
"func()",
# endif
};
int pin, prn;
pin = 0;
while (pi) {
fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "",
pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]);
prn = 0;
while (prn < pi->num_cmds) {
struct command *command = &pi->cmds[prn];
char **argv = command->argv;
fprintf(stderr, "%*s cmd %d assignment_cnt:%d",
lvl*2, "", prn,
command->assignment_cnt);
if (command->group) {
fprintf(stderr, " group %s: (argv=%p)%s%s\n",
CMDTYPE[command->cmd_type],
argv
# if !BB_MMU
, " group_as_string:", command->group_as_string
# else
, "", ""
# endif
);
debug_print_tree(command->group, lvl+1);
prn++;
continue;
}
if (argv) while (*argv) {
fprintf(stderr, " '%s'", *argv);
argv++;
}
fprintf(stderr, "\n");
prn++;
}
pi = pi->next;
pin++;
}
}
#endif /* debug_print_tree */
static struct pipe *new_pipe(void)
{
struct pipe *pi;
@ -4011,15 +4098,16 @@ static struct pipe *parse_stream(char **pstring,
goto parse_error;
}
if (ch == '\n') {
#if ENABLE_HUSH_CASE
/* "case ... in <newline> word) ..." -
* newlines are ignored (but ';' wouldn't be) */
if (ctx.command->argv == NULL
&& ctx.ctx_res_w == RES_MATCH
/* Is this a case when newline is simply ignored?
* Some examples:
* "cmd | <newline> cmd ..."
* "case ... in <newline> word) ..."
*/
if (IS_NULL_CMD(ctx.command)
&& dest.length == 0 && !dest.has_quoted_part
) {
continue;
}
#endif
/* Treat newline as a command separator. */
done_pipe(&ctx, PIPE_SEQ);
debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt);
@ -4151,6 +4239,31 @@ static struct pipe *parse_stream(char **pstring,
if (parse_redirect(&ctx, redir_fd, redir_style, input))
goto parse_error;
continue; /* back to top of while (1) */
case '#':
if (dest.length == 0 && !dest.has_quoted_part) {
/* skip "#comment" */
while (1) {
ch = i_peek(input);
if (ch == EOF || ch == '\n')
break;
i_getch(input);
/* note: we do not add it to &ctx.as_string */
}
nommu_addchr(&ctx.as_string, '\n');
continue; /* back to top of while (1) */
}
break;
case '\\':
if (next == '\n') {
/* It's "\<newline>" */
#if !BB_MMU
/* Remove trailing '\' from ctx.as_string */
ctx.as_string.data[--ctx.as_string.length] = '\0';
#endif
ch = i_getch(input); /* eat it */
continue; /* back to top of while (1) */
}
break;
}
if (dest.o_assignment == MAYBE_ASSIGNMENT
@ -4165,19 +4278,8 @@ static struct pipe *parse_stream(char **pstring,
/* Note: nommu_addchr(&ctx.as_string, ch) is already done */
switch (ch) {
case '#':
if (dest.length == 0) {
while (1) {
ch = i_peek(input);
if (ch == EOF || ch == '\n')
break;
i_getch(input);
/* note: we do not add it to &ctx.as_string */
}
nommu_addchr(&ctx.as_string, '\n');
} else {
o_addQchr(&dest, ch);
}
case '#': /* non-comment #: "echo a#b" etc */
o_addQchr(&dest, ch);
break;
case '\\':
if (next == EOF) {
@ -4185,21 +4287,14 @@ static struct pipe *parse_stream(char **pstring,
xfunc_die();
}
ch = i_getch(input);
if (ch != '\n') {
o_addchr(&dest, '\\');
/*nommu_addchr(&ctx.as_string, '\\'); - already done */
o_addchr(&dest, ch);
nommu_addchr(&ctx.as_string, ch);
/* Example: echo Hello \2>file
* we need to know that word 2 is quoted */
dest.has_quoted_part = 1;
}
#if !BB_MMU
else {
/* It's "\<newline>". Remove trailing '\' from ctx.as_string */
ctx.as_string.data[--ctx.as_string.length] = '\0';
}
#endif
/* note: ch != '\n' (that case does not reach this place) */
o_addchr(&dest, '\\');
/*nommu_addchr(&ctx.as_string, '\\'); - already done */
o_addchr(&dest, ch);
nommu_addchr(&ctx.as_string, ch);
/* Example: echo Hello \2>file
* we need to know that word 2 is quoted */
dest.has_quoted_part = 1;
break;
case '$':
if (parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0) != 0) {
@ -6869,94 +6964,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
return -1;
}
#ifndef debug_print_tree
static void debug_print_tree(struct pipe *pi, int lvl)
{
static const char *const PIPE[] = {
[PIPE_SEQ] = "SEQ",
[PIPE_AND] = "AND",
[PIPE_OR ] = "OR" ,
[PIPE_BG ] = "BG" ,
};
static const char *RES[] = {
[RES_NONE ] = "NONE" ,
# if ENABLE_HUSH_IF
[RES_IF ] = "IF" ,
[RES_THEN ] = "THEN" ,
[RES_ELIF ] = "ELIF" ,
[RES_ELSE ] = "ELSE" ,
[RES_FI ] = "FI" ,
# endif
# if ENABLE_HUSH_LOOPS
[RES_FOR ] = "FOR" ,
[RES_WHILE] = "WHILE",
[RES_UNTIL] = "UNTIL",
[RES_DO ] = "DO" ,
[RES_DONE ] = "DONE" ,
# endif
# if ENABLE_HUSH_LOOPS || ENABLE_HUSH_CASE
[RES_IN ] = "IN" ,
# endif
# if ENABLE_HUSH_CASE
[RES_CASE ] = "CASE" ,
[RES_CASE_IN ] = "CASE_IN" ,
[RES_MATCH] = "MATCH",
[RES_CASE_BODY] = "CASE_BODY",
[RES_ESAC ] = "ESAC" ,
# endif
[RES_XXXX ] = "XXXX" ,
[RES_SNTX ] = "SNTX" ,
};
static const char *const CMDTYPE[] = {
"{}",
"()",
"[noglob]",
# if ENABLE_HUSH_FUNCTIONS
"func()",
# endif
};
int pin, prn;
pin = 0;
while (pi) {
fprintf(stderr, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "",
pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]);
prn = 0;
while (prn < pi->num_cmds) {
struct command *command = &pi->cmds[prn];
char **argv = command->argv;
fprintf(stderr, "%*s cmd %d assignment_cnt:%d",
lvl*2, "", prn,
command->assignment_cnt);
if (command->group) {
fprintf(stderr, " group %s: (argv=%p)%s%s\n",
CMDTYPE[command->cmd_type],
argv
# if !BB_MMU
, " group_as_string:", command->group_as_string
# else
, "", ""
# endif
);
debug_print_tree(command->group, lvl+1);
prn++;
continue;
}
if (argv) while (*argv) {
fprintf(stderr, " '%s'", *argv);
argv++;
}
fprintf(stderr, "\n");
prn++;
}
pi = pi->next;
pin++;
}
}
#endif /* debug_print_tree */
/* NB: called by pseudo_exec, and therefore must not modify any
* global data until exec/_exit (we can be a child after vfork!) */
static int run_list(struct pipe *pi)

View File

@ -0,0 +1,2 @@
Done:0
abc=123

View File

@ -0,0 +1,5 @@
# This must be interpreted as assignments
a=1 b\
=2 c=3
echo Done:$?
echo abc=$a$b$c

View File

@ -0,0 +1,2 @@
Nothing:
String: #should-be-echoed

View File

@ -0,0 +1,2 @@
echo Nothing: #should-not-be-echoed
echo String: ""#should-be-echoed

View File

@ -0,0 +1 @@
Done:0

View File

@ -0,0 +1,18 @@
# bug was that we treated <newline> as ';' in this line:
true || echo foo |
echo BAD1 | cat
# variation on the same theme
true || echo foo |
# comment
echo BAD2 | cat
# variation on the same theme
true || echo foo |
echo BAD3 | cat
# this should error out, but currently works in hush:
#true || echo foo |;
echo Done:$?