Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.

Commit 366e03f

Browse files
kbleeskasal
authored andcommitted
Win32: fix broken pipe detection
As of "Win32: Thread-safe windows console output", git-log no longer terminates when the pager process dies. This is due to disabling buffering for the replaced stdout / stderr streams. Git-log will periodically fflush stdout (see write_or_die.c/mayble_flush_or_die()), but with no buffering, this is a NOP that always succeeds (so we never detect the EPIPE error). Exchange the original console handles with our console thread pipe handles by accessing the internal MSVCRT data structures directly (which are exposed via __pioinfo for some reason). Implement this with minimal assumptions about the actual data structure to make it work with different (hopefully even future) MSVCRT versions. While messing with internal data structures is ugly, this patch solves the problem at the source instead of adding more workarounds. We no longer need the special winansi_isatty override, and the limitations documented in "Win32: Thread-safe windows console output" are gone (i.e. fdopen(1/2) returns unbuffered streams now, and isatty() for duped console file descriptors works as expected). Signed-off-by: Karsten Blees <[email protected]>
1 parent c577cd5 commit 366e03f

File tree

2 files changed

+70
-46
lines changed

2 files changed

+70
-46
lines changed

compat/mingw.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,9 +331,7 @@ int mingw_raise(int sig);
331331
*/
332332

333333
void winansi_init(void);
334-
int winansi_isatty(int fd);
335334
HANDLE winansi_get_osfhandle(int fd);
336-
#define isatty winansi_isatty
337335

338336
/*
339337
* git specific compatibility

compat/winansi.c

Lines changed: 70 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,6 @@
77
#include <wingdi.h>
88
#include <winreg.h>
99

10-
/*
11-
Functions to be wrapped:
12-
*/
13-
#undef isatty
14-
1510
/*
1611
ANSI codes used by git: m, K
1712
@@ -103,6 +98,7 @@ static int is_console(int fd)
10398

10499
/* initialize attributes */
105100
if (!initialized) {
101+
console = hcon;
106102
attr = plain_attr = sbi.wAttributes;
107103
negative = 0;
108104
initialized = 1;
@@ -463,29 +459,80 @@ static HANDLE duplicate_handle(HANDLE hnd)
463459
return hresult;
464460
}
465461

