diff options
| author | Ron Yorston <rmy@pobox.com> | 2017-07-29 09:55:08 +0100 |
|---|---|---|
| committer | Ron Yorston <rmy@pobox.com> | 2017-07-29 09:55:08 +0100 |
| commit | 86d60bb0ceb277e500a8daabd995bc713bbdadc9 (patch) | |
| tree | 3e439f92d5a3fec2546d526579cc85e98f066e40 /shell | |
| parent | b30c60a9786a1608211a96755996bd6c02951a27 (diff) | |
| parent | 69be994de69d794f038f10a3e7a67519b2006581 (diff) | |
| download | busybox-w32-86d60bb0ceb277e500a8daabd995bc713bbdadc9.tar.gz busybox-w32-86d60bb0ceb277e500a8daabd995bc713bbdadc9.tar.bz2 busybox-w32-86d60bb0ceb277e500a8daabd995bc713bbdadc9.zip | |
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
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 | ||
| 245 | typedef 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 */ | |||
| 1334 | static struct parsefile *g_parsefile = &basepf; /* current input file */ | 1337 | static struct parsefile *g_parsefile = &basepf; /* current input file */ |
| 1335 | static int startlinno; /* line # where last token started */ | 1338 | static int startlinno; /* line # where last token started */ |
| 1336 | static char *commandname; /* currently executing command */ | 1339 | static char *commandname; /* currently executing command */ |
| 1337 | static 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 | |||
| 1394 | static void raise_error_syntax(const char *) NORETURN; | 1408 | static void raise_error_syntax(const char *) NORETURN; |
| 1395 | static void | 1409 | static void |
| 1396 | raise_error_syntax(const char *msg) | 1410 | raise_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 | */ |
| 2315 | static const char * | 2326 | static ALWAYS_INLINE const char * |
| 2316 | bltinlookup(const char *name) | 2327 | bltinlookup(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 | */ |
| 2334 | static void | 2339 | static struct var * |
| 2335 | setvareq(char *s, int flags) | 2340 | setvareq(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 | */ |
| 2382 | static void | 2402 | static struct var * |
| 2383 | setvar(const char *name, const char *val, int flags) | 2403 | setvar(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 | ||
| 2415 | static void FAST_FUNC | 2438 | static 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 | */ |
| 2424 | static int | 2447 | static void |
| 2425 | unsetvar(const char *s) | 2448 | unsetvar(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 | |||
| 4067 | xtcsetpgrp(int fd, pid_t pgrp) | 4057 | xtcsetpgrp(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 | */ | ||
| 5535 | static int | ||
| 5536 | noclobberopen(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) | |||
| 5645 | static int | 5573 | static int |
| 5646 | openredirect(union node *redir) | 5574 | openredirect(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) */ | ||
| 6143 | typedef long arith_t; | ||
| 6144 | # define ARITH_FMT "%ld" | ||
| 6145 | #endif | ||
| 6146 | static int | 6085 | static int |
| 6147 | cvtnum(arith_t num) | 6086 | cvtnum(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 */ | ||
| 6573 | static int evaltree(union node *, int); | 6519 | static 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 | */ | ||
| 6526 | static ALWAYS_INLINE NORETURN void | ||
| 6527 | evaltreenr(union node *n, int flags) | ||
| 6528 | { | ||
| 6529 | evaltree(n, flags); | ||
| 6530 | bb_unreachable(abort()); | ||
| 6531 | /* NOTREACHED */ | ||
| 6532 | } | ||
| 6533 | |||
| 6575 | static void FAST_FUNC | 6534 | static void FAST_FUNC |
| 6576 | evalbackcmd(union node *n, struct backcmd *result) | 6535 | evalbackcmd(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 */ |
| 6753 | static char *evalvar(char *p, int flags, struct strlist *var_str_list); | 6712 | static 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 | */ |
| 6764 | static void | 6719 | static void |
| 6765 | argstr(char *p, int flags, struct strlist *var_str_list) | 6720 | argstr(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 | ||
| 7024 | static const char * | 6979 | static const char * |
| 7025 | subevalvar(char *p, char *varname, int strloc, int subtype, | 6980 | subevalvar(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 | */ |
| 7321 | static NOINLINE ssize_t | 7276 | static NOINLINE ssize_t |
| 7322 | varvalue(char *name, int varflags, int flags, struct strlist *var_str_list, int *quotedp) | 7277 | varvalue(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 | */ |
| 7467 | static char * | 7397 | static char * |
| 7468 | evalvar(char *p, int flag, struct strlist *var_str_list) | 7398 | evalvar(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, "ed); | 7422 | varlen = varvalue(var, varflags, flag, "ed); |
| 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 | ||
| 8146 | static void | 8069 | static void |
| 8147 | tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **envp) | 8070 | tryexec(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; | |||
| 8810 | static char **nodeptr; | 8734 | static 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 | |||
| 8817 | static const uint8_t nodesize[N_NUMBER] ALIGN1 = { | 8737 | static 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) | ||
| 9340 | static | ||
| 9341 | #endif | ||
| 9342 | int evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); | ||
| 9343 | |||
| 9344 | static int | 9259 | static int |
| 9345 | skiploop(void) | 9260 | skiploop(void) |
| 9346 | { | 9261 | { |
| @@ -9699,27 +9614,57 @@ optschanged(void) | |||
| 9699 | #endif | 9614 | #endif |
| 9700 | } | 9615 | } |
| 9701 | 9616 | ||
| 9702 | static struct localvar *localvars; | 9617 | struct localvar_list { |
| 9618 | struct localvar_list *next; | ||
| 9619 | struct localvar *lv; | ||
| 9620 | }; | ||
| 9621 | |||
| 9622 | static 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 | */ |
| 9708 | static void | 9628 | static void |
| 9709 | poplocalvars(void) | 9629 | poplocalvars(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 | */ | ||
| 9685 | static struct localvar_list * | ||
| 9686 | pushlocalvars(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 | |||
| 9700 | static void | ||
| 9701 | unwindlocalvars(struct localvar_list *stop) | ||
| 9702 | { | ||
| 9703 | while (localvar_stack != stop) | ||
| 9704 | poplocalvars(0); | ||
| 9734 | } | 9705 | } |
| 9735 | 9706 | ||
| 9736 | static int | 9707 | static int |
| 9737 | evalfun(struct funcnode *func, int argc, char **argv, int flags) | 9708 | evalfun(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 | ||
| 13021 | static inline int | ||
| 13022 | parser_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 | ||
| 13774 | static const unsigned char timescmd_str[] ALIGN1 = { | 13750 | static 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 | ||
| 2 | 127 | ||
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 | ||
| 2 | a''=b true | ||
| 3 | echo $? | ||
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 @@ | |||
| 1 | Null 0th arg: | ||
| 2 | ./empty_args.tests: line 2: : Permission denied | ||
| 3 | 127 | ||
| 4 | Null 1st arg: | ||
| 5 | 0 | ||
| 6 | Null 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 @@ | |||
| 1 | echo Null 0th arg: | ||
| 2 | "" | ||
| 3 | echo $? | ||
| 4 | echo Null 1st arg: | ||
| 5 | # printf without args would print usage info | ||
| 6 | printf "" | ||
| 7 | echo $? | ||
| 8 | echo Null arg in exec: | ||
| 9 | exec 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 @@ | |||
| 1 | var=val | ||
| 2 | var=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 @@ | |||
| 1 | var=old | ||
| 2 | f() { 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. | ||
| 7 | var=val f | ||
| 8 | echo "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 @@ | |||
| 1 | 0 | ||
| 2 | 0 | ||
| 3 | ./emptytick.tests: line 3: : Permission denied | ||
| 4 | 127 | ||
| 5 | ./emptytick.tests: line 4: : Permission denied | ||
| 6 | 127 | ||
| 7 | 0 | ||
| 8 | 0 | ||
| 9 | 0 | ||
| 10 | 0 | ||
| 11 | ./emptytick.tests: line 10: : Permission denied | ||
| 12 | 127 | ||
| 13 | ./emptytick.tests: line 11: : Permission denied | ||
| 14 | 127 | ||
| 15 | 0 | ||
| 16 | 0 | ||
| 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 @@ | |||
| 1 | true; ``; echo $? | ||
| 2 | false; ``; echo $? | ||
| 3 | true; `""`; echo $? | ||
| 4 | false; `""`; echo $? | ||
| 5 | true; ` `; echo $? | ||
| 6 | false; ` `; echo $? | ||
| 7 | |||
| 8 | true; $(); echo $? | ||
| 9 | false; $(); echo $? | ||
| 10 | true; $(""); echo $? | ||
| 11 | false; $(""); echo $? | ||
| 12 | true; $( ); echo $? | ||
| 13 | false; $( ); echo $? | ||
| 14 | |||
| 15 | exec ''; echo $? | ||
| 16 | echo 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 @@ | |||
| 1 | 1 | ||
| 2 | 1 | ||
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 @@ | |||
| 1 | true | ||
| 2 | false; echo `echo $?` | ||
| 3 | true | ||
| 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 @@ | |||
| 1 | if false; then | ||
| 2 | echo "FOO" | ||
| 3 | tmp=`echo BAR >&2` | ||
| 4 | fi | ||
| 5 | echo 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 | ||
| 3 | Q | ||
| 4 | a\bc | ||
| 5 | 11-$a-\t-\-\"-`-\--\z-\*-\?-22 33-$a-\t-\-"-`-\--\z-\*-\?-44 | ||
| 6 | done: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 @@ | |||
| 1 | test "$CONFIG_FEATURE_FANCY_ECHO" = "y" || exit 77 | ||
| 2 | |||
| 3 | TEST=Q | ||
| 4 | # \` is special | ||
| 5 | echo `echo '\'TEST\`echo ZZ\`BEST` | ||
| 6 | # \$ and \\ are special | ||
| 7 | echo `echo \\$TEST` | ||
| 8 | echo `echo \$TEST` | ||
| 9 | echo a`echo \\\\b`c | ||
| 10 | |||
| 11 | # \" is not special if in unquoted `cmd` (passed verbatim WITH \), | ||
| 12 | # but is special in quoted one | ||
| 13 | echo `echo 11'-$a-\t-\\-\"-\`-\--\z-\*-\?-'22` "`echo 33'-$a-\t-\\-\"-\`-\--\z-\*-\?-'44`" | ||
| 14 | echo 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 | ||
| 2 | TEST) BEST | ||
| 3 | ((TEST) BEST | ||
| 4 | ) | ||
| 5 | abc | ||
| 6 | a)c | ||
| 7 | OK: 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 @@ | |||
| 1 | echo $(echo '(TEST)' BEST) | ||
| 2 | echo $(echo 'TEST)' BEST) | ||
| 3 | echo $(echo \(\(TEST\) BEST) | ||
| 4 | echo $(echo \)) | ||
| 5 | echo $(echo a"`echo "b"`"c ) | ||
| 6 | echo $(echo a"`echo ")"`"c ) | ||
| 7 | echo 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 @@ | |||
| 1 | 546ed3f5c81c780d3ab86ada14824237 - | ||
| 2 | 546ed3f5c81c780d3ab86ada14824237 - | ||
| 3 | End | ||
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 | ||
| 2 | yes "123456789 123456789 123456789 123456789" | head -3000 >>"$0.tmp" | ||
| 3 | |||
| 4 | echo "`cat $0.tmp`" | md5sum | ||
| 5 | rm "$0.tmp" | ||
| 6 | yes "123456789 123456789 123456789 123456789" | head -3000 | md5sum | ||
| 7 | echo 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 @@ | |||
| 1 | sending USR2 | ||
| 2 | caught | ||
| 3 | sending USR2 | ||
| 4 | sending USR2 | ||
| 5 | User 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 | ||
| 2 | trap ":" USR1 USR2 | ||
| 3 | |||
| 4 | "$THIS_SH" -c ' | ||
| 5 | trap "echo caught" USR2 | ||
| 6 | echo "sending USR2" | ||
| 7 | kill -USR2 $$ | ||
| 8 | |||
| 9 | trap "" USR2 | ||
| 10 | echo "sending USR2" | ||
| 11 | kill -USR2 $$ | ||
| 12 | |||
| 13 | trap "-" USR2 | ||
| 14 | echo "sending USR2" | ||
| 15 | kill -USR2 $$ | ||
| 16 | |||
| 17 | echo "not reached" | ||
| 18 | ' | ||
| 19 | |||
| 20 | trap "-" 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 @@ | |||
| 1 | Hangup | ||
| 2 | Done: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 $$) & | ||
| 3 | while true; do | ||
| 4 | read ignored | ||
| 5 | done | ||
| 6 | ' | ||
| 7 | echo "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 @@ | |||
| 1 | trap -- '' HUP | ||
| 2 | trap -- '' QUIT | ||
| 3 | trap -- '' SYS | ||
| 4 | Ok | ||
| 5 | trap -- '' HUP | ||
| 6 | trap -- '' QUIT | ||
| 7 | trap -- '' SYS | ||
| 8 | Ok | ||
| 9 | trap -- '' HUP | ||
| 10 | trap -- '' QUIT | ||
| 11 | trap -- '' SYS | ||
| 12 | Ok | ||
| 13 | trap -- '' HUP | ||
| 14 | trap -- '' QUIT | ||
| 15 | trap -- '' SYS | ||
| 16 | Ok | ||
| 17 | trap -- '' HUP | ||
| 18 | trap -- '' QUIT | ||
| 19 | trap -- '' SYS | ||
| 20 | Terminated | ||
| 21 | Done | ||
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 | ||
| 4 | trap '' HUP | ||
| 5 | # QUIT is always special | ||
| 6 | trap '' QUIT | ||
| 7 | # SYS is not special | ||
| 8 | trap '' SYS | ||
| 9 | # WINCH is harmless | ||
| 10 | trap 'bad: caught WINCH' WINCH | ||
| 11 | # With TERM we'll check whether it is reset | ||
| 12 | trap '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) | ||
| 19 | echo 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 @@ | |||
| 1 | SHELL: line 1: syntax error: bad substitution | ||
| 2 | SHELL: 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 _${#+}_ | ||
| 15 | echo _${#:+}_ | ||
| 16 | # Forms with non-empty word work as expected in both ash and bash. | ||
| 17 | echo _${#+z}_ _${#:+z}_ | ||
| 18 | |||
| 19 | # now some valid ones | ||
| 20 | set -- | ||
| 21 | echo _$1 _${1+} _${1:+} _${1+word} _${1:+word} | ||
| 22 | |||
| 23 | set -- aaaa | ||
| 24 | echo _$1 _${1+} _${1:+} _${1+word} _${1:+word} | ||
| 25 | |||
| 26 | unset f | ||
| 27 | echo _$f _${f+} _${f:+} _${f+word} _${f:+word} | ||
| 28 | |||
| 29 | f= | ||
| 30 | echo _$f _${f+} _${f:+} _${f+word} _${f:+word} | ||
| 31 | |||
| 32 | f=fff | ||
| 33 | echo _$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 @@ | |||
| 1 | SHELL: line 1: syntax error: bad substitution | ||
| 2 | SHELL: line 1: syntax error: bad substitution | ||
| 3 | 0 | ||
| 4 | 0 | ||
| 5 | SHELL: line 1: 1: bad variable name | ||
| 6 | SHELL: line 1: 1: bad variable name | ||
| 7 | SHELL: line 1: 1: bad variable name | ||
| 8 | SHELL: 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 | ||
| 23 | unset f; echo _$f | ||
| 24 | unset f; echo _${f=} | ||
| 25 | unset f; echo _${f:=} | ||
| 26 | unset f; echo _${f=word} | ||
| 27 | unset f; echo _${f:=word} | ||
| 28 | |||
| 29 | f=; echo _$f | ||
| 30 | f=; echo _${f=} | ||
| 31 | f=; echo _${f:=} | ||
| 32 | f=; echo _${f=word} | ||
| 33 | f=; echo _${f:=word} | ||
| 34 | |||
| 35 | f=fff; echo _$f | ||
| 36 | f=fff; echo _${f=} | ||
| 37 | f=fff; echo _${f:=} | ||
| 38 | f=fff; echo _${f=word} | ||
| 39 | f=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 @@ | |||
| 1 | SHELL: line 1: syntax error: bad substitution | ||
| 2 | SHELL: line 1: syntax error: bad substitution | ||
| 3 | SHELL: line 1: syntax error: bad substitution | ||
| 4 | SHELL: line 1: syntax error: bad substitution | ||
| 5 | SHELL: line 1: syntax error: missing '}' | ||
| 6 | 1 =|| | ||
| 7 | 1:1 =|| | ||
| 8 | 1:1:2=|| | ||
| 9 | 1::2 =|| | ||
| 10 | 1:1: =|| | ||
| 11 | 1:: =|| | ||
| 12 | 1 =|0123| | ||
| 13 | 1:1 =|123| | ||
| 14 | 1:1:2=|12| | ||
| 15 | 1::2 =|01| | ||
| 16 | 1:1: =|| | ||
| 17 | 1:: =|| | ||
| 18 | f =|| | ||
| 19 | f:1 =|| | ||
| 20 | f:1:2=|| | ||
| 21 | f::2 =|| | ||
| 22 | f:1: =|| | ||
| 23 | f:: =|| | ||
| 24 | f =|| | ||
| 25 | f:1 =|| | ||
| 26 | f:1:2=|| | ||
| 27 | f::2 =|| | ||
| 28 | f:1: =|| | ||
| 29 | f:: =|| | ||
| 30 | f =|a| | ||
| 31 | f:1 =|| | ||
| 32 | f:1:2=|| | ||
| 33 | f::2 =|a| | ||
| 34 | f:1: =|| | ||
| 35 | f:: =|| | ||
| 36 | f =|0123456789| | ||
| 37 | f:1 =|123456789| | ||
| 38 | f:1:2=|12| | ||
| 39 | f::2 =|01| | ||
| 40 | f:1: =|| | ||
| 41 | f:: =|| | ||
| 42 | Substrings 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 | #:: =|| | ||
| 55 | Substrings with expressions | ||
| 56 | f =|01234567| | ||
| 57 | f:1+1:2+2 =|2345| | ||
| 58 | f:-1:2+2 =|01234567| | ||
| 59 | f:1:f =|1234567| | ||
| 60 | f:1:$f =|1234567| | ||
| 61 | f:1:${f} =|1234567| | ||
| 62 | f:1:${f:3:1} =|123| | ||
| 63 | f:1:1`echo 1`=|1| | ||
| 64 | Done | ||
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) | ||
| 4 | export 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 | ||
| 17 | set --; echo "1 =|${1}|" | ||
| 18 | set --; echo "1:1 =|${1:1}|" | ||
| 19 | set --; echo "1:1:2=|${1:1:2}|" | ||
| 20 | set --; echo "1::2 =|${1::2}|" | ||
| 21 | set --; echo "1:1: =|${1:1:}|" | ||
| 22 | set --; echo "1:: =|${1::}|" | ||
| 23 | |||
| 24 | set -- 0123; echo "1 =|${1}|" | ||
| 25 | set -- 0123; echo "1:1 =|${1:1}|" | ||
| 26 | set -- 0123; echo "1:1:2=|${1:1:2}|" | ||
| 27 | set -- 0123; echo "1::2 =|${1::2}|" | ||
| 28 | set -- 0123; echo "1:1: =|${1:1:}|" | ||
| 29 | set -- 0123; echo "1:: =|${1::}|" | ||
| 30 | |||
| 31 | unset f; echo "f =|$f|" | ||
| 32 | unset f; echo "f:1 =|${f:1}|" | ||
| 33 | unset f; echo "f:1:2=|${f:1:2}|" | ||
| 34 | unset f; echo "f::2 =|${f::2}|" | ||
| 35 | unset f; echo "f:1: =|${f:1:}|" | ||
| 36 | unset f; echo "f:: =|${f::}|" | ||
| 37 | |||
| 38 | f=; echo "f =|$f|" | ||
| 39 | f=; echo "f:1 =|${f:1}|" | ||
| 40 | f=; echo "f:1:2=|${f:1:2}|" | ||
| 41 | f=; echo "f::2 =|${f::2}|" | ||
| 42 | f=; echo "f:1: =|${f:1:}|" | ||
| 43 | f=; echo "f:: =|${f::}|" | ||
| 44 | |||
| 45 | f=a; echo "f =|$f|" | ||
| 46 | f=a; echo "f:1 =|${f:1}|" | ||
| 47 | f=a; echo "f:1:2=|${f:1:2}|" | ||
| 48 | f=a; echo "f::2 =|${f::2}|" | ||
| 49 | f=a; echo "f:1: =|${f:1:}|" | ||
| 50 | f=a; echo "f:: =|${f::}|" | ||
| 51 | |||
| 52 | f=0123456789; echo "f =|$f|" | ||
| 53 | f=0123456789; echo "f:1 =|${f:1}|" | ||
| 54 | f=0123456789; echo "f:1:2=|${f:1:2}|" | ||
| 55 | f=0123456789; echo "f::2 =|${f::2}|" | ||
| 56 | f=0123456789; echo "f:1: =|${f:1:}|" | ||
| 57 | f=0123456789; echo "f:: =|${f::}|" | ||
| 58 | |||
| 59 | echo "Substrings from special vars" | ||
| 60 | echo '? '"=|$?|" | ||
| 61 | echo '?:1 '"=|${?:1}|" | ||
| 62 | echo '?:1:2'"=|${?:1:2}|" | ||
| 63 | echo '?::2 '"=|${?::2}|" | ||
| 64 | echo '?:1: '"=|${?:1:}|" | ||
| 65 | echo '?:: '"=|${?::}|" | ||
| 66 | set -- 1 2 3 4 5 6 7 8 9 10 11 | ||
| 67 | echo '# '"=|$#|" | ||
| 68 | echo '#:1 '"=|${#:1}|" | ||
| 69 | echo '#:1:2'"=|${#:1:2}|" | ||
| 70 | echo '#::2 '"=|${#::2}|" | ||
| 71 | echo '#:1: '"=|${#:1:}|" | ||
| 72 | echo '#:: '"=|${#::}|" | ||
| 73 | |||
| 74 | echo "Substrings with expressions" | ||
| 75 | f=01234567; echo 'f '"=|$f|" | ||
| 76 | f=01234567; echo 'f:1+1:2+2 '"=|${f:1+1:2+2}|" | ||
| 77 | f=01234567; echo 'f:-1:2+2 '"=|${f:-1:2+2}|" | ||
| 78 | f=01234567; echo 'f:1:f '"=|${f:1:f}|" | ||
| 79 | f=01234567; echo 'f:1:$f '"=|${f:1:$f}|" | ||
| 80 | f=01234567; echo 'f:1:${f} '"=|${f:1:${f}}|" | ||
| 81 | f=01234567; echo 'f:1:${f:3:1} '"=|${f:1:${f:3:1}}|" | ||
| 82 | f=01234567; echo 'f:1:1`echo 1`'"=|${f:1:`echo 1`}|" | ||
| 83 | |||
| 84 | echo 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 @@ | |||
| 1 | SHELL: 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 | ||
| 7 | echo _${#-} _${#:-} | ||
| 8 | |||
| 9 | # now some valid ones | ||
| 10 | set -- | ||
| 11 | echo _$1 _${1-} _${1:-} _${1-word} _${1:-word} | ||
| 12 | |||
| 13 | set -- aaaa | ||
| 14 | echo _$1 _${1-} _${1:-} _${1-word} _${1:-word} | ||
| 15 | |||
| 16 | unset f | ||
| 17 | echo _$f _${f-} _${f:-} _${f-word} _${f:-word} | ||
| 18 | |||
| 19 | f= | ||
| 20 | echo _$f _${f-} _${f:-} _${f-word} _${f:-word} | ||
| 21 | |||
| 22 | f=fff | ||
| 23 | echo _$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 @@ | |||
| 1 | SHELL: line 1: syntax error: bad substitution | ||
| 2 | 1 | ||
| 3 | 0 | ||
| 4 | ==== | ||
| 5 | _ | ||
| 6 | SHELL: line 1: 1: parameter not set | ||
| 7 | SHELL: line 1: 1: parameter not set or null | ||
| 8 | SHELL: line 1: 1: message1 | ||
| 9 | SHELL: line 1: 1: message1 | ||
| 10 | SHELL: line 1: 1: unset! | ||
| 11 | SHELL: line 1: 1: null or unset! | ||
| 12 | ==== | ||
| 13 | _aaaa | ||
| 14 | _aaaa | ||
| 15 | _aaaa | ||
| 16 | _aaaa | ||
| 17 | _aaaa | ||
| 18 | _aaaa | ||
| 19 | _aaaa | ||
| 20 | ==== | ||
| 21 | _ | ||
| 22 | SHELL: line 1: f: parameter not set | ||
| 23 | SHELL: line 1: f: parameter not set or null | ||
| 24 | SHELL: line 1: f: message3 | ||
| 25 | SHELL: line 1: f: message3 | ||
| 26 | SHELL: line 1: f: unset! | ||
| 27 | SHELL: line 1: f: null or unset! | ||
| 28 | ==== | ||
| 29 | _ | ||
| 30 | _ | ||
| 31 | SHELL: line 1: f: parameter not set or null | ||
| 32 | _ | ||
| 33 | SHELL: line 1: f: message4 | ||
| 34 | _ | ||
| 35 | SHELL: 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 | ||
| 15 | export msg_unset="unset!" | ||
| 16 | export msg_null_or_unset="null or unset!" | ||
| 17 | |||
| 18 | echo ==== | ||
| 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 | |||
| 27 | echo ==== | ||
| 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 | |||
| 36 | echo ==== | ||
| 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 | |||
| 45 | echo ==== | ||
| 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 | |||
| 54 | echo ==== | ||
| 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 @@ | |||
| 1 | One:1 | ||
| 2 | Two:2 | ||
| 3 | Three:3 | ||
| 4 | |||
| 5 | One:1 | ||
| 6 | Two:2 | ||
| 7 | Three:3 | ||
| 8 | |||
| 9 | Ok ${#$}: 0 | ||
| 10 | |||
| 11 | Ok ${#!}: 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 | |||
| 3 | false | ||
| 4 | echo One:${#?} | ||
| 5 | (exit 10) | ||
| 6 | echo Two:${#?} | ||
| 7 | (exit 100) | ||
| 8 | echo Three:${#?} | ||
| 9 | |||
| 10 | echo | ||
| 11 | echo One:${##} | ||
| 12 | set -- 1 2 3 4 5 6 7 8 9 0 | ||
| 13 | echo Two:${##} | ||
| 14 | set -- 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 | ||
| 18 | echo Three:${##} | ||
| 19 | |||
| 20 | echo | ||
| 21 | v=$$ | ||
| 22 | test "${#v}" = "${#$}" | ||
| 23 | echo 'Ok ${#$}:' $? | ||
| 24 | |||
| 25 | echo | ||
| 26 | sleep 0 & | ||
| 27 | v=$! | ||
| 28 | test "${#v}" = "${#!}" | ||
| 29 | echo '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 |
| 11 | Fail:2 | 11 | Fail:2 |
| 12 | 12 | ||
| 13 | Fail: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 | ||
| 2 | 2 | ||
| 3 | ./unset.tests: unset: line 5: illegal option -m | ||
| 4 | 2 | ||
| 5 | 0 | ||
| 6 | ___ | ||
| 7 | 0 f g | ||
| 8 | 0 g | ||
| 9 | 0 | ||
| 10 | ___ | ||
| 11 | 0 f g | ||
| 12 | 0 | ||
| 13 | 0 f g | ||
| 14 | 0 | ||
| 15 | ___ | ||
| 16 | ./unset.tests: unset: line 36: VAR_RO: is read only | ||
| 17 | 2 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 -) | ||
| 4 | echo $? | ||
| 5 | (unset -m a b c) | ||
| 6 | echo $? | ||
| 7 | |||
| 8 | # check funky usage | ||
| 9 | unset | ||
| 10 | echo $? | ||
| 11 | |||
| 12 | # check normal usage | ||
| 13 | echo ___ | ||
| 14 | f=f g=g | ||
| 15 | echo $? $f $g | ||
| 16 | unset f | ||
| 17 | echo $? $f $g | ||
| 18 | unset g | ||
| 19 | echo $? $f $g | ||
| 20 | |||
| 21 | echo ___ | ||
| 22 | f=f g=g | ||
| 23 | echo $? $f $g | ||
| 24 | unset f g | ||
| 25 | echo $? $f $g | ||
| 26 | f=f g=g | ||
| 27 | echo $? $f $g | ||
| 28 | unset -v f g | ||
| 29 | echo $? $f $g | ||
| 30 | |||
| 31 | # check read only vars | ||
| 32 | echo ___ | ||
| 33 | f=f g=g | ||
| 34 | VAR_RO=1 | ||
| 35 | readonly VAR_RO | ||
| 36 | (unset VAR_RO) | ||
| 37 | echo $? $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 | ||
| 6665 | static 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 | |||
| 6646 | static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd) | 6674 | static 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 @@ | |||
| 1 | var=old | 1 | var=old |
| 2 | f() { echo "var=$var"; } | 2 | f() { 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. | ||
| 3 | var=val f | 7 | var=val f |
| 4 | echo "var=$var" | 8 | echo "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 @@ | |||
| 1 | hush: write error: Bad file descriptor | ||
| 2 | TEST | ||
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 | ||
| 2 | exec 1>&- | ||
| 3 | echo TEST >TEST | ||
| 4 | echo JUNK # lost: stdout is closed | ||
| 5 | cat TEST >&2 | ||
| 6 | rm 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 @@ | |||
| 1 | hush: syntax error: unterminated ${name} | 1 | hush: syntax error: unterminated ${name} |
| 2 | hush: syntax error: unterminated ${name} | 2 | hush: 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. |
| 6 | echo _${#+}_ _${#:+}_ | 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 _${#+}_ | ||
| 15 | echo _${#:+}_ | ||
| 16 | # Forms with non-empty word work as expected in both ash and bash. | ||
| 17 | echo _${#+z}_ _${#:+z}_ | ||
| 7 | 18 | ||
| 8 | # now some valid ones | 19 | # now some valid ones |
| 9 | set -- | 20 | set -- |
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 |
| 22 | unset f; echo _$f | 23 | unset 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) | ||
| 3 | export var=0123456789 | 4 | export 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 |
| 6 | echo _${#-} _${#:-} | 8 | echo _${#-} _${#:-} |
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 @@ | |||
| 1 | hush: syntax error: unterminated ${name} | 1 | hush: syntax error: unterminated ${name} |
| 2 | 0 | 2 | 1 |
| 3 | 0 | 3 | 0 |
| 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 @@ | |||
| 1 | One:1 | ||
| 2 | Two:2 | ||
| 3 | Three:3 | ||
| 4 | |||
| 5 | One:1 | ||
| 6 | Two:2 | ||
| 7 | Three:3 | ||
| 8 | |||
| 9 | Ok ${#$}: 0 | ||
| 10 | |||
| 11 | Ok ${#!}: 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 | |||
| 3 | false | ||
| 4 | echo One:${#?} | ||
| 5 | (exit 10) | ||
| 6 | echo Two:${#?} | ||
| 7 | (exit 100) | ||
| 8 | echo Three:${#?} | ||
| 9 | |||
| 10 | echo | ||
| 11 | echo One:${##} | ||
| 12 | set -- 1 2 3 4 5 6 7 8 9 0 | ||
| 13 | echo Two:${##} | ||
| 14 | set -- 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 | ||
| 18 | echo Three:${##} | ||
| 19 | |||
| 20 | echo | ||
| 21 | v=$$ | ||
| 22 | test "${#v}" = "${#$}" | ||
| 23 | echo 'Ok ${#$}:' $? | ||
| 24 | |||
| 25 | echo | ||
| 26 | sleep 0 & | ||
| 27 | v=$! | ||
| 28 | test "${#v}" = "${#!}" | ||
| 29 | echo '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 @@ ___ | |||
| 12 | 0 f g | 12 | 0 f g |
| 13 | 0 | 13 | 0 |
| 14 | ___ | 14 | ___ |
| 15 | hush: HUSH_VERSION: readonly variable | 15 | hush: VAR_RO: readonly variable |
| 16 | 1 f g | 16 | 1 f g |
| 17 | hush: HUSH_VERSION: readonly variable | 17 | hush: VAR_RO: readonly variable |
| 18 | 1 | 18 | 1 |
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 | ||
| 2 | unset - | 3 | unset - |
| 3 | echo $? | 4 | echo $? |
| 4 | unset -m a b c | 5 | unset -m a b c |
| @@ -30,7 +31,9 @@ echo $? $f $g | |||
| 30 | # check read only vars | 31 | # check read only vars |
| 31 | echo ___ | 32 | echo ___ |
| 32 | f=f g=g | 33 | f=f g=g |
| 33 | unset HUSH_VERSION | 34 | VAR_RO=1 |
| 35 | readonly VAR_RO | ||
| 36 | unset VAR_RO | ||
| 34 | echo $? $f $g | 37 | echo $? $f $g |
| 35 | unset f HUSH_VERSION g | 38 | unset f VAR_RO g |
| 36 | echo $? $f $g | 39 | echo $? $f $g |
