diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2017-08-04 14:28:16 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2017-08-04 14:28:16 +0200 |
commit | 49e6bf2db92d896a71d08eb364069ba50fa82781 (patch) | |
tree | b0940fde4d99a73e1a27c7ad0b610fe7e3f1be0c | |
parent | 3346b4afc5c81d53eae4e7fc7e12ebd6fa573a4e (diff) | |
download | busybox-w32-49e6bf2db92d896a71d08eb364069ba50fa82781.tar.gz busybox-w32-49e6bf2db92d896a71d08eb364069ba50fa82781.tar.bz2 busybox-w32-49e6bf2db92d896a71d08eb364069ba50fa82781.zip |
sheel: improve comments on signal handling
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r-- | NOFORK_NOEXEC.lst | 4 | ||||
-rw-r--r-- | docs/nofork_noexec.txt | 24 | ||||
-rw-r--r-- | shell/ash.c | 27 | ||||
-rw-r--r-- | shell/hush.c | 7 |
4 files changed, 45 insertions, 17 deletions
diff --git a/NOFORK_NOEXEC.lst b/NOFORK_NOEXEC.lst index 12ae1cd55..14019bf7d 100644 --- a/NOFORK_NOEXEC.lst +++ b/NOFORK_NOEXEC.lst | |||
@@ -4,10 +4,12 @@ Why can't be NOFORK: | |||
4 | interactive: may wait for user input, ^C has to work | 4 | interactive: may wait for user input, ^C has to work |
5 | spawner: "tool PROG ARGS" which changes program's environment - must fork | 5 | spawner: "tool PROG ARGS" which changes program's environment - must fork |
6 | changes state: e.g. environment, signal handlers | 6 | changes state: e.g. environment, signal handlers |
7 | alloc+xfunc: xmalloc, then xfunc - leaks memory if xfunc dies | ||
8 | open+xfunc: opens fd, then calls xfunc - fd is leaked if xfunc dies | ||
7 | runner: sometimes may run for long(ish) time, and/or works with network: | 9 | runner: sometimes may run for long(ish) time, and/or works with network: |
8 | ^C has to work (cat BIGFILE, chmod -R, ftpget, nc) | 10 | ^C has to work (cat BIGFILE, chmod -R, ftpget, nc) |
9 | 11 | ||
10 | "runners" can become eligible after hush is taught ^C to interrupt NOFORKs! | 12 | "runners" can become eligible after shell is taught ^C to interrupt NOFORKs! |
11 | 13 | ||
12 | Why can't be NOEXEC: | 14 | Why can't be NOEXEC: |
13 | suid: runs under different uid - must fork+exec | 15 | suid: runs under different uid - must fork+exec |
diff --git a/docs/nofork_noexec.txt b/docs/nofork_noexec.txt index cfb02145a..b45a4be89 100644 --- a/docs/nofork_noexec.txt +++ b/docs/nofork_noexec.txt | |||
@@ -64,15 +64,27 @@ This poses much more serious limitations on what applet can do: | |||
64 | * do not use shared global data, or save/restore shared global data | 64 | * do not use shared global data, or save/restore shared global data |
65 | (e.g. bb_common_bufsiz1) prior to returning. | 65 | (e.g. bb_common_bufsiz1) prior to returning. |
66 | - getopt32() is ok to use. You do not need to save/restore option_mask32, | 66 | - getopt32() is ok to use. You do not need to save/restore option_mask32, |
67 | it is already done by core code. | 67 | xfunc_error_retval, and logmode - it is already done by core code. |
68 | * if you allocate memory, you can use xmalloc() only on the very first | 68 | * if you allocate memory, you can use xmalloc() only on the very first |
69 | allocation. All other allocations should use malloc[_or_warn](). | 69 | allocation. All other allocations should use malloc[_or_warn](). |
70 | After first allocation, you cannot use any xfuncs. | 70 | After first allocation, you cannot use any xfuncs. |
71 | Otherwise, failing xfunc will return to caller applet | 71 | Otherwise, failing xfunc will return to caller applet |
72 | without freeing malloced data! | 72 | without freeing malloced data! |
73 | * All allocated data, opened files, signal handlers, termios settings, | 73 | * the same applies to other resources, such as open fds: no xfuncs after |
74 | O_NONBLOCK flags etc should be freed/closed/restored prior to return. | 74 | acquiring them! |
75 | * ... | 75 | * All allocated data, opened files, signal handlers, termios settings |
76 | etc should be freed/closed/restored prior to return. | ||
77 | |||
78 | Currently, ash shell signal handling is implemented in a way that signals | ||
79 | have non-SA_RESTARTed handlers. This means that system calls can | ||
80 | return EINTR. An example of such problem is "yes" applet: | ||
81 | it is implemented so that it has a writing loop, this loop is exited on | ||
82 | any write error, and in the case of user pressing ^C the error was EINTR. | ||
83 | The problem is, the error causes stdout FILE* object to get into error | ||
84 | state, needing clearerr() - or else subsequent shell output will also | ||
85 | not work. ("yes" has been downgraded to NOEXEC, since hush signal handling | ||
86 | does not have this problem - which makes "yes" to not exit on ^C (bug). | ||
87 | But stray EINTRs can be seen in any NOFORK under ash, until ash is fixed). | ||
76 | 88 | ||
77 | NOFORK applets give the most of speed advantage, but are trickiest | 89 | NOFORK applets give the most of speed advantage, but are trickiest |
78 | to implement. In order to minimize amount of bugs and maintenance, | 90 | to implement. In order to minimize amount of bugs and maintenance, |
@@ -82,6 +94,8 @@ frequently executed from shell/find/xargs, particularly in shell | |||
82 | script loops. Applets which mess with signal handlers, termios etc | 94 | script loops. Applets which mess with signal handlers, termios etc |
83 | are probably not worth the effort. | 95 | are probably not worth the effort. |
84 | 96 | ||
97 | Applets which must be interruptible by ^C in shells can not be NOFORKs. | ||
98 | |||
85 | Any NOFORK applet is also a NOEXEC applet. | 99 | Any NOFORK applet is also a NOEXEC applet. |
86 | 100 | ||
87 | 101 | ||
@@ -94,7 +108,7 @@ API to call NOFORK applets is two functions: | |||
94 | 108 | ||
95 | First one is directly used by shells if FEATURE_SH_NOFORK=y. | 109 | First one is directly used by shells if FEATURE_SH_NOFORK=y. |
96 | Second one is used by many applets, but main users are xargs and find. | 110 | Second one is used by many applets, but main users are xargs and find. |
97 | It itself calls run_nofork_applet(), if argv[0] turned out to be a name | 111 | It itself calls run_nofork_applet(), if argv[0] is a name |
98 | of a NOFORK applet. | 112 | of a NOFORK applet. |
99 | 113 | ||
100 | run_nofork_applet() saves/inits/restores option parsing, xfunc_error_retval, | 114 | run_nofork_applet() saves/inits/restores option parsing, xfunc_error_retval, |
diff --git a/shell/ash.c b/shell/ash.c index 8c9f4adc6..ca9926b54 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
@@ -3536,9 +3536,12 @@ setsignal(int signo) | |||
3536 | #endif | 3536 | #endif |
3537 | } | 3537 | } |
3538 | } | 3538 | } |
3539 | //TODO: if !rootshell, we reset SIGQUIT to DFL, | 3539 | /* if !rootshell, we reset SIGQUIT to DFL, |
3540 | //whereas we have to restore it to what shell got on entry | 3540 | * whereas we have to restore it to what shell got on entry. |
3541 | //from the parent. See comment above | 3541 | * This is handled by the fact that if signal was IGNored on entry, |
3542 | * then cur_act is S_HARD_IGN and we never change its sigaction | ||
3543 | * (see code below). | ||
3544 | */ | ||
3542 | 3545 | ||
3543 | if (signo == SIGCHLD) | 3546 | if (signo == SIGCHLD) |
3544 | new_act = S_CATCH; | 3547 | new_act = S_CATCH; |
@@ -3566,6 +3569,8 @@ setsignal(int signo) | |||
3566 | if (cur_act == S_HARD_IGN || cur_act == new_act) | 3569 | if (cur_act == S_HARD_IGN || cur_act == new_act) |
3567 | return; | 3570 | return; |
3568 | 3571 | ||
3572 | *t = new_act; | ||
3573 | |||
3569 | act.sa_handler = SIG_DFL; | 3574 | act.sa_handler = SIG_DFL; |
3570 | switch (new_act) { | 3575 | switch (new_act) { |
3571 | case S_CATCH: | 3576 | case S_CATCH: |
@@ -3575,16 +3580,13 @@ setsignal(int signo) | |||
3575 | act.sa_handler = SIG_IGN; | 3580 | act.sa_handler = SIG_IGN; |
3576 | break; | 3581 | break; |
3577 | } | 3582 | } |
3578 | |||
3579 | /* flags and mask matter only if !DFL and !IGN, but we do it | 3583 | /* flags and mask matter only if !DFL and !IGN, but we do it |
3580 | * for all cases for more deterministic behavior: | 3584 | * for all cases for more deterministic behavior: |
3581 | */ | 3585 | */ |
3582 | act.sa_flags = 0; | 3586 | act.sa_flags = 0; //TODO: why not SA_RESTART? |
3583 | sigfillset(&act.sa_mask); | 3587 | sigfillset(&act.sa_mask); |
3584 | 3588 | ||
3585 | sigaction_set(signo, &act); | 3589 | sigaction_set(signo, &act); |
3586 | |||
3587 | *t = new_act; | ||
3588 | } | 3590 | } |
3589 | 3591 | ||
3590 | /* mode flags for set_curjob */ | 3592 | /* mode flags for set_curjob */ |
@@ -13429,7 +13431,9 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) | |||
13429 | INT_ON; | 13431 | INT_ON; |
13430 | 13432 | ||
13431 | if ((uintptr_t)r == 1 && errno == EINTR) { | 13433 | if ((uintptr_t)r == 1 && errno == EINTR) { |
13432 | /* to get SIGCHLD: sleep 1 & read x; echo $x */ | 13434 | /* To get SIGCHLD: sleep 1 & read x; echo $x |
13435 | * Correct behavior is to not exit "read" | ||
13436 | */ | ||
13433 | if (pending_sig == 0) | 13437 | if (pending_sig == 0) |
13434 | goto again; | 13438 | goto again; |
13435 | } | 13439 | } |
@@ -13544,13 +13548,14 @@ exitshell(void) | |||
13544 | /* NOTREACHED */ | 13548 | /* NOTREACHED */ |
13545 | } | 13549 | } |
13546 | 13550 | ||
13547 | static void | 13551 | /* Don't inline: conserve stack of caller from having our locals too */ |
13552 | static NOINLINE void | ||
13548 | init(void) | 13553 | init(void) |
13549 | { | 13554 | { |
13550 | /* we will never free this */ | 13555 | /* we will never free this */ |
13551 | basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); | 13556 | basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); |
13552 | 13557 | ||
13553 | sigmode[SIGCHLD - 1] = S_DFL; | 13558 | sigmode[SIGCHLD - 1] = S_DFL; /* ensure we install handler even if it is SIG_IGNed */ |
13554 | setsignal(SIGCHLD); | 13559 | setsignal(SIGCHLD); |
13555 | 13560 | ||
13556 | /* bash re-enables SIGHUP which is SIG_IGNed on entry. | 13561 | /* bash re-enables SIGHUP which is SIG_IGNed on entry. |
@@ -13561,7 +13566,6 @@ init(void) | |||
13561 | { | 13566 | { |
13562 | char **envp; | 13567 | char **envp; |
13563 | const char *p; | 13568 | const char *p; |
13564 | struct stat st1, st2; | ||
13565 | 13569 | ||
13566 | initvar(); | 13570 | initvar(); |
13567 | for (envp = environ; envp && *envp; envp++) { | 13571 | for (envp = environ; envp && *envp; envp++) { |
@@ -13587,6 +13591,7 @@ init(void) | |||
13587 | #endif | 13591 | #endif |
13588 | p = lookupvar("PWD"); | 13592 | p = lookupvar("PWD"); |
13589 | if (p) { | 13593 | if (p) { |
13594 | struct stat st1, st2; | ||
13590 | if (p[0] != '/' || stat(p, &st1) || stat(".", &st2) | 13595 | if (p[0] != '/' || stat(p, &st1) || stat(".", &st2) |
13591 | || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino | 13596 | || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino |
13592 | ) { | 13597 | ) { |
diff --git a/shell/hush.c b/shell/hush.c index b04f793f1..bb80f422c 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -1979,6 +1979,9 @@ static int check_and_run_traps(void) | |||
1979 | break; | 1979 | break; |
1980 | #if ENABLE_HUSH_JOB | 1980 | #if ENABLE_HUSH_JOB |
1981 | case SIGHUP: { | 1981 | case SIGHUP: { |
1982 | //TODO: why are we doing this? ash and dash don't do this, | ||
1983 | //they have no handler for SIGHUP at all, | ||
1984 | //they rely on kernel to send SIGHUP+SIGCONT to orphaned process groups | ||
1982 | struct pipe *job; | 1985 | struct pipe *job; |
1983 | debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig); | 1986 | debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig); |
1984 | /* bash is observed to signal whole process groups, | 1987 | /* bash is observed to signal whole process groups, |
@@ -8646,6 +8649,10 @@ static void install_sighandlers(unsigned mask) | |||
8646 | */ | 8649 | */ |
8647 | if (sig == SIGCHLD) | 8650 | if (sig == SIGCHLD) |
8648 | continue; | 8651 | continue; |
8652 | /* bash re-enables SIGHUP which is SIG_IGNed on entry. | ||
8653 | * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$" | ||
8654 | */ | ||
8655 | //if (sig == SIGHUP) continue; - TODO? | ||
8649 | if (old_handler == SIG_IGN) { | 8656 | if (old_handler == SIG_IGN) { |
8650 | /* oops... restore back to IGN, and record this fact */ | 8657 | /* oops... restore back to IGN, and record this fact */ |
8651 | install_sighandler(sig, old_handler); | 8658 | install_sighandler(sig, old_handler); |