diff options
Diffstat (limited to 'compat/winansi.c')
-rw-r--r-- | compat/winansi.c | 207 |
1 files changed, 138 insertions, 69 deletions
diff --git a/compat/winansi.c b/compat/winansi.c index efc5bb3a4b..a11a0f16d2 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -6,6 +6,12 @@ #include "../git-compat-util.h" #include <wingdi.h> #include <winreg.h> +#include "win32.h" + +static int fd_is_interactive[3] = { 0, 0, 0 }; +#define FD_CONSOLE 0x1 +#define FD_SWAPPED 0x2 +#define FD_MSYS 0x4 /* ANSI codes used by git: m, K @@ -23,6 +29,7 @@ static HANDLE hthread, hread, hwrite; static HANDLE hconsole1, hconsole2; #ifdef __MINGW32__ +#if !defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 5 typedef struct _CONSOLE_FONT_INFOEX { ULONG cbSize; DWORD nFont; @@ -32,6 +39,7 @@ typedef struct _CONSOLE_FONT_INFOEX { WCHAR FaceName[LF_FACESIZE]; } CONSOLE_FONT_INFOEX, *PCONSOLE_FONT_INFOEX; #endif +#endif typedef BOOL (WINAPI *PGETCURRENTCONSOLEFONTEX)(HANDLE, BOOL, PCONSOLE_FONT_INFOEX); @@ -79,6 +87,7 @@ static void warn_if_raster_font(void) static int is_console(int fd) { CONSOLE_SCREEN_BUFFER_INFO sbi; + DWORD mode; HANDLE hcon; static int initialized = 0; @@ -93,9 +102,22 @@ static int is_console(int fd) return 0; /* check if its a handle to a console output screen buffer */ - if (!GetConsoleScreenBufferInfo(hcon, &sbi)) + if (!fd) { + if (!GetConsoleMode(hcon, &mode)) + return 0; + /* + * This code path is only reached if there is no console + * attached to stdout/stderr, i.e. we will not need to output + * any text to any console, therefore we might just as well + * use black as foreground color. + */ + sbi.wAttributes = 0; + } else if (!GetConsoleScreenBufferInfo(hcon, &sbi)) return 0; + if (fd >= 0 && fd <= 2) + fd_is_interactive[fd] |= FD_CONSOLE; + /* initialize attributes */ if (!initialized) { console = hcon; @@ -118,6 +140,11 @@ static void write_console(unsigned char *str, size_t len) /* convert utf-8 to utf-16 */ int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len); + if (wlen < 0) { + wchar_t *err = L"[invalid]"; + WriteConsoleW(console, err, wcslen(err), &dummy, NULL); + return; + } /* write directly to console */ WriteConsoleW(console, wbuf, wlen, &dummy, NULL); @@ -452,79 +479,109 @@ static HANDLE duplicate_handle(HANDLE hnd) HANDLE hresult, hproc = GetCurrentProcess(); if (!DuplicateHandle(hproc, hnd, hproc, &hresult, 0, TRUE, DUPLICATE_SAME_ACCESS)) - die_lasterr("DuplicateHandle(%li) failed", (long) hnd); + die_lasterr("DuplicateHandle(%li) failed", + (long) (intptr_t) hnd); return hresult; } +static HANDLE swap_osfhnd(int fd, HANDLE new_handle) +{ + /* + * Create a copy of the original handle associated with fd + * because the original will get closed when we dup2(). + */ + HANDLE handle = (HANDLE)_get_osfhandle(fd); + HANDLE duplicate = duplicate_handle(handle); + + /* Create a temp fd associated with the already open "new_handle". */ + int new_fd = _open_osfhandle((intptr_t)new_handle, O_BINARY); + + assert((fd == 1) || (fd == 2)); + + /* + * Use stock dup2() to re-bind fd to the new handle. Note that + * this will implicitly close(1) and close both fd=1 and the + * originally associated handle. It will open a new fd=1 and + * call DuplicateHandle() on the handle associated with new_fd. + * It is because of this implicit close() that we created the + * copy of the original. + * + * Note that we need to update the cached console handle to the + * duplicated one because the dup2() call will implicitly close + * the original one. + * + * Note that dup2() when given target := {0,1,2} will also + * call SetStdHandle(), so we don't need to worry about that. + */ + if (console == handle) + console = duplicate; + dup2(new_fd, fd); + + /* Close the temp fd. This explicitly closes "new_handle" + * (because it has been associated with it). + */ + close(new_fd); + + if (fd == 2) + setvbuf(stderr, NULL, _IONBF, BUFSIZ); + fd_is_interactive[fd] |= FD_SWAPPED; + + return duplicate; +} -/* - * Make MSVCRT's internal file descriptor control structure accessible - * so that we can tweak OS handles and flags directly (we need MSVCRT - * to treat our pipe handle as if it were a console). - * - * We assume that the ioinfo structure (exposed by MSVCRT.dll via - * __pioinfo) starts with the OS handle and the flags. The exact size - * varies between MSVCRT versions, so we try different sizes until - * toggling the FDEV bit of _pioinfo(1)->osflags is reflected in - * isatty(1). - */ -typedef struct { - HANDLE osfhnd; - char osflags; -} ioinfo; - -extern __declspec(dllimport) ioinfo *__pioinfo[]; +#ifdef DETECT_MSYS_TTY -static size_t sizeof_ioinfo = 0; +#include <winternl.h> +#include <ntstatus.h> -#define IOINFO_L2E 5 -#define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E) +static void detect_msys_tty(int fd) +{ + ULONG result; + BYTE buffer[1024]; + POBJECT_NAME_INFORMATION nameinfo = (POBJECT_NAME_INFORMATION) buffer; + PWSTR name; + + /* check if fd is a pipe */ + HANDLE h = (HANDLE) _get_osfhandle(fd); + if (GetFileType(h) != FILE_TYPE_PIPE) + return; -#define FDEV 0x40 + /* get pipe name */ + if (!NT_SUCCESS(NtQueryObject(h, ObjectNameInformation, + buffer, sizeof(buffer) - 2, &result))) + return; + name = nameinfo->Name.Buffer; + name[nameinfo->Name.Length / sizeof(*name)] = 0; + + /* + * Check if this could be a MSYS2 pty pipe ('msys-XXXX-ptyN-XX') + * or a cygwin pty pipe ('cygwin-XXXX-ptyN-XX') + */ + if ((!wcsstr(name, L"msys-") && !wcsstr(name, L"cygwin-")) || + !wcsstr(name, L"-pty")) + return; -static inline ioinfo* _pioinfo(int fd) -{ - return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] + - (fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo); + if (fd == 2) + setvbuf(stderr, NULL, _IONBF, BUFSIZ); + fd_is_interactive[fd] |= FD_MSYS; } -static int init_sizeof_ioinfo() -{ - int istty, wastty; - /* don't init twice */ - if (sizeof_ioinfo) - return sizeof_ioinfo >= 256; - - sizeof_ioinfo = sizeof(ioinfo); - wastty = isatty(1); - while (sizeof_ioinfo < 256) { - /* toggle FDEV flag, check isatty, then toggle back */ - _pioinfo(1)->osflags ^= FDEV; - istty = isatty(1); - _pioinfo(1)->osflags ^= FDEV; - /* return if we found the correct size */ - if (istty != wastty) - return 0; - sizeof_ioinfo += sizeof(void*); - } - error("Tweaking file descriptors doesn't work with this MSVCRT.dll"); - return 1; -} +#endif -static HANDLE swap_osfhnd(int fd, HANDLE new_handle) +/* + * Wrapper for isatty(). Most calls in the main git code + * call isatty(1 or 2) to see if the instance is interactive + * and should: be colored, show progress, paginate output. + * We lie and give results for what the descriptor WAS at + * startup (and ignore any pipe redirection we internally + * do). + */ +#undef isatty +int winansi_isatty(int fd) { - ioinfo *pioinfo; - HANDLE old_handle; - - /* init ioinfo size if we haven't done so */ - if (init_sizeof_ioinfo()) - return INVALID_HANDLE_VALUE; - - /* get ioinfo pointer and change the handles */ - pioinfo = _pioinfo(fd); - old_handle = pioinfo->osfhnd; - pioinfo->osfhnd = new_handle; - return old_handle; + if (fd >= 0 && fd <= 2) + return fd_is_interactive[fd] != 0; + return isatty(fd); } void winansi_init(void) @@ -535,11 +592,22 @@ void winansi_init(void) /* check if either stdout or stderr is a console output screen buffer */ con1 = is_console(1); con2 = is_console(2); - if (!con1 && !con2) + + /* Also compute console bit for fd 0 even though we don't need the result here. */ + is_console(0); + + if (!con1 && !con2) { +#ifdef DETECT_MSYS_TTY + /* check if stdin / stdout / stderr are MSYS2 pty pipes */ + detect_msys_tty(0); + detect_msys_tty(1); + detect_msys_tty(2); +#endif return; + } /* create a named pipe to communicate with the console thread */ - sprintf(name, "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId()); + xsnprintf(name, sizeof(name), "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId()); hwrite = CreateNamedPipe(name, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL); if (hwrite == INVALID_HANDLE_VALUE) @@ -571,9 +639,10 @@ void winansi_init(void) */ HANDLE winansi_get_osfhandle(int fd) { - HANDLE hnd = (HANDLE) _get_osfhandle(fd); - if ((fd == 1 || fd == 2) && isatty(fd) - && GetFileType(hnd) == FILE_TYPE_PIPE) - return (fd == 1) ? hconsole1 : hconsole2; - return hnd; + if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED)) + return hconsole1; + if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED)) + return hconsole2; + + return (HANDLE)_get_osfhandle(fd); } |