aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorNguyễn Thái Ngọc Duy <pclouds@gmail.com>2010-09-14 11:41:16 +1000
committerNguyễn Thái Ngọc Duy <pclouds@gmail.com>2010-09-14 11:41:16 +1000
commit743d85e7d1c2a721baf020b9d79f45f0df2420a9 (patch)
tree9e551b726ac2bf9fd8eafb2764e7237f352bb429 /shell
parent87911f4fcd86f7abc0aefba8e9cbec2ac9d127e4 (diff)
parentc7f95d23f6bc7e17a3b79decf83eb362b389e53a (diff)
downloadbusybox-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.in15
-rw-r--r--shell/README190
-rw-r--r--shell/ash.c111
-rw-r--r--shell/ash_test/ash-misc/source2.right1
-rwxr-xr-xshell/ash_test/ash-misc/source2.tests3
-rwxr-xr-xshell/ash_test/ash-redir/redir3.tests2
-rwxr-xr-xshell/ash_test/ash-redir/redir7.tests2
-rwxr-xr-xshell/ash_test/ash-redir/redir8.tests2
-rw-r--r--shell/ash_test/ash-signals/signal5.right12
-rwxr-xr-xshell/ash_test/ash-signals/signal5.tests14
-rw-r--r--shell/ash_test/ash-signals/signal6.right2
-rwxr-xr-xshell/ash_test/ash-signals/signal6.tests2
-rw-r--r--shell/ash_test/ash-vars/var_leak.right3
-rwxr-xr-xshell/ash_test/ash-vars/var_leak.tests19
-rw-r--r--shell/cttyhack.c43
-rw-r--r--shell/hush.c58
-rw-r--r--shell/hush_test/hush-parsing/group2.right2
-rwxr-xr-xshell/hush_test/hush-parsing/group2.tests3
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
362endmenu 373endmenu
diff --git a/shell/README b/shell/README
index 59efe499f..550c712d3 100644
--- a/shell/README
+++ b/shell/README
@@ -1,108 +1,82 @@
1Various bits of what is known about busybox shells, in no particular order. 1http://www.opengroup.org/onlinepubs/9699919799/
2 2Open Group Base Specifications Issue 7
32008-02-14 3
4ash: does not restore tty pgrp if killed by HUP. Symptom: Midnight Commander 4
5is backgrounded if you started ash under it, and then killed it with HUP. 5http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap01.html
6 6Shell & Utilities
72007-11-23 7
8hush: fixed bogus glob handling; fixed exec <"$1"; added test and echo builtins 8It says that any of the standard utilities may be implemented
9 9as a regular shell built-in. It gives a list of utilities which
102007-06-13 10are usually implemented that way (and some of them can only
11hush: exec <"$1" doesn't do parameter subst 11be implemented as built-ins, like "alias"):
12 12
132007-05-24 13alias
14hush: environment-related memory leak plugged, with net code size 14bg
15decrease. 15cd
16 16command
172007-05-24 17false
18hush: '( echo ${name )' will show syntax error message, but prompt 18fc
19doesn't return (need to press <enter>). Pressing Ctrl-C, <enter>, 19fg
20'( echo ${name )' again, Ctrl-C segfaults. 20getopts
21 21jobs
222007-05-21 22kill
23hush: environment cannot be handled by libc routines as they are leaky 23newgrp
24(by API design and thus unfixable): hush will leak memory in this script, 24pwd
25bash does not: 25read
26pid=$$ 26true
27while true; do 27umask
28 unset t; 28unalias
29 t=111111111111111111111111111111111111111111111111111111111111111111111111 29wait
30 export t 30
31 ps -o vsz,pid,comm | grep " $pid " 31
32done 32http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
33The fix is to not use setenv/putenv/unsetenv but manipulate env ourself. TODO. 33Shell Command Language
34hush: meanwhile, first three command subst bugs mentioned below are fixed. :) 34
35 35It says that shell must implement special built-ins. Special built-ins
362007-05-06 36differ from regular ones by the fact that variable assignments
37hush: more bugs spotted. Comparison with bash: 37done on special builtin is *PRESERVED*. That is,
38bash-3.2# echo "TEST`date;echo;echo`BEST" 38
39TESTSun May 6 09:21:05 CEST 2007BEST [we dont strip eols] 39VAR=VAL special_builtin; echo $VAR
40bash-3.2# echo "TEST`echo '$(echo ZZ)'`BEST" 40
41TEST$(echo ZZ)BEST [we execute inner echo] 41should print VAL.
42bash-3.2# echo "TEST`echo "'"`BEST" 42
43TEST'BEST [we totally mess up this one] 43(Another distinction is that an error in special built-in should
44bash-3.2# echo `sleep 5` 44abort 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] 45and moreover, at least bash's "set" does not follow this rule,
46bash-3.2# if true; then 46which is even codified in autoconf now...).
47> [Ctrl-C] 47
48bash-3.2# [we re-issue "> "] 48List of special builtins:
49bash-3.2# if echo `sleep 5`; then 49
50> true; fi [we execute sleep before "> "] 50. file
51 51: [argument...]
522007-05-04 52break [n]
53hush: made ctrl-Z/C work correctly for "while true; do true; done" 53continue [n]
54(namely, it backgrounds/interrupts entire "while") 54eval [argument...]
55 55exec [command [argument...]]
562007-05-03 56exit [n]
57hush: new bug spotted: Ctrl-C on "while true; do true; done" doesn't 57export name[=word]...
58work right: 58export -p
59# while true; do true; done 59readonly name[=word]...
60[1] 0 true <-- pressing Ctrl-C several times... 60readonly -p
61[2] 0 true 61return [n]
62[3] 0 true 62set [-abCefhmnuvx] [-o option] [argument...]
63Segmentation fault 63set [+abCefhmnuvx] [+o option] [argument...]
64 64set -- [argument...]
652007-05-03 65set -o
66hush: update on "sleep 1 | exit 3; echo $?" bug. 66set +o
67parse_stream_outer() repeatedly calls parse_stream(). 67shift [n]
68parse_stream() is now fixed to stop on ';' in this example, 68times
69fixing it (parse_stream_outer() will call parse_stream() 1st time, 69trap n [condition...]
70execute the parse tree, call parse_stream() 2nd time and execute the tree). 70trap [action condition...]
71But it's not the end of story. 71unset [-fv] name...
72In more complex situations we _must_ parse way farther before executing. 72
73Example #2: "{ sleep 1 | exit 3; echo $?; ...few_lines... } >file". 73In practice, no one uses this obscure feature - none of these builtins
74Because of redirection, we cannot execute 1st pipe before we parse it all. 74gives any special reasons to play such dirty tricks.
75We probably need to learn to store $var expressions in parse tree. 75
76Debug printing of parse tree would be nice too. 76However. This section says that *function invocation* should act
77 77similar to special built-in. That is, variable assignments
782007-04-28 78done on function invocation should be preserved after function invocation.
79hush: Ctrl-C and Ctrl-Z for single NOFORK commands are working. 79
80Memory and other resource leaks (opendir) are not addressed 80This is significant: it is not unthinkable to want to run a function
81(testcase is "rm -i" interrupted by ctrl-c). 81with some variables set to special values. But because of the above,
82 82it does not work: variable will "leak" out of the function.
832007-04-21
84hush: "sleep 5 | sleep 6" + Ctrl-Z + fg seems to work.
85"rm -i" + Ctrl-C, "sleep 5" + Ctrl-Z still doesn't work
86for SH_STANDALONE case :(
87
882007-04-21
89hush: fixed non-backgrounding of "sleep 1 &" and totally broken
90"sleep 1 | sleep 2 &". Noticed a bug where successive jobs
91get numbers 1,2,3 even when job #1 has exited before job# 2 is started.
92(bash reuses #1 in this case)
93
942007-04-21
95hush: "sleep 1 | exit 3; echo $?" prints 0 because $? is substituted
96_before_ pipe gets executed!! run_list_real() already has "pipe;echo"
97parsed and handed to it for execution, so it sees "pipe"; "echo 0".
98
992007-04-21
100hush: removed setsid() and made job control sort-of-sometimes-work.
101Ctrl-C in "rm -i" works now except for SH_STANDALONE case.
102"sleep 1 | exit 3" + "echo $?" works, "sleep 1 | exit 3; echo $?"
103shows exitcode 0 (should be 3). "sleep 1 | sleep 2 &" fails horribly.
104
1052007-04-14
106lash, hush: both do setsid() and as a result don't have ctty!
107Ctrl-C doesn't work for any child (try rm -i), etc...
108lash: 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 */
3429static void 3429static void
3430onsig(int signo) 3430signal_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
4085static int 4085static int
4086blocking_wait_with_raise_on_sig(struct job *job) 4086blocking_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)
12484static int FAST_FUNC 12501static int FAST_FUNC
12485dotcmd(int argc, char **argv) 12502dotcmd(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
12519static int FAST_FUNC 12543static 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
13219init(void) 13245init(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 @@
1false
2. /dev/null
3echo 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
3echo TEST 9>/dev/null 3echo TEST 9>/dev/null
4echo MUST ERROR OUT >&9 4echo 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 @@
7echo -e 'echo Ok >uni\x81code' >>unicode.sh 7echo -e 'echo Ok >uni\x81code' >>unicode.sh
8echo -e 'cat uni\x81code' >>unicode.sh 8echo -e 'cat uni\x81code' >>unicode.sh
9echo -e 'cat uni?code' >>unicode.sh 9echo -e 'cat uni?code' >>unicode.sh
10. unicode.sh 10. ./unicode.sh
11rm uni*code* 11rm uni*code*
12echo Done 12echo 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
10echo -e 'echo Ok >"$v"' >>unicode.sh 10echo -e 'echo Ok >"$v"' >>unicode.sh
11echo -e 'cat uni\x81code' >>unicode.sh 11echo -e 'cat uni\x81code' >>unicode.sh
12echo -e 'cat uni?code' >>unicode.sh 12echo -e 'cat uni?code' >>unicode.sh
13. unicode.sh 13. ./unicode.sh
14rm uni*code* 14rm uni*code*
15echo Done 15echo 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 @@
1sleeping for 3 sec
2sleeping for 2 sec
3Waiting
42 sec passed, sending USR1 to parent
5USR1 received
6Wait exit code: 138
7Waiting
83 sec passed, sending USR1 to parent
9USR1 received
10Wait exit code: 138
11Waiting
12Wait 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 @@
1trap "echo USR1 received" USR1
2stub() {
3 echo "sleeping for $1 sec"
4 sleep $1
5 echo "$1 sec passed, sending USR1 to parent"
6 kill -USR1 $$
7}
8stub 3 &
9stub 2 &
10sleep 1
11until { echo "Waiting"; wait; } do
12 echo "Wait exit code: $?"
13done
14echo "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 @@
1got TERM
2Done: 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
2echo 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 @@
1should be empty: '' 1should be empty: ''
2should be empty: '' 2should be not empty: 'val2'
3should 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
2VAR='' 3VAR=''
3VAR=qwe true 4VAR=val1 true
4echo "should be empty: '$VAR'" 5echo "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)
7VAR='' 9VAR=''
8VAR=qwe exec 2>&1 10VAR=val2 exec 2>&1
9echo "should be empty: '$VAR'" 11echo "should be not empty: '$VAR'"
12
13# ash follows the "function call is a special builtin" rule here
14# (bash does not do it)
15f() { true; }
16VAR=''
17VAR=val3 f
18echo "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
7860static int FAST_FUNC builtin_source(char **argv) 7871static 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 @@
1got TERM
2Done: 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
3echo Done: $?