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>
Phillip Wood committed
Mar 16, 2022 at 18:54 UTC
0f584debc74afefdd24449136ffb2c77859e38c9
1 file changed
+129
-3
compat/terminal.c
+129
-3
@@ -1,4 +1,4 @@
1
-#include "git-compat-util.h"
1
+#include "cache.h"
2
#include "compat/terminal.h"
3
#include "sigchain.h"
4
#include "strbuf.h"
@@ -24,6 +24,102 @@ static volatile sig_atomic_t term_fd_needs_closing;
24
static int term_fd = -1;
25
static struct termios old_term;
26
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
+
123
static void close_term_fd(void)
124
{
125
if (term_fd_needs_closing)
@@ -40,10 +136,13 @@ void restore_term(void)
136
tcsetattr(term_fd, TCSAFLUSH, &old_term);
137
close_term_fd();
138
sigchain_pop_common();
139
+ reset_job_signals();
140
}
141
142
int save_term(enum save_term_flags flags)
143
{
144
+ struct sigaction sa;
145
+
146
if (term_fd < 0)
147
term_fd = ((flags & SAVE_TERM_STDIN)
148
? 0
@@ -56,6 +155,26 @@ int save_term(enum save_term_flags flags)
155
return -1;
156
}
157
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);
178
179
return 0;
180
}
@@ -78,6 +197,7 @@ static int disable_bits(enum save_term_flags flags, tcflag_t bits)
197
return 0;
198
199
sigchain_pop_common();
200
+ reset_job_signals();
201
close_term_fd();
202
return -1;
203
}
@@ -102,6 +222,7 @@ static int getchar_with_timeout(int timeout)
222
fd_set readfds;
223
int res;
224
225
+ again:
226
if (timeout >= 0) {
227
tv.tv_sec = timeout / 1000;
228
tv.tv_usec = (timeout % 1000) * 1000;
@@ -111,9 +232,14 @@ static int getchar_with_timeout(int timeout)
232
FD_ZERO(&readfds);
233
FD_SET(0, &readfds);
234
res = select(1, &readfds, NULL, NULL, tvp);
114
- if (res <= 0)
235
+ if (!res)
236
return EOF;
116
-
237
+ if (res < 0) {
238
+ if (errno == EINTR)
239
+ goto again;
240
+ else
241
+ return EOF;
242
+ }
243
return getchar();
244
}
245