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 "" */ |
