aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2022-01-17 03:02:40 +0100
committerDenys Vlasenko <vda.linux@googlemail.com>2022-01-17 11:46:23 +0100
commit12566e7f9b5e5c5d445bc4d36991d134b431dc6c (patch)
tree2571356a77f7d421da368e9b31dad182e83b2408
parenta277506a64404e6c4472ff89c944c4f353db1c33 (diff)
downloadbusybox-w32-12566e7f9b5e5c5d445bc4d36991d134b431dc6c.tar.gz
busybox-w32-12566e7f9b5e5c5d445bc4d36991d134b431dc6c.tar.bz2
busybox-w32-12566e7f9b5e5c5d445bc4d36991d134b431dc6c.zip
ash,hush: fix handling of SIGINT while waiting for interactive input
function old new delta lineedit_read_key 160 237 +77 __pgetc 522 589 +67 fgetc_interactive 244 309 +65 safe_read_key - 39 +39 read_key 588 607 +19 record_pending_signo 23 32 +9 signal_handler 75 81 +6 .rodata 104312 104309 -3 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 6/1 up/down: 282/-3) Total: 279 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--editors/vi.c4
-rw-r--r--include/libbb.h5
-rw-r--r--libbb/lineedit.c24
-rw-r--r--libbb/read_key.c16
-rw-r--r--miscutils/hexedit.c2
-rw-r--r--miscutils/less.c4
-rw-r--r--procps/top.c2
-rw-r--r--shell/ash.c39
-rw-r--r--shell/hush.c67
9 files changed, 122 insertions, 41 deletions
diff --git a/editors/vi.c b/editors/vi.c
index 3dbe5b471..d37cd48a3 100644
--- a/editors/vi.c
+++ b/editors/vi.c
@@ -1122,7 +1122,7 @@ static int readit(void) // read (maybe cursor) key from stdin
1122 // on nonblocking stdin. 1122 // on nonblocking stdin.
1123 // Note: read_key sets errno to 0 on success. 1123 // Note: read_key sets errno to 0 on success.
1124 again: 1124 again:
1125 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1); 1125 c = safe_read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
1126 if (c == -1) { // EOF/error 1126 if (c == -1) { // EOF/error
1127 if (errno == EAGAIN) // paranoia 1127 if (errno == EAGAIN) // paranoia
1128 goto again; 1128 goto again;
@@ -4770,7 +4770,7 @@ static void edit_file(char *fn)
4770 uint64_t k; 4770 uint64_t k;
4771 write1(ESC"[999;999H" ESC"[6n"); 4771 write1(ESC"[999;999H" ESC"[6n");
4772 fflush_all(); 4772 fflush_all();
4773 k = read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100); 4773 k = safe_read_key(STDIN_FILENO, readbuffer, /*timeout_ms:*/ 100);
4774 if ((int32_t)k == KEYCODE_CURSOR_POS) { 4774 if ((int32_t)k == KEYCODE_CURSOR_POS) {
4775 uint32_t rc = (k >> 32); 4775 uint32_t rc = (k >> 32);
4776 columns = (rc & 0x7fff); 4776 columns = (rc & 0x7fff);
diff --git a/include/libbb.h b/include/libbb.h
index 91b456915..b45ce91c5 100644
--- a/include/libbb.h
+++ b/include/libbb.h
@@ -1908,6 +1908,8 @@ enum {
1908 * >=0: poll() for TIMEOUT milliseconds, return -1/EAGAIN on timeout 1908 * >=0: poll() for TIMEOUT milliseconds, return -1/EAGAIN on timeout
1909 */ 1909 */
1910int64_t read_key(int fd, char *buffer, int timeout) FAST_FUNC; 1910int64_t read_key(int fd, char *buffer, int timeout) FAST_FUNC;
1911/* This version loops on EINTR: */
1912int64_t safe_read_key(int fd, char *buffer, int timeout) FAST_FUNC;
1911void read_key_ungets(char *buffer, const char *str, unsigned len) FAST_FUNC; 1913void read_key_ungets(char *buffer, const char *str, unsigned len) FAST_FUNC;
1912 1914
1913 1915
@@ -1961,7 +1963,8 @@ enum {
1961 USERNAME_COMPLETION = 4 * ENABLE_FEATURE_USERNAME_COMPLETION, 1963 USERNAME_COMPLETION = 4 * ENABLE_FEATURE_USERNAME_COMPLETION,
1962 VI_MODE = 8 * ENABLE_FEATURE_EDITING_VI, 1964 VI_MODE = 8 * ENABLE_FEATURE_EDITING_VI,
1963 WITH_PATH_LOOKUP = 0x10, 1965 WITH_PATH_LOOKUP = 0x10,
1964 FOR_SHELL = DO_HISTORY | TAB_COMPLETION | USERNAME_COMPLETION, 1966 LI_INTERRUPTIBLE = 0x20,
1967 FOR_SHELL = DO_HISTORY | TAB_COMPLETION | USERNAME_COMPLETION | LI_INTERRUPTIBLE,
1965}; 1968};
1966line_input_t *new_line_input_t(int flags) FAST_FUNC; 1969line_input_t *new_line_input_t(int flags) FAST_FUNC;
1967#if ENABLE_FEATURE_EDITING_SAVEHISTORY 1970#if ENABLE_FEATURE_EDITING_SAVEHISTORY
diff --git a/libbb/lineedit.c b/libbb/lineedit.c
index e14c78707..f76afd37d 100644
--- a/libbb/lineedit.c
+++ b/libbb/lineedit.c
@@ -2161,12 +2161,30 @@ static int lineedit_read_key(char *read_key_buffer, int timeout)
2161 * insist on full MB_CUR_MAX buffer to declare input like 2161 * insist on full MB_CUR_MAX buffer to declare input like
2162 * "\xff\n",pause,"ls\n" invalid and thus won't lose "ls". 2162 * "\xff\n",pause,"ls\n" invalid and thus won't lose "ls".
2163 * 2163 *
2164 * If LI_INTERRUPTIBLE, return -1 if got EINTR in poll()
2165 * inside read_key, or if bb_got_signal != 0 (IOW: if signal
2166 * arrived before poll() is reached).
2167 *
2164 * Note: read_key sets errno to 0 on success. 2168 * Note: read_key sets errno to 0 on success.
2165 */ 2169 */
2166 IF_FEATURE_EDITING_WINCH(S.ok_to_redraw = 1;) 2170 do {
2167 ic = read_key(STDIN_FILENO, read_key_buffer, timeout); 2171 if ((state->flags & LI_INTERRUPTIBLE) && bb_got_signal) {
2168 IF_FEATURE_EDITING_WINCH(S.ok_to_redraw = 0;) 2172 errno = EINTR;
2173 return -1;
2174 }
2175//FIXME: still races here with signals, but small window to poll() inside read_key
2176 IF_FEATURE_EDITING_WINCH(S.ok_to_redraw = 1;)
2177 ic = read_key(STDIN_FILENO, read_key_buffer, timeout);
2178 IF_FEATURE_EDITING_WINCH(S.ok_to_redraw = 0;)
2179 } while (!(state->flags & LI_INTERRUPTIBLE) && errno == EINTR);
2180
2169 if (errno) { 2181 if (errno) {
2182 /* LI_INTERRUPTIBLE can bail out with EINTR here,
2183 * but nothing really guarantees that bb_got_signal
2184 * is nonzero. Follow the least surprise principle:
2185 */
2186 if (errno == EINTR && bb_got_signal == 0)
2187 bb_got_signal = 255; /* something nonzero */
2170#if ENABLE_UNICODE_SUPPORT 2188#if ENABLE_UNICODE_SUPPORT
2171 if (errno == EAGAIN && unicode_idx != 0) 2189 if (errno == EAGAIN && unicode_idx != 0)
2172 goto pushback; 2190 goto pushback;
diff --git a/libbb/read_key.c b/libbb/read_key.c
index 03b7da656..829ae215c 100644
--- a/libbb/read_key.c
+++ b/libbb/read_key.c
@@ -126,7 +126,10 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout)
126 * if fd can be in non-blocking mode. 126 * if fd can be in non-blocking mode.
127 */ 127 */
128 if (timeout >= -1) { 128 if (timeout >= -1) {
129 if (safe_poll(&pfd, 1, timeout) == 0) { 129 n = poll(&pfd, 1, timeout);
130 if (n < 0 && errno == EINTR)
131 return n;
132 if (n == 0) {
130 /* Timed out */ 133 /* Timed out */
131 errno = EAGAIN; 134 errno = EAGAIN;
132 return -1; 135 return -1;
@@ -138,7 +141,7 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout)
138 * When we were reading 3 bytes here, we were eating 141 * When we were reading 3 bytes here, we were eating
139 * "li" too, and cat was getting wrong input. 142 * "li" too, and cat was getting wrong input.
140 */ 143 */
141 n = safe_read(fd, buffer, 1); 144 n = read(fd, buffer, 1);
142 if (n <= 0) 145 if (n <= 0)
143 return -1; 146 return -1;
144 } 147 }
@@ -284,6 +287,15 @@ int64_t FAST_FUNC read_key(int fd, char *buffer, int timeout)
284 goto start_over; 287 goto start_over;
285} 288}
286 289
290int64_t FAST_FUNC safe_read_key(int fd, char *buffer, int timeout)
291{
292 int64_t r;
293 do {
294 r = read_key(fd, buffer, timeout);
295 } while (errno == EINTR);
296 return r;
297}
298
287void FAST_FUNC read_key_ungets(char *buffer, const char *str, unsigned len) 299void FAST_FUNC read_key_ungets(char *buffer, const char *str, unsigned len)
288{ 300{
289 unsigned cur_len = (unsigned char)buffer[0]; 301 unsigned cur_len = (unsigned char)buffer[0];
diff --git a/miscutils/hexedit.c b/miscutils/hexedit.c
index f8ff9b62b..15ad78377 100644
--- a/miscutils/hexedit.c
+++ b/miscutils/hexedit.c
@@ -292,7 +292,7 @@ int hexedit_main(int argc UNUSED_PARAM, char **argv)
292 fflush_all(); 292 fflush_all();
293 G.in_read_key = 1; 293 G.in_read_key = 1;
294 if (!bb_got_signal) 294 if (!bb_got_signal)
295 key = read_key(STDIN_FILENO, G.read_key_buffer, -1); 295 key = safe_read_key(STDIN_FILENO, G.read_key_buffer, -1);
296 G.in_read_key = 0; 296 G.in_read_key = 0;
297 if (bb_got_signal) 297 if (bb_got_signal)
298 key = CTRL('X'); 298 key = CTRL('X');
diff --git a/miscutils/less.c b/miscutils/less.c
index 82c4b21f0..8a0525cb7 100644
--- a/miscutils/less.c
+++ b/miscutils/less.c
@@ -1137,9 +1137,9 @@ static int64_t getch_nowait(void)
1137#endif 1137#endif
1138 } 1138 }
1139 1139
1140 /* We have kbd_fd in O_NONBLOCK mode, read inside read_key() 1140 /* We have kbd_fd in O_NONBLOCK mode, read inside safe_read_key()
1141 * would not block even if there is no input available */ 1141 * would not block even if there is no input available */
1142 key64 = read_key(kbd_fd, kbd_input, /*timeout off:*/ -2); 1142 key64 = safe_read_key(kbd_fd, kbd_input, /*timeout off:*/ -2);
1143 if ((int)key64 == -1) { 1143 if ((int)key64 == -1) {
1144 if (errno == EAGAIN) { 1144 if (errno == EAGAIN) {
1145 /* No keyboard input available. Since poll() did return, 1145 /* No keyboard input available. Since poll() did return,
diff --git a/procps/top.c b/procps/top.c
index 4cd545c69..804d6f258 100644
--- a/procps/top.c
+++ b/procps/top.c
@@ -913,7 +913,7 @@ static unsigned handle_input(unsigned scan_mask, duration_t interval)
913 while (1) { 913 while (1) {
914 int32_t c; 914 int32_t c;
915 915
916 c = read_key(STDIN_FILENO, G.kbd_input, interval * 1000); 916 c = safe_read_key(STDIN_FILENO, G.kbd_input, interval * 1000);
917 if (c == -1 && errno != EAGAIN) { 917 if (c == -1 && errno != EAGAIN) {
918 /* error/EOF */ 918 /* error/EOF */
919 option_mask32 |= OPT_EOF; 919 option_mask32 |= OPT_EOF;
diff --git a/shell/ash.c b/shell/ash.c
index 086773dd7..55df54bd0 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -3679,7 +3679,9 @@ signal_handler(int signo)
3679 if (!trap[SIGCHLD]) 3679 if (!trap[SIGCHLD])
3680 return; 3680 return;
3681 } 3681 }
3682 3682#if ENABLE_FEATURE_EDITING
3683 bb_got_signal = signo; /* for read_line_input: "we got a signal" */
3684#endif
3683 gotsig[signo - 1] = 1; 3685 gotsig[signo - 1] = 1;
3684 pending_sig = signo; 3686 pending_sig = signo;
3685 3687
@@ -10784,33 +10786,52 @@ preadfd(void)
10784# endif 10786# endif
10785 reinit_unicode_for_ash(); 10787 reinit_unicode_for_ash();
10786 again: 10788 again:
10787//BUG: not in INT_OFF/INT_ON section - SIGINT et al would longjmp out of read_line_input()! 10789 /* For shell, LI_INTERRUPTIBLE is set:
10788//This would cause a memory leak in interactive shell 10790 * read_line_input will abort on either
10789//(repeated internal allocations in read_line_input): 10791 * getting EINTR in poll(), or if it sees bb_got_signal != 0
10790// (while kill -INT $$; do :; done) & 10792 * (IOW: if signal arrives before poll() is reached).
10793 * Interactive testcases:
10794 * (while kill -INT $$; do sleep 1; done) &
10795 * #^^^ prints ^C, prints prompt, repeats
10796 * trap 'echo I' int; (while kill -INT $$; do sleep 1; done) &
10797 * #^^^ prints ^C, prints "I", prints prompt, repeats
10798 * trap 'echo T' term; (while kill $$; do sleep 1; done) &
10799 * #^^^ prints "T", prints prompt, repeats
10800 * #(bash 5.0.17 exits after first "T", looks like a bug)
10801 */
10802 bb_got_signal = 0;
10803 INT_OFF; /* no longjmp'ing out of read_line_input please */
10791 nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ); 10804 nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ);
10805 if (bb_got_signal == SIGINT)
10806 write(STDOUT_FILENO, "^C\n", 3);
10807 INT_ON; /* here non-blocked SIGINT will longjmp */
10792 if (nr == 0) { 10808 if (nr == 0) {
10793 /* ^C pressed, "convert" to SIGINT */ 10809 /* ^C pressed, "convert" to SIGINT */
10794 write(STDOUT_FILENO, "^C", 2); 10810 write(STDOUT_FILENO, "^C\n", 3);
10795 raise(SIGINT); 10811 raise(SIGINT); /* here non-blocked SIGINT will longjmp */
10796 /* raise(SIGINT) did not work! (e.g. if SIGINT 10812 /* raise(SIGINT) did not work! (e.g. if SIGINT
10797 * is SIG_IGNed on startup, it stays SIG_IGNed) 10813 * is SIG_IGNed on startup, it stays SIG_IGNed)
10798 */ 10814 */
10799 if (trap[SIGINT]) { 10815 if (trap[SIGINT]) {
10816 empty_line_input:
10800 buf[0] = '\n'; 10817 buf[0] = '\n';
10801 buf[1] = '\0'; 10818 buf[1] = '\0';
10802 return 1; 10819 return 1;
10803 } 10820 }
10804 exitstatus = 128 + SIGINT; 10821 exitstatus = 128 + SIGINT;
10805 /* bash behavior on ^C + ignored SIGINT: */ 10822 /* bash behavior on ^C + ignored SIGINT: */
10806 write(STDOUT_FILENO, "\n", 1);
10807 goto again; 10823 goto again;
10808 } 10824 }
10809 if (nr < 0) { 10825 if (nr < 0) {
10810 if (errno == 0) { 10826 if (errno == 0) {
10811 /* Ctrl+D pressed */ 10827 /* ^D pressed */
10812 nr = 0; 10828 nr = 0;
10813 } 10829 }
10830 else if (errno == EINTR) { /* got signal? */
10831 if (bb_got_signal != SIGINT)
10832 write(STDOUT_FILENO, "\n", 1);
10833 goto empty_line_input;
10834 }
10814# if ENABLE_ASH_IDLE_TIMEOUT 10835# if ENABLE_ASH_IDLE_TIMEOUT
10815 else if (errno == EAGAIN && timeout > 0) { 10836 else if (errno == EAGAIN && timeout > 0) {
10816 puts("\007timed out waiting for input: auto-logout"); 10837 puts("\007timed out waiting for input: auto-logout");
diff --git a/shell/hush.c b/shell/hush.c
index 7d0dc67e4..6dc2ecaac 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -918,6 +918,7 @@ struct globals {
918#if ENABLE_HUSH_INTERACTIVE 918#if ENABLE_HUSH_INTERACTIVE
919 smallint promptmode; /* 0: PS1, 1: PS2 */ 919 smallint promptmode; /* 0: PS1, 1: PS2 */
920#endif 920#endif
921 /* set by signal handler if SIGINT is received _and_ its trap is not set */
921 smallint flag_SIGINT; 922 smallint flag_SIGINT;
922#if ENABLE_HUSH_LOOPS 923#if ENABLE_HUSH_LOOPS
923 smallint flag_break_continue; 924 smallint flag_break_continue;
@@ -1944,6 +1945,9 @@ enum {
1944static void record_pending_signo(int sig) 1945static void record_pending_signo(int sig)
1945{ 1946{
1946 sigaddset(&G.pending_set, sig); 1947 sigaddset(&G.pending_set, sig);
1948#if ENABLE_FEATURE_EDITING
1949 bb_got_signal = sig; /* for read_line_input: "we got a signal" */
1950#endif
1947#if ENABLE_HUSH_FAST 1951#if ENABLE_HUSH_FAST
1948 if (sig == SIGCHLD) { 1952 if (sig == SIGCHLD) {
1949 G.count_SIGCHLD++; 1953 G.count_SIGCHLD++;
@@ -2652,30 +2656,53 @@ static int get_user_input(struct in_str *i)
2652 for (;;) { 2656 for (;;) {
2653 reinit_unicode_for_hush(); 2657 reinit_unicode_for_hush();
2654 G.flag_SIGINT = 0; 2658 G.flag_SIGINT = 0;
2655 /* buglet: SIGINT will not make new prompt to appear _at once_, 2659
2656 * only after <Enter>. (^C works immediately) */ 2660 bb_got_signal = 0;
2657 r = read_line_input(G.line_input_state, prompt_str, 2661 if (!sigisemptyset(&G.pending_set)) {
2662 /* Whoops, already got a signal, do not call read_line_input */
2663 bb_got_signal = r = -1;
2664 } else {
2665 /* For shell, LI_INTERRUPTIBLE is set:
2666 * read_line_input will abort on either
2667 * getting EINTR in poll(), or if it sees bb_got_signal != 0
2668 * (IOW: if signal arrives before poll() is reached).
2669 * Interactive testcases:
2670 * (while kill -INT $$; do sleep 1; done) &
2671 * #^^^ prints ^C, prints prompt, repeats
2672 * trap 'echo I' int; (while kill -INT $$; do sleep 1; done) &
2673 * #^^^ prints ^C, prints "I", prints prompt, repeats
2674 * trap 'echo T' term; (while kill $$; do sleep 1; done) &
2675 * #^^^ prints "T", prints prompt, repeats
2676 * #(bash 5.0.17 exits after first "T", looks like a bug)
2677 */
2678 r = read_line_input(G.line_input_state, prompt_str,
2658 G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1 2679 G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1
2659 ); 2680 );
2660 /* read_line_input intercepts ^C, "convert" it to SIGINT */ 2681 /* read_line_input intercepts ^C, "convert" it to SIGINT */
2661 if (r == 0) { 2682 if (r == 0)
2662 raise(SIGINT); 2683 raise(SIGINT);
2684 }
2685 /* bash prints ^C (before running a trap, if any)
2686 * both on keyboard ^C and on real SIGINT (non-kbd generated).
2687 */
2688 if (sigismember(&G.pending_set, SIGINT)) {
2689 write(STDOUT_FILENO, "^C\n", 3);
2690 G.last_exitcode = 128 | SIGINT;
2663 } 2691 }
2664 check_and_run_traps(); 2692 check_and_run_traps();
2665 if (r != 0 && !G.flag_SIGINT) 2693 if (r == 0) /* keyboard ^C? */
2694 continue; /* go back, read another input line */
2695 if (r > 0) /* normal input? (no ^C, no ^D, no signals) */
2666 break; 2696 break;
2667 /* ^C or SIGINT: repeat */ 2697 if (!bb_got_signal) {
2668 /* bash prints ^C even on real SIGINT (non-kbd generated) */ 2698 /* r < 0: ^D/EOF/error detected (but not signal) */
2669 write(STDOUT_FILENO, "^C\n", 3); 2699 /* ^D on interactive input goes to next line before exiting: */
2670 G.last_exitcode = 128 | SIGINT; 2700 write(STDOUT_FILENO, "\n", 1);
2671 } 2701 i->p = NULL;
2672 if (r < 0) { 2702 i->peek_buf[0] = r = EOF;
2673 /* EOF/error detected */ 2703 return r;
2674 /* ^D on interactive input goes to next line before exiting: */ 2704 }
2675 write(STDOUT_FILENO, "\n", 1); 2705 /* it was a signal: go back, read another input line */
2676 i->p = NULL;
2677 i->peek_buf[0] = r = EOF;
2678 return r;
2679 } 2706 }
2680 i->p = G.user_input_buf; 2707 i->p = G.user_input_buf;
2681 return (unsigned char)*i->p++; 2708 return (unsigned char)*i->p++;