git

Форк
0
/
terminal.c 
621 строка · 13.7 Кб
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

12
static 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

24
static volatile sig_atomic_t term_fd_needs_closing;
25
static int term_fd = -1;
26
static struct termios old_term;
27

28
static const char *background_resume_msg;
29
static const char *restore_error_msg;
30
static volatile sig_atomic_t ttou_received;
31

32
/* async safe error function for use by signal handlers. */
33
static 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

40
static 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

60
static 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

113
static 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

124
static 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

132
void 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

143
int 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

183
static 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

206
static int disable_echo(enum save_term_flags flags)
207
{
208
	return disable_bits(flags, ECHO);
209
}
210

211
static 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
 */
220
static 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

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;
258

259
void restore_term(void)
260
{
261
	if (use_stty) {
262
		int i;
263
		struct child_process cp = CHILD_PROCESS_INIT;
264

265
		if (stty_restore.nr == 0)
266
			return;
267

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);
271
		run_command(&cp);
272
		string_list_clear(&stty_restore, 0);
273
		return;
274
	}
275

276
	sigchain_pop_common();
277

278
	if (hconin == INVALID_HANDLE_VALUE)
279
		return;
280

281
	SetConsoleMode(hconin, cmode_in);
282
	CloseHandle(hconin);
283
	if (cmode_out) {
284
		assert(hconout != INVALID_HANDLE_VALUE);
285
		SetConsoleMode(hconout, cmode_out);
286
		CloseHandle(hconout);
287
	}
288

289
	hconin = hconout = INVALID_HANDLE_VALUE;
290
}
291

292
int save_term(enum save_term_flags flags)
293
{
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)
298
		return -1;
299

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)
305
			goto error;
306

307
		GetConsoleMode(hconout, &cmode_out);
308
	}
309

310
	GetConsoleMode(hconin, &cmode_in);
311
	use_stty = 0;
312
	sigchain_push_common(restore_term_on_signal);
313
	return 0;
314
error:
315
	CloseHandle(hconin);
316
	hconin = INVALID_HANDLE_VALUE;
317
	return -1;
318
}
319

320
static int disable_bits(enum save_term_flags flags, DWORD bits)
321
{
322
	if (use_stty) {
323
		struct child_process cp = CHILD_PROCESS_INIT;
324

325
		strvec_push(&cp.args, "stty");
326

327
		if (bits & ENABLE_LINE_INPUT) {
328
			string_list_append(&stty_restore, "icanon");
329
			/*
330
			 * POSIX allows VMIN and VTIME to overlap with VEOF and
331
			 * VEOL - let's hope that is not the case on windows.
332
			 */
333
			strvec_pushl(&cp.args, "-icanon", "min", "1", "time", "0", NULL);
334
		}
335

336
		if (bits & ENABLE_ECHO_INPUT) {
337
			string_list_append(&stty_restore, "echo");
338
			strvec_push(&cp.args, "-echo");
339
		}
340

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, "");
348
		}
349

350
		if (run_command(&cp) == 0)
351
			return 0;
352

353
		/* `stty` could not be executed; access the Console directly */
354
		use_stty = 0;
355
	}
356

357
	if (save_term(flags) < 0)
358
		return -1;
359

360
	if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
361
		CloseHandle(hconin);
362
		hconin = INVALID_HANDLE_VALUE;
363
		sigchain_pop_common();
364
		return -1;
365
	}
366

367
	return 0;
368
}
369

370
static int disable_echo(enum save_term_flags flags)
371
{
372
	return disable_bits(flags, ENABLE_ECHO_INPUT);
373
}
374

375
static int enable_non_canonical(enum save_term_flags flags)
376
{
377
	return disable_bits(flags,
378
			    ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
379
}
380

381
/*
382
 * Override `getchar()`, as the default implementation does not use
383
 * `ReadFile()`.
384
 *
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).
391
 *
392
 * So let's just override `getchar()` with a version backed by `ReadFile()` and
393
 * go our merry ways from here.
394
 */
395
static int mingw_getchar(void)
396
{
397
	DWORD read = 0;
398
	unsigned char ch;
399

400
	if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL))
401
		return EOF;
402

403
	if (!read) {
404
		error("Unexpected 0 read");
405
		return EOF;
406
	}
407

408
	return ch;
409
}
410
#define getchar mingw_getchar
411

412
static int getchar_with_timeout(int timeout)
413
{
414
	struct pollfd pfd = { .fd = 0, .events = POLLIN };
415

416
	if (poll(&pfd, 1, timeout) < 1)
417
		return EOF;
418

419
	return getchar();
420
}
421

