diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2018-07-20 16:18:59 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2018-07-20 16:29:43 +0200 |
commit | 294eb4612cd668521faa48711297196f00af61d9 (patch) | |
tree | 15b002f51fdbfc3d32b1c33b8dd886d791f70cbc | |
parent | 57235beb696a7dbdb48751b9721c4c025127ae96 (diff) | |
download | busybox-w32-294eb4612cd668521faa48711297196f00af61d9.tar.gz busybox-w32-294eb4612cd668521faa48711297196f00af61d9.tar.bz2 busybox-w32-294eb4612cd668521faa48711297196f00af61d9.zip |
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 <vda.linux@googlemail.com>
-rw-r--r-- | shell/ash_test/ash-quoting/dollar_altvalue9.right | 24 | ||||
-rwxr-xr-x | shell/ash_test/ash-quoting/dollar_altvalue9.tests | 17 | ||||
-rw-r--r-- | shell/hush.c | 263 | ||||
-rw-r--r-- | shell/hush_test/hush-quoting/dollar_altvalue9.right | 24 | ||||
-rwxr-xr-x | shell/hush_test/hush-quoting/dollar_altvalue9.tests | 17 |
5 files changed, 284 insertions, 61 deletions
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 @@ | |||
1 | Unquoted 1: | ||
2 | |a| | ||
3 | |x y| | ||
4 | |1| | ||
5 | |2| | ||
6 | |1 2| | ||
7 | |A| | ||
8 | |B| | ||
9 | |C D| | ||
10 | |zb| | ||
11 | Quoted 1: | ||
12 | |a 'x y' 1 2 '' 1 2 A B C D zb| | ||
13 | Unquoted 2: | ||
14 | |ax y| | ||
15 | |1| | ||
16 | |2| | ||
17 | |1 2| | ||
18 | |A| | ||
19 | |B| | ||
20 | |C D| | ||
21 | |z| | ||
22 | |b| | ||
23 | Quoted 2: | ||
24 | |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 @@ | |||
1 | f() { for i; do echo "|$i|"; done; } | ||
2 | |||
3 | echo Unquoted 1: | ||
4 | x='1 2'; f a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b | ||
5 | echo Quoted 1: | ||
6 | x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b" | ||
7 | |||
8 | echo Unquoted 2: | ||
9 | x='1 2'; f a${x:+'x y' $x '' "$x" `echo A B` "`echo C D`" z }b | ||
10 | echo Quoted 2: | ||
11 | x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z }b" | ||
12 | |||
13 | #echo Unquoted 3: | ||
14 | #e= | ||
15 | #x='1 2'; f a${x:+'x y' $x '' "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b | ||
16 | #echo Quoted 3: | ||
17 | #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 | |||
5873 | return exp_str; | 5873 | return exp_str; |
5874 | } | 5874 | } |
5875 | 5875 | ||
5876 | static int expand_vars_to_list(o_string *output, int n, char *arg); | ||
5877 | |||
5878 | static int encode_then_append_var_plusminus(o_string *output, int n, | ||
5879 | const char *str, int dquoted) | ||
5880 | { | ||
5881 | struct in_str input; | ||
5882 | o_string dest = NULL_O_STRING; | ||
5883 | |||
5884 | #if 0 //todo? | ||
5885 | const char *cp; | ||
5886 | cp = str; | ||
5887 | for (;;) { | ||
5888 | if (!*cp) return NULL; /* string has no special chars */ | ||
5889 | if (*cp == '$') break; | ||
5890 | if (*cp == '\\') break; | ||
5891 | if (*cp == '\'') break; | ||
5892 | if (*cp == '"') break; | ||
5893 | #if ENABLE_HUSH_TICK | ||
5894 | if (*cp == '`') break; | ||
5895 | #endif | ||
5896 | cp++; | ||
5897 | } | ||
5898 | #endif | ||
5899 | |||
5900 | /* Expanding ARG in ${var+ARG}, ${var-ARG} */ | ||
5901 | |||
5902 | setup_string_in_str(&input, str); | ||
5903 | |||
5904 | for (;;) { | ||
5905 | int ch; | ||
5906 | |||
5907 | ch = i_getch(&input); | ||
5908 | debug_printf_parse("%s: ch=%c (%d) escape=%x\n", | ||
5909 | __func__, ch, ch, dest.o_expflags); | ||
5910 | |||
5911 | if (!dest.o_expflags) { | ||
5912 | if (ch == EOF) | ||
5913 | break; | ||
5914 | if (!dquoted && strchr(G.ifs, ch)) { | ||
5915 | /* PREFIX${x:d${e}f ...} and we met space: expand "d${e}f" and start new word. | ||
5916 | * do not assume we are at the start of the word (PREFIX above). | ||
5917 | */ | ||
5918 | if (dest.data) { | ||
5919 | n = expand_vars_to_list(output, n, dest.data); | ||
5920 | o_free(&dest); | ||
5921 | o_addchr(output, '\0'); | ||
5922 | n = o_save_ptr(output, n); /* create next word */ | ||
5923 | } else | ||
5924 | if (output->length != o_get_last_ptr(output, n) | ||
5925 | || output->has_quoted_part | ||
5926 | ) { | ||
5927 | /* For these cases: | ||
5928 | * f() { for i; do echo "|$i|"; done; }; x=x | ||
5929 | * f a${x:+ }b # 1st condition | ||
5930 | * |a| | ||
5931 | * |b| | ||
5932 | * f ""${x:+ }b # 2nd condition | ||
5933 | * || | ||
5934 | * |b| | ||
5935 | */ | ||
5936 | o_addchr(output, '\0'); | ||
5937 | n = o_save_ptr(output, n); /* create next word */ | ||
5938 | } | ||
5939 | continue; | ||
5940 | } | ||
5941 | if (!dquoted && ch == '\'') { | ||
5942 | //quoting version of add_till_single_quote() (try to merge?): | ||
5943 | for (;;) { | ||
5944 | ch = i_getch(&input); | ||
5945 | if (ch == EOF) { | ||
5946 | syntax_error_unterm_ch('\''); | ||
5947 | goto ret; /* error */ | ||
5948 | } | ||
5949 | if (ch == '\'') | ||
5950 | break; | ||
5951 | o_addqchr(&dest, ch); | ||
5952 | } | ||
5953 | continue; | ||
5954 | } | ||
5955 | } | ||
5956 | if (ch == EOF) { | ||
5957 | syntax_error_unterm_ch('"'); | ||
5958 | goto ret; /* error */ | ||
5959 | } | ||
5960 | if (ch == '"') { | ||
5961 | dest.o_expflags ^= EXP_FLAG_ESC_GLOB_CHARS; | ||
5962 | continue; | ||
5963 | } | ||
5964 | if (ch == '\\') { | ||
5965 | ch = i_getch(&input); | ||
5966 | if (ch == EOF) { | ||
5967 | //example? error message? syntax_error_unterm_ch('"'); | ||
5968 | debug_printf_parse("%s: error: \\<eof>\n", __func__); | ||
5969 | goto ret; | ||
5970 | } | ||
5971 | o_addqchr(&dest, ch); | ||
5972 | continue; | ||
5973 | } | ||
5974 | if (ch == '$') { | ||
5975 | if (!parse_dollar(NULL, &dest, &input, /*quote_mask:*/ (dest.o_expflags || dquoted) ? 0x80 : 0)) { | ||
5976 | debug_printf_parse("%s: error: parse_dollar returned 0 (error)\n", __func__); | ||
5977 | goto ret; | ||
5978 | } | ||
5979 | continue; | ||
5980 | } | ||
5981 | #if ENABLE_HUSH_TICK | ||
5982 | if (ch == '`') { | ||
5983 | //unsigned pos = dest->length; | ||
5984 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | ||
5985 | o_addchr(&dest, (dest.o_expflags || dquoted) ? 0x80 | '`' : '`'); | ||
5986 | if (!add_till_backquote(&dest, &input, | ||
5987 | /*in_dquote:*/ dest.o_expflags /* nonzero if EXP_FLAG_ESC_GLOB_CHARS set */ | ||
5988 | ) | ||
5989 | ) { | ||
5990 | goto ret; /* error */ | ||
5991 | } | ||
5992 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | ||
5993 | //debug_printf_subst("SUBST RES3 '%s'\n", dest->data + pos); | ||
5994 | continue; | ||
5995 | } | ||
5996 | #endif | ||
5997 | o_addQchr(&dest, ch); | ||
5998 | } /* for (;;) */ | ||
5999 | |||
6000 | if (dest.data) { | ||
6001 | n = expand_vars_to_list(output, n, dest.data); | ||
6002 | } | ||
6003 | ret: | ||
6004 | o_free_unsafe(&dest); | ||
6005 | return n; | ||
6006 | } | ||
6007 | |||
5876 | #if ENABLE_FEATURE_SH_MATH | 6008 | #if ENABLE_FEATURE_SH_MATH |
5877 | static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p) | 6009 | static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p) |
5878 | { | 6010 | { |
@@ -6231,78 +6363,86 @@ static NOINLINE int expand_one_var(o_string *output, | |||
6231 | * | 6363 | * |
6232 | * Colon forms (${var:-word}, ${var:=word} etc) do the same, | 6364 | * Colon forms (${var:-word}, ${var:=word} etc) do the same, |
6233 | * but also treat null var as if it is unset. | 6365 | * but also treat null var as if it is unset. |
6366 | * | ||
6367 | * Word-splitting and single quote behavior: | ||
6368 | * | ||
6369 | * $ f() { for i; do echo "|$i|"; done; }; | ||
6370 | * | ||
6371 | * $ x=; f ${x:?'x y' z} | ||
6372 | * bash: x: x y z #BUG: does not abort, ${} results in empty expansion | ||
6373 | * $ x=; f "${x:?'x y' z}" | ||
6374 | * bash: x: x y z # dash prints: dash: x: 'x y' z #BUG: does not abort, ${} results in "" | ||
6375 | * | ||
6376 | * $ x=; f ${x:='x y' z} | ||
6377 | * |x| | ||
6378 | * |y| | ||
6379 | * |z| | ||
6380 | * $ x=; f "${x:='x y' z}" | ||
6381 | * |'x y' z| | ||
6382 | * | ||
6383 | * $ x=x; f ${x:+'x y' z}| | ||
6384 | * |x y| | ||
6385 | * |z| | ||
6386 | * $ x=x; f "${x:+'x y' z}" | ||
6387 | * |'x y' z| | ||
6388 | * | ||
6389 | * $ x=; f ${x:-'x y' z} | ||
6390 | * |x y| | ||
6391 | * |z| | ||
6392 | * $ x=; f "${x:-'x y' z}" | ||
6393 | * |'x y' z| | ||
6234 | */ | 6394 | */ |
6235 | /* | ||
6236 | * Word-splitting and squote behavior of bash: | ||
6237 | * $ f() { for i; do echo "|$i|"; done; }; | ||
6238 | * | ||
6239 | * $ x=; f ${x:?'x y' z} | ||
6240 | * bash: x: x y z #BUG: does not abort, ${} results in empty expansion | ||
6241 | * $ x=; f "${x:?'x y' z}" | ||
6242 | * bash: x: x y z # dash prints: dash: x: 'x y' z #BUG: does not abort, ${} results in "" | ||
6243 | * | ||
6244 | * $ x=; f ${x:='x y' z} | ||
6245 | * |x| | ||
6246 | * |y| | ||
6247 | * |z| | ||
6248 | * $ x=; f "${x:='x y' z}" | ||
6249 | * |'x y' z| | ||
6250 | * | ||
6251 | * $ x=x; f ${x:+'x y' z} | ||
6252 | * |x y| | ||
6253 | * |z| | ||
6254 | * $ x=x; f "${x:+'x y' z}" | ||
6255 | * |'x y' z| | ||
6256 | * | ||
6257 | * $ x=; f ${x:-'x y' z} | ||
6258 | * |x y| | ||
6259 | * |z| | ||
6260 | * $ x=; f "${x:-'x y' z}" | ||
6261 | * |'x y' z| | ||
6262 | */ | ||
6263 | int use_word = (!val || ((exp_save == ':') && !val[0])); | 6395 | int use_word = (!val || ((exp_save == ':') && !val[0])); |
6264 | if (exp_op == '+') | 6396 | if (exp_op == '+') |
6265 | use_word = !use_word; | 6397 | use_word = !use_word; |
6266 | debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, | 6398 | debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op, |
6267 | (exp_save == ':') ? "true" : "false", use_word); | 6399 | (exp_save == ':') ? "true" : "false", use_word); |
6268 | if (use_word) { | 6400 | if (use_word) { |
6269 | //FIXME: unquoted ${x:+"b c" d} and ${x:+'b c' d} should expand to two words | 6401 | if (exp_op == '+' || exp_op == '-') { |
6270 | //currently it expands to three. | 6402 | /* ${var+word} - use alternative value */ |
6271 | to_be_freed = encode_then_expand_vararg(exp_word, | 6403 | /* ${var-word} - use default value */ |
6272 | /*handle_squotes:*/ !(arg0 & 0x80), | 6404 | n = encode_then_append_var_plusminus(output, n, exp_word, |
6273 | /*unbackslash:*/ 0 | 6405 | /*dquoted:*/ (arg0 & 0x80) |
6274 | ); | ||
6275 | if (to_be_freed) | ||
6276 | exp_word = to_be_freed; | ||
6277 | if (exp_op == '?') { | ||
6278 | /* mimic bash message */ | ||
6279 | msg_and_die_if_script("%s: %s", | ||
6280 | var, | ||
6281 | exp_word[0] | ||
6282 | ? exp_word | ||
6283 | : "parameter null or not set" | ||
6284 | /* ash has more specific messages, a-la: */ | ||
6285 | /*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/ | ||
6286 | ); | 6406 | ); |
6407 | val = NULL; | ||
6408 | } else { | ||
6409 | /* ${var?word} - indicate error if unset */ | ||
6410 | /* ${var=word} - assign and use default value */ | ||
6411 | to_be_freed = encode_then_expand_vararg(exp_word, | ||
6412 | /*handle_squotes:*/ !(arg0 & 0x80), | ||
6413 | /*unbackslash:*/ 0 | ||
6414 | ); | ||
6415 | if (to_be_freed) | ||
6416 | exp_word = to_be_freed; | ||
6417 | if (exp_op == '?') { | ||
6418 | /* mimic bash message */ | ||
6419 | msg_and_die_if_script("%s: %s", | ||
6420 | var, | ||
6421 | exp_word[0] | ||
6422 | ? exp_word | ||
6423 | : "parameter null or not set" | ||
6424 | /* ash has more specific messages, a-la: */ | ||
6425 | /*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/ | ||
6426 | ); | ||
6287 | //TODO: how interactive bash aborts expansion mid-command? | 6427 | //TODO: how interactive bash aborts expansion mid-command? |
6288 | //It aborts the entire line, returns to prompt: | 6428 | //It aborts the entire line, returns to prompt: |
6289 | // $ f() { for i; do echo "|$i|"; done; }; x=; f "${x:?'x y' z}"; echo YO | 6429 | // $ f() { for i; do echo "|$i|"; done; }; x=; f "${x:?'x y' z}"; echo YO |
6290 | // bash: x: x y z | 6430 | // bash: x: x y z |
6291 | // $ | 6431 | // $ |
6292 | // ("echo YO" is not executed, neither the f function call) | 6432 | // ("echo YO" is not executed, neither the f function call) |
6293 | } else { | ||
6294 | val = exp_word; | ||
6295 | } | ||
6296 | |||
6297 | if (exp_op == '=') { | ||
6298 | /* ${var=[word]} or ${var:=[word]} */ | ||
6299 | if (isdigit(var[0]) || var[0] == '#') { | ||
6300 | /* mimic bash message */ | ||
6301 | msg_and_die_if_script("$%s: cannot assign in this way", var); | ||
6302 | val = NULL; | ||
6303 | } else { | 6433 | } else { |
6304 | char *new_var = xasprintf("%s=%s", var, val); | 6434 | val = exp_word; |
6305 | set_local_var(new_var, /*flag:*/ 0); | 6435 | } |
6436 | if (exp_op == '=') { | ||
6437 | /* ${var=[word]} or ${var:=[word]} */ | ||
6438 | if (isdigit(var[0]) || var[0] == '#') { | ||
6439 | /* mimic bash message */ | ||
6440 | msg_and_die_if_script("$%s: cannot assign in this way", var); | ||
6441 | val = NULL; | ||
6442 | } else { | ||
6443 | char *new_var = xasprintf("%s=%s", var, val); | ||
6444 | set_local_var(new_var, /*flag:*/ 0); | ||
6445 | } | ||
6306 | } | 6446 | } |
6307 | } | 6447 | } |
6308 | } | 6448 | } |
@@ -6482,8 +6622,9 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg) | |||
6482 | } | 6622 | } |
6483 | debug_print_list("expand_vars_to_list[a]", output, n); | 6623 | debug_print_list("expand_vars_to_list[a]", output, n); |
6484 | /* this part is literal, and it was already pre-quoted | 6624 | /* this part is literal, and it was already pre-quoted |
6485 | * if needed (much earlier), do not use o_addQstr here! */ | 6625 | * if needed (much earlier), do not use o_addQstr here! |
6486 | o_addstr_with_NUL(output, arg); | 6626 | */ |
6627 | o_addstr(output, arg); | ||
6487 | debug_print_list("expand_vars_to_list[b]", output, n); | 6628 | debug_print_list("expand_vars_to_list[b]", output, n); |
6488 | } else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */ | 6629 | } else if (output->length == o_get_last_ptr(output, n) /* expansion is empty */ |
6489 | && !(cant_be_null & 0x80) /* and all vars were not quoted. */ | 6630 | && !(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) | |||
6491 | n--; | 6632 | n--; |
6492 | /* allow to reuse list[n] later without re-growth */ | 6633 | /* allow to reuse list[n] later without re-growth */ |
6493 | output->has_empty_slot = 1; | 6634 | output->has_empty_slot = 1; |
6494 | } else { | ||
6495 | o_addchr(output, '\0'); | ||
6496 | } | 6635 | } |
6497 | 6636 | ||
6498 | return n; | 6637 | return n; |
@@ -6517,6 +6656,8 @@ static char **expand_variables(char **argv, unsigned expflags) | |||
6517 | 6656 | ||
6518 | /* expand argv[i] */ | 6657 | /* expand argv[i] */ |
6519 | n = expand_vars_to_list(&output, n, *argv++); | 6658 | n = expand_vars_to_list(&output, n, *argv++); |
6659 | /* if (!output->has_empty_slot) -- need this?? */ | ||
6660 | o_addchr(&output, '\0'); | ||
6520 | } | 6661 | } |
6521 | debug_print_list("expand_variables", &output, n); | 6662 | debug_print_list("expand_variables", &output, n); |
6522 | 6663 | ||
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 @@ | |||
1 | Unquoted 1: | ||
2 | |a| | ||
3 | |x y| | ||
4 | |1| | ||
5 | |2| | ||
6 | |1 2| | ||
7 | |A| | ||
8 | |B| | ||
9 | |C D| | ||
10 | |zb| | ||
11 | Quoted 1: | ||
12 | |a 'x y' 1 2 '' 1 2 A B C D zb| | ||
13 | Unquoted 2: | ||
14 | |ax y| | ||
15 | |1| | ||
16 | |2| | ||
17 | |1 2| | ||
18 | |A| | ||
19 | |B| | ||
20 | |C D| | ||
21 | |z| | ||
22 | |b| | ||
23 | Quoted 2: | ||
24 | |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 @@ | |||
1 | f() { for i; do echo "|$i|"; done; } | ||
2 | |||
3 | echo Unquoted 1: | ||
4 | x='1 2'; f a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b | ||
5 | echo Quoted 1: | ||
6 | x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z}b" | ||
7 | |||
8 | echo Unquoted 2: | ||
9 | x='1 2'; f a${x:+'x y' $x '' "$x" `echo A B` "`echo C D`" z }b | ||
10 | echo Quoted 2: | ||
11 | x='1 2'; f "a${x:+ 'x y' $x '' "$x" `echo A B` "`echo C D`" z }b" | ||
12 | |||
13 | #echo Unquoted 3: | ||
14 | #e= | ||
15 | #x='1 2'; f a${x:+'x y' $x '' "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b | ||
16 | #echo Quoted 3: | ||
17 | #x='1 2'; f "a${x:+ 'x y' $x '' "$x" $e $e "$e" $e `echo A B` "`echo C D`" z }b" | ||