terminal.c 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. #include "git-compat-util.h"
  2. #include "compat/terminal.h"
  3. #include "sigchain.h"
  4. #include "strbuf.h"
  5. #include "run-command.h"
  6. #include "string-list.h"
  7. #include "hashmap.h"
  8. #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
  9. static void restore_term(void);
  10. static void restore_term_on_signal(int sig)
  11. {
  12. restore_term();
  13. sigchain_pop(sig);
  14. raise(sig);
  15. }
  16. #ifdef HAVE_DEV_TTY
  17. #define INPUT_PATH "/dev/tty"
  18. #define OUTPUT_PATH "/dev/tty"
  19. static int term_fd = -1;
  20. static struct termios old_term;
  21. static void restore_term(void)
  22. {
  23. if (term_fd < 0)
  24. return;
  25. tcsetattr(term_fd, TCSAFLUSH, &old_term);
  26. close(term_fd);
  27. term_fd = -1;
  28. }
  29. static int disable_bits(tcflag_t bits)
  30. {
  31. struct termios t;
  32. term_fd = open("/dev/tty", O_RDWR);
  33. if (tcgetattr(term_fd, &t) < 0)
  34. goto error;
  35. old_term = t;
  36. sigchain_push_common(restore_term_on_signal);
  37. t.c_lflag &= ~bits;
  38. if (!tcsetattr(term_fd, TCSAFLUSH, &t))
  39. return 0;
  40. error:
  41. close(term_fd);
  42. term_fd = -1;
  43. return -1;
  44. }
  45. static int disable_echo(void)
  46. {
  47. return disable_bits(ECHO);
  48. }
  49. static int enable_non_canonical(void)
  50. {
  51. return disable_bits(ICANON | ECHO);
  52. }
  53. #elif defined(GIT_WINDOWS_NATIVE)
  54. #define INPUT_PATH "CONIN$"
  55. #define OUTPUT_PATH "CONOUT$"
  56. #define FORCE_TEXT "t"
  57. static int use_stty = 1;
  58. static struct string_list stty_restore = STRING_LIST_INIT_DUP;
  59. static HANDLE hconin = INVALID_HANDLE_VALUE;
  60. static DWORD cmode;
  61. static void restore_term(void)
  62. {
  63. if (use_stty) {
  64. int i;
  65. struct child_process cp = CHILD_PROCESS_INIT;
  66. if (stty_restore.nr == 0)
  67. return;
  68. strvec_push(&cp.args, "stty");
  69. for (i = 0; i < stty_restore.nr; i++)
  70. strvec_push(&cp.args, stty_restore.items[i].string);
  71. run_command(&cp);
  72. string_list_clear(&stty_restore, 0);
  73. return;
  74. }
  75. if (hconin == INVALID_HANDLE_VALUE)
  76. return;
  77. SetConsoleMode(hconin, cmode);
  78. CloseHandle(hconin);
  79. hconin = INVALID_HANDLE_VALUE;
  80. }
  81. static int disable_bits(DWORD bits)
  82. {
  83. if (use_stty) {
  84. struct child_process cp = CHILD_PROCESS_INIT;
  85. strvec_push(&cp.args, "stty");
  86. if (bits & ENABLE_LINE_INPUT) {
  87. string_list_append(&stty_restore, "icanon");
  88. strvec_push(&cp.args, "-icanon");
  89. }
  90. if (bits & ENABLE_ECHO_INPUT) {
  91. string_list_append(&stty_restore, "echo");
  92. strvec_push(&cp.args, "-echo");
  93. }
  94. if (bits & ENABLE_PROCESSED_INPUT) {
  95. string_list_append(&stty_restore, "-ignbrk");
  96. string_list_append(&stty_restore, "intr");
  97. string_list_append(&stty_restore, "^c");
  98. strvec_push(&cp.args, "ignbrk");
  99. strvec_push(&cp.args, "intr");
  100. strvec_push(&cp.args, "");
  101. }
  102. if (run_command(&cp) == 0)
  103. return 0;
  104. /* `stty` could not be executed; access the Console directly */
  105. use_stty = 0;
  106. }
  107. hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE,
  108. FILE_SHARE_READ, NULL, OPEN_EXISTING,
  109. FILE_ATTRIBUTE_NORMAL, NULL);
  110. if (hconin == INVALID_HANDLE_VALUE)
  111. return -1;
  112. GetConsoleMode(hconin, &cmode);
  113. sigchain_push_common(restore_term_on_signal);
  114. if (!SetConsoleMode(hconin, cmode & ~bits)) {
  115. CloseHandle(hconin);
  116. hconin = INVALID_HANDLE_VALUE;
  117. return -1;
  118. }
  119. return 0;
  120. }
  121. static int disable_echo(void)
  122. {
  123. return disable_bits(ENABLE_ECHO_INPUT);
  124. }
  125. static int enable_non_canonical(void)
  126. {
  127. return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
  128. }
  129. /*
  130. * Override `getchar()`, as the default implementation does not use
  131. * `ReadFile()`.
  132. *
  133. * This poses a problem when we want to see whether the standard
  134. * input has more characters, as the default of Git for Windows is to start the
  135. * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
  136. * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
  137. * `ReadFile()` to be called first to work properly (it only reports 0
  138. * available bytes, otherwise).
  139. *
  140. * So let's just override `getchar()` with a version backed by `ReadFile()` and
  141. * go our merry ways from here.
  142. */
  143. static int mingw_getchar(void)
  144. {
  145. DWORD read = 0;
  146. unsigned char ch;
  147. if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL))
  148. return EOF;
  149. if (!read) {
  150. error("Unexpected 0 read");
  151. return EOF;
  152. }
  153. return ch;
  154. }
  155. #define getchar mingw_getchar
  156. #endif
  157. #ifndef FORCE_TEXT
  158. #define FORCE_TEXT
  159. #endif
  160. char *git_terminal_prompt(const char *prompt, int echo)
  161. {
  162. static struct strbuf buf = STRBUF_INIT;
  163. int r;
  164. FILE *input_fh, *output_fh;
  165. input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT);
  166. if (!input_fh)
  167. return NULL;
  168. output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT);
  169. if (!output_fh) {
  170. fclose(input_fh);
  171. return NULL;
  172. }
  173. if (!echo && disable_echo()) {
  174. fclose(input_fh);
  175. fclose(output_fh);
  176. return NULL;
  177. }
  178. fputs(prompt, output_fh);
  179. fflush(output_fh);
  180. r = strbuf_getline_lf(&buf, input_fh);
  181. if (!echo) {
  182. putc('\n', output_fh);
  183. fflush(output_fh);
  184. }
  185. restore_term();
  186. fclose(input_fh);
  187. fclose(output_fh);
  188. if (r == EOF)
  189. return NULL;
  190. return buf.buf;
  191. }
  192. /*
  193. * The `is_known_escape_sequence()` function returns 1 if the passed string
  194. * corresponds to an Escape sequence that the terminal capabilities contains.
  195. *
  196. * To avoid depending on ncurses or other platform-specific libraries, we rely
  197. * on the presence of the `infocmp` executable to do the job for us (failing
  198. * silently if the program is not available or refused to run).
  199. */
  200. struct escape_sequence_entry {
  201. struct hashmap_entry entry;
  202. char sequence[FLEX_ARRAY];
  203. };
  204. static int sequence_entry_cmp(const void *hashmap_cmp_fn_data,
  205. const struct escape_sequence_entry *e1,
  206. const struct escape_sequence_entry *e2,
  207. const void *keydata)
  208. {
  209. return strcmp(e1->sequence, keydata ? keydata : e2->sequence);
  210. }
  211. static int is_known_escape_sequence(const char *sequence)
  212. {
  213. static struct hashmap sequences;
  214. static int initialized;
  215. if (!initialized) {
  216. struct child_process cp = CHILD_PROCESS_INIT;
  217. struct strbuf buf = STRBUF_INIT;
  218. char *p, *eol;
  219. hashmap_init(&sequences, (hashmap_cmp_fn)sequence_entry_cmp,
  220. NULL, 0);
  221. strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL);
  222. if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0))
  223. strbuf_setlen(&buf, 0);
  224. for (eol = p = buf.buf; *p; p = eol + 1) {
  225. p = strchr(p, '=');
  226. if (!p)
  227. break;
  228. p++;
  229. eol = strchrnul(p, '\n');
  230. if (starts_with(p, "\\E")) {
  231. char *comma = memchr(p, ',', eol - p);
  232. struct escape_sequence_entry *e;
  233. p[0] = '^';
  234. p[1] = '[';
  235. FLEX_ALLOC_MEM(e, sequence, p, comma - p);
  236. hashmap_entry_init(&e->entry,
  237. strhash(e->sequence));
  238. hashmap_add(&sequences, &e->entry);
  239. }
  240. if (!*eol)
  241. break;
  242. }
  243. initialized = 1;
  244. }
  245. return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence);
  246. }
  247. int read_key_without_echo(struct strbuf *buf)
  248. {
  249. static int warning_displayed;
  250. int ch;
  251. if (warning_displayed || enable_non_canonical() < 0) {
  252. if (!warning_displayed) {
  253. warning("reading single keystrokes not supported on "
  254. "this platform; reading line instead");
  255. warning_displayed = 1;
  256. }
  257. return strbuf_getline(buf, stdin);
  258. }
  259. strbuf_reset(buf);
  260. ch = getchar();
  261. if (ch == EOF) {
  262. restore_term();
  263. return EOF;
  264. }
  265. strbuf_addch(buf, ch);
  266. if (ch == '\033' /* ESC */) {
  267. /*
  268. * We are most likely looking at an Escape sequence. Let's try
  269. * to read more bytes, waiting at most half a second, assuming
  270. * that the sequence is complete if we did not receive any byte
  271. * within that time.
  272. *
  273. * Start by replacing the Escape byte with ^[ */
  274. strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
  275. /*
  276. * Query the terminal capabilities once about all the Escape
  277. * sequences it knows about, so that we can avoid waiting for
  278. * half a second when we know that the sequence is complete.
  279. */
  280. while (!is_known_escape_sequence(buf->buf)) {
  281. struct pollfd pfd = { .fd = 0, .events = POLLIN };
  282. if (poll(&pfd, 1, 500) < 1)
  283. break;
  284. ch = getchar();
  285. if (ch == EOF)
  286. return 0;
  287. strbuf_addch(buf, ch);
  288. }
  289. }
  290. restore_term();
  291. return 0;
  292. }
  293. #else
  294. char *git_terminal_prompt(const char *prompt, int echo)
  295. {
  296. return getpass(prompt);
  297. }
  298. int read_key_without_echo(struct strbuf *buf)
  299. {
  300. static int warning_displayed;
  301. const char *res;
  302. if (!warning_displayed) {
  303. warning("reading single keystrokes not supported on this "
  304. "platform; reading line instead");
  305. warning_displayed = 1;
  306. }
  307. res = getpass("");
  308. strbuf_reset(buf);
  309. if (!res)
  310. return EOF;
  311. strbuf_addstr(buf, res);
  312. return 0;
  313. }
  314. #endif