hush: LINENO fix
Script triggering the bug: t=0 echo "at line ${LINENO}" while [ ${t} -lt 10 ]; do echo "at line ${LINENO}" # LINENO was 3 instead of 4 here t=$((t+1)) done function old new delta parse_stream 2754 2788 +34 done_word 711 738 +27 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 61/0) Total: 61 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
parent
68ae54243c
commit
5807e18f0c
101
shell/hush.c
101
shell/hush.c
@ -120,6 +120,11 @@
|
|||||||
//config: help
|
//config: help
|
||||||
//config: Enable {abc,def} extension.
|
//config: Enable {abc,def} extension.
|
||||||
//config:
|
//config:
|
||||||
|
//config:config HUSH_LINENO_VAR
|
||||||
|
//config: bool "$LINENO variable"
|
||||||
|
//config: default y
|
||||||
|
//config: depends on HUSH_BASH_COMPAT
|
||||||
|
//config:
|
||||||
//config:config HUSH_BASH_SOURCE_CURDIR
|
//config:config HUSH_BASH_SOURCE_CURDIR
|
||||||
//config: bool "'source' and '.' builtins search current directory after $PATH"
|
//config: bool "'source' and '.' builtins search current directory after $PATH"
|
||||||
//config: default n # do not encourage non-standard behavior
|
//config: default n # do not encourage non-standard behavior
|
||||||
@ -368,7 +373,6 @@
|
|||||||
#define BASH_SUBSTR ENABLE_HUSH_BASH_COMPAT
|
#define BASH_SUBSTR ENABLE_HUSH_BASH_COMPAT
|
||||||
#define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT
|
#define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT
|
||||||
#define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT
|
#define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT
|
||||||
#define BASH_LINENO_VAR ENABLE_HUSH_BASH_COMPAT
|
|
||||||
#define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
|
#define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
|
||||||
#define BASH_READ_D ENABLE_HUSH_BASH_COMPAT
|
#define BASH_READ_D ENABLE_HUSH_BASH_COMPAT
|
||||||
|
|
||||||
@ -620,7 +624,7 @@ typedef enum redir_type {
|
|||||||
struct command {
|
struct command {
|
||||||
pid_t pid; /* 0 if exited */
|
pid_t pid; /* 0 if exited */
|
||||||
int assignment_cnt; /* how many argv[i] are assignments? */
|
int assignment_cnt; /* how many argv[i] are assignments? */
|
||||||
#if BASH_LINENO_VAR
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
unsigned lineno;
|
unsigned lineno;
|
||||||
#endif
|
#endif
|
||||||
smallint cmd_type; /* CMD_xxx */
|
smallint cmd_type; /* CMD_xxx */
|
||||||
@ -942,7 +946,7 @@ struct globals {
|
|||||||
unsigned handled_SIGCHLD;
|
unsigned handled_SIGCHLD;
|
||||||
smallint we_have_children;
|
smallint we_have_children;
|
||||||
#endif
|
#endif
|
||||||
#if BASH_LINENO_VAR
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
unsigned lineno;
|
unsigned lineno;
|
||||||
char *lineno_var;
|
char *lineno_var;
|
||||||
#endif
|
#endif
|
||||||
@ -2152,7 +2156,7 @@ static int set_local_var(char *str, unsigned flags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
name_len = eq_sign - str + 1; /* including '=' */
|
name_len = eq_sign - str + 1; /* including '=' */
|
||||||
#if BASH_LINENO_VAR
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
if (G.lineno_var) {
|
if (G.lineno_var) {
|
||||||
if (name_len == 7 && strncmp("LINENO", str, 6) == 0)
|
if (name_len == 7 && strncmp("LINENO", str, 6) == 0)
|
||||||
G.lineno_var = NULL;
|
G.lineno_var = NULL;
|
||||||
@ -2285,7 +2289,7 @@ static int unset_local_var_len(const char *name, int name_len)
|
|||||||
if (name_len == 6 && strncmp(name, "OPTIND", 6) == 0)
|
if (name_len == 6 && strncmp(name, "OPTIND", 6) == 0)
|
||||||
G.getopt_count = 0;
|
G.getopt_count = 0;
|
||||||
#endif
|
#endif
|
||||||
#if BASH_LINENO_VAR
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
if (name_len == 6 && G.lineno_var && strncmp(name, "LINENO", 6) == 0)
|
if (name_len == 6 && G.lineno_var && strncmp(name, "LINENO", 6) == 0)
|
||||||
G.lineno_var = NULL;
|
G.lineno_var = NULL;
|
||||||
#endif
|
#endif
|
||||||
@ -2608,9 +2612,11 @@ static int i_getch(struct in_str *i)
|
|||||||
out:
|
out:
|
||||||
debug_printf("file_get: got '%c' %d\n", ch, ch);
|
debug_printf("file_get: got '%c' %d\n", ch, ch);
|
||||||
i->last_char = ch;
|
i->last_char = ch;
|
||||||
#if BASH_LINENO_VAR
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
if (ch == '\n')
|
if (ch == '\n') {
|
||||||
G.lineno++;
|
G.lineno++;
|
||||||
|
debug_printf_parse("G.lineno++ = %u\n", G.lineno);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return ch;
|
return ch;
|
||||||
}
|
}
|
||||||
@ -3412,8 +3418,13 @@ static void debug_print_tree(struct pipe *pi, int lvl)
|
|||||||
|
|
||||||
pin = 0;
|
pin = 0;
|
||||||
while (pi) {
|
while (pi) {
|
||||||
fdprintf(2, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "",
|
fdprintf(2, "%*spipe %d %sres_word=%s followup=%d %s\n",
|
||||||
pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]);
|
lvl*2, "",
|
||||||
|
pin,
|
||||||
|
(IF_HAS_KEYWORDS(pi->pi_inverted ? "! " :) ""),
|
||||||
|
RES[pi->res_word],
|
||||||
|
pi->followup, PIPE[pi->followup]
|
||||||
|
);
|
||||||
prn = 0;
|
prn = 0;
|
||||||
while (prn < pi->num_cmds) {
|
while (prn < pi->num_cmds) {
|
||||||
struct command *command = &pi->cmds[prn];
|
struct command *command = &pi->cmds[prn];
|
||||||
@ -3422,6 +3433,9 @@ static void debug_print_tree(struct pipe *pi, int lvl)
|
|||||||
fdprintf(2, "%*s cmd %d assignment_cnt:%d",
|
fdprintf(2, "%*s cmd %d assignment_cnt:%d",
|
||||||
lvl*2, "", prn,
|
lvl*2, "", prn,
|
||||||
command->assignment_cnt);
|
command->assignment_cnt);
|
||||||
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
|
fdprintf(2, " LINENO:%u", command->lineno);
|
||||||
|
#endif
|
||||||
if (command->group) {
|
if (command->group) {
|
||||||
fdprintf(2, " group %s: (argv=%p)%s%s\n",
|
fdprintf(2, " group %s: (argv=%p)%s%s\n",
|
||||||
CMDTYPE[command->cmd_type],
|
CMDTYPE[command->cmd_type],
|
||||||
@ -3494,8 +3508,9 @@ static int done_command(struct parse_context *ctx)
|
|||||||
ctx->command = command = &pi->cmds[pi->num_cmds];
|
ctx->command = command = &pi->cmds[pi->num_cmds];
|
||||||
clear_and_ret:
|
clear_and_ret:
|
||||||
memset(command, 0, sizeof(*command));
|
memset(command, 0, sizeof(*command));
|
||||||
#if BASH_LINENO_VAR
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
command->lineno = G.lineno;
|
command->lineno = G.lineno;
|
||||||
|
debug_printf_parse("command->lineno = G.lineno (%u)\n", G.lineno);
|
||||||
#endif
|
#endif
|
||||||
return pi->num_cmds; /* used only for 0/nonzero check */
|
return pi->num_cmds; /* used only for 0/nonzero check */
|
||||||
}
|
}
|
||||||
@ -3684,9 +3699,9 @@ static const struct reserved_combo* match_reserved_word(o_string *word)
|
|||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
/* Return 0: not a keyword, 1: keyword
|
/* Return NULL: not a keyword, else: keyword
|
||||||
*/
|
*/
|
||||||
static int reserved_word(o_string *word, struct parse_context *ctx)
|
static const struct reserved_combo* reserved_word(o_string *word, struct parse_context *ctx)
|
||||||
{
|
{
|
||||||
# if ENABLE_HUSH_CASE
|
# if ENABLE_HUSH_CASE
|
||||||
static const struct reserved_combo reserved_match = {
|
static const struct reserved_combo reserved_match = {
|
||||||
@ -3699,7 +3714,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
|
|||||||
return 0;
|
return 0;
|
||||||
r = match_reserved_word(word);
|
r = match_reserved_word(word);
|
||||||
if (!r)
|
if (!r)
|
||||||
return 0;
|
return r; /* NULL */
|
||||||
|
|
||||||
debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
|
debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
|
||||||
# if ENABLE_HUSH_CASE
|
# if ENABLE_HUSH_CASE
|
||||||
@ -3714,7 +3729,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
|
|||||||
ctx->ctx_res_w = RES_SNTX;
|
ctx->ctx_res_w = RES_SNTX;
|
||||||
}
|
}
|
||||||
ctx->ctx_inverted = 1;
|
ctx->ctx_inverted = 1;
|
||||||
return 1;
|
return r;
|
||||||
}
|
}
|
||||||
if (r->flag & FLAG_START) {
|
if (r->flag & FLAG_START) {
|
||||||
struct parse_context *old;
|
struct parse_context *old;
|
||||||
@ -3726,7 +3741,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
|
|||||||
} 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_error_at(word->data);
|
syntax_error_at(word->data);
|
||||||
ctx->ctx_res_w = RES_SNTX;
|
ctx->ctx_res_w = RES_SNTX;
|
||||||
return 1;
|
return r;
|
||||||
} else {
|
} else {
|
||||||
/* "{...} fi" is ok. "{...} if" is not
|
/* "{...} fi" is ok. "{...} if" is not
|
||||||
* Example:
|
* Example:
|
||||||
@ -3776,7 +3791,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
|
|||||||
*ctx = *old; /* physical copy */
|
*ctx = *old; /* physical copy */
|
||||||
free(old);
|
free(old);
|
||||||
}
|
}
|
||||||
return 1;
|
return r;
|
||||||
}
|
}
|
||||||
#endif /* HAS_KEYWORDS */
|
#endif /* HAS_KEYWORDS */
|
||||||
|
|
||||||
@ -3842,9 +3857,26 @@ static int done_word(o_string *word, struct parse_context *ctx)
|
|||||||
&& ctx->ctx_res_w != RES_CASE
|
&& ctx->ctx_res_w != RES_CASE
|
||||||
# endif
|
# endif
|
||||||
) {
|
) {
|
||||||
int reserved = reserved_word(word, ctx);
|
const struct reserved_combo *reserved;
|
||||||
debug_printf_parse("checking for reserved-ness: %d\n", reserved);
|
reserved = reserved_word(word, ctx);
|
||||||
|
debug_printf_parse("checking for reserved-ness: %d\n", !!reserved);
|
||||||
if (reserved) {
|
if (reserved) {
|
||||||
|
# if ENABLE_HUSH_LINENO_VAR
|
||||||
|
/* Case:
|
||||||
|
* "while ...; do
|
||||||
|
* cmd ..."
|
||||||
|
* If we don't close the pipe _now_, immediately after "do", lineno logic
|
||||||
|
* sees "cmd" as starting at "do" - i.e., at the previous line.
|
||||||
|
*/
|
||||||
|
if (0
|
||||||
|
IF_HUSH_IF(|| reserved->res == RES_THEN)
|
||||||
|
IF_HUSH_IF(|| reserved->res == RES_ELIF)
|
||||||
|
IF_HUSH_IF(|| reserved->res == RES_ELSE)
|
||||||
|
IF_HUSH_LOOPS(|| reserved->res == RES_DO)
|
||||||
|
) {
|
||||||
|
done_pipe(ctx, PIPE_SEQ);
|
||||||
|
}
|
||||||
|
# endif
|
||||||
o_reset_to_empty_unquoted(word);
|
o_reset_to_empty_unquoted(word);
|
||||||
debug_printf_parse("done_word return %d\n",
|
debug_printf_parse("done_word return %d\n",
|
||||||
(ctx->ctx_res_w == RES_SNTX));
|
(ctx->ctx_res_w == RES_SNTX));
|
||||||
@ -4979,6 +5011,27 @@ static struct pipe *parse_stream(char **pstring,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_blank) {
|
if (is_blank) {
|
||||||
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
|
/* Case:
|
||||||
|
* "while ...; do<whitespace><newline>
|
||||||
|
* cmd ..."
|
||||||
|
* would think that "cmd" starts in <whitespace> -
|
||||||
|
* i.e., at the previous line.
|
||||||
|
* We need to skip all whitespace before newlines.
|
||||||
|
*/
|
||||||
|
if (ch != '\n') {
|
||||||
|
/* It was whitespace, but not a newline.
|
||||||
|
* Eat all whitespace.
|
||||||
|
*/
|
||||||
|
for (;;) {
|
||||||
|
next = i_peek(input);
|
||||||
|
if (next != ' ' && next != '\t' && next != '\n')
|
||||||
|
break; /* next char is not ws */
|
||||||
|
ch = i_getch(input);
|
||||||
|
}
|
||||||
|
/* ch == last eaten whitespace char */
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (done_word(&dest, &ctx)) {
|
if (done_word(&dest, &ctx)) {
|
||||||
goto parse_error;
|
goto parse_error;
|
||||||
}
|
}
|
||||||
@ -6573,7 +6626,7 @@ static void parse_and_run_string(const char *s)
|
|||||||
static void parse_and_run_file(FILE *f)
|
static void parse_and_run_file(FILE *f)
|
||||||
{
|
{
|
||||||
struct in_str input;
|
struct in_str input;
|
||||||
#if BASH_LINENO_VAR
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
unsigned sv;
|
unsigned sv;
|
||||||
|
|
||||||
sv = G.lineno;
|
sv = G.lineno;
|
||||||
@ -6581,7 +6634,7 @@ static void parse_and_run_file(FILE *f)
|
|||||||
#endif
|
#endif
|
||||||
setup_file_in_str(&input, f);
|
setup_file_in_str(&input, f);
|
||||||
parse_and_run_stream(&input, ';');
|
parse_and_run_stream(&input, ';');
|
||||||
#if BASH_LINENO_VAR
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
G.lineno = sv;
|
G.lineno = sv;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -8130,7 +8183,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
|
|||||||
char **new_env = NULL;
|
char **new_env = NULL;
|
||||||
struct variable *old_vars = NULL;
|
struct variable *old_vars = NULL;
|
||||||
|
|
||||||
#if BASH_LINENO_VAR
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
if (G.lineno_var)
|
if (G.lineno_var)
|
||||||
strcpy(G.lineno_var + sizeof("LINENO=")-1, utoa(command->lineno));
|
strcpy(G.lineno_var + sizeof("LINENO=")-1, utoa(command->lineno));
|
||||||
#endif
|
#endif
|
||||||
@ -8339,7 +8392,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
|
|||||||
if (cmd_no < pi->num_cmds)
|
if (cmd_no < pi->num_cmds)
|
||||||
xpiped_pair(pipefds);
|
xpiped_pair(pipefds);
|
||||||
|
|
||||||
#if BASH_LINENO_VAR
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
if (G.lineno_var)
|
if (G.lineno_var)
|
||||||
strcpy(G.lineno_var + sizeof("LINENO=")-1, utoa(command->lineno));
|
strcpy(G.lineno_var + sizeof("LINENO=")-1, utoa(command->lineno));
|
||||||
#endif
|
#endif
|
||||||
@ -9057,8 +9110,8 @@ int hush_main(int argc, char **argv)
|
|||||||
*/
|
*/
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if BASH_LINENO_VAR
|
#if ENABLE_HUSH_LINENO_VAR
|
||||||
if (BASH_LINENO_VAR) {
|
if (ENABLE_HUSH_LINENO_VAR) {
|
||||||
char *p = xasprintf("LINENO=%*s", (int)(sizeof(int)*3), "");
|
char *p = xasprintf("LINENO=%*s", (int)(sizeof(int)*3), "");
|
||||||
set_local_var(p, /*flags*/ 0);
|
set_local_var(p, /*flags*/ 0);
|
||||||
G.lineno_var = p; /* can't assign before set_local_var("LINENO=...") */
|
G.lineno_var = p; /* can't assign before set_local_var("LINENO=...") */
|
||||||
|
Loading…
Reference in New Issue
Block a user