From 7c5f18a3bab721cdfa515220ad8d481643aaae23 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 26 Jul 2018 15:21:50 +0200 Subject: hush: improve set -x: make "+++" indent level increase in `cmd` and eval. function old new delta dump_cmd_in_x_mode 126 144 +18 run_pipe 1873 1883 +10 builtin_eval 119 127 +8 expand_vars_to_list 1100 1103 +3 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 4/0 up/down: 39/0) Total: 39 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index e3c6e2de9..02fb1b5ef 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -896,6 +896,7 @@ struct globals { char o_opt[NUM_OPT_O]; #if ENABLE_HUSH_MODE_X + smalluint x_mode_depth; # define G_x_mode (G.o_opt[OPT_O_XTRACE]) #else # define G_x_mode 0 @@ -7182,11 +7183,8 @@ static int generate_stream_from_string(const char *s, pid_t *pid_p) + (1 << SIGTTIN) + (1 << SIGTTOU) , SIG_IGN); - CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ close(channel[0]); /* NB: close _first_, then move fd! */ xmove_fd(channel[1], 1); - /* Prevent it from trying to handle ctrl-z etc */ - IF_HUSH_JOB(G.run_list_level = 1;) # if ENABLE_HUSH_TRAP /* Awful hack for `trap` or $(trap). * @@ -7233,7 +7231,11 @@ static int generate_stream_from_string(const char *s, pid_t *pid_p) } # endif # if BB_MMU + /* Prevent it from trying to handle ctrl-z etc */ + IF_HUSH_JOB(G.run_list_level = 1;) + CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ reset_traps_to_defaults(); + IF_HUSH_MODE_X(G.x_mode_depth++;) parse_and_run_string(s); _exit(G.last_exitcode); # else @@ -8022,13 +8024,14 @@ static void dump_cmd_in_x_mode(char **argv) int len; int n; - len = 3; + len = G.x_mode_depth + 3; /* "+[+++...]\n\0" */ n = 0; while (argv[n]) len += strlen(argv[n++]) + 1; - buf = xmalloc(len); - buf[0] = '+'; - p = buf + 1; + p = buf = xmalloc(len); + n = G.x_mode_depth; + while (n-- >= 0) + *p++ = '+'; n = 0; while (argv[n]) p += sprintf(p, " %s", argv[n++]); @@ -8821,8 +8824,13 @@ static NOINLINE int run_pipe(struct pipe *pi) restore_redirects(squirrel); /* Set shell variables */ - if (G_x_mode) - bb_putchar_stderr('+'); +#if ENABLE_HUSH_MODE_X + if (G_x_mode) { + int n = G.x_mode_depth; + while (n-- >= 0) + bb_putchar_stderr('+'); + } +#endif i = 0; while (i < command->assignment_cnt) { char *p = expand_string_to_string(argv[i], @@ -10226,6 +10234,7 @@ static int FAST_FUNC builtin_eval(char **argv) if (!argv[0]) return EXIT_SUCCESS; + IF_HUSH_MODE_X(G.x_mode_depth++;) if (!argv[1]) { /* bash: * eval "echo Hi; done" ("done" is syntax error): @@ -10255,6 +10264,7 @@ static int FAST_FUNC builtin_eval(char **argv) parse_and_run_string(str); free(str); } + IF_HUSH_MODE_X(G.x_mode_depth--;) return G.last_exitcode; } -- cgit v1.2.3-55-g6feb From 186cf4976768029113cf8438734a65bf2c489c5c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 27 Jul 2018 12:14:39 +0200 Subject: hush: in some cases, expand_on_ifs() relied of uninitialized memory The n > 0 check to prevent access to the last byte of non-existing argv[-1] wasn't enough. Switched to making sure there are initialized (zero) bytes there. A predictable testcase is rather hard to construct, unfortunately, contents of memory depends on allocator behavior and whatnot. function old new delta o_save_ptr_helper 119 137 +18 expand_on_ifs 345 339 -6 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 18/-6) Total: 12 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 02fb1b5ef..1ac2db381 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -3069,6 +3069,13 @@ static int o_save_ptr_helper(o_string *o, int n) o->data = xrealloc(o->data, o->maxlen + 1); list = (char**)o->data; memmove(list + n + 0x10, list + n, string_len); + /* + * expand_on_ifs() has a "previous argv[] ends in IFS?" + * check. (grep for -prev-ifs-check-). + * Ensure that argv[-1][last] is not garbage + * but zero bytes, to save index check there. + */ + list[n + 0x10 - 1] = 0; o->length += 0x10 * sizeof(list[0]); } else { debug_printf_list("list[%d]=%d string_start=%d\n", @@ -5797,12 +5804,16 @@ static int expand_on_ifs(o_string *output, int n, const char *str) /* Start new word... but not always! */ /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */ if (output->has_quoted_part - /* Case "v=' a'; echo $v": + /* + * Case "v=' a'; echo $v": * here nothing precedes the space in $v expansion, * therefore we should not finish the word * (IOW: if there *is* word to finalize, only then do it): + * It's okay if this accesses the byte before first argv[]: + * past call to o_save_ptr() cleared it to zero byte + * (grep for -prev-ifs-check-). */ - || (n > 0 && output->data[output->length - 1]) + || output->data[output->length - 1] ) { new_word: o_addchr(output, '\0'); -- cgit v1.2.3-55-g6feb From 9dda9270df3108bb85b29c6d382a3477aeb3344b Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 27 Jul 2018 14:12:05 +0200 Subject: hush: fix "set -x" output prefix overlapping for v="..`cmd`.." case Was printing initial "+" prefix for the assignment, that printing "+ cmd" then printing the expanded " v=VAL" string. Delay printing of "+" prefix for the assignment to after expansion. function old new delta run_pipe 1883 1902 +19 builtin_eval 127 133 +6 expand_vars_to_list 1103 1106 +3 dump_cmd_in_x_mode 144 142 -2 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/1 up/down: 28/-2) Total: 26 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 1ac2db381..e9212cefc 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -896,7 +896,6 @@ struct globals { char o_opt[NUM_OPT_O]; #if ENABLE_HUSH_MODE_X - smalluint x_mode_depth; # define G_x_mode (G.o_opt[OPT_O_XTRACE]) #else # define G_x_mode 0 @@ -955,6 +954,9 @@ struct globals { unsigned func_nest_level; /* solely to prevent "local v" in non-functions */ # endif struct function *top_func; +#endif +#if ENABLE_HUSH_MODE_X + unsigned x_mode_depth; #endif /* Signal and trap handling */ #if ENABLE_HUSH_FAST @@ -7247,6 +7249,7 @@ static int generate_stream_from_string(const char *s, pid_t *pid_p) CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ reset_traps_to_defaults(); IF_HUSH_MODE_X(G.x_mode_depth++;) + //bb_error_msg("%s: ++x_mode_depth=%d", __func__, G.x_mode_depth); parse_and_run_string(s); _exit(G.last_exitcode); # else @@ -8032,17 +8035,16 @@ static void dump_cmd_in_x_mode(char **argv) if (G_x_mode && argv) { /* We want to output the line in one write op */ char *buf, *p; - int len; - int n; + unsigned len; + unsigned n; - len = G.x_mode_depth + 3; /* "+[+++...]\n\0" */ + len = G.x_mode_depth + 3; /* "+[+++...][ cmd...]\n\0" */ n = 0; while (argv[n]) len += strlen(argv[n++]) + 1; p = buf = xmalloc(len); n = G.x_mode_depth; - while (n-- >= 0) - *p++ = '+'; + do *p++ = '+'; while ((int)(--n) >= 0); n = 0; while (argv[n]) p += sprintf(p, " %s", argv[n++]); @@ -8835,21 +8837,23 @@ static NOINLINE int run_pipe(struct pipe *pi) restore_redirects(squirrel); /* Set shell variables */ -#if ENABLE_HUSH_MODE_X - if (G_x_mode) { - int n = G.x_mode_depth; - while (n-- >= 0) - bb_putchar_stderr('+'); - } -#endif i = 0; while (i < command->assignment_cnt) { char *p = expand_string_to_string(argv[i], EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1 ); - if (G_x_mode) +#if ENABLE_HUSH_MODE_X + if (G_x_mode) { + if (i == 0) { + unsigned n = G.x_mode_depth; + do + bb_putchar_stderr('+'); + while ((int)(--n) >= 0); + } fprintf(stderr, " %s", p); + } +#endif debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); if (set_local_var(p, /*flag:*/ 0)) { /* assignment to readonly var / putenv error? */ @@ -10246,6 +10250,7 @@ static int FAST_FUNC builtin_eval(char **argv) return EXIT_SUCCESS; IF_HUSH_MODE_X(G.x_mode_depth++;) + //bb_error_msg("%s: ++x_mode_depth=%d", __func__, G.x_mode_depth); if (!argv[1]) { /* bash: * eval "echo Hi; done" ("done" is syntax error): @@ -10276,6 +10281,7 @@ static int FAST_FUNC builtin_eval(char **argv) free(str); } IF_HUSH_MODE_X(G.x_mode_depth--;) + //bb_error_msg("%s: --x_mode_depth=%d", __func__, G.x_mode_depth); return G.last_exitcode; } -- cgit v1.2.3-55-g6feb From 4b70c926bcbfaf6df6e21c98ea096b0db8629095 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 27 Jul 2018 17:42:38 +0200 Subject: hush: make "set -x" output closer to bash function old new delta print_optionally_squoted - 145 +145 run_pipe 1902 1919 +17 dump_cmd_in_x_mode 142 110 -32 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 1/1 up/down: 162/-32) Total: 130 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 18 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index e9212cefc..ac8467fb4 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -8030,28 +8030,67 @@ static void execvp_or_die(char **argv) } #if ENABLE_HUSH_MODE_X +static void print_optionally_squoted(FILE *fp, const char *str) +{ + unsigned len; + const char *cp; + + cp = str; + if (str[0] != '{' && str[0] != '(') for (;;) { + if (!*cp) { + /* string has no special chars */ + fputs(str, fp); + return; + } + if (*cp == '\\') break; + if (*cp == '\'') break; + if (*cp == '"') break; + if (*cp == '$') break; + if (*cp == '!') break; + if (*cp == '*') break; + if (*cp == '[') break; + if (*cp == ']') break; +#if ENABLE_HUSH_TICK + if (*cp == '`') break; +#endif + if (isspace(*cp)) break; + cp++; + } + + cp = str; + for (;;) { + /* print '....' up to EOL or first squote */ + len = (int)(strchrnul(cp, '\'') - cp); + if (len != 0) { + fprintf(fp, "'%.*s'", len, cp); + cp += len; + } + if (*cp == '\0') + break; + /* string contains squote(s), print them as \' */ + fprintf(fp, "\\'"); + cp++; + } +} static void dump_cmd_in_x_mode(char **argv) { if (G_x_mode && argv) { - /* We want to output the line in one write op */ - char *buf, *p; - unsigned len; unsigned n; - len = G.x_mode_depth + 3; /* "+[+++...][ cmd...]\n\0" */ - n = 0; - while (argv[n]) - len += strlen(argv[n++]) + 1; - p = buf = xmalloc(len); + /* "+[+++...][ cmd...]\n\0" */ n = G.x_mode_depth; - do *p++ = '+'; while ((int)(--n) >= 0); + do bb_putchar_stderr('+'); while ((int)(--n) >= 0); n = 0; - while (argv[n]) - p += sprintf(p, " %s", argv[n++]); - *p++ = '\n'; - *p = '\0'; - fputs(buf, stderr); - free(buf); + while (argv[n]) { + if (argv[n][0] == '\0') + fputs(" ''", stderr); + else { + bb_putchar_stderr(' '); + print_optionally_squoted(stderr, argv[n]); + } + n++; + } + bb_putchar_stderr('\n'); } } #else @@ -8845,13 +8884,18 @@ static NOINLINE int run_pipe(struct pipe *pi) ); #if ENABLE_HUSH_MODE_X if (G_x_mode) { + char *eq; if (i == 0) { unsigned n = G.x_mode_depth; do bb_putchar_stderr('+'); while ((int)(--n) >= 0); } - fprintf(stderr, " %s", p); + eq = strchrnul(p, '='); + fprintf(stderr, " %.*s=", (int)(eq - p), p); + if (*eq) + print_optionally_squoted(stderr, eq + 1); + bb_putchar_stderr('\n'); } #endif debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); @@ -8861,8 +8905,6 @@ static NOINLINE int run_pipe(struct pipe *pi) } i++; } - if (G_x_mode) - bb_putchar_stderr('\n'); /* Redirect error sets $? to 1. Otherwise, * if evaluating assignment value set $?, retain it. * Else, clear $?: -- cgit v1.2.3-55-g6feb From aa449c927d1d84092f9654e45ab9f68847e81226 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 28 Jul 2018 12:13:58 +0200 Subject: hush: make "set -x" output don't redirectable when fd#2 redirected function old new delta x_mode_print_optionally_squoted - 120 +120 x_mode_flush - 68 +68 save_fd_on_redirect 208 243 +35 x_mode_prefix - 27 +27 x_mode_addblock - 23 +23 x_mode_addchr - 17 +17 dump_cmd_in_x_mode 110 85 -25 run_pipe 1919 1890 -29 print_optionally_squoted 145 - -145 ------------------------------------------------------------------------------ (add/remove: 5/1 grow/shrink: 1/2 up/down: 290/-199) Total: 91 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 129 ++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 84 insertions(+), 45 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index ac8467fb4..9676819fa 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -954,9 +954,6 @@ struct globals { unsigned func_nest_level; /* solely to prevent "local v" in non-functions */ # endif struct function *top_func; -#endif -#if ENABLE_HUSH_MODE_X - unsigned x_mode_depth; #endif /* Signal and trap handling */ #if ENABLE_HUSH_FAST @@ -993,6 +990,15 @@ struct globals { #if ENABLE_HUSH_MEMLEAK unsigned long memleak_value; #endif +#if ENABLE_HUSH_MODE_X + unsigned x_mode_depth; + /* "set -x" output should not be redirectable with subsequent 2>FILE. + * We dup fd#2 to x_mode_fd when "set -x" is executed, and use it + * for all subsequent output. + */ + int x_mode_fd; + o_string x_mode_buf; +#endif #if HUSH_DEBUG int debug_indent; #endif @@ -1660,6 +1666,12 @@ static int move_HFILEs_on_redirect(int fd, int avoid_fd) } fl = fl->next_hfile; } +#if ENABLE_HUSH_MODE_X + if (G.x_mode_fd > 0 && fd == G.x_mode_fd) { + G.x_mode_fd = xdup_CLOEXEC_and_close(fd, avoid_fd); + return 1; /* "found and moved" */ + } +#endif return 0; /* "not in the list" */ } #if ENABLE_FEATURE_SH_STANDALONE && BB_MMU @@ -2903,6 +2915,11 @@ static void o_addstr(o_string *o, const char *str) o_addblock(o, str, strlen(str)); } +static void o_addstr_with_NUL(o_string *o, const char *str) +{ + o_addblock(o, str, strlen(str) + 1); +} + #if !BB_MMU static void nommu_addchr(o_string *o, int ch) { @@ -2913,10 +2930,36 @@ static void nommu_addchr(o_string *o, int ch) # define nommu_addchr(o, str) ((void)0) #endif -static void o_addstr_with_NUL(o_string *o, const char *str) +#if ENABLE_HUSH_MODE_X +static void x_mode_addchr(int ch) { - o_addblock(o, str, strlen(str) + 1); + o_addchr(&G.x_mode_buf, ch); } +static void x_mode_addstr(const char *str) +{ + o_addstr(&G.x_mode_buf, str); +} +static void x_mode_addblock(const char *str, int len) +{ + o_addblock(&G.x_mode_buf, str, len); +} +static void x_mode_prefix(void) +{ + int n = G.x_mode_depth; + do x_mode_addchr('+'); while (--n >= 0); +} +static void x_mode_flush(void) +{ + int len = G.x_mode_buf.length; + if (len <= 0) + return; + if (G.x_mode_fd > 0) { + G.x_mode_buf.data[len] = '\n'; + full_write(G.x_mode_fd, G.x_mode_buf.data, len + 1); + } + G.x_mode_buf.length = 0; +} +#endif /* * HUSH_BRACE_EXPANSION code needs corresponding quoting on variable expansion side. @@ -8030,31 +8073,26 @@ static void execvp_or_die(char **argv) } #if ENABLE_HUSH_MODE_X -static void print_optionally_squoted(FILE *fp, const char *str) +static void x_mode_print_optionally_squoted(const char *str) { unsigned len; const char *cp; cp = str; - if (str[0] != '{' && str[0] != '(') for (;;) { - if (!*cp) { - /* string has no special chars */ - fputs(str, fp); - return; - } - if (*cp == '\\') break; - if (*cp == '\'') break; - if (*cp == '"') break; - if (*cp == '$') break; - if (*cp == '!') break; - if (*cp == '*') break; - if (*cp == '[') break; - if (*cp == ']') break; -#if ENABLE_HUSH_TICK - if (*cp == '`') break; -#endif - if (isspace(*cp)) break; - cp++; + + /* the set of chars which-cause-string-to-be-squoted mimics bash */ + /* test a char with: bash -c 'set -x; echo "CH"' */ + if (str[strcspn(str, "\\\"'`$(){}[]<>;#&|~*?!^" + " " "\001\002\003\004\005\006\007" + "\010\011\012\013\014\015\016\017" + "\020\021\022\023\024\025\026\027" + "\030\031\032\033\034\035\036\037" + ) + ] == '\0' + ) { + /* string has no special chars */ + x_mode_addstr(str); + return; } cp = str; @@ -8062,13 +8100,16 @@ static void print_optionally_squoted(FILE *fp, const char *str) /* print '....' up to EOL or first squote */ len = (int)(strchrnul(cp, '\'') - cp); if (len != 0) { - fprintf(fp, "'%.*s'", len, cp); + x_mode_addchr('\''); + x_mode_addblock(cp, len); + x_mode_addchr('\''); cp += len; } if (*cp == '\0') break; /* string contains squote(s), print them as \' */ - fprintf(fp, "\\'"); + x_mode_addchr('\\'); + x_mode_addchr('\''); cp++; } } @@ -8078,19 +8119,19 @@ static void dump_cmd_in_x_mode(char **argv) unsigned n; /* "+[+++...][ cmd...]\n\0" */ - n = G.x_mode_depth; - do bb_putchar_stderr('+'); while ((int)(--n) >= 0); + x_mode_prefix(); n = 0; while (argv[n]) { - if (argv[n][0] == '\0') - fputs(" ''", stderr); - else { - bb_putchar_stderr(' '); - print_optionally_squoted(stderr, argv[n]); + x_mode_addchr(' '); + if (argv[n][0] == '\0') { + x_mode_addchr('\''); + x_mode_addchr('\''); + } else { + x_mode_print_optionally_squoted(argv[n]); } n++; } - bb_putchar_stderr('\n'); + x_mode_flush(); } } #else @@ -8885,17 +8926,14 @@ static NOINLINE int run_pipe(struct pipe *pi) #if ENABLE_HUSH_MODE_X if (G_x_mode) { char *eq; - if (i == 0) { - unsigned n = G.x_mode_depth; - do - bb_putchar_stderr('+'); - while ((int)(--n) >= 0); - } + if (i == 0) + x_mode_prefix(); + x_mode_addchr(' '); eq = strchrnul(p, '='); - fprintf(stderr, " %.*s=", (int)(eq - p), p); - if (*eq) - print_optionally_squoted(stderr, eq + 1); - bb_putchar_stderr('\n'); + if (*eq) eq++; + x_mode_addblock(p, (eq - p)); + x_mode_print_optionally_squoted(eq); + x_mode_flush(); } #endif debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); @@ -9691,6 +9729,7 @@ static int set_mode(int state, char mode, const char *o_opt) break; case 'x': IF_HUSH_MODE_X(G_x_mode = state;) + IF_HUSH_MODE_X(if (G.x_mode_fd <= 0) G.x_mode_fd = dup_CLOEXEC(2, 10);) break; case 'o': if (!o_opt) { -- cgit v1.2.3-55-g6feb From a8e7441176ec945a1bfb117a1067ac3a6680a19c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 28 Jul 2018 12:16:30 +0200 Subject: hush: disable debug_indent increment/decrement for HUSH_DEBUG < 2 builds function old new delta run_list 1063 1046 -17 parse_stream 2296 2249 -47 run_pipe 1890 1840 -50 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/3 up/down: 0/-114) Total: -114 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 9676819fa..14681aa48 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -999,7 +999,7 @@ struct globals { int x_mode_fd; o_string x_mode_buf; #endif -#if HUSH_DEBUG +#if HUSH_DEBUG >= 2 int debug_indent; #endif struct sigaction sa; @@ -1221,7 +1221,7 @@ static const struct built_in_command bltins2[] = { /* Debug printouts. */ -#if HUSH_DEBUG +#if HUSH_DEBUG >= 2 /* prevent disasters with G.debug_indent < 0 */ # define indent() fdprintf(2, "%*s", (G.debug_indent * 2) & 0xff, "") # define debug_enter() (G.debug_indent++) -- cgit v1.2.3-55-g6feb From a5db1d7354977fe295882ff557e8f5fb23741b13 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 28 Jul 2018 12:42:08 +0200 Subject: hush: fix another case where empty "for" wasn't setting exitcode to 0 Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-misc/empty_for1.right | 1 + shell/ash_test/ash-misc/empty_for1.tests | 5 +++++ shell/hush.c | 2 +- shell/hush_test/hush-misc/empty_for1.right | 1 + shell/hush_test/hush-misc/empty_for1.tests | 5 +++++ 5 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 shell/ash_test/ash-misc/empty_for1.right create mode 100755 shell/ash_test/ash-misc/empty_for1.tests create mode 100644 shell/hush_test/hush-misc/empty_for1.right create mode 100755 shell/hush_test/hush-misc/empty_for1.tests (limited to 'shell') diff --git a/shell/ash_test/ash-misc/empty_for1.right b/shell/ash_test/ash-misc/empty_for1.right new file mode 100644 index 000000000..46ffcece7 --- /dev/null +++ b/shell/ash_test/ash-misc/empty_for1.right @@ -0,0 +1 @@ +Zero:0 diff --git a/shell/ash_test/ash-misc/empty_for1.tests b/shell/ash_test/ash-misc/empty_for1.tests new file mode 100755 index 000000000..5a2554d54 --- /dev/null +++ b/shell/ash_test/ash-misc/empty_for1.tests @@ -0,0 +1,5 @@ +false +for v; do + exit 2 +done +echo Zero:$? diff --git a/shell/hush.c b/shell/hush.c index 14681aa48..d17f7f29e 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9391,11 +9391,11 @@ static int run_list(struct pipe *pi) }; /* argv list with one element: "$@" */ char **vals; + G.last_exitcode = rcode = EXIT_SUCCESS; vals = (char**)encoded_dollar_at_argv; if (pi->next->res_word == RES_IN) { /* if no variable values after "in" we skip "for" */ if (!pi->next->cmds[0].argv) { - G.last_exitcode = rcode = EXIT_SUCCESS; debug_printf_exec(": null FOR: exitcode EXIT_SUCCESS\n"); break; } diff --git a/shell/hush_test/hush-misc/empty_for1.right b/shell/hush_test/hush-misc/empty_for1.right new file mode 100644 index 000000000..46ffcece7 --- /dev/null +++ b/shell/hush_test/hush-misc/empty_for1.right @@ -0,0 +1 @@ +Zero:0 diff --git a/shell/hush_test/hush-misc/empty_for1.tests b/shell/hush_test/hush-misc/empty_for1.tests new file mode 100755 index 000000000..5a2554d54 --- /dev/null +++ b/shell/hush_test/hush-misc/empty_for1.tests @@ -0,0 +1,5 @@ +false +for v; do + exit 2 +done +echo Zero:$? -- cgit v1.2.3-55-g6feb From 54fdabda3b953087f669bfcba99b9ae3b0c09fec Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 31 Jul 2018 10:36:29 +0200 Subject: hush: speed up ${var:+ARG} for literal ARGs function old new delta first_special_char_in_vararg - 52 +52 expand_one_var 2248 2296 +48 encode_then_expand_vararg 357 336 -21 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 1/1 up/down: 100/-21) Total: 79 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 58 +++++++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 27 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index d17f7f29e..6852b5f79 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5913,6 +5913,26 @@ static char *encode_then_expand_string(const char *str) return exp_str; } +static const char *first_special_char_in_vararg(const char *cp) +{ + for (;;) { + if (!*cp) return NULL; /* string has no special chars */ + if (*cp == '$') return cp; + if (*cp == '\\') return cp; + if (*cp == '\'') return cp; + if (*cp == '"') return cp; +#if ENABLE_HUSH_TICK + if (*cp == '`') return cp; +#endif + /* dquoted "${x:+ARG}" should not glob, therefore + * '*' et al require some non-literal processing: */ + if (*cp == '*') return cp; + if (*cp == '?') return cp; + if (*cp == '[') return cp; + cp++; + } +} + /* Expanding ARG in ${var#ARG}, ${var%ARG}, or ${var/ARG/ARG}. * These can contain single- and double-quoted strings, * and treated as if the ARG string is initially unquoted. IOW: @@ -5932,19 +5952,10 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int char *exp_str; struct in_str input; o_string dest = NULL_O_STRING; - const char *cp; - cp = str; - for (;;) { - if (!*cp) return NULL; /* string has no special chars */ - if (*cp == '$') break; - if (*cp == '\\') break; - if (*cp == '\'') break; - if (*cp == '"') break; -#if ENABLE_HUSH_TICK - if (*cp == '`') break; -#endif - cp++; + if (!first_special_char_in_vararg(str)) { + /* string has no special chars */ + return NULL; } setup_string_in_str(&input, str); @@ -6025,26 +6036,19 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int /* Expanding ARG in ${var+ARG}, ${var-ARG} */ static int encode_then_append_var_plusminus(o_string *output, int n, - const char *str, int dquoted) + char *str, int dquoted) { struct in_str input; o_string dest = NULL_O_STRING; -#if 0 //todo? - const char *cp; - cp = str; - for (;;) { - if (!*cp) return NULL; /* string has no special chars */ - if (*cp == '$') break; - if (*cp == '\\') break; - if (*cp == '\'') break; - if (*cp == '"') break; -#if ENABLE_HUSH_TICK - if (*cp == '`') break; -#endif - cp++; + if (!first_special_char_in_vararg(str) + && '\0' == str[strcspn(str, G.ifs)] + ) { + /* string has no special chars + * && string has no $IFS chars + */ + return expand_vars_to_list(output, n, str); } -#endif setup_string_in_str(&input, str); -- cgit v1.2.3-55-g6feb From c2aa218f23a4e952746ebef7bb86668c6255471c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 4 Aug 2018 22:25:28 +0200 Subject: ash,hush: properly handle ${v//pattern/repl} if pattern starts with / Closes 2695 function old new delta parse_dollar 762 790 +28 subevalvar 1258 1267 +9 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 37/0) Total: 37 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 10 +++++++++- .../ash-vars/var_bash_pattern_starting_with_slash.right | 2 ++ .../ash-vars/var_bash_pattern_starting_with_slash.tests | 3 +++ shell/hush.c | 9 +++++++++ .../hush-vars/var_bash_pattern_starting_with_slash.right | 2 ++ .../hush-vars/var_bash_pattern_starting_with_slash.tests | 3 +++ 6 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.right create mode 100755 shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.tests create mode 100644 shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.right create mode 100755 shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.tests (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 03fbbee53..5c431c9ff 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -6854,8 +6854,15 @@ subevalvar(char *p, char *varname, int strloc, int subtype, if (subtype == VSREPLACE || subtype == VSREPLACEALL) { /* Find '/' and replace with NUL */ repl = p; + /* The pattern can't be empty. + * IOW: if the first char after "${v//" is a slash, + * it does not terminate the pattern - it's the first char of the pattern: + * v=/dev/ram; echo ${v////-} prints -dev-ram (pattern is "/") + * v=/dev/ram; echo ${v///r/-} prints /dev-am (pattern is "/r") + */ + if (*repl == '/') + repl++; for (;;) { - /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */ if (*repl == '\0') { repl = NULL; break; @@ -6864,6 +6871,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype, *repl = '\0'; break; } + /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */ if ((unsigned char)*repl == CTLESC && repl[1]) repl++; repl++; diff --git a/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.right b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.right new file mode 100644 index 000000000..439dca578 --- /dev/null +++ b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.right @@ -0,0 +1,2 @@ +-dev-ram +/dev-am diff --git a/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.tests b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.tests new file mode 100755 index 000000000..b83fb8eeb --- /dev/null +++ b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.tests @@ -0,0 +1,3 @@ +v=/dev/ram +echo ${v////-} +echo ${v///r/-} diff --git a/shell/hush.c b/shell/hush.c index 6852b5f79..3407711cd 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4930,6 +4930,15 @@ static int parse_dollar(o_string *as_string, end_ch = '}' * 0x100 + '/'; } o_addchr(dest, ch); + /* The pattern can't be empty. + * IOW: if the first char after "${v//" is a slash, + * it does not terminate the pattern - it's the first char of the pattern: + * v=/dev/ram; echo ${v////-} prints -dev-ram (pattern is "/") + * v=/dev/ram; echo ${v///r/-} prints /dev-am (pattern is "/r") + */ + if (i_peek(input) == '/') { + o_addchr(dest, i_getch(input)); + } again: if (!BB_MMU) pos = dest->length; diff --git a/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.right b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.right new file mode 100644 index 000000000..439dca578 --- /dev/null +++ b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.right @@ -0,0 +1,2 @@ +-dev-ram +/dev-am diff --git a/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.tests b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.tests new file mode 100755 index 000000000..b83fb8eeb --- /dev/null +++ b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.tests @@ -0,0 +1,3 @@ +v=/dev/ram +echo ${v////-} +echo ${v///r/-} -- cgit v1.2.3-55-g6feb From 9abf53beb48b40e98d50fd35f445d6474af3a494 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 5 Aug 2018 10:39:18 +0200 Subject: ash: eval: Variable assignments on functions are no longer persistent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream commit: Date: Wed, 4 Apr 2018 17:54:01 +0800 eval: Variable assignments on functions are no longer persistent Dirk Fieldhouse wrote: > In POSIX.1-2017 ("simultaneously IEEE Std 1003.1™-2017 and The Open > Group Technical Standard Base Specifications, Issue 7") > , > we read under '2.9.1 Simple Commands' > > "Variable assignments shall be performed as follows: > ... > - If the command name is a standard utility implemented as a function > (see XBD Utility), the effect of variable assignments shall be as if the > utility was not implemented as a function. > ... > - If the command name is a function that is not a standard utility > implemented as a function, variable assignments shall affect the current > execution environment during the execution of the function. It is > unspecified: > > * Whether or not the variable assignments persist after the > completion of the function > > * Whether or not the variables gain the export attribute during > the execution of the function > > * Whether or not export attributes gained as a result of the > variable assignments persist after the completion of the function (if > variable assignments persist after the completion of the function)" POSIX used to require the current dash behaviour. However, you're right that this is no longer the case. This patch will remove the persistence of the variable assignment. I have considered the exporting the variables during the function execution but have decided against it because: 1) It makes the code bigger. 2) dash has never done this in the past. 3) You cannot use this portably anyway. Reported-by: Dirk Fieldhouse Signed-off-by: Herbert Xu function old new delta evalcommand 1606 1635 +29 evalcase 313 317 +4 evalfun 280 268 -12 pushlocalvars 48 - -48 ------------------------------------------------------------------------------ (add/remove: 0/1 grow/shrink: 2/1 up/down: 33/-60) Total: -27 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 3 --- shell/ash_test/ash-misc/env_and_func.right | 2 +- shell/ash_test/ash-misc/env_and_func.tests | 2 +- shell/ash_test/ash-vars/var_leak.right | 2 +- shell/ash_test/ash-vars/var_leak.tests | 4 +--- shell/hush_test/hush-misc/env_and_func.tests | 2 +- 6 files changed, 5 insertions(+), 10 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 5c431c9ff..6cda7251e 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -9600,9 +9600,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) shellparam.optind = 1; shellparam.optoff = -1; #endif - pushlocalvars(); evaltree(func->n.ndefun.body, flags & EV_TESTED); - poplocalvars(0); funcdone: INT_OFF; funcline = savefuncline; @@ -10235,7 +10233,6 @@ evalcommand(union node *cmd, int flags) goto readstatus; case CMDFUNCTION: - poplocalvars(1); /* See above for the rationale */ dowait(DOWAIT_NONBLOCK, NULL); if (evalfun(cmdentry.u.func, argc, argv, flags)) diff --git a/shell/ash_test/ash-misc/env_and_func.right b/shell/ash_test/ash-misc/env_and_func.right index 5fc3488ae..4a1545058 100644 --- a/shell/ash_test/ash-misc/env_and_func.right +++ b/shell/ash_test/ash-misc/env_and_func.right @@ -1,2 +1,2 @@ var=val -var=val +var=old diff --git a/shell/ash_test/ash-misc/env_and_func.tests b/shell/ash_test/ash-misc/env_and_func.tests index 3efef1a41..1c63eafd8 100755 --- a/shell/ash_test/ash-misc/env_and_func.tests +++ b/shell/ash_test/ash-misc/env_and_func.tests @@ -3,6 +3,6 @@ f() { echo "var=$var"; } # bash: POSIXLY_CORRECT behavior is to "leak" new variable values # out of function invocations (similar to "special builtins" behavior); # but in "bash mode", they don't leak. -# hush does not "leak" values. ash does. +# hush does not "leak" values. ash used to, but now does not. var=val f echo "var=$var" diff --git a/shell/ash_test/ash-vars/var_leak.right b/shell/ash_test/ash-vars/var_leak.right index 01a5e3263..764680086 100644 --- a/shell/ash_test/ash-vars/var_leak.right +++ b/shell/ash_test/ash-vars/var_leak.right @@ -1,4 +1,4 @@ should be empty: '' should be empty: '' should be not empty: 'val2' -should be not empty: 'val3' +should be empty: '' diff --git a/shell/ash_test/ash-vars/var_leak.tests b/shell/ash_test/ash-vars/var_leak.tests index 5242e24eb..adf66692e 100755 --- a/shell/ash_test/ash-vars/var_leak.tests +++ b/shell/ash_test/ash-vars/var_leak.tests @@ -15,9 +15,7 @@ VAR='' VAR=val2 exec 2>&1 echo "should be not empty: '$VAR'" -# ash follows the "function call is a special builtin" rule here -# (bash does not do it) f() { true; } VAR='' VAR=val3 f -echo "should be not empty: '$VAR'" +echo "should be empty: '$VAR'" diff --git a/shell/hush_test/hush-misc/env_and_func.tests b/shell/hush_test/hush-misc/env_and_func.tests index 3efef1a41..1c63eafd8 100755 --- a/shell/hush_test/hush-misc/env_and_func.tests +++ b/shell/hush_test/hush-misc/env_and_func.tests @@ -3,6 +3,6 @@ f() { echo "var=$var"; } # bash: POSIXLY_CORRECT behavior is to "leak" new variable values # out of function invocations (similar to "special builtins" behavior); # but in "bash mode", they don't leak. -# hush does not "leak" values. ash does. +# hush does not "leak" values. ash used to, but now does not. var=val f echo "var=$var" -- cgit v1.2.3-55-g6feb From 67dae152f4bf5456e4ea7950f4b55356aa37ec6c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 5 Aug 2018 13:59:35 +0200 Subject: ash: var: Set IFS to fixed value at start time Upstream commit: Date: Sat, 19 May 2018 02:39:43 +0800 var: Set IFS to fixed value at start time This patch forces the IFS variable to always be set to its default value, regardless of the environment. It also removes the long unused IFS_BROKEN code. Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 1 + 1 file changed, 1 insertion(+) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 6cda7251e..6ef0a7ac2 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -13960,6 +13960,7 @@ init(void) } } + setvareq((char*)defifsvar, VTEXTFIXED); setvareq((char*)defoptindvar, VTEXTFIXED); setvar0("PPID", utoa(getppid())); -- cgit v1.2.3-55-g6feb From 440da97ed79841b55f0b61e4108a336b61642bff Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 5 Aug 2018 14:29:58 +0200 Subject: ash: expand: Fix ghost fields with unquoted $@/$* Upstream commit: Date: Fri, 23 Mar 2018 18:58:47 +0800 expand: Fix ghost fields with unquoted $@/$* You're right. The proper fix to this is to ensure that nulonly is not set in varvalue for $*. It should only be set for $@ when it's inside double quotes. In fact there is another bug while we're playing with $@/$*. When IFS is set to a non-whitespace character such as :, $* outside quotes won't remove empty fields as it should. This patch fixes both problems. Reported-by: Martijn Dekker Suggested-by: Harald van Dijk Signed-off-by: Herbert Xu function old new delta argstr 1111 1113 +2 evalvar 571 569 -2 varvalue 579 576 -3 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/2 up/down: 2/-5) Total: -3 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 38 +++++++++++++++------- shell/ash_test/ash-vars/var_wordsplit_ifs5.right | 1 + shell/ash_test/ash-vars/var_wordsplit_ifs5.tests | 4 +++ shell/hush_test/hush-vars/var_wordsplit_ifs5.right | 1 + shell/hush_test/hush-vars/var_wordsplit_ifs5.tests | 4 +++ 5 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 shell/ash_test/ash-vars/var_wordsplit_ifs5.right create mode 100755 shell/ash_test/ash-vars/var_wordsplit_ifs5.tests create mode 100644 shell/hush_test/hush-vars/var_wordsplit_ifs5.right create mode 100755 shell/hush_test/hush-vars/var_wordsplit_ifs5.tests (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 6ef0a7ac2..4641dfd19 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5902,7 +5902,7 @@ static int substr_atoi(const char *s) #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ #define EXP_VARTILDE2 0x20 /* expand tildes after colons only */ #define EXP_WORD 0x40 /* expand word in parameter expansion */ -#define EXP_QUOTED 0x80 /* expand word in double quotes */ +#define EXP_QUOTED 0x100 /* expand word in double quotes */ /* * rmescape() flags */ @@ -7175,14 +7175,13 @@ subevalvar(char *p, char *varname, int strloc, int subtype, * ash -c 'echo ${#1#}' name:'1=#' */ static NOINLINE ssize_t -varvalue(char *name, int varflags, int flags, int *quotedp) +varvalue(char *name, int varflags, int flags, int quoted) { const char *p; int num; int i; ssize_t len = 0; int sep; - int quoted = *quotedp; int subtype = varflags & VSTYPE; int discard = subtype == VSPLUS || subtype == VSLENGTH; int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL; @@ -7230,13 +7229,27 @@ varvalue(char *name, int varflags, int flags, int *quotedp) case '*': { char **ap; char sepc; + char c; - if (quoted) - sep = 0; - sep |= ifsset() ? ifsval()[0] : ' '; + /* We will set c to 0 or ~0 depending on whether + * we're doing field splitting. We won't do field + * splitting if either we're quoted or sep is zero. + * + * Instead of testing (quoted || !sep) the following + * trick optimises away any branches by using the + * fact that EXP_QUOTED (which is the only bit that + * can be set in quoted) is the same as EXP_FULL << + * CHAR_BIT (which is the only bit that can be set + * in sep). + */ +#if EXP_QUOTED >> CHAR_BIT != EXP_FULL +#error The following two lines expect EXP_QUOTED == EXP_FULL << CHAR_BIT +#endif + c = !((quoted | ~sep) & EXP_QUOTED) - 1; + sep &= ~quoted; + sep |= ifsset() ? (unsigned char)(c & ifsval()[0]) : ' '; param: sepc = sep; - *quotedp = !sepc; ap = shellparam.p; if (!ap) return -1; @@ -7301,7 +7314,6 @@ evalvar(char *p, int flag) char varflags; char subtype; int quoted; - char easy; char *var; int patloc; int startloc; @@ -7315,12 +7327,11 @@ evalvar(char *p, int flag) quoted = flag & EXP_QUOTED; var = p; - easy = (!quoted || (*var == '@' && shellparam.nparam)); startloc = expdest - (char *)stackblock(); p = strchr(p, '=') + 1; //TODO: use var_end(p)? again: - varlen = varvalue(var, varflags, flag, "ed); + varlen = varvalue(var, varflags, flag, quoted); if (varflags & VSNUL) varlen--; @@ -7366,8 +7377,11 @@ evalvar(char *p, int flag) if (subtype == VSNORMAL) { record: - if (!easy) - goto end; + if (quoted) { + quoted = *var == '@' && shellparam.nparam; + if (!quoted) + goto end; + } recordregion(startloc, expdest - (char *)stackblock(), quoted); goto end; } diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs5.right b/shell/ash_test/ash-vars/var_wordsplit_ifs5.right new file mode 100644 index 000000000..46ffcece7 --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs5.right @@ -0,0 +1 @@ +Zero:0 diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs5.tests b/shell/ash_test/ash-vars/var_wordsplit_ifs5.tests new file mode 100755 index 000000000..d382116df --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs5.tests @@ -0,0 +1,4 @@ +IFS= +set -- +set -- $@ $* +echo Zero:$# diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs5.right b/shell/hush_test/hush-vars/var_wordsplit_ifs5.right new file mode 100644 index 000000000..46ffcece7 --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs5.right @@ -0,0 +1 @@ +Zero:0 diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs5.tests b/shell/hush_test/hush-vars/var_wordsplit_ifs5.tests new file mode 100755 index 000000000..d382116df --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs5.tests @@ -0,0 +1,4 @@ +IFS= +set -- +set -- $@ $* +echo Zero:$# -- cgit v1.2.3-55-g6feb From e9dccab9f4bf3311ae50f19e39e7e499b25edca2 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 5 Aug 2018 14:55:01 +0200 Subject: hush: fix fallout from FILE->HFILE conversion Signed-off-by: Denys Vlasenko --- shell/hush.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 3407711cd..c4b124825 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -1683,9 +1683,15 @@ static void close_all_HFILE_list(void) * It is disastrous if we share memory with a vforked parent. * I'm not sure we never come here after vfork. * Therefore just close fd, nothing more. + * + * ">" instead of ">=": we don't close fd#0, + * interactive shell uses hfopen(NULL) as stdin input + * which has fl->fd == 0, but fd#0 gets redirected in pipes. + * If we'd close it here, then e.g. interactive "set | sort" + * with NOFORKed sort, would have sort's input fd closed. */ - /*hfclose(fl); - unsafe */ - if (fl->fd >= 0) + if (fl->fd > 0) + /*hfclose(fl); - unsafe */ close(fl->fd); fl = fl->next_hfile; } -- cgit v1.2.3-55-g6feb From fd6f295a98956b7f495ba09a56528ef9e0d51398 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 5 Aug 2018 15:13:08 +0200 Subject: hush: set IFS to default on startup function old new delta hush_main 1095 1110 +15 Signed-off-by: Denys Vlasenko --- shell/hush.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index c4b124825..4c8814a01 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2397,6 +2397,12 @@ static int set_local_var(char *str, unsigned flags) return 0; } +static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) +{ + char *var = xasprintf("%s=%s", name, val); + set_local_var(var, /*flag:*/ 0); +} + /* Used at startup and after each cd */ static void set_pwd_var(unsigned flag) { @@ -2443,15 +2449,6 @@ static int unset_local_var(const char *name) } #endif -#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS \ - || (ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_EDITING_FANCY_PROMPT) -static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) -{ - char *var = xasprintf("%s=%s", name, val); - set_local_var(var, /*flag:*/ 0); -} -#endif - /* * Helpers for "var1=val1 var2=val2 cmd" feature @@ -9854,6 +9851,10 @@ int hush_main(int argc, char **argv) uname(&uts); set_local_var_from_halves("HOSTNAME", uts.nodename); } +#endif + /* IFS is not inherited from the parent environment */ + set_local_var_from_halves("IFS", defifs); + /* bash also exports SHLVL and _, * and sets (but doesn't export) the following variables: * BASH=/bin/bash @@ -9884,10 +9885,8 @@ int hush_main(int argc, char **argv) * TERM=dumb * OPTERR=1 * OPTIND=1 - * IFS=$' \t\n' * PS4='+ ' */ -#endif #if ENABLE_HUSH_LINENO_VAR if (ENABLE_HUSH_LINENO_VAR) { -- cgit v1.2.3-55-g6feb From 19358cc31317dca4642417066c1445ce00438e18 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 5 Aug 2018 15:42:29 +0200 Subject: ash,hush: fold shell_builtin_read() way-too-many params into a struct param function old new delta getoptscmd 587 584 -3 readcmd 240 224 -16 shell_builtin_read 1426 1399 -27 builtin_read 210 182 -28 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/4 up/down: 0/-74) Total: -74 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 38 ++++++++++++++---------------------- shell/hush.c | 33 +++++++++++-------------------- shell/shell_common.c | 55 +++++++++++++++++++++++++--------------------------- shell/shell_common.h | 22 +++++++++++---------- 4 files changed, 64 insertions(+), 84 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 4641dfd19..f74bef6b1 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -13762,38 +13762,35 @@ letcmd(int argc UNUSED_PARAM, char **argv) static int FAST_FUNC readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { - char *opt_n = NULL; - char *opt_p = NULL; - char *opt_t = NULL; - char *opt_u = NULL; - char *opt_d = NULL; /* optimized out if !BASH */ - int read_flags = 0; + struct builtin_read_params params; const char *r; int i; + memset(¶ms, 0, sizeof(params)); + while ((i = nextopt("p:u:rt:n:sd:")) != '\0') { switch (i) { case 'p': - opt_p = optionarg; + params.opt_p = optionarg; break; case 'n': - opt_n = optionarg; + params.opt_n = optionarg; break; case 's': - read_flags |= BUILTIN_READ_SILENT; + params.read_flags |= BUILTIN_READ_SILENT; break; case 't': - opt_t = optionarg; + params.opt_t = optionarg; break; case 'r': - read_flags |= BUILTIN_READ_RAW; + params.read_flags |= BUILTIN_READ_RAW; break; case 'u': - opt_u = optionarg; + params.opt_u = optionarg; break; #if BASH_READ_D case 'd': - opt_d = optionarg; + params.opt_d = optionarg; break; #endif default: @@ -13801,21 +13798,16 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) } } + params.argv = argptr; + params.setvar = setvar0; + params.ifs = bltinlookup("IFS"); /* can be NULL */ + /* "read -s" needs to save/restore termios, can't allow ^C * to jump out of it. */ again: INT_OFF; - r = shell_builtin_read(setvar0, - argptr, - bltinlookup("IFS"), /* can be NULL */ - read_flags, - opt_n, - opt_p, - opt_t, - opt_u, - opt_d - ); + r = shell_builtin_read(¶ms); INT_ON; if ((uintptr_t)r == 1 && errno == EINTR) { diff --git a/shell/hush.c b/shell/hush.c index 4c8814a01..3c19bceaa 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -10500,40 +10500,29 @@ static int FAST_FUNC builtin_type(char **argv) static int FAST_FUNC builtin_read(char **argv) { const char *r; - char *opt_n = NULL; - char *opt_p = NULL; - char *opt_t = NULL; - char *opt_u = NULL; - char *opt_d = NULL; /* optimized out if !BASH */ - const char *ifs; - int read_flags; + struct builtin_read_params params; + + memset(¶ms, 0, sizeof(params)); /* "!": do not abort on errors. * Option string must start with "sr" to match BUILTIN_READ_xxx */ - read_flags = getopt32(argv, + params.read_flags = getopt32(argv, #if BASH_READ_D - "!srn:p:t:u:d:", &opt_n, &opt_p, &opt_t, &opt_u, &opt_d + "!srn:p:t:u:d:", ¶ms.opt_n, ¶ms.opt_p, ¶ms.opt_t, ¶ms.opt_u, ¶ms.opt_d #else - "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u + "!srn:p:t:u:", ¶ms.opt_n, ¶ms.opt_p, ¶ms.opt_t, ¶ms.opt_u #endif ); - if (read_flags == (uint32_t)-1) + if ((uint32_t)params.read_flags == (uint32_t)-1) return EXIT_FAILURE; argv += optind; - ifs = get_local_var_value("IFS"); /* can be NULL */ + params.argv = argv; + params.setvar = set_local_var_from_halves; + params.ifs = get_local_var_value("IFS"); /* can be NULL */ again: - r = shell_builtin_read(set_local_var_from_halves, - argv, - ifs, - read_flags, - opt_n, - opt_p, - opt_t, - opt_u, - opt_d - ); + r = shell_builtin_read(¶ms); if ((uintptr_t)r == 1 && errno == EINTR) { unsigned sig = check_and_run_traps(); diff --git a/shell/shell_common.c b/shell/shell_common.c index 0a07296f3..f2bf5ab65 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -46,16 +46,7 @@ int FAST_FUNC is_well_formed_var_name(const char *s, char terminator) //Here we can simply store "VAR=" at buffer start and store read data directly //after "=", then pass buffer to setvar() to consume. const char* FAST_FUNC -shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), - char **argv, - const char *ifs, - int read_flags, - const char *opt_n, - const char *opt_p, - const char *opt_t, - const char *opt_u, - const char *opt_d -) +shell_builtin_read(struct builtin_read_params *params) { struct pollfd pfd[1]; #define fd (pfd[0].fd) /* -u FD */ @@ -70,9 +61,13 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), int bufpos; /* need to be able to hold -1 */ int startword; smallint backslash; + char **argv; + const char *ifs; + int read_flags; errno = err = 0; + argv = params->argv; pp = argv; while (*pp) { if (!is_well_formed_var_name(*pp, '\0')) { @@ -84,29 +79,29 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), } nchars = 0; /* if != 0, -n is in effect */ - if (opt_n) { - nchars = bb_strtou(opt_n, NULL, 10); + if (params->opt_n) { + nchars = bb_strtou(params->opt_n, NULL, 10); if (nchars < 0 || errno) return "invalid count"; /* note: "-n 0": off (bash 3.2 does this too) */ } end_ms = 0; - if (opt_t && !ENABLE_FEATURE_SH_READ_FRAC) { - end_ms = bb_strtou(opt_t, NULL, 10); + if (params->opt_t && !ENABLE_FEATURE_SH_READ_FRAC) { + end_ms = bb_strtou(params->opt_t, NULL, 10); if (errno) return "invalid timeout"; if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */ end_ms = UINT_MAX / 2048; end_ms *= 1000; } - if (opt_t && ENABLE_FEATURE_SH_READ_FRAC) { + if (params->opt_t && ENABLE_FEATURE_SH_READ_FRAC) { /* bash 4.3 (maybe earlier) supports -t N.NNNNNN */ char *p; /* Eat up to three fractional digits */ int frac_digits = 3 + 1; - end_ms = bb_strtou(opt_t, &p, 10); + end_ms = bb_strtou(params->opt_t, &p, 10); if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */ end_ms = UINT_MAX / 2048; @@ -128,13 +123,13 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), } fd = STDIN_FILENO; - if (opt_u) { - fd = bb_strtou(opt_u, NULL, 10); + if (params->opt_u) { + fd = bb_strtou(params->opt_u, NULL, 10); if (fd < 0 || errno) return "invalid file descriptor"; } - if (opt_t && end_ms == 0) { + if (params->opt_t && end_ms == 0) { /* "If timeout is 0, read returns immediately, without trying * to read any data. The exit status is 0 if input is available * on the specified file descriptor, non-zero otherwise." @@ -147,14 +142,16 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), return (const char *)(uintptr_t)(r <= 0); } - if (opt_p && isatty(fd)) { - fputs(opt_p, stderr); + if (params->opt_p && isatty(fd)) { + fputs(params->opt_p, stderr); fflush_all(); } + ifs = params->ifs; if (ifs == NULL) ifs = defifs; + read_flags = params->read_flags; if (nchars || (read_flags & BUILTIN_READ_SILENT)) { tcgetattr(fd, &tty); old_tty = tty; @@ -181,11 +178,11 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), retval = (const char *)(uintptr_t)0; startword = 1; backslash = 0; - if (opt_t) + if (params->opt_t) end_ms += (unsigned)monotonic_ms(); buffer = NULL; bufpos = 0; - delim = opt_d ? *opt_d : '\n'; + delim = params->opt_d ? params->opt_d[0] : '\n'; do { char c; int timeout; @@ -194,7 +191,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), buffer = xrealloc(buffer, bufpos + 0x101); timeout = -1; - if (opt_t) { + if (params->opt_t) { timeout = end_ms - (unsigned)monotonic_ms(); /* ^^^^^^^^^^^^^ all values are unsigned, * wrapping math is used here, good even if @@ -246,7 +243,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), * without variable names (bash compat). * Thus, "read" and "read REPLY" are not the same. */ - if (!opt_d && argv[0]) { + if (!params->opt_d && argv[0]) { /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */ const char *is_ifs = strchr(ifs, c); if (startword && is_ifs) { @@ -261,7 +258,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), if (argv[1] != NULL && is_ifs) { buffer[bufpos] = '\0'; bufpos = 0; - setvar(*argv, buffer); + params->setvar(*argv, buffer); argv++; /* can we skip one non-space ifs char? (2: yes) */ startword = isspace(c) ? 2 : 1; @@ -313,14 +310,14 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), } /* Use the remainder as a value for the next variable */ - setvar(*argv, buffer); + params->setvar(*argv, buffer); /* Set the rest to "" */ while (*++argv) - setvar(*argv, ""); + params->setvar(*argv, ""); } else { /* Note: no $IFS removal */ buffer[bufpos] = '\0'; - setvar("REPLY", buffer); + params->setvar("REPLY", buffer); } ret: diff --git a/shell/shell_common.h b/shell/shell_common.h index 875fd9ea7..a1323021d 100644 --- a/shell/shell_common.h +++ b/shell/shell_common.h @@ -30,6 +30,17 @@ int FAST_FUNC is_well_formed_var_name(const char *s, char terminator); /* Builtins */ +struct builtin_read_params { + int read_flags; + void FAST_FUNC (*setvar)(const char *name, const char *val); + char **argv; + const char *ifs; + const char *opt_n; + const char *opt_p; + const char *opt_t; + const char *opt_u; + const char *opt_d; +}; enum { BUILTIN_READ_SILENT = 1 << 0, BUILTIN_READ_RAW = 1 << 1, @@ -40,16 +51,7 @@ enum { // shell_builtin_read(setvar,argv,ifs,read_flags) //#endif const char* FAST_FUNC -shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), - char **argv, - const char *ifs, - int read_flags, - const char *opt_n, - const char *opt_p, - const char *opt_t, - const char *opt_u, - const char *opt_d -); +shell_builtin_read(struct builtin_read_params *params); int FAST_FUNC shell_builtin_ulimit(char **argv); -- cgit v1.2.3-55-g6feb From 58eb805c2c453c6764acbd65f5604465438d9272 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 5 Aug 2018 15:58:13 +0200 Subject: ash: parser: Fix parsing of ${} Upstream commit: Date: Tue, 3 Apr 2018 00:40:25 +0800 parser: Fix parsing of ${} dash -c 'echo ${}' should print "Bad subtitution" but instead fails with "Syntax error: Missing '}'". This is caused by us reading an extra character beyond the right brace. This patch fixes it so that this construct only fails during expansion rather than during parsing. Fixes: 3df3edd13389 ("[PARSER] Report substition errors at...") Signed-off-by: Herbert Xu function old new delta readtoken1 2907 2916 +9 Signed-off-by: Denys Vlasenko --- shell/ash.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index f74bef6b1..b596833e7 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -12431,7 +12431,7 @@ parsesub: { STPUTC(c, out); c = pgetc_eatbnl(); } while (isdigit(c)); - } else { + } else if (c != '}') { /* $[{[#]][}] */ int cc = c; @@ -12457,7 +12457,8 @@ parsesub: { } USTPUTC(cc, out); - } + } else + goto badsub; if (c != '}' && subtype == VSLENGTH) { /* ${#VAR didn't end with } */ -- cgit v1.2.3-55-g6feb From 1c5eb88cd84c90e4c3d014f4abc8f8310c008842 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 5 Aug 2018 17:07:26 +0200 Subject: ash: eval: Restore input files in evalcommand Upstream commit: Date: Tue, 27 Mar 2018 00:39:35 +0800 eval: Restore input files in evalcommand When evalcommand invokes a command that modifies parsefile and then bails out without popping the file, we need to ensure the input file is restored so that the shell can continue to execute. Reported-by: Martijn Dekker Signed-off-by: Herbert Xu function old new delta unwindfiles - 20 +20 evalcommand 1635 1653 +18 getoptscmd 584 595 +11 popallfiles 20 10 -10 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 2/1 up/down: 49/-10) Total: 39 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index b596833e7..79ade5df4 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -9942,6 +9942,7 @@ find_builtin(const char *name) /* * Execute a simple command. */ +static void unwindfiles(struct parsefile *stop); static int isassignment(const char *p) { @@ -9964,6 +9965,7 @@ evalcommand(union node *cmd, int flags) "\0\0", bltincmd /* why three NULs? */ }; struct localvar_list *localvar_stop; + struct parsefile *file_stop; struct redirtab *redir_stop; struct stackmark smark; union node *argp; @@ -9989,6 +9991,7 @@ evalcommand(union node *cmd, int flags) TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); setstackmark(&smark); localvar_stop = pushlocalvars(); + file_stop = g_parsefile; back_exitstatus = 0; cmdentry.cmdtype = CMDBUILTIN; @@ -10260,6 +10263,7 @@ evalcommand(union node *cmd, int flags) if (cmd->ncmd.redirect) popredir(/*drop:*/ cmd_is_exec); unwindredir(redir_stop); + unwindfiles(file_stop); unwindlocalvars(localvar_stop); if (lastarg) { /* dsl: I think this is intended to be used to support @@ -10782,14 +10786,20 @@ popfile(void) INT_ON; } +static void +unwindfiles(struct parsefile *stop) +{ + while (g_parsefile != stop) + popfile(); +} + /* * Return to top level. */ static void popallfiles(void) { - while (g_parsefile != &basepf) - popfile(); + unwindfiles(&basepf); } /* -- cgit v1.2.3-55-g6feb From 2596f412cd02d4b040262e5f40de2e7a7e6b32cf Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 5 Aug 2018 18:04:09 +0200 Subject: ash: exec: Return 126 on most errors in shellexec Upstream commit: Date: Sat, 19 May 2018 02:39:37 +0800 exec: Return 126 on most errors in shellexec Currently when shellexec fails on most errors the shell will exit with exit status 2. This patch changes it to 126 in order to avoid ambiguities with the exit status from a successful exec. The errors that result in 127 has also been expanded to include ENOTDIR, ENAMETOOLONG and ELOOP. Signed-off-by: Herbert Xu function old new delta shellexec 245 254 +9 Signed-off-by: Denys Vlasenko --- shell/ash.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 79ade5df4..ad50537a1 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -8098,15 +8098,15 @@ static void shellexec(char *prog, char **argv, const char *path, int idx) /* Map to POSIX errors */ switch (e) { - case EACCES: + default: exerrno = 126; break; + case ELOOP: + case ENAMETOOLONG: case ENOENT: + case ENOTDIR: exerrno = 127; break; - default: - exerrno = 2; - break; } exitstatus = exerrno; TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n", -- cgit v1.2.3-55-g6feb From 77c18491b8647832422be3f95187e5aa5270e044 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 5 Aug 2018 20:03:04 +0200 Subject: hush: adopt ash's quote_in_varexp1.tests Signed-off-by: Denys Vlasenko --- shell/hush_test/hush-quoting/quote_in_varexp1.right | 2 ++ shell/hush_test/hush-quoting/quote_in_varexp1.tests | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 shell/hush_test/hush-quoting/quote_in_varexp1.right create mode 100755 shell/hush_test/hush-quoting/quote_in_varexp1.tests (limited to 'shell') diff --git a/shell/hush_test/hush-quoting/quote_in_varexp1.right b/shell/hush_test/hush-quoting/quote_in_varexp1.right new file mode 100644 index 000000000..99a0aea7c --- /dev/null +++ b/shell/hush_test/hush-quoting/quote_in_varexp1.right @@ -0,0 +1,2 @@ +'' +Ok:0 diff --git a/shell/hush_test/hush-quoting/quote_in_varexp1.tests b/shell/hush_test/hush-quoting/quote_in_varexp1.tests new file mode 100755 index 000000000..1b97b0556 --- /dev/null +++ b/shell/hush_test/hush-quoting/quote_in_varexp1.tests @@ -0,0 +1,2 @@ +x="''''"; echo "${x#"${x+''}"''}" +echo Ok:$? -- cgit v1.2.3-55-g6feb From eb54ca8be0b45a101f9bdcf6efa26645c6b94a08 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 7 Aug 2018 18:54:52 +0200 Subject: ash: expand: Do not quote backslashes in unquoted parameter expansion Upstream commit: Date: Wed, 28 Mar 2018 18:37:51 +0800 expand: Do not quote backslashes in unquoted parameter expansion Here is a better example: a="/*/\nullx" b="/*/\null"; printf "%s\n" $a $b dash currently prints /*/\nullx /*/\null bash prints /*/\nullx /dev/null You may argue the bash behaviour is inconsistent but it actually makes sense. What happens is that quote removal only applies to the original token as seen by the shell. It is never applied to the result of parameter expansion. Now you may ask why on earth does the second line say "/dev/null" instead of "/dev/\null". Well that's because it is not the quote removal step that removed the backslash, but the pathname expansion. The fact that the /de\v does not become /dev even though it exists is just the result of the optimisation to avoid unnecessarily calling stat(2). I have checked POSIX and I don't see anything that forbids this behaviour. So going back to dash yes I think we should adopt the bash behaviour for pathname expansion and keep the existing case semantics. This patch does exactly that. Note that this patch does not work unless you have already applied https://patchwork.kernel.org/patch/10306507/ because otherwise the optimisation mentioned above does not get detected correctly and we will end up doing quote removal twice. This patch also updates expmeta to handle naked backslashes at the end of the pattern which is now possible. Signed-off-by: Herbert Xu function old new delta expmeta 618 653 +35 memtodest 146 147 +1 Tested to work with both ASH_INTERNAL_GLOB on and off. hush does not handle this correctly. Signed-off-by: Denys Vlasenko --- shell/ash.c | 10 ++++------ shell/ash_test/ash-glob/glob_bkslash_in_var.right | 4 ++++ shell/ash_test/ash-glob/glob_bkslash_in_var.tests | 10 ++++++++++ shell/hush_test/hush-glob/glob_bkslash_in_var.right | 4 ++++ shell/hush_test/hush-glob/glob_bkslash_in_var.tests | 10 ++++++++++ 5 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 shell/ash_test/ash-glob/glob_bkslash_in_var.right create mode 100755 shell/ash_test/ash-glob/glob_bkslash_in_var.tests create mode 100644 shell/hush_test/hush-glob/glob_bkslash_in_var.right create mode 100755 shell/hush_test/hush-glob/glob_bkslash_in_var.tests (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index ad50537a1..dc1a55a6b 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -6236,9 +6236,7 @@ memtodest(const char *p, size_t len, int syntax, int quotes) if (quotes & QUOTES_ESC) { int n = SIT(c, syntax); if (n == CCTL - || (((quotes & EXP_FULL) || syntax != BASESYNTAX) - && n == CBACK - ) + || (syntax != BASESYNTAX && n == CBACK) ) { USTPUTC(CTLESC, q); } @@ -7639,7 +7637,7 @@ expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) } } } else { - if (*p == '\\') + if (*p == '\\' && p[1]) esc++; if (p[esc] == '/') { if (metaflag) @@ -7653,7 +7651,7 @@ expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) return; p = name; do { - if (*p == '\\') + if (*p == '\\' && p[1]) p++; *enddir++ = *p; } while (*p++); @@ -7665,7 +7663,7 @@ expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) if (name < start) { p = name; do { - if (*p == '\\') + if (*p == '\\' && p[1]) p++; *enddir++ = *p++; } while (p < start); diff --git a/shell/ash_test/ash-glob/glob_bkslash_in_var.right b/shell/ash_test/ash-glob/glob_bkslash_in_var.right new file mode 100644 index 000000000..f1484b9e4 --- /dev/null +++ b/shell/ash_test/ash-glob/glob_bkslash_in_var.right @@ -0,0 +1,4 @@ +Unquoted non-matching glob in var:'test*.TMP/\name_doesnt_exist' +Unquoted matching glob in var: 'testdir.TMP/name' +Quoted non-matching glob in var: 'test*.TMP/\name_doesnt_exist' +Quoted matching glob in var: 'test*.TMP/\name' diff --git a/shell/ash_test/ash-glob/glob_bkslash_in_var.tests b/shell/ash_test/ash-glob/glob_bkslash_in_var.tests new file mode 100755 index 000000000..e3dedc4ac --- /dev/null +++ b/shell/ash_test/ash-glob/glob_bkslash_in_var.tests @@ -0,0 +1,10 @@ +mkdir testdir.TMP +>testdir.TMP/name +a="test*.TMP/\name_doesnt_exist" +b="test*.TMP/\name" +printf "Unquoted non-matching glob in var:'%s'\n" $a +printf "Unquoted matching glob in var: '%s'\n" $b +printf "Quoted non-matching glob in var: '%s'\n" "$a" +printf "Quoted matching glob in var: '%s'\n" "$b" +rm -f testdir.TMP/name +rmdir testdir.TMP diff --git a/shell/hush_test/hush-glob/glob_bkslash_in_var.right b/shell/hush_test/hush-glob/glob_bkslash_in_var.right new file mode 100644 index 000000000..f1484b9e4 --- /dev/null +++ b/shell/hush_test/hush-glob/glob_bkslash_in_var.right @@ -0,0 +1,4 @@ +Unquoted non-matching glob in var:'test*.TMP/\name_doesnt_exist' +Unquoted matching glob in var: 'testdir.TMP/name' +Quoted non-matching glob in var: 'test*.TMP/\name_doesnt_exist' +Quoted matching glob in var: 'test*.TMP/\name' diff --git a/shell/hush_test/hush-glob/glob_bkslash_in_var.tests b/shell/hush_test/hush-glob/glob_bkslash_in_var.tests new file mode 100755 index 000000000..e3dedc4ac --- /dev/null +++ b/shell/hush_test/hush-glob/glob_bkslash_in_var.tests @@ -0,0 +1,10 @@ +mkdir testdir.TMP +>testdir.TMP/name +a="test*.TMP/\name_doesnt_exist" +b="test*.TMP/\name" +printf "Unquoted non-matching glob in var:'%s'\n" $a +printf "Unquoted matching glob in var: '%s'\n" $b +printf "Quoted non-matching glob in var: '%s'\n" "$a" +printf "Quoted matching glob in var: '%s'\n" "$b" +rm -f testdir.TMP/name +rmdir testdir.TMP -- cgit v1.2.3-55-g6feb From 4bf08542480fe6271692cc9359f9747e9d727a79 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 11 Aug 2018 18:44:11 +0200 Subject: hush: add a comment on how globbing (should) work Signed-off-by: Denys Vlasenko --- shell/hush.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 3c19bceaa..4b46752a3 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -3151,6 +3151,41 @@ static int o_get_last_ptr(o_string *o, int n) return ((int)(uintptr_t)list[n-1]) + string_start; } +/* + * Globbing routines. + * + * Most words in commands need to be globbed, even ones which are + * (single or double) quoted. This stems from the possiblity of + * constructs like "abc"* and 'abc'* - these should be globbed. + * Having a different code path for fully-quoted strings ("abc", + * 'abc') would only help performance-wise, but we still need + * code for partially-quoted strings. + * + * Unfortunately, if we want to match bash and ash behavior in all cases, + * the logic can't be see as "shell-syntax argument is first transformed + * to a string, then globbed, and if globbing does not match anything, + * it is used verbatim". Here are two examples where it fails: + * + * echo 'b\*'? + * + * The globbing can't be avoided (because of '?' at the end). + * The glob pattern is: b\\\*? - IOW, both \ and * are literals + * and are glob-escaped. If this does not match, bash/ash print b\*? + * - IOW: they "unbackslash" the pattern. + * Now, look at this: + * + * v='\\\*'; echo b$v? + * + * The glob pattern is the same here: b\\\*? - an unquoted $var expansion + * should be used as glob pattern with no changes. However, if glob + * does not match, bash/ash print b\\\*? - NOT THE SAME as 1st example! + * + * ash implements this by having an encoded representation of the word + * to glob, which IS NOT THE SAME as the glob pattern - it has more data. + * Glob pattern is derived from it. If glob fails, the decision what result + * should be is made using that encoded representation. Not glob pattern. + */ + #if ENABLE_HUSH_BRACE_EXPANSION /* There in a GNU extension, GLOB_BRACE, but it is not usable: * first, it processes even {a} (no commas), second, -- cgit v1.2.3-55-g6feb From c97df2939ec82bdc36586897e02416f935e89519 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 14 Aug 2018 11:04:58 +0200 Subject: hush: tweak comment, no code changes Signed-off-by: Denys Vlasenko --- shell/hush.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 4b46752a3..881331c5b 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -3162,7 +3162,7 @@ static int o_get_last_ptr(o_string *o, int n) * code for partially-quoted strings. * * Unfortunately, if we want to match bash and ash behavior in all cases, - * the logic can't be see as "shell-syntax argument is first transformed + * the logic can't be "shell-syntax argument is first transformed * to a string, then globbed, and if globbing does not match anything, * it is used verbatim". Here are two examples where it fails: * @@ -3171,14 +3171,14 @@ static int o_get_last_ptr(o_string *o, int n) * The globbing can't be avoided (because of '?' at the end). * The glob pattern is: b\\\*? - IOW, both \ and * are literals * and are glob-escaped. If this does not match, bash/ash print b\*? - * - IOW: they "unbackslash" the pattern. + * - IOW: they "unbackslash" the glob pattern. * Now, look at this: * * v='\\\*'; echo b$v? * - * The glob pattern is the same here: b\\\*? - an unquoted $var expansion + * The glob pattern is the same here: b\\\*? - the unquoted $v expansion * should be used as glob pattern with no changes. However, if glob - * does not match, bash/ash print b\\\*? - NOT THE SAME as 1st example! + * does not match, bash/ash print b\\\*? - NOT THE SAME as first example! * * ash implements this by having an encoded representation of the word * to glob, which IS NOT THE SAME as the glob pattern - it has more data. -- cgit v1.2.3-55-g6feb