466-
static HANDLE redirect_console(FILE *stream, HANDLE *phcon, int new_fd)
467-
{
468-
/* get original console handle */
469-
int fd = _fileno(stream);
470-
HANDLE hcon = (HANDLE) _get_osfhandle(fd);
471-
if (hcon == INVALID_HANDLE_VALUE)
472-
die_errno("_get_osfhandle(%i) failed", fd);
473462

474-
/* save a copy to phcon and console (used by the background thread) */
475-
console = *phcon = duplicate_handle(hcon);
463+
/*
464+
* Make MSVCRT's internal file descriptor control structure accessible
465+
* so that we can tweak OS handles and flags directly (we need MSVCRT
466+
* to treat our pipe handle as if it were a console).
467+
*
468+
* We assume that the ioinfo structure (exposed by MSVCRT.dll via
469+
* __pioinfo) starts with the OS handle and the flags. The exact size
470+
* varies between MSVCRT versions, so we try different sizes until
471+
* toggling the FDEV bit of _pioinfo(1)->osflags is reflected in
472+
* isatty(1).
473+
*/
474+
typedef struct {
475+
HANDLE osfhnd;
476+
char osflags;
477+
} ioinfo;
478+
479+
extern __declspec(dllimport) ioinfo *__pioinfo[];
476480

477-
/* duplicate new_fd over fd (closes fd and associated handle (hcon)) */
478-
if (_dup2(new_fd, fd))
479-
die_errno("_dup2(%i, %i) failed", new_fd, fd);
481+
static size_t sizeof_ioinfo = 0;
480482

481-
/* no buffering, or stdout / stderr will be out of sync */
482-
setbuf(stream, NULL);
483-
return (HANDLE) _get_osfhandle(fd);
483+
#define IOINFO_L2E 5
484+
#define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E)
485+
486+
#define FDEV 0x40
487+
488+
static inline ioinfo* _pioinfo(int fd)
489+
{
490+
return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] +
491+
(fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo);
492+
}
493+
494+
static int init_sizeof_ioinfo()
495+
{
496+
int istty, wastty;
497+
/* don't init twice */
498+
if (sizeof_ioinfo)
499+
return sizeof_ioinfo >= 256;
500+
501+
sizeof_ioinfo = sizeof(ioinfo);
502+
wastty = isatty(1);
503+
while (sizeof_ioinfo < 256) {
504+
/* toggle FDEV flag, check isatty, then toggle back */
505+
_pioinfo(1)->osflags ^= FDEV;
506+
istty = isatty(1);
507+
_pioinfo(1)->osflags ^= FDEV;
508+
/* return if we found the correct size */
509+
if (istty != wastty)
510+
return 0;
511+
sizeof_ioinfo += sizeof(void*);
512+
}
513+
error("Tweaking file descriptors doesn't work with this MSVCRT.dll");
514+
return 1;
515+
}
516+
517+
static HANDLE swap_osfhnd(int fd, HANDLE new_handle)
518+
{
519+
ioinfo *pioinfo;
520+
HANDLE old_handle;
521+
522+
/* init ioinfo size if we haven't done so */
523+
if (init_sizeof_ioinfo())
524+
return INVALID_HANDLE_VALUE;
525+
526+
/* get ioinfo pointer and change the handles */
527+
pioinfo = _pioinfo(fd);
528+
old_handle = pioinfo->osfhnd;
529+
pioinfo->osfhnd = new_handle;
530+
return old_handle;
484531
}
485532

486533
void winansi_init(void)
487534
{
488-
int con1, con2, hwrite_fd;
535+
int con1, con2;
489536
char name[32];
490537

491538
/* check if either stdout or stderr is a console output screen buffer */
@@ -514,39 +561,18 @@ void winansi_init(void)
514561
if (atexit(winansi_exit))
515562
die_errno("atexit(winansi_exit) failed");
516563

517-
/* create a file descriptor for the write end of the pipe */
518-
hwrite_fd = _open_osfhandle((long) duplicate_handle(hwrite), _O_BINARY);
519-
if (hwrite_fd == -1)
520-
die_errno("_open_osfhandle(%li) failed", (long) hwrite);
521-
522564
/* redirect stdout / stderr to the pipe */
523565
if (con1)
524-
hwrite1 = redirect_console(stdout, &hconsole1, hwrite_fd);
566+
hconsole1 = swap_osfhnd(1, hwrite1 = duplicate_handle(hwrite));
525567
if (con2)
526-
hwrite2 = redirect_console(stderr, &hconsole2, hwrite_fd);
527-
528-
/* close pipe file descriptor (also closes the duped hwrite) */
529-
close(hwrite_fd);
568+
hconsole2 = swap_osfhnd(2, hwrite2 = duplicate_handle(hwrite));
530569
}
531570

532571
static int is_same_handle(HANDLE hnd, int fd)
533572
{
534573
return hnd != INVALID_HANDLE_VALUE && hnd == (HANDLE) _get_osfhandle(fd);
535574
}
536575

537-
/*
538-
* Return true if stdout / stderr is a pipe redirecting to the console.
539-
*/
540-
int winansi_isatty(int fd)
541-
{
542-
if (fd == 1 && is_same_handle(hwrite1, 1))
543-
return 1;
544-
else if (fd == 2 && is_same_handle(hwrite2, 2))
545-
return 1;
546-
else
547-
return isatty(fd);
548-
}
549-
550576
/*
551577
* Returns the real console handle if stdout / stderr is a pipe redirecting
552578
* to the console. Allows spawn / exec to pass the console to the next process.

0 commit comments

Comments
 (0)