terminal: restore settings on SIGTSTP

If the user suspends git while it is waiting for a keypress reset the
terminal before stopping and restore the settings when git resumes. If
the user tries to resume in the background print an error
message (taking care to use async safe functions) before stopping
again. Ideally we would reprint the prompt for the user when git
resumes but this patch just restarts the read().

The signal handler is established with sigaction() rather than using
sigchain_push() as this allows us to control the signal mask when the
handler is invoked and ensure SA_RESTART is used to restart the
read() when resuming.

Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
pull/1231/head
Phillip Wood 6 months ago committed by Junio C Hamano
parent 6606d99bae
commit 0f584debc7
  1. 132
      compat/terminal.c

@ -1,4 +1,4 @@
#include "git-compat-util.h"
#include "cache.h"
#include "compat/terminal.h"
#include "sigchain.h"
#include "strbuf.h"
@ -24,6 +24,102 @@ static volatile sig_atomic_t term_fd_needs_closing;
static int term_fd = -1;
static struct termios old_term;
static const char *background_resume_msg;
static const char *restore_error_msg;
static volatile sig_atomic_t ttou_received;
/* async safe error function for use by signal handlers. */
static void write_err(const char *msg)
{
write_in_full(2, "error: ", strlen("error: "));
write_in_full(2, msg, strlen(msg));
write_in_full(2, "\n", 1);
}
static void print_background_resume_msg(int signo)
{
int saved_errno = errno;
sigset_t mask;
struct sigaction old_sa;
struct sigaction sa = { .sa_handler = SIG_DFL };
ttou_received = 1;
write_err(background_resume_msg);
sigaction(signo, &sa, &old_sa);
raise(signo);
sigemptyset(&mask);
sigaddset(&mask, signo);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
/* Stopped here */
sigprocmask(SIG_BLOCK, &mask, NULL);
sigaction(signo, &old_sa, NULL);
errno = saved_errno;
}
static void restore_terminal_on_suspend(int signo)
{
int saved_errno = errno;
int res;
struct termios t;
sigset_t mask;
struct sigaction old_sa;
struct sigaction sa = { .sa_handler = SIG_DFL };
int can_restore = 1;
if (tcgetattr(term_fd, &t) < 0)
can_restore = 0;
if (tcsetattr(term_fd, TCSAFLUSH, &old_term) < 0)
write_err(restore_error_msg);
sigaction(signo, &sa, &old_sa);
raise(signo);
sigemptyset(&mask);
sigaddset(&mask, signo);
sigprocmask(SIG_UNBLOCK, &mask, NULL);
/* Stopped here */
sigprocmask(SIG_BLOCK, &mask, NULL);
sigaction(signo, &old_sa, NULL);
if (!can_restore) {
write_err(restore_error_msg);
goto out;
}
/*
* If we resume in the background then we receive SIGTTOU when calling
* tcsetattr() below. Set up a handler to print an error message in that
* case.
*/
sigemptyset(&mask);
sigaddset(&mask, SIGTTOU);
sa.sa_mask = old_sa.sa_mask;
sa.sa_handler = print_background_resume_msg;
sa.sa_flags = SA_RESTART;
sigaction(SIGTTOU, &sa, &old_sa);
again:
ttou_received = 0;
sigprocmask(SIG_UNBLOCK, &mask, NULL);
res = tcsetattr(term_fd, TCSAFLUSH, &t);
sigprocmask(SIG_BLOCK, &mask, NULL);
if (ttou_received)
goto again;
else if (res < 0)
write_err(restore_error_msg);
sigaction(SIGTTOU, &old_sa, NULL);
out:
errno = saved_errno;
}
static void reset_job_signals(void)
{
if (restore_error_msg) {
signal(SIGTTIN, SIG_DFL);
signal(SIGTTOU, SIG_DFL);
signal(SIGTSTP, SIG_DFL);
restore_error_msg = NULL;
background_resume_msg = NULL;
}
}
static void close_term_fd(void)
{
if (term_fd_needs_closing)
@ -40,10 +136,13 @@ void restore_term(void)
tcsetattr(term_fd, TCSAFLUSH, &old_term);
close_term_fd();
sigchain_pop_common();
reset_job_signals();
}
int save_term(enum save_term_flags flags)
{
struct sigaction sa;
if (term_fd < 0)
term_fd = ((flags & SAVE_TERM_STDIN)
? 0
@ -56,6 +155,26 @@ int save_term(enum save_term_flags flags)
return -1;
}
sigchain_push_common(restore_term_on_signal);
/*
* If job control is disabled then the shell will have set the
* disposition of SIGTSTP to SIG_IGN.
*/
sigaction(SIGTSTP, NULL, &sa);
if (sa.sa_handler == SIG_IGN)
return 0;
/* avoid calling gettext() from signal handler */
background_resume_msg = _("cannot resume in the background, please use 'fg' to resume");
restore_error_msg = _("cannot restore terminal settings");
sa.sa_handler = restore_terminal_on_suspend;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGTSTP);
sigaddset(&sa.sa_mask, SIGTTIN);
sigaddset(&sa.sa_mask, SIGTTOU);
sigaction(SIGTSTP, &sa, NULL);
sigaction(SIGTTIN, &sa, NULL);
sigaction(SIGTTOU, &sa, NULL);
return 0;
}
@ -78,6 +197,7 @@ static int disable_bits(enum save_term_flags flags, tcflag_t bits)
return 0;
sigchain_pop_common();
reset_job_signals();
close_term_fd();
return -1;
}
@ -102,6 +222,7 @@ static int getchar_with_timeout(int timeout)
fd_set readfds;
int res;
again:
if (timeout >= 0) {
tv.tv_sec = timeout / 1000;
tv.tv_usec = (timeout % 1000) * 1000;
@ -111,9 +232,14 @@ static int getchar_with_timeout(int timeout)
FD_ZERO(&readfds);
FD_SET(0, &readfds);
res = select(1, &readfds, NULL, NULL, tvp);
if (res <= 0)
if (!res)
return EOF;
if (res < 0) {
if (errno == EINTR)
goto again;
else
return EOF;
}
return getchar();
}

Loading…
Cancel
Save