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>
This commit is contained in:
Denys Vlasenko 2022-01-17 03:02:40 +01:00
parent a277506a64
commit 12566e7f9b
9 changed files with 122 additions and 41 deletions

View file

@ -918,6 +918,7 @@ struct globals {
#if ENABLE_HUSH_INTERACTIVE
smallint promptmode; /* 0: PS1, 1: PS2 */
#endif
/* set by signal handler if SIGINT is received _and_ its trap is not set */
smallint flag_SIGINT;
#if ENABLE_HUSH_LOOPS
smallint flag_break_continue;
@ -1944,6 +1945,9 @@ enum {
static void record_pending_signo(int sig)
{
sigaddset(&G.pending_set, sig);
#if ENABLE_FEATURE_EDITING
bb_got_signal = sig; /* for read_line_input: "we got a signal" */
#endif
#if ENABLE_HUSH_FAST
if (sig == SIGCHLD) {
G.count_SIGCHLD++;
@ -2652,30 +2656,53 @@ static int get_user_input(struct in_str *i)
for (;;) {
reinit_unicode_for_hush();
G.flag_SIGINT = 0;
/* buglet: SIGINT will not make new prompt to appear _at once_,
* only after <Enter>. (^C works immediately) */
r = read_line_input(G.line_input_state, prompt_str,
bb_got_signal = 0;
if (!sigisemptyset(&G.pending_set)) {
/* Whoops, already got a signal, do not call read_line_input */
bb_got_signal = r = -1;
} else {
/* For shell, LI_INTERRUPTIBLE is set:
* read_line_input will abort on either
* getting EINTR in poll(), or if it sees bb_got_signal != 0
* (IOW: if signal arrives before poll() is reached).
* Interactive testcases:
* (while kill -INT $$; do sleep 1; done) &
* #^^^ prints ^C, prints prompt, repeats
* trap 'echo I' int; (while kill -INT $$; do sleep 1; done) &
* #^^^ prints ^C, prints "I", prints prompt, repeats
* trap 'echo T' term; (while kill $$; do sleep 1; done) &
* #^^^ prints "T", prints prompt, repeats
* #(bash 5.0.17 exits after first "T", looks like a bug)
*/
r = read_line_input(G.line_input_state, prompt_str,
G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1
);
/* read_line_input intercepts ^C, "convert" it to SIGINT */
if (r == 0) {
raise(SIGINT);
);
/* read_line_input intercepts ^C, "convert" it to SIGINT */
if (r == 0)
raise(SIGINT);
}
/* bash prints ^C (before running a trap, if any)
* both on keyboard ^C and on real SIGINT (non-kbd generated).
*/
if (sigismember(&G.pending_set, SIGINT)) {
write(STDOUT_FILENO, "^C\n", 3);
G.last_exitcode = 128 | SIGINT;
}
check_and_run_traps();
if (r != 0 && !G.flag_SIGINT)
if (r == 0) /* keyboard ^C? */
continue; /* go back, read another input line */
if (r > 0) /* normal input? (no ^C, no ^D, no signals) */
break;
/* ^C or SIGINT: repeat */
/* bash prints ^C even on real SIGINT (non-kbd generated) */
write(STDOUT_FILENO, "^C\n", 3);
G.last_exitcode = 128 | SIGINT;
}
if (r < 0) {
/* EOF/error detected */
/* ^D on interactive input goes to next line before exiting: */
write(STDOUT_FILENO, "\n", 1);
i->p = NULL;
i->peek_buf[0] = r = EOF;
return r;
if (!bb_got_signal) {
/* r < 0: ^D/EOF/error detected (but not signal) */
/* ^D on interactive input goes to next line before exiting: */
write(STDOUT_FILENO, "\n", 1);
i->p = NULL;
i->peek_buf[0] = r = EOF;
return r;
}
/* it was a signal: go back, read another input line */
}
i->p = G.user_input_buf;
return (unsigned char)*i->p++;