From 43b17b1cd0dd3eac740e4770be77db2c9010ad04 Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Thu, 31 May 2018 22:15:55 -0700 Subject: restore documentation on the build config language Kconfig-language.txt was deleted in commit 4fa499a17b52b back in 2006. Move to docs/ as suggested by Xabier Oneca: http://lists.busybox.net/pipermail/busybox/2014-May/080914.html Also update references to it everywhere. Signed-off-by: Kartik Agaram Signed-off-by: Denys Vlasenko --- shell/Config.src | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'shell') diff --git a/shell/Config.src b/shell/Config.src index 81c4ec874..959d3cb42 100644 --- a/shell/Config.src +++ b/shell/Config.src @@ -1,6 +1,6 @@ # # For a description of the syntax of this configuration file, -# see scripts/kbuild/config-language.txt. +# see docs/Kconfig-language.txt. # menu "Shells" -- cgit v1.2.3-55-g6feb From 817a20296fd9e4e8eed095836d7dc28183794247 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 26 Jun 2018 15:35:17 +0200 Subject: randomconfig fixes Signed-off-by: Denys Vlasenko --- include/bb_archive.h | 4 ++-- shell/hush.c | 2 ++ util-linux/mkfs_vfat.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'shell') diff --git a/include/bb_archive.h b/include/bb_archive.h index 2ee30f794..d2022336b 100644 --- a/include/bb_archive.h +++ b/include/bb_archive.h @@ -116,10 +116,10 @@ typedef struct archive_handle_t { #if ENABLE_FEATURE_AR_CREATE const char *ar__name; struct archive_handle_t *ar__out; -# if ENABLE_FEATURE_AR_LONG_FILENAMES +#endif +#if ENABLE_FEATURE_AR_LONG_FILENAMES char *ar__long_names; unsigned ar__long_name_size; -# endif #endif } archive_handle_t; /* bits in ah_flags */ diff --git a/shell/hush.c b/shell/hush.c index c77700175..0b36dad80 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4577,7 +4577,9 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign # endif end_ch &= (DOUBLE_CLOSE_CHAR_FLAG - 1); +#if ENABLE_HUSH_INTERACTIVE G.promptmode = 1; /* PS2 */ +#endif debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode); while (1) { diff --git a/util-linux/mkfs_vfat.c b/util-linux/mkfs_vfat.c index 26a919536..92f0e3b1a 100644 --- a/util-linux/mkfs_vfat.c +++ b/util-linux/mkfs_vfat.c @@ -522,7 +522,7 @@ int mkfs_vfat_main(int argc UNUSED_PARAM, char **argv) //STORE_LE(boot_blk->reserved2[3], 0,0,0); STORE_LE(boot_blk->vi.ext_boot_sign, 0x29); STORE_LE(boot_blk->vi.volume_id32, volume_id); - strncpy(boot_blk->vi.fs_type, "FAT32 ", sizeof(boot_blk->vi.fs_type)); + memcpy(boot_blk->vi.fs_type, "FAT32 ", sizeof(boot_blk->vi.fs_type)); strncpy(boot_blk->vi.volume_label, volume_label, sizeof(boot_blk->vi.volume_label)); memcpy(boot_blk->boot_code, boot_code, sizeof(boot_code)); STORE_LE(boot_blk->boot_sign, BOOT_SIGN); -- cgit v1.2.3-55-g6feb From 99496dc7160552824d6146ec951b2f7b03a51759 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 26 Jun 2018 15:36:58 +0200 Subject: hush: variable nesting code is used also if HUSH_FUNCTIONS is not enabled Signed-off-by: Denys Vlasenko --- shell/hush.c | 104 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 52 insertions(+), 52 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 0b36dad80..738a6b286 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7314,6 +7314,58 @@ static const struct built_in_command *find_builtin(const char *name) return find_builtin_helper(name, bltins2, &bltins2[ARRAY_SIZE(bltins2)]); } +static void remove_nested_vars(void) +{ + struct variable *cur; + struct variable **cur_pp; + + cur_pp = &G.top_var; + while ((cur = *cur_pp) != NULL) { + if (cur->var_nest_level <= G.var_nest_level) { + cur_pp = &cur->next; + continue; + } + /* Unexport */ + if (cur->flg_export) { + debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); + bb_unsetenv(cur->varstr); + } + /* Remove from global list */ + *cur_pp = cur->next; + /* Free */ + if (!cur->max_len) { + debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); + free(cur->varstr); + } + free(cur); + } +} + +static void enter_var_nest_level(void) +{ + G.var_nest_level++; + debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); + + /* Try: f() { echo -n .; f; }; f + * struct variable::var_nest_level is uint16_t, + * thus limiting recursion to < 2^16. + * In any case, with 8 Mbyte stack SEGV happens + * not too long after 2^16 recursions anyway. + */ + if (G.var_nest_level > 0xff00) + bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); +} + +static void leave_var_nest_level(void) +{ + G.var_nest_level--; + debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); + if (HUSH_DEBUG && (int)G.var_nest_level < 0) + bb_error_msg_and_die("BUG: nesting underflow"); + + remove_nested_vars(); +} + #if ENABLE_HUSH_FUNCTIONS static struct function **find_function_slot(const char *name) { @@ -7400,58 +7452,6 @@ static void unset_func(const char *name) } # endif -static void remove_nested_vars(void) -{ - struct variable *cur; - struct variable **cur_pp; - - cur_pp = &G.top_var; - while ((cur = *cur_pp) != NULL) { - if (cur->var_nest_level <= G.var_nest_level) { - cur_pp = &cur->next; - continue; - } - /* Unexport */ - if (cur->flg_export) { - debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); - bb_unsetenv(cur->varstr); - } - /* Remove from global list */ - *cur_pp = cur->next; - /* Free */ - if (!cur->max_len) { - debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); - free(cur->varstr); - } - free(cur); - } -} - -static void enter_var_nest_level(void) -{ - G.var_nest_level++; - debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); - - /* Try: f() { echo -n .; f; }; f - * struct variable::var_nest_level is uint16_t, - * thus limiting recursion to < 2^16. - * In any case, with 8 Mbyte stack SEGV happens - * not too long after 2^16 recursions anyway. - */ - if (G.var_nest_level > 0xff00) - bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); -} - -static void leave_var_nest_level(void) -{ - G.var_nest_level--; - debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); - if (HUSH_DEBUG && (int)G.var_nest_level < 0) - bb_error_msg_and_die("BUG: nesting underflow"); - - remove_nested_vars(); -} - # if BB_MMU #define exec_function(to_free, funcp, argv) \ exec_function(funcp, argv) -- cgit v1.2.3-55-g6feb From c96bb2c5ab2772d649c67a716615a72f4aca9cd3 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 26 Jun 2018 15:43:56 +0200 Subject: hush: fix for !ENABLE_HUSH_MODE_X configuration 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 738a6b286..8d4478ae5 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -8255,8 +8255,8 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe) * subshell: ( list ) [&] */ #if !ENABLE_HUSH_MODE_X -#define redirect_and_varexp_helper(old_vars_p, command, squirrel, argv_expanded) \ - redirect_and_varexp_helper(old_vars_p, command, squirrel) +#define redirect_and_varexp_helper(command, squirrel, argv_expanded) \ + redirect_and_varexp_helper(command, squirrel) #endif static int redirect_and_varexp_helper( struct command *command, -- cgit v1.2.3-55-g6feb From d1a83234c05e0a977e33d409d3a8cd0441e9e5a1 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 26 Jun 2018 15:50:33 +0200 Subject: hush: fix dup_CLOEXEC() call without "avoid_fd" parameter Signed-off-by: Denys Vlasenko --- shell/hush.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 8d4478ae5..5d907e200 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9668,7 +9668,7 @@ int hush_main(int argc, char **argv) G_interactive_fd = dup_CLOEXEC(STDIN_FILENO, 254); if (G_interactive_fd < 0) { /* try to dup to any fd */ - G_interactive_fd = dup_CLOEXEC(STDIN_FILENO); + G_interactive_fd = dup_CLOEXEC(STDIN_FILENO, -1); if (G_interactive_fd < 0) /* give up */ G_interactive_fd = 0; -- cgit v1.2.3-55-g6feb From b2b14cbd4c20d4dda1599fc95680b0583533ab48 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 26 Jun 2018 18:09:22 +0200 Subject: hush: fix compile problem found by randomconfig Signed-off-by: Denys Vlasenko --- shell/hush.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 5d907e200..c3cb9382a 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2373,7 +2373,8 @@ static int unset_local_var(const char *name) } #endif -#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS +#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); -- cgit v1.2.3-55-g6feb From 35a017c0c52bc6de761b30bc0468a6fec380ab27 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 26 Jun 2018 18:27:54 +0200 Subject: hush: unset_local_var_len is only used by unset_local_var Signed-off-by: Denys Vlasenko --- shell/hush.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index c3cb9382a..1f83267be 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2333,6 +2333,7 @@ static void set_pwd_var(unsigned flag) set_local_var(xasprintf("PWD=%s", get_cwd(/*force:*/ 1)), flag); } +#if ENABLE_HUSH_UNSET || ENABLE_HUSH_GETOPTS static int unset_local_var_len(const char *name, int name_len) { struct variable *cur; @@ -2366,7 +2367,6 @@ static int unset_local_var_len(const char *name, int name_len) return EXIT_SUCCESS; } -#if ENABLE_HUSH_UNSET || ENABLE_HUSH_GETOPTS static int unset_local_var(const char *name) { return unset_local_var_len(name, strlen(name)); -- cgit v1.2.3-55-g6feb From b0441a7189c874808dcfc36567d3e878c6ec7ba3 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 15 Jul 2018 18:03:56 +0200 Subject: hush: shrink code in builtin_eval function old new delta builtin_eval 126 119 -7 Signed-off-by: Denys Vlasenko --- shell/hush.c | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 1f83267be..9d3f06db0 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9806,41 +9806,41 @@ static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM) static int FAST_FUNC builtin_eval(char **argv) { - int rcode = EXIT_SUCCESS; - argv = skip_dash_dash(argv); - if (argv[0]) { - char *str = NULL; - if (argv[1]) { - /* "The eval utility shall construct a command by - * concatenating arguments together, separating - * each with a character." - */ - char *p; - unsigned len = 0; - char **pp = argv; - do - len += strlen(*pp) + 1; - while (*++pp); - str = p = xmalloc(len); - pp = argv; - do { - p = stpcpy(p, *pp); - *p++ = ' '; - } while (*++pp); - p[-1] = '\0'; - } + if (!argv[0]) + return EXIT_SUCCESS; + if (!argv[1]) { /* bash: * eval "echo Hi; done" ("done" is syntax error): * "echo Hi" will not execute too. */ - parse_and_run_string(str ? str : argv[0]); + parse_and_run_string(argv[0]); + } else { + /* "The eval utility shall construct a command by + * concatenating arguments together, separating + * each with a character." + */ + char *str, *p; + unsigned len = 0; + char **pp = argv; + do + len += strlen(*pp) + 1; + while (*++pp); + str = p = xmalloc(len); + pp = argv; + for (;;) { + p = stpcpy(p, *pp); + pp++; + if (!*pp) + break; + *p++ = ' '; + } + parse_and_run_string(str); free(str); - rcode = G.last_exitcode; } - return rcode; + return G.last_exitcode; } static int FAST_FUNC builtin_exec(char **argv) -- cgit v1.2.3-55-g6feb From b762c784caa78877a9949224af425e52db237beb Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 17 Jul 2018 14:21:38 +0200 Subject: hush: improve ${var#...}, ${var:+...} and ${var/.../...} - handle quoting dollar_altvalue1 test partially fails: word splitting of unquoted ${var:+...} is not correct function old new delta encode_then_expand_vararg - 443 +443 expand_one_var 1599 1610 +11 parse_stream 2756 2753 -3 encode_string 250 242 -8 setup_heredoc 308 298 -10 expand_and_evaluate_arith 106 96 -10 encode_then_expand_string 142 126 -16 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 1/5 up/down: 454/-47) Total: 407 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-quoting/dollar_altvalue1.right | 16 ++ shell/ash_test/ash-quoting/dollar_altvalue1.tests | 16 ++ shell/ash_test/ash-quoting/dollar_repl_bash1.right | 14 ++ shell/ash_test/ash-quoting/dollar_repl_bash1.tests | 12 ++ shell/ash_test/ash-quoting/squote_in_varexp.right | 4 + shell/ash_test/ash-quoting/squote_in_varexp.tests | 4 + shell/ash_test/ash-quoting/squote_in_varexp2.right | 2 + shell/ash_test/ash-quoting/squote_in_varexp2.tests | 2 + shell/ash_test/ash-z_slow/many_ifs.tests | 4 +- shell/hush.c | 200 +++++++++++++++------ .../hush_test/hush-quoting/dollar_altvalue1.right | 16 ++ .../hush_test/hush-quoting/dollar_altvalue1.tests | 16 ++ .../hush_test/hush-quoting/dollar_repl_bash1.right | 14 ++ .../hush_test/hush-quoting/dollar_repl_bash1.tests | 12 ++ .../hush_test/hush-quoting/squote_in_varexp.right | 4 + .../hush_test/hush-quoting/squote_in_varexp.tests | 4 + .../hush_test/hush-quoting/squote_in_varexp2.right | 2 + .../hush_test/hush-quoting/squote_in_varexp2.tests | 2 + shell/hush_test/hush-z_slow/many_ifs.tests | 4 +- 19 files changed, 291 insertions(+), 57 deletions(-) create mode 100644 shell/ash_test/ash-quoting/dollar_altvalue1.right create mode 100755 shell/ash_test/ash-quoting/dollar_altvalue1.tests create mode 100644 shell/ash_test/ash-quoting/dollar_repl_bash1.right create mode 100755 shell/ash_test/ash-quoting/dollar_repl_bash1.tests create mode 100644 shell/hush_test/hush-quoting/dollar_altvalue1.right create mode 100755 shell/hush_test/hush-quoting/dollar_altvalue1.tests create mode 100644 shell/hush_test/hush-quoting/dollar_repl_bash1.right create mode 100755 shell/hush_test/hush-quoting/dollar_repl_bash1.tests (limited to 'shell') diff --git a/shell/ash_test/ash-quoting/dollar_altvalue1.right b/shell/ash_test/ash-quoting/dollar_altvalue1.right new file mode 100644 index 000000000..5cd495d3b --- /dev/null +++ b/shell/ash_test/ash-quoting/dollar_altvalue1.right @@ -0,0 +1,16 @@ +Unquoted b c d +|b| +|c| +|d| +Unquoted 'b c' d +|b c| +|d| +Unquoted "b c" d +|b c| +|d| +Quoted b c d +|b c d| +Quoted 'b c' d +|'b c' d| +Quoted "b c" d +|b c d| diff --git a/shell/ash_test/ash-quoting/dollar_altvalue1.tests b/shell/ash_test/ash-quoting/dollar_altvalue1.tests new file mode 100755 index 000000000..f4dc8caec --- /dev/null +++ b/shell/ash_test/ash-quoting/dollar_altvalue1.tests @@ -0,0 +1,16 @@ +f() { for i; do echo "|$i|"; done; } +x=a + +echo Unquoted b c d +f ${x:+b c d} +echo Unquoted "'b c' d" +f ${x:+'b c' d} +echo Unquoted '"b c" d' +f ${x:+"b c" d} + +echo Quoted b c d +f "${x:+b c d}" +echo Quoted "'b c' d" +f "${x:+'b c' d}" +echo Quoted '"b c" d' +f "${x:+"b c" d}" diff --git a/shell/ash_test/ash-quoting/dollar_repl_bash1.right b/shell/ash_test/ash-quoting/dollar_repl_bash1.right new file mode 100644 index 000000000..f5e9309f4 --- /dev/null +++ b/shell/ash_test/ash-quoting/dollar_repl_bash1.right @@ -0,0 +1,14 @@ +|y| +|zx| +|y| +|zx| +|y zx| +|y zx| +|y| +|zy| +|z| +|y| +|zy| +|z| +|y zy z| +|y zy z| diff --git a/shell/ash_test/ash-quoting/dollar_repl_bash1.tests b/shell/ash_test/ash-quoting/dollar_repl_bash1.tests new file mode 100755 index 000000000..912635925 --- /dev/null +++ b/shell/ash_test/ash-quoting/dollar_repl_bash1.tests @@ -0,0 +1,12 @@ +f() { for i; do echo "|$i|"; done; } +v=xx + +f ${v/'x'/"y z"} +f ${v/"x"/'y z'} +f "${v/'x'/"y z"}" +f "${v/"x"/'y z'}" + +f ${v//'x'/"y z"} +f ${v//"x"/'y z'} +f "${v//'x'/"y z"}" +f "${v//"x"/'y z'}" diff --git a/shell/ash_test/ash-quoting/squote_in_varexp.right b/shell/ash_test/ash-quoting/squote_in_varexp.right index a75c0bfd6..4a457021b 100644 --- a/shell/ash_test/ash-quoting/squote_in_varexp.right +++ b/shell/ash_test/ash-quoting/squote_in_varexp.right @@ -1,5 +1,9 @@ z z +z +z +y +y y y Ok:0 diff --git a/shell/ash_test/ash-quoting/squote_in_varexp.tests b/shell/ash_test/ash-quoting/squote_in_varexp.tests index a2d05a246..4afc52107 100755 --- a/shell/ash_test/ash-quoting/squote_in_varexp.tests +++ b/shell/ash_test/ash-quoting/squote_in_varexp.tests @@ -1,6 +1,10 @@ x=yz echo ${x#'y'} echo "${x#'y'}" +echo ${x#"y"} +echo "${x#"y"}" echo ${x%'z'} echo "${x%'z'}" +echo ${x%"z"} +echo "${x%"z"}" echo Ok:$? diff --git a/shell/ash_test/ash-quoting/squote_in_varexp2.right b/shell/ash_test/ash-quoting/squote_in_varexp2.right index 9d0add3c5..d03047024 100644 --- a/shell/ash_test/ash-quoting/squote_in_varexp2.right +++ b/shell/ash_test/ash-quoting/squote_in_varexp2.right @@ -1,3 +1,5 @@ Nothing: Nothing: +Nothing: +Nothing: Ok:0 diff --git a/shell/ash_test/ash-quoting/squote_in_varexp2.tests b/shell/ash_test/ash-quoting/squote_in_varexp2.tests index 806ad12b9..2797725cc 100755 --- a/shell/ash_test/ash-quoting/squote_in_varexp2.tests +++ b/shell/ash_test/ash-quoting/squote_in_varexp2.tests @@ -1,4 +1,6 @@ x='\\\\' printf Nothing:'%s\n' ${x#'\\\\'} printf Nothing:'%s\n' "${x#'\\\\'}" +printf Nothing:'%s\n' ${x#"\\\\\\\\"} +printf Nothing:'%s\n' "${x#"\\\\\\\\"}" echo Ok:$? diff --git a/shell/ash_test/ash-z_slow/many_ifs.tests b/shell/ash_test/ash-z_slow/many_ifs.tests index 1f5b1b3a6..cf9a89874 100755 --- a/shell/ash_test/ash-z_slow/many_ifs.tests +++ b/shell/ash_test/ash-z_slow/many_ifs.tests @@ -229,8 +229,8 @@ do '') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;; ' ') ;; *) x=$f2$d2$f3$d3 - x=${x# } #was x=${x#' '} hush needs fixing for this to work - x=${x% } #was x=${x%' '} + x=${x#' '} + x=${x%' '} split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)" ;; esac diff --git a/shell/hush.c b/shell/hush.c index 9d3f06db0..415993e71 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4888,34 +4888,15 @@ static int parse_dollar(o_string *as_string, } #if BB_MMU -# if BASH_PATTERN_SUBST -#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \ - encode_string(dest, input, dquote_end, process_bkslash) -# else -/* only ${var/pattern/repl} (its pattern part) needs additional mode */ -#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \ +#define encode_string(as_string, dest, input, dquote_end) \ encode_string(dest, input, dquote_end) -# endif #define as_string NULL - -#else /* !MMU */ - -# if BASH_PATTERN_SUBST -/* all parameters are needed, no macro tricks */ -# else -#define encode_string(as_string, dest, input, dquote_end, process_bkslash) \ - encode_string(as_string, dest, input, dquote_end) -# endif #endif static int encode_string(o_string *as_string, o_string *dest, struct in_str *input, - int dquote_end, - int process_bkslash) + int dquote_end) { -#if !BASH_PATTERN_SUBST - const int process_bkslash = 1; -#endif int ch; int next; @@ -4938,7 +4919,7 @@ static int encode_string(o_string *as_string, } debug_printf_parse("\" ch=%c (%d) escape=%d\n", ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); - if (process_bkslash && ch == '\\') { + if (ch == '\\') { if (next == EOF) { /* Testcase: in interactive shell a file with * echo "unterminated string\ @@ -5447,7 +5428,7 @@ static struct pipe *parse_stream(char **pstring, } if (ctx.is_assignment == NOT_ASSIGNMENT) ctx.word.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; - if (!encode_string(&ctx.as_string, &ctx.word, input, '"', /*process_bkslash:*/ 1)) + if (!encode_string(&ctx.as_string, &ctx.word, input, '"')) goto parse_error; ctx.word.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; continue; /* get next char */ @@ -5744,16 +5725,8 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha * Returns malloced string. * As an optimization, we return NULL if expansion is not needed. */ -#if !BASH_PATTERN_SUBST -/* only ${var/pattern/repl} (its pattern part) needs additional mode */ -#define encode_then_expand_string(str, process_bkslash, do_unbackslash) \ - encode_then_expand_string(str) -#endif -static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash) +static char *encode_then_expand_string(const char *str) { -#if !BASH_PATTERN_SUBST - enum { do_unbackslash = 1 }; -#endif char *exp_str; struct in_str input; o_string dest = NULL_O_STRING; @@ -5771,14 +5744,135 @@ static char *encode_then_expand_string(const char *str, int process_bkslash, int * echo $(($a + `echo 1`)) $((1 + $((2)) )) */ setup_string_in_str(&input, str); - encode_string(NULL, &dest, &input, EOF, process_bkslash); + encode_string(NULL, &dest, &input, EOF); //TODO: error check (encode_string returns 0 on error)? //bb_error_msg("'%s' -> '%s'", str, dest.data); + exp_str = expand_string_to_string(dest.data, + EXP_FLAG_ESC_GLOB_CHARS, + /*unbackslash:*/ 1 + ); + //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); + o_free_unsafe(&dest); + return exp_str; +} + +#if !BASH_PATTERN_SUBST +#define encode_then_expand_vararg(str, handle_squotes, do_unbackslash) \ + encode_then_expand_vararg(str, handle_squotes) +#endif +static char *encode_then_expand_vararg(const char *str, int handle_squotes, int do_unbackslash) +{ +#if !BASH_PATTERN_SUBST + const int do_unbackslash = 0; +#endif + char *exp_str; + struct in_str input; + o_string dest = NULL_O_STRING; + + if (!strchr(str, '$') + && !strchr(str, '\\') + && !strchr(str, '\'') +//todo:better code + && !strchr(str, '"') +#if ENABLE_HUSH_TICK + && !strchr(str, '`') +#endif + ) { + return NULL; + } + + /* 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: + * ${var#ARG} and "${var#ARG}" treat ARG the same (ARG can even be + * a dquoted string: "${var#"zz"}"), the difference only comes later + * (word splitting and globbing of the ${var...} result). + */ + + setup_string_in_str(&input, str); + o_addchr(&dest, '\0'); + dest.length = 0; + exp_str = NULL; + + for (;;) { + int ch; + int next; + + ch = i_getch(&input); + if (ch == EOF) { + if (dest.o_expflags) { /* EXP_FLAG_ESC_GLOB_CHARS set? */ + syntax_error_unterm_ch('"'); + goto ret; /* error */ + } + break; + } + debug_printf_parse("%s: ch=%c (%d) escape=%d\n", + __func__, ch, ch, !!dest.o_expflags); + if (ch == '\'' && handle_squotes && !dest.o_expflags) { +//quoting version of add_till_single_quote() (try to merge?): + for (;;) { + ch = i_getch(&input); + if (ch == EOF) { + syntax_error_unterm_ch('\''); + goto ret; /* error */ + } + if (ch == '\'') + break; + o_addqchr(&dest, ch); + } + continue; + } + if (ch == '"') { + dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS; + continue; + } + if (ch == '\\') { + ch = i_getch(&input); + if (ch == EOF) { +//example? error message? syntax_error_unterm_ch('"'); + debug_printf_parse("%s: error: \\\n", __func__); + goto ret; + } + o_addqchr(&dest, ch); + continue; + } + next = '\0'; + if (ch != '\n') { + next = i_peek(&input); + } + if (ch == '$') { + if (!parse_dollar(NULL, &dest, &input, /*quote_mask:*/ 0x80)) { + debug_printf_parse("%s: error: parse_dollar returned 0 (error)\n", __func__); + goto ret; + } + continue; + } +#if ENABLE_HUSH_TICK + if (ch == '`') { + //unsigned pos = dest->length; + o_addchr(&dest, SPECIAL_VAR_SYMBOL); + o_addchr(&dest, 0x80 | '`'); + if (!add_till_backquote(&dest, &input, + /*in_dquote:*/ dest.o_expflags /* nonzero if EXP_FLAG_ESC_GLOB_CHARS set */ + ) + ) { + goto ret; /* error */ + } + o_addchr(&dest, SPECIAL_VAR_SYMBOL); + //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos); + continue; + } +#endif + o_addQchr(&dest, ch); + } /* for (;;) */ + + debug_printf_parse("encode: '%s' -> '%s'\n", str, dest.data); exp_str = expand_string_to_string(dest.data, do_unbackslash ? EXP_FLAG_ESC_GLOB_CHARS : 0, do_unbackslash ); - //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); + ret: + debug_printf_parse("expand: '%s' -> '%s'\n", dest.data, exp_str); o_free_unsafe(&dest); return exp_str; } @@ -5793,7 +5887,7 @@ static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p) math_state.lookupvar = get_local_var_value; math_state.setvar = set_local_var_from_halves; //math_state.endofname = endofname; - exp_str = encode_then_expand_string(arg, /*process_bkslash:*/ 1, /*unbackslash:*/ 1); + exp_str = encode_then_expand_string(arg); res = arith(&math_state, exp_str ? exp_str : arg); free(exp_str); if (errmsg_p) @@ -5869,7 +5963,6 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha char *to_be_freed; char *p; char *var; - char first_char; char exp_op; char exp_save = exp_save; /* for compiler */ char *exp_saveptr; /* points to expansion operator */ @@ -5883,10 +5976,10 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha var = arg; exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL; arg0 = arg[0]; - first_char = arg[0] = arg0 & 0x7f; + arg[0] = (arg0 & 0x7f); exp_op = 0; - if (first_char == '#' && arg[1] /* ${#...} but not ${#} */ + if (arg[0] == '#' && arg[1] /* ${#...} but not ${#} */ && (!exp_saveptr /* and ( not(${#...}) */ || (arg[2] == '\0' && strchr(SPECIAL_VARS_STR, arg[1])) /* or ${#C} "len of $C" ) */ ) /* NB: skipping ^^^specvar check mishandles ${#::2} */ @@ -5897,7 +5990,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha } else { /* Maybe handle parameter expansion */ if (exp_saveptr /* if 2nd char is one of expansion operators */ - && strchr(NUMERIC_SPECVARS_STR, first_char) /* 1st char is special variable */ + && strchr(NUMERIC_SPECVARS_STR, arg[0]) /* 1st char is special variable */ ) { /* ${?:0}, ${#[:]%0} etc */ exp_saveptr = var + 1; @@ -5966,6 +6059,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha * Word is expanded to produce a glob pattern. * Then var's value is matched to it and matching part removed. */ +//FIXME: ${x#...${...}...} +//should evaluate inner ${...} even if x is "" and no shrinking of it is possible - +//inner ${...} may have side effects! if (val && val[0]) { char *t; char *exp_exp_word; @@ -5974,20 +6070,16 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha if (exp_op == *exp_word) /* ## or %% */ exp_word++; debug_printf_expand("expand: exp_word:'%s'\n", exp_word); - /* - * process_bkslash:1 unbackslash:1 breaks this: - * a='a\\'; echo ${a%\\\\} # correct output is: a - * process_bkslash:1 unbackslash:0 breaks this: - * a='a}'; echo ${a%\}} # correct output is: a - */ - exp_exp_word = encode_then_expand_string(exp_word, /*process_bkslash:*/ 0, /*unbackslash:*/ 0); + exp_exp_word = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 0); if (exp_exp_word) exp_word = exp_exp_word; - debug_printf_expand("expand: exp_exp_word:'%s'\n", exp_word); - /* HACK ALERT. We depend here on the fact that + debug_printf_expand("expand: exp_word:'%s'\n", exp_word); + /* + * HACK ALERT. We depend here on the fact that * G.global_argv and results of utoa and get_local_var_value * are actually in writable memory: - * scan_and_match momentarily stores NULs there. */ + * scan_and_match momentarily stores NULs there. + */ t = (char*)val; loc = scan_and_match(t, exp_word, scan_flags); debug_printf_expand("op:%c str:'%s' pat:'%s' res:'%s'\n", exp_op, t, exp_word, loc); @@ -6020,7 +6112,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha * (note that a*z _pattern_ is never globbed!) */ char *pattern, *repl, *t; - pattern = encode_then_expand_string(exp_word, /*process_bkslash:*/ 0, /*unbackslash:*/ 0); + pattern = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 0); if (!pattern) pattern = xstrdup(exp_word); debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern); @@ -6028,7 +6120,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha exp_word = p; p = strchr(p, SPECIAL_VAR_SYMBOL); *p = '\0'; - repl = encode_then_expand_string(exp_word, /*process_bkslash:*/ 0, /*unbackslash:*/ 1); + repl = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ 1, /*unbackslash:*/ 1); debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl); /* HACK ALERT. We depend here on the fact that * G.global_argv and results of utoa and get_local_var_value @@ -6131,7 +6223,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, (exp_save == ':') ? "true" : "false", use_word); if (use_word) { - to_be_freed = encode_then_expand_string(exp_word, /*process_bkslash:*/ 1, /*unbackslash:*/ 1); +//FIXME: unquoted ${x:+"b c" d} and ${x:+'b c' d} should expand to two words +//currently it expands to three. + to_be_freed = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ !(arg0 & 0x80), /*unbackslash:*/ 0); if (to_be_freed) exp_word = to_be_freed; if (exp_op == '?') { @@ -6934,7 +7028,7 @@ static void setup_heredoc(struct redir_struct *redir) expanded = NULL; if (!(redir->rd_dup & HEREDOC_QUOTED)) { - expanded = encode_then_expand_string(heredoc, /*process_bkslash:*/ 1, /*unbackslash:*/ 1); + expanded = encode_then_expand_string(heredoc); if (expanded) heredoc = expanded; } diff --git a/shell/hush_test/hush-quoting/dollar_altvalue1.right b/shell/hush_test/hush-quoting/dollar_altvalue1.right new file mode 100644 index 000000000..5cd495d3b --- /dev/null +++ b/shell/hush_test/hush-quoting/dollar_altvalue1.right @@ -0,0 +1,16 @@ +Unquoted b c d +|b| +|c| +|d| +Unquoted 'b c' d +|b c| +|d| +Unquoted "b c" d +|b c| +|d| +Quoted b c d +|b c d| +Quoted 'b c' d +|'b c' d| +Quoted "b c" d +|b c d| diff --git a/shell/hush_test/hush-quoting/dollar_altvalue1.tests b/shell/hush_test/hush-quoting/dollar_altvalue1.tests new file mode 100755 index 000000000..f4dc8caec --- /dev/null +++ b/shell/hush_test/hush-quoting/dollar_altvalue1.tests @@ -0,0 +1,16 @@ +f() { for i; do echo "|$i|"; done; } +x=a + +echo Unquoted b c d +f ${x:+b c d} +echo Unquoted "'b c' d" +f ${x:+'b c' d} +echo Unquoted '"b c" d' +f ${x:+"b c" d} + +echo Quoted b c d +f "${x:+b c d}" +echo Quoted "'b c' d" +f "${x:+'b c' d}" +echo Quoted '"b c" d' +f "${x:+"b c" d}" diff --git a/shell/hush_test/hush-quoting/dollar_repl_bash1.right b/shell/hush_test/hush-quoting/dollar_repl_bash1.right new file mode 100644 index 000000000..f5e9309f4 --- /dev/null +++ b/shell/hush_test/hush-quoting/dollar_repl_bash1.right @@ -0,0 +1,14 @@ +|y| +|zx| +|y| +|zx| +|y zx| +|y zx| +|y| +|zy| +|z| +|y| +|zy| +|z| +|y zy z| +|y zy z| diff --git a/shell/hush_test/hush-quoting/dollar_repl_bash1.tests b/shell/hush_test/hush-quoting/dollar_repl_bash1.tests new file mode 100755 index 000000000..912635925 --- /dev/null +++ b/shell/hush_test/hush-quoting/dollar_repl_bash1.tests @@ -0,0 +1,12 @@ +f() { for i; do echo "|$i|"; done; } +v=xx + +f ${v/'x'/"y z"} +f ${v/"x"/'y z'} +f "${v/'x'/"y z"}" +f "${v/"x"/'y z'}" + +f ${v//'x'/"y z"} +f ${v//"x"/'y z'} +f "${v//'x'/"y z"}" +f "${v//"x"/'y z'}" diff --git a/shell/hush_test/hush-quoting/squote_in_varexp.right b/shell/hush_test/hush-quoting/squote_in_varexp.right index a75c0bfd6..4a457021b 100644 --- a/shell/hush_test/hush-quoting/squote_in_varexp.right +++ b/shell/hush_test/hush-quoting/squote_in_varexp.right @@ -1,5 +1,9 @@ z z +z +z +y +y y y Ok:0 diff --git a/shell/hush_test/hush-quoting/squote_in_varexp.tests b/shell/hush_test/hush-quoting/squote_in_varexp.tests index a2d05a246..4afc52107 100755 --- a/shell/hush_test/hush-quoting/squote_in_varexp.tests +++ b/shell/hush_test/hush-quoting/squote_in_varexp.tests @@ -1,6 +1,10 @@ x=yz echo ${x#'y'} echo "${x#'y'}" +echo ${x#"y"} +echo "${x#"y"}" echo ${x%'z'} echo "${x%'z'}" +echo ${x%"z"} +echo "${x%"z"}" echo Ok:$? diff --git a/shell/hush_test/hush-quoting/squote_in_varexp2.right b/shell/hush_test/hush-quoting/squote_in_varexp2.right index 9d0add3c5..d03047024 100644 --- a/shell/hush_test/hush-quoting/squote_in_varexp2.right +++ b/shell/hush_test/hush-quoting/squote_in_varexp2.right @@ -1,3 +1,5 @@ Nothing: Nothing: +Nothing: +Nothing: Ok:0 diff --git a/shell/hush_test/hush-quoting/squote_in_varexp2.tests b/shell/hush_test/hush-quoting/squote_in_varexp2.tests index 806ad12b9..2797725cc 100755 --- a/shell/hush_test/hush-quoting/squote_in_varexp2.tests +++ b/shell/hush_test/hush-quoting/squote_in_varexp2.tests @@ -1,4 +1,6 @@ x='\\\\' printf Nothing:'%s\n' ${x#'\\\\'} printf Nothing:'%s\n' "${x#'\\\\'}" +printf Nothing:'%s\n' ${x#"\\\\\\\\"} +printf Nothing:'%s\n' "${x#"\\\\\\\\"}" echo Ok:$? diff --git a/shell/hush_test/hush-z_slow/many_ifs.tests b/shell/hush_test/hush-z_slow/many_ifs.tests index 1f5b1b3a6..cf9a89874 100755 --- a/shell/hush_test/hush-z_slow/many_ifs.tests +++ b/shell/hush_test/hush-z_slow/many_ifs.tests @@ -229,8 +229,8 @@ do '') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;; ' ') ;; *) x=$f2$d2$f3$d3 - x=${x# } #was x=${x#' '} hush needs fixing for this to work - x=${x% } #was x=${x%' '} + x=${x#' '} + x=${x%' '} split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)" ;; esac -- cgit v1.2.3-55-g6feb From 0d2e0de42bab54c31ba37f1c6fd10dcdc7008ccf Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 17 Jul 2018 14:33:19 +0200 Subject: hush: faster/smaller code to check for presense of multiple chars in string Go over the string only once. function old new delta encode_then_expand_string 126 105 -21 encode_then_expand_vararg 443 399 -44 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-65) Total: -65 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 415993e71..238f997da 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5730,14 +5730,17 @@ static char *encode_then_expand_string(const char *str) char *exp_str; struct in_str input; o_string dest = NULL_O_STRING; + const char *cp; - if (!strchr(str, '$') - && !strchr(str, '\\') + cp = str; + for (;;) { + if (!*cp) return NULL; /* string has no special chars */ + if (*cp == '$') break; + if (*cp == '\\') break; #if ENABLE_HUSH_TICK - && !strchr(str, '`') + if (*cp == '`') break; #endif - ) { - return NULL; + cp++; } /* We need to expand. Example: @@ -5768,17 +5771,19 @@ 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; - if (!strchr(str, '$') - && !strchr(str, '\\') - && !strchr(str, '\'') -//todo:better code - && !strchr(str, '"') + 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 - && !strchr(str, '`') + if (*cp == '`') break; #endif - ) { - return NULL; + cp++; } /* Expanding ARG in ${var#ARG}, ${var%ARG}, or ${var/ARG/ARG}. -- cgit v1.2.3-55-g6feb From 4c201c00a3650cdacad5fc098ca255416687fb0f Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 17 Jul 2018 15:04:17 +0200 Subject: whitespace fixes Signed-off-by: Denys Vlasenko --- libbb/appletlib.c | 2 +- libbb/nuke_str.c | 2 +- shell/hush.c | 6 +++--- shell/random.c | 12 ++++++------ util-linux/unshare.c | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) (limited to 'shell') diff --git a/libbb/appletlib.c b/libbb/appletlib.c index f155d0908..319bcc263 100644 --- a/libbb/appletlib.c +++ b/libbb/appletlib.c @@ -737,7 +737,7 @@ static void install_links(const char *busybox, int use_symbolic_links, * busybox.h::bb_install_loc_t, or else... */ int (*lf)(const char *, const char *); char *fpc; - const char *appname = applet_names; + const char *appname = applet_names; unsigned i; int rc; diff --git a/libbb/nuke_str.c b/libbb/nuke_str.c index 240e68004..b5385e956 100644 --- a/libbb/nuke_str.c +++ b/libbb/nuke_str.c @@ -12,7 +12,7 @@ void FAST_FUNC nuke_str(char *str) { - if (str) { + if (str) { while (*str) *str++ = 0; /* or: memset(str, 0, strlen(str)); - not as small as above */ diff --git a/shell/hush.c b/shell/hush.c index 238f997da..7da8f334c 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2470,7 +2470,7 @@ static void reinit_unicode_for_hush(void) */ if (ENABLE_FEATURE_CHECK_UNICODE_IN_ENV || ENABLE_UNICODE_USING_LOCALE - ) { + ) { const char *s = get_local_var_value("LC_ALL"); if (!s) s = get_local_var_value("LC_CTYPE"); if (!s) s = get_local_var_value("LANG"); @@ -5795,8 +5795,8 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int */ setup_string_in_str(&input, str); - o_addchr(&dest, '\0'); - dest.length = 0; + o_addchr(&dest, '\0'); + dest.length = 0; exp_str = NULL; for (;;) { diff --git a/shell/random.c b/shell/random.c index 5d3620516..56c7c5a3c 100644 --- a/shell/random.c +++ b/shell/random.c @@ -46,11 +46,11 @@ next_random(random_t *rnd) * Choices for a,b,c: 10,13,10; 8,9,22; 2,7,3; 23,3,24 * (given by algorithm author) */ - enum { - a = 2, - b = 7, - c = 3, - }; + enum { + a = 2, + b = 7, + c = 3, + }; uint32_t t; @@ -154,7 +154,7 @@ int main(int argc, char **argv) write(1, buf, sizeof(buf)); } - return 0; + return 0; } #endif diff --git a/util-linux/unshare.c b/util-linux/unshare.c index 7c295da1f..fffee28a0 100644 --- a/util-linux/unshare.c +++ b/util-linux/unshare.c @@ -73,7 +73,7 @@ #include "libbb.h" static void mount_or_die(const char *source, const char *target, - const char *fstype, unsigned long mountflags) + const char *fstype, unsigned long mountflags) { if (mount(source, target, fstype, mountflags, NULL)) { bb_perror_msg_and_die("can't mount %s on %s (flags:0x%lx)", -- cgit v1.2.3-55-g6feb From 8b08d5a502adec2067e3ebf7f27513f75b0b95e8 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 18 Jul 2018 15:48:53 +0200 Subject: hush: smaller code to set o_string to "" function old new delta encode_then_expand_vararg 399 398 -1 parse_stream 2753 2748 -5 Signed-off-by: Denys Vlasenko --- shell/hush.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 7da8f334c..534fabbd0 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4997,8 +4997,7 @@ static struct pipe *parse_stream(char **pstring, /* If very first arg is "" or '', ctx.word.data may end up NULL. * Preventing this: */ - o_addchr(&ctx.word, '\0'); - ctx.word.length = 0; + ctx.word.data = xzalloc(1); /* start as "", not as NULL */ /* We used to separate words on $IFS here. This was wrong. * $IFS is used only for word splitting when $var is expanded, @@ -5795,8 +5794,7 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int */ setup_string_in_str(&input, str); - o_addchr(&dest, '\0'); - dest.length = 0; + dest.data = xzalloc(1); /* start as "", not as NULL */ exp_str = NULL; for (;;) { -- cgit v1.2.3-55-g6feb From 2e71101e31b8b421cee0107c4136d68b65e3a5d8 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 18 Jul 2018 16:02:25 +0200 Subject: hush: fix 'x=; echo ${x:-"$@"}' producing 'BUG in varexp2' message function old new delta expand_string_to_string 126 128 +2 Signed-off-by: Denys Vlasenko --- shell/hush.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 534fabbd0..da10a09a8 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -6529,13 +6529,20 @@ static char *expand_string_to_string(const char *str, int EXP_flags, int do_unba argv[0] = (char*)str; argv[1] = NULL; list = expand_variables(argv, EXP_flags | EXP_FLAG_SINGLEWORD); - if (HUSH_DEBUG) - if (!list[0] || list[1]) - bb_error_msg_and_die("BUG in varexp2"); - /* actually, just move string 2*sizeof(char*) bytes back */ - overlapping_strcpy((char*)list, list[0]); - if (do_unbackslash) - unbackslash((char*)list); + if (!list[0]) { + /* Example where it happens: + * x=; echo ${x:-"$@"} + */ + ((char*)list)[0] = '\0'; + } else { + if (HUSH_DEBUG) + if (list[1]) + bb_error_msg_and_die("BUG in varexp2"); + /* actually, just move string 2*sizeof(char*) bytes back */ + overlapping_strcpy((char*)list, list[0]); + if (do_unbackslash) + unbackslash((char*)list); + } debug_printf_expand("string_to_string=>'%s'\n", (char*)list); return (char*)list; } -- cgit v1.2.3-55-g6feb From e36a5894bd24a28b3529998bb7d0f87d7f5eb61b Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 18 Jul 2018 16:12:23 +0200 Subject: hush: reduce indentation, no code changes Signed-off-by: Denys Vlasenko --- shell/hush.c | 52 +++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index da10a09a8..92c79b8b6 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2423,35 +2423,33 @@ static void set_vars_and_save_old(char **strings) char *eq; eq = strchr(*s, '='); - if (eq) { - var_pp = get_ptr_to_local_var(*s, eq - *s); - if (var_pp) { - var_p = *var_pp; - if (var_p->flg_read_only) { - char **p; - bb_error_msg("%s: readonly variable", *s); - /* - * "VAR=V BLTIN" unsets VARs after BLTIN completes. - * If VAR is readonly, leaving it in the list - * after asssignment error (msg above) - * causes doubled error message later, on unset. - */ - debug_printf_env("removing/freeing '%s' element\n", *s); - free(*s); - p = s; - do { *p = p[1]; p++; } while (*p); - goto next; - } - /* below, set_local_var() with nest level will - * "shadow" (remove) this variable from - * global linked list. - */ - } - debug_printf_env("%s: env override '%s'/%u\n", __func__, *s, G.var_nest_level); - set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT); - } else if (HUSH_DEBUG) { + if (HUSH_DEBUG && !eq) bb_error_msg_and_die("BUG in varexp4"); + var_pp = get_ptr_to_local_var(*s, eq - *s); + if (var_pp) { + var_p = *var_pp; + if (var_p->flg_read_only) { + char **p; + bb_error_msg("%s: readonly variable", *s); + /* + * "VAR=V BLTIN" unsets VARs after BLTIN completes. + * If VAR is readonly, leaving it in the list + * after asssignment error (msg above) + * causes doubled error message later, on unset. + */ + debug_printf_env("removing/freeing '%s' element\n", *s); + free(*s); + p = s; + do { *p = p[1]; p++; } while (*p); + goto next; + } + /* below, set_local_var() with nest level will + * "shadow" (remove) this variable from + * global linked list. + */ } + debug_printf_env("%s: env override '%s'/%u\n", __func__, *s, G.var_nest_level); + set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT); s++; next: ; } -- cgit v1.2.3-55-g6feb From 116b50a5c1ea9d80d60641f2df2b61473b57fe47 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 19 Jul 2018 11:16:53 +0200 Subject: hush: make expand_vars_to_list() a bit more sane function old new delta append_str_maybe_ifs_split - 64 +64 expand_vars_to_list 1167 1139 -28 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/1 up/down: 64/-28) Total: 36 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 60 ++++++++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 28 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 92c79b8b6..b738d2fd8 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5955,6 +5955,24 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c } #endif /* BASH_PATTERN_SUBST */ +static int append_str_maybe_ifs_split(o_string *output, int *ended_in_ifs, int n, + int first_ch, const char *val) +{ + if (!(first_ch & 0x80)) { /* unquoted $VAR */ + debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, + !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); + if (val && val[0]) + n = expand_on_ifs(ended_in_ifs, output, n, val); + } else { /* quoted "$VAR" */ + output->has_quoted_part = 1; + debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, + !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); + if (val && val[0]) + o_addQstr(output, val); + } + return n; +} + /* Helper: * Handles varname... construct. */ @@ -6290,11 +6308,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) { char first_ch; - char *to_be_freed = NULL; const char *val = NULL; -#if ENABLE_HUSH_TICK - o_string subst_result = NULL_O_STRING; -#endif #if ENABLE_FEATURE_SH_MATH char arith_buf[sizeof(arith_t)*3 + 2]; #endif @@ -6382,7 +6396,9 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) val = SPECIAL_VAR_SYMBOL_STR; break; #if ENABLE_HUSH_TICK - case '`': /* `cmd */ + case '`': { /* `cmd */ + o_string subst_result = NULL_O_STRING; + *p = '\0'; /* replace trailing */ arg++; /* Can't just stuff it into output o_string, @@ -6392,8 +6408,10 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) G.last_exitcode = process_command_subs(&subst_result, arg); G.expand_exitcode = G.last_exitcode; debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); - val = subst_result.data; - goto store_val; + n = append_str_maybe_ifs_split(output, &ended_in_ifs, n, first_ch, subst_result.data); + o_free_unsafe(&subst_result); + goto restore; + } #endif #if ENABLE_FEATURE_SH_MATH case '+': { /* +cmd */ @@ -6409,37 +6427,23 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) break; } #endif - default: + default: { + char *to_be_freed; val = expand_one_var(&to_be_freed, arg, &p); - IF_HUSH_TICK(store_val:) - if (!(first_ch & 0x80)) { /* unquoted $VAR */ - debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, - !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); - if (val && val[0]) { - n = expand_on_ifs(&ended_in_ifs, output, n, val); - val = NULL; - } - } else { /* quoted $VAR, val will be appended below */ - output->has_quoted_part = 1; - debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, - !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); - } - break; + n = append_str_maybe_ifs_split(output, &ended_in_ifs, n, first_ch, val); + free(to_be_freed); + goto restore; + } /* default: */ } /* switch (char after ) */ if (val && val[0]) { o_addQstr(output, val); } - free(to_be_freed); - + restore: /* Restore NULL'ed SPECIAL_VAR_SYMBOL. * Do the check to avoid writing to a const string. */ if (*p != SPECIAL_VAR_SYMBOL) *p = SPECIAL_VAR_SYMBOL; - -#if ENABLE_HUSH_TICK - o_free(&subst_result); -#endif arg = ++p; } /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */ -- cgit v1.2.3-55-g6feb From 8a6a4615048d51af0e765e893211073faa7951cc Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 19 Jul 2018 12:14:47 +0200 Subject: hush: propagate (output,n) parameters into expand_one_var() This is necessary since expand_one_var() for ${var:+ARG} must create more than one output word, and thus can't simply return a char*. function old new delta expand_one_var 1610 1643 +33 expand_vars_to_list 1139 1125 -14 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 33/-14) Total: 19 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 14 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index b738d2fd8..ea259f53c 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5976,7 +5976,8 @@ static int append_str_maybe_ifs_split(o_string *output, int *ended_in_ifs, int n /* Helper: * Handles varname... construct. */ -static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, char **pp) +static NOINLINE int expand_one_var(o_string *output, + int *ended_in_ifs, int n, int first_ch, char *arg, char **pp) { const char *val; char *to_be_freed; @@ -6038,9 +6039,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha /* Look up the variable in question */ if (isdigit(var[0])) { /* parse_dollar should have vetted var for us */ - int n = xatoi_positive(var); - if (n < G.global_argc) - val = G.global_argv[n]; + int nn = xatoi_positive(var); + if (nn < G.global_argc) + val = G.global_argv[nn]; /* else val remains NULL: $N with too big N */ } else { switch (var[0]) { @@ -6236,6 +6237,34 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha * Colon forms (${var:-word}, ${var:=word} etc) do the same, * but also treat null var as if it is unset. */ +/* + * Word-splitting and squote behavior of bash: + * $ f() { for i; do echo "|$i|"; done; }; + * + * $ x=; f ${x:?'x y' z} + * bash: x: x y z + * $ x=; f "${x:?'x y' z}" + * bash: x: x y z # dash prints: dash: x: 'x y' z + * + * $ x=; f ${x:='x y' z} + * |x| + * |y| + * |z| + * $ x=; f "${x:='x y' z}" + * |'x y' z| + * + * $ x=x; f ${x:+'x y' z} + * |x y| + * |z| + * $ x=x; f "${x:+'x y' z}" + * |'x y' z| + * + * $ x=; f ${x:-'x y' z} + * |x y| + * |z| + * $ x=; f "${x:-'x y' z}" + * |'x y' z| + */ int use_word = (!val || ((exp_save == ':') && !val[0])); if (exp_op == '+') use_word = !use_word; @@ -6244,7 +6273,10 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha if (use_word) { //FIXME: unquoted ${x:+"b c" d} and ${x:+'b c' d} should expand to two words //currently it expands to three. - to_be_freed = encode_then_expand_vararg(exp_word, /*handle_squotes:*/ !(arg0 & 0x80), /*unbackslash:*/ 0); + to_be_freed = encode_then_expand_vararg(exp_word, + /*handle_squotes:*/ !(arg0 & 0x80), + /*unbackslash:*/ 0 + ); if (to_be_freed) exp_word = to_be_freed; if (exp_op == '?') { @@ -6258,6 +6290,11 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha /*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/ ); //TODO: how interactive bash aborts expansion mid-command? +//It aborts the entire line: +// $ f() { for i; do echo "|$i|"; done; }; x=; f "${x:?'x y' z}"; echo YO +// bash: x: x y z +// $ +// ("echo YO" is not executed, neither the f function call) } else { val = exp_word; } @@ -6280,10 +6317,12 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha } /* if (exp_op) */ arg[0] = arg0; - *pp = p; - *to_be_freed_pp = to_be_freed; - return val; + + n = append_str_maybe_ifs_split(output, ended_in_ifs, n, first_ch, val); + + free(to_be_freed); + return n; } /* Expand all variable references in given string, adding words to list[] @@ -6427,13 +6466,9 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) break; } #endif - default: { - char *to_be_freed; - val = expand_one_var(&to_be_freed, arg, &p); - n = append_str_maybe_ifs_split(output, &ended_in_ifs, n, first_ch, val); - free(to_be_freed); + default: + n = expand_one_var(output, &ended_in_ifs, n, first_ch, arg, &p); goto restore; - } /* default: */ } /* switch (char after ) */ if (val && val[0]) { -- cgit v1.2.3-55-g6feb From 168579a34c6fc6c2f2cd20256367ceb61f50bf68 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 19 Jul 2018 13:45:54 +0200 Subject: hush: store "ended_in_ifs" flag in o_string This simplifies function parameter passing. function old new delta expand_one_var 1643 1639 -4 append_str_maybe_ifs_split 64 52 -12 expand_vars_to_list 1125 1112 -13 expand_on_ifs 361 345 -16 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/4 up/down: 0/-45) Total: -45 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index ea259f53c..2d4e3fa66 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -534,6 +534,7 @@ typedef struct o_string { * possibly empty one: word"", wo''rd etc. */ smallint has_quoted_part; smallint has_empty_slot; + smallint ended_in_ifs; } o_string; enum { EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ @@ -5643,10 +5644,10 @@ static void o_addblock_duplicate_backslash(o_string *o, const char *str, int len /* Store given string, finalizing the word and starting new one whenever * we encounter IFS char(s). This is used for expanding variable values. * End-of-string does NOT finalize word: think about 'echo -$VAR-'. - * Return in *ended_with_ifs: + * Return in output->ended_in_ifs: * 1 - ended with IFS char, else 0 (this includes case of empty str). */ -static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const char *str) +static int expand_on_ifs(o_string *output, int n, const char *str) { int last_is_ifs = 0; @@ -5709,8 +5710,7 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha } } - if (ended_with_ifs) - *ended_with_ifs = last_is_ifs; + output->ended_in_ifs = last_is_ifs; debug_print_list("expand_on_ifs[1]", output, n); return n; } @@ -5955,14 +5955,14 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c } #endif /* BASH_PATTERN_SUBST */ -static int append_str_maybe_ifs_split(o_string *output, int *ended_in_ifs, int n, +static int append_str_maybe_ifs_split(o_string *output, int n, int first_ch, const char *val) { if (!(first_ch & 0x80)) { /* unquoted $VAR */ debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, !!(output->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); if (val && val[0]) - n = expand_on_ifs(ended_in_ifs, output, n, val); + n = expand_on_ifs(output, n, val); } else { /* quoted "$VAR" */ output->has_quoted_part = 1; debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, @@ -5977,7 +5977,7 @@ static int append_str_maybe_ifs_split(o_string *output, int *ended_in_ifs, int n * Handles varname... construct. */ static NOINLINE int expand_one_var(o_string *output, - int *ended_in_ifs, int n, int first_ch, char *arg, char **pp) + int n, int first_ch, char *arg, char **pp) { const char *val; char *to_be_freed; @@ -6242,9 +6242,9 @@ static NOINLINE int expand_one_var(o_string *output, * $ f() { for i; do echo "|$i|"; done; }; * * $ x=; f ${x:?'x y' z} - * bash: x: x y z + * bash: x: x y z #BUG: does not abort, ${} results in empty expansion * $ x=; f "${x:?'x y' z}" - * bash: x: x y z # dash prints: dash: x: 'x y' z + * bash: x: x y z # dash prints: dash: x: 'x y' z #BUG: does not abort, ${} results in "" * * $ x=; f ${x:='x y' z} * |x| @@ -6290,7 +6290,7 @@ static NOINLINE int expand_one_var(o_string *output, /*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/ ); //TODO: how interactive bash aborts expansion mid-command? -//It aborts the entire line: +//It aborts the entire line, returns to prompt: // $ f() { for i; do echo "|$i|"; done; }; x=; f "${x:?'x y' z}"; echo YO // bash: x: x y z // $ @@ -6319,7 +6319,7 @@ static NOINLINE int expand_one_var(o_string *output, arg[0] = arg0; *pp = p; - n = append_str_maybe_ifs_split(output, ended_in_ifs, n, first_ch, val); + n = append_str_maybe_ifs_split(output, n, first_ch, val); free(to_be_freed); return n; @@ -6336,9 +6336,10 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) * expansion of right-hand side of assignment == 1-element expand. */ char cant_be_null = 0; /* only bit 0x80 matters */ - int ended_in_ifs = 0; /* did last unquoted expansion end with IFS chars? */ char *p; + output->ended_in_ifs = 0; /* did last unquoted expansion end with IFS chars? */ + debug_printf_expand("expand_vars_to_list: arg:'%s' singleword:%x\n", arg, !!(output->o_expflags & EXP_FLAG_SINGLEWORD)); debug_print_list("expand_vars_to_list", output, n); @@ -6352,10 +6353,10 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) char arith_buf[sizeof(arith_t)*3 + 2]; #endif - if (ended_in_ifs) { + if (output->ended_in_ifs) { o_addchr(output, '\0'); n = o_save_ptr(output, n); - ended_in_ifs = 0; + output->ended_in_ifs = 0; } o_addblock(output, arg, p - arg); @@ -6386,7 +6387,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) cant_be_null |= first_ch; /* do it for "$@" _now_, when we know it's not empty */ if (!(first_ch & 0x80)) { /* unquoted $* or $@ */ while (G.global_argv[i]) { - n = expand_on_ifs(NULL, output, n, G.global_argv[i]); + n = expand_on_ifs(output, n, G.global_argv[i]); debug_printf_expand("expand_vars_to_list: argv %d (last %d)\n", i, G.global_argc - 1); if (G.global_argv[i++][0] && G.global_argv[i]) { /* this argv[] is not empty and not last: @@ -6447,7 +6448,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) G.last_exitcode = process_command_subs(&subst_result, arg); G.expand_exitcode = G.last_exitcode; debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); - n = append_str_maybe_ifs_split(output, &ended_in_ifs, n, first_ch, subst_result.data); + n = append_str_maybe_ifs_split(output, n, first_ch, subst_result.data); o_free_unsafe(&subst_result); goto restore; } @@ -6467,7 +6468,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) } #endif default: - n = expand_one_var(output, &ended_in_ifs, n, first_ch, arg, &p); + n = expand_one_var(output, n, first_ch, arg, &p); goto restore; } /* switch (char after ) */ @@ -6483,7 +6484,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) } /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */ if (arg[0]) { - if (ended_in_ifs) { + if (output->ended_in_ifs) { o_addchr(output, '\0'); n = o_save_ptr(output, n); } -- cgit v1.2.3-55-g6feb From 18e8b6129298c02f7b5ccc17fee6bccc01a8968f Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 20 Jul 2018 14:24:56 +0200 Subject: hush: remove pointless "next" var, simplify expand_vars_to_list() function old new delta o_addstr - 26 +26 expand_vars_to_list 1112 1117 +5 encode_then_expand_vararg 398 382 -16 parse_dollar 779 762 -17 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 1/2 up/down: 31/-33) Total: -2 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 2d4e3fa66..fca67dc17 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -475,6 +475,7 @@ #endif #define SPECIAL_VAR_SYMBOL_STR "\3" +#define SPECIAL_VAR_SYMBOL_CHR '\3' #define SPECIAL_VAR_SYMBOL 3 /* The "variable" with name "\1" emits string "\3". Testcase: "echo ^C" */ #define SPECIAL_VAR_QUOTED_SVS 1 @@ -5797,7 +5798,6 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int for (;;) { int ch; - int next; ch = i_getch(&input); if (ch == EOF) { @@ -5837,10 +5837,6 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int o_addqchr(&dest, ch); continue; } - next = '\0'; - if (ch != '\n') { - next = i_peek(&input); - } if (ch == '$') { if (!parse_dollar(NULL, &dest, &input, /*quote_mask:*/ 0x80)) { debug_printf_parse("%s: error: parse_dollar returned 0 (error)\n", __func__); @@ -5956,7 +5952,7 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c #endif /* BASH_PATTERN_SUBST */ static int append_str_maybe_ifs_split(o_string *output, int n, - int first_ch, const char *val) + int first_ch, const char *val) { if (!(first_ch & 0x80)) { /* unquoted $VAR */ debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, @@ -6348,7 +6344,6 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) { char first_ch; - const char *val = NULL; #if ENABLE_FEATURE_SH_MATH char arith_buf[sizeof(arith_t)*3 + 2]; #endif @@ -6424,19 +6419,23 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) } break; } - case SPECIAL_VAR_SYMBOL: /* */ + case SPECIAL_VAR_SYMBOL: { + /* */ /* "Empty variable", used to make "" etc to not disappear */ output->has_quoted_part = 1; - arg++; cant_be_null = 0x80; + arg++; break; + } case SPECIAL_VAR_QUOTED_SVS: /* */ + /* "^C variable", represents literal ^C char (possible in scripts) */ + o_addchr(output, SPECIAL_VAR_SYMBOL_CHR); arg++; - val = SPECIAL_VAR_SYMBOL_STR; break; #if ENABLE_HUSH_TICK - case '`': { /* `cmd */ + case '`': { + /* `cmd */ o_string subst_result = NULL_O_STRING; *p = '\0'; /* replace trailing */ @@ -6450,11 +6449,12 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); n = append_str_maybe_ifs_split(output, n, first_ch, subst_result.data); o_free_unsafe(&subst_result); - goto restore; + break; } #endif #if ENABLE_FEATURE_SH_MATH - case '+': { /* +cmd */ + case '+': { + /* +arith */ arith_t res; arg++; /* skip '+' */ @@ -6463,19 +6463,16 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) res = expand_and_evaluate_arith(arg, NULL); debug_printf_subst("ARITH RES '"ARITH_FMT"'\n", res); sprintf(arith_buf, ARITH_FMT, res); - val = arith_buf; + o_addstr(output, arith_buf); break; } #endif default: + /* varname[ops] */ n = expand_one_var(output, n, first_ch, arg, &p); - goto restore; + break; } /* switch (char after ) */ - if (val && val[0]) { - o_addQstr(output, val); - } - restore: /* Restore NULL'ed SPECIAL_VAR_SYMBOL. * Do the check to avoid writing to a const string. */ if (*p != SPECIAL_VAR_SYMBOL) -- cgit v1.2.3-55-g6feb From 57235beb696a7dbdb48751b9721c4c025127ae96 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 20 Jul 2018 14:45:12 +0200 Subject: hush: expand_vars_to_list() should not assume it starts new word function old new delta expand_variables 112 115 +3 expand_vars_to_list 1117 1108 -9 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 3/-9) Total: -6 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index fca67dc17..b8af1b088 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -3343,7 +3343,6 @@ static char **o_finalize_list(o_string *o, int n) char **list; int string_start; - n = o_save_ptr(o, n); /* force growth for list[n] if necessary */ if (DEBUG_EXPAND) debug_print_list("finalized", o, n); debug_printf_expand("finalized n:%d\n", n); @@ -6334,12 +6333,8 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) char cant_be_null = 0; /* only bit 0x80 matters */ char *p; - output->ended_in_ifs = 0; /* did last unquoted expansion end with IFS chars? */ - debug_printf_expand("expand_vars_to_list: arg:'%s' singleword:%x\n", arg, !!(output->o_expflags & EXP_FLAG_SINGLEWORD)); - debug_print_list("expand_vars_to_list", output, n); - n = o_save_ptr(output, n); debug_print_list("expand_vars_to_list[0]", output, n); while ((p = strchr(arg, SPECIAL_VAR_SYMBOL)) != NULL) { @@ -6512,9 +6507,16 @@ static char **expand_variables(char **argv, unsigned expflags) output.o_expflags = expflags; n = 0; - while (*argv) { - n = expand_vars_to_list(&output, n, *argv); - argv++; + for (;;) { + /* go to next list[n] */ + output.ended_in_ifs = 0; + n = o_save_ptr(&output, n); + + if (!*argv) + break; + + /* expand argv[i] */ + n = expand_vars_to_list(&output, n, *argv++); } debug_print_list("expand_variables", &output, n); -- cgit v1.2.3-55-g6feb From 294eb4612cd668521faa48711297196f00af61d9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 20 Jul 2018 16:18:59 +0200 Subject: hush: fix word splitting in ${v:+ARG} - dollar_altvalue1 test ash might be a bit buggy, need to investigate dollar_altvalue9 test function old new delta expand_one_var 1639 2236 +597 expand_variables 112 128 +16 expand_vars_to_list 1117 1097 -20 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/1 up/down: 613/-20) Total: 593 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-quoting/dollar_altvalue9.right | 24 ++ shell/ash_test/ash-quoting/dollar_altvalue9.tests | 17 ++ shell/hush.c | 263 ++++++++++++++++----- .../hush_test/hush-quoting/dollar_altvalue9.right | 24 ++ .../hush_test/hush-quoting/dollar_altvalue9.tests | 17 ++ 5 files changed, 284 insertions(+), 61 deletions(-) create mode 100644 shell/ash_test/ash-quoting/dollar_altvalue9.right create mode 100755 shell/ash_test/ash-quoting/dollar_altvalue9.tests create mode 100644 shell/hush_test/hush-quoting/dollar_altvalue9.right create mode 100755 shell/hush_test/hush-quoting/dollar_altvalue9.tests (limited to 'shell') diff --git a/shell/ash_test/ash-quoting/dollar_altvalue9.right b/shell/ash_test/ash-quoting/dollar_altvalue9.right new file mode 100644 index 000000000..fc6c2697c --- /dev/null +++ b/shell/ash_test/ash-quoting/dollar_altvalue9.right @@ -0,0 +1,24 @@ +Unquoted 1: +|a| +|x y| +|1| +|2| +|1 2| +|A| +|B| +|C D| +|zb| +Quoted 1: +|a 'x y' 1 2 '' 1 2 A B C D zb| +Unquoted 2: +|ax y| +|1| +|2| +|1 2| +|A| +|B| +|C D| +|z| +|b| +Quoted 2: +|a 'x y' 1 2 '' 1 2 A B C D z b| diff --git a/shell/ash_test/ash-quoting/dollar_altvalue9.tests b/shell/ash_test/ash-quoting/dollar_altvalue9.tests new file mode 100755 index 000000000..27a6f4f3c --- /dev/null +++ b/shell/ash_test/ash-quoting/dollar_altvalue9.tests @@ -0,0 +1,17 @@ +f() { for i; do echo "|$i|"; done; } + +echo Unquoted 1: +x='1 2'; f a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b +echo Quoted 1: +x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b" + +echo Unquoted 2: +x='1 2'; f a${x:+'x y' $x '' "$x" `echo A B` "`echo C D`" z }b +echo Quoted 2: +x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z }b" + +#echo Unquoted 3: +#e= +#x='1 2'; f a${x:+'x y' $x '' "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b +#echo Quoted 3: +#x='1 2'; f "a${x:+ 'x y' $x '' "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b" diff --git a/shell/hush.c b/shell/hush.c index b8af1b088..fc77b89fc 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5873,6 +5873,138 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int return exp_str; } +static int expand_vars_to_list(o_string *output, int n, char *arg); + +static int encode_then_append_var_plusminus(o_string *output, int n, + const 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++; + } +#endif + + /* Expanding ARG in ${var+ARG}, ${var-ARG} */ + + setup_string_in_str(&input, str); + + for (;;) { + int ch; + + ch = i_getch(&input); + debug_printf_parse("%s: ch=%c (%d) escape=%x\n", + __func__, ch, ch, dest.o_expflags); + + if (!dest.o_expflags) { + if (ch == EOF) + break; + if (!dquoted && strchr(G.ifs, ch)) { + /* PREFIX${x:d${e}f ...} and we met space: expand "d${e}f" and start new word. + * do not assume we are at the start of the word (PREFIX above). + */ + if (dest.data) { + n = expand_vars_to_list(output, n, dest.data); + o_free(&dest); + o_addchr(output, '\0'); + n = o_save_ptr(output, n); /* create next word */ + } else + if (output->length != o_get_last_ptr(output, n) + || output->has_quoted_part + ) { + /* For these cases: + * f() { for i; do echo "|$i|"; done; }; x=x + * f a${x:+ }b # 1st condition + * |a| + * |b| + * f ""${x:+ }b # 2nd condition + * || + * |b| + */ + o_addchr(output, '\0'); + n = o_save_ptr(output, n); /* create next word */ + } + continue; + } + if (!dquoted && ch == '\'') { +//quoting version of add_till_single_quote() (try to merge?): + for (;;) { + ch = i_getch(&input); + if (ch == EOF) { + syntax_error_unterm_ch('\''); + goto ret; /* error */ + } + if (ch == '\'') + break; + o_addqchr(&dest, ch); + } + continue; + } + } + if (ch == EOF) { + syntax_error_unterm_ch('"'); + goto ret; /* error */ + } + if (ch == '"') { + dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS; + continue; + } + if (ch == '\\') { + ch = i_getch(&input); + if (ch == EOF) { +//example? error message? syntax_error_unterm_ch('"'); + debug_printf_parse("%s: error: \\\n", __func__); + goto ret; + } + o_addqchr(&dest, ch); + continue; + } + if (ch == '$') { + if (!parse_dollar(NULL, &dest, &input, /*quote_mask:*/ (dest.o_expflags || dquoted) ? 0x80 : 0)) { + debug_printf_parse("%s: error: parse_dollar returned 0 (error)\n", __func__); + goto ret; + } + continue; + } +#if ENABLE_HUSH_TICK + if (ch == '`') { + //unsigned pos = dest->length; + o_addchr(&dest, SPECIAL_VAR_SYMBOL); + o_addchr(&dest, (dest.o_expflags || dquoted) ? 0x80 | '`' : '`'); + if (!add_till_backquote(&dest, &input, + /*in_dquote:*/ dest.o_expflags /* nonzero if EXP_FLAG_ESC_GLOB_CHARS set */ + ) + ) { + goto ret; /* error */ + } + o_addchr(&dest, SPECIAL_VAR_SYMBOL); + //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos); + continue; + } +#endif + o_addQchr(&dest, ch); + } /* for (;;) */ + + if (dest.data) { + n = expand_vars_to_list(output, n, dest.data); + } + ret: + o_free_unsafe(&dest); + return n; +} + #if ENABLE_FEATURE_SH_MATH static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p) { @@ -6231,78 +6363,86 @@ static NOINLINE int expand_one_var(o_string *output, * * Colon forms (${var:-word}, ${var:=word} etc) do the same, * but also treat null var as if it is unset. + * + * Word-splitting and single quote behavior: + * + * $ f() { for i; do echo "|$i|"; done; }; + * + * $ x=; f ${x:?'x y' z} + * bash: x: x y z #BUG: does not abort, ${} results in empty expansion + * $ x=; f "${x:?'x y' z}" + * bash: x: x y z # dash prints: dash: x: 'x y' z #BUG: does not abort, ${} results in "" + * + * $ x=; f ${x:='x y' z} + * |x| + * |y| + * |z| + * $ x=; f "${x:='x y' z}" + * |'x y' z| + * + * $ x=x; f ${x:+'x y' z}| + * |x y| + * |z| + * $ x=x; f "${x:+'x y' z}" + * |'x y' z| + * + * $ x=; f ${x:-'x y' z} + * |x y| + * |z| + * $ x=; f "${x:-'x y' z}" + * |'x y' z| */ -/* - * Word-splitting and squote behavior of bash: - * $ f() { for i; do echo "|$i|"; done; }; - * - * $ x=; f ${x:?'x y' z} - * bash: x: x y z #BUG: does not abort, ${} results in empty expansion - * $ x=; f "${x:?'x y' z}" - * bash: x: x y z # dash prints: dash: x: 'x y' z #BUG: does not abort, ${} results in "" - * - * $ x=; f ${x:='x y' z} - * |x| - * |y| - * |z| - * $ x=; f "${x:='x y' z}" - * |'x y' z| - * - * $ x=x; f ${x:+'x y' z} - * |x y| - * |z| - * $ x=x; f "${x:+'x y' z}" - * |'x y' z| - * - * $ x=; f ${x:-'x y' z} - * |x y| - * |z| - * $ x=; f "${x:-'x y' z}" - * |'x y' z| - */ int use_word = (!val || ((exp_save == ':') && !val[0])); if (exp_op == '+') use_word = !use_word; debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, (exp_save == ':') ? "true" : "false", use_word); if (use_word) { -//FIXME: unquoted ${x:+"b c" d} and ${x:+'b c' d} should expand to two words -//currently it expands to three. - to_be_freed = encode_then_expand_vararg(exp_word, - /*handle_squotes:*/ !(arg0 & 0x80), - /*unbackslash:*/ 0 - ); - if (to_be_freed) - exp_word = to_be_freed; - if (exp_op == '?') { - /* mimic bash message */ - msg_and_die_if_script("%s: %s", - var, - exp_word[0] - ? exp_word - : "parameter null or not set" - /* ash has more specific messages, a-la: */ - /*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/ + if (exp_op == '+' || exp_op == '-') { + /* ${var+word} - use alternative value */ + /* ${var-word} - use default value */ + n = encode_then_append_var_plusminus(output, n, exp_word, + /*dquoted:*/ (arg0 & 0x80) ); + val = NULL; + } else { + /* ${var?word} - indicate error if unset */ + /* ${var=word} - assign and use default value */ + to_be_freed = encode_then_expand_vararg(exp_word, + /*handle_squotes:*/ !(arg0 & 0x80), + /*unbackslash:*/ 0 + ); + if (to_be_freed) + exp_word = to_be_freed; + if (exp_op == '?') { + /* mimic bash message */ + msg_and_die_if_script("%s: %s", + var, + exp_word[0] + ? exp_word + : "parameter null or not set" + /* ash has more specific messages, a-la: */ + /*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/ + ); //TODO: how interactive bash aborts expansion mid-command? //It aborts the entire line, returns to prompt: // $ f() { for i; do echo "|$i|"; done; }; x=; f "${x:?'x y' z}"; echo YO // bash: x: x y z // $ // ("echo YO" is not executed, neither the f function call) - } else { - val = exp_word; - } - - if (exp_op == '=') { - /* ${var=[word]} or ${var:=[word]} */ - if (isdigit(var[0]) || var[0] == '#') { - /* mimic bash message */ - msg_and_die_if_script("$%s: cannot assign in this way", var); - val = NULL; } else { - char *new_var = xasprintf("%s=%s", var, val); - set_local_var(new_var, /*flag:*/ 0); + val = exp_word; + } + if (exp_op == '=') { + /* ${var=[word]} or ${var:=[word]} */ + if (isdigit(var[0]) || var[0] == '#') { + /* mimic bash message */ + msg_and_die_if_script("$%s: cannot assign in this way", var); + val = NULL; + } else { + char *new_var = xasprintf("%s=%s", var, val); + set_local_var(new_var, /*flag:*/ 0); + } } } } @@ -6482,8 +6622,9 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) } debug_print_list("expand_vars_to_list[a]", output, n); /* this part is literal, and it was already pre-quoted - * if needed (much earlier), do not use o_addQstr here! */ - o_addstr_with_NUL(output, arg); + * if needed (much earlier), do not use o_addQstr here! + */ + o_addstr(output, arg); debug_print_list("expand_vars_to_list[b]", output, n); } else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */ && !(cant_be_null & 0x80) /* and all vars were not quoted. */ @@ -6491,8 +6632,6 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) n--; /* allow to reuse list[n] later without re-growth */ output->has_empty_slot = 1; - } else { - o_addchr(output, '\0'); } return n; @@ -6517,6 +6656,8 @@ static char **expand_variables(char **argv, unsigned expflags) /* expand argv[i] */ n = expand_vars_to_list(&output, n, *argv++); + /* if (!output->has_empty_slot) -- need this?? */ + o_addchr(&output, '\0'); } debug_print_list("expand_variables", &output, n); diff --git a/shell/hush_test/hush-quoting/dollar_altvalue9.right b/shell/hush_test/hush-quoting/dollar_altvalue9.right new file mode 100644 index 000000000..fc6c2697c --- /dev/null +++ b/shell/hush_test/hush-quoting/dollar_altvalue9.right @@ -0,0 +1,24 @@ +Unquoted 1: +|a| +|x y| +|1| +|2| +|1 2| +|A| +|B| +|C D| +|zb| +Quoted 1: +|a 'x y' 1 2 '' 1 2 A B C D zb| +Unquoted 2: +|ax y| +|1| +|2| +|1 2| +|A| +|B| +|C D| +|z| +|b| +Quoted 2: +|a 'x y' 1 2 '' 1 2 A B C D z b| diff --git a/shell/hush_test/hush-quoting/dollar_altvalue9.tests b/shell/hush_test/hush-quoting/dollar_altvalue9.tests new file mode 100755 index 000000000..27a6f4f3c --- /dev/null +++ b/shell/hush_test/hush-quoting/dollar_altvalue9.tests @@ -0,0 +1,17 @@ +f() { for i; do echo "|$i|"; done; } + +echo Unquoted 1: +x='1 2'; f a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b +echo Quoted 1: +x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b" + +echo Unquoted 2: +x='1 2'; f a${x:+'x y' $x '' "$x" `echo A B` "`echo C D`" z }b +echo Quoted 2: +x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z }b" + +#echo Unquoted 3: +#e= +#x='1 2'; f a${x:+'x y' $x '' "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b +#echo Quoted 3: +#x='1 2'; f "a${x:+ 'x y' $x '' "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b" -- cgit v1.2.3-55-g6feb From 83e434d5b56baccf617ebcc8a752959f7c4aacfc Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 20 Jul 2018 17:36:06 +0200 Subject: hush: fix handling of '' in ${var:+ARG} This wasn't an ash bug in dollar_altvalue9, it was hush bug (and bash!) function old new delta expand_one_var 2236 2254 +18 expand_vars_to_list 1097 1103 +6 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 24/0) Total: 24 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-quoting/dollar_altvalue2.right | 69 ++++++++++++++++++++++ shell/ash_test/ash-quoting/dollar_altvalue2.tests | 33 +++++++++++ shell/ash_test/ash-quoting/dollar_altvalue9.right | 2 + shell/hush.c | 12 +++- .../hush_test/hush-quoting/dollar_altvalue2.right | 69 ++++++++++++++++++++++ .../hush_test/hush-quoting/dollar_altvalue2.tests | 33 +++++++++++ .../hush_test/hush-quoting/dollar_altvalue9.right | 2 + 7 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 shell/ash_test/ash-quoting/dollar_altvalue2.right create mode 100755 shell/ash_test/ash-quoting/dollar_altvalue2.tests create mode 100644 shell/hush_test/hush-quoting/dollar_altvalue2.right create mode 100755 shell/hush_test/hush-quoting/dollar_altvalue2.tests (limited to 'shell') diff --git a/shell/ash_test/ash-quoting/dollar_altvalue2.right b/shell/ash_test/ash-quoting/dollar_altvalue2.right new file mode 100644 index 000000000..7cf37e379 --- /dev/null +++ b/shell/ash_test/ash-quoting/dollar_altvalue2.right @@ -0,0 +1,69 @@ +Unquoted '': +start: +|| +end +start: +|| +end +start: +|| +end +start: +|| +end +start: +|| +|| +end + +Unquoted "": +start: +|| +end +start: +|| +end +start: +|| +end +start: +|| +end +start: +|| +|| +end + +Quoted '': +start: +|''| +end +start: +|'' | +end +start: +| ''| +end +start: +| '' | +end +start: +|'' ''| +end + +Quoted "": +start: +|| +end +start: +| | +end +start: +| | +end +start: +| | +end +start: +| | +end diff --git a/shell/ash_test/ash-quoting/dollar_altvalue2.tests b/shell/ash_test/ash-quoting/dollar_altvalue2.tests new file mode 100755 index 000000000..3377eb27f --- /dev/null +++ b/shell/ash_test/ash-quoting/dollar_altvalue2.tests @@ -0,0 +1,33 @@ +f() { echo start:; for i; do echo "|$i|"; done; echo end; } +x=a + +echo "Unquoted '':" +f ${x:+''} +f ${x:+'' } +f ${x:+ ''} +f ${x:+ '' } +f ${x:+'' ''} + +echo +echo 'Unquoted "":' +f ${x:+""} +f ${x:+"" } +f ${x:+ ""} +f ${x:+ "" } +f ${x:+"" ""} + +echo +echo "Quoted '':" +f "${x:+''}" +f "${x:+'' }" +f "${x:+ ''}" +f "${x:+ '' }" +f "${x:+'' ''}" + +echo +echo 'Quoted "":' +f "${x:+""}" +f "${x:+"" }" +f "${x:+ ""}" +f "${x:+ "" }" +f "${x:+"" ""}" diff --git a/shell/ash_test/ash-quoting/dollar_altvalue9.right b/shell/ash_test/ash-quoting/dollar_altvalue9.right index fc6c2697c..39342fe7c 100644 --- a/shell/ash_test/ash-quoting/dollar_altvalue9.right +++ b/shell/ash_test/ash-quoting/dollar_altvalue9.right @@ -3,6 +3,7 @@ Unquoted 1: |x y| |1| |2| +|| |1 2| |A| |B| @@ -14,6 +15,7 @@ Unquoted 2: |ax y| |1| |2| +|| |1 2| |A| |B| diff --git a/shell/hush.c b/shell/hush.c index fc77b89fc..559595d2e 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -475,7 +475,6 @@ #endif #define SPECIAL_VAR_SYMBOL_STR "\3" -#define SPECIAL_VAR_SYMBOL_CHR '\3' #define SPECIAL_VAR_SYMBOL 3 /* The "variable" with name "\1" emits string "\3". Testcase: "echo ^C" */ #define SPECIAL_VAR_QUOTED_SVS 1 @@ -5950,6 +5949,8 @@ static int encode_then_append_var_plusminus(o_string *output, int n, break; o_addqchr(&dest, ch); } + o_addchr(&dest, SPECIAL_VAR_SYMBOL); + o_addchr(&dest, SPECIAL_VAR_SYMBOL); continue; } } @@ -5959,6 +5960,10 @@ static int encode_then_append_var_plusminus(o_string *output, int n, } if (ch == '"') { dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS; + if (dest.o_expflags) { + o_addchr(&dest, SPECIAL_VAR_SYMBOL); + o_addchr(&dest, SPECIAL_VAR_SYMBOL); + } continue; } if (ch == '\\') { @@ -6565,7 +6570,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) case SPECIAL_VAR_QUOTED_SVS: /* */ /* "^C variable", represents literal ^C char (possible in scripts) */ - o_addchr(output, SPECIAL_VAR_SYMBOL_CHR); + o_addchr(output, SPECIAL_VAR_SYMBOL); arg++; break; #if ENABLE_HUSH_TICK @@ -6627,7 +6632,8 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) o_addstr(output, arg); debug_print_list("expand_vars_to_list[b]", output, n); } else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */ - && !(cant_be_null & 0x80) /* and all vars were not quoted. */ + && !(cant_be_null & 0x80) /* and all vars were not quoted */ + && !output->has_quoted_part ) { n--; /* allow to reuse list[n] later without re-growth */ diff --git a/shell/hush_test/hush-quoting/dollar_altvalue2.right b/shell/hush_test/hush-quoting/dollar_altvalue2.right new file mode 100644 index 000000000..7cf37e379 --- /dev/null +++ b/shell/hush_test/hush-quoting/dollar_altvalue2.right @@ -0,0 +1,69 @@ +Unquoted '': +start: +|| +end +start: +|| +end +start: +|| +end +start: +|| +end +start: +|| +|| +end + +Unquoted "": +start: +|| +end +start: +|| +end +start: +|| +end +start: +|| +end +start: +|| +|| +end + +Quoted '': +start: +|''| +end +start: +|'' | +end +start: +| ''| +end +start: +| '' | +end +start: +|'' ''| +end + +Quoted "": +start: +|| +end +start: +| | +end +start: +| | +end +start: +| | +end +start: +| | +end diff --git a/shell/hush_test/hush-quoting/dollar_altvalue2.tests b/shell/hush_test/hush-quoting/dollar_altvalue2.tests new file mode 100755 index 000000000..3377eb27f --- /dev/null +++ b/shell/hush_test/hush-quoting/dollar_altvalue2.tests @@ -0,0 +1,33 @@ +f() { echo start:; for i; do echo "|$i|"; done; echo end; } +x=a + +echo "Unquoted '':" +f ${x:+''} +f ${x:+'' } +f ${x:+ ''} +f ${x:+ '' } +f ${x:+'' ''} + +echo +echo 'Unquoted "":' +f ${x:+""} +f ${x:+"" } +f ${x:+ ""} +f ${x:+ "" } +f ${x:+"" ""} + +echo +echo "Quoted '':" +f "${x:+''}" +f "${x:+'' }" +f "${x:+ ''}" +f "${x:+ '' }" +f "${x:+'' ''}" + +echo +echo 'Quoted "":' +f "${x:+""}" +f "${x:+"" }" +f "${x:+ ""}" +f "${x:+ "" }" +f "${x:+"" ""}" diff --git a/shell/hush_test/hush-quoting/dollar_altvalue9.right b/shell/hush_test/hush-quoting/dollar_altvalue9.right index fc6c2697c..39342fe7c 100644 --- a/shell/hush_test/hush-quoting/dollar_altvalue9.right +++ b/shell/hush_test/hush-quoting/dollar_altvalue9.right @@ -3,6 +3,7 @@ Unquoted 1: |x y| |1| |2| +|| |1 2| |A| |B| @@ -14,6 +15,7 @@ Unquoted 2: |ax y| |1| |2| +|| |1 2| |A| |B| -- cgit v1.2.3-55-g6feb From 1856740ec0b4ffd8f780c3cc1ef9b38c9fc1eead Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 20 Jul 2018 17:51:31 +0200 Subject: hush: better names for o_free_unsafe() / o_free(), no logic changes o_free() made o_string NULL-initialized, o_free_unsafe() did not bother reinitializing (expected caller to not need it). Signed-off-by: Denys Vlasenko --- shell/hush.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 559595d2e..2da288c15 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2797,13 +2797,13 @@ static void o_reset_to_empty_unquoted(o_string *o) o->data[0] = '\0'; } -static void o_free(o_string *o) +static void o_free_and_set_NULL(o_string *o) { free(o->data); memset(o, 0, sizeof(*o)); } -static ALWAYS_INLINE void o_free_unsafe(o_string *o) +static ALWAYS_INLINE void o_free(o_string *o) { free(o->data); } @@ -3852,7 +3852,7 @@ static const struct reserved_combo* reserved_word(struct parse_context *ctx) int len = old->as_string.length; /* Concatenate halves */ o_addstr(&old->as_string, ctx->as_string.data); - o_free_unsafe(&ctx->as_string); + o_free(&ctx->as_string); /* Find where leading keyword starts in first half */ str = old->as_string.data + len; if (str > old->as_string.data) @@ -4263,7 +4263,7 @@ static char *fetch_till_str(o_string *as_string, } } if (ch == EOF) { - o_free_unsafe(&heredoc); + o_free(&heredoc); return NULL; } o_addchr(&heredoc, ch); @@ -5033,7 +5033,7 @@ static struct pipe *parse_stream(char **pstring, if (done_word(&ctx)) { goto parse_error; } - o_free(&ctx.word); + o_free_and_set_NULL(&ctx.word); done_pipe(&ctx, PIPE_SEQ); pi = ctx.list_head; /* If we got nothing... */ @@ -5050,7 +5050,7 @@ static struct pipe *parse_stream(char **pstring, if (pstring) *pstring = ctx.as_string.data; else - o_free_unsafe(&ctx.as_string); + o_free(&ctx.as_string); #endif debug_leave(); debug_printf_parse("parse_stream return %p\n", pi); @@ -5284,13 +5284,13 @@ static struct pipe *parse_stream(char **pstring, if (!HAS_KEYWORDS IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0)) ) { - o_free(&ctx.word); + o_free_and_set_NULL(&ctx.word); #if !BB_MMU debug_printf_parse("as_string2 '%s'\n", ctx.as_string.data); if (pstring) *pstring = ctx.as_string.data; else - o_free_unsafe(&ctx.as_string); + o_free(&ctx.as_string); #endif if (ch != ';' && IS_NULL_PIPE(ctx.list_head)) { /* Example: bare "{ }", "()" */ @@ -5568,7 +5568,7 @@ static struct pipe *parse_stream(char **pstring, free_pipe_list(pctx->list_head); debug_printf_clean("freed list %p\n", pctx->list_head); #if !BB_MMU - o_free_unsafe(&pctx->as_string); + o_free(&pctx->as_string); #endif IF_HAS_KEYWORDS(p2 = pctx->stack;) if (pctx != &ctx) { @@ -5577,7 +5577,7 @@ static struct pipe *parse_stream(char **pstring, IF_HAS_KEYWORDS(pctx = p2;) } while (HAS_KEYWORDS && pctx); - o_free(&ctx.word); + o_free_and_set_NULL(&ctx.word); #if !BB_MMU if (pstring) *pstring = NULL; @@ -5751,7 +5751,7 @@ static char *encode_then_expand_string(const char *str) /*unbackslash:*/ 1 ); //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); - o_free_unsafe(&dest); + o_free(&dest); return exp_str; } @@ -5868,7 +5868,7 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int ); ret: debug_printf_parse("expand: '%s' -> '%s'\n", dest.data, exp_str); - o_free_unsafe(&dest); + o_free(&dest); return exp_str; } @@ -5916,7 +5916,7 @@ static int encode_then_append_var_plusminus(o_string *output, int n, */ if (dest.data) { n = expand_vars_to_list(output, n, dest.data); - o_free(&dest); + o_free_and_set_NULL(&dest); o_addchr(output, '\0'); n = o_save_ptr(output, n); /* create next word */ } else @@ -6006,7 +6006,7 @@ static int encode_then_append_var_plusminus(o_string *output, int n, n = expand_vars_to_list(output, n, dest.data); } ret: - o_free_unsafe(&dest); + o_free(&dest); return n; } @@ -6588,7 +6588,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) G.expand_exitcode = G.last_exitcode; debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); n = append_str_maybe_ifs_split(output, n, first_ch, subst_result.data); - o_free_unsafe(&subst_result); + o_free(&subst_result); break; } #endif @@ -6631,7 +6631,8 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) */ o_addstr(output, arg); debug_print_list("expand_vars_to_list[b]", output, n); - } else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */ + } else + if (output->length == o_get_last_ptr(output, n) /* expansion is empty */ && !(cant_be_null & 0x80) /* and all vars were not quoted */ && !output->has_quoted_part ) { -- cgit v1.2.3-55-g6feb From 4c3c8a1a61e33a8c55991a260ba73d6a75d74810 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 20 Jul 2018 19:11:09 +0200 Subject: hush: tidy up code after previous commits Signed-off-by: Denys Vlasenko --- shell/hush.c | 91 ++++++++++++++++++++++++++++-------------------------------- 1 file changed, 43 insertions(+), 48 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 2da288c15..dddba5e30 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4471,7 +4471,6 @@ static int parse_group(struct parse_context *ctx, #if ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS /* Subroutines for copying $(...) and `...` things */ -static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote); /* '...' */ static int add_till_single_quote(o_string *dest, struct in_str *input) { @@ -4486,7 +4485,21 @@ static int add_till_single_quote(o_string *dest, struct in_str *input) o_addchr(dest, ch); } } +static int add_till_single_quote_dquoted(o_string *dest, struct in_str *input) +{ + while (1) { + int ch = i_getch(input); + if (ch == EOF) { + syntax_error_unterm_ch('\''); + return 0; + } + if (ch == '\'') + return 1; + o_addqchr(dest, ch); + } +} /* "...\"...`..`...." - do we need to handle "...$(..)..." too? */ +static int add_till_backquote(o_string *dest, struct in_str *input, int in_dquote); static int add_till_double_quote(o_string *dest, struct in_str *input) { while (1) { @@ -5599,6 +5612,7 @@ static char *expand_string_to_string(const char *str, int EXP_flags, int do_unba #if ENABLE_HUSH_TICK static int process_command_subs(o_string *dest, const char *s); #endif +static int expand_vars_to_list(o_string *output, int n, char *arg); /* expand_strvec_to_strvec() takes a list of strings, expands * all variable references within and returns a pointer to @@ -5755,6 +5769,13 @@ static char *encode_then_expand_string(const char *str) return exp_str; } +/* 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: + * ${var#ARG} and "${var#ARG}" treat ARG the same (ARG can even be + * a dquoted string: "${var#"zz"}"), the difference only comes later + * (word splitting and globbing of the ${var...} result). + */ #if !BASH_PATTERN_SUBST #define encode_then_expand_vararg(str, handle_squotes, do_unbackslash) \ encode_then_expand_vararg(str, handle_squotes) @@ -5782,14 +5803,6 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int 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: - * ${var#ARG} and "${var#ARG}" treat ARG the same (ARG can even be - * a dquoted string: "${var#"zz"}"), the difference only comes later - * (word splitting and globbing of the ${var...} result). - */ - setup_string_in_str(&input, str); dest.data = xzalloc(1); /* start as "", not as NULL */ exp_str = NULL; @@ -5798,28 +5811,21 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int int ch; ch = i_getch(&input); - if (ch == EOF) { - if (dest.o_expflags) { /* EXP_FLAG_ESC_GLOB_CHARS set? */ - syntax_error_unterm_ch('"'); - goto ret; /* error */ - } - break; - } debug_printf_parse("%s: ch=%c (%d) escape=%d\n", __func__, ch, ch, !!dest.o_expflags); - if (ch == '\'' && handle_squotes && !dest.o_expflags) { -//quoting version of add_till_single_quote() (try to merge?): - for (;;) { - ch = i_getch(&input); - if (ch == EOF) { - syntax_error_unterm_ch('\''); + + if (!dest.o_expflags) { + if (ch == EOF) + break; + if (handle_squotes && ch == '\'') { + if (!add_till_single_quote_dquoted(&dest, &input)) goto ret; /* error */ - } - if (ch == '\'') - break; - o_addqchr(&dest, ch); + continue; } - continue; + } + if (ch == EOF) { + syntax_error_unterm_ch('"'); + goto ret; /* error */ } if (ch == '"') { dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS; @@ -5872,8 +5878,8 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int return exp_str; } -static int expand_vars_to_list(o_string *output, int n, char *arg); - +/* Expanding ARG in ${var+ARG}, ${var-ARG} + */ static int encode_then_append_var_plusminus(o_string *output, int n, const char *str, int dquoted) { @@ -5896,8 +5902,6 @@ static int encode_then_append_var_plusminus(o_string *output, int n, } #endif - /* Expanding ARG in ${var+ARG}, ${var-ARG} */ - setup_string_in_str(&input, str); for (;;) { @@ -5938,17 +5942,8 @@ static int encode_then_append_var_plusminus(o_string *output, int n, continue; } if (!dquoted && ch == '\'') { -//quoting version of add_till_single_quote() (try to merge?): - for (;;) { - ch = i_getch(&input); - if (ch == EOF) { - syntax_error_unterm_ch('\''); - goto ret; /* error */ - } - if (ch == '\'') - break; - o_addqchr(&dest, ch); - } + if (!add_till_single_quote_dquoted(&dest, &input)) + goto ret; /* error */ o_addchr(&dest, SPECIAL_VAR_SYMBOL); o_addchr(&dest, SPECIAL_VAR_SYMBOL); continue; @@ -6105,11 +6100,10 @@ static int append_str_maybe_ifs_split(o_string *output, int n, return n; } -/* Helper: - * Handles varname... construct. +/* Handle varname... construct. */ -static NOINLINE int expand_one_var(o_string *output, - int n, int first_ch, char *arg, char **pp) +static NOINLINE int expand_one_var(o_string *output, int n, + int first_ch, char *arg, char **pp) { const char *val; char *to_be_freed; @@ -6385,7 +6379,7 @@ static NOINLINE int expand_one_var(o_string *output, * $ x=; f "${x:='x y' z}" * |'x y' z| * - * $ x=x; f ${x:+'x y' z}| + * $ x=x; f ${x:+'x y' z} * |x y| * |z| * $ x=x; f "${x:+'x y' z}" @@ -6620,7 +6614,8 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) arg = ++p; } /* end of "while (SPECIAL_VAR_SYMBOL is found) ..." */ - if (arg[0]) { + if (*arg) { + /* handle trailing string */ if (output->ended_in_ifs) { o_addchr(output, '\0'); n = o_save_ptr(output, n); -- cgit v1.2.3-55-g6feb From f36caa4071bbb5bb6d098ced8116accc65370dd8 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 20 Jul 2018 19:29:41 +0200 Subject: hush: never glob result of dquoted "${v:+/bin/c*}" Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-glob/glob_altvalue1.right | 7 +++++++ shell/ash_test/ash-glob/glob_altvalue1.tests | 13 +++++++++++++ shell/hush.c | 14 +++++++++++++- shell/hush_test/hush-glob/glob_altvalue1.right | 7 +++++++ shell/hush_test/hush-glob/glob_altvalue1.tests | 13 +++++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 shell/ash_test/ash-glob/glob_altvalue1.right create mode 100755 shell/ash_test/ash-glob/glob_altvalue1.tests create mode 100644 shell/hush_test/hush-glob/glob_altvalue1.right create mode 100755 shell/hush_test/hush-glob/glob_altvalue1.tests (limited to 'shell') diff --git a/shell/ash_test/ash-glob/glob_altvalue1.right b/shell/ash_test/ash-glob/glob_altvalue1.right new file mode 100644 index 000000000..bd3592229 --- /dev/null +++ b/shell/ash_test/ash-glob/glob_altvalue1.right @@ -0,0 +1,7 @@ +1u: glob_altvalue1.tests +2u: glob_altvalue1.t* +3u: glob_altvalue1.t* +4u: glob_altvalue1.t* +1q: glob_altvalue1.t* +2q: 'glob_altvalue1.t*' +3q: glob_altvalue1.t* diff --git a/shell/ash_test/ash-glob/glob_altvalue1.tests b/shell/ash_test/ash-glob/glob_altvalue1.tests new file mode 100755 index 000000000..5483d63e6 --- /dev/null +++ b/shell/ash_test/ash-glob/glob_altvalue1.tests @@ -0,0 +1,13 @@ +x=x + +echo 1u: ${x:+glob_altvalue1.t*} +echo 2u: ${x:+'glob_altvalue1.t*'} +echo 3u: ${x:+"glob_altvalue1.t*"} +echo 4u: ${x:+glob_altvalue1.t\*} +##echo 5u: ${x:+"glob_altvalue1.t\*"} + +echo 1q: "${x:+glob_altvalue1.t*}" +echo 2q: "${x:+'glob_altvalue1.t*'}" +echo 3q: "${x:+"glob_altvalue1.t*"}" +##echo 4q: "${x:+glob_altvalue1.t\*}" +##echo 5q: "${x:+"glob_altvalue1.t\*"}" diff --git a/shell/hush.c b/shell/hush.c index dddba5e30..4fdd15900 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5994,7 +5994,19 @@ static int encode_then_append_var_plusminus(o_string *output, int n, continue; } #endif - o_addQchr(&dest, ch); + if (dquoted) { + /* Always glob-protect if in dquotes: + * x=x; echo "${x:+/bin/c*}" - prints: /bin/c* + * x=x; echo "${x:+"/bin/c*"}" - prints: /bin/c* + */ + o_addqchr(&dest, ch); + } else { + /* Glob-protect only if char is quoted: + * x=x; echo ${x:+/bin/c*} - prints many filenames + * x=x; echo ${x:+"/bin/c*"} - prints: /bin/c* + */ + o_addQchr(&dest, ch); + } } /* for (;;) */ if (dest.data) { diff --git a/shell/hush_test/hush-glob/glob_altvalue1.right b/shell/hush_test/hush-glob/glob_altvalue1.right new file mode 100644 index 000000000..bd3592229 --- /dev/null +++ b/shell/hush_test/hush-glob/glob_altvalue1.right @@ -0,0 +1,7 @@ +1u: glob_altvalue1.tests +2u: glob_altvalue1.t* +3u: glob_altvalue1.t* +4u: glob_altvalue1.t* +1q: glob_altvalue1.t* +2q: 'glob_altvalue1.t*' +3q: glob_altvalue1.t* diff --git a/shell/hush_test/hush-glob/glob_altvalue1.tests b/shell/hush_test/hush-glob/glob_altvalue1.tests new file mode 100755 index 000000000..5483d63e6 --- /dev/null +++ b/shell/hush_test/hush-glob/glob_altvalue1.tests @@ -0,0 +1,13 @@ +x=x + +echo 1u: ${x:+glob_altvalue1.t*} +echo 2u: ${x:+'glob_altvalue1.t*'} +echo 3u: ${x:+"glob_altvalue1.t*"} +echo 4u: ${x:+glob_altvalue1.t\*} +##echo 5u: ${x:+"glob_altvalue1.t\*"} + +echo 1q: "${x:+glob_altvalue1.t*}" +echo 2q: "${x:+'glob_altvalue1.t*'}" +echo 3q: "${x:+"glob_altvalue1.t*"}" +##echo 4q: "${x:+glob_altvalue1.t\*}" +##echo 5q: "${x:+"glob_altvalue1.t\*"}" -- cgit v1.2.3-55-g6feb From d73cdbf84c9c7d509baf69eb3256dcaf733f4d93 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 23 Jul 2018 15:43:57 +0200 Subject: hush: fix handling of heredocs starting with empty lines function old new delta parse_stream 2748 2759 +11 Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-heredoc/heredoc_empty3.right | 2 ++ shell/ash_test/ash-heredoc/heredoc_empty3.tests | 4 ++++ shell/hush.c | 8 ++++++++ shell/hush_test/hush-heredoc/heredoc_empty3.right | 2 ++ shell/hush_test/hush-heredoc/heredoc_empty3.tests | 4 ++++ 5 files changed, 20 insertions(+) create mode 100644 shell/ash_test/ash-heredoc/heredoc_empty3.right create mode 100755 shell/ash_test/ash-heredoc/heredoc_empty3.tests create mode 100644 shell/hush_test/hush-heredoc/heredoc_empty3.right create mode 100755 shell/hush_test/hush-heredoc/heredoc_empty3.tests (limited to 'shell') diff --git a/shell/ash_test/ash-heredoc/heredoc_empty3.right b/shell/ash_test/ash-heredoc/heredoc_empty3.right new file mode 100644 index 000000000..0b54a9c93 --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_empty3.right @@ -0,0 +1,2 @@ + +Ok diff --git a/shell/ash_test/ash-heredoc/heredoc_empty3.tests b/shell/ash_test/ash-heredoc/heredoc_empty3.tests new file mode 100755 index 000000000..828c2dd89 --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_empty3.tests @@ -0,0 +1,4 @@ +cat < Date: Mon, 23 Jul 2018 16:31:21 +0200 Subject: hush: fix heredoc handling in the "cmd <" case function old new delta parse_stream 2759 2787 +28 Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-heredoc/heredocA.right | 1 + shell/ash_test/ash-heredoc/heredocA.tests | 4 +++ shell/hush.c | 54 ++++++++++++++++------------- shell/hush_test/hush-heredoc/heredoc9.right | 1 + shell/hush_test/hush-heredoc/heredoc9.tests | 9 +++++ shell/hush_test/hush-heredoc/heredocA.right | 1 + shell/hush_test/hush-heredoc/heredocA.tests | 4 +++ 7 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 shell/ash_test/ash-heredoc/heredocA.right create mode 100755 shell/ash_test/ash-heredoc/heredocA.tests create mode 100644 shell/hush_test/hush-heredoc/heredoc9.right create mode 100755 shell/hush_test/hush-heredoc/heredoc9.tests create mode 100644 shell/hush_test/hush-heredoc/heredocA.right create mode 100755 shell/hush_test/hush-heredoc/heredocA.tests (limited to 'shell') diff --git a/shell/ash_test/ash-heredoc/heredocA.right b/shell/ash_test/ash-heredoc/heredocA.right new file mode 100644 index 000000000..7326d9603 --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredocA.right @@ -0,0 +1 @@ +Ok diff --git a/shell/ash_test/ash-heredoc/heredocA.tests b/shell/ash_test/ash-heredoc/heredocA.tests new file mode 100755 index 000000000..440aaf906 --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredocA.tests @@ -0,0 +1,4 @@ +{ cat <cmds; - debug_printf_parse("fetch_heredocs: num_cmds:%d cmd argv0:'%s'\n", + debug_printf_heredoc("fetch_heredocs: num_cmds:%d cmd argv0:'%s'\n", pi->num_cmds, - cmd->argv ? cmd->argv[0] : "NONE"); + cmd->argv ? cmd->argv[0] : "NONE" + ); for (i = 0; i < pi->num_cmds; i++) { struct redir_struct *redir = cmd->redirects; - debug_printf_parse("fetch_heredocs: %d cmd argv0:'%s'\n", + debug_printf_heredoc("fetch_heredocs: %d cmd argv0:'%s'\n", i, cmd->argv ? cmd->argv[0] : "NONE"); while (redir) { if (redir->rd_type == REDIRECT_HEREDOC) { @@ -4325,11 +4331,9 @@ static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_ } pi = pi->next; } -#if 0 /* Should be 0. If it isn't, it's a parse error */ - if (heredoc_cnt) + if (HUSH_DEBUG && heredoc_cnt) bb_error_msg_and_die("heredoc BUG 2"); -#endif return 0; } @@ -5200,7 +5204,9 @@ static struct pipe *parse_stream(char **pstring, * "case ... in word) ..." */ if (IS_NULL_CMD(ctx.command) - && ctx.word.length == 0 && !ctx.word.has_quoted_part + && ctx.word.length == 0 + && !ctx.word.has_quoted_part + && heredoc_cnt == 0 ) { /* This newline can be ignored. But... * Without check #1, interactive shell @@ -5228,7 +5234,7 @@ static struct pipe *parse_stream(char **pstring, } /* Treat newline as a command separator. */ done_pipe(&ctx, PIPE_SEQ); - debug_printf_parse("heredoc_cnt:%d\n", heredoc_cnt); + debug_printf_heredoc("heredoc_cnt:%d\n", heredoc_cnt); if (heredoc_cnt) { if (fetch_heredocs(heredoc_cnt, &ctx, input)) { goto parse_error; @@ -5362,7 +5368,7 @@ static struct pipe *parse_stream(char **pstring, if (next == '<') { redir_style = REDIRECT_HEREDOC; heredoc_cnt++; - debug_printf_parse("++heredoc_cnt=%d\n", heredoc_cnt); + debug_printf_heredoc("++heredoc_cnt=%d\n", heredoc_cnt); ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); } else if (next == '>') { diff --git a/shell/hush_test/hush-heredoc/heredoc9.right b/shell/hush_test/hush-heredoc/heredoc9.right new file mode 100644 index 000000000..ce0136250 --- /dev/null +++ b/shell/hush_test/hush-heredoc/heredoc9.right @@ -0,0 +1 @@ +hello diff --git a/shell/hush_test/hush-heredoc/heredoc9.tests b/shell/hush_test/hush-heredoc/heredoc9.tests new file mode 100755 index 000000000..96c227cc1 --- /dev/null +++ b/shell/hush_test/hush-heredoc/heredoc9.tests @@ -0,0 +1,9 @@ +echo hello >greeting +cat </dev/null +rm greeting diff --git a/shell/hush_test/hush-heredoc/heredocA.right b/shell/hush_test/hush-heredoc/heredocA.right new file mode 100644 index 000000000..7326d9603 --- /dev/null +++ b/shell/hush_test/hush-heredoc/heredocA.right @@ -0,0 +1 @@ +Ok diff --git a/shell/hush_test/hush-heredoc/heredocA.tests b/shell/hush_test/hush-heredoc/heredocA.tests new file mode 100755 index 000000000..440aaf906 --- /dev/null +++ b/shell/hush_test/hush-heredoc/heredocA.tests @@ -0,0 +1,4 @@ +{ cat < Date: Tue, 24 Jul 2018 13:03:03 +0200 Subject: hush: fix handling of heredocs not enclosed in groups where they are "declared" function old new delta fetch_heredocs - 479 +479 parse_and_run_stream 146 148 +2 parse_stream 2787 2296 -491 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 1/1 up/down: 481/-491) Total: -10 bytes Signed-off-by: Denys Vlasenko --- .../ash-heredoc/heredoc_after_compound1.right | 2 + .../ash-heredoc/heredoc_after_compound1.tests | 3 + shell/hush.c | 104 ++++++++++++--------- .../hush-heredoc/heredoc_after_compound1.right | 2 + .../hush-heredoc/heredoc_after_compound1.tests | 3 + .../hush-heredoc/heredoc_var_expand1.right | 4 + .../hush-heredoc/heredoc_var_expand1.tests | 11 +++ 7 files changed, 84 insertions(+), 45 deletions(-) create mode 100644 shell/ash_test/ash-heredoc/heredoc_after_compound1.right create mode 100755 shell/ash_test/ash-heredoc/heredoc_after_compound1.tests create mode 100644 shell/hush_test/hush-heredoc/heredoc_after_compound1.right create mode 100755 shell/hush_test/hush-heredoc/heredoc_after_compound1.tests create mode 100644 shell/hush_test/hush-heredoc/heredoc_var_expand1.right create mode 100755 shell/hush_test/hush-heredoc/heredoc_var_expand1.tests (limited to 'shell') diff --git a/shell/ash_test/ash-heredoc/heredoc_after_compound1.right b/shell/ash_test/ash-heredoc/heredoc_after_compound1.right new file mode 100644 index 000000000..9052f7d1f --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_after_compound1.right @@ -0,0 +1,2 @@ +Ok1 +Ok2 diff --git a/shell/ash_test/ash-heredoc/heredoc_after_compound1.tests b/shell/ash_test/ash-heredoc/heredoc_after_compound1.tests new file mode 100755 index 000000000..e7cfb5be1 --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_after_compound1.tests @@ -0,0 +1,3 @@ +{ cat <redirects) + fdprintf(2, " {redir}"); fdprintf(2, "\n"); prn++; } @@ -4292,10 +4294,12 @@ static char *fetch_till_str(o_string *as_string, /* Look at entire parse tree for not-yet-loaded REDIRECT_HEREDOCs * and load them all. There should be exactly heredoc_cnt of them. */ -static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_str *input) +#if BB_MMU +#define fetch_heredocs(as_string, pi, heredoc_cnt, input) \ + fetch_heredocs(pi, heredoc_cnt, input) +#endif +static int fetch_heredocs(o_string *as_string, struct pipe *pi, int heredoc_cnt, struct in_str *input) { - struct pipe *pi = ctx->list_head; - while (pi && heredoc_cnt) { int i; struct command *cmd = pi->cmds; @@ -4315,11 +4319,11 @@ static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_ redir->rd_type = REDIRECT_HEREDOC2; /* redir->rd_dup is (ab)used to indicate <<- */ - p = fetch_till_str(&ctx->as_string, input, + p = fetch_till_str(as_string, input, redir->rd_filename, redir->rd_dup); if (!p) { syntax_error("unexpected EOF in here document"); - return 1; + return -1; } free(redir->rd_filename); redir->rd_filename = p; @@ -4327,29 +4331,36 @@ static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_ } redir = redir->next; } + if (cmd->group) { + //bb_error_msg("%s:%u heredoc_cnt:%d", __func__, __LINE__, heredoc_cnt); + heredoc_cnt = fetch_heredocs(as_string, cmd->group, heredoc_cnt, input); + //bb_error_msg("%s:%u heredoc_cnt:%d", __func__, __LINE__, heredoc_cnt); + if (heredoc_cnt < 0) + return heredoc_cnt; /* error */ + } cmd++; } pi = pi->next; } - /* Should be 0. If it isn't, it's a parse error */ - if (HUSH_DEBUG && heredoc_cnt) - bb_error_msg_and_die("heredoc BUG 2"); - return 0; + return heredoc_cnt; } static int run_list(struct pipe *pi); #if BB_MMU -#define parse_stream(pstring, input, end_trigger) \ - parse_stream(input, end_trigger) +#define parse_stream(pstring, heredoc_cnt_ptr, input, end_trigger) \ + parse_stream(heredoc_cnt_ptr, input, end_trigger) #endif static struct pipe *parse_stream(char **pstring, + int *heredoc_cnt_ptr, struct in_str *input, int end_trigger); - +/* Returns number of heredocs not yet consumed, + * or -1 on error. + */ static int parse_group(struct parse_context *ctx, - struct in_str *input, int ch) + struct in_str *input, int ch) { /* ctx->word contains characters seen prior to ( or {. * Typically it's empty, but for function defs, @@ -4360,6 +4371,7 @@ static int parse_group(struct parse_context *ctx, char *as_string = NULL; #endif struct pipe *pipe_list; + int heredoc_cnt = 0; int endch; struct command *command = ctx->command; @@ -4368,12 +4380,12 @@ static int parse_group(struct parse_context *ctx, if (ch == '(' && !ctx->word.has_quoted_part) { if (ctx->word.length) if (done_word(ctx)) - return 1; + return -1; if (!command->argv) goto skip; /* (... */ if (command->argv[1]) { /* word word ... (... */ syntax_error_unexpected_ch('('); - return 1; + return -1; } /* it is "word(..." or "word (..." */ do @@ -4381,7 +4393,7 @@ static int parse_group(struct parse_context *ctx, while (ch == ' ' || ch == '\t'); if (ch != ')') { syntax_error_unexpected_ch(ch); - return 1; + return -1; } nommu_addchr(&ctx->as_string, ch); do @@ -4389,7 +4401,7 @@ static int parse_group(struct parse_context *ctx, while (ch == ' ' || ch == '\t' || ch == '\n'); if (ch != '{' && ch != '(') { syntax_error_unexpected_ch(ch); - return 1; + return -1; } nommu_addchr(&ctx->as_string, ch); command->cmd_type = CMD_FUNCDEF; @@ -4403,9 +4415,9 @@ static int parse_group(struct parse_context *ctx, || ctx->word.has_quoted_part /* ""{... */ ) { syntax_error(NULL); - debug_printf_parse("parse_group return 1: " + debug_printf_parse("parse_group return -1: " "syntax error, groups and arglists don't mix\n"); - return 1; + return -1; } #endif @@ -4423,7 +4435,7 @@ static int parse_group(struct parse_context *ctx, && ch != '(' /* but "{(..." is allowed (without whitespace) */ ) { syntax_error_unexpected_ch(ch); - return 1; + return -1; } if (ch != '(') { ch = i_getch(input); @@ -4431,7 +4443,9 @@ static int parse_group(struct parse_context *ctx, } } - pipe_list = parse_stream(&as_string, input, endch); + debug_printf_heredoc("calling parse_stream, heredoc_cnt:%d\n", heredoc_cnt); + pipe_list = parse_stream(&as_string, &heredoc_cnt, input, endch); + debug_printf_heredoc("parse_stream returned: heredoc_cnt:%d\n", heredoc_cnt); #if !BB_MMU if (as_string) o_addstr(&ctx->as_string, as_string); @@ -4442,9 +4456,9 @@ static int parse_group(struct parse_context *ctx, /* parse_stream already emitted error msg */ if (!BB_MMU) free(as_string); - debug_printf_parse("parse_group return 1: " + debug_printf_parse("parse_group return -1: " "parse_stream returned %p\n", pipe_list); - return 1; + return -1; } #if !BB_MMU as_string[strlen(as_string) - 1] = '\0'; /* plink ')' or '}' */ @@ -4475,8 +4489,8 @@ static int parse_group(struct parse_context *ctx, command->group = pipe_list; - debug_printf_parse("parse_group return 0\n"); - return 0; + debug_printf_parse("parse_group return %d\n", heredoc_cnt); + return heredoc_cnt; /* command remains "open", available for possible redirects */ #undef as_string } @@ -5002,6 +5016,7 @@ static int encode_string(o_string *as_string, * or return ERR_PTR. */ static struct pipe *parse_stream(char **pstring, + int *heredoc_cnt_ptr, struct in_str *input, int end_trigger) { @@ -5077,7 +5092,11 @@ static struct pipe *parse_stream(char **pstring, else o_free(&ctx.as_string); #endif + // heredoc_cnt must be 0 here anyway + //if (heredoc_cnt_ptr) + // *heredoc_cnt_ptr = heredoc_cnt; debug_leave(); + debug_printf_heredoc("parse_stream return heredoc_cnt:%d\n", heredoc_cnt); debug_printf_parse("parse_stream return %p\n", pi); return pi; } @@ -5236,10 +5255,9 @@ static struct pipe *parse_stream(char **pstring, done_pipe(&ctx, PIPE_SEQ); debug_printf_heredoc("heredoc_cnt:%d\n", heredoc_cnt); if (heredoc_cnt) { - if (fetch_heredocs(heredoc_cnt, &ctx, input)) { + heredoc_cnt = fetch_heredocs(&ctx.as_string, ctx.list_head, heredoc_cnt, input); + if (heredoc_cnt != 0) goto parse_error; - } - heredoc_cnt = 0; } ctx.is_assignment = MAYBE_ASSIGNMENT; debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); @@ -5288,19 +5306,6 @@ static struct pipe *parse_stream(char **pstring, ) #endif ) { - if (heredoc_cnt) { - /* This is technically valid: - * { cat < Date: Tue, 24 Jul 2018 14:03:18 +0200 Subject: hush: handle backslash-newline in heredoc terminators function old new delta fetch_heredocs 479 527 +48 (ash fails this test) Signed-off-by: Denys Vlasenko --- .../ash-heredoc/heredoc_bkslash_newline2.right | 1 + .../ash-heredoc/heredoc_bkslash_newline2.tests | 4 ++++ shell/hush.c | 20 ++++++++++++++++++-- .../hush-heredoc/heredoc_bkslash_newline2.right | 1 + .../hush-heredoc/heredoc_bkslash_newline2.tests | 4 ++++ 5 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 shell/ash_test/ash-heredoc/heredoc_bkslash_newline2.right create mode 100755 shell/ash_test/ash-heredoc/heredoc_bkslash_newline2.tests create mode 100644 shell/hush_test/hush-heredoc/heredoc_bkslash_newline2.right create mode 100755 shell/hush_test/hush-heredoc/heredoc_bkslash_newline2.tests (limited to 'shell') diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2.right b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2.right new file mode 100644 index 000000000..3d79316d7 --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2.right @@ -0,0 +1 @@ +Ok1 diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2.tests b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2.tests new file mode 100755 index 000000000..1d2a26504 --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2.tests @@ -0,0 +1,4 @@ +cat < (not a line cont.) */ - prev = 0; /* not \ */ + prev = 0; /* not '\' */ else prev = ch; } diff --git a/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2.right b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2.right new file mode 100644 index 000000000..3d79316d7 --- /dev/null +++ b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2.right @@ -0,0 +1 @@ +Ok1 diff --git a/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2.tests b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2.tests new file mode 100755 index 000000000..1d2a26504 --- /dev/null +++ b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2.tests @@ -0,0 +1,4 @@ +cat < Date: Tue, 24 Jul 2018 16:54:41 +0200 Subject: hush: fix nested redirects colliding with script fds This necessitates switch from libc FILE api to a simple homegrown replacement. The change which fixes the bug here is the deleting of restore_redirected_FILEs(); line. It was prematurely moving (restoring) script fd#3. The fix is: we don't even _want_ to restore scrit fds, we are perfectly fine with them being moved. The only reason we tried to restore them is that FILE api did not allow moving of FILE->fd. function old new delta refill_HFILE_and_getc - 93 +93 hfopen - 90 +90 hfclose - 66 +66 pseudo_exec_argv 591 597 +6 hush_main 1089 1095 +6 builtin_source 209 214 +5 save_fd_on_redirect 197 200 +3 setup_redirects 320 321 +1 fgetc_interactive 235 236 +1 i_peek_and_eat_bkslash_nl 99 97 -2 expand_vars_to_list 1103 1100 -3 restore_redirects 99 52 -47 fclose_and_forget 57 - -57 remember_FILE 63 - -63 ------------------------------------------------------------------------------ (add/remove: 3/2 grow/shrink: 6/3 up/down: 271/-172) Total: 99 bytes Signed-off-by: Denys Vlasenko --- libbb/xfuncs_printf.c | 1 + shell/ash_test/ash-heredoc/heredocB.right | 3 + shell/ash_test/ash-heredoc/heredocB.tests | 12 ++ shell/ash_test/ash-redir/redir_script.tests | 4 + shell/hush.c | 234 +++++++++++++++----------- shell/hush_test/hush-heredoc/heredocB.right | 3 + shell/hush_test/hush-heredoc/heredocB.tests | 12 ++ shell/hush_test/hush-redir/redir_script.tests | 4 + 8 files changed, 175 insertions(+), 98 deletions(-) create mode 100644 shell/ash_test/ash-heredoc/heredocB.right create mode 100755 shell/ash_test/ash-heredoc/heredocB.tests create mode 100644 shell/hush_test/hush-heredoc/heredocB.right create mode 100755 shell/hush_test/hush-heredoc/heredocB.tests (limited to 'shell') diff --git a/libbb/xfuncs_printf.c b/libbb/xfuncs_printf.c index 7247c915b..6cc60f6c0 100644 --- a/libbb/xfuncs_printf.c +++ b/libbb/xfuncs_printf.c @@ -222,6 +222,7 @@ void FAST_FUNC xdup2(int from, int to) { if (dup2(from, to) != to) bb_perror_msg_and_die("can't duplicate file descriptor"); + // " %d to %d", from, to); } // "Renumber" opened fd diff --git a/shell/ash_test/ash-heredoc/heredocB.right b/shell/ash_test/ash-heredoc/heredocB.right new file mode 100644 index 000000000..43ba0b4f9 --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredocB.right @@ -0,0 +1,3 @@ +one - alpha +two - beta +three - gamma diff --git a/shell/ash_test/ash-heredoc/heredocB.tests b/shell/ash_test/ash-heredoc/heredocB.tests new file mode 100755 index 000000000..45ea4687f --- /dev/null +++ b/shell/ash_test/ash-heredoc/heredocB.tests @@ -0,0 +1,12 @@ +while read line1; do + read line2 <&3 + echo $line1 - $line2 +done <&- 3>&-" && \ test x"$fds" = x" 11>&- 3>&-" \ && { echo "Ok: script fd is not closed"; exit 0; } +# or we see that fd 3 moved to fd 10: +test x"$fds1" = x" 3>&- 4>&-" && \ +test x"$fds" = x" 10>&- 3>&-" \ +&& { echo "Ok: script fd is not closed"; exit 0; } echo "Bug: script fd is closed" echo "fds1:$fds1" diff --git a/shell/hush.c b/shell/hush.c index c26484b49..7a1513c24 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -357,6 +357,9 @@ #else # define CLEAR_RANDOM_T(rnd) ((void)0) #endif +#ifndef O_CLOEXEC +# define O_CLOEXEC 0 +#endif #ifndef F_DUPFD_CLOEXEC # define F_DUPFD_CLOEXEC F_DUPFD #endif @@ -556,11 +559,27 @@ static const char *const assignment_flag[] = { }; #endif +/* We almost can use standard FILE api, but we need an ability to move + * its fd when redirects coincide with it. No api exists for that + * (RFE for it at https://sourceware.org/bugzilla/show_bug.cgi?id=21902). + * HFILE is our internal alternative. Only supports reading. + * Since we now can, we incorporate linked list of all opened HFILEs + * into the struct (used to be a separate mini-list). + */ +typedef struct HFILE { + char *cur; + char *end; + struct HFILE *next_hfile; + int is_stdin; + int fd; + char buf[1024]; +} HFILE; + typedef struct in_str { const char *p; int peek_buf[2]; int last_char; - FILE *file; + HFILE *file; } in_str; /* The descrip member of this structure is only used to make @@ -814,14 +833,6 @@ enum { NUM_OPT_O }; - -struct FILE_list { - struct FILE_list *next; - FILE *fp; - int fd; -}; - - /* "Globals" within this file */ /* Sorted roughly by size (smaller offsets == smaller code) */ struct globals { @@ -954,7 +965,7 @@ struct globals { unsigned lineno; char *lineno_var; #endif - struct FILE_list *FILE_list; + HFILE *HFILE_list; /* Which signals have non-DFL handler (even with no traps set)? * Set at the start to: * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS) @@ -1563,83 +1574,115 @@ static int xdup_CLOEXEC_and_close(int fd, int avoid_fd) } -/* Manipulating the list of open FILEs */ -static FILE *remember_FILE(FILE *fp) +/* Manipulating HFILEs */ +static HFILE *hfopen(const char *name) { - if (fp) { - struct FILE_list *n = xmalloc(sizeof(*n)); - n->next = G.FILE_list; - G.FILE_list = n; - n->fp = fp; - n->fd = fileno(fp); - close_on_exec_on(n->fd); + HFILE *fp; + int fd; + + fd = STDIN_FILENO; + if (name) { + fd = open(name, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return NULL; + if (O_CLOEXEC == 0) /* ancient libc */ + close_on_exec_on(fd); } + + fp = xmalloc(sizeof(*fp)); + fp->is_stdin = (name == NULL); + fp->fd = fd; + fp->cur = fp->end = fp->buf; + fp->next_hfile = G.HFILE_list; + G.HFILE_list = fp; return fp; } -static void fclose_and_forget(FILE *fp) +static void hfclose(HFILE *fp) { - struct FILE_list **pp = &G.FILE_list; + HFILE **pp = &G.HFILE_list; while (*pp) { - struct FILE_list *cur = *pp; - if (cur->fp == fp) { - *pp = cur->next; - free(cur); + HFILE *cur = *pp; + if (cur == fp) { + *pp = cur->next_hfile; break; } - pp = &cur->next; + pp = &cur->next_hfile; } - fclose(fp); + if (fp->fd >= 0) + close(fp->fd); + free(fp); } -static int save_FILEs_on_redirect(int fd, int avoid_fd) +static int refill_HFILE_and_getc(HFILE *fp) { - struct FILE_list *fl = G.FILE_list; + int n; + + if (fp->fd < 0) { + /* Already saw EOF */ + return EOF; + } + /* Try to buffer more input */ + fp->cur = fp->buf; + n = safe_read(fp->fd, fp->buf, sizeof(fp->buf)); + if (n < 0) { + bb_perror_msg("read error"); + n = 0; + } + fp->end = fp->buf + n; + if (n == 0) { + /* EOF/error */ + close(fp->fd); + fp->fd = -1; + return EOF; + } + return (unsigned char)(*fp->cur++); +} +/* Inlined for common case of non-empty buffer. + */ +static ALWAYS_INLINE int hfgetc(HFILE *fp) +{ + if (fp->cur < fp->end) + return (unsigned char)(*fp->cur++); + /* Buffer empty */ + return refill_HFILE_and_getc(fp); +} +static int move_HFILEs_on_redirect(int fd, int avoid_fd) +{ + HFILE *fl = G.HFILE_list; while (fl) { if (fd == fl->fd) { /* We use it only on script files, they are all CLOEXEC */ fl->fd = xdup_CLOEXEC_and_close(fd, avoid_fd); debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd); - return 1; - } - fl = fl->next; - } - return 0; -} -static void restore_redirected_FILEs(void) -{ - struct FILE_list *fl = G.FILE_list; - while (fl) { - int should_be = fileno(fl->fp); - if (fl->fd != should_be) { - debug_printf_redir("restoring script fd from %d to %d\n", fl->fd, should_be); - xmove_fd(fl->fd, should_be); - fl->fd = should_be; + return 1; /* "found and moved" */ } - fl = fl->next; + fl = fl->next_hfile; } + return 0; /* "not in the list" */ } #if ENABLE_FEATURE_SH_STANDALONE && BB_MMU -static void close_all_FILE_list(void) +static void close_all_HFILE_list(void) { - struct FILE_list *fl = G.FILE_list; + HFILE *fl = G.HFILE_list; while (fl) { - /* fclose would also free FILE object. + /* hfclose would also free HFILE object. * 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. */ - /*fclose(fl->fp); - unsafe */ - close(fl->fd); - fl = fl->next; + /*hfclose(fl); - unsafe */ + if (fl->fd >= 0) + close(fl->fd); + fl = fl->next_hfile; } } #endif -static int fd_in_FILEs(int fd) +static int fd_in_HFILEs(int fd) { - struct FILE_list *fl = G.FILE_list; + HFILE *fl = G.HFILE_list; while (fl) { if (fl->fd == fd) return 1; - fl = fl->next; + fl = fl->next_hfile; } return 0; } @@ -2580,7 +2623,7 @@ static int get_user_input(struct in_str *i) } fflush_all(); //FIXME: here ^C or SIGINT will have effect only after - r = fgetc(i->file); + r = hfgetc(i->file); /* In !ENABLE_FEATURE_EDITING we don't use read_line_input, * no ^C masking happens during fgetc, no special code for ^C: * it generates SIGINT as usual. @@ -2600,22 +2643,22 @@ static int fgetc_interactive(struct in_str *i) { int ch; /* If it's interactive stdin, get new line. */ - if (G_interactive_fd && i->file == stdin) { + if (G_interactive_fd && i->file->is_stdin) { /* Returns first char (or EOF), the rest is in i->p[] */ ch = get_user_input(i); G.promptmode = 1; /* PS2 */ debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode); } else { /* Not stdin: script file, sourced file, etc */ - do ch = fgetc(i->file); while (ch == '\0'); + do ch = hfgetc(i->file); while (ch == '\0'); } return ch; } #else -static inline int fgetc_interactive(struct in_str *i) +static ALWAYS_INLINE int fgetc_interactive(struct in_str *i) { int ch; - do ch = fgetc(i->file); while (ch == '\0'); + do ch = hfgetc(i->file); while (ch == '\0'); return ch; } #endif /* INTERACTIVE */ @@ -2730,7 +2773,7 @@ static int i_peek2(struct in_str *i) ch = i->peek_buf[1]; if (ch == 0) { /* We did not read it yet, get it now */ - do ch = fgetc(i->file); while (ch == '\0'); + do ch = hfgetc(i->file); while (ch == '\0'); i->peek_buf[1] = ch; } @@ -2774,10 +2817,10 @@ static int i_peek_and_eat_bkslash_nl(struct in_str *input) } } -static void setup_file_in_str(struct in_str *i, FILE *f) +static void setup_file_in_str(struct in_str *i, HFILE *fp) { memset(i, 0, sizeof(*i)); - i->file = f; + i->file = fp; /* i->p = NULL; */ } @@ -7106,19 +7149,19 @@ static void parse_and_run_string(const char *s) //IF_HUSH_LINENO_VAR(G.lineno = sv;) } -static void parse_and_run_file(FILE *f) +static void parse_and_run_file(HFILE *fp) { struct in_str input; IF_HUSH_LINENO_VAR(unsigned sv = G.lineno;) IF_HUSH_LINENO_VAR(G.lineno = 1;) - setup_file_in_str(&input, f); + setup_file_in_str(&input, fp); parse_and_run_stream(&input, ';'); IF_HUSH_LINENO_VAR(G.lineno = sv;) } #if ENABLE_HUSH_TICK -static FILE *generate_stream_from_string(const char *s, pid_t *pid_p) +static int generate_stream_from_string(const char *s, pid_t *pid_p) { pid_t pid; int channel[2]; @@ -7220,7 +7263,7 @@ static FILE *generate_stream_from_string(const char *s, pid_t *pid_p) free(to_free); # endif close(channel[1]); - return remember_FILE(xfdopen_for_read(channel[0])); + return channel[0]; } /* Return code is exit status of the process that is run. */ @@ -7230,7 +7273,7 @@ static int process_command_subs(o_string *dest, const char *s) pid_t pid; int status, ch, eol_cnt; - fp = generate_stream_from_string(s, &pid); + fp = xfdopen_for_read(generate_stream_from_string(s, &pid)); /* Now send results of command back into original context */ eol_cnt = 0; @@ -7249,7 +7292,7 @@ static int process_command_subs(o_string *dest, const char *s) } debug_printf("done reading from `cmd` pipe, closing it\n"); - fclose_and_forget(fp); + fclose(fp); /* We need to extract exitcode. Test case * "true; echo `sleep 1; false` $?" * should print 1 */ @@ -7427,20 +7470,17 @@ static int save_fd_on_redirect(int fd, int avoid_fd, struct squirrel **sqp) return 1; /* "we closed fd" */ } #endif + /* If this one of script's fds? */ + if (move_HFILEs_on_redirect(fd, avoid_fd)) + return 1; /* yes. "we closed fd" (actually moved it) */ + /* Are we called from setup_redirects(squirrel==NULL)? Two cases: - * (1) Redirect in a forked child. No need to save FILEs' fds, - * we aren't going to use them anymore, ok to trash. - * (2) "exec 3>FILE". Bummer. We can save script FILEs' fds, - * but how are we doing to restore them? - * "fileno(fd) = new_fd" can't be done. + * (1) Redirect in a forked child. + * (2) "exec 3>FILE". */ if (!sqp) return 0; - /* If this one of script's fds? */ - if (save_FILEs_on_redirect(fd, avoid_fd)) - return 1; /* yes. "we closed fd" */ - /* Check whether it collides with any open fds (e.g. stdio), save fds as needed */ *sqp = add_squirrel(*sqp, fd, avoid_fd); return 0; /* "we did not close fd" */ @@ -7465,8 +7505,6 @@ static void restore_redirects(struct squirrel *sq) } /* If moved, G.interactive_fd stays on new fd, not restoring it */ - - restore_redirected_FILEs(); } #if ENABLE_FEATURE_SH_STANDALONE && BB_MMU @@ -7474,7 +7512,7 @@ static void close_saved_fds_and_FILE_fds(void) { if (G_interactive_fd) close(G_interactive_fd); - close_all_FILE_list(); + close_all_HFILE_list(); } #endif @@ -7487,7 +7525,7 @@ static int internally_opened_fd(int fd, struct squirrel *sq) return 1; #endif /* If this one of script's fds? */ - if (fd_in_FILEs(fd)) + if (fd_in_HFILEs(fd)) return 1; if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) { @@ -7512,7 +7550,7 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) save_fd_on_redirect(redir->rd_fd, /*avoid:*/ 0, sqp); /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ * of the heredoc */ - debug_printf_parse("set heredoc '%s'\n", + debug_printf_redir("set heredoc '%s'\n", redir->rd_filename); setup_heredoc(redir); continue; @@ -7524,8 +7562,7 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) int mode; if (redir->rd_filename == NULL) { - /* - * Examples: + /* Examples: * "cmd >" (no filename) * "cmd > " (which is never interactive (unless -i?)) * sources $BASH_ENV here (without scanning $PATH). @@ -9902,13 +9938,15 @@ int hush_main(int argc, char **argv) G.global_argv++; debug_printf("running script '%s'\n", G.global_argv[0]); xfunc_error_retval = 127; /* for "hush /does/not/exist" case */ - input = xfopen_for_read(G.global_argv[0]); + input = hfopen(G.global_argv[0]); + if (!input) { + bb_simple_perror_msg_and_die(G.global_argv[0]); + } xfunc_error_retval = 1; - remember_FILE(input); install_special_sighandlers(); parse_and_run_file(input); #if ENABLE_FEATURE_CLEAN_UP - fclose_and_forget(input); + hfclose(input); #endif goto final_return; } @@ -10038,7 +10076,7 @@ int hush_main(int argc, char **argv) ); } - parse_and_run_file(stdin); + parse_and_run_file(hfopen(NULL)); /* stdin */ final_return: hush_exit(G.last_exitcode); @@ -10849,7 +10887,7 @@ Test that VAR is a valid variable name? static int FAST_FUNC builtin_source(char **argv) { char *arg_path, *filename; - FILE *input; + HFILE *input; save_arg_t sv; char *args_need_save; #if ENABLE_HUSH_FUNCTIONS @@ -10873,10 +10911,10 @@ static int FAST_FUNC builtin_source(char **argv) return EXIT_FAILURE; } } - input = remember_FILE(fopen_or_warn(filename, "r")); + input = hfopen(filename); free(arg_path); if (!input) { - /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */ + bb_perror_msg("%s", filename); /* POSIX: non-interactive shell should abort here, * not merely fail. So far no one complained :) */ @@ -10895,7 +10933,7 @@ static int FAST_FUNC builtin_source(char **argv) /* "false; . ./empty_line; echo Zero:$?" should print 0 */ G.last_exitcode = 0; parse_and_run_file(input); - fclose_and_forget(input); + hfclose(input); if (args_need_save) /* can't use argv[1] instead: "shift" can mangle it */ restore_G_args(&sv, argv); diff --git a/shell/hush_test/hush-heredoc/heredocB.right b/shell/hush_test/hush-heredoc/heredocB.right new file mode 100644 index 000000000..43ba0b4f9 --- /dev/null +++ b/shell/hush_test/hush-heredoc/heredocB.right @@ -0,0 +1,3 @@ +one - alpha +two - beta +three - gamma diff --git a/shell/hush_test/hush-heredoc/heredocB.tests b/shell/hush_test/hush-heredoc/heredocB.tests new file mode 100755 index 000000000..45ea4687f --- /dev/null +++ b/shell/hush_test/hush-heredoc/heredocB.tests @@ -0,0 +1,12 @@ +while read line1; do + read line2 <&3 + echo $line1 - $line2 +done <&- 3>&-" && \ test x"$fds" = x" 11>&- 3>&-" \ && { echo "Ok: script fd is not closed"; exit 0; } +# or we see that fd 3 moved to fd 10: +test x"$fds1" = x" 3>&- 4>&-" && \ +test x"$fds" = x" 10>&- 3>&-" \ +&& { echo "Ok: script fd is not closed"; exit 0; } echo "Bug: script fd is closed" echo "fds1:$fds1" -- cgit v1.2.3-55-g6feb From 63c42afaa43d42def05dfbca1f4e10c7314b1f77 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 24 Jul 2018 17:08:04 +0200 Subject: hush: add "heredoc.tests" from ash, tweak ash "is a function" message Signed-off-by: Denys Vlasenko --- shell/ash.c | 3 +- shell/ash_test/ash-heredoc/heredoc.right | 2 +- shell/hush_test/hush-heredoc/heredoc.right | 20 ++++++ shell/hush_test/hush-heredoc/heredoc.tests | 97 ++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 shell/hush_test/hush-heredoc/heredoc.right create mode 100755 shell/hush_test/hush-heredoc/heredoc.tests (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 051cc671f..03fbbee53 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -8512,7 +8512,8 @@ describe_command(char *command, const char *path, int describe_command_verbose) case CMDFUNCTION: if (describe_command_verbose) { - out1str(" is a shell function"); + /*out1str(" is a shell function");*/ + out1str(" is a function"); /* bash says this */ } else { out1str(command); } diff --git a/shell/ash_test/ash-heredoc/heredoc.right b/shell/ash_test/ash-heredoc/heredoc.right index baf115166..85d36dae9 100644 --- a/shell/ash_test/ash-heredoc/heredoc.right +++ b/shell/ash_test/ash-heredoc/heredoc.right @@ -16,6 +16,6 @@ tab 3 abc def ghi jkl mno -fff is a shell function +fff is a function hi there diff --git a/shell/hush_test/hush-heredoc/heredoc.right b/shell/hush_test/hush-heredoc/heredoc.right new file mode 100644 index 000000000..9b9e2aae9 --- /dev/null +++ b/shell/hush_test/hush-heredoc/heredoc.right @@ -0,0 +1,20 @@ +there +one - alpha +two - beta +three - gamma +hi\ +there$a +stuff +hi\ +there +EO\ +F +hi +hi +tab 1 +tab 2 +tab 3 +abc +def ghi +jkl mno +fff is a function diff --git a/shell/hush_test/hush-heredoc/heredoc.tests b/shell/hush_test/hush-heredoc/heredoc.tests new file mode 100755 index 000000000..39345c51b --- /dev/null +++ b/shell/hush_test/hush-heredoc/heredoc.tests @@ -0,0 +1,97 @@ +# check order and content of multiple here docs + +cat << EOF1 << EOF2 +hi +EOF1 +there +EOF2 + +while read line1; do + read line2 <&3 + echo $line1 - $line2 +done < /tmp/bash-zzz << EOF +abc +EOF +cat >> /tmp/bash-zzz << EOF +def ghi +jkl mno +EOF +cat /tmp/bash-zzz +rm -f /tmp/bash-zzz + +# make sure command printing puts the here-document as the last redirection +# on the line, and the function export code preserves syntactic correctness +fff() +{ + ed /tmp/foo </dev/null +/^name/d +w +q +ENDOFINPUT +aa=1 +} + +type fff +#ash# export -f fff +#ash# ${THIS_SH} -c 'type fff' + +#hush# bash warns: "here-document at line N delimited by end-of-file", +#hush# ash allows it, +#hush# hush errors out for now: +#hush# # check that end of file delimits a here-document +#hush# # THIS MUST BE LAST! +#hush# +#hush# cat << EOF +#hush# hi +#hush# there -- cgit v1.2.3-55-g6feb From 945e9b05c910d3b946a5d5a396c5a7e075513e6e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 24 Jul 2018 18:01:22 +0200 Subject: hush: fix/explain corner cases of redirection colliding with script fd function old new delta save_fd_on_redirect 200 208 +8 run_pipe 1870 1873 +3 setup_redirects 321 322 +1 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/0 up/down: 12/0) Total: 12 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 12 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 7a1513c24..e3c6e2de9 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7470,16 +7470,54 @@ static int save_fd_on_redirect(int fd, int avoid_fd, struct squirrel **sqp) return 1; /* "we closed fd" */ } #endif + /* Are we called from setup_redirects(squirrel==NULL) + * in redirect in a [v]forked child? + */ + if (sqp == NULL) { + /* No need to move script fds. + * For NOMMU case, it's actively wrong: we'd change ->fd + * fields in memory for the parent, but parent's fds + * aren't be moved, it would use wrong fd! + * Reproducer: "cmd 3>FILE" in script. + * If we would call move_HFILEs_on_redirect(), child would: + * fcntl64(3, F_DUPFD_CLOEXEC, 10) = 10 + * close(3) = 0 + * and change ->fd to 10 if fd#3 is a script fd. WRONG. + */ + //bb_error_msg("sqp == NULL: [v]forked child"); + return 0; + } + /* If this one of script's fds? */ if (move_HFILEs_on_redirect(fd, avoid_fd)) return 1; /* yes. "we closed fd" (actually moved it) */ - /* Are we called from setup_redirects(squirrel==NULL)? Two cases: - * (1) Redirect in a forked child. - * (2) "exec 3>FILE". + /* Are we called for "exec 3>FILE"? Came through + * redirect_and_varexp_helper(squirrel=ERR_PTR) -> setup_redirects(ERR_PTR) + * This case used to fail for this script: + * exec 3>FILE + * echo Ok + * ...100000 more lines... + * echo Ok + * as follows: + * read(3, "exec 3>FILE\necho Ok\necho Ok"..., 1024) = 1024 + * open("FILE", O_WRONLY|O_CREAT|O_TRUNC|O_LARGEFILE, 0666) = 4 + * dup2(4, 3) = 3 + * ^^^^^^^^ oops, we lost fd#3 opened to our script! + * close(4) = 0 + * write(1, "Ok\n", 3) = 3 + * ... = 3 + * write(1, "Ok\n", 3) = 3 + * read(3, 0x94fbc08, 1024) = -1 EBADF (Bad file descriptor) + * ^^^^^^^^ oops, wrong fd!!! + * With this case separate from sqp == NULL and *after* move_HFILEs, + * it now works: */ - if (!sqp) + if (sqp == ERR_PTR) { + /* Don't preserve redirected fds: exec is _meant_ to change these */ + //bb_error_msg("sqp == ERR_PTR: exec >FILE"); return 0; + } /* Check whether it collides with any open fds (e.g. stdio), save fds as needed */ *sqp = add_squirrel(*sqp, fd, avoid_fd); @@ -7618,7 +7656,7 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) */ } else { /* if newfd is a script fd or saved fd, simulate EBADF */ - if (internally_opened_fd(newfd, sqp ? *sqp : NULL)) { + if (internally_opened_fd(newfd, sqp && sqp != ERR_PTR ? *sqp : NULL)) { //errno = EBADF; //bb_perror_msg_and_die("can't duplicate file descriptor"); newfd = -1; /* same effect as code above */ @@ -8633,8 +8671,8 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe) * subshell: ( list ) [&] */ #if !ENABLE_HUSH_MODE_X -#define redirect_and_varexp_helper(command, squirrel, argv_expanded) \ - redirect_and_varexp_helper(command, squirrel) +#define redirect_and_varexp_helper(command, sqp, argv_expanded) \ + redirect_and_varexp_helper(command, sqp) #endif static int redirect_and_varexp_helper( struct command *command, @@ -8651,10 +8689,6 @@ static int redirect_and_varexp_helper( /* this takes ownership of new_env[i] elements, and frees new_env: */ set_vars_and_save_old(new_env); - /* setup_redirects acts on file descriptors, not FILEs. - * This is perfect for work that comes after exec(). - * Is it really safe for inline use? Experimentally, - * things seem to work. */ return setup_redirects(command, sqp); } static NOINLINE int run_pipe(struct pipe *pi) @@ -8855,7 +8889,10 @@ static NOINLINE int run_pipe(struct pipe *pi) */ enter_var_nest_level(); G.shadowed_vars_pp = &old_vars; - rcode = redirect_and_varexp_helper(command, /*squirrel:*/ NULL, argv_expanded); + rcode = redirect_and_varexp_helper(command, + /*squirrel:*/ ERR_PTR, + argv_expanded + ); G.shadowed_vars_pp = sv_shadowed; /* rcode=1 can be if redir file can't be opened */ -- cgit v1.2.3-55-g6feb