Skip to content

Commit d723492

Browse files
committed
Merge branch 'pw/add-p-single-key'
Finishing touches to C rewrite of "git add -i" in single-key interactive mode. * pw/add-p-single-key: terminal: restore settings on SIGTSTP terminal: work around macos poll() bug terminal: don't assume stdin is /dev/tty terminal: use flags for save_term()
2 parents 83791bc + 0f584de commit d723492

File tree

2 files changed

+211
-36
lines changed

2 files changed

+211
-36
lines changed

compat/terminal.c

Lines changed: 203 additions & 35 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"
@@ -20,39 +20,171 @@ static void restore_term_on_signal(int sig)
2020
#define INPUT_PATH "/dev/tty"
2121
#define OUTPUT_PATH "/dev/tty"
2222

23+
static volatile sig_atomic_t term_fd_needs_closing;
2324
static int term_fd = -1;
2425
static struct termios old_term;
2526

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)
126+
close(term_fd);
127+
term_fd_needs_closing = 0;
128+
term_fd = -1;
129+
}
130+
26131
void restore_term(void)
27132
{
28133
if (term_fd < 0)
29134
return;
30135

31136
tcsetattr(term_fd, TCSAFLUSH, &old_term);
32-
close(term_fd);
33-
term_fd = -1;
137+
close_term_fd();
34138
sigchain_pop_common();
139+
reset_job_signals();
35140
}
36141

37-
int save_term(int full_duplex)
142+
int save_term(enum save_term_flags flags)
38143
{
144+
struct sigaction sa;
145+
39146
if (term_fd < 0)
40-
term_fd = open("/dev/tty", O_RDWR);
147+
term_fd = ((flags & SAVE_TERM_STDIN)
148+
? 0
149+
: open("/dev/tty", O_RDWR));
41150
if (term_fd < 0)
42151
return -1;
43-
if (tcgetattr(term_fd, &old_term) < 0)
152+
term_fd_needs_closing = !(flags & SAVE_TERM_STDIN);
153+
if (tcgetattr(term_fd, &old_term) < 0) {
154+
close_term_fd();
44155
return -1;
156+
}
45157
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);
46178

47179
return 0;
48180
}
49181

50-
static int disable_bits(tcflag_t bits)
182+
static int disable_bits(enum save_term_flags flags, tcflag_t bits)
51183
{
52184
struct termios t;
53185

54-
if (save_term(0) < 0)
55-
goto error;
186+
if (save_term(flags) < 0)
187+
return -1;
56188

57189
t = old_term;
58190

@@ -65,20 +197,50 @@ static int disable_bits(tcflag_t bits)
65197
return 0;
66198

67199
sigchain_pop_common();
68-
error:
69-
close(term_fd);
70-
term_fd = -1;
200+
reset_job_signals();
201+
close_term_fd();
71202
return -1;
72203
}
73204

74-
static int disable_echo(void)
205+
static int disable_echo(enum save_term_flags flags)
75206
{
76-
return disable_bits(ECHO);
207+
return disable_bits(flags, ECHO);
77208
}
78209

79-
static int enable_non_canonical(void)
210+
static int enable_non_canonical(enum save_term_flags flags)
80211
{
81-
return disable_bits(ICANON | ECHO);
212+
return disable_bits(flags, ICANON | ECHO);
213+
}
214+
215+
/*
216+
* On macos it is not possible to use poll() with a terminal so use select
217+
* instead.
218+
*/
219+
static int getchar_with_timeout(int timeout)
220+
{
221+
struct timeval tv, *tvp = NULL;
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;
229+
tvp = &tv;
230+
}
231+
232+
FD_ZERO(&readfds);
233+
FD_SET(0, &readfds);
234+
res = select(1, &readfds, NULL, NULL, tvp);
235+
if (!res)
236+
return EOF;
237+
if (res < 0) {
238+
if (errno == EINTR)
239+
goto again;
240+
else
241+
return EOF;
242+
}
243+
return getchar();
82244
}
83245

84246
#elif defined(GIT_WINDOWS_NATIVE)
@@ -126,15 +288,15 @@ void restore_term(void)
126288
hconin = hconout = INVALID_HANDLE_VALUE;
127289
}
128290

129-
int save_term(int full_duplex)
291+
int save_term(enum save_term_flags flags)
130292
{
131293
hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE,
132294
FILE_SHARE_READ, NULL, OPEN_EXISTING,
133295
FILE_ATTRIBUTE_NORMAL, NULL);
134296
if (hconin == INVALID_HANDLE_VALUE)
135297
return -1;
136298

