diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2011-05-08 21:23:43 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2011-05-08 21:23:43 +0200 |
commit | 80542bad2f1df9d99b579c9eeb3c2675c14c72c0 (patch) | |
tree | 906cdea5609e0272fda16dc02caa3c683912c228 | |
parent | 80c5b6893d4708b3683ad9a51c990a326a8f1dff (diff) | |
download | busybox-w32-80542bad2f1df9d99b579c9eeb3c2675c14c72c0.tar.gz busybox-w32-80542bad2f1df9d99b579c9eeb3c2675c14c72c0.tar.bz2 busybox-w32-80542bad2f1df9d99b579c9eeb3c2675c14c72c0.zip |
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 <vda.linux@googlemail.com>
-rw-r--r-- | include/libbb.h | 2 | ||||
-rw-r--r-- | libbb/read_printf.c | 9 | ||||
-rw-r--r-- | shell/ash.c | 6 | ||||
-rw-r--r-- | shell/hush.c | 98 | ||||
-rw-r--r-- | shell/shell_common.c | 17 |
5 files changed, 120 insertions, 12 deletions
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 | |||
672 | 672 | ||
673 | 673 | ||
674 | extern ssize_t safe_read(int fd, void *buf, size_t count) FAST_FUNC; | 674 | extern ssize_t safe_read(int fd, void *buf, size_t count) FAST_FUNC; |
675 | extern ssize_t nonblock_immune_read(int fd, void *buf, size_t count) FAST_FUNC; | 675 | extern ssize_t nonblock_immune_read(int fd, void *buf, size_t count, int loop_on_EINTR) FAST_FUNC; |
676 | // NB: will return short read on error, not -1, | 676 | // NB: will return short read on error, not -1, |
677 | // if some data was read before error occurred | 677 | // if some data was read before error occurred |
678 | extern ssize_t full_read(int fd, void *buf, size_t count) FAST_FUNC; | 678 | 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 @@ | |||
55 | * which detects EAGAIN and uses poll() to wait on the fd. | 55 | * which detects EAGAIN and uses poll() to wait on the fd. |
56 | * Thankfully, poll() doesn't care about O_NONBLOCK flag. | 56 | * Thankfully, poll() doesn't care about O_NONBLOCK flag. |
57 | */ | 57 | */ |
58 | ssize_t FAST_FUNC nonblock_immune_read(int fd, void *buf, size_t count) | 58 | ssize_t FAST_FUNC nonblock_immune_read(int fd, void *buf, size_t count, int loop_on_EINTR) |
59 | { | 59 | { |
60 | struct pollfd pfd[1]; | 60 | struct pollfd pfd[1]; |
61 | ssize_t n; | 61 | ssize_t n; |
62 | 62 | ||
63 | while (1) { | 63 | while (1) { |
64 | n = safe_read(fd, buf, count); | 64 | n = loop_on_EINTR ? safe_read(fd, buf, count) : read(fd, buf, count); |
65 | if (n >= 0 || errno != EAGAIN) | 65 | if (n >= 0 || errno != EAGAIN) |
66 | return n; | 66 | return n; |
67 | /* fd is in O_NONBLOCK mode. Wait using poll and repeat */ | 67 | /* fd is in O_NONBLOCK mode. Wait using poll and repeat */ |
68 | pfd[0].fd = fd; | 68 | pfd[0].fd = fd; |
69 | pfd[0].events = POLLIN; | 69 | pfd[0].events = POLLIN; |
70 | safe_poll(pfd, 1, -1); /* note: this pulls in printf */ | 70 | /* note: safe_poll pulls in printf */ |
71 | loop_on_EINTR ? safe_poll(pfd, 1, -1) : poll(pfd, 1, -1); | ||
71 | } | 72 | } |
72 | } | 73 | } |
73 | 74 | ||
@@ -90,7 +91,7 @@ char* FAST_FUNC xmalloc_reads(int fd, size_t *maxsz_p) | |||
90 | p = buf + sz; | 91 | p = buf + sz; |
91 | sz += 128; | 92 | sz += 128; |
92 | } | 93 | } |
93 | if (nonblock_immune_read(fd, p, 1) != 1) { | 94 | if (nonblock_immune_read(fd, p, 1, /*loop_on_EINTR:*/ 1) != 1) { |
94 | /* EOF/error */ | 95 | /* EOF/error */ |
95 | if (p == buf) { /* we read nothing */ | 96 | if (p == buf) { /* we read nothing */ |
96 | free(buf); | 97 | 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) | |||
5918 | read: | 5918 | read: |
5919 | if (in.fd < 0) | 5919 | if (in.fd < 0) |
5920 | break; | 5920 | break; |
5921 | i = nonblock_immune_read(in.fd, buf, sizeof(buf)); | 5921 | i = nonblock_immune_read(in.fd, buf, sizeof(buf), /*loop_on_EINTR:*/ 1); |
5922 | TRACE(("expbackq: read returns %d\n", i)); | 5922 | TRACE(("expbackq: read returns %d\n", i)); |
5923 | if (i <= 0) | 5923 | if (i <= 0) |
5924 | break; | 5924 | break; |
@@ -9617,7 +9617,7 @@ preadfd(void) | |||
9617 | #if ENABLE_FEATURE_EDITING | 9617 | #if ENABLE_FEATURE_EDITING |
9618 | retry: | 9618 | retry: |
9619 | if (!iflag || g_parsefile->pf_fd != STDIN_FILENO) | 9619 | if (!iflag || g_parsefile->pf_fd != STDIN_FILENO) |
9620 | nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); | 9620 | nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1, /*loop_on_EINTR:*/ 1); |
9621 | else { | 9621 | else { |
9622 | int timeout = -1; | 9622 | int timeout = -1; |
9623 | # if ENABLE_ASH_IDLE_TIMEOUT | 9623 | # if ENABLE_ASH_IDLE_TIMEOUT |
@@ -9663,7 +9663,7 @@ preadfd(void) | |||
9663 | } | 9663 | } |
9664 | } | 9664 | } |
9665 | #else | 9665 | #else |
9666 | nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); | 9666 | nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1, /*loop_on_EINTR:*/ 1); |
9667 | #endif | 9667 | #endif |
9668 | 9668 | ||
9669 | #if 0 /* disabled: nonblock_immune_read() handles this problem */ | 9669 | #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 { | |||
795 | /* which signals have non-DFL handler (even with no traps set)? */ | 795 | /* which signals have non-DFL handler (even with no traps set)? */ |
796 | unsigned non_DFL_mask; | 796 | unsigned non_DFL_mask; |
797 | char **traps; /* char *traps[NSIG] */ | 797 | char **traps; /* char *traps[NSIG] */ |
798 | sigset_t blocked_set; | 798 | /* Signal mask on the entry to the (top-level) shell. Never modified. */ |
799 | sigset_t inherited_set; | 799 | sigset_t inherited_set; |
800 | /* Starts equal to inherited_set, | ||
801 | * but shell-special signals are added and SIGCHLD is removed. | ||
802 | * When a trap is set/cleared, signal is added to/removed from it: | ||
803 | */ | ||
804 | sigset_t blocked_set; | ||
805 | /* Used by read() */ | ||
806 | sigset_t detected_set; | ||
800 | #if HUSH_DEBUG | 807 | #if HUSH_DEBUG |
801 | unsigned long memleak_value; | 808 | unsigned long memleak_value; |
802 | int debug_indent; | 809 | int debug_indent; |
@@ -1476,6 +1483,17 @@ static int check_and_run_traps(int sig) | |||
1476 | goto got_sig; | 1483 | goto got_sig; |
1477 | 1484 | ||
1478 | while (1) { | 1485 | while (1) { |
1486 | if (!sigisemptyset(&G.detected_set)) { | ||
1487 | sig = 0; | ||
1488 | do { | ||
1489 | sig++; | ||
1490 | if (sigismember(&G.detected_set, sig)) { | ||
1491 | sigdelset(&G.detected_set, sig); | ||
1492 | goto got_sig; | ||
1493 | } | ||
1494 | } while (sig < NSIG); | ||
1495 | } | ||
1496 | |||
1479 | sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec); | 1497 | sig = sigtimedwait(&G.blocked_set, NULL, &zero_timespec); |
1480 | if (sig <= 0) | 1498 | if (sig <= 0) |
1481 | break; | 1499 | break; |
@@ -8484,6 +8502,32 @@ static int FAST_FUNC builtin_pwd(char **argv UNUSED_PARAM) | |||
8484 | return EXIT_SUCCESS; | 8502 | return EXIT_SUCCESS; |
8485 | } | 8503 | } |
8486 | 8504 | ||
8505 | /* Interruptibility of read builtin in bash | ||
8506 | * (tested on bash-4.2.8 by sending signals (not by ^C)): | ||
8507 | * | ||
8508 | * Empty trap makes read ignore corresponding signal, for any signal. | ||
8509 | * | ||
8510 | * SIGINT: | ||
8511 | * - terminates non-interactive shell; | ||
8512 | * - interrupts read in interactive shell; | ||
8513 | * if it has non-empty trap: | ||
8514 | * - executes trap and returns to command prompt in interactive shell; | ||
8515 | * - executes trap and returns to read in non-interactive shell; | ||
8516 | * SIGTERM: | ||
8517 | * - is ignored (does not interrupt) read in interactive shell; | ||
8518 | * - terminates non-interactive shell; | ||
8519 | * if it has non-empty trap: | ||
8520 | * - executes trap and returns to read; | ||
8521 | * SIGHUP: | ||
8522 | * - terminates shell (regardless of interactivity); | ||
8523 | * if it has non-empty trap: | ||
8524 | * - executes trap and returns to read; | ||
8525 | */ | ||
8526 | /* helper */ | ||
8527 | static void record_signal(int sig) | ||
8528 | { | ||
8529 | sigaddset(&G.detected_set, sig); | ||
8530 | } | ||
8487 | static int FAST_FUNC builtin_read(char **argv) | 8531 | static int FAST_FUNC builtin_read(char **argv) |
8488 | { | 8532 | { |
8489 | const char *r; | 8533 | const char *r; |
@@ -8491,7 +8535,9 @@ static int FAST_FUNC builtin_read(char **argv) | |||
8491 | char *opt_p = NULL; | 8535 | char *opt_p = NULL; |
8492 | char *opt_t = NULL; | 8536 | char *opt_t = NULL; |
8493 | char *opt_u = NULL; | 8537 | char *opt_u = NULL; |
8538 | const char *ifs; | ||
8494 | int read_flags; | 8539 | int read_flags; |
8540 | sigset_t saved_blkd_set; | ||
8495 | 8541 | ||
8496 | /* "!": do not abort on errors. | 8542 | /* "!": do not abort on errors. |
8497 | * Option string must start with "sr" to match BUILTIN_READ_xxx | 8543 | * Option string must start with "sr" to match BUILTIN_READ_xxx |
@@ -8500,10 +8546,47 @@ static int FAST_FUNC builtin_read(char **argv) | |||
8500 | if (read_flags == (uint32_t)-1) | 8546 | if (read_flags == (uint32_t)-1) |
8501 | return EXIT_FAILURE; | 8547 | return EXIT_FAILURE; |
8502 | argv += optind; | 8548 | argv += optind; |
8549 | ifs = get_local_var_value("IFS"); /* can be NULL */ | ||
8550 | |||
8551 | again: | ||
8552 | /* We need to temporarily unblock and record signals around read */ | ||
8553 | |||
8554 | saved_blkd_set = G.blocked_set; | ||
8555 | { | ||
8556 | unsigned sig; | ||
8557 | struct sigaction sa, old_sa; | ||
8558 | |||
8559 | memset(&sa, 0, sizeof(sa)); | ||
8560 | sigfillset(&sa.sa_mask); | ||
8561 | /*sa.sa_flags = 0;*/ | ||
8562 | sa.sa_handler = record_signal; | ||
8563 | |||
8564 | sig = 0; | ||
8565 | do { | ||
8566 | sig++; | ||
8567 | if (sigismember(&G.blocked_set, sig)) { | ||
8568 | char *sig_trap = (G.traps && G.traps[sig]) ? G.traps[sig] : NULL; | ||
8569 | /* If has a nonempty trap... */ | ||
8570 | if ((sig_trap && sig_trap[0]) | ||
8571 | /* ...or has no trap and is SIGINT or SIGHUP */ | ||
8572 | || (!sig_trap && (sig == SIGINT || sig == SIGHUP)) | ||
8573 | ) { | ||
8574 | sigaction(sig, &sa, &old_sa); | ||
8575 | if (old_sa.sa_handler == SIG_IGN) /* oops... restore back to IGN! */ | ||
8576 | sigaction_set(sig, &old_sa); | ||
8577 | else | ||
8578 | sigdelset(&G.blocked_set, sig); | ||
8579 | } | ||
8580 | } | ||
8581 | } while (sig < NSIG-1); | ||
8582 | } | ||
8583 | |||
8584 | if (memcmp(&saved_blkd_set, &G.blocked_set, sizeof(saved_blkd_set)) != 0) | ||
8585 | sigprocmask_set(&G.blocked_set); | ||
8503 | 8586 | ||
8504 | r = shell_builtin_read(set_local_var_from_halves, | 8587 | r = shell_builtin_read(set_local_var_from_halves, |
8505 | argv, | 8588 | argv, |
8506 | get_local_var_value("IFS"), /* can be NULL */ | 8589 | ifs, |
8507 | read_flags, | 8590 | read_flags, |
8508 | opt_n, | 8591 | opt_n, |
8509 | opt_p, | 8592 | opt_p, |
@@ -8511,6 +8594,17 @@ static int FAST_FUNC builtin_read(char **argv) | |||
8511 | opt_u | 8594 | opt_u |
8512 | ); | 8595 | ); |
8513 | 8596 | ||
8597 | if (memcmp(&saved_blkd_set, &G.blocked_set, sizeof(saved_blkd_set)) != 0) { | ||
8598 | G.blocked_set = saved_blkd_set; | ||
8599 | sigprocmask_set(&G.blocked_set); | ||
8600 | } | ||
8601 | |||
8602 | if ((uintptr_t)r == 1 && errno == EINTR) { | ||
8603 | unsigned sig = check_and_run_traps(0); | ||
8604 | if (sig && sig != SIGINT) | ||
8605 | goto again; | ||
8606 | } | ||
8607 | |||
8514 | if ((uintptr_t)r > 1) { | 8608 | if ((uintptr_t)r > 1) { |
8515 | bb_error_msg("%s", r); | 8609 | bb_error_msg("%s", r); |
8516 | r = (char*)(uintptr_t)1; | 8610 | 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) | |||
36 | 36 | ||
37 | /* read builtin */ | 37 | /* read builtin */ |
38 | 38 | ||
39 | /* Needs to be interruptible: shell mush handle traps and shell-special signals | ||
40 | * while inside read. To implement this, be sure to not loop on EINTR | ||
41 | * and return errno == EINTR reliably. | ||
42 | */ | ||
39 | //TODO: use more efficient setvar() which takes a pointer to malloced "VAR=VAL" | 43 | //TODO: use more efficient setvar() which takes a pointer to malloced "VAR=VAL" |
40 | //string. hush naturally has it, and ash has setvareq(). | 44 | //string. hush naturally has it, and ash has setvareq(). |
41 | //Here we can simply store "VAR=" at buffer start and store read data directly | 45 | //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), | |||
51 | const char *opt_u | 55 | const char *opt_u |
52 | ) | 56 | ) |
53 | { | 57 | { |
58 | unsigned err; | ||
54 | unsigned end_ms; /* -t TIMEOUT */ | 59 | unsigned end_ms; /* -t TIMEOUT */ |
55 | int fd; /* -u FD */ | 60 | int fd; /* -u FD */ |
56 | int nchars; /* -n NUM */ | 61 | int nchars; /* -n NUM */ |
@@ -62,6 +67,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
62 | int startword; | 67 | int startword; |
63 | smallint backslash; | 68 | smallint backslash; |
64 | 69 | ||
70 | errno = err = 0; | ||
71 | |||
65 | pp = argv; | 72 | pp = argv; |
66 | while (*pp) { | 73 | while (*pp) { |
67 | if (!is_well_formed_var_name(*pp, '\0')) { | 74 | 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), | |||
153 | do { | 160 | do { |
154 | char c; | 161 | char c; |
155 | 162 | ||
163 | errno = 0; | ||
164 | |||
156 | if (end_ms) { | 165 | if (end_ms) { |
157 | int timeout; | 166 | int timeout; |
158 | struct pollfd pfd[1]; | 167 | struct pollfd pfd[1]; |
@@ -161,8 +170,9 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
161 | pfd[0].events = POLLIN; | 170 | pfd[0].events = POLLIN; |
162 | timeout = end_ms - (unsigned)monotonic_ms(); | 171 | timeout = end_ms - (unsigned)monotonic_ms(); |
163 | if (timeout <= 0 /* already late? */ | 172 | if (timeout <= 0 /* already late? */ |
164 | || safe_poll(pfd, 1, timeout) != 1 /* no? wait... */ | 173 | || poll(pfd, 1, timeout) != 1 /* no? wait... */ |
165 | ) { /* timed out! */ | 174 | ) { /* timed out! */ |
175 | err = errno; | ||
166 | retval = (const char *)(uintptr_t)1; | 176 | retval = (const char *)(uintptr_t)1; |
167 | goto ret; | 177 | goto ret; |
168 | } | 178 | } |
@@ -170,7 +180,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
170 | 180 | ||
171 | if ((bufpos & 0xff) == 0) | 181 | if ((bufpos & 0xff) == 0) |
172 | buffer = xrealloc(buffer, bufpos + 0x100); | 182 | buffer = xrealloc(buffer, bufpos + 0x100); |
173 | if (nonblock_immune_read(fd, &buffer[bufpos], 1) != 1) { | 183 | if (nonblock_immune_read(fd, &buffer[bufpos], 1, /*loop_on_EINTR:*/ 0) != 1) { |
184 | err = errno; | ||
174 | retval = (const char *)(uintptr_t)1; | 185 | retval = (const char *)(uintptr_t)1; |
175 | break; | 186 | break; |
176 | } | 187 | } |
@@ -240,6 +251,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
240 | free(buffer); | 251 | free(buffer); |
241 | if (read_flags & BUILTIN_READ_SILENT) | 252 | if (read_flags & BUILTIN_READ_SILENT) |
242 | tcsetattr(fd, TCSANOW, &old_tty); | 253 | tcsetattr(fd, TCSANOW, &old_tty); |
254 | |||
255 | errno = err; | ||
243 | return retval; | 256 | return retval; |
244 | } | 257 | } |
245 | 258 | ||