Skip to content

Commit 0f584de

Browse files
phillipwoodgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 6606d99 commit 0f584de

File tree

1 file changed

+129
-3
lines changed

1 file changed

+129
-3
lines changed

compat/terminal.c

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#include "git-compat-util.h"
1+
#include "cache.h"
22
#include "compat/terminal.h"
33
#include "sigchain.h"
44
#include "strbuf.h"
@@ -24,6 +24,102 @@ static volatile sig_atomic_t term_fd_needs_closing;
2424
static int term_fd = -1;
2525
static struct termios old_term;
2626

27+
static const char *background_resume_msg;
28+
static const char *restore_error_msg;
29+
static volatile sig_atomic_t ttou_received;
30+
31+
/* async safe error function for use by signal handlers. */
32+
static void write_err(const char *msg)
33+
{
34+
write_in_full(2, "error: ", strlen("error: "));
35+
write_in_full(2, msg, strlen(msg));
36+
write_in_full(2, "\n", 1);
37+
}
38+
39+
static void print_background_resume_msg(int signo)
40+
{
41+
int saved_errno = errno;
42+
sigset_t mask;
43+
struct sigaction old_sa;
44+
struct sigaction sa = { .sa_handler = SIG_DFL };
45+
46+
ttou_received = 1;
47+
write_err(background_resume_msg);
48+
sigaction(signo, &sa, &old_sa);
49+
raise(signo);
50+
sigemptyset(&mask);
51+
sigaddset(&mask, signo);
52+
sigprocmask(SIG_UNBLOCK, &mask, NULL);
53+
/* Stopped here */
54+
sigprocmask(SIG_BLOCK, &mask, NULL);
55+
sigaction(signo, &old_sa, NULL);
56+
errno = saved_errno;
57+
}
58+
59+
static void restore_terminal_on_suspend(int signo)
60+
{
61+
int saved_errno = errno;
62+
int res;
63+
struct termios t;
64+
sigset_t mask;
65+
struct sigaction old_sa;
66+
struct sigaction sa = { .sa_handler = SIG_DFL };
67+
int can_restore = 1;
68+
69+
if (tcgetattr(term_fd, &t) < 0)
70+
can_restore = 0;
71+
72+
if (tcsetattr(term_fd, TCSAFLUSH, &old_term) < 0)
73+
write_err(restore_error_msg);
74+
75+
sigaction(signo, &sa, &old_sa);
76+
raise(signo);
77+
sigemptyset(&mask);
78+
sigaddset(&mask, signo);
79+
sigprocmask(SIG_UNBLOCK, &mask, NULL);
80+
/* Stopped here */
81+
sigprocmask(SIG_BLOCK, &mask, NULL);
82+
sigaction(signo, &old_sa, NULL);
83+
if (!can_restore) {
84+
write_err(restore_error_msg);
85+
goto out;
86+
}
87+
/*
88+
* If we resume in the background then we receive SIGTTOU when calling
89+
* tcsetattr() below. Set up a handler to print an error message in that
90+
* case.
91+
*/
92+
sigemptyset(&mask);
93+
sigaddset(&mask, SIGTTOU);
94+
sa.sa_mask = old_sa.sa_mask;
95+
sa.sa_handler = print_background_resume_msg;
96+
sa.sa_flags = SA_RESTART;
97+
sigaction(SIGTTOU, &sa, &old_sa);
98+
again:
99+
ttou_received = 0;
100+
sigprocmask(SIG_UNBLOCK, &mask, NULL);
101+
res = tcsetattr(term_fd, TCSAFLUSH, &t);
102+
sigprocmask(SIG_BLOCK, &mask, NULL);
103+
if (ttou_received)
104+
goto again;
105+
else if (res < 0)
106+
write_err(restore_error_msg);
107+
sigaction(SIGTTOU, &old_sa, NULL);
108+
out:
109+
errno = saved_errno;
110+
}
111+
112+
static void reset_job_signals(void)
113+
{
114+
if (restore_error_msg) {
115+
signal(SIGTTIN, SIG_DFL);
116+
signal(SIGTTOU, SIG_DFL);
117+
signal(SIGTSTP, SIG_DFL);
118+
restore_error_msg = NULL;
119+
background_resume_msg = NULL;
120+
}
121+
}
122+
27123
static void close_term_fd(void)
28124
{
29125
if (term_fd_needs_closing)
@@ -40,10 +136,13 @@ void restore_term(void)
40136
tcsetattr(term_fd, TCSAFLUSH, &old_term);
41137
close_term_fd();
42138
sigchain_pop_common();
139+
reset_job_signals();
43140
}
44141

45142
int save_term(enum save_term_flags flags)
46143
{
144+
struct sigaction sa;
145+
47146
if (term_fd < 0)
48147
term_fd = ((flags & SAVE_TERM_STDIN)
49148
? 0
@@ -56,6 +155,26 @@ int save_term(enum save_term_flags flags)
56155
return -1;
57156
}
58157
sigchain_push_common(restore_term_on_signal);
158+
/*
159+
* If job control is disabled then the shell will have set the
160+
* disposition of SIGTSTP to SIG_IGN.
161+
*/
162+
sigaction(SIGTSTP, NULL, &sa);
163+
if (sa.sa_handler == SIG_IGN)
164+
return 0;
165+
166+
/* avoid calling gettext() from signal handler */
167+
background_resume_msg = _("cannot resume in the background, please use 'fg' to resume");
168+
restore_error_msg = _("cannot restore terminal settings");
169+
sa.sa_handler = restore_terminal_on_suspend;
170+
sa.sa_flags = SA_RESTART;
171+
sigemptyset(&sa.sa_mask);
172+
sigaddset(&sa.sa_mask, SIGTSTP);
173+
sigaddset(&sa.sa_mask, SIGTTIN);
174+
sigaddset(&sa.sa_mask, SIGTTOU);
175+
sigaction(SIGTSTP, &sa, NULL);
176+
sigaction(SIGTTIN, &sa, NULL);
177+
sigaction(SIGTTOU, &sa, NULL);
59178

60179
return 0;
61180
}
@@ -78,6 +197,7 @@ static int disable_bits(enum save_term_flags flags, tcflag_t bits)
78197
return 0;
79198

80199
sigchain_pop_common();
200+
reset_job_signals();
81201
close_term_fd();
82202
return -1;
83203
}
@@ -102,6 +222,7 @@ static int getchar_with_timeout(int timeout)
102222
fd_set readfds;
103223
int res;
104224

225+
again:
105226
if (timeout >= 0) {
106227
tv.tv_sec = timeout / 1000;
107228
tv.tv_usec = (timeout % 1000) * 1000;
@@ -111,9 +232,14 @@ static int getchar_with_timeout(int timeout)
111232
FD_ZERO(&readfds);
112233
FD_SET(0, &readfds);
113234
res = select(1, &readfds, NULL, NULL, tvp);
114-
if (res <= 0)
235+
if (!res)
115236
return EOF;
116-
237+
if (res < 0) {
238+
if (errno == EINTR)
239+
goto again;
240+
else
241+
return EOF;
242+
}
117243
return getchar();
118244
}
119245

0 commit comments

Comments
 (0)