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 }