summaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
Diffstat (limited to 'shell')
-rw-r--r--shell/ash.c189
-rw-r--r--shell/ash_test/ash-read/read_ifs.right7
-rwxr-xr-xshell/ash_test/ash-read/read_ifs.tests7
-rw-r--r--shell/hush.c95
-rw-r--r--shell/hush_test/hush-parsing/starquoted2.right1
-rwxr-xr-xshell/hush_test/hush-parsing/starquoted2.tests3
6 files changed, 230 insertions, 72 deletions
diff --git a/shell/ash.c b/shell/ash.c
index d6fd38849..e3f79fcd5 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -30,7 +30,7 @@
30 */ 30 */
31 31
32/* 32/*
33 * The follow should be set to reflect the type of system you have: 33 * The following should be set to reflect the type of system you have:
34 * JOBS -> 1 if you have Berkeley job control, 0 otherwise. 34 * JOBS -> 1 if you have Berkeley job control, 0 otherwise.
35 * define SYSV if you are running under System V. 35 * define SYSV if you are running under System V.
36 * define DEBUG=1 to compile in debugging ('set -o debug' to turn on) 36 * define DEBUG=1 to compile in debugging ('set -o debug' to turn on)
@@ -40,6 +40,11 @@
40 * a quit signal will generate a core dump. 40 * a quit signal will generate a core dump.
41 */ 41 */
42#define DEBUG 0 42#define DEBUG 0
43/* Tweak debug output verbosity here */
44#define DEBUG_TIME 0
45#define DEBUG_PID 1
46#define DEBUG_SIG 1
47
43#define PROFILE 0 48#define PROFILE 0
44 49
45#define IFS_BROKEN 50#define IFS_BROKEN
@@ -47,9 +52,9 @@
47#define JOBS ENABLE_ASH_JOB_CONTROL 52#define JOBS ENABLE_ASH_JOB_CONTROL
48 53
49#if DEBUG 54#if DEBUG
50#ifndef _GNU_SOURCE 55# ifndef _GNU_SOURCE
51#define _GNU_SOURCE 56# define _GNU_SOURCE
52#endif 57# endif
53#endif 58#endif
54 59
55#include "busybox.h" /* for applet_names */ 60#include "busybox.h" /* for applet_names */
@@ -57,15 +62,15 @@
57#include <setjmp.h> 62#include <setjmp.h>
58#include <fnmatch.h> 63#include <fnmatch.h>
59#if JOBS || ENABLE_ASH_READ_NCHARS 64#if JOBS || ENABLE_ASH_READ_NCHARS
60#include <termios.h> 65# include <termios.h>
61#endif 66#endif
62 67
63#ifndef PIPE_BUF 68#ifndef PIPE_BUF
64#define PIPE_BUF 4096 /* amount of buffering in a pipe */ 69# define PIPE_BUF 4096 /* amount of buffering in a pipe */
65#endif 70#endif
66 71
67#if defined(__uClinux__) 72#if defined(__uClinux__)
68#error "Do not even bother, ash will not run on uClinux" 73# error "Do not even bother, ash will not run on uClinux"
69#endif 74#endif
70 75
71 76
@@ -76,14 +81,6 @@
76#define CMDTABLESIZE 31 /* should be prime */ 81#define CMDTABLESIZE 31 /* should be prime */
77 82
78 83
79/* ============ Misc helpers */
80
81#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0)
82
83/* C99 say: "char" declaration may be signed or unsigned default */
84#define signed_char2int(sc) ((int)((signed char)sc))
85
86
87/* ============ Shell options */ 84/* ============ Shell options */
88 85
89static const char *const optletters_optnames[] = { 86static const char *const optletters_optnames[] = {
@@ -245,7 +242,30 @@ extern struct globals_misc *const ash_ptr_to_globals_misc;
245} while (0) 242} while (0)
246 243
247 244
245/* ============ DEBUG */
246#if DEBUG
247static void trace_printf(const char *fmt, ...);
248static void trace_vprintf(const char *fmt, va_list va);
249# define TRACE(param) trace_printf param
250# define TRACEV(param) trace_vprintf param
251# define close(f) do { \
252 int dfd = (f); \
253 if (close(dfd) < 0) \
254 bb_error_msg("bug on %d: closing %d(%x)", \
255 __LINE__, dfd, dfd); \
256} while (0)
257#else
258# define TRACE(param)
259# define TRACEV(param)
260#endif
261
262
248/* ============ Utility functions */ 263/* ============ Utility functions */
264#define xbarrier() do { __asm__ __volatile__ ("": : :"memory"); } while (0)
265
266/* C99 say: "char" declaration may be signed or unsigned by default */
267#define signed_char2int(sc) ((int)(signed char)(sc))
268
249static int isdigit_str9(const char *str) 269static int isdigit_str9(const char *str)
250{ 270{
251 int maxlen = 9 + 1; /* max 9 digits: 999999999 */ 271 int maxlen = 9 + 1; /* max 9 digits: 999999999 */
@@ -284,6 +304,12 @@ raise_exception(int e)
284 exception = e; 304 exception = e;
285 longjmp(exception_handler->loc, 1); 305 longjmp(exception_handler->loc, 1);
286} 306}
307#if DEBUG
308#define raise_exception(e) do { \
309 TRACE(("raising exception %d on line %d\n", (e), __LINE__)); \
310 raise_exception(e); \
311} while (0)
312#endif
287 313
288/* 314/*
289 * Called from trap.c when a SIGINT is received. (If the user specifies 315 * Called from trap.c when a SIGINT is received. (If the user specifies
@@ -316,6 +342,12 @@ raise_interrupt(void)
316 raise_exception(i); 342 raise_exception(i);
317 /* NOTREACHED */ 343 /* NOTREACHED */
318} 344}
345#if DEBUG
346#define raise_interrupt() do { \
347 TRACE(("raising interrupt on line %d\n", __LINE__)); \
348 raise_interrupt(); \
349} while (0)
350#endif
319 351
320#if ENABLE_ASH_OPTIMIZE_FOR_SIZE 352#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
321static void 353static void
@@ -334,7 +366,9 @@ force_int_on(void)
334 raise_interrupt(); 366 raise_interrupt();
335} 367}
336#define FORCE_INT_ON force_int_on() 368#define FORCE_INT_ON force_int_on()
337#else 369
370#else /* !ASH_OPTIMIZE_FOR_SIZE */
371
338#define INT_ON do { \ 372#define INT_ON do { \
339 xbarrier(); \ 373 xbarrier(); \
340 if (--suppressint == 0 && intpending) \ 374 if (--suppressint == 0 && intpending) \
@@ -346,7 +380,7 @@ force_int_on(void)
346 if (intpending) \ 380 if (intpending) \
347 raise_interrupt(); \ 381 raise_interrupt(); \
348} while (0) 382} while (0)
349#endif /* ASH_OPTIMIZE_FOR_SIZE */ 383#endif /* !ASH_OPTIMIZE_FOR_SIZE */
350 384
351#define SAVE_INT(v) ((v) = suppressint) 385#define SAVE_INT(v) ((v) = suppressint)
352 386
@@ -376,7 +410,6 @@ static void
376onsig(int signo) 410onsig(int signo)
377{ 411{
378 gotsig[signo - 1] = 1; 412 gotsig[signo - 1] = 1;
379 pendingsig = signo;
380 413
381 if (/* exsig || */ (signo == SIGINT && !trap[SIGINT])) { 414 if (/* exsig || */ (signo == SIGINT && !trap[SIGINT])) {
382 if (!suppressint) { 415 if (!suppressint) {
@@ -384,6 +417,8 @@ onsig(int signo)
384 raise_interrupt(); /* does not return */ 417 raise_interrupt(); /* does not return */
385 } 418 }
386 intpending = 1; 419 intpending = 1;
420 } else {
421 pendingsig = signo;
387 } 422 }
388} 423}
389 424
@@ -684,6 +719,12 @@ trace_printf(const char *fmt, ...)
684 719
685 if (debug != 1) 720 if (debug != 1)
686 return; 721 return;
722 if (DEBUG_TIME)
723 fprintf(tracefile, "%u ", (int) time(NULL));
724 if (DEBUG_PID)
725 fprintf(tracefile, "[%u] ", (int) getpid());
726 if (DEBUG_SIG)
727 fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pendingsig, intpending, suppressint);
687 va_start(va, fmt); 728 va_start(va, fmt);
688 vfprintf(tracefile, fmt, va); 729 vfprintf(tracefile, fmt, va);
689 va_end(va); 730 va_end(va);
@@ -694,6 +735,12 @@ trace_vprintf(const char *fmt, va_list va)
694{ 735{
695 if (debug != 1) 736 if (debug != 1)
696 return; 737 return;
738 if (DEBUG_TIME)
739 fprintf(tracefile, "%u ", (int) time(NULL));
740 if (DEBUG_PID)
741 fprintf(tracefile, "[%u] ", (int) getpid());
742 if (DEBUG_SIG)
743 fprintf(tracefile, "pending s:%d i:%d(supp:%d) ", pendingsig, intpending, suppressint);
697 vfprintf(tracefile, fmt, va); 744 vfprintf(tracefile, fmt, va);
698} 745}
699 746
@@ -998,14 +1045,6 @@ showtree(union node *n)
998 shtree(n, 1, NULL, stdout); 1045 shtree(n, 1, NULL, stdout);
999} 1046}
1000 1047
1001#define TRACE(param) trace_printf param
1002#define TRACEV(param) trace_vprintf param
1003
1004#else
1005
1006#define TRACE(param)
1007#define TRACEV(param)
1008
1009#endif /* DEBUG */ 1048#endif /* DEBUG */
1010 1049
1011 1050
@@ -3779,7 +3818,7 @@ dowait(int wait_flags, struct job *job)
3779 * NB: _not_ safe_waitpid, we need to detect EINTR */ 3818 * NB: _not_ safe_waitpid, we need to detect EINTR */
3780 pid = waitpid(-1, &status, 3819 pid = waitpid(-1, &status,
3781 (doing_jobctl ? (wait_flags | WUNTRACED) : wait_flags)); 3820 (doing_jobctl ? (wait_flags | WUNTRACED) : wait_flags));
3782 TRACE(("wait returns pid=%d, status=0x%x\n", pid, status)); 3821 TRACE(("wait returns pid=%d, status=0x%x, errno=%d(%s)\n", pid, status, errno, strerror(errno)));
3783 3822
3784 if (pid <= 0) { 3823 if (pid <= 0) {
3785 /* If we were doing blocking wait and (probably) got EINTR, 3824 /* If we were doing blocking wait and (probably) got EINTR,
@@ -5031,7 +5070,9 @@ redirect(union node *redir, int flags)
5031 if (newfd < 0) { 5070 if (newfd < 0) {
5032 /* NTOFD/NFROMFD: copy redir->ndup.dupfd to fd */ 5071 /* NTOFD/NFROMFD: copy redir->ndup.dupfd to fd */
5033 if (redir->ndup.dupfd < 0) { /* "fd>&-" */ 5072 if (redir->ndup.dupfd < 0) { /* "fd>&-" */
5034 close(fd); 5073 /* Don't want to trigger debugging */
5074 if (fd != -1)
5075 close(fd);
5035 } else { 5076 } else {
5036 copyfd(redir->ndup.dupfd, fd | COPYFD_EXACT); 5077 copyfd(redir->ndup.dupfd, fd | COPYFD_EXACT);
5037 } 5078 }
@@ -5084,7 +5125,7 @@ popredir(int drop, int restore)
5084 /*close(fd);*/ 5125 /*close(fd);*/
5085 copyfd(copy, fd | COPYFD_EXACT); 5126 copyfd(copy, fd | COPYFD_EXACT);
5086 } 5127 }
5087 close(copy); 5128 close(copy & ~COPYFD_RESTORE);
5088 } 5129 }
5089 } 5130 }
5090 redirlist = rp->next; 5131 redirlist = rp->next;
@@ -7871,20 +7912,30 @@ dotrap(void)
7871 pendingsig = 0; 7912 pendingsig = 0;
7872 xbarrier(); 7913 xbarrier();
7873 7914
7915 TRACE(("dotrap entered\n"));
7874 for (i = 1, q = gotsig; i < NSIG; i++, q++) { 7916 for (i = 1, q = gotsig; i < NSIG; i++, q++) {
7875 if (!*q) 7917 if (!*q)
7876 continue; 7918 continue;
7877 *q = '\0';
7878 7919
7879 p = trap[i]; 7920 p = trap[i];
7921 /* non-trapped SIGINT is handled separately by raise_interrupt,
7922 * don't upset it by resetting gotsig[SIGINT-1] */
7923 if (i == SIGINT && !p)
7924 continue;
7925
7926 TRACE(("sig %d is active, will run handler '%s'\n", i, p));
7927 *q = '\0';
7880 if (!p) 7928 if (!p)
7881 continue; 7929 continue;
7882 skip = evalstring(p, SKIPEVAL); 7930 skip = evalstring(p, SKIPEVAL);
7883 exitstatus = savestatus; 7931 exitstatus = savestatus;
7884 if (skip) 7932 if (skip) {
7933 TRACE(("dotrap returns %d\n", skip));
7885 return skip; 7934 return skip;
7935 }
7886 } 7936 }
7887 7937
7938 TRACE(("dotrap returns 0\n"));
7888 return 0; 7939 return 0;
7889} 7940}
7890 7941
@@ -7906,28 +7957,32 @@ static void prehash(union node *);
7906static void 7957static void
7907evaltree(union node *n, int flags) 7958evaltree(union node *n, int flags)
7908{ 7959{
7909
7910 struct jmploc *volatile savehandler = exception_handler; 7960 struct jmploc *volatile savehandler = exception_handler;
7911 struct jmploc jmploc; 7961 struct jmploc jmploc;
7912 int checkexit = 0; 7962 int checkexit = 0;
7913 void (*evalfn)(union node *, int); 7963 void (*evalfn)(union node *, int);
7914 int status; 7964 int status;
7965 int int_level;
7966
7967 SAVE_INT(int_level);
7915 7968
7916 if (n == NULL) { 7969 if (n == NULL) {
7917 TRACE(("evaltree(NULL) called\n")); 7970 TRACE(("evaltree(NULL) called\n"));
7918 goto out1; 7971 goto out1;
7919 } 7972 }
7920 TRACE(("pid %d, evaltree(%p: %d, %d) called\n", 7973 TRACE(("evaltree(%p: %d, %d) called\n", n, n->type, flags));
7921 getpid(), n, n->type, flags));
7922 7974
7923 exception_handler = &jmploc; 7975 exception_handler = &jmploc;
7924 { 7976 {
7925 int err = setjmp(jmploc.loc); 7977 int err = setjmp(jmploc.loc);
7926 if (err) { 7978 if (err) {
7927 /* if it was a signal, check for trap handlers */ 7979 /* if it was a signal, check for trap handlers */
7928 if (exception == EXSIG) 7980 if (exception == EXSIG) {
7981 TRACE(("exception %d (EXSIG) in evaltree, err=%d\n", exception, err));
7929 goto out; 7982 goto out;
7983 }
7930 /* continue on the way out */ 7984 /* continue on the way out */
7985 TRACE(("exception %d in evaltree, propagating err=%d\n", exception, err));
7931 exception_handler = savehandler; 7986 exception_handler = savehandler;
7932 longjmp(exception_handler->loc, err); 7987 longjmp(exception_handler->loc, err);
7933 } 7988 }
@@ -8010,7 +8065,8 @@ evaltree(union node *n, int flags)
8010 if (exitstatus == 0) { 8065 if (exitstatus == 0) {
8011 n = n->nif.ifpart; 8066 n = n->nif.ifpart;
8012 goto evaln; 8067 goto evaln;
8013 } else if (n->nif.elsepart) { 8068 }
8069 if (n->nif.elsepart) {
8014 n = n->nif.elsepart; 8070 n = n->nif.elsepart;
8015 goto evaln; 8071 goto evaln;
8016 } 8072 }
@@ -8036,6 +8092,9 @@ evaltree(union node *n, int flags)
8036 exexit: 8092 exexit:
8037 raise_exception(EXEXIT); 8093 raise_exception(EXEXIT);
8038 } 8094 }
8095
8096 RESTORE_INT(int_level);
8097 TRACE(("leaving evaltree (no interrupts)\n"));
8039} 8098}
8040 8099
8041#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) 8100#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
@@ -8281,7 +8340,9 @@ evalpipe(union node *n, int flags)
8281 if (prevfd >= 0) 8340 if (prevfd >= 0)
8282 close(prevfd); 8341 close(prevfd);
8283 prevfd = pip[0]; 8342 prevfd = pip[0];
8284 close(pip[1]); 8343 /* Don't want to trigger debugging */
8344 if (pip[1] != -1)
8345 close(pip[1]);
8285 } 8346 }
8286 if (n->npipe.pipe_backgnd == 0) { 8347 if (n->npipe.pipe_backgnd == 0) {
8287 exitstatus = waitforjob(jp); 8348 exitstatus = waitforjob(jp);
@@ -8913,6 +8974,7 @@ evalcommand(union node *cmd, int flags)
8913 if (forkshell(jp, cmd, FORK_FG) != 0) { 8974 if (forkshell(jp, cmd, FORK_FG) != 0) {
8914 exitstatus = waitforjob(jp); 8975 exitstatus = waitforjob(jp);
8915 INT_ON; 8976 INT_ON;
8977 TRACE(("forked child exited with %d\n", exitstatus));
8916 break; 8978 break;
8917 } 8979 }
8918 FORCE_INT_ON; 8980 FORCE_INT_ON;
@@ -12391,7 +12453,7 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
12391#endif 12453#endif
12392 12454
12393 status = 0; 12455 status = 0;
12394 startword = 1; 12456 startword = 2;
12395 backslash = 0; 12457 backslash = 0;
12396#if ENABLE_ASH_READ_TIMEOUT 12458#if ENABLE_ASH_READ_TIMEOUT
12397 if (timeout) /* NB: ensuring end_ms is nonzero */ 12459 if (timeout) /* NB: ensuring end_ms is nonzero */
@@ -12399,6 +12461,8 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
12399#endif 12461#endif
12400 STARTSTACKSTR(p); 12462 STARTSTACKSTR(p);
12401 do { 12463 do {
12464 const char *is_ifs;
12465
12402#if ENABLE_ASH_READ_TIMEOUT 12466#if ENABLE_ASH_READ_TIMEOUT
12403 if (end_ms) { 12467 if (end_ms) {
12404 struct pollfd pfd[1]; 12468 struct pollfd pfd[1];
@@ -12428,25 +12492,34 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
12428 continue; 12492 continue;
12429 } 12493 }
12430 if (!rflag && c == '\\') { 12494 if (!rflag && c == '\\') {
12431 backslash++; 12495 backslash = 1;
12432 continue; 12496 continue;
12433 } 12497 }
12434 if (c == '\n') 12498 if (c == '\n')
12435 break; 12499 break;
12436 if (startword && *ifs == ' ' && strchr(ifs, c)) { 12500 is_ifs = strchr(ifs, c);
12437 continue; 12501 if (startword && is_ifs) {
12502 if (isspace(c))
12503 continue;
12504 /* non-space ifs char */
12505 startword--;
12506 if (startword == 1) /* first one? */
12507 continue;
12438 } 12508 }
12439 startword = 0; 12509 startword = 0;
12440 if (ap[1] != NULL && strchr(ifs, c) != NULL) { 12510 if (ap[1] != NULL && is_ifs) {
12511 const char *beg;
12441 STACKSTRNUL(p); 12512 STACKSTRNUL(p);
12442 setvar(*ap, stackblock(), 0); 12513 beg = stackblock();
12514 setvar(*ap, beg, 0);
12443 ap++; 12515 ap++;
12444 startword = 1; 12516 /* can we skip one non-space ifs? (2: yes) */
12517 startword = isspace(c) ? 2 : 1;
12445 STARTSTACKSTR(p); 12518 STARTSTACKSTR(p);
12446 } else { 12519 continue;
12447 put:
12448 STPUTC(c, p);
12449 } 12520 }
12521 put:
12522 STPUTC(c, p);
12450 } 12523 }
12451/* end of do {} while: */ 12524/* end of do {} while: */
12452#if ENABLE_ASH_READ_NCHARS 12525#if ENABLE_ASH_READ_NCHARS
@@ -12460,8 +12533,8 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
12460#endif 12533#endif
12461 12534
12462 STACKSTRNUL(p); 12535 STACKSTRNUL(p);
12463 /* Remove trailing blanks */ 12536 /* Remove trailing space ifs chars */
12464 while ((char *)stackblock() <= --p && strchr(ifs, *p) != NULL) 12537 while ((char *)stackblock() <= --p && isspace(*p) && strchr(ifs, *p) != NULL)
12465 *p = '\0'; 12538 *p = '\0';
12466 setvar(*ap, stackblock(), 0); 12539 setvar(*ap, stackblock(), 0);
12467 while (*++ap != NULL) 12540 while (*++ap != NULL)
@@ -13640,7 +13713,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
13640 exception_handler = &jmploc; 13713 exception_handler = &jmploc;
13641#if DEBUG 13714#if DEBUG
13642 opentrace(); 13715 opentrace();
13643 trace_puts("Shell args: "); 13716 TRACE(("Shell args: "));
13644 trace_puts_args(argv); 13717 trace_puts_args(argv);
13645#endif 13718#endif
13646 rootpid = getpid(); 13719 rootpid = getpid();
@@ -13692,8 +13765,14 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
13692 } 13765 }
13693 state3: 13766 state3:
13694 state = 4; 13767 state = 4;
13695 if (minusc) 13768 if (minusc) {
13769 /* evalstring pushes parsefile stack.
13770 * Ensure we don't falsely claim that 0 (stdin)
13771 * is one of stacked source fds */
13772 if (!sflag)
13773 g_parsefile->fd = -1;
13696 evalstring(minusc, 0); 13774 evalstring(minusc, 0);
13775 }
13697 13776
13698 if (sflag || minusc == NULL) { 13777 if (sflag || minusc == NULL) {
13699#if ENABLE_FEATURE_EDITING_SAVEHISTORY 13778#if ENABLE_FEATURE_EDITING_SAVEHISTORY
@@ -13720,14 +13799,6 @@ int ash_main(int argc UNUSED_PARAM, char **argv)
13720 /* NOTREACHED */ 13799 /* NOTREACHED */
13721} 13800}
13722 13801
13723#if DEBUG
13724const char *applet_name = "debug stuff usage";
13725int main(int argc, char **argv)
13726{
13727 return ash_main(argc, argv);
13728}
13729#endif
13730
13731 13802
13732/*- 13803/*-
13733 * Copyright (c) 1989, 1991, 1993, 1994 13804 * Copyright (c) 1989, 1991, 1993, 1994
diff --git a/shell/ash_test/ash-read/read_ifs.right b/shell/ash_test/ash-read/read_ifs.right
new file mode 100644
index 000000000..027ecd18f
--- /dev/null
+++ b/shell/ash_test/ash-read/read_ifs.right
@@ -0,0 +1,7 @@
1.a. .b. .c.
2.a. .b. .c.
3.a. .. .b,c.
4.a. .. .b,c.
5.a. .. .c.
6.a. .. .c. .d.
7.a. .. .b,c,d , ,.
diff --git a/shell/ash_test/ash-read/read_ifs.tests b/shell/ash_test/ash-read/read_ifs.tests
new file mode 100755
index 000000000..cf7cd934c
--- /dev/null
+++ b/shell/ash_test/ash-read/read_ifs.tests
@@ -0,0 +1,7 @@
1printf 'a\t\tb\tc\n' | ( IFS=$(printf "\t") read a b c; echo ".$a. .$b. .$c." )
2printf 'a\t\tb\tc\n' | ( IFS=$(printf " \t") read a b c; echo ".$a. .$b. .$c." )
3printf 'a,,b,c\n' | ( IFS="," read a b c; echo ".$a. .$b. .$c." )
4printf 'a,,b,c\n' | ( IFS=" ," read a b c; echo ".$a. .$b. .$c." )
5printf 'a ,, c\n' | ( IFS=" ," read a b c; echo ".$a. .$b. .$c." )
6printf 'a ,, c d\n' | ( IFS=" ," read a b c d; echo ".$a. .$b. .$c. .$d." )
7printf ' a,,b,c,d , ,\n' | ( IFS=" ," read a b c; echo ".$a. .$b. .$c." )
diff --git a/shell/hush.c b/shell/hush.c
index 421272971..9e7b10535 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -458,8 +458,11 @@ struct globals {
458 smallint fake_mode; 458 smallint fake_mode;
459 /* these three support $?, $#, and $1 */ 459 /* these three support $?, $#, and $1 */
460 smalluint last_return_code; 460 smalluint last_return_code;
461 char **global_argv; 461 /* is global_argv and global_argv[1..n] malloced? (note: not [0]) */
462 smalluint global_args_malloced;
463 /* how many non-NULL argv's we have. NB: $# + 1 */
462 int global_argc; 464 int global_argc;
465 char **global_argv;
463#if ENABLE_HUSH_LOOPS 466#if ENABLE_HUSH_LOOPS
464 unsigned depth_break_continue; 467 unsigned depth_break_continue;
465 unsigned depth_of_loop; 468 unsigned depth_of_loop;
@@ -633,7 +636,7 @@ static char *unbackslash(char *src)
633 return dst; 636 return dst;
634} 637}
635 638
636static char **add_strings_to_strings(char **strings, char **add) 639static char **add_strings_to_strings(char **strings, char **add, int need_to_dup)
637{ 640{
638 int i; 641 int i;
639 unsigned count1; 642 unsigned count1;
@@ -658,7 +661,7 @@ static char **add_strings_to_strings(char **strings, char **add)
658 v[count1 + count2] = NULL; 661 v[count1 + count2] = NULL;
659 i = count2; 662 i = count2;
660 while (--i >= 0) 663 while (--i >= 0)
661 v[count1 + i] = add[i]; 664 v[count1 + i] = (need_to_dup ? xstrdup(add[i]) : add[i]);
662 return v; 665 return v;
663} 666}
664 667
@@ -667,7 +670,7 @@ static char **add_string_to_strings(char **strings, char *add)
667 char *v[2]; 670 char *v[2];
668 v[0] = add; 671 v[0] = add;
669 v[1] = NULL; 672 v[1] = NULL;
670 return add_strings_to_strings(strings, v); 673 return add_strings_to_strings(strings, v, /*dup:*/ 0);
671} 674}
672 675
673static void putenv_all(char **strings) 676static void putenv_all(char **strings)
@@ -1213,8 +1216,13 @@ static int o_glob(o_string *o, int n)
1213 * Otherwise, just finish current list[] and start new */ 1216 * Otherwise, just finish current list[] and start new */
1214static int o_save_ptr(o_string *o, int n) 1217static int o_save_ptr(o_string *o, int n)
1215{ 1218{
1216 if (o->o_glob) 1219 if (o->o_glob) { /* if globbing is requested */
1217 return o_glob(o, n); /* o_save_ptr_helper is inside */ 1220 /* If o->has_empty_slot, list[n] was already globbed
1221 * (if it was requested back then when it was filled)
1222 * so don't do that again! */
1223 if (!o->has_empty_slot)
1224 return o_glob(o, n); /* o_save_ptr_helper is inside */
1225 }
1218 return o_save_ptr_helper(o, n); 1226 return o_save_ptr_helper(o, n);
1219} 1227}
1220 1228
@@ -4279,6 +4287,11 @@ int hush_main(int argc, char **argv)
4279 switch (opt) { 4287 switch (opt) {
4280 case 'c': 4288 case 'c':
4281 G.global_argv = argv + optind; 4289 G.global_argv = argv + optind;
4290 if (!argv[optind]) {
4291 /* -c 'script' (no params): prevent empty $0 */
4292 *--G.global_argv = argv[0];
4293 optind--;
4294 } /* else -c 'script' PAR0 PAR1: $0 is PAR0 */
4282 G.global_argc = argc - optind; 4295 G.global_argc = argc - optind;
4283 opt = parse_and_run_string(optarg, 0 /* parse_flag */); 4296 opt = parse_and_run_string(optarg, 0 /* parse_flag */);
4284 goto final_return; 4297 goto final_return;
@@ -4639,17 +4652,68 @@ static int builtin_read(char **argv)
4639 return set_local_var(string, 0); 4652 return set_local_var(string, 0);
4640} 4653}
4641 4654
4642/* built-in 'set [VAR=value]' handler */ 4655/* built-in 'set' handler
4656 * SUSv3 says:
4657 * set [-abCefmnuvx] [-h] [-o option] [argument...]
4658 * set [+abCefmnuvx] [+h] [+o option] [argument...]
4659 * set -- [argument...]
4660 * set -o
4661 * set +o
4662 * Implementations shall support the options in both their hyphen and
4663 * plus-sign forms. These options can also be specified as options to sh.
4664 * Examples:
4665 * Write out all variables and their values: set
4666 * Set $1, $2, and $3 and set "$#" to 3: set c a b
4667 * Turn on the -x and -v options: set -xv
4668 * Unset all positional parameters: set --
4669 * Set $1 to the value of x, even if it begins with '-' or '+': set -- "$x"
4670 * Set the positional parameters to the expansion of x, even if x expands
4671 * with a leading '-' or '+': set -- $x
4672 *
4673 * So far, we only support "set -- [argument...]" by ignoring all options
4674 * (also, "-o option" will be mishandled by taking "option" as parameter #1).
4675 */
4643static int builtin_set(char **argv) 4676static int builtin_set(char **argv)
4644{ 4677{
4645 char *temp = argv[1];
4646 struct variable *e; 4678 struct variable *e;
4679 char **pp;
4680 char *arg = *++argv;
4647 4681
4648 if (temp == NULL) 4682 if (arg == NULL) {
4649 for (e = G.top_var; e; e = e->next) 4683 for (e = G.top_var; e; e = e->next)
4650 puts(e->varstr); 4684 puts(e->varstr);
4651 else 4685 } else {
4652 set_local_var(xstrdup(temp), 0); 4686 /* NB: G.global_argv[0] ($0) is never freed/changed */
4687
4688 if (G.global_args_malloced) {
4689 pp = G.global_argv;
4690 while (*++pp)
4691 free(*pp);
4692 G.global_argv[1] = NULL;
4693 } else {
4694 G.global_args_malloced = 1;
4695 pp = xzalloc(sizeof(pp[0]) * 2);
4696 pp[0] = G.global_argv[0]; /* retain $0 */
4697 G.global_argv = pp;
4698 }
4699 do {
4700 if (arg[0] == '+')
4701 continue;
4702 if (arg[0] != '-')
4703 break;
4704 if (arg[1] == '-' && arg[2] == '\0') {
4705 argv++;
4706 break;
4707 }
4708 } while ((arg = *++argv) != NULL);
4709 /* Now argv[0] is 1st argument */
4710
4711 /* This realloc's G.global_argv */
4712 G.global_argv = pp = add_strings_to_strings(G.global_argv, argv, /*dup:*/ 1);
4713 G.global_argc = 1;
4714 while (*++pp)
4715 G.global_argc++;
4716 }
4653 4717
4654 return EXIT_SUCCESS; 4718 return EXIT_SUCCESS;
4655} 4719}
@@ -4661,9 +4725,14 @@ static int builtin_shift(char **argv)
4661 n = atoi(argv[1]); 4725 n = atoi(argv[1]);
4662 } 4726 }
4663 if (n >= 0 && n < G.global_argc) { 4727 if (n >= 0 && n < G.global_argc) {
4664 G.global_argv[n] = G.global_argv[0]; 4728 if (G.global_args_malloced) {
4729 int m = 1;
4730 while (m <= n)
4731 free(G.global_argv[m++]);
4732 }
4665 G.global_argc -= n; 4733 G.global_argc -= n;
4666 G.global_argv += n; 4734 memmove(&G.global_argv[1], &G.global_argv[n+1],
4735 G.global_argc * sizeof(G.global_argv[0]));
4667 return EXIT_SUCCESS; 4736 return EXIT_SUCCESS;
4668 } 4737 }
4669 return EXIT_FAILURE; 4738 return EXIT_FAILURE;
diff --git a/shell/hush_test/hush-parsing/starquoted2.right b/shell/hush_test/hush-parsing/starquoted2.right
index 46f24369e..f4624dad5 100644
--- a/shell/hush_test/hush-parsing/starquoted2.right
+++ b/shell/hush_test/hush-parsing/starquoted2.right
@@ -1,2 +1,3 @@
1Should be printed 1Should be printed
2Should be printed 2Should be printed
3Empty:
diff --git a/shell/hush_test/hush-parsing/starquoted2.tests b/shell/hush_test/hush-parsing/starquoted2.tests
index 782d71b88..3475edeb2 100755
--- a/shell/hush_test/hush-parsing/starquoted2.tests
+++ b/shell/hush_test/hush-parsing/starquoted2.tests
@@ -12,3 +12,6 @@ for a in "$@"""; do echo Should not be printed; done
12for a in """$@"; do echo Should not be printed; done 12for a in """$@"; do echo Should not be printed; done
13for a in """$@"''"$@"''; do echo Should not be printed; done 13for a in """$@"''"$@"''; do echo Should not be printed; done
14for a in ""; do echo Should be printed; done 14for a in ""; do echo Should be printed; done
15
16# Bug 207: "$@" expands to nothing, and we erroneously glob "%s\\n" twice:
17printf "Empty:%s\\n" "$@"