mingw: replace isatty() hack
Git for Windows has carried a patch that depended on internals of MSVC runtime, but it does not work correctly with recent MSVC runtime. A replacement was written originally for compiling with VC++. The patch in this message is a backport of that replacement, and it also fixes the previous attempt to make isatty() tell that /dev/null is *not* an interactive terminal. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Tested-by: Beat Bolli <dev+git@drbeat.li> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jeff Hostetler committed
Dec 22, 2016 at 18:09 UTC
a9b8a09c3c30886c79133da9f48ef9f98c21c3b2
1 file changed
+69
-107
compat/winansi.c
+69
-107
@@ -6,9 +6,12 @@
6
#include "../git-compat-util.h"
7
#include <wingdi.h>
8
#include <winreg.h>
9
+#include "win32.h"
10
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
15
16
/*
17
ANSI codes used by git: m, K
@@ -105,6 +108,9 @@ static int is_console(int fd)
108
} else if (!GetConsoleScreenBufferInfo(hcon, &sbi))
109
return 0;
110
111
+ if (fd >= 0 && fd <= 2)
112
+ fd_is_interactive[fd] |= FD_CONSOLE;
113
+
114
/* initialize attributes */
115
if (!initialized) {
116
console = hcon;
@@ -466,76 +472,50 @@ static HANDLE duplicate_handle(HANDLE hnd)
472
return hresult;
473
}
474
475
+static HANDLE swap_osfhnd(int fd, HANDLE new_handle)
476
+{
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
470
-/*
471
- * Make MSVCRT's internal file descriptor control structure accessible
472
- * so that we can tweak OS handles and flags directly (we need MSVCRT
473
- * to treat our pipe handle as if it were a console).
474
- *
475
- * We assume that the ioinfo structure (exposed by MSVCRT.dll via
476
- * __pioinfo) starts with the OS handle and the flags. The exact size
477
- * varies between MSVCRT versions, so we try different sizes until
478
- * toggling the FDEV bit of _pioinfo(1)->osflags is reflected in
479
- * isatty(1).
480
- */
481
-typedef struct {
482
- HANDLE osfhnd;
483
- char osflags;
484
-} ioinfo;
485
-
486
-extern __declspec(dllimport) ioinfo *__pioinfo[];
487
-
488
-static size_t sizeof_ioinfo = 0;
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
490
-#define IOINFO_L2E 5
491
-#define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E)
487
+ assert((fd == 1) || (fd == 2));
488
493
-#define FPIPE 0x08
494
-#define FDEV 0x40
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
496
-static inline ioinfo* _pioinfo(int fd)
497
-{
498
- return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] +
499
- (fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo);
500
-}
511
+ /* Close the temp fd. This explicitly closes "new_handle"
512
+ * (because it has been associated with it).
513
+ */
514
+ close(new_fd);
515
502
-static int init_sizeof_ioinfo(void)
503
-{
504
- int istty, wastty;
505
- /* don't init twice */
506
- if (sizeof_ioinfo)
507
- return sizeof_ioinfo >= 256;
508
-
509
- sizeof_ioinfo = sizeof(ioinfo);
510
- wastty = isatty(1);
511
- while (sizeof_ioinfo < 256) {
512
- /* toggle FDEV flag, check isatty, then toggle back */
513
- _pioinfo(1)->osflags ^= FDEV;
514
- istty = isatty(1);
515
- _pioinfo(1)->osflags ^= FDEV;
516
- /* return if we found the correct size */
517
- if (istty != wastty)
518
- return 0;
519
- sizeof_ioinfo += sizeof(void*);
520
- }
521
- error("Tweaking file descriptors doesn't work with this MSVCRT.dll");
522
- return 1;
523
-}
516
+ fd_is_interactive[fd] |= FD_SWAPPED;
517
525
-static HANDLE swap_osfhnd(int fd, HANDLE new_handle)
526
-{
527
- ioinfo *pioinfo;
528
- HANDLE old_handle;
529
-
530
- /* init ioinfo size if we haven't done so */
531
- if (init_sizeof_ioinfo())
532
- return INVALID_HANDLE_VALUE;
533
-
534
- /* get ioinfo pointer and change the handles */
535
- pioinfo = _pioinfo(fd);
536
- old_handle = pioinfo->osfhnd;
537
- pioinfo->osfhnd = new_handle;
538
- return old_handle;
518
+ return duplicate;
519
}
520
521
#ifdef DETECT_MSYS_TTY
@@ -570,45 +550,25 @@ static void detect_msys_tty(int fd)
550
!wcsstr(name, L"-pty"))
551
return;
552
573
- /* init ioinfo size if we haven't done so */
574
- if (init_sizeof_ioinfo())
575
- return;
576
-
577
- /* set FDEV flag, reset FPIPE flag */
578
- _pioinfo(fd)->osflags &= ~FPIPE;
579
- _pioinfo(fd)->osflags |= FDEV;
553
+ fd_is_interactive[fd] |= FD_MSYS;
554
}
555
556
#endif
557
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
567
int winansi_isatty(int fd)
568
{
586
- int res = isatty(fd);
587
-
588
- if (res) {
589
- /*
590
- * Make sure that /dev/null is not fooling Git into believing
591
- * that we are connected to a terminal, as "_isatty() returns a
592
- * nonzero value if the descriptor is associated with a
593
- * character device."; for more information, see
594
- *
595
- * https://msdn.microsoft.com/en-us/library/f4s0ddew.aspx
596
- */
597
- HANDLE handle = (HANDLE)_get_osfhandle(fd);
598
- if (fd == STDIN_FILENO) {
599
- DWORD dummy;
600
-
601
- if (!GetConsoleMode(handle, &dummy))
602
- res = 0;
603
- } else if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
604
- CONSOLE_SCREEN_BUFFER_INFO dummy;
605
-
606
- if (!GetConsoleScreenBufferInfo(handle, &dummy))
607
- res = 0;
608
- }
609
- }
610
-
611
- return res;
569
+ if (fd >= 0 && fd <= 2)
570
+ return fd_is_interactive[fd] != 0;
571
+ return isatty(fd);
572
}
573
574
void winansi_init(void)
@@ -619,6 +579,10 @@ void winansi_init(void)
579
/* check if either stdout or stderr is a console output screen buffer */
580
con1 = is_console(1);
581
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
+
586
if (!con1 && !con2) {
587
#ifdef DETECT_MSYS_TTY
588
/* check if stdin / stdout / stderr are MSYS2 pty pipes */
@@ -662,12 +626,10 @@ void winansi_init(void)
626
*/
627
HANDLE winansi_get_osfhandle(int fd)
628
{
665
- HANDLE hnd = (HANDLE) _get_osfhandle(fd);
666
- if (isatty(fd) && GetFileType(hnd) == FILE_TYPE_PIPE) {
667
- if (fd == 1 && hconsole1)
668
- return hconsole1;
669
- else if (fd == 2 && hconsole2)
670
- return hconsole2;
671
- }
672
- 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);
635
}