1
#include "git-compat-util.h"
2
#include "compat/terminal.h"
6
#include "run-command.h"
7
#include "string-list.h"
10
#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
12
static void restore_term_on_signal(int sig)
15
/* restore_term calls sigchain_pop_common */
21
#define INPUT_PATH "/dev/tty"
22
#define OUTPUT_PATH "/dev/tty"
24
static volatile sig_atomic_t term_fd_needs_closing;
25
static int term_fd = -1;
26
static struct termios old_term;
28
static const char *background_resume_msg;
29
static const char *restore_error_msg;
30
static volatile sig_atomic_t ttou_received;
32
/* async safe error function for use by signal handlers. */
33
static void write_err(const char *msg)
35
write_in_full(2, "error: ", strlen("error: "));
36
write_in_full(2, msg, strlen(msg));
37
write_in_full(2, "\n", 1);
40
static void print_background_resume_msg(int signo)
42
int saved_errno = errno;
44
struct sigaction old_sa;
45
struct sigaction sa = { .sa_handler = SIG_DFL };
48
write_err(background_resume_msg);
49
sigaction(signo, &sa, &old_sa);
52
sigaddset(&mask, signo);
53
sigprocmask(SIG_UNBLOCK, &mask, NULL);
55
sigprocmask(SIG_BLOCK, &mask, NULL);
56
sigaction(signo, &old_sa, NULL);
60
static void restore_terminal_on_suspend(int signo)
62
int saved_errno = errno;
66
struct sigaction old_sa;
67
struct sigaction sa = { .sa_handler = SIG_DFL };
70
if (tcgetattr(term_fd, &t) < 0)
73
if (tcsetattr(term_fd, TCSAFLUSH, &old_term) < 0)
74
write_err(restore_error_msg);
76
sigaction(signo, &sa, &old_sa);
79
sigaddset(&mask, signo);
80
sigprocmask(SIG_UNBLOCK, &mask, NULL);
82
sigprocmask(SIG_BLOCK, &mask, NULL);
83
sigaction(signo, &old_sa, NULL);
85
write_err(restore_error_msg);
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
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);
101
sigprocmask(SIG_UNBLOCK, &mask, NULL);
102
res = tcsetattr(term_fd, TCSAFLUSH, &t);
103
sigprocmask(SIG_BLOCK, &mask, NULL);
107
write_err(restore_error_msg);
108
sigaction(SIGTTOU, &old_sa, NULL);
113
static void reset_job_signals(void)
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;
124
static void close_term_fd(void)
126
if (term_fd_needs_closing)
128
term_fd_needs_closing = 0;
132
void restore_term(void)
137
tcsetattr(term_fd, TCSAFLUSH, &old_term);
139
sigchain_pop_common();
143
int save_term(enum save_term_flags flags)
148
term_fd = ((flags & SAVE_TERM_STDIN)
150
: open("/dev/tty", O_RDWR));
153
term_fd_needs_closing = !(flags & SAVE_TERM_STDIN);
154
if (tcgetattr(term_fd, &old_term) < 0) {
158
sigchain_push_common(restore_term_on_signal);
160
* If job control is disabled then the shell will have set the
161
* disposition of SIGTSTP to SIG_IGN.
163
sigaction(SIGTSTP, NULL, &sa);
164
if (sa.sa_handler == SIG_IGN)
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);
183
static int disable_bits(enum save_term_flags flags, tcflag_t bits)
187
if (save_term(flags) < 0)
197
if (!tcsetattr(term_fd, TCSAFLUSH, &t))
200
sigchain_pop_common();
206
static int disable_echo(enum save_term_flags flags)
208
return disable_bits(flags, ECHO);
211
static int enable_non_canonical(enum save_term_flags flags)
213
return disable_bits(flags, ICANON | ECHO);
217
* On macos it is not possible to use poll() with a terminal so use select
220
static int getchar_with_timeout(int timeout)
222
struct timeval tv, *tvp = NULL;
228
tv.tv_sec = timeout / 1000;
229
tv.tv_usec = (timeout % 1000) * 1000;
235
res = select(1, &readfds, NULL, NULL, tvp);
247
#elif defined(GIT_WINDOWS_NATIVE)
249
#define INPUT_PATH "CONIN$"
250
#define OUTPUT_PATH "CONOUT$"
251
#define FORCE_TEXT "t"
253
static int use_stty = 1;
254
static struct string_list stty_restore = STRING_LIST_INIT_DUP;
255
static HANDLE hconin = INVALID_HANDLE_VALUE;
256
static HANDLE hconout = INVALID_HANDLE_VALUE;
257
static DWORD cmode_in, cmode_out;
259
void restore_term(void)
263
struct child_process cp = CHILD_PROCESS_INIT;
265
if (stty_restore.nr == 0)
268
strvec_push(&cp.args, "stty");
269
for (i = 0; i < stty_restore.nr; i++)
270
strvec_push(&cp.args, stty_restore.items[i].string);
272
string_list_clear(&stty_restore, 0);
276
sigchain_pop_common();
278
if (hconin == INVALID_HANDLE_VALUE)
281
SetConsoleMode(hconin, cmode_in);
284
assert(hconout != INVALID_HANDLE_VALUE);
285
SetConsoleMode(hconout, cmode_out);
286
CloseHandle(hconout);
289
hconin = hconout = INVALID_HANDLE_VALUE;
292
int save_term(enum save_term_flags flags)
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)
300
if (flags & SAVE_TERM_DUPLEX) {
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)
307
GetConsoleMode(hconout, &cmode_out);
310
GetConsoleMode(hconin, &cmode_in);
312
sigchain_push_common(restore_term_on_signal);
316
hconin = INVALID_HANDLE_VALUE;
320
static int disable_bits(enum save_term_flags flags, DWORD bits)
323
struct child_process cp = CHILD_PROCESS_INIT;
325
strvec_push(&cp.args, "stty");
327
if (bits & ENABLE_LINE_INPUT) {
328
string_list_append(&stty_restore, "icanon");
330
* POSIX allows VMIN and VTIME to overlap with VEOF and
331
* VEOL - let's hope that is not the case on windows.
333
strvec_pushl(&cp.args, "-icanon", "min", "1", "time", "0", NULL);
336
if (bits & ENABLE_ECHO_INPUT) {
337
string_list_append(&stty_restore, "echo");
338
strvec_push(&cp.args, "-echo");
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");
345
strvec_push(&cp.args, "ignbrk");
346
strvec_push(&cp.args, "intr");
347
strvec_push(&cp.args, "");
350
if (run_command(&cp) == 0)
353
/* `stty` could not be executed; access the Console directly */
357
if (save_term(flags) < 0)
360
if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
362
hconin = INVALID_HANDLE_VALUE;
363
sigchain_pop_common();
370
static int disable_echo(enum save_term_flags flags)
372
return disable_bits(flags, ENABLE_ECHO_INPUT);
375
static int enable_non_canonical(enum save_term_flags flags)
377
return disable_bits(flags,
378
ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
382
* Override `getchar()`, as the default implementation does not use
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).
392
* So let's just override `getchar()` with a version backed by `ReadFile()` and
393
* go our merry ways from here.
395
static int mingw_getchar(void)
400
if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL))
404
error("Unexpected 0 read");
410
#define getchar mingw_getchar
412
static int getchar_with_timeout(int timeout)
414
struct pollfd pfd = { .fd = 0, .events = POLLIN };
416
if (poll(&pfd, 1, timeout) < 1)
428
char *git_terminal_prompt(const char *prompt, int echo)
430
static struct strbuf buf = STRBUF_INIT;
432
FILE *input_fh, *output_fh;
434
input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT);
438
output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT);
444
if (!echo && disable_echo(0)) {
450
fputs(prompt, output_fh);
453
r = strbuf_getline_lf(&buf, input_fh);
455
putc('\n', output_fh);
469
* The `is_known_escape_sequence()` function returns 1 if the passed string
470
* corresponds to an Escape sequence that the terminal capabilities contains.
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).
476
struct escape_sequence_entry {
477
struct hashmap_entry entry;
478
char sequence[FLEX_ARRAY];
481
static int sequence_entry_cmp(const void *hashmap_cmp_fn_data UNUSED,
482
const struct hashmap_entry *he1,
483
const struct hashmap_entry *he2,
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);
489
return strcmp(e1->sequence, keydata ? keydata : e2->sequence);
492
static int is_known_escape_sequence(const char *sequence)
494
static struct hashmap sequences;
495
static int initialized;
498
struct child_process cp = CHILD_PROCESS_INIT;
499
struct strbuf buf = STRBUF_INIT;
502
hashmap_init(&sequences, sequence_entry_cmp, NULL, 0);
504
strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL);
505
if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0))
506
strbuf_setlen(&buf, 0);
508
for (eol = p = buf.buf; *p; p = eol + 1) {
513
eol = strchrnul(p, '\n');
515
if (starts_with(p, "\\E")) {
516
char *comma = memchr(p, ',', eol - p);
517
struct escape_sequence_entry *e;
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);
532
return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence);
535
int read_key_without_echo(struct strbuf *buf)
537
static int warning_displayed;
540
if (warning_displayed || enable_non_canonical(SAVE_TERM_STDIN) < 0) {
541
if (!warning_displayed) {
542
warning("reading single keystrokes not supported on "
543
"this platform; reading line instead");
544
warning_displayed = 1;
547
return strbuf_getline(buf, stdin);
556
strbuf_addch(buf, ch);
558
if (ch == '\033' /* ESC */) {
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
565
* Start by replacing the Escape byte with ^[ */
566
strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
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.
573
while (!is_known_escape_sequence(buf->buf)) {
574
ch = getchar_with_timeout(500);
577
strbuf_addch(buf, ch);
587
int save_term(enum save_term_flags flags)
589
/* no duplex support available */
590
return -!!(flags & SAVE_TERM_DUPLEX);
593
void restore_term(void)
597
char *git_terminal_prompt(const char *prompt, int echo)
599
return getpass(prompt);
602
int read_key_without_echo(struct strbuf *buf)
604
static int warning_displayed;
607
if (!warning_displayed) {
608
warning("reading single keystrokes not supported on this "
609
"platform; reading line instead");
610
warning_displayed = 1;
617
strbuf_addstr(buf, res);