Skip to content

Commit 58fcd54

Browse files
committed
Merge branch 'js/mingw-isatty'
Update the isatty() emulation for Windows by updating the previous hack that depended on internals of (older) MSVC runtime. * js/mingw-isatty: mingw: replace isatty() hack mingw: fix colourization on Cygwin pseudo terminals mingw: adjust is_console() to work with stdin
2 parents 7143258 + a9b8a09 commit 58fcd54

File tree

1 file changed

+84
-114
lines changed

1 file changed

+84
-114
lines changed

compat/winansi.c

Lines changed: 84 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
#include "../git-compat-util.h"
77
#include <wingdi.h>
88
#include <winreg.h>
9+
#include "win32.h"
910

10-
/* In this file, we actually want to use Windows' own isatty(). */
11-
#undef isatty
11+
static int fd_is_interactive[3] = { 0, 0, 0 };
12+
#define FD_CONSOLE 0x1
13+
#define FD_SWAPPED 0x2
14+
#define FD_MSYS 0x4
1215

1316
/*
1417
ANSI codes used by git: m, K
@@ -84,6 +87,7 @@ static void warn_if_raster_font(void)
8487
static int is_console(int fd)
8588
{
8689
CONSOLE_SCREEN_BUFFER_INFO sbi;
90+
DWORD mode;
8791
HANDLE hcon;
8892

8993
static int initialized = 0;
@@ -98,9 +102,15 @@ static int is_console(int fd)
98102
return 0;
99103

100104
/* check if its a handle to a console output screen buffer */
101-
if (!GetConsoleScreenBufferInfo(hcon, &sbi))
105+
if (!fd) {
106+
if (!GetConsoleMode(hcon, &mode))
107+
return 0;
108+
} else if (!GetConsoleScreenBufferInfo(hcon, &sbi))
102109
return 0;
103110

111+
if (fd >= 0 && fd <= 2)
112+
fd_is_interactive[fd] |= FD_CONSOLE;
113+
104114
/* initialize attributes */
105115
if (!initialized) {
106116
console = hcon;
@@ -462,76 +472,50 @@ static HANDLE duplicate_handle(HANDLE hnd)
462472
return hresult;
463473
}
464474

465-
466-
/*
467-
* Make MSVCRT's internal file descriptor control structure accessible
468-
* so that we can tweak OS handles and flags directly (we need MSVCRT
469-
* to treat our pipe handle as if it were a console).
470-
*
471-
* We assume that the ioinfo structure (exposed by MSVCRT.dll via
472-
* __pioinfo) starts with the OS handle and the flags. The exact size
473-
* varies between MSVCRT versions, so we try different sizes until
474-
* toggling the FDEV bit of _pioinfo(1)->osflags is reflected in
475-
* isatty(1).
476-
*/
477-
typedef struct {
478-
HANDLE osfhnd;
479-
char osflags;
480-
} ioinfo;
481-
482-
extern __declspec(dllimport) ioinfo *__pioinfo[];
483-
484-
static size_t sizeof_ioinfo = 0;
485-
486-
#define IOINFO_L2E 5
487-
#define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E)
488-
489-
#define FPIPE 0x08
490-
#define FDEV 0x40
491-
492-
static inline ioinfo* _pioinfo(int fd)
493-
{
494-
return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] +
495-
(fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo);
496-
}
497-
498-
static int init_sizeof_ioinfo(void)
499-
{
500-
int istty, wastty;
501-
/* don't init twice */
502-
if (sizeof_ioinfo)
503-
return sizeof_ioinfo >= 256;
504-
505-
sizeof_ioinfo = sizeof(ioinfo);
506-
wastty = isatty(1);
507-
while (sizeof_ioinfo < 256) {
508-
/* toggle FDEV flag, check isatty, then toggle back */
509-
_pioinfo(1)->osflags ^= FDEV;
510-
istty = isatty(1);
511-
_pioinfo(1)->osflags ^= FDEV;
512-
/* return if we found the correct size */
513-
if (istty != wastty)
514-
return 0;
515-
sizeof_ioinfo += sizeof(void*);
516-
}
517-
error("Tweaking file descriptors doesn't work with this MSVCRT.dll");
518-
return 1;
519-
}
520-
521475
static HANDLE swap_osfhnd(int fd, HANDLE new_handle)
522476
{
523-
ioinfo *pioinfo;
524-
HANDLE old_handle;
525-
526-
/* init ioinfo size if we haven't done so */
527-
if (init_sizeof_ioinfo())
528-
return INVALID_HANDLE_VALUE;
529-
530-
/* get ioinfo pointer and change the handles */
531-
pioinfo = _pioinfo(fd);
532-
old_handle = pioinfo->osfhnd;
533-
pioinfo->osfhnd = new_handle;
534-
return old_handle;
477+
/*
478+
* Create a copy of the original handle associated with fd
479+
* because the original will get closed when we dup2().
480+
*/
481+
HANDLE handle = (HANDLE)_get_osfhandle(fd);
482+
HANDLE duplicate = duplicate_handle(handle);
483+
484+
/* Create a temp fd associated with the already open "new_handle". */
485+
int new_fd = _open_osfhandle((intptr_t)new_handle, O_BINARY);
486+
487+
assert((fd == 1) || (fd == 2));
488+
489+
/*
490+
* Use stock dup2() to re-bind fd to the new handle. Note that
491+
* this will implicitly close(1) and close both fd=1 and the
492+
* originally associated handle. It will open a new fd=1 and
493+
* call DuplicateHandle() on the handle associated with new_fd.
494+
* It is because of this implicit close() that we created the
495+
* copy of the original.
496+
*
497+
* Note that the OS can recycle HANDLE (numbers) just like it
498+
* recycles fd (numbers), so we must update the cached value
499+
* of "console". You can use GetFileType() to see that
500+
* handle and _get_osfhandle(fd) may have the same number
501+
* value, but they refer to different actual files now.
502+
*
503+
* Note that dup2() when given target := {0,1,2} will also
504+
* call SetStdHandle(), so we don't need to worry about that.
505+
*/
506+
dup2(new_fd, fd);
507+
if (console == handle)
508+
console = duplicate;
509+
handle = INVALID_HANDLE_VALUE;
510+
511+
/* Close the temp fd. This explicitly closes "new_handle"
512+
* (because it has been associated with it).
513+
*/
514+
close(new_fd);
515+
516+
fd_is_interactive[fd] |= FD_SWAPPED;
517+
518+
return duplicate;
535519
}
536520

