aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2017-08-22 14:56:12 +0100
committerRon Yorston <rmy@pobox.com>2017-08-22 14:56:12 +0100
commitce9af1cc5ea23f754587448cf35b5120c77bfeef (patch)
tree69e5eaba5e75ab909ed92d5045393471b8ff3c13 /shell
parentc170026700eabb10147dd848c45c06995b43a32e (diff)
parente837a0dbbebf4229306df98fe9ee3b9bb30630c4 (diff)
downloadbusybox-w32-ce9af1cc5ea23f754587448cf35b5120c77bfeef.tar.gz
busybox-w32-ce9af1cc5ea23f754587448cf35b5120c77bfeef.tar.bz2
busybox-w32-ce9af1cc5ea23f754587448cf35b5120c77bfeef.zip
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
-rw-r--r--shell/ash.c152
-rw-r--r--shell/ash_LINENO.patch498
-rw-r--r--shell/ash_test/ash-getopts/getopt_optarg.right18
-rwxr-xr-xshell/ash_test/ash-getopts/getopt_optarg.tests18
-rw-r--r--shell/ash_test/ash-getopts/getopt_positional.right6
-rwxr-xr-xshell/ash_test/ash-getopts/getopt_positional.tests8
-rw-r--r--shell/ash_test/ash-getopts/getopt_silent.right6
-rwxr-xr-xshell/ash_test/ash-getopts/getopt_silent.tests23
-rw-r--r--shell/ash_test/ash-getopts/getopt_simple.right34
-rwxr-xr-xshell/ash_test/ash-getopts/getopt_simple.tests75
-rw-r--r--shell/ash_test/ash-getopts/getopt_test_libc_bug.right26
-rwxr-xr-xshell/ash_test/ash-getopts/getopt_test_libc_bug.tests38
-rw-r--r--shell/ash_test/ash-parsing/groups_and_keywords2.right3
-rwxr-xr-xshell/ash_test/ash-parsing/groups_and_keywords2.tests9
-rw-r--r--shell/ash_test/ash-vars/param_expand_assign.right2
-rw-r--r--shell/ash_test/printenv.c2
-rw-r--r--shell/cttyhack.c2
-rw-r--r--shell/hush.c270
-rw-r--r--shell/hush_test/hush-getopts/getopt_optarg.right18
-rwxr-xr-xshell/hush_test/hush-getopts/getopt_optarg.tests24
-rw-r--r--shell/hush_test/hush-getopts/getopt_positional.right6
-rwxr-xr-xshell/hush_test/hush-getopts/getopt_positional.tests13
-rw-r--r--shell/hush_test/hush-getopts/getopt_silent.right6
-rwxr-xr-xshell/hush_test/hush-getopts/getopt_silent.tests29
-rw-r--r--shell/hush_test/hush-getopts/getopt_simple.right34
-rwxr-xr-xshell/hush_test/hush-getopts/getopt_simple.tests81
-rw-r--r--shell/hush_test/hush-getopts/getopt_test_libc_bug.right26
-rwxr-xr-xshell/hush_test/hush-getopts/getopt_test_libc_bug.tests44
-rw-r--r--shell/hush_test/hush-parsing/groups_and_keywords2.right3
-rwxr-xr-xshell/hush_test/hush-parsing/groups_and_keywords2.tests9
-rwxr-xr-xshell/hush_test/run-all2
-rw-r--r--shell/math.c2
-rw-r--r--shell/shell_common.c9
-rw-r--r--shell/shell_common.h8
34 files changed, 1422 insertions, 82 deletions
diff --git a/shell/ash.c b/shell/ash.c
index 6a572cbc3..ac1ae05fc 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -215,6 +215,9 @@
215#define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT 215#define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT
216#define BASH_HOSTNAME_VAR ENABLE_ASH_BASH_COMPAT 216#define BASH_HOSTNAME_VAR ENABLE_ASH_BASH_COMPAT
217#define BASH_SHLVL_VAR ENABLE_ASH_BASH_COMPAT 217#define BASH_SHLVL_VAR ENABLE_ASH_BASH_COMPAT
218#define BASH_XTRACEFD ENABLE_ASH_BASH_COMPAT
219#define BASH_READ_D ENABLE_ASH_BASH_COMPAT
220#define IF_BASH_READ_D IF_ASH_BASH_COMPAT
218 221
219#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24 222#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24
220/* Bionic at least up to version 24 has no glob() */ 223/* Bionic at least up to version 24 has no glob() */
@@ -452,7 +455,7 @@ struct globals_misc {
452#define S_DFL 1 /* default signal handling (SIG_DFL) */ 455#define S_DFL 1 /* default signal handling (SIG_DFL) */
453#define S_CATCH 2 /* signal is caught */ 456#define S_CATCH 2 /* signal is caught */
454#define S_IGN 3 /* signal is ignored (SIG_IGN) */ 457#define S_IGN 3 /* signal is ignored (SIG_IGN) */
455#define S_HARD_IGN 4 /* signal is ignored permanently */ 458#define S_HARD_IGN 4 /* signal is ignored permanently (it was SIG_IGN on entry to shell) */
456 459
457 /* indicates specified signal received */ 460 /* indicates specified signal received */
458 uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */ 461 uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */
@@ -1313,6 +1316,10 @@ struct strpush {
1313 int unget; 1316 int unget;
1314}; 1317};
1315 1318
1319/*
1320 * The parsefile structure pointed to by the global variable parsefile
1321 * contains information about the current file being read.
1322 */
1316struct parsefile { 1323struct parsefile {
1317 struct parsefile *prev; /* preceding file on stack */ 1324 struct parsefile *prev; /* preceding file on stack */
1318 int linno; /* current line */ 1325 int linno; /* current line */
@@ -1986,10 +1993,6 @@ nextopt(const char *optstring)
1986 1993
1987/* ============ Shell variables */ 1994/* ============ Shell variables */
1988 1995
1989/*
1990 * The parsefile structure pointed to by the global variable parsefile
1991 * contains information about the current file being read.
1992 */
1993struct shparam { 1996struct shparam {
1994 int nparam; /* # of positional parameters (without $0) */ 1997 int nparam; /* # of positional parameters (without $0) */
1995#if ENABLE_ASH_GETOPTS 1998#if ENABLE_ASH_GETOPTS
@@ -2183,7 +2186,9 @@ extern struct globals_var *const ash_ptr_to_globals_var;
2183static void FAST_FUNC 2186static void FAST_FUNC
2184getoptsreset(const char *value) 2187getoptsreset(const char *value)
2185{ 2188{
2186 shellparam.optind = number(value) ?: 1; 2189 shellparam.optind = 1;
2190 if (is_number(value))
2191 shellparam.optind = number(value) ?: 1;
2187 shellparam.optoff = -1; 2192 shellparam.optoff = -1;
2188} 2193}
2189#endif 2194#endif
@@ -3751,9 +3756,12 @@ setsignal(int signo)
3751#endif 3756#endif
3752 } 3757 }
3753 } 3758 }
3754//TODO: if !rootshell, we reset SIGQUIT to DFL, 3759 /* if !rootshell, we reset SIGQUIT to DFL,
3755//whereas we have to restore it to what shell got on entry 3760 * whereas we have to restore it to what shell got on entry.
3756//from the parent. See comment above 3761 * This is handled by the fact that if signal was IGNored on entry,
3762 * then cur_act is S_HARD_IGN and we never change its sigaction
3763 * (see code below).
3764 */
3757 3765
3758 if (signo == SIGCHLD) 3766 if (signo == SIGCHLD)
3759 new_act = S_CATCH; 3767 new_act = S_CATCH;
@@ -3777,10 +3785,18 @@ setsignal(int signo)
3777 cur_act = S_IGN; /* don't hard ignore these */ 3785 cur_act = S_IGN; /* don't hard ignore these */
3778 } 3786 }
3779 } 3787 }
3788 if (act.sa_handler == SIG_DFL && new_act == S_DFL) {
3789 /* installing SIG_DFL over SIG_DFL is a no-op */
3790 /* saves one sigaction call in each "sh -c SCRIPT" invocation */
3791 *t = S_DFL;
3792 return;
3793 }
3780 } 3794 }
3781 if (cur_act == S_HARD_IGN || cur_act == new_act) 3795 if (cur_act == S_HARD_IGN || cur_act == new_act)
3782 return; 3796 return;
3783 3797
3798 *t = new_act;
3799
3784 act.sa_handler = SIG_DFL; 3800 act.sa_handler = SIG_DFL;
3785 switch (new_act) { 3801 switch (new_act) {
3786 case S_CATCH: 3802 case S_CATCH:
@@ -3790,16 +3806,13 @@ setsignal(int signo)
3790 act.sa_handler = SIG_IGN; 3806 act.sa_handler = SIG_IGN;
3791 break; 3807 break;
3792 } 3808 }
3793
3794 /* flags and mask matter only if !DFL and !IGN, but we do it 3809 /* flags and mask matter only if !DFL and !IGN, but we do it
3795 * for all cases for more deterministic behavior: 3810 * for all cases for more deterministic behavior:
3796 */ 3811 */
3797 act.sa_flags = 0; 3812 act.sa_flags = 0; //TODO: why not SA_RESTART?
3798 sigfillset(&act.sa_mask); 3813 sigfillset(&act.sa_mask);
3799 3814
3800 sigaction_set(signo, &act); 3815 sigaction_set(signo, &act);
3801
3802 *t = new_act;
3803} 3816}
3804#else 3817#else
3805#define setsignal(s) 3818#define setsignal(s)
@@ -5915,12 +5928,10 @@ static void
5915redirect(union node *redir, int flags) 5928redirect(union node *redir, int flags)
5916{ 5929{
5917 struct redirtab *sv; 5930 struct redirtab *sv;
5918 int sv_pos;
5919 5931
5920 if (!redir) 5932 if (!redir)
5921 return; 5933 return;
5922 5934
5923 sv_pos = 0;
5924 sv = NULL; 5935 sv = NULL;
5925 INT_OFF; 5936 INT_OFF;
5926 if (flags & REDIR_PUSH) 5937 if (flags & REDIR_PUSH)
@@ -8076,8 +8087,9 @@ static int
8076patmatch(char *pattern, const char *string) 8087patmatch(char *pattern, const char *string)
8077{ 8088{
8078 char *p = preglob(pattern, 0); 8089 char *p = preglob(pattern, 0);
8079 //bb_error_msg("fnmatch(pattern:'%s',str:'%s')", p, string); 8090 int r = pmatch(p, string);
8080 return pmatch(p, string); 8091 //bb_error_msg("!fnmatch(pattern:'%s',str:'%s',0):%d", p, string, r);
8092 return r;
8081} 8093}
8082 8094
8083/* 8095/*
@@ -8179,7 +8191,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c
8179 while (*envp) 8191 while (*envp)
8180 putenv(*envp++); 8192 putenv(*envp++);
8181 popredir(/*drop:*/ 1); 8193 popredir(/*drop:*/ 1);
8182 run_applet_no_and_exit(applet_no, cmd, argv); 8194 run_noexec_applet_and_exit(applet_no, cmd, argv);
8183 } 8195 }
8184 /* re-exec ourselves with the new arguments */ 8196 /* re-exec ourselves with the new arguments */
8185 execve(bb_busybox_exec_path, argv, envp); 8197 execve(bb_busybox_exec_path, argv, envp);
@@ -10250,6 +10262,15 @@ evalcommand(union node *cmd, int flags)
10250 expredir(cmd->ncmd.redirect); 10262 expredir(cmd->ncmd.redirect);
10251 redir_stop = pushredir(cmd->ncmd.redirect); 10263 redir_stop = pushredir(cmd->ncmd.redirect);
10252 preverrout_fd = 2; 10264 preverrout_fd = 2;
10265 if (BASH_XTRACEFD && xflag) {
10266 /* NB: bash closes fd == $BASH_XTRACEFD when it is changed.
10267 * we do not emulate this. We only use its value.
10268 */
10269 const char *xtracefd = lookupvar("BASH_XTRACEFD");
10270 if (xtracefd && is_number(xtracefd))
10271 preverrout_fd = atoi(xtracefd);
10272
10273 }
10253 status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2); 10274 status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2);
10254 10275
10255 path = vpath.var_text; 10276 path = vpath.var_text;
@@ -10383,12 +10404,30 @@ evalcommand(union node *cmd, int flags)
10383#endif 10404#endif
10384 ) { 10405 ) {
10385 listsetvar(varlist.list, VEXPORT|VSTACK); 10406 listsetvar(varlist.list, VEXPORT|VSTACK);
10386 /* run <applet>_main() */ 10407 /*
10408 * Run <applet>_main().
10409 * Signals (^C) can't interrupt here.
10410 * Otherwise we can mangle stdio or malloc internal state.
10411 * This makes applets which can run for a long time
10412 * and/or wait for user input ineligible for NOFORK:
10413 * for example, "yes" or "rm" (rm -i waits for input).
10414 */
10415 INT_OFF;
10387 status = run_nofork_applet(applet_no, argv); 10416 status = run_nofork_applet(applet_no, argv);
10417 /*
10418 * Try enabling NOFORK for "yes" applet.
10419 * ^C _will_ stop it (write returns EINTR),
10420 * but this causes stdout FILE to be stuck
10421 * and needing clearerr(). What if other applets
10422 * also can get EINTRs? Do we need to switch
10423 * our signals to SA_RESTART?
10424 */
10425 /*clearerr(stdout);*/
10426 INT_ON;
10388 break; 10427 break;
10389 } 10428 }
10390#endif 10429#endif
10391 /* Can we avoid forking off? For example, very last command 10430 /* Can we avoid forking? For example, very last command
10392 * in a script or a subshell does not need forking, 10431 * in a script or a subshell does not need forking,
10393 * we can just exec it. 10432 * we can just exec it.
10394 */ 10433 */
@@ -10674,8 +10713,8 @@ preadfd(void)
10674 if (!iflag || g_parsefile->pf_fd != STDIN_FILENO) 10713 if (!iflag || g_parsefile->pf_fd != STDIN_FILENO)
10675 nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); 10714 nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1);
10676 else { 10715 else {
10677 int timeout = -1;
10678# if ENABLE_ASH_IDLE_TIMEOUT 10716# if ENABLE_ASH_IDLE_TIMEOUT
10717 int timeout = -1;
10679 if (iflag) { 10718 if (iflag) {
10680 const char *tmout_var = lookupvar("TMOUT"); 10719 const char *tmout_var = lookupvar("TMOUT");
10681 if (tmout_var) { 10720 if (tmout_var) {
@@ -10684,12 +10723,13 @@ preadfd(void)
10684 timeout = -1; 10723 timeout = -1;
10685 } 10724 }
10686 } 10725 }
10726 line_input_state->timeout = timeout;
10687# endif 10727# endif
10688# if ENABLE_FEATURE_TAB_COMPLETION 10728# if ENABLE_FEATURE_TAB_COMPLETION
10689 line_input_state->path_lookup = pathval(); 10729 line_input_state->path_lookup = pathval();
10690# endif 10730# endif
10691 reinit_unicode_for_ash(); 10731 reinit_unicode_for_ash();
10692 nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ, timeout); 10732 nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ);
10693 if (nr == 0) { 10733 if (nr == 0) {
10694 /* ^C pressed, "convert" to SIGINT */ 10734 /* ^C pressed, "convert" to SIGINT */
10695 write(STDOUT_FILENO, "^C", 2); 10735 write(STDOUT_FILENO, "^C", 2);
@@ -11399,6 +11439,7 @@ getopts(char *optstr, char *optvar, char **optfirst)
11399 p = *optnext; 11439 p = *optnext;
11400 if (p == NULL || *p != '-' || *++p == '\0') { 11440 if (p == NULL || *p != '-' || *++p == '\0') {
11401 atend: 11441 atend:
11442 unsetvar("OPTARG");
11402 p = NULL; 11443 p = NULL;
11403 done = 1; 11444 done = 1;
11404 goto out; 11445 goto out;
@@ -11411,7 +11452,11 @@ getopts(char *optstr, char *optvar, char **optfirst)
11411 c = *p++; 11452 c = *p++;
11412 for (q = optstr; *q != c;) { 11453 for (q = optstr; *q != c;) {
11413 if (*q == '\0') { 11454 if (*q == '\0') {
11414 if (optstr[0] == ':') { 11455 /* OPTERR is a bashism */
11456 const char *cp = lookupvar("OPTERR");
11457 if ((cp && LONE_CHAR(cp, '0'))
11458 || (optstr[0] == ':')
11459 ) {
11415 sbuf[0] = c; 11460 sbuf[0] = c;
11416 /*sbuf[1] = '\0'; - already is */ 11461 /*sbuf[1] = '\0'; - already is */
11417 setvar0("OPTARG", sbuf); 11462 setvar0("OPTARG", sbuf);
@@ -11428,7 +11473,11 @@ getopts(char *optstr, char *optvar, char **optfirst)
11428 11473
11429 if (*++q == ':') { 11474 if (*++q == ':') {
11430 if (*p == '\0' && (p = *optnext) == NULL) { 11475 if (*p == '\0' && (p = *optnext) == NULL) {
11431 if (optstr[0] == ':') { 11476 /* OPTERR is a bashism */
11477 const char *cp = lookupvar("OPTERR");
11478 if ((cp && LONE_CHAR(cp, '0'))
11479 || (optstr[0] == ':')
11480 ) {
11432 sbuf[0] = c; 11481 sbuf[0] = c;
11433 /*sbuf[1] = '\0'; - already is */ 11482 /*sbuf[1] = '\0'; - already is */
11434 setvar0("OPTARG", sbuf); 11483 setvar0("OPTARG", sbuf);
@@ -12571,7 +12620,7 @@ parsesub: {
12571 STPUTC(c, out); 12620 STPUTC(c, out);
12572 c = pgetc_eatbnl(); 12621 c = pgetc_eatbnl();
12573 } while (isdigit(c)); 12622 } while (isdigit(c));
12574 } else if (is_special(c)) { 12623 } else {
12575 /* $[{[#]]<specialchar>[}] */ 12624 /* $[{[#]]<specialchar>[}] */
12576 int cc = c; 12625 int cc = c;
12577 12626
@@ -12589,10 +12638,16 @@ parsesub: {
12589 cc = '#'; 12638 cc = '#';
12590 } 12639 }
12591 } 12640 }
12641
12642 if (!is_special(cc)) {
12643 if (subtype == VSLENGTH)
12644 subtype = 0;
12645 goto badsub;
12646 }
12647
12592 USTPUTC(cc, out); 12648 USTPUTC(cc, out);
12593 } else {
12594 goto badsub;
12595 } 12649 }
12650
12596 if (c != '}' && subtype == VSLENGTH) { 12651 if (c != '}' && subtype == VSLENGTH) {
12597 /* ${#VAR didn't end with } */ 12652 /* ${#VAR didn't end with } */
12598 goto badsub; 12653 goto badsub;
@@ -13850,21 +13905,23 @@ static const unsigned char timescmd_str[] ALIGN1 = {
13850static int FAST_FUNC 13905static int FAST_FUNC
13851timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) 13906timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
13852{ 13907{
13853 unsigned long clk_tck, s, t; 13908 unsigned clk_tck;
13854 const unsigned char *p; 13909 const unsigned char *p;
13855 struct tms buf; 13910 struct tms buf;
13856 13911
13857 clk_tck = bb_clk_tck(); 13912 clk_tck = bb_clk_tck();
13858 times(&buf);
13859 13913
13914 times(&buf);
13860 p = timescmd_str; 13915 p = timescmd_str;
13861 do { 13916 do {
13917 unsigned sec, frac;
13918 unsigned long t;
13862 t = *(clock_t *)(((char *) &buf) + p[1]); 13919 t = *(clock_t *)(((char *) &buf) + p[1]);
13863 s = t / clk_tck; 13920 sec = t / clk_tck;
13864 t = t % clk_tck; 13921 frac = t % clk_tck;
13865 out1fmt("%lum%lu.%03lus%c", 13922 out1fmt("%um%u.%03us%c",
13866 s / 60, s % 60, 13923 sec / 60, sec % 60,
13867 (t * 1000) / clk_tck, 13924 (frac * 1000) / clk_tck,
13868 p[0]); 13925 p[0]);
13869 p += 2; 13926 p += 2;
13870 } while (*p); 13927 } while (*p);
@@ -13903,10 +13960,10 @@ letcmd(int argc UNUSED_PARAM, char **argv)
13903 * -p PROMPT Display PROMPT on stderr (if input is from tty) 13960 * -p PROMPT Display PROMPT on stderr (if input is from tty)
13904 * -t SECONDS Timeout after SECONDS (tty or pipe only) 13961 * -t SECONDS Timeout after SECONDS (tty or pipe only)
13905 * -u FD Read from given FD instead of fd 0 13962 * -u FD Read from given FD instead of fd 0
13963 * -d DELIM End on DELIM char, not newline
13906 * This uses unbuffered input, which may be avoidable in some cases. 13964 * This uses unbuffered input, which may be avoidable in some cases.
13907 * TODO: bash also has: 13965 * TODO: bash also has:
13908 * -a ARRAY Read into array[0],[1],etc 13966 * -a ARRAY Read into array[0],[1],etc
13909 * -d DELIM End on DELIM char, not newline
13910 * -e Use line editing (tty only) 13967 * -e Use line editing (tty only)
13911 */ 13968 */
13912static int FAST_FUNC 13969static int FAST_FUNC
@@ -13916,11 +13973,12 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
13916 char *opt_p = NULL; 13973 char *opt_p = NULL;
13917 char *opt_t = NULL; 13974 char *opt_t = NULL;
13918 char *opt_u = NULL; 13975 char *opt_u = NULL;
13976 char *opt_d = NULL; /* optimized out if !BASH */
13919 int read_flags = 0; 13977 int read_flags = 0;
13920 const char *r; 13978 const char *r;
13921 int i; 13979 int i;
13922 13980
13923 while ((i = nextopt("p:u:rt:n:s")) != '\0') { 13981 while ((i = nextopt("p:u:rt:n:sd:")) != '\0') {
13924 switch (i) { 13982 switch (i) {
13925 case 'p': 13983 case 'p':
13926 opt_p = optionarg; 13984 opt_p = optionarg;
@@ -13940,6 +13998,11 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
13940 case 'u': 13998 case 'u':
13941 opt_u = optionarg; 13999 opt_u = optionarg;
13942 break; 14000 break;
14001#if BASH_READ_D
14002 case 'd':
14003 opt_d = optionarg;
14004 break;
14005#endif
13943 default: 14006 default:
13944 break; 14007 break;
13945 } 14008 }
@@ -13957,12 +14020,15 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
13957 opt_n, 14020 opt_n,
13958 opt_p, 14021 opt_p,
13959 opt_t, 14022 opt_t,
13960 opt_u 14023 opt_u,
14024 opt_d
13961 ); 14025 );
13962 INT_ON; 14026 INT_ON;
13963 14027
13964 if ((uintptr_t)r == 1 && errno == EINTR) { 14028 if ((uintptr_t)r == 1 && errno == EINTR) {
13965 /* to get SIGCHLD: sleep 1 & read x; echo $x */ 14029 /* To get SIGCHLD: sleep 1 & read x; echo $x
14030 * Correct behavior is to not exit "read"
14031 */
13966 if (pending_sig == 0) 14032 if (pending_sig == 0)
13967 goto again; 14033 goto again;
13968 } 14034 }
@@ -14077,7 +14143,8 @@ exitshell(void)
14077 /* NOTREACHED */ 14143 /* NOTREACHED */
14078} 14144}
14079 14145
14080static void 14146/* Don't inline: conserve stack of caller from having our locals too */
14147static NOINLINE void
14081#if ENABLE_PLATFORM_MINGW32 14148#if ENABLE_PLATFORM_MINGW32
14082init(int xp) 14149init(int xp)
14083#else 14150#else
@@ -14086,8 +14153,9 @@ init(void)
14086{ 14153{
14087 /* we will never free this */ 14154 /* we will never free this */
14088 basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); 14155 basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ);
14156 basepf.linno = 1;
14089 14157
14090 sigmode[SIGCHLD - 1] = S_DFL; 14158 sigmode[SIGCHLD - 1] = S_DFL; /* ensure we install handler even if it is SIG_IGNed */
14091 setsignal(SIGCHLD); 14159 setsignal(SIGCHLD);
14092 14160
14093 /* bash re-enables SIGHUP which is SIG_IGNed on entry. 14161 /* bash re-enables SIGHUP which is SIG_IGNed on entry.
@@ -14098,7 +14166,6 @@ init(void)
14098 { 14166 {
14099 char **envp; 14167 char **envp;
14100 const char *p; 14168 const char *p;
14101 struct stat st1, st2;
14102 14169
14103 initvar(); 14170 initvar();
14104 14171
@@ -14204,6 +14271,7 @@ init(void)
14204#endif 14271#endif
14205 p = lookupvar("PWD"); 14272 p = lookupvar("PWD");
14206 if (p) { 14273 if (p) {
14274 struct stat st1, st2;
14207 if (p[0] != '/' || stat(p, &st1) || stat(".", &st2) 14275 if (p[0] != '/' || stat(p, &st1) || stat(".", &st2)
14208 || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino 14276 || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino
14209 ) { 14277 ) {
@@ -15161,7 +15229,7 @@ sticky_free(void *base)
15161 * may be used to endorse or promote products derived from this software 15229 * may be used to endorse or promote products derived from this software
15162 * without specific prior written permission. 15230 * without specific prior written permission.
15163 * 15231 *
15164 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 15232 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND
15165 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15233 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15166 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15234 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15167 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 15235 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
diff --git a/shell/ash_LINENO.patch b/shell/ash_LINENO.patch
new file mode 100644
index 000000000..a71549d6a
--- /dev/null
+++ b/shell/ash_LINENO.patch
@@ -0,0 +1,498 @@
1This patch is a backport from dash of the combination of:
2 [SHELL] Add preliminary LINENO support
3 [VAR] Fix varinit ordering that broke fc
4 [SHELL] Improve LINENO support
5
6Applies cleanly on top of:
7 commit 9832bbaba966f0e52e183f10cd93fad7f8f643fe
8 Date: Tue Aug 15 15:44:41 2017 +0200
9
10Testsuite needs some tweaks (line numbers in some messages change).
11
12Unfortunately, it is somewhat big:
13
14function old new delta
15parse_command 1581 1658 +77
16calcsize 203 272 +69
17copynode 195 257 +62
18lookupvar 59 108 +49
19evaltree 494 534 +40
20evalfor 152 187 +35
21evalcase 278 313 +35
22evalcommand 1547 1581 +34
23evalsubshell 169 199 +30
24linenovar - 22 +22
25raise_error_syntax 11 29 +18
26evalfun 266 280 +14
27varinit_data 96 108 +12
28cmdtxt 626 631 +5
29lineno - 4 +4
30funcline - 4 +4
31ash_vmsg 144 141 -3
32startlinno 4 - -4
33funcnest 4 - -4
34xxreadtoken 272 259 -13
35readtoken1 2635 2594 -41
36------------------------------------------------------------------------------
37(add/remove: 3/2 grow/shrink: 13/3 up/down: 510/-65) Total: 445 bytes
38 text data bss dec hex filename
39 912030 563 5844 918437 e03a5 busybox_old
40 912473 587 5844 918904 e0578 busybox_unstripped
41
42diff --git a/shell/ash.c b/shell/ash.c
43index 703802f..93a3814 100644
44--- a/shell/ash.c
45+++ b/shell/ash.c
46@@ -312,6 +312,8 @@ struct globals_misc {
47 /* shell level: 0 for the main shell, 1 for its children, and so on */
48 int shlvl;
49 #define rootshell (!shlvl)
50+ int errlinno;
51+
52 char *minusc; /* argument to -c option */
53
54 char *curdir; // = nullstr; /* current working directory */
55@@ -389,6 +391,7 @@ extern struct globals_misc *const ash_ptr_to_globals_misc;
56 #define job_warning (G_misc.job_warning)
57 #define rootpid (G_misc.rootpid )
58 #define shlvl (G_misc.shlvl )
59+#define errlinno (G_misc.errlinno )
60 #define minusc (G_misc.minusc )
61 #define curdir (G_misc.curdir )
62 #define physdir (G_misc.physdir )
63@@ -723,6 +726,7 @@ union node;
64
65 struct ncmd {
66 smallint type; /* Nxxxx */
67+ int linno;
68 union node *assign;
69 union node *args;
70 union node *redirect;
71@@ -736,6 +740,7 @@ struct npipe {
72
73 struct nredir {
74 smallint type;
75+ int linno;
76 union node *n;
77 union node *redirect;
78 };
79@@ -755,6 +760,7 @@ struct nif {
80
81 struct nfor {
82 smallint type;
83+ int linno;
84 union node *args;
85 union node *body;
86 char *var;
87@@ -762,6 +768,7 @@ struct nfor {
88
89 struct ncase {
90 smallint type;
91+ int linno;
92 union node *expr;
93 union node *cases;
94 };
95@@ -773,6 +780,13 @@ struct nclist {
96 union node *body;
97 };
98
99+struct ndefun {
100+ smallint type;
101+ int linno;
102+ char *text;
103+ union node *body;
104+};
105+
106 struct narg {
107 smallint type;
108 union node *next;
109@@ -824,6 +838,7 @@ union node {
110 struct nfor nfor;
111 struct ncase ncase;
112 struct nclist nclist;
113+ struct ndefun ndefun;
114 struct narg narg;
115 struct nfile nfile;
116 struct ndup ndup;
117@@ -1253,7 +1268,6 @@ struct parsefile {
118
119 static struct parsefile basepf; /* top level input file */
120 static struct parsefile *g_parsefile = &basepf; /* current input file */
121-static int startlinno; /* line # where last token started */
122 static char *commandname; /* currently executing command */
123
124
125@@ -1267,7 +1281,7 @@ ash_vmsg(const char *msg, va_list ap)
126 if (strcmp(arg0, commandname))
127 fprintf(stderr, "%s: ", commandname);
128 if (!iflag || g_parsefile->pf_fd > 0)
129- fprintf(stderr, "line %d: ", startlinno);
130+ fprintf(stderr, "line %d: ", errlinno);
131 }
132 vfprintf(stderr, msg, ap);
133 newline_and_flush(stderr);
134@@ -1327,6 +1341,7 @@ static void raise_error_syntax(const char *) NORETURN;
135 static void
136 raise_error_syntax(const char *msg)
137 {
138+ errlinno = g_parsefile->linno;
139 ash_msg_and_raise_error("syntax error: %s", msg);
140 /* NOTREACHED */
141 }
142@@ -1993,6 +2008,9 @@ static void changepath(const char *) FAST_FUNC;
143 static void change_random(const char *) FAST_FUNC;
144 #endif
145
146+static int lineno;
147+static char linenovar[sizeof("LINENO=%d") + sizeof(int)*3] = "LINENO=";
148+
149 static const struct {
150 int flags;
151 const char *var_text;
152@@ -2014,6 +2032,7 @@ static const struct {
153 #if ENABLE_ASH_GETOPTS
154 { VSTRFIXED|VTEXTFIXED , defoptindvar, getoptsreset },
155 #endif
156+ { VSTRFIXED|VTEXTFIXED , linenovar , NULL },
157 #if ENABLE_ASH_RANDOM_SUPPORT
158 { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random },
159 #endif
160@@ -2066,12 +2085,14 @@ extern struct globals_var *const ash_ptr_to_globals_var;
161 #define vps4 (&vps2)[1]
162 #if ENABLE_ASH_GETOPTS
163 # define voptind (&vps4)[1]
164+# define vlineno (&voptind)[1]
165 # if ENABLE_ASH_RANDOM_SUPPORT
166-# define vrandom (&voptind)[1]
167+# define vrandom (&vlineno)[1]
168 # endif
169 #else
170+# define vlineno (&vps4)[1]
171 # if ENABLE_ASH_RANDOM_SUPPORT
172-# define vrandom (&vps4)[1]
173+# define vrandom (&vlineno)[1]
174 # endif
175 #endif
176
177@@ -2209,8 +2230,12 @@ lookupvar(const char *name)
178 if (v->flags & VDYNAMIC)
179 v->var_func(NULL);
180 #endif
181- if (!(v->flags & VUNSET))
182+ if (!(v->flags & VUNSET)) {
183+ if (v == &vlineno && v->var_text == linenovar) {
184+ fmtstr(linenovar+7, sizeof(linenovar)-7, "%d", lineno);
185+ }
186 return var_end(v->var_text);
187+ }
188 }
189 return NULL;
190 }
191@@ -4783,7 +4808,7 @@ cmdtxt(union node *n)
192 p = "; done";
193 goto dodo;
194 case NDEFUN:
195- cmdputs(n->narg.text);
196+ cmdputs(n->ndefun.text);
197 p = "() { ... }";
198 goto dotail2;
199 case NCMD:
200@@ -8551,6 +8576,9 @@ calcsize(int funcblocksize, union node *n)
201 funcblocksize = calcsize(funcblocksize, n->nclist.next);
202 break;
203 case NDEFUN:
204+ funcblocksize = calcsize(funcblocksize, n->ndefun.body);
205+ funcblocksize += SHELL_ALIGN(strlen(n->ndefun.text) + 1);
206+ break;
207 case NARG:
208 funcblocksize = sizenodelist(funcblocksize, n->narg.backquote);
209 funcblocksize += SHELL_ALIGN(strlen(n->narg.text) + 1); /* was funcstringsize += ... */
210@@ -8626,6 +8654,7 @@ copynode(union node *n)
211 new->ncmd.redirect = copynode(n->ncmd.redirect);
212 new->ncmd.args = copynode(n->ncmd.args);
213 new->ncmd.assign = copynode(n->ncmd.assign);
214+ new->ncmd.linno = n->ncmd.linno;
215 break;
216 case NPIPE:
217 new->npipe.cmdlist = copynodelist(n->npipe.cmdlist);
218@@ -8636,6 +8665,7 @@ copynode(union node *n)
219 case NSUBSHELL:
220 new->nredir.redirect = copynode(n->nredir.redirect);
221 new->nredir.n = copynode(n->nredir.n);
222+ new->nredir.linno = n->nredir.linno;
223 break;
224 case NAND:
225 case NOR:
226@@ -8654,10 +8684,12 @@ copynode(union node *n)
227 new->nfor.var = nodeckstrdup(n->nfor.var);
228 new->nfor.body = copynode(n->nfor.body);
229 new->nfor.args = copynode(n->nfor.args);
230+ new->nfor.linno = n->nfor.linno;
231 break;
232 case NCASE:
233 new->ncase.cases = copynode(n->ncase.cases);
234 new->ncase.expr = copynode(n->ncase.expr);
235+ new->ncase.linno = n->ncase.linno;
236 break;
237 case NCLIST:
238 new->nclist.body = copynode(n->nclist.body);
239@@ -8665,6 +8697,10 @@ copynode(union node *n)
240 new->nclist.next = copynode(n->nclist.next);
241 break;
242 case NDEFUN:
243+ new->ndefun.body = copynode(n->ndefun.body);
244+ new->ndefun.text = nodeckstrdup(n->ndefun.text);
245+ new->ndefun.linno = n->ndefun.linno;
246+ break;
247 case NARG:
248 new->narg.backquote = copynodelist(n->narg.backquote);
249 new->narg.text = nodeckstrdup(n->narg.text);
250@@ -8733,7 +8769,7 @@ defun(union node *func)
251 INT_OFF;
252 entry.cmdtype = CMDFUNCTION;
253 entry.u.func = copyfunc(func);
254- addcmdentry(func->narg.text, &entry);
255+ addcmdentry(func->ndefun.text, &entry);
256 INT_ON;
257 }
258
259@@ -8743,8 +8779,8 @@ defun(union node *func)
260 #define SKIPFUNC (1 << 2)
261 static smallint evalskip; /* set to SKIPxxx if we are skipping commands */
262 static int skipcount; /* number of levels to skip */
263-static int funcnest; /* depth of function calls */
264 static int loopnest; /* current loop nesting level */
265+static int funcline; /* starting line number of current function, or 0 if not in a function */
266
267 /* Forward decl way out to parsing code - dotrap needs it */
268 static int evalstring(char *s, int flags);
269@@ -8839,6 +8875,9 @@ evaltree(union node *n, int flags)
270 status = !evaltree(n->nnot.com, EV_TESTED);
271 goto setstatus;
272 case NREDIR:
273+ errlinno = lineno = n->nredir.linno;
274+ if (funcline)
275+ lineno -= funcline - 1;
276 expredir(n->nredir.redirect);
277 pushredir(n->nredir.redirect);
278 status = redirectsafe(n->nredir.redirect, REDIR_PUSH);
279@@ -8993,6 +9032,10 @@ evalfor(union node *n, int flags)
280 struct stackmark smark;
281 int status = 0;
282
283+ errlinno = lineno = n->ncase.linno;
284+ if (funcline)
285+ lineno -= funcline - 1;
286+
287 setstackmark(&smark);
288 arglist.list = NULL;
289 arglist.lastp = &arglist.list;
290@@ -9024,6 +9067,10 @@ evalcase(union node *n, int flags)
291 struct stackmark smark;
292 int status = 0;
293
294+ errlinno = lineno = n->ncase.linno;
295+ if (funcline)
296+ lineno -= funcline - 1;
297+
298 setstackmark(&smark);
299 arglist.list = NULL;
300 arglist.lastp = &arglist.list;
301@@ -9058,6 +9105,10 @@ evalsubshell(union node *n, int flags)
302 int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */
303 int status;
304
305+ errlinno = lineno = n->nredir.linno;
306+ if (funcline)
307+ lineno -= funcline - 1;
308+
309 expredir(n->nredir.redirect);
310 if (!backgnd && (flags & EV_EXIT) && !may_have_traps)
311 goto nofork;
312@@ -9365,8 +9416,10 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
313 struct jmploc *volatile savehandler;
314 struct jmploc jmploc;
315 int e;
316+ int savefuncline;
317
318 saveparam = shellparam;
319+ savefuncline = funcline;
320 savehandler = exception_handler;
321 e = setjmp(jmploc.loc);
322 if (e) {
323@@ -9376,7 +9429,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
324 exception_handler = &jmploc;
325 shellparam.malloced = 0;
326 func->count++;
327- funcnest++;
328+ funcline = func->n.ndefun.linno;
329 INT_ON;
330 shellparam.nparam = argc - 1;
331 shellparam.p = argv + 1;
332@@ -9385,11 +9438,11 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
333 shellparam.optoff = -1;
334 #endif
335 pushlocalvars();
336- evaltree(func->n.narg.next, flags & EV_TESTED);
337+ evaltree(func->n.ndefun.body, flags & EV_TESTED);
338 poplocalvars(0);
339 funcdone:
340 INT_OFF;
341- funcnest--;
342+ funcline = savefuncline;
343 freefunc(func);
344 freeparam(&shellparam);
345 shellparam = saveparam;
346@@ -9753,6 +9806,10 @@ evalcommand(union node *cmd, int flags)
347 char **nargv;
348 smallint cmd_is_exec;
349
350+ errlinno = lineno = cmd->ncmd.linno;
351+ if (funcline)
352+ lineno -= funcline - 1;
353+
354 /* First expand the arguments. */
355 TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
356 setstackmark(&smark);
357@@ -9798,7 +9855,7 @@ evalcommand(union node *cmd, int flags)
358 *nargv = NULL;
359
360 lastarg = NULL;
361- if (iflag && funcnest == 0 && argc > 0)
362+ if (iflag && funcline == 0 && argc > 0)
363 lastarg = nargv[-1];
364
365 expredir(cmd->ncmd.redirect);
366@@ -11317,6 +11374,7 @@ simplecmd(void)
367 union node *vars, **vpp;
368 union node **rpp, *redir;
369 int savecheckkwd;
370+ int savelinno;
371 #if BASH_TEST2
372 smallint double_brackets_flag = 0;
373 #endif
374@@ -11330,6 +11388,7 @@ simplecmd(void)
375 rpp = &redir;
376
377 savecheckkwd = CHKALIAS;
378+ savelinno = g_parsefile->linno;
379 for (;;) {
380 int t;
381 checkkwd = savecheckkwd;
382@@ -11419,7 +11478,9 @@ simplecmd(void)
383 }
384 n->type = NDEFUN;
385 checkkwd = CHKNL | CHKKWD | CHKALIAS;
386- n->narg.next = parse_command();
387+ n->ndefun.text = n->narg.text;
388+ n->ndefun.linno = g_parsefile->linno;
389+ n->ndefun.body = parse_command();
390 return n;
391 }
392 IF_BASH_FUNCTION(function_flag = 0;)
393@@ -11435,6 +11496,7 @@ simplecmd(void)
394 *rpp = NULL;
395 n = stzalloc(sizeof(struct ncmd));
396 n->type = NCMD;
397+ n->ncmd.linno = savelinno;
398 n->ncmd.args = args;
399 n->ncmd.assign = vars;
400 n->ncmd.redirect = redir;
401@@ -11450,10 +11512,13 @@ parse_command(void)
402 union node *redir, **rpp;
403 union node **rpp2;
404 int t;
405+ int savelinno;
406
407 redir = NULL;
408 rpp2 = &redir;
409
410+ savelinno = g_parsefile->linno;
411+
412 switch (readtoken()) {
413 default:
414 raise_error_unexpected_syntax(-1);
415@@ -11504,6 +11569,7 @@ parse_command(void)
416 raise_error_syntax("bad for loop variable");
417 n1 = stzalloc(sizeof(struct nfor));
418 n1->type = NFOR;
419+ n1->nfor.linno = savelinno;
420 n1->nfor.var = wordtext;
421 checkkwd = CHKNL | CHKKWD | CHKALIAS;
422 if (readtoken() == TIN) {
423@@ -11544,6 +11610,7 @@ parse_command(void)
424 case TCASE:
425 n1 = stzalloc(sizeof(struct ncase));
426 n1->type = NCASE;
427+ n1->ncase.linno = savelinno;
428 if (readtoken() != TWORD)
429 raise_error_unexpected_syntax(TWORD);
430 n1->ncase.expr = n2 = stzalloc(sizeof(struct narg));
431@@ -11595,6 +11662,7 @@ parse_command(void)
432 case TLP:
433 n1 = stzalloc(sizeof(struct nredir));
434 n1->type = NSUBSHELL;
435+ n1->nredir.linno = savelinno;
436 n1->nredir.n = list(0);
437 /*n1->nredir.redirect = NULL; - stzalloc did it */
438 t = TRP;
439@@ -11628,6 +11696,7 @@ parse_command(void)
440 if (n1->type != NSUBSHELL) {
441 n2 = stzalloc(sizeof(struct nredir));
442 n2->type = NREDIR;
443+ n2->nredir.linno = savelinno;
444 n2->nredir.n = n1;
445 n1 = n2;
446 }
447@@ -11726,10 +11795,8 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
448 IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */
449 IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
450 int dqvarnest; /* levels of variables expansion within double quotes */
451-
452 IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
453
454- startlinno = g_parsefile->linno;
455 bqlist = NULL;
456 quotef = 0;
457 IF_FEATURE_SH_MATH(prevsyntax = 0;)
458@@ -11906,7 +11973,6 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
459 if (syntax != BASESYNTAX && eofmark == NULL)
460 raise_error_syntax("unterminated quoted string");
461 if (varnest != 0) {
462- startlinno = g_parsefile->linno;
463 /* { */
464 raise_error_syntax("missing '}'");
465 }
466@@ -12298,7 +12364,6 @@ parsebackq: {
467
468 case PEOF:
469 IF_ASH_ALIAS(case PEOA:)
470- startlinno = g_parsefile->linno;
471 raise_error_syntax("EOF in backquote substitution");
472
473 case '\n':
474@@ -12380,8 +12445,6 @@ parsearith: {
475 * quoted.
476 * If the token is TREDIR, then we set redirnode to a structure containing
477 * the redirection.
478- * In all cases, the variable startlinno is set to the number of the line
479- * on which the token starts.
480 *
481 * [Change comment: here documents and internal procedures]
482 * [Readtoken shouldn't have any arguments. Perhaps we should make the
483@@ -12419,7 +12482,6 @@ xxreadtoken(void)
484 return lasttoken;
485 }
486 setprompt_if(needprompt, 2);
487- startlinno = g_parsefile->linno;
488 for (;;) { /* until token or start of word found */
489 c = pgetc();
490 if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA))
491@@ -12480,7 +12542,6 @@ xxreadtoken(void)
492 return lasttoken;
493 }
494 setprompt_if(needprompt, 2);
495- startlinno = g_parsefile->linno;
496 for (;;) { /* until token or start of word found */
497 c = pgetc();
498 switch (c) {
diff --git a/shell/ash_test/ash-getopts/getopt_optarg.right b/shell/ash_test/ash-getopts/getopt_optarg.right
new file mode 100644
index 000000000..dff28de57
--- /dev/null
+++ b/shell/ash_test/ash-getopts/getopt_optarg.right
@@ -0,0 +1,18 @@
1*** no OPTIND, optstring:'w:et' args:-q -w e -r -t -y
2Illegal option -q
3var:'?' OPTIND:2 OPTARG:''
4var:'w' OPTIND:4 OPTARG:'e'
5Illegal option -r
6var:'?' OPTIND:5 OPTARG:''
7var:'t' OPTIND:6 OPTARG:''
8Illegal option -y
9var:'?' OPTIND:7 OPTARG:''
10exited: var:'?' OPTIND:7 OPTARG:''
11*** OPTIND=0, optstring:'w:et' args:-w 1 -w2 -w -e -e -t -t
12var:'w' OPTIND:3 OPTARG:'1'
13var:'w' OPTIND:4 OPTARG:'2'
14var:'w' OPTIND:6 OPTARG:'-e'
15var:'e' OPTIND:7 OPTARG:''
16var:'t' OPTIND:8 OPTARG:''
17var:'t' OPTIND:9 OPTARG:''
18exited: var:'?' OPTIND:9 OPTARG:''
diff --git a/shell/ash_test/ash-getopts/getopt_optarg.tests b/shell/ash_test/ash-getopts/getopt_optarg.tests
new file mode 100755
index 000000000..33682e868
--- /dev/null
+++ b/shell/ash_test/ash-getopts/getopt_optarg.tests
@@ -0,0 +1,18 @@
1set -- -q -w e -r -t -y
2echo "*** no OPTIND, optstring:'w:et' args:$*"
3var=QWERTY
4OPTARG=ASDFGH
5while getopts "w:et" var; do
6 echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
7 OPTARG=ASDFGH
8done
9echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
10
11set -- -w 1 -w2 -w -e -e -t -t
12echo "*** OPTIND=0, optstring:'w:et' args:$*"
13OPTIND=0
14while getopts "w:et" var; do
15 echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
16 OPTARG=ASDFGH
17done
18echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
diff --git a/shell/ash_test/ash-getopts/getopt_positional.right b/shell/ash_test/ash-getopts/getopt_positional.right
new file mode 100644
index 000000000..37d0ec845
--- /dev/null
+++ b/shell/ash_test/ash-getopts/getopt_positional.right
@@ -0,0 +1,6 @@
1*** no OPTIND, optstring:'we' args:-q -w -e r -t -y
2Illegal option -q
3var:'?' OPTIND:2
4var:'w' OPTIND:3
5var:'e' OPTIND:4
6exited: var:'?' OPTIND:4
diff --git a/shell/ash_test/ash-getopts/getopt_positional.tests b/shell/ash_test/ash-getopts/getopt_positional.tests
new file mode 100755
index 000000000..a5404a2a0
--- /dev/null
+++ b/shell/ash_test/ash-getopts/getopt_positional.tests
@@ -0,0 +1,8 @@
1set -- -q -w -e r -t -y
2echo "*** no OPTIND, optstring:'we' args:$*"
3var=QWERTY
4while getopts "we" var; do
5 echo "var:'$var' OPTIND:$OPTIND"
6done
7# unfortunately, "rc:0" is shown since while's overall exitcode is "success"
8echo "exited: var:'$var' OPTIND:$OPTIND"
diff --git a/shell/ash_test/ash-getopts/getopt_silent.right b/shell/ash_test/ash-getopts/getopt_silent.right
new file mode 100644
index 000000000..03d4eb149
--- /dev/null
+++ b/shell/ash_test/ash-getopts/getopt_silent.right
@@ -0,0 +1,6 @@
1*** optstring:':ac' args:-a -b -c
21 rc:0 var:'a' OPTIND:2 OPTARG:''
32 rc:0 var:'?' OPTIND:3 OPTARG:'b'
43 rc:0 var:'c' OPTIND:4 OPTARG:''
54 rc:1 var:'?' OPTIND:4 OPTARG:''
65 rc:1 var:'?' OPTIND:4 OPTARG:''
diff --git a/shell/ash_test/ash-getopts/getopt_silent.tests b/shell/ash_test/ash-getopts/getopt_silent.tests
new file mode 100755
index 000000000..097d7ba85
--- /dev/null
+++ b/shell/ash_test/ash-getopts/getopt_silent.tests
@@ -0,0 +1,23 @@
1# Open Group Base Specifications Issue 7:
2# """
3# If an unknown option is met, VAR shall be set to "?". In this case,
4# if the first character in optstring is ":", OPTARG shall be set
5# to the option character found, but no output shall be written
6# to standard error; otherwise, the shell variable OPTARG shall be
7# unset and a diagnostic message shall be written to standard error."
8# ...
9# If an option-argument is missing:
10# If the first character of optstring is ":", VAR shall be set to ":"
11# and OPTARG shall be set to the option character found.
12# """
13
14echo "*** optstring:':ac' args:-a -b -c"
15getopts ":ac" var -a -b -c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
16getopts ":ac" var -a -b -c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
17getopts ":ac" var -a -b -c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
18getopts ":ac" var -a -b -c; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
19# Previous line should result in "rc:1", which is normally treated
20# in getopts loops as exit condition.
21# Nevertheless, let's verify that calling it yet another time doesn't do
22# anything weird:
23getopts ":ac" var -a -b -c; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
diff --git a/shell/ash_test/ash-getopts/getopt_simple.right b/shell/ash_test/ash-getopts/getopt_simple.right
new file mode 100644
index 000000000..07e3c57f5
--- /dev/null
+++ b/shell/ash_test/ash-getopts/getopt_simple.right
@@ -0,0 +1,34 @@
1*** no OPTIND, optstring:'ab' args:-a -b c
2var:'a' OPTIND:2
3var:'b' OPTIND:3
4exited: rc:0 var:'?' OPTIND:3
5*** OPTIND=1, optstring:'ab' args:-a -b c
6var:'a' OPTIND:2
7var:'b' OPTIND:3
8exited: rc:0 var:'?' OPTIND:3
9*** OPTIND=0, optstring:'ab' args:-a -b c
10var:'a' OPTIND:2
11var:'b' OPTIND:3
12exited: rc:0 var:'?' OPTIND:3
13*** unset OPTIND, optstring:'ab' args:-a -b c
14var:'a' OPTIND:2
15var:'b' OPTIND:3
16exited: rc:0 var:'?' OPTIND:3
17*** optstring:'ab' args:-a -b c
181 rc:0 var:'a' OPTIND:2
192 rc:0 var:'b' OPTIND:3
203 rc:1 var:'?' OPTIND:3
21*** unset OPTIND, optstring:'ab' args:-a c -c -b d
22var:'a' OPTIND:2
23exited: rc:0 var:'?' OPTIND:2
24*** unset OPTIND, optstring:'ab' args:-a -c -b d
25var:'a' OPTIND:2
26Illegal option -c
27var:'?' OPTIND:3
28var:'b' OPTIND:4
29exited: rc:0 var:'?' OPTIND:4
30*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d
31var:'a' OPTIND:2
32var:'?' OPTIND:3
33var:'b' OPTIND:4
34exited: rc:0 var:'?' OPTIND:4
diff --git a/shell/ash_test/ash-getopts/getopt_simple.tests b/shell/ash_test/ash-getopts/getopt_simple.tests
new file mode 100755
index 000000000..8615ae366
--- /dev/null
+++ b/shell/ash_test/ash-getopts/getopt_simple.tests
@@ -0,0 +1,75 @@
1# Simple usage cases for getopts.
2#
3# OPTIND is either not touched at all (first loop with getopts,
4# relying on shell startup init), or getopts state is reset
5# before new loop with "unset OPTIND", "OPTIND=1" or "OPTIND=0".
6#
7# Each option is a separate argument (no "-abc"). This conceptually
8# needs only $OPTIND to hold getopts state.
9#
10# We check that loop does not stop on unknown option (sets "?"),
11# stops on _first_ non-option argument.
12
13echo "*** no OPTIND, optstring:'ab' args:-a -b c"
14var=QWERTY
15while getopts "ab" var -a -b c; do
16 echo "var:'$var' OPTIND:$OPTIND"
17done
18# unfortunately, "rc:0" is shown since while's overall exitcode is "success"
19echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
20
21# Resetting behavior =1
22echo "*** OPTIND=1, optstring:'ab' args:-a -b c"
23OPTIND=1
24while getopts "ab" var -a -b c; do
25 echo "var:'$var' OPTIND:$OPTIND"
26done
27echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
28
29# Resetting behavior =0
30echo "*** OPTIND=0, optstring:'ab' args:-a -b c"
31OPTIND=0
32while getopts "ab" var -a -b c; do
33 echo "var:'$var' OPTIND:$OPTIND"
34done
35echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
36
37# Resetting behavior "unset"
38echo "*** unset OPTIND, optstring:'ab' args:-a -b c"
39unset OPTIND
40while getopts "ab" var -a -b c; do
41 echo "var:'$var' OPTIND:$OPTIND"
42done
43echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
44
45# What is the final exitcode?
46echo "*** optstring:'ab' args:-a -b c"
47unset OPTIND
48getopts "ab" var -a -b c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND"
49getopts "ab" var -a -b c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND"
50getopts "ab" var -a -b c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND"
51
52# Where would it stop? c or -c?
53echo "*** unset OPTIND, optstring:'ab' args:-a c -c -b d"
54unset OPTIND
55while getopts "ab" var -a c -c -b d; do
56 echo "var:'$var' OPTIND:$OPTIND"
57done
58echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
59
60# What happens on unknown option?
61echo "*** unset OPTIND, optstring:'ab' args:-a -c -b d"
62unset OPTIND
63while getopts "ab" var -a -c -b d; do
64 echo "var:'$var' OPTIND:$OPTIND"
65done
66echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
67
68# ORTERR=0 suppresses error message?
69echo "*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d"
70unset OPTIND
71OPTERR=0
72while getopts "ab" var -a -c -b d; do
73 echo "var:'$var' OPTIND:$OPTIND"
74done
75echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
diff --git a/shell/ash_test/ash-getopts/getopt_test_libc_bug.right b/shell/ash_test/ash-getopts/getopt_test_libc_bug.right
new file mode 100644
index 000000000..f6ad4602d
--- /dev/null
+++ b/shell/ash_test/ash-getopts/getopt_test_libc_bug.right
@@ -0,0 +1,26 @@
1*** optstring:'ac' args:-a -b -c -d e
21 rc:0 var:'a' OPTIND:2 OPTARG:''
3Illegal option -b
42 rc:0 var:'?' OPTIND:3 OPTARG:''
53 rc:0 var:'c' OPTIND:4 OPTARG:''
6Illegal option -d
74 rc:0 var:'?' OPTIND:5 OPTARG:''
85 rc:1 var:'?' OPTIND:5 OPTARG:''
9
10*** optstring:'ac' args:-a -b -c -d e
111 rc:0 var:'a' OPTIND:2 OPTARG:''
12Illegal option -b
132 rc:0 var:'?' OPTIND:3 OPTARG:''
143 rc:0 var:'c' OPTIND:4 OPTARG:''
15Illegal option -d
164 rc:0 var:'?' OPTIND:5 OPTARG:''
175 rc:1 var:'?' OPTIND:5 OPTARG:''
18
19*** optstring:'ac' args:-a -b -c -d e
201 rc:0 var:'a' OPTIND:2 OPTARG:''
21Illegal option -b
222 rc:0 var:'?' OPTIND:3 OPTARG:''
233 rc:0 var:'c' OPTIND:4 OPTARG:''
24Illegal option -d
254 rc:0 var:'?' OPTIND:5 OPTARG:''
265 rc:1 var:'?' OPTIND:5 OPTARG:''
diff --git a/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests b/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests
new file mode 100755
index 000000000..fcaac81a2
--- /dev/null
+++ b/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests
@@ -0,0 +1,38 @@
1# This test can fail with libc with buggy getopt() implementation.
2# If getopt() wants to parse multi-option args (-abc),
3# it needs to remember a position within current arg.
4#
5# If this position is kept as a POINTER, not an offset,
6# and if argv[] ADDRESSES (not contents!) change, it blows up.
7
8echo "*** optstring:'ac' args:-a -b -c -d e"
9getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
10getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
11getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
12getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
13getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
14
15# Above: args are (usually) in the same locations in memory.
16# Below: variable allocations change the location.
17
18echo
19echo "*** optstring:'ac' args:-a -b -c -d e"
20unset OPTIND
21OPTARG=QWERTY; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
22NEWVAR=NEWVAL; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
23VAR111=NEWVAL; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
24VAR222=NEWVAL; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
25VAR333=NEWVAL; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
26
27# Slightly different attempts to force reallocations
28
29echo
30echo "*** optstring:'ac' args:-a -b -c -d e"
31unset OPTIND
32export OPTARG; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
33export NEWVAR; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
34export VAR111; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
35export VAR222; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
36export VAR333; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
37
38# All copies of code above should generate identical output
diff --git a/shell/ash_test/ash-parsing/groups_and_keywords2.right b/shell/ash_test/ash-parsing/groups_and_keywords2.right
new file mode 100644
index 000000000..3fcbeb662
--- /dev/null
+++ b/shell/ash_test/ash-parsing/groups_and_keywords2.right
@@ -0,0 +1,3 @@
1./groups_and_keywords2.tests: eval: line 1: syntax error: unexpected ")"
2Fail:2
3./groups_and_keywords2.tests: line 8: syntax error: unexpected ")"
diff --git a/shell/ash_test/ash-parsing/groups_and_keywords2.tests b/shell/ash_test/ash-parsing/groups_and_keywords2.tests
new file mode 100755
index 000000000..ab33b909f
--- /dev/null
+++ b/shell/ash_test/ash-parsing/groups_and_keywords2.tests
@@ -0,0 +1,9 @@
1# This is an error
2(eval 'if() { echo; }')
3echo Fail:$?
4# ^^^^^^ bash prints 1, but interactively it sets $? = 2
5# we print 2
6
7# This is an error, and it aborts in script
8if() { echo; }
9echo Not reached
diff --git a/shell/ash_test/ash-vars/param_expand_assign.right b/shell/ash_test/ash-vars/param_expand_assign.right
index 9b07d8cd4..6e9ea1379 100644
--- a/shell/ash_test/ash-vars/param_expand_assign.right
+++ b/shell/ash_test/ash-vars/param_expand_assign.right
@@ -1,6 +1,6 @@
1SHELL: line 1: syntax error: bad substitution 1SHELL: line 1: syntax error: bad substitution
2SHELL: line 1: syntax error: bad substitution 2SHELL: line 1: syntax error: bad substitution
30 3SHELL: line 1: syntax error: bad substitution
40 40
5SHELL: line 1: 1: bad variable name 5SHELL: line 1: 1: bad variable name
6SHELL: line 1: 1: bad variable name 6SHELL: line 1: 1: bad variable name
diff --git a/shell/ash_test/printenv.c b/shell/ash_test/printenv.c
index c0c5e197c..c86308d3b 100644
--- a/shell/ash_test/printenv.c
+++ b/shell/ash_test/printenv.c
@@ -56,7 +56,7 @@ main (argc, argv)
56 if (**argv == **envp && strncmp (*envp, *argv, len) == 0) 56 if (**argv == **envp && strncmp (*envp, *argv, len) == 0)
57 { 57 {
58 eval = *envp + len; 58 eval = *envp + len;
59 /* If the environment variable doesn't have an `=', ignore it. */ 59 /* If the environment variable doesn't have an '=', ignore it. */
60 if (*eval == '=') 60 if (*eval == '=')
61 { 61 {
62 puts (eval + 1); 62 puts (eval + 1);
diff --git a/shell/cttyhack.c b/shell/cttyhack.c
index 9004b4763..849fe9e48 100644
--- a/shell/cttyhack.c
+++ b/shell/cttyhack.c
@@ -6,7 +6,7 @@
6 */ 6 */
7#include "libbb.h" 7#include "libbb.h"
8 8
9//applet:IF_CTTYHACK(APPLET(cttyhack, BB_DIR_BIN, BB_SUID_DROP)) 9//applet:IF_CTTYHACK(APPLET_NOEXEC(cttyhack, cttyhack, BB_DIR_BIN, BB_SUID_DROP, cttyhack))
10 10
11//kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o 11//kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o
12 12
diff --git a/shell/hush.c b/shell/hush.c
index 9f946d82f..cdc3a8618 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -48,7 +48,7 @@
48 * tilde expansion 48 * tilde expansion
49 * aliases 49 * aliases
50 * builtins mandated by standards we don't support: 50 * builtins mandated by standards we don't support:
51 * [un]alias, command, fc, getopts, times: 51 * [un]alias, command, fc:
52 * command -v CMD: print "/path/to/CMD" 52 * command -v CMD: print "/path/to/CMD"
53 * prints "CMD" for builtins 53 * prints "CMD" for builtins
54 * prints "alias ALIAS='EXPANSION'" for aliases 54 * prints "alias ALIAS='EXPANSION'" for aliases
@@ -58,8 +58,6 @@
58 * (can use this to override standalone shell as well) 58 * (can use this to override standalone shell as well)
59 * -p: use default $PATH 59 * -p: use default $PATH
60 * command BLTIN: disables special-ness (e.g. errors do not abort) 60 * command BLTIN: disables special-ness (e.g. errors do not abort)
61 * getopts: getopt() for shells
62 * times: print getrusage(SELF/CHILDREN).ru_utime/ru_stime
63 * fc -l[nr] [BEG] [END]: list range of commands in history 61 * fc -l[nr] [BEG] [END]: list range of commands in history
64 * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands 62 * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands
65 * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP 63 * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP
@@ -265,6 +263,11 @@
265//config: default y 263//config: default y
266//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH 264//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
267//config: 265//config:
266//config:config HUSH_TIMES
267//config: bool "times builtin"
268//config: default y
269//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
270//config:
268//config:config HUSH_READ 271//config:config HUSH_READ
269//config: bool "read builtin" 272//config: bool "read builtin"
270//config: default y 273//config: default y
@@ -290,6 +293,11 @@
290//config: default y 293//config: default y
291//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH 294//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
292//config: 295//config:
296//config:config HUSH_GETOPTS
297//config: bool "getopts builtin"
298//config: default y
299//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
300//config:
293//config:config HUSH_MEMLEAK 301//config:config HUSH_MEMLEAK
294//config: bool "memleak builtin (debugging)" 302//config: bool "memleak builtin (debugging)"
295//config: default n 303//config: default n
@@ -325,6 +333,7 @@
325#if ENABLE_HUSH_CASE 333#if ENABLE_HUSH_CASE
326# include <fnmatch.h> 334# include <fnmatch.h>
327#endif 335#endif
336#include <sys/times.h>
328#include <sys/utsname.h> /* for setting $HOSTNAME */ 337#include <sys/utsname.h> /* for setting $HOSTNAME */
329 338
330#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ 339#include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */
@@ -352,6 +361,7 @@
352#define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT 361#define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT
353#define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT 362#define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT
354#define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST) 363#define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
364#define BASH_READ_D ENABLE_HUSH_BASH_COMPAT
355 365
356 366
357/* Build knobs */ 367/* Build knobs */
@@ -977,6 +987,9 @@ static int builtin_readonly(char **argv) FAST_FUNC;
977static int builtin_fg_bg(char **argv) FAST_FUNC; 987static int builtin_fg_bg(char **argv) FAST_FUNC;
978static int builtin_jobs(char **argv) FAST_FUNC; 988static int builtin_jobs(char **argv) FAST_FUNC;
979#endif 989#endif
990#if ENABLE_HUSH_GETOPTS
991static int builtin_getopts(char **argv) FAST_FUNC;
992#endif
980#if ENABLE_HUSH_HELP 993#if ENABLE_HUSH_HELP
981static int builtin_help(char **argv) FAST_FUNC; 994static int builtin_help(char **argv) FAST_FUNC;
982#endif 995#endif
@@ -1010,6 +1023,9 @@ static int builtin_trap(char **argv) FAST_FUNC;
1010#if ENABLE_HUSH_TYPE 1023#if ENABLE_HUSH_TYPE
1011static int builtin_type(char **argv) FAST_FUNC; 1024static int builtin_type(char **argv) FAST_FUNC;
1012#endif 1025#endif
1026#if ENABLE_HUSH_TIMES
1027static int builtin_times(char **argv) FAST_FUNC;
1028#endif
1013static int builtin_true(char **argv) FAST_FUNC; 1029static int builtin_true(char **argv) FAST_FUNC;
1014#if ENABLE_HUSH_UMASK 1030#if ENABLE_HUSH_UMASK
1015static int builtin_umask(char **argv) FAST_FUNC; 1031static int builtin_umask(char **argv) FAST_FUNC;
@@ -1070,6 +1086,9 @@ static const struct built_in_command bltins1[] = {
1070#if ENABLE_HUSH_JOB 1086#if ENABLE_HUSH_JOB
1071 BLTIN("fg" , builtin_fg_bg , "Bring job to foreground"), 1087 BLTIN("fg" , builtin_fg_bg , "Bring job to foreground"),
1072#endif 1088#endif
1089#if ENABLE_HUSH_GETOPTS
1090 BLTIN("getopts" , builtin_getopts , NULL),
1091#endif
1073#if ENABLE_HUSH_HELP 1092#if ENABLE_HUSH_HELP
1074 BLTIN("help" , builtin_help , NULL), 1093 BLTIN("help" , builtin_help , NULL),
1075#endif 1094#endif
@@ -1104,6 +1123,9 @@ static const struct built_in_command bltins1[] = {
1104#if BASH_SOURCE 1123#if BASH_SOURCE
1105 BLTIN("source" , builtin_source , NULL), 1124 BLTIN("source" , builtin_source , NULL),
1106#endif 1125#endif
1126#if ENABLE_HUSH_TIMES
1127 BLTIN("times" , builtin_times , NULL),
1128#endif
1107#if ENABLE_HUSH_TRAP 1129#if ENABLE_HUSH_TRAP
1108 BLTIN("trap" , builtin_trap , "Trap signals"), 1130 BLTIN("trap" , builtin_trap , "Trap signals"),
1109#endif 1131#endif
@@ -1272,7 +1294,7 @@ static void xxfree(void *ptr)
1272 * HUSH_DEBUG >= 2 prints line number in this file where it was detected. 1294 * HUSH_DEBUG >= 2 prints line number in this file where it was detected.
1273 */ 1295 */
1274#if HUSH_DEBUG < 2 1296#if HUSH_DEBUG < 2
1275# define die_if_script(lineno, ...) die_if_script(__VA_ARGS__) 1297# define msg_and_die_if_script(lineno, ...) msg_and_die_if_script(__VA_ARGS__)
1276# define syntax_error(lineno, msg) syntax_error(msg) 1298# define syntax_error(lineno, msg) syntax_error(msg)
1277# define syntax_error_at(lineno, msg) syntax_error_at(msg) 1299# define syntax_error_at(lineno, msg) syntax_error_at(msg)
1278# define syntax_error_unterm_ch(lineno, ch) syntax_error_unterm_ch(ch) 1300# define syntax_error_unterm_ch(lineno, ch) syntax_error_unterm_ch(ch)
@@ -1280,7 +1302,16 @@ static void xxfree(void *ptr)
1280# define syntax_error_unexpected_ch(lineno, ch) syntax_error_unexpected_ch(ch) 1302# define syntax_error_unexpected_ch(lineno, ch) syntax_error_unexpected_ch(ch)
1281#endif 1303#endif
1282 1304
1283static void die_if_script(unsigned lineno, const char *fmt, ...) 1305static void die_if_script(void)
1306{
1307 if (!G_interactive_fd) {
1308 if (G.last_exitcode) /* sometines it's 2, not 1 (bash compat) */
1309 xfunc_error_retval = G.last_exitcode;
1310 xfunc_die();
1311 }
1312}
1313
1314static void msg_and_die_if_script(unsigned lineno, const char *fmt, ...)
1284{ 1315{
1285 va_list p; 1316 va_list p;
1286 1317
@@ -1290,8 +1321,7 @@ static void die_if_script(unsigned lineno, const char *fmt, ...)
1290 va_start(p, fmt); 1321 va_start(p, fmt);
1291 bb_verror_msg(fmt, p, NULL); 1322 bb_verror_msg(fmt, p, NULL);
1292 va_end(p); 1323 va_end(p);
1293 if (!G_interactive_fd) 1324 die_if_script();
1294 xfunc_die();
1295} 1325}
1296 1326
1297static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg) 1327static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
@@ -1300,16 +1330,20 @@ static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg)
1300 bb_error_msg("syntax error: %s", msg); 1330 bb_error_msg("syntax error: %s", msg);
1301 else 1331 else
1302 bb_error_msg("syntax error"); 1332 bb_error_msg("syntax error");
1333 die_if_script();
1303} 1334}
1304 1335
1305static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg) 1336static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg)
1306{ 1337{
1307 bb_error_msg("syntax error at '%s'", msg); 1338 bb_error_msg("syntax error at '%s'", msg);
1339 die_if_script();
1308} 1340}
1309 1341
1310static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s) 1342static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s)
1311{ 1343{
1312 bb_error_msg("syntax error: unterminated %s", s); 1344 bb_error_msg("syntax error: unterminated %s", s);
1345//? source4.tests fails: in bash, echo ${^} in script does not terminate the script
1346// die_if_script();
1313} 1347}
1314 1348
1315static void syntax_error_unterm_ch(unsigned lineno, char ch) 1349static void syntax_error_unterm_ch(unsigned lineno, char ch)
@@ -1327,17 +1361,18 @@ static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch)
1327 bb_error_msg("hush.c:%u", lineno); 1361 bb_error_msg("hush.c:%u", lineno);
1328#endif 1362#endif
1329 bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg); 1363 bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg);
1364 die_if_script();
1330} 1365}
1331 1366
1332#if HUSH_DEBUG < 2 1367#if HUSH_DEBUG < 2
1333# undef die_if_script 1368# undef msg_and_die_if_script
1334# undef syntax_error 1369# undef syntax_error
1335# undef syntax_error_at 1370# undef syntax_error_at
1336# undef syntax_error_unterm_ch 1371# undef syntax_error_unterm_ch
1337# undef syntax_error_unterm_str 1372# undef syntax_error_unterm_str
1338# undef syntax_error_unexpected_ch 1373# undef syntax_error_unexpected_ch
1339#else 1374#else
1340# define die_if_script(...) die_if_script(__LINE__, __VA_ARGS__) 1375# define msg_and_die_if_script(...) msg_and_die_if_script(__LINE__, __VA_ARGS__)
1341# define syntax_error(msg) syntax_error(__LINE__, msg) 1376# define syntax_error(msg) syntax_error(__LINE__, msg)
1342# define syntax_error_at(msg) syntax_error_at(__LINE__, msg) 1377# define syntax_error_at(msg) syntax_error_at(__LINE__, msg)
1343# define syntax_error_unterm_ch(ch) syntax_error_unterm_ch(__LINE__, ch) 1378# define syntax_error_unterm_ch(ch) syntax_error_unterm_ch(__LINE__, ch)
@@ -1800,7 +1835,7 @@ static void restore_ttypgrp_and__exit(void)
1800 * echo END_OF_SCRIPT 1835 * echo END_OF_SCRIPT
1801 * lseeks fd in input FILE object from EOF to "e" in "echo END_OF_SCRIPT". 1836 * lseeks fd in input FILE object from EOF to "e" in "echo END_OF_SCRIPT".
1802 * This makes "echo END_OF_SCRIPT" executed twice. 1837 * This makes "echo END_OF_SCRIPT" executed twice.
1803 * Similar problems can be seen with die_if_script() -> xfunc_die() 1838 * Similar problems can be seen with msg_and_die_if_script() -> xfunc_die()
1804 * and in `cmd` handling. 1839 * and in `cmd` handling.
1805 * If set as die_func(), this makes xfunc_die() exit via _exit(), not exit(): 1840 * If set as die_func(), this makes xfunc_die() exit via _exit(), not exit():
1806 */ 1841 */
@@ -1966,6 +2001,9 @@ static int check_and_run_traps(void)
1966 break; 2001 break;
1967#if ENABLE_HUSH_JOB 2002#if ENABLE_HUSH_JOB
1968 case SIGHUP: { 2003 case SIGHUP: {
2004//TODO: why are we doing this? ash and dash don't do this,
2005//they have no handler for SIGHUP at all,
2006//they rely on kernel to send SIGHUP+SIGCONT to orphaned process groups
1969 struct pipe *job; 2007 struct pipe *job;
1970 debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig); 2008 debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig);
1971 /* bash is observed to signal whole process groups, 2009 /* bash is observed to signal whole process groups,
@@ -2411,18 +2449,17 @@ static int get_user_input(struct in_str *i)
2411 /* buglet: SIGINT will not make new prompt to appear _at once_, 2449 /* buglet: SIGINT will not make new prompt to appear _at once_,
2412 * only after <Enter>. (^C works immediately) */ 2450 * only after <Enter>. (^C works immediately) */
2413 r = read_line_input(G.line_input_state, prompt_str, 2451 r = read_line_input(G.line_input_state, prompt_str,
2414 G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, 2452 G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1
2415 /*timeout*/ -1
2416 ); 2453 );
2417 /* read_line_input intercepts ^C, "convert" it to SIGINT */ 2454 /* read_line_input intercepts ^C, "convert" it to SIGINT */
2418 if (r == 0) { 2455 if (r == 0)
2419 write(STDOUT_FILENO, "^C", 2);
2420 raise(SIGINT); 2456 raise(SIGINT);
2421 }
2422 check_and_run_traps(); 2457 check_and_run_traps();
2423 if (r != 0 && !G.flag_SIGINT) 2458 if (r != 0 && !G.flag_SIGINT)
2424 break; 2459 break;
2425 /* ^C or SIGINT: repeat */ 2460 /* ^C or SIGINT: repeat */
2461 /* bash prints ^C even on real SIGINT (non-kbd generated) */
2462 write(STDOUT_FILENO, "^C", 2);
2426 G.last_exitcode = 128 + SIGINT; 2463 G.last_exitcode = 128 + SIGINT;
2427 } 2464 }
2428 if (r < 0) { 2465 if (r < 0) {
@@ -3384,7 +3421,7 @@ static int done_command(struct parse_context *ctx)
3384#if 0 /* Instead we emit error message at run time */ 3421#if 0 /* Instead we emit error message at run time */
3385 if (ctx->pending_redirect) { 3422 if (ctx->pending_redirect) {
3386 /* For example, "cmd >" (no filename to redirect to) */ 3423 /* For example, "cmd >" (no filename to redirect to) */
3387 die_if_script("syntax error: %s", "invalid redirect"); 3424 syntax_error("invalid redirect");
3388 ctx->pending_redirect = NULL; 3425 ctx->pending_redirect = NULL;
3389 } 3426 }
3390#endif 3427#endif
@@ -3950,7 +3987,7 @@ static int parse_redirect(struct parse_context *ctx,
3950#if 0 /* Instead we emit error message at run time */ 3987#if 0 /* Instead we emit error message at run time */
3951 if (ctx->pending_redirect) { 3988 if (ctx->pending_redirect) {
3952 /* For example, "cmd > <file" */ 3989 /* For example, "cmd > <file" */
3953 die_if_script("syntax error: %s", "invalid redirect"); 3990 syntax_error("invalid redirect");
3954 } 3991 }
3955#endif 3992#endif
3956 /* Set ctx->pending_redirect, so we know what to do at the 3993 /* Set ctx->pending_redirect, so we know what to do at the
@@ -5022,10 +5059,16 @@ static struct pipe *parse_stream(char **pstring,
5022 else 5059 else
5023 o_free_unsafe(&ctx.as_string); 5060 o_free_unsafe(&ctx.as_string);
5024#endif 5061#endif
5025 debug_leave(); 5062 if (ch != ';' && IS_NULL_PIPE(ctx.list_head)) {
5063 /* Example: bare "{ }", "()" */
5064 G.last_exitcode = 2; /* bash compat */
5065 syntax_error_unexpected_ch(ch);
5066 goto parse_error2;
5067 }
5026 debug_printf_parse("parse_stream return %p: " 5068 debug_printf_parse("parse_stream return %p: "
5027 "end_trigger char found\n", 5069 "end_trigger char found\n",
5028 ctx.list_head); 5070 ctx.list_head);
5071 debug_leave();
5029 return ctx.list_head; 5072 return ctx.list_head;
5030 } 5073 }
5031 } 5074 }
@@ -5283,8 +5326,8 @@ static struct pipe *parse_stream(char **pstring,
5283 /* proper use of this character is caught by end_trigger: 5326 /* proper use of this character is caught by end_trigger:
5284 * if we see {, we call parse_group(..., end_trigger='}') 5327 * if we see {, we call parse_group(..., end_trigger='}')
5285 * and it will match } earlier (not here). */ 5328 * and it will match } earlier (not here). */
5286 syntax_error_unexpected_ch(ch);
5287 G.last_exitcode = 2; 5329 G.last_exitcode = 2;
5330 syntax_error_unexpected_ch(ch);
5288 goto parse_error2; 5331 goto parse_error2;
5289 default: 5332 default:
5290 if (HUSH_DEBUG) 5333 if (HUSH_DEBUG)
@@ -5514,7 +5557,7 @@ static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p)
5514 if (errmsg_p) 5557 if (errmsg_p)
5515 *errmsg_p = math_state.errmsg; 5558 *errmsg_p = math_state.errmsg;
5516 if (math_state.errmsg) 5559 if (math_state.errmsg)
5517 die_if_script(math_state.errmsg); 5560 msg_and_die_if_script(math_state.errmsg);
5518 return res; 5561 return res;
5519} 5562}
5520#endif 5563#endif
@@ -5781,7 +5824,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5781 /* in bash, len=-n means strlen()-n */ 5824 /* in bash, len=-n means strlen()-n */
5782 len = (arith_t)strlen(val) - beg + len; 5825 len = (arith_t)strlen(val) - beg + len;
5783 if (len < 0) /* bash compat */ 5826 if (len < 0) /* bash compat */
5784 die_if_script("%s: substring expression < 0", var); 5827 msg_and_die_if_script("%s: substring expression < 0", var);
5785 } 5828 }
5786 if (len <= 0 || !val || beg >= strlen(val)) { 5829 if (len <= 0 || !val || beg >= strlen(val)) {
5787 arith_err: 5830 arith_err:
@@ -5795,7 +5838,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5795 } 5838 }
5796 debug_printf_varexp("val:'%s'\n", val); 5839 debug_printf_varexp("val:'%s'\n", val);
5797#else /* not (HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH) */ 5840#else /* not (HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH) */
5798 die_if_script("malformed ${%s:...}", var); 5841 msg_and_die_if_script("malformed ${%s:...}", var);
5799 val = NULL; 5842 val = NULL;
5800#endif 5843#endif
5801 } else { /* one of "-=+?" */ 5844 } else { /* one of "-=+?" */
@@ -5832,7 +5875,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5832 exp_word = to_be_freed; 5875 exp_word = to_be_freed;
5833 if (exp_op == '?') { 5876 if (exp_op == '?') {
5834 /* mimic bash message */ 5877 /* mimic bash message */
5835 die_if_script("%s: %s", 5878 msg_and_die_if_script("%s: %s",
5836 var, 5879 var,
5837 exp_word[0] 5880 exp_word[0]
5838 ? exp_word 5881 ? exp_word
@@ -5849,7 +5892,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5849 /* ${var=[word]} or ${var:=[word]} */ 5892 /* ${var=[word]} or ${var:=[word]} */
5850 if (isdigit(var[0]) || var[0] == '#') { 5893 if (isdigit(var[0]) || var[0] == '#') {
5851 /* mimic bash message */ 5894 /* mimic bash message */
5852 die_if_script("$%s: cannot assign in this way", var); 5895 msg_and_die_if_script("$%s: cannot assign in this way", var);
5853 val = NULL; 5896 val = NULL;
5854 } else { 5897 } else {
5855 char *new_var = xasprintf("%s=%s", var, val); 5898 char *new_var = xasprintf("%s=%s", var, val);
@@ -6698,7 +6741,8 @@ static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
6698 int moved_to; 6741 int moved_to;
6699 int i; 6742 int i;
6700 6743
6701 if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) { 6744 i = 0;
6745 if (sq) for (; sq[i].orig_fd >= 0; i++) {
6702 /* If we collide with an already moved fd... */ 6746 /* If we collide with an already moved fd... */
6703 if (fd == sq[i].moved_to) { 6747 if (fd == sq[i].moved_to) {
6704 sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd); 6748 sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd);
@@ -6726,7 +6770,8 @@ static struct squirrel *add_squirrel_closed(struct squirrel *sq, int fd)
6726{ 6770{
6727 int i; 6771 int i;
6728 6772
6729 if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) { 6773 i = 0;
6774 if (sq) for (; sq[i].orig_fd >= 0; i++) {
6730 /* If we collide with an already moved fd... */ 6775 /* If we collide with an already moved fd... */
6731 if (fd == sq[i].orig_fd) { 6776 if (fd == sq[i].orig_fd) {
6732 /* Examples: 6777 /* Examples:
@@ -6863,7 +6908,7 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp)
6863 * "cmd >" (no filename) 6908 * "cmd >" (no filename)
6864 * "cmd > <file" (2nd redirect starts too early) 6909 * "cmd > <file" (2nd redirect starts too early)
6865 */ 6910 */
6866 die_if_script("syntax error: %s", "invalid redirect"); 6911 syntax_error("invalid redirect");
6867 continue; 6912 continue;
6868 } 6913 }
6869 mode = redir_table[redir->rd_type].mode; 6914 mode = redir_table[redir->rd_type].mode;
@@ -7363,8 +7408,10 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7363 */ 7408 */
7364 close_saved_fds_and_FILE_fds(); 7409 close_saved_fds_and_FILE_fds();
7365//FIXME: should also close saved redir fds 7410//FIXME: should also close saved redir fds
7411 /* Without this, "rm -i FILE" can't be ^C'ed: */
7412 switch_off_special_sigs(G.special_sig_mask);
7366 debug_printf_exec("running applet '%s'\n", argv[0]); 7413 debug_printf_exec("running applet '%s'\n", argv[0]);
7367 run_applet_no_and_exit(a, argv[0], argv); 7414 run_noexec_applet_and_exit(a, argv[0], argv);
7368 } 7415 }
7369# endif 7416# endif
7370 /* Re-exec ourselves */ 7417 /* Re-exec ourselves */
@@ -8045,6 +8092,24 @@ static NOINLINE int run_pipe(struct pipe *pi)
8045 add_vars(old_vars); 8092 add_vars(old_vars);
8046/* clean_up_and_ret0: */ 8093/* clean_up_and_ret0: */
8047 restore_redirects(squirrel); 8094 restore_redirects(squirrel);
8095 /*
8096 * Try "usleep 99999999" + ^C + "echo $?"
8097 * with FEATURE_SH_NOFORK=y.
8098 */
8099 if (!funcp) {
8100 /* It was builtin or nofork.
8101 * if this would be a real fork/execed program,
8102 * it should have died if a fatal sig was received.
8103 * But OTOH, there was no separate process,
8104 * the sig was sent to _shell_, not to non-existing
8105 * child.
8106 * Let's just handle ^C only, this one is obvious:
8107 * we aren't ok with exitcode 0 when ^C was pressed
8108 * during builtin/nofork.
8109 */
8110 if (sigismember(&G.pending_set, SIGINT))
8111 rcode = 128 + SIGINT;
8112 }
8048 clean_up_and_ret1: 8113 clean_up_and_ret1:
8049 free(argv_expanded); 8114 free(argv_expanded);
8050 IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) 8115 IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;)
@@ -8060,6 +8125,14 @@ static NOINLINE int run_pipe(struct pipe *pi)
8060 if (rcode == 0) { 8125 if (rcode == 0) {
8061 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", 8126 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
8062 argv_expanded[0], argv_expanded[1]); 8127 argv_expanded[0], argv_expanded[1]);
8128 /*
8129 * Note: signals (^C) can't interrupt here.
8130 * We remember them and they will be acted upon
8131 * after applet returns.
8132 * This makes applets which can run for a long time
8133 * and/or wait for user input ineligible for NOFORK:
8134 * for example, "yes" or "rm" (rm -i waits for input).
8135 */
8063 rcode = run_nofork_applet(n, argv_expanded); 8136 rcode = run_nofork_applet(n, argv_expanded);
8064 } 8137 }
8065 goto clean_up_and_ret; 8138 goto clean_up_and_ret;
@@ -8491,7 +8564,7 @@ static int run_list(struct pipe *pi)
8491 G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; 8564 G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid;
8492 G.last_bg_pid_exitcode = 0; 8565 G.last_bg_pid_exitcode = 0;
8493 debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); 8566 debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
8494/* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */ 8567/* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash say 0 */
8495 rcode = EXIT_SUCCESS; 8568 rcode = EXIT_SUCCESS;
8496 goto check_traps; 8569 goto check_traps;
8497 } else { 8570 } else {
@@ -8600,6 +8673,10 @@ static void install_sighandlers(unsigned mask)
8600 */ 8673 */
8601 if (sig == SIGCHLD) 8674 if (sig == SIGCHLD)
8602 continue; 8675 continue;
8676 /* bash re-enables SIGHUP which is SIG_IGNed on entry.
8677 * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$"
8678 */
8679 //if (sig == SIGHUP) continue; - TODO?
8603 if (old_handler == SIG_IGN) { 8680 if (old_handler == SIG_IGN) {
8604 /* oops... restore back to IGN, and record this fact */ 8681 /* oops... restore back to IGN, and record this fact */
8605 install_sighandler(sig, old_handler); 8682 install_sighandler(sig, old_handler);
@@ -9381,13 +9458,20 @@ static int FAST_FUNC builtin_read(char **argv)
9381 char *opt_p = NULL; 9458 char *opt_p = NULL;
9382 char *opt_t = NULL; 9459 char *opt_t = NULL;
9383 char *opt_u = NULL; 9460 char *opt_u = NULL;
9461 char *opt_d = NULL; /* optimized out if !BASH */
9384 const char *ifs; 9462 const char *ifs;
9385 int read_flags; 9463 int read_flags;
9386 9464
9387 /* "!": do not abort on errors. 9465 /* "!": do not abort on errors.
9388 * Option string must start with "sr" to match BUILTIN_READ_xxx 9466 * Option string must start with "sr" to match BUILTIN_READ_xxx
9389 */ 9467 */
9390 read_flags = getopt32(argv, "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u); 9468 read_flags = getopt32(argv,
9469#if BASH_READ_D
9470 "!srn:p:t:u:d:", &opt_n, &opt_p, &opt_t, &opt_u, &opt_d
9471#else
9472 "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u
9473#endif
9474 );
9391 if (read_flags == (uint32_t)-1) 9475 if (read_flags == (uint32_t)-1)
9392 return EXIT_FAILURE; 9476 return EXIT_FAILURE;
9393 argv += optind; 9477 argv += optind;
@@ -9401,7 +9485,8 @@ static int FAST_FUNC builtin_read(char **argv)
9401 opt_n, 9485 opt_n,
9402 opt_p, 9486 opt_p,
9403 opt_t, 9487 opt_t,
9404 opt_u 9488 opt_u,
9489 opt_d
9405 ); 9490 );
9406 9491
9407 if ((uintptr_t)r == 1 && errno == EINTR) { 9492 if ((uintptr_t)r == 1 && errno == EINTR) {
@@ -9786,6 +9871,93 @@ static int FAST_FUNC builtin_shift(char **argv)
9786 return EXIT_FAILURE; 9871 return EXIT_FAILURE;
9787} 9872}
9788 9873
9874#if ENABLE_HUSH_GETOPTS
9875static int FAST_FUNC builtin_getopts(char **argv)
9876{
9877/* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html
9878
9879TODO:
9880If a required argument is not found, and getopts is not silent,
9881a question mark (?) is placed in VAR, OPTARG is unset, and a
9882diagnostic message is printed. If getopts is silent, then a
9883colon (:) is placed in VAR and OPTARG is set to the option
9884character found.
9885
9886Test that VAR is a valid variable name?
9887
9888"Whenever the shell is invoked, OPTIND shall be initialized to 1"
9889*/
9890 char cbuf[2];
9891 const char *cp, *optstring, *var;
9892 int c, exitcode;
9893
9894 optstring = *++argv;
9895 if (!optstring || !(var = *++argv)) {
9896 bb_error_msg("usage: getopts OPTSTRING VAR [ARGS]");
9897 return EXIT_FAILURE;
9898 }
9899
9900 c = 0;
9901 if (optstring[0] != ':') {
9902 cp = get_local_var_value("OPTERR");
9903 /* 0 if "OPTERR=0", 1 otherwise */
9904 c = (!cp || NOT_LONE_CHAR(cp, '0'));
9905 }
9906 opterr = c;
9907 cp = get_local_var_value("OPTIND");
9908 optind = cp ? atoi(cp) : 0;
9909 optarg = NULL;
9910 cbuf[1] = '\0';
9911
9912 /* getopts stops on first non-option. Add "+" to force that */
9913 /*if (optstring[0] != '+')*/ {
9914 char *s = alloca(strlen(optstring) + 2);
9915 sprintf(s, "+%s", optstring);
9916 optstring = s;
9917 }
9918
9919 if (argv[1])
9920 argv[0] = G.global_argv[0]; /* for error messages */
9921 else
9922 argv = G.global_argv;
9923 c = getopt(string_array_len(argv), argv, optstring);
9924
9925 /* Set OPTARG */
9926 /* Always set or unset, never left as-is, even on exit/error:
9927 * "If no option was found, or if the option that was found
9928 * does not have an option-argument, OPTARG shall be unset."
9929 */
9930 cp = optarg;
9931 if (c == '?') {
9932 /* If ":optstring" and unknown option is seen,
9933 * it is stored to OPTARG.
9934 */
9935 if (optstring[1] == ':') {
9936 cbuf[0] = optopt;
9937 cp = cbuf;
9938 }
9939 }
9940 if (cp)
9941 set_local_var_from_halves("OPTARG", cp);
9942 else
9943 unset_local_var("OPTARG");
9944
9945 /* Convert -1 to "?" */
9946 exitcode = EXIT_SUCCESS;
9947 if (c < 0) { /* -1: end of options */
9948 exitcode = EXIT_FAILURE;
9949 c = '?';
9950 }
9951
9952 /* Set VAR and OPTIND */
9953 cbuf[0] = c;
9954 set_local_var_from_halves(var, cbuf);
9955 set_local_var_from_halves("OPTIND", utoa(optind));
9956
9957 return exitcode;
9958}
9959#endif
9960
9789static int FAST_FUNC builtin_source(char **argv) 9961static int FAST_FUNC builtin_source(char **argv)
9790{ 9962{
9791 char *arg_path, *filename; 9963 char *arg_path, *filename;
@@ -10178,6 +10350,7 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid
10178 /* So, did we get a signal? */ 10350 /* So, did we get a signal? */
10179 sig = check_and_run_traps(); 10351 sig = check_and_run_traps();
10180 if (sig /*&& sig != SIGCHLD - always true */) { 10352 if (sig /*&& sig != SIGCHLD - always true */) {
10353 /* Do this for any (non-ignored) signal, not only for ^C */
10181 ret = 128 + sig; 10354 ret = 128 + sig;
10182 break; 10355 break;
10183 } 10356 }
@@ -10344,6 +10517,41 @@ static int FAST_FUNC builtin_return(char **argv)
10344} 10517}
10345#endif 10518#endif
10346 10519
10520#if ENABLE_HUSH_TIMES
10521static int FAST_FUNC builtin_times(char **argv UNUSED_PARAM)
10522{
10523 static const uint8_t times_tbl[] ALIGN1 = {
10524 ' ', offsetof(struct tms, tms_utime),
10525 '\n', offsetof(struct tms, tms_stime),
10526 ' ', offsetof(struct tms, tms_cutime),
10527 '\n', offsetof(struct tms, tms_cstime),
10528 0
10529 };
10530 const uint8_t *p;
10531 unsigned clk_tck;
10532 struct tms buf;
10533
10534 clk_tck = bb_clk_tck();
10535
10536 times(&buf);
10537 p = times_tbl;
10538 do {
10539 unsigned sec, frac;
10540 unsigned long t;
10541 t = *(clock_t *)(((char *) &buf) + p[1]);
10542 sec = t / clk_tck;
10543 frac = t % clk_tck;
10544 printf("%um%u.%03us%c",
10545 sec / 60, sec % 60,
10546 (frac * 1000) / clk_tck,
10547 p[0]);
10548 p += 2;
10549 } while (*p);
10550
10551 return EXIT_SUCCESS;
10552}
10553#endif
10554
10347#if ENABLE_HUSH_MEMLEAK 10555#if ENABLE_HUSH_MEMLEAK
10348static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM) 10556static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM)
10349{ 10557{
diff --git a/shell/hush_test/hush-getopts/getopt_optarg.right b/shell/hush_test/hush-getopts/getopt_optarg.right
new file mode 100644
index 000000000..9dbd8460e
--- /dev/null
+++ b/shell/hush_test/hush-getopts/getopt_optarg.right
@@ -0,0 +1,18 @@
1*** no OPTIND, optstring:'w:et' args:-q -w e -r -t -y
2./getopt_optarg.tests: invalid option -- q
3var:'?' OPTIND:2 OPTARG:''
4var:'w' OPTIND:4 OPTARG:'e'
5./getopt_optarg.tests: invalid option -- r
6var:'?' OPTIND:5 OPTARG:''
7var:'t' OPTIND:6 OPTARG:''
8./getopt_optarg.tests: invalid option -- y
9var:'?' OPTIND:7 OPTARG:''
10exited: var:'?' OPTIND:7 OPTARG:''
11*** OPTIND=0, optstring:'w:et' args:-w 1 -w2 -w -e -e -t -t
12var:'w' OPTIND:3 OPTARG:'1'
13var:'w' OPTIND:4 OPTARG:'2'
14var:'w' OPTIND:6 OPTARG:'-e'
15var:'e' OPTIND:7 OPTARG:''
16var:'t' OPTIND:8 OPTARG:''
17var:'t' OPTIND:9 OPTARG:''
18exited: var:'?' OPTIND:9 OPTARG:''
diff --git a/shell/hush_test/hush-getopts/getopt_optarg.tests b/shell/hush_test/hush-getopts/getopt_optarg.tests
new file mode 100755
index 000000000..881cc7884
--- /dev/null
+++ b/shell/hush_test/hush-getopts/getopt_optarg.tests
@@ -0,0 +1,24 @@
1(
2
3set -- -q -w e -r -t -y
4echo "*** no OPTIND, optstring:'w:et' args:$*"
5var=QWERTY
6OPTARG=ASDFGH
7while getopts "w:et" var; do
8 echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
9 OPTARG=ASDFGH
10done
11echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
12
13set -- -w 1 -w2 -w -e -e -t -t
14echo "*** OPTIND=0, optstring:'w:et' args:$*"
15OPTIND=0
16while getopts "w:et" var; do
17 echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
18 OPTARG=ASDFGH
19done
20echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
21
22) 2>&1 \
23| sed -e 's/ unrecognized option: / invalid option -- /' \
24 -e 's/ illegal option -- / invalid option -- /' \
diff --git a/shell/hush_test/hush-getopts/getopt_positional.right b/shell/hush_test/hush-getopts/getopt_positional.right
new file mode 100644
index 000000000..f1c942476
--- /dev/null
+++ b/shell/hush_test/hush-getopts/getopt_positional.right
@@ -0,0 +1,6 @@
1*** no OPTIND, optstring:'we' args:-q -w -e r -t -y
2./getopt_positional.tests: invalid option -- q
3var:'?' OPTIND:2
4var:'w' OPTIND:3
5var:'e' OPTIND:4
6exited: var:'?' OPTIND:4
diff --git a/shell/hush_test/hush-getopts/getopt_positional.tests b/shell/hush_test/hush-getopts/getopt_positional.tests
new file mode 100755
index 000000000..20716bb0c
--- /dev/null
+++ b/shell/hush_test/hush-getopts/getopt_positional.tests
@@ -0,0 +1,13 @@
1(
2
3set -- -q -w -e r -t -y
4echo "*** no OPTIND, optstring:'we' args:$*"
5var=QWERTY
6while getopts "we" var; do
7 echo "var:'$var' OPTIND:$OPTIND"
8done
9echo "exited: var:'$var' OPTIND:$OPTIND"
10
11) 2>&1 \
12| sed -e 's/ unrecognized option: / invalid option -- /' \
13 -e 's/ illegal option -- / invalid option -- /' \
diff --git a/shell/hush_test/hush-getopts/getopt_silent.right b/shell/hush_test/hush-getopts/getopt_silent.right
new file mode 100644
index 000000000..03d4eb149
--- /dev/null
+++ b/shell/hush_test/hush-getopts/getopt_silent.right
@@ -0,0 +1,6 @@
1*** optstring:':ac' args:-a -b -c
21 rc:0 var:'a' OPTIND:2 OPTARG:''
32 rc:0 var:'?' OPTIND:3 OPTARG:'b'
43 rc:0 var:'c' OPTIND:4 OPTARG:''
54 rc:1 var:'?' OPTIND:4 OPTARG:''
65 rc:1 var:'?' OPTIND:4 OPTARG:''
diff --git a/shell/hush_test/hush-getopts/getopt_silent.tests b/shell/hush_test/hush-getopts/getopt_silent.tests
new file mode 100755
index 000000000..5f255db7f
--- /dev/null
+++ b/shell/hush_test/hush-getopts/getopt_silent.tests
@@ -0,0 +1,29 @@
1# Open Group Base Specifications Issue 7:
2# """
3# If an unknown option is met, VAR shall be set to "?". In this case,
4# if the first character in optstring is ":", OPTARG shall be set
5# to the option character found, but no output shall be written
6# to standard error; otherwise, the shell variable OPTARG shall be
7# unset and a diagnostic message shall be written to standard error."
8# ...
9# If an option-argument is missing:
10# If the first character of optstring is ":", VAR shall be set to ":"
11# and OPTARG shall be set to the option character found.
12# """
13
14(
15
16echo "*** optstring:':ac' args:-a -b -c"
17getopts ":ac" var -a -b -c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
18getopts ":ac" var -a -b -c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
19getopts ":ac" var -a -b -c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
20getopts ":ac" var -a -b -c; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
21# Previous line should result in "rc:1", which is normally treated
22# in getopts loops as exit condition.
23# Nevertheless, let's verify that calling it yet another time doesn't do
24# anything weird:
25getopts ":ac" var -a -b -c; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
26
27) 2>&1 \
28| sed -e 's/ unrecognized option: / invalid option -- /' \
29 -e 's/ illegal option -- / invalid option -- /' \
diff --git a/shell/hush_test/hush-getopts/getopt_simple.right b/shell/hush_test/hush-getopts/getopt_simple.right
new file mode 100644
index 000000000..b4855fa1a
--- /dev/null
+++ b/shell/hush_test/hush-getopts/getopt_simple.right
@@ -0,0 +1,34 @@
1*** no OPTIND, optstring:'ab' args:-a -b c
2var:'a' OPTIND:2
3var:'b' OPTIND:3
4exited: rc:0 var:'?' OPTIND:3
5*** OPTIND=1, optstring:'ab' args:-a -b c
6var:'a' OPTIND:2
7var:'b' OPTIND:3
8exited: rc:0 var:'?' OPTIND:3
9*** OPTIND=0, optstring:'ab' args:-a -b c
10var:'a' OPTIND:2
11var:'b' OPTIND:3
12exited: rc:0 var:'?' OPTIND:3
13*** unset OPTIND, optstring:'ab' args:-a -b c
14var:'a' OPTIND:2
15var:'b' OPTIND:3
16exited: rc:0 var:'?' OPTIND:3
17*** optstring:'ab' args:-a -b c
181 rc:0 var:'a' OPTIND:2
192 rc:0 var:'b' OPTIND:3
203 rc:1 var:'?' OPTIND:3
21*** unset OPTIND, optstring:'ab' args:-a c -c -b d
22var:'a' OPTIND:2
23exited: rc:0 var:'?' OPTIND:2
24*** unset OPTIND, optstring:'ab' args:-a -c -b d
25var:'a' OPTIND:2
26./getopt_simple.tests: invalid option -- c
27var:'?' OPTIND:3
28var:'b' OPTIND:4
29exited: rc:0 var:'?' OPTIND:4
30*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d
31var:'a' OPTIND:2
32var:'?' OPTIND:3
33var:'b' OPTIND:4
34exited: rc:0 var:'?' OPTIND:4
diff --git a/shell/hush_test/hush-getopts/getopt_simple.tests b/shell/hush_test/hush-getopts/getopt_simple.tests
new file mode 100755
index 000000000..50718cc98
--- /dev/null
+++ b/shell/hush_test/hush-getopts/getopt_simple.tests
@@ -0,0 +1,81 @@
1# Simple usage cases for getopts.
2#
3# OPTIND is either not touched at all (first loop with getopts,
4# relying on shell startup init), or getopts state is reset
5# before new loop with "unset OPTIND", "OPTIND=1" or "OPTIND=0".
6#
7# Each option is a separate argument (no "-abc"). This conceptually
8# needs only $OPTIND to hold getopts state.
9#
10# We check that loop does not stop on unknown option (sets "?"),
11# stops on _first_ non-option argument.
12
13(
14
15echo "*** no OPTIND, optstring:'ab' args:-a -b c"
16var=QWERTY
17while getopts "ab" var -a -b c; do
18 echo "var:'$var' OPTIND:$OPTIND"
19done
20# unfortunately, "rc:0" is shown since while's overall exitcode is "success"
21echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
22
23# Resetting behavior =1
24echo "*** OPTIND=1, optstring:'ab' args:-a -b c"
25OPTIND=1
26while getopts "ab" var -a -b c; do
27 echo "var:'$var' OPTIND:$OPTIND"
28done
29echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
30
31# Resetting behavior =0
32echo "*** OPTIND=0, optstring:'ab' args:-a -b c"
33OPTIND=0
34while getopts "ab" var -a -b c; do
35 echo "var:'$var' OPTIND:$OPTIND"
36done
37echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
38
39# Resetting behavior "unset"
40echo "*** unset OPTIND, optstring:'ab' args:-a -b c"
41unset OPTIND
42while getopts "ab" var -a -b c; do
43 echo "var:'$var' OPTIND:$OPTIND"
44done
45echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
46
47# What is the final exitcode?
48echo "*** optstring:'ab' args:-a -b c"
49unset OPTIND
50getopts "ab" var -a -b c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND"
51getopts "ab" var -a -b c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND"
52getopts "ab" var -a -b c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND"
53
54# Where would it stop? c or -c?
55echo "*** unset OPTIND, optstring:'ab' args:-a c -c -b d"
56unset OPTIND
57while getopts "ab" var -a c -c -b d; do
58 echo "var:'$var' OPTIND:$OPTIND"
59done
60echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
61
62# What happens on unknown option?
63echo "*** unset OPTIND, optstring:'ab' args:-a -c -b d"
64unset OPTIND
65while getopts "ab" var -a -c -b d; do
66 echo "var:'$var' OPTIND:$OPTIND"
67done
68echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
69
70# ORTERR=0 suppresses error message?
71echo "*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d"
72unset OPTIND
73OPTERR=0
74while getopts "ab" var -a -c -b d; do
75 echo "var:'$var' OPTIND:$OPTIND"
76done
77echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
78
79) 2>&1 \
80| sed -e 's/ unrecognized option: / invalid option -- /' \
81 -e 's/ illegal option -- / invalid option -- /' \
diff --git a/shell/hush_test/hush-getopts/getopt_test_libc_bug.right b/shell/hush_test/hush-getopts/getopt_test_libc_bug.right
new file mode 100644
index 000000000..6694e8f0c
--- /dev/null
+++ b/shell/hush_test/hush-getopts/getopt_test_libc_bug.right
@@ -0,0 +1,26 @@
1*** optstring:'ac' args:-a -b -c -d e
21 rc:0 var:'a' OPTIND:2 OPTARG:''
3./getopt_test_libc_bug.tests: invalid option -- b
42 rc:0 var:'?' OPTIND:3 OPTARG:''
53 rc:0 var:'c' OPTIND:4 OPTARG:''
6./getopt_test_libc_bug.tests: invalid option -- d
74 rc:0 var:'?' OPTIND:5 OPTARG:''
85 rc:1 var:'?' OPTIND:5 OPTARG:''
9
10*** optstring:'ac' args:-a -b -c -d e
111 rc:0 var:'a' OPTIND:2 OPTARG:''
12./getopt_test_libc_bug.tests: invalid option -- b
132 rc:0 var:'?' OPTIND:3 OPTARG:''
143 rc:0 var:'c' OPTIND:4 OPTARG:''
15./getopt_test_libc_bug.tests: invalid option -- d
164 rc:0 var:'?' OPTIND:5 OPTARG:''
175 rc:1 var:'?' OPTIND:5 OPTARG:''
18
19*** optstring:'ac' args:-a -b -c -d e
201 rc:0 var:'a' OPTIND:2 OPTARG:''
21./getopt_test_libc_bug.tests: invalid option -- b
222 rc:0 var:'?' OPTIND:3 OPTARG:''
233 rc:0 var:'c' OPTIND:4 OPTARG:''
24./getopt_test_libc_bug.tests: invalid option -- d
254 rc:0 var:'?' OPTIND:5 OPTARG:''
265 rc:1 var:'?' OPTIND:5 OPTARG:''
diff --git a/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests b/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests
new file mode 100755
index 000000000..ab7bc3b09
--- /dev/null
+++ b/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests
@@ -0,0 +1,44 @@
1# This test can fail with libc with buggy getopt() implementation.
2# If getopt() wants to parse multi-option args (-abc),
3# it needs to remember a position within current arg.
4#
5# If this position is kept as a POINTER, not an offset,
6# and if argv[] ADDRESSES (not contents!) change, it blows up.
7
8(
9
10echo "*** optstring:'ac' args:-a -b -c -d e"
11getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
12getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
13getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
14getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
15getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
16
17# Above: args are (usually) in the same locations in memory.
18# Below: variable allocations change the location.
19
20echo
21echo "*** optstring:'ac' args:-a -b -c -d e"
22unset OPTIND
23OPTARG=QWERTY; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
24NEWVAR=NEWVAL; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
25VAR111=NEWVAL; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
26VAR222=NEWVAL; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
27VAR333=NEWVAL; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
28
29# Slightly different attempts to force reallocations
30
31echo
32echo "*** optstring:'ac' args:-a -b -c -d e"
33unset OPTIND
34export OPTARG; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
35export NEWVAR; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
36export VAR111; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
37export VAR222; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
38export VAR333; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'"
39
40# All copies of code above should generate identical output
41
42) 2>&1 \
43| sed -e 's/ unrecognized option: / invalid option -- /' \
44 -e 's/ illegal option -- / invalid option -- /' \
diff --git a/shell/hush_test/hush-parsing/groups_and_keywords2.right b/shell/hush_test/hush-parsing/groups_and_keywords2.right
new file mode 100644
index 000000000..ae74a5db9
--- /dev/null
+++ b/shell/hush_test/hush-parsing/groups_and_keywords2.right
@@ -0,0 +1,3 @@
1hush: syntax error: unexpected )
2Fail:2
3hush: syntax error: unexpected )
diff --git a/shell/hush_test/hush-parsing/groups_and_keywords2.tests b/shell/hush_test/hush-parsing/groups_and_keywords2.tests
new file mode 100755
index 000000000..ab33b909f
--- /dev/null
+++ b/shell/hush_test/hush-parsing/groups_and_keywords2.tests
@@ -0,0 +1,9 @@
1# This is an error
2(eval 'if() { echo; }')
3echo Fail:$?
4# ^^^^^^ bash prints 1, but interactively it sets $? = 2
5# we print 2
6
7# This is an error, and it aborts in script
8if() { echo; }
9echo Not reached
diff --git a/shell/hush_test/run-all b/shell/hush_test/run-all
index 1dd0edc39..3fbc7c531 100755
--- a/shell/hush_test/run-all
+++ b/shell/hush_test/run-all
@@ -80,7 +80,7 @@ do_test()
80 case $? in 80 case $? in
81 0) echo " ok";; 81 0) echo " ok";;
82 77) echo " skip (feature disabled)";; 82 77) echo " skip (feature disabled)";;
83 *) echo " fail"; tret=1;; 83 *) echo " fail ($?)"; tret=1;;
84 esac 84 esac
85 done 85 done
86 exit ${tret} 86 exit ${tret}
diff --git a/shell/math.c b/shell/math.c
index 006221b6a..f01f24362 100644
--- a/shell/math.c
+++ b/shell/math.c
@@ -743,7 +743,7 @@ arith(arith_state_t *math_state, const char *expr)
743 * may be used to endorse or promote products derived from this software 743 * may be used to endorse or promote products derived from this software
744 * without specific prior written permission. 744 * without specific prior written permission.
745 * 745 *
746 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 746 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND
747 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 747 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
748 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 748 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
749 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 749 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
diff --git a/shell/shell_common.c b/shell/shell_common.c
index 750adc5d8..bc34de404 100644
--- a/shell/shell_common.c
+++ b/shell/shell_common.c
@@ -58,7 +58,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
58 const char *opt_n, 58 const char *opt_n,
59 const char *opt_p, 59 const char *opt_p,
60 const char *opt_t, 60 const char *opt_t,
61 const char *opt_u 61 const char *opt_u,
62 const char *opt_d
62) 63)
63{ 64{
64 struct pollfd pfd[1]; 65 struct pollfd pfd[1];
@@ -68,6 +69,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
68 int nchars; /* -n NUM */ 69 int nchars; /* -n NUM */
69 char **pp; 70 char **pp;
70 char *buffer; 71 char *buffer;
72 char delim;
71 struct termios tty, old_tty; 73 struct termios tty, old_tty;
72 const char *retval; 74 const char *retval;
73 int bufpos; /* need to be able to hold -1 */ 75 int bufpos; /* need to be able to hold -1 */
@@ -188,6 +190,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
188 end_ms += (unsigned)monotonic_ms(); 190 end_ms += (unsigned)monotonic_ms();
189 buffer = NULL; 191 buffer = NULL;
190 bufpos = 0; 192 bufpos = 0;
193 delim = opt_d ? *opt_d : '\n';
191 do { 194 do {
192 char c; 195 char c;
193 int timeout; 196 int timeout;
@@ -243,14 +246,14 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
243 continue; 246 continue;
244 } 247 }
245 } 248 }
246 if (c == '\n') 249 if (c == delim) /* '\n' or -d CHAR */
247 break; 250 break;
248 251
249 /* $IFS splitting. NOT done if we run "read" 252 /* $IFS splitting. NOT done if we run "read"
250 * without variable names (bash compat). 253 * without variable names (bash compat).
251 * Thus, "read" and "read REPLY" are not the same. 254 * Thus, "read" and "read REPLY" are not the same.
252 */ 255 */
253 if (argv[0]) { 256 if (!opt_d && argv[0]) {
254/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */ 257/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */
255 const char *is_ifs = strchr(ifs, c); 258 const char *is_ifs = strchr(ifs, c);
256 if (startword && is_ifs) { 259 if (startword && is_ifs) {
diff --git a/shell/shell_common.h b/shell/shell_common.h
index a82535c86..875fd9ea7 100644
--- a/shell/shell_common.h
+++ b/shell/shell_common.h
@@ -34,6 +34,11 @@ enum {
34 BUILTIN_READ_SILENT = 1 << 0, 34 BUILTIN_READ_SILENT = 1 << 0,
35 BUILTIN_READ_RAW = 1 << 1, 35 BUILTIN_READ_RAW = 1 << 1,
36}; 36};
37//TODO? do not provide bashisms if not asked for:
38//#if !ENABLE_HUSH_BASH_COMPAT && !ENABLE_ASH_BASH_COMPAT
39//#define shell_builtin_read(setvar,argv,ifs,read_flags,n,p,t,u,d)
40// shell_builtin_read(setvar,argv,ifs,read_flags)
41//#endif
37const char* FAST_FUNC 42const char* FAST_FUNC
38shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), 43shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
39 char **argv, 44 char **argv,
@@ -42,7 +47,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
42 const char *opt_n, 47 const char *opt_n,
43 const char *opt_p, 48 const char *opt_p,
44 const char *opt_t, 49 const char *opt_t,
45 const char *opt_u 50 const char *opt_u,
51 const char *opt_d
46); 52);
47 53
48int FAST_FUNC 54int FAST_FUNC