aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2017-07-18 15:58:52 +0100
committerRon Yorston <rmy@pobox.com>2017-07-18 15:58:52 +0100
commitb680f05ad449505e3d914bebd4c8d83bf768c094 (patch)
treec08ded13d430b0e7e0104f2eb594fad190ce98a3 /shell
parent258200ff81d5a9da54dab35acf36213eff1e399b (diff)
parent513a2457b65894b10b9fd6aa8753fca59eced08c (diff)
downloadbusybox-w32-b680f05ad449505e3d914bebd4c8d83bf768c094.tar.gz
busybox-w32-b680f05ad449505e3d914bebd4c8d83bf768c094.tar.bz2
busybox-w32-b680f05ad449505e3d914bebd4c8d83bf768c094.zip
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
-rw-r--r--shell/ash.c109
-rw-r--r--shell/ash_test/ash-heredoc/heredoc1.right6
-rwxr-xr-xshell/ash_test/ash-heredoc/heredoc1.tests12
-rw-r--r--shell/ash_test/ash-heredoc/heredoc3.right10
-rwxr-xr-xshell/ash_test/ash-heredoc/heredoc3.tests21
-rw-r--r--shell/ash_test/ash-heredoc/heredoc8.right1
-rwxr-xr-xshell/ash_test/ash-heredoc/heredoc8.tests3
-rw-r--r--shell/ash_test/ash-heredoc/heredoc9.right1
-rwxr-xr-xshell/ash_test/ash-heredoc/heredoc9.tests9
-rwxr-xr-xshell/ash_test/ash-misc/shift1.tests2
-rw-r--r--shell/ash_test/ash-misc/sigint1.right1
-rwxr-xr-xshell/ash_test/ash-misc/sigint1.tests41
-rwxr-xr-xshell/ash_test/ash-misc/tickquote1.tests2
-rw-r--r--shell/ash_test/ash-misc/wait6.right2
-rwxr-xr-xshell/ash_test/ash-misc/wait6.tests6
-rw-r--r--shell/ash_test/ash-parsing/and_or_and_backgrounding.right (renamed from shell/hush_test/hush-bugs/and_or_and_backgrounding.right)0
-rwxr-xr-xshell/ash_test/ash-parsing/and_or_and_backgrounding.tests (renamed from shell/hush_test/hush-bugs/and_or_and_backgrounding.tests)1
-rw-r--r--shell/ash_test/ash-quoting/quoted_punct.right35
-rwxr-xr-xshell/ash_test/ash-quoting/quoted_punct.tests41
-rw-r--r--shell/ash_test/ash-quoting/unicode_8x_chars.right6
-rwxr-xr-xshell/ash_test/ash-quoting/unicode_8x_chars.tests28
-rw-r--r--shell/ash_test/ash-redir/redir5.right6
-rwxr-xr-xshell/ash_test/ash-redir/redir5.tests16
-rw-r--r--shell/ash_test/ash-redir/redir_to_bad_fd.right2
-rwxr-xr-xshell/ash_test/ash-redir/redir_to_bad_fd.tests3
-rwxr-xr-xshell/ash_test/ash-vars/var-utf8-length.tests2
-rwxr-xr-xshell/ash_test/ash-vars/var_bash1a.tests2
-rw-r--r--shell/ash_test/ash-vars/var_bash1b.right23
-rwxr-xr-xshell/ash_test/ash-vars/var_bash1b.tests24
-rw-r--r--shell/ash_test/ash-vars/var_bash6.right5
-rwxr-xr-xshell/ash_test/ash-vars/var_bash6.tests9
-rwxr-xr-xshell/ash_test/run-all102
-rw-r--r--shell/hush.c739
-rw-r--r--shell/hush_test/hush-heredoc/heredoc8.right1
-rwxr-xr-xshell/hush_test/hush-heredoc/heredoc8.tests3
-rw-r--r--shell/hush_test/hush-misc/errexit1.right1
-rwxr-xr-xshell/hush_test/hush-misc/errexit1.tests5
-rw-r--r--shell/hush_test/hush-misc/shift1.right10
-rwxr-xr-xshell/hush_test/hush-misc/shift1.tests10
-rw-r--r--shell/hush_test/hush-misc/sigint1.right1
-rwxr-xr-xshell/hush_test/hush-misc/sigint1.tests41
-rw-r--r--shell/hush_test/hush-misc/tickquote1.right1
-rwxr-xr-xshell/hush_test/hush-misc/tickquote1.tests2
-rw-r--r--shell/hush_test/hush-misc/wait6.right2
-rwxr-xr-xshell/hush_test/hush-misc/wait6.tests6
-rw-r--r--shell/hush_test/hush-parsing/and_or_and_backgrounding.right4
-rwxr-xr-xshell/hush_test/hush-parsing/and_or_and_backgrounding.tests30
-rw-r--r--shell/hush_test/hush-quoting/quoted_punct.right35
-rwxr-xr-xshell/hush_test/hush-quoting/quoted_punct.tests41
-rw-r--r--shell/hush_test/hush-quoting/unicode_8x_chars.right6
-rwxr-xr-xshell/hush_test/hush-quoting/unicode_8x_chars.tests28
-rw-r--r--shell/hush_test/hush-redir/redir3.right16
-rwxr-xr-xshell/hush_test/hush-redir/redir3.tests14
-rw-r--r--shell/hush_test/hush-redir/redir_errors.right14
-rwxr-xr-xshell/hush_test/hush-redir/redir_errors.tests9
-rw-r--r--shell/hush_test/hush-redir/redir_to_bad_fd.right1
-rwxr-xr-xshell/hush_test/hush-redir/redir_to_bad_fd.tests3
-rw-r--r--shell/hush_test/hush-signals/signal4.right4
-rwxr-xr-xshell/hush_test/hush-signals/signal4.tests5
-rw-r--r--shell/hush_test/hush-signals/signal8.right3
-rwxr-xr-xshell/hush_test/hush-signals/signal8.tests18
-rw-r--r--shell/hush_test/hush-signals/signal9.right3
-rwxr-xr-xshell/hush_test/hush-signals/signal9.tests21
-rw-r--r--shell/hush_test/hush-vars/readonly0.right20
-rwxr-xr-xshell/hush_test/hush-vars/readonly0.tests42
-rw-r--r--shell/hush_test/hush-vars/readonly2.right5
-rwxr-xr-xshell/hush_test/hush-vars/readonly2.tests7
-rw-r--r--shell/hush_test/hush-vars/var-utf8-length.right1
-rwxr-xr-xshell/hush_test/hush-vars/var-utf8-length.tests4
-rw-r--r--shell/hush_test/hush-vars/var_bash1a.right6
-rwxr-xr-xshell/hush_test/hush-vars/var_bash1a.tests11
-rw-r--r--shell/hush_test/hush-vars/var_bash1b.right23
-rwxr-xr-xshell/hush_test/hush-vars/var_bash1b.tests24
-rwxr-xr-xshell/hush_test/run-all51
-rw-r--r--shell/shell_common.c4
75 files changed, 1305 insertions, 501 deletions
diff --git a/shell/ash.c b/shell/ash.c
index 5debd82f2..6cc29d25e 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -2566,12 +2566,8 @@ putprompt(const char *s)
2566} 2566}
2567#endif 2567#endif
2568 2568
2569#if ENABLE_ASH_EXPAND_PRMT
2570/* expandstr() needs parsing machinery, so it is far away ahead... */ 2569/* expandstr() needs parsing machinery, so it is far away ahead... */
2571static const char *expandstr(const char *ps); 2570static const char *expandstr(const char *ps);
2572#else
2573#define expandstr(s) s
2574#endif
2575 2571
2576static void 2572static void
2577setprompt_if(smallint do_set, int whichprompt) 2573setprompt_if(smallint do_set, int whichprompt)
@@ -2596,10 +2592,10 @@ setprompt_if(smallint do_set, int whichprompt)
2596 } 2592 }
2597#if ENABLE_ASH_EXPAND_PRMT 2593#if ENABLE_ASH_EXPAND_PRMT
2598 pushstackmark(&smark, stackblocksize()); 2594 pushstackmark(&smark, stackblocksize());
2599#endif
2600 putprompt(expandstr(prompt)); 2595 putprompt(expandstr(prompt));
2601#if ENABLE_ASH_EXPAND_PRMT
2602 popstackmark(&smark); 2596 popstackmark(&smark);
2597#else
2598 putprompt(prompt);
2603#endif 2599#endif
2604} 2600}
2605 2601
@@ -4244,15 +4240,19 @@ sprint_status48(char *s, int status, int sigonly)
4244 4240
4245 col = 0; 4241 col = 0;
4246 if (!WIFEXITED(status)) { 4242 if (!WIFEXITED(status)) {
4247 if (JOBS && WIFSTOPPED(status)) 4243#if JOBS
4244 if (WIFSTOPPED(status))
4248 st = WSTOPSIG(status); 4245 st = WSTOPSIG(status);
4249 else 4246 else
4247#endif
4250 st = WTERMSIG(status); 4248 st = WTERMSIG(status);
4251 if (sigonly) { 4249 if (sigonly) {
4252 if (st == SIGINT || st == SIGPIPE) 4250 if (st == SIGINT || st == SIGPIPE)
4253 goto out; 4251 goto out;
4254 if (JOBS && WIFSTOPPED(status)) 4252#if JOBS
4253 if (WIFSTOPPED(status))
4255 goto out; 4254 goto out;
4255#endif
4256 } 4256 }
4257 st &= 0x7f; 4257 st &= 0x7f;
4258//TODO: use bbox's get_signame? strsignal adds ~600 bytes to text+rodata 4258//TODO: use bbox's get_signame? strsignal adds ~600 bytes to text+rodata
@@ -4501,8 +4501,10 @@ dowait(int block, struct job *job)
4501 goto out; 4501 goto out;
4502 } 4502 }
4503 /* The process wasn't found in job list */ 4503 /* The process wasn't found in job list */
4504 if (JOBS && !WIFSTOPPED(status)) 4504#if JOBS
4505 if (!WIFSTOPPED(status))
4505 jobless--; 4506 jobless--;
4507#endif
4506 out: 4508 out:
4507 INT_ON; 4509 INT_ON;
4508 4510
@@ -6285,6 +6287,7 @@ rmescapes(char *str, int flag)
6285 while (*p) { 6287 while (*p) {
6286 if ((unsigned char)*p == CTLQUOTEMARK) { 6288 if ((unsigned char)*p == CTLQUOTEMARK) {
6287// Note: both inquotes and protect_against_glob only affect whether 6289// Note: both inquotes and protect_against_glob only affect whether
6290// CTLESC,<ch> gets converted to <ch> or to \<ch>
6288 inquotes = ~inquotes; 6291 inquotes = ~inquotes;
6289 p++; 6292 p++;
6290 protect_against_glob = globbing; 6293 protect_against_glob = globbing;
@@ -6297,7 +6300,33 @@ rmescapes(char *str, int flag)
6297 ash_msg_and_raise_error("CTLESC at EOL (shouldn't happen)"); 6300 ash_msg_and_raise_error("CTLESC at EOL (shouldn't happen)");
6298#endif 6301#endif
6299 if (protect_against_glob) { 6302 if (protect_against_glob) {
6300 *q++ = '\\'; 6303 /*
6304 * We used to trust glob() and fnmatch() to eat
6305 * superfluous escapes (\z where z has no
6306 * special meaning anyway). But this causes
6307 * bugs such as string of one greek letter rho
6308 * (unicode-encoded as two bytes "cf,81")
6309 * getting encoded as "cf,CTLESC,81"
6310 * and here, converted to "cf,\,81" -
6311 * which does not go well with some flavors
6312 * of fnmatch() in unicode locales
6313 * (for example, glibc <= 2.22).
6314 *
6315 * Lets add "\" only on the chars which need it.
6316 * Testcases for less obvious chars are shown.
6317 */
6318 if (*p == '*'
6319 || *p == '?'
6320 || *p == '['
6321 || *p == '\\' /* case '\' in \\ ) echo ok;; *) echo WRONG;; esac */
6322 || *p == ']' /* case ']' in [a\]] ) echo ok;; *) echo WRONG;; esac */
6323 || *p == '-' /* case '-' in [a\-c]) echo ok;; *) echo WRONG;; esac */
6324 || *p == '!' /* case '!' in [\!] ) echo ok;; *) echo WRONG;; esac */
6325 /* Some libc support [^negate], that's why "^" also needs love */
6326 || *p == '^' /* case '^' in [\^] ) echo ok;; *) echo WRONG;; esac */
6327 ) {
6328 *q++ = '\\';
6329 }
6301 } 6330 }
6302 } else if (*p == '\\' && !inquotes) { 6331 } else if (*p == '\\' && !inquotes) {
6303 /* naked back slash */ 6332 /* naked back slash */
@@ -6963,7 +6992,6 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
6963 char *loc; 6992 char *loc;
6964 char *rmesc, *rmescend; 6993 char *rmesc, *rmescend;
6965 char *str; 6994 char *str;
6966 IF_BASH_SUBSTR(int pos, len, orig_len;)
6967 int amount, resetloc; 6995 int amount, resetloc;
6968 IF_BASH_PATTERN_SUBST(int workloc;) 6996 IF_BASH_PATTERN_SUBST(int workloc;)
6969 IF_BASH_PATTERN_SUBST(char *repl = NULL;) 6997 IF_BASH_PATTERN_SUBST(char *repl = NULL;)
@@ -6992,14 +7020,23 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
6992 /* NOTREACHED */ 7020 /* NOTREACHED */
6993 7021
6994#if BASH_SUBSTR 7022#if BASH_SUBSTR
6995 case VSSUBSTR: 7023 case VSSUBSTR: {
6996//TODO: support more general format ${v:EXPR:EXPR}, 7024 int pos, len, orig_len;
6997// where EXPR follows $(()) rules 7025 char *colon;
7026
6998 loc = str = stackblock() + strloc; 7027 loc = str = stackblock() + strloc;
7028
7029# if !ENABLE_FEATURE_SH_MATH
7030# define ash_arith number
7031# endif
6999 /* Read POS in ${var:POS:LEN} */ 7032 /* Read POS in ${var:POS:LEN} */
7000 pos = atoi(loc); /* number(loc) errors out on "1:4" */ 7033 colon = strchr(loc, ':');
7001 len = str - startp - 1; 7034 if (colon) *colon = '\0';
7035 pos = ash_arith(loc);
7036 if (colon) *colon = ':';
7002 7037
7038 /* Read LEN in ${var:POS:LEN} */
7039 len = str - startp - 1;
7003 /* *loc != '\0', guaranteed by parser */ 7040 /* *loc != '\0', guaranteed by parser */
7004 if (quotes) { 7041 if (quotes) {
7005 char *ptr; 7042 char *ptr;
@@ -7013,25 +7050,21 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7013 } 7050 }
7014 } 7051 }
7015 orig_len = len; 7052 orig_len = len;
7016
7017 if (*loc++ == ':') { 7053 if (*loc++ == ':') {
7018 /* ${var::LEN} */ 7054 /* ${var::LEN} */
7019 len = number(loc); 7055 len = ash_arith(loc);
7020 } else { 7056 } else {
7021 /* Skip POS in ${var:POS:LEN} */ 7057 /* Skip POS in ${var:POS:LEN} */
7022 len = orig_len; 7058 len = orig_len;
7023 while (*loc && *loc != ':') { 7059 while (*loc && *loc != ':') {
7024 /* TODO?
7025 * bash complains on: var=qwe; echo ${var:1a:123}
7026 if (!isdigit(*loc))
7027 ash_msg_and_raise_error(msg_illnum, str);
7028 */
7029 loc++; 7060 loc++;
7030 } 7061 }
7031 if (*loc++ == ':') { 7062 if (*loc++ == ':') {
7032 len = number(loc); 7063 len = ash_arith(loc);
7033 } 7064 }
7034 } 7065 }
7066# undef ash_arith
7067
7035 if (pos < 0) { 7068 if (pos < 0) {
7036 /* ${VAR:$((-n)):l} starts n chars from the end */ 7069 /* ${VAR:$((-n)):l} starts n chars from the end */
7037 pos = orig_len + pos; 7070 pos = orig_len + pos;
@@ -7039,12 +7072,16 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7039 if ((unsigned)pos >= orig_len) { 7072 if ((unsigned)pos >= orig_len) {
7040 /* apart from obvious ${VAR:999999:l}, 7073 /* apart from obvious ${VAR:999999:l},
7041 * covers ${VAR:$((-9999999)):l} - result is "" 7074 * covers ${VAR:$((-9999999)):l} - result is ""
7042 * (bash-compat) 7075 * (bash compat)
7043 */ 7076 */
7044 pos = 0; 7077 pos = 0;
7045 len = 0; 7078 len = 0;
7046 } 7079 }
7047 if (len > (orig_len - pos)) 7080 if (len < 0) {
7081 /* ${VAR:N:-M} sets LEN to strlen()-M */
7082 len = (orig_len - pos) + len;
7083 }
7084 if ((unsigned)len > (orig_len - pos))
7048 len = orig_len - pos; 7085 len = orig_len - pos;
7049 7086
7050 for (str = startp; pos; str++, pos--) { 7087 for (str = startp; pos; str++, pos--) {
@@ -7060,6 +7097,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7060 amount = loc - expdest; 7097 amount = loc - expdest;
7061 STADJUST(amount, expdest); 7098 STADJUST(amount, expdest);
7062 return loc; 7099 return loc;
7100 }
7063#endif /* BASH_SUBSTR */ 7101#endif /* BASH_SUBSTR */
7064 } 7102 }
7065 7103
@@ -7104,6 +7142,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7104#if BASH_PATTERN_SUBST 7142#if BASH_PATTERN_SUBST
7105 workloc = expdest - (char *)stackblock(); 7143 workloc = expdest - (char *)stackblock();
7106 if (subtype == VSREPLACE || subtype == VSREPLACEALL) { 7144 if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
7145 int len;
7107 char *idx, *end; 7146 char *idx, *end;
7108 7147
7109 if (!repl) { 7148 if (!repl) {
@@ -7970,7 +8009,9 @@ expandhere(union node *arg, int fd)
7970static int 8009static int
7971patmatch(char *pattern, const char *string) 8010patmatch(char *pattern, const char *string)
7972{ 8011{
7973 return pmatch(preglob(pattern, 0), string); 8012 char *p = preglob(pattern, 0);
8013 //bb_error_msg("fnmatch(pattern:'%s',str:'%s')", p, string);
8014 return pmatch(p, string);
7974} 8015}
7975 8016
7976/* 8017/*
@@ -8072,7 +8113,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) char *cmd, char **argv, char **
8072 clearenv(); 8113 clearenv();
8073 while (*envp) 8114 while (*envp)
8074 putenv(*envp++); 8115 putenv(*envp++);
8075 run_applet_no_and_exit(applet_no, argv); 8116 run_applet_no_and_exit(applet_no, cmd, argv);
8076 } 8117 }
8077 /* re-exec ourselves with the new arguments */ 8118 /* re-exec ourselves with the new arguments */
8078 execve(bb_busybox_exec_path, argv, envp); 8119 execve(bb_busybox_exec_path, argv, envp);
@@ -12022,9 +12063,7 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12022 smallint dblquote; 12063 smallint dblquote;
12023 smallint oldstyle; 12064 smallint oldstyle;
12024 IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */ 12065 IF_FEATURE_SH_MATH(smallint prevsyntax;) /* syntax before arithmetic */
12025#if ENABLE_ASH_EXPAND_PRMT
12026 smallint pssyntax; /* we are expanding a prompt string */ 12066 smallint pssyntax; /* we are expanding a prompt string */
12027#endif
12028 int varnest; /* levels of variables expansion */ 12067 int varnest; /* levels of variables expansion */
12029 IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */ 12068 IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */
12030 IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */ 12069 IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
@@ -12036,11 +12075,9 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12036 bqlist = NULL; 12075 bqlist = NULL;
12037 quotef = 0; 12076 quotef = 0;
12038 IF_FEATURE_SH_MATH(prevsyntax = 0;) 12077 IF_FEATURE_SH_MATH(prevsyntax = 0;)
12039#if ENABLE_ASH_EXPAND_PRMT
12040 pssyntax = (syntax == PSSYNTAX); 12078 pssyntax = (syntax == PSSYNTAX);
12041 if (pssyntax) 12079 if (pssyntax)
12042 syntax = DQSYNTAX; 12080 syntax = DQSYNTAX;
12043#endif
12044 dblquote = (syntax == DQSYNTAX); 12081 dblquote = (syntax == DQSYNTAX);
12045 varnest = 0; 12082 varnest = 0;
12046 IF_FEATURE_SH_MATH(arinest = 0;) 12083 IF_FEATURE_SH_MATH(arinest = 0;)
@@ -12094,12 +12131,10 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12094 } else if (c == '\n') { 12131 } else if (c == '\n') {
12095 nlprompt(); 12132 nlprompt();
12096 } else { 12133 } else {
12097#if ENABLE_ASH_EXPAND_PRMT
12098 if (c == '$' && pssyntax) { 12134 if (c == '$' && pssyntax) {
12099 USTPUTC(CTLESC, out); 12135 USTPUTC(CTLESC, out);
12100 USTPUTC('\\', out); 12136 USTPUTC('\\', out);
12101 } 12137 }
12102#endif
12103 /* Backslash is retained if we are in "str" and next char isn't special */ 12138 /* Backslash is retained if we are in "str" and next char isn't special */
12104 if (dblquote 12139 if (dblquote
12105 && c != '\\' 12140 && c != '\\'
@@ -12384,7 +12419,7 @@ parsesub: {
12384#if ENABLE_FEATURE_SH_MATH 12419#if ENABLE_FEATURE_SH_MATH
12385 PARSEARITH(); 12420 PARSEARITH();
12386#else 12421#else
12387 raise_error_syntax("you disabled math support for $((arith)) syntax"); 12422 raise_error_syntax("support for $((arith)) is disabled");
12388#endif 12423#endif
12389 } else { 12424 } else {
12390 pungetc(); 12425 pungetc();
@@ -12933,7 +12968,6 @@ parseheredoc(void)
12933/* 12968/*
12934 * called by editline -- any expansions to the prompt should be added here. 12969 * called by editline -- any expansions to the prompt should be added here.
12935 */ 12970 */
12936#if ENABLE_ASH_EXPAND_PRMT
12937static const char * 12971static const char *
12938expandstr(const char *ps) 12972expandstr(const char *ps)
12939{ 12973{
@@ -12959,7 +12993,6 @@ expandstr(const char *ps)
12959 expandarg(&n, NULL, EXP_QUOTED); 12993 expandarg(&n, NULL, EXP_QUOTED);
12960 return stackblock(); 12994 return stackblock();
12961} 12995}
12962#endif
12963 12996
12964/* 12997/*
12965 * Execute a command or commands contained in a string. 12998 * Execute a command or commands contained in a string.
@@ -13497,7 +13530,7 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM)
13497 exitcode = 0; 13530 exitcode = 0;
13498 while (*ap) { 13531 while (*ap) {
13499 signo = get_signum(*ap); 13532 signo = get_signum(*ap);
13500 if (signo < 0) { 13533 if (signo < 0 || signo >= NSIG) {
13501 /* Mimic bash message exactly */ 13534 /* Mimic bash message exactly */
13502 ash_msg("%s: invalid signal specification", *ap); 13535 ash_msg("%s: invalid signal specification", *ap);
13503 exitcode = 1; 13536 exitcode = 1;
diff --git a/shell/ash_test/ash-heredoc/heredoc1.right b/shell/ash_test/ash-heredoc/heredoc1.right
index 40aa5a5fe..7fc68f3e1 100644
--- a/shell/ash_test/ash-heredoc/heredoc1.right
+++ b/shell/ash_test/ash-heredoc/heredoc1.right
@@ -1 +1,5 @@
1./heredoc1.tests: line 3: syntax error: unexpected "then" 1qwe
2asd
3123
4456
5Ok
diff --git a/shell/ash_test/ash-heredoc/heredoc1.tests b/shell/ash_test/ash-heredoc/heredoc1.tests
index a912a67c7..2eeb4726b 100755
--- a/shell/ash_test/ash-heredoc/heredoc1.tests
+++ b/shell/ash_test/ash-heredoc/heredoc1.tests
@@ -1,3 +1,9 @@
1# We used to SEGV on this: 1cat <<000; cat <<www; cat <<eee
2 2000
3<<EOF; then <W 3qwe
4asd
5www
6123
7456
8eee
9echo Ok
diff --git a/shell/ash_test/ash-heredoc/heredoc3.right b/shell/ash_test/ash-heredoc/heredoc3.right
index ce0136250..6ed517f74 100644
--- a/shell/ash_test/ash-heredoc/heredoc3.right
+++ b/shell/ash_test/ash-heredoc/heredoc3.right
@@ -1 +1,9 @@
1hello 1exit EOF-f
2"
3echo $f
4echo `echo Hello World`
5moo
6 EOF-f
7EOF-f f
8EOF-f
9Ok
diff --git a/shell/ash_test/ash-heredoc/heredoc3.tests b/shell/ash_test/ash-heredoc/heredoc3.tests
index 96c227cc1..938577a89 100755
--- a/shell/ash_test/ash-heredoc/heredoc3.tests
+++ b/shell/ash_test/ash-heredoc/heredoc3.tests
@@ -1,9 +1,12 @@
1echo hello >greeting 1f=1
2cat <<EOF && 2 cat <<- EOF-f""
3$(cat greeting) 3 exit EOF-f
4EOF 4"
5{ 5echo $f
6 echo $? 6echo `echo Hello World`
7 cat greeting 7 moo
8} >/dev/null 8 EOF-f
9rm greeting 9EOF-f f
10EOF-f
11EOF-f
12echo Ok
diff --git a/shell/ash_test/ash-heredoc/heredoc8.right b/shell/ash_test/ash-heredoc/heredoc8.right
new file mode 100644
index 000000000..af396660e
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc8.right
@@ -0,0 +1 @@
./heredoc8.tests: line 3: syntax error: unexpected "then"
diff --git a/shell/ash_test/ash-heredoc/heredoc8.tests b/shell/ash_test/ash-heredoc/heredoc8.tests
new file mode 100755
index 000000000..f7bc0737a
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc8.tests
@@ -0,0 +1,3 @@
1# ash used to SEGV on this:
2
3<<EOF; then <W
diff --git a/shell/ash_test/ash-heredoc/heredoc9.right b/shell/ash_test/ash-heredoc/heredoc9.right
new file mode 100644
index 000000000..ce0136250
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc9.right
@@ -0,0 +1 @@
hello
diff --git a/shell/ash_test/ash-heredoc/heredoc9.tests b/shell/ash_test/ash-heredoc/heredoc9.tests
new file mode 100755
index 000000000..96c227cc1
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc9.tests
@@ -0,0 +1,9 @@
1echo hello >greeting
2cat <<EOF &&
3$(cat greeting)
4EOF
5{
6 echo $?
7 cat greeting
8} >/dev/null
9rm greeting
diff --git a/shell/ash_test/ash-misc/shift1.tests b/shell/ash_test/ash-misc/shift1.tests
index 0992d9b1b..2774b35ea 100755
--- a/shell/ash_test/ash-misc/shift1.tests
+++ b/shell/ash_test/ash-misc/shift1.tests
@@ -1,5 +1,5 @@
1$THIS_SH -c 'shift; echo "$@"' 0 1 2 3 4 1$THIS_SH -c 'shift; echo "$@"' 0 1 2 3 4
2#We do abort on -1, but then we abort. bash executes echo. 2# We do complain on -1, but then we abort. bash executes echo.
3$THIS_SH -c 'shift -1; echo "$@"' 0 1 2 3 4 3$THIS_SH -c 'shift -1; echo "$@"' 0 1 2 3 4
4$THIS_SH -c 'shift 0; echo "$@"' 0 1 2 3 4 4$THIS_SH -c 'shift 0; echo "$@"' 0 1 2 3 4
5$THIS_SH -c 'shift 1; echo "$@"' 0 1 2 3 4 5$THIS_SH -c 'shift 1; echo "$@"' 0 1 2 3 4
diff --git a/shell/ash_test/ash-misc/sigint1.right b/shell/ash_test/ash-misc/sigint1.right
deleted file mode 100644
index a9094b056..000000000
--- a/shell/ash_test/ash-misc/sigint1.right
+++ /dev/null
@@ -1 +0,0 @@
1Sending SIGINT to main shell PID
diff --git a/shell/ash_test/ash-misc/sigint1.tests b/shell/ash_test/ash-misc/sigint1.tests
deleted file mode 100755
index 3d483d32a..000000000
--- a/shell/ash_test/ash-misc/sigint1.tests
+++ /dev/null
@@ -1,41 +0,0 @@
1# What should happen if non-interactive shell gets SIGINT?
2
3(sleep 1; echo Sending SIGINT to main shell PID; exec kill -INT $$) &
4
5# We create a child which exits with 0 even on SIGINT
6# (The complex command is necessary only if SIGINT is generated by ^C,
7# in this testcase even bare "sleep 2" would do because
8# in the testcase we don't send SIGINT *to the child*...)
9$THIS_SH -c 'trap "exit 0" SIGINT; sleep 2'
10
11# In one second, we (main shell) get SIGINT here.
12# The question is whether we should, or should not, exit.
13
14# bash will not stop here. It will execute next command(s).
15
16# The rationale for this is described here:
17# http://www.cons.org/cracauer/sigint.html
18#
19# Basically, bash will not exit on SIGINT immediately if it waits
20# for a child. It will wait for the child to exit.
21# If child exits NOT by dying on SIGINT, then bash will not exit.
22#
23# The idea is that the following script:
24# | emacs file.txt
25# | more cmds
26# User may use ^C to interrupt editor's ops like search. But then
27# emacs exits normally. User expects that script doesn't stop.
28#
29# This is a nice idea, but detecting "did process really exit
30# with SIGINT?" is racy. Consider:
31# | bash -c 'while true; do /bin/true; done'
32# When ^C is pressed while bash waits for /bin/true to exit,
33# it may happen that /bin/true exits with exitcode 0 before
34# ^C is delivered to it as SIGINT. bash will see SIGINT, then
35# it will see that child exited with 0, and bash will NOT EXIT.
36
37# Therefore we do not implement bash behavior.
38# I'd say that emacs need to put itself into a separate pgrp
39# to isolate shell from getting stray SIGINTs from ^C.
40
41echo Next command after SIGINT was executed
diff --git a/shell/ash_test/ash-misc/tickquote1.tests b/shell/ash_test/ash-misc/tickquote1.tests
index 90d5bbc9b..8416ad996 100755
--- a/shell/ash_test/ash-misc/tickquote1.tests
+++ b/shell/ash_test/ash-misc/tickquote1.tests
@@ -1 +1 @@
echo `"pwd` echo _`"pwd`_
diff --git a/shell/ash_test/ash-misc/wait6.right b/shell/ash_test/ash-misc/wait6.right
new file mode 100644
index 000000000..12decc137
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait6.right
@@ -0,0 +1,2 @@
10
23
diff --git a/shell/ash_test/ash-misc/wait6.tests b/shell/ash_test/ash-misc/wait6.tests
new file mode 100755
index 000000000..c23713199
--- /dev/null
+++ b/shell/ash_test/ash-misc/wait6.tests
@@ -0,0 +1,6 @@
1# In bash, "wait $!" extracts correct exitcode even if bg task has already exited
2# It prints 0, then 3:
3(sleep 0; exit 3) & sleep 1
4echo $?
5wait $!
6echo $?
diff --git a/shell/hush_test/hush-bugs/and_or_and_backgrounding.right b/shell/ash_test/ash-parsing/and_or_and_backgrounding.right
index 90ce63e01..90ce63e01 100644
--- a/shell/hush_test/hush-bugs/and_or_and_backgrounding.right
+++ b/shell/ash_test/ash-parsing/and_or_and_backgrounding.right
diff --git a/shell/hush_test/hush-bugs/and_or_and_backgrounding.tests b/shell/ash_test/ash-parsing/and_or_and_backgrounding.tests
index 05acfb863..204d44490 100755
--- a/shell/hush_test/hush-bugs/and_or_and_backgrounding.tests
+++ b/shell/ash_test/ash-parsing/and_or_and_backgrounding.tests
@@ -1,4 +1,3 @@
1# UNFIXED BUG: hush thinks that ; && || & have the same precedence.
2# According to this doc, && || have higher precedence than ; &. 1# According to this doc, && || have higher precedence than ; &.
3# See example below. 2# See example below.
4# Precedence of ; is not a problem in practice. Precedence of & is. 3# Precedence of ; is not a problem in practice. Precedence of & is.
diff --git a/shell/ash_test/ash-quoting/quoted_punct.right b/shell/ash_test/ash-quoting/quoted_punct.right
new file mode 100644
index 000000000..ab66c3ce0
--- /dev/null
+++ b/shell/ash_test/ash-quoting/quoted_punct.right
@@ -0,0 +1,35 @@
1ok
2ok
3ok
4ok
5ok
6ok
7ok
8ok
9ok
10ok
11ok
12ok
13ok
14ok
15ok
16ok
17ok
18ok
19ok
20ok
21ok
22ok
23ok
24ok
25ok
26ok
27ok
28ok
29ok
30ok
31ok
32ok
33ok
34ok
35ok
diff --git a/shell/ash_test/ash-quoting/quoted_punct.tests b/shell/ash_test/ash-quoting/quoted_punct.tests
new file mode 100755
index 000000000..83ee40bf4
--- /dev/null
+++ b/shell/ash_test/ash-quoting/quoted_punct.tests
@@ -0,0 +1,41 @@
1# Testing glob-escaping of every ASCII punctuation char
2# Some chars have more than one test
3# 21..2f
4case '!' in [\!] ) echo ok;; *) echo 'WRONG!';; esac
5case '"' in [\"] ) echo ok;; *) echo 'WRONG"';; esac
6case '#' in [\#] ) echo ok;; *) echo 'WRONG#';; esac
7case '$' in [\$] ) echo ok;; *) echo 'WRONG$';; esac
8case '%' in [\%] ) echo ok;; *) echo 'WRONG%';; esac
9case '&' in [\&] ) echo ok;; *) echo 'WRONG&';; esac
10case "'" in [\'] ) echo ok;; *) echo "WRONG'";; esac
11case '(' in [\(] ) echo ok;; *) echo 'WRONG(';; esac
12case ')' in [\)] ) echo ok;; *) echo 'WRONG)';; esac
13case '*' in [\*] ) echo ok;; *) echo 'WRONG*';; esac
14case '+' in [\+] ) echo ok;; *) echo 'WRONG+';; esac
15case ',' in [\,] ) echo ok;; *) echo 'WRONG,';; esac
16case '-' in [\-] ) echo ok;; *) echo 'WRONG-';; esac
17case '-' in [a\-c]) echo ok;; *) echo 'WRONGa\-c';; esac
18case '.' in [\.] ) echo ok;; *) echo 'WRONG.';; esac
19case '/' in [\/] ) echo ok;; *) echo 'WRONG/';; esac
20# 3a..40
21case ':' in [\:] ) echo ok;; *) echo 'WRONG:';; esac
22case ';' in [\;] ) echo ok;; *) echo 'WRONG;';; esac
23case '<' in [\<] ) echo ok;; *) echo 'WRONG<';; esac
24case '=' in [\=] ) echo ok;; *) echo 'WRONG=';; esac
25case '>' in [\>] ) echo ok;; *) echo 'WRONG>';; esac
26case '?' in [\?] ) echo ok;; *) echo 'WRONG?';; esac
27case '@' in [\@] ) echo ok;; *) echo 'WRONG@';; esac
28# 5b..60
29case '[' in [\[] ) echo ok;; *) echo 'WRONG[';; esac
30case '\' in [\\] ) echo ok;; *) echo 'WRONG\';; esac
31case '\' in \\ ) echo ok;; *) echo 'WRONG\\';; esac
32case ']' in [\]] ) echo ok;; *) echo 'WRONG]';; esac
33case ']' in [a\]]) echo ok;; *) echo 'WRONGa]';; esac
34case '^' in [\^] ) echo ok;; *) echo 'WRONG^';; esac
35case '_' in [\_] ) echo ok;; *) echo 'WRONG_';; esac
36case '`' in [\`] ) echo ok;; *) echo 'WRONG`';; esac
37# 7b..7e
38case '{' in [\{] ) echo ok;; *) echo 'WRONG{';; esac
39case '|' in [\|] ) echo ok;; *) echo 'WRONG|';; esac
40case '}' in [\}] ) echo ok;; *) echo 'WRONG}';; esac
41case '~' in [\~] ) echo ok;; *) echo 'WRONG~';; esac
diff --git a/shell/ash_test/ash-quoting/unicode_8x_chars.right b/shell/ash_test/ash-quoting/unicode_8x_chars.right
new file mode 100644
index 000000000..7780b88b4
--- /dev/null
+++ b/shell/ash_test/ash-quoting/unicode_8x_chars.right
@@ -0,0 +1,6 @@
1ok
2ok
3ok
4ok
5ok
6ok
diff --git a/shell/ash_test/ash-quoting/unicode_8x_chars.tests b/shell/ash_test/ash-quoting/unicode_8x_chars.tests
new file mode 100755
index 000000000..1258745ec
--- /dev/null
+++ b/shell/ash_test/ash-quoting/unicode_8x_chars.tests
@@ -0,0 +1,28 @@
1# Unicode: cf 80
2case π in
3( "π" ) echo ok ;;
4( * ) echo WRONG ;;
5esac
6# Unicode: cf 81
7case ρ in
8( "ρ" ) echo ok ;;
9( * ) echo WRONG ;;
10esac
11# Unicode: cf 82
12case ς in
13( "ς" ) echo ok ;;
14( * ) echo WRONG ;;
15esac
16
17case "π" in
18( π ) echo ok ;;
19( * ) echo WRONG ;;
20esac
21case "ρ" in
22( ρ ) echo ok ;;
23( * ) echo WRONG ;;
24esac
25case "ς" in
26( ς ) echo ok ;;
27( * ) echo WRONG ;;
28esac
diff --git a/shell/ash_test/ash-redir/redir5.right b/shell/ash_test/ash-redir/redir5.right
index 9d0877765..52cce4feb 100644
--- a/shell/ash_test/ash-redir/redir5.right
+++ b/shell/ash_test/ash-redir/redir5.right
@@ -1,2 +1,4 @@
1./redir5.tests: line 2: 10: Bad file descriptor 1Backgrounded pipes shall have their stdin redirected to /dev/null
2OK 2Zero:0
3Zero:0
4Done
diff --git a/shell/ash_test/ash-redir/redir5.tests b/shell/ash_test/ash-redir/redir5.tests
index 91b0c1ff9..957f9c81f 100755
--- a/shell/ash_test/ash-redir/redir5.tests
+++ b/shell/ash_test/ash-redir/redir5.tests
@@ -1,3 +1,13 @@
1# ash uses fd 10 (usually) for reading the script 1echo "Backgrounded pipes shall have their stdin redirected to /dev/null"
2echo LOST >&10 2
3echo OK 3# 1. bash does not redirect stdin to /dev/null if it is interactive.
4# hush does it always (this is allowed by standards).
5
6# 2. Failure will result in this script hanging
7
8cat & wait; echo Zero:$?
9
10# This does not work for bash! bash bug?
11cat | cat & wait; echo Zero:$?
12
13echo Done
diff --git a/shell/ash_test/ash-redir/redir_to_bad_fd.right b/shell/ash_test/ash-redir/redir_to_bad_fd.right
new file mode 100644
index 000000000..43b8af293
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir_to_bad_fd.right
@@ -0,0 +1,2 @@
1./redir_to_bad_fd.tests: line 2: 10: Bad file descriptor
2OK
diff --git a/shell/ash_test/ash-redir/redir_to_bad_fd.tests b/shell/ash_test/ash-redir/redir_to_bad_fd.tests
new file mode 100755
index 000000000..91b0c1ff9
--- /dev/null
+++ b/shell/ash_test/ash-redir/redir_to_bad_fd.tests
@@ -0,0 +1,3 @@
1# ash uses fd 10 (usually) for reading the script
2echo LOST >&10
3echo OK
diff --git a/shell/ash_test/ash-vars/var-utf8-length.tests b/shell/ash_test/ash-vars/var-utf8-length.tests
index d04b2cbb6..b6e87f191 100755
--- a/shell/ash_test/ash-vars/var-utf8-length.tests
+++ b/shell/ash_test/ash-vars/var-utf8-length.tests
@@ -1,2 +1,4 @@
1LANG=en_US.UTF-8
2LC_ALL=en_US.UTF-8
1X=abcdÉfghÍjklmnÓpqrstÚvwcyz 3X=abcdÉfghÍjklmnÓpqrstÚvwcyz
2echo ${#X} 4echo ${#X}
diff --git a/shell/ash_test/ash-vars/var_bash1a.tests b/shell/ash_test/ash-vars/var_bash1a.tests
index 551dd9acc..c75c2dc53 100755
--- a/shell/ash_test/ash-vars/var_bash1a.tests
+++ b/shell/ash_test/ash-vars/var_bash1a.tests
@@ -5,7 +5,7 @@ echo "parameter '${parameter}'"
5echo "varoffset2 '${parameter:${offset}}'" 5echo "varoffset2 '${parameter:${offset}}'"
6echo "varoffset-2 '${parameter:${noffset}}'" 6echo "varoffset-2 '${parameter:${noffset}}'"
7echo "literal '2' '${parameter:2}'" 7echo "literal '2' '${parameter:2}'"
8# This is not inrpreted as ${VAR:POS{:LEN}}, 8# This is not interpreted as ${VAR:POS{:LEN}},
9# but as ${VAR:=WORD} - if VAR is unset or null, substitute WORD 9# but as ${VAR:=WORD} - if VAR is unset or null, substitute WORD
10echo "literal '-2' '${parameter:-2}'" 10echo "literal '-2' '${parameter:-2}'"
11echo "literal ' -2' '${parameter: -2}'" 11echo "literal ' -2' '${parameter: -2}'"
diff --git a/shell/ash_test/ash-vars/var_bash1b.right b/shell/ash_test/ash-vars/var_bash1b.right
new file mode 100644
index 000000000..fafc0f07c
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash1b.right
@@ -0,0 +1,23 @@
1all |0123456
24: |456
34:2 |45
44:-1 |45
54:-2 |4
64:-3 |
7-4: |3456
8-4:2 |34
9-4:-1 |345
10-4:-2 |34
11-4:-3 |3
12-4:-4 |
13-4:i=2 |34
14-4:i=-2|34
15-4:i=-3|3
16-4:i=-4|
17-5: |23456
18-6: |123456
19-7: |0123456
20-8: |
21-9: |
22-9:-99 |
23Ok:0
diff --git a/shell/ash_test/ash-vars/var_bash1b.tests b/shell/ash_test/ash-vars/var_bash1b.tests
new file mode 100755
index 000000000..efbdef35c
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash1b.tests
@@ -0,0 +1,24 @@
1set -- 0123456
2 echo "all |"$1
3 echo "4: |"${1:4}
4 echo "4:2 |"${1:4:2}
5 echo "4:-1 |"${1:4:-1}
6 echo "4:-2 |"${1:4:-2}
7 echo "4:-3 |"${1:4:-3}
8 echo "-4: |"${1: -4}
9 echo "-4:2 |"${1: -4:2}
10 echo "-4:-1 |"${1: -4:-1}
11 echo "-4:-2 |"${1: -4:-2}
12 echo "-4:-3 |"${1: -4:-3}
13 echo "-4:-4 |"${1: -4:-4}
14i=2; echo "-4:i=2 |"${1: -4:i}
15i=-2; echo "-4:i=-2|"${1: -4:i}
16i=-3; echo "-4:i=-3|"${1: -4:i}
17i=-4; echo "-4:i=-4|"${1: -4:i}
18 echo "-5: |"${1: -5}
19 echo "-6: |"${1: -6}
20 echo "-7: |"${1: -7}
21 echo "-8: |"${1: -8}
22 echo "-9: |"${1: -9}
23 echo "-9:-99 |"${1: -9:-99}
24echo Ok:$?
diff --git a/shell/ash_test/ash-vars/var_bash6.right b/shell/ash_test/ash-vars/var_bash6.right
new file mode 100644
index 000000000..63fc23df8
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash6.right
@@ -0,0 +1,5 @@
1Expected Actual
2a*z : a*z
3\z : \z
4a1z a2z: a1z a2z
5z : z
diff --git a/shell/ash_test/ash-vars/var_bash6.tests b/shell/ash_test/ash-vars/var_bash6.tests
new file mode 100755
index 000000000..cf2e4f020
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash6.tests
@@ -0,0 +1,9 @@
1# This testcase checks globbing correctness in ${v/a/b}
2
3>a1z; >a2z;
4 echo 'Expected' 'Actual'
5v='a bz'; echo 'a*z :' "${v/a*z/a*z}"
6v='a bz'; echo '\z :' "${v/a*z/\z}"
7v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z}
8v='a bz'; echo 'z :' ${v/a*z/\z}
9rm a1z a2z
diff --git a/shell/ash_test/run-all b/shell/ash_test/run-all
index 354cc1fcf..983e6d184 100755
--- a/shell/ash_test/run-all
+++ b/shell/ash_test/run-all
@@ -1,6 +1,6 @@
1#!/bin/sh 1#!/bin/sh
2 2
3TOPDIR=$PWD 3TOPDIR=`pwd`
4 4
5test -x ash || { 5test -x ash || {
6 echo "No ./ash - creating a link to ../../busybox" 6 echo "No ./ash - creating a link to ../../busybox"
@@ -10,67 +10,73 @@ test -x printenv || gcc -O2 -o printenv printenv.c || exit $?
10test -x recho || gcc -O2 -o recho recho.c || exit $? 10test -x recho || gcc -O2 -o recho recho.c || exit $?
11test -x zecho || gcc -O2 -o zecho zecho.c || exit $? 11test -x zecho || gcc -O2 -o zecho zecho.c || exit $?
12 12
13PATH="$PWD:$PATH" # for ash and recho/zecho/printenv 13PATH="`pwd`:$PATH" # for ash and recho/zecho/printenv
14export PATH 14export PATH
15 15
16THIS_SH="$PWD/ash" 16THIS_SH="`pwd`/ash"
17export THIS_SH 17export THIS_SH
18 18
19do_test() 19do_test()
20{ 20{
21 test -d "$1" || return 0 21 test -d "$1" || return 0
22# echo do_test "$1" 22 d=${d%/}
23 # $1 but with / replaced by # so that it can be used as filename part 23# echo Running tests in directory "$1"
24 noslash=`echo "$1" | sed 's:/:#:g'` 24 # $1 but with / replaced by # so that it can be used as filename part
25 ( 25 noslash=`echo "$1" | sed 's:/:#:g'`
26 cd "$1" || { echo "cannot cd $1!"; exit 1; } 26 (
27 for x in run-*; do 27 cd "$1" || { echo "cannot cd $1!"; exit 1; }
28 test -f "$x" || continue 28 for x in run-*; do
29 case "$x" in 29 test -f "$x" || continue
30 "$0"|run-minimal|run-gprof) ;; 30 case "$x" in
31 *.orig|*~) ;; 31 "$0"|run-minimal|run-gprof) ;;
32 #*) echo $x ; sh $x ;; 32 *.orig|*~) ;;
33 *) 33 #*) echo $x ; sh $x ;;
34 echo -n "$1/$x: " 34 *)
35 sh "$x" >"$TOPDIR/$noslash-$x.fail" 2>&1 && \ 35 echo -n "$1/$x:"
36 { echo "ok"; rm "$TOPDIR/$noslash-$x.fail"; } || echo "fail"; 36 sh "$x" >"$TOPDIR/$noslash-$x.fail" 2>&1 && \
37 ;; 37 { { echo " ok"; rm "$TOPDIR/$noslash-$x.fail"; } || echo " fail"; }
38 esac 38 ;;
39 done 39 esac
40 # Many bash run-XXX scripts just do this, 40 done
41 # no point in duplication it all over the place 41 # Many bash run-XXX scripts just do this,
42 for x in *.tests; do 42 # no point in duplication it all over the place
43 test -x "$x" || continue 43 for x in *.tests; do
44 name="${x%%.tests}" 44 test -x "$x" || continue
45 test -f "$name.right" || continue 45 name="${x%%.tests}"
46 echo -n "$1/$x: " 46 test -f "$name.right" || continue
47 { 47# echo Running test: "$x"
48 "$THIS_SH" "./$x" >"$name.xx" 2>&1 48 echo -n "$1/$x:"
49 diff -u "$name.xx" "$name.right" >"$TOPDIR/$noslash-$x.fail" \ 49 {
50 && rm -f "$name.xx" "$TOPDIR/$noslash-$x.fail" 50 "$THIS_SH" "./$x" >"$name.xx" 2>&1
51 } && echo "ok" || echo "fail" 51 diff -u "$name.xx" "$name.right" >"$TOPDIR/$noslash-$x.fail" \
52 done 52 && rm -f "$name.xx" "$TOPDIR/$noslash-$x.fail"
53 ) 53 } && echo " ok" || echo " fail"
54 done
55 )
54} 56}
55 57
56# main part of this script 58# Main part of this script
57# Usage: run-all [directories] 59# Usage: run-all [directories]
58 60
61ret=0
62
59if [ $# -lt 1 ]; then 63if [ $# -lt 1 ]; then
60 # All sub directories 64 # All sub directories
61 modules=`ls -d ash-*` 65 modules=`ls -d ash-*`
62 # If you want to test ash against hush and msh testsuites 66 # If you want to test ash against hush testsuite
63 # (have to copy hush_test and msh_test dirs to current dir first): 67 # (have to copy hush_test dir to current dir first):
64 #modules=`ls -d ash-* hush_test/hush-* msh_test/msh-*` 68 #modules=`ls -d ash-* hush_test/hush-*`
65 69
66 for module in $modules; do 70 for module in $modules; do
67 do_test $module 71 do_test $module || ret=1
68 done 72 done
69else 73else
70 while [ $# -ge 1 ]; do 74 while [ $# -ge 1 ]; do
71 if [ -d $1 ]; then 75 if [ -d $1 ]; then
72 do_test $1 76 do_test $1 || ret=1
73 fi 77 fi
74 shift 78 shift
75 done 79 done
76fi 80fi
81
82exit ${ret}
diff --git a/shell/hush.c b/shell/hush.c
index 125463a56..f6da826d3 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -41,14 +41,28 @@
41 * 41 *
42 * TODOs: 42 * TODOs:
43 * grep for "TODO" and fix (some of them are easy) 43 * grep for "TODO" and fix (some of them are easy)
44 * make complex ${var%...} constructs support optional
45 * make here documents optional
44 * special variables (done: PWD, PPID, RANDOM) 46 * special variables (done: PWD, PPID, RANDOM)
47 * follow IFS rules more precisely, including update semantics
45 * tilde expansion 48 * tilde expansion
46 * aliases 49 * aliases
47 * follow IFS rules more precisely, including update semantics
48 * builtins mandated by standards we don't support: 50 * builtins mandated by standards we don't support:
49 * [un]alias, command, fc, getopts, newgrp, readonly, times 51 * [un]alias, command, fc, getopts, times:
50 * make complex ${var%...} constructs support optional 52 * command -v CMD: print "/path/to/CMD"
51 * make here documents optional 53 * prints "CMD" for builtins
54 * prints "alias ALIAS='EXPANSION'" for aliases
55 * prints nothing and sets $? to 1 if not found
56 * command -V CMD: print "CMD is /path/CMD|a shell builtin|etc"
57 * command [-p] CMD: run CMD, even if a function CMD also exists
58 * (can use this to override standalone shell as well)
59 * -p: use default $PATH
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
64 * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands
65 * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP
52 * 66 *
53 * Bash compat TODO: 67 * Bash compat TODO:
54 * redirection of stdout+stderr: &> and >& 68 * redirection of stdout+stderr: &> and >&
@@ -64,8 +78,13 @@
64 * The EXPR is evaluated according to ARITHMETIC EVALUATION. 78 * The EXPR is evaluated according to ARITHMETIC EVALUATION.
65 * This is exactly equivalent to let "EXPR". 79 * This is exactly equivalent to let "EXPR".
66 * $[EXPR]: synonym for $((EXPR)) 80 * $[EXPR]: synonym for $((EXPR))
81 * indirect expansion: ${!VAR}
82 * substring op on @: ${@:n:m}
67 * 83 *
68 * Won't do: 84 * Won't do:
85 * Some builtins mandated by standards:
86 * newgrp [GRP]: not a builtin in bash but a suid binary
87 * which spawns a new shell with new group ID
69 * In bash, export builtin is special, its arguments are assignments 88 * In bash, export builtin is special, its arguments are assignments
70 * and therefore expansion of them should be "one-word" expansion: 89 * and therefore expansion of them should be "one-word" expansion:
71 * $ export i=`echo 'a b'` # export has one arg: "i=a b" 90 * $ export i=`echo 'a b'` # export has one arg: "i=a b"
@@ -219,6 +238,13 @@
219//config: help 238//config: help
220//config: export -n unexports variables. It is a bash extension. 239//config: export -n unexports variables. It is a bash extension.
221//config: 240//config:
241//config:config HUSH_READONLY
242//config: bool "readonly builtin"
243//config: default y
244//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
245//config: help
246//config: Enable support for read-only variables.
247//config:
222//config:config HUSH_KILL 248//config:config HUSH_KILL
223//config: bool "kill builtin (supports kill %jobspec)" 249//config: bool "kill builtin (supports kill %jobspec)"
224//config: default y 250//config: default y
@@ -268,17 +294,9 @@
268//config: bool "memleak builtin (debugging)" 294//config: bool "memleak builtin (debugging)"
269//config: default n 295//config: default n
270//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH 296//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
271//config:
272//config:config MSH
273//config: bool "msh (deprecated: aliased to hush)"
274//config: default n
275//config: select HUSH
276//config: help
277//config: msh is deprecated and will be removed, please migrate to hush.
278 297
279//applet:IF_HUSH(APPLET(hush, BB_DIR_BIN, BB_SUID_DROP)) 298//applet:IF_HUSH(APPLET(hush, BB_DIR_BIN, BB_SUID_DROP))
280// APPLET_ODDNAME:name main location suid_type help 299// APPLET_ODDNAME:name main location suid_type help
281//applet:IF_MSH( APPLET_ODDNAME(msh, hush, BB_DIR_BIN, BB_SUID_DROP, hush))
282//applet:IF_SH_IS_HUSH( APPLET_ODDNAME(sh, hush, BB_DIR_BIN, BB_SUID_DROP, hush)) 300//applet:IF_SH_IS_HUSH( APPLET_ODDNAME(sh, hush, BB_DIR_BIN, BB_SUID_DROP, hush))
283//applet:IF_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, BB_DIR_BIN, BB_SUID_DROP, hush)) 301//applet:IF_BASH_IS_HUSH(APPLET_ODDNAME(bash, hush, BB_DIR_BIN, BB_SUID_DROP, hush))
284 302
@@ -293,7 +311,7 @@
293 * therefore we don't show them either. 311 * therefore we don't show them either.
294 */ 312 */
295//usage:#define hush_trivial_usage 313//usage:#define hush_trivial_usage
296//usage: "[-nxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]" 314//usage: "[-enxl] [-c 'SCRIPT' [ARG0 [ARGS]] / FILE [ARGS]]"
297//usage:#define hush_full_usage "\n\n" 315//usage:#define hush_full_usage "\n\n"
298//usage: "Unix shell interpreter" 316//usage: "Unix shell interpreter"
299 317
@@ -331,9 +349,9 @@
331/* Separate defines document which part of code implements what */ 349/* Separate defines document which part of code implements what */
332#define BASH_PATTERN_SUBST ENABLE_HUSH_BASH_COMPAT 350#define BASH_PATTERN_SUBST ENABLE_HUSH_BASH_COMPAT
333#define BASH_SUBSTR ENABLE_HUSH_BASH_COMPAT 351#define BASH_SUBSTR ENABLE_HUSH_BASH_COMPAT
334#define BASH_TEST2 ENABLE_HUSH_BASH_COMPAT
335#define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT 352#define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT
336#define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT 353#define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT
354#define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST)
337 355
338 356
339/* Build knobs */ 357/* Build knobs */
@@ -410,6 +428,7 @@
410#define debug_printf_expand(...) do {} while (0) 428#define debug_printf_expand(...) do {} while (0)
411#define debug_printf_varexp(...) do {} while (0) 429#define debug_printf_varexp(...) do {} while (0)
412#define debug_printf_glob(...) do {} while (0) 430#define debug_printf_glob(...) do {} while (0)
431#define debug_printf_redir(...) do {} while (0)
413#define debug_printf_list(...) do {} while (0) 432#define debug_printf_list(...) do {} while (0)
414#define debug_printf_subst(...) do {} while (0) 433#define debug_printf_subst(...) do {} while (0)
415#define debug_printf_clean(...) do {} while (0) 434#define debug_printf_clean(...) do {} while (0)
@@ -753,6 +772,7 @@ struct function {
753static const char o_opt_strings[] ALIGN1 = 772static const char o_opt_strings[] ALIGN1 =
754 "pipefail\0" 773 "pipefail\0"
755 "noexec\0" 774 "noexec\0"
775 "errexit\0"
756#if ENABLE_HUSH_MODE_X 776#if ENABLE_HUSH_MODE_X
757 "xtrace\0" 777 "xtrace\0"
758#endif 778#endif
@@ -760,6 +780,7 @@ static const char o_opt_strings[] ALIGN1 =
760enum { 780enum {
761 OPT_O_PIPEFAIL, 781 OPT_O_PIPEFAIL,
762 OPT_O_NOEXEC, 782 OPT_O_NOEXEC,
783 OPT_O_ERREXIT,
763#if ENABLE_HUSH_MODE_X 784#if ENABLE_HUSH_MODE_X
764 OPT_O_XTRACE, 785 OPT_O_XTRACE,
765#endif 786#endif
@@ -816,6 +837,25 @@ struct globals {
816#else 837#else
817# define G_saved_tty_pgrp 0 838# define G_saved_tty_pgrp 0
818#endif 839#endif
840 /* How deeply are we in context where "set -e" is ignored */
841 int errexit_depth;
842 /* "set -e" rules (do we follow them correctly?):
843 * Exit if pipe, list, or compound command exits with a non-zero status.
844 * Shell does not exit if failed command is part of condition in
845 * if/while, part of && or || list except the last command, any command
846 * in a pipe but the last, or if the command's return value is being
847 * inverted with !. If a compound command other than a subshell returns a
848 * non-zero status because a command failed while -e was being ignored, the
849 * shell does not exit. A trap on ERR, if set, is executed before the shell
850 * exits [ERR is a bashism].
851 *
852 * If a compound command or function executes in a context where -e is
853 * ignored, none of the commands executed within are affected by the -e
854 * setting. If a compound command or function sets -e while executing in a
855 * context where -e is ignored, that setting does not have any effect until
856 * the compound command or the command containing the function call completes.
857 */
858
819 char o_opt[NUM_OPT_O]; 859 char o_opt[NUM_OPT_O];
820#if ENABLE_HUSH_MODE_X 860#if ENABLE_HUSH_MODE_X
821# define G_x_mode (G.o_opt[OPT_O_XTRACE]) 861# define G_x_mode (G.o_opt[OPT_O_XTRACE])
@@ -839,6 +879,7 @@ struct globals {
839 smallint exiting; /* used to prevent EXIT trap recursion */ 879 smallint exiting; /* used to prevent EXIT trap recursion */
840 /* These four support $?, $#, and $1 */ 880 /* These four support $?, $#, and $1 */
841 smalluint last_exitcode; 881 smalluint last_exitcode;
882 smalluint last_bg_pid_exitcode;
842#if ENABLE_HUSH_SET 883#if ENABLE_HUSH_SET
843 /* are global_argv and global_argv[1..n] malloced? (note: not [0]) */ 884 /* are global_argv and global_argv[1..n] malloced? (note: not [0]) */
844 smalluint global_args_malloced; 885 smalluint global_args_malloced;
@@ -929,6 +970,9 @@ static int builtin_exit(char **argv) FAST_FUNC;
929#if ENABLE_HUSH_EXPORT 970#if ENABLE_HUSH_EXPORT
930static int builtin_export(char **argv) FAST_FUNC; 971static int builtin_export(char **argv) FAST_FUNC;
931#endif 972#endif
973#if ENABLE_HUSH_READONLY
974static int builtin_readonly(char **argv) FAST_FUNC;
975#endif
932#if ENABLE_HUSH_JOB 976#if ENABLE_HUSH_JOB
933static int builtin_fg_bg(char **argv) FAST_FUNC; 977static int builtin_fg_bg(char **argv) FAST_FUNC;
934static int builtin_jobs(char **argv) FAST_FUNC; 978static int builtin_jobs(char **argv) FAST_FUNC;
@@ -1047,6 +1091,9 @@ static const struct built_in_command bltins1[] = {
1047#if ENABLE_HUSH_READ 1091#if ENABLE_HUSH_READ
1048 BLTIN("read" , builtin_read , "Input into variable"), 1092 BLTIN("read" , builtin_read , "Input into variable"),
1049#endif 1093#endif
1094#if ENABLE_HUSH_READONLY
1095 BLTIN("readonly" , builtin_readonly, "Make variables read-only"),
1096#endif
1050#if ENABLE_HUSH_FUNCTIONS 1097#if ENABLE_HUSH_FUNCTIONS
1051 BLTIN("return" , builtin_return , "Return from function"), 1098 BLTIN("return" , builtin_return , "Return from function"),
1052#endif 1099#endif
@@ -1154,6 +1201,10 @@ static const struct built_in_command bltins2[] = {
1154# define DEBUG_GLOB 0 1201# define DEBUG_GLOB 0
1155#endif 1202#endif
1156 1203
1204#ifndef debug_printf_redir
1205# define debug_printf_redir(...) (indent(), fdprintf(2, __VA_ARGS__))
1206#endif
1207
1157#ifndef debug_printf_list 1208#ifndef debug_printf_list
1158# define debug_printf_list(...) (indent(), fdprintf(2, __VA_ARGS__)) 1209# define debug_printf_list(...) (indent(), fdprintf(2, __VA_ARGS__))
1159#endif 1210#endif
@@ -1389,12 +1440,30 @@ static void free_strings(char **strings)
1389 free(strings); 1440 free(strings);
1390} 1441}
1391 1442
1443static int fcntl_F_DUPFD(int fd, int avoid_fd)
1444{
1445 int newfd;
1446 repeat:
1447 newfd = fcntl(fd, F_DUPFD, avoid_fd + 1);
1448 if (newfd < 0) {
1449 if (errno == EBUSY)
1450 goto repeat;
1451 if (errno == EINTR)
1452 goto repeat;
1453 }
1454 return newfd;
1455}
1392 1456
1393static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC) 1457static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC, int avoid_fd)
1394{ 1458{
1395 /* We avoid taking stdio fds. Mimicking ash: use fds above 9 */ 1459 int newfd;
1396 int newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, 10); 1460 repeat:
1461 newfd = fcntl(fd, F_DUPFD_maybe_CLOEXEC, avoid_fd + 1);
1397 if (newfd < 0) { 1462 if (newfd < 0) {
1463 if (errno == EBUSY)
1464 goto repeat;
1465 if (errno == EINTR)
1466 goto repeat;
1398 /* fd was not open? */ 1467 /* fd was not open? */
1399 if (errno == EBADF) 1468 if (errno == EBADF)
1400 return fd; 1469 return fd;
@@ -1432,13 +1501,14 @@ static void fclose_and_forget(FILE *fp)
1432 } 1501 }
1433 fclose(fp); 1502 fclose(fp);
1434} 1503}
1435static int save_FILEs_on_redirect(int fd) 1504static int save_FILEs_on_redirect(int fd, int avoid_fd)
1436{ 1505{
1437 struct FILE_list *fl = G.FILE_list; 1506 struct FILE_list *fl = G.FILE_list;
1438 while (fl) { 1507 while (fl) {
1439 if (fd == fl->fd) { 1508 if (fd == fl->fd) {
1440 /* We use it only on script files, they are all CLOEXEC */ 1509 /* We use it only on script files, they are all CLOEXEC */
1441 fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC); 1510 fl->fd = xdup_and_close(fd, F_DUPFD_CLOEXEC, avoid_fd);
1511 debug_printf_redir("redirect_fd %d: matches a script fd, moving it to %d\n", fd, fl->fd);
1442 return 1; 1512 return 1;
1443 } 1513 }
1444 fl = fl->next; 1514 fl = fl->next;
@@ -1451,13 +1521,14 @@ static void restore_redirected_FILEs(void)
1451 while (fl) { 1521 while (fl) {
1452 int should_be = fileno(fl->fp); 1522 int should_be = fileno(fl->fp);
1453 if (fl->fd != should_be) { 1523 if (fl->fd != should_be) {
1524 debug_printf_redir("restoring script fd from %d to %d\n", fl->fd, should_be);
1454 xmove_fd(fl->fd, should_be); 1525 xmove_fd(fl->fd, should_be);
1455 fl->fd = should_be; 1526 fl->fd = should_be;
1456 } 1527 }
1457 fl = fl->next; 1528 fl = fl->next;
1458 } 1529 }
1459} 1530}
1460#if ENABLE_FEATURE_SH_STANDALONE 1531#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
1461static void close_all_FILE_list(void) 1532static void close_all_FILE_list(void)
1462{ 1533{
1463 struct FILE_list *fl = G.FILE_list; 1534 struct FILE_list *fl = G.FILE_list;
@@ -1486,8 +1557,6 @@ typedef struct save_arg_t {
1486 1557
1487static void save_and_replace_G_args(save_arg_t *sv, char **argv) 1558static void save_and_replace_G_args(save_arg_t *sv, char **argv)
1488{ 1559{
1489 int n;
1490
1491 sv->sv_argv0 = argv[0]; 1560 sv->sv_argv0 = argv[0];
1492 sv->sv_g_argv = G.global_argv; 1561 sv->sv_g_argv = G.global_argv;
1493 sv->sv_g_argc = G.global_argc; 1562 sv->sv_g_argc = G.global_argc;
@@ -1497,10 +1566,7 @@ static void save_and_replace_G_args(save_arg_t *sv, char **argv)
1497 G.global_argv = argv; 1566 G.global_argv = argv;
1498 IF_HUSH_SET(G.global_args_malloced = 0;) 1567 IF_HUSH_SET(G.global_args_malloced = 0;)
1499 1568
1500 n = 1; 1569 G.global_argc = 1 + string_array_len(argv + 1);
1501 while (*++argv)
1502 n++;
1503 G.global_argc = n;
1504} 1570}
1505 1571
1506static void restore_G_args(save_arg_t *sv, char **argv) 1572static void restore_G_args(save_arg_t *sv, char **argv)
@@ -1991,32 +2057,19 @@ static const char* FAST_FUNC get_local_var_value(const char *name)
1991 2057
1992/* str holds "NAME=VAL" and is expected to be malloced. 2058/* str holds "NAME=VAL" and is expected to be malloced.
1993 * We take ownership of it. 2059 * We take ownership of it.
1994 * flg_export:
1995 * 0: do not change export flag
1996 * (if creating new variable, flag will be 0)
1997 * 1: set export flag and putenv the variable
1998 * -1: clear export flag and unsetenv the variable
1999 * flg_read_only is set only when we handle -R var=val
2000 */ 2060 */
2001#if !BB_MMU && ENABLE_HUSH_LOCAL 2061#define SETFLAG_EXPORT (1 << 0)
2002/* all params are used */ 2062#define SETFLAG_UNEXPORT (1 << 1)
2003#elif BB_MMU && ENABLE_HUSH_LOCAL 2063#define SETFLAG_MAKE_RO (1 << 2)
2004#define set_local_var(str, flg_export, local_lvl, flg_read_only) \ 2064#define SETFLAG_LOCAL_SHIFT 3
2005 set_local_var(str, flg_export, local_lvl) 2065static int set_local_var(char *str, unsigned flags)
2006#elif BB_MMU && !ENABLE_HUSH_LOCAL
2007#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
2008 set_local_var(str, flg_export)
2009#elif !BB_MMU && !ENABLE_HUSH_LOCAL
2010#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
2011 set_local_var(str, flg_export, flg_read_only)
2012#endif
2013static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_only)
2014{ 2066{
2015 struct variable **var_pp; 2067 struct variable **var_pp;
2016 struct variable *cur; 2068 struct variable *cur;
2017 char *free_me = NULL; 2069 char *free_me = NULL;
2018 char *eq_sign; 2070 char *eq_sign;
2019 int name_len; 2071 int name_len;
2072 IF_HUSH_LOCAL(unsigned local_lvl = (flags >> SETFLAG_LOCAL_SHIFT);)
2020 2073
2021 eq_sign = strchr(str, '='); 2074 eq_sign = strchr(str, '=');
2022 if (!eq_sign) { /* not expected to ever happen? */ 2075 if (!eq_sign) { /* not expected to ever happen? */
@@ -2034,14 +2087,13 @@ static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_
2034 2087
2035 /* We found an existing var with this name */ 2088 /* We found an existing var with this name */
2036 if (cur->flg_read_only) { 2089 if (cur->flg_read_only) {
2037#if !BB_MMU 2090 bb_error_msg("%s: readonly variable", str);
2038 if (!flg_read_only)
2039#endif
2040 bb_error_msg("%s: readonly variable", str);
2041 free(str); 2091 free(str);
2092//NOTE: in bash, assignment in "export READONLY_VAR=Z" fails, and sets $?=1,
2093//but export per se succeeds (does put the var in env). We don't mimic that.
2042 return -1; 2094 return -1;
2043 } 2095 }
2044 if (flg_export == -1) { // "&& cur->flg_export" ? 2096 if (flags & SETFLAG_UNEXPORT) { // && cur->flg_export ?
2045 debug_printf_env("%s: unsetenv '%s'\n", __func__, str); 2097 debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
2046 *eq_sign = '\0'; 2098 *eq_sign = '\0';
2047 unsetenv(str); 2099 unsetenv(str);
@@ -2065,7 +2117,7 @@ static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_
2065 * z=z 2117 * z=z
2066 */ 2118 */
2067 if (cur->flg_export) 2119 if (cur->flg_export)
2068 flg_export = 1; 2120 flags |= SETFLAG_EXPORT;
2069 break; 2121 break;
2070 } 2122 }
2071#endif 2123#endif
@@ -2096,24 +2148,24 @@ static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_
2096 2148
2097 /* Not found - create new variable struct */ 2149 /* Not found - create new variable struct */
2098 cur = xzalloc(sizeof(*cur)); 2150 cur = xzalloc(sizeof(*cur));
2099#if ENABLE_HUSH_LOCAL 2151 IF_HUSH_LOCAL(cur->func_nest_level = local_lvl;)
2100 cur->func_nest_level = local_lvl;
2101#endif
2102 cur->next = *var_pp; 2152 cur->next = *var_pp;
2103 *var_pp = cur; 2153 *var_pp = cur;
2104 2154
2105 set_str_and_exp: 2155 set_str_and_exp:
2106 cur->varstr = str; 2156 cur->varstr = str;
2107#if !BB_MMU
2108 cur->flg_read_only = flg_read_only;
2109#endif
2110 exp: 2157 exp:
2111 if (flg_export == 1) 2158#if !BB_MMU || ENABLE_HUSH_READONLY
2159 if (flags & SETFLAG_MAKE_RO) {
2160 cur->flg_read_only = 1;
2161 }
2162#endif
2163 if (flags & SETFLAG_EXPORT)
2112 cur->flg_export = 1; 2164 cur->flg_export = 1;
2113 if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S') 2165 if (name_len == 4 && cur->varstr[0] == 'P' && cur->varstr[1] == 'S')
2114 cmdedit_update_prompt(); 2166 cmdedit_update_prompt();
2115 if (cur->flg_export) { 2167 if (cur->flg_export) {
2116 if (flg_export == -1) { 2168 if (flags & SETFLAG_UNEXPORT) {
2117 cur->flg_export = 0; 2169 cur->flg_export = 0;
2118 /* unsetenv was already done */ 2170 /* unsetenv was already done */
2119 } else { 2171 } else {
@@ -2130,10 +2182,9 @@ static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_
2130} 2182}
2131 2183
2132/* Used at startup and after each cd */ 2184/* Used at startup and after each cd */
2133static void set_pwd_var(int exp) 2185static void set_pwd_var(unsigned flag)
2134{ 2186{
2135 set_local_var(xasprintf("PWD=%s", get_cwd(/*force:*/ 1)), 2187 set_local_var(xasprintf("PWD=%s", get_cwd(/*force:*/ 1)), flag);
2136 /*exp:*/ exp, /*lvl:*/ 0, /*ro:*/ 0);
2137} 2188}
2138 2189
2139static int unset_local_var_len(const char *name, int name_len) 2190static int unset_local_var_len(const char *name, int name_len)
@@ -2191,7 +2242,7 @@ static void unset_vars(char **strings)
2191static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) 2242static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
2192{ 2243{
2193 char *var = xasprintf("%s=%s", name, val); 2244 char *var = xasprintf("%s=%s", name, val);
2194 set_local_var(var, /*flags:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); 2245 set_local_var(var, /*flag:*/ 0);
2195} 2246}
2196#endif 2247#endif
2197 2248
@@ -2234,16 +2285,32 @@ static struct variable *set_vars_and_save_old(char **strings)
2234 if (eq) { 2285 if (eq) {
2235 var_pp = get_ptr_to_local_var(*s, eq - *s); 2286 var_pp = get_ptr_to_local_var(*s, eq - *s);
2236 if (var_pp) { 2287 if (var_pp) {
2237 /* Remove variable from global linked list */
2238 var_p = *var_pp; 2288 var_p = *var_pp;
2289 if (var_p->flg_read_only) {
2290 char **p;
2291 bb_error_msg("%s: readonly variable", *s);
2292 /*
2293 * "VAR=V BLTIN" unsets VARs after BLTIN completes.
2294 * If VAR is readonly, leaving it in the list
2295 * after asssignment error (msg above)
2296 * causes doubled error message later, on unset.
2297 */
2298 debug_printf_env("removing/freeing '%s' element\n", *s);
2299 free(*s);
2300 p = s;
2301 do { *p = p[1]; p++; } while (*p);
2302 goto next;
2303 }
2304 /* Remove variable from global linked list */
2239 debug_printf_env("%s: removing '%s'\n", __func__, var_p->varstr); 2305 debug_printf_env("%s: removing '%s'\n", __func__, var_p->varstr);
2240 *var_pp = var_p->next; 2306 *var_pp = var_p->next;
2241 /* Add it to returned list */ 2307 /* Add it to returned list */
2242 var_p->next = old; 2308 var_p->next = old;
2243 old = var_p; 2309 old = var_p;
2244 } 2310 }
2245 set_local_var(*s, /*exp:*/ 1, /*lvl:*/ 0, /*ro:*/ 0); 2311 set_local_var(*s, SETFLAG_EXPORT);
2246 } 2312 }
2313 next:
2247 s++; 2314 s++;
2248 } 2315 }
2249 return old; 2316 return old;
@@ -3339,12 +3406,49 @@ static void done_pipe(struct parse_context *ctx, pipe_style type)
3339 debug_printf_parse("done_pipe entered, followup %d\n", type); 3406 debug_printf_parse("done_pipe entered, followup %d\n", type);
3340 /* Close previous command */ 3407 /* Close previous command */
3341 not_null = done_command(ctx); 3408 not_null = done_command(ctx);
3342 ctx->pipe->followup = type;
3343#if HAS_KEYWORDS 3409#if HAS_KEYWORDS
3344 ctx->pipe->pi_inverted = ctx->ctx_inverted; 3410 ctx->pipe->pi_inverted = ctx->ctx_inverted;
3345 ctx->ctx_inverted = 0; 3411 ctx->ctx_inverted = 0;
3346 ctx->pipe->res_word = ctx->ctx_res_w; 3412 ctx->pipe->res_word = ctx->ctx_res_w;
3347#endif 3413#endif
3414 if (type == PIPE_BG && ctx->list_head != ctx->pipe) {
3415 /* Necessary since && and || have precedence over &:
3416 * "cmd1 && cmd2 &" must spawn both cmds, not only cmd2,
3417 * in a backgrounded subshell.
3418 */
3419 struct pipe *pi;
3420 struct command *command;
3421
3422 /* Is this actually this construct, all pipes end with && or ||? */
3423 pi = ctx->list_head;
3424 while (pi != ctx->pipe) {
3425 if (pi->followup != PIPE_AND && pi->followup != PIPE_OR)
3426 goto no_conv;
3427 pi = pi->next;
3428 }
3429
3430 debug_printf_parse("BG with more than one pipe, converting to { p1 &&...pN; } &\n");
3431 pi->followup = PIPE_SEQ; /* close pN _not_ with "&"! */
3432 pi = xzalloc(sizeof(*pi));
3433 pi->followup = PIPE_BG;
3434 pi->num_cmds = 1;
3435 pi->cmds = xzalloc(sizeof(pi->cmds[0]));
3436 command = &pi->cmds[0];
3437 if (CMD_NORMAL != 0) /* "if xzalloc didn't do that already" */
3438 command->cmd_type = CMD_NORMAL;
3439 command->group = ctx->list_head;
3440#if !BB_MMU
3441 command->group_as_string = xstrndup(
3442 ctx->as_string.data,
3443 ctx->as_string.length - 1 /* do not copy last char, "&" */
3444 );
3445#endif
3446 /* Replace all pipes in ctx with one newly created */
3447 ctx->list_head = ctx->pipe = pi;
3448 } else {
3449 no_conv:
3450 ctx->pipe->followup = type;
3451 }
3348 3452
3349 /* Without this check, even just <enter> on command line generates 3453 /* Without this check, even just <enter> on command line generates
3350 * tree of three NOPs (!). Which is harmless but annoying. 3454 * tree of three NOPs (!). Which is harmless but annoying.
@@ -3514,9 +3618,8 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
3514 if (r->flag & FLAG_START) { 3618 if (r->flag & FLAG_START) {
3515 struct parse_context *old; 3619 struct parse_context *old;
3516 3620
3517 old = xmalloc(sizeof(*old)); 3621 old = xmemdup(ctx, sizeof(*ctx));
3518 debug_printf_parse("push stack %p\n", old); 3622 debug_printf_parse("push stack %p\n", old);
3519 *old = *ctx; /* physical copy */
3520 initialize_context(ctx); 3623 initialize_context(ctx);
3521 ctx->stack = old; 3624 ctx->stack = old;
3522 } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { 3625 } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) {
@@ -4787,7 +4890,9 @@ static struct pipe *parse_stream(char **pstring,
4787 * Really, ask yourself, why 4890 * Really, ask yourself, why
4788 * "cmd && <newline>" doesn't start 4891 * "cmd && <newline>" doesn't start
4789 * cmd but waits for more input? 4892 * cmd but waits for more input?
4790 * No reason...) 4893 * The only reason is that it might be
4894 * a "cmd1 && <nl> cmd2 &" construct,
4895 * cmd1 may need to run in BG).
4791 */ 4896 */
4792 struct pipe *pi = ctx.list_head; 4897 struct pipe *pi = ctx.list_head;
4793 if (pi->num_cmds != 0 /* check #1 */ 4898 if (pi->num_cmds != 0 /* check #1 */
@@ -5146,7 +5251,7 @@ static struct pipe *parse_stream(char **pstring,
5146 * and it will match } earlier (not here). */ 5251 * and it will match } earlier (not here). */
5147 syntax_error_unexpected_ch(ch); 5252 syntax_error_unexpected_ch(ch);
5148 G.last_exitcode = 2; 5253 G.last_exitcode = 2;
5149 goto parse_error1; 5254 goto parse_error2;
5150 default: 5255 default:
5151 if (HUSH_DEBUG) 5256 if (HUSH_DEBUG)
5152 bb_error_msg_and_die("BUG: unexpected %c\n", ch); 5257 bb_error_msg_and_die("BUG: unexpected %c\n", ch);
@@ -5155,7 +5260,7 @@ static struct pipe *parse_stream(char **pstring,
5155 5260
5156 parse_error: 5261 parse_error:
5157 G.last_exitcode = 1; 5262 G.last_exitcode = 1;
5158 parse_error1: 5263 parse_error2:
5159 { 5264 {
5160 struct parse_context *pctx; 5265 struct parse_context *pctx;
5161 IF_HAS_KEYWORDS(struct parse_context *p2;) 5266 IF_HAS_KEYWORDS(struct parse_context *p2;)
@@ -5202,7 +5307,7 @@ static struct pipe *parse_stream(char **pstring,
5202/*** Execution routines ***/ 5307/*** Execution routines ***/
5203 5308
5204/* Expansion can recurse, need forward decls: */ 5309/* Expansion can recurse, need forward decls: */
5205#if !BASH_PATTERN_SUBST 5310#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE
5206/* only ${var/pattern/repl} (its pattern part) needs additional mode */ 5311/* only ${var/pattern/repl} (its pattern part) needs additional mode */
5207#define expand_string_to_string(str, do_unbackslash) \ 5312#define expand_string_to_string(str, do_unbackslash) \
5208 expand_string_to_string(str) 5313 expand_string_to_string(str)
@@ -5330,6 +5435,9 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha
5330#endif 5435#endif
5331static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash) 5436static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash)
5332{ 5437{
5438#if !BASH_PATTERN_SUBST
5439 const int do_unbackslash = 1;
5440#endif
5333 char *exp_str; 5441 char *exp_str;
5334 struct in_str input; 5442 struct in_str input;
5335 o_string dest = NULL_O_STRING; 5443 o_string dest = NULL_O_STRING;
@@ -5628,27 +5736,34 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5628 if (errmsg) 5736 if (errmsg)
5629 goto arith_err; 5737 goto arith_err;
5630 debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len); 5738 debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
5631 if (len >= 0) { /* bash compat: len < 0 is illegal */ 5739 if (beg < 0) {
5632 if (beg < 0) /* bash compat */ 5740 /* negative beg counts from the end */
5633 beg = 0; 5741 beg = (arith_t)strlen(val) + beg;
5634 debug_printf_varexp("from val:'%s'\n", val); 5742 if (beg < 0) /* ${v: -999999} is "" */
5635 if (len == 0 || !val || beg >= strlen(val)) { 5743 beg = len = 0;
5744 }
5745 debug_printf_varexp("from val:'%s'\n", val);
5746 if (len < 0) {
5747 /* in bash, len=-n means strlen()-n */
5748 len = (arith_t)strlen(val) - beg + len;
5749 if (len < 0) /* bash compat */
5750 die_if_script("%s: substring expression < 0", var);
5751 }
5752 if (len <= 0 || !val || beg >= strlen(val)) {
5636 arith_err: 5753 arith_err:
5637 val = NULL;
5638 } else {
5639 /* Paranoia. What if user entered 9999999999999
5640 * which fits in arith_t but not int? */
5641 if (len >= INT_MAX)
5642 len = INT_MAX;
5643 val = to_be_freed = xstrndup(val + beg, len);
5644 }
5645 debug_printf_varexp("val:'%s'\n", val);
5646 } else
5647#endif /* HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH */
5648 {
5649 die_if_script("malformed ${%s:...}", var);
5650 val = NULL; 5754 val = NULL;
5755 } else {
5756 /* Paranoia. What if user entered 9999999999999
5757 * which fits in arith_t but not int? */
5758 if (len >= INT_MAX)
5759 len = INT_MAX;
5760 val = to_be_freed = xstrndup(val + beg, len);
5651 } 5761 }
5762 debug_printf_varexp("val:'%s'\n", val);
5763#else /* not (HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH) */
5764 die_if_script("malformed ${%s:...}", var);
5765 val = NULL;
5766#endif
5652 } else { /* one of "-=+?" */ 5767 } else { /* one of "-=+?" */
5653 /* Standard-mandated substitution ops: 5768 /* Standard-mandated substitution ops:
5654 * ${var?word} - indicate error if unset 5769 * ${var?word} - indicate error if unset
@@ -5700,7 +5815,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5700 val = NULL; 5815 val = NULL;
5701 } else { 5816 } else {
5702 char *new_var = xasprintf("%s=%s", var, val); 5817 char *new_var = xasprintf("%s=%s", var, val);
5703 set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); 5818 set_local_var(new_var, /*flag:*/ 0);
5704 } 5819 }
5705 } 5820 }
5706 } 5821 }
@@ -5949,7 +6064,7 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv)
5949 */ 6064 */
5950static char *expand_string_to_string(const char *str, int do_unbackslash) 6065static char *expand_string_to_string(const char *str, int do_unbackslash)
5951{ 6066{
5952#if !BASH_PATTERN_SUBST 6067#if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE
5953 const int do_unbackslash = 1; 6068 const int do_unbackslash = 1;
5954#endif 6069#endif
5955 char *argv[2], **list; 6070 char *argv[2], **list;
@@ -5982,7 +6097,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash)
5982 return (char*)list; 6097 return (char*)list;
5983} 6098}
5984 6099
5985/* Used for "eval" builtin */ 6100/* Used for "eval" builtin and case string */
5986static char* expand_strvec_to_string(char **argv) 6101static char* expand_strvec_to_string(char **argv)
5987{ 6102{
5988 char **list; 6103 char **list;
@@ -6524,77 +6639,108 @@ static void setup_heredoc(struct redir_struct *redir)
6524 wait(NULL); /* wait till child has died */ 6639 wait(NULL); /* wait till child has died */
6525} 6640}
6526 6641
6527/* fd: redirect wants this fd to be used (e.g. 3>file). 6642struct squirrel {
6528 * Move all conflicting internally used fds, 6643 int orig_fd;
6529 * and remember them so that we can restore them later. 6644 int moved_to;
6530 */ 6645 /* moved_to = n: fd was moved to n; restore back to orig_fd after redir */
6531static int save_fds_on_redirect(int fd, int squirrel[3]) 6646 /* moved_to = -1: fd was opened by redirect; close orig_fd after redir */
6647};
6648
6649static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd)
6532{ 6650{
6533 if (squirrel) { 6651 int i = 0;
6534 /* Handle redirects of fds 0,1,2 */
6535 6652
6536 /* If we collide with an already moved stdio fd... */ 6653 if (sq) while (sq[i].orig_fd >= 0) {
6537 if (fd == squirrel[0]) { 6654 /* If we collide with an already moved fd... */
6538 squirrel[0] = xdup_and_close(squirrel[0], F_DUPFD); 6655 if (fd == sq[i].moved_to) {
6539 return 1; 6656 sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd);
6540 } 6657 debug_printf_redir("redirect_fd %d: already busy, moving to %d\n", fd, sq[i].moved_to);
6541 if (fd == squirrel[1]) { 6658 if (sq[i].moved_to < 0) /* what? */
6542 squirrel[1] = xdup_and_close(squirrel[1], F_DUPFD);
6543 return 1;
6544 }
6545 if (fd == squirrel[2]) {
6546 squirrel[2] = xdup_and_close(squirrel[2], F_DUPFD);
6547 return 1;
6548 }
6549 /* If we are about to redirect stdio fd, and did not yet move it... */
6550 if (fd <= 2 && squirrel[fd] < 0) {
6551 /* We avoid taking stdio fds */
6552 squirrel[fd] = fcntl(fd, F_DUPFD, 10);
6553 if (squirrel[fd] < 0 && errno != EBADF)
6554 xfunc_die(); 6659 xfunc_die();
6555 return 0; /* "we did not close fd" */ 6660 return sq;
6661 }
6662 if (fd == sq[i].orig_fd) {
6663 /* Example: echo Hello >/dev/null 1>&2 */
6664 debug_printf_redir("redirect_fd %d: already moved\n", fd);
6665 return sq;
6556 } 6666 }
6667 i++;
6557 } 6668 }
6558 6669
6670 sq = xrealloc(sq, (i + 2) * sizeof(sq[0]));
6671 sq[i].orig_fd = fd;
6672 /* If this fd is open, we move and remember it; if it's closed, moved_to = -1 */
6673 sq[i].moved_to = fcntl_F_DUPFD(fd, avoid_fd);
6674 debug_printf_redir("redirect_fd %d: previous fd is moved to %d (-1 if it was closed)\n", fd, sq[i].moved_to);
6675 if (sq[i].moved_to < 0 && errno != EBADF)
6676 xfunc_die();
6677 sq[i+1].orig_fd = -1; /* end marker */
6678 return sq;
6679}
6680
6681/* fd: redirect wants this fd to be used (e.g. 3>file).
6682 * Move all conflicting internally used fds,
6683 * and remember them so that we can restore them later.
6684 */
6685static int save_fds_on_redirect(int fd, int avoid_fd, struct squirrel **sqp)
6686{
6687 if (avoid_fd < 9) /* the important case here is that it can be -1 */
6688 avoid_fd = 9;
6689
6559#if ENABLE_HUSH_INTERACTIVE 6690#if ENABLE_HUSH_INTERACTIVE
6560 if (fd != 0 && fd == G.interactive_fd) { 6691 if (fd != 0 && fd == G.interactive_fd) {
6561 G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC); 6692 G.interactive_fd = xdup_and_close(G.interactive_fd, F_DUPFD_CLOEXEC, avoid_fd);
6562 return 1; 6693 debug_printf_redir("redirect_fd %d: matches interactive_fd, moving it to %d\n", fd, G.interactive_fd);
6694 return 1; /* "we closed fd" */
6563 } 6695 }
6564#endif 6696#endif
6565
6566 /* Are we called from setup_redirects(squirrel==NULL)? Two cases: 6697 /* Are we called from setup_redirects(squirrel==NULL)? Two cases:
6567 * (1) Redirect in a forked child. No need to save FILEs' fds, 6698 * (1) Redirect in a forked child. No need to save FILEs' fds,
6568 * we aren't going to use them anymore, ok to trash. 6699 * we aren't going to use them anymore, ok to trash.
6569 * (2) "exec 3>FILE". Bummer. We can save FILEs' fds, 6700 * (2) "exec 3>FILE". Bummer. We can save script FILEs' fds,
6570 * but how are we doing to use them? 6701 * but how are we doing to restore them?
6571 * "fileno(fd) = new_fd" can't be done. 6702 * "fileno(fd) = new_fd" can't be done.
6572 */ 6703 */
6573 if (!squirrel) 6704 if (!sqp)
6574 return 0; 6705 return 0;
6575 6706
6576 return save_FILEs_on_redirect(fd); 6707 /* If this one of script's fds? */
6708 if (save_FILEs_on_redirect(fd, avoid_fd))
6709 return 1; /* yes. "we closed fd" */
6710
6711 /* Check whether it collides with any open fds (e.g. stdio), save fds as needed */
6712 *sqp = add_squirrel(*sqp, fd, avoid_fd);
6713 return 0; /* "we did not close fd" */
6577} 6714}
6578 6715
6579static void restore_redirects(int squirrel[3]) 6716static void restore_redirects(struct squirrel *sq)
6580{ 6717{
6581 int i, fd; 6718
6582 for (i = 0; i <= 2; i++) { 6719 if (sq) {
6583 fd = squirrel[i]; 6720 int i = 0;
6584 if (fd != -1) { 6721 while (sq[i].orig_fd >= 0) {
6585 /* We simply die on error */ 6722 if (sq[i].moved_to >= 0) {
6586 xmove_fd(fd, i); 6723 /* We simply die on error */
6724 debug_printf_redir("restoring redirected fd from %d to %d\n", sq[i].moved_to, sq[i].orig_fd);
6725 xmove_fd(sq[i].moved_to, sq[i].orig_fd);
6726 } else {
6727 /* cmd1 9>FILE; cmd2_should_see_fd9_closed */
6728 debug_printf_redir("restoring redirected fd %d: closing it\n", sq[i].orig_fd);
6729 close(sq[i].orig_fd);
6730 }
6731 i++;
6587 } 6732 }
6733 free(sq);
6588 } 6734 }
6589 6735
6590 /* Moved G.interactive_fd stays on new fd, not doing anything for it */ 6736 /* If moved, G.interactive_fd stays on new fd, not restoring it */
6591 6737
6592 restore_redirected_FILEs(); 6738 restore_redirected_FILEs();
6593} 6739}
6594 6740
6595/* squirrel != NULL means we squirrel away copies of stdin, stdout, 6741/* squirrel != NULL means we squirrel away copies of stdin, stdout,
6596 * and stderr if they are redirected. */ 6742 * and stderr if they are redirected. */
6597static int setup_redirects(struct command *prog, int squirrel[]) 6743static int setup_redirects(struct command *prog, struct squirrel **sqp)
6598{ 6744{
6599 int openfd, mode; 6745 int openfd, mode;
6600 struct redir_struct *redir; 6746 struct redir_struct *redir;
@@ -6602,7 +6748,7 @@ static int setup_redirects(struct command *prog, int squirrel[])
6602 for (redir = prog->redirects; redir; redir = redir->next) { 6748 for (redir = prog->redirects; redir; redir = redir->next) {
6603 if (redir->rd_type == REDIRECT_HEREDOC2) { 6749 if (redir->rd_type == REDIRECT_HEREDOC2) {
6604 /* "rd_fd<<HERE" case */ 6750 /* "rd_fd<<HERE" case */
6605 save_fds_on_redirect(redir->rd_fd, squirrel); 6751 save_fds_on_redirect(redir->rd_fd, /*avoid:*/ 0, sqp);
6606 /* for REDIRECT_HEREDOC2, rd_filename holds _contents_ 6752 /* for REDIRECT_HEREDOC2, rd_filename holds _contents_
6607 * of the heredoc */ 6753 * of the heredoc */
6608 debug_printf_parse("set heredoc '%s'\n", 6754 debug_printf_parse("set heredoc '%s'\n",
@@ -6641,7 +6787,7 @@ static int setup_redirects(struct command *prog, int squirrel[])
6641 } 6787 }
6642 6788
6643 if (openfd != redir->rd_fd) { 6789 if (openfd != redir->rd_fd) {
6644 int closed = save_fds_on_redirect(redir->rd_fd, squirrel); 6790 int closed = save_fds_on_redirect(redir->rd_fd, /*avoid:*/ openfd, sqp);
6645 if (openfd == REDIRFD_CLOSE) { 6791 if (openfd == REDIRFD_CLOSE) {
6646 /* "rd_fd >&-" means "close me" */ 6792 /* "rd_fd >&-" means "close me" */
6647 if (!closed) { 6793 if (!closed) {
@@ -6817,13 +6963,11 @@ static void exec_function(char ***to_free,
6817 char **argv) 6963 char **argv)
6818{ 6964{
6819# if BB_MMU 6965# if BB_MMU
6820 int n = 1; 6966 int n;
6821 6967
6822 argv[0] = G.global_argv[0]; 6968 argv[0] = G.global_argv[0];
6823 G.global_argv = argv; 6969 G.global_argv = argv;
6824 while (*++argv) 6970 G.global_argc = n = 1 + string_array_len(argv + 1);
6825 n++;
6826 G.global_argc = n;
6827 /* On MMU, funcp->body is always non-NULL */ 6971 /* On MMU, funcp->body is always non-NULL */
6828 n = run_list(funcp->body); 6972 n = run_list(funcp->body);
6829 fflush_all(); 6973 fflush_all();
@@ -7071,7 +7215,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7071 /* Do not leak open fds from opened script files etc */ 7215 /* Do not leak open fds from opened script files etc */
7072 close_all_FILE_list(); 7216 close_all_FILE_list();
7073 debug_printf_exec("running applet '%s'\n", argv[0]); 7217 debug_printf_exec("running applet '%s'\n", argv[0]);
7074 run_applet_no_and_exit(a, argv); 7218 run_applet_no_and_exit(a, argv[0], argv);
7075 } 7219 }
7076# endif 7220# endif
7077 /* Re-exec ourselves */ 7221 /* Re-exec ourselves */
@@ -7168,24 +7312,54 @@ static const char *get_cmdtext(struct pipe *pi)
7168 return pi->cmdtext; 7312 return pi->cmdtext;
7169} 7313}
7170 7314
7171static void insert_bg_job(struct pipe *pi) 7315static void remove_job_from_table(struct pipe *pi)
7316{
7317 struct pipe *prev_pipe;
7318
7319 if (pi == G.job_list) {
7320 G.job_list = pi->next;
7321 } else {
7322 prev_pipe = G.job_list;
7323 while (prev_pipe->next != pi)
7324 prev_pipe = prev_pipe->next;
7325 prev_pipe->next = pi->next;
7326 }
7327 G.last_jobid = 0;
7328 if (G.job_list)
7329 G.last_jobid = G.job_list->jobid;
7330}
7331
7332static void delete_finished_job(struct pipe *pi)
7333{
7334 remove_job_from_table(pi);
7335 free_pipe(pi);
7336}
7337
7338static void clean_up_last_dead_job(void)
7339{
7340 if (G.job_list && !G.job_list->alive_cmds)
7341 delete_finished_job(G.job_list);
7342}
7343
7344static void insert_job_into_table(struct pipe *pi)
7172{ 7345{
7173 struct pipe *job, **jobp; 7346 struct pipe *job, **jobp;
7174 int i; 7347 int i;
7175 7348
7176 /* Linear search for the ID of the job to use */ 7349 clean_up_last_dead_job();
7177 pi->jobid = 1;
7178 for (job = G.job_list; job; job = job->next)
7179 if (job->jobid >= pi->jobid)
7180 pi->jobid = job->jobid + 1;
7181 7350
7182 /* Add job to the list of running jobs */ 7351 /* Find the end of the list, and find next job ID to use */
7352 i = 0;
7183 jobp = &G.job_list; 7353 jobp = &G.job_list;
7184 while ((job = *jobp) != NULL) 7354 while ((job = *jobp) != NULL) {
7355 if (job->jobid > i)
7356 i = job->jobid;
7185 jobp = &job->next; 7357 jobp = &job->next;
7186 job = *jobp = xmalloc(sizeof(*job)); 7358 }
7359 pi->jobid = i + 1;
7187 7360
7188 *job = *pi; /* physical copy */ 7361 /* Create a new job struct at the end */
7362 job = *jobp = xmemdup(pi, sizeof(*pi));
7189 job->next = NULL; 7363 job->next = NULL;
7190 job->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds); 7364 job->cmds = xzalloc(sizeof(pi->cmds[0]) * pi->num_cmds);
7191 /* Cannot copy entire pi->cmds[] vector! This causes double frees */ 7365 /* Cannot copy entire pi->cmds[] vector! This causes double frees */
@@ -7199,31 +7373,6 @@ static void insert_bg_job(struct pipe *pi)
7199 printf("[%u] %u %s\n", job->jobid, (unsigned)job->cmds[0].pid, job->cmdtext); 7373 printf("[%u] %u %s\n", job->jobid, (unsigned)job->cmds[0].pid, job->cmdtext);
7200 G.last_jobid = job->jobid; 7374 G.last_jobid = job->jobid;
7201} 7375}
7202
7203static void remove_bg_job(struct pipe *pi)
7204{
7205 struct pipe *prev_pipe;
7206
7207 if (pi == G.job_list) {
7208 G.job_list = pi->next;
7209 } else {
7210 prev_pipe = G.job_list;
7211 while (prev_pipe->next != pi)
7212 prev_pipe = prev_pipe->next;
7213 prev_pipe->next = pi->next;
7214 }
7215 if (G.job_list)
7216 G.last_jobid = G.job_list->jobid;
7217 else
7218 G.last_jobid = 0;
7219}
7220
7221/* Remove a backgrounded job */
7222static void delete_finished_bg_job(struct pipe *pi)
7223{
7224 remove_bg_job(pi);
7225 free_pipe(pi);
7226}
7227#endif /* JOB */ 7376#endif /* JOB */
7228 7377
7229static int job_exited_or_stopped(struct pipe *pi) 7378static int job_exited_or_stopped(struct pipe *pi)
@@ -7310,7 +7459,7 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
7310 if (G_interactive_fd) { 7459 if (G_interactive_fd) {
7311#if ENABLE_HUSH_JOB 7460#if ENABLE_HUSH_JOB
7312 if (fg_pipe->alive_cmds != 0) 7461 if (fg_pipe->alive_cmds != 0)
7313 insert_bg_job(fg_pipe); 7462 insert_job_into_table(fg_pipe);
7314#endif 7463#endif
7315 return rcode; 7464 return rcode;
7316 } 7465 }
@@ -7339,16 +7488,31 @@ static int process_wait_result(struct pipe *fg_pipe, pid_t childpid, int status)
7339 found_pi_and_prognum: 7488 found_pi_and_prognum:
7340 if (dead) { 7489 if (dead) {
7341 /* child exited */ 7490 /* child exited */
7342 pi->cmds[i].pid = 0; 7491 int rcode = WEXITSTATUS(status);
7343 pi->cmds[i].cmd_exitcode = WEXITSTATUS(status);
7344 if (WIFSIGNALED(status)) 7492 if (WIFSIGNALED(status))
7345 pi->cmds[i].cmd_exitcode = 128 + WTERMSIG(status); 7493 rcode = 128 + WTERMSIG(status);
7494 pi->cmds[i].cmd_exitcode = rcode;
7495 if (G.last_bg_pid == pi->cmds[i].pid)
7496 G.last_bg_pid_exitcode = rcode;
7497 pi->cmds[i].pid = 0;
7346 pi->alive_cmds--; 7498 pi->alive_cmds--;
7347 if (!pi->alive_cmds) { 7499 if (!pi->alive_cmds) {
7348 if (G_interactive_fd) 7500 if (G_interactive_fd) {
7349 printf(JOB_STATUS_FORMAT, pi->jobid, 7501 printf(JOB_STATUS_FORMAT, pi->jobid,
7350 "Done", pi->cmdtext); 7502 "Done", pi->cmdtext);
7351 delete_finished_bg_job(pi); 7503 delete_finished_job(pi);
7504 } else {
7505/*
7506 * bash deletes finished jobs from job table only in interactive mode,
7507 * after "jobs" cmd, or if pid of a new process matches one of the old ones
7508 * (see cleanup_dead_jobs(), delete_old_job(), J_NOTIFIED in bash source).
7509 * Testcase script: "(exit 3) & sleep 1; wait %1; echo $?" prints 3 in bash.
7510 * We only retain one "dead" job, if it's the single job on the list.
7511 * This covers most of real-world scenarios where this is useful.
7512 */
7513 if (pi != G.job_list)
7514 delete_finished_job(pi);
7515 }
7352 } 7516 }
7353 } else { 7517 } else {
7354 /* child stopped */ 7518 /* child stopped */
@@ -7505,14 +7669,14 @@ static int checkjobs_and_fg_shell(struct pipe *fg_pipe)
7505static int redirect_and_varexp_helper(char ***new_env_p, 7669static int redirect_and_varexp_helper(char ***new_env_p,
7506 struct variable **old_vars_p, 7670 struct variable **old_vars_p,
7507 struct command *command, 7671 struct command *command,
7508 int squirrel[3], 7672 struct squirrel **sqp,
7509 char **argv_expanded) 7673 char **argv_expanded)
7510{ 7674{
7511 /* setup_redirects acts on file descriptors, not FILEs. 7675 /* setup_redirects acts on file descriptors, not FILEs.
7512 * This is perfect for work that comes after exec(). 7676 * This is perfect for work that comes after exec().
7513 * Is it really safe for inline use? Experimentally, 7677 * Is it really safe for inline use? Experimentally,
7514 * things seem to work. */ 7678 * things seem to work. */
7515 int rcode = setup_redirects(command, squirrel); 7679 int rcode = setup_redirects(command, sqp);
7516 if (rcode == 0) { 7680 if (rcode == 0) {
7517 char **new_env = expand_assignments(command->argv, command->assignment_cnt); 7681 char **new_env = expand_assignments(command->argv, command->assignment_cnt);
7518 *new_env_p = new_env; 7682 *new_env_p = new_env;
@@ -7532,8 +7696,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
7532 struct command *command; 7696 struct command *command;
7533 char **argv_expanded; 7697 char **argv_expanded;
7534 char **argv; 7698 char **argv;
7535 /* it is not always needed, but we aim to smaller code */ 7699 struct squirrel *squirrel = NULL;
7536 int squirrel[] = { -1, -1, -1 };
7537 int rcode; 7700 int rcode;
7538 7701
7539 debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds); 7702 debug_printf_exec("run_pipe start: members:%d\n", pi->num_cmds);
@@ -7590,7 +7753,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
7590 /* { list } */ 7753 /* { list } */
7591 debug_printf("non-subshell group\n"); 7754 debug_printf("non-subshell group\n");
7592 rcode = 1; /* exitcode if redir failed */ 7755 rcode = 1; /* exitcode if redir failed */
7593 if (setup_redirects(command, squirrel) == 0) { 7756 if (setup_redirects(command, &squirrel) == 0) {
7594 debug_printf_exec(": run_list\n"); 7757 debug_printf_exec(": run_list\n");
7595 rcode = run_list(command->group) & 0xff; 7758 rcode = run_list(command->group) & 0xff;
7596 } 7759 }
@@ -7617,15 +7780,15 @@ static NOINLINE int run_pipe(struct pipe *pi)
7617 /* Ensure redirects take effect (that is, create files). 7780 /* Ensure redirects take effect (that is, create files).
7618 * Try "a=t >file" */ 7781 * Try "a=t >file" */
7619#if 0 /* A few cases in testsuite fail with this code. FIXME */ 7782#if 0 /* A few cases in testsuite fail with this code. FIXME */
7620 rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, squirrel, /*argv_expanded:*/ NULL); 7783 rcode = redirect_and_varexp_helper(&new_env, /*old_vars:*/ NULL, command, &squirrel, /*argv_expanded:*/ NULL);
7621 /* Set shell variables */ 7784 /* Set shell variables */
7622 if (new_env) { 7785 if (new_env) {
7623 argv = new_env; 7786 argv = new_env;
7624 while (*argv) { 7787 while (*argv) {
7625 set_local_var(*argv, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); 7788 if (set_local_var(*argv, /*flag:*/ 0)) {
7626 /* Do we need to flag set_local_var() errors? 7789 /* assignment to readonly var / putenv error? */
7627 * "assignment to readonly var" and "putenv error" 7790 rcode = 1;
7628 */ 7791 }
7629 argv++; 7792 argv++;
7630 } 7793 }
7631 } 7794 }
@@ -7639,7 +7802,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
7639 7802
7640#else /* Older, bigger, but more correct code */ 7803#else /* Older, bigger, but more correct code */
7641 7804
7642 rcode = setup_redirects(command, squirrel); 7805 rcode = setup_redirects(command, &squirrel);
7643 restore_redirects(squirrel); 7806 restore_redirects(squirrel);
7644 /* Set shell variables */ 7807 /* Set shell variables */
7645 if (G_x_mode) 7808 if (G_x_mode)
@@ -7650,10 +7813,10 @@ static NOINLINE int run_pipe(struct pipe *pi)
7650 fprintf(stderr, " %s", p); 7813 fprintf(stderr, " %s", p);
7651 debug_printf_exec("set shell var:'%s'->'%s'\n", 7814 debug_printf_exec("set shell var:'%s'->'%s'\n",
7652 *argv, p); 7815 *argv, p);
7653 set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); 7816 if (set_local_var(p, /*flag:*/ 0)) {
7654 /* Do we need to flag set_local_var() errors? 7817 /* assignment to readonly var / putenv error? */
7655 * "assignment to readonly var" and "putenv error" 7818 rcode = 1;
7656 */ 7819 }
7657 argv++; 7820 argv++;
7658 } 7821 }
7659 if (G_x_mode) 7822 if (G_x_mode)
@@ -7702,7 +7865,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
7702 goto clean_up_and_ret1; 7865 goto clean_up_and_ret1;
7703 } 7866 }
7704 } 7867 }
7705 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); 7868 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded);
7706 if (rcode == 0) { 7869 if (rcode == 0) {
7707 if (!funcp) { 7870 if (!funcp) {
7708 debug_printf_exec(": builtin '%s' '%s'...\n", 7871 debug_printf_exec(": builtin '%s' '%s'...\n",
@@ -7743,7 +7906,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
7743 if (ENABLE_FEATURE_SH_NOFORK) { 7906 if (ENABLE_FEATURE_SH_NOFORK) {
7744 int n = find_applet_by_name(argv_expanded[0]); 7907 int n = find_applet_by_name(argv_expanded[0]);
7745 if (n >= 0 && APPLET_IS_NOFORK(n)) { 7908 if (n >= 0 && APPLET_IS_NOFORK(n)) {
7746 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, squirrel, argv_expanded); 7909 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded);
7747 if (rcode == 0) { 7910 if (rcode == 0) {
7748 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", 7911 debug_printf_exec(": run_nofork_applet '%s' '%s'...\n",
7749 argv_expanded[0], argv_expanded[1]); 7912 argv_expanded[0], argv_expanded[1]);
@@ -7968,6 +8131,7 @@ static int run_list(struct pipe *pi)
7968 /* Go through list of pipes, (maybe) executing them. */ 8131 /* Go through list of pipes, (maybe) executing them. */
7969 for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) { 8132 for (; pi; pi = IF_HUSH_LOOPS(rword == RES_DONE ? loop_top : ) pi->next) {
7970 int r; 8133 int r;
8134 int sv_errexit_depth;
7971 8135
7972 if (G.flag_SIGINT) 8136 if (G.flag_SIGINT)
7973 break; 8137 break;
@@ -7977,6 +8141,13 @@ static int run_list(struct pipe *pi)
7977 IF_HAS_KEYWORDS(rword = pi->res_word;) 8141 IF_HAS_KEYWORDS(rword = pi->res_word;)
7978 debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n", 8142 debug_printf_exec(": rword=%d cond_code=%d last_rword=%d\n",
7979 rword, cond_code, last_rword); 8143 rword, cond_code, last_rword);
8144
8145 sv_errexit_depth = G.errexit_depth;
8146 if (IF_HAS_KEYWORDS(rword == RES_IF || rword == RES_ELIF ||)
8147 pi->followup != PIPE_SEQ
8148 ) {
8149 G.errexit_depth++;
8150 }
7980#if ENABLE_HUSH_LOOPS 8151#if ENABLE_HUSH_LOOPS
7981 if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR) 8152 if ((rword == RES_WHILE || rword == RES_UNTIL || rword == RES_FOR)
7982 && loop_top == NULL /* avoid bumping G.depth_of_loop twice */ 8153 && loop_top == NULL /* avoid bumping G.depth_of_loop twice */
@@ -8054,7 +8225,7 @@ static int run_list(struct pipe *pi)
8054 } 8225 }
8055 /* Insert next value from for_lcur */ 8226 /* Insert next value from for_lcur */
8056 /* note: *for_lcur already has quotes removed, $var expanded, etc */ 8227 /* note: *for_lcur already has quotes removed, $var expanded, etc */
8057 set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0); 8228 set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*flag:*/ 0);
8058 continue; 8229 continue;
8059 } 8230 }
8060 if (rword == RES_IN) { 8231 if (rword == RES_IN) {
@@ -8068,6 +8239,7 @@ static int run_list(struct pipe *pi)
8068 if (rword == RES_CASE) { 8239 if (rword == RES_CASE) {
8069 debug_printf_exec("CASE cond_code:%d\n", cond_code); 8240 debug_printf_exec("CASE cond_code:%d\n", cond_code);
8070 case_word = expand_strvec_to_string(pi->cmds->argv); 8241 case_word = expand_strvec_to_string(pi->cmds->argv);
8242 unbackslash(case_word);
8071 continue; 8243 continue;
8072 } 8244 }
8073 if (rword == RES_MATCH) { 8245 if (rword == RES_MATCH) {
@@ -8079,9 +8251,10 @@ static int run_list(struct pipe *pi)
8079 /* all prev words didn't match, does this one match? */ 8251 /* all prev words didn't match, does this one match? */
8080 argv = pi->cmds->argv; 8252 argv = pi->cmds->argv;
8081 while (*argv) { 8253 while (*argv) {
8082 char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 1); 8254 char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 0);
8083 /* TODO: which FNM_xxx flags to use? */ 8255 /* TODO: which FNM_xxx flags to use? */
8084 cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); 8256 cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0);
8257 debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", pattern, case_word, cond_code);
8085 free(pattern); 8258 free(pattern);
8086 if (cond_code == 0) { /* match! we will execute this branch */ 8259 if (cond_code == 0) { /* match! we will execute this branch */
8087 free(case_word); 8260 free(case_word);
@@ -8162,10 +8335,11 @@ static int run_list(struct pipe *pi)
8162 * I'm NOT treating inner &'s as jobs */ 8335 * I'm NOT treating inner &'s as jobs */
8163#if ENABLE_HUSH_JOB 8336#if ENABLE_HUSH_JOB
8164 if (G.run_list_level == 1) 8337 if (G.run_list_level == 1)
8165 insert_bg_job(pi); 8338 insert_job_into_table(pi);
8166#endif 8339#endif
8167 /* Last command's pid goes to $! */ 8340 /* Last command's pid goes to $! */
8168 G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; 8341 G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid;
8342 G.last_bg_pid_exitcode = 0;
8169 debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); 8343 debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n");
8170/* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */ 8344/* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */
8171 rcode = EXIT_SUCCESS; 8345 rcode = EXIT_SUCCESS;
@@ -8187,6 +8361,14 @@ static int run_list(struct pipe *pi)
8187 check_and_run_traps(); 8361 check_and_run_traps();
8188 } 8362 }
8189 8363
8364 /* Handle "set -e" */
8365 if (rcode != 0 && G.o_opt[OPT_O_ERREXIT]) {
8366 debug_printf_exec("ERREXIT:1 errexit_depth:%d\n", G.errexit_depth);
8367 if (G.errexit_depth == 0)
8368 hush_exit(rcode);
8369 }
8370 G.errexit_depth = sv_errexit_depth;
8371
8190 /* Analyze how result affects subsequent commands */ 8372 /* Analyze how result affects subsequent commands */
8191#if ENABLE_HUSH_IF 8373#if ENABLE_HUSH_IF
8192 if (rword == RES_IF || rword == RES_ELIF) 8374 if (rword == RES_IF || rword == RES_ELIF)
@@ -8366,6 +8548,9 @@ static int set_mode(int state, char mode, const char *o_opt)
8366 G.o_opt[idx] = state; 8548 G.o_opt[idx] = state;
8367 break; 8549 break;
8368 } 8550 }
8551 case 'e':
8552 G.o_opt[OPT_O_ERREXIT] = state;
8553 break;
8369 default: 8554 default:
8370 return EXIT_FAILURE; 8555 return EXIT_FAILURE;
8371 } 8556 }
@@ -8425,7 +8610,7 @@ int hush_main(int argc, char **argv)
8425 putenv(shell_ver->varstr); 8610 putenv(shell_ver->varstr);
8426 8611
8427 /* Export PWD */ 8612 /* Export PWD */
8428 set_pwd_var(/*exp:*/ 1); 8613 set_pwd_var(SETFLAG_EXPORT);
8429 8614
8430#if BASH_HOSTNAME_VAR 8615#if BASH_HOSTNAME_VAR
8431 /* Set (but not export) HOSTNAME unless already set */ 8616 /* Set (but not export) HOSTNAME unless already set */
@@ -8492,7 +8677,7 @@ int hush_main(int argc, char **argv)
8492 flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0; 8677 flags = (argv[0] && argv[0][0] == '-') ? OPT_login : 0;
8493 builtin_argc = 0; 8678 builtin_argc = 0;
8494 while (1) { 8679 while (1) {
8495 opt = getopt(argc, argv, "+c:xinsl" 8680 opt = getopt(argc, argv, "+c:exinsl"
8496#if !BB_MMU 8681#if !BB_MMU
8497 "<:$:R:V:" 8682 "<:$:R:V:"
8498# if ENABLE_HUSH_FUNCTIONS 8683# if ENABLE_HUSH_FUNCTIONS
@@ -8575,8 +8760,9 @@ int hush_main(int argc, char **argv)
8575 optarg++; 8760 optarg++;
8576 empty_trap_mask = bb_strtoull(optarg, &optarg, 16); 8761 empty_trap_mask = bb_strtoull(optarg, &optarg, 16);
8577 if (empty_trap_mask != 0) { 8762 if (empty_trap_mask != 0) {
8578 int sig; 8763 IF_HUSH_TRAP(int sig;)
8579 install_special_sighandlers(); 8764 install_special_sighandlers();
8765# if ENABLE_HUSH_TRAP
8580 G_traps = xzalloc(sizeof(G_traps[0]) * NSIG); 8766 G_traps = xzalloc(sizeof(G_traps[0]) * NSIG);
8581 for (sig = 1; sig < NSIG; sig++) { 8767 for (sig = 1; sig < NSIG; sig++) {
8582 if (empty_trap_mask & (1LL << sig)) { 8768 if (empty_trap_mask & (1LL << sig)) {
@@ -8584,6 +8770,7 @@ int hush_main(int argc, char **argv)
8584 install_sighandler(sig, SIG_IGN); 8770 install_sighandler(sig, SIG_IGN);
8585 } 8771 }
8586 } 8772 }
8773# endif
8587 } 8774 }
8588# if ENABLE_HUSH_LOOPS 8775# if ENABLE_HUSH_LOOPS
8589 optarg++; 8776 optarg++;
@@ -8593,7 +8780,7 @@ int hush_main(int argc, char **argv)
8593 } 8780 }
8594 case 'R': 8781 case 'R':
8595 case 'V': 8782 case 'V':
8596 set_local_var(xstrdup(optarg), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ opt == 'R'); 8783 set_local_var(xstrdup(optarg), opt == 'R' ? SETFLAG_MAKE_RO : 0);
8597 break; 8784 break;
8598# if ENABLE_HUSH_FUNCTIONS 8785# if ENABLE_HUSH_FUNCTIONS
8599 case 'F': { 8786 case 'F': {
@@ -8608,6 +8795,7 @@ int hush_main(int argc, char **argv)
8608#endif 8795#endif
8609 case 'n': 8796 case 'n':
8610 case 'x': 8797 case 'x':
8798 case 'e':
8611 if (set_mode(1, opt, NULL) == 0) /* no error */ 8799 if (set_mode(1, opt, NULL) == 0) /* no error */
8612 break; 8800 break;
8613 default: 8801 default:
@@ -8694,7 +8882,7 @@ int hush_main(int argc, char **argv)
8694 G_saved_tty_pgrp = 0; 8882 G_saved_tty_pgrp = 0;
8695 8883
8696 /* try to dup stdin to high fd#, >= 255 */ 8884 /* try to dup stdin to high fd#, >= 255 */
8697 G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); 8885 G_interactive_fd = fcntl_F_DUPFD(STDIN_FILENO, 254);
8698 if (G_interactive_fd < 0) { 8886 if (G_interactive_fd < 0) {
8699 /* try to dup to any fd */ 8887 /* try to dup to any fd */
8700 G_interactive_fd = dup(STDIN_FILENO); 8888 G_interactive_fd = dup(STDIN_FILENO);
@@ -8767,7 +8955,7 @@ int hush_main(int argc, char **argv)
8767#elif ENABLE_HUSH_INTERACTIVE 8955#elif ENABLE_HUSH_INTERACTIVE
8768 /* No job control compiled in, only prompt/line editing */ 8956 /* No job control compiled in, only prompt/line editing */
8769 if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) { 8957 if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
8770 G_interactive_fd = fcntl(STDIN_FILENO, F_DUPFD, 255); 8958 G_interactive_fd = fcntl_F_DUPFD(STDIN_FILENO, 254);
8771 if (G_interactive_fd < 0) { 8959 if (G_interactive_fd < 0) {
8772 /* try to dup to any fd */ 8960 /* try to dup to any fd */
8773 G_interactive_fd = dup(STDIN_FILENO); 8961 G_interactive_fd = dup(STDIN_FILENO);
@@ -8806,16 +8994,6 @@ int hush_main(int argc, char **argv)
8806} 8994}
8807 8995
8808 8996
8809#if ENABLE_MSH
8810int msh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
8811int msh_main(int argc, char **argv)
8812{
8813 bb_error_msg("msh is deprecated, please use hush instead");
8814 return hush_main(argc, argv);
8815}
8816#endif
8817
8818
8819/* 8997/*
8820 * Built-ins 8998 * Built-ins
8821 */ 8999 */
@@ -8827,12 +9005,8 @@ static int FAST_FUNC builtin_true(char **argv UNUSED_PARAM)
8827#if ENABLE_HUSH_TEST || ENABLE_HUSH_ECHO || ENABLE_HUSH_PRINTF || ENABLE_HUSH_KILL 9005#if ENABLE_HUSH_TEST || ENABLE_HUSH_ECHO || ENABLE_HUSH_PRINTF || ENABLE_HUSH_KILL
8828static int run_applet_main(char **argv, int (*applet_main_func)(int argc, char **argv)) 9006static int run_applet_main(char **argv, int (*applet_main_func)(int argc, char **argv))
8829{ 9007{
8830 int argc = 0; 9008 int argc = string_array_len(argv);
8831 while (*argv) { 9009 return applet_main_func(argc, argv);
8832 argc++;
8833 argv++;
8834 }
8835 return applet_main_func(argc, argv - argc);
8836} 9010}
8837#endif 9011#endif
8838#if ENABLE_HUSH_TEST || BASH_TEST2 9012#if ENABLE_HUSH_TEST || BASH_TEST2
@@ -8909,7 +9083,7 @@ static int FAST_FUNC builtin_cd(char **argv)
8909 * Note: do not enforce exporting. If PWD was unset or unexported, 9083 * Note: do not enforce exporting. If PWD was unset or unexported,
8910 * set it again, but do not export. bash does the same. 9084 * set it again, but do not export. bash does the same.
8911 */ 9085 */
8912 set_pwd_var(/*exp:*/ 0); 9086 set_pwd_var(/*flag:*/ 0);
8913 return EXIT_SUCCESS; 9087 return EXIT_SUCCESS;
8914} 9088}
8915 9089
@@ -9147,12 +9321,8 @@ static void print_escaped(const char *s)
9147} 9321}
9148#endif 9322#endif
9149 9323
9150#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_LOCAL 9324#if ENABLE_HUSH_EXPORT || ENABLE_HUSH_LOCAL || ENABLE_HUSH_READONLY
9151# if !ENABLE_HUSH_LOCAL 9325static int helper_export_local(char **argv, unsigned flags)
9152#define helper_export_local(argv, exp, lvl) \
9153 helper_export_local(argv, exp)
9154# endif
9155static void helper_export_local(char **argv, int exp, int lvl)
9156{ 9326{
9157 do { 9327 do {
9158 char *name = *argv; 9328 char *name = *argv;
@@ -9166,7 +9336,7 @@ static void helper_export_local(char **argv, int exp, int lvl)
9166 vpp = get_ptr_to_local_var(name, name_end - name); 9336 vpp = get_ptr_to_local_var(name, name_end - name);
9167 var = vpp ? *vpp : NULL; 9337 var = vpp ? *vpp : NULL;
9168 9338
9169 if (exp == -1) { /* unexporting? */ 9339 if (flags & SETFLAG_UNEXPORT) {
9170 /* export -n NAME (without =VALUE) */ 9340 /* export -n NAME (without =VALUE) */
9171 if (var) { 9341 if (var) {
9172 var->flg_export = 0; 9342 var->flg_export = 0;
@@ -9175,7 +9345,7 @@ static void helper_export_local(char **argv, int exp, int lvl)
9175 } /* else: export -n NOT_EXISTING_VAR: no-op */ 9345 } /* else: export -n NOT_EXISTING_VAR: no-op */
9176 continue; 9346 continue;
9177 } 9347 }
9178 if (exp == 1) { /* exporting? */ 9348 if (flags & SETFLAG_EXPORT) {
9179 /* export NAME (without =VALUE) */ 9349 /* export NAME (without =VALUE) */
9180 if (var) { 9350 if (var) {
9181 var->flg_export = 1; 9351 var->flg_export = 1;
@@ -9184,28 +9354,45 @@ static void helper_export_local(char **argv, int exp, int lvl)
9184 continue; 9354 continue;
9185 } 9355 }
9186 } 9356 }
9357 if (flags & SETFLAG_MAKE_RO) {
9358 /* readonly NAME (without =VALUE) */
9359 if (var) {
9360 var->flg_read_only = 1;
9361 continue;
9362 }
9363 }
9187# if ENABLE_HUSH_LOCAL 9364# if ENABLE_HUSH_LOCAL
9188 if (exp == 0 /* local? */ 9365 /* Is this "local" bltin? */
9189 && var && var->func_nest_level == lvl 9366 if (!(flags & (SETFLAG_EXPORT|SETFLAG_UNEXPORT|SETFLAG_MAKE_RO))) {
9190 ) { 9367 unsigned lvl = flags >> SETFLAG_LOCAL_SHIFT;
9191 /* "local x=abc; ...; local x" - ignore second local decl */ 9368 if (var && var->func_nest_level == lvl) {
9192 continue; 9369 /* "local x=abc; ...; local x" - ignore second local decl */
9370 continue;
9371 }
9193 } 9372 }
9194# endif 9373# endif
9195 /* Exporting non-existing variable. 9374 /* Exporting non-existing variable.
9196 * bash does not put it in environment, 9375 * bash does not put it in environment,
9197 * but remembers that it is exported, 9376 * but remembers that it is exported,
9198 * and does put it in env when it is set later. 9377 * and does put it in env when it is set later.
9199 * We just set it to "" and export. */ 9378 * We just set it to "" and export.
9379 */
9200 /* Or, it's "local NAME" (without =VALUE). 9380 /* Or, it's "local NAME" (without =VALUE).
9201 * bash sets the value to "". */ 9381 * bash sets the value to "".
9382 */
9383 /* Or, it's "readonly NAME" (without =VALUE).
9384 * bash remembers NAME and disallows its creation
9385 * in the future.
9386 */
9202 name = xasprintf("%s=", name); 9387 name = xasprintf("%s=", name);
9203 } else { 9388 } else {
9204 /* (Un)exporting/making local NAME=VALUE */ 9389 /* (Un)exporting/making local NAME=VALUE */
9205 name = xstrdup(name); 9390 name = xstrdup(name);
9206 } 9391 }
9207 set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ 0); 9392 if (set_local_var(name, flags))
9393 return EXIT_FAILURE;
9208 } while (*++argv); 9394 } while (*++argv);
9395 return EXIT_SUCCESS;
9209} 9396}
9210#endif 9397#endif
9211 9398
@@ -9251,9 +9438,7 @@ static int FAST_FUNC builtin_export(char **argv)
9251 return EXIT_SUCCESS; 9438 return EXIT_SUCCESS;
9252 } 9439 }
9253 9440
9254 helper_export_local(argv, (opt_unexport ? -1 : 1), 0); 9441 return helper_export_local(argv, opt_unexport ? SETFLAG_UNEXPORT : SETFLAG_EXPORT);
9255
9256 return EXIT_SUCCESS;
9257} 9442}
9258#endif 9443#endif
9259 9444
@@ -9264,8 +9449,29 @@ static int FAST_FUNC builtin_local(char **argv)
9264 bb_error_msg("%s: not in a function", argv[0]); 9449 bb_error_msg("%s: not in a function", argv[0]);
9265 return EXIT_FAILURE; /* bash compat */ 9450 return EXIT_FAILURE; /* bash compat */
9266 } 9451 }
9267 helper_export_local(argv, 0, G.func_nest_level); 9452 argv++;
9268 return EXIT_SUCCESS; 9453 return helper_export_local(argv, G.func_nest_level << SETFLAG_LOCAL_SHIFT);
9454}
9455#endif
9456
9457#if ENABLE_HUSH_READONLY
9458static int FAST_FUNC builtin_readonly(char **argv)
9459{
9460 argv++;
9461 if (*argv == NULL) {
9462 /* bash: readonly [-p]: list all readonly VARs
9463 * (-p has no effect in bash)
9464 */
9465 struct variable *e;
9466 for (e = G.top_var; e; e = e->next) {
9467 if (e->flg_read_only) {
9468//TODO: quote value: readonly VAR='VAL'
9469 printf("readonly %s\n", e->varstr);
9470 }
9471 }
9472 return EXIT_SUCCESS;
9473 }
9474 return helper_export_local(argv, SETFLAG_MAKE_RO);
9269} 9475}
9270#endif 9476#endif
9271 9477
@@ -9379,10 +9585,7 @@ static int FAST_FUNC builtin_set(char **argv)
9379 /* This realloc's G.global_argv */ 9585 /* This realloc's G.global_argv */
9380 G.global_argv = pp = add_strings_to_strings(g_argv, argv, /*dup:*/ 1); 9586 G.global_argv = pp = add_strings_to_strings(g_argv, argv, /*dup:*/ 1);
9381 9587
9382 n = 1; 9588 G.global_argc = 1 + string_array_len(pp + 1);
9383 while (*++pp)
9384 n++;
9385 G.global_argc = n;
9386 9589
9387 return EXIT_SUCCESS; 9590 return EXIT_SUCCESS;
9388 9591
@@ -9398,7 +9601,18 @@ static int FAST_FUNC builtin_shift(char **argv)
9398 int n = 1; 9601 int n = 1;
9399 argv = skip_dash_dash(argv); 9602 argv = skip_dash_dash(argv);
9400 if (argv[0]) { 9603 if (argv[0]) {
9401 n = atoi(argv[0]); 9604 n = bb_strtou(argv[0], NULL, 10);
9605 if (errno || n < 0) {
9606 /* shared string with ash.c */
9607 bb_error_msg("Illegal number: %s", argv[0]);
9608 /*
9609 * ash aborts in this case.
9610 * bash prints error message and set $? to 1.
9611 * Interestingly, for "shift 99999" bash does not
9612 * print error message, but does set $? to 1
9613 * (and does no shifting at all).
9614 */
9615 }
9402 } 9616 }
9403 if (n >= 0 && n < G.global_argc) { 9617 if (n >= 0 && n < G.global_argc) {
9404 if (G_global_args_malloced) { 9618 if (G_global_args_malloced) {
@@ -9511,7 +9725,7 @@ static int FAST_FUNC builtin_trap(char **argv)
9511 if (sig < 0 || sig >= NSIG) { 9725 if (sig < 0 || sig >= NSIG) {
9512 ret = EXIT_FAILURE; 9726 ret = EXIT_FAILURE;
9513 /* Mimic bash message exactly */ 9727 /* Mimic bash message exactly */
9514 bb_perror_msg("trap: %s: invalid signal specification", argv[-1]); 9728 bb_error_msg("trap: %s: invalid signal specification", argv[-1]);
9515 continue; 9729 continue;
9516 } 9730 }
9517 9731
@@ -9604,6 +9818,9 @@ static int FAST_FUNC builtin_jobs(char **argv UNUSED_PARAM)
9604 9818
9605 printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext); 9819 printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->cmdtext);
9606 } 9820 }
9821
9822 clean_up_last_dead_job();
9823
9607 return EXIT_SUCCESS; 9824 return EXIT_SUCCESS;
9608} 9825}
9609 9826
@@ -9648,14 +9865,14 @@ static int FAST_FUNC builtin_fg_bg(char **argv)
9648 i = kill(- pi->pgrp, SIGCONT); 9865 i = kill(- pi->pgrp, SIGCONT);
9649 if (i < 0) { 9866 if (i < 0) {
9650 if (errno == ESRCH) { 9867 if (errno == ESRCH) {
9651 delete_finished_bg_job(pi); 9868 delete_finished_job(pi);
9652 return EXIT_SUCCESS; 9869 return EXIT_SUCCESS;
9653 } 9870 }
9654 bb_perror_msg("kill (SIGCONT)"); 9871 bb_perror_msg("kill (SIGCONT)");
9655 } 9872 }
9656 9873
9657 if (argv[0][0] == 'f') { 9874 if (argv[0][0] == 'f') {
9658 remove_bg_job(pi); 9875 remove_job_from_table(pi); /* FG job shouldn't be in job table */
9659 return checkjobs_and_fg_shell(pi); 9876 return checkjobs_and_fg_shell(pi);
9660 } 9877 }
9661 return EXIT_SUCCESS; 9878 return EXIT_SUCCESS;
@@ -9847,8 +10064,12 @@ static int FAST_FUNC builtin_wait(char **argv)
9847 wait_pipe = parse_jobspec(*argv); 10064 wait_pipe = parse_jobspec(*argv);
9848 if (wait_pipe) { 10065 if (wait_pipe) {
9849 ret = job_exited_or_stopped(wait_pipe); 10066 ret = job_exited_or_stopped(wait_pipe);
9850 if (ret < 0) 10067 if (ret < 0) {
9851 ret = wait_for_child_or_signal(wait_pipe, 0); 10068 ret = wait_for_child_or_signal(wait_pipe, 0);
10069 } else {
10070 /* waiting on "last dead job" removes it */
10071 clean_up_last_dead_job();
10072 }
9852 } 10073 }
9853 /* else: parse_jobspec() already emitted error msg */ 10074 /* else: parse_jobspec() already emitted error msg */
9854 continue; 10075 continue;
@@ -9864,14 +10085,15 @@ static int FAST_FUNC builtin_wait(char **argv)
9864 ret = waitpid(pid, &status, WNOHANG); 10085 ret = waitpid(pid, &status, WNOHANG);
9865 if (ret < 0) { 10086 if (ret < 0) {
9866 /* No */ 10087 /* No */
10088 ret = 127;
9867 if (errno == ECHILD) { 10089 if (errno == ECHILD) {
9868 if (G.last_bg_pid > 0 && pid == G.last_bg_pid) { 10090 if (pid == G.last_bg_pid) {
9869 /* "wait $!" but last bg task has already exited. Try: 10091 /* "wait $!" but last bg task has already exited. Try:
9870 * (sleep 1; exit 3) & sleep 2; echo $?; wait $!; echo $? 10092 * (sleep 1; exit 3) & sleep 2; echo $?; wait $!; echo $?
9871 * In bash it prints exitcode 0, then 3. 10093 * In bash it prints exitcode 0, then 3.
9872 * In dash, it is 127. 10094 * In dash, it is 127.
9873 */ 10095 */
9874 /* ret = G.last_bg_pid_exitstatus - FIXME */ 10096 ret = G.last_bg_pid_exitcode;
9875 } else { 10097 } else {
9876 /* Example: "wait 1". mimic bash message */ 10098 /* Example: "wait 1". mimic bash message */
9877 bb_error_msg("wait: pid %d is not a child of this shell", (int)pid); 10099 bb_error_msg("wait: pid %d is not a child of this shell", (int)pid);
@@ -9880,7 +10102,6 @@ static int FAST_FUNC builtin_wait(char **argv)
9880 /* ??? */ 10102 /* ??? */
9881 bb_perror_msg("wait %s", *argv); 10103 bb_perror_msg("wait %s", *argv);
9882 } 10104 }
9883 ret = 127;
9884 continue; /* bash checks all argv[] */ 10105 continue; /* bash checks all argv[] */
9885 } 10106 }
9886 if (ret == 0) { 10107 if (ret == 0) {
diff --git a/shell/hush_test/hush-heredoc/heredoc8.right b/shell/hush_test/hush-heredoc/heredoc8.right
new file mode 100644
index 000000000..558858f47
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc8.right
@@ -0,0 +1 @@
hush: syntax error at 'then'
diff --git a/shell/hush_test/hush-heredoc/heredoc8.tests b/shell/hush_test/hush-heredoc/heredoc8.tests
new file mode 100755
index 000000000..f7bc0737a
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc8.tests
@@ -0,0 +1,3 @@
1# ash used to SEGV on this:
2
3<<EOF; then <W
diff --git a/shell/hush_test/hush-misc/errexit1.right b/shell/hush_test/hush-misc/errexit1.right
new file mode 100644
index 000000000..d86bac9de
--- /dev/null
+++ b/shell/hush_test/hush-misc/errexit1.right
@@ -0,0 +1 @@
OK
diff --git a/shell/hush_test/hush-misc/errexit1.tests b/shell/hush_test/hush-misc/errexit1.tests
new file mode 100755
index 000000000..7b4a15634
--- /dev/null
+++ b/shell/hush_test/hush-misc/errexit1.tests
@@ -0,0 +1,5 @@
1set -e
2(true)
3echo OK
4(false)
5echo FAIL
diff --git a/shell/hush_test/hush-misc/shift1.right b/shell/hush_test/hush-misc/shift1.right
new file mode 100644
index 000000000..e3ab61392
--- /dev/null
+++ b/shell/hush_test/hush-misc/shift1.right
@@ -0,0 +1,10 @@
12 3 4
2hush: Illegal number: -1
31 2 3 4
41 2 3 4
52 3 4
63 4
74
8
91 2 3 4
101 2 3 4
diff --git a/shell/hush_test/hush-misc/shift1.tests b/shell/hush_test/hush-misc/shift1.tests
new file mode 100755
index 000000000..f2a264751
--- /dev/null
+++ b/shell/hush_test/hush-misc/shift1.tests
@@ -0,0 +1,10 @@
1$THIS_SH -c 'shift; echo "$@"' 0 1 2 3 4
2#We complain on -1 and continue.
3$THIS_SH -c 'shift -1; echo "$@"' 0 1 2 3 4
4$THIS_SH -c 'shift 0; echo "$@"' 0 1 2 3 4
5$THIS_SH -c 'shift 1; echo "$@"' 0 1 2 3 4
6$THIS_SH -c 'shift 2; echo "$@"' 0 1 2 3 4
7$THIS_SH -c 'shift 3; echo "$@"' 0 1 2 3 4
8$THIS_SH -c 'shift 4; echo "$@"' 0 1 2 3 4
9$THIS_SH -c 'shift 5; echo "$@"' 0 1 2 3 4
10$THIS_SH -c 'shift 6; echo "$@"' 0 1 2 3 4
diff --git a/shell/hush_test/hush-misc/sigint1.right b/shell/hush_test/hush-misc/sigint1.right
deleted file mode 100644
index a9094b056..000000000
--- a/shell/hush_test/hush-misc/sigint1.right
+++ /dev/null
@@ -1 +0,0 @@
1Sending SIGINT to main shell PID
diff --git a/shell/hush_test/hush-misc/sigint1.tests b/shell/hush_test/hush-misc/sigint1.tests
deleted file mode 100755
index 3d483d32a..000000000
--- a/shell/hush_test/hush-misc/sigint1.tests
+++ /dev/null
@@ -1,41 +0,0 @@
1# What should happen if non-interactive shell gets SIGINT?
2
3(sleep 1; echo Sending SIGINT to main shell PID; exec kill -INT $$) &
4
5# We create a child which exits with 0 even on SIGINT
6# (The complex command is necessary only if SIGINT is generated by ^C,
7# in this testcase even bare "sleep 2" would do because
8# in the testcase we don't send SIGINT *to the child*...)
9$THIS_SH -c 'trap "exit 0" SIGINT; sleep 2'
10
11# In one second, we (main shell) get SIGINT here.
12# The question is whether we should, or should not, exit.
13
14# bash will not stop here. It will execute next command(s).
15
16# The rationale for this is described here:
17# http://www.cons.org/cracauer/sigint.html
18#
19# Basically, bash will not exit on SIGINT immediately if it waits
20# for a child. It will wait for the child to exit.
21# If child exits NOT by dying on SIGINT, then bash will not exit.
22#
23# The idea is that the following script:
24# | emacs file.txt
25# | more cmds
26# User may use ^C to interrupt editor's ops like search. But then
27# emacs exits normally. User expects that script doesn't stop.
28#
29# This is a nice idea, but detecting "did process really exit
30# with SIGINT?" is racy. Consider:
31# | bash -c 'while true; do /bin/true; done'
32# When ^C is pressed while bash waits for /bin/true to exit,
33# it may happen that /bin/true exits with exitcode 0 before
34# ^C is delivered to it as SIGINT. bash will see SIGINT, then
35# it will see that child exited with 0, and bash will NOT EXIT.
36
37# Therefore we do not implement bash behavior.
38# I'd say that emacs need to put itself into a separate pgrp
39# to isolate shell from getting stray SIGINTs from ^C.
40
41echo Next command after SIGINT was executed
diff --git a/shell/hush_test/hush-misc/tickquote1.right b/shell/hush_test/hush-misc/tickquote1.right
new file mode 100644
index 000000000..56f8515b7
--- /dev/null
+++ b/shell/hush_test/hush-misc/tickquote1.right
@@ -0,0 +1 @@
hush: syntax error: unterminated "
diff --git a/shell/hush_test/hush-misc/tickquote1.tests b/shell/hush_test/hush-misc/tickquote1.tests
new file mode 100755
index 000000000..02e3904f1
--- /dev/null
+++ b/shell/hush_test/hush-misc/tickquote1.tests
@@ -0,0 +1,2 @@
1# UNFIXED BUG: hush does not parse embedded `cmd` at embedding document parse time
2echo _`"pwd`_
diff --git a/shell/hush_test/hush-misc/wait6.right b/shell/hush_test/hush-misc/wait6.right
new file mode 100644
index 000000000..12decc137
--- /dev/null
+++ b/shell/hush_test/hush-misc/wait6.right
@@ -0,0 +1,2 @@
10
23
diff --git a/shell/hush_test/hush-misc/wait6.tests b/shell/hush_test/hush-misc/wait6.tests
new file mode 100755
index 000000000..c23713199
--- /dev/null
+++ b/shell/hush_test/hush-misc/wait6.tests
@@ -0,0 +1,6 @@
1# In bash, "wait $!" extracts correct exitcode even if bg task has already exited
2# It prints 0, then 3:
3(sleep 0; exit 3) & sleep 1
4echo $?
5wait $!
6echo $?
diff --git a/shell/hush_test/hush-parsing/and_or_and_backgrounding.right b/shell/hush_test/hush-parsing/and_or_and_backgrounding.right
new file mode 100644
index 000000000..90ce63e01
--- /dev/null
+++ b/shell/hush_test/hush-parsing/and_or_and_backgrounding.right
@@ -0,0 +1,4 @@
1First
2Second
3Third
4Done
diff --git a/shell/hush_test/hush-parsing/and_or_and_backgrounding.tests b/shell/hush_test/hush-parsing/and_or_and_backgrounding.tests
new file mode 100755
index 000000000..204d44490
--- /dev/null
+++ b/shell/hush_test/hush-parsing/and_or_and_backgrounding.tests
@@ -0,0 +1,30 @@
1# According to this doc, && || have higher precedence than ; &.
2# See example below.
3# Precedence of ; is not a problem in practice. Precedence of & is.
4#
5#http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html
6#
7#2.9.3 Lists
8#
9#An AND-OR list is a sequence of one or more pipelines separated by
10#the operators "&&" and "||" .
11#
12#A list is a sequence of one or more AND-OR lists separated by the operators
13#';' and '&' and optionally terminated by ';', '&', or <newline>.
14#
15#The operators "&&" and "||" shall have equal precedence and shall be
16#evaluated with left associativity. For example, both of the following
17#commands write solely bar to standard output:
18#
19# false && echo foo || echo bar
20# true || echo foo && echo bar
21#
22#A ';' or <newline> terminator shall cause the preceding AND-OR list
23#to be executed sequentially; an '&' shall cause asynchronous execution
24#of the preceding AND-OR list.
25
26echo First && sleep 0.2 && echo Third &
27sleep 0.1
28echo Second
29wait
30echo Done
diff --git a/shell/hush_test/hush-quoting/quoted_punct.right b/shell/hush_test/hush-quoting/quoted_punct.right
new file mode 100644
index 000000000..ab66c3ce0
--- /dev/null
+++ b/shell/hush_test/hush-quoting/quoted_punct.right
@@ -0,0 +1,35 @@
1ok
2ok
3ok
4ok
5ok
6ok
7ok
8ok
9ok
10ok
11ok
12ok
13ok
14ok
15ok
16ok
17ok
18ok
19ok
20ok
21ok
22ok
23ok
24ok
25ok
26ok
27ok
28ok
29ok
30ok
31ok
32ok
33ok
34ok
35ok
diff --git a/shell/hush_test/hush-quoting/quoted_punct.tests b/shell/hush_test/hush-quoting/quoted_punct.tests
new file mode 100755
index 000000000..83ee40bf4
--- /dev/null
+++ b/shell/hush_test/hush-quoting/quoted_punct.tests
@@ -0,0 +1,41 @@
1# Testing glob-escaping of every ASCII punctuation char
2# Some chars have more than one test
3# 21..2f
4case '!' in [\!] ) echo ok;; *) echo 'WRONG!';; esac
5case '"' in [\"] ) echo ok;; *) echo 'WRONG"';; esac
6case '#' in [\#] ) echo ok;; *) echo 'WRONG#';; esac
7case '$' in [\$] ) echo ok;; *) echo 'WRONG$';; esac
8case '%' in [\%] ) echo ok;; *) echo 'WRONG%';; esac
9case '&' in [\&] ) echo ok;; *) echo 'WRONG&';; esac
10case "'" in [\'] ) echo ok;; *) echo "WRONG'";; esac
11case '(' in [\(] ) echo ok;; *) echo 'WRONG(';; esac
12case ')' in [\)] ) echo ok;; *) echo 'WRONG)';; esac
13case '*' in [\*] ) echo ok;; *) echo 'WRONG*';; esac
14case '+' in [\+] ) echo ok;; *) echo 'WRONG+';; esac
15case ',' in [\,] ) echo ok;; *) echo 'WRONG,';; esac
16case '-' in [\-] ) echo ok;; *) echo 'WRONG-';; esac
17case '-' in [a\-c]) echo ok;; *) echo 'WRONGa\-c';; esac
18case '.' in [\.] ) echo ok;; *) echo 'WRONG.';; esac
19case '/' in [\/] ) echo ok;; *) echo 'WRONG/';; esac
20# 3a..40
21case ':' in [\:] ) echo ok;; *) echo 'WRONG:';; esac
22case ';' in [\;] ) echo ok;; *) echo 'WRONG;';; esac
23case '<' in [\<] ) echo ok;; *) echo 'WRONG<';; esac
24case '=' in [\=] ) echo ok;; *) echo 'WRONG=';; esac
25case '>' in [\>] ) echo ok;; *) echo 'WRONG>';; esac
26case '?' in [\?] ) echo ok;; *) echo 'WRONG?';; esac
27case '@' in [\@] ) echo ok;; *) echo 'WRONG@';; esac
28# 5b..60
29case '[' in [\[] ) echo ok;; *) echo 'WRONG[';; esac
30case '\' in [\\] ) echo ok;; *) echo 'WRONG\';; esac
31case '\' in \\ ) echo ok;; *) echo 'WRONG\\';; esac
32case ']' in [\]] ) echo ok;; *) echo 'WRONG]';; esac
33case ']' in [a\]]) echo ok;; *) echo 'WRONGa]';; esac
34case '^' in [\^] ) echo ok;; *) echo 'WRONG^';; esac
35case '_' in [\_] ) echo ok;; *) echo 'WRONG_';; esac
36case '`' in [\`] ) echo ok;; *) echo 'WRONG`';; esac
37# 7b..7e
38case '{' in [\{] ) echo ok;; *) echo 'WRONG{';; esac
39case '|' in [\|] ) echo ok;; *) echo 'WRONG|';; esac
40case '}' in [\}] ) echo ok;; *) echo 'WRONG}';; esac
41case '~' in [\~] ) echo ok;; *) echo 'WRONG~';; esac
diff --git a/shell/hush_test/hush-quoting/unicode_8x_chars.right b/shell/hush_test/hush-quoting/unicode_8x_chars.right
new file mode 100644
index 000000000..7780b88b4
--- /dev/null
+++ b/shell/hush_test/hush-quoting/unicode_8x_chars.right
@@ -0,0 +1,6 @@
1ok
2ok
3ok
4ok
5ok
6ok
diff --git a/shell/hush_test/hush-quoting/unicode_8x_chars.tests b/shell/hush_test/hush-quoting/unicode_8x_chars.tests
new file mode 100755
index 000000000..1258745ec
--- /dev/null
+++ b/shell/hush_test/hush-quoting/unicode_8x_chars.tests
@@ -0,0 +1,28 @@
1# Unicode: cf 80
2case π in
3( "π" ) echo ok ;;
4( * ) echo WRONG ;;
5esac
6# Unicode: cf 81
7case ρ in
8( "ρ" ) echo ok ;;
9( * ) echo WRONG ;;
10esac
11# Unicode: cf 82
12case ς in
13( "ς" ) echo ok ;;
14( * ) echo WRONG ;;
15esac
16
17case "π" in
18( π ) echo ok ;;
19( * ) echo WRONG ;;
20esac
21case "ρ" in
22( ρ ) echo ok ;;
23( * ) echo WRONG ;;
24esac
25case "ς" in
26( ς ) echo ok ;;
27( * ) echo WRONG ;;
28esac
diff --git a/shell/hush_test/hush-redir/redir3.right b/shell/hush_test/hush-redir/redir3.right
index 3d20bbf68..e3c878b7d 100644
--- a/shell/hush_test/hush-redir/redir3.right
+++ b/shell/hush_test/hush-redir/redir3.right
@@ -1,14 +1,2 @@
1hush: can't open '/does/not/exist': No such file or directory 1TEST
2One:1 2hush: can't duplicate file descriptor: Bad file descriptor
3hush: can't open '/cant/be/created': No such file or directory
4One:1
5Ok
6hush: can't open '/cant/be/created': No such file or directory
7Zero:0
8hush: can't open '/cant/be/created': No such file or directory
9One:1
10hush: can't open '/cant/be/created': No such file or directory
11One:1
12hush: can't open '/cant/be/created': No such file or directory
13Zero:0
14Done
diff --git a/shell/hush_test/hush-redir/redir3.tests b/shell/hush_test/hush-redir/redir3.tests
index 7c28e4324..e37d5e45a 100755
--- a/shell/hush_test/hush-redir/redir3.tests
+++ b/shell/hush_test/hush-redir/redir3.tests
@@ -1,9 +1,5 @@
1echo Error >/does/not/exist; echo One:$? 1# redirects to closed descriptors should not leave these descriptors
2t=BAD 2# open afterwards
3t=Ok >>/cant/be/created; echo One:$? 3echo TEST 9>/dev/null
4echo $t 4echo MUST ERROR OUT >&9
5! >/cant/be/created; echo Zero:$? 5echo "Output to fd#9: $?"
6exec >/cant/be/created; echo One:$?
7exec /bin/true >/cant/be/created; echo One:$?
8! exec /bin/true >/cant/be/created; echo Zero:$?
9echo Done
diff --git a/shell/hush_test/hush-redir/redir_errors.right b/shell/hush_test/hush-redir/redir_errors.right
new file mode 100644
index 000000000..3d20bbf68
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_errors.right
@@ -0,0 +1,14 @@
1hush: can't open '/does/not/exist': No such file or directory
2One:1
3hush: can't open '/cant/be/created': No such file or directory
4One:1
5Ok
6hush: can't open '/cant/be/created': No such file or directory
7Zero:0
8hush: can't open '/cant/be/created': No such file or directory
9One:1
10hush: can't open '/cant/be/created': No such file or directory
11One:1
12hush: can't open '/cant/be/created': No such file or directory
13Zero:0
14Done
diff --git a/shell/hush_test/hush-redir/redir_errors.tests b/shell/hush_test/hush-redir/redir_errors.tests
new file mode 100755
index 000000000..7c28e4324
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_errors.tests
@@ -0,0 +1,9 @@
1echo Error >/does/not/exist; echo One:$?
2t=BAD
3t=Ok >>/cant/be/created; echo One:$?
4echo $t
5! >/cant/be/created; echo Zero:$?
6exec >/cant/be/created; echo One:$?
7exec /bin/true >/cant/be/created; echo One:$?
8! exec /bin/true >/cant/be/created; echo Zero:$?
9echo Done
diff --git a/shell/hush_test/hush-redir/redir_to_bad_fd.right b/shell/hush_test/hush-redir/redir_to_bad_fd.right
new file mode 100644
index 000000000..936911ce5
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_to_bad_fd.right
@@ -0,0 +1 @@
hush: can't duplicate file descriptor: Bad file descriptor
diff --git a/shell/hush_test/hush-redir/redir_to_bad_fd.tests b/shell/hush_test/hush-redir/redir_to_bad_fd.tests
new file mode 100755
index 000000000..91b0c1ff9
--- /dev/null
+++ b/shell/hush_test/hush-redir/redir_to_bad_fd.tests
@@ -0,0 +1,3 @@
1# ash uses fd 10 (usually) for reading the script
2echo LOST >&10
3echo OK
diff --git a/shell/hush_test/hush-signals/signal4.right b/shell/hush_test/hush-signals/signal4.right
new file mode 100644
index 000000000..2d0624714
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal4.right
@@ -0,0 +1,4 @@
1hush: trap: BADNAME: invalid signal specification
21
3Trapped
4Ok
diff --git a/shell/hush_test/hush-signals/signal4.tests b/shell/hush_test/hush-signals/signal4.tests
new file mode 100755
index 000000000..6f1c4a950
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal4.tests
@@ -0,0 +1,5 @@
1#!/bin/sh
2
3trap "echo Trapped" BADNAME TERM; echo $?
4kill $$
5echo Ok
diff --git a/shell/hush_test/hush-signals/signal8.right b/shell/hush_test/hush-signals/signal8.right
new file mode 100644
index 000000000..39572f30e
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal8.right
@@ -0,0 +1,3 @@
1Removing traps
2End of exit_func
3Done: 0
diff --git a/shell/hush_test/hush-signals/signal8.tests b/shell/hush_test/hush-signals/signal8.tests
new file mode 100755
index 000000000..731af7477
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal8.tests
@@ -0,0 +1,18 @@
1"$THIS_SH" -c '
2exit_func() {
3 echo "Removing traps"
4 trap - EXIT TERM INT
5 echo "End of exit_func"
6}
7set -e
8trap exit_func EXIT TERM INT
9sleep 2
10exit 77
11' &
12
13sleep 1
14# BUG: ash kills -PGRP, but in non-interactive shell we do not create pgrps!
15# In this case, bash kills by PID, not PGRP.
16kill -TERM %1
17wait
18echo Done: $?
diff --git a/shell/hush_test/hush-signals/signal9.right b/shell/hush_test/hush-signals/signal9.right
new file mode 100644
index 000000000..39572f30e
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal9.right
@@ -0,0 +1,3 @@
1Removing traps
2End of exit_func
3Done: 0
diff --git a/shell/hush_test/hush-signals/signal9.tests b/shell/hush_test/hush-signals/signal9.tests
new file mode 100755
index 000000000..18e71012b
--- /dev/null
+++ b/shell/hush_test/hush-signals/signal9.tests
@@ -0,0 +1,21 @@
1# Note: the inner script is a test which checks for a different bug
2# (ordering between INT handler and exit on "set -e"),
3# but so far I did not figure out how to simulate it non-interactively.
4
5"$THIS_SH" -c '
6exit_func() {
7 echo "Removing traps"
8 trap - EXIT TERM INT
9 echo "End of exit_func"
10}
11set -e
12trap exit_func EXIT TERM INT
13sleep 2
14exit 77
15' &
16
17child=$!
18sleep 1
19kill -TERM $child
20wait
21echo Done: $?
diff --git a/shell/hush_test/hush-vars/readonly0.right b/shell/hush_test/hush-vars/readonly0.right
new file mode 100644
index 000000000..8b750eb5f
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly0.right
@@ -0,0 +1,20 @@
1readonly a=A
2readonly b=B
3Ok:0
4
5hush: a=A: readonly variable
6Fail:1
7hush: a=A: readonly variable
8Fail:1
9
10hush: a=Z: readonly variable
11Fail:1
12
13hush: a=Z: readonly variable
14b=B
15^^^a is not exported
16hush: a=Z: readonly variable
17Visible:42
18
19hush: a: readonly variable
20Fail:1
diff --git a/shell/hush_test/hush-vars/readonly0.tests b/shell/hush_test/hush-vars/readonly0.tests
new file mode 100755
index 000000000..0833ccf29
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly0.tests
@@ -0,0 +1,42 @@
1unset a b
2#
3readonly a=A
4b=B
5readonly b
6# readonly on already readonly var is harmless:
7readonly b a
8readonly | grep '^readonly [ab]='
9# this should work:
10export a b
11export -n a b
12echo Ok:$?
13env | grep -e^a= -e^b= # shows nothing
14
15echo
16# these should all fail (despite the same value being assigned)
17# bash does not abort even in non-interactive more (in script)
18true; a=A
19echo Fail:$?
20true; readonly a=A
21echo Fail:$?
22
23echo
24# in bash, assignment in export fails, but export succeeds! :)
25# we don't mimic that!
26true; export a=Z
27echo Fail:$?
28#env | grep '^a='
29#echo "^^^a is exported"
30export -n a # undo that bashism, if it happens
31
32echo
33export b
34# this fails to both set and export a:
35a=Z env | grep '^[ab]='
36echo "^^^a is not exported"
37# but external command does get executed, and $? is not mangled (stays 42):
38(exit 42); a=Z env echo Visible:$?
39
40echo
41true; unset a
42echo Fail:$?
diff --git a/shell/hush_test/hush-vars/readonly2.right b/shell/hush_test/hush-vars/readonly2.right
new file mode 100644
index 000000000..38551d4ad
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly2.right
@@ -0,0 +1,5 @@
1hush: a=Z: readonly variable
2Visible:42
3
4hush: a=Z: readonly variable
5Visible:42
diff --git a/shell/hush_test/hush-vars/readonly2.tests b/shell/hush_test/hush-vars/readonly2.tests
new file mode 100755
index 000000000..b758d9602
--- /dev/null
+++ b/shell/hush_test/hush-vars/readonly2.tests
@@ -0,0 +1,7 @@
1unset a
2readonly a=A
3
4# external commands and builtins should behave the same:
5(exit 42); a=Z echo "Visible:$?"
6echo
7(exit 42); a=Z env echo "Visible:$?"
diff --git a/shell/hush_test/hush-vars/var-utf8-length.right b/shell/hush_test/hush-vars/var-utf8-length.right
new file mode 100644
index 000000000..6f4247a62
--- /dev/null
+++ b/shell/hush_test/hush-vars/var-utf8-length.right
@@ -0,0 +1 @@
26
diff --git a/shell/hush_test/hush-vars/var-utf8-length.tests b/shell/hush_test/hush-vars/var-utf8-length.tests
new file mode 100755
index 000000000..b6e87f191
--- /dev/null
+++ b/shell/hush_test/hush-vars/var-utf8-length.tests
@@ -0,0 +1,4 @@
1LANG=en_US.UTF-8
2LC_ALL=en_US.UTF-8
3X=abcdÉfghÍjklmnÓpqrstÚvwcyz
4echo ${#X}
diff --git a/shell/hush_test/hush-vars/var_bash1a.right b/shell/hush_test/hush-vars/var_bash1a.right
new file mode 100644
index 000000000..1965b5c6c
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash1a.right
@@ -0,0 +1,6 @@
1parameter 'abcdef'
2varoffset2 'cdef'
3varoffset-2 'ef'
4literal '2' 'cdef'
5literal '-2' 'abcdef'
6literal ' -2' 'ef'
diff --git a/shell/hush_test/hush-vars/var_bash1a.tests b/shell/hush_test/hush-vars/var_bash1a.tests
new file mode 100755
index 000000000..c75c2dc53
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash1a.tests
@@ -0,0 +1,11 @@
1parameter=abcdef
2offset=2
3noffset=-2
4echo "parameter '${parameter}'"
5echo "varoffset2 '${parameter:${offset}}'"
6echo "varoffset-2 '${parameter:${noffset}}'"
7echo "literal '2' '${parameter:2}'"
8# This is not interpreted as ${VAR:POS{:LEN}},
9# but as ${VAR:=WORD} - if VAR is unset or null, substitute WORD
10echo "literal '-2' '${parameter:-2}'"
11echo "literal ' -2' '${parameter: -2}'"
diff --git a/shell/hush_test/hush-vars/var_bash1b.right b/shell/hush_test/hush-vars/var_bash1b.right
new file mode 100644
index 000000000..fafc0f07c
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash1b.right
@@ -0,0 +1,23 @@
1all |0123456
24: |456
34:2 |45
44:-1 |45
54:-2 |4
64:-3 |
7-4: |3456
8-4:2 |34
9-4:-1 |345
10-4:-2 |34
11-4:-3 |3
12-4:-4 |
13-4:i=2 |34
14-4:i=-2|34
15-4:i=-3|3
16-4:i=-4|
17-5: |23456
18-6: |123456
19-7: |0123456
20-8: |
21-9: |
22-9:-99 |
23Ok:0
diff --git a/shell/hush_test/hush-vars/var_bash1b.tests b/shell/hush_test/hush-vars/var_bash1b.tests
new file mode 100755
index 000000000..efbdef35c
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash1b.tests
@@ -0,0 +1,24 @@
1set -- 0123456
2 echo "all |"$1
3 echo "4: |"${1:4}
4 echo "4:2 |"${1:4:2}
5 echo "4:-1 |"${1:4:-1}
6 echo "4:-2 |"${1:4:-2}
7 echo "4:-3 |"${1:4:-3}
8 echo "-4: |"${1: -4}
9 echo "-4:2 |"${1: -4:2}
10 echo "-4:-1 |"${1: -4:-1}
11 echo "-4:-2 |"${1: -4:-2}
12 echo "-4:-3 |"${1: -4:-3}
13 echo "-4:-4 |"${1: -4:-4}
14i=2; echo "-4:i=2 |"${1: -4:i}
15i=-2; echo "-4:i=-2|"${1: -4:i}
16i=-3; echo "-4:i=-3|"${1: -4:i}
17i=-4; echo "-4:i=-4|"${1: -4:i}
18 echo "-5: |"${1: -5}
19 echo "-6: |"${1: -6}
20 echo "-7: |"${1: -7}
21 echo "-8: |"${1: -8}
22 echo "-9: |"${1: -9}
23 echo "-9:-99 |"${1: -9:-99}
24echo Ok:$?
diff --git a/shell/hush_test/run-all b/shell/hush_test/run-all
index 837b3f7da..1dd0edc39 100755
--- a/shell/hush_test/run-all
+++ b/shell/hush_test/run-all
@@ -9,6 +9,8 @@ unset LC_NUMERIC
9unset LC_TIME 9unset LC_TIME
10unset LC_ALL 10unset LC_ALL
11 11
12TOPDIR=`pwd`
13
12if test ! -x hush; then 14if test ! -x hush; then
13 if test ! -x ../../busybox; then 15 if test ! -x ../../busybox; then
14 echo "Can't run tests. Put hush binary into this directory (`pwd`)" 16 echo "Can't run tests. Put hush binary into this directory (`pwd`)"
@@ -38,6 +40,8 @@ do_test()
38 test -d "$1" || return 0 40 test -d "$1" || return 0
39 d=${d%/} 41 d=${d%/}
40# echo Running tests in directory "$1" 42# echo Running tests in directory "$1"
43 # $1 but with / replaced by # so that it can be used as filename part
44 noslash=`echo "$1" | sed 's:/:#:g'`
41 ( 45 (
42 tret=0 46 tret=0
43 cd "$1" || { echo "cannot cd $1!"; exit 1; } 47 cd "$1" || { echo "cannot cd $1!"; exit 1; }
@@ -49,34 +53,35 @@ do_test()
49 #*) echo $x ; sh $x ;; 53 #*) echo $x ; sh $x ;;
50 *) 54 *)
51 echo -n "$1/$x:" 55 echo -n "$1/$x:"
52 sh "$x" >"../$1-$x.fail" 2>&1 && \ 56 sh "$x" >"$TOPDIR/$noslash-$x.fail" 2>&1 && \
53 { { echo " ok"; rm "../$1-$x.fail"; } || echo " fail"; } 57 { { echo " ok"; rm "$TOPDIR/$noslash-$x.fail"; } || echo " fail"; }
54 ;; 58 ;;
55 esac 59 esac
56 done 60 done
57 # Many bash run-XXX scripts just do this, 61 # Many bash run-XXX scripts just do this,
58 # no point in duplication it all over the place 62 # no point in duplication it all over the place
59 for x in *.tests; do 63 for x in *.tests; do
60 test -x "$x" || continue 64 test -x "$x" || continue
61 name="${x%%.tests}" 65 name="${x%%.tests}"
62 test -f "$name.right" || continue 66 test -f "$name.right" || continue
63# echo Running test: "$x" 67# echo Running test: "$x"
64 echo -n "$1/$x:" 68 echo -n "$1/$x:"
65 ( 69 (
66 "$THIS_SH" "./$x" >"$name.xx" 2>&1 70 "$THIS_SH" "./$x" >"$name.xx" 2>&1
67 r=$? 71 r=$?
68 # filter C library differences 72 # filter C library differences
69 sed -i \ 73 sed -i \
70 -e "/: invalid option /s:'::g" \ 74 -e "/: invalid option /s:'::g" \
71 "$name.xx" 75 "$name.xx"
72 test $r -eq 77 && rm -f "../$1-$x.fail" && exit 77 76 test $r -eq 77 && rm -f "$TOPDIR/$noslash-$x.fail" && exit 77
73 diff -u "$name.xx" "$name.right" >"../$1-$x.fail" && rm -f "$name.xx" "../$1-$x.fail" 77 diff -u "$name.xx" "$name.right" >"$TOPDIR/$noslash-$x.fail" \
74 ) 78 && rm -f "$name.xx" "$TOPDIR/$noslash-$x.fail"
75 case $? in 79 )
76 0) echo " ok";; 80 case $? in
77 77) echo " skip (feature disabled)";; 81 0) echo " ok";;
78 *) echo " fail"; tret=1;; 82 77) echo " skip (feature disabled)";;
79 esac 83 *) echo " fail"; tret=1;;
84 esac
80 done 85 done
81 exit ${tret} 86 exit ${tret}
82 ) 87 )
@@ -92,7 +97,7 @@ if [ $# -lt 1 ]; then
92 modules=`ls -d hush-*` 97 modules=`ls -d hush-*`
93 98
94 for module in $modules; do 99 for module in $modules; do
95 do_test $module || ret=1 100 do_test $module || ret=1
96 done 101 done
97else 102else
98 while [ $# -ge 1 ]; do 103 while [ $# -ge 1 ]; do
diff --git a/shell/shell_common.c b/shell/shell_common.c
index 55617b167..154b860f8 100644
--- a/shell/shell_common.c
+++ b/shell/shell_common.c
@@ -412,9 +412,7 @@ shell_builtin_ulimit(char **argv)
412 */ 412 */
413 GETOPT_RESET(); 413 GETOPT_RESET();
414 414
415 argc = 1; 415 argc = string_array_len(argv);
416 while (argv[argc])
417 argc++;
418 416
419 opts = 0; 417 opts = 0;
420 while (1) { 418 while (1) {