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