diff options
author | Ron Yorston <rmy@pobox.com> | 2017-07-18 15:58:52 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2017-07-18 15:58:52 +0100 |
commit | b680f05ad449505e3d914bebd4c8d83bf768c094 (patch) | |
tree | c08ded13d430b0e7e0104f2eb594fad190ce98a3 /shell | |
parent | 258200ff81d5a9da54dab35acf36213eff1e399b (diff) | |
parent | 513a2457b65894b10b9fd6aa8753fca59eced08c (diff) | |
download | busybox-w32-b680f05ad449505e3d914bebd4c8d83bf768c094.tar.gz busybox-w32-b680f05ad449505e3d914bebd4c8d83bf768c094.tar.bz2 busybox-w32-b680f05ad449505e3d914bebd4c8d83bf768c094.zip |
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
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... */ |
2571 | static const char *expandstr(const char *ps); | 2570 | static const char *expandstr(const char *ps); |
2572 | #else | ||
2573 | #define expandstr(s) s | ||
2574 | #endif | ||
2575 | 2571 | ||
2576 | static void | 2572 | static void |
2577 | setprompt_if(smallint do_set, int whichprompt) | 2573 | setprompt_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) | |||
7970 | static int | 8009 | static int |
7971 | patmatch(char *pattern, const char *string) | 8010 | patmatch(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 | ||
12937 | static const char * | 12971 | static const char * |
12938 | expandstr(const char *ps) | 12972 | expandstr(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" | 1 | qwe |
2 | asd | ||
3 | 123 | ||
4 | 456 | ||
5 | Ok | ||
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: | 1 | cat <<000; cat <<www; cat <<eee |
2 | 2 | 000 | |
3 | <<EOF; then <W | 3 | qwe |
4 | asd | ||
5 | www | ||
6 | 123 | ||
7 | 456 | ||
8 | eee | ||
9 | echo 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 @@ | |||
1 | hello | 1 | exit EOF-f |
2 | " | ||
3 | echo $f | ||
4 | echo `echo Hello World` | ||
5 | moo | ||
6 | EOF-f | ||
7 | EOF-f f | ||
8 | EOF-f | ||
9 | Ok | ||
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 @@ | |||
1 | echo hello >greeting | 1 | f=1 |
2 | cat <<EOF && | 2 | cat <<- EOF-f"" |
3 | $(cat greeting) | 3 | exit EOF-f |
4 | EOF | 4 | " |
5 | { | 5 | echo $f |
6 | echo $? | 6 | echo `echo Hello World` |
7 | cat greeting | 7 | moo |
8 | } >/dev/null | 8 | EOF-f |
9 | rm greeting | 9 | EOF-f f |
10 | EOF-f | ||
11 | EOF-f | ||
12 | echo 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 @@ | |||
1 | echo hello >greeting | ||
2 | cat <<EOF && | ||
3 | $(cat greeting) | ||
4 | EOF | ||
5 | { | ||
6 | echo $? | ||
7 | cat greeting | ||
8 | } >/dev/null | ||
9 | rm 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 @@ | |||
1 | Sending 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 | |||
41 | echo 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 @@ | |||
1 | 0 | ||
2 | 3 | ||
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 | ||
4 | echo $? | ||
5 | wait $! | ||
6 | echo $? | ||
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 @@ | |||
1 | ok | ||
2 | ok | ||
3 | ok | ||
4 | ok | ||
5 | ok | ||
6 | ok | ||
7 | ok | ||
8 | ok | ||
9 | ok | ||
10 | ok | ||
11 | ok | ||
12 | ok | ||
13 | ok | ||
14 | ok | ||
15 | ok | ||
16 | ok | ||
17 | ok | ||
18 | ok | ||
19 | ok | ||
20 | ok | ||
21 | ok | ||
22 | ok | ||
23 | ok | ||
24 | ok | ||
25 | ok | ||
26 | ok | ||
27 | ok | ||
28 | ok | ||
29 | ok | ||
30 | ok | ||
31 | ok | ||
32 | ok | ||
33 | ok | ||
34 | ok | ||
35 | ok | ||
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 | ||
4 | case '!' in [\!] ) echo ok;; *) echo 'WRONG!';; esac | ||
5 | case '"' in [\"] ) echo ok;; *) echo 'WRONG"';; esac | ||
6 | case '#' in [\#] ) echo ok;; *) echo 'WRONG#';; esac | ||
7 | case '$' in [\$] ) echo ok;; *) echo 'WRONG$';; esac | ||
8 | case '%' in [\%] ) echo ok;; *) echo 'WRONG%';; esac | ||
9 | case '&' in [\&] ) echo ok;; *) echo 'WRONG&';; esac | ||
10 | case "'" in [\'] ) echo ok;; *) echo "WRONG'";; esac | ||
11 | case '(' in [\(] ) echo ok;; *) echo 'WRONG(';; esac | ||
12 | case ')' in [\)] ) echo ok;; *) echo 'WRONG)';; esac | ||
13 | case '*' in [\*] ) echo ok;; *) echo 'WRONG*';; esac | ||
14 | case '+' in [\+] ) echo ok;; *) echo 'WRONG+';; esac | ||
15 | case ',' in [\,] ) echo ok;; *) echo 'WRONG,';; esac | ||
16 | case '-' in [\-] ) echo ok;; *) echo 'WRONG-';; esac | ||
17 | case '-' in [a\-c]) echo ok;; *) echo 'WRONGa\-c';; esac | ||
18 | case '.' in [\.] ) echo ok;; *) echo 'WRONG.';; esac | ||
19 | case '/' in [\/] ) echo ok;; *) echo 'WRONG/';; esac | ||
20 | # 3a..40 | ||
21 | case ':' in [\:] ) echo ok;; *) echo 'WRONG:';; esac | ||
22 | case ';' in [\;] ) echo ok;; *) echo 'WRONG;';; esac | ||
23 | case '<' in [\<] ) echo ok;; *) echo 'WRONG<';; esac | ||
24 | case '=' in [\=] ) echo ok;; *) echo 'WRONG=';; esac | ||
25 | case '>' in [\>] ) echo ok;; *) echo 'WRONG>';; esac | ||
26 | case '?' in [\?] ) echo ok;; *) echo 'WRONG?';; esac | ||
27 | case '@' in [\@] ) echo ok;; *) echo 'WRONG@';; esac | ||
28 | # 5b..60 | ||
29 | case '[' in [\[] ) echo ok;; *) echo 'WRONG[';; esac | ||
30 | case '\' in [\\] ) echo ok;; *) echo 'WRONG\';; esac | ||
31 | case '\' in \\ ) echo ok;; *) echo 'WRONG\\';; esac | ||
32 | case ']' in [\]] ) echo ok;; *) echo 'WRONG]';; esac | ||
33 | case ']' in [a\]]) echo ok;; *) echo 'WRONGa]';; esac | ||
34 | case '^' in [\^] ) echo ok;; *) echo 'WRONG^';; esac | ||
35 | case '_' in [\_] ) echo ok;; *) echo 'WRONG_';; esac | ||
36 | case '`' in [\`] ) echo ok;; *) echo 'WRONG`';; esac | ||
37 | # 7b..7e | ||
38 | case '{' in [\{] ) echo ok;; *) echo 'WRONG{';; esac | ||
39 | case '|' in [\|] ) echo ok;; *) echo 'WRONG|';; esac | ||
40 | case '}' in [\}] ) echo ok;; *) echo 'WRONG}';; esac | ||
41 | case '~' 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 @@ | |||
1 | ok | ||
2 | ok | ||
3 | ok | ||
4 | ok | ||
5 | ok | ||
6 | ok | ||
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 | ||
2 | case π in | ||
3 | ( "π" ) echo ok ;; | ||
4 | ( * ) echo WRONG ;; | ||
5 | esac | ||
6 | # Unicode: cf 81 | ||
7 | case ρ in | ||
8 | ( "ρ" ) echo ok ;; | ||
9 | ( * ) echo WRONG ;; | ||
10 | esac | ||
11 | # Unicode: cf 82 | ||
12 | case ς in | ||
13 | ( "ς" ) echo ok ;; | ||
14 | ( * ) echo WRONG ;; | ||
15 | esac | ||
16 | |||
17 | case "π" in | ||
18 | ( π ) echo ok ;; | ||
19 | ( * ) echo WRONG ;; | ||
20 | esac | ||
21 | case "ρ" in | ||
22 | ( ρ ) echo ok ;; | ||
23 | ( * ) echo WRONG ;; | ||
24 | esac | ||
25 | case "ς" in | ||
26 | ( ς ) echo ok ;; | ||
27 | ( * ) echo WRONG ;; | ||
28 | esac | ||
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 | 1 | Backgrounded pipes shall have their stdin redirected to /dev/null |
2 | OK | 2 | Zero:0 |
3 | Zero:0 | ||
4 | Done | ||
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 | 1 | echo "Backgrounded pipes shall have their stdin redirected to /dev/null" |
2 | echo LOST >&10 | 2 | |
3 | echo 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 | |||
8 | cat & wait; echo Zero:$? | ||
9 | |||
10 | # This does not work for bash! bash bug? | ||
11 | cat | cat & wait; echo Zero:$? | ||
12 | |||
13 | echo 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 | ||
2 | OK | ||
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 | ||
2 | echo LOST >&10 | ||
3 | echo 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 @@ | |||
1 | LANG=en_US.UTF-8 | ||
2 | LC_ALL=en_US.UTF-8 | ||
1 | X=abcdÉfghÍjklmnÓpqrstÚvwcyz | 3 | X=abcdÉfghÍjklmnÓpqrstÚvwcyz |
2 | echo ${#X} | 4 | echo ${#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}'" | |||
5 | echo "varoffset2 '${parameter:${offset}}'" | 5 | echo "varoffset2 '${parameter:${offset}}'" |
6 | echo "varoffset-2 '${parameter:${noffset}}'" | 6 | echo "varoffset-2 '${parameter:${noffset}}'" |
7 | echo "literal '2' '${parameter:2}'" | 7 | echo "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 |
10 | echo "literal '-2' '${parameter:-2}'" | 10 | echo "literal '-2' '${parameter:-2}'" |
11 | echo "literal ' -2' '${parameter: -2}'" | 11 | echo "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 @@ | |||
1 | all |0123456 | ||
2 | 4: |456 | ||
3 | 4:2 |45 | ||
4 | 4:-1 |45 | ||
5 | 4:-2 |4 | ||
6 | 4:-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 | | ||
23 | Ok: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 @@ | |||
1 | set -- 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} | ||
14 | i=2; echo "-4:i=2 |"${1: -4:i} | ||
15 | i=-2; echo "-4:i=-2|"${1: -4:i} | ||
16 | i=-3; echo "-4:i=-3|"${1: -4:i} | ||
17 | i=-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} | ||
24 | echo 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 @@ | |||
1 | Expected Actual | ||
2 | a*z : a*z | ||
3 | \z : \z | ||
4 | a1z a2z: a1z a2z | ||
5 | z : 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' | ||
5 | v='a bz'; echo 'a*z :' "${v/a*z/a*z}" | ||
6 | v='a bz'; echo '\z :' "${v/a*z/\z}" | ||
7 | v='a bz'; echo 'a1z a2z:' ${v/a*z/a*z} | ||
8 | v='a bz'; echo 'z :' ${v/a*z/\z} | ||
9 | rm 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 | ||
3 | TOPDIR=$PWD | 3 | TOPDIR=`pwd` |
4 | 4 | ||
5 | test -x ash || { | 5 | test -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 $? | |||
10 | test -x recho || gcc -O2 -o recho recho.c || exit $? | 10 | test -x recho || gcc -O2 -o recho recho.c || exit $? |
11 | test -x zecho || gcc -O2 -o zecho zecho.c || exit $? | 11 | test -x zecho || gcc -O2 -o zecho zecho.c || exit $? |
12 | 12 | ||
13 | PATH="$PWD:$PATH" # for ash and recho/zecho/printenv | 13 | PATH="`pwd`:$PATH" # for ash and recho/zecho/printenv |
14 | export PATH | 14 | export PATH |
15 | 15 | ||
16 | THIS_SH="$PWD/ash" | 16 | THIS_SH="`pwd`/ash" |
17 | export THIS_SH | 17 | export THIS_SH |
18 | 18 | ||
19 | do_test() | 19 | do_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 | ||
61 | ret=0 | ||
62 | |||
59 | if [ $# -lt 1 ]; then | 63 | if [ $# -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 |
69 | else | 73 | else |
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 |
76 | fi | 80 | fi |
81 | |||
82 | exit ${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 { | |||
753 | static const char o_opt_strings[] ALIGN1 = | 772 | static 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 = | |||
760 | enum { | 780 | enum { |
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 |
930 | static int builtin_export(char **argv) FAST_FUNC; | 971 | static int builtin_export(char **argv) FAST_FUNC; |
931 | #endif | 972 | #endif |
973 | #if ENABLE_HUSH_READONLY | ||
974 | static int builtin_readonly(char **argv) FAST_FUNC; | ||
975 | #endif | ||
932 | #if ENABLE_HUSH_JOB | 976 | #if ENABLE_HUSH_JOB |
933 | static int builtin_fg_bg(char **argv) FAST_FUNC; | 977 | static int builtin_fg_bg(char **argv) FAST_FUNC; |
934 | static int builtin_jobs(char **argv) FAST_FUNC; | 978 | static 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 | ||
1443 | static 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 | ||
1393 | static int xdup_and_close(int fd, int F_DUPFD_maybe_CLOEXEC) | 1457 | static 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 | } |
1435 | static int save_FILEs_on_redirect(int fd) | 1504 | static 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 |
1461 | static void close_all_FILE_list(void) | 1532 | static 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 | ||
1487 | static void save_and_replace_G_args(save_arg_t *sv, char **argv) | 1558 | static 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 | ||
1506 | static void restore_G_args(save_arg_t *sv, char **argv) | 1572 | static 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) | 2065 | static 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 | ||
2013 | static 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 */ |
2133 | static void set_pwd_var(int exp) | 2185 | static 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 | ||
2139 | static int unset_local_var_len(const char *name, int name_len) | 2190 | static int unset_local_var_len(const char *name, int name_len) |
@@ -2191,7 +2242,7 @@ static void unset_vars(char **strings) | |||
2191 | static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) | 2242 | static 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 |
5331 | static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash) | 5436 | static 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 | */ |
5950 | static char *expand_string_to_string(const char *str, int do_unbackslash) | 6065 | static 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 */ |
5986 | static char* expand_strvec_to_string(char **argv) | 6101 | static 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). | 6642 | struct 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 */ |
6531 | static 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 | |||
6649 | static 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 | */ | ||
6685 | static 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 | ||
6579 | static void restore_redirects(int squirrel[3]) | 6716 | static 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. */ |
6597 | static int setup_redirects(struct command *prog, int squirrel[]) | 6743 | static 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 | ||
7171 | static void insert_bg_job(struct pipe *pi) | 7315 | static 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 | |||
7332 | static void delete_finished_job(struct pipe *pi) | ||
7333 | { | ||
7334 | remove_job_from_table(pi); | ||
7335 | free_pipe(pi); | ||
7336 | } | ||
7337 | |||
7338 | static 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 | |||
7344 | static 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 | |||
7203 | static 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 */ | ||
7222 | static 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 | ||
7229 | static int job_exited_or_stopped(struct pipe *pi) | 7378 | static 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) | |||
7505 | static int redirect_and_varexp_helper(char ***new_env_p, | 7669 | static 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 | ||
8810 | int msh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
8811 | int 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 |
8828 | static int run_applet_main(char **argv, int (*applet_main_func)(int argc, char **argv)) | 9006 | static 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 | 9325 | static int helper_export_local(char **argv, unsigned flags) |
9152 | #define helper_export_local(argv, exp, lvl) \ | ||
9153 | helper_export_local(argv, exp) | ||
9154 | # endif | ||
9155 | static 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 | ||
9458 | static 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 @@ | |||
1 | set -e | ||
2 | (true) | ||
3 | echo OK | ||
4 | (false) | ||
5 | echo 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 @@ | |||
1 | 2 3 4 | ||
2 | hush: Illegal number: -1 | ||
3 | 1 2 3 4 | ||
4 | 1 2 3 4 | ||
5 | 2 3 4 | ||
6 | 3 4 | ||
7 | 4 | ||
8 | |||
9 | 1 2 3 4 | ||
10 | 1 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 @@ | |||
1 | Sending 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 | |||
41 | echo 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 | ||
2 | echo _`"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 @@ | |||
1 | 0 | ||
2 | 3 | ||
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 | ||
4 | echo $? | ||
5 | wait $! | ||
6 | echo $? | ||
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 @@ | |||
1 | First | ||
2 | Second | ||
3 | Third | ||
4 | Done | ||
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 | |||
26 | echo First && sleep 0.2 && echo Third & | ||
27 | sleep 0.1 | ||
28 | echo Second | ||
29 | wait | ||
30 | echo 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 @@ | |||
1 | ok | ||
2 | ok | ||
3 | ok | ||
4 | ok | ||
5 | ok | ||
6 | ok | ||
7 | ok | ||
8 | ok | ||
9 | ok | ||
10 | ok | ||
11 | ok | ||
12 | ok | ||
13 | ok | ||
14 | ok | ||
15 | ok | ||
16 | ok | ||
17 | ok | ||
18 | ok | ||
19 | ok | ||
20 | ok | ||
21 | ok | ||
22 | ok | ||
23 | ok | ||
24 | ok | ||
25 | ok | ||
26 | ok | ||
27 | ok | ||
28 | ok | ||
29 | ok | ||
30 | ok | ||
31 | ok | ||
32 | ok | ||
33 | ok | ||
34 | ok | ||
35 | ok | ||
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 | ||
4 | case '!' in [\!] ) echo ok;; *) echo 'WRONG!';; esac | ||
5 | case '"' in [\"] ) echo ok;; *) echo 'WRONG"';; esac | ||
6 | case '#' in [\#] ) echo ok;; *) echo 'WRONG#';; esac | ||
7 | case '$' in [\$] ) echo ok;; *) echo 'WRONG$';; esac | ||
8 | case '%' in [\%] ) echo ok;; *) echo 'WRONG%';; esac | ||
9 | case '&' in [\&] ) echo ok;; *) echo 'WRONG&';; esac | ||
10 | case "'" in [\'] ) echo ok;; *) echo "WRONG'";; esac | ||
11 | case '(' in [\(] ) echo ok;; *) echo 'WRONG(';; esac | ||
12 | case ')' in [\)] ) echo ok;; *) echo 'WRONG)';; esac | ||
13 | case '*' in [\*] ) echo ok;; *) echo 'WRONG*';; esac | ||
14 | case '+' in [\+] ) echo ok;; *) echo 'WRONG+';; esac | ||
15 | case ',' in [\,] ) echo ok;; *) echo 'WRONG,';; esac | ||
16 | case '-' in [\-] ) echo ok;; *) echo 'WRONG-';; esac | ||
17 | case '-' in [a\-c]) echo ok;; *) echo 'WRONGa\-c';; esac | ||
18 | case '.' in [\.] ) echo ok;; *) echo 'WRONG.';; esac | ||
19 | case '/' in [\/] ) echo ok;; *) echo 'WRONG/';; esac | ||
20 | # 3a..40 | ||
21 | case ':' in [\:] ) echo ok;; *) echo 'WRONG:';; esac | ||
22 | case ';' in [\;] ) echo ok;; *) echo 'WRONG;';; esac | ||
23 | case '<' in [\<] ) echo ok;; *) echo 'WRONG<';; esac | ||
24 | case '=' in [\=] ) echo ok;; *) echo 'WRONG=';; esac | ||
25 | case '>' in [\>] ) echo ok;; *) echo 'WRONG>';; esac | ||
26 | case '?' in [\?] ) echo ok;; *) echo 'WRONG?';; esac | ||
27 | case '@' in [\@] ) echo ok;; *) echo 'WRONG@';; esac | ||
28 | # 5b..60 | ||
29 | case '[' in [\[] ) echo ok;; *) echo 'WRONG[';; esac | ||
30 | case '\' in [\\] ) echo ok;; *) echo 'WRONG\';; esac | ||
31 | case '\' in \\ ) echo ok;; *) echo 'WRONG\\';; esac | ||
32 | case ']' in [\]] ) echo ok;; *) echo 'WRONG]';; esac | ||
33 | case ']' in [a\]]) echo ok;; *) echo 'WRONGa]';; esac | ||
34 | case '^' in [\^] ) echo ok;; *) echo 'WRONG^';; esac | ||
35 | case '_' in [\_] ) echo ok;; *) echo 'WRONG_';; esac | ||
36 | case '`' in [\`] ) echo ok;; *) echo 'WRONG`';; esac | ||
37 | # 7b..7e | ||
38 | case '{' in [\{] ) echo ok;; *) echo 'WRONG{';; esac | ||
39 | case '|' in [\|] ) echo ok;; *) echo 'WRONG|';; esac | ||
40 | case '}' in [\}] ) echo ok;; *) echo 'WRONG}';; esac | ||
41 | case '~' 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 @@ | |||
1 | ok | ||
2 | ok | ||
3 | ok | ||
4 | ok | ||
5 | ok | ||
6 | ok | ||
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 | ||
2 | case π in | ||
3 | ( "π" ) echo ok ;; | ||
4 | ( * ) echo WRONG ;; | ||
5 | esac | ||
6 | # Unicode: cf 81 | ||
7 | case ρ in | ||
8 | ( "ρ" ) echo ok ;; | ||
9 | ( * ) echo WRONG ;; | ||
10 | esac | ||
11 | # Unicode: cf 82 | ||
12 | case ς in | ||
13 | ( "ς" ) echo ok ;; | ||
14 | ( * ) echo WRONG ;; | ||
15 | esac | ||
16 | |||
17 | case "π" in | ||
18 | ( π ) echo ok ;; | ||
19 | ( * ) echo WRONG ;; | ||
20 | esac | ||
21 | case "ρ" in | ||
22 | ( ρ ) echo ok ;; | ||
23 | ( * ) echo WRONG ;; | ||
24 | esac | ||
25 | case "ς" in | ||
26 | ( ς ) echo ok ;; | ||
27 | ( * ) echo WRONG ;; | ||
28 | esac | ||
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 @@ | |||
1 | hush: can't open '/does/not/exist': No such file or directory | 1 | TEST |
2 | One:1 | 2 | hush: can't duplicate file descriptor: Bad file descriptor |
3 | hush: can't open '/cant/be/created': No such file or directory | ||
4 | One:1 | ||
5 | Ok | ||
6 | hush: can't open '/cant/be/created': No such file or directory | ||
7 | Zero:0 | ||
8 | hush: can't open '/cant/be/created': No such file or directory | ||
9 | One:1 | ||
10 | hush: can't open '/cant/be/created': No such file or directory | ||
11 | One:1 | ||
12 | hush: can't open '/cant/be/created': No such file or directory | ||
13 | Zero:0 | ||
14 | Done | ||
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 @@ | |||
1 | echo Error >/does/not/exist; echo One:$? | 1 | # redirects to closed descriptors should not leave these descriptors |
2 | t=BAD | 2 | # open afterwards |
3 | t=Ok >>/cant/be/created; echo One:$? | 3 | echo TEST 9>/dev/null |
4 | echo $t | 4 | echo MUST ERROR OUT >&9 |
5 | ! >/cant/be/created; echo Zero:$? | 5 | echo "Output to fd#9: $?" |
6 | exec >/cant/be/created; echo One:$? | ||
7 | exec /bin/true >/cant/be/created; echo One:$? | ||
8 | ! exec /bin/true >/cant/be/created; echo Zero:$? | ||
9 | echo 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 @@ | |||
1 | hush: can't open '/does/not/exist': No such file or directory | ||
2 | One:1 | ||
3 | hush: can't open '/cant/be/created': No such file or directory | ||
4 | One:1 | ||
5 | Ok | ||
6 | hush: can't open '/cant/be/created': No such file or directory | ||
7 | Zero:0 | ||
8 | hush: can't open '/cant/be/created': No such file or directory | ||
9 | One:1 | ||
10 | hush: can't open '/cant/be/created': No such file or directory | ||
11 | One:1 | ||
12 | hush: can't open '/cant/be/created': No such file or directory | ||
13 | Zero:0 | ||
14 | Done | ||
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 @@ | |||
1 | echo Error >/does/not/exist; echo One:$? | ||
2 | t=BAD | ||
3 | t=Ok >>/cant/be/created; echo One:$? | ||
4 | echo $t | ||
5 | ! >/cant/be/created; echo Zero:$? | ||
6 | exec >/cant/be/created; echo One:$? | ||
7 | exec /bin/true >/cant/be/created; echo One:$? | ||
8 | ! exec /bin/true >/cant/be/created; echo Zero:$? | ||
9 | echo 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 | ||
2 | echo LOST >&10 | ||
3 | echo 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 @@ | |||
1 | hush: trap: BADNAME: invalid signal specification | ||
2 | 1 | ||
3 | Trapped | ||
4 | Ok | ||
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 | |||
3 | trap "echo Trapped" BADNAME TERM; echo $? | ||
4 | kill $$ | ||
5 | echo 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 @@ | |||
1 | Removing traps | ||
2 | End of exit_func | ||
3 | Done: 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 ' | ||
2 | exit_func() { | ||
3 | echo "Removing traps" | ||
4 | trap - EXIT TERM INT | ||
5 | echo "End of exit_func" | ||
6 | } | ||
7 | set -e | ||
8 | trap exit_func EXIT TERM INT | ||
9 | sleep 2 | ||
10 | exit 77 | ||
11 | ' & | ||
12 | |||
13 | sleep 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. | ||
16 | kill -TERM %1 | ||
17 | wait | ||
18 | echo 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 @@ | |||
1 | Removing traps | ||
2 | End of exit_func | ||
3 | Done: 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 ' | ||
6 | exit_func() { | ||
7 | echo "Removing traps" | ||
8 | trap - EXIT TERM INT | ||
9 | echo "End of exit_func" | ||
10 | } | ||
11 | set -e | ||
12 | trap exit_func EXIT TERM INT | ||
13 | sleep 2 | ||
14 | exit 77 | ||
15 | ' & | ||
16 | |||
17 | child=$! | ||
18 | sleep 1 | ||
19 | kill -TERM $child | ||
20 | wait | ||
21 | echo 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 @@ | |||
1 | readonly a=A | ||
2 | readonly b=B | ||
3 | Ok:0 | ||
4 | |||
5 | hush: a=A: readonly variable | ||
6 | Fail:1 | ||
7 | hush: a=A: readonly variable | ||
8 | Fail:1 | ||
9 | |||
10 | hush: a=Z: readonly variable | ||
11 | Fail:1 | ||
12 | |||
13 | hush: a=Z: readonly variable | ||
14 | b=B | ||
15 | ^^^a is not exported | ||
16 | hush: a=Z: readonly variable | ||
17 | Visible:42 | ||
18 | |||
19 | hush: a: readonly variable | ||
20 | Fail: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 @@ | |||
1 | unset a b | ||
2 | # | ||
3 | readonly a=A | ||
4 | b=B | ||
5 | readonly b | ||
6 | # readonly on already readonly var is harmless: | ||
7 | readonly b a | ||
8 | readonly | grep '^readonly [ab]=' | ||
9 | # this should work: | ||
10 | export a b | ||
11 | export -n a b | ||
12 | echo Ok:$? | ||
13 | env | grep -e^a= -e^b= # shows nothing | ||
14 | |||
15 | echo | ||
16 | # these should all fail (despite the same value being assigned) | ||
17 | # bash does not abort even in non-interactive more (in script) | ||
18 | true; a=A | ||
19 | echo Fail:$? | ||
20 | true; readonly a=A | ||
21 | echo Fail:$? | ||
22 | |||
23 | echo | ||
24 | # in bash, assignment in export fails, but export succeeds! :) | ||
25 | # we don't mimic that! | ||
26 | true; export a=Z | ||
27 | echo Fail:$? | ||
28 | #env | grep '^a=' | ||
29 | #echo "^^^a is exported" | ||
30 | export -n a # undo that bashism, if it happens | ||
31 | |||
32 | echo | ||
33 | export b | ||
34 | # this fails to both set and export a: | ||
35 | a=Z env | grep '^[ab]=' | ||
36 | echo "^^^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 | |||
40 | echo | ||
41 | true; unset a | ||
42 | echo 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 @@ | |||
1 | hush: a=Z: readonly variable | ||
2 | Visible:42 | ||
3 | |||
4 | hush: a=Z: readonly variable | ||
5 | Visible: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 @@ | |||
1 | unset a | ||
2 | readonly a=A | ||
3 | |||
4 | # external commands and builtins should behave the same: | ||
5 | (exit 42); a=Z echo "Visible:$?" | ||
6 | echo | ||
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 @@ | |||
1 | LANG=en_US.UTF-8 | ||
2 | LC_ALL=en_US.UTF-8 | ||
3 | X=abcdÉfghÍjklmnÓpqrstÚvwcyz | ||
4 | echo ${#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 @@ | |||
1 | parameter 'abcdef' | ||
2 | varoffset2 'cdef' | ||
3 | varoffset-2 'ef' | ||
4 | literal '2' 'cdef' | ||
5 | literal '-2' 'abcdef' | ||
6 | literal ' -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 @@ | |||
1 | parameter=abcdef | ||
2 | offset=2 | ||
3 | noffset=-2 | ||
4 | echo "parameter '${parameter}'" | ||
5 | echo "varoffset2 '${parameter:${offset}}'" | ||
6 | echo "varoffset-2 '${parameter:${noffset}}'" | ||
7 | echo "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 | ||
10 | echo "literal '-2' '${parameter:-2}'" | ||
11 | echo "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 @@ | |||
1 | all |0123456 | ||
2 | 4: |456 | ||
3 | 4:2 |45 | ||
4 | 4:-1 |45 | ||
5 | 4:-2 |4 | ||
6 | 4:-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 | | ||
23 | Ok: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 @@ | |||
1 | set -- 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} | ||
14 | i=2; echo "-4:i=2 |"${1: -4:i} | ||
15 | i=-2; echo "-4:i=-2|"${1: -4:i} | ||
16 | i=-3; echo "-4:i=-3|"${1: -4:i} | ||
17 | i=-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} | ||
24 | echo 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 | |||
9 | unset LC_TIME | 9 | unset LC_TIME |
10 | unset LC_ALL | 10 | unset LC_ALL |
11 | 11 | ||
12 | TOPDIR=`pwd` | ||
13 | |||
12 | if test ! -x hush; then | 14 | if 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 |
97 | else | 102 | else |
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) { |