Git fork
at reftables-rust 620 lines 14 kB view raw
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 10#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE) 11 12static void restore_term_on_signal(int sig) 13{ 14 restore_term(); 15 /* restore_term calls sigchain_pop_common */ 16 raise(sig); 17} 18 19#ifdef HAVE_DEV_TTY 20 21#define INPUT_PATH "/dev/tty" 22#define OUTPUT_PATH "/dev/tty" 23 24static volatile sig_atomic_t term_fd_needs_closing; 25static int term_fd = -1; 26static struct termios old_term; 27 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 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 132void restore_term(void) 133{ 134 if (term_fd < 0) 135 return; 136 137 tcsetattr(term_fd, TCSAFLUSH, &old_term); 138 close_term_fd(); 139 sigchain_pop_common(); 140 reset_job_signals(); 141} 142 143int save_term(enum save_term_flags flags) 144{ 145 struct sigaction sa; 146 147 if (term_fd < 0) 148 term_fd = ((flags & SAVE_TERM_STDIN) 149 ? 0 150 : open("/dev/tty", O_RDWR)); 151 if (term_fd < 0) 152 return -1; 153 term_fd_needs_closing = !(flags & SAVE_TERM_STDIN); 154 if (tcgetattr(term_fd, &old_term) < 0) { 155 close_term_fd(); 156 return -1; 157 } 158 sigchain_push_common(restore_term_on_signal); 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); 179 180 return 0; 181} 182 183static int disable_bits(enum save_term_flags flags, tcflag_t bits) 184{ 185 struct termios t; 186 187 if (save_term(flags) < 0) 188 return -1; 189 190 t = old_term; 191 192 t.c_lflag &= ~bits; 193 if (bits & ICANON) { 194 t.c_cc[VMIN] = 1; 195 t.c_cc[VTIME] = 0; 196 } 197 if (!tcsetattr(term_fd, TCSAFLUSH, &t)) 198 return 0; 199 200 sigchain_pop_common(); 201 reset_job_signals(); 202 close_term_fd(); 203 return -1; 204} 205 206static int disable_echo(enum save_term_flags flags) 207{ 208 return disable_bits(flags, ECHO); 209} 210 211static int enable_non_canonical(enum save_term_flags flags) 212{ 213 return disable_bits(flags, ICANON | ECHO); 214} 215 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 226 again: 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); 236 if (!res) 237 return EOF; 238 if (res < 0) { 239 if (errno == EINTR) 240 goto again; 241 else 242 return EOF; 243 } 244 return getchar(); 245} 246 247#elif defined(GIT_WINDOWS_NATIVE) 248 249#define INPUT_PATH "CONIN$" 250#define OUTPUT_PATH "CONOUT$" 251#define FORCE_TEXT "t" 252 253static int use_stty = 1; 254static struct string_list stty_restore = STRING_LIST_INIT_DUP; 255static HANDLE hconin = INVALID_HANDLE_VALUE; 256static HANDLE hconout = INVALID_HANDLE_VALUE; 257static DWORD cmode_in, cmode_out; 258 259void restore_term(void) 260{ 261 if (use_stty) { 262 struct child_process cp = CHILD_PROCESS_INIT; 263 264 if (stty_restore.nr == 0) 265 return; 266 267 strvec_push(&cp.args, "stty"); 268 for (size_t i = 0; i < stty_restore.nr; i++) 269 strvec_push(&cp.args, stty_restore.items[i].string); 270 run_command(&cp); 271 string_list_clear(&stty_restore, 0); 272 return; 273 } 274 275 sigchain_pop_common(); 276 277 if (hconin == INVALID_HANDLE_VALUE) 278 return; 279 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 291int save_term(enum save_term_flags flags) 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 299 if (flags & SAVE_TERM_DUPLEX) { 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; 311 sigchain_push_common(restore_term_on_signal); 312 return 0; 313error: 314 CloseHandle(hconin); 315 hconin = INVALID_HANDLE_VALUE; 316 return -1; 317} 318 319static int disable_bits(enum save_term_flags flags, DWORD bits) 320{ 321 if (use_stty) { 322 struct child_process cp = CHILD_PROCESS_INIT; 323 324 strvec_push(&cp.args, "stty"); 325 326 if (bits & ENABLE_LINE_INPUT) { 327 string_list_append(&stty_restore, "icanon"); 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); 333 } 334 335 if (bits & ENABLE_ECHO_INPUT) { 336 string_list_append(&stty_restore, "echo"); 337 strvec_push(&cp.args, "-echo"); 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"); 344 strvec_push(&cp.args, "ignbrk"); 345 strvec_push(&cp.args, "intr"); 346 strvec_push(&cp.args, ""); 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 356 if (save_term(flags) < 0) 357 return -1; 358 359 if (!SetConsoleMode(hconin, cmode_in & ~bits)) { 360 CloseHandle(hconin); 361 hconin = INVALID_HANDLE_VALUE; 362 sigchain_pop_common(); 363 return -1; 364 } 365 366 return 0; 367} 368 369static int disable_echo(enum save_term_flags flags) 370{ 371 return disable_bits(flags, ENABLE_ECHO_INPUT); 372} 373 374static int enable_non_canonical(enum save_term_flags flags) 375{ 376 return disable_bits(flags, 377 ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); 378} 379 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 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 421#endif 422 423#ifndef FORCE_TEXT 424#define FORCE_TEXT 425#endif 426 427char *git_terminal_prompt(const char *prompt, int echo) 428{ 429 static struct strbuf buf = STRBUF_INIT; 430 int r; 431 FILE *input_fh, *output_fh; 432 433 input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT); 434 if (!input_fh) 435 return NULL; 436 437 output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT); 438 if (!output_fh) { 439 fclose(input_fh); 440 return NULL; 441 } 442 443 if (!echo && disable_echo(0)) { 444 fclose(input_fh); 445 fclose(output_fh); 446 return NULL; 447 } 448 449 fputs(prompt, output_fh); 450 fflush(output_fh); 451 452 r = strbuf_getline_lf(&buf, input_fh); 453 if (!echo) { 454 putc('\n', output_fh); 455 fflush(output_fh); 456 } 457 458 restore_term(); 459 fclose(input_fh); 460 fclose(output_fh); 461 462 if (r == EOF) 463 return NULL; 464 return buf.buf; 465} 466 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 480static int sequence_entry_cmp(const void *hashmap_cmp_fn_data UNUSED, 481 const struct hashmap_entry *he1, 482 const struct hashmap_entry *he2, 483 const void *keydata) 484{ 485 const struct escape_sequence_entry 486 *e1 = container_of(he1, const struct escape_sequence_entry, entry), 487 *e2 = container_of(he2, const struct escape_sequence_entry, entry); 488 return strcmp(e1->sequence, keydata ? keydata : e2->sequence); 489} 490 491static int is_known_escape_sequence(const char *sequence) 492{ 493 static struct hashmap sequences; 494 static int initialized; 495 496 if (!initialized) { 497 struct child_process cp = CHILD_PROCESS_INIT; 498 struct strbuf buf = STRBUF_INIT; 499 char *p, *eol; 500 501 hashmap_init(&sequences, sequence_entry_cmp, 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 534int 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 586int save_term(enum save_term_flags flags) 587{ 588 /* no duplex support available */ 589 return -!!(flags & SAVE_TERM_DUPLEX); 590} 591 592void restore_term(void) 593{ 594} 595 596char *git_terminal_prompt(const char *prompt, int echo UNUSED) 597{ 598 return getpass(prompt); 599} 600 601int 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