aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
Diffstat (limited to 'shell')
-rw-r--r--shell/ash.c459
-rw-r--r--shell/ash_test/ash-misc/assignment2.right2
-rwxr-xr-xshell/ash_test/ash-misc/assignment2.tests3
-rw-r--r--shell/ash_test/ash-misc/empty_args.right6
-rwxr-xr-xshell/ash_test/ash-misc/empty_args.tests9
-rw-r--r--shell/ash_test/ash-misc/env_and_func.right2
-rwxr-xr-xshell/ash_test/ash-misc/env_and_func.tests8
-rw-r--r--shell/ash_test/ash-psubst/emptytick.right17
-rwxr-xr-xshell/ash_test/ash-psubst/emptytick.tests16
-rw-r--r--shell/ash_test/ash-psubst/tick.right2
-rwxr-xr-xshell/ash_test/ash-psubst/tick.tests4
-rw-r--r--shell/ash_test/ash-psubst/tick2.right1
-rwxr-xr-xshell/ash_test/ash-psubst/tick2.tests5
-rw-r--r--shell/ash_test/ash-psubst/tick3.right6
-rwxr-xr-xshell/ash_test/ash-psubst/tick3.tests14
-rw-r--r--shell/ash_test/ash-psubst/tick4.right7
-rwxr-xr-xshell/ash_test/ash-psubst/tick4.tests7
-rw-r--r--shell/ash_test/ash-psubst/tick_huge.right3
-rwxr-xr-xshell/ash_test/ash-psubst/tick_huge.tests7
-rw-r--r--shell/ash_test/ash-signals/catch.right5
-rwxr-xr-xshell/ash_test/ash-signals/catch.tests20
-rw-r--r--shell/ash_test/ash-signals/signal_read2.right2
-rwxr-xr-xshell/ash_test/ash-signals/signal_read2.tests7
-rw-r--r--shell/ash_test/ash-signals/subshell.right21
-rwxr-xr-xshell/ash_test/ash-signals/subshell.tests19
-rw-r--r--shell/ash_test/ash-vars/param_expand_alt.right9
-rwxr-xr-xshell/ash_test/ash-vars/param_expand_alt.tests33
-rw-r--r--shell/ash_test/ash-vars/param_expand_assign.right27
-rwxr-xr-xshell/ash_test/ash-vars/param_expand_assign.tests39
-rw-r--r--shell/ash_test/ash-vars/param_expand_bash_substring.right64
-rwxr-xr-xshell/ash_test/ash-vars/param_expand_bash_substring.tests84
-rw-r--r--shell/ash_test/ash-vars/param_expand_default.right7
-rwxr-xr-xshell/ash_test/ash-vars/param_expand_default.tests23
-rw-r--r--shell/ash_test/ash-vars/param_expand_indicate_error.right43
-rwxr-xr-xshell/ash_test/ash-vars/param_expand_indicate_error.tests61
-rw-r--r--shell/ash_test/ash-vars/param_expand_len1.right11
-rwxr-xr-xshell/ash_test/ash-vars/param_expand_len1.tests31
-rw-r--r--shell/ash_test/ash-vars/readonly0.right2
-rw-r--r--shell/ash_test/ash-vars/unset.right17
-rwxr-xr-xshell/ash_test/ash-vars/unset.tests40
-rw-r--r--shell/hush.c67
-rwxr-xr-xshell/hush_test/hush-misc/env_and_func.tests4
-rw-r--r--shell/hush_test/hush-redir/redir.right2
-rwxr-xr-xshell/hush_test/hush-redir/redir.tests6
-rw-r--r--shell/hush_test/hush-vars/param_expand_alt.right3
-rwxr-xr-xshell/hush_test/hush-vars/param_expand_alt.tests21
-rwxr-xr-xshell/hush_test/hush-vars/param_expand_assign.tests27
-rwxr-xr-xshell/hush_test/hush-vars/param_expand_bash_substring.tests13
-rwxr-xr-xshell/hush_test/hush-vars/param_expand_default.tests6
-rw-r--r--shell/hush_test/hush-vars/param_expand_indicate_error.right2
-rwxr-xr-xshell/hush_test/hush-vars/param_expand_indicate_error.tests2
-rw-r--r--shell/hush_test/hush-vars/param_expand_len1.right11
-rwxr-xr-xshell/hush_test/hush-vars/param_expand_len1.tests31
-rw-r--r--shell/hush_test/hush-vars/unset.right4
-rwxr-xr-xshell/hush_test/hush-vars/unset.tests7
55 files changed, 1061 insertions, 288 deletions
diff --git a/shell/ash.c b/shell/ash.c
index 28b522d7c..6aaeecfac 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -241,6 +241,9 @@
241#include "shell_common.h" 241#include "shell_common.h"
242#if ENABLE_FEATURE_SH_MATH 242#if ENABLE_FEATURE_SH_MATH
243# include "math.h" 243# include "math.h"
244#else
245typedef long arith_t;
246# define ARITH_FMT "%ld"
244#endif 247#endif
245#if ENABLE_ASH_RANDOM_SUPPORT 248#if ENABLE_ASH_RANDOM_SUPPORT
246# include "random.h" 249# include "random.h"
@@ -708,8 +711,8 @@ fmtstr(char *outbuf, size_t length, const char *fmt, ...)
708 va_list ap; 711 va_list ap;
709 int ret; 712 int ret;
710 713
711 va_start(ap, fmt);
712 INT_OFF; 714 INT_OFF;
715 va_start(ap, fmt);
713 ret = vsnprintf(outbuf, length, fmt, ap); 716 ret = vsnprintf(outbuf, length, fmt, ap);
714 va_end(ap); 717 va_end(ap);
715 INT_ON; 718 INT_ON;
@@ -1334,7 +1337,6 @@ static struct parsefile basepf; /* top level input file */
1334static struct parsefile *g_parsefile = &basepf; /* current input file */ 1337static struct parsefile *g_parsefile = &basepf; /* current input file */
1335static int startlinno; /* line # where last token started */ 1338static int startlinno; /* line # where last token started */
1336static char *commandname; /* currently executing command */ 1339static char *commandname; /* currently executing command */
1337static struct strlist *cmdenviron; /* environment for builtin command */
1338 1340
1339 1341
1340/* ============ Message printing */ 1342/* ============ Message printing */
@@ -1391,6 +1393,18 @@ ash_msg_and_raise_error(const char *msg, ...)
1391 va_end(ap); 1393 va_end(ap);
1392} 1394}
1393 1395
1396/*
1397 * Use '%m' to append error string on platforms that support it, '%s' and
1398 * strerror() on those that don't.
1399 *
1400 * 'fmt' must be a string literal.
1401 */
1402#ifdef HAVE_PRINTF_PERCENTM
1403#define ash_msg_and_raise_perror(fmt, ...) ash_msg_and_raise_error(fmt ": %m", ##__VA_ARGS__)
1404#else
1405#define ash_msg_and_raise_perror(fmt, ...) ash_msg_and_raise_error(fmt ": %s", ##__VA_ARGS__, strerror(errno))
1406#endif
1407
1394static void raise_error_syntax(const char *) NORETURN; 1408static void raise_error_syntax(const char *) NORETURN;
1395static void 1409static void
1396raise_error_syntax(const char *msg) 1410raise_error_syntax(const char *msg)
@@ -1875,9 +1889,6 @@ single_quote(const char *s)
1875 1889
1876/* 1890/*
1877 * Produce a possibly single quoted string suitable as input to the shell. 1891 * Produce a possibly single quoted string suitable as input to the shell.
1878 * If 'conditional' is nonzero, quoting is only done if the string contains
1879 * non-shellsafe characters, or is identical to a shell keyword (reserved
1880 * word); if it is zero, quoting is always done.
1881 * If quoting was done, the return string is allocated on the stack, 1892 * If quoting was done, the return string is allocated on the stack,
1882 * otherwise a pointer to the original string is returned. 1893 * otherwise a pointer to the original string is returned.
1883 */ 1894 */
@@ -2312,15 +2323,9 @@ reinit_unicode_for_ash(void)
2312/* 2323/*
2313 * Search the environment of a builtin command. 2324 * Search the environment of a builtin command.
2314 */ 2325 */
2315static const char * 2326static ALWAYS_INLINE const char *
2316bltinlookup(const char *name) 2327bltinlookup(const char *name)
2317{ 2328{
2318 struct strlist *sp;
2319
2320 for (sp = cmdenviron; sp; sp = sp->next) {
2321 if (varcmp(sp->text, name) == 0)
2322 return var_end(sp->text);
2323 }
2324 return lookupvar(name); 2329 return lookupvar(name);
2325} 2330}
2326 2331
@@ -2331,14 +2336,15 @@ bltinlookup(const char *name)
2331 * will go away. 2336 * will go away.
2332 * Called with interrupts off. 2337 * Called with interrupts off.
2333 */ 2338 */
2334static void 2339static struct var *
2335setvareq(char *s, int flags) 2340setvareq(char *s, int flags)
2336{ 2341{
2337 struct var *vp, **vpp; 2342 struct var *vp, **vpp;
2338 2343
2339 vpp = hashvar(s); 2344 vpp = hashvar(s);
2340 flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1)); 2345 flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
2341 vp = *findvar(vpp, s); 2346 vpp = findvar(vpp, s);
2347 vp = *vpp;
2342 if (vp) { 2348 if (vp) {
2343 if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) { 2349 if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) {
2344 const char *n; 2350 const char *n;
@@ -2351,7 +2357,7 @@ setvareq(char *s, int flags)
2351 } 2357 }
2352 2358
2353 if (flags & VNOSET) 2359 if (flags & VNOSET)
2354 return; 2360 goto out;
2355 2361
2356 if (vp->var_func && !(flags & VNOFUNC)) 2362 if (vp->var_func && !(flags & VNOFUNC))
2357 vp->var_func(var_end(s)); 2363 vp->var_func(var_end(s));
@@ -2359,11 +2365,22 @@ setvareq(char *s, int flags)
2359 if (!(vp->flags & (VTEXTFIXED|VSTACK))) 2365 if (!(vp->flags & (VTEXTFIXED|VSTACK)))
2360 free((char*)vp->var_text); 2366 free((char*)vp->var_text);
2361 2367
2368 if (((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) | (vp->flags & VSTRFIXED)) == VUNSET) {
2369 *vpp = vp->next;
2370 free(vp);
2371 out_free:
2372 if ((flags & (VTEXTFIXED|VSTACK|VNOSAVE)) == VNOSAVE)
2373 free(s);
2374 goto out;
2375 }
2376
2362 flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); 2377 flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
2363 } else { 2378 } else {
2364 /* variable s is not found */ 2379 /* variable s is not found */
2365 if (flags & VNOSET) 2380 if (flags & VNOSET)
2366 return; 2381 goto out;
2382 if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
2383 goto out_free;
2367 vp = ckzalloc(sizeof(*vp)); 2384 vp = ckzalloc(sizeof(*vp));
2368 vp->next = *vpp; 2385 vp->next = *vpp;
2369 /*vp->func = NULL; - ckzalloc did it */ 2386 /*vp->func = NULL; - ckzalloc did it */
@@ -2373,13 +2390,16 @@ setvareq(char *s, int flags)
2373 s = ckstrdup(s); 2390 s = ckstrdup(s);
2374 vp->var_text = s; 2391 vp->var_text = s;
2375 vp->flags = flags; 2392 vp->flags = flags;
2393
2394 out:
2395 return vp;
2376} 2396}
2377 2397
2378/* 2398/*
2379 * Set the value of a variable. The flags argument is ored with the 2399 * Set the value of a variable. The flags argument is ored with the
2380 * flags of the variable. If val is NULL, the variable is unset. 2400 * flags of the variable. If val is NULL, the variable is unset.
2381 */ 2401 */
2382static void 2402static struct var *
2383setvar(const char *name, const char *val, int flags) 2403setvar(const char *name, const char *val, int flags)
2384{ 2404{
2385 const char *q; 2405 const char *q;
@@ -2387,6 +2407,7 @@ setvar(const char *name, const char *val, int flags)
2387 char *nameeq; 2407 char *nameeq;
2388 size_t namelen; 2408 size_t namelen;
2389 size_t vallen; 2409 size_t vallen;
2410 struct var *vp;
2390 2411
2391 q = endofname(name); 2412 q = endofname(name);
2392 p = strchrnul(q, '='); 2413 p = strchrnul(q, '=');
@@ -2408,8 +2429,10 @@ setvar(const char *name, const char *val, int flags)
2408 p = mempcpy(p, val, vallen); 2429 p = mempcpy(p, val, vallen);
2409 } 2430 }
2410 *p = '\0'; 2431 *p = '\0';
2411 setvareq(nameeq, flags | VNOSAVE); 2432 vp = setvareq(nameeq, flags | VNOSAVE);
2412 INT_ON; 2433 INT_ON;
2434
2435 return vp;
2413} 2436}
2414 2437
2415static void FAST_FUNC 2438static void FAST_FUNC
@@ -2421,43 +2444,10 @@ setvar0(const char *name, const char *val)
2421/* 2444/*
2422 * Unset the specified variable. 2445 * Unset the specified variable.
2423 */ 2446 */
2424static int 2447static void
2425unsetvar(const char *s) 2448unsetvar(const char *s)
2426{ 2449{
2427 struct var **vpp; 2450 setvar(s, NULL, 0);
2428 struct var *vp;
2429 int retval;
2430
2431 vpp = findvar(hashvar(s), s);
2432 vp = *vpp;
2433 retval = 2;
2434 if (vp) {
2435 int flags = vp->flags;
2436
2437 retval = 1;
2438 if (flags & VREADONLY)
2439 goto out;
2440#if ENABLE_ASH_RANDOM_SUPPORT
2441 vp->flags &= ~VDYNAMIC;
2442#endif
2443 if (flags & VUNSET)
2444 goto ok;
2445 if ((flags & VSTRFIXED) == 0) {
2446 INT_OFF;
2447 if ((flags & (VTEXTFIXED|VSTACK)) == 0)
2448 free((char*)vp->var_text);
2449 *vpp = vp->next;
2450 free(vp);
2451 INT_ON;
2452 } else {
2453 setvar0(s, NULL);
2454 vp->flags &= ~VEXPORT;
2455 }
2456 ok:
2457 retval = 0;
2458 }
2459 out:
2460 return retval;
2461} 2451}
2462 2452
2463/* 2453/*
@@ -4067,7 +4057,7 @@ static void
4067xtcsetpgrp(int fd, pid_t pgrp) 4057xtcsetpgrp(int fd, pid_t pgrp)
4068{ 4058{
4069 if (tcsetpgrp(fd, pgrp)) 4059 if (tcsetpgrp(fd, pgrp))
4070 ash_msg_and_raise_error("can't set tty process group (%m)"); 4060 ash_msg_and_raise_perror("can't set tty process group");
4071} 4061}
4072 4062
4073/* 4063/*
@@ -5529,68 +5519,6 @@ stoppedjobs(void)
5529#define CLOSED -3 /* marks a slot of previously-closed fd */ 5519#define CLOSED -3 /* marks a slot of previously-closed fd */
5530 5520
5531/* 5521/*
5532 * Open a file in noclobber mode.
5533 * The code was copied from bash.
5534 */
5535static int
5536noclobberopen(const char *fname)
5537{
5538 int r, fd;
5539 struct stat finfo, finfo2;
5540
5541 /*
5542 * If the file exists and is a regular file, return an error
5543 * immediately.
5544 */
5545 r = stat(fname, &finfo);
5546 if (r == 0 && S_ISREG(finfo.st_mode)) {
5547 errno = EEXIST;
5548 return -1;
5549 }
5550
5551 /*
5552 * If the file was not present (r != 0), make sure we open it
5553 * exclusively so that if it is created before we open it, our open
5554 * will fail. Make sure that we do not truncate an existing file.
5555 * Note that we don't turn on O_EXCL unless the stat failed -- if the
5556 * file was not a regular file, we leave O_EXCL off.
5557 */
5558 if (r != 0)
5559 return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
5560 fd = open(fname, O_WRONLY|O_CREAT, 0666);
5561
5562 /* If the open failed, return the file descriptor right away. */
5563 if (fd < 0)
5564 return fd;
5565
5566 /*
5567 * OK, the open succeeded, but the file may have been changed from a
5568 * non-regular file to a regular file between the stat and the open.
5569 * We are assuming that the O_EXCL open handles the case where FILENAME
5570 * did not exist and is symlinked to an existing file between the stat
5571 * and open.
5572 */
5573
5574 /*
5575 * If we can open it and fstat the file descriptor, and neither check
5576 * revealed that it was a regular file, and the file has not been
5577 * replaced, return the file descriptor.
5578 */
5579 if (fstat(fd, &finfo2) == 0
5580 && !S_ISREG(finfo2.st_mode)
5581 && finfo.st_dev == finfo2.st_dev
5582 && finfo.st_ino == finfo2.st_ino
5583 ) {
5584 return fd;
5585 }
5586
5587 /* The file has been replaced. badness. */
5588 close(fd);
5589 errno = EEXIST;
5590 return -1;
5591}
5592
5593/*
5594 * Handle here documents. Normally we fork off a process to write the 5522 * Handle here documents. Normally we fork off a process to write the
5595 * data to a pipe. If the document is short, we can stuff the data in 5523 * data to a pipe. If the document is short, we can stuff the data in
5596 * the pipe without forking. 5524 * the pipe without forking.
@@ -5645,6 +5573,7 @@ openhere(union node *redir)
5645static int 5573static int
5646openredirect(union node *redir) 5574openredirect(union node *redir)
5647{ 5575{
5576 struct stat sb;
5648 char *fname; 5577 char *fname;
5649 int f; 5578 int f;
5650 5579
@@ -5709,9 +5638,23 @@ openredirect(union node *redir)
5709#endif 5638#endif
5710 /* Take care of noclobber mode. */ 5639 /* Take care of noclobber mode. */
5711 if (Cflag) { 5640 if (Cflag) {
5712 f = noclobberopen(fname); 5641 if (stat(fname, &sb) < 0) {
5713 if (f < 0) 5642 f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666);
5643 if (f < 0)
5644 goto ecreate;
5645 } else if (!S_ISREG(sb.st_mode)) {
5646 f = open(fname, O_WRONLY, 0666);
5647 if (f < 0)
5648 goto ecreate;
5649 if (fstat(f, &sb) < 0 && S_ISREG(sb.st_mode)) {
5650 close(f);
5651 errno = EEXIST;
5652 goto ecreate;
5653 }
5654 } else {
5655 errno = EEXIST;
5714 goto ecreate; 5656 goto ecreate;
5657 }
5715 break; 5658 break;
5716 } 5659 }
5717 /* FALLTHROUGH */ 5660 /* FALLTHROUGH */
@@ -5750,7 +5693,7 @@ savefd(int from)
5750 err = newfd < 0 ? errno : 0; 5693 err = newfd < 0 ? errno : 0;
5751 if (err != EBADF) { 5694 if (err != EBADF) {
5752 if (err) 5695 if (err)
5753 ash_msg_and_raise_error("%d: %m", from); 5696 ash_msg_and_raise_perror("%d", from);
5754 close(from); 5697 close(from);
5755 fcntl(newfd, F_SETFD, FD_CLOEXEC); 5698 fcntl(newfd, F_SETFD, FD_CLOEXEC);
5756 } 5699 }
@@ -5765,7 +5708,7 @@ dup2_or_raise(int from, int to)
5765 newfd = (from != to) ? dup2(from, to) : to; 5708 newfd = (from != to) ? dup2(from, to) : to;
5766 if (newfd < 0) { 5709 if (newfd < 0) {
5767 /* Happens when source fd is not open: try "echo >&99" */ 5710 /* Happens when source fd is not open: try "echo >&99" */
5768 ash_msg_and_raise_error("%d: %m", from); 5711 ash_msg_and_raise_perror("%d", from);
5769 } 5712 }
5770 return newfd; 5713 return newfd;
5771} 5714}
@@ -5896,7 +5839,7 @@ redirect(union node *redir, int flags)
5896 /* "echo >&10" and 10 is a fd opened to a sh script? */ 5839 /* "echo >&10" and 10 is a fd opened to a sh script? */
5897 if (is_hidden_fd(sv, right_fd)) { 5840 if (is_hidden_fd(sv, right_fd)) {
5898 errno = EBADF; /* as if it is closed */ 5841 errno = EBADF; /* as if it is closed */
5899 ash_msg_and_raise_error("%d: %m", right_fd); 5842 ash_msg_and_raise_perror("%d", right_fd);
5900 } 5843 }
5901 newfd = -1; 5844 newfd = -1;
5902 } else { 5845 } else {
@@ -5930,7 +5873,7 @@ redirect(union node *redir, int flags)
5930 if (newfd >= 0) 5873 if (newfd >= 0)
5931 close(newfd); 5874 close(newfd);
5932 errno = i; 5875 errno = i;
5933 ash_msg_and_raise_error("%d: %m", fd); 5876 ash_msg_and_raise_perror("%d", fd);
5934 /* NOTREACHED */ 5877 /* NOTREACHED */
5935 } 5878 }
5936 /* EBADF: it is not open - good, remember to close it */ 5879 /* EBADF: it is not open - good, remember to close it */
@@ -6104,7 +6047,7 @@ ash_arith(const char *s)
6104#define RMESCAPE_SLASH 0x20 /* Stop globbing after slash */ 6047#define RMESCAPE_SLASH 0x20 /* Stop globbing after slash */
6105 6048
6106/* Add CTLESC when necessary. */ 6049/* Add CTLESC when necessary. */
6107#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT | EXP_REDIR) 6050#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT)
6108/* Do not skip NUL characters. */ 6051/* Do not skip NUL characters. */
6109#define QUOTES_KEEPNUL EXP_TILDE 6052#define QUOTES_KEEPNUL EXP_TILDE
6110 6053
@@ -6137,19 +6080,20 @@ static struct arglist exparg;
6137 6080
6138/* 6081/*
6139 * Our own itoa(). 6082 * Our own itoa().
6083 * cvtnum() is used even if math support is off (to prepare $? values and such).
6140 */ 6084 */
6141#if !ENABLE_FEATURE_SH_MATH
6142/* cvtnum() is used even if math support is off (to prepare $? values and such) */
6143typedef long arith_t;
6144# define ARITH_FMT "%ld"
6145#endif
6146static int 6085static int
6147cvtnum(arith_t num) 6086cvtnum(arith_t num)
6148{ 6087{
6149 int len; 6088 int len;
6150 6089
6151 expdest = makestrspace(sizeof(arith_t)*3 + 2, expdest); 6090 /* 32-bit and wider ints require buffer size of bytes*3 (or less) */
6152 len = fmtstr(expdest, sizeof(arith_t)*3 + 2, ARITH_FMT, num); 6091 len = sizeof(arith_t) * 3;
6092 /* If narrower: worst case, 1-byte ints: need 5 bytes: "-127<NUL>" */
6093 if (sizeof(arith_t) < 4) len += 2;
6094
6095 expdest = makestrspace(len, expdest);
6096 len = fmtstr(expdest, len, ARITH_FMT, num);
6153 STADJUST(len, expdest); 6097 STADJUST(len, expdest);
6154 return len; 6098 return len;
6155} 6099}
@@ -6569,9 +6513,24 @@ struct backcmd { /* result of evalbackcmd */
6569}; 6513};
6570 6514
6571/* These forward decls are needed to use "eval" code for backticks handling: */ 6515/* These forward decls are needed to use "eval" code for backticks handling: */
6572#define EV_EXIT 01 /* exit after evaluating tree */ 6516/* flags in argument to evaltree */
6517#define EV_EXIT 01 /* exit after evaluating tree */
6518#define EV_TESTED 02 /* exit status is checked; ignore -e flag */
6573static int evaltree(union node *, int); 6519static int evaltree(union node *, int);
6574 6520
6521/* An evaltree() which is known to never return.
6522 * Used to use an alias:
6523 * static int evaltreenr(union node *, int) __attribute__((alias("evaltree"),__noreturn__));
6524 * but clang was reported to "transfer" noreturn-ness to evaltree() as well.
6525 */
6526static ALWAYS_INLINE NORETURN void
6527evaltreenr(union node *n, int flags)
6528{
6529 evaltree(n, flags);
6530 bb_unreachable(abort());
6531 /* NOTREACHED */
6532}
6533
6575static void FAST_FUNC 6534static void FAST_FUNC
6576evalbackcmd(union node *n, struct backcmd *result) 6535evalbackcmd(union node *n, struct backcmd *result)
6577{ 6536{
@@ -6617,7 +6576,7 @@ evalbackcmd(union node *n, struct backcmd *result)
6617 */ 6576 */
6618 eflag = 0; 6577 eflag = 0;
6619 ifsfree(); 6578 ifsfree();
6620 evaltree(n, EV_EXIT); /* actually evaltreenr... */ 6579 evaltreenr(n, EV_EXIT);
6621 /* NOTREACHED */ 6580 /* NOTREACHED */
6622 } 6581 }
6623#endif 6582#endif
@@ -6750,19 +6709,15 @@ expari(int flag)
6750#endif 6709#endif
6751 6710
6752/* argstr needs it */ 6711/* argstr needs it */
6753static char *evalvar(char *p, int flags, struct strlist *var_str_list); 6712static char *evalvar(char *p, int flags);
6754 6713
6755/* 6714/*
6756 * Perform variable and command substitution. If EXP_FULL is set, output CTLESC 6715 * Perform variable and command substitution. If EXP_FULL is set, output CTLESC
6757 * characters to allow for further processing. Otherwise treat 6716 * characters to allow for further processing. Otherwise treat
6758 * $@ like $* since no splitting will be performed. 6717 * $@ like $* since no splitting will be performed.
6759 *
6760 * var_str_list (can be NULL) is a list of "VAR=val" strings which take precedence
6761 * over shell variables. Needed for "A=a B=$A; echo $B" case - we use it
6762 * for correct expansion of "B=$A" word.
6763 */ 6718 */
6764static void 6719static void
6765argstr(char *p, int flags, struct strlist *var_str_list) 6720argstr(char *p, int flags)
6766{ 6721{
6767 static const char spclchars[] ALIGN1 = { 6722 static const char spclchars[] ALIGN1 = {
6768 '=', 6723 '=',
@@ -6855,7 +6810,7 @@ argstr(char *p, int flags, struct strlist *var_str_list)
6855 inquotes ^= EXP_QUOTED; 6810 inquotes ^= EXP_QUOTED;
6856 /* "$@" syntax adherence hack */ 6811 /* "$@" syntax adherence hack */
6857 if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) { 6812 if (inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
6858 p = evalvar(p + 1, flags | inquotes, /* var_str_list: */ NULL) + 1; 6813 p = evalvar(p + 1, flags | inquotes) + 1;
6859 goto start; 6814 goto start;
6860 } 6815 }
6861 addquote: 6816 addquote:
@@ -6881,7 +6836,7 @@ argstr(char *p, int flags, struct strlist *var_str_list)
6881 goto addquote; 6836 goto addquote;
6882 case CTLVAR: 6837 case CTLVAR:
6883 TRACE(("argstr: evalvar('%s')\n", p)); 6838 TRACE(("argstr: evalvar('%s')\n", p));
6884 p = evalvar(p, flags | inquotes, var_str_list); 6839 p = evalvar(p, flags | inquotes);
6885 TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock())); 6840 TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock()));
6886 goto start; 6841 goto start;
6887 case CTLBACKQ: 6842 case CTLBACKQ:
@@ -7023,7 +6978,7 @@ varunset(const char *end, const char *var, const char *umsg, int varflags)
7023 6978
7024static const char * 6979static const char *
7025subevalvar(char *p, char *varname, int strloc, int subtype, 6980subevalvar(char *p, char *varname, int strloc, int subtype,
7026 int startloc, int varflags, int flag, struct strlist *var_str_list) 6981 int startloc, int varflags, int flag)
7027{ 6982{
7028 struct nodelist *saveargbackq = argbackq; 6983 struct nodelist *saveargbackq = argbackq;
7029 int quotes = flag & QUOTES_ESC; 6984 int quotes = flag & QUOTES_ESC;
@@ -7041,8 +6996,8 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7041 // p, varname, strloc, subtype, startloc, varflags, quotes); 6996 // p, varname, strloc, subtype, startloc, varflags, quotes);
7042 6997
7043 argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ? 6998 argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ?
7044 (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0), 6999 (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0)
7045 var_str_list); 7000 );
7046 STPUTC('\0', expdest); 7001 STPUTC('\0', expdest);
7047 argbackq = saveargbackq; 7002 argbackq = saveargbackq;
7048 startp = (char *)stackblock() + startloc; 7003 startp = (char *)stackblock() + startloc;
@@ -7319,7 +7274,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7319 * ash -c 'echo ${#1#}' name:'1=#' 7274 * ash -c 'echo ${#1#}' name:'1=#'
7320 */ 7275 */
7321static NOINLINE ssize_t 7276static NOINLINE ssize_t
7322varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int *quotedp) 7277varvalue(char *name, int varflags, int flags, int *quotedp)
7323{ 7278{
7324 const char *p; 7279 const char *p;
7325 int num; 7280 int num;
@@ -7411,31 +7366,6 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int
7411 goto value; 7366 goto value;
7412 default: 7367 default:
7413 /* NB: name has form "VAR=..." */ 7368 /* NB: name has form "VAR=..." */
7414
7415 /* "A=a B=$A" case: var_str_list is a list of "A=a" strings
7416 * which should be considered before we check variables. */
7417 if (var_str_list) {
7418 unsigned name_len = (strchrnul(name, '=') - name) + 1;
7419 p = NULL;
7420 do {
7421 char *str, *eq;
7422 str = var_str_list->text;
7423 eq = strchr(str, '=');
7424 if (!eq) /* stop at first non-assignment */
7425 break;
7426 eq++;
7427 if (name_len == (unsigned)(eq - str)
7428 && strncmp(str, name, name_len) == 0
7429 ) {
7430 p = eq;
7431 /* goto value; - WRONG! */
7432 /* think "A=1 A=2 B=$A" */
7433 }
7434 var_str_list = var_str_list->next;
7435 } while (var_str_list);
7436 if (p)
7437 goto value;
7438 }
7439 p = lookupvar(name); 7369 p = lookupvar(name);
7440 value: 7370 value:
7441 if (!p) 7371 if (!p)
@@ -7465,7 +7395,7 @@ varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int
7465 * input string. 7395 * input string.
7466 */ 7396 */
7467static char * 7397static char *
7468evalvar(char *p, int flag, struct strlist *var_str_list) 7398evalvar(char *p, int flag)
7469{ 7399{
7470 char varflags; 7400 char varflags;
7471 char subtype; 7401 char subtype;
@@ -7489,7 +7419,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
7489 p = strchr(p, '=') + 1; //TODO: use var_end(p)? 7419 p = strchr(p, '=') + 1; //TODO: use var_end(p)?
7490 7420
7491 again: 7421 again:
7492 varlen = varvalue(var, varflags, flag, var_str_list, &quoted); 7422 varlen = varvalue(var, varflags, flag, &quoted);
7493 if (varflags & VSNUL) 7423 if (varflags & VSNUL)
7494 varlen--; 7424 varlen--;
7495 7425
@@ -7503,8 +7433,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
7503 if (varlen < 0) { 7433 if (varlen < 0) {
7504 argstr( 7434 argstr(
7505 p, 7435 p,
7506 flag | EXP_TILDE | EXP_WORD, 7436 flag | EXP_TILDE | EXP_WORD
7507 var_str_list
7508 ); 7437 );
7509 goto end; 7438 goto end;
7510 } 7439 }
@@ -7516,7 +7445,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
7516 goto record; 7445 goto record;
7517 7446
7518 subevalvar(p, var, 0, subtype, startloc, varflags, 7447 subevalvar(p, var, 0, subtype, startloc, varflags,
7519 flag & ~QUOTES_ESC, var_str_list); 7448 flag & ~QUOTES_ESC);
7520 varflags &= ~VSNUL; 7449 varflags &= ~VSNUL;
7521 /* 7450 /*
7522 * Remove any recorded regions beyond 7451 * Remove any recorded regions beyond
@@ -7569,7 +7498,7 @@ evalvar(char *p, int flag, struct strlist *var_str_list)
7569 STPUTC('\0', expdest); 7498 STPUTC('\0', expdest);
7570 patloc = expdest - (char *)stackblock(); 7499 patloc = expdest - (char *)stackblock();
7571 if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype, 7500 if (NULL == subevalvar(p, /* varname: */ NULL, patloc, subtype,
7572 startloc, varflags, flag, var_str_list)) { 7501 startloc, varflags, flag)) {
7573 int amount = expdest - ( 7502 int amount = expdest - (
7574 (char *)stackblock() + patloc - 1 7503 (char *)stackblock() + patloc - 1
7575 ); 7504 );
@@ -7993,8 +7922,7 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
7993 argbackq = arg->narg.backquote; 7922 argbackq = arg->narg.backquote;
7994 STARTSTACKSTR(expdest); 7923 STARTSTACKSTR(expdest);
7995 TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag)); 7924 TRACE(("expandarg: argstr('%s',flags:%x)\n", arg->narg.text, flag));
7996 argstr(arg->narg.text, flag, 7925 argstr(arg->narg.text, flag);
7997 /* var_str_list: */ arglist ? arglist->list : NULL);
7998 p = _STPUTC('\0', expdest); 7926 p = _STPUTC('\0', expdest);
7999 expdest = p - 1; 7927 expdest = p - 1;
8000 if (arglist == NULL) { 7928 if (arglist == NULL) {
@@ -8013,10 +7941,6 @@ expandarg(union node *arg, struct arglist *arglist, int flag)
8013 exparg.lastp = &exparg.list; 7941 exparg.lastp = &exparg.list;
8014 expandmeta(exparg.list /*, flag*/); 7942 expandmeta(exparg.list /*, flag*/);
8015 } else { 7943 } else {
8016 if (flag & EXP_REDIR) { /*XXX - for now, just remove escapes */
8017 rmescapes(p, 0);
8018 TRACE(("expandarg: rmescapes:'%s'\n", p));
8019 }
8020 sp = stzalloc(sizeof(*sp)); 7944 sp = stzalloc(sizeof(*sp));
8021 sp->text = p; 7945 sp->text = p;
8022 *exparg.lastp = sp; 7946 *exparg.lastp = sp;
@@ -8065,8 +7989,7 @@ casematch(union node *pattern, char *val)
8065 setstackmark(&smark); 7989 setstackmark(&smark);
8066 argbackq = pattern->narg.backquote; 7990 argbackq = pattern->narg.backquote;
8067 STARTSTACKSTR(expdest); 7991 STARTSTACKSTR(expdest);
8068 argstr(pattern->narg.text, EXP_TILDE | EXP_CASE, 7992 argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
8069 /* var_str_list: */ NULL);
8070 STACKSTRNUL(expdest); 7993 STACKSTRNUL(expdest);
8071 ifsfree(); 7994 ifsfree();
8072 result = patmatch(stackblock(), val); 7995 result = patmatch(stackblock(), val);
@@ -8144,7 +8067,7 @@ static int builtinloc = -1; /* index in path of %builtin, or -1 */
8144 8067
8145 8068
8146static void 8069static void
8147tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **envp) 8070tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, char **envp)
8148{ 8071{
8149#if ENABLE_FEATURE_SH_STANDALONE 8072#if ENABLE_FEATURE_SH_STANDALONE
8150 if (applet_no >= 0) { 8073 if (applet_no >= 0) {
@@ -8152,6 +8075,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
8152 clearenv(); 8075 clearenv();
8153 while (*envp) 8076 while (*envp)
8154 putenv(*envp++); 8077 putenv(*envp++);
8078 popredir(/*drop:*/ 1, /*restore:*/ 0);
8155 run_applet_no_and_exit(applet_no, cmd, argv); 8079 run_applet_no_and_exit(applet_no, cmd, argv);
8156 } 8080 }
8157 /* re-exec ourselves with the new arguments */ 8081 /* re-exec ourselves with the new arguments */
@@ -8169,7 +8093,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
8169#else 8093#else
8170 execve(cmd, argv, envp); 8094 execve(cmd, argv, envp);
8171#endif 8095#endif
8172 if (cmd != (char*) bb_busybox_exec_path && errno == ENOEXEC) { 8096 if (cmd != bb_busybox_exec_path && errno == ENOEXEC) {
8173 /* Run "cmd" as a shell script: 8097 /* Run "cmd" as a shell script:
8174 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html 8098 * http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
8175 * "If the execve() function fails with ENOEXEC, the shell 8099 * "If the execve() function fails with ENOEXEC, the shell
@@ -8186,8 +8110,8 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
8186 * message and exit code 126. For one, this prevents attempts 8110 * message and exit code 126. For one, this prevents attempts
8187 * to interpret foreign ELF binaries as shell scripts. 8111 * to interpret foreign ELF binaries as shell scripts.
8188 */ 8112 */
8189 argv[0] = cmd; 8113 argv[0] = (char*) cmd;
8190 cmd = (char*) bb_busybox_exec_path; 8114 cmd = bb_busybox_exec_path;
8191 /* NB: this is only possible because all callers of shellexec() 8115 /* NB: this is only possible because all callers of shellexec()
8192 * ensure that the argv[-1] slot exists! 8116 * ensure that the argv[-1] slot exists!
8193 */ 8117 */
@@ -8810,10 +8734,6 @@ static int nodeptrsize;
8810static char **nodeptr; 8734static char **nodeptr;
8811#endif 8735#endif
8812 8736
8813/* flags in argument to evaltree */
8814#define EV_EXIT 01 /* exit after evaluating tree */
8815#define EV_TESTED 02 /* exit status is checked; ignore -e flag */
8816
8817static const uint8_t nodesize[N_NUMBER] ALIGN1 = { 8737static const uint8_t nodesize[N_NUMBER] ALIGN1 = {
8818 [NCMD ] = SHELL_ALIGN(sizeof(struct ncmd)), 8738 [NCMD ] = SHELL_ALIGN(sizeof(struct ncmd)),
8819 [NPIPE ] = SHELL_ALIGN(sizeof(struct npipe)), 8739 [NPIPE ] = SHELL_ALIGN(sizeof(struct npipe)),
@@ -9336,11 +9256,6 @@ evaltree(union node *n, int flags)
9336 return exitstatus; 9256 return exitstatus;
9337} 9257}
9338 9258
9339#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
9340static
9341#endif
9342int evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__));
9343
9344static int 9259static int
9345skiploop(void) 9260skiploop(void)
9346{ 9261{
@@ -9699,27 +9614,57 @@ optschanged(void)
9699#endif 9614#endif
9700} 9615}
9701 9616
9702static struct localvar *localvars; 9617struct localvar_list {
9618 struct localvar_list *next;
9619 struct localvar *lv;
9620};
9621
9622static struct localvar_list *localvar_stack;
9703 9623
9704/* 9624/*
9705 * Called after a function returns. 9625 * Called after a function returns.
9706 * Interrupts must be off. 9626 * Interrupts must be off.
9707 */ 9627 */
9708static void 9628static void
9709poplocalvars(void) 9629poplocalvars(int keep)
9710{ 9630{
9711 struct localvar *lvp; 9631 struct localvar_list *ll;
9632 struct localvar *lvp, *next;
9712 struct var *vp; 9633 struct var *vp;
9713 9634
9714 while ((lvp = localvars) != NULL) { 9635 INT_OFF;
9715 localvars = lvp->next; 9636 ll = localvar_stack;
9637 localvar_stack = ll->next;
9638
9639 next = ll->lv;
9640 free(ll);
9641
9642 while ((lvp = next) != NULL) {
9643 next = lvp->next;
9716 vp = lvp->vp; 9644 vp = lvp->vp;
9717 TRACE(("poplocalvar %s\n", vp ? vp->var_text : "-")); 9645 TRACE(("poplocalvar %s\n", vp ? vp->var_text : "-"));
9718 if (vp == NULL) { /* $- saved */ 9646 if (keep) {
9647 int bits = VSTRFIXED;
9648
9649 if (lvp->flags != VUNSET) {
9650 if (vp->var_text == lvp->text)
9651 bits |= VTEXTFIXED;
9652 else if (!(lvp->flags & (VTEXTFIXED|VSTACK)))
9653 free((char*)lvp->text);
9654 }
9655
9656 vp->flags &= ~bits;
9657 vp->flags |= (lvp->flags & bits);
9658
9659 if ((vp->flags &
9660 (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
9661 unsetvar(vp->var_text);
9662 } else if (vp == NULL) { /* $- saved */
9719 memcpy(optlist, lvp->text, sizeof(optlist)); 9663 memcpy(optlist, lvp->text, sizeof(optlist));
9720 free((char*)lvp->text); 9664 free((char*)lvp->text);
9721 optschanged(); 9665 optschanged();
9722 } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { 9666 } else if (lvp->flags == VUNSET) {
9667 vp->flags &= ~(VSTRFIXED|VREADONLY);
9723 unsetvar(vp->var_text); 9668 unsetvar(vp->var_text);
9724 } else { 9669 } else {
9725 if (vp->var_func) 9670 if (vp->var_func)
@@ -9731,19 +9676,43 @@ poplocalvars(void)
9731 } 9676 }
9732 free(lvp); 9677 free(lvp);
9733 } 9678 }
9679 INT_ON;
9680}
9681
9682/*
9683 * Create a new localvar environment.
9684 */
9685static struct localvar_list *
9686pushlocalvars(void)
9687{
9688 struct localvar_list *ll;
9689
9690 INT_OFF;
9691 ll = ckzalloc(sizeof(*ll));
9692 /*ll->lv = NULL; - zalloc did it */
9693 ll->next = localvar_stack;
9694 localvar_stack = ll;
9695 INT_ON;
9696
9697 return ll->next;
9698}
9699
9700static void
9701unwindlocalvars(struct localvar_list *stop)
9702{
9703 while (localvar_stack != stop)
9704 poplocalvars(0);
9734} 9705}
9735 9706
9736static int 9707static int
9737evalfun(struct funcnode *func, int argc, char **argv, int flags) 9708evalfun(struct funcnode *func, int argc, char **argv, int flags)
9738{ 9709{
9739 volatile struct shparam saveparam; 9710 volatile struct shparam saveparam;
9740 struct localvar *volatile savelocalvars;
9741 struct jmploc *volatile savehandler; 9711 struct jmploc *volatile savehandler;
9742 struct jmploc jmploc; 9712 struct jmploc jmploc;
9743 int e; 9713 int e;
9744 9714
9745 saveparam = shellparam; 9715 saveparam = shellparam;
9746 savelocalvars = localvars;
9747 savehandler = exception_handler; 9716 savehandler = exception_handler;
9748 e = setjmp(jmploc.loc); 9717 e = setjmp(jmploc.loc);
9749 if (e) { 9718 if (e) {
@@ -9751,7 +9720,6 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
9751 } 9720 }
9752 INT_OFF; 9721 INT_OFF;
9753 exception_handler = &jmploc; 9722 exception_handler = &jmploc;
9754 localvars = NULL;
9755 shellparam.malloced = 0; 9723 shellparam.malloced = 0;
9756 func->count++; 9724 func->count++;
9757 funcnest++; 9725 funcnest++;
@@ -9762,13 +9730,13 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
9762 shellparam.optind = 1; 9730 shellparam.optind = 1;
9763 shellparam.optoff = -1; 9731 shellparam.optoff = -1;
9764#endif 9732#endif
9733 pushlocalvars();
9765 evaltree(func->n.narg.next, flags & EV_TESTED); 9734 evaltree(func->n.narg.next, flags & EV_TESTED);
9735 poplocalvars(0);
9766 funcdone: 9736 funcdone:
9767 INT_OFF; 9737 INT_OFF;
9768 funcnest--; 9738 funcnest--;
9769 freefunc(func); 9739 freefunc(func);
9770 poplocalvars();
9771 localvars = savelocalvars;
9772 freeparam(&shellparam); 9740 freeparam(&shellparam);
9773 shellparam = saveparam; 9741 shellparam = saveparam;
9774 exception_handler = savehandler; 9742 exception_handler = savehandler;
@@ -9797,7 +9765,7 @@ mklocal(char *name)
9797 * x=0; f() { local x=1; echo $x; local x; echo $x; }; f; echo $x 9765 * x=0; f() { local x=1; echo $x; local x; echo $x; }; f; echo $x
9798 * x=0; f() { local x=1; echo $x; local x=2; echo $x; }; f; echo $x 9766 * x=0; f() { local x=1; echo $x; local x=2; echo $x; }; f; echo $x
9799 */ 9767 */
9800 lvp = localvars; 9768 lvp = localvar_stack->lv;
9801 while (lvp) { 9769 while (lvp) {
9802 if (lvp->vp && varcmp(lvp->vp->var_text, name) == 0) { 9770 if (lvp->vp && varcmp(lvp->vp->var_text, name) == 0) {
9803 if (eq) 9771 if (eq)
@@ -9822,10 +9790,9 @@ mklocal(char *name)
9822 if (vp == NULL) { 9790 if (vp == NULL) {
9823 /* variable did not exist yet */ 9791 /* variable did not exist yet */
9824 if (eq) 9792 if (eq)
9825 setvareq(name, VSTRFIXED); 9793 vp = setvareq(name, VSTRFIXED);
9826 else 9794 else
9827 setvar(name, NULL, VSTRFIXED); 9795 vp = setvar(name, NULL, VSTRFIXED);
9828 vp = *vpp; /* the new variable */
9829 lvp->flags = VUNSET; 9796 lvp->flags = VUNSET;
9830 } else { 9797 } else {
9831 lvp->text = vp->var_text; 9798 lvp->text = vp->var_text;
@@ -9842,8 +9809,8 @@ mklocal(char *name)
9842 } 9809 }
9843 } 9810 }
9844 lvp->vp = vp; 9811 lvp->vp = vp;
9845 lvp->next = localvars; 9812 lvp->next = localvar_stack->lv;
9846 localvars = lvp; 9813 localvar_stack->lv = lvp;
9847 ret: 9814 ret:
9848 INT_ON; 9815 INT_ON;
9849} 9816}
@@ -9856,7 +9823,7 @@ localcmd(int argc UNUSED_PARAM, char **argv)
9856{ 9823{
9857 char *name; 9824 char *name;
9858 9825
9859 if (!funcnest) 9826 if (!localvar_stack)
9860 ash_msg_and_raise_error("not in a function"); 9827 ash_msg_and_raise_error("not in a function");
9861 9828
9862 argv = argptr; 9829 argv = argptr;
@@ -10025,7 +9992,7 @@ static const struct builtincmd builtintab[] = {
10025#if ENABLE_FEATURE_SH_MATH 9992#if ENABLE_FEATURE_SH_MATH
10026 { BUILTIN_NOSPEC "let" , letcmd }, 9993 { BUILTIN_NOSPEC "let" , letcmd },
10027#endif 9994#endif
10028 { BUILTIN_ASSIGN "local" , localcmd }, 9995 { BUILTIN_SPEC_REG_ASSG "local" , localcmd },
10029#if ENABLE_ASH_PRINTF 9996#if ENABLE_ASH_PRINTF
10030 { BUILTIN_REGULAR "printf" , printfcmd }, 9997 { BUILTIN_REGULAR "printf" , printfcmd },
10031#endif 9998#endif
@@ -10115,6 +10082,7 @@ evalcommand(union node *cmd, int flags)
10115 static const struct builtincmd null_bltin = { 10082 static const struct builtincmd null_bltin = {
10116 "\0\0", bltincmd /* why three NULs? */ 10083 "\0\0", bltincmd /* why three NULs? */
10117 }; 10084 };
10085 struct localvar_list *localvar_stop;
10118 struct stackmark smark; 10086 struct stackmark smark;
10119 union node *argp; 10087 union node *argp;
10120 struct arglist arglist; 10088 struct arglist arglist;
@@ -10136,6 +10104,7 @@ evalcommand(union node *cmd, int flags)
10136 /* First expand the arguments. */ 10104 /* First expand the arguments. */
10137 TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); 10105 TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
10138 setstackmark(&smark); 10106 setstackmark(&smark);
10107 localvar_stop = pushlocalvars();
10139 back_exitstatus = 0; 10108 back_exitstatus = 0;
10140 10109
10141 cmdentry.cmdtype = CMDBUILTIN; 10110 cmdentry.cmdtype = CMDBUILTIN;
@@ -10189,6 +10158,8 @@ evalcommand(union node *cmd, int flags)
10189 spp = varlist.lastp; 10158 spp = varlist.lastp;
10190 expandarg(argp, &varlist, EXP_VARTILDE); 10159 expandarg(argp, &varlist, EXP_VARTILDE);
10191 10160
10161 mklocal((*spp)->text);
10162
10192 /* 10163 /*
10193 * Modify the command lookup path, if a PATH= assignment 10164 * Modify the command lookup path, if a PATH= assignment
10194 * is present 10165 * is present
@@ -10354,17 +10325,12 @@ evalcommand(union node *cmd, int flags)
10354 /* NOTREACHED */ 10325 /* NOTREACHED */
10355 } /* default */ 10326 } /* default */
10356 case CMDBUILTIN: 10327 case CMDBUILTIN:
10357 cmdenviron = varlist.list; 10328 if (spclbltin > 0 || argc == 0) {
10358 if (cmdenviron) { 10329 poplocalvars(1);
10359 struct strlist *list = cmdenviron; 10330 if (cmd_is_exec && argc > 1)
10360 int i = VNOSET; 10331 listsetvar(varlist.list, VEXPORT);
10361 if (spclbltin > 0 || argc == 0) {
10362 i = 0;
10363 if (cmd_is_exec && argc > 1)
10364 i = VEXPORT;
10365 }
10366 listsetvar(list, i);
10367 } 10332 }
10333
10368 /* Tight loop with builtins only: 10334 /* Tight loop with builtins only:
10369 * "while kill -0 $child; do true; done" 10335 * "while kill -0 $child; do true; done"
10370 * will never exit even if $child died, unless we do this 10336 * will never exit even if $child died, unless we do this
@@ -10382,7 +10348,7 @@ evalcommand(union node *cmd, int flags)
10382 goto readstatus; 10348 goto readstatus;
10383 10349
10384 case CMDFUNCTION: 10350 case CMDFUNCTION:
10385 listsetvar(varlist.list, 0); 10351 poplocalvars(1);
10386 /* See above for the rationale */ 10352 /* See above for the rationale */
10387 dowait(DOWAIT_NONBLOCK, NULL); 10353 dowait(DOWAIT_NONBLOCK, NULL);
10388 if (evalfun(cmdentry.u.func, argc, argv, flags)) 10354 if (evalfun(cmdentry.u.func, argc, argv, flags))
@@ -10395,6 +10361,7 @@ evalcommand(union node *cmd, int flags)
10395 out: 10361 out:
10396 if (cmd->ncmd.redirect) 10362 if (cmd->ncmd.redirect)
10397 popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec); 10363 popredir(/*drop:*/ cmd_is_exec, /*restore:*/ cmd_is_exec);
10364 unwindlocalvars(localvar_stop);
10398 if (lastarg) { 10365 if (lastarg) {
10399 /* dsl: I think this is intended to be used to support 10366 /* dsl: I think this is intended to be used to support
10400 * '_' in 'vi' command mode during line editing... 10367 * '_' in 'vi' command mode during line editing...
@@ -13051,6 +13018,12 @@ expandstr(const char *ps)
13051 return stackblock(); 13018 return stackblock();
13052} 13019}
13053 13020
13021static inline int
13022parser_eof(void)
13023{
13024 return tokpushback && lasttoken == TEOF;
13025}
13026
13054/* 13027/*
13055 * Execute a command or commands contained in a string. 13028 * Execute a command or commands contained in a string.
13056 */ 13029 */
@@ -13086,7 +13059,7 @@ evalstring(char *s, int flags)
13086 while ((n = parsecmd(0)) != NODE_EOF) { 13059 while ((n = parsecmd(0)) != NODE_EOF) {
13087 int i; 13060 int i;
13088 13061
13089 i = evaltree(n, flags); 13062 i = evaltree(n, flags & ~(parser_eof() ? 0 : EV_EXIT));
13090 if (n) 13063 if (n)
13091 status = i; 13064 status = i;
13092 popstackmark(&smark); 13065 popstackmark(&smark);
@@ -13237,11 +13210,12 @@ dotcmd(int argc_ UNUSED_PARAM, char **argv_ UNUSED_PARAM)
13237 char *fullname; 13210 char *fullname;
13238 char **argv; 13211 char **argv;
13239 char *args_need_save; 13212 char *args_need_save;
13240 struct strlist *sp;
13241 volatile struct shparam saveparam; 13213 volatile struct shparam saveparam;
13242 13214
13243 for (sp = cmdenviron; sp; sp = sp->next) 13215//???
13244 setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED); 13216// struct strlist *sp;
13217// for (sp = cmdenviron; sp; sp = sp->next)
13218// setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED);
13245 13219
13246 nextopt(nullstr); /* handle possible "--" */ 13220 nextopt(nullstr); /* handle possible "--" */
13247 argv = argptr; 13221 argv = argptr;
@@ -13588,13 +13562,18 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
13588 return 0; 13562 return 0;
13589 } 13563 }
13590 13564
13565 /* Why the second check?
13566 * "trap NUM [sig2]..." is the same as "trap - NUM [sig2]..."
13567 * In this case, NUM is signal no, not an action.
13568 */
13591 action = NULL; 13569 action = NULL;
13592 if (ap[1]) 13570 if (ap[1] && !is_number(ap[0]))
13593 action = *ap++; 13571 action = *ap++;
13572
13594 exitcode = 0; 13573 exitcode = 0;
13595 while (*ap) { 13574 while (*ap) {
13596 signo = get_signum(*ap); 13575 signo = get_signum(*ap);
13597 if (signo < 0 || signo >= NSIG) { 13576 if (signo < 0) {
13598 /* Mimic bash message exactly */ 13577 /* Mimic bash message exactly */
13599 ash_msg("%s: invalid signal specification", *ap); 13578 ash_msg("%s: invalid signal specification", *ap);
13600 exitcode = 1; 13579 exitcode = 1;
@@ -13752,7 +13731,6 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
13752 char **ap; 13731 char **ap;
13753 int i; 13732 int i;
13754 int flag = 0; 13733 int flag = 0;
13755 int ret = 0;
13756 13734
13757 while ((i = nextopt("vf")) != 0) { 13735 while ((i = nextopt("vf")) != 0) {
13758 flag = i; 13736 flag = i;
@@ -13760,15 +13738,13 @@ unsetcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
13760 13738
13761 for (ap = argptr; *ap; ap++) { 13739 for (ap = argptr; *ap; ap++) {
13762 if (flag != 'f') { 13740 if (flag != 'f') {
13763 i = unsetvar(*ap); 13741 unsetvar(*ap);
13764 ret |= i; 13742 continue;
13765 if (!(i & 2))
13766 continue;
13767 } 13743 }
13768 if (flag != 'v') 13744 if (flag != 'v')
13769 unsetfunc(*ap); 13745 unsetfunc(*ap);
13770 } 13746 }
13771 return ret & 1; 13747 return 0;
13772} 13748}
13773 13749
13774static const unsigned char timescmd_str[] ALIGN1 = { 13750static const unsigned char timescmd_str[] ALIGN1 = {
@@ -14250,6 +14226,9 @@ reset(void)
14250 /* from redir.c: */ 14226 /* from redir.c: */
14251 while (redirlist) 14227 while (redirlist)
14252 popredir(/*drop:*/ 0, /*restore:*/ 0); 14228 popredir(/*drop:*/ 0, /*restore:*/ 0);
14229
14230 /* from var.c: */
14231 unwindlocalvars(NULL);
14253} 14232}
14254 14233
14255#if PROFILE 14234#if PROFILE
@@ -14386,7 +14365,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
14386 // if (!sflag) g_parsefile->pf_fd = -1; 14365 // if (!sflag) g_parsefile->pf_fd = -1;
14387 // ^^ not necessary since now we special-case fd 0 14366 // ^^ not necessary since now we special-case fd 0
14388 // in is_hidden_fd() to not be considered "hidden fd" 14367 // in is_hidden_fd() to not be considered "hidden fd"
14389 evalstring(minusc, 0); 14368 evalstring(minusc, sflag ? 0 : EV_EXIT);
14390 } 14369 }
14391 14370
14392 if (sflag || minusc == NULL) { 14371 if (sflag || minusc == NULL) {
diff --git a/shell/ash_test/ash-misc/assignment2.right b/shell/ash_test/ash-misc/assignment2.right
new file mode 100644
index 000000000..179c71c5a
--- /dev/null
+++ b/shell/ash_test/ash-misc/assignment2.right
@@ -0,0 +1,2 @@
1./assignment2.tests: line 2: a=b: not found
2127
diff --git a/shell/ash_test/ash-misc/assignment2.tests b/shell/ash_test/ash-misc/assignment2.tests
new file mode 100755
index 000000000..f6938434c
--- /dev/null
+++ b/shell/ash_test/ash-misc/assignment2.tests
@@ -0,0 +1,3 @@
1# This must not be interpreted as an assignment
2a''=b true
3echo $?
diff --git a/shell/ash_test/ash-misc/empty_args.right b/shell/ash_test/ash-misc/empty_args.right
new file mode 100644
index 000000000..968b5a4d9
--- /dev/null
+++ b/shell/ash_test/ash-misc/empty_args.right
@@ -0,0 +1,6 @@
1Null 0th arg:
2./empty_args.tests: line 2: : Permission denied
3127
4Null 1st arg:
50
6Null arg in exec:
diff --git a/shell/ash_test/ash-misc/empty_args.tests b/shell/ash_test/ash-misc/empty_args.tests
new file mode 100755
index 000000000..efce5494a
--- /dev/null
+++ b/shell/ash_test/ash-misc/empty_args.tests
@@ -0,0 +1,9 @@
1echo Null 0th arg:
2""
3echo $?
4echo Null 1st arg:
5# printf without args would print usage info
6printf ""
7echo $?
8echo Null arg in exec:
9exec printf ""
diff --git a/shell/ash_test/ash-misc/env_and_func.right b/shell/ash_test/ash-misc/env_and_func.right
new file mode 100644
index 000000000..5fc3488ae
--- /dev/null
+++ b/shell/ash_test/ash-misc/env_and_func.right
@@ -0,0 +1,2 @@
1var=val
2var=val
diff --git a/shell/ash_test/ash-misc/env_and_func.tests b/shell/ash_test/ash-misc/env_and_func.tests
new file mode 100755
index 000000000..3efef1a41
--- /dev/null
+++ b/shell/ash_test/ash-misc/env_and_func.tests
@@ -0,0 +1,8 @@
1var=old
2f() { echo "var=$var"; }
3# bash: POSIXLY_CORRECT behavior is to "leak" new variable values
4# out of function invocations (similar to "special builtins" behavior);
5# but in "bash mode", they don't leak.
6# hush does not "leak" values. ash does.
7var=val f
8echo "var=$var"
diff --git a/shell/ash_test/ash-psubst/emptytick.right b/shell/ash_test/ash-psubst/emptytick.right
new file mode 100644
index 000000000..7629deba6
--- /dev/null
+++ b/shell/ash_test/ash-psubst/emptytick.right
@@ -0,0 +1,17 @@
10
20
3./emptytick.tests: line 3: : Permission denied
4127
5./emptytick.tests: line 4: : Permission denied
6127
70
80
90
100
11./emptytick.tests: line 10: : Permission denied
12127
13./emptytick.tests: line 11: : Permission denied
14127
150
160
17./emptytick.tests: exec: line 15: : Permission denied
diff --git a/shell/ash_test/ash-psubst/emptytick.tests b/shell/ash_test/ash-psubst/emptytick.tests
new file mode 100755
index 000000000..eaffafb22
--- /dev/null
+++ b/shell/ash_test/ash-psubst/emptytick.tests
@@ -0,0 +1,16 @@
1true; ``; echo $?
2false; ``; echo $?
3true; `""`; echo $?
4false; `""`; echo $?
5true; ` `; echo $?
6false; ` `; echo $?
7
8true; $(); echo $?
9false; $(); echo $?
10true; $(""); echo $?
11false; $(""); echo $?
12true; $( ); echo $?
13false; $( ); echo $?
14
15exec ''; echo $?
16echo Not reached
diff --git a/shell/ash_test/ash-psubst/tick.right b/shell/ash_test/ash-psubst/tick.right
new file mode 100644
index 000000000..6ed281c75
--- /dev/null
+++ b/shell/ash_test/ash-psubst/tick.right
@@ -0,0 +1,2 @@
11
21
diff --git a/shell/ash_test/ash-psubst/tick.tests b/shell/ash_test/ash-psubst/tick.tests
new file mode 100755
index 000000000..1f749a9cd
--- /dev/null
+++ b/shell/ash_test/ash-psubst/tick.tests
@@ -0,0 +1,4 @@
1true
2false; echo `echo $?`
3true
4{ false; echo `echo $?`; }
diff --git a/shell/ash_test/ash-psubst/tick2.right b/shell/ash_test/ash-psubst/tick2.right
new file mode 100644
index 000000000..216c883b8
--- /dev/null
+++ b/shell/ash_test/ash-psubst/tick2.right
@@ -0,0 +1 @@
BAZ
diff --git a/shell/ash_test/ash-psubst/tick2.tests b/shell/ash_test/ash-psubst/tick2.tests
new file mode 100755
index 000000000..db4e944fe
--- /dev/null
+++ b/shell/ash_test/ash-psubst/tick2.tests
@@ -0,0 +1,5 @@
1if false; then
2 echo "FOO"
3 tmp=`echo BAR >&2`
4fi
5echo BAZ
diff --git a/shell/ash_test/ash-psubst/tick3.right b/shell/ash_test/ash-psubst/tick3.right
new file mode 100644
index 000000000..00f267ae5
--- /dev/null
+++ b/shell/ash_test/ash-psubst/tick3.right
@@ -0,0 +1,6 @@
1\TESTZZBEST
2$TEST
3Q
4a\bc
511-$a-\t-\-\"-`-\--\z-\*-\?-22 33-$a-\t-\-"-`-\--\z-\*-\?-44
6done:0
diff --git a/shell/ash_test/ash-psubst/tick3.tests b/shell/ash_test/ash-psubst/tick3.tests
new file mode 100755
index 000000000..3aeb241c3
--- /dev/null
+++ b/shell/ash_test/ash-psubst/tick3.tests
@@ -0,0 +1,14 @@
1test "$CONFIG_FEATURE_FANCY_ECHO" = "y" || exit 77
2
3TEST=Q
4# \` is special
5echo `echo '\'TEST\`echo ZZ\`BEST`
6# \$ and \\ are special
7echo `echo \\$TEST`
8echo `echo \$TEST`
9echo a`echo \\\\b`c
10
11# \" is not special if in unquoted `cmd` (passed verbatim WITH \),
12# but is special in quoted one
13echo `echo 11'-$a-\t-\\-\"-\`-\--\z-\*-\?-'22` "`echo 33'-$a-\t-\\-\"-\`-\--\z-\*-\?-'44`"
14echo done:$?
diff --git a/shell/ash_test/ash-psubst/tick4.right b/shell/ash_test/ash-psubst/tick4.right
new file mode 100644
index 000000000..d8030eafd
--- /dev/null
+++ b/shell/ash_test/ash-psubst/tick4.right
@@ -0,0 +1,7 @@
1(TEST) BEST
2TEST) BEST
3((TEST) BEST
4)
5abc
6a)c
7OK: 0
diff --git a/shell/ash_test/ash-psubst/tick4.tests b/shell/ash_test/ash-psubst/tick4.tests
new file mode 100755
index 000000000..f2305fb3d
--- /dev/null
+++ b/shell/ash_test/ash-psubst/tick4.tests
@@ -0,0 +1,7 @@
1echo $(echo '(TEST)' BEST)
2echo $(echo 'TEST)' BEST)
3echo $(echo \(\(TEST\) BEST)
4echo $(echo \))
5echo $(echo a"`echo "b"`"c )
6echo $(echo a"`echo ")"`"c )
7echo OK: $?
diff --git a/shell/ash_test/ash-psubst/tick_huge.right b/shell/ash_test/ash-psubst/tick_huge.right
new file mode 100644
index 000000000..11740f674
--- /dev/null
+++ b/shell/ash_test/ash-psubst/tick_huge.right
@@ -0,0 +1,3 @@
1546ed3f5c81c780d3ab86ada14824237 -
2546ed3f5c81c780d3ab86ada14824237 -
3End
diff --git a/shell/ash_test/ash-psubst/tick_huge.tests b/shell/ash_test/ash-psubst/tick_huge.tests
new file mode 100755
index 000000000..acce92fb2
--- /dev/null
+++ b/shell/ash_test/ash-psubst/tick_huge.tests
@@ -0,0 +1,7 @@
1# This creates 120k file
2yes "123456789 123456789 123456789 123456789" | head -3000 >>"$0.tmp"
3
4echo "`cat $0.tmp`" | md5sum
5rm "$0.tmp"
6yes "123456789 123456789 123456789 123456789" | head -3000 | md5sum
7echo End
diff --git a/shell/ash_test/ash-signals/catch.right b/shell/ash_test/ash-signals/catch.right
new file mode 100644
index 000000000..68530c6e7
--- /dev/null
+++ b/shell/ash_test/ash-signals/catch.right
@@ -0,0 +1,5 @@
1sending USR2
2caught
3sending USR2
4sending USR2
5User defined signal 2
diff --git a/shell/ash_test/ash-signals/catch.tests b/shell/ash_test/ash-signals/catch.tests
new file mode 100755
index 000000000..d2a21d17e
--- /dev/null
+++ b/shell/ash_test/ash-signals/catch.tests
@@ -0,0 +1,20 @@
1# avoid ugly warnings about signals not being caught
2trap ":" USR1 USR2
3
4"$THIS_SH" -c '
5trap "echo caught" USR2
6echo "sending USR2"
7kill -USR2 $$
8
9trap "" USR2
10echo "sending USR2"
11kill -USR2 $$
12
13trap "-" USR2
14echo "sending USR2"
15kill -USR2 $$
16
17echo "not reached"
18'
19
20trap "-" USR1 USR2
diff --git a/shell/ash_test/ash-signals/signal_read2.right b/shell/ash_test/ash-signals/signal_read2.right
new file mode 100644
index 000000000..87d8da304
--- /dev/null
+++ b/shell/ash_test/ash-signals/signal_read2.right
@@ -0,0 +1,2 @@
1Hangup
2Done:129
diff --git a/shell/ash_test/ash-signals/signal_read2.tests b/shell/ash_test/ash-signals/signal_read2.tests
new file mode 100755
index 000000000..eab5b9b5b
--- /dev/null
+++ b/shell/ash_test/ash-signals/signal_read2.tests
@@ -0,0 +1,7 @@
1$THIS_SH -c '
2(sleep 1; kill -HUP $$) &
3while true; do
4 read ignored
5done
6'
7echo "Done:$?"
diff --git a/shell/ash_test/ash-signals/subshell.right b/shell/ash_test/ash-signals/subshell.right
new file mode 100644
index 000000000..248fcc41a
--- /dev/null
+++ b/shell/ash_test/ash-signals/subshell.right
@@ -0,0 +1,21 @@
1trap -- '' HUP
2trap -- '' QUIT
3trap -- '' SYS
4Ok
5trap -- '' HUP
6trap -- '' QUIT
7trap -- '' SYS
8Ok
9trap -- '' HUP
10trap -- '' QUIT
11trap -- '' SYS
12Ok
13trap -- '' HUP
14trap -- '' QUIT
15trap -- '' SYS
16Ok
17trap -- '' HUP
18trap -- '' QUIT
19trap -- '' SYS
20Terminated
21Done
diff --git a/shell/ash_test/ash-signals/subshell.tests b/shell/ash_test/ash-signals/subshell.tests
new file mode 100755
index 000000000..d877f2b82
--- /dev/null
+++ b/shell/ash_test/ash-signals/subshell.tests
@@ -0,0 +1,19 @@
1# Non-empty traps should be reset in subshell
2
3# HUP is special in interactive shells
4trap '' HUP
5# QUIT is always special
6trap '' QUIT
7# SYS is not special
8trap '' SYS
9# WINCH is harmless
10trap 'bad: caught WINCH' WINCH
11# With TERM we'll check whether it is reset
12trap 'bad: caught TERM' TERM
13
14(trap; "$THIS_SH" -c 'kill -HUP $PPID'; echo Ok)
15(trap; "$THIS_SH" -c 'kill -QUIT $PPID'; echo Ok)
16(trap; "$THIS_SH" -c 'kill -SYS $PPID'; echo Ok)
17(trap; "$THIS_SH" -c 'kill -WINCH $PPID'; echo Ok)
18(trap; "$THIS_SH" -c 'kill -TERM $PPID'; echo Bad: TERM is not reset)
19echo Done
diff --git a/shell/ash_test/ash-vars/param_expand_alt.right b/shell/ash_test/ash-vars/param_expand_alt.right
new file mode 100644
index 000000000..1303f8064
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_alt.right
@@ -0,0 +1,9 @@
1SHELL: line 1: syntax error: bad substitution
2SHELL: line 1: syntax error: bad substitution
3__
4_z_ _z_
5_ _ _ _ _
6_aaaa _ _ _word _word
7_ _ _ _ _
8_ _ _ _word _
9_fff _ _ _word _word
diff --git a/shell/ash_test/ash-vars/param_expand_alt.tests b/shell/ash_test/ash-vars/param_expand_alt.tests
new file mode 100755
index 000000000..23e9a26be
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_alt.tests
@@ -0,0 +1,33 @@
1# First try some invalid patterns. Do in subshell due to parsing error.
2# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
3"$THIS_SH" -c 'echo ${+} ; echo moo' SHELL
4"$THIS_SH" -c 'echo ${:+} ; echo moo' SHELL
5
6# now some funky ones.
7# ${V+word} "if V unset, then substitute nothing, else substitute word"
8# ${V:+word} "if V unset or '', then substitute nothing, else substitute word"
9#
10# ${#:+} is a :+ op on $#, but ${#+} (and any other ${#c}) is "length of $c",
11# not + op on $#.
12# bash and dash do not accept ${#+}. it's possible for some shell to skip
13# the check that c is valid and interpret ${#+} as "len of $+". Not testing it.
14# echo _${#+}_
15echo _${#:+}_
16# Forms with non-empty word work as expected in both ash and bash.
17echo _${#+z}_ _${#:+z}_
18
19# now some valid ones
20set --
21echo _$1 _${1+} _${1:+} _${1+word} _${1:+word}
22
23set -- aaaa
24echo _$1 _${1+} _${1:+} _${1+word} _${1:+word}
25
26unset f
27echo _$f _${f+} _${f:+} _${f+word} _${f:+word}
28
29f=
30echo _$f _${f+} _${f:+} _${f+word} _${f:+word}
31
32f=fff
33echo _$f _${f+} _${f:+} _${f+word} _${f:+word}
diff --git a/shell/ash_test/ash-vars/param_expand_assign.right b/shell/ash_test/ash-vars/param_expand_assign.right
new file mode 100644
index 000000000..9b07d8cd4
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_assign.right
@@ -0,0 +1,27 @@
1SHELL: line 1: syntax error: bad substitution
2SHELL: line 1: syntax error: bad substitution
30
40
5SHELL: line 1: 1: bad variable name
6SHELL: line 1: 1: bad variable name
7SHELL: line 1: 1: bad variable name
8SHELL: line 1: 1: bad variable name
9_aa
10_aa
11_aa
12_aa
13_
14_
15_
16_word
17_word
18_
19_
20_
21_
22_word
23_fff
24_fff
25_fff
26_fff
27_fff
diff --git a/shell/ash_test/ash-vars/param_expand_assign.tests b/shell/ash_test/ash-vars/param_expand_assign.tests
new file mode 100755
index 000000000..79de95613
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_assign.tests
@@ -0,0 +1,39 @@
1# First try some invalid patterns. Do in subshell due to parsing error.
2# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
3"$THIS_SH" -c 'echo ${=}' SHELL
4"$THIS_SH" -c 'echo ${:=}' SHELL
5
6# now some funky ones
7"$THIS_SH" -c 'echo ${#=}' SHELL
8"$THIS_SH" -c 'echo ${#:=}' SHELL
9
10# should error out
11"$THIS_SH" -c 'set --; echo _${1=}' SHELL
12"$THIS_SH" -c 'set --; echo _${1:=}' SHELL
13"$THIS_SH" -c 'set --; echo _${1=word}' SHELL
14"$THIS_SH" -c 'set --; echo _${1:=word}' SHELL
15
16# should not error
17"$THIS_SH" -c 'set aa; echo _${1=}' SHELL
18"$THIS_SH" -c 'set aa; echo _${1:=}' SHELL
19"$THIS_SH" -c 'set aa; echo _${1=word}' SHELL
20"$THIS_SH" -c 'set aa; echo _${1:=word}' SHELL
21
22# should work fine
23unset f; echo _$f
24unset f; echo _${f=}
25unset f; echo _${f:=}
26unset f; echo _${f=word}
27unset f; echo _${f:=word}
28
29f=; echo _$f
30f=; echo _${f=}
31f=; echo _${f:=}
32f=; echo _${f=word}
33f=; echo _${f:=word}
34
35f=fff; echo _$f
36f=fff; echo _${f=}
37f=fff; echo _${f:=}
38f=fff; echo _${f=word}
39f=fff; echo _${f:=word}
diff --git a/shell/ash_test/ash-vars/param_expand_bash_substring.right b/shell/ash_test/ash-vars/param_expand_bash_substring.right
new file mode 100644
index 000000000..9ad6dbcad
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_bash_substring.right
@@ -0,0 +1,64 @@
1SHELL: line 1: syntax error: bad substitution
2SHELL: line 1: syntax error: bad substitution
3SHELL: line 1: syntax error: bad substitution
4SHELL: line 1: syntax error: bad substitution
5SHELL: line 1: syntax error: missing '}'
61 =||
71:1 =||
81:1:2=||
91::2 =||
101:1: =||
111:: =||
121 =|0123|
131:1 =|123|
141:1:2=|12|
151::2 =|01|
161:1: =||
171:: =||
18f =||
19f:1 =||
20f:1:2=||
21f::2 =||
22f:1: =||
23f:: =||
24f =||
25f:1 =||
26f:1:2=||
27f::2 =||
28f:1: =||
29f:: =||
30f =|a|
31f:1 =||
32f:1:2=||
33f::2 =|a|
34f:1: =||
35f:: =||
36f =|0123456789|
37f:1 =|123456789|
38f:1:2=|12|
39f::2 =|01|
40f:1: =||
41f:: =||
42Substrings from special vars
43? =|0|
44?:1 =||
45?:1:2=||
46?::2 =|0|
47?:1: =||
48?:: =||
49# =|11|
50#:1 =|1|
51#:1:2=|1|
52#::2 =|11|
53#:1: =||
54#:: =||
55Substrings with expressions
56f =|01234567|
57f:1+1:2+2 =|2345|
58f:-1:2+2 =|01234567|
59f:1:f =|1234567|
60f:1:$f =|1234567|
61f:1:${f} =|1234567|
62f:1:${f:3:1} =|123|
63f:1:1`echo 1`=|1|
64Done
diff --git a/shell/ash_test/ash-vars/param_expand_bash_substring.tests b/shell/ash_test/ash-vars/param_expand_bash_substring.tests
new file mode 100755
index 000000000..cce9f123e
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_bash_substring.tests
@@ -0,0 +1,84 @@
1# first try some invalid patterns
2# do all of these in subshells since it's supposed to error out
3# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
4export var=0123456789
5"$THIS_SH" -c 'echo ${:}' SHELL
6"$THIS_SH" -c 'echo ${::}' SHELL
7"$THIS_SH" -c 'echo ${:1}' SHELL
8"$THIS_SH" -c 'echo ${::1}' SHELL
9
10#this also is not valid in bash, hush accepts it:
11"$THIS_SH" -c 'echo ${var:}' SHELL
12
13# then some funky ones
14# UNFIXED BUG: this should work: "$THIS_SH" -c 'echo ${?:0}'
15
16# now some valid ones
17set --; echo "1 =|${1}|"
18set --; echo "1:1 =|${1:1}|"
19set --; echo "1:1:2=|${1:1:2}|"
20set --; echo "1::2 =|${1::2}|"
21set --; echo "1:1: =|${1:1:}|"
22set --; echo "1:: =|${1::}|"
23
24set -- 0123; echo "1 =|${1}|"
25set -- 0123; echo "1:1 =|${1:1}|"
26set -- 0123; echo "1:1:2=|${1:1:2}|"
27set -- 0123; echo "1::2 =|${1::2}|"
28set -- 0123; echo "1:1: =|${1:1:}|"
29set -- 0123; echo "1:: =|${1::}|"
30
31unset f; echo "f =|$f|"
32unset f; echo "f:1 =|${f:1}|"
33unset f; echo "f:1:2=|${f:1:2}|"
34unset f; echo "f::2 =|${f::2}|"
35unset f; echo "f:1: =|${f:1:}|"
36unset f; echo "f:: =|${f::}|"
37
38f=; echo "f =|$f|"
39f=; echo "f:1 =|${f:1}|"
40f=; echo "f:1:2=|${f:1:2}|"
41f=; echo "f::2 =|${f::2}|"
42f=; echo "f:1: =|${f:1:}|"
43f=; echo "f:: =|${f::}|"
44
45f=a; echo "f =|$f|"
46f=a; echo "f:1 =|${f:1}|"
47f=a; echo "f:1:2=|${f:1:2}|"
48f=a; echo "f::2 =|${f::2}|"
49f=a; echo "f:1: =|${f:1:}|"
50f=a; echo "f:: =|${f::}|"
51
52f=0123456789; echo "f =|$f|"
53f=0123456789; echo "f:1 =|${f:1}|"
54f=0123456789; echo "f:1:2=|${f:1:2}|"
55f=0123456789; echo "f::2 =|${f::2}|"
56f=0123456789; echo "f:1: =|${f:1:}|"
57f=0123456789; echo "f:: =|${f::}|"
58
59echo "Substrings from special vars"
60echo '? '"=|$?|"
61echo '?:1 '"=|${?:1}|"
62echo '?:1:2'"=|${?:1:2}|"
63echo '?::2 '"=|${?::2}|"
64echo '?:1: '"=|${?:1:}|"
65echo '?:: '"=|${?::}|"
66set -- 1 2 3 4 5 6 7 8 9 10 11
67echo '# '"=|$#|"
68echo '#:1 '"=|${#:1}|"
69echo '#:1:2'"=|${#:1:2}|"
70echo '#::2 '"=|${#::2}|"
71echo '#:1: '"=|${#:1:}|"
72echo '#:: '"=|${#::}|"
73
74echo "Substrings with expressions"
75f=01234567; echo 'f '"=|$f|"
76f=01234567; echo 'f:1+1:2+2 '"=|${f:1+1:2+2}|"
77f=01234567; echo 'f:-1:2+2 '"=|${f:-1:2+2}|"
78f=01234567; echo 'f:1:f '"=|${f:1:f}|"
79f=01234567; echo 'f:1:$f '"=|${f:1:$f}|"
80f=01234567; echo 'f:1:${f} '"=|${f:1:${f}}|"
81f=01234567; echo 'f:1:${f:3:1} '"=|${f:1:${f:3:1}}|"
82f=01234567; echo 'f:1:1`echo 1`'"=|${f:1:`echo 1`}|"
83
84echo Done
diff --git a/shell/ash_test/ash-vars/param_expand_default.right b/shell/ash_test/ash-vars/param_expand_default.right
new file mode 100644
index 000000000..3eecd1375
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_default.right
@@ -0,0 +1,7 @@
1SHELL: line 1: syntax error: bad substitution
2_0 _0
3_ _ _ _word _word
4_aaaa _aaaa _aaaa _aaaa _aaaa
5_ _ _ _word _word
6_ _ _ _ _word
7_fff _fff _fff _fff _fff
diff --git a/shell/ash_test/ash-vars/param_expand_default.tests b/shell/ash_test/ash-vars/param_expand_default.tests
new file mode 100755
index 000000000..5e42d30e3
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_default.tests
@@ -0,0 +1,23 @@
1# first try some invalid patterns (do in subshell due to parsing error)
2# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
3# valid in bash and ash (same as $-): "$THIS_SH" -c 'echo ${-}' SHELL
4"$THIS_SH" -c 'echo ${:-}' SHELL
5
6# now some funky ones
7echo _${#-} _${#:-}
8
9# now some valid ones
10set --
11echo _$1 _${1-} _${1:-} _${1-word} _${1:-word}
12
13set -- aaaa
14echo _$1 _${1-} _${1:-} _${1-word} _${1:-word}
15
16unset f
17echo _$f _${f-} _${f:-} _${f-word} _${f:-word}
18
19f=
20echo _$f _${f-} _${f:-} _${f-word} _${f:-word}
21
22f=fff
23echo _$f _${f-} _${f:-} _${f-word} _${f:-word}
diff --git a/shell/ash_test/ash-vars/param_expand_indicate_error.right b/shell/ash_test/ash-vars/param_expand_indicate_error.right
new file mode 100644
index 000000000..33afacee0
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_indicate_error.right
@@ -0,0 +1,43 @@
1SHELL: line 1: syntax error: bad substitution
21
30
4====
5_
6SHELL: line 1: 1: parameter not set
7SHELL: line 1: 1: parameter not set or null
8SHELL: line 1: 1: message1
9SHELL: line 1: 1: message1
10SHELL: line 1: 1: unset!
11SHELL: line 1: 1: null or unset!
12====
13_aaaa
14_aaaa
15_aaaa
16_aaaa
17_aaaa
18_aaaa
19_aaaa
20====
21_
22SHELL: line 1: f: parameter not set
23SHELL: line 1: f: parameter not set or null
24SHELL: line 1: f: message3
25SHELL: line 1: f: message3
26SHELL: line 1: f: unset!
27SHELL: line 1: f: null or unset!
28====
29_
30_
31SHELL: line 1: f: parameter not set or null
32_
33SHELL: line 1: f: message4
34_
35SHELL: line 1: f: null or unset!
36====
37_fff
38_fff
39_fff
40_fff
41_fff
42_fff
43_fff
diff --git a/shell/ash_test/ash-vars/param_expand_indicate_error.tests b/shell/ash_test/ash-vars/param_expand_indicate_error.tests
new file mode 100755
index 000000000..0f3061949
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_indicate_error.tests
@@ -0,0 +1,61 @@
1# do all of these in subshells since it's supposed to error out
2# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
3
4# first try some invalid patterns
5#"$THIS_SH" -c 'echo ${?}' SHELL -- this is valid as it's the same as $?
6"$THIS_SH" -c 'echo ${:?}' SHELL
7
8# then some funky ones
9# note: bash prints 1 - treats it as "length of $#"
10"$THIS_SH" -c 'echo ${#?}' SHELL
11# bash prints 0
12"$THIS_SH" -c 'echo ${#:?}' SHELL
13
14# now some valid ones
15export msg_unset="unset!"
16export msg_null_or_unset="null or unset!"
17
18echo ====
19"$THIS_SH" -c 'set --; echo _$1' SHELL
20"$THIS_SH" -c 'set --; echo _${1?}' SHELL
21"$THIS_SH" -c 'set --; echo _${1:?}' SHELL
22"$THIS_SH" -c 'set --; echo _${1?message1}' SHELL
23"$THIS_SH" -c 'set --; echo _${1:?message1}' SHELL
24"$THIS_SH" -c 'set --; echo _${1?$msg_unset}' SHELL
25"$THIS_SH" -c 'set --; echo _${1:?$msg_null_or_unset}' SHELL
26
27echo ====
28"$THIS_SH" -c 'set -- aaaa; echo _$1' SHELL
29"$THIS_SH" -c 'set -- aaaa; echo _${1?}' SHELL
30"$THIS_SH" -c 'set -- aaaa; echo _${1:?}' SHELL
31"$THIS_SH" -c 'set -- aaaa; echo _${1?word}' SHELL
32"$THIS_SH" -c 'set -- aaaa; echo _${1:?word}' SHELL
33"$THIS_SH" -c 'set -- aaaa; echo _${1?$msg_unset}' SHELL
34"$THIS_SH" -c 'set -- aaaa; echo _${1:?$msg_null_or_unset}' SHELL
35
36echo ====
37"$THIS_SH" -c 'unset f; echo _$f' SHELL
38"$THIS_SH" -c 'unset f; echo _${f?}' SHELL
39"$THIS_SH" -c 'unset f; echo _${f:?}' SHELL
40"$THIS_SH" -c 'unset f; echo _${f?message3}' SHELL
41"$THIS_SH" -c 'unset f; echo _${f:?message3}' SHELL
42"$THIS_SH" -c 'unset f; echo _${f?$msg_unset}' SHELL
43"$THIS_SH" -c 'unset f; echo _${f:?$msg_null_or_unset}' SHELL
44
45echo ====
46"$THIS_SH" -c 'f=; echo _$f' SHELL
47"$THIS_SH" -c 'f=; echo _${f?}' SHELL
48"$THIS_SH" -c 'f=; echo _${f:?}' SHELL
49"$THIS_SH" -c 'f=; echo _${f?word}' SHELL
50"$THIS_SH" -c 'f=; echo _${f:?message4}' SHELL
51"$THIS_SH" -c 'f=; echo _${f?$msg_unset}' SHELL
52"$THIS_SH" -c 'f=; echo _${f:?$msg_null_or_unset}' SHELL
53
54echo ====
55"$THIS_SH" -c 'f=fff; echo _$f' SHELL
56"$THIS_SH" -c 'f=fff; echo _${f?}' SHELL
57"$THIS_SH" -c 'f=fff; echo _${f:?}' SHELL
58"$THIS_SH" -c 'f=fff; echo _${f?word}' SHELL
59"$THIS_SH" -c 'f=fff; echo _${f:?word}' SHELL
60"$THIS_SH" -c 'f=fff; echo _${f?$msg_unset}' SHELL
61"$THIS_SH" -c 'f=fff; echo _${f:?$msg_null_or_unset}' SHELL
diff --git a/shell/ash_test/ash-vars/param_expand_len1.right b/shell/ash_test/ash-vars/param_expand_len1.right
new file mode 100644
index 000000000..dff3c7bb1
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_len1.right
@@ -0,0 +1,11 @@
1One:1
2Two:2
3Three:3
4
5One:1
6Two:2
7Three:3
8
9Ok ${#$}: 0
10
11Ok ${#!}: 0
diff --git a/shell/ash_test/ash-vars/param_expand_len1.tests b/shell/ash_test/ash-vars/param_expand_len1.tests
new file mode 100755
index 000000000..e1beab320
--- /dev/null
+++ b/shell/ash_test/ash-vars/param_expand_len1.tests
@@ -0,0 +1,31 @@
1# ${#c} for any single char c means "length of $c", including all special vars
2
3false
4echo One:${#?}
5(exit 10)
6echo Two:${#?}
7(exit 100)
8echo Three:${#?}
9
10echo
11echo One:${##}
12set -- 1 2 3 4 5 6 7 8 9 0
13echo Two:${##}
14set -- 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
15 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
16 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
17 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
18echo Three:${##}
19
20echo
21v=$$
22test "${#v}" = "${#$}"
23echo 'Ok ${#$}:' $?
24
25echo
26sleep 0 &
27v=$!
28test "${#v}" = "${#!}"
29echo 'Ok ${#!}:' $?
30
31# TODO: ${#-} ${#_}
diff --git a/shell/ash_test/ash-vars/readonly0.right b/shell/ash_test/ash-vars/readonly0.right
index f3a6bde9e..ecc4054f8 100644
--- a/shell/ash_test/ash-vars/readonly0.right
+++ b/shell/ash_test/ash-vars/readonly0.right
@@ -10,4 +10,4 @@ Fail:2
10./readonly0.tests: export: line 27: a: is read only 10./readonly0.tests: export: line 27: a: is read only
11Fail:2 11Fail:2
12 12
13Fail:1 13./readonly0.tests: unset: line 44: a: is read only
diff --git a/shell/ash_test/ash-vars/unset.right b/shell/ash_test/ash-vars/unset.right
new file mode 100644
index 000000000..77d5abe9e
--- /dev/null
+++ b/shell/ash_test/ash-vars/unset.right
@@ -0,0 +1,17 @@
1./unset.tests: unset: line 3: -: bad variable name
22
3./unset.tests: unset: line 5: illegal option -m
42
50
6___
70 f g
80 g
90
10___
110 f g
120
130 f g
140
15___
16./unset.tests: unset: line 36: VAR_RO: is read only
172 f g
diff --git a/shell/ash_test/ash-vars/unset.tests b/shell/ash_test/ash-vars/unset.tests
new file mode 100755
index 000000000..11b392744
--- /dev/null
+++ b/shell/ash_test/ash-vars/unset.tests
@@ -0,0 +1,40 @@
1# check invalid options are rejected
2# bash: in posix mode, aborts if non-interactive; using subshell to avoid that
3(unset -)
4echo $?
5(unset -m a b c)
6echo $?
7
8# check funky usage
9unset
10echo $?
11
12# check normal usage
13echo ___
14f=f g=g
15echo $? $f $g
16unset f
17echo $? $f $g
18unset g
19echo $? $f $g
20
21echo ___
22f=f g=g
23echo $? $f $g
24unset f g
25echo $? $f $g
26f=f g=g
27echo $? $f $g
28unset -v f g
29echo $? $f $g
30
31# check read only vars
32echo ___
33f=f g=g
34VAR_RO=1
35readonly VAR_RO
36(unset VAR_RO)
37echo $? $f $g
38# not testing "do variables survive error halfway through unset" since unset aborts
39# unset f VAR_RO g
40#echo $? $f $g
diff --git a/shell/hush.c b/shell/hush.c
index 309ed2139..d0225edb9 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -4466,6 +4466,8 @@ static int parse_dollar(o_string *as_string,
4466 case '@': /* args */ 4466 case '@': /* args */
4467 goto make_one_char_var; 4467 goto make_one_char_var;
4468 case '{': { 4468 case '{': {
4469 char len_single_ch;
4470
4469 o_addchr(dest, SPECIAL_VAR_SYMBOL); 4471 o_addchr(dest, SPECIAL_VAR_SYMBOL);
4470 4472
4471 ch = i_getch(input); /* eat '{' */ 4473 ch = i_getch(input); /* eat '{' */
@@ -4485,6 +4487,7 @@ static int parse_dollar(o_string *as_string,
4485 return 0; 4487 return 0;
4486 } 4488 }
4487 nommu_addchr(as_string, ch); 4489 nommu_addchr(as_string, ch);
4490 len_single_ch = ch;
4488 ch |= quote_mask; 4491 ch |= quote_mask;
4489 4492
4490 /* It's possible to just call add_till_closing_bracket() at this point. 4493 /* It's possible to just call add_till_closing_bracket() at this point.
@@ -4509,9 +4512,18 @@ static int parse_dollar(o_string *as_string,
4509 /* handle parameter expansions 4512 /* handle parameter expansions
4510 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02 4513 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
4511 */ 4514 */
4512 if (!strchr(VAR_SUBST_OPS, ch)) /* ${var<bad_char>... */ 4515 if (!strchr(VAR_SUBST_OPS, ch)) { /* ${var<bad_char>... */
4513 goto bad_dollar_syntax; 4516 if (len_single_ch != '#'
4514 4517 /*|| !strchr(SPECIAL_VARS_STR, ch) - disallow errors like ${#+} ? */
4518 || i_peek(input) != '}'
4519 ) {
4520 goto bad_dollar_syntax;
4521 }
4522 /* else: it's "length of C" ${#C} op,
4523 * where C is a single char
4524 * special var name, e.g. ${#!}.
4525 */
4526 }
4515 /* Eat everything until closing '}' (or ':') */ 4527 /* Eat everything until closing '}' (or ':') */
4516 end_ch = '}'; 4528 end_ch = '}';
4517 if (BASH_SUBSTR 4529 if (BASH_SUBSTR
@@ -4568,6 +4580,7 @@ static int parse_dollar(o_string *as_string,
4568 } 4580 }
4569 break; 4581 break;
4570 } 4582 }
4583 len_single_ch = 0; /* it can't be ${#C} op */
4571 } 4584 }
4572 o_addchr(dest, SPECIAL_VAR_SYMBOL); 4585 o_addchr(dest, SPECIAL_VAR_SYMBOL);
4573 break; 4586 break;
@@ -5559,8 +5572,10 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5559 first_char = arg[0] = arg0 & 0x7f; 5572 first_char = arg[0] = arg0 & 0x7f;
5560 exp_op = 0; 5573 exp_op = 0;
5561 5574
5562 if (first_char == '#' /* ${#... */ 5575 if (first_char == '#' && arg[1] /* ${#...} but not ${#} */
5563 && arg[1] && !exp_saveptr /* not ${#} and not ${#<op_char>...} */ 5576 && (!exp_saveptr /* and ( not(${#<op_char>...}) */
5577 || (arg[2] == '\0' && strchr(SPECIAL_VARS_STR, arg[1])) /* or ${#C} "len of $C" ) */
5578 ) /* NB: skipping ^^^specvar check mishandles ${#::2} */
5564 ) { 5579 ) {
5565 /* It must be length operator: ${#var} */ 5580 /* It must be length operator: ${#var} */
5566 var++; 5581 var++;
@@ -5797,7 +5812,11 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5797 /* mimic bash message */ 5812 /* mimic bash message */
5798 die_if_script("%s: %s", 5813 die_if_script("%s: %s",
5799 var, 5814 var,
5800 exp_word[0] ? exp_word : "parameter null or not set" 5815 exp_word[0]
5816 ? exp_word
5817 : "parameter null or not set"
5818 /* ash has more specific messages, a-la: */
5819 /*: (exp_save == ':' ? "parameter null or not set" : "parameter not set")*/
5801 ); 5820 );
5802//TODO: how interactive bash aborts expansion mid-command? 5821//TODO: how interactive bash aborts expansion mid-command?
5803 } else { 5822 } else {
@@ -6643,8 +6662,18 @@ struct squirrel {
6643 /* moved_to = -1: fd was opened by redirect; close orig_fd after redir */ 6662 /* moved_to = -1: fd was opened by redirect; close orig_fd after redir */
6644}; 6663};
6645 6664
6665static struct squirrel *append_squirrel(struct squirrel *sq, int i, int orig, int moved)
6666{
6667 sq = xrealloc(sq, (i + 2) * sizeof(sq[0]));
6668 sq[i].orig_fd = orig;
6669 sq[i].moved_to = moved;
6670 sq[i+1].orig_fd = -1; /* end marker */
6671 return sq;
6672}
6673
6646static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd) 6674static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
6647{ 6675{
6676 int moved_to;
6648 int i = 0; 6677 int i = 0;
6649 6678
6650 if (sq) while (sq[i].orig_fd >= 0) { 6679 if (sq) while (sq[i].orig_fd >= 0) {
@@ -6664,15 +6693,12 @@ static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
6664 i++; 6693 i++;
6665 } 6694 }
6666 6695
6667 sq = xrealloc(sq, (i + 2) * sizeof(sq[0]));
6668 sq[i].orig_fd = fd;
6669 /* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */ 6696 /* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */
6670 sq[i].moved_to = fcntl_F_DUPFD(fd, avoid_fd); 6697 moved_to = fcntl_F_DUPFD(fd, avoid_fd);
6671 debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, sq[i].moved_to); 6698 debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, moved_to);
6672 if (sq[i].moved_to < 0 && errno != EBADF) 6699 if (moved_to < 0 && errno != EBADF)
6673 xfunc_die(); 6700 xfunc_die();
6674 sq[i+1].orig_fd = -1; /* end marker */ 6701 return append_squirrel(sq, i, fd, moved_to);
6675 return sq;
6676} 6702}
6677 6703
6678/* fd: redirect wants this fd to be used (e.g. 3>file). 6704/* fd: redirect wants this fd to be used (e.g. 3>file).
@@ -6778,6 +6804,19 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp)
6778 */ 6804 */
6779 return 1; 6805 return 1;
6780 } 6806 }
6807 if (openfd == redir->rd_fd && sqp) {
6808 /* open() gave us precisely the fd we wanted.
6809 * This means that this fd was not busy
6810 * (not opened to anywhere).
6811 * Remember to close it on restore:
6812 */
6813 struct squirrel *sq = *sqp;
6814 int i = 0;
6815 if (sq) while (sq[i].orig_fd >= 0)
6816 i++;
6817 *sqp = append_squirrel(sq, i, openfd, -1); /* -1 = "it was closed" */
6818 debug_printf_redir("redir to previously closed fd %d\n", openfd);
6819 }
6781 } else { 6820 } else {
6782 /* "rd_fd<*>rd_dup" or "rd_fd<*>-" cases */ 6821 /* "rd_fd<*>rd_dup" or "rd_fd<*>-" cases */
6783 openfd = redir->rd_dup; 6822 openfd = redir->rd_dup;
@@ -9719,7 +9758,7 @@ static int FAST_FUNC builtin_trap(char **argv)
9719 sighandler_t handler; 9758 sighandler_t handler;
9720 9759
9721 sig = get_signum(*argv++); 9760 sig = get_signum(*argv++);
9722 if (sig < 0 || sig >= NSIG) { 9761 if (sig < 0) {
9723 ret = EXIT_FAILURE; 9762 ret = EXIT_FAILURE;
9724 /* Mimic bash message exactly */ 9763 /* Mimic bash message exactly */
9725 bb_error_msg("trap: %s: invalid signal specification", argv[-1]); 9764 bb_error_msg("trap: %s: invalid signal specification", argv[-1]);
diff --git a/shell/hush_test/hush-misc/env_and_func.tests b/shell/hush_test/hush-misc/env_and_func.tests
index 1d4eaf3a7..3efef1a41 100755
--- a/shell/hush_test/hush-misc/env_and_func.tests
+++ b/shell/hush_test/hush-misc/env_and_func.tests
@@ -1,4 +1,8 @@
1var=old 1var=old
2f() { echo "var=$var"; } 2f() { echo "var=$var"; }
3# bash: POSIXLY_CORRECT behavior is to "leak" new variable values
4# out of function invocations (similar to "special builtins" behavior);
5# but in "bash mode", they don't leak.
6# hush does not "leak" values. ash does.
3var=val f 7var=val f
4echo "var=$var" 8echo "var=$var"
diff --git a/shell/hush_test/hush-redir/redir.right b/shell/hush_test/hush-redir/redir.right
new file mode 100644
index 000000000..4de5ec701
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir.right
@@ -0,0 +1,2 @@
1hush: write error: Bad file descriptor
2TEST
diff --git a/shell/hush_test/hush-redir/redir.tests b/shell/hush_test/hush-redir/redir.tests
new file mode 100755
index 000000000..7a1a66806
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir.tests
@@ -0,0 +1,6 @@
1# test: closed fds should stay closed
2exec 1>&-
3echo TEST >TEST
4echo JUNK # lost: stdout is closed
5cat TEST >&2
6rm TEST
diff --git a/shell/hush_test/hush-vars/param_expand_alt.right b/shell/hush_test/hush-vars/param_expand_alt.right
index 67f18d69c..c46786e1f 100644
--- a/shell/hush_test/hush-vars/param_expand_alt.right
+++ b/shell/hush_test/hush-vars/param_expand_alt.right
@@ -1,6 +1,7 @@
1hush: syntax error: unterminated ${name} 1hush: syntax error: unterminated ${name}
2hush: syntax error: unterminated ${name} 2hush: syntax error: unterminated ${name}
3__ __ 3__
4_z_ _z_
4_ _ _ _ _ 5_ _ _ _ _
5_aaaa _ _ _word _word 6_aaaa _ _ _word _word
6_ _ _ _ _ 7_ _ _ _ _
diff --git a/shell/hush_test/hush-vars/param_expand_alt.tests b/shell/hush_test/hush-vars/param_expand_alt.tests
index 3b646b142..23e9a26be 100755
--- a/shell/hush_test/hush-vars/param_expand_alt.tests
+++ b/shell/hush_test/hush-vars/param_expand_alt.tests
@@ -1,9 +1,20 @@
1# first try some invalid patterns (do in subshell due to parsing error) 1# First try some invalid patterns. Do in subshell due to parsing error.
2"$THIS_SH" -c 'echo ${+} ; echo moo' 2# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
3"$THIS_SH" -c 'echo ${:+} ; echo moo' 3"$THIS_SH" -c 'echo ${+} ; echo moo' SHELL
4"$THIS_SH" -c 'echo ${:+} ; echo moo' SHELL
4 5
5# now some funky ones. (bash doesn't accept ${#+}) 6# now some funky ones.
6echo _${#+}_ _${#:+}_ 7# ${V+word} "if V unset, then substitute nothing, else substitute word"
8# ${V:+word} "if V unset or '', then substitute nothing, else substitute word"
9#
10# ${#:+} is a :+ op on $#, but ${#+} (and any other ${#c}) is "length of $c",
11# not + op on $#.
12# bash and dash do not accept ${#+}. it's possible for some shell to skip
13# the check that c is valid and interpret ${#+} as "len of $+". Not testing it.
14# echo _${#+}_
15echo _${#:+}_
16# Forms with non-empty word work as expected in both ash and bash.
17echo _${#+z}_ _${#:+z}_
7 18
8# now some valid ones 19# now some valid ones
9set -- 20set --
diff --git a/shell/hush_test/hush-vars/param_expand_assign.tests b/shell/hush_test/hush-vars/param_expand_assign.tests
index 149cb20df..79de95613 100755
--- a/shell/hush_test/hush-vars/param_expand_assign.tests
+++ b/shell/hush_test/hush-vars/param_expand_assign.tests
@@ -1,22 +1,23 @@
1# first try some invalid patterns (do in subshell due to parsing error) 1# First try some invalid patterns. Do in subshell due to parsing error.
2"$THIS_SH" -c 'echo ${=}' 2# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
3"$THIS_SH" -c 'echo ${:=}' 3"$THIS_SH" -c 'echo ${=}' SHELL
4"$THIS_SH" -c 'echo ${:=}' SHELL
4 5
5# now some funky ones 6# now some funky ones
6"$THIS_SH" -c 'echo ${#=}' 7"$THIS_SH" -c 'echo ${#=}' SHELL
7"$THIS_SH" -c 'echo ${#:=}' 8"$THIS_SH" -c 'echo ${#:=}' SHELL
8 9
9# should error out 10# should error out
10"$THIS_SH" -c 'set --; echo _${1=}' 11"$THIS_SH" -c 'set --; echo _${1=}' SHELL
11"$THIS_SH" -c 'set --; echo _${1:=}' 12"$THIS_SH" -c 'set --; echo _${1:=}' SHELL
12"$THIS_SH" -c 'set --; echo _${1=word}' 13"$THIS_SH" -c 'set --; echo _${1=word}' SHELL
13"$THIS_SH" -c 'set --; echo _${1:=word}' 14"$THIS_SH" -c 'set --; echo _${1:=word}' SHELL
14 15
15# should not error 16# should not error
16"$THIS_SH" -c 'set aa; echo _${1=}' 17"$THIS_SH" -c 'set aa; echo _${1=}' SHELL
17"$THIS_SH" -c 'set aa; echo _${1:=}' 18"$THIS_SH" -c 'set aa; echo _${1:=}' SHELL
18"$THIS_SH" -c 'set aa; echo _${1=word}' 19"$THIS_SH" -c 'set aa; echo _${1=word}' SHELL
19"$THIS_SH" -c 'set aa; echo _${1:=word}' 20"$THIS_SH" -c 'set aa; echo _${1:=word}' SHELL
20 21
21# should work fine 22# should work fine
22unset f; echo _$f 23unset f; echo _$f
diff --git a/shell/hush_test/hush-vars/param_expand_bash_substring.tests b/shell/hush_test/hush-vars/param_expand_bash_substring.tests
index 5c9552dba..cce9f123e 100755
--- a/shell/hush_test/hush-vars/param_expand_bash_substring.tests
+++ b/shell/hush_test/hush-vars/param_expand_bash_substring.tests
@@ -1,13 +1,14 @@
1# first try some invalid patterns 1# first try some invalid patterns
2# do all of these in subshells since it's supposed to error out 2# do all of these in subshells since it's supposed to error out
3# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
3export var=0123456789 4export var=0123456789
4"$THIS_SH" -c 'echo ${:}' 5"$THIS_SH" -c 'echo ${:}' SHELL
5"$THIS_SH" -c 'echo ${::}' 6"$THIS_SH" -c 'echo ${::}' SHELL
6"$THIS_SH" -c 'echo ${:1}' 7"$THIS_SH" -c 'echo ${:1}' SHELL
7"$THIS_SH" -c 'echo ${::1}' 8"$THIS_SH" -c 'echo ${::1}' SHELL
8 9
9#this also is not valid in bash, but we accept it: 10#this also is not valid in bash, hush accepts it:
10"$THIS_SH" -c 'echo ${var:}' 11"$THIS_SH" -c 'echo ${var:}' SHELL
11 12
12# then some funky ones 13# then some funky ones
13# UNFIXED BUG: this should work: "$THIS_SH" -c 'echo ${?:0}' 14# UNFIXED BUG: this should work: "$THIS_SH" -c 'echo ${?:0}'
diff --git a/shell/hush_test/hush-vars/param_expand_default.tests b/shell/hush_test/hush-vars/param_expand_default.tests
index 1ea051748..16e5f8efe 100755
--- a/shell/hush_test/hush-vars/param_expand_default.tests
+++ b/shell/hush_test/hush-vars/param_expand_default.tests
@@ -1,6 +1,8 @@
1# first try some invalid patterns (do in subshell due to parsing error) 1# first try some invalid patterns (do in subshell due to parsing error)
2"$THIS_SH" -c 'echo ${-}' 2# (set argv0 to "SHELL" to avoid "/path/to/shell: blah" in error messages)
3"$THIS_SH" -c 'echo ${:-}' 3# valid in bash and ash (same as $-), not supported in hush (yet?):
4"$THIS_SH" -c 'echo ${-}' SHELL
5"$THIS_SH" -c 'echo ${:-}' SHELL
4 6
5# now some funky ones 7# now some funky ones
6echo _${#-} _${#:-} 8echo _${#-} _${#:-}
diff --git a/shell/hush_test/hush-vars/param_expand_indicate_error.right b/shell/hush_test/hush-vars/param_expand_indicate_error.right
index 06fcc5104..acf293893 100644
--- a/shell/hush_test/hush-vars/param_expand_indicate_error.right
+++ b/shell/hush_test/hush-vars/param_expand_indicate_error.right
@@ -1,5 +1,5 @@
1hush: syntax error: unterminated ${name} 1hush: syntax error: unterminated ${name}
20 21
30 30
4==== 4====
5_ 5_
diff --git a/shell/hush_test/hush-vars/param_expand_indicate_error.tests b/shell/hush_test/hush-vars/param_expand_indicate_error.tests
index be14b1e37..5f946e39a 100755
--- a/shell/hush_test/hush-vars/param_expand_indicate_error.tests
+++ b/shell/hush_test/hush-vars/param_expand_indicate_error.tests
@@ -5,7 +5,7 @@
5"$THIS_SH" -c 'echo ${:?}' 5"$THIS_SH" -c 'echo ${:?}'
6 6
7# then some funky ones 7# then some funky ones
8# note: bash prints 1 - treats it as "length of $#"? We print 0 8# note: bash prints 1 - treats it as "length of $#"
9"$THIS_SH" -c 'echo ${#?}' 9"$THIS_SH" -c 'echo ${#?}'
10# bash prints 0 10# bash prints 0
11"$THIS_SH" -c 'echo ${#:?}' 11"$THIS_SH" -c 'echo ${#:?}'
diff --git a/shell/hush_test/hush-vars/param_expand_len1.right b/shell/hush_test/hush-vars/param_expand_len1.right
new file mode 100644
index 000000000..dff3c7bb1
--- /dev/null
+++ b/shell/hush_test/hush-vars/param_expand_len1.right
@@ -0,0 +1,11 @@
1One:1
2Two:2
3Three:3
4
5One:1
6Two:2
7Three:3
8
9Ok ${#$}: 0
10
11Ok ${#!}: 0
diff --git a/shell/hush_test/hush-vars/param_expand_len1.tests b/shell/hush_test/hush-vars/param_expand_len1.tests
new file mode 100755
index 000000000..e1beab320
--- /dev/null
+++ b/shell/hush_test/hush-vars/param_expand_len1.tests
@@ -0,0 +1,31 @@
1# ${#c} for any single char c means "length of $c", including all special vars
2
3false
4echo One:${#?}
5(exit 10)
6echo Two:${#?}
7(exit 100)
8echo Three:${#?}
9
10echo
11echo One:${##}
12set -- 1 2 3 4 5 6 7 8 9 0
13echo Two:${##}
14set -- 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
15 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
16 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 \
17 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
18echo Three:${##}
19
20echo
21v=$$
22test "${#v}" = "${#$}"
23echo 'Ok ${#$}:' $?
24
25echo
26sleep 0 &
27v=$!
28test "${#v}" = "${#!}"
29echo 'Ok ${#!}:' $?
30
31# TODO: ${#-} ${#_}
diff --git a/shell/hush_test/hush-vars/unset.right b/shell/hush_test/hush-vars/unset.right
index 1fbe76a73..097274201 100644
--- a/shell/hush_test/hush-vars/unset.right
+++ b/shell/hush_test/hush-vars/unset.right
@@ -12,7 +12,7 @@ ___
120 f g 120 f g
130 130
14___ 14___
15hush: HUSH_VERSION: readonly variable 15hush: VAR_RO: readonly variable
161 f g 161 f g
17hush: HUSH_VERSION: readonly variable 17hush: VAR_RO: readonly variable
181 181
diff --git a/shell/hush_test/hush-vars/unset.tests b/shell/hush_test/hush-vars/unset.tests
index f59ce5923..81243fbf9 100755
--- a/shell/hush_test/hush-vars/unset.tests
+++ b/shell/hush_test/hush-vars/unset.tests
@@ -1,4 +1,5 @@
1# check invalid options are rejected 1# check invalid options are rejected
2# bash: in posix mode, aborts if non-interactive
2unset - 3unset -
3echo $? 4echo $?
4unset -m a b c 5unset -m a b c
@@ -30,7 +31,9 @@ echo $? $f $g
30# check read only vars 31# check read only vars
31echo ___ 32echo ___
32f=f g=g 33f=f g=g
33unset HUSH_VERSION 34VAR_RO=1
35readonly VAR_RO
36unset VAR_RO
34echo $? $f $g 37echo $? $f $g
35unset f HUSH_VERSION g 38unset f VAR_RO g
36echo $? $f $g 39echo $? $f $g