diff options
author | Nguyễn Thái Ngọc Duy <pclouds@gmail.com> | 2010-09-14 11:41:16 +1000 |
---|---|---|
committer | Nguyễn Thái Ngọc Duy <pclouds@gmail.com> | 2010-09-14 11:41:16 +1000 |
commit | 743d85e7d1c2a721baf020b9d79f45f0df2420a9 (patch) | |
tree | 9e551b726ac2bf9fd8eafb2764e7237f352bb429 /shell | |
parent | 87911f4fcd86f7abc0aefba8e9cbec2ac9d127e4 (diff) | |
parent | c7f95d23f6bc7e17a3b79decf83eb362b389e53a (diff) | |
download | busybox-w32-743d85e7d1c2a721baf020b9d79f45f0df2420a9.tar.gz busybox-w32-743d85e7d1c2a721baf020b9d79f45f0df2420a9.tar.bz2 busybox-w32-743d85e7d1c2a721baf020b9d79f45f0df2420a9.zip |
Merge branch 'origin/master' (early part)
Diffstat (limited to 'shell')
-rw-r--r-- | shell/Config.in | 15 | ||||
-rw-r--r-- | shell/README | 190 | ||||
-rw-r--r-- | shell/ash.c | 111 | ||||
-rw-r--r-- | shell/ash_test/ash-misc/source2.right | 1 | ||||
-rwxr-xr-x | shell/ash_test/ash-misc/source2.tests | 3 | ||||
-rwxr-xr-x | shell/ash_test/ash-redir/redir3.tests | 2 | ||||
-rwxr-xr-x | shell/ash_test/ash-redir/redir7.tests | 2 | ||||
-rwxr-xr-x | shell/ash_test/ash-redir/redir8.tests | 2 | ||||
-rw-r--r-- | shell/ash_test/ash-signals/signal5.right | 12 | ||||
-rwxr-xr-x | shell/ash_test/ash-signals/signal5.tests | 14 | ||||
-rw-r--r-- | shell/ash_test/ash-signals/signal6.right | 2 | ||||
-rwxr-xr-x | shell/ash_test/ash-signals/signal6.tests | 2 | ||||
-rw-r--r-- | shell/ash_test/ash-vars/var_leak.right | 3 | ||||
-rwxr-xr-x | shell/ash_test/ash-vars/var_leak.tests | 19 | ||||
-rw-r--r-- | shell/cttyhack.c | 43 | ||||
-rw-r--r-- | shell/hush.c | 58 | ||||
-rw-r--r-- | shell/hush_test/hush-parsing/group2.right | 2 | ||||
-rwxr-xr-x | shell/hush_test/hush-parsing/group2.tests | 3 |
18 files changed, 285 insertions, 199 deletions
diff --git a/shell/Config.in b/shell/Config.in index 3b1650615..cf599dff4 100644 --- a/shell/Config.in +++ b/shell/Config.in | |||
@@ -354,9 +354,20 @@ config CTTYHACK | |||
354 | It analyzes stdin with various ioctls, trying to determine whether | 354 | It analyzes stdin with various ioctls, trying to determine whether |
355 | it is a /dev/ttyN or /dev/ttySN (virtual terminal or serial line). | 355 | it is a /dev/ttyN or /dev/ttySN (virtual terminal or serial line). |
356 | If it detects one, it closes stdin/out/err and reopens that device. | 356 | If it detects one, it closes stdin/out/err and reopens that device. |
357 | Then it executes given program. Usage example for /etc/inittab | 357 | Then it executes given program. Opening the device will make |
358 | (for busybox init): | 358 | that device a controlling tty. This may require cttyhack |
359 | to be a session leader. | ||
360 | |||
361 | Example for /etc/inittab (for busybox init): | ||
359 | 362 | ||
360 | ::respawn:/bin/cttyhack /bin/sh | 363 | ::respawn:/bin/cttyhack /bin/sh |
361 | 364 | ||
365 | Giving controlling tty to shell running with PID 1: | ||
366 | |||
367 | $ exec cttyhack sh | ||
368 | |||
369 | Starting an interactive shell from boot shell script: | ||
370 | |||
371 | setsid cttyhack sh | ||
372 | |||
362 | endmenu | 373 | endmenu |
diff --git a/shell/README b/shell/README index 59efe499f..550c712d3 100644 --- a/shell/README +++ b/shell/README | |||
@@ -1,108 +1,82 @@ | |||
1 | Various bits of what is known about busybox shells, in no particular order. | 1 | http://www.opengroup.org/onlinepubs/9699919799/ |
2 | 2 | Open Group Base Specifications Issue 7 | |
3 | 2008-02-14 | 3 | |
4 | ash: does not restore tty pgrp if killed by HUP. Symptom: Midnight Commander | 4 | |
5 | is backgrounded if you started ash under it, and then killed it with HUP. | 5 | http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap01.html |
6 | 6 | Shell & Utilities | |
7 | 2007-11-23 | 7 | |
8 | hush: fixed bogus glob handling; fixed exec <"$1"; added test and echo builtins | 8 | It says that any of the standard utilities may be implemented |
9 | 9 | as a regular shell built-in. It gives a list of utilities which | |
10 | 2007-06-13 | 10 | are usually implemented that way (and some of them can only |
11 | hush: exec <"$1" doesn't do parameter subst | 11 | be implemented as built-ins, like "alias"): |
12 | 12 | ||
13 | 2007-05-24 | 13 | alias |
14 | hush: environment-related memory leak plugged, with net code size | 14 | bg |
15 | decrease. | 15 | cd |
16 | 16 | command | |
17 | 2007-05-24 | 17 | false |
18 | hush: '( echo ${name )' will show syntax error message, but prompt | 18 | fc |
19 | doesn't return (need to press <enter>). Pressing Ctrl-C, <enter>, | 19 | fg |
20 | '( echo ${name )' again, Ctrl-C segfaults. | 20 | getopts |
21 | 21 | jobs | |
22 | 2007-05-21 | 22 | kill |
23 | hush: environment cannot be handled by libc routines as they are leaky | 23 | newgrp |
24 | (by API design and thus unfixable): hush will leak memory in this script, | 24 | pwd |
25 | bash does not: | 25 | read |
26 | pid=$$ | 26 | true |
27 | while true; do | 27 | umask |
28 | unset t; | 28 | unalias |
29 | t=111111111111111111111111111111111111111111111111111111111111111111111111 | 29 | wait |
30 | export t | 30 | |
31 | ps -o vsz,pid,comm | grep " $pid " | 31 | |
32 | done | 32 | http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html |
33 | The fix is to not use setenv/putenv/unsetenv but manipulate env ourself. TODO. | 33 | Shell Command Language |
34 | hush: meanwhile, first three command subst bugs mentioned below are fixed. :) | 34 | |
35 | 35 | It says that shell must implement special built-ins. Special built-ins | |
36 | 2007-05-06 | 36 | differ from regular ones by the fact that variable assignments |
37 | hush: more bugs spotted. Comparison with bash: | 37 | done on special builtin is *PRESERVED*. That is, |
38 | bash-3.2# echo "TEST`date;echo;echo`BEST" | 38 | |
39 | TESTSun May 6 09:21:05 CEST 2007BEST [we dont strip eols] | 39 | VAR=VAL special_builtin; echo $VAR |
40 | bash-3.2# echo "TEST`echo '$(echo ZZ)'`BEST" | 40 | |
41 | TEST$(echo ZZ)BEST [we execute inner echo] | 41 | should print VAL. |
42 | bash-3.2# echo "TEST`echo "'"`BEST" | 42 | |
43 | TEST'BEST [we totally mess up this one] | 43 | (Another distinction is that an error in special built-in should |
44 | bash-3.2# echo `sleep 5` | 44 | abort the shell, but this is not such a critical difference, |
45 | [Ctrl-C should work, Ctrl-Z should do nothing][we totally mess up this one] | 45 | and moreover, at least bash's "set" does not follow this rule, |
46 | bash-3.2# if true; then | 46 | which is even codified in autoconf now...). |
47 | > [Ctrl-C] | 47 | |
48 | bash-3.2# [we re-issue "> "] | 48 | List of special builtins: |
49 | bash-3.2# if echo `sleep 5`; then | 49 | |
50 | > true; fi [we execute sleep before "> "] | 50 | . file |
51 | 51 | : [argument...] | |
52 | 2007-05-04 | 52 | break [n] |
53 | hush: made ctrl-Z/C work correctly for "while true; do true; done" | 53 | continue [n] |
54 | (namely, it backgrounds/interrupts entire "while") | 54 | eval [argument...] |
55 | 55 | exec [command [argument...]] | |
56 | 2007-05-03 | 56 | exit [n] |
57 | hush: new bug spotted: Ctrl-C on "while true; do true; done" doesn't | 57 | export name[=word]... |
58 | work right: | 58 | export -p |
59 | # while true; do true; done | 59 | readonly name[=word]... |
60 | [1] 0 true <-- pressing Ctrl-C several times... | 60 | readonly -p |
61 | [2] 0 true | 61 | return [n] |
62 | [3] 0 true | 62 | set [-abCefhmnuvx] [-o option] [argument...] |
63 | Segmentation fault | 63 | set [+abCefhmnuvx] [+o option] [argument...] |
64 | 64 | set -- [argument...] | |
65 | 2007-05-03 | 65 | set -o |
66 | hush: update on "sleep 1 | exit 3; echo $?" bug. | 66 | set +o |
67 | parse_stream_outer() repeatedly calls parse_stream(). | 67 | shift [n] |
68 | parse_stream() is now fixed to stop on ';' in this example, | 68 | times |
69 | fixing it (parse_stream_outer() will call parse_stream() 1st time, | 69 | trap n [condition...] |
70 | execute the parse tree, call parse_stream() 2nd time and execute the tree). | 70 | trap [action condition...] |
71 | But it's not the end of story. | 71 | unset [-fv] name... |
72 | In more complex situations we _must_ parse way farther before executing. | 72 | |
73 | Example #2: "{ sleep 1 | exit 3; echo $?; ...few_lines... } >file". | 73 | In practice, no one uses this obscure feature - none of these builtins |
74 | Because of redirection, we cannot execute 1st pipe before we parse it all. | 74 | gives any special reasons to play such dirty tricks. |
75 | We probably need to learn to store $var expressions in parse tree. | 75 | |
76 | Debug printing of parse tree would be nice too. | 76 | However. This section says that *function invocation* should act |
77 | 77 | similar to special built-in. That is, variable assignments | |
78 | 2007-04-28 | 78 | done on function invocation should be preserved after function invocation. |
79 | hush: Ctrl-C and Ctrl-Z for single NOFORK commands are working. | 79 | |
80 | Memory and other resource leaks (opendir) are not addressed | 80 | This is significant: it is not unthinkable to want to run a function |
81 | (testcase is "rm -i" interrupted by ctrl-c). | 81 | with some variables set to special values. But because of the above, |
82 | 82 | it does not work: variable will "leak" out of the function. | |
83 | 2007-04-21 | ||
84 | hush: "sleep 5 | sleep 6" + Ctrl-Z + fg seems to work. | ||
85 | "rm -i" + Ctrl-C, "sleep 5" + Ctrl-Z still doesn't work | ||
86 | for SH_STANDALONE case :( | ||
87 | |||
88 | 2007-04-21 | ||
89 | hush: fixed non-backgrounding of "sleep 1 &" and totally broken | ||
90 | "sleep 1 | sleep 2 &". Noticed a bug where successive jobs | ||
91 | get numbers 1,2,3 even when job #1 has exited before job# 2 is started. | ||
92 | (bash reuses #1 in this case) | ||
93 | |||
94 | 2007-04-21 | ||
95 | hush: "sleep 1 | exit 3; echo $?" prints 0 because $? is substituted | ||
96 | _before_ pipe gets executed!! run_list_real() already has "pipe;echo" | ||
97 | parsed and handed to it for execution, so it sees "pipe"; "echo 0". | ||
98 | |||
99 | 2007-04-21 | ||
100 | hush: removed setsid() and made job control sort-of-sometimes-work. | ||
101 | Ctrl-C in "rm -i" works now except for SH_STANDALONE case. | ||
102 | "sleep 1 | exit 3" + "echo $?" works, "sleep 1 | exit 3; echo $?" | ||
103 | shows exitcode 0 (should be 3). "sleep 1 | sleep 2 &" fails horribly. | ||
104 | |||
105 | 2007-04-14 | ||
106 | lash, hush: both do setsid() and as a result don't have ctty! | ||
107 | Ctrl-C doesn't work for any child (try rm -i), etc... | ||
108 | lash: bare ">file" doesn't create a file (hush works) | ||
diff --git a/shell/ash.c b/shell/ash.c index 5d8415a79..16a331bb0 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
@@ -257,6 +257,7 @@ struct globals_misc { | |||
257 | 257 | ||
258 | /* indicates specified signal received */ | 258 | /* indicates specified signal received */ |
259 | uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */ | 259 | uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */ |
260 | uint8_t may_have_traps; /* 0: definitely no traps are set, 1: some traps may be set */ | ||
260 | char *trap[NSIG]; | 261 | char *trap[NSIG]; |
261 | char **trap_ptr; /* used only by "trap hack" */ | 262 | char **trap_ptr; /* used only by "trap hack" */ |
262 | 263 | ||
@@ -285,6 +286,7 @@ extern struct globals_misc *const ash_ptr_to_globals_misc; | |||
285 | #define optlist (G_misc.optlist ) | 286 | #define optlist (G_misc.optlist ) |
286 | #define sigmode (G_misc.sigmode ) | 287 | #define sigmode (G_misc.sigmode ) |
287 | #define gotsig (G_misc.gotsig ) | 288 | #define gotsig (G_misc.gotsig ) |
289 | #define may_have_traps (G_misc.may_have_traps ) | ||
288 | #define trap (G_misc.trap ) | 290 | #define trap (G_misc.trap ) |
289 | #define trap_ptr (G_misc.trap_ptr ) | 291 | #define trap_ptr (G_misc.trap_ptr ) |
290 | #define random_gen (G_misc.random_gen ) | 292 | #define random_gen (G_misc.random_gen ) |
@@ -382,7 +384,7 @@ raise_interrupt(void) | |||
382 | /* Signal is not automatically unmasked after it is raised, | 384 | /* Signal is not automatically unmasked after it is raised, |
383 | * do it ourself - unmask all signals */ | 385 | * do it ourself - unmask all signals */ |
384 | sigprocmask_allsigs(SIG_UNBLOCK); | 386 | sigprocmask_allsigs(SIG_UNBLOCK); |
385 | /* pending_sig = 0; - now done in onsig() */ | 387 | /* pending_sig = 0; - now done in signal_handler() */ |
386 | 388 | ||
387 | ex_type = EXSIG; | 389 | ex_type = EXSIG; |
388 | if (gotsig[SIGINT - 1] && !trap[SIGINT]) { | 390 | if (gotsig[SIGINT - 1] && !trap[SIGINT]) { |
@@ -2743,9 +2745,7 @@ pwdcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) | |||
2743 | /* ============ ... */ | 2745 | /* ============ ... */ |
2744 | 2746 | ||
2745 | 2747 | ||
2746 | #define IBUFSIZ COMMON_BUFSIZE | 2748 | #define IBUFSIZ (ENABLE_FEATURE_EDITING ? CONFIG_FEATURE_EDITING_MAX_LEN : 1024) |
2747 | /* buffer for top level input file */ | ||
2748 | #define basebuf bb_common_bufsiz1 | ||
2749 | 2749 | ||
2750 | /* Syntax classes */ | 2750 | /* Syntax classes */ |
2751 | #define CWORD 0 /* character is nothing special */ | 2751 | #define CWORD 0 /* character is nothing special */ |
@@ -3424,10 +3424,10 @@ ignoresig(int signo) | |||
3424 | } | 3424 | } |
3425 | 3425 | ||
3426 | /* | 3426 | /* |
3427 | * Signal handler. Only one usage site - in setsignal() | 3427 | * Only one usage site - in setsignal() |
3428 | */ | 3428 | */ |
3429 | static void | 3429 | static void |
3430 | onsig(int signo) | 3430 | signal_handler(int signo) |
3431 | { | 3431 | { |
3432 | gotsig[signo - 1] = 1; | 3432 | gotsig[signo - 1] = 1; |
3433 | 3433 | ||
@@ -3524,7 +3524,7 @@ setsignal(int signo) | |||
3524 | act.sa_handler = SIG_DFL; | 3524 | act.sa_handler = SIG_DFL; |
3525 | switch (new_act) { | 3525 | switch (new_act) { |
3526 | case S_CATCH: | 3526 | case S_CATCH: |
3527 | act.sa_handler = onsig; | 3527 | act.sa_handler = signal_handler; |
3528 | act.sa_flags = 0; /* matters only if !DFL and !IGN */ | 3528 | act.sa_flags = 0; /* matters only if !DFL and !IGN */ |
3529 | sigfillset(&act.sa_mask); /* ditto */ | 3529 | sigfillset(&act.sa_mask); /* ditto */ |
3530 | break; | 3530 | break; |
@@ -4083,9 +4083,9 @@ dowait(int wait_flags, struct job *job) | |||
4083 | } | 4083 | } |
4084 | 4084 | ||
4085 | static int | 4085 | static int |
4086 | blocking_wait_with_raise_on_sig(struct job *job) | 4086 | blocking_wait_with_raise_on_sig(void) |
4087 | { | 4087 | { |
4088 | pid_t pid = dowait(DOWAIT_BLOCK, job); | 4088 | pid_t pid = dowait(DOWAIT_BLOCK, NULL); |
4089 | if (pid <= 0 && pending_sig) | 4089 | if (pid <= 0 && pending_sig) |
4090 | raise_exception(EXSIG); | 4090 | raise_exception(EXSIG); |
4091 | return pid; | 4091 | return pid; |
@@ -4281,14 +4281,21 @@ waitcmd(int argc UNUSED_PARAM, char **argv) | |||
4281 | jp->waited = 1; | 4281 | jp->waited = 1; |
4282 | jp = jp->prev_job; | 4282 | jp = jp->prev_job; |
4283 | } | 4283 | } |
4284 | blocking_wait_with_raise_on_sig(); | ||
4284 | /* man bash: | 4285 | /* man bash: |
4285 | * "When bash is waiting for an asynchronous command via | 4286 | * "When bash is waiting for an asynchronous command via |
4286 | * the wait builtin, the reception of a signal for which a trap | 4287 | * the wait builtin, the reception of a signal for which a trap |
4287 | * has been set will cause the wait builtin to return immediately | 4288 | * has been set will cause the wait builtin to return immediately |
4288 | * with an exit status greater than 128, immediately after which | 4289 | * with an exit status greater than 128, immediately after which |
4289 | * the trap is executed." | 4290 | * the trap is executed." |
4290 | * Do we do it that way? */ | 4291 | * |
4291 | blocking_wait_with_raise_on_sig(NULL); | 4292 | * blocking_wait_with_raise_on_sig raises signal handlers |
4293 | * if it gets no pid (pid < 0). However, | ||
4294 | * if child sends us a signal *and immediately exits*, | ||
4295 | * blocking_wait_with_raise_on_sig gets pid > 0 | ||
4296 | * and does not handle pending_sig. Check this case: */ | ||
4297 | if (pending_sig) | ||
4298 | raise_exception(EXSIG); | ||
4292 | } | 4299 | } |
4293 | } | 4300 | } |
4294 | 4301 | ||
@@ -4308,7 +4315,7 @@ waitcmd(int argc UNUSED_PARAM, char **argv) | |||
4308 | job = getjob(*argv, 0); | 4315 | job = getjob(*argv, 0); |
4309 | /* loop until process terminated or stopped */ | 4316 | /* loop until process terminated or stopped */ |
4310 | while (job->state == JOBRUNNING) | 4317 | while (job->state == JOBRUNNING) |
4311 | blocking_wait_with_raise_on_sig(NULL); | 4318 | blocking_wait_with_raise_on_sig(); |
4312 | job->waited = 1; | 4319 | job->waited = 1; |
4313 | retval = getstatus(job); | 4320 | retval = getstatus(job); |
4314 | repeat: ; | 4321 | repeat: ; |
@@ -5715,7 +5722,11 @@ rmescapes(char *str, int flag) | |||
5715 | size_t fulllen = len + strlen(p) + 1; | 5722 | size_t fulllen = len + strlen(p) + 1; |
5716 | 5723 | ||
5717 | if (flag & RMESCAPE_GROW) { | 5724 | if (flag & RMESCAPE_GROW) { |
5725 | int strloc = str - (char *)stackblock(); | ||
5718 | r = makestrspace(fulllen, expdest); | 5726 | r = makestrspace(fulllen, expdest); |
5727 | /* p and str may be invalidated by makestrspace */ | ||
5728 | str = (char *)stackblock() + strloc; | ||
5729 | p = str + len; | ||
5719 | } else if (flag & RMESCAPE_HEAP) { | 5730 | } else if (flag & RMESCAPE_HEAP) { |
5720 | r = ckmalloc(fulllen); | 5731 | r = ckmalloc(fulllen); |
5721 | } else { | 5732 | } else { |
@@ -8819,7 +8830,7 @@ evalsubshell(union node *n, int flags) | |||
8819 | int status; | 8830 | int status; |
8820 | 8831 | ||
8821 | expredir(n->nredir.redirect); | 8832 | expredir(n->nredir.redirect); |
8822 | if (!backgnd && flags & EV_EXIT && !trap[0]) | 8833 | if (!backgnd && (flags & EV_EXIT) && !may_have_traps) |
8823 | goto nofork; | 8834 | goto nofork; |
8824 | INT_OFF; | 8835 | INT_OFF; |
8825 | jp = makejob(/*n,*/ 1); | 8836 | jp = makejob(/*n,*/ 1); |
@@ -8832,10 +8843,11 @@ evalsubshell(union node *n, int flags) | |||
8832 | ash_msg_and_raise_error("unable to spawn shell"); | 8843 | ash_msg_and_raise_error("unable to spawn shell"); |
8833 | #endif | 8844 | #endif |
8834 | if (forkshell(jp, n, backgnd) == 0) { | 8845 | if (forkshell(jp, n, backgnd) == 0) { |
8846 | /* child */ | ||
8835 | INT_ON; | 8847 | INT_ON; |
8836 | flags |= EV_EXIT; | 8848 | flags |= EV_EXIT; |
8837 | if (backgnd) | 8849 | if (backgnd) |
8838 | flags &=~ EV_TESTED; | 8850 | flags &= ~EV_TESTED; |
8839 | nofork: | 8851 | nofork: |
8840 | redirect(n->nredir.redirect, 0); | 8852 | redirect(n->nredir.redirect, 0); |
8841 | evaltreenr(n->nredir.n, flags); | 8853 | evaltreenr(n->nredir.n, flags); |
@@ -9365,7 +9377,9 @@ static const struct builtincmd builtintab[] = { | |||
9365 | { BUILTIN_SPEC_REG "return" , returncmd }, | 9377 | { BUILTIN_SPEC_REG "return" , returncmd }, |
9366 | { BUILTIN_SPEC_REG "set" , setcmd }, | 9378 | { BUILTIN_SPEC_REG "set" , setcmd }, |
9367 | { BUILTIN_SPEC_REG "shift" , shiftcmd }, | 9379 | { BUILTIN_SPEC_REG "shift" , shiftcmd }, |
9380 | #if ENABLE_ASH_BASH_COMPAT | ||
9368 | { BUILTIN_SPEC_REG "source" , dotcmd }, | 9381 | { BUILTIN_SPEC_REG "source" , dotcmd }, |
9382 | #endif | ||
9369 | #if ENABLE_ASH_BUILTIN_TEST | 9383 | #if ENABLE_ASH_BUILTIN_TEST |
9370 | { BUILTIN_REGULAR "test" , testcmd }, | 9384 | { BUILTIN_REGULAR "test" , testcmd }, |
9371 | #endif | 9385 | #endif |
@@ -9646,16 +9660,19 @@ evalcommand(union node *cmd, int flags) | |||
9646 | /* goes through to shellexec() */ | 9660 | /* goes through to shellexec() */ |
9647 | #endif | 9661 | #endif |
9648 | /* Fork off a child process if necessary. */ | 9662 | /* Fork off a child process if necessary. */ |
9649 | if (!(flags & EV_EXIT) || trap[0]) { | 9663 | if (!(flags & EV_EXIT) || may_have_traps) { |
9650 | INT_OFF; | 9664 | INT_OFF; |
9651 | jp = makejob(/*cmd,*/ 1); | 9665 | jp = makejob(/*cmd,*/ 1); |
9652 | if (forkshell(jp, cmd, FORK_FG) != 0) { | 9666 | if (forkshell(jp, cmd, FORK_FG) != 0) { |
9667 | /* parent */ | ||
9653 | exitstatus = waitforjob(jp); | 9668 | exitstatus = waitforjob(jp); |
9654 | INT_ON; | 9669 | INT_ON; |
9655 | TRACE(("forked child exited with %d\n", exitstatus)); | 9670 | TRACE(("forked child exited with %d\n", exitstatus)); |
9656 | break; | 9671 | break; |
9657 | } | 9672 | } |
9673 | /* child */ | ||
9658 | FORCE_INT_ON; | 9674 | FORCE_INT_ON; |
9675 | /* fall through to exec'ing external program */ | ||
9659 | } | 9676 | } |
9660 | listsetvar(varlist.list, VEXPORT|VSTACK); | 9677 | listsetvar(varlist.list, VEXPORT|VSTACK); |
9661 | shellexec(argv, path, cmdentry.u.index); | 9678 | shellexec(argv, path, cmdentry.u.index); |
@@ -9900,12 +9917,12 @@ preadfd(void) | |||
9900 | #if ENABLE_FEATURE_EDITING | 9917 | #if ENABLE_FEATURE_EDITING |
9901 | retry: | 9918 | retry: |
9902 | if (!iflag || g_parsefile->fd != STDIN_FILENO) | 9919 | if (!iflag || g_parsefile->fd != STDIN_FILENO) |
9903 | nr = nonblock_safe_read(g_parsefile->fd, buf, BUFSIZ - 1); | 9920 | nr = nonblock_safe_read(g_parsefile->fd, buf, IBUFSIZ - 1); |
9904 | else { | 9921 | else { |
9905 | #if ENABLE_FEATURE_TAB_COMPLETION | 9922 | #if ENABLE_FEATURE_TAB_COMPLETION |
9906 | line_input_state->path_lookup = pathval(); | 9923 | line_input_state->path_lookup = pathval(); |
9907 | #endif | 9924 | #endif |
9908 | nr = read_line_input(cmdedit_prompt, buf, BUFSIZ, line_input_state); | 9925 | nr = read_line_input(cmdedit_prompt, buf, IBUFSIZ, line_input_state); |
9909 | if (nr == 0) { | 9926 | if (nr == 0) { |
9910 | /* Ctrl+C pressed */ | 9927 | /* Ctrl+C pressed */ |
9911 | if (trap[SIGINT]) { | 9928 | if (trap[SIGINT]) { |
@@ -9922,7 +9939,7 @@ preadfd(void) | |||
9922 | } | 9939 | } |
9923 | } | 9940 | } |
9924 | #else | 9941 | #else |
9925 | nr = nonblock_safe_read(g_parsefile->fd, buf, BUFSIZ - 1); | 9942 | nr = nonblock_safe_read(g_parsefile->fd, buf, IBUFSIZ - 1); |
9926 | #endif | 9943 | #endif |
9927 | 9944 | ||
9928 | #if 0 | 9945 | #if 0 |
@@ -12484,36 +12501,43 @@ find_dot_file(char *name) | |||
12484 | static int FAST_FUNC | 12501 | static int FAST_FUNC |
12485 | dotcmd(int argc, char **argv) | 12502 | dotcmd(int argc, char **argv) |
12486 | { | 12503 | { |
12504 | char *fullname; | ||
12487 | struct strlist *sp; | 12505 | struct strlist *sp; |
12488 | volatile struct shparam saveparam; | 12506 | volatile struct shparam saveparam; |
12489 | int status = 0; | ||
12490 | 12507 | ||
12491 | for (sp = cmdenviron; sp; sp = sp->next) | 12508 | for (sp = cmdenviron; sp; sp = sp->next) |
12492 | setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED); | 12509 | setvareq(ckstrdup(sp->text), VSTRFIXED | VTEXTFIXED); |
12493 | 12510 | ||
12494 | if (argv[1]) { /* That's what SVR2 does */ | 12511 | if (!argv[1]) { |
12495 | char *fullname = find_dot_file(argv[1]); | 12512 | /* bash says: "bash: .: filename argument required" */ |
12496 | argv += 2; | 12513 | return 2; /* bash compat */ |
12497 | argc -= 2; | ||
12498 | if (argc) { /* argc > 0, argv[0] != NULL */ | ||
12499 | saveparam = shellparam; | ||
12500 | shellparam.malloced = 0; | ||
12501 | shellparam.nparam = argc; | ||
12502 | shellparam.p = argv; | ||
12503 | }; | ||
12504 | |||
12505 | setinputfile(fullname, INPUT_PUSH_FILE); | ||
12506 | commandname = fullname; | ||
12507 | cmdloop(0); | ||
12508 | popfile(); | ||
12509 | |||
12510 | if (argc) { | ||
12511 | freeparam(&shellparam); | ||
12512 | shellparam = saveparam; | ||
12513 | }; | ||
12514 | status = exitstatus; | ||
12515 | } | 12514 | } |
12516 | return status; | 12515 | |
12516 | /* "false; . empty_file; echo $?" should print 0, not 1: */ | ||
12517 | exitstatus = 0; | ||
12518 | |||
12519 | fullname = find_dot_file(argv[1]); | ||
12520 | |||
12521 | argv += 2; | ||
12522 | argc -= 2; | ||
12523 | if (argc) { /* argc > 0, argv[0] != NULL */ | ||
12524 | saveparam = shellparam; | ||
12525 | shellparam.malloced = 0; | ||
12526 | shellparam.nparam = argc; | ||
12527 | shellparam.p = argv; | ||
12528 | }; | ||
12529 | |||
12530 | setinputfile(fullname, INPUT_PUSH_FILE); | ||
12531 | commandname = fullname; | ||
12532 | cmdloop(0); | ||
12533 | popfile(); | ||
12534 | |||
12535 | if (argc) { | ||
12536 | freeparam(&shellparam); | ||
12537 | shellparam = saveparam; | ||
12538 | }; | ||
12539 | |||
12540 | return exitstatus; | ||
12517 | } | 12541 | } |
12518 | 12542 | ||
12519 | static int FAST_FUNC | 12543 | static int FAST_FUNC |
@@ -12824,6 +12848,8 @@ trapcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) | |||
12824 | action = ckstrdup(action); | 12848 | action = ckstrdup(action); |
12825 | } | 12849 | } |
12826 | free(trap[signo]); | 12850 | free(trap[signo]); |
12851 | if (action) | ||
12852 | may_have_traps = 1; | ||
12827 | trap[signo] = action; | 12853 | trap[signo] = action; |
12828 | if (signo != 0) | 12854 | if (signo != 0) |
12829 | setsignal(signo); | 12855 | setsignal(signo); |
@@ -13219,7 +13245,8 @@ static void | |||
13219 | init(void) | 13245 | init(void) |
13220 | { | 13246 | { |
13221 | /* from input.c: */ | 13247 | /* from input.c: */ |
13222 | basepf.next_to_pgetc = basepf.buf = basebuf; | 13248 | /* we will never free this */ |
13249 | basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); | ||
13223 | 13250 | ||
13224 | /* from trap.c: */ | 13251 | /* from trap.c: */ |
13225 | signal(SIGCHLD, SIG_DFL); | 13252 | signal(SIGCHLD, SIG_DFL); |
diff --git a/shell/ash_test/ash-misc/source2.right b/shell/ash_test/ash-misc/source2.right new file mode 100644 index 000000000..ce7171c87 --- /dev/null +++ b/shell/ash_test/ash-misc/source2.right | |||
@@ -0,0 +1 @@ | |||
Done: 0 | |||
diff --git a/shell/ash_test/ash-misc/source2.tests b/shell/ash_test/ash-misc/source2.tests new file mode 100755 index 000000000..1870cdf7e --- /dev/null +++ b/shell/ash_test/ash-misc/source2.tests | |||
@@ -0,0 +1,3 @@ | |||
1 | false | ||
2 | . /dev/null | ||
3 | echo Done: $? | ||
diff --git a/shell/ash_test/ash-redir/redir3.tests b/shell/ash_test/ash-redir/redir3.tests index f50a7674c..e37d5e45a 100755 --- a/shell/ash_test/ash-redir/redir3.tests +++ b/shell/ash_test/ash-redir/redir3.tests | |||
@@ -1,4 +1,4 @@ | |||
1 | # redirects to closed descriptors should not leave these descriptors" | 1 | # redirects to closed descriptors should not leave these descriptors |
2 | # open afterwards | 2 | # open afterwards |
3 | echo TEST 9>/dev/null | 3 | echo TEST 9>/dev/null |
4 | echo MUST ERROR OUT >&9 | 4 | echo MUST ERROR OUT >&9 |
diff --git a/shell/ash_test/ash-redir/redir7.tests b/shell/ash_test/ash-redir/redir7.tests index 17d1040e0..ca3979a81 100755 --- a/shell/ash_test/ash-redir/redir7.tests +++ b/shell/ash_test/ash-redir/redir7.tests | |||
@@ -7,6 +7,6 @@ | |||
7 | echo -e 'echo Ok >uni\x81code' >>unicode.sh | 7 | echo -e 'echo Ok >uni\x81code' >>unicode.sh |
8 | echo -e 'cat uni\x81code' >>unicode.sh | 8 | echo -e 'cat uni\x81code' >>unicode.sh |
9 | echo -e 'cat uni?code' >>unicode.sh | 9 | echo -e 'cat uni?code' >>unicode.sh |
10 | . unicode.sh | 10 | . ./unicode.sh |
11 | rm uni*code* | 11 | rm uni*code* |
12 | echo Done | 12 | echo Done |
diff --git a/shell/ash_test/ash-redir/redir8.tests b/shell/ash_test/ash-redir/redir8.tests index 32ab607b8..8cb42c0d3 100755 --- a/shell/ash_test/ash-redir/redir8.tests +++ b/shell/ash_test/ash-redir/redir8.tests | |||
@@ -10,6 +10,6 @@ echo -e 'v=uni\x81code' >>unicode.sh | |||
10 | echo -e 'echo Ok >"$v"' >>unicode.sh | 10 | echo -e 'echo Ok >"$v"' >>unicode.sh |
11 | echo -e 'cat uni\x81code' >>unicode.sh | 11 | echo -e 'cat uni\x81code' >>unicode.sh |
12 | echo -e 'cat uni?code' >>unicode.sh | 12 | echo -e 'cat uni?code' >>unicode.sh |
13 | . unicode.sh | 13 | . ./unicode.sh |
14 | rm uni*code* | 14 | rm uni*code* |
15 | echo Done | 15 | echo Done |
diff --git a/shell/ash_test/ash-signals/signal5.right b/shell/ash_test/ash-signals/signal5.right new file mode 100644 index 000000000..162f56bbc --- /dev/null +++ b/shell/ash_test/ash-signals/signal5.right | |||
@@ -0,0 +1,12 @@ | |||
1 | sleeping for 3 sec | ||
2 | sleeping for 2 sec | ||
3 | Waiting | ||
4 | 2 sec passed, sending USR1 to parent | ||
5 | USR1 received | ||
6 | Wait exit code: 138 | ||
7 | Waiting | ||
8 | 3 sec passed, sending USR1 to parent | ||
9 | USR1 received | ||
10 | Wait exit code: 138 | ||
11 | Waiting | ||
12 | Wait returned 0 | ||
diff --git a/shell/ash_test/ash-signals/signal5.tests b/shell/ash_test/ash-signals/signal5.tests new file mode 100755 index 000000000..371120e95 --- /dev/null +++ b/shell/ash_test/ash-signals/signal5.tests | |||
@@ -0,0 +1,14 @@ | |||
1 | trap "echo USR1 received" USR1 | ||
2 | stub() { | ||
3 | echo "sleeping for $1 sec" | ||
4 | sleep $1 | ||
5 | echo "$1 sec passed, sending USR1 to parent" | ||
6 | kill -USR1 $$ | ||
7 | } | ||
8 | stub 3 & | ||
9 | stub 2 & | ||
10 | sleep 1 | ||
11 | until { echo "Waiting"; wait; } do | ||
12 | echo "Wait exit code: $?" | ||
13 | done | ||
14 | echo "Wait returned 0" | ||
diff --git a/shell/ash_test/ash-signals/signal6.right b/shell/ash_test/ash-signals/signal6.right new file mode 100644 index 000000000..df4d9306a --- /dev/null +++ b/shell/ash_test/ash-signals/signal6.right | |||
@@ -0,0 +1,2 @@ | |||
1 | got TERM | ||
2 | Done: 0 | ||
diff --git a/shell/ash_test/ash-signals/signal6.tests b/shell/ash_test/ash-signals/signal6.tests new file mode 100755 index 000000000..3ce151060 --- /dev/null +++ b/shell/ash_test/ash-signals/signal6.tests | |||
@@ -0,0 +1,2 @@ | |||
1 | { trap "echo got TERM" TERM; sleep 3; }& sleep 1; kill $!; wait | ||
2 | echo Done: $? | ||
diff --git a/shell/ash_test/ash-vars/var_leak.right b/shell/ash_test/ash-vars/var_leak.right index 45c5458dd..be78112c8 100644 --- a/shell/ash_test/ash-vars/var_leak.right +++ b/shell/ash_test/ash-vars/var_leak.right | |||
@@ -1,2 +1,3 @@ | |||
1 | should be empty: '' | 1 | should be empty: '' |
2 | should be empty: '' | 2 | should be not empty: 'val2' |
3 | should be not empty: 'val3' | ||
diff --git a/shell/ash_test/ash-vars/var_leak.tests b/shell/ash_test/ash-vars/var_leak.tests index 1b1123fb7..032059295 100755 --- a/shell/ash_test/ash-vars/var_leak.tests +++ b/shell/ash_test/ash-vars/var_leak.tests | |||
@@ -1,9 +1,18 @@ | |||
1 | # This currently fails with CONFIG_FEATURE_SH_NOFORK=y | 1 | # true is a regular builtin, varibale should not leak out of it |
2 | # this currently fails with CONFIG_FEATURE_SH_NOFORK=y | ||
2 | VAR='' | 3 | VAR='' |
3 | VAR=qwe true | 4 | VAR=val1 true |
4 | echo "should be empty: '$VAR'" | 5 | echo "should be empty: '$VAR'" |
5 | 6 | ||
6 | # This fails (always) | 7 | # ash follows the "special builtin leaks variables" rule here: |
8 | # exec is a special builtin. (bash does not do it) | ||
7 | VAR='' | 9 | VAR='' |
8 | VAR=qwe exec 2>&1 | 10 | VAR=val2 exec 2>&1 |
9 | echo "should be empty: '$VAR'" | 11 | echo "should be not empty: '$VAR'" |
12 | |||
13 | # ash follows the "function call is a special builtin" rule here | ||
14 | # (bash does not do it) | ||
15 | f() { true; } | ||
16 | VAR='' | ||
17 | VAR=val3 f | ||
18 | echo "should be not empty: '$VAR'" | ||
diff --git a/shell/cttyhack.c b/shell/cttyhack.c index 572a3af03..bde2acdc9 100644 --- a/shell/cttyhack.c +++ b/shell/cttyhack.c | |||
@@ -53,23 +53,32 @@ int cttyhack_main(int argc UNUSED_PARAM, char **argv) | |||
53 | } | 53 | } |
54 | 54 | ||
55 | strcpy(console, "/dev/tty"); | 55 | strcpy(console, "/dev/tty"); |
56 | if (ioctl(0, TIOCGSERIAL, &u.sr) == 0) { | 56 | fd = open(console, O_RDWR); |
57 | /* this is a serial console */ | 57 | if (fd >= 0) { |
58 | sprintf(console + 8, "S%d", u.sr.line); | 58 | /* We already have ctty, nothing to do */ |
59 | } else if (ioctl(0, VT_GETSTATE, &u.vt) == 0) { | 59 | close(fd); |
60 | /* this is linux virtual tty */ | 60 | } else { |
61 | sprintf(console + 8, "S%d" + 1, u.vt.v_active); | 61 | /* We don't have ctty (or don't have "/dev/tty" node...) */ |
62 | } | 62 | if (ioctl(0, TIOCGSERIAL, &u.sr) == 0) { |
63 | 63 | /* this is a serial console */ | |
64 | if (console[8]) { | 64 | sprintf(console + 8, "S%d", u.sr.line); |
65 | fd = xopen(console, O_RDWR); | 65 | } else if (ioctl(0, VT_GETSTATE, &u.vt) == 0) { |
66 | //bb_error_msg("switching to '%s'", console); | 66 | /* this is linux virtual tty */ |
67 | dup2(fd, 0); | 67 | sprintf(console + 8, "S%d" + 1, u.vt.v_active); |
68 | dup2(fd, 1); | 68 | } |
69 | dup2(fd, 2); | 69 | if (console[8]) { |
70 | while (fd > 2) close(fd--); | 70 | fd = xopen(console, O_RDWR); |
71 | /* Some other session may have it as ctty. Steal it from them */ | 71 | //bb_error_msg("switching to '%s'", console); |
72 | ioctl(0, TIOCSCTTY, 1); | 72 | dup2(fd, 0); |
73 | dup2(fd, 1); | ||
74 | dup2(fd, 2); | ||
75 | while (fd > 2) | ||
76 | close(fd--); | ||
77 | /* Some other session may have it as ctty, | ||
78 | * steal it from them: | ||
79 | */ | ||
80 | ioctl(0, TIOCSCTTY, 1); | ||
81 | } | ||
73 | } | 82 | } |
74 | 83 | ||
75 | BB_EXECVP(argv[0], argv); | 84 | BB_EXECVP(argv[0], argv); |
diff --git a/shell/hush.c b/shell/hush.c index 9b15efb30..8baccf246 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -39,25 +39,28 @@ | |||
39 | * | 39 | * |
40 | * POSIX syntax not implemented: | 40 | * POSIX syntax not implemented: |
41 | * aliases | 41 | * aliases |
42 | * <(list) and >(list) Process Substitution | ||
43 | * Tilde Expansion | 42 | * Tilde Expansion |
44 | * | 43 | * |
45 | * Bash stuff (optionally enabled): | 44 | * Bash compat TODO: |
46 | * &> and >& redirection of stdout+stderr | 45 | * redirection of stdout+stderr: &> and >& |
47 | * Brace Expansion | 46 | * brace expansion: one/{two,three,four} |
48 | * reserved words: [[ ]] function select | 47 | * reserved words: function select |
49 | * substrings ${var:1:5} | 48 | * advanced test: [[ ]] |
49 | * substrings: ${var:1:5} | ||
50 | * process substitution: <(list) and >(list) | ||
51 | * =~: regex operator | ||
50 | * let EXPR [EXPR...] | 52 | * let EXPR [EXPR...] |
51 | * Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION) | 53 | * Each EXPR is an arithmetic expression (ARITHMETIC EVALUATION) |
52 | * If the last arg evaluates to 0, let returns 1; 0 otherwise. | 54 | * If the last arg evaluates to 0, let returns 1; 0 otherwise. |
53 | * NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used) | 55 | * NB: let `echo 'a=a + 1'` - error (IOW: multi-word expansion is used) |
54 | * ((EXPR)) | 56 | * ((EXPR)) |
55 | * The EXPR is evaluated according to ARITHMETIC EVALUATION. | 57 | * The EXPR is evaluated according to ARITHMETIC EVALUATION. |
56 | * This is exactly equivalent to let "expression". | 58 | * This is exactly equivalent to let "EXPR". |
59 | * $[EXPR]: synonym for $((EXPR)) | ||
57 | * | 60 | * |
58 | * TODOs: | 61 | * TODOs: |
59 | * grep for "TODO" and fix (some of them are easy) | 62 | * grep for "TODO" and fix (some of them are easy) |
60 | * special variables (done: PWD) | 63 | * special variables (done: PWD, PPID, RANDOM) |
61 | * follow IFS rules more precisely, including update semantics | 64 | * follow IFS rules more precisely, including update semantics |
62 | * export builtin should be special, its arguments are assignments | 65 | * export builtin should be special, its arguments are assignments |
63 | * and therefore expansion of them should be "one-word" expansion: | 66 | * and therefore expansion of them should be "one-word" expansion: |
@@ -673,6 +676,9 @@ static const struct built_in_command bltins1[] = { | |||
673 | #endif | 676 | #endif |
674 | BLTIN("set" , builtin_set , "Set/unset positional parameters"), | 677 | BLTIN("set" , builtin_set , "Set/unset positional parameters"), |
675 | BLTIN("shift" , builtin_shift , "Shift positional parameters"), | 678 | BLTIN("shift" , builtin_shift , "Shift positional parameters"), |
679 | #if ENABLE_HUSH_BASH_COMPAT | ||
680 | BLTIN("source" , builtin_source , "Run commands in a file"), | ||
681 | #endif | ||
676 | BLTIN("trap" , builtin_trap , "Trap signals"), | 682 | BLTIN("trap" , builtin_trap , "Trap signals"), |
677 | BLTIN("type" , builtin_type , "Show command type"), | 683 | BLTIN("type" , builtin_type , "Show command type"), |
678 | BLTIN("ulimit" , shell_builtin_ulimit , "Control resource limits"), | 684 | BLTIN("ulimit" , shell_builtin_ulimit , "Control resource limits"), |
@@ -6232,10 +6238,15 @@ static struct pipe *parse_stream(char **pstring, | |||
6232 | is_special = "{}<>;&|()#'" /* special outside of "str" */ | 6238 | is_special = "{}<>;&|()#'" /* special outside of "str" */ |
6233 | "\\$\"" IF_HUSH_TICK("`"); /* always special */ | 6239 | "\\$\"" IF_HUSH_TICK("`"); /* always special */ |
6234 | /* Are { and } special here? */ | 6240 | /* Are { and } special here? */ |
6235 | if (ctx.command->argv /* word [word]{... */ | 6241 | if (ctx.command->argv /* word [word]{... - non-special */ |
6236 | || dest.length /* word{... */ | 6242 | || dest.length /* word{... - non-special */ |
6237 | || dest.o_quoted /* ""{... */ | 6243 | || dest.o_quoted /* ""{... - non-special */ |
6238 | || (next != ';' && next != ')' && !strchr(G.ifs, next)) /* {word */ | 6244 | || (next != ';' /* }; - special */ |
6245 | && next != ')' /* }) - special */ | ||
6246 | && next != '&' /* }& and }&& ... - special */ | ||
6247 | && next != '|' /* }|| ... - special */ | ||
6248 | && !strchr(G.ifs, next) /* {word - non-special */ | ||
6249 | ) | ||
6239 | ) { | 6250 | ) { |
6240 | /* They are not special, skip "{}" */ | 6251 | /* They are not special, skip "{}" */ |
6241 | is_special += 2; | 6252 | is_special += 2; |
@@ -7859,21 +7870,26 @@ static int FAST_FUNC builtin_shift(char **argv) | |||
7859 | 7870 | ||
7860 | static int FAST_FUNC builtin_source(char **argv) | 7871 | static int FAST_FUNC builtin_source(char **argv) |
7861 | { | 7872 | { |
7862 | char *arg_path; | 7873 | char *arg_path, *filename; |
7863 | FILE *input; | 7874 | FILE *input; |
7864 | save_arg_t sv; | 7875 | save_arg_t sv; |
7865 | #if ENABLE_HUSH_FUNCTIONS | 7876 | #if ENABLE_HUSH_FUNCTIONS |
7866 | smallint sv_flg; | 7877 | smallint sv_flg; |
7867 | #endif | 7878 | #endif |
7868 | 7879 | ||
7869 | if (*++argv == NULL) | 7880 | arg_path = NULL; |
7870 | return EXIT_FAILURE; | 7881 | filename = *++argv; |
7871 | 7882 | if (!filename) { | |
7872 | if (strchr(*argv, '/') == NULL && (arg_path = find_in_path(*argv)) != NULL) { | 7883 | /* bash says: "bash: .: filename argument required" */ |
7873 | input = fopen_for_read(arg_path); | 7884 | return 2; /* bash compat */ |
7874 | free(arg_path); | 7885 | } |
7875 | } else | 7886 | if (!strchr(filename, '/')) { |
7876 | input = fopen_or_warn(*argv, "r"); | 7887 | arg_path = find_in_path(filename); |
7888 | if (arg_path) | ||
7889 | filename = arg_path; | ||
7890 | } | ||
7891 | input = fopen_or_warn(filename, "r"); | ||
7892 | free(arg_path); | ||
7877 | if (!input) { | 7893 | if (!input) { |
7878 | /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */ | 7894 | /* bb_perror_msg("%s", *argv); - done by fopen_or_warn */ |
7879 | return EXIT_FAILURE; | 7895 | return EXIT_FAILURE; |
diff --git a/shell/hush_test/hush-parsing/group2.right b/shell/hush_test/hush-parsing/group2.right new file mode 100644 index 000000000..df4d9306a --- /dev/null +++ b/shell/hush_test/hush-parsing/group2.right | |||
@@ -0,0 +1,2 @@ | |||
1 | got TERM | ||
2 | Done: 0 | ||
diff --git a/shell/hush_test/hush-parsing/group2.tests b/shell/hush_test/hush-parsing/group2.tests new file mode 100755 index 000000000..d99178585 --- /dev/null +++ b/shell/hush_test/hush-parsing/group2.tests | |||
@@ -0,0 +1,3 @@ | |||
1 | # Bug was in handling of "}&" without space | ||
2 | { trap "echo got TERM" TERM; sleep 2; }& sleep 1; kill $!; wait | ||
3 | echo Done: $? | ||