aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2018-04-09 08:50:34 +0100
committerRon Yorston <rmy@pobox.com>2018-04-09 08:50:34 +0100
commit921c1ab66bad54d4ad8591bb74e41ac985248496 (patch)
tree552a04c691e78e78570e4ec2c83fbc0e59953924 /shell
parent5b6f06f5eb8628955262508d153627fe6f2d1c8b (diff)
parenta1870f4807a75663a085c9f5e92870fa7554f0ad (diff)
downloadbusybox-w32-921c1ab66bad54d4ad8591bb74e41ac985248496.tar.gz
busybox-w32-921c1ab66bad54d4ad8591bb74e41ac985248496.tar.bz2
busybox-w32-921c1ab66bad54d4ad8591bb74e41ac985248496.zip
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
-rw-r--r--shell/ash.c251
-rw-r--r--shell/ash_test/ash-arith/arith_nested1.right1
-rwxr-xr-xshell/ash_test/ash-arith/arith_nested1.tests1
-rw-r--r--shell/ash_test/ash-heredoc/heredoc_var_expand1.right4
-rwxr-xr-xshell/ash_test/ash-heredoc/heredoc_var_expand1.tests11
-rw-r--r--shell/ash_test/ash-misc/assignment5.right5
-rwxr-xr-xshell/ash_test/ash-misc/assignment5.tests9
-rw-r--r--shell/ash_test/ash-misc/func5.right3
-rwxr-xr-xshell/ash_test/ash-misc/func5.tests5
-rw-r--r--shell/ash_test/ash-misc/func_compound1.right3
-rwxr-xr-xshell/ash_test/ash-misc/func_compound1.tests4
-rw-r--r--shell/ash_test/ash-parsing/starquoted3.right2
-rwxr-xr-xshell/ash_test/ash-parsing/starquoted3.tests1
-rw-r--r--shell/ash_test/ash-quoting/bkslash_case2.right3
-rwxr-xr-xshell/ash_test/ash-quoting/bkslash_case2.tests13
-rw-r--r--shell/ash_test/ash-quoting/quote_in_varexp1.right2
-rwxr-xr-xshell/ash_test/ash-quoting/quote_in_varexp1.tests2
-rw-r--r--shell/ash_test/ash-quoting/squote_in_varexp3.right1
-rwxr-xr-xshell/ash_test/ash-quoting/squote_in_varexp3.tests1
-rw-r--r--shell/ash_test/ash-redir/redir_exec1.right2
-rwxr-xr-xshell/ash_test/ash-redir/redir_exec1.tests2
-rw-r--r--shell/ash_test/ash-vars/var_bash3.right4
-rw-r--r--shell/ash_test/ash-vars/var_bash4.right16
-rw-r--r--shell/ash_test/ash-vars/var_bash6.right2
-rwxr-xr-xshell/ash_test/ash-vars/var_bash6.tests2
-rw-r--r--shell/ash_test/ash-vars/var_bash7.right1
-rwxr-xr-xshell/ash_test/ash-vars/var_bash7.tests1
-rw-r--r--shell/hush.c868
-rw-r--r--shell/hush_test/hush-arith/arith_nested1.right1
-rwxr-xr-xshell/hush_test/hush-arith/arith_nested1.tests1
-rw-r--r--shell/hush_test/hush-misc/assignment5.right5
-rwxr-xr-xshell/hush_test/hush-misc/assignment5.tests9
-rwxr-xr-xshell/hush_test/hush-misc/func5.tests5
-rw-r--r--shell/hush_test/hush-parsing/starquoted3.right2
-rwxr-xr-xshell/hush_test/hush-parsing/starquoted3.tests1
-rw-r--r--shell/hush_test/hush-psubst/falsetick2.right1
-rwxr-xr-xshell/hush_test/hush-psubst/falsetick2.tests3
-rw-r--r--shell/hush_test/hush-quoting/bkslash_case2.right3
-rwxr-xr-xshell/hush_test/hush-quoting/bkslash_case2.tests13
-rw-r--r--shell/hush_test/hush-quoting/squote_in_varexp3.right1
-rwxr-xr-xshell/hush_test/hush-quoting/squote_in_varexp3.tests1
-rw-r--r--shell/hush_test/hush-redir/redir_backquote1.right11
-rwxr-xr-xshell/hush_test/hush-redir/redir_backquote1.tests19
-rw-r--r--shell/hush_test/hush-redir/redir_exec1.right3
-rwxr-xr-xshell/hush_test/hush-redir/redir_exec1.tests2
-rw-r--r--shell/hush_test/hush-vars/readonly3.right4
-rwxr-xr-xshell/hush_test/hush-vars/readonly3.tests4
-rw-r--r--shell/hush_test/hush-vars/var_bash3.right4
-rw-r--r--shell/hush_test/hush-vars/var_bash4.right16
-rw-r--r--shell/hush_test/hush-vars/var_bash6.right2
-rwxr-xr-xshell/hush_test/hush-vars/var_bash6.tests2
-rw-r--r--shell/hush_test/hush-vars/var_nested1.right3
-rwxr-xr-xshell/hush_test/hush-vars/var_nested1.tests16
-rw-r--r--shell/hush_test/hush-vars/var_nested2.right1
-rwxr-xr-xshell/hush_test/hush-vars/var_nested2.tests2
-rw-r--r--shell/shell_common.c1
56 files changed, 840 insertions, 516 deletions
diff --git a/shell/ash.c b/shell/ash.c
index 5ec035043..35438a887 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -204,7 +204,6 @@
204 204
205#define JOBS ENABLE_ASH_JOB_CONTROL 205#define JOBS ENABLE_ASH_JOB_CONTROL
206 206
207#include <setjmp.h>
208#include <fnmatch.h> 207#include <fnmatch.h>
209#include <sys/times.h> 208#include <sys/times.h>
210#include <sys/utsname.h> /* for setting $HOSTNAME */ 209#include <sys/utsname.h> /* for setting $HOSTNAME */
@@ -5755,7 +5754,7 @@ openredirect(union node *redir)
5755 f = open(fname, O_WRONLY, 0666); 5754 f = open(fname, O_WRONLY, 0666);
5756 if (f < 0) 5755 if (f < 0)
5757 goto ecreate; 5756 goto ecreate;
5758 if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) { 5757 if (!fstat(f, &sb) && S_ISREG(sb.st_mode)) {
5759 close(f); 5758 close(f);
5760 errno = EEXIST; 5759 errno = EEXIST;
5761 goto ecreate; 5760 goto ecreate;
@@ -6255,10 +6254,9 @@ static int substr_atoi(const char *s)
6255 * performs globbing, and thus diverges from what we do). 6254 * performs globbing, and thus diverges from what we do).
6256 */ 6255 */
6257#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ 6256#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
6258#define EXP_QPAT 0x20 /* pattern in quoted parameter expansion */ 6257#define EXP_VARTILDE2 0x20 /* expand tildes after colons only */
6259#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ 6258#define EXP_WORD 0x40 /* expand word in parameter expansion */
6260#define EXP_WORD 0x80 /* expand word in parameter expansion */ 6259#define EXP_QUOTED 0x80 /* expand word in double quotes */
6261#define EXP_QUOTED 0x100 /* expand word in double quotes */
6262/* 6260/*
6263 * rmescape() flags 6261 * rmescape() flags
6264 */ 6262 */
@@ -6268,7 +6266,7 @@ static int substr_atoi(const char *s)
6268#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ 6266#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */
6269 6267
6270/* Add CTLESC when necessary. */ 6268/* Add CTLESC when necessary. */
6271#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT) 6269#define QUOTES_ESC (EXP_FULL | EXP_CASE)
6272/* Do not skip NUL characters. */ 6270/* Do not skip NUL characters. */
6273#define QUOTES_KEEPNUL EXP_TILDE 6271#define QUOTES_KEEPNUL EXP_TILDE
6274 6272
@@ -6343,7 +6341,10 @@ ifsbreakup(char *string, struct arglist *arglist)
6343 realifs = ifsset() ? ifsval() : defifs; 6341 realifs = ifsset() ? ifsval() : defifs;
6344 ifsp = &ifsfirst; 6342 ifsp = &ifsfirst;
6345 do { 6343 do {
6344 int afternul;
6345
6346 p = string + ifsp->begoff; 6346 p = string + ifsp->begoff;
6347 afternul = nulonly;
6347 nulonly = ifsp->nulonly; 6348 nulonly = ifsp->nulonly;
6348 ifs = nulonly ? nullstr : realifs; 6349 ifs = nulonly ? nullstr : realifs;
6349 ifsspc = 0; 6350 ifsspc = 0;
@@ -6355,7 +6356,7 @@ ifsbreakup(char *string, struct arglist *arglist)
6355 p++; 6356 p++;
6356 continue; 6357 continue;
6357 } 6358 }
6358 if (!nulonly) 6359 if (!(afternul || nulonly))
6359 ifsspc = (strchr(defifs, *p) != NULL); 6360 ifsspc = (strchr(defifs, *p) != NULL);
6360 /* Ignore IFS whitespace at start */ 6361 /* Ignore IFS whitespace at start */
6361 if (q == start && ifsspc) { 6362 if (q == start && ifsspc) {
@@ -6457,7 +6458,6 @@ rmescapes(char *str, int flag, int *slash_position)
6457 IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' }; 6458 IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' };
6458 6459
6459 char *p, *q, *r; 6460 char *p, *q, *r;
6460 unsigned inquotes;
6461 unsigned protect_against_glob; 6461 unsigned protect_against_glob;
6462 unsigned globbing; 6462 unsigned globbing;
6463 6463
@@ -6488,18 +6488,21 @@ rmescapes(char *str, int flag, int *slash_position)
6488 } 6488 }
6489 } 6489 }
6490 6490
6491 inquotes = 0;
6492 globbing = flag & RMESCAPE_GLOB; 6491 globbing = flag & RMESCAPE_GLOB;
6493 protect_against_glob = globbing; 6492 protect_against_glob = globbing;
6494 while (*p) { 6493 while (*p) {
6495 if ((unsigned char)*p == CTLQUOTEMARK) { 6494 if ((unsigned char)*p == CTLQUOTEMARK) {
6496// Note: both inquotes and protect_against_glob only affect whether 6495// Note: protect_against_glob only affect whether
6497// CTLESC,<ch> gets converted to <ch> or to \<ch> 6496// CTLESC,<ch> gets converted to <ch> or to \<ch>
6498 inquotes = ~inquotes;
6499 p++; 6497 p++;
6500 protect_against_glob = globbing; 6498 protect_against_glob = globbing;
6501 continue; 6499 continue;
6502 } 6500 }
6501 if (*p == '\\') {
6502 /* naked back slash */
6503 protect_against_glob = 0;
6504 goto copy;
6505 }
6503 if ((unsigned char)*p == CTLESC) { 6506 if ((unsigned char)*p == CTLESC) {
6504 p++; 6507 p++;
6505#if DEBUG 6508#if DEBUG
@@ -6535,10 +6538,6 @@ rmescapes(char *str, int flag, int *slash_position)
6535 *q++ = '\\'; 6538 *q++ = '\\';
6536 } 6539 }
6537 } 6540 }
6538 } else if (*p == '\\' && !inquotes) {
6539 /* naked back slash */
6540 protect_against_glob = 0;
6541 goto copy;
6542 } 6541 }
6543#if BASH_PATTERN_SUBST 6542#if BASH_PATTERN_SUBST
6544 else if (slash_position && p == str + *slash_position) { 6543 else if (slash_position && p == str + *slash_position) {
@@ -7032,12 +7031,12 @@ argstr(char *p, int flags)
7032 case CTLENDVAR: /* ??? */ 7031 case CTLENDVAR: /* ??? */
7033 goto breakloop; 7032 goto breakloop;
7034 case CTLQUOTEMARK: 7033 case CTLQUOTEMARK:
7035 inquotes ^= EXP_QUOTED;
7036 /* "$@" syntax adherence hack */ 7034 /* "$@" syntax adherence hack */
7037 if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) { 7035 if (!inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
7038 p = evalvar(p + 1, flags | inquotes) + 1; 7036 p = evalvar(p + 1, flags | EXP_QUOTED) + 1;
7039 goto start; 7037 goto start;
7040 } 7038 }
7039 inquotes ^= EXP_QUOTED;
7041 addquote: 7040 addquote:
7042 if (flags & QUOTES_ESC) { 7041 if (flags & QUOTES_ESC) {
7043 p--; 7042 p--;
@@ -7048,16 +7047,6 @@ argstr(char *p, int flags)
7048 case CTLESC: 7047 case CTLESC:
7049 startloc++; 7048 startloc++;
7050 length++; 7049 length++;
7051
7052 /*
7053 * Quoted parameter expansion pattern: remove quote
7054 * unless inside inner quotes or we have a literal
7055 * backslash.
7056 */
7057 if (((flags | inquotes) & (EXP_QPAT | EXP_QUOTED)) ==
7058 EXP_QPAT && *p != '\\')
7059 break;
7060
7061 goto addquote; 7050 goto addquote;
7062 case CTLVAR: 7051 case CTLVAR:
7063 TRACE(("argstr: evalvar('%s')\n", p)); 7052 TRACE(("argstr: evalvar('%s')\n", p));
@@ -7248,15 +7237,24 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7248 } 7237 }
7249#endif 7238#endif
7250 argstr_flags = EXP_TILDE; 7239 argstr_flags = EXP_TILDE;
7251 if (subtype != VSASSIGN && subtype != VSQUESTION) 7240 if (subtype != VSASSIGN
7252 argstr_flags |= (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE); 7241 && subtype != VSQUESTION
7242#if BASH_SUBSTR
7243 && subtype != VSSUBSTR
7244#endif
7245 ) {
7246 /* EXP_CASE keeps CTLESC's */
7247 argstr_flags = EXP_TILDE | EXP_CASE;
7248 }
7253 argstr(p, argstr_flags); 7249 argstr(p, argstr_flags);
7250 //bb_error_msg("str0:'%s'", (char *)stackblock() + strloc);
7254#if BASH_PATTERN_SUBST 7251#if BASH_PATTERN_SUBST
7255 slash_pos = -1; 7252 slash_pos = -1;
7256 if (repl) { 7253 if (repl) {
7257 slash_pos = expdest - ((char *)stackblock() + strloc); 7254 slash_pos = expdest - ((char *)stackblock() + strloc);
7258 STPUTC('/', expdest); 7255 STPUTC('/', expdest);
7259 argstr(repl + 1, argstr_flags); 7256 //bb_error_msg("repl+1:'%s'", repl + 1);
7257 argstr(repl + 1, EXP_TILDE); /* EXP_TILDE: echo "${v/x/~}" expands ~ ! */
7260 *repl = '/'; 7258 *repl = '/';
7261 } 7259 }
7262#endif 7260#endif
@@ -11182,6 +11180,34 @@ pgetc_eatbnl(void)
11182 return c; 11180 return c;
11183} 11181}
11184 11182
11183struct synstack {
11184 smalluint syntax;
11185 uint8_t innerdq :1;
11186 uint8_t varpushed :1;
11187 uint8_t dblquote :1;
11188 int varnest; /* levels of variables expansion */
11189 int dqvarnest; /* levels of variables expansion within double quotes */
11190 int parenlevel; /* levels of parens in arithmetic */
11191 struct synstack *prev;
11192 struct synstack *next;
11193};
11194
11195static void
11196synstack_push(struct synstack **stack, struct synstack *next, int syntax)
11197{
11198 memset(next, 0, sizeof(*next));
11199 next->syntax = syntax;
11200 next->next = *stack;
11201 (*stack)->prev = next;
11202 *stack = next;
11203}
11204
11205static ALWAYS_INLINE void
11206synstack_pop(struct synstack **stack)
11207{
11208 *stack = (*stack)->next;
11209}
11210
11185/* 11211/*
11186 * To handle the "." command, a stack of input files is used. Pushfile 11212 * To handle the "." command, a stack of input files is used. Pushfile
11187 * adds a new entry to the stack and popfile restores the previous level. 11213 * adds a new entry to the stack and popfile restores the previous level.
@@ -12443,19 +12469,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12443 size_t len; 12469 size_t len;
12444 struct nodelist *bqlist; 12470 struct nodelist *bqlist;
12445 smallint quotef; 12471 smallint quotef;
12446 smallint dblquote;
12447 smallint oldstyle; 12472 smallint oldstyle;
12448 IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */
12449 smallint pssyntax; /* we are expanding a prompt string */ 12473 smallint pssyntax; /* we are expanding a prompt string */
12450 int varnest; /* levels of variables expansion */
12451 IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */
12452 IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
12453 int dqvarnest; /* levels of variables expansion within double quotes */
12454 IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) 12474 IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
12475 /* syntax stack */
12476 struct synstack synbase = { };
12477 struct synstack *synstack = &synbase;
12455 12478
12456 bqlist = NULL;
12457 quotef = 0;
12458 IF_FEATURE_SH_MATH(prevsyntax = 0;)
12459#if ENABLE_ASH_EXPAND_PRMT 12479#if ENABLE_ASH_EXPAND_PRMT
12460 pssyntax = (syntax == PSSYNTAX); 12480 pssyntax = (syntax == PSSYNTAX);
12461 if (pssyntax) 12481 if (pssyntax)
@@ -12463,11 +12483,12 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12463#else 12483#else
12464 pssyntax = 0; /* constant */ 12484 pssyntax = 0; /* constant */
12465#endif 12485#endif
12466 dblquote = (syntax == DQSYNTAX); 12486 synstack->syntax = syntax;
12467 varnest = 0; 12487
12468 IF_FEATURE_SH_MATH(arinest = 0;) 12488 if (syntax == DQSYNTAX)
12469 IF_FEATURE_SH_MATH(parenlevel = 0;) 12489 synstack->dblquote = 1;
12470 dqvarnest = 0; 12490 quotef = 0;
12491 bqlist = NULL;
12471 12492
12472 STARTSTACKSTR(out); 12493 STARTSTACKSTR(out);
12473 loop: 12494 loop:
@@ -12475,9 +12496,9 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12475 CHECKEND(); /* set c to PEOF if at end of here document */ 12496 CHECKEND(); /* set c to PEOF if at end of here document */
12476 for (;;) { /* until end of line or end of word */ 12497 for (;;) { /* until end of line or end of word */
12477 CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ 12498 CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */
12478 switch (SIT(c, syntax)) { 12499 switch (SIT(c, synstack->syntax)) {
12479 case CNL: /* '\n' */ 12500 case CNL: /* '\n' */
12480 if (syntax == BASESYNTAX) 12501 if (synstack->syntax == BASESYNTAX)
12481 goto endword; /* exit outer loop */ 12502 goto endword; /* exit outer loop */
12482 USTPUTC(c, out); 12503 USTPUTC(c, out);
12483 nlprompt(); 12504 nlprompt();
@@ -12497,13 +12518,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12497 if (c & 0x100) { 12518 if (c & 0x100) {
12498 /* Unknown escape. Encode as '\z' */ 12519 /* Unknown escape. Encode as '\z' */
12499 c = (unsigned char)c; 12520 c = (unsigned char)c;
12500 if (eofmark == NULL || dblquote) 12521 if (eofmark == NULL || synstack->dblquote)
12501 USTPUTC(CTLESC, out); 12522 USTPUTC(CTLESC, out);
12502 USTPUTC('\\', out); 12523 USTPUTC('\\', out);
12503 } 12524 }
12504 } 12525 }
12505#endif 12526#endif
12506 if (eofmark == NULL || dblquote) 12527 if (!eofmark || synstack->dblquote || synstack->varnest)
12507 USTPUTC(CTLESC, out); 12528 USTPUTC(CTLESC, out);
12508 USTPUTC(c, out); 12529 USTPUTC(c, out);
12509 break; 12530 break;
@@ -12523,20 +12544,13 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12523 /* Backslash is retained if we are in "str" 12544 /* Backslash is retained if we are in "str"
12524 * and next char isn't dquote-special. 12545 * and next char isn't dquote-special.
12525 */ 12546 */
12526 if (dblquote 12547 if (synstack->dblquote
12527 && c != '\\' 12548 && c != '\\'
12528 && c != '`' 12549 && c != '`'
12529 && c != '$' 12550 && c != '$'
12530 && (c != '"' || eofmark != NULL) 12551 && (c != '"' || (eofmark != NULL && !synstack->varnest))
12552 && (c != '}' || !synstack->varnest)
12531 ) { 12553 ) {
12532//dash survives not doing USTPUTC(CTLESC), but merely by chance:
12533//Example: "\z" gets encoded as "\<CTLESC>z".
12534//rmescapes() then emits "\", "\z", protecting z from globbing.
12535//But it's wrong, should protect _both_ from globbing:
12536//everything in double quotes is not globbed.
12537//Unlike dash, we have a fix in rmescapes() which emits bare "z"
12538//for "<CTLESC>z" since "z" is not glob-special (else unicode may break),
12539//and glob would see "\z" and eat "\". Thus:
12540 USTPUTC(CTLESC, out); /* protect '\' from glob */ 12554 USTPUTC(CTLESC, out); /* protect '\' from glob */
12541 USTPUTC('\\', out); 12555 USTPUTC('\\', out);
12542 } 12556 }
@@ -12546,56 +12560,62 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12546 } 12560 }
12547 break; 12561 break;
12548 case CSQUOTE: 12562 case CSQUOTE:
12549 syntax = SQSYNTAX; 12563 synstack->syntax = SQSYNTAX;
12550 quotemark: 12564 quotemark:
12551 if (eofmark == NULL) { 12565 if (eofmark == NULL) {
12552 USTPUTC(CTLQUOTEMARK, out); 12566 USTPUTC(CTLQUOTEMARK, out);
12553 } 12567 }
12554 break; 12568 break;
12555 case CDQUOTE: 12569 case CDQUOTE:
12556 syntax = DQSYNTAX; 12570 synstack->syntax = DQSYNTAX;
12557 dblquote = 1; 12571 synstack->dblquote = 1;
12572 toggledq:
12573 if (synstack->varnest)
12574 synstack->innerdq ^= 1;
12558 goto quotemark; 12575 goto quotemark;
12559 case CENDQUOTE: 12576 case CENDQUOTE:
12560 IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;) 12577 IF_BASH_DOLLAR_SQUOTE(bash_dollar_squote = 0;)
12561 if (eofmark != NULL && varnest == 0) { 12578 if (eofmark != NULL && synstack->varnest == 0) {
12562 USTPUTC(c, out); 12579 USTPUTC(c, out);
12563 } else { 12580 break;
12564 if (dqvarnest == 0) {
12565 syntax = BASESYNTAX;
12566 dblquote = 0;
12567 }
12568 quotef = 1;
12569 goto quotemark;
12570 } 12581 }
12571 break; 12582
12583 if (synstack->dqvarnest == 0) {
12584 synstack->syntax = BASESYNTAX;
12585 synstack->dblquote = 0;
12586 }
12587
12588 quotef = 1;
12589
12590 if (c == '"')
12591 goto toggledq;
12592
12593 goto quotemark;
12572 case CVAR: /* '$' */ 12594 case CVAR: /* '$' */
12573 PARSESUB(); /* parse substitution */ 12595 PARSESUB(); /* parse substitution */
12574 break; 12596 break;
12575 case CENDVAR: /* '}' */ 12597 case CENDVAR: /* '}' */
12576 if (varnest > 0) { 12598 if (!synstack->innerdq && synstack->varnest > 0) {
12577 varnest--; 12599 if (!--synstack->varnest && synstack->varpushed)
12578 if (dqvarnest > 0) { 12600 synstack_pop(&synstack);
12579 dqvarnest--; 12601 else if (synstack->dqvarnest > 0)
12580 } 12602 synstack->dqvarnest--;
12581 c = CTLENDVAR; 12603 c = CTLENDVAR;
12582 } 12604 }
12583 USTPUTC(c, out); 12605 USTPUTC(c, out);
12584 break; 12606 break;
12585#if ENABLE_FEATURE_SH_MATH 12607#if ENABLE_FEATURE_SH_MATH
12586 case CLP: /* '(' in arithmetic */ 12608 case CLP: /* '(' in arithmetic */
12587 parenlevel++; 12609 synstack->parenlevel++;
12588 USTPUTC(c, out); 12610 USTPUTC(c, out);
12589 break; 12611 break;
12590 case CRP: /* ')' in arithmetic */ 12612 case CRP: /* ')' in arithmetic */
12591 if (parenlevel > 0) { 12613 if (synstack->parenlevel > 0) {
12592 parenlevel--; 12614 synstack->parenlevel--;
12593 } else { 12615 } else {
12594 if (pgetc_eatbnl() == ')') { 12616 if (pgetc_eatbnl() == ')') {
12595 c = CTLENDARI; 12617 c = CTLENDARI;
12596 if (--arinest == 0) { 12618 synstack_pop(&synstack);
12597 syntax = prevsyntax;
12598 }
12599 } else { 12619 } else {
12600 /* 12620 /*
12601 * unbalanced parens 12621 * unbalanced parens
@@ -12621,7 +12641,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12621 case CIGN: 12641 case CIGN:
12622 break; 12642 break;
12623 default: 12643 default:
12624 if (varnest == 0) { 12644 if (synstack->varnest == 0) {
12625#if BASH_REDIR_OUTPUT 12645#if BASH_REDIR_OUTPUT
12626 if (c == '&') { 12646 if (c == '&') {
12627//Can't call pgetc_eatbnl() here, this requires three-deep pungetc() 12647//Can't call pgetc_eatbnl() here, this requires three-deep pungetc()
@@ -12640,12 +12660,12 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12640 endword: 12660 endword:
12641 12661
12642#if ENABLE_FEATURE_SH_MATH 12662#if ENABLE_FEATURE_SH_MATH
12643 if (syntax == ARISYNTAX) 12663 if (synstack->syntax == ARISYNTAX)
12644 raise_error_syntax("missing '))'"); 12664 raise_error_syntax("missing '))'");
12645#endif 12665#endif
12646 if (syntax != BASESYNTAX && eofmark == NULL) 12666 if (synstack->syntax != BASESYNTAX && eofmark == NULL)
12647 raise_error_syntax("unterminated quoted string"); 12667 raise_error_syntax("unterminated quoted string");
12648 if (varnest != 0) { 12668 if (synstack->varnest != 0) {
12649 /* { */ 12669 /* { */
12650 raise_error_syntax("missing '}'"); 12670 raise_error_syntax("missing '}'");
12651 } 12671 }
@@ -12827,7 +12847,7 @@ parsesub: {
12827 || (c != '(' && c != '{' && !is_name(c) && !is_special(c)) 12847 || (c != '(' && c != '{' && !is_name(c) && !is_special(c))
12828 ) { 12848 ) {
12829#if BASH_DOLLAR_SQUOTE 12849#if BASH_DOLLAR_SQUOTE
12830 if (syntax != DQSYNTAX && c == '\'') 12850 if (synstack->syntax != DQSYNTAX && c == '\'')
12831 bash_dollar_squote = 1; 12851 bash_dollar_squote = 1;
12832 else 12852 else
12833#endif 12853#endif
@@ -12847,6 +12867,8 @@ parsesub: {
12847 } 12867 }
12848 } else { 12868 } else {
12849 /* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */ 12869 /* $VAR, $<specialchar>, ${...}, or PEOA/PEOF */
12870 smalluint newsyn = synstack->syntax;
12871
12850 USTPUTC(CTLVAR, out); 12872 USTPUTC(CTLVAR, out);
12851 typeloc = out - (char *)stackblock(); 12873 typeloc = out - (char *)stackblock();
12852 STADJUST(1, out); 12874 STADJUST(1, out);
@@ -12905,6 +12927,8 @@ parsesub: {
12905 static const char types[] ALIGN1 = "}-+?="; 12927 static const char types[] ALIGN1 = "}-+?=";
12906 /* ${VAR...} but not $VAR or ${#VAR} */ 12928 /* ${VAR...} but not $VAR or ${#VAR} */
12907 /* c == first char after VAR */ 12929 /* c == first char after VAR */
12930 int cc = c;
12931
12908 switch (c) { 12932 switch (c) {
12909 case ':': 12933 case ':':
12910 c = pgetc_eatbnl(); 12934 c = pgetc_eatbnl();
@@ -12929,21 +12953,24 @@ parsesub: {
12929 break; 12953 break;
12930 } 12954 }
12931 case '%': 12955 case '%':
12932 case '#': { 12956 case '#':
12933 int cc = c;
12934 subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT); 12957 subtype = (c == '#' ? VSTRIMLEFT : VSTRIMRIGHT);
12935 c = pgetc_eatbnl(); 12958 c = pgetc_eatbnl();
12936 if (c != cc) 12959 if (c == cc)
12937 goto badsub; 12960 subtype++;
12938 subtype++; 12961 else
12962 pungetc();
12963
12964 newsyn = BASESYNTAX;
12939 break; 12965 break;
12940 }
12941#if BASH_PATTERN_SUBST 12966#if BASH_PATTERN_SUBST
12942 case '/': 12967 case '/':
12943 /* ${v/[/]pattern/repl} */ 12968 /* ${v/[/]pattern/repl} */
12944//TODO: encode pattern and repl separately. 12969//TODO: encode pattern and repl separately.
12945// Currently ${v/$var_with_slash/repl} is horribly broken 12970// Currently cases like: v=1;echo ${v/$((1/1))/ONE}
12971// are broken (should print "ONE")
12946 subtype = VSREPLACE; 12972 subtype = VSREPLACE;
12973 newsyn = BASESYNTAX;
12947 c = pgetc_eatbnl(); 12974 c = pgetc_eatbnl();
12948 if (c != '/') 12975 if (c != '/')
12949 goto badsub; 12976 goto badsub;
@@ -12955,11 +12982,26 @@ parsesub: {
12955 badsub: 12982 badsub:
12956 pungetc(); 12983 pungetc();
12957 } 12984 }
12985
12986 if (newsyn == ARISYNTAX)
12987 newsyn = DQSYNTAX;
12988
12989 if ((newsyn != synstack->syntax || synstack->innerdq)
12990 && subtype != VSNORMAL
12991 ) {
12992 synstack_push(&synstack,
12993 synstack->prev ?: alloca(sizeof(*synstack)),
12994 newsyn);
12995
12996 synstack->varpushed = 1;
12997 synstack->dblquote = newsyn != BASESYNTAX;
12998 }
12999
12958 ((unsigned char *)stackblock())[typeloc] = subtype; 13000 ((unsigned char *)stackblock())[typeloc] = subtype;
12959 if (subtype != VSNORMAL) { 13001 if (subtype != VSNORMAL) {
12960 varnest++; 13002 synstack->varnest++;
12961 if (dblquote) 13003 if (synstack->dblquote)
12962 dqvarnest++; 13004 synstack->dqvarnest++;
12963 } 13005 }
12964 STPUTC('=', out); 13006 STPUTC('=', out);
12965 } 13007 }
@@ -13016,7 +13058,7 @@ parsebackq: {
13016 case '\\': 13058 case '\\':
13017 pc = pgetc(); /* or pgetc_eatbnl()? why (example)? */ 13059 pc = pgetc(); /* or pgetc_eatbnl()? why (example)? */
13018 if (pc != '\\' && pc != '`' && pc != '$' 13060 if (pc != '\\' && pc != '`' && pc != '$'
13019 && (!dblquote || pc != '"') 13061 && (!synstack->dblquote || pc != '"')
13020 ) { 13062 ) {
13021 STPUTC('\\', pout); 13063 STPUTC('\\', pout);
13022 } 13064 }
@@ -13091,10 +13133,11 @@ parsebackq: {
13091 * Parse an arithmetic expansion (indicate start of one and set state) 13133 * Parse an arithmetic expansion (indicate start of one and set state)
13092 */ 13134 */
13093parsearith: { 13135parsearith: {
13094 if (++arinest == 1) { 13136
13095 prevsyntax = syntax; 13137 synstack_push(&synstack,
13096 syntax = ARISYNTAX; 13138 synstack->prev ?: alloca(sizeof(*synstack)),
13097 } 13139 ARISYNTAX);
13140 synstack->dblquote = 1;
13098 USTPUTC(CTLARI, out); 13141 USTPUTC(CTLARI, out);
13099 goto parsearith_return; 13142 goto parsearith_return;
13100} 13143}
@@ -14542,7 +14585,7 @@ init(void)
14542 14585
14543 14586
14544//usage:#define ash_trivial_usage 14587//usage:#define ash_trivial_usage
14545//usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" 14588//usage: "[-/+OPTIONS] [-/+o OPT]... [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]"
14546//usage:#define ash_full_usage "\n\n" 14589//usage:#define ash_full_usage "\n\n"
14547//usage: "Unix shell interpreter" 14590//usage: "Unix shell interpreter"
14548 14591
diff --git a/shell/ash_test/ash-arith/arith_nested1.right b/shell/ash_test/ash-arith/arith_nested1.right
new file mode 100644
index 000000000..d00491fd7
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith_nested1.right
@@ -0,0 +1 @@
1
diff --git a/shell/ash_test/ash-arith/arith_nested1.tests b/shell/ash_test/ash-arith/arith_nested1.tests
new file mode 100755
index 000000000..28571b833
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith_nested1.tests
@@ -0,0 +1 @@
echo $(( ( $((1)) ) ))
diff --git a/shell/ash_test/ash-heredoc/heredoc_var_expand1.right b/shell/ash_test/ash-heredoc/heredoc_var_expand1.right
new file mode 100644
index 000000000..eb221832d
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_var_expand1.right
@@ -0,0 +1,4 @@
1
2Ok1:0
3
4Ok2:0
diff --git a/shell/ash_test/ash-heredoc/heredoc_var_expand1.tests b/shell/ash_test/ash-heredoc/heredoc_var_expand1.tests
new file mode 100755
index 000000000..3b00bab7b
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_var_expand1.tests
@@ -0,0 +1,11 @@
1x='*'
2
3cat <<- EOF
4 ${x#'*'}
5EOF
6echo Ok1:$?
7
8cat <<EOF
9${x#'*'}
10EOF
11echo Ok2:$?
diff --git a/shell/ash_test/ash-misc/assignment5.right b/shell/ash_test/ash-misc/assignment5.right
new file mode 100644
index 000000000..a91554c09
--- /dev/null
+++ b/shell/ash_test/ash-misc/assignment5.right
@@ -0,0 +1,5 @@
1Zero1:0
2Zero2:0
3Zero3:0
4Zero4:0 x:1 y:1
5Three:3 x:1 y:1
diff --git a/shell/ash_test/ash-misc/assignment5.tests b/shell/ash_test/ash-misc/assignment5.tests
new file mode 100755
index 000000000..0b8104285
--- /dev/null
+++ b/shell/ash_test/ash-misc/assignment5.tests
@@ -0,0 +1,9 @@
1true; a=1; echo Zero1:$?
2false; a=1; echo Zero2:$?
3false || a=1; echo Zero3:$?
4
5false || x=$? y=`echo $?`; echo Zero4:$? x:$x y:$y
6false || x=$? y=`echo $?; exit 3`; echo Three:$? x:$x y:$y
7
8#ash sets z=1 instead of z=3. disabled for now
9#false || x=$? y=`echo $?; exit 3` z=`echo $?`; echo x:$x y:$y z:$z
diff --git a/shell/ash_test/ash-misc/func5.right b/shell/ash_test/ash-misc/func5.right
index 2c9d316b3..01e79c32a 100644
--- a/shell/ash_test/ash-misc/func5.right
+++ b/shell/ash_test/ash-misc/func5.right
@@ -1,6 +1,3 @@
11 11
22 22
33 33
41
52
63
diff --git a/shell/ash_test/ash-misc/func5.tests b/shell/ash_test/ash-misc/func5.tests
index e967208cc..5c33560bc 100755
--- a/shell/ash_test/ash-misc/func5.tests
+++ b/shell/ash_test/ash-misc/func5.tests
@@ -6,8 +6,3 @@ f 2
6 6
7f() ( echo $1 ) 7f() ( echo $1 )
8f 3 8f 3
9
10f() for i in 1 2 3; do
11 echo $i
12done
13f
diff --git a/shell/ash_test/ash-misc/func_compound1.right b/shell/ash_test/ash-misc/func_compound1.right
new file mode 100644
index 000000000..01e79c32a
--- /dev/null
+++ b/shell/ash_test/ash-misc/func_compound1.right
@@ -0,0 +1,3 @@
11
22
33
diff --git a/shell/ash_test/ash-misc/func_compound1.tests b/shell/ash_test/ash-misc/func_compound1.tests
new file mode 100755
index 000000000..20c8bf18b
--- /dev/null
+++ b/shell/ash_test/ash-misc/func_compound1.tests
@@ -0,0 +1,4 @@
1f() for i in 1 2 3; do
2 echo $i
3done
4f
diff --git a/shell/ash_test/ash-parsing/starquoted3.right b/shell/ash_test/ash-parsing/starquoted3.right
new file mode 100644
index 000000000..fea246c14
--- /dev/null
+++ b/shell/ash_test/ash-parsing/starquoted3.right
@@ -0,0 +1,2 @@
1<a>
2<>
diff --git a/shell/ash_test/ash-parsing/starquoted3.tests b/shell/ash_test/ash-parsing/starquoted3.tests
new file mode 100755
index 000000000..8eefe4245
--- /dev/null
+++ b/shell/ash_test/ash-parsing/starquoted3.tests
@@ -0,0 +1 @@
set -- a ""; space=" "; printf "<%s>\n" "$@"$space
diff --git a/shell/ash_test/ash-quoting/bkslash_case2.right b/shell/ash_test/ash-quoting/bkslash_case2.right
new file mode 100644
index 000000000..8d2038bff
--- /dev/null
+++ b/shell/ash_test/ash-quoting/bkslash_case2.right
@@ -0,0 +1,3 @@
1ok1
2ok2
3Ok:0
diff --git a/shell/ash_test/ash-quoting/bkslash_case2.tests b/shell/ash_test/ash-quoting/bkslash_case2.tests
new file mode 100755
index 000000000..348ddc236
--- /dev/null
+++ b/shell/ash_test/ash-quoting/bkslash_case2.tests
@@ -0,0 +1,13 @@
1x='\abc'
2
3case "$x" in
4\\*) echo ok1;;
5*) echo BUG1;;
6esac
7
8case $x in
9\\*) echo ok2;;
10*) echo BUG2;;
11esac
12
13echo Ok:$?
diff --git a/shell/ash_test/ash-quoting/quote_in_varexp1.right b/shell/ash_test/ash-quoting/quote_in_varexp1.right
new file mode 100644
index 000000000..99a0aea7c
--- /dev/null
+++ b/shell/ash_test/ash-quoting/quote_in_varexp1.right
@@ -0,0 +1,2 @@
1''
2Ok:0
diff --git a/shell/ash_test/ash-quoting/quote_in_varexp1.tests b/shell/ash_test/ash-quoting/quote_in_varexp1.tests
new file mode 100755
index 000000000..1b97b0556
--- /dev/null
+++ b/shell/ash_test/ash-quoting/quote_in_varexp1.tests
@@ -0,0 +1,2 @@
1x="''''"; echo "${x#"${x+''}"''}"
2echo Ok:$?
diff --git a/shell/ash_test/ash-quoting/squote_in_varexp3.right b/shell/ash_test/ash-quoting/squote_in_varexp3.right
new file mode 100644
index 000000000..223b7836f
--- /dev/null
+++ b/shell/ash_test/ash-quoting/squote_in_varexp3.right
@@ -0,0 +1 @@
B
diff --git a/shell/ash_test/ash-quoting/squote_in_varexp3.tests b/shell/ash_test/ash-quoting/squote_in_varexp3.tests
new file mode 100755
index 000000000..028a88fd9
--- /dev/null
+++ b/shell/ash_test/ash-quoting/squote_in_varexp3.tests
@@ -0,0 +1 @@
x=\'B; echo "${x#\'}"
diff --git a/shell/ash_test/ash-redir/redir_exec1.right b/shell/ash_test/ash-redir/redir_exec1.right
new file mode 100644
index 000000000..d4393d10c
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir_exec1.right
@@ -0,0 +1,2 @@
1redir_exec1.tests: line 1: can't create /cant/be/created: nonexistent directory
2First
diff --git a/shell/ash_test/ash-redir/redir_exec1.tests b/shell/ash_test/ash-redir/redir_exec1.tests
new file mode 100755
index 000000000..290e1cb39
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir_exec1.tests
@@ -0,0 +1,2 @@
1v=`echo First >&2` exec >/cant/be/created
2echo One:$?
diff --git a/shell/ash_test/ash-vars/var_bash3.right b/shell/ash_test/ash-vars/var_bash3.right
index a97c850ea..8899d981c 100644
--- a/shell/ash_test/ash-vars/var_bash3.right
+++ b/shell/ash_test/ash-vars/var_bash3.right
@@ -1,6 +1,6 @@
11 a041#c 11 a041#c
22 a041#c 22 a041#c
33 a\041#c 33 a041#c
44 a\041#c 44 a\041#c
55 a\041#c 55 a\041#c
66 a\041#c 66 a\041#c
@@ -17,4 +17,4 @@
1717 a\tc 1717 a\tc
1818 a\tc 1818 a\tc
1919 atc 1919 atc
2020 a\tc 2020 atc
diff --git a/shell/ash_test/ash-vars/var_bash4.right b/shell/ash_test/ash-vars/var_bash4.right
index 0ef1bf661..9067e58e6 100644
--- a/shell/ash_test/ash-vars/var_bash4.right
+++ b/shell/ash_test/ash-vars/var_bash4.right
@@ -3,26 +3,26 @@ Replace str: _\\_\z_
3Pattern: single backslash and star: "replace literal star" 3Pattern: single backslash and star: "replace literal star"
4Unquoted: a_\_z_b\*c 4Unquoted: a_\_z_b\*c
5Unquoted =: a_\_z_b\*c 5Unquoted =: a_\_z_b\*c
6Quoted: a_\_\z_b\*c 6Quoted: a_\_z_b\*c
7Quoted =: a_\_\z_b\*c 7Quoted =: a_\_z_b\*c
8Pattern: double backslash and star: "replace backslash and everything after it" 8Pattern: double backslash and star: "replace backslash and everything after it"
9Unquoted: a*b_\_z_ 9Unquoted: a*b_\_z_
10Unquoted =: a*b_\_z_ 10Unquoted =: a*b_\_z_
11Quoted: a*b_\_\z_ 11Quoted: a*b_\_z_
12Quoted =: a*b_\_\z_ 12Quoted =: a*b_\_z_
13 13
14Source: a\bc 14Source: a\bc
15Replace str: _\\_\z_ 15Replace str: _\\_\z_
16Pattern: single backslash and b: "replace b" 16Pattern: single backslash and b: "replace b"
17Unquoted: a\_\_z_c 17Unquoted: a\_\_z_c
18Unquoted =: a\_\_z_c 18Unquoted =: a\_\_z_c
19Quoted: a\_\_\z_c 19Quoted: a\_\_z_c
20Quoted =: a\_\_\z_c 20Quoted =: a\_\_z_c
21Pattern: double backslash and b: "replace backslash and b" 21Pattern: double backslash and b: "replace backslash and b"
22Unquoted: a_\_z_c 22Unquoted: a_\_z_c
23Unquoted =: a_\_z_c 23Unquoted =: a_\_z_c
24Quoted: a_\_\z_c 24Quoted: a_\_z_c
25Quoted =: a_\_\z_c 25Quoted =: a_\_z_c
26 26
27Source: a\bc 27Source: a\bc
28Replace str: _\\_\z_ (as variable $s) 28Replace str: _\\_\z_ (as variable $s)
diff --git a/shell/ash_test/ash-vars/var_bash6.right b/shell/ash_test/ash-vars/var_bash6.right
index 63fc23df8..115ff8b04 100644
--- a/shell/ash_test/ash-vars/var_bash6.right
+++ b/shell/ash_test/ash-vars/var_bash6.right
@@ -1,5 +1,5 @@
1Expected Actual 1Expected Actual
2a*z : a*z 2a*z : a*z
3\z : \z 3z : z
4a1z a2z: a1z a2z 4a1z a2z: a1z a2z
5z : z 5z : z
diff --git a/shell/ash_test/ash-vars/var_bash6.tests b/shell/ash_test/ash-vars/var_bash6.tests
index cf2e4f020..686834177 100755
--- a/shell/ash_test/ash-vars/var_bash6.tests
+++ b/shell/ash_test/ash-vars/var_bash6.tests
@@ -3,7 +3,7 @@
3>a1z; >a2z; 3>a1z; >a2z;
4 echo 'Expected' 'Actual' 4 echo 'Expected' 'Actual'
5v='a bz'; echo 'a*z :' "${v/a*z/a*z}" 5v='a bz'; echo 'a*z :' "${v/a*z/a*z}"
6v='a bz'; echo '\z :' "${v/a*z/\z}" 6v='a bz'; echo 'z :' "${v/a*z/\z}"
7v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z} 7v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z}
8v='a bz'; echo 'z :' ${v/a*z/\z} 8v='a bz'; echo 'z :' ${v/a*z/\z}
9rm a1z a2z 9rm a1z a2z
diff --git a/shell/ash_test/ash-vars/var_bash7.right b/shell/ash_test/ash-vars/var_bash7.right
new file mode 100644
index 000000000..223b7836f
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash7.right
@@ -0,0 +1 @@
B
diff --git a/shell/ash_test/ash-vars/var_bash7.tests b/shell/ash_test/ash-vars/var_bash7.tests
new file mode 100755
index 000000000..c4ce03f7f
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash7.tests
@@ -0,0 +1 @@
x=AB; echo "${x#$'\x41'}"
diff --git a/shell/hush.c b/shell/hush.c
index 06fe0e405..d5ea3b21f 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -79,20 +79,6 @@
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 * In bash, export builtin is special, its arguments are assignments
83 * and therefore expansion of them should be "one-word" expansion:
84 * $ export i=`echo 'a b'` # export has one arg: "i=a b"
85 * compare with:
86 * $ ls i=`echo 'a b'` # ls has two args: "i=a" and "b"
87 * ls: cannot access i=a: No such file or directory
88 * ls: cannot access b: No such file or directory
89 * Note1: same applies to local builtin.
90 * Note2: bash 3.2.33(1) does this only if export word itself
91 * is not quoted:
92 * $ export i=`echo 'aaa bbb'`; echo "$i"
93 * aaa bbb
94 * $ "export" i=`echo 'aaa bbb'`; echo "$i"
95 * aaa
96 */ 82 */
97//config:config HUSH 83//config:config HUSH
98//config: bool "hush (64 kb)" 84//config: bool "hush (64 kb)"
@@ -326,13 +312,13 @@
326//kbuild:lib-$(CONFIG_BASH_IS_HUSH) += hush.o match.o shell_common.o 312//kbuild:lib-$(CONFIG_BASH_IS_HUSH) += hush.o match.o shell_common.o
327//kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o 313//kbuild:lib-$(CONFIG_HUSH_RANDOM_SUPPORT) += random.o
328 314
329/* -i (interactive) and -s (read stdin) are also accepted, 315/* -i (interactive) is also accepted,
330 * but currently do nothing, therefore aren't shown in help. 316 * but does nothing, therefore not shown in help.
331 * NOMMU-specific options are not meant to be used by users, 317 * NOMMU-specific options are not meant to be used by users,
332 * therefore we don't show them either. 318 * therefore we don't show them either.
333 */ 319 */
334//usage:#define hush_trivial_usage 320//usage:#define hush_trivial_usage
335//usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" 321//usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS] / -s [ARGS]]"
336//usage:#define hush_full_usage "\n\n" 322//usage:#define hush_full_usage "\n\n"
337//usage: "Unix shell interpreter" 323//usage: "Unix shell interpreter"
338 324
@@ -454,6 +440,7 @@
454#define debug_printf_redir(...) do {} while (0) 440#define debug_printf_redir(...) do {} while (0)
455#define debug_printf_list(...) do {} while (0) 441#define debug_printf_list(...) do {} while (0)
456#define debug_printf_subst(...) do {} while (0) 442#define debug_printf_subst(...) do {} while (0)
443#define debug_printf_prompt(...) do {} while (0)
457#define debug_printf_clean(...) do {} while (0) 444#define debug_printf_clean(...) do {} while (0)
458 445
459#define ERR_PTR ((void*)(long)1) 446#define ERR_PTR ((void*)(long)1)
@@ -489,7 +476,6 @@ static const char hush_version_str[] ALIGN1 = "HUSH_VERSION="BB_VER;
489 */ 476 */
490#if !BB_MMU 477#if !BB_MMU
491typedef struct nommu_save_t { 478typedef struct nommu_save_t {
492 char **new_env;
493 struct variable *old_vars; 479 struct variable *old_vars;
494 char **argv; 480 char **argv;
495 char **argv_from_re_execing; 481 char **argv_from_re_execing;
@@ -566,9 +552,6 @@ static const char *const assignment_flag[] = {
566 552
567typedef struct in_str { 553typedef struct in_str {
568 const char *p; 554 const char *p;
569#if ENABLE_HUSH_INTERACTIVE
570 smallint promptmode; /* 0: PS1, 1: PS2 */
571#endif
572 int peek_buf[2]; 555 int peek_buf[2];
573 int last_char; 556 int last_char;
574 FILE *file; 557 FILE *file;
@@ -623,15 +606,17 @@ typedef enum redir_type {
623 606
624struct command { 607struct command {
625 pid_t pid; /* 0 if exited */ 608 pid_t pid; /* 0 if exited */
626 int assignment_cnt; /* how many argv[i] are assignments? */ 609 unsigned assignment_cnt; /* how many argv[i] are assignments? */
627#if ENABLE_HUSH_LINENO_VAR 610#if ENABLE_HUSH_LINENO_VAR
628 unsigned lineno; 611 unsigned lineno;
629#endif 612#endif
630 smallint cmd_type; /* CMD_xxx */ 613 smallint cmd_type; /* CMD_xxx */
631#define CMD_NORMAL 0 614#define CMD_NORMAL 0
632#define CMD_SUBSHELL 1 615#define CMD_SUBSHELL 1
633#if BASH_TEST2 616#if BASH_TEST2 || ENABLE_HUSH_LOCAL || ENABLE_HUSH_EXPORT || ENABLE_HUSH_READONLY
634/* used for "[[ EXPR ]]" */ 617/* used for "[[ EXPR ]]", and to prevent word splitting and globbing in
618 * "export v=t*"
619 */
635# define CMD_SINGLEWORD_NOGLOB 2 620# define CMD_SINGLEWORD_NOGLOB 2
636#endif 621#endif
637#if ENABLE_HUSH_FUNCTIONS 622#if ENABLE_HUSH_FUNCTIONS
@@ -742,10 +727,8 @@ struct parse_context {
742struct variable { 727struct variable {
743 struct variable *next; 728 struct variable *next;
744 char *varstr; /* points to "name=" portion */ 729 char *varstr; /* points to "name=" portion */
745#if ENABLE_HUSH_LOCAL
746 unsigned func_nest_level;
747#endif
748 int max_len; /* if > 0, name is part of initial env; else name is malloced */ 730 int max_len; /* if > 0, name is part of initial env; else name is malloced */
731 uint16_t var_nest_level;
749 smallint flg_export; /* putenv should be done on this var */ 732 smallint flg_export; /* putenv should be done on this var */
750 smallint flg_read_only; 733 smallint flg_read_only;
751}; 734};
@@ -843,7 +826,7 @@ struct globals {
843 * _AND_ if we decided to act interactively */ 826 * _AND_ if we decided to act interactively */
844 int interactive_fd; 827 int interactive_fd;
845 const char *PS1; 828 const char *PS1;
846 const char *PS2; 829 IF_FEATURE_EDITING_FANCY_PROMPT(const char *PS2;)
847# define G_interactive_fd (G.interactive_fd) 830# define G_interactive_fd (G.interactive_fd)
848#else 831#else
849# define G_interactive_fd 0 832# define G_interactive_fd 0
@@ -891,6 +874,9 @@ struct globals {
891#else 874#else
892# define G_x_mode 0 875# define G_x_mode 0
893#endif 876#endif
877#if ENABLE_HUSH_INTERACTIVE
878 smallint promptmode; /* 0: PS1, 1: PS2 */
879#endif
894 smallint flag_SIGINT; 880 smallint flag_SIGINT;
895#if ENABLE_HUSH_LOOPS 881#if ENABLE_HUSH_LOOPS
896 smallint flag_break_continue; 882 smallint flag_break_continue;
@@ -906,8 +892,9 @@ struct globals {
906# define G_flag_return_in_progress 0 892# define G_flag_return_in_progress 0
907#endif 893#endif
908 smallint exiting; /* used to prevent EXIT trap recursion */ 894 smallint exiting; /* used to prevent EXIT trap recursion */
909 /* These four support $?, $#, and $1 */ 895 /* These support $?, $#, and $1 */
910 smalluint last_exitcode; 896 smalluint last_exitcode;
897 smalluint expand_exitcode;
911 smalluint last_bg_pid_exitcode; 898 smalluint last_bg_pid_exitcode;
912#if ENABLE_HUSH_SET 899#if ENABLE_HUSH_SET
913 /* are global_argv and global_argv[1..n] malloced? (note: not [0]) */ 900 /* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
@@ -933,12 +920,13 @@ struct globals {
933 const char *cwd; 920 const char *cwd;
934 struct variable *top_var; 921 struct variable *top_var;
935 char **expanded_assignments; 922 char **expanded_assignments;
923 struct variable **shadowed_vars_pp;
924 unsigned var_nest_level;
936#if ENABLE_HUSH_FUNCTIONS 925#if ENABLE_HUSH_FUNCTIONS
937 struct function *top_func;
938# if ENABLE_HUSH_LOCAL 926# if ENABLE_HUSH_LOCAL
939 struct variable **shadowed_vars_pp; 927 unsigned func_nest_level; /* solely to prevent "local v" in non-functions */
940 unsigned func_nest_level;
941# endif 928# endif
929 struct function *top_func;
942#endif 930#endif
943 /* Signal and trap handling */ 931 /* Signal and trap handling */
944#if ENABLE_HUSH_FAST 932#if ENABLE_HUSH_FAST
@@ -1261,6 +1249,10 @@ static const struct built_in_command bltins2[] = {
1261# define debug_printf_subst(...) (indent(), fdprintf(2, __VA_ARGS__)) 1249# define debug_printf_subst(...) (indent(), fdprintf(2, __VA_ARGS__))
1262#endif 1250#endif
1263 1251
1252#ifndef debug_printf_prompt
1253# define debug_printf_prompt(...) (indent(), fdprintf(2, __VA_ARGS__))
1254#endif
1255
1264#ifndef debug_printf_clean 1256#ifndef debug_printf_clean
1265# define debug_printf_clean(...) (indent(), fdprintf(2, __VA_ARGS__)) 1257# define debug_printf_clean(...) (indent(), fdprintf(2, __VA_ARGS__))
1266# define DEBUG_CLEAN 1 1258# define DEBUG_CLEAN 1
@@ -1407,7 +1399,7 @@ static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch)
1407#endif 1399#endif
1408 1400
1409 1401
1410#if ENABLE_HUSH_INTERACTIVE 1402#if ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_EDITING_FANCY_PROMPT
1411static void cmdedit_update_prompt(void); 1403static void cmdedit_update_prompt(void);
1412#else 1404#else
1413# define cmdedit_update_prompt() ((void)0) 1405# define cmdedit_update_prompt() ((void)0)
@@ -2136,40 +2128,58 @@ static const char* FAST_FUNC get_local_var_value(const char *name)
2136 return NULL; 2128 return NULL;
2137} 2129}
2138 2130
2131static void handle_changed_special_names(const char *name, unsigned name_len)
2132{
2133 if (ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_EDITING_FANCY_PROMPT
2134 && name_len == 3 && name[0] == 'P' && name[1] == 'S'
2135 ) {
2136 cmdedit_update_prompt();
2137 return;
2138 }
2139
2140 if ((ENABLE_HUSH_LINENO_VAR || ENABLE_HUSH_GETOPTS)
2141 && name_len == 6
2142 ) {
2143#if ENABLE_HUSH_LINENO_VAR
2144 if (strncmp(name, "LINENO", 6) == 0) {
2145 G.lineno_var = NULL;
2146 return;
2147 }
2148#endif
2149#if ENABLE_HUSH_GETOPTS
2150 if (strncmp(name, "OPTIND", 6) == 0) {
2151 G.getopt_count = 0;
2152 return;
2153 }
2154#endif
2155 }
2156}
2157
2139/* str holds "NAME=VAL" and is expected to be malloced. 2158/* str holds "NAME=VAL" and is expected to be malloced.
2140 * We take ownership of it. 2159 * We take ownership of it.
2141 */ 2160 */
2142#define SETFLAG_EXPORT (1 << 0) 2161#define SETFLAG_EXPORT (1 << 0)
2143#define SETFLAG_UNEXPORT (1 << 1) 2162#define SETFLAG_UNEXPORT (1 << 1)
2144#define SETFLAG_MAKE_RO (1 << 2) 2163#define SETFLAG_MAKE_RO (1 << 2)
2145#define SETFLAG_LOCAL_SHIFT 3 2164#define SETFLAG_VARLVL_SHIFT 3
2146static int set_local_var(char *str, unsigned flags) 2165static int set_local_var(char *str, unsigned flags)
2147{ 2166{
2148 struct variable **var_pp; 2167 struct variable **cur_pp;
2149 struct variable *cur; 2168 struct variable *cur;
2150 char *free_me = NULL; 2169 char *free_me = NULL;
2151 char *eq_sign; 2170 char *eq_sign;
2152 int name_len; 2171 int name_len;
2153 IF_HUSH_LOCAL(unsigned local_lvl = (flags >> SETFLAG_LOCAL_SHIFT);) 2172 unsigned local_lvl = (flags >> SETFLAG_VARLVL_SHIFT);
2154 2173
2155 eq_sign = strchr(str, '='); 2174 eq_sign = strchr(str, '=');
2156 if (!eq_sign) { /* not expected to ever happen? */ 2175 if (HUSH_DEBUG && !eq_sign)
2157 free(str); 2176 bb_error_msg_and_die("BUG in setvar");
2158 return -1;
2159 }
2160 2177
2161 name_len = eq_sign - str + 1; /* including '=' */ 2178 name_len = eq_sign - str + 1; /* including '=' */
2162#if ENABLE_HUSH_LINENO_VAR 2179 cur_pp = &G.top_var;
2163 if (G.lineno_var) { 2180 while ((cur = *cur_pp) != NULL) {
2164 if (name_len == 7 && strncmp("LINENO", str, 6) == 0)
2165 G.lineno_var = NULL;
2166 }
2167#endif
2168
2169 var_pp = &G.top_var;
2170 while ((cur = *var_pp) != NULL) {
2171 if (strncmp(cur->varstr, str, name_len) != 0) { 2181 if (strncmp(cur->varstr, str, name_len) != 0) {
2172 var_pp = &cur->next; 2182 cur_pp = &cur->next;
2173 continue; 2183 continue;
2174 } 2184 }
2175 2185
@@ -2187,15 +2197,7 @@ static int set_local_var(char *str, unsigned flags)
2187 unsetenv(str); 2197 unsetenv(str);
2188 *eq_sign = '='; 2198 *eq_sign = '=';
2189 } 2199 }
2190#if ENABLE_HUSH_LOCAL 2200 if (cur->var_nest_level < local_lvl) {
2191 if (cur->func_nest_level < local_lvl) {
2192 /* New variable is declared as local,
2193 * and existing one is global, or local
2194 * from enclosing function.
2195 * Remove and save old one: */
2196 *var_pp = cur->next;
2197 cur->next = *G.shadowed_vars_pp;
2198 *G.shadowed_vars_pp = cur;
2199 /* bash 3.2.33(1) and exported vars: 2201 /* bash 3.2.33(1) and exported vars:
2200 * # export z=z 2202 * # export z=z
2201 * # f() { local z=a; env | grep ^z; } 2203 * # f() { local z=a; env | grep ^z; }
@@ -2206,17 +2208,46 @@ static int set_local_var(char *str, unsigned flags)
2206 */ 2208 */
2207 if (cur->flg_export) 2209 if (cur->flg_export)
2208 flags |= SETFLAG_EXPORT; 2210 flags |= SETFLAG_EXPORT;
2211 /* New variable is local ("local VAR=VAL" or
2212 * "VAR=VAL cmd")
2213 * and existing one is global, or local
2214 * on a lower level that new one.
2215 * Remove it from global variable list:
2216 */
2217 *cur_pp = cur->next;
2218 if (G.shadowed_vars_pp) {
2219 /* Save in "shadowed" list */
2220 debug_printf_env("shadowing %s'%s'/%u by '%s'/%u\n",
2221 cur->flg_export ? "exported " : "",
2222 cur->varstr, cur->var_nest_level, str, local_lvl
2223 );
2224 cur->next = *G.shadowed_vars_pp;
2225 *G.shadowed_vars_pp = cur;
2226 } else {
2227 /* Came from pseudo_exec_argv(), no need to save: delete it */
2228 debug_printf_env("shadow-deleting %s'%s'/%u by '%s'/%u\n",
2229 cur->flg_export ? "exported " : "",
2230 cur->varstr, cur->var_nest_level, str, local_lvl
2231 );
2232 if (cur->max_len == 0) /* allocated "VAR=VAL"? */
2233 free_me = cur->varstr; /* then free it later */
2234 free(cur);
2235 }
2209 break; 2236 break;
2210 } 2237 }
2211#endif 2238
2212 if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) { 2239 if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
2240 debug_printf_env("assignement '%s' does not change anything\n", str);
2213 free_and_exp: 2241 free_and_exp:
2214 free(str); 2242 free(str);
2215 goto exp; 2243 goto exp;
2216 } 2244 }
2245
2246 /* Replace the value in the found "struct variable" */
2217 if (cur->max_len != 0) { 2247 if (cur->max_len != 0) {
2218 if (cur->max_len >= strlen(str)) { 2248 if (cur->max_len >= strnlen(str, cur->max_len + 1)) {
2219 /* This one is from startup env, reuse space */ 2249 /* This one is from startup env, reuse space */
2250 debug_printf_env("reusing startup env for '%s'\n", str);
2220 strcpy(cur->varstr, str); 2251 strcpy(cur->varstr, str);
2221 goto free_and_exp; 2252 goto free_and_exp;
2222 } 2253 }
@@ -2234,11 +2265,11 @@ static int set_local_var(char *str, unsigned flags)
2234 goto set_str_and_exp; 2265 goto set_str_and_exp;
2235 } 2266 }
2236 2267
2237 /* Not found - create new variable struct */ 2268 /* Not found or shadowed - create new variable struct */
2238 cur = xzalloc(sizeof(*cur)); 2269 cur = xzalloc(sizeof(*cur));
2239 IF_HUSH_LOCAL(cur->func_nest_level = local_lvl;) 2270 cur->var_nest_level = local_lvl;
2240 cur->next = *var_pp; 2271 cur->next = *cur_pp;
2241 *var_pp = cur; 2272 *cur_pp = cur;
2242 2273
2243 set_str_and_exp: 2274 set_str_and_exp:
2244 cur->varstr = str; 2275 cur->varstr = str;
@@ -2250,20 +2281,13 @@ static int set_local_var(char *str, unsigned flags)
2250#endif 2281#endif
2251 if (flags & SETFLAG_EXPORT) 2282 if (flags & SETFLAG_EXPORT)
2252 cur->flg_export = 1; 2283 cur->flg_export = 1;
2253 if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
2254 cmdedit_update_prompt();
2255#if ENABLE_HUSH_GETOPTS
2256 /* defoptindvar is a "OPTIND=..." constant string */
2257 if (strncmp(cur->varstr, defoptindvar, 7) == 0)
2258 G.getopt_count = 0;
2259#endif
2260 if (cur->flg_export) { 2284 if (cur->flg_export) {
2261 if (flags & SETFLAG_UNEXPORT) { 2285 if (flags & SETFLAG_UNEXPORT) {
2262 cur->flg_export = 0; 2286 cur->flg_export = 0;
2263 /* unsetenv was already done */ 2287 /* unsetenv was already done */
2264 } else { 2288 } else {
2265 int i; 2289 int i;
2266 debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr); 2290 debug_printf_env("%s: putenv '%s'/%u\n", __func__, cur->varstr, cur->var_nest_level);
2267 i = putenv(cur->varstr); 2291 i = putenv(cur->varstr);
2268 /* only now we can free old exported malloced string */ 2292 /* only now we can free old exported malloced string */
2269 free(free_me); 2293 free(free_me);
@@ -2271,6 +2295,9 @@ static int set_local_var(char *str, unsigned flags)
2271 } 2295 }
2272 } 2296 }
2273 free(free_me); 2297 free(free_me);
2298
2299 handle_changed_special_names(cur->varstr, name_len - 1);
2300
2274 return 0; 2301 return 0;
2275} 2302}
2276 2303
@@ -2283,39 +2310,33 @@ static void set_pwd_var(unsigned flag)
2283static int unset_local_var_len(const char *name, int name_len) 2310static int unset_local_var_len(const char *name, int name_len)
2284{ 2311{
2285 struct variable *cur; 2312 struct variable *cur;
2286 struct variable **var_pp; 2313 struct variable **cur_pp;
2287
2288 if (!name)
2289 return EXIT_SUCCESS;
2290 2314
2291#if ENABLE_HUSH_GETOPTS 2315 cur_pp = &G.top_var;
2292 if (name_len == 6 && strncmp(name, "OPTIND", 6) == 0) 2316 while ((cur = *cur_pp) != NULL) {
2293 G.getopt_count = 0; 2317 if (strncmp(cur->varstr, name, name_len) == 0
2294#endif 2318 && cur->varstr[name_len] == '='
2295#if ENABLE_HUSH_LINENO_VAR 2319 ) {
2296 if (name_len == 6 && G.lineno_var && strncmp(name, "LINENO", 6) == 0)
2297 G.lineno_var = NULL;
2298#endif
2299
2300 var_pp = &G.top_var;
2301 while ((cur = *var_pp) != NULL) {
2302 if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') {
2303 if (cur->flg_read_only) { 2320 if (cur->flg_read_only) {
2304 bb_error_msg("%s: readonly variable", name); 2321 bb_error_msg("%s: readonly variable", name);
2305 return EXIT_FAILURE; 2322 return EXIT_FAILURE;
2306 } 2323 }
2307 *var_pp = cur->next; 2324
2325 *cur_pp = cur->next;
2308 debug_printf_env("%s: unsetenv '%s'\n", __func__, cur->varstr); 2326 debug_printf_env("%s: unsetenv '%s'\n", __func__, cur->varstr);
2309 bb_unsetenv(cur->varstr); 2327 bb_unsetenv(cur->varstr);
2310 if (name_len == 3 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
2311 cmdedit_update_prompt();
2312 if (!cur->max_len) 2328 if (!cur->max_len)
2313 free(cur->varstr); 2329 free(cur->varstr);
2314 free(cur); 2330 free(cur);
2315 return EXIT_SUCCESS; 2331
2332 break;
2316 } 2333 }
2317 var_pp = &cur->next; 2334 cur_pp = &cur->next;
2318 } 2335 }
2336
2337 /* Handle "unset PS1" et al even if did not find the variable to unset */
2338 handle_changed_special_names(name, name_len);
2339
2319 return EXIT_SUCCESS; 2340 return EXIT_SUCCESS;
2320} 2341}
2321 2342
@@ -2326,21 +2347,6 @@ static int unset_local_var(const char *name)
2326} 2347}
2327#endif 2348#endif
2328 2349
2329static void unset_vars(char **strings)
2330{
2331 char **v;
2332
2333 if (!strings)
2334 return;
2335 v = strings;
2336 while (*v) {
2337 const char *eq = strchrnul(*v, '=');
2338 unset_local_var_len(*v, (int)(eq - *v));
2339 v++;
2340 }
2341 free(strings);
2342}
2343
2344#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS 2350#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS
2345static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) 2351static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
2346{ 2352{
@@ -2362,22 +2368,27 @@ static void add_vars(struct variable *var)
2362 var->next = G.top_var; 2368 var->next = G.top_var;
2363 G.top_var = var; 2369 G.top_var = var;
2364 if (var->flg_export) { 2370 if (var->flg_export) {
2365 debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr); 2371 debug_printf_env("%s: restoring exported '%s'/%u\n", __func__, var->varstr, var->var_nest_level);
2366 putenv(var->varstr); 2372 putenv(var->varstr);
2367 } else { 2373 } else {
2368 debug_printf_env("%s: restoring variable '%s'\n", __func__, var->varstr); 2374 debug_printf_env("%s: restoring variable '%s'/%u\n", __func__, var->varstr, var->var_nest_level);
2369 } 2375 }
2370 var = next; 2376 var = next;
2371 } 2377 }
2372} 2378}
2373 2379
2374static struct variable *set_vars_and_save_old(char **strings) 2380/* We put strings[i] into variable table and possibly putenv them.
2381 * If variable is read only, we can free the strings[i]
2382 * which attempts to overwrite it.
2383 * The strings[] vector itself is freed.
2384 */
2385static void set_vars_and_save_old(char **strings)
2375{ 2386{
2376 char **s; 2387 char **s;
2377 struct variable *old = NULL;
2378 2388
2379 if (!strings) 2389 if (!strings)
2380 return old; 2390 return;
2391
2381 s = strings; 2392 s = strings;
2382 while (*s) { 2393 while (*s) {
2383 struct variable *var_p; 2394 struct variable *var_p;
@@ -2404,19 +2415,20 @@ static struct variable *set_vars_and_save_old(char **strings)
2404 do { *p = p[1]; p++; } while (*p); 2415 do { *p = p[1]; p++; } while (*p);
2405 goto next; 2416 goto next;
2406 } 2417 }
2407 /* Remove variable from global linked list */ 2418 /* below, set_local_var() with nest level will
2408 debug_printf_env("%s: removing '%s'\n", __func__, var_p->varstr); 2419 * "shadow" (remove) this variable from
2409 *var_pp = var_p->next; 2420 * global linked list.
2410 /* Add it to returned list */ 2421 */
2411 var_p->next = old;
2412 old = var_p;
2413 } 2422 }
2414 set_local_var(*s, SETFLAG_EXPORT); 2423 //bb_error_msg("G.var_nest_level:%d", G.var_nest_level);
2424 set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT);
2425 } else if (HUSH_DEBUG) {
2426 bb_error_msg_and_die("BUG in varexp4");
2415 } 2427 }
2416 next:
2417 s++; 2428 s++;
2429 next: ;
2418 } 2430 }
2419 return old; 2431 free(strings);
2420} 2432}
2421 2433
2422 2434
@@ -2452,36 +2464,36 @@ static void reinit_unicode_for_hush(void)
2452 * \ 2464 * \
2453 * It exercises a lot of corner cases. 2465 * It exercises a lot of corner cases.
2454 */ 2466 */
2467# if ENABLE_FEATURE_EDITING_FANCY_PROMPT
2455static void cmdedit_update_prompt(void) 2468static void cmdedit_update_prompt(void)
2456{ 2469{
2457 if (ENABLE_FEATURE_EDITING_FANCY_PROMPT) { 2470 G.PS1 = get_local_var_value("PS1");
2458 G.PS1 = get_local_var_value("PS1"); 2471 if (G.PS1 == NULL)
2459 if (G.PS1 == NULL) 2472 G.PS1 = "";
2460 G.PS1 = "\\w \\$ "; 2473 G.PS2 = get_local_var_value("PS2");
2461 G.PS2 = get_local_var_value("PS2");
2462 } else {
2463 G.PS1 = NULL;
2464 }
2465 if (G.PS2 == NULL) 2474 if (G.PS2 == NULL)
2466 G.PS2 = "> "; 2475 G.PS2 = "";
2467} 2476}
2468static const char *setup_prompt_string(int promptmode) 2477# endif
2478static const char *setup_prompt_string(void)
2469{ 2479{
2470 const char *prompt_str; 2480 const char *prompt_str;
2471 debug_printf("setup_prompt_string %d ", promptmode); 2481
2472 if (!ENABLE_FEATURE_EDITING_FANCY_PROMPT) { 2482 debug_printf_prompt("%s promptmode:%d\n", __func__, G.promptmode);
2473 /* Set up the prompt */ 2483
2474 if (promptmode == 0) { /* PS1 */ 2484 IF_FEATURE_EDITING_FANCY_PROMPT( prompt_str = G.PS2;)
2485 IF_NOT_FEATURE_EDITING_FANCY_PROMPT(prompt_str = "> ";)
2486 if (G.promptmode == 0) { /* PS1 */
2487 if (!ENABLE_FEATURE_EDITING_FANCY_PROMPT) {
2488 /* No fancy prompts supported, (re)generate "CURDIR $ " by hand */
2475 free((char*)G.PS1); 2489 free((char*)G.PS1);
2476 /* bash uses $PWD value, even if it is set by user. 2490 /* bash uses $PWD value, even if it is set by user.
2477 * It uses current dir only if PWD is unset. 2491 * It uses current dir only if PWD is unset.
2478 * We always use current dir. */ 2492 * We always use current dir. */
2479 G.PS1 = xasprintf("%s %c ", get_cwd(0), (geteuid() != 0) ? '$' : '#'); 2493 G.PS1 = xasprintf("%s %c ", get_cwd(0), (geteuid() != 0) ? '$' : '#');
2480 prompt_str = G.PS1; 2494 }
2481 } else 2495 prompt_str = G.PS1;
2482 prompt_str = G.PS2; 2496 }
2483 } else
2484 prompt_str = (promptmode == 0) ? G.PS1 : G.PS2;
2485 debug_printf("prompt_str '%s'\n", prompt_str); 2497 debug_printf("prompt_str '%s'\n", prompt_str);
2486 return prompt_str; 2498 return prompt_str;
2487} 2499}
@@ -2490,7 +2502,7 @@ static int get_user_input(struct in_str *i)
2490 int r; 2502 int r;
2491 const char *prompt_str; 2503 const char *prompt_str;
2492 2504
2493 prompt_str = setup_prompt_string(i->promptmode); 2505 prompt_str = setup_prompt_string();
2494# if ENABLE_FEATURE_EDITING 2506# if ENABLE_FEATURE_EDITING
2495 for (;;) { 2507 for (;;) {
2496 reinit_unicode_for_hush(); 2508 reinit_unicode_for_hush();
@@ -2560,7 +2572,8 @@ static int fgetc_interactive(struct in_str *i)
2560 if (G_interactive_fd && i->file == stdin) { 2572 if (G_interactive_fd && i->file == stdin) {
2561 /* Returns first char (or EOF), the rest is in i->p[] */ 2573 /* Returns first char (or EOF), the rest is in i->p[] */
2562 ch = get_user_input(i); 2574 ch = get_user_input(i);
2563 i->promptmode = 1; /* PS2 */ 2575 G.promptmode = 1; /* PS2 */
2576 debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode);
2564 } else { 2577 } else {
2565 /* Not stdin: script file, sourced file, etc */ 2578 /* Not stdin: script file, sourced file, etc */
2566 do ch = fgetc(i->file); while (ch == '\0'); 2579 do ch = fgetc(i->file); while (ch == '\0');
@@ -2733,7 +2746,6 @@ static int i_peek_and_eat_bkslash_nl(struct in_str *input)
2733static void setup_file_in_str(struct in_str *i, FILE *f) 2746static void setup_file_in_str(struct in_str *i, FILE *f)
2734{ 2747{
2735 memset(i, 0, sizeof(*i)); 2748 memset(i, 0, sizeof(*i));
2736 /* i->promptmode = 0; - PS1 (memset did it) */
2737 i->file = f; 2749 i->file = f;
2738 /* i->p = NULL; */ 2750 /* i->p = NULL; */
2739} 2751}
@@ -2741,7 +2753,6 @@ static void setup_file_in_str(struct in_str *i, FILE *f)
2741static void setup_string_in_str(struct in_str *i, const char *s) 2753static void setup_string_in_str(struct in_str *i, const char *s)
2742{ 2754{
2743 memset(i, 0, sizeof(*i)); 2755 memset(i, 0, sizeof(*i));
2744 /* i->promptmode = 0; - PS1 (memset did it) */
2745 /*i->file = NULL */; 2756 /*i->file = NULL */;
2746 i->p = s; 2757 i->p = s;
2747} 2758}
@@ -3933,14 +3944,37 @@ static int done_word(o_string *word, struct parse_context *ctx)
3933 (ctx->ctx_res_w == RES_SNTX)); 3944 (ctx->ctx_res_w == RES_SNTX));
3934 return (ctx->ctx_res_w == RES_SNTX); 3945 return (ctx->ctx_res_w == RES_SNTX);
3935 } 3946 }
3936# if BASH_TEST2 3947# if defined(CMD_SINGLEWORD_NOGLOB)
3937 if (strcmp(word->data, "[[") == 0) { 3948 if (0
3949# if BASH_TEST2
3950 || strcmp(word->data, "[[") == 0
3951# endif
3952 /* In bash, local/export/readonly are special, args
3953 * are assignments and therefore expansion of them
3954 * should be "one-word" expansion:
3955 * $ export i=`echo 'a b'` # one arg: "i=a b"
3956 * compare with:
3957 * $ ls i=`echo 'a b'` # two args: "i=a" and "b"
3958 * ls: cannot access i=a: No such file or directory
3959 * ls: cannot access b: No such file or directory
3960 * Note: bash 3.2.33(1) does this only if export word
3961 * itself is not quoted:
3962 * $ export i=`echo 'aaa bbb'`; echo "$i"
3963 * aaa bbb
3964 * $ "export" i=`echo 'aaa bbb'`; echo "$i"
3965 * aaa
3966 */
3967 IF_HUSH_LOCAL( || strcmp(word->data, "local") == 0)
3968 IF_HUSH_EXPORT( || strcmp(word->data, "export") == 0)
3969 IF_HUSH_READONLY( || strcmp(word->data, "readonly") == 0)
3970 ) {
3938 command->cmd_type = CMD_SINGLEWORD_NOGLOB; 3971 command->cmd_type = CMD_SINGLEWORD_NOGLOB;
3939 } 3972 }
3940 /* fall through */ 3973 /* fall through */
3941# endif 3974# endif
3942 } 3975 }
3943#endif 3976#endif /* HAS_KEYWORDS */
3977
3944 if (command->group) { 3978 if (command->group) {
3945 /* "{ echo foo; } echo bar" - bad */ 3979 /* "{ echo foo; } echo bar" - bad */
3946 syntax_error_at(word->data); 3980 syntax_error_at(word->data);
@@ -4240,7 +4274,6 @@ static int fetch_heredocs(int heredoc_cnt, struct parse_context *ctx, struct in_
4240 4274
4241 redir->rd_type = REDIRECT_HEREDOC2; 4275 redir->rd_type = REDIRECT_HEREDOC2;
4242 /* redir->rd_dup is (ab)used to indicate <<- */ 4276 /* redir->rd_dup is (ab)used to indicate <<- */
4243bb_error_msg("redir->rd_filename:'%s'", redir->rd_filename);
4244 p = fetch_till_str(&ctx->as_string, input, 4277 p = fetch_till_str(&ctx->as_string, input,
4245 redir->rd_filename, redir->rd_dup); 4278 redir->rd_filename, redir->rd_dup);
4246 if (!p) { 4279 if (!p) {
@@ -4286,6 +4319,11 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
4286 /* dest contains characters seen prior to ( or {. 4319 /* dest contains characters seen prior to ( or {.
4287 * Typically it's empty, but for function defs, 4320 * Typically it's empty, but for function defs,
4288 * it contains function name (without '()'). */ 4321 * it contains function name (without '()'). */
4322#if BB_MMU
4323# define as_string NULL
4324#else
4325 char *as_string = NULL;
4326#endif
4289 struct pipe *pipe_list; 4327 struct pipe *pipe_list;
4290 int endch; 4328 int endch;
4291 struct command *command = ctx->command; 4329 struct command *command = ctx->command;
@@ -4314,7 +4352,7 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
4314 do 4352 do
4315 ch = i_getch(input); 4353 ch = i_getch(input);
4316 while (ch == ' ' || ch == '\t' || ch == '\n'); 4354 while (ch == ' ' || ch == '\t' || ch == '\n');
4317 if (ch != '{') { 4355 if (ch != '{' && ch != '(') {
4318 syntax_error_unexpected_ch(ch); 4356 syntax_error_unexpected_ch(ch);
4319 return 1; 4357 return 1;
4320 } 4358 }
@@ -4336,13 +4374,13 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
4336 } 4374 }
4337#endif 4375#endif
4338 4376
4339#if ENABLE_HUSH_FUNCTIONS 4377 IF_HUSH_FUNCTIONS(skip:)
4340 skip: 4378
4341#endif
4342 endch = '}'; 4379 endch = '}';
4343 if (ch == '(') { 4380 if (ch == '(') {
4344 endch = ')'; 4381 endch = ')';
4345 command->cmd_type = CMD_SUBSHELL; 4382 IF_HUSH_FUNCTIONS(if (command->cmd_type != CMD_FUNCDEF))
4383 command->cmd_type = CMD_SUBSHELL;
4346 } else { 4384 } else {
4347 /* bash does not allow "{echo...", requires whitespace */ 4385 /* bash does not allow "{echo...", requires whitespace */
4348 ch = i_peek(input); 4386 ch = i_peek(input);
@@ -4358,38 +4396,54 @@ static int parse_group(o_string *dest, struct parse_context *ctx,
4358 } 4396 }
4359 } 4397 }
4360 4398
4361 { 4399 pipe_list = parse_stream(&as_string, input, endch);
4362#if BB_MMU
4363# define as_string NULL
4364#else
4365 char *as_string = NULL;
4366#endif
4367 pipe_list = parse_stream(&as_string, input, endch);
4368#if !BB_MMU 4400#if !BB_MMU
4369 if (as_string) 4401 if (as_string)
4370 o_addstr(&ctx->as_string, as_string); 4402 o_addstr(&ctx->as_string, as_string);
4371#endif 4403#endif
4372 /* empty ()/{} or parse error? */ 4404
4373 if (!pipe_list || pipe_list == ERR_PTR) { 4405 /* empty ()/{} or parse error? */
4374 /* parse_stream already emitted error msg */ 4406 if (!pipe_list || pipe_list == ERR_PTR) {
4375 if (!BB_MMU) 4407 /* parse_stream already emitted error msg */
4376 free(as_string); 4408 if (!BB_MMU)
4377 debug_printf_parse("parse_group return 1: " 4409 free(as_string);
4378 "parse_stream returned %p\n", pipe_list); 4410 debug_printf_parse("parse_group return 1: "
4379 return 1; 4411 "parse_stream returned %p\n", pipe_list);
4380 } 4412 return 1;
4381 command->group = pipe_list; 4413 }
4382#if !BB_MMU 4414#if !BB_MMU
4383 as_string[strlen(as_string) - 1] = '\0'; /* plink ')' or '}' */ 4415 as_string[strlen(as_string) - 1] = '\0'; /* plink ')' or '}' */
4384 command->group_as_string = as_string; 4416 command->group_as_string = as_string;
4385 debug_printf_parse("end of group, remembering as:'%s'\n", 4417 debug_printf_parse("end of group, remembering as:'%s'\n",
4386 command->group_as_string); 4418 command->group_as_string);
4387#endif 4419#endif
4388#undef as_string 4420
4421#if ENABLE_HUSH_FUNCTIONS
4422 /* Convert "f() (cmds)" to "f() {(cmds)}" */
4423 if (command->cmd_type == CMD_FUNCDEF && endch == ')') {
4424 struct command *cmd2;
4425
4426 cmd2 = xzalloc(sizeof(*cmd2));
4427 cmd2->cmd_type = CMD_SUBSHELL;
4428 cmd2->group = pipe_list;
4429# if !BB_MMU
4430//UNTESTED!
4431 cmd2->group_as_string = command->group_as_string;
4432 command->group_as_string = xasprintf("(%s)", command->group_as_string);
4433# endif
4434
4435 pipe_list = new_pipe();
4436 pipe_list->cmds = cmd2;
4437 pipe_list->num_cmds = 1;
4389 } 4438 }
4439#endif
4440
4441 command->group = pipe_list;
4442
4390 debug_printf_parse("parse_group return 0\n"); 4443 debug_printf_parse("parse_group return 0\n");
4391 return 0; 4444 return 0;
4392 /* command remains "open", available for possible redirects */ 4445 /* command remains "open", available for possible redirects */
4446#undef as_string
4393} 4447}
4394 4448
4395#if ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS 4449#if ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS
@@ -4499,6 +4553,9 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign
4499# endif 4553# endif
4500 end_ch &= (DOUBLE_CLOSE_CHAR_FLAG - 1); 4554 end_ch &= (DOUBLE_CLOSE_CHAR_FLAG - 1);
4501 4555
4556 G.promptmode = 1; /* PS2 */
4557 debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode);
4558
4502 while (1) { 4559 while (1) {
4503 ch = i_getch(input); 4560 ch = i_getch(input);
4504 if (ch == EOF) { 4561 if (ch == EOF) {
@@ -4564,6 +4621,7 @@ static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsign
4564 continue; 4621 continue;
4565 } 4622 }
4566 } 4623 }
4624 debug_printf_parse("%s return '%s' ch:'%c'\n", __func__, dest->data, ch);
4567 return ch; 4625 return ch;
4568} 4626}
4569#endif /* ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS */ 4627#endif /* ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS */
@@ -5477,7 +5535,7 @@ static struct pipe *parse_stream(char **pstring,
5477 goto parse_error2; 5535 goto parse_error2;
5478 default: 5536 default:
5479 if (HUSH_DEBUG) 5537 if (HUSH_DEBUG)
5480 bb_error_msg_and_die("BUG: unexpected %c\n", ch); 5538 bb_error_msg_and_die("BUG: unexpected %c", ch);
5481 } 5539 }
5482 } /* while (1) */ 5540 } /* while (1) */
5483 5541
@@ -5916,11 +5974,11 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5916 /* pattern uses non-standard expansion. 5974 /* pattern uses non-standard expansion.
5917 * repl should be unbackslashed and globbed 5975 * repl should be unbackslashed and globbed
5918 * by the usual expansion rules: 5976 * by the usual expansion rules:
5919 * >az; >bz; 5977 * >az >bz
5920 * v='a bz'; echo "${v/a*z/a*z}" prints "a*z" 5978 * v='a bz'; echo "${v/a*z/a*z}" #prints "a*z"
5921 * v='a bz'; echo "${v/a*z/\z}" prints "\z" 5979 * v='a bz'; echo "${v/a*z/\z}" #prints "z"
5922 * v='a bz'; echo ${v/a*z/a*z} prints "az" 5980 * v='a bz'; echo ${v/a*z/a*z} #prints "az"
5923 * v='a bz'; echo ${v/a*z/\z} prints "z" 5981 * v='a bz'; echo ${v/a*z/\z} #prints "z"
5924 * (note that a*z _pattern_ is never globbed!) 5982 * (note that a*z _pattern_ is never globbed!)
5925 */ 5983 */
5926 char *pattern, *repl, *t; 5984 char *pattern, *repl, *t;
@@ -5932,7 +5990,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5932 exp_word = p; 5990 exp_word = p;
5933 p = strchr(p, SPECIAL_VAR_SYMBOL); 5991 p = strchr(p, SPECIAL_VAR_SYMBOL);
5934 *p = '\0'; 5992 *p = '\0';
5935 repl = encode_then_expand_string(exp_word, /*process_bkslash:*/ arg0 & 0x80, /*unbackslash:*/ 1); 5993 repl = encode_then_expand_string(exp_word, /*process_bkslash:*/ 0, /*unbackslash:*/ 1);
5936 debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl); 5994 debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl);
5937 /* HACK ALERT. We depend here on the fact that 5995 /* HACK ALERT. We depend here on the fact that
5938 * G.global_argv and results of utoa and get_local_var_value 5996 * G.global_argv and results of utoa and get_local_var_value
@@ -6199,6 +6257,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
6199 * and $IFS-split */ 6257 * and $IFS-split */
6200 debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch); 6258 debug_printf_subst("SUBST '%s' first_ch %x\n", arg, first_ch);
6201 G.last_exitcode = process_command_subs(&subst_result, arg); 6259 G.last_exitcode = process_command_subs(&subst_result, arg);
6260 G.expand_exitcode = G.last_exitcode;
6202 debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data); 6261 debug_printf_subst("SUBST RES:%d '%s'\n", G.last_exitcode, subst_result.data);
6203 val = subst_result.data; 6262 val = subst_result.data;
6204 goto store_val; 6263 goto store_val;
@@ -6300,7 +6359,7 @@ static char **expand_strvec_to_strvec(char **argv)
6300 return expand_variables(argv, EXP_FLAG_GLOB | EXP_FLAG_ESC_GLOB_CHARS); 6359 return expand_variables(argv, EXP_FLAG_GLOB | EXP_FLAG_ESC_GLOB_CHARS);
6301} 6360}
6302 6361
6303#if BASH_TEST2 6362#if defined(CMD_SINGLEWORD_NOGLOB)
6304static char **expand_strvec_to_strvec_singleword_noglob(char **argv) 6363static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
6305{ 6364{
6306 return expand_variables(argv, EXP_FLAG_SINGLEWORD); 6365 return expand_variables(argv, EXP_FLAG_SINGLEWORD);
@@ -6308,7 +6367,7 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
6308#endif 6367#endif
6309 6368
6310/* Used for expansion of right hand of assignments, 6369/* Used for expansion of right hand of assignments,
6311 * $((...)), heredocs, variable espansion parts. 6370 * $((...)), heredocs, variable expansion parts.
6312 * 6371 *
6313 * NB: should NOT do globbing! 6372 * NB: should NOT do globbing!
6314 * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" 6373 * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*"
@@ -6348,7 +6407,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash)
6348 return (char*)list; 6407 return (char*)list;
6349} 6408}
6350 6409
6351#if ENABLE_HUSH_CASE 6410#if 0
6352static char* expand_strvec_to_string(char **argv) 6411static char* expand_strvec_to_string(char **argv)
6353{ 6412{
6354 char **list; 6413 char **list;
@@ -6619,8 +6678,10 @@ static void parse_and_run_stream(struct in_str *inp, int end_trigger)
6619 struct pipe *pipe_list; 6678 struct pipe *pipe_list;
6620 6679
6621#if ENABLE_HUSH_INTERACTIVE 6680#if ENABLE_HUSH_INTERACTIVE
6622 if (end_trigger == ';') 6681 if (end_trigger == ';') {
6623 inp->promptmode = 0; /* PS1 */ 6682 G.promptmode = 0; /* PS1 */
6683 debug_printf_prompt("%s promptmode=%d\n", __func__, G.promptmode);
6684 }
6624#endif 6685#endif
6625 pipe_list = parse_stream(NULL, inp, end_trigger); 6686 pipe_list = parse_stream(NULL, inp, end_trigger);
6626 if (!pipe_list || pipe_list == ERR_PTR) { /* EOF/error */ 6687 if (!pipe_list || pipe_list == ERR_PTR) { /* EOF/error */
@@ -7329,8 +7390,9 @@ static void exec_function(char ***to_free,
7329// for "more correctness" we might want to close those extra fds here: 7390// for "more correctness" we might want to close those extra fds here:
7330//? close_saved_fds_and_FILE_fds(); 7391//? close_saved_fds_and_FILE_fds();
7331 7392
7332 /* "we are in function, ok to use return" */ 7393 /* "we are in a function, ok to use return" */
7333 G_flag_return_in_progress = -1; 7394 G_flag_return_in_progress = -1;
7395 G.var_nest_level++;
7334 IF_HUSH_LOCAL(G.func_nest_level++;) 7396 IF_HUSH_LOCAL(G.func_nest_level++;)
7335 7397
7336 /* On MMU, funcp->body is always non-NULL */ 7398 /* On MMU, funcp->body is always non-NULL */
@@ -7350,6 +7412,53 @@ static void exec_function(char ***to_free,
7350# endif 7412# endif
7351} 7413}
7352 7414
7415static void enter_var_nest_level(void)
7416{
7417 G.var_nest_level++;
7418 debug_printf_env("var_nest_level++ %u\n", G.var_nest_level);
7419
7420 /* Try: f() { echo -n .; f; }; f
7421 * struct variable::var_nest_level is uint16_t,
7422 * thus limiting recursion to < 2^16.
7423 * In any case, with 8 Mbyte stack SEGV happens
7424 * not too long after 2^16 recursions anyway.
7425 */
7426 if (G.var_nest_level > 0xff00)
7427 bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level);
7428}
7429
7430static void leave_var_nest_level(void)
7431{
7432 struct variable *cur;
7433 struct variable **cur_pp;
7434
7435 cur_pp = &G.top_var;
7436 while ((cur = *cur_pp) != NULL) {
7437 if (cur->var_nest_level < G.var_nest_level) {
7438 cur_pp = &cur->next;
7439 continue;
7440 }
7441 /* Unexport */
7442 if (cur->flg_export) {
7443 debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level);
7444 bb_unsetenv(cur->varstr);
7445 }
7446 /* Remove from global list */
7447 *cur_pp = cur->next;
7448 /* Free */
7449 if (!cur->max_len) {
7450 debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level);
7451 free(cur->varstr);
7452 }
7453 free(cur);
7454 }
7455
7456 G.var_nest_level--;
7457 debug_printf_env("var_nest_level-- %u\n", G.var_nest_level);
7458 if (HUSH_DEBUG && (int)G.var_nest_level < 0)
7459 bb_error_msg_and_die("BUG: nesting underflow");
7460}
7461
7353static int run_function(const struct function *funcp, char **argv) 7462static int run_function(const struct function *funcp, char **argv)
7354{ 7463{
7355 int rc; 7464 int rc;
@@ -7358,9 +7467,12 @@ static int run_function(const struct function *funcp, char **argv)
7358 7467
7359 save_and_replace_G_args(&sv, argv); 7468 save_and_replace_G_args(&sv, argv);
7360 7469
7361 /* "we are in function, ok to use return" */ 7470 /* "We are in function, ok to use return" */
7362 sv_flg = G_flag_return_in_progress; 7471 sv_flg = G_flag_return_in_progress;
7363 G_flag_return_in_progress = -1; 7472 G_flag_return_in_progress = -1;
7473
7474 /* Make "local" variables properly shadow previous ones */
7475 IF_HUSH_LOCAL(enter_var_nest_level();)
7364 IF_HUSH_LOCAL(G.func_nest_level++;) 7476 IF_HUSH_LOCAL(G.func_nest_level++;)
7365 7477
7366 /* On MMU, funcp->body is always non-NULL */ 7478 /* On MMU, funcp->body is always non-NULL */
@@ -7375,30 +7487,9 @@ static int run_function(const struct function *funcp, char **argv)
7375 rc = run_list(funcp->body); 7487 rc = run_list(funcp->body);
7376 } 7488 }
7377 7489
7378# if ENABLE_HUSH_LOCAL 7490 IF_HUSH_LOCAL(G.func_nest_level--;)
7379 { 7491 IF_HUSH_LOCAL(leave_var_nest_level();)
7380 struct variable *var;
7381 struct variable **var_pp;
7382 7492
7383 var_pp = &G.top_var;
7384 while ((var = *var_pp) != NULL) {
7385 if (var->func_nest_level < G.func_nest_level) {
7386 var_pp = &var->next;
7387 continue;
7388 }
7389 /* Unexport */
7390 if (var->flg_export)
7391 bb_unsetenv(var->varstr);
7392 /* Remove from global list */
7393 *var_pp = var->next;
7394 /* Free */
7395 if (!var->max_len)
7396 free(var->varstr);
7397 free(var);
7398 }
7399 G.func_nest_level--;
7400 }
7401# endif
7402 G_flag_return_in_progress = sv_flg; 7493 G_flag_return_in_progress = sv_flg;
7403 7494
7404 restore_G_args(&sv, argv); 7495 restore_G_args(&sv, argv);
@@ -7535,10 +7626,10 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7535 char **argv_expanded) 7626 char **argv_expanded)
7536{ 7627{
7537 const struct built_in_command *x; 7628 const struct built_in_command *x;
7629 struct variable **sv_shadowed;
7538 char **new_env; 7630 char **new_env;
7539#if ENABLE_HUSH_COMMAND 7631 IF_HUSH_COMMAND(char opt_vV = 0;)
7540 char opt_vV = 0; 7632 IF_HUSH_FUNCTIONS(const struct function *funcp;)
7541#endif
7542 7633
7543 new_env = expand_assignments(argv, assignment_cnt); 7634 new_env = expand_assignments(argv, assignment_cnt);
7544 dump_cmd_in_x_mode(new_env); 7635 dump_cmd_in_x_mode(new_env);
@@ -7552,15 +7643,14 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7552 _exit(EXIT_SUCCESS); 7643 _exit(EXIT_SUCCESS);
7553 } 7644 }
7554 7645
7646 sv_shadowed = G.shadowed_vars_pp;
7555#if BB_MMU 7647#if BB_MMU
7556 set_vars_and_save_old(new_env); 7648 G.shadowed_vars_pp = NULL; /* "don't save, free them instead" */
7557 free(new_env); /* optional */
7558 /* we can also destroy set_vars_and_save_old's return value,
7559 * to save memory */
7560#else 7649#else
7561 nommu_save->new_env = new_env; 7650 G.shadowed_vars_pp = &nommu_save->old_vars;
7562 nommu_save->old_vars = set_vars_and_save_old(new_env);
7563#endif 7651#endif
7652 set_vars_and_save_old(new_env);
7653 G.shadowed_vars_pp = sv_shadowed;
7564 7654
7565 if (argv_expanded) { 7655 if (argv_expanded) {
7566 argv = argv_expanded; 7656 argv = argv_expanded;
@@ -7579,12 +7669,9 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7579 7669
7580#if ENABLE_HUSH_FUNCTIONS 7670#if ENABLE_HUSH_FUNCTIONS
7581 /* Check if the command matches any functions (this goes before bltins) */ 7671 /* Check if the command matches any functions (this goes before bltins) */
7582 { 7672 funcp = find_function(argv[0]);
7583 const struct function *funcp = find_function(argv[0]); 7673 if (funcp)
7584 if (funcp) { 7674 exec_function(&nommu_save->argv_from_re_execing, funcp, argv);
7585 exec_function(&nommu_save->argv_from_re_execing, funcp, argv);
7586 }
7587 }
7588#endif 7675#endif
7589 7676
7590#if ENABLE_HUSH_COMMAND 7677#if ENABLE_HUSH_COMMAND
@@ -7693,6 +7780,15 @@ static void pseudo_exec(nommu_save_t *nommu_save,
7693 struct command *command, 7780 struct command *command,
7694 char **argv_expanded) 7781 char **argv_expanded)
7695{ 7782{
7783#if ENABLE_HUSH_FUNCTIONS
7784 if (command->cmd_type == CMD_FUNCDEF) {
7785 /* Ignore funcdefs in pipes:
7786 * true | f() { cmd }
7787 */
7788 _exit(0);
7789 }
7790#endif
7791
7696 if (command->argv) { 7792 if (command->argv) {
7697 pseudo_exec_argv(nommu_save, command->argv, 7793 pseudo_exec_argv(nommu_save, command->argv,
7698 command->assignment_cnt, argv_expanded); 7794 command->assignment_cnt, argv_expanded);
@@ -8111,29 +8207,29 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe)
8111 * subshell: ( list ) [&] 8207 * subshell: ( list ) [&]
8112 */ 8208 */
8113#if !ENABLE_HUSH_MODE_X 8209#if !ENABLE_HUSH_MODE_X
8114#define redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel, argv_expanded) \ 8210#define redirect_and_varexp_helper(old_vars_p, command, squirrel, argv_expanded) \
8115 redirect_and_varexp_helper(new_env_p, old_vars_p, command, squirrel) 8211 redirect_and_varexp_helper(old_vars_p, command, squirrel)
8116#endif 8212#endif
8117static int redirect_and_varexp_helper(char ***new_env_p, 8213static int redirect_and_varexp_helper(
8118 struct variable **old_vars_p,
8119 struct command *command, 8214 struct command *command,
8120 struct squirrel **sqp, 8215 struct squirrel **sqp,
8121 char **argv_expanded) 8216 char **argv_expanded)
8122{ 8217{
8218 /* Assignments occur before redirects. Try:
8219 * a=`sleep 1` sleep 2 3>/qwe/rty
8220 */
8221
8222 char **new_env = expand_assignments(command->argv, command->assignment_cnt);
8223 dump_cmd_in_x_mode(new_env);
8224 dump_cmd_in_x_mode(argv_expanded);
8225 /* this takes ownership of new_env[i] elements, and frees new_env: */
8226 set_vars_and_save_old(new_env);
8227
8123 /* setup_redirects acts on file descriptors, not FILEs. 8228 /* setup_redirects acts on file descriptors, not FILEs.
8124 * This is perfect for work that comes after exec(). 8229 * This is perfect for work that comes after exec().
8125 * Is it really safe for inline use? Experimentally, 8230 * Is it really safe for inline use? Experimentally,
8126 * things seem to work. */ 8231 * things seem to work. */
8127 int rcode = setup_redirects(command, sqp); 8232 return setup_redirects(command, sqp);
8128 if (rcode == 0) {
8129 char **new_env = expand_assignments(command->argv, command->assignment_cnt);
8130 *new_env_p = new_env;
8131 dump_cmd_in_x_mode(new_env);
8132 dump_cmd_in_x_mode(argv_expanded);
8133 if (old_vars_p)
8134 *old_vars_p = set_vars_and_save_old(new_env);
8135 }
8136 return rcode;
8137} 8233}
8138static NOINLINE int run_pipe(struct pipe *pi) 8234static NOINLINE int run_pipe(struct pipe *pi)
8139{ 8235{
@@ -8221,13 +8317,10 @@ static NOINLINE int run_pipe(struct pipe *pi)
8221 argv = command->argv ? command->argv : (char **) &null_ptr; 8317 argv = command->argv ? command->argv : (char **) &null_ptr;
8222 { 8318 {
8223 const struct built_in_command *x; 8319 const struct built_in_command *x;
8224#if ENABLE_HUSH_FUNCTIONS 8320 IF_HUSH_FUNCTIONS(const struct function *funcp;)
8225 const struct function *funcp; 8321 IF_NOT_HUSH_FUNCTIONS(enum { funcp = 0 };)
8226#else 8322 struct variable **sv_shadowed;
8227 enum { funcp = 0 }; 8323 struct variable *old_vars;
8228#endif
8229 char **new_env = NULL;
8230 struct variable *old_vars = NULL;
8231 8324
8232#if ENABLE_HUSH_LINENO_VAR 8325#if ENABLE_HUSH_LINENO_VAR
8233 if (G.lineno_var) 8326 if (G.lineno_var)
@@ -8235,99 +8328,109 @@ static NOINLINE int run_pipe(struct pipe *pi)
8235#endif 8328#endif
8236 8329
8237 if (argv[command->assignment_cnt] == NULL) { 8330 if (argv[command->assignment_cnt] == NULL) {
8238 /* Assignments, but no command */ 8331 /* Assignments, but no command.
8239 /* Ensure redirects take effect (that is, create files). 8332 * Ensure redirects take effect (that is, create files).
8240 * Try "a=t >file" */ 8333 * Try "a=t >file"
8241#if 0 /* A few cases in testsuite fail with this code. FIXME */ 8334 */
8242 rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, &squirrel, /*argv_expanded:*/ NULL); 8335 unsigned i;
8243 /* Set shell variables */ 8336 G.expand_exitcode = 0;
8244 if (new_env) { 8337 only_assignments:
8245 argv = new_env;
8246 while (*argv) {
8247 if (set_local_var(*argv, /*flag:*/ 0)) {
8248 /* assignment to readonly var / putenv error? */
8249 rcode = 1;
8250 }
8251 argv++;
8252 }
8253 }
8254 /* Redirect error sets $? to 1. Otherwise,
8255 * if evaluating assignment value set $?, retain it.
8256 * Try "false; q=`exit 2`; echo $?" - should print 2: */
8257 if (rcode == 0)
8258 rcode = G.last_exitcode;
8259 /* Exit, _skipping_ variable restoring code: */
8260 goto clean_up_and_ret0;
8261
8262#else /* Older, bigger, but more correct code */
8263
8264 rcode = setup_redirects(command, &squirrel); 8338 rcode = setup_redirects(command, &squirrel);
8265 restore_redirects(squirrel); 8339 restore_redirects(squirrel);
8340
8266 /* Set shell variables */ 8341 /* Set shell variables */
8267 if (G_x_mode) 8342 if (G_x_mode)
8268 bb_putchar_stderr('+'); 8343 bb_putchar_stderr('+');
8269 while (*argv) { 8344 i = 0;
8270 char *p = expand_string_to_string(*argv, /*unbackslash:*/ 1); 8345 while (i < command->assignment_cnt) {
8346 char *p = expand_string_to_string(argv[i], /*unbackslash:*/ 1);
8271 if (G_x_mode) 8347 if (G_x_mode)
8272 fprintf(stderr, " %s", p); 8348 fprintf(stderr, " %s", p);
8273 debug_printf_exec("set shell var:'%s'->'%s'\n", 8349 debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p);
8274 *argv, p);
8275 if (set_local_var(p, /*flag:*/ 0)) { 8350 if (set_local_var(p, /*flag:*/ 0)) {
8276 /* assignment to readonly var / putenv error? */ 8351 /* assignment to readonly var / putenv error? */
8277 rcode = 1; 8352 rcode = 1;
8278 } 8353 }
8279 argv++; 8354 i++;
8280 } 8355 }
8281 if (G_x_mode) 8356 if (G_x_mode)
8282 bb_putchar_stderr('\n'); 8357 bb_putchar_stderr('\n');
8283 /* Redirect error sets $? to 1. Otherwise, 8358 /* Redirect error sets $? to 1. Otherwise,
8284 * if evaluating assignment value set $?, retain it. 8359 * if evaluating assignment value set $?, retain it.
8285 * Try "false; q=`exit 2`; echo $?" - should print 2: */ 8360 * Else, clear $?:
8361 * false; q=`exit 2`; echo $? - should print 2
8362 * false; x=1; echo $? - should print 0
8363 * Because of the 2nd case, we can't just use G.last_exitcode.
8364 */
8286 if (rcode == 0) 8365 if (rcode == 0)
8287 rcode = G.last_exitcode; 8366 rcode = G.expand_exitcode;
8288 IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) 8367 IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
8289 debug_leave(); 8368 debug_leave();
8290 debug_printf_exec("run_pipe: return %d\n", rcode); 8369 debug_printf_exec("run_pipe: return %d\n", rcode);
8291 return rcode; 8370 return rcode;
8292#endif
8293 } 8371 }
8294 8372
8295 /* Expand the rest into (possibly) many strings each */ 8373 /* Expand the rest into (possibly) many strings each */
8296#if BASH_TEST2 8374#if defined(CMD_SINGLEWORD_NOGLOB)
8297 if (command->cmd_type == CMD_SINGLEWORD_NOGLOB) { 8375 if (command->cmd_type == CMD_SINGLEWORD_NOGLOB)
8298 argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt); 8376 argv_expanded = expand_strvec_to_strvec_singleword_noglob(argv + command->assignment_cnt);
8299 } else 8377 else
8300#endif 8378#endif
8301 {
8302 argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt); 8379 argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
8303 }
8304 8380
8305 /* if someone gives us an empty string: `cmd with empty output` */ 8381 /* If someone gives us an empty string: `cmd with empty output` */
8306 if (!argv_expanded[0]) { 8382 if (!argv_expanded[0]) {
8307 free(argv_expanded); 8383 free(argv_expanded);
8308 debug_leave(); 8384 /* `false` still has to set exitcode 1 */
8309 return G.last_exitcode; 8385 G.expand_exitcode = G.last_exitcode;
8386 goto only_assignments;
8310 } 8387 }
8311 8388
8312#if ENABLE_HUSH_FUNCTIONS 8389 old_vars = NULL;
8390 sv_shadowed = G.shadowed_vars_pp;
8391
8313 /* Check if argv[0] matches any functions (this goes before bltins) */ 8392 /* Check if argv[0] matches any functions (this goes before bltins) */
8314 funcp = find_function(argv_expanded[0]); 8393 IF_HUSH_FUNCTIONS(funcp = find_function(argv_expanded[0]);)
8315#endif 8394 IF_HUSH_FUNCTIONS(x = NULL;)
8316 x = NULL; 8395 IF_HUSH_FUNCTIONS(if (!funcp))
8317 if (!funcp)
8318 x = find_builtin(argv_expanded[0]); 8396 x = find_builtin(argv_expanded[0]);
8319 if (x || funcp) { 8397 if (x || funcp) {
8320 if (!funcp) { 8398 if (x && x->b_function == builtin_exec && argv_expanded[1] == NULL) {
8321 if (x->b_function == builtin_exec && argv_expanded[1] == NULL) { 8399 debug_printf("exec with redirects only\n");
8322 debug_printf("exec with redirects only\n"); 8400 /*
8323 rcode = setup_redirects(command, NULL); 8401 * Variable assignments are executed, but then "forgotten":
8324 /* rcode=1 can be if redir file can't be opened */ 8402 * a=`sleep 1;echo A` exec 3>&-; echo $a
8325 goto clean_up_and_ret1; 8403 * sleeps, but prints nothing.
8326 } 8404 */
8405 enter_var_nest_level();
8406 G.shadowed_vars_pp = &old_vars;
8407 rcode = redirect_and_varexp_helper(command, /*squirrel:*/ NULL, argv_expanded);
8408 G.shadowed_vars_pp = sv_shadowed;
8409 /* rcode=1 can be if redir file can't be opened */
8410
8411 goto clean_up_and_ret1;
8327 } 8412 }
8328 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); 8413
8414 /* Bump var nesting, or this will leak exported $a:
8415 * a=b true; env | grep ^a=
8416 */
8417 enter_var_nest_level();
8418 /* Collect all variables "shadowed" by helper
8419 * (IOW: old vars overridden by "var1=val1 var2=val2 cmd..." syntax)
8420 * into old_vars list:
8421 */
8422 G.shadowed_vars_pp = &old_vars;
8423 rcode = redirect_and_varexp_helper(command, &squirrel, argv_expanded);
8329 if (rcode == 0) { 8424 if (rcode == 0) {
8330 if (!funcp) { 8425 if (!funcp) {
8426 /* Do not collect *to old_vars list* vars shadowed
8427 * by e.g. "local VAR" builtin (collect them
8428 * in the previously nested list instead):
8429 * don't want them to be restored immediately
8430 * after "local" completes.
8431 */
8432 G.shadowed_vars_pp = sv_shadowed;
8433
8331 debug_printf_exec(": builtin '%s' '%s'...\n", 8434 debug_printf_exec(": builtin '%s' '%s'...\n",
8332 x->b_cmd, argv_expanded[1]); 8435 x->b_cmd, argv_expanded[1]);
8333 fflush_all(); 8436 fflush_all();
@@ -8336,72 +8439,74 @@ static NOINLINE int run_pipe(struct pipe *pi)
8336 } 8439 }
8337#if ENABLE_HUSH_FUNCTIONS 8440#if ENABLE_HUSH_FUNCTIONS
8338 else { 8441 else {
8339# if ENABLE_HUSH_LOCAL
8340 struct variable **sv;
8341 sv = G.shadowed_vars_pp;
8342 G.shadowed_vars_pp = &old_vars;
8343# endif
8344 debug_printf_exec(": function '%s' '%s'...\n", 8442 debug_printf_exec(": function '%s' '%s'...\n",
8345 funcp->name, argv_expanded[1]); 8443 funcp->name, argv_expanded[1]);
8346 rcode = run_function(funcp, argv_expanded) & 0xff; 8444 rcode = run_function(funcp, argv_expanded) & 0xff;
8347# if ENABLE_HUSH_LOCAL 8445 /*
8348 G.shadowed_vars_pp = sv; 8446 * But do collect *to old_vars list* vars shadowed
8349# endif 8447 * within function execution. To that end, restore
8448 * this pointer _after_ function run:
8449 */
8450 G.shadowed_vars_pp = sv_shadowed;
8350 } 8451 }
8351#endif 8452#endif
8352 } 8453 }
8353 clean_up_and_ret: 8454 } else
8354 unset_vars(new_env); 8455 if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) {
8355 add_vars(old_vars); 8456 int n = find_applet_by_name(argv_expanded[0]);
8356/* clean_up_and_ret0: */ 8457 if (n < 0 || !APPLET_IS_NOFORK(n))
8357 restore_redirects(squirrel); 8458 goto must_fork;
8358 /* 8459
8359 * Try "usleep 99999999" + ^C + "echo $?" 8460 enter_var_nest_level();
8360 * with FEATURE_SH_NOFORK=y. 8461 /* Collect all variables "shadowed" by helper into old_vars list */
8361 */ 8462 G.shadowed_vars_pp = &old_vars;
8362 if (!funcp) { 8463 rcode = redirect_and_varexp_helper(command, &squirrel, argv_expanded);
8363 /* It was builtin or nofork. 8464 G.shadowed_vars_pp = sv_shadowed;
8364 * if this would be a real fork/execed program, 8465
8365 * it should have died if a fatal sig was received. 8466 if (rcode == 0) {
8366 * But OTOH, there was no separate process, 8467 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
8367 * the sig was sent to _shell_, not to non-existing 8468 argv_expanded[0], argv_expanded[1]);
8368 * child. 8469 /*
8369 * Let's just handle ^C only, this one is obvious: 8470 * Note: signals (^C) can't interrupt here.
8370 * we aren't ok with exitcode 0 when ^C was pressed 8471 * We remember them and they will be acted upon
8371 * during builtin/nofork. 8472 * after applet returns.
8473 * This makes applets which can run for a long time
8474 * and/or wait for user input ineligible for NOFORK:
8475 * for example, "yes" or "rm" (rm -i waits for input).
8372 */ 8476 */
8373 if (sigismember(&G.pending_set, SIGINT)) 8477 rcode = run_nofork_applet(n, argv_expanded);
8374 rcode = 128 + SIGINT;
8375 } 8478 }
8479 } else
8480 goto must_fork;
8481
8482 restore_redirects(squirrel);
8376 clean_up_and_ret1: 8483 clean_up_and_ret1:
8377 free(argv_expanded); 8484 leave_var_nest_level();
8378 IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) 8485 add_vars(old_vars);
8379 debug_leave();
8380 debug_printf_exec("run_pipe return %d\n", rcode);
8381 return rcode;
8382 }
8383 8486
8384 if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) { 8487 /*
8385 int n = find_applet_by_name(argv_expanded[0]); 8488 * Try "usleep 99999999" + ^C + "echo $?"
8386 if (n >= 0 && APPLET_IS_NOFORK(n)) { 8489 * with FEATURE_SH_NOFORK=y.
8387 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); 8490 */
8388 if (rcode == 0) { 8491 if (!funcp) {
8389 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", 8492 /* It was builtin or nofork.
8390 argv_expanded[0], argv_expanded[1]); 8493 * if this would be a real fork/execed program,
8391 /* 8494 * it should have died if a fatal sig was received.
8392 * Note: signals (^C) can't interrupt here. 8495 * But OTOH, there was no separate process,
8393 * We remember them and they will be acted upon 8496 * the sig was sent to _shell_, not to non-existing
8394 * after applet returns. 8497 * child.
8395 * This makes applets which can run for a long time 8498 * Let's just handle ^C only, this one is obvious:
8396 * and/or wait for user input ineligible for NOFORK: 8499 * we aren't ok with exitcode 0 when ^C was pressed
8397 * for example, "yes" or "rm" (rm -i waits for input). 8500 * during builtin/nofork.
8398 */ 8501 */
8399 rcode = run_nofork_applet(n, argv_expanded); 8502 if (sigismember(&G.pending_set, SIGINT))
8400 } 8503 rcode = 128 + SIGINT;
8401 goto clean_up_and_ret;
8402 }
8403 } 8504 }
8404 /* It is neither builtin nor applet. We must fork. */ 8505 free(argv_expanded);
8506 IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
8507 debug_leave();
8508 debug_printf_exec("run_pipe return %d\n", rcode);
8509 return rcode;
8405 } 8510 }
8406 8511
8407 must_fork: 8512 must_fork:
@@ -8418,7 +8523,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
8418 struct fd_pair pipefds; 8523 struct fd_pair pipefds;
8419#if !BB_MMU 8524#if !BB_MMU
8420 volatile nommu_save_t nommu_save; 8525 volatile nommu_save_t nommu_save;
8421 nommu_save.new_env = NULL;
8422 nommu_save.old_vars = NULL; 8526 nommu_save.old_vars = NULL;
8423 nommu_save.argv = NULL; 8527 nommu_save.argv = NULL;
8424 nommu_save.argv_from_re_execing = NULL; 8528 nommu_save.argv_from_re_execing = NULL;
@@ -8511,7 +8615,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
8511 /* Clean up after vforked child */ 8615 /* Clean up after vforked child */
8512 free(nommu_save.argv); 8616 free(nommu_save.argv);
8513 free(nommu_save.argv_from_re_execing); 8617 free(nommu_save.argv_from_re_execing);
8514 unset_vars(nommu_save.new_env);
8515 add_vars(nommu_save.old_vars); 8618 add_vars(nommu_save.old_vars);
8516#endif 8619#endif
8517 free(argv_expanded); 8620 free(argv_expanded);
@@ -8732,8 +8835,10 @@ static int run_list(struct pipe *pi)
8732#if ENABLE_HUSH_CASE 8835#if ENABLE_HUSH_CASE
8733 if (rword == RES_CASE) { 8836 if (rword == RES_CASE) {
8734 debug_printf_exec("CASE cond_code:%d\n", cond_code); 8837 debug_printf_exec("CASE cond_code:%d\n", cond_code);
8735 case_word = expand_strvec_to_string(pi->cmds->argv); 8838 case_word = expand_string_to_string(pi->cmds->argv[0], 1);
8736 unbackslash(case_word); 8839 debug_printf_exec("CASE word1:'%s'\n", case_word);
8840 //unbackslash(case_word);
8841 //debug_printf_exec("CASE word2:'%s'\n", case_word);
8737 continue; 8842 continue;
8738 } 8843 }
8739 if (rword == RES_MATCH) { 8844 if (rword == RES_MATCH) {
@@ -9060,9 +9165,9 @@ int hush_main(int argc, char **argv)
9060{ 9165{
9061 enum { 9166 enum {
9062 OPT_login = (1 << 0), 9167 OPT_login = (1 << 0),
9168 OPT_s = (1 << 1),
9063 }; 9169 };
9064 unsigned flags; 9170 unsigned flags;
9065 int opt;
9066 unsigned builtin_argc; 9171 unsigned builtin_argc;
9067 char **e; 9172 char **e;
9068 struct variable *cur_var; 9173 struct variable *cur_var;
@@ -9112,6 +9217,14 @@ int hush_main(int argc, char **argv)
9112 /* Export PWD */ 9217 /* Export PWD */
9113 set_pwd_var(SETFLAG_EXPORT); 9218 set_pwd_var(SETFLAG_EXPORT);
9114 9219
9220#if ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_EDITING_FANCY_PROMPT
9221 /* Set (but not export) PS1/2 unless already set */
9222 if (!get_local_var_value("PS1"))
9223 set_local_var_from_halves("PS1", "\\w \\$ ");
9224 if (!get_local_var_value("PS2"))
9225 set_local_var_from_halves("PS2", "> ");
9226#endif
9227
9115#if BASH_HOSTNAME_VAR 9228#if BASH_HOSTNAME_VAR
9116 /* Set (but not export) HOSTNAME unless already set */ 9229 /* Set (but not export) HOSTNAME unless already set */
9117 if (!get_local_var_value("HOSTNAME")) { 9230 if (!get_local_var_value("HOSTNAME")) {
@@ -9150,8 +9263,6 @@ int hush_main(int argc, char **argv)
9150 * OPTERR=1 9263 * OPTERR=1
9151 * OPTIND=1 9264 * OPTIND=1
9152 * IFS=$' \t\n' 9265 * IFS=$' \t\n'
9153 * PS1='\s-\v\$ '
9154 * PS2='> '
9155 * PS4='+ ' 9266 * PS4='+ '
9156 */ 9267 */
9157#endif 9268#endif
@@ -9185,7 +9296,7 @@ int hush_main(int argc, char **argv)
9185 flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0; 9296 flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0;
9186 builtin_argc = 0; 9297 builtin_argc = 0;
9187 while (1) { 9298 while (1) {
9188 opt = getopt(argc, argv, "+c:exinsl" 9299 int opt = getopt(argc, argv, "+c:exinsl"
9189#if !BB_MMU 9300#if !BB_MMU
9190 "<:$:R:V:" 9301 "<:$:R:V:"
9191# if ENABLE_HUSH_FUNCTIONS 9302# if ENABLE_HUSH_FUNCTIONS
@@ -9243,8 +9354,7 @@ int hush_main(int argc, char **argv)
9243 /* G_interactive_fd++; */ 9354 /* G_interactive_fd++; */
9244 break; 9355 break;
9245 case 's': 9356 case 's':
9246 /* "-s" means "read from stdin", but this is how we always 9357 flags |= OPT_s;
9247 * operate, so simply do nothing here. */
9248 break; 9358 break;
9249 case 'l': 9359 case 'l':
9250 flags |= OPT_login; 9360 flags |= OPT_login;
@@ -9347,7 +9457,8 @@ int hush_main(int argc, char **argv)
9347 */ 9457 */
9348 } 9458 }
9349 9459
9350 if (G.global_argv[1]) { 9460 /* -s is: hush -s ARGV1 ARGV2 (no SCRIPT) */
9461 if (!(flags & OPT_s) && G.global_argv[1]) {
9351 FILE *input; 9462 FILE *input;
9352 /* 9463 /*
9353 * "bash <script>" (which is never interactive (unless -i?)) 9464 * "bash <script>" (which is never interactive (unless -i?))
@@ -9909,8 +10020,8 @@ static int helper_export_local(char **argv, unsigned flags)
9909# if ENABLE_HUSH_LOCAL 10020# if ENABLE_HUSH_LOCAL
9910 /* Is this "local" bltin? */ 10021 /* Is this "local" bltin? */
9911 if (!(flags & (SETFLAG_EXPORT|SETFLAG_UNEXPORT|SETFLAG_MAKE_RO))) { 10022 if (!(flags & (SETFLAG_EXPORT|SETFLAG_UNEXPORT|SETFLAG_MAKE_RO))) {
9912 unsigned lvl = flags >> SETFLAG_LOCAL_SHIFT; 10023 unsigned lvl = flags >> SETFLAG_VARLVL_SHIFT;
9913 if (var && var->func_nest_level == lvl) { 10024 if (var && var->var_nest_level == lvl) {
9914 /* "local x=abc; ...; local x" - ignore second local decl */ 10025 /* "local x=abc; ...; local x" - ignore second local decl */
9915 continue; 10026 continue;
9916 } 10027 }
@@ -9934,6 +10045,7 @@ static int helper_export_local(char **argv, unsigned flags)
9934 /* (Un)exporting/making local NAME=VALUE */ 10045 /* (Un)exporting/making local NAME=VALUE */
9935 name = xstrdup(name); 10046 name = xstrdup(name);
9936 } 10047 }
10048 debug_printf_env("%s: set_local_var('%s')\n", __func__, name);
9937 if (set_local_var(name, flags)) 10049 if (set_local_var(name, flags))
9938 return EXIT_FAILURE; 10050 return EXIT_FAILURE;
9939 } while (*++argv); 10051 } while (*++argv);
@@ -9995,7 +10107,11 @@ static int FAST_FUNC builtin_local(char **argv)
9995 return EXIT_FAILURE; /* bash compat */ 10107 return EXIT_FAILURE; /* bash compat */
9996 } 10108 }
9997 argv++; 10109 argv++;
9998 return helper_export_local(argv, G.func_nest_level << SETFLAG_LOCAL_SHIFT); 10110 /* Since all builtins run in a nested variable level,
10111 * need to use level - 1 here. Or else the variable will be removed at once
10112 * after builtin returns.
10113 */
10114 return helper_export_local(argv, (G.var_nest_level - 1) << SETFLAG_VARLVL_SHIFT);
9999} 10115}
10000#endif 10116#endif
10001 10117
diff --git a/shell/hush_test/hush-arith/arith_nested1.right b/shell/hush_test/hush-arith/arith_nested1.right
new file mode 100644
index 000000000..d00491fd7
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith_nested1.right
@@ -0,0 +1 @@
1
diff --git a/shell/hush_test/hush-arith/arith_nested1.tests b/shell/hush_test/hush-arith/arith_nested1.tests
new file mode 100755
index 000000000..28571b833
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith_nested1.tests
@@ -0,0 +1 @@
echo $(( ( $((1)) ) ))
diff --git a/shell/hush_test/hush-misc/assignment5.right b/shell/hush_test/hush-misc/assignment5.right
new file mode 100644
index 000000000..a91554c09
--- /dev/null
+++ b/shell/hush_test/hush-misc/assignment5.right
@@ -0,0 +1,5 @@
1Zero1:0
2Zero2:0
3Zero3:0
4Zero4:0 x:1 y:1
5Three:3 x:1 y:1
diff --git a/shell/hush_test/hush-misc/assignment5.tests b/shell/hush_test/hush-misc/assignment5.tests
new file mode 100755
index 000000000..0b8104285
--- /dev/null
+++ b/shell/hush_test/hush-misc/assignment5.tests
@@ -0,0 +1,9 @@
1true; a=1; echo Zero1:$?
2false; a=1; echo Zero2:$?
3false || a=1; echo Zero3:$?
4
5false || x=$? y=`echo $?`; echo Zero4:$? x:$x y:$y
6false || x=$? y=`echo $?; exit 3`; echo Three:$? x:$x y:$y
7
8#ash sets z=1 instead of z=3. disabled for now
9#false || x=$? y=`echo $?; exit 3` z=`echo $?`; echo x:$x y:$y z:$z
diff --git a/shell/hush_test/hush-misc/func5.tests b/shell/hush_test/hush-misc/func5.tests
index 9c5f9fa48..5c33560bc 100755
--- a/shell/hush_test/hush-misc/func5.tests
+++ b/shell/hush_test/hush-misc/func5.tests
@@ -1,9 +1,8 @@
1f() { echo $1; } 1f() { echo $1; }
2f 1 2f 1
3 3
4# hush fails on this syntax, but i've never seen anyone use it ... 4f() ( echo $1; )
5#f() ( echo $1; )
6f 2 5f 2
7 6
8#f() ( echo $1 ) 7f() ( echo $1 )
9f 3 8f 3
diff --git a/shell/hush_test/hush-parsing/starquoted3.right b/shell/hush_test/hush-parsing/starquoted3.right
new file mode 100644
index 000000000..fea246c14
--- /dev/null
+++ b/shell/hush_test/hush-parsing/starquoted3.right
@@ -0,0 +1,2 @@
1<a>
2<>
diff --git a/shell/hush_test/hush-parsing/starquoted3.tests b/shell/hush_test/hush-parsing/starquoted3.tests
new file mode 100755
index 000000000..8eefe4245
--- /dev/null
+++ b/shell/hush_test/hush-parsing/starquoted3.tests
@@ -0,0 +1 @@
set -- a ""; space=" "; printf "<%s>\n" "$@"$space
diff --git a/shell/hush_test/hush-psubst/falsetick2.right b/shell/hush_test/hush-psubst/falsetick2.right
new file mode 100644
index 000000000..670f560f1
--- /dev/null
+++ b/shell/hush_test/hush-psubst/falsetick2.right
@@ -0,0 +1 @@
Two:2 v:[]
diff --git a/shell/hush_test/hush-psubst/falsetick2.tests b/shell/hush_test/hush-psubst/falsetick2.tests
new file mode 100755
index 000000000..cfbd1a5de
--- /dev/null
+++ b/shell/hush_test/hush-psubst/falsetick2.tests
@@ -0,0 +1,3 @@
1v=v
2v=`exit 2` `false`
3echo Two:$? v:"[$v]"
diff --git a/shell/hush_test/hush-quoting/bkslash_case2.right b/shell/hush_test/hush-quoting/bkslash_case2.right
new file mode 100644
index 000000000..8d2038bff
--- /dev/null
+++ b/shell/hush_test/hush-quoting/bkslash_case2.right
@@ -0,0 +1,3 @@
1ok1
2ok2
3Ok:0
diff --git a/shell/hush_test/hush-quoting/bkslash_case2.tests b/shell/hush_test/hush-quoting/bkslash_case2.tests
new file mode 100755
index 000000000..348ddc236
--- /dev/null
+++ b/shell/hush_test/hush-quoting/bkslash_case2.tests
@@ -0,0 +1,13 @@
1x='\abc'
2
3case "$x" in
4\\*) echo ok1;;
5*) echo BUG1;;
6esac
7
8case $x in
9\\*) echo ok2;;
10*) echo BUG2;;
11esac
12
13echo Ok:$?
diff --git a/shell/hush_test/hush-quoting/squote_in_varexp3.right b/shell/hush_test/hush-quoting/squote_in_varexp3.right
new file mode 100644
index 000000000..223b7836f
--- /dev/null
+++ b/shell/hush_test/hush-quoting/squote_in_varexp3.right
@@ -0,0 +1 @@
B
diff --git a/shell/hush_test/hush-quoting/squote_in_varexp3.tests b/shell/hush_test/hush-quoting/squote_in_varexp3.tests
new file mode 100755
index 000000000..028a88fd9
--- /dev/null
+++ b/shell/hush_test/hush-quoting/squote_in_varexp3.tests
@@ -0,0 +1 @@
x=\'B; echo "${x#\'}"
diff --git a/shell/hush_test/hush-redir/redir_backquote1.right b/shell/hush_test/hush-redir/redir_backquote1.right
new file mode 100644
index 000000000..810cc2314
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_backquote1.right
@@ -0,0 +1,11 @@
1hush: can't open '/cant/be/created': No such file or directory
2First
3One:1 v1:[]
4hush: can't open '/cant/be/created': No such file or directory
5Second
6One:1 v2:[]
7Third
8Zero:0 v3:[]
9Fourth
10Zero:0 v4:[]
11Zero:0 v5:[1]
diff --git a/shell/hush_test/hush-redir/redir_backquote1.tests b/shell/hush_test/hush-redir/redir_backquote1.tests
new file mode 100755
index 000000000..41bb4913c
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_backquote1.tests
@@ -0,0 +1,19 @@
1v=v
2v=`echo First >&2` `` >/cant/be/created
3echo One:$? v1:"[$v]"
4
5v=v
6v=`echo Second >&2` `true` >/cant/be/created
7echo One:$? v2:"[$v]"
8
9v=v
10v=`echo Third >&2` `true` 2>/dev/null
11echo Zero:$? v3:"[$v]"
12
13v=v
14v=`echo Fourth >&2` `false` 2>/dev/null
15echo Zero:$? v4:"[$v]"
16
17v=v
18v=`echo $?` `false` 2>/dev/null
19echo Zero:$? v5:"[$v]"
diff --git a/shell/hush_test/hush-redir/redir_exec1.right b/shell/hush_test/hush-redir/redir_exec1.right
new file mode 100644
index 000000000..6ff8fc832
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_exec1.right
@@ -0,0 +1,3 @@
1First
2hush: can't open '/cant/be/created': No such file or directory
3One:1
diff --git a/shell/hush_test/hush-redir/redir_exec1.tests b/shell/hush_test/hush-redir/redir_exec1.tests
new file mode 100755
index 000000000..290e1cb39
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_exec1.tests
@@ -0,0 +1,2 @@
1v=`echo First >&2` exec >/cant/be/created
2echo One:$?
diff --git a/shell/hush_test/hush-vars/readonly3.right b/shell/hush_test/hush-vars/readonly3.right
new file mode 100644
index 000000000..acd931243
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly3.right
@@ -0,0 +1,4 @@
1hush: v=2: readonly variable
2hush: v=3: readonly variable
31
4Ok:1
diff --git a/shell/hush_test/hush-vars/readonly3.tests b/shell/hush_test/hush-vars/readonly3.tests
new file mode 100755
index 000000000..17780cfda
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly3.tests
@@ -0,0 +1,4 @@
1readonly v=1
2# there was a bug causing second assignment to be not checked
3v=2 v=3 echo $v
4echo Ok:$v
diff --git a/shell/hush_test/hush-vars/var_bash3.right b/shell/hush_test/hush-vars/var_bash3.right
index a97c850ea..8899d981c 100644
--- a/shell/hush_test/hush-vars/var_bash3.right
+++ b/shell/hush_test/hush-vars/var_bash3.right
@@ -1,6 +1,6 @@
11 a041#c 11 a041#c
22 a041#c 22 a041#c
33 a\041#c 33 a041#c
44 a\041#c 44 a\041#c
55 a\041#c 55 a\041#c
66 a\041#c 66 a\041#c
@@ -17,4 +17,4 @@
1717 a\tc 1717 a\tc
1818 a\tc 1818 a\tc
1919 atc 1919 atc
2020 a\tc 2020 atc
diff --git a/shell/hush_test/hush-vars/var_bash4.right b/shell/hush_test/hush-vars/var_bash4.right
index 0ef1bf661..9067e58e6 100644
--- a/shell/hush_test/hush-vars/var_bash4.right
+++ b/shell/hush_test/hush-vars/var_bash4.right
@@ -3,26 +3,26 @@ Replace str: _\\_\z_
3Pattern: single backslash and star: "replace literal star" 3Pattern: single backslash and star: "replace literal star"
4Unquoted: a_\_z_b\*c 4Unquoted: a_\_z_b\*c
5Unquoted =: a_\_z_b\*c 5Unquoted =: a_\_z_b\*c
6Quoted: a_\_\z_b\*c 6Quoted: a_\_z_b\*c
7Quoted =: a_\_\z_b\*c 7Quoted =: a_\_z_b\*c
8Pattern: double backslash and star: "replace backslash and everything after it" 8Pattern: double backslash and star: "replace backslash and everything after it"
9Unquoted: a*b_\_z_ 9Unquoted: a*b_\_z_
10Unquoted =: a*b_\_z_ 10Unquoted =: a*b_\_z_
11Quoted: a*b_\_\z_ 11Quoted: a*b_\_z_
12Quoted =: a*b_\_\z_ 12Quoted =: a*b_\_z_
13 13
14Source: a\bc 14Source: a\bc
15Replace str: _\\_\z_ 15Replace str: _\\_\z_
16Pattern: single backslash and b: "replace b" 16Pattern: single backslash and b: "replace b"
17Unquoted: a\_\_z_c 17Unquoted: a\_\_z_c
18Unquoted =: a\_\_z_c 18Unquoted =: a\_\_z_c
19Quoted: a\_\_\z_c 19Quoted: a\_\_z_c
20Quoted =: a\_\_\z_c 20Quoted =: a\_\_z_c
21Pattern: double backslash and b: "replace backslash and b" 21Pattern: double backslash and b: "replace backslash and b"
22Unquoted: a_\_z_c 22Unquoted: a_\_z_c
23Unquoted =: a_\_z_c 23Unquoted =: a_\_z_c
24Quoted: a_\_\z_c 24Quoted: a_\_z_c
25Quoted =: a_\_\z_c 25Quoted =: a_\_z_c
26 26
27Source: a\bc 27Source: a\bc
28Replace str: _\\_\z_ (as variable $s) 28Replace str: _\\_\z_ (as variable $s)
diff --git a/shell/hush_test/hush-vars/var_bash6.right b/shell/hush_test/hush-vars/var_bash6.right
index 63fc23df8..115ff8b04 100644
--- a/shell/hush_test/hush-vars/var_bash6.right
+++ b/shell/hush_test/hush-vars/var_bash6.right
@@ -1,5 +1,5 @@
1Expected Actual 1Expected Actual
2a*z : a*z 2a*z : a*z
3\z : \z 3z : z
4a1z a2z: a1z a2z 4a1z a2z: a1z a2z
5z : z 5z : z
diff --git a/shell/hush_test/hush-vars/var_bash6.tests b/shell/hush_test/hush-vars/var_bash6.tests
index cf2e4f020..686834177 100755
--- a/shell/hush_test/hush-vars/var_bash6.tests
+++ b/shell/hush_test/hush-vars/var_bash6.tests
@@ -3,7 +3,7 @@
3>a1z; >a2z; 3>a1z; >a2z;
4 echo 'Expected' 'Actual' 4 echo 'Expected' 'Actual'
5v='a bz'; echo 'a*z :' "${v/a*z/a*z}" 5v='a bz'; echo 'a*z :' "${v/a*z/a*z}"
6v='a bz'; echo '\z :' "${v/a*z/\z}" 6v='a bz'; echo 'z :' "${v/a*z/\z}"
7v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z} 7v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z}
8v='a bz'; echo 'z :' ${v/a*z/\z} 8v='a bz'; echo 'z :' ${v/a*z/\z}
9rm a1z a2z 9rm a1z a2z
diff --git a/shell/hush_test/hush-vars/var_nested1.right b/shell/hush_test/hush-vars/var_nested1.right
new file mode 100644
index 000000000..f4bc5f4b6
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_nested1.right
@@ -0,0 +1,3 @@
1Expected:AB Actual:AB
2Expected:Ab Actual:Ab
3Expected:ab Actual:ab
diff --git a/shell/hush_test/hush-vars/var_nested1.tests b/shell/hush_test/hush-vars/var_nested1.tests
new file mode 100755
index 000000000..59e4a14fa
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_nested1.tests
@@ -0,0 +1,16 @@
1f() { a=A; b=B; }
2
3a=a
4b=b
5f
6echo Expected:AB Actual:$a$b
7
8a=a
9b=b
10b= f
11echo Expected:Ab Actual:$a$b
12
13a=a
14b=b
15a= b= f
16echo Expected:ab Actual:$a$b
diff --git a/shell/hush_test/hush-vars/var_nested2.right b/shell/hush_test/hush-vars/var_nested2.right
new file mode 100644
index 000000000..c930d971c
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_nested2.right
@@ -0,0 +1 @@
aB
diff --git a/shell/hush_test/hush-vars/var_nested2.tests b/shell/hush_test/hush-vars/var_nested2.tests
new file mode 100755
index 000000000..e8865861e
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_nested2.tests
@@ -0,0 +1,2 @@
1# the bug was easier to trigger in one-liner form
2a=a; b=b; f() { a=A; b=B; }; a= f; echo $a$b
diff --git a/shell/shell_common.c b/shell/shell_common.c
index a44ad0caf..aa0791285 100644
--- a/shell/shell_common.c
+++ b/shell/shell_common.c
@@ -18,7 +18,6 @@
18 */ 18 */
19#include "libbb.h" 19#include "libbb.h"
20#include "shell_common.h" 20#include "shell_common.h"
21#include <sys/resource.h> /* getrlimit */
22 21
23#if !ENABLE_PLATFORM_MINGW32 22#if !ENABLE_PLATFORM_MINGW32
24const char defifsvar[] ALIGN1 = "IFS= \t\n"; 23const char defifsvar[] ALIGN1 = "IFS= \t\n";