diff options
author | Ron Yorston <rmy@pobox.com> | 2018-05-13 08:15:58 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2018-05-13 08:15:58 +0100 |
commit | 3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d (patch) | |
tree | a527d0db15f34a137fc11df5538c7f2f7c6d72de /shell | |
parent | 6f7d1af269eed4b42daeb9c6dfd2ba62f9cd47e4 (diff) | |
parent | d80eecb86812c1fbda652f9b995060c26ba0b155 (diff) | |
download | busybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.tar.gz busybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.tar.bz2 busybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.zip |
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
32 files changed, 1066 insertions, 294 deletions
diff --git a/shell/ash.c b/shell/ash.c index a8ba9c4ef..7fa9dae21 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
@@ -224,7 +224,20 @@ | |||
224 | #define IF_BASH_PATTERN_SUBST IF_ASH_BASH_COMPAT | 224 | #define IF_BASH_PATTERN_SUBST IF_ASH_BASH_COMPAT |
225 | #define BASH_SUBSTR ENABLE_ASH_BASH_COMPAT | 225 | #define BASH_SUBSTR ENABLE_ASH_BASH_COMPAT |
226 | #define IF_BASH_SUBSTR IF_ASH_BASH_COMPAT | 226 | #define IF_BASH_SUBSTR IF_ASH_BASH_COMPAT |
227 | /* [[ EXPR ]] */ | 227 | /* BASH_TEST2: [[ EXPR ]] |
228 | * Status of [[ support: | ||
229 | * We replace && and || with -a and -o | ||
230 | * TODO: | ||
231 | * singleword+noglob expansion: | ||
232 | * v='a b'; [[ $v = 'a b' ]]; echo 0:$? | ||
233 | * [[ /bin/n* ]]; echo 0:$? | ||
234 | * -a/-o are not AND/OR ops! (they are just strings) | ||
235 | * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) | ||
236 | * = is glob match operator, not equality operator: STR = GLOB | ||
237 | * (in GLOB, quoting is significant on char-by-char basis: a*cd"*") | ||
238 | * == same as = | ||
239 | * add =~ regex match operator: STR =~ REGEX | ||
240 | */ | ||
228 | #define BASH_TEST2 (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST) | 241 | #define BASH_TEST2 (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST) |
229 | #define BASH_SOURCE ENABLE_ASH_BASH_COMPAT | 242 | #define BASH_SOURCE ENABLE_ASH_BASH_COMPAT |
230 | #define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT | 243 | #define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT |
@@ -7944,9 +7957,16 @@ expandmeta(struct strlist *str /*, int flag*/) | |||
7944 | /* | 7957 | /* |
7945 | * Do metacharacter (i.e. *, ?, [...]) expansion. | 7958 | * Do metacharacter (i.e. *, ?, [...]) expansion. |
7946 | */ | 7959 | */ |
7960 | typedef struct exp_t { | ||
7961 | char *dir; | ||
7962 | unsigned dir_max; | ||
7963 | } exp_t; | ||
7947 | static void | 7964 | static void |
7948 | expmeta(char *expdir, char *enddir, char *name) | 7965 | expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) |
7949 | { | 7966 | { |
7967 | #define expdir exp->dir | ||
7968 | #define expdir_max exp->dir_max | ||
7969 | char *enddir = expdir + expdir_len; | ||
7950 | char *p; | 7970 | char *p; |
7951 | const char *cp; | 7971 | const char *cp; |
7952 | char *start; | 7972 | char *start; |
@@ -7989,15 +8009,15 @@ expmeta(char *expdir, char *enddir, char *name) | |||
7989 | } | 8009 | } |
7990 | } | 8010 | } |
7991 | if (metaflag == 0) { /* we've reached the end of the file name */ | 8011 | if (metaflag == 0) { /* we've reached the end of the file name */ |
7992 | if (enddir != expdir) | 8012 | if (!expdir_len) |
7993 | metaflag++; | 8013 | return; |
7994 | p = name; | 8014 | p = name; |
7995 | do { | 8015 | do { |
7996 | if (*p == '\\') | 8016 | if (*p == '\\') |
7997 | p++; | 8017 | p++; |
7998 | *enddir++ = *p; | 8018 | *enddir++ = *p; |
7999 | } while (*p++); | 8019 | } while (*p++); |
8000 | if (metaflag == 0 || lstat(expdir, &statb) >= 0) | 8020 | if (lstat(expdir, &statb) == 0) |
8001 | addfname(expdir); | 8021 | addfname(expdir); |
8002 | return; | 8022 | return; |
8003 | } | 8023 | } |
@@ -8010,19 +8030,14 @@ expmeta(char *expdir, char *enddir, char *name) | |||
8010 | *enddir++ = *p++; | 8030 | *enddir++ = *p++; |
8011 | } while (p < start); | 8031 | } while (p < start); |
8012 | } | 8032 | } |
8013 | if (enddir == expdir) { | 8033 | *enddir = '\0'; |
8034 | cp = expdir; | ||
8035 | expdir_len = enddir - cp; | ||
8036 | if (!expdir_len) | ||
8014 | cp = "."; | 8037 | cp = "."; |
8015 | } else if (enddir == expdir + 1 && *expdir == '/') { | ||
8016 | cp = "/"; | ||
8017 | } else { | ||
8018 | cp = expdir; | ||
8019 | enddir[-1] = '\0'; | ||
8020 | } | ||
8021 | dirp = opendir(cp); | 8038 | dirp = opendir(cp); |
8022 | if (dirp == NULL) | 8039 | if (dirp == NULL) |
8023 | return; | 8040 | return; |
8024 | if (enddir != expdir) | ||
8025 | enddir[-1] = '/'; | ||
8026 | if (*endname == 0) { | 8041 | if (*endname == 0) { |
8027 | atend = 1; | 8042 | atend = 1; |
8028 | } else { | 8043 | } else { |
@@ -8030,6 +8045,7 @@ expmeta(char *expdir, char *enddir, char *name) | |||
8030 | *endname = '\0'; | 8045 | *endname = '\0'; |
8031 | endname += esc + 1; | 8046 | endname += esc + 1; |
8032 | } | 8047 | } |
8048 | name_len -= endname - name; | ||
8033 | matchdot = 0; | 8049 | matchdot = 0; |
8034 | p = start; | 8050 | p = start; |
8035 | if (*p == '\\') | 8051 | if (*p == '\\') |
@@ -8044,16 +8060,30 @@ expmeta(char *expdir, char *enddir, char *name) | |||
8044 | strcpy(enddir, dp->d_name); | 8060 | strcpy(enddir, dp->d_name); |
8045 | addfname(expdir); | 8061 | addfname(expdir); |
8046 | } else { | 8062 | } else { |
8047 | for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';) | 8063 | unsigned offset; |
8048 | continue; | 8064 | unsigned len; |
8049 | p[-1] = '/'; | 8065 | |
8050 | expmeta(expdir, p, endname); | 8066 | p = stpcpy(enddir, dp->d_name); |
8067 | *p = '/'; | ||
8068 | |||
8069 | offset = p - expdir + 1; | ||
8070 | len = offset + name_len + NAME_MAX; | ||
8071 | if (len > expdir_max) { | ||
8072 | len += PATH_MAX; | ||
8073 | expdir = ckrealloc(expdir, len); | ||
8074 | expdir_max = len; | ||
8075 | } | ||
8076 | |||
8077 | expmeta(exp, endname, name_len, offset); | ||
8078 | enddir = expdir + expdir_len; | ||
8051 | } | 8079 | } |
8052 | } | 8080 | } |
8053 | } | 8081 | } |
8054 | closedir(dirp); | 8082 | closedir(dirp); |
8055 | if (!atend) | 8083 | if (!atend) |
8056 | endname[-esc - 1] = esc ? '\\' : '/'; | 8084 | endname[-esc - 1] = esc ? '\\' : '/'; |
8085 | #undef expdir | ||
8086 | #undef expdir_max | ||
8057 | } | 8087 | } |
8058 | 8088 | ||
8059 | static struct strlist * | 8089 | static struct strlist * |
@@ -8126,10 +8156,11 @@ expandmeta(struct strlist *str /*, int flag*/) | |||
8126 | /* TODO - EXP_REDIR */ | 8156 | /* TODO - EXP_REDIR */ |
8127 | 8157 | ||
8128 | while (str) { | 8158 | while (str) { |
8129 | char *expdir; | 8159 | exp_t exp; |
8130 | struct strlist **savelastp; | 8160 | struct strlist **savelastp; |
8131 | struct strlist *sp; | 8161 | struct strlist *sp; |
8132 | char *p; | 8162 | char *p; |
8163 | unsigned len; | ||
8133 | 8164 | ||
8134 | if (fflag) | 8165 | if (fflag) |
8135 | goto nometa; | 8166 | goto nometa; |
@@ -8139,13 +8170,12 @@ expandmeta(struct strlist *str /*, int flag*/) | |||
8139 | 8170 | ||
8140 | INT_OFF; | 8171 | INT_OFF; |
8141 | p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); | 8172 | p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); |
8142 | { | 8173 | len = strlen(p); |
8143 | int i = strlen(str->text); | 8174 | exp.dir_max = len + PATH_MAX; |
8144 | //BUGGY estimation of how long expanded name can be | 8175 | exp.dir = ckmalloc(exp.dir_max); |
8145 | expdir = ckmalloc(i < 2048 ? 2048 : i+1); | 8176 | |
8146 | } | 8177 | expmeta(&exp, p, len, 0); |
8147 | expmeta(expdir, expdir, p); | 8178 | free(exp.dir); |
8148 | free(expdir); | ||
8149 | if (p != str->text) | 8179 | if (p != str->text) |
8150 | free(p); | 8180 | free(p); |
8151 | INT_ON; | 8181 | INT_ON; |
@@ -12128,10 +12158,12 @@ simplecmd(void) | |||
12128 | case TLP: | 12158 | case TLP: |
12129 | function_flag = 0; | 12159 | function_flag = 0; |
12130 | break; | 12160 | break; |
12161 | # if BASH_TEST2 | ||
12131 | case TWORD: | 12162 | case TWORD: |
12132 | if (strcmp("[[", wordtext) == 0) | 12163 | if (strcmp("[[", wordtext) == 0) |
12133 | goto do_func; | 12164 | goto do_func; |
12134 | /* fall through */ | 12165 | /* fall through */ |
12166 | # endif | ||
12135 | default: | 12167 | default: |
12136 | raise_error_unexpected_syntax(-1); | 12168 | raise_error_unexpected_syntax(-1); |
12137 | } | 12169 | } |
@@ -12179,7 +12211,8 @@ simplecmd(void) | |||
12179 | *vpp = NULL; | 12211 | *vpp = NULL; |
12180 | *rpp = NULL; | 12212 | *rpp = NULL; |
12181 | n = stzalloc(sizeof(struct ncmd)); | 12213 | n = stzalloc(sizeof(struct ncmd)); |
12182 | n->type = NCMD; | 12214 | if (NCMD != 0) |
12215 | n->type = NCMD; | ||
12183 | n->ncmd.linno = savelinno; | 12216 | n->ncmd.linno = savelinno; |
12184 | n->ncmd.args = args; | 12217 | n->ncmd.args = args; |
12185 | n->ncmd.assign = vars; | 12218 | n->ncmd.assign = vars; |
@@ -12500,8 +12533,11 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) | |||
12500 | CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ | 12533 | CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ |
12501 | switch (SIT(c, synstack->syntax)) { | 12534 | switch (SIT(c, synstack->syntax)) { |
12502 | case CNL: /* '\n' */ | 12535 | case CNL: /* '\n' */ |
12503 | if (synstack->syntax == BASESYNTAX) | 12536 | if (synstack->syntax == BASESYNTAX |
12537 | && !synstack->varnest | ||
12538 | ) { | ||
12504 | goto endword; /* exit outer loop */ | 12539 | goto endword; /* exit outer loop */ |
12540 | } | ||
12505 | USTPUTC(c, out); | 12541 | USTPUTC(c, out); |
12506 | nlprompt(); | 12542 | nlprompt(); |
12507 | c = pgetc(); | 12543 | c = pgetc(); |
diff --git a/shell/ash_test/ash-parsing/bkslash_eof1.right b/shell/ash_test/ash-parsing/bkslash_eof1.right new file mode 100644 index 000000000..6c6df0b0c --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_eof1.right | |||
@@ -0,0 +1 @@ | |||
ok\ | |||
diff --git a/shell/ash_test/ash-parsing/bkslash_eof1.tests b/shell/ash_test/ash-parsing/bkslash_eof1.tests new file mode 100755 index 000000000..97629cb13 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_eof1.tests | |||
@@ -0,0 +1 @@ | |||
eval 'echo ok\' | |||
diff --git a/shell/ash_test/ash-parsing/bkslash_newline3.right b/shell/ash_test/ash-parsing/bkslash_newline3.right new file mode 100644 index 000000000..e635074e5 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_newline3.right | |||
@@ -0,0 +1 @@ | |||
a:[a] | |||
diff --git a/shell/ash_test/ash-parsing/bkslash_newline3.tests b/shell/ash_test/ash-parsing/bkslash_newline3.tests new file mode 100755 index 000000000..2accd4395 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_newline3.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | for s in \ | ||
2 | a; do | ||
3 | echo "a:[$s]" | ||
4 | done | ||
diff --git a/shell/ash_test/ash-quoting/case_glob1.right b/shell/ash_test/ash-quoting/case_glob1.right new file mode 100644 index 000000000..b4785957b --- /dev/null +++ b/shell/ash_test/ash-quoting/case_glob1.right | |||
@@ -0,0 +1 @@ | |||
s | |||
diff --git a/shell/ash_test/ash-quoting/case_glob1.tests b/shell/ash_test/ash-quoting/case_glob1.tests new file mode 100755 index 000000000..8dbbc0fb1 --- /dev/null +++ b/shell/ash_test/ash-quoting/case_glob1.tests | |||
@@ -0,0 +1,8 @@ | |||
1 | g='[3](a)(b)(c)' | ||
2 | s='[3](a)(b)(c)' | ||
3 | case $g in | ||
4 | "$s") echo s | ||
5 | ;; | ||
6 | *) echo "*" | ||
7 | ;; | ||
8 | esac | ||
diff --git a/shell/ash_test/ash-redir/redir_exec1.right b/shell/ash_test/ash-redir/redir_exec1.right index d4393d10c..c98455bf5 100644 --- a/shell/ash_test/ash-redir/redir_exec1.right +++ b/shell/ash_test/ash-redir/redir_exec1.right | |||
@@ -1,2 +1,2 @@ | |||
1 | redir_exec1.tests: line 1: can't create /cant/be/created: nonexistent directory | 1 | ./redir_exec1.tests: line 1: can't create /cant/be/created: nonexistent directory |
2 | First | 2 | First |
diff --git a/shell/ash_test/ash-vars/param_expand_alt2.right b/shell/ash_test/ash-vars/param_expand_alt2.right new file mode 100644 index 000000000..fef5889ca --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_alt2.right | |||
@@ -0,0 +1,4 @@ | |||
1 | Unquoted: H H | ||
2 | Quoted: H | ||
3 | H | ||
4 | Ok:0 | ||
diff --git a/shell/ash_test/ash-vars/param_expand_alt2.tests b/shell/ash_test/ash-vars/param_expand_alt2.tests new file mode 100755 index 000000000..d8abf4c3b --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_alt2.tests | |||
@@ -0,0 +1,7 @@ | |||
1 | echo Unquoted: H${$+ | ||
2 | }H | ||
3 | |||
4 | echo Quoted: "H${$+ | ||
5 | }H" | ||
6 | |||
7 | echo Ok:$? | ||
diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs4.right b/shell/ash_test/ash-vars/var_wordsplit_ifs4.right new file mode 100644 index 000000000..c27284c31 --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs4.right | |||
@@ -0,0 +1,5 @@ | |||
1 | |x| | ||
2 | Ok1:0 | ||
3 | |x| | ||
4 | || | ||
5 | Ok2:0 | ||
diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests b/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests new file mode 100755 index 000000000..638bfbb28 --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done | ||
2 | echo Ok1:$? | ||
3 | IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done | ||
4 | echo Ok2:$? | ||
diff --git a/shell/ash_test/ash-z_slow/many_ifs.right b/shell/ash_test/ash-z_slow/many_ifs.right new file mode 100644 index 000000000..f3bdccc6c --- /dev/null +++ b/shell/ash_test/ash-z_slow/many_ifs.right | |||
@@ -0,0 +1 @@ | |||
# tests 6856 passed 6856 failed 0 | |||
diff --git a/shell/ash_test/ash-z_slow/many_ifs.tests b/shell/ash_test/ash-z_slow/many_ifs.tests new file mode 100755 index 000000000..1f5b1b3a6 --- /dev/null +++ b/shell/ash_test/ash-z_slow/many_ifs.tests | |||
@@ -0,0 +1,257 @@ | |||
1 | # Usage: $SHELL ifs.sh | ||
2 | # | ||
3 | # This script generates 6856 tests for the set(1) and read(1) | ||
4 | # builtins w.r.t. IFS whitespace and non-whitespace characters. | ||
5 | # Each failed test produces one line on the standard output that | ||
6 | # contains the test along with the expected and actual results. | ||
7 | # The last output line contains the test result counts. ordered>0 | ||
8 | # are the number of tests where IFS=": " produced different results | ||
9 | # than IFS=" :". If a test fails the same way for IFS=": " and | ||
10 | # IFS=" :" then the second output line is suppressed. | ||
11 | |||
12 | TESTS=6856 | ||
13 | |||
14 | ksh_read=0 | ||
15 | echo 1 | read ksh_read | ||
16 | ksh_arith=0 | ||
17 | eval '((ksh_arith+=1))' 2>/dev/null | ||
18 | |||
19 | failed=0 | ||
20 | ordered=0 | ||
21 | passed=0 | ||
22 | |||
23 | split() | ||
24 | { | ||
25 | i=$1 s=$2 r=$3 S='' R='' | ||
26 | for ifs in ': ' ' :' | ||
27 | do IFS=$ifs | ||
28 | set x $i | ||
29 | shift | ||
30 | IFS=' ' | ||
31 | g="[$#]" | ||
32 | while : | ||
33 | do case $# in | ||
34 | 0) break ;; | ||
35 | esac | ||
36 | g="$g($1)" | ||
37 | shift | ||
38 | done | ||
39 | case $g in | ||
40 | "$s") case $ksh_arith in | ||
41 | 1) ((passed+=1)) ;; | ||
42 | *) passed=`expr $passed + 1` ;; | ||
43 | esac | ||
44 | case $S in | ||
45 | '') S=$g | ||
46 | ;; | ||
47 | "$g") ;; | ||
48 | *) case $ksh_arith in | ||
49 | 1) ((ordered+=1)) ;; | ||
50 | *) ordered=`expr $ordered + 1` ;; | ||
51 | esac | ||
52 | ;; | ||
53 | esac | ||
54 | ;; | ||
55 | "$S") case $ksh_arith in | ||
56 | 1) ((failed+=1)) ;; | ||
57 | *) failed=`expr $failed + 1` ;; | ||
58 | esac | ||
59 | ;; | ||
60 | *) case $ksh_arith in | ||
61 | 1) ((failed+=1)) ;; | ||
62 | *) failed=`expr $failed + 1` ;; | ||
63 | esac | ||
64 | case $s in | ||
65 | "$S") ;; | ||
66 | ?0*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#]\" # expected \"$s\" got \"$g\"" ;; | ||
67 | ?1*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)\" # expected \"$s\" got \"$g\"" ;; | ||
68 | ?2*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)\" # expected \"$s\" got \"$g\"" ;; | ||
69 | ?3*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)(\$3)\" # expected \"$s\" got \"$g\"" ;; | ||
70 | *) echo TEST ERROR i="'$i'" s="'$s'" ;; | ||
71 | esac | ||
72 | case $S in | ||
73 | '') S=$g | ||
74 | ;; | ||
75 | "$g") ;; | ||
76 | *) case $ksh_arith in | ||
77 | 1) ((ordered+=1)) ;; | ||
78 | *) ordered=`expr $ordered + 1` ;; | ||
79 | esac | ||
80 | ;; | ||
81 | esac | ||
82 | esac | ||
83 | case $ksh_read in | ||
84 | 1) echo "$i" | IFS=$ifs read x y; g="($x)($y)" ;; | ||
85 | *) g=`export ifs; echo "$i" | ( IFS=$ifs; read x y; echo "($x)($y)" )` ;; | ||
86 | esac | ||
87 | case $g in | ||
88 | "$r") case $ksh_arith in | ||
89 | 1) ((passed+=1)) ;; | ||
90 | *) passed=`expr $passed + 1` ;; | ||
91 | esac | ||
92 | case $R in | ||
93 | '') R=$g | ||
94 | ;; | ||
95 | "$g") ;; | ||
96 | *) case $ksh_arith in | ||
97 | 1) ((ordered+=1)) ;; | ||
98 | *) ordered=`expr $ordered + 1` ;; | ||
99 | esac | ||
100 | ;; | ||
101 | esac | ||
102 | ;; | ||
103 | "$R") case $ksh_arith in | ||
104 | 1) ((failed+=1)) ;; | ||
105 | *) failed=`expr $failed + 1` ;; | ||
106 | esac | ||
107 | ;; | ||
108 | *) case $ksh_arith in | ||
109 | 1) ((failed+=1)) ;; | ||
110 | *) failed=`expr $failed + 1` ;; | ||
111 | esac | ||
112 | case $r in | ||
113 | "$R") ;; | ||
114 | *) echo "echo \"$i\" | ( IFS=\"$ifs\" read x y; echo \"(\$x)(\$y)\" ) # expected \"$r\" got \"$g\"" ;; | ||
115 | esac | ||
116 | case $R in | ||
117 | '') R=$g | ||
118 | ;; | ||
119 | "$g") ;; | ||
120 | *) case $ksh_arith in | ||
121 | 1) ((ordered+=1)) ;; | ||
122 | *) ordered=`expr $ordered + 1` ;; | ||
123 | esac | ||
124 | ;; | ||
125 | esac | ||
126 | ;; | ||
127 | esac | ||
128 | done | ||
129 | } | ||
130 | |||
131 | for str in \ | ||
132 | '-' \ | ||
133 | 'a' \ | ||
134 | '- -' \ | ||
135 | '- a' \ | ||
136 | 'a -' \ | ||
137 | 'a b' \ | ||
138 | '- - -' \ | ||
139 | '- - a' \ | ||
140 | '- a -' \ | ||
141 | '- a b' \ | ||
142 | 'a - -' \ | ||
143 | 'a - b' \ | ||
144 | 'a b -' \ | ||
145 | 'a b c' \ | ||
146 | |||
147 | do | ||
148 | IFS=' ' | ||
149 | set x $str | ||
150 | |||
151 | shift | ||
152 | case $# in | ||
153 | 0) continue ;; | ||
154 | esac | ||
155 | |||
156 | f1=$1 | ||
157 | case $f1 in | ||
158 | '-') f1='' ;; | ||
159 | esac | ||
160 | |||
161 | shift | ||
162 | case $# in | ||
163 | 0) for d0 in '' ' ' | ||
164 | do | ||
165 | for d1 in '' ' ' ':' ' :' ': ' ' : ' | ||
166 | do | ||
167 | case $f1$d1 in | ||
168 | '') split "$d0$f1$d1" "[0]" "()()" ;; | ||
169 | ' ') ;; | ||
170 | *) split "$d0$f1$d1" "[1]($f1)" "($f1)()" ;; | ||
171 | esac | ||
172 | done | ||
173 | done | ||
174 | continue | ||
175 | ;; | ||
176 | esac | ||
177 | f2=$1 | ||
178 | case $f2 in | ||
179 | '-') f2='' ;; | ||
180 | esac | ||
181 | |||
182 | shift | ||
183 | case $# in | ||
184 | 0) for d0 in '' ' ' | ||
185 | do | ||
186 | for d1 in ' ' ':' ' :' ': ' ' : ' | ||
187 | do | ||
188 | case ' ' in | ||
189 | $f1$d1|$d1$f2) continue ;; | ||
190 | esac | ||
191 | for d2 in '' ' ' ':' ' :' ': ' ' : ' | ||
192 | do | ||
193 | case $f2$d2 in | ||
194 | '') split "$d0$f1$d1$f2$d2" "[1]($f1)" "($f1)()" ;; | ||
195 | ' ') ;; | ||
196 | *) split "$d0$f1$d1$f2$d2" "[2]($f1)($f2)" "($f1)($f2)" ;; | ||
197 | esac | ||
198 | done | ||
199 | done | ||
200 | done | ||
201 | continue | ||
202 | ;; | ||
203 | esac | ||
204 | f3=$1 | ||
205 | case $f3 in | ||
206 | '-') f3='' ;; | ||
207 | esac | ||
208 | |||
209 | shift | ||
210 | case $# in | ||
211 | 0) for d0 in '' ' ' | ||
212 | do | ||
213 | for d1 in ':' ' :' ': ' ' : ' | ||
214 | do | ||
215 | case ' ' in | ||
216 | $f1$d1|$d1$f2) continue ;; | ||
217 | esac | ||
218 | for d2 in ' ' ':' ' :' ': ' ' : ' | ||
219 | do | ||
220 | case $f2$d2 in | ||
221 | ' ') continue ;; | ||
222 | esac | ||
223 | case ' ' in | ||
224 | $f2$d2|$d2$f3) continue ;; | ||
225 | esac | ||
226 | for d3 in '' ' ' ':' ' :' ': ' ' : ' | ||
227 | do | ||
228 | case $f3$d3 in | ||
229 | '') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;; | ||
230 | ' ') ;; | ||
231 | *) x=$f2$d2$f3$d3 | ||
232 | x=${x# } #was x=${x#' '} hush needs fixing for this to work | ||
233 | x=${x% } #was x=${x%' '} | ||
234 | split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)" | ||
235 | ;; | ||
236 | esac | ||
237 | done | ||
238 | done | ||
239 | done | ||
240 | done | ||
241 | continue | ||
242 | ;; | ||
243 | esac | ||
244 | done | ||
245 | case $ksh_arith in | ||
246 | 1) ((tests=passed+failed)) ;; | ||
247 | *) tests=`expr $passed + $failed` ;; | ||
248 | esac | ||
249 | case $ordered in | ||
250 | 0) ordered="" ;; | ||
251 | *) ordered=" ordered $ordered" ;; | ||
252 | esac | ||
253 | case $tests in | ||
254 | $TESTS) fatal="" ;; | ||
255 | *) fatal=" -- fundamental IFS error -- $TESTS tests expected" | ||
256 | esac | ||
257 | echo "# tests $tests passed $passed failed $failed$ordered$fatal" | ||
diff --git a/shell/hush.c b/shell/hush.c index d5ea3b21f..c77700175 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -79,6 +79,18 @@ | |||
79 | * Some builtins mandated by standards: | 79 | * Some builtins mandated by standards: |
80 | * newgrp [GRP]: not a builtin in bash but a suid binary | 80 | * newgrp [GRP]: not a builtin in bash but a suid binary |
81 | * which spawns a new shell with new group ID | 81 | * which spawns a new shell with new group ID |
82 | * | ||
83 | * Status of [[ support: | ||
84 | * [[ args ]] are CMD_SINGLEWORD_NOGLOB: | ||
85 | * v='a b'; [[ $v = 'a b' ]]; echo 0:$? | ||
86 | * [[ /bin/n* ]]; echo 0:$? | ||
87 | * TODO: | ||
88 | * &&/|| are AND/OR ops, -a/-o are not | ||
89 | * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) | ||
90 | * = is glob match operator, not equality operator: STR = GLOB | ||
91 | * (in GLOB, quoting is significant on char-by-char basis: a*cd"*") | ||
92 | * == same as = | ||
93 | * add =~ regex match operator: STR =~ REGEX | ||
82 | */ | 94 | */ |
83 | //config:config HUSH | 95 | //config:config HUSH |
84 | //config: bool "hush (64 kb)" | 96 | //config: bool "hush (64 kb)" |
@@ -522,7 +534,6 @@ typedef struct o_string { | |||
522 | * possibly empty one: word"", wo''rd etc. */ | 534 | * possibly empty one: word"", wo''rd etc. */ |
523 | smallint has_quoted_part; | 535 | smallint has_quoted_part; |
524 | smallint has_empty_slot; | 536 | smallint has_empty_slot; |
525 | smallint o_assignment; /* 0:maybe, 1:yes, 2:no */ | ||
526 | } o_string; | 537 | } o_string; |
527 | enum { | 538 | enum { |
528 | EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ | 539 | EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ |
@@ -531,13 +542,6 @@ enum { | |||
531 | * by prepending \ to *, ?, [, \ */ | 542 | * by prepending \ to *, ?, [, \ */ |
532 | EXP_FLAG_ESC_GLOB_CHARS = 0x1, | 543 | EXP_FLAG_ESC_GLOB_CHARS = 0x1, |
533 | }; | 544 | }; |
534 | enum { | ||
535 | MAYBE_ASSIGNMENT = 0, | ||
536 | DEFINITELY_ASSIGNMENT = 1, | ||
537 | NOT_ASSIGNMENT = 2, | ||
538 | /* Not an assignment, but next word may be: "if v=xyz cmd;" */ | ||
539 | WORD_IS_KEYWORD = 3, | ||
540 | }; | ||
541 | /* Used for initialization: o_string foo = NULL_O_STRING; */ | 545 | /* Used for initialization: o_string foo = NULL_O_STRING; */ |
542 | #define NULL_O_STRING { NULL } | 546 | #define NULL_O_STRING { NULL } |
543 | 547 | ||
@@ -694,9 +698,11 @@ struct parse_context { | |||
694 | struct command *command; | 698 | struct command *command; |
695 | /* last redirect in command->redirects list */ | 699 | /* last redirect in command->redirects list */ |
696 | struct redir_struct *pending_redirect; | 700 | struct redir_struct *pending_redirect; |
701 | o_string word; | ||
697 | #if !BB_MMU | 702 | #if !BB_MMU |
698 | o_string as_string; | 703 | o_string as_string; |
699 | #endif | 704 | #endif |
705 | smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */ | ||
700 | #if HAS_KEYWORDS | 706 | #if HAS_KEYWORDS |
701 | smallint ctx_res_w; | 707 | smallint ctx_res_w; |
702 | smallint ctx_inverted; /* "! cmd | cmd" */ | 708 | smallint ctx_inverted; /* "! cmd | cmd" */ |
@@ -717,6 +723,13 @@ struct parse_context { | |||
717 | struct parse_context *stack; | 723 | struct parse_context *stack; |
718 | #endif | 724 | #endif |
719 | }; | 725 | }; |
726 | enum { | ||
727 | MAYBE_ASSIGNMENT = 0, | ||
728 | DEFINITELY_ASSIGNMENT = 1, | ||
729 | NOT_ASSIGNMENT = 2, | ||
730 | /* Not an assignment, but next word may be: "if v=xyz cmd;" */ | ||
731 | WORD_IS_KEYWORD = 3, | ||
732 | }; | ||
720 | 733 | ||
721 | /* On program start, environ points to initial environment. | 734 | /* On program start, environ points to initial environment. |
722 | * putenv adds new pointers into it, unsetenv removes them. | 735 | * putenv adds new pointers into it, unsetenv removes them. |
@@ -917,6 +930,7 @@ struct globals { | |||
917 | unsigned getopt_count; | 930 | unsigned getopt_count; |
918 | #endif | 931 | #endif |
919 | const char *ifs; | 932 | const char *ifs; |
933 | char *ifs_whitespace; /* = G.ifs or malloced */ | ||
920 | const char *cwd; | 934 | const char *cwd; |
921 | struct variable *top_var; | 935 | struct variable *top_var; |
922 | char **expanded_assignments; | 936 | char **expanded_assignments; |
@@ -1413,8 +1427,19 @@ static char *unbackslash(char *src) | |||
1413 | { | 1427 | { |
1414 | char *dst = src = strchrnul(src, '\\'); | 1428 | char *dst = src = strchrnul(src, '\\'); |
1415 | while (1) { | 1429 | while (1) { |
1416 | if (*src == '\\') | 1430 | if (*src == '\\') { |
1417 | src++; | 1431 | src++; |
1432 | if (*src != '\0') { | ||
1433 | /* \x -> x */ | ||
1434 | *dst++ = *src++; | ||
1435 | continue; | ||
1436 | } | ||
1437 | /* else: "\<nul>". Do not delete this backslash. | ||
1438 | * Testcase: eval 'echo ok\' | ||
1439 | */ | ||
1440 | *dst++ = '\\'; | ||
1441 | /* fallthrough */ | ||
1442 | } | ||
1418 | if ((*dst++ = *src++) == '\0') | 1443 | if ((*dst++ = *src++) == '\0') |
1419 | break; | 1444 | break; |
1420 | } | 1445 | } |
@@ -2266,6 +2291,7 @@ static int set_local_var(char *str, unsigned flags) | |||
2266 | } | 2291 | } |
2267 | 2292 | ||
2268 | /* Not found or shadowed - create new variable struct */ | 2293 | /* Not found or shadowed - create new variable struct */ |
2294 | debug_printf_env("%s: alloc new var '%s'/%u\n", __func__, str, local_lvl); | ||
2269 | cur = xzalloc(sizeof(*cur)); | 2295 | cur = xzalloc(sizeof(*cur)); |
2270 | cur->var_nest_level = local_lvl; | 2296 | cur->var_nest_level = local_lvl; |
2271 | cur->next = *cur_pp; | 2297 | cur->next = *cur_pp; |
@@ -2420,7 +2446,7 @@ static void set_vars_and_save_old(char **strings) | |||
2420 | * global linked list. | 2446 | * global linked list. |
2421 | */ | 2447 | */ |
2422 | } | 2448 | } |
2423 | //bb_error_msg("G.var_nest_level:%d", G.var_nest_level); | 2449 | debug_printf_env("%s: env override '%s'/%u\n", __func__, *s, G.var_nest_level); |
2424 | set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT); | 2450 | set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT); |
2425 | } else if (HUSH_DEBUG) { | 2451 | } else if (HUSH_DEBUG) { |
2426 | bb_error_msg_and_die("BUG in varexp4"); | 2452 | bb_error_msg_and_die("BUG in varexp4"); |
@@ -3670,6 +3696,8 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) | |||
3670 | static void initialize_context(struct parse_context *ctx) | 3696 | static void initialize_context(struct parse_context *ctx) |
3671 | { | 3697 | { |
3672 | memset(ctx, 0, sizeof(*ctx)); | 3698 | memset(ctx, 0, sizeof(*ctx)); |
3699 | if (MAYBE_ASSIGNMENT != 0) | ||
3700 | ctx->is_assignment = MAYBE_ASSIGNMENT; | ||
3673 | ctx->pipe = ctx->list_head = new_pipe(); | 3701 | ctx->pipe = ctx->list_head = new_pipe(); |
3674 | /* Create the memory for command, roughly: | 3702 | /* Create the memory for command, roughly: |
3675 | * ctx->pipe->cmds = new struct command; | 3703 | * ctx->pipe->cmds = new struct command; |
@@ -3751,7 +3779,7 @@ static const struct reserved_combo* match_reserved_word(o_string *word) | |||
3751 | } | 3779 | } |
3752 | /* Return NULL: not a keyword, else: keyword | 3780 | /* Return NULL: not a keyword, else: keyword |
3753 | */ | 3781 | */ |
3754 | static const struct reserved_combo* reserved_word(o_string *word, struct parse_context *ctx) | 3782 | static const struct reserved_combo* reserved_word(struct parse_context *ctx) |
3755 | { | 3783 | { |
3756 | # if ENABLE_HUSH_CASE | 3784 | # if ENABLE_HUSH_CASE |
3757 | static const struct reserved_combo reserved_match = { | 3785 | static const struct reserved_combo reserved_match = { |
@@ -3760,9 +3788,9 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c | |||
3760 | # endif | 3788 | # endif |
3761 | const struct reserved_combo *r; | 3789 | const struct reserved_combo *r; |
3762 | 3790 | ||
3763 | if (word->has_quoted_part) | 3791 | if (ctx->word.has_quoted_part) |
3764 | return 0; | 3792 | return 0; |
3765 | r = match_reserved_word(word); | 3793 | r = match_reserved_word(&ctx->word); |
3766 | if (!r) | 3794 | if (!r) |
3767 | return r; /* NULL */ | 3795 | return r; /* NULL */ |
3768 | 3796 | ||
@@ -3789,7 +3817,7 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c | |||
3789 | initialize_context(ctx); | 3817 | initialize_context(ctx); |
3790 | ctx->stack = old; | 3818 | ctx->stack = old; |
3791 | } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { | 3819 | } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { |
3792 | syntax_error_at(word->data); | 3820 | syntax_error_at(ctx->word.data); |
3793 | ctx->ctx_res_w = RES_SNTX; | 3821 | ctx->ctx_res_w = RES_SNTX; |
3794 | return r; | 3822 | return r; |
3795 | } else { | 3823 | } else { |
@@ -3802,8 +3830,8 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c | |||
3802 | 3830 | ||
3803 | ctx->ctx_res_w = r->res; | 3831 | ctx->ctx_res_w = r->res; |
3804 | ctx->old_flag = r->flag; | 3832 | ctx->old_flag = r->flag; |
3805 | word->o_assignment = r->assignment_flag; | 3833 | ctx->is_assignment = r->assignment_flag; |
3806 | debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); | 3834 | debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]); |
3807 | 3835 | ||
3808 | if (ctx->old_flag & FLAG_END) { | 3836 | if (ctx->old_flag & FLAG_END) { |
3809 | struct parse_context *old; | 3837 | struct parse_context *old; |
@@ -3849,12 +3877,12 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c | |||
3849 | * Normal return is 0. Syntax errors return 1. | 3877 | * Normal return is 0. Syntax errors return 1. |
3850 | * Note: on return, word is reset, but not o_free'd! | 3878 | * Note: on return, word is reset, but not o_free'd! |
3851 | */ | 3879 | */ |
3852 | static int done_word(o_string *word, struct parse_context *ctx) | 3880 | static int done_word(struct parse_context *ctx) |
3853 | { | 3881 | { |
3854 | struct command *command = ctx->command; | 3882 | struct command *command = ctx->command; |
3855 | 3883 | ||
3856 | debug_printf_parse("done_word entered: '%s' %p\n", word->data, command); | 3884 | debug_printf_parse("done_word entered: '%s' %p\n", ctx->word.data, command); |
3857 | if (word->length == 0 && !word->has_quoted_part) { | 3885 | if (ctx->word.length == 0 && !ctx->word.has_quoted_part) { |
3858 | debug_printf_parse("done_word return 0: true null, ignored\n"); | 3886 | debug_printf_parse("done_word return 0: true null, ignored\n"); |
3859 | return 0; | 3887 | return 0; |
3860 | } | 3888 | } |
@@ -3884,7 +3912,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3884 | // <<EOF$((1)) | 3912 | // <<EOF$((1)) |
3885 | // <<EOF`true` [this case also makes heredoc "quoted", a-la <<"EOF". Probably bash-4.3.43 bug] | 3913 | // <<EOF`true` [this case also makes heredoc "quoted", a-la <<"EOF". Probably bash-4.3.43 bug] |
3886 | 3914 | ||
3887 | ctx->pending_redirect->rd_filename = xstrdup(word->data); | 3915 | ctx->pending_redirect->rd_filename = xstrdup(ctx->word.data); |
3888 | /* Cater for >\file case: | 3916 | /* Cater for >\file case: |
3889 | * >\a creates file a; >\\a, >"\a", >"\\a" create file \a | 3917 | * >\a creates file a; >\\a, >"\a", >"\\a" create file \a |
3890 | * Same with heredocs: | 3918 | * Same with heredocs: |
@@ -3893,17 +3921,17 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3893 | if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) { | 3921 | if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) { |
3894 | unbackslash(ctx->pending_redirect->rd_filename); | 3922 | unbackslash(ctx->pending_redirect->rd_filename); |
3895 | /* Is it <<"HEREDOC"? */ | 3923 | /* Is it <<"HEREDOC"? */ |
3896 | if (word->has_quoted_part) { | 3924 | if (ctx->word.has_quoted_part) { |
3897 | ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED; | 3925 | ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED; |
3898 | } | 3926 | } |
3899 | } | 3927 | } |
3900 | debug_printf_parse("word stored in rd_filename: '%s'\n", word->data); | 3928 | debug_printf_parse("word stored in rd_filename: '%s'\n", ctx->word.data); |
3901 | ctx->pending_redirect = NULL; | 3929 | ctx->pending_redirect = NULL; |
3902 | } else { | 3930 | } else { |
3903 | #if HAS_KEYWORDS | 3931 | #if HAS_KEYWORDS |
3904 | # if ENABLE_HUSH_CASE | 3932 | # if ENABLE_HUSH_CASE |
3905 | if (ctx->ctx_dsemicolon | 3933 | if (ctx->ctx_dsemicolon |
3906 | && strcmp(word->data, "esac") != 0 /* not "... pattern) cmd;; esac" */ | 3934 | && strcmp(ctx->word.data, "esac") != 0 /* not "... pattern) cmd;; esac" */ |
3907 | ) { | 3935 | ) { |
3908 | /* already done when ctx_dsemicolon was set to 1: */ | 3936 | /* already done when ctx_dsemicolon was set to 1: */ |
3909 | /* ctx->ctx_res_w = RES_MATCH; */ | 3937 | /* ctx->ctx_res_w = RES_MATCH; */ |
@@ -3920,7 +3948,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3920 | # endif | 3948 | # endif |
3921 | ) { | 3949 | ) { |
3922 | const struct reserved_combo *reserved; | 3950 | const struct reserved_combo *reserved; |
3923 | reserved = reserved_word(word, ctx); | 3951 | reserved = reserved_word(ctx); |
3924 | debug_printf_parse("checking for reserved-ness: %d\n", !!reserved); | 3952 | debug_printf_parse("checking for reserved-ness: %d\n", !!reserved); |
3925 | if (reserved) { | 3953 | if (reserved) { |
3926 | # if ENABLE_HUSH_LINENO_VAR | 3954 | # if ENABLE_HUSH_LINENO_VAR |
@@ -3939,7 +3967,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3939 | done_pipe(ctx, PIPE_SEQ); | 3967 | done_pipe(ctx, PIPE_SEQ); |
3940 | } | 3968 | } |
3941 | # endif | 3969 | # endif |
3942 | o_reset_to_empty_unquoted(word); | 3970 | o_reset_to_empty_unquoted(&ctx->word); |
3943 | debug_printf_parse("done_word return %d\n", | 3971 | debug_printf_parse("done_word return %d\n", |
3944 | (ctx->ctx_res_w == RES_SNTX)); | 3972 | (ctx->ctx_res_w == RES_SNTX)); |
3945 | return (ctx->ctx_res_w == RES_SNTX); | 3973 | return (ctx->ctx_res_w == RES_SNTX); |
@@ -3947,7 +3975,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3947 | # if defined(CMD_SINGLEWORD_NOGLOB) | 3975 | # if defined(CMD_SINGLEWORD_NOGLOB) |
3948 | if (0 | 3976 | if (0 |
3949 | # if BASH_TEST2 | 3977 | # if BASH_TEST2 |
3950 | || strcmp(word->data, "[[") == 0 | 3978 | || strcmp(ctx->word.data, "[[") == 0 |
3951 | # endif | 3979 | # endif |
3952 | /* In bash, local/export/readonly are special, args | 3980 | /* In bash, local/export/readonly are special, args |
3953 | * are assignments and therefore expansion of them | 3981 | * are assignments and therefore expansion of them |
@@ -3964,9 +3992,9 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3964 | * $ "export" i=`echo 'aaa bbb'`; echo "$i" | 3992 | * $ "export" i=`echo 'aaa bbb'`; echo "$i" |
3965 | * aaa | 3993 | * aaa |
3966 | */ | 3994 | */ |
3967 | IF_HUSH_LOCAL( || strcmp(word->data, "local") == 0) | 3995 | IF_HUSH_LOCAL( || strcmp(ctx->word.data, "local") == 0) |
3968 | IF_HUSH_EXPORT( || strcmp(word->data, "export") == 0) | 3996 | IF_HUSH_EXPORT( || strcmp(ctx->word.data, "export") == 0) |
3969 | IF_HUSH_READONLY( || strcmp(word->data, "readonly") == 0) | 3997 | IF_HUSH_READONLY(|| strcmp(ctx->word.data, "readonly") == 0) |
3970 | ) { | 3998 | ) { |
3971 | command->cmd_type = CMD_SINGLEWORD_NOGLOB; | 3999 | command->cmd_type = CMD_SINGLEWORD_NOGLOB; |
3972 | } | 4000 | } |
@@ -3977,7 +4005,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3977 | 4005 | ||
3978 | if (command->group) { | 4006 | if (command->group) { |
3979 | /* "{ echo foo; } echo bar" - bad */ | 4007 | /* "{ echo foo; } echo bar" - bad */ |
3980 | syntax_error_at(word->data); | 4008 | syntax_error_at(ctx->word.data); |
3981 | debug_printf_parse("done_word return 1: syntax error, " | 4009 | debug_printf_parse("done_word return 1: syntax error, " |
3982 | "groups and arglists don't mix\n"); | 4010 | "groups and arglists don't mix\n"); |
3983 | return 1; | 4011 | return 1; |
@@ -3985,26 +4013,26 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
3985 | 4013 | ||
3986 | /* If this word wasn't an assignment, next ones definitely | 4014 | /* If this word wasn't an assignment, next ones definitely |
3987 | * can't be assignments. Even if they look like ones. */ | 4015 | * can't be assignments. Even if they look like ones. */ |
3988 | if (word->o_assignment != DEFINITELY_ASSIGNMENT | 4016 | if (ctx->is_assignment != DEFINITELY_ASSIGNMENT |
3989 | && word->o_assignment != WORD_IS_KEYWORD | 4017 | && ctx->is_assignment != WORD_IS_KEYWORD |
3990 | ) { | 4018 | ) { |
3991 | word->o_assignment = NOT_ASSIGNMENT; | 4019 | ctx->is_assignment = NOT_ASSIGNMENT; |
3992 | } else { | 4020 | } else { |
3993 | if (word->o_assignment == DEFINITELY_ASSIGNMENT) { | 4021 | if (ctx->is_assignment == DEFINITELY_ASSIGNMENT) { |
3994 | command->assignment_cnt++; | 4022 | command->assignment_cnt++; |
3995 | debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt); | 4023 | debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt); |
3996 | } | 4024 | } |
3997 | debug_printf_parse("word->o_assignment was:'%s'\n", assignment_flag[word->o_assignment]); | 4025 | debug_printf_parse("ctx->is_assignment was:'%s'\n", assignment_flag[ctx->is_assignment]); |
3998 | word->o_assignment = MAYBE_ASSIGNMENT; | 4026 | ctx->is_assignment = MAYBE_ASSIGNMENT; |
3999 | } | 4027 | } |
4000 | debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); | 4028 | debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]); |
4001 | command->argv = add_string_to_strings(command->argv, xstrdup(word->data)); | 4029 | command->argv = add_string_to_strings(command->argv, xstrdup(ctx->word.data)); |
4002 | debug_print_strings("word appended to argv", command->argv); | 4030 | debug_print_strings("word appended to argv", command->argv); |
4003 | } | 4031 | } |
4004 | 4032 | ||
4005 | #if ENABLE_HUSH_LOOPS | 4033 | #if ENABLE_HUSH_LOOPS |
4006 | if (ctx->ctx_res_w == RES_FOR) { | 4034 | if (ctx->ctx_res_w == RES_FOR) { |
4007 | if (word->has_quoted_part | 4035 | if (ctx->word.has_quoted_part |
4008 | || !is_well_formed_var_name(command->argv[0], '\0') | 4036 | || !is_well_formed_var_name(command->argv[0], '\0') |
4009 | ) { | 4037 | ) { |
4010 | /* bash says just "not a valid identifier" */ | 4038 | /* bash says just "not a valid identifier" */ |
@@ -4025,7 +4053,7 @@ static int done_word(o_string *word, struct parse_context *ctx) | |||
4025 | } | 4053 | } |
4026 | #endif | 4054 | #endif |
4027 | 4055 | ||
4028 | o_reset_to_empty_unquoted(word); | 4056 | o_reset_to_empty_unquoted(&ctx->word); |
4029 | 4057 | ||
4030 | debug_printf_parse("done_word return 0\n"); | 4058 | debug_printf_parse("done_word return 0\n"); |
4031 | return 0; | 4059 | return 0; |
@@ -4309,14 +4337,10 @@ static struct pipe *parse_stream(char **pstring, | |||
4309 | int end_trigger); | 4337 | int end_trigger); |
4310 | 4338 | ||
4311 | 4339 | ||
4312 | #if !ENABLE_HUSH_FUNCTIONS | 4340 | static int parse_group(struct parse_context *ctx, |
4313 | #define parse_group(dest, ctx, input, ch) \ | ||
4314 | parse_group(ctx, input, ch) | ||
4315 | #endif | ||
4316 | static int parse_group(o_string *dest, struct parse_context *ctx, | ||
4317 | struct in_str *input, int ch) | 4341 | struct in_str *input, int ch) |
4318 | { | 4342 | { |
4319 | /* dest contains characters seen prior to ( or {. | 4343 | /* ctx->word contains characters seen prior to ( or {. |
4320 | * Typically it's empty, but for function defs, | 4344 | * Typically it's empty, but for function defs, |
4321 | * it contains function name (without '()'). */ | 4345 | * it contains function name (without '()'). */ |
4322 | #if BB_MMU | 4346 | #if BB_MMU |
@@ -4330,9 +4354,9 @@ static int parse_group(o_string *dest, struct parse_context *ctx, | |||
4330 | 4354 | ||
4331 | debug_printf_parse("parse_group entered\n"); | 4355 | debug_printf_parse("parse_group entered\n"); |
4332 | #if ENABLE_HUSH_FUNCTIONS | 4356 | #if ENABLE_HUSH_FUNCTIONS |
4333 | if (ch == '(' && !dest->has_quoted_part) { | 4357 | if (ch == '(' && !ctx->word.has_quoted_part) { |
4334 | if (dest->length) | 4358 | if (ctx->word.length) |
4335 | if (done_word(dest, ctx)) | 4359 | if (done_word(ctx)) |
4336 | return 1; | 4360 | return 1; |
4337 | if (!command->argv) | 4361 | if (!command->argv) |
4338 | goto skip; /* (... */ | 4362 | goto skip; /* (... */ |
@@ -4364,8 +4388,8 @@ static int parse_group(o_string *dest, struct parse_context *ctx, | |||
4364 | 4388 | ||
4365 | #if 0 /* Prevented by caller */ | 4389 | #if 0 /* Prevented by caller */ |
4366 | if (command->argv /* word [word]{... */ | 4390 | if (command->argv /* word [word]{... */ |
4367 | || dest->length /* word{... */ | 4391 | || ctx->word.length /* word{... */ |
4368 | || dest->has_quoted_part /* ""{... */ | 4392 | || ctx->word.has_quoted_part /* ""{... */ |
4369 | ) { | 4393 | ) { |
4370 | syntax_error(NULL); | 4394 | syntax_error(NULL); |
4371 | debug_printf_parse("parse_group return 1: " | 4395 | debug_printf_parse("parse_group return 1: " |
@@ -4913,8 +4937,12 @@ static int encode_string(o_string *as_string, | |||
4913 | ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); | 4937 | ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); |
4914 | if (process_bkslash && ch == '\\') { | 4938 | if (process_bkslash && ch == '\\') { |
4915 | if (next == EOF) { | 4939 | if (next == EOF) { |
4916 | syntax_error("\\<eof>"); | 4940 | /* Testcase: in interactive shell a file with |
4917 | xfunc_die(); | 4941 | * echo "unterminated string\<eof> |
4942 | * is sourced. | ||
4943 | */ | ||
4944 | syntax_error_unterm_ch('"'); | ||
4945 | return 0; /* error */ | ||
4918 | } | 4946 | } |
4919 | /* bash: | 4947 | /* bash: |
4920 | * "The backslash retains its special meaning [in "..."] | 4948 | * "The backslash retains its special meaning [in "..."] |
@@ -4971,29 +4999,28 @@ static struct pipe *parse_stream(char **pstring, | |||
4971 | int end_trigger) | 4999 | int end_trigger) |
4972 | { | 5000 | { |
4973 | struct parse_context ctx; | 5001 | struct parse_context ctx; |
4974 | o_string dest = NULL_O_STRING; | ||
4975 | int heredoc_cnt; | 5002 | int heredoc_cnt; |
4976 | 5003 | ||
4977 | /* Single-quote triggers a bypass of the main loop until its mate is | 5004 | /* Single-quote triggers a bypass of the main loop until its mate is |
4978 | * found. When recursing, quote state is passed in via dest->o_expflags. | 5005 | * found. When recursing, quote state is passed in via ctx.word.o_expflags. |
4979 | */ | 5006 | */ |
4980 | debug_printf_parse("parse_stream entered, end_trigger='%c'\n", | 5007 | debug_printf_parse("parse_stream entered, end_trigger='%c'\n", |
4981 | end_trigger ? end_trigger : 'X'); | 5008 | end_trigger ? end_trigger : 'X'); |
4982 | debug_enter(); | 5009 | debug_enter(); |
4983 | 5010 | ||
4984 | /* If very first arg is "" or '', dest.data may end up NULL. | 5011 | initialize_context(&ctx); |
4985 | * Preventing this: */ | 5012 | |
4986 | o_addchr(&dest, '\0'); | 5013 | /* If very first arg is "" or '', ctx.word.data may end up NULL. |
4987 | dest.length = 0; | 5014 | * Preventing this: |
5015 | */ | ||
5016 | o_addchr(&ctx.word, '\0'); | ||
5017 | ctx.word.length = 0; | ||
4988 | 5018 | ||
4989 | /* We used to separate words on $IFS here. This was wrong. | 5019 | /* We used to separate words on $IFS here. This was wrong. |
4990 | * $IFS is used only for word splitting when $var is expanded, | 5020 | * $IFS is used only for word splitting when $var is expanded, |
4991 | * here we should use blank chars as separators, not $IFS | 5021 | * here we should use blank chars as separators, not $IFS |
4992 | */ | 5022 | */ |
4993 | 5023 | ||
4994 | if (MAYBE_ASSIGNMENT != 0) | ||
4995 | dest.o_assignment = MAYBE_ASSIGNMENT; | ||
4996 | initialize_context(&ctx); | ||
4997 | heredoc_cnt = 0; | 5024 | heredoc_cnt = 0; |
4998 | while (1) { | 5025 | while (1) { |
4999 | const char *is_blank; | 5026 | const char *is_blank; |
@@ -5005,7 +5032,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5005 | 5032 | ||
5006 | ch = i_getch(input); | 5033 | ch = i_getch(input); |
5007 | debug_printf_parse(": ch=%c (%d) escape=%d\n", | 5034 | debug_printf_parse(": ch=%c (%d) escape=%d\n", |
5008 | ch, ch, !!(dest.o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); | 5035 | ch, ch, !!(ctx.word.o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); |
5009 | if (ch == EOF) { | 5036 | if (ch == EOF) { |
5010 | struct pipe *pi; | 5037 | struct pipe *pi; |
5011 | 5038 | ||
@@ -5022,10 +5049,10 @@ static struct pipe *parse_stream(char **pstring, | |||
5022 | goto parse_error; | 5049 | goto parse_error; |
5023 | } | 5050 | } |
5024 | 5051 | ||
5025 | if (done_word(&dest, &ctx)) { | 5052 | if (done_word(&ctx)) { |
5026 | goto parse_error; | 5053 | goto parse_error; |
5027 | } | 5054 | } |
5028 | o_free(&dest); | 5055 | o_free(&ctx.word); |
5029 | done_pipe(&ctx, PIPE_SEQ); | 5056 | done_pipe(&ctx, PIPE_SEQ); |
5030 | pi = ctx.list_head; | 5057 | pi = ctx.list_head; |
5031 | /* If we got nothing... */ | 5058 | /* If we got nothing... */ |
@@ -5048,25 +5075,74 @@ static struct pipe *parse_stream(char **pstring, | |||
5048 | debug_printf_parse("parse_stream return %p\n", pi); | 5075 | debug_printf_parse("parse_stream return %p\n", pi); |
5049 | return pi; | 5076 | return pi; |
5050 | } | 5077 | } |
5051 | nommu_addchr(&ctx.as_string, ch); | ||
5052 | 5078 | ||
5053 | next = '\0'; | 5079 | /* Handle "'" and "\" first, as they won't play nice with |
5054 | if (ch != '\n') { | 5080 | * i_peek_and_eat_bkslash_nl() anyway: |
5055 | next = i_peek(input); | 5081 | * echo z\\ |
5056 | /* Can't use i_peek_and_eat_bkslash_nl(input) here: | 5082 | * and |
5057 | * echo '\ | 5083 | * echo '\ |
5058 | * ' | 5084 | * ' |
5059 | * will break. | 5085 | * would break. |
5086 | */ | ||
5087 | if (ch == '\\') { | ||
5088 | ch = i_getch(input); | ||
5089 | if (ch == '\n') | ||
5090 | continue; /* drop \<newline>, get next char */ | ||
5091 | nommu_addchr(&ctx.as_string, '\\'); | ||
5092 | o_addchr(&ctx.word, '\\'); | ||
5093 | if (ch == EOF) { | ||
5094 | /* Testcase: eval 'echo Ok\' */ | ||
5095 | /* bash-4.3.43 was removing backslash, | ||
5096 | * but 4.4.19 retains it, most other shells too | ||
5097 | */ | ||
5098 | continue; /* get next char */ | ||
5099 | } | ||
5100 | /* Example: echo Hello \2>file | ||
5101 | * we need to know that word 2 is quoted | ||
5060 | */ | 5102 | */ |
5103 | ctx.word.has_quoted_part = 1; | ||
5104 | nommu_addchr(&ctx.as_string, ch); | ||
5105 | o_addchr(&ctx.word, ch); | ||
5106 | continue; /* get next char */ | ||
5061 | } | 5107 | } |
5108 | nommu_addchr(&ctx.as_string, ch); | ||
5109 | if (ch == '\'') { | ||
5110 | ctx.word.has_quoted_part = 1; | ||
5111 | next = i_getch(input); | ||
5112 | if (next == '\'' && !ctx.pending_redirect) | ||
5113 | goto insert_empty_quoted_str_marker; | ||
5062 | 5114 | ||
5063 | is_special = "{}<>;&|()#'" /* special outside of "str" */ | 5115 | ch = next; |
5064 | "\\$\"" IF_HUSH_TICK("`") /* always special */ | 5116 | while (1) { |
5117 | if (ch == EOF) { | ||
5118 | syntax_error_unterm_ch('\''); | ||
5119 | goto parse_error; | ||
5120 | } | ||
5121 | nommu_addchr(&ctx.as_string, ch); | ||
5122 | if (ch == '\'') | ||
5123 | break; | ||
5124 | if (ch == SPECIAL_VAR_SYMBOL) { | ||
5125 | /* Convert raw ^C to corresponding special variable reference */ | ||
5126 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); | ||
5127 | o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); | ||
5128 | } | ||
5129 | o_addqchr(&ctx.word, ch); | ||
5130 | ch = i_getch(input); | ||
5131 | } | ||
5132 | continue; /* get next char */ | ||
5133 | } | ||
5134 | |||
5135 | next = '\0'; | ||
5136 | if (ch != '\n') | ||
5137 | next = i_peek_and_eat_bkslash_nl(input); | ||
5138 | |||
5139 | is_special = "{}<>;&|()#" /* special outside of "str" */ | ||
5140 | "$\"" IF_HUSH_TICK("`") /* always special */ | ||
5065 | SPECIAL_VAR_SYMBOL_STR; | 5141 | SPECIAL_VAR_SYMBOL_STR; |
5066 | /* Are { and } special here? */ | 5142 | /* Are { and } special here? */ |
5067 | if (ctx.command->argv /* word [word]{... - non-special */ | 5143 | if (ctx.command->argv /* word [word]{... - non-special */ |
5068 | || dest.length /* word{... - non-special */ | 5144 | || ctx.word.length /* word{... - non-special */ |
5069 | || dest.has_quoted_part /* ""{... - non-special */ | 5145 | || ctx.word.has_quoted_part /* ""{... - non-special */ |
5070 | || (next != ';' /* }; - special */ | 5146 | || (next != ';' /* }; - special */ |
5071 | && next != ')' /* }) - special */ | 5147 | && next != ')' /* }) - special */ |
5072 | && next != '(' /* {( - special */ | 5148 | && next != '(' /* {( - special */ |
@@ -5083,14 +5159,14 @@ static struct pipe *parse_stream(char **pstring, | |||
5083 | 5159 | ||
5084 | if (!is_special && !is_blank) { /* ordinary char */ | 5160 | if (!is_special && !is_blank) { /* ordinary char */ |
5085 | ordinary_char: | 5161 | ordinary_char: |
5086 | o_addQchr(&dest, ch); | 5162 | o_addQchr(&ctx.word, ch); |
5087 | if ((dest.o_assignment == MAYBE_ASSIGNMENT | 5163 | if ((ctx.is_assignment == MAYBE_ASSIGNMENT |
5088 | || dest.o_assignment == WORD_IS_KEYWORD) | 5164 | || ctx.is_assignment == WORD_IS_KEYWORD) |
5089 | && ch == '=' | 5165 | && ch == '=' |
5090 | && is_well_formed_var_name(dest.data, '=') | 5166 | && is_well_formed_var_name(ctx.word.data, '=') |
5091 | ) { | 5167 | ) { |
5092 | dest.o_assignment = DEFINITELY_ASSIGNMENT; | 5168 | ctx.is_assignment = DEFINITELY_ASSIGNMENT; |
5093 | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); | 5169 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); |
5094 | } | 5170 | } |
5095 | continue; | 5171 | continue; |
5096 | } | 5172 | } |
@@ -5112,7 +5188,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5112 | } | 5188 | } |
5113 | /* ch == last eaten whitespace char */ | 5189 | /* ch == last eaten whitespace char */ |
5114 | #endif | 5190 | #endif |
5115 | if (done_word(&dest, &ctx)) { | 5191 | if (done_word(&ctx)) { |
5116 | goto parse_error; | 5192 | goto parse_error; |
5117 | } | 5193 | } |
5118 | if (ch == '\n') { | 5194 | if (ch == '\n') { |
@@ -5122,7 +5198,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5122 | * "case ... in <newline> word) ..." | 5198 | * "case ... in <newline> word) ..." |
5123 | */ | 5199 | */ |
5124 | if (IS_NULL_CMD(ctx.command) | 5200 | if (IS_NULL_CMD(ctx.command) |
5125 | && dest.length == 0 && !dest.has_quoted_part | 5201 | && ctx.word.length == 0 && !ctx.word.has_quoted_part |
5126 | ) { | 5202 | ) { |
5127 | /* This newline can be ignored. But... | 5203 | /* This newline can be ignored. But... |
5128 | * Without check #1, interactive shell | 5204 | * Without check #1, interactive shell |
@@ -5157,8 +5233,8 @@ static struct pipe *parse_stream(char **pstring, | |||
5157 | } | 5233 | } |
5158 | heredoc_cnt = 0; | 5234 | heredoc_cnt = 0; |
5159 | } | 5235 | } |
5160 | dest.o_assignment = MAYBE_ASSIGNMENT; | 5236 | ctx.is_assignment = MAYBE_ASSIGNMENT; |
5161 | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); | 5237 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); |
5162 | ch = ';'; | 5238 | ch = ';'; |
5163 | /* note: if (is_blank) continue; | 5239 | /* note: if (is_blank) continue; |
5164 | * will still trigger for us */ | 5240 | * will still trigger for us */ |
@@ -5170,8 +5246,8 @@ static struct pipe *parse_stream(char **pstring, | |||
5170 | * Pathological example: { ""}; } should exec "}" cmd | 5246 | * Pathological example: { ""}; } should exec "}" cmd |
5171 | */ | 5247 | */ |
5172 | if (ch == '}') { | 5248 | if (ch == '}') { |
5173 | if (dest.length != 0 /* word} */ | 5249 | if (ctx.word.length != 0 /* word} */ |
5174 | || dest.has_quoted_part /* ""} */ | 5250 | || ctx.word.has_quoted_part /* ""} */ |
5175 | ) { | 5251 | ) { |
5176 | goto ordinary_char; | 5252 | goto ordinary_char; |
5177 | } | 5253 | } |
@@ -5200,7 +5276,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5200 | #if ENABLE_HUSH_CASE | 5276 | #if ENABLE_HUSH_CASE |
5201 | && (ch != ')' | 5277 | && (ch != ')' |
5202 | || ctx.ctx_res_w != RES_MATCH | 5278 | || ctx.ctx_res_w != RES_MATCH |
5203 | || (!dest.has_quoted_part && strcmp(dest.data, "esac") == 0) | 5279 | || (!ctx.word.has_quoted_part && strcmp(ctx.word.data, "esac") == 0) |
5204 | ) | 5280 | ) |
5205 | #endif | 5281 | #endif |
5206 | ) { | 5282 | ) { |
@@ -5217,17 +5293,17 @@ static struct pipe *parse_stream(char **pstring, | |||
5217 | syntax_error_unterm_str("here document"); | 5293 | syntax_error_unterm_str("here document"); |
5218 | goto parse_error; | 5294 | goto parse_error; |
5219 | } | 5295 | } |
5220 | if (done_word(&dest, &ctx)) { | 5296 | if (done_word(&ctx)) { |
5221 | goto parse_error; | 5297 | goto parse_error; |
5222 | } | 5298 | } |
5223 | done_pipe(&ctx, PIPE_SEQ); | 5299 | done_pipe(&ctx, PIPE_SEQ); |
5224 | dest.o_assignment = MAYBE_ASSIGNMENT; | 5300 | ctx.is_assignment = MAYBE_ASSIGNMENT; |
5225 | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); | 5301 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); |
5226 | /* Do we sit outside of any if's, loops or case's? */ | 5302 | /* Do we sit outside of any if's, loops or case's? */ |
5227 | if (!HAS_KEYWORDS | 5303 | if (!HAS_KEYWORDS |
5228 | IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0)) | 5304 | IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0)) |
5229 | ) { | 5305 | ) { |
5230 | o_free(&dest); | 5306 | o_free(&ctx.word); |
5231 | #if !BB_MMU | 5307 | #if !BB_MMU |
5232 | debug_printf_parse("as_string2 '%s'\n", ctx.as_string.data); | 5308 | debug_printf_parse("as_string2 '%s'\n", ctx.as_string.data); |
5233 | if (pstring) | 5309 | if (pstring) |
@@ -5248,7 +5324,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5248 | return ctx.list_head; | 5324 | return ctx.list_head; |
5249 | } | 5325 | } |
5250 | } | 5326 | } |
5251 | skip_end_trigger: | 5327 | |
5252 | if (is_blank) | 5328 | if (is_blank) |
5253 | continue; | 5329 | continue; |
5254 | 5330 | ||
@@ -5256,13 +5332,11 @@ static struct pipe *parse_stream(char **pstring, | |||
5256 | * an assignment. a=1 2>z b=2: b=2 is still assignment */ | 5332 | * an assignment. a=1 2>z b=2: b=2 is still assignment */ |
5257 | switch (ch) { | 5333 | switch (ch) { |
5258 | case '>': | 5334 | case '>': |
5259 | redir_fd = redirect_opt_num(&dest); | 5335 | redir_fd = redirect_opt_num(&ctx.word); |
5260 | if (done_word(&dest, &ctx)) { | 5336 | if (done_word(&ctx)) { |
5261 | goto parse_error; | 5337 | goto parse_error; |
5262 | } | 5338 | } |
5263 | redir_style = REDIRECT_OVERWRITE; | 5339 | redir_style = REDIRECT_OVERWRITE; |
5264 | if (next == '\\') | ||
5265 | next = i_peek_and_eat_bkslash_nl(input); | ||
5266 | if (next == '>') { | 5340 | if (next == '>') { |
5267 | redir_style = REDIRECT_APPEND; | 5341 | redir_style = REDIRECT_APPEND; |
5268 | ch = i_getch(input); | 5342 | ch = i_getch(input); |
@@ -5276,15 +5350,13 @@ static struct pipe *parse_stream(char **pstring, | |||
5276 | #endif | 5350 | #endif |
5277 | if (parse_redirect(&ctx, redir_fd, redir_style, input)) | 5351 | if (parse_redirect(&ctx, redir_fd, redir_style, input)) |
5278 | goto parse_error; | 5352 | goto parse_error; |
5279 | continue; /* back to top of while (1) */ | 5353 | continue; /* get next char */ |
5280 | case '<': | 5354 | case '<': |
5281 | redir_fd = redirect_opt_num(&dest); | 5355 | redir_fd = redirect_opt_num(&ctx.word); |
5282 | if (done_word(&dest, &ctx)) { | 5356 | if (done_word(&ctx)) { |
5283 | goto parse_error; | 5357 | goto parse_error; |
5284 | } | 5358 | } |
5285 | redir_style = REDIRECT_INPUT; | 5359 | redir_style = REDIRECT_INPUT; |
5286 | if (next == '\\') | ||
5287 | next = i_peek_and_eat_bkslash_nl(input); | ||
5288 | if (next == '<') { | 5360 | if (next == '<') { |
5289 | redir_style = REDIRECT_HEREDOC; | 5361 | redir_style = REDIRECT_HEREDOC; |
5290 | heredoc_cnt++; | 5362 | heredoc_cnt++; |
@@ -5304,9 +5376,9 @@ static struct pipe *parse_stream(char **pstring, | |||
5304 | #endif | 5376 | #endif |
5305 | if (parse_redirect(&ctx, redir_fd, redir_style, input)) | 5377 | if (parse_redirect(&ctx, redir_fd, redir_style, input)) |
5306 | goto parse_error; | 5378 | goto parse_error; |
5307 | continue; /* back to top of while (1) */ | 5379 | continue; /* get next char */ |
5308 | case '#': | 5380 | case '#': |
5309 | if (dest.length == 0 && !dest.has_quoted_part) { | 5381 | if (ctx.word.length == 0 && !ctx.word.has_quoted_part) { |
5310 | /* skip "#comment" */ | 5382 | /* skip "#comment" */ |
5311 | /* note: we do not add it to &ctx.as_string */ | 5383 | /* note: we do not add it to &ctx.as_string */ |
5312 | /* TODO: in bash: | 5384 | /* TODO: in bash: |
@@ -5325,30 +5397,20 @@ static struct pipe *parse_stream(char **pstring, | |||
5325 | if (ch == EOF) | 5397 | if (ch == EOF) |
5326 | break; | 5398 | break; |
5327 | } | 5399 | } |
5328 | continue; /* back to top of while (1) */ | 5400 | continue; /* get next char */ |
5329 | } | ||
5330 | break; | ||
5331 | case '\\': | ||
5332 | if (next == '\n') { | ||
5333 | /* It's "\<newline>" */ | ||
5334 | #if !BB_MMU | ||
5335 | /* Remove trailing '\' from ctx.as_string */ | ||
5336 | ctx.as_string.data[--ctx.as_string.length] = '\0'; | ||
5337 | #endif | ||
5338 | ch = i_getch(input); /* eat it */ | ||
5339 | continue; /* back to top of while (1) */ | ||
5340 | } | 5401 | } |
5341 | break; | 5402 | break; |
5342 | } | 5403 | } |
5404 | skip_end_trigger: | ||
5343 | 5405 | ||
5344 | if (dest.o_assignment == MAYBE_ASSIGNMENT | 5406 | if (ctx.is_assignment == MAYBE_ASSIGNMENT |
5345 | /* check that we are not in word in "a=1 2>word b=1": */ | 5407 | /* check that we are not in word in "a=1 2>word b=1": */ |
5346 | && !ctx.pending_redirect | 5408 | && !ctx.pending_redirect |
5347 | ) { | 5409 | ) { |
5348 | /* ch is a special char and thus this word | 5410 | /* ch is a special char and thus this word |
5349 | * cannot be an assignment */ | 5411 | * cannot be an assignment */ |
5350 | dest.o_assignment = NOT_ASSIGNMENT; | 5412 | ctx.is_assignment = NOT_ASSIGNMENT; |
5351 | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); | 5413 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); |
5352 | } | 5414 | } |
5353 | 5415 | ||
5354 | /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ | 5416 | /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ |
@@ -5356,95 +5418,59 @@ static struct pipe *parse_stream(char **pstring, | |||
5356 | switch (ch) { | 5418 | switch (ch) { |
5357 | case SPECIAL_VAR_SYMBOL: | 5419 | case SPECIAL_VAR_SYMBOL: |
5358 | /* Convert raw ^C to corresponding special variable reference */ | 5420 | /* Convert raw ^C to corresponding special variable reference */ |
5359 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | 5421 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); |
5360 | o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS); | 5422 | o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); |
5361 | /* fall through */ | 5423 | /* fall through */ |
5362 | case '#': | 5424 | case '#': |
5363 | /* non-comment #: "echo a#b" etc */ | 5425 | /* non-comment #: "echo a#b" etc */ |
5364 | o_addchr(&dest, ch); | 5426 | o_addchr(&ctx.word, ch); |
5365 | break; | 5427 | continue; /* get next char */ |
5366 | case '\\': | ||
5367 | if (next == EOF) { | ||
5368 | syntax_error("\\<eof>"); | ||
5369 | xfunc_die(); | ||
5370 | } | ||
5371 | ch = i_getch(input); | ||
5372 | /* note: ch != '\n' (that case does not reach this place) */ | ||
5373 | o_addchr(&dest, '\\'); | ||
5374 | /*nommu_addchr(&ctx.as_string, '\\'); - already done */ | ||
5375 | o_addchr(&dest, ch); | ||
5376 | nommu_addchr(&ctx.as_string, ch); | ||
5377 | /* Example: echo Hello \2>file | ||
5378 | * we need to know that word 2 is quoted */ | ||
5379 | dest.has_quoted_part = 1; | ||
5380 | break; | ||
5381 | case '$': | 5428 | case '$': |
5382 | if (!parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0)) { | 5429 | if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) { |
5383 | debug_printf_parse("parse_stream parse error: " | 5430 | debug_printf_parse("parse_stream parse error: " |
5384 | "parse_dollar returned 0 (error)\n"); | 5431 | "parse_dollar returned 0 (error)\n"); |
5385 | goto parse_error; | 5432 | goto parse_error; |
5386 | } | 5433 | } |
5387 | break; | 5434 | continue; /* get next char */ |
5388 | case '\'': | 5435 | case '"': |
5389 | dest.has_quoted_part = 1; | 5436 | ctx.word.has_quoted_part = 1; |
5390 | if (next == '\'' && !ctx.pending_redirect) { | 5437 | if (next == '"' && !ctx.pending_redirect) { |
5438 | i_getch(input); /* eat second " */ | ||
5391 | insert_empty_quoted_str_marker: | 5439 | insert_empty_quoted_str_marker: |
5392 | nommu_addchr(&ctx.as_string, next); | 5440 | nommu_addchr(&ctx.as_string, next); |
5393 | i_getch(input); /* eat second ' */ | 5441 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); |
5394 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | 5442 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); |
5395 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | 5443 | continue; /* get next char */ |
5396 | } else { | ||
5397 | while (1) { | ||
5398 | ch = i_getch(input); | ||
5399 | if (ch == EOF) { | ||
5400 | syntax_error_unterm_ch('\''); | ||
5401 | goto parse_error; | ||
5402 | } | ||
5403 | nommu_addchr(&ctx.as_string, ch); | ||
5404 | if (ch == '\'') | ||
5405 | break; | ||
5406 | if (ch == SPECIAL_VAR_SYMBOL) { | ||
5407 | /* Convert raw ^C to corresponding special variable reference */ | ||
5408 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | ||
5409 | o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS); | ||
5410 | } | ||
5411 | o_addqchr(&dest, ch); | ||
5412 | } | ||
5413 | } | 5444 | } |
5414 | break; | 5445 | if (ctx.is_assignment == NOT_ASSIGNMENT) |
5415 | case '"': | 5446 | ctx.word.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; |
5416 | dest.has_quoted_part = 1; | 5447 | if (!encode_string(&ctx.as_string, &ctx.word, input, '"', /*process_bkslash:*/ 1)) |
5417 | if (next == '"' && !ctx.pending_redirect) | ||
5418 | goto insert_empty_quoted_str_marker; | ||
5419 | if (dest.o_assignment == NOT_ASSIGNMENT) | ||
5420 | dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; | ||
5421 | if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1)) | ||
5422 | goto parse_error; | 5448 | goto parse_error; |
5423 | dest.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; | 5449 | ctx.word.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; |
5424 | break; | 5450 | continue; /* get next char */ |
5425 | #if ENABLE_HUSH_TICK | 5451 | #if ENABLE_HUSH_TICK |
5426 | case '`': { | 5452 | case '`': { |
5427 | USE_FOR_NOMMU(unsigned pos;) | 5453 | USE_FOR_NOMMU(unsigned pos;) |
5428 | 5454 | ||
5429 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | 5455 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); |
5430 | o_addchr(&dest, '`'); | 5456 | o_addchr(&ctx.word, '`'); |
5431 | USE_FOR_NOMMU(pos = dest.length;) | 5457 | USE_FOR_NOMMU(pos = ctx.word.length;) |
5432 | if (!add_till_backquote(&dest, input, /*in_dquote:*/ 0)) | 5458 | if (!add_till_backquote(&ctx.word, input, /*in_dquote:*/ 0)) |
5433 | goto parse_error; | 5459 | goto parse_error; |
5434 | # if !BB_MMU | 5460 | # if !BB_MMU |
5435 | o_addstr(&ctx.as_string, dest.data + pos); | 5461 | o_addstr(&ctx.as_string, ctx.word.data + pos); |
5436 | o_addchr(&ctx.as_string, '`'); | 5462 | o_addchr(&ctx.as_string, '`'); |
5437 | # endif | 5463 | # endif |
5438 | o_addchr(&dest, SPECIAL_VAR_SYMBOL); | 5464 | o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); |
5439 | //debug_printf_subst("SUBST RES3 '%s'\n", dest.data + pos); | 5465 | //debug_printf_subst("SUBST RES3 '%s'\n", ctx.word.data + pos); |
5440 | break; | 5466 | continue; /* get next char */ |
5441 | } | 5467 | } |
5442 | #endif | 5468 | #endif |
5443 | case ';': | 5469 | case ';': |
5444 | #if ENABLE_HUSH_CASE | 5470 | #if ENABLE_HUSH_CASE |
5445 | case_semi: | 5471 | case_semi: |
5446 | #endif | 5472 | #endif |
5447 | if (done_word(&dest, &ctx)) { | 5473 | if (done_word(&ctx)) { |
5448 | goto parse_error; | 5474 | goto parse_error; |
5449 | } | 5475 | } |
5450 | done_pipe(&ctx, PIPE_SEQ); | 5476 | done_pipe(&ctx, PIPE_SEQ); |
@@ -5467,15 +5493,13 @@ static struct pipe *parse_stream(char **pstring, | |||
5467 | new_cmd: | 5493 | new_cmd: |
5468 | /* We just finished a cmd. New one may start | 5494 | /* We just finished a cmd. New one may start |
5469 | * with an assignment */ | 5495 | * with an assignment */ |
5470 | dest.o_assignment = MAYBE_ASSIGNMENT; | 5496 | ctx.is_assignment = MAYBE_ASSIGNMENT; |
5471 | debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); | 5497 | debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); |
5472 | break; | 5498 | continue; /* get next char */ |
5473 | case '&': | 5499 | case '&': |
5474 | if (done_word(&dest, &ctx)) { | 5500 | if (done_word(&ctx)) { |
5475 | goto parse_error; | 5501 | goto parse_error; |
5476 | } | 5502 | } |
5477 | if (next == '\\') | ||
5478 | next = i_peek_and_eat_bkslash_nl(input); | ||
5479 | if (next == '&') { | 5503 | if (next == '&') { |
5480 | ch = i_getch(input); | 5504 | ch = i_getch(input); |
5481 | nommu_addchr(&ctx.as_string, ch); | 5505 | nommu_addchr(&ctx.as_string, ch); |
@@ -5485,15 +5509,13 @@ static struct pipe *parse_stream(char **pstring, | |||
5485 | } | 5509 | } |
5486 | goto new_cmd; | 5510 | goto new_cmd; |
5487 | case '|': | 5511 | case '|': |
5488 | if (done_word(&dest, &ctx)) { | 5512 | if (done_word(&ctx)) { |
5489 | goto parse_error; | 5513 | goto parse_error; |
5490 | } | 5514 | } |
5491 | #if ENABLE_HUSH_CASE | 5515 | #if ENABLE_HUSH_CASE |
5492 | if (ctx.ctx_res_w == RES_MATCH) | 5516 | if (ctx.ctx_res_w == RES_MATCH) |
5493 | break; /* we are in case's "word | word)" */ | 5517 | break; /* we are in case's "word | word)" */ |
5494 | #endif | 5518 | #endif |
5495 | if (next == '\\') | ||
5496 | next = i_peek_and_eat_bkslash_nl(input); | ||
5497 | if (next == '|') { /* || */ | 5519 | if (next == '|') { /* || */ |
5498 | ch = i_getch(input); | 5520 | ch = i_getch(input); |
5499 | nommu_addchr(&ctx.as_string, ch); | 5521 | nommu_addchr(&ctx.as_string, ch); |
@@ -5510,14 +5532,14 @@ static struct pipe *parse_stream(char **pstring, | |||
5510 | /* "case... in [(]word)..." - skip '(' */ | 5532 | /* "case... in [(]word)..." - skip '(' */ |
5511 | if (ctx.ctx_res_w == RES_MATCH | 5533 | if (ctx.ctx_res_w == RES_MATCH |
5512 | && ctx.command->argv == NULL /* not (word|(... */ | 5534 | && ctx.command->argv == NULL /* not (word|(... */ |
5513 | && dest.length == 0 /* not word(... */ | 5535 | && ctx.word.length == 0 /* not word(... */ |
5514 | && dest.has_quoted_part == 0 /* not ""(... */ | 5536 | && ctx.word.has_quoted_part == 0 /* not ""(... */ |
5515 | ) { | 5537 | ) { |
5516 | continue; | 5538 | continue; /* get next char */ |
5517 | } | 5539 | } |
5518 | #endif | 5540 | #endif |
5519 | case '{': | 5541 | case '{': |
5520 | if (parse_group(&dest, &ctx, input, ch) != 0) { | 5542 | if (parse_group(&ctx, input, ch) != 0) { |
5521 | goto parse_error; | 5543 | goto parse_error; |
5522 | } | 5544 | } |
5523 | goto new_cmd; | 5545 | goto new_cmd; |
@@ -5574,7 +5596,7 @@ static struct pipe *parse_stream(char **pstring, | |||
5574 | IF_HAS_KEYWORDS(pctx = p2;) | 5596 | IF_HAS_KEYWORDS(pctx = p2;) |
5575 | } while (HAS_KEYWORDS && pctx); | 5597 | } while (HAS_KEYWORDS && pctx); |
5576 | 5598 | ||
5577 | o_free(&dest); | 5599 | o_free(&ctx.word); |
5578 | #if !BB_MMU | 5600 | #if !BB_MMU |
5579 | if (pstring) | 5601 | if (pstring) |
5580 | *pstring = NULL; | 5602 | *pstring = NULL; |
@@ -5589,11 +5611,10 @@ static struct pipe *parse_stream(char **pstring, | |||
5589 | 5611 | ||
5590 | /* Expansion can recurse, need forward decls: */ | 5612 | /* Expansion can recurse, need forward decls: */ |
5591 | #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE | 5613 | #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE |
5592 | /* only ${var/pattern/repl} (its pattern part) needs additional mode */ | 5614 | #define expand_string_to_string(str, EXP_flags, do_unbackslash) \ |
5593 | #define expand_string_to_string(str, do_unbackslash) \ | ||
5594 | expand_string_to_string(str) | 5615 | expand_string_to_string(str) |
5595 | #endif | 5616 | #endif |
5596 | static char *expand_string_to_string(const char *str, int do_unbackslash); | 5617 | static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash); |
5597 | #if ENABLE_HUSH_TICK | 5618 | #if ENABLE_HUSH_TICK |
5598 | static int process_command_subs(o_string *dest, const char *s); | 5619 | static int process_command_subs(o_string *dest, const char *s); |
5599 | #endif | 5620 | #endif |
@@ -5676,10 +5697,20 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha | |||
5676 | 5697 | ||
5677 | /* We know str here points to at least one IFS char */ | 5698 | /* We know str here points to at least one IFS char */ |
5678 | last_is_ifs = 1; | 5699 | last_is_ifs = 1; |
5679 | str += strspn(str, G.ifs); /* skip IFS chars */ | 5700 | str += strspn(str, G.ifs_whitespace); /* skip IFS whitespace chars */ |
5680 | if (!*str) /* EOL - do not finalize word */ | 5701 | if (!*str) /* EOL - do not finalize word */ |
5681 | break; | 5702 | break; |
5682 | 5703 | ||
5704 | if (G.ifs_whitespace != G.ifs /* usually false ($IFS is usually all whitespace), */ | ||
5705 | && strchr(G.ifs, *str) /* the second check would fail */ | ||
5706 | ) { | ||
5707 | /* This is a non-whitespace $IFS char */ | ||
5708 | /* Skip it and IFS whitespace chars, start new word */ | ||
5709 | str++; | ||
5710 | str += strspn(str, G.ifs_whitespace); | ||
5711 | goto new_word; | ||
5712 | } | ||
5713 | |||
5683 | /* Start new word... but not always! */ | 5714 | /* Start new word... but not always! */ |
5684 | /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */ | 5715 | /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */ |
5685 | if (output->has_quoted_part | 5716 | if (output->has_quoted_part |
@@ -5690,6 +5721,7 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha | |||
5690 | */ | 5721 | */ |
5691 | || (n > 0 && output->data[output->length - 1]) | 5722 | || (n > 0 && output->data[output->length - 1]) |
5692 | ) { | 5723 | ) { |
5724 | new_word: | ||
5693 | o_addchr(output, '\0'); | 5725 | o_addchr(output, '\0'); |
5694 | debug_print_list("expand_on_ifs", output, n); | 5726 | debug_print_list("expand_on_ifs", output, n); |
5695 | n = o_save_ptr(output, n); | 5727 | n = o_save_ptr(output, n); |
@@ -5739,7 +5771,10 @@ static char *encode_then_expand_string(const char *str, int process_bkslash, int | |||
5739 | encode_string(NULL, &dest, &input, EOF, process_bkslash); | 5771 | encode_string(NULL, &dest, &input, EOF, process_bkslash); |
5740 | //TODO: error check (encode_string returns 0 on error)? | 5772 | //TODO: error check (encode_string returns 0 on error)? |
5741 | //bb_error_msg("'%s' -> '%s'", str, dest.data); | 5773 | //bb_error_msg("'%s' -> '%s'", str, dest.data); |
5742 | exp_str = expand_string_to_string(dest.data, /*unbackslash:*/ do_unbackslash); | 5774 | exp_str = expand_string_to_string(dest.data, |
5775 | do_unbackslash ? EXP_FLAG_ESC_GLOB_CHARS : 0, | ||
5776 | do_unbackslash | ||
5777 | ); | ||
5743 | //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); | 5778 | //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); |
5744 | o_free_unsafe(&dest); | 5779 | o_free_unsafe(&dest); |
5745 | return exp_str; | 5780 | return exp_str; |
@@ -6372,10 +6407,11 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv) | |||
6372 | * NB: should NOT do globbing! | 6407 | * NB: should NOT do globbing! |
6373 | * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" | 6408 | * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" |
6374 | */ | 6409 | */ |
6375 | static char *expand_string_to_string(const char *str, int do_unbackslash) | 6410 | static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash) |
6376 | { | 6411 | { |
6377 | #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE | 6412 | #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE |
6378 | const int do_unbackslash = 1; | 6413 | const int do_unbackslash = 1; |
6414 | const int EXP_flags = EXP_FLAG_ESC_GLOB_CHARS; | ||
6379 | #endif | 6415 | #endif |
6380 | char *argv[2], **list; | 6416 | char *argv[2], **list; |
6381 | 6417 | ||
@@ -6392,10 +6428,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash) | |||
6392 | 6428 | ||
6393 | argv[0] = (char*)str; | 6429 | argv[0] = (char*)str; |
6394 | argv[1] = NULL; | 6430 | argv[1] = NULL; |
6395 | list = expand_variables(argv, do_unbackslash | 6431 | list = expand_variables(argv, EXP_flags | EXP_FLAG_SINGLEWORD); |
6396 | ? EXP_FLAG_ESC_GLOB_CHARS | EXP_FLAG_SINGLEWORD | ||
6397 | : EXP_FLAG_SINGLEWORD | ||
6398 | ); | ||
6399 | if (HUSH_DEBUG) | 6432 | if (HUSH_DEBUG) |
6400 | if (!list[0] || list[1]) | 6433 | if (!list[0] || list[1]) |
6401 | bb_error_msg_and_die("BUG in varexp2"); | 6434 | bb_error_msg_and_die("BUG in varexp2"); |
@@ -6439,7 +6472,13 @@ static char **expand_assignments(char **argv, int count) | |||
6439 | G.expanded_assignments = p = NULL; | 6472 | G.expanded_assignments = p = NULL; |
6440 | /* Expand assignments into one string each */ | 6473 | /* Expand assignments into one string each */ |
6441 | for (i = 0; i < count; i++) { | 6474 | for (i = 0; i < count; i++) { |
6442 | G.expanded_assignments = p = add_string_to_strings(p, expand_string_to_string(argv[i], /*unbackslash:*/ 1)); | 6475 | p = add_string_to_strings(p, |
6476 | expand_string_to_string(argv[i], | ||
6477 | EXP_FLAG_ESC_GLOB_CHARS, | ||
6478 | /*unbackslash:*/ 1 | ||
6479 | ) | ||
6480 | ); | ||
6481 | G.expanded_assignments = p; | ||
6443 | } | 6482 | } |
6444 | G.expanded_assignments = NULL; | 6483 | G.expanded_assignments = NULL; |
6445 | return p; | 6484 | return p; |
@@ -7151,7 +7190,8 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) | |||
7151 | continue; | 7190 | continue; |
7152 | } | 7191 | } |
7153 | mode = redir_table[redir->rd_type].mode; | 7192 | mode = redir_table[redir->rd_type].mode; |
7154 | p = expand_string_to_string(redir->rd_filename, /*unbackslash:*/ 1); | 7193 | p = expand_string_to_string(redir->rd_filename, |
7194 | EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1); | ||
7155 | newfd = open_or_warn(p, mode); | 7195 | newfd = open_or_warn(p, mode); |
7156 | free(p); | 7196 | free(p); |
7157 | if (newfd < 0) { | 7197 | if (newfd < 0) { |
@@ -7358,6 +7398,58 @@ static void unset_func(const char *name) | |||
7358 | } | 7398 | } |
7359 | # endif | 7399 | # endif |
7360 | 7400 | ||
7401 | static void remove_nested_vars(void) | ||
7402 | { | ||
7403 | struct variable *cur; | ||
7404 | struct variable **cur_pp; | ||
7405 | |||
7406 | cur_pp = &G.top_var; | ||
7407 | while ((cur = *cur_pp) != NULL) { | ||
7408 | if (cur->var_nest_level <= G.var_nest_level) { | ||
7409 | cur_pp = &cur->next; | ||
7410 | continue; | ||
7411 | } | ||
7412 | /* Unexport */ | ||
7413 | if (cur->flg_export) { | ||
7414 | debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); | ||
7415 | bb_unsetenv(cur->varstr); | ||
7416 | } | ||
7417 | /* Remove from global list */ | ||
7418 | *cur_pp = cur->next; | ||
7419 | /* Free */ | ||
7420 | if (!cur->max_len) { | ||
7421 | debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); | ||
7422 | free(cur->varstr); | ||
7423 | } | ||
7424 | free(cur); | ||
7425 | } | ||
7426 | } | ||
7427 | |||
7428 | static void enter_var_nest_level(void) | ||
7429 | { | ||
7430 | G.var_nest_level++; | ||
7431 | debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); | ||
7432 | |||
7433 | /* Try: f() { echo -n .; f; }; f | ||
7434 | * struct variable::var_nest_level is uint16_t, | ||
7435 | * thus limiting recursion to < 2^16. | ||
7436 | * In any case, with 8 Mbyte stack SEGV happens | ||
7437 | * not too long after 2^16 recursions anyway. | ||
7438 | */ | ||
7439 | if (G.var_nest_level > 0xff00) | ||
7440 | bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); | ||
7441 | } | ||
7442 | |||
7443 | static void leave_var_nest_level(void) | ||
7444 | { | ||
7445 | G.var_nest_level--; | ||
7446 | debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); | ||
7447 | if (HUSH_DEBUG && (int)G.var_nest_level < 0) | ||
7448 | bb_error_msg_and_die("BUG: nesting underflow"); | ||
7449 | |||
7450 | remove_nested_vars(); | ||
7451 | } | ||
7452 | |||
7361 | # if BB_MMU | 7453 | # if BB_MMU |
7362 | #define exec_function(to_free, funcp, argv) \ | 7454 | #define exec_function(to_free, funcp, argv) \ |
7363 | exec_function(funcp, argv) | 7455 | exec_function(funcp, argv) |
@@ -7392,7 +7484,7 @@ static void exec_function(char ***to_free, | |||
7392 | 7484 | ||
7393 | /* "we are in a function, ok to use return" */ | 7485 | /* "we are in a function, ok to use return" */ |
7394 | G_flag_return_in_progress = -1; | 7486 | G_flag_return_in_progress = -1; |
7395 | G.var_nest_level++; | 7487 | enter_var_nest_level(); |
7396 | IF_HUSH_LOCAL(G.func_nest_level++;) | 7488 | IF_HUSH_LOCAL(G.func_nest_level++;) |
7397 | 7489 | ||
7398 | /* On MMU, funcp->body is always non-NULL */ | 7490 | /* On MMU, funcp->body is always non-NULL */ |
@@ -7412,53 +7504,6 @@ static void exec_function(char ***to_free, | |||
7412 | # endif | 7504 | # endif |
7413 | } | 7505 | } |
7414 | 7506 | ||
7415 | static void enter_var_nest_level(void) | ||
7416 | { | ||
7417 | G.var_nest_level++; | ||
7418 | debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); | ||
7419 | |||
7420 | /* Try: f() { echo -n .; f; }; f | ||
7421 | * struct variable::var_nest_level is uint16_t, | ||
7422 | * thus limiting recursion to < 2^16. | ||
7423 | * In any case, with 8 Mbyte stack SEGV happens | ||
7424 | * not too long after 2^16 recursions anyway. | ||
7425 | */ | ||
7426 | if (G.var_nest_level > 0xff00) | ||
7427 | bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); | ||
7428 | } | ||
7429 | |||
7430 | static void leave_var_nest_level(void) | ||
7431 | { | ||
7432 | struct variable *cur; | ||
7433 | struct variable **cur_pp; | ||
7434 | |||
7435 | cur_pp = &G.top_var; | ||
7436 | while ((cur = *cur_pp) != NULL) { | ||
7437 | if (cur->var_nest_level < G.var_nest_level) { | ||
7438 | cur_pp = &cur->next; | ||
7439 | continue; | ||
7440 | } | ||
7441 | /* Unexport */ | ||
7442 | if (cur->flg_export) { | ||
7443 | debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); | ||
7444 | bb_unsetenv(cur->varstr); | ||
7445 | } | ||
7446 | /* Remove from global list */ | ||
7447 | *cur_pp = cur->next; | ||
7448 | /* Free */ | ||
7449 | if (!cur->max_len) { | ||
7450 | debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); | ||
7451 | free(cur->varstr); | ||
7452 | } | ||
7453 | free(cur); | ||
7454 | } | ||
7455 | |||
7456 | G.var_nest_level--; | ||
7457 | debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); | ||
7458 | if (HUSH_DEBUG && (int)G.var_nest_level < 0) | ||
7459 | bb_error_msg_and_die("BUG: nesting underflow"); | ||
7460 | } | ||
7461 | |||
7462 | static int run_function(const struct function *funcp, char **argv) | 7507 | static int run_function(const struct function *funcp, char **argv) |
7463 | { | 7508 | { |
7464 | int rc; | 7509 | int rc; |
@@ -7648,6 +7693,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, | |||
7648 | G.shadowed_vars_pp = NULL; /* "don't save, free them instead" */ | 7693 | G.shadowed_vars_pp = NULL; /* "don't save, free them instead" */ |
7649 | #else | 7694 | #else |
7650 | G.shadowed_vars_pp = &nommu_save->old_vars; | 7695 | G.shadowed_vars_pp = &nommu_save->old_vars; |
7696 | G.var_nest_level++; | ||
7651 | #endif | 7697 | #endif |
7652 | set_vars_and_save_old(new_env); | 7698 | set_vars_and_save_old(new_env); |
7653 | G.shadowed_vars_pp = sv_shadowed; | 7699 | G.shadowed_vars_pp = sv_shadowed; |
@@ -8249,9 +8295,31 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8249 | /* Testcase: set -- q w e; (IFS='' echo "$*"; IFS=''; echo "$*"); echo "$*" | 8295 | /* Testcase: set -- q w e; (IFS='' echo "$*"; IFS=''; echo "$*"); echo "$*" |
8250 | * Result should be 3 lines: q w e, qwe, q w e | 8296 | * Result should be 3 lines: q w e, qwe, q w e |
8251 | */ | 8297 | */ |
8298 | if (G.ifs_whitespace != G.ifs) | ||
8299 | free(G.ifs_whitespace); | ||
8252 | G.ifs = get_local_var_value("IFS"); | 8300 | G.ifs = get_local_var_value("IFS"); |
8253 | if (!G.ifs) | 8301 | if (G.ifs) { |
8302 | char *p; | ||
8303 | G.ifs_whitespace = (char*)G.ifs; | ||
8304 | p = skip_whitespace(G.ifs); | ||
8305 | if (*p) { | ||
8306 | /* Not all $IFS is whitespace */ | ||
8307 | char *d; | ||
8308 | int len = p - G.ifs; | ||
8309 | p = skip_non_whitespace(p); | ||
8310 | G.ifs_whitespace = xmalloc(len + strlen(p) + 1); /* can overestimate */ | ||
8311 | d = mempcpy(G.ifs_whitespace, G.ifs, len); | ||
8312 | while (*p) { | ||
8313 | if (isspace(*p)) | ||
8314 | *d++ = *p; | ||
8315 | p++; | ||
8316 | } | ||
8317 | *d = '\0'; | ||
8318 | } | ||
8319 | } else { | ||
8254 | G.ifs = defifs; | 8320 | G.ifs = defifs; |
8321 | G.ifs_whitespace = (char*)G.ifs; | ||
8322 | } | ||
8255 | 8323 | ||
8256 | IF_HUSH_JOB(pi->pgrp = -1;) | 8324 | IF_HUSH_JOB(pi->pgrp = -1;) |
8257 | pi->stopped_cmds = 0; | 8325 | pi->stopped_cmds = 0; |
@@ -8343,7 +8411,10 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8343 | bb_putchar_stderr('+'); | 8411 | bb_putchar_stderr('+'); |
8344 | i = 0; | 8412 | i = 0; |
8345 | while (i < command->assignment_cnt) { | 8413 | while (i < command->assignment_cnt) { |
8346 | char *p = expand_string_to_string(argv[i], /*unbackslash:*/ 1); | 8414 | char *p = expand_string_to_string(argv[i], |
8415 | EXP_FLAG_ESC_GLOB_CHARS, | ||
8416 | /*unbackslash:*/ 1 | ||
8417 | ); | ||
8347 | if (G_x_mode) | 8418 | if (G_x_mode) |
8348 | fprintf(stderr, " %s", p); | 8419 | fprintf(stderr, " %s", p); |
8349 | debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); | 8420 | debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); |
@@ -8522,6 +8593,7 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8522 | while (cmd_no < pi->num_cmds) { | 8593 | while (cmd_no < pi->num_cmds) { |
8523 | struct fd_pair pipefds; | 8594 | struct fd_pair pipefds; |
8524 | #if !BB_MMU | 8595 | #if !BB_MMU |
8596 | int sv_var_nest_level = G.var_nest_level; | ||
8525 | volatile nommu_save_t nommu_save; | 8597 | volatile nommu_save_t nommu_save; |
8526 | nommu_save.old_vars = NULL; | 8598 | nommu_save.old_vars = NULL; |
8527 | nommu_save.argv = NULL; | 8599 | nommu_save.argv = NULL; |
@@ -8615,6 +8687,8 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8615 | /* Clean up after vforked child */ | 8687 | /* Clean up after vforked child */ |
8616 | free(nommu_save.argv); | 8688 | free(nommu_save.argv); |
8617 | free(nommu_save.argv_from_re_execing); | 8689 | free(nommu_save.argv_from_re_execing); |
8690 | G.var_nest_level = sv_var_nest_level; | ||
8691 | remove_nested_vars(); | ||
8618 | add_vars(nommu_save.old_vars); | 8692 | add_vars(nommu_save.old_vars); |
8619 | #endif | 8693 | #endif |
8620 | free(argv_expanded); | 8694 | free(argv_expanded); |
@@ -8835,7 +8909,8 @@ static int run_list(struct pipe *pi) | |||
8835 | #if ENABLE_HUSH_CASE | 8909 | #if ENABLE_HUSH_CASE |
8836 | if (rword == RES_CASE) { | 8910 | if (rword == RES_CASE) { |
8837 | debug_printf_exec("CASE cond_code:%d\n", cond_code); | 8911 | debug_printf_exec("CASE cond_code:%d\n", cond_code); |
8838 | case_word = expand_string_to_string(pi->cmds->argv[0], 1); | 8912 | case_word = expand_string_to_string(pi->cmds->argv[0], |
8913 | EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1); | ||
8839 | debug_printf_exec("CASE word1:'%s'\n", case_word); | 8914 | debug_printf_exec("CASE word1:'%s'\n", case_word); |
8840 | //unbackslash(case_word); | 8915 | //unbackslash(case_word); |
8841 | //debug_printf_exec("CASE word2:'%s'\n", case_word); | 8916 | //debug_printf_exec("CASE word2:'%s'\n", case_word); |
@@ -8850,12 +8925,19 @@ static int run_list(struct pipe *pi) | |||
8850 | /* all prev words didn't match, does this one match? */ | 8925 | /* all prev words didn't match, does this one match? */ |
8851 | argv = pi->cmds->argv; | 8926 | argv = pi->cmds->argv; |
8852 | while (*argv) { | 8927 | while (*argv) { |
8853 | char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 0); | 8928 | char *pattern; |
8929 | debug_printf_exec("expand_string_to_string('%s')\n", *argv); | ||
8930 | pattern = expand_string_to_string(*argv, | ||
8931 | EXP_FLAG_ESC_GLOB_CHARS, | ||
8932 | /*unbackslash:*/ 0 | ||
8933 | ); | ||
8854 | /* TODO: which FNM_xxx flags to use? */ | 8934 | /* TODO: which FNM_xxx flags to use? */ |
8855 | cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); | 8935 | cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); |
8856 | debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", pattern, case_word, cond_code); | 8936 | debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", |
8937 | pattern, case_word, cond_code); | ||
8857 | free(pattern); | 8938 | free(pattern); |
8858 | if (cond_code == 0) { /* match! we will execute this branch */ | 8939 | if (cond_code == 0) { |
8940 | /* match! we will execute this branch */ | ||
8859 | free(case_word); | 8941 | free(case_word); |
8860 | case_word = NULL; /* make future "word)" stop */ | 8942 | case_word = NULL; /* make future "word)" stop */ |
8861 | break; | 8943 | break; |
@@ -9394,6 +9476,13 @@ int hush_main(int argc, char **argv) | |||
9394 | optarg++; | 9476 | optarg++; |
9395 | G.depth_of_loop = bb_strtou(optarg, &optarg, 16); | 9477 | G.depth_of_loop = bb_strtou(optarg, &optarg, 16); |
9396 | # endif | 9478 | # endif |
9479 | # if ENABLE_HUSH_FUNCTIONS | ||
9480 | /* nommu uses re-exec trick for "... | func | ...", | ||
9481 | * should allow "return". | ||
9482 | * This accidentally allows returns in subshells. | ||
9483 | */ | ||
9484 | G_flag_return_in_progress = -1; | ||
9485 | # endif | ||
9397 | break; | 9486 | break; |
9398 | } | 9487 | } |
9399 | case 'R': | 9488 | case 'R': |
diff --git a/shell/hush_test/hush-parsing/bkslash_eof1.right b/shell/hush_test/hush-parsing/bkslash_eof1.right new file mode 100644 index 000000000..6c6df0b0c --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof1.right | |||
@@ -0,0 +1 @@ | |||
ok\ | |||
diff --git a/shell/hush_test/hush-parsing/bkslash_eof1.tests b/shell/hush_test/hush-parsing/bkslash_eof1.tests new file mode 100755 index 000000000..97629cb13 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof1.tests | |||
@@ -0,0 +1 @@ | |||
eval 'echo ok\' | |||
diff --git a/shell/hush_test/hush-parsing/bkslash_eof2.right b/shell/hush_test/hush-parsing/bkslash_eof2.right new file mode 100644 index 000000000..8be75727f --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof2.right | |||
@@ -0,0 +1,2 @@ | |||
1 | hush: syntax error: unterminated " | ||
2 | One:1 | ||
diff --git a/shell/hush_test/hush-parsing/bkslash_eof2.tests b/shell/hush_test/hush-parsing/bkslash_eof2.tests new file mode 100755 index 000000000..da1f08db6 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof2.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | printf 'echo "unterminated string\\' >test.tmp.sh | ||
2 | . ./test.tmp.sh | ||
3 | echo One:$? | ||
4 | rm -f test.tmp.sh | ||
diff --git a/shell/hush_test/hush-parsing/bkslash_newline3.right b/shell/hush_test/hush-parsing/bkslash_newline3.right new file mode 100644 index 000000000..e635074e5 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_newline3.right | |||
@@ -0,0 +1 @@ | |||
a:[a] | |||
diff --git a/shell/hush_test/hush-parsing/bkslash_newline3.tests b/shell/hush_test/hush-parsing/bkslash_newline3.tests new file mode 100755 index 000000000..2accd4395 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_newline3.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | for s in \ | ||
2 | a; do | ||
3 | echo "a:[$s]" | ||
4 | done | ||
diff --git a/shell/hush_test/hush-quoting/case_glob1.right b/shell/hush_test/hush-quoting/case_glob1.right new file mode 100644 index 000000000..b4785957b --- /dev/null +++ b/shell/hush_test/hush-quoting/case_glob1.right | |||
@@ -0,0 +1 @@ | |||
s | |||
diff --git a/shell/hush_test/hush-quoting/case_glob1.tests b/shell/hush_test/hush-quoting/case_glob1.tests new file mode 100755 index 000000000..8dbbc0fb1 --- /dev/null +++ b/shell/hush_test/hush-quoting/case_glob1.tests | |||
@@ -0,0 +1,8 @@ | |||
1 | g='[3](a)(b)(c)' | ||
2 | s='[3](a)(b)(c)' | ||
3 | case $g in | ||
4 | "$s") echo s | ||
5 | ;; | ||
6 | *) echo "*" | ||
7 | ;; | ||
8 | esac | ||
diff --git a/shell/hush_test/hush-read/read_ifs2.right b/shell/hush_test/hush-read/read_ifs2.right new file mode 100644 index 000000000..797137dae --- /dev/null +++ b/shell/hush_test/hush-read/read_ifs2.right | |||
@@ -0,0 +1,9 @@ | |||
1 | |X|Y:Z:| | ||
2 | |X|Y:Z| | ||
3 | |X|Y| | ||
4 | |X|Y| | ||
5 | |X|| | ||
6 | |X|| | ||
7 | ||| | ||
8 | Whitespace should be trimmed too: | ||
9 | |X|Y| | ||
diff --git a/shell/hush_test/hush-read/read_ifs2.tests b/shell/hush_test/hush-read/read_ifs2.tests new file mode 100755 index 000000000..f01a68978 --- /dev/null +++ b/shell/hush_test/hush-read/read_ifs2.tests | |||
@@ -0,0 +1,9 @@ | |||
1 | echo "X:Y:Z:" | (IFS=": " read x y; echo "|$x|$y|") | ||
2 | echo "X:Y:Z" | (IFS=": " read x y; echo "|$x|$y|") | ||
3 | echo "X:Y:" | (IFS=": " read x y; echo "|$x|$y|") | ||
4 | echo "X:Y" | (IFS=": " read x y; echo "|$x|$y|") | ||
5 | echo "X:" | (IFS=": " read x y; echo "|$x|$y|") | ||
6 | echo "X" | (IFS=": " read x y; echo "|$x|$y|") | ||
7 | echo "" | (IFS=": " read x y; echo "|$x|$y|") | ||
8 | echo Whitespace should be trimmed too: | ||
9 | echo "X:Y : " | (IFS=": " read x y; echo "|$x|$y|") | ||
diff --git a/shell/hush_test/hush-vars/param_expand_alt2.right b/shell/hush_test/hush-vars/param_expand_alt2.right new file mode 100644 index 000000000..fef5889ca --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_alt2.right | |||
@@ -0,0 +1,4 @@ | |||
1 | Unquoted: H H | ||
2 | Quoted: H | ||
3 | H | ||
4 | Ok:0 | ||
diff --git a/shell/hush_test/hush-vars/param_expand_alt2.tests b/shell/hush_test/hush-vars/param_expand_alt2.tests new file mode 100755 index 000000000..d8abf4c3b --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_alt2.tests | |||
@@ -0,0 +1,7 @@ | |||
1 | echo Unquoted: H${$+ | ||
2 | }H | ||
3 | |||
4 | echo Quoted: "H${$+ | ||
5 | }H" | ||
6 | |||
7 | echo Ok:$? | ||
diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs4.right b/shell/hush_test/hush-vars/var_wordsplit_ifs4.right new file mode 100644 index 000000000..c27284c31 --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs4.right | |||
@@ -0,0 +1,5 @@ | |||
1 | |x| | ||
2 | Ok1:0 | ||
3 | |x| | ||
4 | || | ||
5 | Ok2:0 | ||
diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests b/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests new file mode 100755 index 000000000..638bfbb28 --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done | ||
2 | echo Ok1:$? | ||
3 | IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done | ||
4 | echo Ok2:$? | ||
diff --git a/shell/hush_test/hush-z_slow/many_ifs.right b/shell/hush_test/hush-z_slow/many_ifs.right new file mode 100644 index 000000000..f3bdccc6c --- /dev/null +++ b/shell/hush_test/hush-z_slow/many_ifs.right | |||
@@ -0,0 +1 @@ | |||
# tests 6856 passed 6856 failed 0 | |||
diff --git a/shell/hush_test/hush-z_slow/many_ifs.tests b/shell/hush_test/hush-z_slow/many_ifs.tests new file mode 100755 index 000000000..1f5b1b3a6 --- /dev/null +++ b/shell/hush_test/hush-z_slow/many_ifs.tests | |||
@@ -0,0 +1,257 @@ | |||
1 | # Usage: $SHELL ifs.sh | ||
2 | # | ||
3 | # This script generates 6856 tests for the set(1) and read(1) | ||
4 | # builtins w.r.t. IFS whitespace and non-whitespace characters. | ||
5 | # Each failed test produces one line on the standard output that | ||
6 | # contains the test along with the expected and actual results. | ||
7 | # The last output line contains the test result counts. ordered>0 | ||
8 | # are the number of tests where IFS=": " produced different results | ||
9 | # than IFS=" :". If a test fails the same way for IFS=": " and | ||
10 | # IFS=" :" then the second output line is suppressed. | ||
11 | |||
12 | TESTS=6856 | ||
13 | |||
14 | ksh_read=0 | ||
15 | echo 1 | read ksh_read | ||
16 | ksh_arith=0 | ||
17 | eval '((ksh_arith+=1))' 2>/dev/null | ||
18 | |||
19 | failed=0 | ||
20 | ordered=0 | ||
21 | passed=0 | ||
22 | |||
23 | split() | ||
24 | { | ||
25 | i=$1 s=$2 r=$3 S='' R='' | ||
26 | for ifs in ': ' ' :' | ||
27 | do IFS=$ifs | ||
28 | set x $i | ||
29 | shift | ||
30 | IFS=' ' | ||
31 | g="[$#]" | ||
32 | while : | ||
33 | do case $# in | ||
34 | 0) break ;; | ||
35 | esac | ||
36 | g="$g($1)" | ||
37 | shift | ||
38 | done | ||
39 | case $g in | ||
40 | "$s") case $ksh_arith in | ||
41 | 1) ((passed+=1)) ;; | ||
42 | *) passed=`expr $passed + 1` ;; | ||
43 | esac | ||
44 | case $S in | ||
45 | '') S=$g | ||
46 | ;; | ||
47 | "$g") ;; | ||
48 | *) case $ksh_arith in | ||
49 | 1) ((ordered+=1)) ;; | ||
50 | *) ordered=`expr $ordered + 1` ;; | ||
51 | esac | ||
52 | ;; | ||
53 | esac | ||
54 | ;; | ||
55 | "$S") case $ksh_arith in | ||
56 | 1) ((failed+=1)) ;; | ||
57 | *) failed=`expr $failed + 1` ;; | ||
58 | esac | ||
59 | ;; | ||
60 | *) case $ksh_arith in | ||
61 | 1) ((failed+=1)) ;; | ||
62 | *) failed=`expr $failed + 1` ;; | ||
63 | esac | ||
64 | case $s in | ||
65 | "$S") ;; | ||
66 | ?0*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#]\" # expected \"$s\" got \"$g\"" ;; | ||
67 | ?1*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)\" # expected \"$s\" got \"$g\"" ;; | ||
68 | ?2*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)\" # expected \"$s\" got \"$g\"" ;; | ||
69 | ?3*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)(\$3)\" # expected \"$s\" got \"$g\"" ;; | ||
70 | *) echo TEST ERROR i="'$i'" s="'$s'" ;; | ||
71 | esac | ||
72 | case $S in | ||
73 | '') S=$g | ||
74 | ;; | ||
75 | "$g") ;; | ||
76 | *) case $ksh_arith in | ||
77 | 1) ((ordered+=1)) ;; | ||
78 | *) ordered=`expr $ordered + 1` ;; | ||
79 | esac | ||
80 | ;; | ||
81 | esac | ||
82 | esac | ||
83 | case $ksh_read in | ||
84 | 1) echo "$i" | IFS=$ifs read x y; g="($x)($y)" ;; | ||
85 | *) g=`export ifs; echo "$i" | ( IFS=$ifs; read x y; echo "($x)($y)" )` ;; | ||
86 | esac | ||
87 | case $g in | ||
88 | "$r") case $ksh_arith in | ||
89 | 1) ((passed+=1)) ;; | ||
90 | *) passed=`expr $passed + 1` ;; | ||
91 | esac | ||
92 | case $R in | ||
93 | '') R=$g | ||
94 | ;; | ||
95 | "$g") ;; | ||
96 | *) case $ksh_arith in | ||
97 | 1) ((ordered+=1)) ;; | ||
98 | *) ordered=`expr $ordered + 1` ;; | ||
99 | esac | ||
100 | ;; | ||
101 | esac | ||
102 | ;; | ||
103 | "$R") case $ksh_arith in | ||
104 | 1) ((failed+=1)) ;; | ||
105 | *) failed=`expr $failed + 1` ;; | ||
106 | esac | ||
107 | ;; | ||
108 | *) case $ksh_arith in | ||
109 | 1) ((failed+=1)) ;; | ||
110 | *) failed=`expr $failed + 1` ;; | ||
111 | esac | ||
112 | case $r in | ||
113 | "$R") ;; | ||
114 | *) echo "echo \"$i\" | ( IFS=\"$ifs\" read x y; echo \"(\$x)(\$y)\" ) # expected \"$r\" got \"$g\"" ;; | ||
115 | esac | ||
116 | case $R in | ||
117 | '') R=$g | ||
118 | ;; | ||
119 | "$g") ;; | ||
120 | *) case $ksh_arith in | ||
121 | 1) ((ordered+=1)) ;; | ||
122 | *) ordered=`expr $ordered + 1` ;; | ||
123 | esac | ||
124 | ;; | ||
125 | esac | ||
126 | ;; | ||
127 | esac | ||
128 | done | ||
129 | } | ||
130 | |||
131 | for str in \ | ||
132 | '-' \ | ||
133 | 'a' \ | ||
134 | '- -' \ | ||
135 | '- a' \ | ||
136 | 'a -' \ | ||
137 | 'a b' \ | ||
138 | '- - -' \ | ||
139 | '- - a' \ | ||
140 | '- a -' \ | ||
141 | '- a b' \ | ||
142 | 'a - -' \ | ||
143 | 'a - b' \ | ||
144 | 'a b -' \ | ||
145 | 'a b c' \ | ||
146 | |||
147 | do | ||
148 | IFS=' ' | ||
149 | set x $str | ||
150 | |||
151 | shift | ||
152 | case $# in | ||
153 | 0) continue ;; | ||
154 | esac | ||
155 | |||
156 | f1=$1 | ||
157 | case $f1 in | ||
158 | '-') f1='' ;; | ||
159 | esac | ||
160 | |||
161 | shift | ||
162 | case $# in | ||
163 | 0) for d0 in '' ' ' | ||
164 | do | ||
165 | for d1 in '' ' ' ':' ' :' ': ' ' : ' | ||
166 | do | ||
167 | case $f1$d1 in | ||
168 | '') split "$d0$f1$d1" "[0]" "()()" ;; | ||
169 | ' ') ;; | ||
170 | *) split "$d0$f1$d1" "[1]($f1)" "($f1)()" ;; | ||
171 | esac | ||
172 | done | ||
173 | done | ||
174 | continue | ||
175 | ;; | ||
176 | esac | ||
177 | f2=$1 | ||
178 | case $f2 in | ||
179 | '-') f2='' ;; | ||
180 | esac | ||
181 | |||
182 | shift | ||
183 | case $# in | ||
184 | 0) for d0 in '' ' ' | ||
185 | do | ||
186 | for d1 in ' ' ':' ' :' ': ' ' : ' | ||
187 | do | ||
188 | case ' ' in | ||
189 | $f1$d1|$d1$f2) continue ;; | ||
190 | esac | ||
191 | for d2 in '' ' ' ':' ' :' ': ' ' : ' | ||
192 | do | ||
193 | case $f2$d2 in | ||
194 | '') split "$d0$f1$d1$f2$d2" "[1]($f1)" "($f1)()" ;; | ||
195 | ' ') ;; | ||
196 | *) split "$d0$f1$d1$f2$d2" "[2]($f1)($f2)" "($f1)($f2)" ;; | ||
197 | esac | ||
198 | done | ||
199 | done | ||
200 | done | ||
201 | continue | ||
202 | ;; | ||
203 | esac | ||
204 | f3=$1 | ||
205 | case $f3 in | ||
206 | '-') f3='' ;; | ||
207 | esac | ||
208 | |||
209 | shift | ||
210 | case $# in | ||
211 | 0) for d0 in '' ' ' | ||
212 | do | ||
213 | for d1 in ':' ' :' ': ' ' : ' | ||
214 | do | ||
215 | case ' ' in | ||
216 | $f1$d1|$d1$f2) continue ;; | ||
217 | esac | ||
218 | for d2 in ' ' ':' ' :' ': ' ' : ' | ||
219 | do | ||
220 | case $f2$d2 in | ||
221 | ' ') continue ;; | ||
222 | esac | ||
223 | case ' ' in | ||
224 | $f2$d2|$d2$f3) continue ;; | ||
225 | esac | ||
226 | for d3 in '' ' ' ':' ' :' ': ' ' : ' | ||
227 | do | ||
228 | case $f3$d3 in | ||
229 | '') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;; | ||
230 | ' ') ;; | ||
231 | *) x=$f2$d2$f3$d3 | ||
232 | x=${x# } #was x=${x#' '} hush needs fixing for this to work | ||
233 | x=${x% } #was x=${x%' '} | ||
234 | split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)" | ||
235 | ;; | ||
236 | esac | ||
237 | done | ||
238 | done | ||
239 | done | ||
240 | done | ||
241 | continue | ||
242 | ;; | ||
243 | esac | ||
244 | done | ||
245 | case $ksh_arith in | ||
246 | 1) ((tests=passed+failed)) ;; | ||
247 | *) tests=`expr $passed + $failed` ;; | ||
248 | esac | ||
249 | case $ordered in | ||
250 | 0) ordered="" ;; | ||
251 | *) ordered=" ordered $ordered" ;; | ||
252 | esac | ||
253 | case $tests in | ||
254 | $TESTS) fatal="" ;; | ||
255 | *) fatal=" -- fundamental IFS error -- $TESTS tests expected" | ||
256 | esac | ||
257 | echo "# tests $tests passed $passed failed $failed$ordered$fatal" | ||
diff --git a/shell/shell_common.c b/shell/shell_common.c index 94c94a147..82102778c 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c | |||
@@ -313,9 +313,44 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
313 | 313 | ||
314 | if (argv[0]) { | 314 | if (argv[0]) { |
315 | /* Remove trailing space $IFS chars */ | 315 | /* Remove trailing space $IFS chars */ |
316 | while (--bufpos >= 0 && isspace(buffer[bufpos]) && strchr(ifs, buffer[bufpos]) != NULL) | 316 | while (--bufpos >= 0 |
317 | && isspace(buffer[bufpos]) | ||
318 | && strchr(ifs, buffer[bufpos]) != NULL | ||
319 | ) { | ||
317 | continue; | 320 | continue; |
321 | } | ||
318 | buffer[bufpos + 1] = '\0'; | 322 | buffer[bufpos + 1] = '\0'; |
323 | |||
324 | /* Last variable takes the entire remainder with delimiters | ||
325 | * (sans trailing whitespace $IFS), | ||
326 | * but ***only "if there are fewer vars than fields"(c)***! | ||
327 | * The "X:Y:" case below: there are two fields, | ||
328 | * and therefore last delimiter (:) is eaten: | ||
329 | * IFS=": " | ||
330 | * echo "X:Y:Z:" | (read x y; echo "|$x|$y|") # |X|Y:Z:| | ||
331 | * echo "X:Y:Z" | (read x y; echo "|$x|$y|") # |X|Y:Z| | ||
332 | * echo "X:Y:" | (read x y; echo "|$x|$y|") # |X|Y|, not |X|Y:| | ||
333 | * echo "X:Y : " | (read x y; echo "|$x|$y|") # |X|Y| | ||
334 | */ | ||
335 | if (bufpos >= 0 | ||
336 | && strchr(ifs, buffer[bufpos]) != NULL | ||
337 | ) { | ||
338 | /* There _is_ a non-whitespace IFS char */ | ||
339 | /* Skip whitespace IFS char before it */ | ||
340 | while (--bufpos >= 0 | ||
341 | && isspace(buffer[bufpos]) | ||
342 | && strchr(ifs, buffer[bufpos]) != NULL | ||
343 | ) { | ||
344 | continue; | ||
345 | } | ||
346 | /* Are there $IFS chars? */ | ||
347 | if (strcspn(buffer, ifs) >= ++bufpos) { | ||
348 | /* No: last var takes one field, not more */ | ||
349 | /* So, drop trailing IFS delims */ | ||
350 | buffer[bufpos] = '\0'; | ||
351 | } | ||
352 | } | ||
353 | |||
319 | /* Use the remainder as a value for the next variable */ | 354 | /* Use the remainder as a value for the next variable */ |
320 | setvar(*argv, buffer); | 355 | setvar(*argv, buffer); |
321 | /* Set the rest to "" */ | 356 | /* Set the rest to "" */ |