422
#endif
423

424
#ifndef FORCE_TEXT
425
#define FORCE_TEXT
426
#endif
427

428
char *git_terminal_prompt(const char *prompt, int echo)
429
{
430
	static struct strbuf buf = STRBUF_INIT;
431
	int r;
432
	FILE *input_fh, *output_fh;
433

434
	input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT);
435
	if (!input_fh)
436
		return NULL;
437

438
	output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT);
439
	if (!output_fh) {
440
		fclose(input_fh);
441
		return NULL;
442
	}
443

444
	if (!echo && disable_echo(0)) {
445
		fclose(input_fh);
446
		fclose(output_fh);
447
		return NULL;
448
	}
449

450
	fputs(prompt, output_fh);
451
	fflush(output_fh);
452

453
	r = strbuf_getline_lf(&buf, input_fh);
454
	if (!echo) {
455
		putc('\n', output_fh);
456
		fflush(output_fh);
457
	}
458

459
	restore_term();
460
	fclose(input_fh);
461
	fclose(output_fh);
462

463
	if (r == EOF)
464
		return NULL;
465
	return buf.buf;
466
}
467

468
/*
469
 * The `is_known_escape_sequence()` function returns 1 if the passed string
470
 * corresponds to an Escape sequence that the terminal capabilities contains.
471
 *
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).
475
 */
476
struct escape_sequence_entry {
477
	struct hashmap_entry entry;
478
	char sequence[FLEX_ARRAY];
479
};
480

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,
484
			      const void *keydata)
485
{
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);
490
}
491

492
static int is_known_escape_sequence(const char *sequence)
493
{
494
	static struct hashmap sequences;
495
	static int initialized;
496

497
	if (!initialized) {
498
		struct child_process cp = CHILD_PROCESS_INIT;
499
		struct strbuf buf = STRBUF_INIT;
500
		char *p, *eol;
501

502
		hashmap_init(&sequences, sequence_entry_cmp, NULL, 0);
503

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);
507

508
		for (eol = p = buf.buf; *p; p = eol + 1) {
509
			p = strchr(p, '=');
510
			if (!p)
511
				break;
512
			p++;
513
			eol = strchrnul(p, '\n');
514

515
			if (starts_with(p, "\\E")) {
516
				char *comma = memchr(p, ',', eol - p);
517
				struct escape_sequence_entry *e;
518

519
				p[0] = '^';
520
				p[1] = '[';
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);
525
			}
526
			if (!*eol)
527
				break;
528
		}
529
		initialized = 1;
530
	}
531

532
	return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence);
533
}
534

535
int read_key_without_echo(struct strbuf *buf)
536
{
537
	static int warning_displayed;
538
	int ch;
539

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;
545
		}
546

547
		return strbuf_getline(buf, stdin);
548
	}
549

550
	strbuf_reset(buf);
551
	ch = getchar();
552
	if (ch == EOF) {
553
		restore_term();
554
		return EOF;
555
	}
556
	strbuf_addch(buf, ch);
557

558
	if (ch == '\033' /* ESC */) {
559
		/*
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
563
		 * within that time.
564
		 *
565
		 * Start by replacing the Escape byte with ^[ */
566
		strbuf_splice(buf, buf->len - 1, 1, "^[", 2);
567

568
		/*
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.
572
		 */
573
		while (!is_known_escape_sequence(buf->buf)) {
574
			ch = getchar_with_timeout(500);
575
			if (ch == EOF)
576
				break;
577
			strbuf_addch(buf, ch);
578
		}
579
	}
580

581
	restore_term();
582
	return 0;
583
}
584

585
#else
586

587
int save_term(enum save_term_flags flags)
588
{
589
	/* no duplex support available */
590
	return -!!(flags & SAVE_TERM_DUPLEX);
591
}
592

593
void restore_term(void)
594
{
595
}
596

597
char *git_terminal_prompt(const char *prompt, int echo)
598
{
599
	return getpass(prompt);
600
}
601

602
int read_key_without_echo(struct strbuf *buf)
603
{
604
	static int warning_displayed;
605
	const char *res;
606

607
	if (!warning_displayed) {
608
		warning("reading single keystrokes not supported on this "
609
			"platform; reading line instead");
610
		warning_displayed = 1;
611
	}
612

613
	res = getpass("");
614
	strbuf_reset(buf);
615
	if (!res)
616
		return EOF;
617
	strbuf_addstr(buf, res);
618
	return 0;
619
}
620

621
#endif
622

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.