diff options
author | Denys Vlasenko <vda.linux@googlemail.com> | 2022-01-17 03:02:40 +0100 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2022-01-17 11:46:23 +0100 |
commit | 12566e7f9b5e5c5d445bc4d36991d134b431dc6c (patch) | |
tree | 2571356a77f7d421da368e9b31dad182e83b2408 /shell | |
parent | a277506a64404e6c4472ff89c944c4f353db1c33 (diff) | |
download | busybox-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>
Diffstat (limited to 'shell')
-rw-r--r-- | shell/ash.c | 39 | ||||
-rw-r--r-- | shell/hush.c | 67 |
2 files changed, 77 insertions, 29 deletions
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 { | |||
1944 | static void record_pending_signo(int sig) | 1945 | static 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++; |