aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2018-05-13 08:15:58 +0100
committerRon Yorston <rmy@pobox.com>2018-05-13 08:15:58 +0100
commit3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d (patch)
treea527d0db15f34a137fc11df5538c7f2f7c6d72de /shell
parent6f7d1af269eed4b42daeb9c6dfd2ba62f9cd47e4 (diff)
parentd80eecb86812c1fbda652f9b995060c26ba0b155 (diff)
downloadbusybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.tar.gz
busybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.tar.bz2
busybox-w32-3ce461fdf3b7adfd44ea058fa0c5ca6d91a5bc5d.zip
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
-rw-r--r--shell/ash.c92
-rw-r--r--shell/ash_test/ash-parsing/bkslash_eof1.right1
-rwxr-xr-xshell/ash_test/ash-parsing/bkslash_eof1.tests1
-rw-r--r--shell/ash_test/ash-parsing/bkslash_newline3.right1
-rwxr-xr-xshell/ash_test/ash-parsing/bkslash_newline3.tests4
-rw-r--r--shell/ash_test/ash-quoting/case_glob1.right1
-rwxr-xr-xshell/ash_test/ash-quoting/case_glob1.tests8
-rw-r--r--shell/ash_test/ash-redir/redir_exec1.right2
-rw-r--r--shell/ash_test/ash-vars/param_expand_alt2.right4
-rwxr-xr-xshell/ash_test/ash-vars/param_expand_alt2.tests7
-rw-r--r--shell/ash_test/ash-vars/var_wordsplit_ifs4.right5
-rwxr-xr-xshell/ash_test/ash-vars/var_wordsplit_ifs4.tests4
-rw-r--r--shell/ash_test/ash-z_slow/many_ifs.right1
-rwxr-xr-xshell/ash_test/ash-z_slow/many_ifs.tests257
-rw-r--r--shell/hush.c617
-rw-r--r--shell/hush_test/hush-parsing/bkslash_eof1.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bkslash_eof1.tests1
-rw-r--r--shell/hush_test/hush-parsing/bkslash_eof2.right2
-rwxr-xr-xshell/hush_test/hush-parsing/bkslash_eof2.tests4
-rw-r--r--shell/hush_test/hush-parsing/bkslash_newline3.right1
-rwxr-xr-xshell/hush_test/hush-parsing/bkslash_newline3.tests4
-rw-r--r--shell/hush_test/hush-quoting/case_glob1.right1
-rwxr-xr-xshell/hush_test/hush-quoting/case_glob1.tests8
-rw-r--r--shell/hush_test/hush-read/read_ifs2.right9
-rwxr-xr-xshell/hush_test/hush-read/read_ifs2.tests9
-rw-r--r--shell/hush_test/hush-vars/param_expand_alt2.right4
-rwxr-xr-xshell/hush_test/hush-vars/param_expand_alt2.tests7
-rw-r--r--shell/hush_test/hush-vars/var_wordsplit_ifs4.right5
-rwxr-xr-xshell/hush_test/hush-vars/var_wordsplit_ifs4.tests4
-rw-r--r--shell/hush_test/hush-z_slow/many_ifs.right1
-rwxr-xr-xshell/hush_test/hush-z_slow/many_ifs.tests257
-rw-r--r--shell/shell_common.c37
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 */
7960typedef struct exp_t {
7961 char *dir;
7962 unsigned dir_max;
7963} exp_t;
7947static void 7964static void
7948expmeta(char *expdir, char *enddir, char *name) 7965expmeta(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
8059static struct strlist * 8089static 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 @@
1for s in \
2a; do
3 echo "a:[$s]"
4done
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 @@
1g='[3](a)(b)(c)'
2s='[3](a)(b)(c)'
3case $g in
4"$s") echo s
5 ;;
6*) echo "*"
7 ;;
8esac
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 @@
1redir_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
2First 2First
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 @@
1Unquoted: H H
2Quoted: H
3H
4Ok: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 @@
1echo Unquoted: H${$+
2}H
3
4echo Quoted: "H${$+
5}H"
6
7echo 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|
2Ok1:0
3|x|
4||
5Ok2: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 @@
1IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done
2echo Ok1:$?
3IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done
4echo 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
12TESTS=6856
13
14ksh_read=0
15echo 1 | read ksh_read
16ksh_arith=0
17eval '((ksh_arith+=1))' 2>/dev/null
18
19failed=0
20ordered=0
21passed=0
22
23split()
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
131for 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
147do
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
244done
245case $ksh_arith in
2461) ((tests=passed+failed)) ;;
247*) tests=`expr $passed + $failed` ;;
248esac
249case $ordered in
2500) ordered="" ;;
251*) ordered=" ordered $ordered" ;;
252esac
253case $tests in
254$TESTS) fatal="" ;;
255*) fatal=" -- fundamental IFS error -- $TESTS tests expected"
256esac
257echo "# 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;
527enum { 538enum {
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};
534enum {
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};
726enum {
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)
3670static void initialize_context(struct parse_context *ctx) 3696static 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 */
3754static const struct reserved_combo* reserved_word(o_string *word, struct parse_context *ctx) 3782static 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 */
3852static int done_word(o_string *word, struct parse_context *ctx) 3880static 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 4340static int parse_group(struct parse_context *ctx,
4313#define parse_group(dest, ctx, input, ch) \
4314 parse_group(ctx, input, ch)
4315#endif
4316static 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
5596static char *expand_string_to_string(const char *str, int do_unbackslash); 5617static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash);
5597#if ENABLE_HUSH_TICK 5618#if ENABLE_HUSH_TICK
5598static int process_command_subs(o_string *dest, const char *s); 5619static 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 */
6375static char *expand_string_to_string(const char *str, int do_unbackslash) 6410static 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
7401static 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
7428static 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
7443static 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
7415static 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
7430static 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
7462static int run_function(const struct function *funcp, char **argv) 7507static 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 @@
1hush: syntax error: unterminated "
2One: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 @@
1printf 'echo "unterminated string\\' >test.tmp.sh
2. ./test.tmp.sh
3echo One:$?
4rm -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 @@
1for s in \
2a; do
3 echo "a:[$s]"
4done
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 @@
1g='[3](a)(b)(c)'
2s='[3](a)(b)(c)'
3case $g in
4"$s") echo s
5 ;;
6*) echo "*"
7 ;;
8esac
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|||
8Whitespace 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 @@
1echo "X:Y:Z:" | (IFS=": " read x y; echo "|$x|$y|")
2echo "X:Y:Z" | (IFS=": " read x y; echo "|$x|$y|")
3echo "X:Y:" | (IFS=": " read x y; echo "|$x|$y|")
4echo "X:Y" | (IFS=": " read x y; echo "|$x|$y|")
5echo "X:" | (IFS=": " read x y; echo "|$x|$y|")
6echo "X" | (IFS=": " read x y; echo "|$x|$y|")
7echo "" | (IFS=": " read x y; echo "|$x|$y|")
8echo Whitespace should be trimmed too:
9echo "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 @@
1Unquoted: H H
2Quoted: H
3H
4Ok: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 @@
1echo Unquoted: H${$+
2}H
3
4echo Quoted: "H${$+
5}H"
6
7echo 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|
2Ok1:0
3|x|
4||
5Ok2: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 @@
1IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done
2echo Ok1:$?
3IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done
4echo 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
12TESTS=6856
13
14ksh_read=0
15echo 1 | read ksh_read
16ksh_arith=0
17eval '((ksh_arith+=1))' 2>/dev/null
18
19failed=0
20ordered=0
21passed=0
22
23split()
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
131for 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
147do
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
244done
245case $ksh_arith in
2461) ((tests=passed+failed)) ;;
247*) tests=`expr $passed + $failed` ;;
248esac
249case $ordered in
2500) ordered="" ;;
251*) ordered=" ordered $ordered" ;;
252esac
253case $tests in
254$TESTS) fatal="" ;;
255*) fatal=" -- fundamental IFS error -- $TESTS tests expected"
256esac
257echo "# 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 "" */