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:
39
shell/ash.c
39
shell/ash.c
@ -3679,7 +3679,9 @@ signal_handler(int signo)
|
||||
if (!trap[SIGCHLD])
|
||||
return;
|
||||
}
|
||||
|
||||
#if ENABLE_FEATURE_EDITING
|
||||
bb_got_signal = signo; /* for read_line_input: "we got a signal" */
|
||||
#endif
|
||||
gotsig[signo - 1] = 1;
|
||||
pending_sig = signo;
|
||||
|
||||
@ -10784,33 +10786,52 @@ preadfd(void)
|
||||
# endif
|
||||
reinit_unicode_for_ash();
|
||||
again:
|
||||
//BUG: not in INT_OFF/INT_ON section - SIGINT et al would longjmp out of read_line_input()!
|
||||
//This would cause a memory leak in interactive shell
|
||||
//(repeated internal allocations in read_line_input):
|
||||
// (while kill -INT $$; do :; done) &
|
||||
/* 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)
|
||||
*/
|
||||
bb_got_signal = 0;
|
||||
INT_OFF; /* no longjmp'ing out of read_line_input please */
|
||||
nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ);
|
||||
if (bb_got_signal == SIGINT)
|
||||
write(STDOUT_FILENO, "^C\n", 3);
|
||||
INT_ON; /* here non-blocked SIGINT will longjmp */
|
||||
if (nr == 0) {
|
||||
/* ^C pressed, "convert" to SIGINT */
|
||||
write(STDOUT_FILENO, "^C", 2);
|
||||
raise(SIGINT);
|
||||
write(STDOUT_FILENO, "^C\n", 3);
|
||||
raise(SIGINT); /* here non-blocked SIGINT will longjmp */
|
||||
/* raise(SIGINT) did not work! (e.g. if SIGINT
|
||||
* is SIG_IGNed on startup, it stays SIG_IGNed)
|
||||
*/
|
||||
if (trap[SIGINT]) {
|
||||
empty_line_input:
|
||||
buf[0] = '\n';
|
||||
buf[1] = '\0';
|
||||
return 1;
|
||||
}
|
||||
exitstatus = 128 + SIGINT;
|
||||
/* bash behavior on ^C + ignored SIGINT: */
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
goto again;
|
||||
}
|
||||
if (nr < 0) {
|
||||
if (errno == 0) {
|
||||
/* Ctrl+D pressed */
|
||||
/* ^D pressed */
|
||||
nr = 0;
|
||||
}
|
||||
else if (errno == EINTR) { /* got signal? */
|
||||
if (bb_got_signal != SIGINT)
|
||||
write(STDOUT_FILENO, "\n", 1);
|
||||
goto empty_line_input;
|
||||
}
|
||||
# if ENABLE_ASH_IDLE_TIMEOUT
|
||||
else if (errno == EAGAIN && timeout > 0) {
|
||||
puts("\007timed out waiting for input: auto-logout");
|
||||
|
67
shell/hush.c
67
shell/hush.c
@ -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++;
|
||||
|
Reference in New Issue
Block a user