137-
if (full_duplex) {
299+
if (flags & SAVE_TERM_DUPLEX) {
138300
hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE,
139301
FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
140302
FILE_ATTRIBUTE_NORMAL, NULL);
@@ -154,7 +316,7 @@ int save_term(int full_duplex)
154316
return -1;
155317
}
156318

157-
static int disable_bits(DWORD bits)
319+
static int disable_bits(enum save_term_flags flags, DWORD bits)
158320
{
159321
if (use_stty) {
160322
struct child_process cp = CHILD_PROCESS_INIT;
@@ -191,7 +353,7 @@ static int disable_bits(DWORD bits)
191353
use_stty = 0;
192354
}
193355

194-
if (save_term(0) < 0)
356+
if (save_term(flags) < 0)
195357
return -1;
196358

197359
if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
@@ -204,14 +366,15 @@ static int disable_bits(DWORD bits)
204366
return 0;
205367
}
206368

207-
static int disable_echo(void)
369+
static int disable_echo(enum save_term_flags flags)
208370
{
209-
return disable_bits(ENABLE_ECHO_INPUT);
371+
return disable_bits(flags, ENABLE_ECHO_INPUT);
210372
}
211373

212-
static int enable_non_canonical(void)
374+
static int enable_non_canonical(enum save_term_flags flags)
213375
{
214-
return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
376+
return disable_bits(flags,
377+
ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
215378
}
216379

217380
/*
@@ -245,6 +408,16 @@ static int mingw_getchar(void)
245408
}
246409
#define getchar mingw_getchar
247410

411+
static int getchar_with_timeout(int timeout)
412+
{
413+
struct pollfd pfd = { .fd = 0, .events = POLLIN };
414+
415+
if (poll(&pfd, 1, timeout) < 1)
416+
return EOF;
417+
418+
return getchar();
419+
}
420+
248421
#endif
249422

250423
#ifndef FORCE_TEXT
@@ -267,7 +440,7 @@ char *git_terminal_prompt(const char *prompt, int echo)
267440
return NULL;
268441
}
269442

270-
if (!echo && disable_echo()) {
443+
if (!echo && disable_echo(0)) {
271444
fclose(input_fh);
272445
fclose(output_fh);
273446
return NULL;
@@ -361,7 +534,7 @@ int read_key_without_echo(struct strbuf *buf)
361534
static int warning_displayed;
362535
int ch;
363536

364-
if (warning_displayed || enable_non_canonical() < 0) {
537+
if (warning_displayed || enable_non_canonical(SAVE_TERM_STDIN) < 0) {
365538
if (!warning_displayed) {
366539
warning("reading single keystrokes not supported on "
367540
"this platform; reading line instead");
@@ -395,12 +568,7 @@ int read_key_without_echo(struct strbuf *buf)
395568
* half a second when we know that the sequence is complete.
396569
*/
397570
while (!is_known_escape_sequence(buf->buf)) {
398-
struct pollfd pfd = { .fd = 0, .events = POLLIN };
399-
400-
if (poll(&pfd, 1, 500) < 1)
401-
break;
402-
403-
ch = getchar();
571+
ch = getchar_with_timeout(500);
404572
if (ch == EOF)
405573
break;
406574
strbuf_addch(buf, ch);
@@ -413,10 +581,10 @@ int read_key_without_echo(struct strbuf *buf)
413581

414582
#else
415583

416-
int save_term(int full_duplex)
584+
int save_term(enum save_term_flags flags)
417585
{
418-
/* full_duplex == 1, but no support available */
419-
return -full_duplex;
586+
/* no duplex support available */
587+
return -!!(flags & SAVE_TERM_DUPLEX);
420588
}
421589

422590
void restore_term(void)

compat/terminal.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
#ifndef COMPAT_TERMINAL_H
22
#define COMPAT_TERMINAL_H
33

4+
enum save_term_flags {
5+
/* Save input and output settings */
6+
SAVE_TERM_DUPLEX = 1 << 0,
7+
/* Save stdin rather than /dev/tty (fails if stdin is not a terminal) */
8+
SAVE_TERM_STDIN = 1 << 1,
9+
};
10+
411
/*
512
* Save the terminal attributes so they can be restored later by a
613
* call to restore_term(). Note that every successful call to
714
* save_term() must be matched by a call to restore_term() even if the
815
* attributes have not been changed. Returns 0 on success, -1 on
916
* failure.
1017
*/
11-
int save_term(int full_duplex);
18+
int save_term(enum save_term_flags flags);
1219
/* Restore the terminal attributes that were saved with save_term() */
1320
void restore_term(void);
1421

0 commit comments

Comments
 (0)