aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2018-09-10 14:37:07 +0100
committerRon Yorston <rmy@pobox.com>2018-09-10 14:59:33 +0100
commitd89ced75b204f0eb5611f522864beb81d1b393f5 (patch)
tree5daa31427e287fe079a0ef551097753773fdb266 /shell
parentf72845d9332fa6311a46dbcad3180d5008182982 (diff)
parent05b18065ab9c375f6185b65a3631d4c6cc1a4be9 (diff)
downloadbusybox-w32-d89ced75b204f0eb5611f522864beb81d1b393f5.tar.gz
busybox-w32-d89ced75b204f0eb5611f522864beb81d1b393f5.tar.bz2
busybox-w32-d89ced75b204f0eb5611f522864beb81d1b393f5.zip
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
-rw-r--r--shell/ash.c127
-rw-r--r--shell/ash_test/ash-glob/glob_bkslash_in_var.right4
-rwxr-xr-xshell/ash_test/ash-glob/glob_bkslash_in_var.tests10
-rw-r--r--shell/ash_test/ash-misc/empty_for1.right1
-rwxr-xr-xshell/ash_test/ash-misc/empty_for1.tests5
-rw-r--r--shell/ash_test/ash-misc/env_and_func.right2
-rwxr-xr-xshell/ash_test/ash-misc/env_and_func.tests2
-rw-r--r--shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.right2
-rwxr-xr-xshell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.tests3
-rw-r--r--shell/ash_test/ash-vars/var_leak.right2
-rwxr-xr-xshell/ash_test/ash-vars/var_leak.tests4
-rw-r--r--shell/ash_test/ash-vars/var_wordsplit_ifs5.right1
-rwxr-xr-xshell/ash_test/ash-vars/var_wordsplit_ifs5.tests4
-rw-r--r--shell/hush.c340
-rw-r--r--shell/hush_test/hush-glob/glob_bkslash_in_var.right4
-rwxr-xr-xshell/hush_test/hush-glob/glob_bkslash_in_var.tests10
-rw-r--r--shell/hush_test/hush-misc/empty_for1.right1
-rwxr-xr-xshell/hush_test/hush-misc/empty_for1.tests5
-rwxr-xr-xshell/hush_test/hush-misc/env_and_func.tests2
-rw-r--r--shell/hush_test/hush-quoting/quote_in_varexp1.right2
-rwxr-xr-xshell/hush_test/hush-quoting/quote_in_varexp1.tests2
-rw-r--r--shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.right2
-rwxr-xr-xshell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.tests3
-rw-r--r--shell/hush_test/hush-vars/var_wordsplit_ifs5.right1
-rwxr-xr-xshell/hush_test/hush-vars/var_wordsplit_ifs5.tests4
-rw-r--r--shell/shell_common.c57
-rw-r--r--shell/shell_common.h22
27 files changed, 427 insertions, 195 deletions
diff --git a/shell/ash.c b/shell/ash.c
index 4bd1c2c9d..7131609e4 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -6272,7 +6272,7 @@ static int substr_atoi(const char *s)
6272#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ 6272#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
6273#define EXP_VARTILDE2 0x20 /* expand tildes after colons only */ 6273#define EXP_VARTILDE2 0x20 /* expand tildes after colons only */
6274#define EXP_WORD 0x40 /* expand word in parameter expansion */ 6274#define EXP_WORD 0x40 /* expand word in parameter expansion */
6275#define EXP_QUOTED 0x80 /* expand word in double quotes */ 6275#define EXP_QUOTED 0x100 /* expand word in double quotes */
6276/* 6276/*
6277 * rmescape() flags 6277 * rmescape() flags
6278 */ 6278 */
@@ -6606,9 +6606,7 @@ memtodest(const char *p, size_t len, int syntax, int quotes)
6606 if (quotes & QUOTES_ESC) { 6606 if (quotes & QUOTES_ESC) {
6607 int n = SIT(c, syntax); 6607 int n = SIT(c, syntax);
6608 if (n == CCTL 6608 if (n == CCTL
6609 || (((quotes & EXP_FULL) || syntax != BASESYNTAX) 6609 || (syntax != BASESYNTAX && n == CBACK)
6610 && n == CBACK
6611 )
6612 ) { 6610 ) {
6613 USTPUTC(CTLESC, q); 6611 USTPUTC(CTLESC, q);
6614 } 6612 }
@@ -7236,8 +7234,15 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7236 if (subtype == VSREPLACE || subtype == VSREPLACEALL) { 7234 if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
7237 /* Find '/' and replace with NUL */ 7235 /* Find '/' and replace with NUL */
7238 repl = p; 7236 repl = p;
7237 /* The pattern can't be empty.
7238 * IOW: if the first char after "${v//" is a slash,
7239 * it does not terminate the pattern - it's the first char of the pattern:
7240 * v=/dev/ram; echo ${v////-} prints -dev-ram (pattern is "/")
7241 * v=/dev/ram; echo ${v///r/-} prints /dev-am (pattern is "/r")
7242 */
7243 if (*repl == '/')
7244 repl++;
7239 for (;;) { 7245 for (;;) {
7240 /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */
7241 if (*repl == '\0') { 7246 if (*repl == '\0') {
7242 repl = NULL; 7247 repl = NULL;
7243 break; 7248 break;
@@ -7246,6 +7251,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7246 *repl = '\0'; 7251 *repl = '\0';
7247 break; 7252 break;
7248 } 7253 }
7254 /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */
7249 if ((unsigned char)*repl == CTLESC && repl[1]) 7255 if ((unsigned char)*repl == CTLESC && repl[1])
7250 repl++; 7256 repl++;
7251 repl++; 7257 repl++;
@@ -7549,14 +7555,13 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7549 * ash -c 'echo ${#1#}' name:'1=#' 7555 * ash -c 'echo ${#1#}' name:'1=#'
7550 */ 7556 */
7551static NOINLINE ssize_t 7557static NOINLINE ssize_t
7552varvalue(char *name, int varflags, int flags, int *quotedp) 7558varvalue(char *name, int varflags, int flags, int quoted)
7553{ 7559{
7554 const char *p; 7560 const char *p;
7555 int num; 7561 int num;
7556 int i; 7562 int i;
7557 ssize_t len = 0; 7563 ssize_t len = 0;
7558 int sep; 7564 int sep;
7559 int quoted = *quotedp;
7560 int subtype = varflags & VSTYPE; 7565 int subtype = varflags & VSTYPE;
7561 int discard = subtype == VSPLUS || subtype == VSLENGTH; 7566 int discard = subtype == VSPLUS || subtype == VSLENGTH;
7562 int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL; 7567 int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL;
@@ -7604,13 +7609,27 @@ varvalue(char *name, int varflags, int flags, int *quotedp)
7604 case '*': { 7609 case '*': {
7605 char **ap; 7610 char **ap;
7606 char sepc; 7611 char sepc;
7612 char c;
7607 7613
7608 if (quoted) 7614 /* We will set c to 0 or ~0 depending on whether
7609 sep = 0; 7615 * we're doing field splitting. We won't do field
7610 sep |= ifsset() ? ifsval()[0] : ' '; 7616 * splitting if either we're quoted or sep is zero.
7617 *
7618 * Instead of testing (quoted || !sep) the following
7619 * trick optimises away any branches by using the
7620 * fact that EXP_QUOTED (which is the only bit that
7621 * can be set in quoted) is the same as EXP_FULL <<
7622 * CHAR_BIT (which is the only bit that can be set
7623 * in sep).
7624 */
7625#if EXP_QUOTED >> CHAR_BIT != EXP_FULL
7626#error The following two lines expect EXP_QUOTED == EXP_FULL << CHAR_BIT
7627#endif
7628 c = !((quoted | ~sep) & EXP_QUOTED) - 1;
7629 sep &= ~quoted;
7630 sep |= ifsset() ? (unsigned char)(c & ifsval()[0]) : ' ';
7611 param: 7631 param:
7612 sepc = sep; 7632 sepc = sep;
7613 *quotedp = !sepc;
7614 ap = shellparam.p; 7633 ap = shellparam.p;
7615 if (!ap) 7634 if (!ap)
7616 return -1; 7635 return -1;
@@ -7675,7 +7694,6 @@ evalvar(char *p, int flag)
7675 char varflags; 7694 char varflags;
7676 char subtype; 7695 char subtype;
7677 int quoted; 7696 int quoted;
7678 char easy;
7679 char *var; 7697 char *var;
7680 int patloc; 7698 int patloc;
7681 int startloc; 7699 int startloc;
@@ -7689,12 +7707,11 @@ evalvar(char *p, int flag)
7689 7707
7690 quoted = flag & EXP_QUOTED; 7708 quoted = flag & EXP_QUOTED;
7691 var = p; 7709 var = p;
7692 easy = (!quoted || (*var == '@' && shellparam.nparam));
7693 startloc = expdest - (char *)stackblock(); 7710 startloc = expdest - (char *)stackblock();
7694 p = strchr(p, '=') + 1; //TODO: use var_end(p)? 7711 p = strchr(p, '=') + 1; //TODO: use var_end(p)?
7695 7712
7696 again: 7713 again:
7697 varlen = varvalue(var, varflags, flag, &quoted); 7714 varlen = varvalue(var, varflags, flag, quoted);
7698 if (varflags & VSNUL) 7715 if (varflags & VSNUL)
7699 varlen--; 7716 varlen--;
7700 7717
@@ -7740,8 +7757,11 @@ evalvar(char *p, int flag)
7740 7757
7741 if (subtype == VSNORMAL) { 7758 if (subtype == VSNORMAL) {
7742 record: 7759 record:
7743 if (!easy) 7760 if (quoted) {
7744 goto end; 7761 quoted = *var == '@' && shellparam.nparam;
7762 if (!quoted)
7763 goto end;
7764 }
7745 recordregion(startloc, expdest - (char *)stackblock(), quoted); 7765 recordregion(startloc, expdest - (char *)stackblock(), quoted);
7746 goto end; 7766 goto end;
7747 } 7767 }
@@ -7999,7 +8019,7 @@ expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len)
7999 } 8019 }
8000 } 8020 }
8001 } else { 8021 } else {
8002 if (*p == '\\') 8022 if (*p == '\\' && p[1])
8003 esc++; 8023 esc++;
8004 if (p[esc] == '/') { 8024 if (p[esc] == '/') {
8005 if (metaflag) 8025 if (metaflag)
@@ -8013,7 +8033,7 @@ expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len)
8013 return; 8033 return;
8014 p = name; 8034 p = name;
8015 do { 8035 do {
8016 if (*p == '\\') 8036 if (*p == '\\' && p[1])
8017 p++; 8037 p++;
8018 *enddir++ = *p; 8038 *enddir++ = *p;
8019 } while (*p++); 8039 } while (*p++);
@@ -8025,7 +8045,7 @@ expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len)
8025 if (name < start) { 8045 if (name < start) {
8026 p = name; 8046 p = name;
8027 do { 8047 do {
8028 if (*p == '\\') 8048 if (*p == '\\' && p[1])
8029 p++; 8049 p++;
8030 *enddir++ = *p++; 8050 *enddir++ = *p++;
8031 } while (p < start); 8051 } while (p < start);
@@ -8473,15 +8493,15 @@ static void shellexec(char *prog, char **argv, const char *path, int idx)
8473 8493
8474 /* Map to POSIX errors */ 8494 /* Map to POSIX errors */
8475 switch (e) { 8495 switch (e) {
8476 case EACCES: 8496 default:
8477 exerrno = 126; 8497 exerrno = 126;
8478 break; 8498 break;
8499 case ELOOP:
8500 case ENAMETOOLONG:
8479 case ENOENT: 8501 case ENOENT:
8502 case ENOTDIR:
8480 exerrno = 127; 8503 exerrno = 127;
8481 break; 8504 break;
8482 default:
8483 exerrno = 2;
8484 break;
8485 } 8505 }
8486 exitstatus = exerrno; 8506 exitstatus = exerrno;
8487 TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n", 8507 TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n",
@@ -10083,9 +10103,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
10083 shellparam.optind = 1; 10103 shellparam.optind = 1;
10084 shellparam.optoff = -1; 10104 shellparam.optoff = -1;
10085#endif 10105#endif
10086 pushlocalvars();
10087 evaltree(func->n.ndefun.body, flags & EV_TESTED); 10106 evaltree(func->n.ndefun.body, flags & EV_TESTED);
10088 poplocalvars(0);
10089 funcdone: 10107 funcdone:
10090 INT_OFF; 10108 INT_OFF;
10091 funcline = savefuncline; 10109 funcline = savefuncline;
@@ -10413,6 +10431,7 @@ find_builtin(const char *name)
10413/* 10431/*
10414 * Execute a simple command. 10432 * Execute a simple command.
10415 */ 10433 */
10434static void unwindfiles(struct parsefile *stop);
10416static int 10435static int
10417isassignment(const char *p) 10436isassignment(const char *p)
10418{ 10437{
@@ -10436,6 +10455,7 @@ evalcommand(union node *cmd, int flags)
10436 "\0\0", bltincmd /* why three NULs? */ 10455 "\0\0", bltincmd /* why three NULs? */
10437 }; 10456 };
10438 struct localvar_list *localvar_stop; 10457 struct localvar_list *localvar_stop;
10458 struct parsefile *file_stop;
10439 struct redirtab *redir_stop; 10459 struct redirtab *redir_stop;
10440 struct stackmark smark; 10460 struct stackmark smark;
10441 union node *argp; 10461 union node *argp;
@@ -10461,6 +10481,7 @@ evalcommand(union node *cmd, int flags)
10461 TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); 10481 TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
10462 setstackmark(&smark); 10482 setstackmark(&smark);
10463 localvar_stop = pushlocalvars(); 10483 localvar_stop = pushlocalvars();
10484 file_stop = g_parsefile;
10464 back_exitstatus = 0; 10485 back_exitstatus = 0;
10465 10486
10466 cmdentry.cmdtype = CMDBUILTIN; 10487 cmdentry.cmdtype = CMDBUILTIN;
@@ -10742,7 +10763,6 @@ evalcommand(union node *cmd, int flags)
10742 goto readstatus; 10763 goto readstatus;
10743 10764
10744 case CMDFUNCTION: 10765 case CMDFUNCTION:
10745 poplocalvars(1);
10746 /* See above for the rationale */ 10766 /* See above for the rationale */
10747 dowait(DOWAIT_NONBLOCK, NULL); 10767 dowait(DOWAIT_NONBLOCK, NULL);
10748 if (evalfun(cmdentry.u.func, argc, argv, flags)) 10768 if (evalfun(cmdentry.u.func, argc, argv, flags))
@@ -10756,6 +10776,7 @@ evalcommand(union node *cmd, int flags)
10756 if (cmd->ncmd.redirect) 10776 if (cmd->ncmd.redirect)
10757 popredir(/*drop:*/ cmd_is_exec); 10777 popredir(/*drop:*/ cmd_is_exec);
10758 unwindredir(redir_stop); 10778 unwindredir(redir_stop);
10779 unwindfiles(file_stop);
10759 unwindlocalvars(localvar_stop); 10780 unwindlocalvars(localvar_stop);
10760 if (lastarg) { 10781 if (lastarg) {
10761 /* dsl: I think this is intended to be used to support 10782 /* dsl: I think this is intended to be used to support
@@ -11278,14 +11299,20 @@ popfile(void)
11278 INT_ON; 11299 INT_ON;
11279} 11300}
11280 11301
11302static void
11303unwindfiles(struct parsefile *stop)
11304{
11305 while (g_parsefile != stop)
11306 popfile();
11307}
11308
11281/* 11309/*
11282 * Return to top level. 11310 * Return to top level.
11283 */ 11311 */
11284static void 11312static void
11285popallfiles(void) 11313popallfiles(void)
11286{ 11314{
11287 while (g_parsefile != &basepf) 11315 unwindfiles(&basepf);
11288 popfile();
11289} 11316}
11290 11317
11291#if !ENABLE_PLATFORM_MINGW32 11318#if !ENABLE_PLATFORM_MINGW32
@@ -12929,7 +12956,7 @@ parsesub: {
12929 STPUTC(c, out); 12956 STPUTC(c, out);
12930 c = pgetc_eatbnl(); 12957 c = pgetc_eatbnl();
12931 } while (isdigit(c)); 12958 } while (isdigit(c));
12932 } else { 12959 } else if (c != '}') {
12933 /* $[{[#]]<specialchar>[}] */ 12960 /* $[{[#]]<specialchar>[}] */
12934 int cc = c; 12961 int cc = c;
12935 12962
@@ -12955,7 +12982,8 @@ parsesub: {
12955 } 12982 }
12956 12983
12957 USTPUTC(cc, out); 12984 USTPUTC(cc, out);
12958 } 12985 } else
12986 goto badsub;
12959 12987
12960 if (c != '}' && subtype == VSLENGTH) { 12988 if (c != '}' && subtype == VSLENGTH) {
12961 /* ${#VAR didn't end with } */ 12989 /* ${#VAR didn't end with } */
@@ -14297,38 +14325,35 @@ letcmd(int argc UNUSED_PARAM, char **argv)
14297static int FAST_FUNC 14325static int FAST_FUNC
14298readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) 14326readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
14299{ 14327{
14300 char *opt_n = NULL; 14328 struct builtin_read_params params;
14301 char *opt_p = NULL;
14302 char *opt_t = NULL;
14303 char *opt_u = NULL;
14304 char *opt_d = NULL; /* optimized out if !BASH */
14305 int read_flags = 0;
14306 const char *r; 14329 const char *r;
14307 int i; 14330 int i;
14308 14331
14332 memset(&params, 0, sizeof(params));
14333
14309 while ((i = nextopt("p:u:rt:n:sd:")) != '\0') { 14334 while ((i = nextopt("p:u:rt:n:sd:")) != '\0') {
14310 switch (i) { 14335 switch (i) {
14311 case 'p': 14336 case 'p':
14312 opt_p = optionarg; 14337 params.opt_p = optionarg;
14313 break; 14338 break;
14314 case 'n': 14339 case 'n':
14315 opt_n = optionarg; 14340 params.opt_n = optionarg;
14316 break; 14341 break;
14317 case 's': 14342 case 's':
14318 read_flags |= BUILTIN_READ_SILENT; 14343 params.read_flags |= BUILTIN_READ_SILENT;
14319 break; 14344 break;
14320 case 't': 14345 case 't':
14321 opt_t = optionarg; 14346 params.opt_t = optionarg;
14322 break; 14347 break;
14323 case 'r': 14348 case 'r':
14324 read_flags |= BUILTIN_READ_RAW; 14349 params.read_flags |= BUILTIN_READ_RAW;
14325 break; 14350 break;
14326 case 'u': 14351 case 'u':
14327 opt_u = optionarg; 14352 params.opt_u = optionarg;
14328 break; 14353 break;
14329#if BASH_READ_D 14354#if BASH_READ_D
14330 case 'd': 14355 case 'd':
14331 opt_d = optionarg; 14356 params.opt_d = optionarg;
14332 break; 14357 break;
14333#endif 14358#endif
14334 default: 14359 default:
@@ -14336,21 +14361,16 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
14336 } 14361 }
14337 } 14362 }
14338 14363
14364 params.argv = argptr;
14365 params.setvar = setvar0;
14366 params.ifs = bltinlookup("IFS"); /* can be NULL */
14367
14339 /* "read -s" needs to save/restore termios, can't allow ^C 14368 /* "read -s" needs to save/restore termios, can't allow ^C
14340 * to jump out of it. 14369 * to jump out of it.
14341 */ 14370 */
14342 again: 14371 again:
14343 INT_OFF; 14372 INT_OFF;
14344 r = shell_builtin_read(setvar0, 14373 r = shell_builtin_read(&params);
14345 argptr,
14346 bltinlookup("IFS"), /* can be NULL */
14347 read_flags,
14348 opt_n,
14349 opt_p,
14350 opt_t,
14351 opt_u,
14352 opt_d
14353 );
14354 INT_ON; 14374 INT_ON;
14355 14375
14356 if ((uintptr_t)r == 1 && errno == EINTR) { 14376 if ((uintptr_t)r == 1 && errno == EINTR) {
@@ -14595,6 +14615,7 @@ init(void)
14595 } 14615 }
14596 } 14616 }
14597 14617
14618 setvareq((char*)defifsvar, VTEXTFIXED);
14598 setvareq((char*)defoptindvar, VTEXTFIXED); 14619 setvareq((char*)defoptindvar, VTEXTFIXED);
14599 14620
14600 setvar0("PPID", utoa(getppid())); 14621 setvar0("PPID", utoa(getppid()));
diff --git a/shell/ash_test/ash-glob/glob_bkslash_in_var.right b/shell/ash_test/ash-glob/glob_bkslash_in_var.right
new file mode 100644
index 000000000..f1484b9e4
--- /dev/null
+++ b/shell/ash_test/ash-glob/glob_bkslash_in_var.right
@@ -0,0 +1,4 @@
1Unquoted non-matching glob in var:'test*.TMP/\name_doesnt_exist'
2Unquoted matching glob in var: 'testdir.TMP/name'
3Quoted non-matching glob in var: 'test*.TMP/\name_doesnt_exist'
4Quoted matching glob in var: 'test*.TMP/\name'
diff --git a/shell/ash_test/ash-glob/glob_bkslash_in_var.tests b/shell/ash_test/ash-glob/glob_bkslash_in_var.tests
new file mode 100755
index 000000000..e3dedc4ac
--- /dev/null
+++ b/shell/ash_test/ash-glob/glob_bkslash_in_var.tests
@@ -0,0 +1,10 @@
1mkdir testdir.TMP
2>testdir.TMP/name
3a="test*.TMP/\name_doesnt_exist"
4b="test*.TMP/\name"
5printf "Unquoted non-matching glob in var:'%s'\n" $a
6printf "Unquoted matching glob in var: '%s'\n" $b
7printf "Quoted non-matching glob in var: '%s'\n" "$a"
8printf "Quoted matching glob in var: '%s'\n" "$b"
9rm -f testdir.TMP/name
10rmdir testdir.TMP
diff --git a/shell/ash_test/ash-misc/empty_for1.right b/shell/ash_test/ash-misc/empty_for1.right
new file mode 100644
index 000000000..46ffcece7
--- /dev/null
+++ b/shell/ash_test/ash-misc/empty_for1.right
@@ -0,0 +1 @@
Zero:0
diff --git a/shell/ash_test/ash-misc/empty_for1.tests b/shell/ash_test/ash-misc/empty_for1.tests
new file mode 100755
index 000000000..5a2554d54
--- /dev/null
+++ b/shell/ash_test/ash-misc/empty_for1.tests
@@ -0,0 +1,5 @@
1false
2for v; do
3 exit 2
4done
5echo Zero:$?
diff --git a/shell/ash_test/ash-misc/env_and_func.right b/shell/ash_test/ash-misc/env_and_func.right
index 5fc3488ae..4a1545058 100644
--- a/shell/ash_test/ash-misc/env_and_func.right
+++ b/shell/ash_test/ash-misc/env_and_func.right
@@ -1,2 +1,2 @@
1var=val 1var=val
2var=val 2var=old
diff --git a/shell/ash_test/ash-misc/env_and_func.tests b/shell/ash_test/ash-misc/env_and_func.tests
index 3efef1a41..1c63eafd8 100755
--- a/shell/ash_test/ash-misc/env_and_func.tests
+++ b/shell/ash_test/ash-misc/env_and_func.tests
@@ -3,6 +3,6 @@ f() { echo "var=$var"; }
3# bash: POSIXLY_CORRECT behavior is to "leak" new variable values 3# bash: POSIXLY_CORRECT behavior is to "leak" new variable values
4# out of function invocations (similar to "special builtins" behavior); 4# out of function invocations (similar to "special builtins" behavior);
5# but in "bash mode", they don't leak. 5# but in "bash mode", they don't leak.
6# hush does not "leak" values. ash does. 6# hush does not "leak" values. ash used to, but now does not.
7var=val f 7var=val f
8echo "var=$var" 8echo "var=$var"
diff --git a/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.right b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.right
new file mode 100644
index 000000000..439dca578
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.right
@@ -0,0 +1,2 @@
1-dev-ram
2/dev-am
diff --git a/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.tests b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.tests
new file mode 100755
index 000000000..b83fb8eeb
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.tests
@@ -0,0 +1,3 @@
1v=/dev/ram
2echo ${v////-}
3echo ${v///r/-}
diff --git a/shell/ash_test/ash-vars/var_leak.right b/shell/ash_test/ash-vars/var_leak.right
index 01a5e3263..764680086 100644
--- a/shell/ash_test/ash-vars/var_leak.right
+++ b/shell/ash_test/ash-vars/var_leak.right
@@ -1,4 +1,4 @@
1should be empty: '' 1should be empty: ''
2should be empty: '' 2should be empty: ''
3should be not empty: 'val2' 3should be not empty: 'val2'
4should be not empty: 'val3' 4should be empty: ''
diff --git a/shell/ash_test/ash-vars/var_leak.tests b/shell/ash_test/ash-vars/var_leak.tests
index 5242e24eb..adf66692e 100755
--- a/shell/ash_test/ash-vars/var_leak.tests
+++ b/shell/ash_test/ash-vars/var_leak.tests
@@ -15,9 +15,7 @@ VAR=''
15VAR=val2 exec 2>&1 15VAR=val2 exec 2>&1
16echo "should be not empty: '$VAR'" 16echo "should be not empty: '$VAR'"
17 17
18# ash follows the "function call is a special builtin" rule here
19# (bash does not do it)
20f() { true; } 18f() { true; }
21VAR='' 19VAR=''
22VAR=val3 f 20VAR=val3 f
23echo "should be not empty: '$VAR'" 21echo "should be empty: '$VAR'"
diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs5.right b/shell/ash_test/ash-vars/var_wordsplit_ifs5.right
new file mode 100644
index 000000000..46ffcece7
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_wordsplit_ifs5.right
@@ -0,0 +1 @@
Zero:0
diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs5.tests b/shell/ash_test/ash-vars/var_wordsplit_ifs5.tests
new file mode 100755
index 000000000..d382116df
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_wordsplit_ifs5.tests
@@ -0,0 +1,4 @@
1IFS=
2set --
3set -- $@ $*
4echo Zero:$#
diff --git a/shell/hush.c b/shell/hush.c
index e3c6e2de9..881331c5b 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -990,7 +990,16 @@ struct globals {
990#if ENABLE_HUSH_MEMLEAK 990#if ENABLE_HUSH_MEMLEAK
991 unsigned long memleak_value; 991 unsigned long memleak_value;
992#endif 992#endif
993#if HUSH_DEBUG 993#if ENABLE_HUSH_MODE_X
994 unsigned x_mode_depth;
995 /* "set -x" output should not be redirectable with subsequent 2>FILE.
996 * We dup fd#2 to x_mode_fd when "set -x" is executed, and use it
997 * for all subsequent output.
998 */
999 int x_mode_fd;
1000 o_string x_mode_buf;
1001#endif
1002#if HUSH_DEBUG >= 2
994 int debug_indent; 1003 int debug_indent;
995#endif 1004#endif
996 struct sigaction sa; 1005 struct sigaction sa;
@@ -1212,7 +1221,7 @@ static const struct built_in_command bltins2[] = {
1212 1221
1213/* Debug printouts. 1222/* Debug printouts.
1214 */ 1223 */
1215#if HUSH_DEBUG 1224#if HUSH_DEBUG >= 2
1216/* prevent disasters with G.debug_indent < 0 */ 1225/* prevent disasters with G.debug_indent < 0 */
1217# define indent() fdprintf(2, "%*s", (G.debug_indent * 2) & 0xff, "") 1226# define indent() fdprintf(2, "%*s", (G.debug_indent * 2) & 0xff, "")
1218# define debug_enter() (G.debug_indent++) 1227# define debug_enter() (G.debug_indent++)
@@ -1657,6 +1666,12 @@ static int move_HFILEs_on_redirect(int fd, int avoid_fd)
1657 } 1666 }
1658 fl = fl->next_hfile; 1667 fl = fl->next_hfile;
1659 } 1668 }
1669#if ENABLE_HUSH_MODE_X
1670 if (G.x_mode_fd > 0 && fd == G.x_mode_fd) {
1671 G.x_mode_fd = xdup_CLOEXEC_and_close(fd, avoid_fd);
1672 return 1; /* "found and moved" */
1673 }
1674#endif
1660 return 0; /* "not in the list" */ 1675 return 0; /* "not in the list" */
1661} 1676}
1662#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU 1677#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
@@ -1668,9 +1683,15 @@ static void close_all_HFILE_list(void)
1668 * It is disastrous if we share memory with a vforked parent. 1683 * It is disastrous if we share memory with a vforked parent.
1669 * I'm not sure we never come here after vfork. 1684 * I'm not sure we never come here after vfork.
1670 * Therefore just close fd, nothing more. 1685 * Therefore just close fd, nothing more.
1686 *
1687 * ">" instead of ">=": we don't close fd#0,
1688 * interactive shell uses hfopen(NULL) as stdin input
1689 * which has fl->fd == 0, but fd#0 gets redirected in pipes.
1690 * If we'd close it here, then e.g. interactive "set | sort"
1691 * with NOFORKed sort, would have sort's input fd closed.
1671 */ 1692 */
1672 /*hfclose(fl); - unsafe */ 1693 if (fl->fd > 0)
1673 if (fl->fd >= 0) 1694 /*hfclose(fl); - unsafe */
1674 close(fl->fd); 1695 close(fl->fd);
1675 fl = fl->next_hfile; 1696 fl = fl->next_hfile;
1676 } 1697 }
@@ -2376,6 +2397,12 @@ static int set_local_var(char *str, unsigned flags)
2376 return 0; 2397 return 0;
2377} 2398}
2378 2399
2400static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
2401{
2402 char *var = xasprintf("%s=%s", name, val);
2403 set_local_var(var, /*flag:*/ 0);
2404}
2405
2379/* Used at startup and after each cd */ 2406/* Used at startup and after each cd */
2380static void set_pwd_var(unsigned flag) 2407static void set_pwd_var(unsigned flag)
2381{ 2408{
@@ -2422,15 +2449,6 @@ static int unset_local_var(const char *name)
2422} 2449}
2423#endif 2450#endif
2424 2451
2425#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS \
2426 || (ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_EDITING_FANCY_PROMPT)
2427static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
2428{
2429 char *var = xasprintf("%s=%s", name, val);
2430 set_local_var(var, /*flag:*/ 0);
2431}
2432#endif
2433
2434 2452
2435/* 2453/*
2436 * Helpers for "var1=val1 var2=val2 cmd" feature 2454 * Helpers for "var1=val1 var2=val2 cmd" feature
@@ -2900,6 +2918,11 @@ static void o_addstr(o_string *o, const char *str)
2900 o_addblock(o, str, strlen(str)); 2918 o_addblock(o, str, strlen(str));
2901} 2919}
2902 2920
2921static void o_addstr_with_NUL(o_string *o, const char *str)
2922{
2923 o_addblock(o, str, strlen(str) + 1);
2924}
2925
2903#if !BB_MMU 2926#if !BB_MMU
2904static void nommu_addchr(o_string *o, int ch) 2927static void nommu_addchr(o_string *o, int ch)
2905{ 2928{
@@ -2910,10 +2933,36 @@ static void nommu_addchr(o_string *o, int ch)
2910# define nommu_addchr(o, str) ((void)0) 2933# define nommu_addchr(o, str) ((void)0)
2911#endif 2934#endif
2912 2935
2913static void o_addstr_with_NUL(o_string *o, const char *str) 2936#if ENABLE_HUSH_MODE_X
2937static void x_mode_addchr(int ch)
2914{ 2938{
2915 o_addblock(o, str, strlen(str) + 1); 2939 o_addchr(&G.x_mode_buf, ch);
2940}
2941static void x_mode_addstr(const char *str)
2942{
2943 o_addstr(&G.x_mode_buf, str);
2916} 2944}
2945static void x_mode_addblock(const char *str, int len)
2946{
2947 o_addblock(&G.x_mode_buf, str, len);
2948}
2949static void x_mode_prefix(void)
2950{
2951 int n = G.x_mode_depth;
2952 do x_mode_addchr('+'); while (--n >= 0);
2953}
2954static void x_mode_flush(void)
2955{
2956 int len = G.x_mode_buf.length;
2957 if (len <= 0)
2958 return;
2959 if (G.x_mode_fd > 0) {
2960 G.x_mode_buf.data[len] = '\n';
2961 full_write(G.x_mode_fd, G.x_mode_buf.data, len + 1);
2962 }
2963 G.x_mode_buf.length = 0;
2964}
2965#endif
2917 2966
2918/* 2967/*
2919 * HUSH_BRACE_EXPANSION code needs corresponding quoting on variable expansion side. 2968 * HUSH_BRACE_EXPANSION code needs corresponding quoting on variable expansion side.
@@ -3068,6 +3117,13 @@ static int o_save_ptr_helper(o_string *o, int n)
3068 o->data = xrealloc(o->data, o->maxlen + 1); 3117 o->data = xrealloc(o->data, o->maxlen + 1);
3069 list = (char**)o->data; 3118 list = (char**)o->data;
3070 memmove(list + n + 0x10, list + n, string_len); 3119 memmove(list + n + 0x10, list + n, string_len);
3120 /*
3121 * expand_on_ifs() has a "previous argv[] ends in IFS?"
3122 * check. (grep for -prev-ifs-check-).
3123 * Ensure that argv[-1][last] is not garbage
3124 * but zero bytes, to save index check there.
3125 */
3126 list[n + 0x10 - 1] = 0;
3071 o->length += 0x10 * sizeof(list[0]); 3127 o->length += 0x10 * sizeof(list[0]);
3072 } else { 3128 } else {
3073 debug_printf_list("list[%d]=%d string_start=%d\n", 3129 debug_printf_list("list[%d]=%d string_start=%d\n",
@@ -3095,6 +3151,41 @@ static int o_get_last_ptr(o_string *o, int n)
3095 return ((int)(uintptr_t)list[n-1]) + string_start; 3151 return ((int)(uintptr_t)list[n-1]) + string_start;
3096} 3152}
3097 3153
3154/*
3155 * Globbing routines.
3156 *
3157 * Most words in commands need to be globbed, even ones which are
3158 * (single or double) quoted. This stems from the possiblity of
3159 * constructs like "abc"* and 'abc'* - these should be globbed.
3160 * Having a different code path for fully-quoted strings ("abc",
3161 * 'abc') would only help performance-wise, but we still need
3162 * code for partially-quoted strings.
3163 *
3164 * Unfortunately, if we want to match bash and ash behavior in all cases,
3165 * the logic can't be "shell-syntax argument is first transformed
3166 * to a string, then globbed, and if globbing does not match anything,
3167 * it is used verbatim". Here are two examples where it fails:
3168 *
3169 * echo 'b\*'?
3170 *
3171 * The globbing can't be avoided (because of '?' at the end).
3172 * The glob pattern is: b\\\*? - IOW, both \ and * are literals
3173 * and are glob-escaped. If this does not match, bash/ash print b\*?
3174 * - IOW: they "unbackslash" the glob pattern.
3175 * Now, look at this:
3176 *
3177 * v='\\\*'; echo b$v?
3178 *
3179 * The glob pattern is the same here: b\\\*? - the unquoted $v expansion
3180 * should be used as glob pattern with no changes. However, if glob
3181 * does not match, bash/ash print b\\\*? - NOT THE SAME as first example!
3182 *
3183 * ash implements this by having an encoded representation of the word
3184 * to glob, which IS NOT THE SAME as the glob pattern - it has more data.
3185 * Glob pattern is derived from it. If glob fails, the decision what result
3186 * should be is made using that encoded representation. Not glob pattern.
3187 */
3188
3098#if ENABLE_HUSH_BRACE_EXPANSION 3189#if ENABLE_HUSH_BRACE_EXPANSION
3099/* There in a GNU extension, GLOB_BRACE, but it is not usable: 3190/* There in a GNU extension, GLOB_BRACE, but it is not usable:
3100 * first, it processes even {a} (no commas), second, 3191 * first, it processes even {a} (no commas), second,
@@ -4877,6 +4968,15 @@ static int parse_dollar(o_string *as_string,
4877 end_ch = '}' * 0x100 + '/'; 4968 end_ch = '}' * 0x100 + '/';
4878 } 4969 }
4879 o_addchr(dest, ch); 4970 o_addchr(dest, ch);
4971 /* The pattern can't be empty.
4972 * IOW: if the first char after "${v//" is a slash,
4973 * it does not terminate the pattern - it's the first char of the pattern:
4974 * v=/dev/ram; echo ${v////-} prints -dev-ram (pattern is "/")
4975 * v=/dev/ram; echo ${v///r/-} prints /dev-am (pattern is "/r")
4976 */
4977 if (i_peek(input) == '/') {
4978 o_addchr(dest, i_getch(input));
4979 }
4880 again: 4980 again:
4881 if (!BB_MMU) 4981 if (!BB_MMU)
4882 pos = dest->length; 4982 pos = dest->length;
@@ -5796,12 +5896,16 @@ static int expand_on_ifs(o_string *output, int n, const char *str)
5796 /* Start new word... but not always! */ 5896 /* Start new word... but not always! */
5797 /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */ 5897 /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */
5798 if (output->has_quoted_part 5898 if (output->has_quoted_part
5799 /* Case "v=' a'; echo $v": 5899 /*
5900 * Case "v=' a'; echo $v":
5800 * here nothing precedes the space in $v expansion, 5901 * here nothing precedes the space in $v expansion,
5801 * therefore we should not finish the word 5902 * therefore we should not finish the word
5802 * (IOW: if there *is* word to finalize, only then do it): 5903 * (IOW: if there *is* word to finalize, only then do it):
5904 * It's okay if this accesses the byte before first argv[]:
5905 * past call to o_save_ptr() cleared it to zero byte
5906 * (grep for -prev-ifs-check-).
5803 */ 5907 */
5804 || (n > 0 && output->data[output->length - 1]) 5908 || output->data[output->length - 1]
5805 ) { 5909 ) {
5806 new_word: 5910 new_word:
5807 o_addchr(output, '\0'); 5911 o_addchr(output, '\0');
@@ -5856,6 +5960,26 @@ static char *encode_then_expand_string(const char *str)
5856 return exp_str; 5960 return exp_str;
5857} 5961}
5858 5962
5963static const char *first_special_char_in_vararg(const char *cp)
5964{
5965 for (;;) {
5966 if (!*cp) return NULL; /* string has no special chars */
5967 if (*cp == '$') return cp;
5968 if (*cp == '\\') return cp;
5969 if (*cp == '\'') return cp;
5970 if (*cp == '"') return cp;
5971#if ENABLE_HUSH_TICK
5972 if (*cp == '`') return cp;
5973#endif
5974 /* dquoted "${x:+ARG}" should not glob, therefore
5975 * '*' et al require some non-literal processing: */
5976 if (*cp == '*') return cp;
5977 if (*cp == '?') return cp;
5978 if (*cp == '[') return cp;
5979 cp++;
5980 }
5981}
5982
5859/* Expanding ARG in ${var#ARG}, ${var%ARG}, or ${var/ARG/ARG}. 5983/* Expanding ARG in ${var#ARG}, ${var%ARG}, or ${var/ARG/ARG}.
5860 * These can contain single- and double-quoted strings, 5984 * These can contain single- and double-quoted strings,
5861 * and treated as if the ARG string is initially unquoted. IOW: 5985 * and treated as if the ARG string is initially unquoted. IOW:
@@ -5875,19 +5999,10 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int
5875 char *exp_str; 5999 char *exp_str;
5876 struct in_str input; 6000 struct in_str input;
5877 o_string dest = NULL_O_STRING; 6001 o_string dest = NULL_O_STRING;
5878 const char *cp;
5879 6002
5880 cp = str; 6003 if (!first_special_char_in_vararg(str)) {
5881 for (;;) { 6004 /* string has no special chars */
5882 if (!*cp) return NULL; /* string has no special chars */ 6005 return NULL;
5883 if (*cp == '$') break;
5884 if (*cp == '\\') break;
5885 if (*cp == '\'') break;
5886 if (*cp == '"') break;
5887#if ENABLE_HUSH_TICK
5888 if (*cp == '`') break;
5889#endif
5890 cp++;
5891 } 6006 }
5892 6007
5893 setup_string_in_str(&input, str); 6008 setup_string_in_str(&input, str);
@@ -5968,26 +6083,19 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int
5968/* Expanding ARG in ${var+ARG}, ${var-ARG} 6083/* Expanding ARG in ${var+ARG}, ${var-ARG}
5969 */ 6084 */
5970static int encode_then_append_var_plusminus(o_string *output, int n, 6085static int encode_then_append_var_plusminus(o_string *output, int n,
5971 const char *str, int dquoted) 6086 char *str, int dquoted)
5972{ 6087{
5973 struct in_str input; 6088 struct in_str input;
5974 o_string dest = NULL_O_STRING; 6089 o_string dest = NULL_O_STRING;
5975 6090
5976#if 0 //todo? 6091 if (!first_special_char_in_vararg(str)
5977 const char *cp; 6092 && '\0' == str[strcspn(str, G.ifs)]
5978 cp = str; 6093 ) {
5979 for (;;) { 6094 /* string has no special chars
5980 if (!*cp) return NULL; /* string has no special chars */ 6095 * && string has no $IFS chars
5981 if (*cp == '$') break; 6096 */
5982 if (*cp == '\\') break; 6097 return expand_vars_to_list(output, n, str);
5983 if (*cp == '\'') break;
5984 if (*cp == '"') break;
5985#if ENABLE_HUSH_TICK
5986 if (*cp == '`') break;
5987#endif
5988 cp++;
5989 } 6098 }
5990#endif
5991 6099
5992 setup_string_in_str(&input, str); 6100 setup_string_in_str(&input, str);
5993 6101
@@ -7182,11 +7290,8 @@ static int generate_stream_from_string(const char *s, pid_t *pid_p)
7182 + (1 << SIGTTIN) 7290 + (1 << SIGTTIN)
7183 + (1 << SIGTTOU) 7291 + (1 << SIGTTOU)
7184 , SIG_IGN); 7292 , SIG_IGN);
7185 CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */
7186 close(channel[0]); /* NB: close _first_, then move fd! */ 7293 close(channel[0]); /* NB: close _first_, then move fd! */
7187 xmove_fd(channel[1], 1); 7294 xmove_fd(channel[1], 1);
7188 /* Prevent it from trying to handle ctrl-z etc */
7189 IF_HUSH_JOB(G.run_list_level = 1;)
7190# if ENABLE_HUSH_TRAP 7295# if ENABLE_HUSH_TRAP
7191 /* Awful hack for `trap` or $(trap). 7296 /* Awful hack for `trap` or $(trap).
7192 * 7297 *
@@ -7233,7 +7338,12 @@ static int generate_stream_from_string(const char *s, pid_t *pid_p)
7233 } 7338 }
7234# endif 7339# endif
7235# if BB_MMU 7340# if BB_MMU
7341 /* Prevent it from trying to handle ctrl-z etc */
7342 IF_HUSH_JOB(G.run_list_level = 1;)
7343 CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */
7236 reset_traps_to_defaults(); 7344 reset_traps_to_defaults();
7345 IF_HUSH_MODE_X(G.x_mode_depth++;)
7346 //bb_error_msg("%s: ++x_mode_depth=%d", __func__, G.x_mode_depth);
7237 parse_and_run_string(s); 7347 parse_and_run_string(s);
7238 _exit(G.last_exitcode); 7348 _exit(G.last_exitcode);
7239# else 7349# else
@@ -8014,28 +8124,65 @@ static void execvp_or_die(char **argv)
8014} 8124}
8015 8125
8016#if ENABLE_HUSH_MODE_X 8126#if ENABLE_HUSH_MODE_X
8127static void x_mode_print_optionally_squoted(const char *str)
8128{
8129 unsigned len;
8130 const char *cp;
8131
8132 cp = str;
8133
8134 /* the set of chars which-cause-string-to-be-squoted mimics bash */
8135 /* test a char with: bash -c 'set -x; echo "CH"' */
8136 if (str[strcspn(str, "\\\"'`$(){}[]<>;#&|~*?!^"
8137 " " "\001\002\003\004\005\006\007"
8138 "\010\011\012\013\014\015\016\017"
8139 "\020\021\022\023\024\025\026\027"
8140 "\030\031\032\033\034\035\036\037"
8141 )
8142 ] == '\0'
8143 ) {
8144 /* string has no special chars */
8145 x_mode_addstr(str);
8146 return;
8147 }
8148
8149 cp = str;
8150 for (;;) {
8151 /* print '....' up to EOL or first squote */
8152 len = (int)(strchrnul(cp, '\'') - cp);
8153 if (len != 0) {
8154 x_mode_addchr('\'');
8155 x_mode_addblock(cp, len);
8156 x_mode_addchr('\'');
8157 cp += len;
8158 }
8159 if (*cp == '\0')
8160 break;
8161 /* string contains squote(s), print them as \' */
8162 x_mode_addchr('\\');
8163 x_mode_addchr('\'');
8164 cp++;
8165 }
8166}
8017static void dump_cmd_in_x_mode(char **argv) 8167static void dump_cmd_in_x_mode(char **argv)
8018{ 8168{
8019 if (G_x_mode && argv) { 8169 if (G_x_mode && argv) {
8020 /* We want to output the line in one write op */ 8170 unsigned n;
8021 char *buf, *p;
8022 int len;
8023 int n;
8024 8171
8025 len = 3; 8172 /* "+[+++...][ cmd...]\n\0" */
8173 x_mode_prefix();
8026 n = 0; 8174 n = 0;
8027 while (argv[n]) 8175 while (argv[n]) {
8028 len += strlen(argv[n++]) + 1; 8176 x_mode_addchr(' ');
8029 buf = xmalloc(len); 8177 if (argv[n][0] == '\0') {
8030 buf[0] = '+'; 8178 x_mode_addchr('\'');
8031 p = buf + 1; 8179 x_mode_addchr('\'');
8032 n = 0; 8180 } else {
8033 while (argv[n]) 8181 x_mode_print_optionally_squoted(argv[n]);
8034 p += sprintf(p, " %s", argv[n++]); 8182 }
8035 *p++ = '\n'; 8183 n++;
8036 *p = '\0'; 8184 }
8037 fputs(buf, stderr); 8185 x_mode_flush();
8038 free(buf);
8039 } 8186 }
8040} 8187}
8041#else 8188#else
@@ -8821,16 +8968,25 @@ static NOINLINE int run_pipe(struct pipe *pi)
8821 restore_redirects(squirrel); 8968 restore_redirects(squirrel);
8822 8969
8823 /* Set shell variables */ 8970 /* Set shell variables */
8824 if (G_x_mode)
8825 bb_putchar_stderr('+');
8826 i = 0; 8971 i = 0;
8827 while (i < command->assignment_cnt) { 8972 while (i < command->assignment_cnt) {
8828 char *p = expand_string_to_string(argv[i], 8973 char *p = expand_string_to_string(argv[i],
8829 EXP_FLAG_ESC_GLOB_CHARS, 8974 EXP_FLAG_ESC_GLOB_CHARS,
8830 /*unbackslash:*/ 1 8975 /*unbackslash:*/ 1
8831 ); 8976 );
8832 if (G_x_mode) 8977#if ENABLE_HUSH_MODE_X
8833 fprintf(stderr, " %s", p); 8978 if (G_x_mode) {
8979 char *eq;
8980 if (i == 0)
8981 x_mode_prefix();
8982 x_mode_addchr(' ');
8983 eq = strchrnul(p, '=');
8984 if (*eq) eq++;
8985 x_mode_addblock(p, (eq - p));
8986 x_mode_print_optionally_squoted(eq);
8987 x_mode_flush();
8988 }
8989#endif
8834 debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); 8990 debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p);
8835 if (set_local_var(p, /*flag:*/ 0)) { 8991 if (set_local_var(p, /*flag:*/ 0)) {
8836 /* assignment to readonly var / putenv error? */ 8992 /* assignment to readonly var / putenv error? */
@@ -8838,8 +8994,6 @@ static NOINLINE int run_pipe(struct pipe *pi)
8838 } 8994 }
8839 i++; 8995 i++;
8840 } 8996 }
8841 if (G_x_mode)
8842 bb_putchar_stderr('\n');
8843 /* Redirect error sets $? to 1. Otherwise, 8997 /* Redirect error sets $? to 1. Otherwise,
8844 * if evaluating assignment value set $?, retain it. 8998 * if evaluating assignment value set $?, retain it.
8845 * Else, clear $?: 8999 * Else, clear $?:
@@ -9288,11 +9442,11 @@ static int run_list(struct pipe *pi)
9288 }; /* argv list with one element: "$@" */ 9442 }; /* argv list with one element: "$@" */
9289 char **vals; 9443 char **vals;
9290 9444
9445 G.last_exitcode = rcode = EXIT_SUCCESS;
9291 vals = (char**)encoded_dollar_at_argv; 9446 vals = (char**)encoded_dollar_at_argv;
9292 if (pi->next->res_word == RES_IN) { 9447 if (pi->next->res_word == RES_IN) {
9293 /* if no variable values after "in" we skip "for" */ 9448 /* if no variable values after "in" we skip "for" */
9294 if (!pi->next->cmds[0].argv) { 9449 if (!pi->next->cmds[0].argv) {
9295 G.last_exitcode = rcode = EXIT_SUCCESS;
9296 debug_printf_exec(": null FOR: exitcode EXIT_SUCCESS\n"); 9450 debug_printf_exec(": null FOR: exitcode EXIT_SUCCESS\n");
9297 break; 9451 break;
9298 } 9452 }
@@ -9626,6 +9780,7 @@ static int set_mode(int state, char mode, const char *o_opt)
9626 break; 9780 break;
9627 case 'x': 9781 case 'x':
9628 IF_HUSH_MODE_X(G_x_mode = state;) 9782 IF_HUSH_MODE_X(G_x_mode = state;)
9783 IF_HUSH_MODE_X(if (G.x_mode_fd <= 0) G.x_mode_fd = dup_CLOEXEC(2, 10);)
9629 break; 9784 break;
9630 case 'o': 9785 case 'o':
9631 if (!o_opt) { 9786 if (!o_opt) {
@@ -9731,6 +9886,10 @@ int hush_main(int argc, char **argv)
9731 uname(&uts); 9886 uname(&uts);
9732 set_local_var_from_halves("HOSTNAME", uts.nodename); 9887 set_local_var_from_halves("HOSTNAME", uts.nodename);
9733 } 9888 }
9889#endif
9890 /* IFS is not inherited from the parent environment */
9891 set_local_var_from_halves("IFS", defifs);
9892
9734 /* bash also exports SHLVL and _, 9893 /* bash also exports SHLVL and _,
9735 * and sets (but doesn't export) the following variables: 9894 * and sets (but doesn't export) the following variables:
9736 * BASH=/bin/bash 9895 * BASH=/bin/bash
@@ -9761,10 +9920,8 @@ int hush_main(int argc, char **argv)
9761 * TERM=dumb 9920 * TERM=dumb
9762 * OPTERR=1 9921 * OPTERR=1
9763 * OPTIND=1 9922 * OPTIND=1
9764 * IFS=$' \t\n'
9765 * PS4='+ ' 9923 * PS4='+ '
9766 */ 9924 */
9767#endif
9768 9925
9769#if ENABLE_HUSH_LINENO_VAR 9926#if ENABLE_HUSH_LINENO_VAR
9770 if (ENABLE_HUSH_LINENO_VAR) { 9927 if (ENABLE_HUSH_LINENO_VAR) {
@@ -10226,6 +10383,8 @@ static int FAST_FUNC builtin_eval(char **argv)
10226 if (!argv[0]) 10383 if (!argv[0])
10227 return EXIT_SUCCESS; 10384 return EXIT_SUCCESS;
10228 10385
10386 IF_HUSH_MODE_X(G.x_mode_depth++;)
10387 //bb_error_msg("%s: ++x_mode_depth=%d", __func__, G.x_mode_depth);
10229 if (!argv[1]) { 10388 if (!argv[1]) {
10230 /* bash: 10389 /* bash:
10231 * eval "echo Hi; done" ("done" is syntax error): 10390 * eval "echo Hi; done" ("done" is syntax error):
@@ -10255,6 +10414,8 @@ static int FAST_FUNC builtin_eval(char **argv)
10255 parse_and_run_string(str); 10414 parse_and_run_string(str);
10256 free(str); 10415 free(str);
10257 } 10416 }
10417 IF_HUSH_MODE_X(G.x_mode_depth--;)
10418 //bb_error_msg("%s: --x_mode_depth=%d", __func__, G.x_mode_depth);
10258 return G.last_exitcode; 10419 return G.last_exitcode;
10259} 10420}
10260 10421
@@ -10374,40 +10535,29 @@ static int FAST_FUNC builtin_type(char **argv)
10374static int FAST_FUNC builtin_read(char **argv) 10535static int FAST_FUNC builtin_read(char **argv)
10375{ 10536{
10376 const char *r; 10537 const char *r;
10377 char *opt_n = NULL; 10538 struct builtin_read_params params;
10378 char *opt_p = NULL; 10539
10379 char *opt_t = NULL; 10540 memset(&params, 0, sizeof(params));
10380 char *opt_u = NULL;
10381 char *opt_d = NULL; /* optimized out if !BASH */
10382 const char *ifs;
10383 int read_flags;
10384 10541
10385 /* "!": do not abort on errors. 10542 /* "!": do not abort on errors.
10386 * Option string must start with "sr" to match BUILTIN_READ_xxx 10543 * Option string must start with "sr" to match BUILTIN_READ_xxx
10387 */ 10544 */
10388 read_flags = getopt32(argv, 10545 params.read_flags = getopt32(argv,
10389#if BASH_READ_D 10546#if BASH_READ_D
10390 "!srn:p:t:u:d:", &opt_n, &opt_p, &opt_t, &opt_u, &opt_d 10547 "!srn:p:t:u:d:", &params.opt_n, &params.opt_p, &params.opt_t, &params.opt_u, &params.opt_d
10391#else 10548#else
10392 "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u 10549 "!srn:p:t:u:", &params.opt_n, &params.opt_p, &params.opt_t, &params.opt_u
10393#endif 10550#endif
10394 ); 10551 );
10395 if (read_flags == (uint32_t)-1) 10552 if ((uint32_t)params.read_flags == (uint32_t)-1)
10396 return EXIT_FAILURE; 10553 return EXIT_FAILURE;
10397 argv += optind; 10554 argv += optind;
10398 ifs = get_local_var_value("IFS"); /* can be NULL */ 10555 params.argv = argv;
10556 params.setvar = set_local_var_from_halves;
10557 params.ifs = get_local_var_value("IFS"); /* can be NULL */
10399 10558
10400 again: 10559 again:
10401 r = shell_builtin_read(set_local_var_from_halves, 10560 r = shell_builtin_read(&params);
10402 argv,
10403 ifs,
10404 read_flags,
10405 opt_n,
10406 opt_p,
10407 opt_t,
10408 opt_u,
10409 opt_d
10410 );
10411 10561
10412 if ((uintptr_t)r == 1 && errno == EINTR) { 10562 if ((uintptr_t)r == 1 && errno == EINTR) {
10413 unsigned sig = check_and_run_traps(); 10563 unsigned sig = check_and_run_traps();
diff --git a/shell/hush_test/hush-glob/glob_bkslash_in_var.right b/shell/hush_test/hush-glob/glob_bkslash_in_var.right
new file mode 100644
index 000000000..f1484b9e4
--- /dev/null
+++ b/shell/hush_test/hush-glob/glob_bkslash_in_var.right
@@ -0,0 +1,4 @@
1Unquoted non-matching glob in var:'test*.TMP/\name_doesnt_exist'
2Unquoted matching glob in var: 'testdir.TMP/name'
3Quoted non-matching glob in var: 'test*.TMP/\name_doesnt_exist'
4Quoted matching glob in var: 'test*.TMP/\name'
diff --git a/shell/hush_test/hush-glob/glob_bkslash_in_var.tests b/shell/hush_test/hush-glob/glob_bkslash_in_var.tests
new file mode 100755
index 000000000..e3dedc4ac
--- /dev/null
+++ b/shell/hush_test/hush-glob/glob_bkslash_in_var.tests
@@ -0,0 +1,10 @@
1mkdir testdir.TMP
2>testdir.TMP/name
3a="test*.TMP/\name_doesnt_exist"
4b="test*.TMP/\name"
5printf "Unquoted non-matching glob in var:'%s'\n" $a
6printf "Unquoted matching glob in var: '%s'\n" $b
7printf "Quoted non-matching glob in var: '%s'\n" "$a"
8printf "Quoted matching glob in var: '%s'\n" "$b"
9rm -f testdir.TMP/name
10rmdir testdir.TMP
diff --git a/shell/hush_test/hush-misc/empty_for1.right b/shell/hush_test/hush-misc/empty_for1.right
new file mode 100644
index 000000000..46ffcece7
--- /dev/null
+++ b/shell/hush_test/hush-misc/empty_for1.right
@@ -0,0 +1 @@
Zero:0
diff --git a/shell/hush_test/hush-misc/empty_for1.tests b/shell/hush_test/hush-misc/empty_for1.tests
new file mode 100755
index 000000000..5a2554d54
--- /dev/null
+++ b/shell/hush_test/hush-misc/empty_for1.tests
@@ -0,0 +1,5 @@
1false
2for v; do
3 exit 2
4done
5echo Zero:$?
diff --git a/shell/hush_test/hush-misc/env_and_func.tests b/shell/hush_test/hush-misc/env_and_func.tests
index 3efef1a41..1c63eafd8 100755
--- a/shell/hush_test/hush-misc/env_and_func.tests
+++ b/shell/hush_test/hush-misc/env_and_func.tests
@@ -3,6 +3,6 @@ f() { echo "var=$var"; }
3# bash: POSIXLY_CORRECT behavior is to "leak" new variable values 3# bash: POSIXLY_CORRECT behavior is to "leak" new variable values
4# out of function invocations (similar to "special builtins" behavior); 4# out of function invocations (similar to "special builtins" behavior);
5# but in "bash mode", they don't leak. 5# but in "bash mode", they don't leak.
6# hush does not "leak" values. ash does. 6# hush does not "leak" values. ash used to, but now does not.
7var=val f 7var=val f
8echo "var=$var" 8echo "var=$var"
diff --git a/shell/hush_test/hush-quoting/quote_in_varexp1.right b/shell/hush_test/hush-quoting/quote_in_varexp1.right
new file mode 100644
index 000000000..99a0aea7c
--- /dev/null
+++ b/shell/hush_test/hush-quoting/quote_in_varexp1.right
@@ -0,0 +1,2 @@
1''
2Ok:0
diff --git a/shell/hush_test/hush-quoting/quote_in_varexp1.tests b/shell/hush_test/hush-quoting/quote_in_varexp1.tests
new file mode 100755
index 000000000..1b97b0556
--- /dev/null
+++ b/shell/hush_test/hush-quoting/quote_in_varexp1.tests
@@ -0,0 +1,2 @@
1x="''''"; echo "${x#"${x+''}"''}"
2echo Ok:$?
diff --git a/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.right b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.right
new file mode 100644
index 000000000..439dca578
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.right
@@ -0,0 +1,2 @@
1-dev-ram
2/dev-am
diff --git a/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.tests b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.tests
new file mode 100755
index 000000000..b83fb8eeb
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.tests
@@ -0,0 +1,3 @@
1v=/dev/ram
2echo ${v////-}
3echo ${v///r/-}
diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs5.right b/shell/hush_test/hush-vars/var_wordsplit_ifs5.right
new file mode 100644
index 000000000..46ffcece7
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_wordsplit_ifs5.right
@@ -0,0 +1 @@
Zero:0
diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs5.tests b/shell/hush_test/hush-vars/var_wordsplit_ifs5.tests
new file mode 100755
index 000000000..d382116df
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_wordsplit_ifs5.tests
@@ -0,0 +1,4 @@
1IFS=
2set --
3set -- $@ $*
4echo Zero:$#
diff --git a/shell/shell_common.c b/shell/shell_common.c
index 82102778c..23e5f1c25 100644
--- a/shell/shell_common.c
+++ b/shell/shell_common.c
@@ -50,16 +50,7 @@ int FAST_FUNC is_well_formed_var_name(const char *s, char terminator)
50//Here we can simply store "VAR=" at buffer start and store read data directly 50//Here we can simply store "VAR=" at buffer start and store read data directly
51//after "=", then pass buffer to setvar() to consume. 51//after "=", then pass buffer to setvar() to consume.
52const char* FAST_FUNC 52const char* FAST_FUNC
53shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), 53shell_builtin_read(struct builtin_read_params *params)
54 char **argv,
55 const char *ifs,
56 int read_flags,
57 const char *opt_n,
58 const char *opt_p,
59 const char *opt_t,
60 const char *opt_u,
61 const char *opt_d
62)
63{ 54{
64 struct pollfd pfd[1]; 55 struct pollfd pfd[1];
65#define fd (pfd[0].fd) /* -u FD */ 56#define fd (pfd[0].fd) /* -u FD */
@@ -76,9 +67,13 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
76 int bufpos; /* need to be able to hold -1 */ 67 int bufpos; /* need to be able to hold -1 */
77 int startword; 68 int startword;
78 smallint backslash; 69 smallint backslash;
70 char **argv;
71 const char *ifs;
72 int read_flags;
79 73
80 errno = err = 0; 74 errno = err = 0;
81 75
76 argv = params->argv;
82 pp = argv; 77 pp = argv;
83 while (*pp) { 78 while (*pp) {
84 if (!is_well_formed_var_name(*pp, '\0')) { 79 if (!is_well_formed_var_name(*pp, '\0')) {
@@ -90,29 +85,29 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
90 } 85 }
91 86
92 nchars = 0; /* if != 0, -n is in effect */ 87 nchars = 0; /* if != 0, -n is in effect */
93 if (opt_n) { 88 if (params->opt_n) {
94 nchars = bb_strtou(opt_n, NULL, 10); 89 nchars = bb_strtou(params->opt_n, NULL, 10);
95 if (nchars < 0 || errno) 90 if (nchars < 0 || errno)
96 return "invalid count"; 91 return "invalid count";
97 /* note: "-n 0": off (bash 3.2 does this too) */ 92 /* note: "-n 0": off (bash 3.2 does this too) */
98 } 93 }
99 94
100 end_ms = 0; 95 end_ms = 0;
101 if (opt_t && !ENABLE_FEATURE_SH_READ_FRAC) { 96 if (params->opt_t && !ENABLE_FEATURE_SH_READ_FRAC) {
102 end_ms = bb_strtou(opt_t, NULL, 10); 97 end_ms = bb_strtou(params->opt_t, NULL, 10);
103 if (errno) 98 if (errno)
104 return "invalid timeout"; 99 return "invalid timeout";
105 if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */ 100 if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
106 end_ms = UINT_MAX / 2048; 101 end_ms = UINT_MAX / 2048;
107 end_ms *= 1000; 102 end_ms *= 1000;
108 } 103 }
109 if (opt_t && ENABLE_FEATURE_SH_READ_FRAC) { 104 if (params->opt_t && ENABLE_FEATURE_SH_READ_FRAC) {
110 /* bash 4.3 (maybe earlier) supports -t N.NNNNNN */ 105 /* bash 4.3 (maybe earlier) supports -t N.NNNNNN */
111 char *p; 106 char *p;
112 /* Eat up to three fractional digits */ 107 /* Eat up to three fractional digits */
113 int frac_digits = 3 + 1; 108 int frac_digits = 3 + 1;
114 109
115 end_ms = bb_strtou(opt_t, &p, 10); 110 end_ms = bb_strtou(params->opt_t, &p, 10);
116 if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */ 111 if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
117 end_ms = UINT_MAX / 2048; 112 end_ms = UINT_MAX / 2048;
118 113
@@ -134,13 +129,13 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
134 } 129 }
135 130
136 fd = STDIN_FILENO; 131 fd = STDIN_FILENO;
137 if (opt_u) { 132 if (params->opt_u) {
138 fd = bb_strtou(opt_u, NULL, 10); 133 fd = bb_strtou(params->opt_u, NULL, 10);
139 if (fd < 0 || errno) 134 if (fd < 0 || errno)
140 return "invalid file descriptor"; 135 return "invalid file descriptor";
141 } 136 }
142 137
143 if (opt_t && end_ms == 0) { 138 if (params->opt_t && end_ms == 0) {
144#if !ENABLE_PLATFORM_MINGW32 139#if !ENABLE_PLATFORM_MINGW32
145 /* "If timeout is 0, read returns immediately, without trying 140 /* "If timeout is 0, read returns immediately, without trying
146 * to read any data. The exit status is 0 if input is available 141 * to read any data. The exit status is 0 if input is available
@@ -157,14 +152,16 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
157#endif 152#endif
158 } 153 }
159 154
160 if (opt_p && isatty(fd)) { 155 if (params->opt_p && isatty(fd)) {
161 fputs(opt_p, stderr); 156 fputs(params->opt_p, stderr);
162 fflush_all(); 157 fflush_all();
163 } 158 }
164 159
160 ifs = params->ifs;
165 if (ifs == NULL) 161 if (ifs == NULL)
166 ifs = defifs; 162 ifs = defifs;
167 163
164 read_flags = params->read_flags;
168#if !ENABLE_PLATFORM_MINGW32 165#if !ENABLE_PLATFORM_MINGW32
169 if (nchars || (read_flags & BUILTIN_READ_SILENT)) { 166 if (nchars || (read_flags & BUILTIN_READ_SILENT)) {
170 tcgetattr(fd, &tty); 167 tcgetattr(fd, &tty);
@@ -193,11 +190,11 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
193 retval = (const char *)(uintptr_t)0; 190 retval = (const char *)(uintptr_t)0;
194 startword = 1; 191 startword = 1;
195 backslash = 0; 192 backslash = 0;
196 if (opt_t) 193 if (params->opt_t)
197 end_ms += (unsigned)monotonic_ms(); 194 end_ms += (unsigned)monotonic_ms();
198 buffer = NULL; 195 buffer = NULL;
199 bufpos = 0; 196 bufpos = 0;
200 delim = opt_d ? *opt_d : '\n'; 197 delim = params->opt_d ? params->opt_d[0] : '\n';
201 do { 198 do {
202 char c; 199 char c;
203 int timeout; 200 int timeout;
@@ -206,7 +203,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
206 buffer = xrealloc(buffer, bufpos + 0x101); 203 buffer = xrealloc(buffer, bufpos + 0x101);
207 204
208 timeout = -1; 205 timeout = -1;
209 if (opt_t) { 206 if (params->opt_t) {
210 timeout = end_ms - (unsigned)monotonic_ms(); 207 timeout = end_ms - (unsigned)monotonic_ms();
211 /* ^^^^^^^^^^^^^ all values are unsigned, 208 /* ^^^^^^^^^^^^^ all values are unsigned,
212 * wrapping math is used here, good even if 209 * wrapping math is used here, good even if
@@ -238,7 +235,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
238 } 235 }
239#else 236#else
240 errno = 0; 237 errno = 0;
241 if (isatty(fd) && (opt_n || opt_d || opt_t || 238 if (isatty(fd) && (params->opt_n || params->opt_d || params->opt_t ||
242 (read_flags & BUILTIN_READ_SILENT))) { 239 (read_flags & BUILTIN_READ_SILENT))) {
243 int64_t key; 240 int64_t key;
244 241
@@ -285,7 +282,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
285 * without variable names (bash compat). 282 * without variable names (bash compat).
286 * Thus, "read" and "read REPLY" are not the same. 283 * Thus, "read" and "read REPLY" are not the same.
287 */ 284 */
288 if (!opt_d && argv[0]) { 285 if (!params->opt_d && argv[0]) {
289/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */ 286/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */
290 const char *is_ifs = strchr(ifs, c); 287 const char *is_ifs = strchr(ifs, c);
291 if (startword && is_ifs) { 288 if (startword && is_ifs) {
@@ -300,7 +297,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
300 if (argv[1] != NULL && is_ifs) { 297 if (argv[1] != NULL && is_ifs) {
301 buffer[bufpos] = '\0'; 298 buffer[bufpos] = '\0';
302 bufpos = 0; 299 bufpos = 0;
303 setvar(*argv, buffer); 300 params->setvar(*argv, buffer);
304 argv++; 301 argv++;
305 /* can we skip one non-space ifs char? (2: yes) */ 302 /* can we skip one non-space ifs char? (2: yes) */
306 startword = isspace(c) ? 2 : 1; 303 startword = isspace(c) ? 2 : 1;
@@ -352,14 +349,14 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
352 } 349 }
353 350
354 /* Use the remainder as a value for the next variable */ 351 /* Use the remainder as a value for the next variable */
355 setvar(*argv, buffer); 352 params->setvar(*argv, buffer);
356 /* Set the rest to "" */ 353 /* Set the rest to "" */
357 while (*++argv) 354 while (*++argv)
358 setvar(*argv, ""); 355 params->setvar(*argv, "");
359 } else { 356 } else {
360 /* Note: no $IFS removal */ 357 /* Note: no $IFS removal */
361 buffer[bufpos] = '\0'; 358 buffer[bufpos] = '\0';
362 setvar("REPLY", buffer); 359 params->setvar("REPLY", buffer);
363 } 360 }
364 361
365 ret: 362 ret:
diff --git a/shell/shell_common.h b/shell/shell_common.h
index 875fd9ea7..a1323021d 100644
--- a/shell/shell_common.h
+++ b/shell/shell_common.h
@@ -30,6 +30,17 @@ int FAST_FUNC is_well_formed_var_name(const char *s, char terminator);
30 30
31/* Builtins */ 31/* Builtins */
32 32
33struct builtin_read_params {
34 int read_flags;
35 void FAST_FUNC (*setvar)(const char *name, const char *val);
36 char **argv;
37 const char *ifs;
38 const char *opt_n;
39 const char *opt_p;
40 const char *opt_t;
41 const char *opt_u;
42 const char *opt_d;
43};
33enum { 44enum {
34 BUILTIN_READ_SILENT = 1 << 0, 45 BUILTIN_READ_SILENT = 1 << 0,
35 BUILTIN_READ_RAW = 1 << 1, 46 BUILTIN_READ_RAW = 1 << 1,
@@ -40,16 +51,7 @@ enum {
40// shell_builtin_read(setvar,argv,ifs,read_flags) 51// shell_builtin_read(setvar,argv,ifs,read_flags)
41//#endif 52//#endif
42const char* FAST_FUNC 53const char* FAST_FUNC
43shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), 54shell_builtin_read(struct builtin_read_params *params);
44 char **argv,
45 const char *ifs,
46 int read_flags,
47 const char *opt_n,
48 const char *opt_p,
49 const char *opt_t,
50 const char *opt_u,
51 const char *opt_d
52);
53 55
54int FAST_FUNC 56int FAST_FUNC
55shell_builtin_ulimit(char **argv); 57shell_builtin_ulimit(char **argv);