537521
#ifdef DETECT_MSYS_TTY
@@ -558,49 +542,33 @@ static void detect_msys_tty(int fd)
558542
name = nameinfo->Name.Buffer;
559543
name[nameinfo->Name.Length / sizeof(*name)] = 0;
560544

561-
/* check if this could be a MSYS2 pty pipe ('msys-XXXX-ptyN-XX') */
562-
if (!wcsstr(name, L"msys-") || !wcsstr(name, L"-pty"))
545+
/*
546+
* Check if this could be a MSYS2 pty pipe ('msys-XXXX-ptyN-XX')
547+
* or a cygwin pty pipe ('cygwin-XXXX-ptyN-XX')
548+
*/
549+
if ((!wcsstr(name, L"msys-") && !wcsstr(name, L"cygwin-")) ||
550+
!wcsstr(name, L"-pty"))
563551
return;
564552

565-
/* init ioinfo size if we haven't done so */
566-
if (init_sizeof_ioinfo())
567-
return;
568-
569-
/* set FDEV flag, reset FPIPE flag */
570-
_pioinfo(fd)->osflags &= ~FPIPE;
571-
_pioinfo(fd)->osflags |= FDEV;
553+
fd_is_interactive[fd] |= FD_MSYS;
572554
}
573555

574556
#endif
575557

558+
/*
559+
* Wrapper for isatty(). Most calls in the main git code
560+
* call isatty(1 or 2) to see if the instance is interactive
561+
* and should: be colored, show progress, paginate output.
562+
* We lie and give results for what the descriptor WAS at
563+
* startup (and ignore any pipe redirection we internally
564+
* do).
565+
*/
566+
#undef isatty
576567
int winansi_isatty(int fd)
577568
{
578-
int res = isatty(fd);
579-
580-
if (res) {
581-
/*
582-
* Make sure that /dev/null is not fooling Git into believing
583-
* that we are connected to a terminal, as "_isatty() returns a
584-
* nonzero value if the descriptor is associated with a
585-
* character device."; for more information, see
586-
*
587-
* https://msdn.microsoft.com/en-us/library/f4s0ddew.aspx
588-
*/
589-
HANDLE handle = (HANDLE)_get_osfhandle(fd);
590-
if (fd == STDIN_FILENO) {
591-
DWORD dummy;
592-
593-
if (!GetConsoleMode(handle, &dummy))
594-
res = 0;
595-
} else if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
596-
CONSOLE_SCREEN_BUFFER_INFO dummy;
597-
598-
if (!GetConsoleScreenBufferInfo(handle, &dummy))
599-
res = 0;
600-
}
601-
}
602-
603-
return res;
569+
if (fd >= 0 && fd <= 2)
570+
return fd_is_interactive[fd] != 0;
571+
return isatty(fd);
604572
}
605573

606574
void winansi_init(void)
@@ -611,6 +579,10 @@ void winansi_init(void)
611579
/* check if either stdout or stderr is a console output screen buffer */
612580
con1 = is_console(1);
613581
con2 = is_console(2);
582+
583+
/* Also compute console bit for fd 0 even though we don't need the result here. */
584+
is_console(0);
585+
614586
if (!con1 && !con2) {
615587
#ifdef DETECT_MSYS_TTY
616588
/* check if stdin / stdout / stderr are MSYS2 pty pipes */
@@ -654,12 +626,10 @@ void winansi_init(void)
654626
*/
655627
HANDLE winansi_get_osfhandle(int fd)
656628
{
657-
HANDLE hnd = (HANDLE) _get_osfhandle(fd);
658-
if (isatty(fd) && GetFileType(hnd) == FILE_TYPE_PIPE) {
659-
if (fd == 1 && hconsole1)
660-
return hconsole1;
661-
else if (fd == 2 && hconsole2)
662-
return hconsole2;
663-
}
664-
return hnd;
629+
if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED))
630+
return hconsole1;
631+
if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED))
632+
return hconsole2;
633+
634+
return (HANDLE)_get_osfhandle(fd);
665635
}

0 commit comments

Comments
 (0)