]> git.ipfire.org Git - thirdparty/git.git/blame - compat/terminal.c
Merge branch 'rs/opt-parse-long-fixups'
[thirdparty/git.git] / compat / terminal.c
CommitLineData
a64acf72 1#include "git-compat-util.h"
21aeafce 2#include "compat/terminal.h"
f394e093 3#include "gettext.h"
21aeafce
JK
4#include "sigchain.h"
5#include "strbuf.h"
9ea416cb
JS
6#include "run-command.h"
7#include "string-list.h"
12acdf57 8#include "hashmap.h"
21aeafce 9
380395d0 10#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
afb43561 11
afb43561
EFL
12static void restore_term_on_signal(int sig)
13{
14 restore_term();
f7da7565 15 /* restore_term calls sigchain_pop_common */
afb43561
EFL
16 raise(sig);
17}
18
21aeafce
JK
19#ifdef HAVE_DEV_TTY
20
afb43561
EFL
21#define INPUT_PATH "/dev/tty"
22#define OUTPUT_PATH "/dev/tty"
23
e4938ce3 24static volatile sig_atomic_t term_fd_needs_closing;
21aeafce
JK
25static int term_fd = -1;
26static struct termios old_term;
27
0f584deb
PW
28static const char *background_resume_msg;
29static const char *restore_error_msg;
30static volatile sig_atomic_t ttou_received;
31
32/* async safe error function for use by signal handlers. */
33static void write_err(const char *msg)
34{
35 write_in_full(2, "error: ", strlen("error: "));
36 write_in_full(2, msg, strlen(msg));
37 write_in_full(2, "\n", 1);
38}
39
40static void print_background_resume_msg(int signo)
41{
42 int saved_errno = errno;
43 sigset_t mask;
44 struct sigaction old_sa;
45 struct sigaction sa = { .sa_handler = SIG_DFL };
46
47 ttou_received = 1;
48 write_err(background_resume_msg);
49 sigaction(signo, &sa, &old_sa);
50 raise(signo);
51 sigemptyset(&mask);
52 sigaddset(&mask, signo);
53 sigprocmask(SIG_UNBLOCK, &mask, NULL);
54 /* Stopped here */
55 sigprocmask(SIG_BLOCK, &mask, NULL);
56 sigaction(signo, &old_sa, NULL);
57 errno = saved_errno;
58}
59
60static void restore_terminal_on_suspend(int signo)
61{
62 int saved_errno = errno;
63 int res;
64 struct termios t;
65 sigset_t mask;
66 struct sigaction old_sa;
67 struct sigaction sa = { .sa_handler = SIG_DFL };
68 int can_restore = 1;
69
70 if (tcgetattr(term_fd, &t) < 0)
71 can_restore = 0;
72
73 if (tcsetattr(term_fd, TCSAFLUSH, &old_term) < 0)
74 write_err(restore_error_msg);
75
76 sigaction(signo, &sa, &old_sa);
77 raise(signo);
78 sigemptyset(&mask);
79 sigaddset(&mask, signo);
80 sigprocmask(SIG_UNBLOCK, &mask, NULL);
81 /* Stopped here */
82 sigprocmask(SIG_BLOCK, &mask, NULL);
83 sigaction(signo, &old_sa, NULL);
84 if (!can_restore) {
85 write_err(restore_error_msg);
86 goto out;
87 }
88 /*
89 * If we resume in the background then we receive SIGTTOU when calling
90 * tcsetattr() below. Set up a handler to print an error message in that
91 * case.
92 */
93 sigemptyset(&mask);
94 sigaddset(&mask, SIGTTOU);
95 sa.sa_mask = old_sa.sa_mask;
96 sa.sa_handler = print_background_resume_msg;
97 sa.sa_flags = SA_RESTART;
98 sigaction(SIGTTOU, &sa, &old_sa);
99 again:
100 ttou_received = 0;
101 sigprocmask(SIG_UNBLOCK, &mask, NULL);
102 res = tcsetattr(term_fd, TCSAFLUSH, &t);
103 sigprocmask(SIG_BLOCK, &mask, NULL);
104 if (ttou_received)
105 goto again;
106 else if (res < 0)
107 write_err(restore_error_msg);
108 sigaction(SIGTTOU, &old_sa, NULL);
109 out:
110 errno = saved_errno;
111}
112
113static void reset_job_signals(void)
114{
115 if (restore_error_msg) {
116 signal(SIGTTIN, SIG_DFL);
117 signal(SIGTTOU, SIG_DFL);
118 signal(SIGTSTP, SIG_DFL);
119 restore_error_msg = NULL;
120 background_resume_msg = NULL;
121 }
122}
123
e4938ce3
PW
124static void close_term_fd(void)
125{
126 if (term_fd_needs_closing)
127 close(term_fd);
128 term_fd_needs_closing = 0;
129 term_fd = -1;
130}
131
e22b245e 132void restore_term(void)
21aeafce
JK
133{
134 if (term_fd < 0)
135 return;
136
137 tcsetattr(term_fd, TCSAFLUSH, &old_term);
e4938ce3 138 close_term_fd();
f7da7565 139 sigchain_pop_common();
0f584deb 140 reset_job_signals();
21aeafce
JK
141}
142
02af15de 143int save_term(enum save_term_flags flags)
e22b245e 144{
0f584deb
PW
145 struct sigaction sa;
146
e22b245e 147 if (term_fd < 0)
e4938ce3
PW
148 term_fd = ((flags & SAVE_TERM_STDIN)
149 ? 0
150 : open("/dev/tty", O_RDWR));
f7da7565
PW
151 if (term_fd < 0)
152 return -1;
e4938ce3
PW
153 term_fd_needs_closing = !(flags & SAVE_TERM_STDIN);
154 if (tcgetattr(term_fd, &old_term) < 0) {
155 close_term_fd();
f7da7565 156 return -1;
e4938ce3 157 }
f7da7565 158 sigchain_push_common(restore_term_on_signal);
0f584deb
PW
159 /*
160 * If job control is disabled then the shell will have set the
161 * disposition of SIGTSTP to SIG_IGN.
162 */
163 sigaction(SIGTSTP, NULL, &sa);
164 if (sa.sa_handler == SIG_IGN)
165 return 0;
166
167 /* avoid calling gettext() from signal handler */
168 background_resume_msg = _("cannot resume in the background, please use 'fg' to resume");
169 restore_error_msg = _("cannot restore terminal settings");
170 sa.sa_handler = restore_terminal_on_suspend;
171 sa.sa_flags = SA_RESTART;
172 sigemptyset(&sa.sa_mask);
173 sigaddset(&sa.sa_mask, SIGTSTP);
174 sigaddset(&sa.sa_mask, SIGTTIN);
175 sigaddset(&sa.sa_mask, SIGTTOU);
176 sigaction(SIGTSTP, &sa, NULL);
177 sigaction(SIGTTIN, &sa, NULL);
178 sigaction(SIGTTOU, &sa, NULL);
e22b245e 179
f7da7565 180 return 0;
e22b245e
CMAB
181}
182
02af15de 183static int disable_bits(enum save_term_flags flags, tcflag_t bits)
9df92e63
EFL
184{
185 struct termios t;
186
02af15de 187 if (save_term(flags) < 0)
e4938ce3 188 return -1;
9df92e63 189
e22b245e 190 t = old_term;
9df92e63 191
94ac3c31 192 t.c_lflag &= ~bits;
2c686021
PW
193 if (bits & ICANON) {
194 t.c_cc[VMIN] = 1;
195 t.c_cc[VTIME] = 0;
196 }
9df92e63
EFL
197 if (!tcsetattr(term_fd, TCSAFLUSH, &t))
198 return 0;
199
f7da7565 200 sigchain_pop_common();
0f584deb 201 reset_job_signals();
e4938ce3 202 close_term_fd();
9df92e63
EFL
203 return -1;
204}
205
02af15de 206static int disable_echo(enum save_term_flags flags)
94ac3c31 207{
02af15de 208 return disable_bits(flags, ECHO);
94ac3c31
JS
209}
210
02af15de 211static int enable_non_canonical(enum save_term_flags flags)
a5e46e6b 212{
02af15de 213 return disable_bits(flags, ICANON | ECHO);
a5e46e6b
JS
214}
215
6606d99b
PW
216/*
217 * On macos it is not possible to use poll() with a terminal so use select
218 * instead.
219 */
220static int getchar_with_timeout(int timeout)
221{
222 struct timeval tv, *tvp = NULL;
223 fd_set readfds;
224 int res;
225
0f584deb 226 again:
6606d99b
PW
227 if (timeout >= 0) {
228 tv.tv_sec = timeout / 1000;
229 tv.tv_usec = (timeout % 1000) * 1000;
230 tvp = &tv;
231 }
232
233 FD_ZERO(&readfds);
234 FD_SET(0, &readfds);
235 res = select(1, &readfds, NULL, NULL, tvp);
0f584deb 236 if (!res)
6606d99b 237 return EOF;
0f584deb
PW
238 if (res < 0) {
239 if (errno == EINTR)
240 goto again;
241 else
242 return EOF;
243 }
6606d99b
PW
244 return getchar();
245}
246
380395d0 247#elif defined(GIT_WINDOWS_NATIVE)
afb43561
EFL
248
249#define INPUT_PATH "CONIN$"
250#define OUTPUT_PATH "CONOUT$"
251#define FORCE_TEXT "t"
252
9ea416cb
JS
253static int use_stty = 1;
254static struct string_list stty_restore = STRING_LIST_INIT_DUP;
afb43561 255static HANDLE hconin = INVALID_HANDLE_VALUE;
e22b245e
CMAB
256static HANDLE hconout = INVALID_HANDLE_VALUE;
257static DWORD cmode_in, cmode_out;
afb43561 258
e22b245e 259void restore_term(void)
afb43561 260{
9ea416cb
JS
261 if (use_stty) {
262 int i;
263 struct child_process cp = CHILD_PROCESS_INIT;
264
265 if (stty_restore.nr == 0)
266 return;
267
ef8d7ac4 268 strvec_push(&cp.args, "stty");
9ea416cb 269 for (i = 0; i < stty_restore.nr; i++)
ef8d7ac4 270 strvec_push(&cp.args, stty_restore.items[i].string);
9ea416cb
JS
271 run_command(&cp);
272 string_list_clear(&stty_restore, 0);
273 return;
274 }
275
f7da7565
PW
276 sigchain_pop_common();
277
afb43561
EFL
278 if (hconin == INVALID_HANDLE_VALUE)
279 return;
280
e22b245e
CMAB
281 SetConsoleMode(hconin, cmode_in);
282 CloseHandle(hconin);
283 if (cmode_out) {
284 assert(hconout != INVALID_HANDLE_VALUE);
285 SetConsoleMode(hconout, cmode_out);
286 CloseHandle(hconout);
287 }
288
289 hconin = hconout = INVALID_HANDLE_VALUE;
290}
291
02af15de 292int save_term(enum save_term_flags flags)
e22b245e
CMAB
293{
294 hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE,
295 FILE_SHARE_READ, NULL, OPEN_EXISTING,
296 FILE_ATTRIBUTE_NORMAL, NULL);
297 if (hconin == INVALID_HANDLE_VALUE)
298 return -1;
299
02af15de 300 if (flags & SAVE_TERM_DUPLEX) {
e22b245e
CMAB
301 hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE,
302 FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
303 FILE_ATTRIBUTE_NORMAL, NULL);
304 if (hconout == INVALID_HANDLE_VALUE)
305 goto error;
306
307 GetConsoleMode(hconout, &cmode_out);
308 }
309
310 GetConsoleMode(hconin, &cmode_in);
311 use_stty = 0;
f7da7565 312 sigchain_push_common(restore_term_on_signal);
e22b245e
CMAB
313 return 0;
314error:
afb43561
EFL
315 CloseHandle(hconin);
316 hconin = INVALID_HANDLE_VALUE;
e22b245e 317 return -1;
afb43561
EFL
318}
319
02af15de 320static int disable_bits(enum save_term_flags flags, DWORD bits)
afb43561 321{
9ea416cb
JS
322 if (use_stty) {
323 struct child_process cp = CHILD_PROCESS_INIT;
324
ef8d7ac4 325 strvec_push(&cp.args, "stty");
9ea416cb
JS
326
327 if (bits & ENABLE_LINE_INPUT) {
328 string_list_append(&stty_restore, "icanon");
2c686021
PW
329 /*
330 * POSIX allows VMIN and VTIME to overlap with VEOF and
331 * VEOL - let's hope that is not the case on windows.
332 */
333 strvec_pushl(&cp.args, "-icanon", "min", "1", "time", "0", NULL);
9ea416cb
JS
334 }
335
336 if (bits & ENABLE_ECHO_INPUT) {
337 string_list_append(&stty_restore, "echo");
ef8d7ac4 338 strvec_push(&cp.args, "-echo");
9ea416cb
JS
339 }
340
341 if (bits & ENABLE_PROCESSED_INPUT) {
342 string_list_append(&stty_restore, "-ignbrk");
343 string_list_append(&stty_restore, "intr");
344 string_list_append(&stty_restore, "^c");
ef8d7ac4
JK
345 strvec_push(&cp.args, "ignbrk");
346 strvec_push(&cp.args, "intr");
347 strvec_push(&cp.args, "");
9ea416cb
JS
348 }
349
350 if (run_command(&cp) == 0)
351 return 0;
352
353 /* `stty` could not be executed; access the Console directly */
354 use_stty = 0;
355 }
356
02af15de 357 if (save_term(flags) < 0)
afb43561
EFL
358 return -1;
359
e22b245e 360 if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
afb43561
EFL
361 CloseHandle(hconin);
362 hconin = INVALID_HANDLE_VALUE;
f7da7565 363 sigchain_pop_common();
afb43561
EFL
364 return -1;
365 }
366
367 return 0;
368}
369
02af15de 370static int disable_echo(enum save_term_flags flags)
94ac3c31 371{
02af15de 372 return disable_bits(flags, ENABLE_ECHO_INPUT);
94ac3c31
JS
373}
374
02af15de 375static int enable_non_canonical(enum save_term_flags flags)
a5e46e6b 376{
02af15de
PW
377 return disable_bits(flags,
378 ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
a5e46e6b 379}
94ac3c31 380
e118f063
JS
381/*
382 * Override `getchar()`, as the default implementation does not use
383 * `ReadFile()`.
384 *
385 * This poses a problem when we want to see whether the standard
386 * input has more characters, as the default of Git for Windows is to start the
387 * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
388 * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
389 * `ReadFile()` to be called first to work properly (it only reports 0
390 * available bytes, otherwise).
391 *
392 * So let's just override `getchar()` with a version backed by `ReadFile()` and
393 * go our merry ways from here.
394 */
395static int mingw_getchar(void)
396{
397 DWORD read = 0;
398 unsigned char ch;
399
400 if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL))
401 return EOF;
402
403 if (!read) {
404 error("Unexpected 0 read");
405 return EOF;
406 }
407
408 return ch;
409}
410#define getchar mingw_getchar
411
6606d99b
PW
412static int getchar_with_timeout(int timeout)
413{
414 struct pollfd pfd = { .fd = 0, .events = POLLIN };
415
416 if (poll(&pfd, 1, timeout) < 1)
417 return EOF;
418
419 return getchar();
420}
421
afb43561
EFL
422#endif
423
424#ifndef FORCE_TEXT
425#define FORCE_TEXT
426#endif
427
21aeafce
JK
428char *git_terminal_prompt(const char *prompt, int echo)
429{
430 static struct strbuf buf = STRBUF_INIT;
431 int r;
67fe7356 432 FILE *input_fh, *output_fh;
21aeafce 433
afb43561 434 input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT);
67fe7356 435 if (!input_fh)
21aeafce
JK
436 return NULL;
437
afb43561 438 output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT);
67fe7356
EFL
439 if (!output_fh) {
440 fclose(input_fh);
441 return NULL;
442 }
443
02af15de 444 if (!echo && disable_echo(0)) {
67fe7356
EFL
445 fclose(input_fh);
446 fclose(output_fh);
9df92e63 447 return NULL;
21aeafce
JK
448 }
449
67fe7356
EFL
450 fputs(prompt, output_fh);
451 fflush(output_fh);
21aeafce 452
8f309aeb 453 r = strbuf_getline_lf(&buf, input_fh);
21aeafce 454 if (!echo) {
67fe7356
EFL
455 putc('\n', output_fh);
456 fflush(output_fh);
21aeafce
JK
457 }
458
459 restore_term();
67fe7356
EFL
460 fclose(input_fh);
461 fclose(output_fh);
21aeafce
JK
462
463 if (r == EOF)
464 return NULL;
465 return buf.buf;
466}
467
12acdf57
JS
468/*
469 * The `is_known_escape_sequence()` function returns 1 if the passed string
470 * corresponds to an Escape sequence that the terminal capabilities contains.
471 *
472 * To avoid depending on ncurses or other platform-specific libraries, we rely
473 * on the presence of the `infocmp` executable to do the job for us (failing
474 * silently if the program is not available or refused to run).
475 */
476struct escape_sequence_entry {
477 struct hashmap_entry entry;
478 char sequence[FLEX_ARRAY];
479};
480
5cf88fd8 481static int sequence_entry_cmp(const void *hashmap_cmp_fn_data UNUSED,
beaa1d95
JK
482 const struct hashmap_entry *he1,
483 const struct hashmap_entry *he2,
12acdf57
JS
484 const void *keydata)
485{
beaa1d95
JK
486 const struct escape_sequence_entry
487 *e1 = container_of(he1, const struct escape_sequence_entry, entry),
488 *e2 = container_of(he2, const struct escape_sequence_entry, entry);
12acdf57
JS
489 return strcmp(e1->sequence, keydata ? keydata : e2->sequence);
490}
491
492static int is_known_escape_sequence(const char *sequence)
493{
494 static struct hashmap sequences;
495 static int initialized;
496
497 if (!initialized) {
498 struct child_process cp = CHILD_PROCESS_INIT;
499 struct strbuf buf = STRBUF_INIT;
500 char *p, *eol;
501
beaa1d95 502 hashmap_init(&sequences, sequence_entry_cmp, NULL, 0);
12acdf57 503
ef8d7ac4 504 strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL);
12acdf57
JS
505 if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0))
506 strbuf_setlen(&buf, 0);
507
508 for (eol = p = buf.buf; *p; p = eol + 1) {
509 p = strchr(p, '=');
510 if (!p)
511 break;
512 p++;
513 eol = strchrnul(p, '\n');
514
515 if (starts_with(p, "\\E")) {
516 char *comma = memchr(p, ',', eol - p);
517 struct escape_sequence_entry *e;
518
519 p[0] = '^';
520 p[1] = '[';
521 FLEX_ALLOC_MEM(e, sequence, p, comma - p);
522 hashmap_entry_init(&e->entry,
523 strhash(e->sequence));
524 hashmap_add(&sequences, &e->entry);
525 }
526 if (!*eol)
527 break;
528 }
529 initialized = 1;
530 }
531
532 return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence);
533}
534
a5e46e6b
JS
535int read_key_without_echo(struct strbuf *buf)
536{
537 static int warning_displayed;
538 int ch;
539
e4938ce3 540 if (warning_displayed || enable_non_canonical(SAVE_TERM_STDIN) < 0) {
a5e46e6b
JS
541 if (!warning_displayed) {
542 warning("reading single keystrokes not supported on "
543 "this platform; reading line instead");
544 warning_displayed = 1;
545 }
546
547 return strbuf_getline(buf, stdin);
548 }
549
550 strbuf_reset(buf);
551 ch = getchar();
552 if (ch == EOF) {
553 restore_term();
554 return EOF;
555 }
a5e46e6b 556 strbuf_addch(buf, ch);
e118f063
JS
557
558 if (ch == '\033' /* ESC */) {
559 /*
560 * We are most likely looking at an Escape sequence. Let's try
561 * to read more bytes, waiting at most half a second, assuming
562 * that the sequence is complete if we did not receive any byte
563 * within that time.
564 *
565 * Start by replacing the Escape byte with ^[ */
566 strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
567
12acdf57
JS
568 /*
569 * Query the terminal capabilities once about all the Escape
570 * sequences it knows about, so that we can avoid waiting for
571 * half a second when we know that the sequence is complete.
572 */
573 while (!is_known_escape_sequence(buf->buf)) {
6606d99b 574 ch = getchar_with_timeout(500);
e118f063 575 if (ch == EOF)
24d7ce38 576 break;
e118f063
JS
577 strbuf_addch(buf, ch);
578 }
579 }
580
a5e46e6b
JS
581 restore_term();
582 return 0;
583}
584
21aeafce
JK
585#else
586
02af15de 587int save_term(enum save_term_flags flags)
e22b245e 588{
02af15de
PW
589 /* no duplex support available */
590 return -!!(flags & SAVE_TERM_DUPLEX);
e22b245e
CMAB
591}
592
593void restore_term(void)
594{
595}
596
21aeafce
JK
597char *git_terminal_prompt(const char *prompt, int echo)
598{
599 return getpass(prompt);
600}
601
a5e46e6b
JS
602int read_key_without_echo(struct strbuf *buf)
603{
604 static int warning_displayed;
605 const char *res;
606
607 if (!warning_displayed) {
608 warning("reading single keystrokes not supported on this "
609 "platform; reading line instead");
610 warning_displayed = 1;
611 }
612
613 res = getpass("");
614 strbuf_reset(buf);
615 if (!res)
616 return EOF;
617 strbuf_addstr(buf, res);
618 return 0;
619}
620
21aeafce 621#endif