git/compat/terminal.c

#include "git-compat-util.h"
#include "compat/terminal.h"
#include "gettext.h"
#include "sigchain.h"
#include "strbuf.h"
#include "run-command.h"
#include "string-list.h"
#include "hashmap.h"

#if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)

static void restore_term_on_signal(int sig)
{}

#ifdef HAVE_DEV_TTY

#define INPUT_PATH
#define OUTPUT_PATH

static volatile sig_atomic_t term_fd_needs_closing;
static int term_fd =;
static struct termios old_term;

static const char *background_resume_msg;
static const char *restore_error_msg;
static volatile sig_atomic_t ttou_received;

/* async safe error function for use by signal handlers. */
static void write_err(const char *msg)
{}

static void print_background_resume_msg(int signo)
{}

static void restore_terminal_on_suspend(int signo)
{}

static void reset_job_signals(void)
{}

static void close_term_fd(void)
{}

void restore_term(void)
{}

int save_term(enum save_term_flags flags)
{}

static int disable_bits(enum save_term_flags flags, tcflag_t bits)
{}

static int disable_echo(enum save_term_flags flags)
{}

static int enable_non_canonical(enum save_term_flags flags)
{}

/*
 * On macos it is not possible to use poll() with a terminal so use select
 * instead.
 */
static int getchar_with_timeout(int timeout)
{}

#elif defined(GIT_WINDOWS_NATIVE)

#define INPUT_PATH
#define OUTPUT_PATH
#define FORCE_TEXT

static int use_stty = 1;
static struct string_list stty_restore = STRING_LIST_INIT_DUP;
static HANDLE hconin = INVALID_HANDLE_VALUE;
static HANDLE hconout = INVALID_HANDLE_VALUE;
static DWORD cmode_in, cmode_out;

void restore_term(void)
{
	if (use_stty) {
		int i;
		struct child_process cp = CHILD_PROCESS_INIT;

		if (stty_restore.nr == 0)
			return;

		strvec_push(&cp.args, "stty");
		for (i = 0; i < stty_restore.nr; i++)
			strvec_push(&cp.args, stty_restore.items[i].string);
		run_command(&cp);
		string_list_clear(&stty_restore, 0);
		return;
	}

	sigchain_pop_common();

	if (hconin == INVALID_HANDLE_VALUE)
		return;

	SetConsoleMode(hconin, cmode_in);
	CloseHandle(hconin);
	if (cmode_out) {
		assert(hconout != INVALID_HANDLE_VALUE);
		SetConsoleMode(hconout, cmode_out);
		CloseHandle(hconout);
	}

	hconin = hconout = INVALID_HANDLE_VALUE;
}

int save_term(enum save_term_flags flags)
{
	hconin = CreateFileA("CONIN$", GENERIC_READ | GENERIC_WRITE,
	    FILE_SHARE_READ, NULL, OPEN_EXISTING,
	    FILE_ATTRIBUTE_NORMAL, NULL);
	if (hconin == INVALID_HANDLE_VALUE)
		return -1;

	if (flags & SAVE_TERM_DUPLEX) {
		hconout = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE,
			FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL, NULL);
		if (hconout == INVALID_HANDLE_VALUE)
			goto error;

		GetConsoleMode(hconout, &cmode_out);
	}

	GetConsoleMode(hconin, &cmode_in);
	use_stty = 0;
	sigchain_push_common(restore_term_on_signal);
	return 0;
error:
	CloseHandle(hconin);
	hconin = INVALID_HANDLE_VALUE;
	return -1;
}

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

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

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

		if (bits & ENABLE_ECHO_INPUT) {
			string_list_append(&stty_restore, "echo");
			strvec_push(&cp.args, "-echo");
		}

		if (bits & ENABLE_PROCESSED_INPUT) {
			string_list_append(&stty_restore, "-ignbrk");
			string_list_append(&stty_restore, "intr");
			string_list_append(&stty_restore, "^c");
			strvec_push(&cp.args, "ignbrk");
			strvec_push(&cp.args, "intr");
			strvec_push(&cp.args, "");
		}

		if (run_command(&cp) == 0)
			return 0;

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

	if (save_term(flags) < 0)
		return -1;

	if (!SetConsoleMode(hconin, cmode_in & ~bits)) {
		CloseHandle(hconin);
		hconin = INVALID_HANDLE_VALUE;
		sigchain_pop_common();
		return -1;
	}

	return 0;
}

static int disable_echo(enum save_term_flags flags)
{
	return disable_bits(flags, ENABLE_ECHO_INPUT);
}

static int enable_non_canonical(enum save_term_flags flags)
{
	return disable_bits(flags,
			    ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
}

/*
 * Override `getchar()`, as the default implementation does not use
 * `ReadFile()`.
 *
 * This poses a problem when we want to see whether the standard
 * input has more characters, as the default of Git for Windows is to start the
 * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case
 * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require
 * `ReadFile()` to be called first to work properly (it only reports 0
 * available bytes, otherwise).
 *
 * So let's just override `getchar()` with a version backed by `ReadFile()` and
 * go our merry ways from here.
 */
static int mingw_getchar(void)
{
	DWORD read = 0;
	unsigned char ch;

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

	if (!read) {
		error("Unexpected 0 read");
		return EOF;
	}

	return ch;
}
#define getchar

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

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

	return getchar();
}

#endif

#ifndef FORCE_TEXT
#define FORCE_TEXT
#endif

char *git_terminal_prompt(const char *prompt, int echo)
{}

/*
 * The `is_known_escape_sequence()` function returns 1 if the passed string
 * corresponds to an Escape sequence that the terminal capabilities contains.
 *
 * To avoid depending on ncurses or other platform-specific libraries, we rely
 * on the presence of the `infocmp` executable to do the job for us (failing
 * silently if the program is not available or refused to run).
 */
struct escape_sequence_entry {};

static int sequence_entry_cmp(const void *hashmap_cmp_fn_data UNUSED,
			      const struct hashmap_entry *he1,
			      const struct hashmap_entry *he2,
			      const void *keydata)
{}

static int is_known_escape_sequence(const char *sequence)
{}

int read_key_without_echo(struct strbuf *buf)
{}

#else

int save_term(enum save_term_flags flags)
{
	/* no duplex support available */
	return -!!(flags & SAVE_TERM_DUPLEX);
}

void restore_term(void)
{
}

char *git_terminal_prompt(const char *prompt, int echo UNUSED)
{
	return getpass(prompt);
}

int read_key_without_echo(struct strbuf *buf)
{
	static int warning_displayed;
	const char *res;

	if (!warning_displayed) {
		warning("reading single keystrokes not supported on this "
			"platform; reading line instead");
		warning_displayed = 1;
	}

	res = getpass("");
	strbuf_reset(buf);
	if (!res)
		return EOF;
	strbuf_addstr(buf, res);
	return 0;
}

#endif