From b8709032a3fb57b3ec536bdf9b92b526ed63b995 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 8 May 2011 21:20:01 +0200 Subject: hush: fix incorrect PS2 dispaly and trap handling while reading command The fix affects only !ENABLE_FEATURE_EDITING configuration Signed-off-by: Denys Vlasenko --- shell/hush.c | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index d3e957c2f..bcd458427 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -1319,6 +1319,8 @@ static void restore_G_args(save_arg_t *sv, char **argv) * "echo $$; sleep 5 & wait; ls -l" + "kill -INT " * Example 3: this does not wait 5 sec, but executes ls: * "sleep 5; ls -l" + press ^C + * Example 4: this does not wait and does not execute ls: + * "sleep 5 & wait; ls -l" + press ^C * * (What happens to signals which are IGN on shell start?) * (What happens with signal mask on shell start?) @@ -1471,13 +1473,13 @@ static int check_and_run_traps(int sig) int last_sig = 0; if (sig) - goto jump_in; + goto got_sig; + while (1) { sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec); if (sig <= 0) break; - jump_in: - last_sig = sig; + got_sig: if (G.traps && G.traps[sig]) { if (G.traps[sig][0]) { /* We have user-defined handler */ @@ -1488,6 +1490,7 @@ static int check_and_run_traps(int sig) save_rcode = G.last_exitcode; builtin_eval(argv); G.last_exitcode = save_rcode; + last_sig = sig; } /* else: "" trap, ignoring signal */ continue; } @@ -1503,6 +1506,7 @@ static int check_and_run_traps(int sig) /* Builtin was ^C'ed, make it look prettier: */ bb_putchar('\n'); G.flag_SIGINT = 1; + last_sig = sig; break; #if ENABLE_HUSH_JOB case SIGHUP: { @@ -1521,6 +1525,11 @@ static int check_and_run_traps(int sig) #endif default: /* ignored: */ /* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */ + /* note: + * we dont do 'last_sig = sig' here -> NOT returning this sig. + * example: wait is not interrupted by TERM + * in interactive shell, because TERM is ignored. + */ break; } } @@ -1921,11 +1930,18 @@ static void get_user_input(struct in_str *i) # else do { G.flag_SIGINT = 0; - fputs(prompt_str, stdout); + if (i->last_char == '\0' || i->last_char == '\n') { + /* Why check_and_run_traps here? Try this interactively: + * $ trap 'echo INT' INT; (sleep 2; kill -INT $$) & + * $ <[enter], repeatedly...> + * Without check_and_run_traps, handler never runs. + */ + check_and_run_traps(0); + fputs(prompt_str, stdout); + } fflush_all(); G.user_input_buf[0] = r = fgetc(i->file); /*G.user_input_buf[1] = '\0'; - already is and never changed */ -//do we need check_and_run_traps(0)? (maybe only if stdin) } while (G.flag_SIGINT); i->eof_flag = (r == EOF); # endif @@ -3322,6 +3338,7 @@ static char *fetch_till_str(o_string *as_string, int ch; goto jump_in; + while (1) { ch = i_getch(input); if (ch != EOF) -- cgit v1.2.3-55-g6feb From 80c5b6893d4708b3683ad9a51c990a326a8f1dff Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 8 May 2011 21:21:10 +0200 Subject: libbb: nonblock_safe_read->nonblock_immune_read, remove unused param of xmalloc_reads Signed-off-by: Denys Vlasenko --- editors/patch.c | 2 +- include/libbb.h | 4 ++-- libbb/read_printf.c | 12 +++++++----- mailutils/mail.c | 4 ++-- printutils/lpd.c | 2 +- shell/ash.c | 8 ++++---- shell/shell_common.c | 2 +- util-linux/acpid.c | 2 +- 8 files changed, 19 insertions(+), 17 deletions(-) (limited to 'shell') diff --git a/editors/patch.c b/editors/patch.c index a90252a03..6d3f319b0 100644 --- a/editors/patch.c +++ b/editors/patch.c @@ -239,7 +239,7 @@ static int apply_one_hunk(void) plist = TT.current_hunk; buf = NULL; if (reverse ? TT.oldlen : TT.newlen) for (;;) { - char *data = xmalloc_reads(TT.filein, NULL, NULL); + char *data = xmalloc_reads(TT.filein, NULL); TT.linenum++; diff --git a/include/libbb.h b/include/libbb.h index 34f7f6a8b..4ea94e77f 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -672,7 +672,7 @@ void* xrealloc_vector_helper(void *vector, unsigned sizeof_and_shift, int idx) F extern ssize_t safe_read(int fd, void *buf, size_t count) FAST_FUNC; -extern ssize_t nonblock_safe_read(int fd, void *buf, size_t count) FAST_FUNC; +extern ssize_t nonblock_immune_read(int fd, void *buf, size_t count) FAST_FUNC; // NB: will return short read on error, not -1, // if some data was read before error occurred extern ssize_t full_read(int fd, void *buf, size_t count) FAST_FUNC; @@ -683,7 +683,7 @@ extern ssize_t open_read_close(const char *filename, void *buf, size_t maxsz) FA // Reads one line a-la fgets (but doesn't save terminating '\n'). // Reads byte-by-byte. Useful when it is important to not read ahead. // Bytes are appended to pfx (which must be malloced, or NULL). -extern char *xmalloc_reads(int fd, char *pfx, size_t *maxsz_p) FAST_FUNC; +extern char *xmalloc_reads(int fd, size_t *maxsz_p) FAST_FUNC; /* Reads block up to *maxsz_p (default: INT_MAX - 4095) */ extern void *xmalloc_read(int fd, size_t *maxsz_p) FAST_FUNC RETURNS_MALLOC; /* Returns NULL if file can't be opened (default max size: INT_MAX - 4095) */ diff --git a/libbb/read_printf.c b/libbb/read_printf.c index 8664bc625..0e6fbf662 100644 --- a/libbb/read_printf.c +++ b/libbb/read_printf.c @@ -55,7 +55,7 @@ * which detects EAGAIN and uses poll() to wait on the fd. * Thankfully, poll() doesn't care about O_NONBLOCK flag. */ -ssize_t FAST_FUNC nonblock_safe_read(int fd, void *buf, size_t count) +ssize_t FAST_FUNC nonblock_immune_read(int fd, void *buf, size_t count) { struct pollfd pfd[1]; ssize_t n; @@ -74,13 +74,15 @@ ssize_t FAST_FUNC nonblock_safe_read(int fd, void *buf, size_t count) // Reads one line a-la fgets (but doesn't save terminating '\n'). // Reads byte-by-byte. Useful when it is important to not read ahead. // Bytes are appended to pfx (which must be malloced, or NULL). -char* FAST_FUNC xmalloc_reads(int fd, char *buf, size_t *maxsz_p) +char* FAST_FUNC xmalloc_reads(int fd, size_t *maxsz_p) { char *p; - size_t sz = buf ? strlen(buf) : 0; + char *buf = NULL; + size_t sz = 0; size_t maxsz = maxsz_p ? *maxsz_p : (INT_MAX - 4095); goto jump_in; + while (sz < maxsz) { if ((size_t)(p - buf) == sz) { jump_in: @@ -88,8 +90,8 @@ char* FAST_FUNC xmalloc_reads(int fd, char *buf, size_t *maxsz_p) p = buf + sz; sz += 128; } - /* nonblock_safe_read() because we are used by e.g. shells */ - if (nonblock_safe_read(fd, p, 1) != 1) { /* EOF/error */ + if (nonblock_immune_read(fd, p, 1) != 1) { + /* EOF/error */ if (p == buf) { /* we read nothing */ free(buf); return NULL; diff --git a/mailutils/mail.c b/mailutils/mail.c index 44957016f..66c79471f 100644 --- a/mailutils/mail.c +++ b/mailutils/mail.c @@ -172,8 +172,8 @@ void FAST_FUNC get_cred_or_die(int fd) G.user = xstrdup(bb_ask(fd, /* timeout: */ 0, "User: ")); G.pass = xstrdup(bb_ask(fd, /* timeout: */ 0, "Password: ")); } else { - G.user = xmalloc_reads(fd, /* pfx: */ NULL, /* maxsize: */ NULL); - G.pass = xmalloc_reads(fd, /* pfx: */ NULL, /* maxsize: */ NULL); + G.user = xmalloc_reads(fd, /* maxsize: */ NULL); + G.pass = xmalloc_reads(fd, /* maxsize: */ NULL); } if (!G.user || !*G.user || !G.pass) bb_error_msg_and_die("no username or password"); diff --git a/printutils/lpd.c b/printutils/lpd.c index 115552e0b..642e8a89e 100644 --- a/printutils/lpd.c +++ b/printutils/lpd.c @@ -102,7 +102,7 @@ static char *xmalloc_read_stdin(void) { // SECURITY: size_t max = 4 * 1024; // more than enough for commands! - return xmalloc_reads(STDIN_FILENO, NULL, &max); + return xmalloc_reads(STDIN_FILENO, &max); } int lpd_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE; diff --git a/shell/ash.c b/shell/ash.c index b50e0952e..b1b11bd1b 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5918,7 +5918,7 @@ expbackq(union node *cmd, int quoted, int quotes) read: if (in.fd < 0) break; - i = nonblock_safe_read(in.fd, buf, sizeof(buf)); + i = nonblock_immune_read(in.fd, buf, sizeof(buf)); TRACE(("expbackq: read returns %d\n", i)); if (i <= 0) break; @@ -9617,7 +9617,7 @@ preadfd(void) #if ENABLE_FEATURE_EDITING retry: if (!iflag || g_parsefile->pf_fd != STDIN_FILENO) - nr = nonblock_safe_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); + nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); else { int timeout = -1; # if ENABLE_ASH_IDLE_TIMEOUT @@ -9663,10 +9663,10 @@ preadfd(void) } } #else - nr = nonblock_safe_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); + nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); #endif -#if 0 /* disabled: nonblock_safe_read() handles this problem */ +#if 0 /* disabled: nonblock_immune_read() handles this problem */ if (nr < 0) { if (parsefile->fd == 0 && errno == EWOULDBLOCK) { int flags = fcntl(0, F_GETFL); diff --git a/shell/shell_common.c b/shell/shell_common.c index 68659abd3..86a6493ed 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -170,7 +170,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), if ((bufpos & 0xff) == 0) buffer = xrealloc(buffer, bufpos + 0x100); - if (nonblock_safe_read(fd, &buffer[bufpos], 1) != 1) { + if (nonblock_immune_read(fd, &buffer[bufpos], 1) != 1) { retval = (const char *)(uintptr_t)1; break; } diff --git a/util-linux/acpid.c b/util-linux/acpid.c index c9eed2a7f..4b7e5cacb 100644 --- a/util-linux/acpid.c +++ b/util-linux/acpid.c @@ -283,7 +283,7 @@ int acpid_main(int argc UNUSED_PARAM, char **argv) char *buf; int len; - buf = xmalloc_reads(pfd[i].fd, NULL, NULL); + buf = xmalloc_reads(pfd[i].fd, NULL); /* buf = "button/power PWRB 00000080 00000000" */ len = strlen(buf) - 9; if (len >= 0) -- cgit v1.2.3-55-g6feb From 80542bad2f1df9d99b579c9eeb3c2675c14c72c0 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 8 May 2011 21:23:43 +0200 Subject: hush: make read builtin interruptible. function old new delta builtin_read 185 471 +286 check_and_run_traps 200 262 +62 nonblock_immune_read 73 119 +46 sigismember - 44 +44 record_signal - 21 +21 sigisemptyset - 16 +16 ... ------------------------------------------------------------------------------ (add/remove: 5/0 grow/shrink: 7/5 up/down: 483/-46) Total: 437 bytes Signed-off-by: Denys Vlasenko --- include/libbb.h | 2 +- libbb/read_printf.c | 9 ++--- shell/ash.c | 6 ++-- shell/hush.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++-- shell/shell_common.c | 17 +++++++-- 5 files changed, 120 insertions(+), 12 deletions(-) (limited to 'shell') diff --git a/include/libbb.h b/include/libbb.h index 4ea94e77f..56dfa61b7 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -672,7 +672,7 @@ void* xrealloc_vector_helper(void *vector, unsigned sizeof_and_shift, int idx) F extern ssize_t safe_read(int fd, void *buf, size_t count) FAST_FUNC; -extern ssize_t nonblock_immune_read(int fd, void *buf, size_t count) FAST_FUNC; +extern ssize_t nonblock_immune_read(int fd, void *buf, size_t count, int loop_on_EINTR) FAST_FUNC; // NB: will return short read on error, not -1, // if some data was read before error occurred extern ssize_t full_read(int fd, void *buf, size_t count) FAST_FUNC; diff --git a/libbb/read_printf.c b/libbb/read_printf.c index 0e6fbf662..192f83d6e 100644 --- a/libbb/read_printf.c +++ b/libbb/read_printf.c @@ -55,19 +55,20 @@ * which detects EAGAIN and uses poll() to wait on the fd. * Thankfully, poll() doesn't care about O_NONBLOCK flag. */ -ssize_t FAST_FUNC nonblock_immune_read(int fd, void *buf, size_t count) +ssize_t FAST_FUNC nonblock_immune_read(int fd, void *buf, size_t count, int loop_on_EINTR) { struct pollfd pfd[1]; ssize_t n; while (1) { - n = safe_read(fd, buf, count); + n = loop_on_EINTR ? safe_read(fd, buf, count) : read(fd, buf, count); if (n >= 0 || errno != EAGAIN) return n; /* fd is in O_NONBLOCK mode. Wait using poll and repeat */ pfd[0].fd = fd; pfd[0].events = POLLIN; - safe_poll(pfd, 1, -1); /* note: this pulls in printf */ + /* note: safe_poll pulls in printf */ + loop_on_EINTR ? safe_poll(pfd, 1, -1) : poll(pfd, 1, -1); } } @@ -90,7 +91,7 @@ char* FAST_FUNC xmalloc_reads(int fd, size_t *maxsz_p) p = buf + sz; sz += 128; } - if (nonblock_immune_read(fd, p, 1) != 1) { + if (nonblock_immune_read(fd, p, 1, /*loop_on_EINTR:*/ 1) != 1) { /* EOF/error */ if (p == buf) { /* we read nothing */ free(buf); diff --git a/shell/ash.c b/shell/ash.c index b1b11bd1b..d48cd016f 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5918,7 +5918,7 @@ expbackq(union node *cmd, int quoted, int quotes) read: if (in.fd < 0) break; - i = nonblock_immune_read(in.fd, buf, sizeof(buf)); + i = nonblock_immune_read(in.fd, buf, sizeof(buf), /*loop_on_EINTR:*/ 1); TRACE(("expbackq: read returns %d\n", i)); if (i <= 0) break; @@ -9617,7 +9617,7 @@ preadfd(void) #if ENABLE_FEATURE_EDITING retry: if (!iflag || g_parsefile->pf_fd != STDIN_FILENO) - nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); + nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1, /*loop_on_EINTR:*/ 1); else { int timeout = -1; # if ENABLE_ASH_IDLE_TIMEOUT @@ -9663,7 +9663,7 @@ preadfd(void) } } #else - nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); + nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1, /*loop_on_EINTR:*/ 1); #endif #if 0 /* disabled: nonblock_immune_read() handles this problem */ diff --git a/shell/hush.c b/shell/hush.c index bcd458427..0b17b222d 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -795,8 +795,15 @@ struct globals { /* which signals have non-DFL handler (even with no traps set)? */ unsigned non_DFL_mask; char **traps; /* char *traps[NSIG] */ - sigset_t blocked_set; + /* Signal mask on the entry to the (top-level) shell. Never modified. */ sigset_t inherited_set; + /* Starts equal to inherited_set, + * but shell-special signals are added and SIGCHLD is removed. + * When a trap is set/cleared, signal is added to/removed from it: + */ + sigset_t blocked_set; + /* Used by read() */ + sigset_t detected_set; #if HUSH_DEBUG unsigned long memleak_value; int debug_indent; @@ -1476,6 +1483,17 @@ static int check_and_run_traps(int sig) goto got_sig; while (1) { + if (!sigisemptyset(&G.detected_set)) { + sig = 0; + do { + sig++; + if (sigismember(&G.detected_set, sig)) { + sigdelset(&G.detected_set, sig); + goto got_sig; + } + } while (sig < NSIG); + } + sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec); if (sig <= 0) break; @@ -8484,6 +8502,32 @@ static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM) return EXIT_SUCCESS; } +/* Interruptibility of read builtin in bash + * (tested on bash-4.2.8 by sending signals (not by ^C)): + * + * Empty trap makes read ignore corresponding signal, for any signal. + * + * SIGINT: + * - terminates non-interactive shell; + * - interrupts read in interactive shell; + * if it has non-empty trap: + * - executes trap and returns to command prompt in interactive shell; + * - executes trap and returns to read in non-interactive shell; + * SIGTERM: + * - is ignored (does not interrupt) read in interactive shell; + * - terminates non-interactive shell; + * if it has non-empty trap: + * - executes trap and returns to read; + * SIGHUP: + * - terminates shell (regardless of interactivity); + * if it has non-empty trap: + * - executes trap and returns to read; + */ +/* helper */ +static void record_signal(int sig) +{ + sigaddset(&G.detected_set, sig); +} static int FAST_FUNC builtin_read(char **argv) { const char *r; @@ -8491,7 +8535,9 @@ static int FAST_FUNC builtin_read(char **argv) char *opt_p = NULL; char *opt_t = NULL; char *opt_u = NULL; + const char *ifs; int read_flags; + sigset_t saved_blkd_set; /* "!": do not abort on errors. * Option string must start with "sr" to match BUILTIN_READ_xxx @@ -8500,10 +8546,47 @@ static int FAST_FUNC builtin_read(char **argv) if (read_flags == (uint32_t)-1) return EXIT_FAILURE; argv += optind; + ifs = get_local_var_value("IFS"); /* can be NULL */ + + again: + /* We need to temporarily unblock and record signals around read */ + + saved_blkd_set = G.blocked_set; + { + unsigned sig; + struct sigaction sa, old_sa; + + memset(&sa, 0, sizeof(sa)); + sigfillset(&sa.sa_mask); + /*sa.sa_flags = 0;*/ + sa.sa_handler = record_signal; + + sig = 0; + do { + sig++; + if (sigismember(&G.blocked_set, sig)) { + char *sig_trap = (G.traps && G.traps[sig]) ? G.traps[sig] : NULL; + /* If has a nonempty trap... */ + if ((sig_trap && sig_trap[0]) + /* ...or has no trap and is SIGINT or SIGHUP */ + || (!sig_trap && (sig == SIGINT || sig == SIGHUP)) + ) { + sigaction(sig, &sa, &old_sa); + if (old_sa.sa_handler == SIG_IGN) /* oops... restore back to IGN! */ + sigaction_set(sig, &old_sa); + else + sigdelset(&G.blocked_set, sig); + } + } + } while (sig < NSIG-1); + } + + if (memcmp(&saved_blkd_set, &G.blocked_set, sizeof(saved_blkd_set)) != 0) + sigprocmask_set(&G.blocked_set); r = shell_builtin_read(set_local_var_from_halves, argv, - get_local_var_value("IFS"), /* can be NULL */ + ifs, read_flags, opt_n, opt_p, @@ -8511,6 +8594,17 @@ static int FAST_FUNC builtin_read(char **argv) opt_u ); + if (memcmp(&saved_blkd_set, &G.blocked_set, sizeof(saved_blkd_set)) != 0) { + G.blocked_set = saved_blkd_set; + sigprocmask_set(&G.blocked_set); + } + + if ((uintptr_t)r == 1 && errno == EINTR) { + unsigned sig = check_and_run_traps(0); + if (sig && sig != SIGINT) + goto again; + } + if ((uintptr_t)r > 1) { bb_error_msg("%s", r); r = (char*)(uintptr_t)1; diff --git a/shell/shell_common.c b/shell/shell_common.c index 86a6493ed..a5c455c8e 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -36,6 +36,10 @@ int FAST_FUNC is_well_formed_var_name(const char *s, char terminator) /* read builtin */ +/* Needs to be interruptible: shell mush handle traps and shell-special signals + * while inside read. To implement this, be sure to not loop on EINTR + * and return errno == EINTR reliably. + */ //TODO: use more efficient setvar() which takes a pointer to malloced "VAR=VAL" //string. hush naturally has it, and ash has setvareq(). //Here we can simply store "VAR=" at buffer start and store read data directly @@ -51,6 +55,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), const char *opt_u ) { + unsigned err; unsigned end_ms; /* -t TIMEOUT */ int fd; /* -u FD */ int nchars; /* -n NUM */ @@ -62,6 +67,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), int startword; smallint backslash; + errno = err = 0; + pp = argv; while (*pp) { if (!is_well_formed_var_name(*pp, '\0')) { @@ -153,6 +160,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), do { char c; + errno = 0; + if (end_ms) { int timeout; struct pollfd pfd[1]; @@ -161,8 +170,9 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), pfd[0].events = POLLIN; timeout = end_ms - (unsigned)monotonic_ms(); if (timeout <= 0 /* already late? */ - || safe_poll(pfd, 1, timeout) != 1 /* no? wait... */ + || poll(pfd, 1, timeout) != 1 /* no? wait... */ ) { /* timed out! */ + err = errno; retval = (const char *)(uintptr_t)1; goto ret; } @@ -170,7 +180,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), if ((bufpos & 0xff) == 0) buffer = xrealloc(buffer, bufpos + 0x100); - if (nonblock_immune_read(fd, &buffer[bufpos], 1) != 1) { + if (nonblock_immune_read(fd, &buffer[bufpos], 1, /*loop_on_EINTR:*/ 0) != 1) { + err = errno; retval = (const char *)(uintptr_t)1; break; } @@ -240,6 +251,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), free(buffer); if (read_flags & BUILTIN_READ_SILENT) tcsetattr(fd, TCSANOW, &old_tty); + + errno = err; return retval; } -- cgit v1.2.3-55-g6feb From 54e9e1217c67563d70d6e2b14021293f707d7927 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 9 May 2011 00:52:15 +0200 Subject: hush: code shrink Signed-off-by: Denys Vlasenko --- shell/hush.c | 125 +++++++++++++++++++++++++++++++---------------------------- 1 file changed, 66 insertions(+), 59 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 0b17b222d..71972f751 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -1379,6 +1379,11 @@ enum { #endif }; +static void sigprocmask_set(sigset_t *set) +{ + sigprocmask(SIG_SETMASK, set, NULL); +} + #if ENABLE_HUSH_FAST static void SIGCHLD_handler(int sig UNUSED_PARAM) { @@ -5378,18 +5383,15 @@ static void reset_traps_to_defaults(void) * Stupid. It can be done with *single* &= op, but we can't use * the fact that G.blocked_set is implemented as a bitmask * in libc... */ - mask = (SPECIAL_INTERACTIVE_SIGS >> 1); - sig = 1; - while (1) { + mask = SPECIAL_INTERACTIVE_SIGS; + sig = 0; + while ((mask >>= 1) != 0) { + sig++; if (mask & 1) { /* Careful. Only if no trap or trap is not "" */ if (!G.traps || !G.traps[sig] || G.traps[sig][0]) sigdelset(&G.blocked_set, sig); } - mask >>= 1; - if (!mask) - break; - sig++; } /* Our homegrown sig mask is saner to work with :) */ G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS; @@ -5411,7 +5413,7 @@ static void reset_traps_to_defaults(void) continue; sigdelset(&G.blocked_set, sig); } - sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); + sigprocmask_set(&G.blocked_set); } #else /* !BB_MMU */ @@ -5541,7 +5543,7 @@ static void re_execute_shell(char ***to_free, const char *s, do_exec: debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s); - sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); + sigprocmask_set(&G.inherited_set); execve(bb_busybox_exec_path, argv, pp); /* Fallback. Useful for init=/bin/hush usage etc */ if (argv[0][0] == '/') @@ -6195,7 +6197,7 @@ static void execvp_or_die(char **argv) NORETURN; static void execvp_or_die(char **argv) { debug_printf_exec("execing '%s'\n", argv[0]); - sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); + sigprocmask_set(&G.inherited_set); execvp(argv[0], argv); bb_perror_msg("can't execute '%s'", argv[0]); _exit(127); /* bash compat */ @@ -6327,7 +6329,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, # endif /* Re-exec ourselves */ debug_printf_exec("re-execing applet '%s'\n", argv[0]); - sigprocmask(SIG_SETMASK, &G.inherited_set, NULL); + sigprocmask_set(&G.inherited_set); execv(bb_busybox_exec_path, argv); /* If they called chroot or otherwise made the binary no longer * executable, fall through */ @@ -7435,84 +7437,89 @@ static void init_sigmasks(void) { unsigned sig; unsigned mask; - sigset_t old_blocked_set; + /* POSIX allows shell to re-enable SIGCHLD + * even if it was SIG_IGN on entry */ +#if ENABLE_HUSH_FAST + G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */ +#endif if (!G.inherited_set_is_saved) { +#if ENABLE_HUSH_FAST + signal(SIGCHLD, SIGCHLD_handler); +#else + signal(SIGCHLD, SIG_DFL); +#endif sigprocmask(SIG_SETMASK, NULL, &G.blocked_set); G.inherited_set = G.blocked_set; } - old_blocked_set = G.blocked_set; + /* Which signals are shell-special? */ mask = (1 << SIGQUIT); if (G_interactive_fd) { - mask = (1 << SIGQUIT) | SPECIAL_INTERACTIVE_SIGS; + mask |= SPECIAL_INTERACTIVE_SIGS; if (G_saved_tty_pgrp) /* we have ctty, job control sigs work */ mask |= SPECIAL_JOB_SIGS; } G.non_DFL_mask = mask; + /* Block them. And unblock SIGCHLD */ sig = 0; - while (mask) { + while ((mask >>= 1) != 0) { + sig++; if (mask & 1) sigaddset(&G.blocked_set, sig); - mask >>= 1; - sig++; } sigdelset(&G.blocked_set, SIGCHLD); - if (memcmp(&old_blocked_set, &G.blocked_set, sizeof(old_blocked_set)) != 0) - sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); - - /* POSIX allows shell to re-enable SIGCHLD - * even if it was SIG_IGN on entry */ -#if ENABLE_HUSH_FAST - G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */ - if (!G.inherited_set_is_saved) - signal(SIGCHLD, SIGCHLD_handler); -#else - if (!G.inherited_set_is_saved) - signal(SIGCHLD, SIG_DFL); -#endif + if (memcmp(&G.inherited_set, &G.blocked_set, sizeof(G.inherited_set)) != 0) + sigprocmask_set(&G.blocked_set); G.inherited_set_is_saved = 1; } #if ENABLE_HUSH_JOB /* helper */ -static void maybe_set_to_sigexit(int sig) +/* Set handlers to restore tty pgrp and exit */ +static void set_fatal_handlers_to_sigexit(void) { void (*handler)(int); + unsigned fatal_sigs, sig; + + /* We will restore tty pgrp on these signals */ + fatal_sigs = 0 + + (1 << SIGILL ) * HUSH_DEBUG + + (1 << SIGFPE ) * HUSH_DEBUG + + (1 << SIGBUS ) * HUSH_DEBUG + + (1 << SIGSEGV) * HUSH_DEBUG + + (1 << SIGTRAP) * HUSH_DEBUG + + (1 << SIGABRT) + /* bash 3.2 seems to handle these just like 'fatal' ones */ + + (1 << SIGPIPE) + + (1 << SIGALRM) + /* if we are interactive, SIGHUP, SIGTERM and SIGINT are masked. + * if we aren't interactive... but in this case + * we never want to restore pgrp on exit, and this fn is not called */ + /*+ (1 << SIGHUP )*/ + /*+ (1 << SIGTERM)*/ + /*+ (1 << SIGINT )*/ + ; + /* non_DFL_mask'ed signals are, well, masked, * no need to set handler for them. */ - if (!((G.non_DFL_mask >> sig) & 1)) { + fatal_sigs &= ~G.non_DFL_mask; + + /* For each sig in fatal_sigs... */ + sig = 0; + while ((fatal_sigs >>= 1) != 0) { + sig++; + if (!(fatal_sigs & 1)) + continue; handler = signal(sig, sigexit); if (handler == SIG_IGN) /* oops... restore back to IGN! */ signal(sig, handler); } } -/* Set handlers to restore tty pgrp and exit */ -static void set_fatal_handlers(void) -{ - /* We _must_ restore tty pgrp on fatal signals */ - if (HUSH_DEBUG) { - maybe_set_to_sigexit(SIGILL ); - maybe_set_to_sigexit(SIGFPE ); - maybe_set_to_sigexit(SIGBUS ); - maybe_set_to_sigexit(SIGSEGV); - maybe_set_to_sigexit(SIGTRAP); - } /* else: hush is perfect. what SEGV? */ - maybe_set_to_sigexit(SIGABRT); - /* bash 3.2 seems to handle these just like 'fatal' ones */ - maybe_set_to_sigexit(SIGPIPE); - maybe_set_to_sigexit(SIGALRM); - /* if we are interactive, SIGHUP, SIGTERM and SIGINT are masked. - * if we aren't interactive... but in this case - * we never want to restore pgrp on exit, and this fn is not called */ - /*maybe_set_to_sigexit(SIGHUP );*/ - /*maybe_set_to_sigexit(SIGTERM);*/ - /*maybe_set_to_sigexit(SIGINT );*/ -} #endif static int set_mode(int state, char mode, const char *o_opt) @@ -7769,7 +7776,7 @@ int hush_main(int argc, char **argv) sigaddset(&G.blocked_set, sig); } } - sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); + sigprocmask_set(&G.blocked_set); } # if ENABLE_HUSH_LOOPS optarg++; @@ -7910,7 +7917,7 @@ int hush_main(int argc, char **argv) if (G_saved_tty_pgrp) { /* Set other signals to restore saved_tty_pgrp */ - set_fatal_handlers(); + set_fatal_handlers_to_sigexit(); /* Put ourselves in our own process group * (bash, too, does this only if ctty is available) */ bb_setpgrp(); /* is the same as setpgid(our_pid, our_pid); */ @@ -8301,7 +8308,7 @@ static int FAST_FUNC builtin_trap(char **argv) sigdelset(&G.blocked_set, sig); } } - sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); + sigprocmask_set(&G.blocked_set); return ret; } @@ -8858,7 +8865,7 @@ static int FAST_FUNC builtin_wait(char **argv) * $ */ sigaddset(&G.blocked_set, SIGCHLD); - sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); + sigprocmask_set(&G.blocked_set); while (1) { checkjobs(NULL); if (errno == ECHILD) @@ -8875,7 +8882,7 @@ static int FAST_FUNC builtin_wait(char **argv) } } sigdelset(&G.blocked_set, SIGCHLD); - sigprocmask(SIG_SETMASK, &G.blocked_set, NULL); + sigprocmask_set(&G.blocked_set); return ret; } -- cgit v1.2.3-55-g6feb From bcf1fa80f31468a2299b681dd096d41a0b5ed7f4 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 9 May 2011 01:05:33 +0200 Subject: hush: add tests for interrupting read Signed-off-by: Denys Vlasenko --- shell/hush_test/hush-trap/signal_read1.right | 1 + shell/hush_test/hush-trap/signal_read1.tests | 5 +++++ shell/hush_test/hush-trap/signal_read2.right | 2 ++ shell/hush_test/hush-trap/signal_read2.tests | 7 +++++++ shell/hush_test/run-all | 10 ++++++---- 5 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 shell/hush_test/hush-trap/signal_read1.right create mode 100755 shell/hush_test/hush-trap/signal_read1.tests create mode 100644 shell/hush_test/hush-trap/signal_read2.right create mode 100755 shell/hush_test/hush-trap/signal_read2.tests (limited to 'shell') diff --git a/shell/hush_test/hush-trap/signal_read1.right b/shell/hush_test/hush-trap/signal_read1.right new file mode 100644 index 000000000..2870a8e70 --- /dev/null +++ b/shell/hush_test/hush-trap/signal_read1.right @@ -0,0 +1 @@ +Got HUP:0 diff --git a/shell/hush_test/hush-trap/signal_read1.tests b/shell/hush_test/hush-trap/signal_read1.tests new file mode 100755 index 000000000..1105479a3 --- /dev/null +++ b/shell/hush_test/hush-trap/signal_read1.tests @@ -0,0 +1,5 @@ +(sleep 1; kill -HUP $$) & +trap 'echo "Got HUP:$?"; exit' HUP +while true; do + read ignored +done diff --git a/shell/hush_test/hush-trap/signal_read2.right b/shell/hush_test/hush-trap/signal_read2.right new file mode 100644 index 000000000..71a6bc16d --- /dev/null +++ b/shell/hush_test/hush-trap/signal_read2.right @@ -0,0 +1,2 @@ +HUP +Done:129 diff --git a/shell/hush_test/hush-trap/signal_read2.tests b/shell/hush_test/hush-trap/signal_read2.tests new file mode 100755 index 000000000..eab5b9b5b --- /dev/null +++ b/shell/hush_test/hush-trap/signal_read2.tests @@ -0,0 +1,7 @@ +$THIS_SH -c ' +(sleep 1; kill -HUP $$) & +while true; do + read ignored +done +' +echo "Done:$?" diff --git a/shell/hush_test/run-all b/shell/hush_test/run-all index 256f152dc..64a7abc47 100755 --- a/shell/hush_test/run-all +++ b/shell/hush_test/run-all @@ -48,8 +48,9 @@ do_test() *.orig|*~) ;; #*) echo $x ; sh $x ;; *) + echo -n "$1/$x:" sh "$x" >"../$1-$x.fail" 2>&1 && \ - { echo "$1/$x: ok"; rm "../$1-$x.fail"; } || echo "$1/$x: fail"; + { { echo " ok"; rm "../$1-$x.fail"; } || echo " fail"; } ;; esac done @@ -60,6 +61,7 @@ do_test() name="${x%%.tests}" test -f "$name.right" || continue # echo Running test: "$x" + echo -n "$1/$x:" ( "$THIS_SH" "./$x" >"$name.xx" 2>&1 # filter C library differences @@ -70,9 +72,9 @@ do_test() diff -u "$name.xx" "$name.right" >"../$1-$x.fail" && rm -f "$name.xx" "../$1-$x.fail" ) case $? in - 0) echo "$1/$x: ok";; - 77) echo "$1/$x: skip (feature disabled)";; - *) echo "$1/$x: fail"; tret=1;; + 0) echo " ok";; + 77) echo " skip (feature disabled)";; + *) echo " fail"; tret=1;; esac done exit ${tret} -- cgit v1.2.3-55-g6feb From 10c0131a8a1b3db7fd6b23b72ebd7b33afc7b018 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 May 2011 11:49:21 +0200 Subject: hush: use SA_RESTARTed signal handlers across read. Signed-off-by: Denys Vlasenko --- shell/hush.c | 37 +++++++++++++++++++++---------------- shell/shell_common.c | 34 +++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 29 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 71972f751..509bd415b 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -792,8 +792,13 @@ struct globals { unsigned handled_SIGCHLD; smallint we_have_children; #endif - /* which signals have non-DFL handler (even with no traps set)? */ - unsigned non_DFL_mask; + /* Which signals have non-DFL handler (even with no traps set)? + * Set at the start to: + * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOB_SIGS) + * SPECIAL_INTERACTIVE_SIGS are cleared after fork. + * Other than these two times, never modified. + */ + unsigned special_sig_mask; char **traps; /* char *traps[NSIG] */ /* Signal mask on the entry to the (top-level) shell. Never modified. */ sigset_t inherited_set; @@ -1341,11 +1346,11 @@ static void restore_G_args(save_arg_t *sv, char **argv) * After each pipe execution, we extract any pending signals via sigtimedwait() * and act on them. * - * unsigned non_DFL_mask: a mask of such "special" signals + * unsigned special_sig_mask: a mask of such "special" signals * sigset_t blocked_set: current blocked signal set * * "trap - SIGxxx": - * clear bit in blocked_set unless it is also in non_DFL_mask + * clear bit in blocked_set unless it is also in special_sig_mask * "trap 'cmd' SIGxxx": * set bit in blocked_set (even if 'cmd' is '') * after [v]fork, if we plan to be a shell: @@ -5376,7 +5381,7 @@ static void reset_traps_to_defaults(void) * Testcase: (while :; do :; done) + ^Z should background. * Same goes for SIGTERM, SIGHUP, SIGINT. */ - if (!G.traps && !(G.non_DFL_mask & SPECIAL_INTERACTIVE_SIGS)) + if (!G.traps && !(G.special_sig_mask & SPECIAL_INTERACTIVE_SIGS)) return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */ /* Switching off SPECIAL_INTERACTIVE_SIGS. @@ -5394,10 +5399,10 @@ static void reset_traps_to_defaults(void) } } /* Our homegrown sig mask is saner to work with :) */ - G.non_DFL_mask &= ~SPECIAL_INTERACTIVE_SIGS; + G.special_sig_mask &= ~SPECIAL_INTERACTIVE_SIGS; /* Resetting all traps to default except empty ones */ - mask = G.non_DFL_mask; + mask = G.special_sig_mask; if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) { if (!G.traps[sig] || !G.traps[sig][0]) continue; @@ -7440,9 +7445,6 @@ static void init_sigmasks(void) /* POSIX allows shell to re-enable SIGCHLD * even if it was SIG_IGN on entry */ -#if ENABLE_HUSH_FAST - G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */ -#endif if (!G.inherited_set_is_saved) { #if ENABLE_HUSH_FAST signal(SIGCHLD, SIGCHLD_handler); @@ -7460,7 +7462,7 @@ static void init_sigmasks(void) if (G_saved_tty_pgrp) /* we have ctty, job control sigs work */ mask |= SPECIAL_JOB_SIGS; } - G.non_DFL_mask = mask; + G.special_sig_mask = mask; /* Block them. And unblock SIGCHLD */ sig = 0; @@ -7504,10 +7506,10 @@ static void set_fatal_handlers_to_sigexit(void) /*+ (1 << SIGINT )*/ ; - /* non_DFL_mask'ed signals are, well, masked, + /* special_sig_mask'ed signals are, well, masked, * no need to set handler for them. */ - fatal_sigs &= ~G.non_DFL_mask; + fatal_sigs &= ~G.special_sig_mask; /* For each sig in fatal_sigs... */ sig = 0; @@ -7571,8 +7573,11 @@ int hush_main(int argc, char **argv) struct variable *shell_ver; INIT_G(); - if (EXIT_SUCCESS) /* if EXIT_SUCCESS == 0, it is already done */ + if (EXIT_SUCCESS != 0) /* if EXIT_SUCCESS == 0, it is already done */ G.last_exitcode = EXIT_SUCCESS; +#if ENABLE_HUSH_FAST + G.count_SIGCHLD++; /* ensure it is != G.handled_SIGCHLD */ +#endif #if !BB_MMU G.argv0_for_re_execing = argv[0]; #endif @@ -8303,7 +8308,7 @@ static int FAST_FUNC builtin_trap(char **argv) /* There was a trap handler, we are removing it * (if sig has non-DFL handling, * we don't need to do anything) */ - if (sig < 32 && (G.non_DFL_mask & (1 << sig))) + if (sig < sizeof(G.special_sig_mask)*8 && (G.special_sig_mask & (1 << sig))) continue; sigdelset(&G.blocked_set, sig); } @@ -8565,7 +8570,7 @@ static int FAST_FUNC builtin_read(char **argv) memset(&sa, 0, sizeof(sa)); sigfillset(&sa.sa_mask); - /*sa.sa_flags = 0;*/ + sa.sa_flags = SA_RESTART; sa.sa_handler = record_signal; sig = 0; diff --git a/shell/shell_common.c b/shell/shell_common.c index a5c455c8e..bbc22ed34 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -159,32 +159,40 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), bufpos = 0; do { char c; + struct pollfd pfd[1]; + int timeout; - errno = 0; + if ((bufpos & 0xff) == 0) + buffer = xrealloc(buffer, bufpos + 0x100); + timeout = -1; if (end_ms) { - int timeout; - struct pollfd pfd[1]; - - pfd[0].fd = fd; - pfd[0].events = POLLIN; timeout = end_ms - (unsigned)monotonic_ms(); - if (timeout <= 0 /* already late? */ - || poll(pfd, 1, timeout) != 1 /* no? wait... */ - ) { /* timed out! */ - err = errno; + if (timeout <= 0) { /* already late? */ retval = (const char *)(uintptr_t)1; goto ret; } } - if ((bufpos & 0xff) == 0) - buffer = xrealloc(buffer, bufpos + 0x100); - if (nonblock_immune_read(fd, &buffer[bufpos], 1, /*loop_on_EINTR:*/ 0) != 1) { + /* We must poll even if timeout is -1: + * we want to be interrupted if signal arrives, + * regardless of SA_RESTART-ness of that signal! + */ + errno = 0; + pfd[0].fd = fd; + pfd[0].events = POLLIN; + if (poll(pfd, 1, timeout) != 1) { + /* timed out, or EINTR */ + err = errno; + retval = (const char *)(uintptr_t)1; + goto ret; + } + if (read(fd, &buffer[bufpos], 1) != 1) { err = errno; retval = (const char *)(uintptr_t)1; break; } + c = buffer[bufpos]; if (c == '\0') continue; -- cgit v1.2.3-55-g6feb From 9d6cbafe728c9100a35e29ba7a3729c5303e73ea Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 May 2011 23:56:11 +0200 Subject: hush: replace signal handling machinery With new version of signal handling, read builtin should be less buggy wrt signals. function old new delta install_sighandlers - 121 +121 switch_off_special_sigs - 84 +84 pick_sighandler - 58 +58 install_special_sighandlers - 47 +47 builtin_wait 284 319 +35 record_pending_signo - 21 +21 execvp_or_die 43 48 +5 file_get 290 288 -2 run_list 1004 998 -6 static.zero_timespec 8 - -8 sigprocmask_set 14 - -14 sigwaitinfo 23 - -23 record_signal 23 - -23 __GI_sigwaitinfo 23 - -23 sigtimedwait 25 - -25 builtin_trap 417 392 -25 __GI_sigtimedwait 25 - -25 hush_main 1003 965 -38 check_and_run_traps 263 217 -46 __rt_sigtimedwait 52 - -52 reset_traps_to_defaults 213 126 -87 init_sigmasks 198 - -198 builtin_read 536 197 -339 ------------------------------------------------------------------------------ (add/remove: 5/10 grow/shrink: 2/7 up/down: 371/-934) Total: -563 bytes text data bss dec hex filename 903075 936 17736 921747 e1093 busybox_old 902547 936 17736 921219 e0e83 busybox_unstripped Signed-off-by: Denys Vlasenko --- shell/hush.c | 510 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 278 insertions(+), 232 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 509bd415b..b2c3a752e 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -106,6 +106,10 @@ # define PIPE_BUF 4096 /* amount of buffering in a pipe */ #endif +/* Not every libc has sighandler_t. Fix it */ +typedef void (*hush_sighandler_t)(int); +#define sighandler_t hush_sighandler_t + //config:config HUSH //config: bool "hush" //config: default y @@ -764,7 +768,6 @@ struct globals { smalluint last_exitcode; /* are global_argv and global_argv[1..n] malloced? (note: not [0]) */ smalluint global_args_malloced; - smalluint inherited_set_is_saved; /* how many non-NULL argv's we have. NB: $# + 1 */ int global_argc; char **global_argv; @@ -794,21 +797,20 @@ struct globals { #endif /* Which signals have non-DFL handler (even with no traps set)? * Set at the start to: - * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOB_SIGS) + * (SIGQUIT + maybe SPECIAL_INTERACTIVE_SIGS + maybe SPECIAL_JOBSTOP_SIGS) * SPECIAL_INTERACTIVE_SIGS are cleared after fork. + * The rest is cleared right before execv syscalls. * Other than these two times, never modified. */ unsigned special_sig_mask; +#if ENABLE_HUSH_JOB + unsigned fatal_sig_mask; +#define G_fatal_sig_mask G.fatal_sig_mask +#else +#define G_fatal_sig_mask 0 +#endif char **traps; /* char *traps[NSIG] */ - /* Signal mask on the entry to the (top-level) shell. Never modified. */ - sigset_t inherited_set; - /* Starts equal to inherited_set, - * but shell-special signals are added and SIGCHLD is removed. - * When a trap is set/cleared, signal is added to/removed from it: - */ - sigset_t blocked_set; - /* Used by read() */ - sigset_t detected_set; + sigset_t pending_set; #if HUSH_DEBUG unsigned long memleak_value; int debug_indent; @@ -1337,8 +1339,8 @@ static void restore_G_args(save_arg_t *sv, char **argv) * (What happens to signals which are IGN on shell start?) * (What happens with signal mask on shell start?) * - * Implementation in hush - * ====================== + * Old implementation + * ================== * We use in-kernel pending signal mask to determine which signals were sent. * We block all signals which we don't want to take action immediately, * i.e. we block all signals which need to have special handling as described @@ -1369,6 +1371,49 @@ static void restore_G_args(save_arg_t *sv, char **argv) * Standard says "When a subshell is entered, traps that are not being ignored * are set to the default actions". bash interprets it so that traps which * are set to '' (ignore) are NOT reset to defaults. We do the same. + * + * Problem: the above approach makes it unwieldy to catch signals while + * we are in read builtin, of while we read commands from stdin: + * masked signals are not visible! + * + * New implementation + * ================== + * We record each signal we are interested in by installing signal handler + * for them - a bit like emulating kernel pending signal mask in userspace. + * We are interested in: signals which need to have special handling + * as described above, and all signals which have traps set. + * Signals are rocorded in pending_set. + * After each pipe execution, we extract any pending signals + * and act on them. + * + * unsigned special_sig_mask: a mask of shell-special signals. + * unsigned fatal_sig_mask: a mask of signals on which we restore tty pgrp. + * char *traps[sig] if trap for sig is set (even if it's ''). + * sigset_t pending_set: set of sigs we received. + * + * "trap - SIGxxx": + * if sig is in special_sig_mask, set handler back to: + * record_pending_signo, or to IGN if it's a tty stop signal + * if sig is in fatal_sig_mask, set handler back to sigexit. + * else: set handler back to SIG_DFL + * "trap 'cmd' SIGxxx": + * set handler to record_pending_signo. + * "trap '' SIGxxx": + * set handler to SIG_IGN. + * after [v]fork, if we plan to be a shell: + * set signals with special interactive handling to SIG_DFL + * (because child shell is not interactive), + * unset all traps except '' (note: regardless of child shell's type - {}, (), etc) + * after [v]fork, if we plan to exec: + * POSIX says fork clears pending signal mask in child - no need to clear it. + * + * To make wait builtin interruptible, we handle SIGCHLD as special signal, + * otherwise (if we leave it SIG_DFL) sigsuspend in wait builtin will not wake up on it. + * + * Note (compat): + * Standard says "When a subshell is entered, traps that are not being ignored + * are set to the default actions". bash interprets it so that traps which + * are set to '' (ignore) are NOT reset to defaults. We do the same. */ enum { SPECIAL_INTERACTIVE_SIGS = 0 @@ -1376,26 +1421,25 @@ enum { | (1 << SIGINT) | (1 << SIGHUP) , - SPECIAL_JOB_SIGS = 0 + SPECIAL_JOBSTOP_SIGS = 0 #if ENABLE_HUSH_JOB | (1 << SIGTTIN) | (1 << SIGTTOU) | (1 << SIGTSTP) #endif + , }; -static void sigprocmask_set(sigset_t *set) +static void record_pending_signo(int sig) { - sigprocmask(SIG_SETMASK, set, NULL); -} - + sigaddset(&G.pending_set, sig); #if ENABLE_HUSH_FAST -static void SIGCHLD_handler(int sig UNUSED_PARAM) -{ - G.count_SIGCHLD++; + if (sig == SIGCHLD) { + G.count_SIGCHLD++; //bb_error_msg("[%d] SIGCHLD_handler: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); -} + } #endif +} #if ENABLE_HUSH_JOB @@ -1433,6 +1477,31 @@ static void sigexit(int sig) #endif +static sighandler_t pick_sighandler(unsigned sig) +{ + sighandler_t handler = SIG_DFL; + if (sig < sizeof(unsigned)*8) { + unsigned sigmask = (1 << sig); + +#if ENABLE_HUSH_JOB + /* sig is fatal? */ + if (G_fatal_sig_mask & sigmask) + handler = sigexit; +#endif + /* sig has special handling? */ + else if (G.special_sig_mask & sigmask) + handler = record_pending_signo; + /* TTIN/TTOU/TSTS can't be set to record_pending_signo + * in order to ignore them: they will be raised + * in an endless loop then when we try to do some + * terminal ioctls! We do nave to _ignore_ these. + */ + if (SPECIAL_JOBSTOP_SIGS & sigmask) + handler = SIG_IGN; + } + return handler; +} + /* Restores tty foreground process group, and exits. */ static void hush_exit(int exitcode) NORETURN; static void hush_exit(int exitcode) @@ -1478,39 +1547,30 @@ static void hush_exit(int exitcode) } -static int check_and_run_traps(int sig) +//TODO: return a mask of ALL handled sigs? +static int check_and_run_traps(void) { - /* I want it in rodata, not in bss. - * gcc 4.2.1 puts it in rodata only if it has { 0, 0 } - * initializer. But other compilers may still use bss. - * TODO: find more portable solution. - */ - static const struct timespec zero_timespec = { 0, 0 }; - smalluint save_rcode; int last_sig = 0; - if (sig) - goto got_sig; - while (1) { - if (!sigisemptyset(&G.detected_set)) { - sig = 0; - do { - sig++; - if (sigismember(&G.detected_set, sig)) { - sigdelset(&G.detected_set, sig); - goto got_sig; - } - } while (sig < NSIG); - } + int sig; - sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec); - if (sig <= 0) + if (sigisemptyset(&G.pending_set)) break; + sig = 0; + do { + sig++; + if (sigismember(&G.pending_set, sig)) { + sigdelset(&G.pending_set, sig); + goto got_sig; + } + } while (sig < NSIG); + break; got_sig: if (G.traps && G.traps[sig]) { if (G.traps[sig][0]) { /* We have user-defined handler */ + smalluint save_rcode; char *argv[3]; /* argv[0] is unused */ argv[1] = G.traps[sig]; @@ -1524,12 +1584,6 @@ static int check_and_run_traps(int sig) } /* not a trap: special action */ switch (sig) { -#if ENABLE_HUSH_FAST - case SIGCHLD: - G.count_SIGCHLD++; -//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); - break; -#endif case SIGINT: /* Builtin was ^C'ed, make it look prettier: */ bb_putchar('\n'); @@ -1550,12 +1604,22 @@ static int check_and_run_traps(int sig) } sigexit(SIGHUP); } +#endif +#if ENABLE_HUSH_FAST + case SIGCHLD: + G.count_SIGCHLD++; +//bb_error_msg("[%d] check_and_run_traps: G.count_SIGCHLD:%d G.handled_SIGCHLD:%d", getpid(), G.count_SIGCHLD, G.handled_SIGCHLD); + /* Note: + * We dont do 'last_sig = sig' here -> NOT returning this sig. + * This simplifies wait builtin a bit. + */ + break; #endif default: /* ignored: */ /* SIGTERM, SIGQUIT, SIGTTIN, SIGTTOU, SIGTSTP */ - /* note: - * we dont do 'last_sig = sig' here -> NOT returning this sig. - * example: wait is not interrupted by TERM + /* Note: + * We dont do 'last_sig = sig' here -> NOT returning this sig. + * Example: wait is not interrupted by TERM * in interactive shell, because TERM is ignored. */ break; @@ -1948,7 +2012,7 @@ static void get_user_input(struct in_str *i) * only after . (^C will work) */ r = read_line_input(G.line_input_state, prompt_str, G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, /*timeout*/ -1); /* catch *SIGINT* etc (^C is handled by read_line_input) */ - check_and_run_traps(0); + check_and_run_traps(); } while (r == 0 || G.flag_SIGINT); /* repeat if ^C or SIGINT */ i->eof_flag = (r < 0); if (i->eof_flag) { /* EOF/error detected */ @@ -1964,7 +2028,7 @@ static void get_user_input(struct in_str *i) * $ <[enter], repeatedly...> * Without check_and_run_traps, handler never runs. */ - check_and_run_traps(0); + check_and_run_traps(); fputs(prompt_str, stdout); } fflush_all(); @@ -5368,6 +5432,25 @@ void re_execute_shell(char ***to_free, const char *s, char *g_argv0, char **g_argv, char **builtin_argv) NORETURN; +static void switch_off_special_sigs(unsigned mask) +{ + unsigned sig = 0; + while ((mask >>= 1) != 0) { + sig++; + if (!(mask & 1)) + continue; + if (G.traps) { + if (G.traps[sig] && !G.traps[sig][0]) + /* trap is '', has to remain SIG_IGN */ + continue; + free(G.traps[sig]); + G.traps[sig] = NULL; + } + /* We are here only if no trap or trap was not '' */ + signal(sig, SIG_DFL); + } +} + static void reset_traps_to_defaults(void) { /* This function is always called in a child shell @@ -5381,44 +5464,35 @@ static void reset_traps_to_defaults(void) * Testcase: (while :; do :; done) + ^Z should background. * Same goes for SIGTERM, SIGHUP, SIGINT. */ - if (!G.traps && !(G.special_sig_mask & SPECIAL_INTERACTIVE_SIGS)) - return; /* already no traps and no SPECIAL_INTERACTIVE_SIGS */ - - /* Switching off SPECIAL_INTERACTIVE_SIGS. - * Stupid. It can be done with *single* &= op, but we can't use - * the fact that G.blocked_set is implemented as a bitmask - * in libc... */ - mask = SPECIAL_INTERACTIVE_SIGS; - sig = 0; - while ((mask >>= 1) != 0) { - sig++; - if (mask & 1) { - /* Careful. Only if no trap or trap is not "" */ - if (!G.traps || !G.traps[sig] || G.traps[sig][0]) - sigdelset(&G.blocked_set, sig); - } - } - /* Our homegrown sig mask is saner to work with :) */ + mask = (G.special_sig_mask & SPECIAL_INTERACTIVE_SIGS) | G_fatal_sig_mask; + if (!G.traps && !mask) + return; /* already no traps and no special sigs */ + + /* Switch off special sigs */ + switch_off_special_sigs(mask); +#if ENABLE_HUSH_JOB + G_fatal_sig_mask = 0; +#endif G.special_sig_mask &= ~SPECIAL_INTERACTIVE_SIGS; + /* SIGQUIT and maybe SPECIAL_JOBSTOP_SIGS remain set in G.special_sig_mask */ - /* Resetting all traps to default except empty ones */ - mask = G.special_sig_mask; - if (G.traps) for (sig = 0; sig < NSIG; sig++, mask >>= 1) { - if (!G.traps[sig] || !G.traps[sig][0]) - continue; + if (!G.traps) + return; + + /* Reset all sigs to default except ones with empty traps */ + for (sig = 0; sig < NSIG; sig++) { + if (!G.traps[sig]) + continue; /* no trap: nothing to do */ + if (!G.traps[sig][0]) + continue; /* empty trap: has to remain SIG_IGN */ + /* sig has non-empty trap, reset it: */ free(G.traps[sig]); G.traps[sig] = NULL; - /* There is no signal for 0 (EXIT) */ + /* There is no signal for trap 0 (EXIT) */ if (sig == 0) continue; - /* There was a trap handler, we just removed it. - * But if sig still has non-DFL handling, - * we should not unblock the sig. */ - if (mask & 1) - continue; - sigdelset(&G.blocked_set, sig); + signal(sig, pick_sighandler(sig)); } - sigprocmask_set(&G.blocked_set); } #else /* !BB_MMU */ @@ -5463,6 +5537,7 @@ static void re_execute_shell(char ***to_free, const char *s, for (sig = 1; sig < NSIG; sig++) { if (G.traps[sig] && !G.traps[sig][0]) empty_trap_mask |= 1LL << sig; +///vda: optimize } } @@ -5548,7 +5623,7 @@ static void re_execute_shell(char ***to_free, const char *s, do_exec: debug_printf_exec("re_execute_shell pid:%d cmd:'%s'\n", getpid(), s); - sigprocmask_set(&G.inherited_set); + switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS); execve(bb_busybox_exec_path, argv, pp); /* Fallback. Useful for init=/bin/hush usage etc */ if (argv[0][0] == '/') @@ -6202,7 +6277,7 @@ static void execvp_or_die(char **argv) NORETURN; static void execvp_or_die(char **argv) { debug_printf_exec("execing '%s'\n", argv[0]); - sigprocmask_set(&G.inherited_set); + switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS); execvp(argv[0], argv); bb_perror_msg("can't execute '%s'", argv[0]); _exit(127); /* bash compat */ @@ -6334,7 +6409,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, # endif /* Re-exec ourselves */ debug_printf_exec("re-execing applet '%s'\n", argv[0]); - sigprocmask_set(&G.inherited_set); + switch_off_special_sigs(G.special_sig_mask & SPECIAL_JOBSTOP_SIGS); execv(bb_busybox_exec_path, argv); /* If they called chroot or otherwise made the binary no longer * executable, fall through */ @@ -7033,9 +7108,6 @@ static NOINLINE int run_pipe(struct pipe *pi) if (setup_redirects(command, NULL)) _exit(1); - /* Restore default handlers just prior to exec */ - /*signal(SIGCHLD, SIG_DFL); - so far we don't have any handlers */ - /* Stores to nommu_save list of env vars putenv'ed * (NOMMU, on MMU we don't need that) */ /* cast away volatility... */ @@ -7313,7 +7385,7 @@ static int run_list(struct pipe *pi) * and we don't need to wait for anything. */ G.last_exitcode = rcode; debug_printf_exec(": builtin/func exitcode %d\n", rcode); - check_and_run_traps(0); + check_and_run_traps(); #if ENABLE_HUSH_LOOPS /* Was it "break" or "continue"? */ if (G.flag_break_continue) { @@ -7345,7 +7417,7 @@ static int run_list(struct pipe *pi) /* even bash 3.2 doesn't do that well with nested bg: * try "{ { sleep 10; echo DEEP; } & echo HERE; } &". * I'm NOT treating inner &'s as jobs */ - check_and_run_traps(0); + check_and_run_traps(); #if ENABLE_HUSH_JOB if (G.run_list_level == 1) insert_bg_job(pi); @@ -7360,13 +7432,13 @@ static int run_list(struct pipe *pi) /* Waits for completion, then fg's main shell */ rcode = checkjobs_and_fg_shell(pi); debug_printf_exec(": checkjobs_and_fg_shell exitcode %d\n", rcode); - check_and_run_traps(0); + check_and_run_traps(); } else #endif { /* This one just waits for completion */ rcode = checkjobs(pi); debug_printf_exec(": checkjobs exitcode %d\n", rcode); - check_and_run_traps(0); + check_and_run_traps(); } G.last_exitcode = rcode; } @@ -7437,58 +7509,61 @@ static int run_and_free_list(struct pipe *pi) } +static void install_sighandlers(unsigned mask) +{ + sighandler_t old_handler; + unsigned sig = 0; + while ((mask >>= 1) != 0) { + sig++; + if (!(mask & 1)) + continue; + old_handler = signal(sig, pick_sighandler(sig)); + /* POSIX allows shell to re-enable SIGCHLD + * even if it was SIG_IGN on entry. + * Therefore we skip IGN check for it: + */ + if (sig == SIGCHLD) + continue; + if (old_handler == SIG_IGN) { + /* oops... restore back to IGN, and record this fact */ + signal(sig, old_handler); + if (!G.traps) + G.traps = xzalloc(sizeof(G.traps[0]) * NSIG); + free(G.traps[sig]); + G.traps[sig] = xzalloc(1); /* == xstrdup(""); */ + } + } +} + /* Called a few times only (or even once if "sh -c") */ -static void init_sigmasks(void) +static void install_special_sighandlers(void) { - unsigned sig; unsigned mask; - /* POSIX allows shell to re-enable SIGCHLD - * even if it was SIG_IGN on entry */ - if (!G.inherited_set_is_saved) { -#if ENABLE_HUSH_FAST - signal(SIGCHLD, SIGCHLD_handler); -#else - signal(SIGCHLD, SIG_DFL); -#endif - sigprocmask(SIG_SETMASK, NULL, &G.blocked_set); - G.inherited_set = G.blocked_set; - } + if (G.special_sig_mask != 0) + return; /* Which signals are shell-special? */ - mask = (1 << SIGQUIT); + mask = (1 << SIGQUIT) | (1 << SIGCHLD); if (G_interactive_fd) { mask |= SPECIAL_INTERACTIVE_SIGS; if (G_saved_tty_pgrp) /* we have ctty, job control sigs work */ - mask |= SPECIAL_JOB_SIGS; + mask |= SPECIAL_JOBSTOP_SIGS; } G.special_sig_mask = mask; - /* Block them. And unblock SIGCHLD */ - sig = 0; - while ((mask >>= 1) != 0) { - sig++; - if (mask & 1) - sigaddset(&G.blocked_set, sig); - } - sigdelset(&G.blocked_set, SIGCHLD); - - if (memcmp(&G.inherited_set, &G.blocked_set, sizeof(G.inherited_set)) != 0) - sigprocmask_set(&G.blocked_set); - - G.inherited_set_is_saved = 1; + install_sighandlers(mask); } #if ENABLE_HUSH_JOB /* helper */ /* Set handlers to restore tty pgrp and exit */ -static void set_fatal_handlers_to_sigexit(void) +static void install_fatal_sighandlers(void) { - void (*handler)(int); - unsigned fatal_sigs, sig; + unsigned mask; /* We will restore tty pgrp on these signals */ - fatal_sigs = 0 + mask = 0 + (1 << SIGILL ) * HUSH_DEBUG + (1 << SIGFPE ) * HUSH_DEBUG + (1 << SIGBUS ) * HUSH_DEBUG @@ -7505,22 +7580,13 @@ static void set_fatal_handlers_to_sigexit(void) /*+ (1 << SIGTERM)*/ /*+ (1 << SIGINT )*/ ; - - /* special_sig_mask'ed signals are, well, masked, + /* special_sig_mask'ed signals are set to record_pending_signo * no need to set handler for them. */ - fatal_sigs &= ~G.special_sig_mask; + /*mask &= ~G.special_sig_mask; - they never overlap */ + G_fatal_sig_mask = mask; - /* For each sig in fatal_sigs... */ - sig = 0; - while ((fatal_sigs >>= 1) != 0) { - sig++; - if (!(fatal_sigs & 1)) - continue; - handler = signal(sig, sigexit); - if (handler == SIG_IGN) /* oops... restore back to IGN! */ - signal(sig, handler); - } + install_sighandlers(mask); } #endif @@ -7682,10 +7748,11 @@ int hush_main(int argc, char **argv) } /* Shell is non-interactive at first. We need to call - * init_sigmasks() if we are going to execute "sh