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