Sync with 'maint'
[gitweb.git] / compat / winansi.c
index fcdd6dc1b394bc71fa71435534f260d3338548a1..efc5bb3a4b63166eccb33f6ec6a8ad57e4c9ac36 100644 (file)
@@ -7,11 +7,6 @@
 #include <wingdi.h>
 #include <winreg.h>
 
-/*
- Functions to be wrapped:
-*/
-#undef isatty
-
 /*
  ANSI codes used by git: m, K
 
@@ -25,7 +20,6 @@ static WORD attr;
 static int negative;
 static int non_ascii_used = 0;
 static HANDLE hthread, hread, hwrite;
-static HANDLE hwrite1 = INVALID_HANDLE_VALUE, hwrite2 = INVALID_HANDLE_VALUE;
 static HANDLE hconsole1, hconsole2;
 
 #ifdef __MINGW32__
@@ -104,6 +98,7 @@ static int is_console(int fd)
 
        /* initialize attributes */
        if (!initialized) {
+               console = hcon;
                attr = plain_attr = sbi.wAttributes;
                negative = 0;
                initialized = 1;
@@ -439,10 +434,6 @@ static void winansi_exit(void)
        WaitForSingleObject(hthread, INFINITE);
 
        /* cleanup handles... */
-       if (hwrite1 != INVALID_HANDLE_VALUE)
-               CloseHandle(hwrite1);
-       if (hwrite2 != INVALID_HANDLE_VALUE)
-               CloseHandle(hwrite2);
        CloseHandle(hwrite);
        CloseHandle(hthread);
 }
@@ -465,29 +456,80 @@ static HANDLE duplicate_handle(HANDLE hnd)
        return hresult;
 }
 
-static HANDLE redirect_console(FILE *stream, HANDLE *phcon, int new_fd)
-{
-       /* get original console handle */
-       int fd = _fileno(stream);
-       HANDLE hcon = (HANDLE) _get_osfhandle(fd);
-       if (hcon == INVALID_HANDLE_VALUE)
-               die_errno("_get_osfhandle(%i) failed", fd);
 
-       /* save a copy to phcon and console (used by the background thread) */
-       console = *phcon = duplicate_handle(hcon);
+/*
+ * 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[];
 
-       /* duplicate new_fd over fd (closes fd and associated handle (hcon)) */
-       if (_dup2(new_fd, fd))
-               die_errno("_dup2(%i, %i) failed", new_fd, fd);
+static size_t sizeof_ioinfo = 0;
 
-       /* no buffering, or stdout / stderr will be out of sync */
-       setbuf(stream, NULL);
-       return (HANDLE) _get_osfhandle(fd);
+#define IOINFO_L2E 5
+#define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E)
+
+#define FDEV  0x40
+
+static inline ioinfo* _pioinfo(int fd)
+{
+       return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] +
+                       (fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo);
+}
+
+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;
+}
+
+static HANDLE swap_osfhnd(int fd, HANDLE new_handle)
+{
+       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;
 }
 
 void winansi_init(void)
 {
-       int con1, con2, hwrite_fd;
+       int con1, con2;
        char name[32];
 
        /* check if either stdout or stderr is a console output screen buffer */
@@ -516,37 +558,11 @@ void winansi_init(void)
        if (atexit(winansi_exit))
                die_errno("atexit(winansi_exit) failed");
 
-       /* create a file descriptor for the write end of the pipe */
-       hwrite_fd = _open_osfhandle((long) duplicate_handle(hwrite), _O_BINARY);
-       if (hwrite_fd == -1)
-               die_errno("_open_osfhandle(%li) failed", (long) hwrite);
-
        /* redirect stdout / stderr to the pipe */
        if (con1)
-               hwrite1 = redirect_console(stdout, &hconsole1, hwrite_fd);
+               hconsole1 = swap_osfhnd(1, duplicate_handle(hwrite));
        if (con2)
-               hwrite2 = redirect_console(stderr, &hconsole2, hwrite_fd);
-
-       /* close pipe file descriptor (also closes the duped hwrite) */
-       close(hwrite_fd);
-}
-
-static int is_same_handle(HANDLE hnd, int fd)
-{
-       return hnd != INVALID_HANDLE_VALUE && hnd == (HANDLE) _get_osfhandle(fd);
-}
-
-/*
- * Return true if stdout / stderr is a pipe redirecting to the console.
- */
-int winansi_isatty(int fd)
-{
-       if (fd == 1 && is_same_handle(hwrite1, 1))
-               return 1;
-       else if (fd == 2 && is_same_handle(hwrite2, 2))
-               return 1;
-       else
-               return isatty(fd);
+               hconsole2 = swap_osfhnd(2, duplicate_handle(hwrite));
 }
 
 /*
@@ -555,10 +571,9 @@ int winansi_isatty(int fd)
  */
 HANDLE winansi_get_osfhandle(int fd)
 {
-       if (fd == 1 && is_same_handle(hwrite1, 1))
-               return hconsole1;
-       else if (fd == 2 && is_same_handle(hwrite2, 2))
-               return hconsole2;
-       else
-               return (HANDLE) _get_osfhandle(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;
 }