git
1/*
2* Copyright 2008 Peter Harris <git@peter.is-a-geek.org>
3*/
4
5#undef NOGDI6
7#include "../git-compat-util.h"8#include <wingdi.h>9#include <winreg.h>10#include "win32.h"11#include "win32/lazyload.h"12
13static int fd_is_interactive[3] = { 0, 0, 0 };14#define FD_CONSOLE 0x115#define FD_SWAPPED 0x216#define FD_MSYS 0x417
18/*
19ANSI codes used by git: m, K
20
21This file is git-specific. Therefore, this file does not attempt
22to implement any codes that are not used by git.
23*/
24
25static HANDLE console;26static WORD plain_attr;27static WORD attr;28static int negative;29static int non_ascii_used = 0;30static HANDLE hthread, hread, hwrite;31static HANDLE hconsole1, hconsole2;32
33#ifdef __MINGW32__34#if !defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 535typedef struct _CONSOLE_FONT_INFOEX {36ULONG cbSize;37DWORD nFont;38COORD dwFontSize;39UINT FontFamily;40UINT FontWeight;41WCHAR FaceName[LF_FACESIZE];42} CONSOLE_FONT_INFOEX, *PCONSOLE_FONT_INFOEX;43#endif44#endif45
46static void warn_if_raster_font(void)47{
48DWORD fontFamily = 0;49DECLARE_PROC_ADDR(kernel32.dll, BOOL, WINAPI,50GetCurrentConsoleFontEx, HANDLE, BOOL,51PCONSOLE_FONT_INFOEX);52
53/* don't bother if output was ascii only */54if (!non_ascii_used)55return;56
57/* GetCurrentConsoleFontEx is available since Vista */58if (INIT_PROC_ADDR(GetCurrentConsoleFontEx)) {59CONSOLE_FONT_INFOEX cfi;60cfi.cbSize = sizeof(cfi);61if (GetCurrentConsoleFontEx(console, 0, &cfi))62fontFamily = cfi.FontFamily;63} else {64/* pre-Vista: check default console font in registry */65HKEY hkey;66if (ERROR_SUCCESS == RegOpenKeyExA(HKEY_CURRENT_USER, "Console",670, KEY_READ, &hkey)) {68DWORD size = sizeof(fontFamily);69RegQueryValueExA(hkey, "FontFamily", NULL, NULL,70(LPVOID) &fontFamily, &size);71RegCloseKey(hkey);72}73}74
75if (!(fontFamily & TMPF_TRUETYPE)) {76const wchar_t *msg = L"\nWarning: Your console font probably "77L"doesn\'t support Unicode. If you experience strange "78L"characters in the output, consider switching to a "79L"TrueType font such as Consolas!\n";80DWORD dummy;81WriteConsoleW(console, msg, wcslen(msg), &dummy, NULL);82}83}
84
85static int is_console(int fd)86{
87CONSOLE_SCREEN_BUFFER_INFO sbi;88DWORD mode;89HANDLE hcon;90
91static int initialized = 0;92
93/* get OS handle of the file descriptor */94hcon = (HANDLE) _get_osfhandle(fd);95if (hcon == INVALID_HANDLE_VALUE)96return 0;97
98/* check if its a device (i.e. console, printer, serial port) */99if (GetFileType(hcon) != FILE_TYPE_CHAR)100return 0;101
102/* check if its a handle to a console output screen buffer */103if (!fd) {104if (!GetConsoleMode(hcon, &mode))105return 0;106/*107* This code path is only reached if there is no console
108* attached to stdout/stderr, i.e. we will not need to output
109* any text to any console, therefore we might just as well
110* use black as foreground color.
111*/
112sbi.wAttributes = 0;113} else if (!GetConsoleScreenBufferInfo(hcon, &sbi))114return 0;115
116if (fd >= 0 && fd <= 2)117fd_is_interactive[fd] |= FD_CONSOLE;118
119/* initialize attributes */120if (!initialized) {121console = hcon;122attr = plain_attr = sbi.wAttributes;123negative = 0;124initialized = 1;125}126
127return 1;128}
129
130#define BUFFER_SIZE 4096131#define MAX_PARAMS 16132
133static void write_console(unsigned char *str, size_t len)134{
135/* only called from console_thread, so a static buffer will do */136static wchar_t wbuf[2 * BUFFER_SIZE + 1];137DWORD dummy;138
139/* convert utf-8 to utf-16 */140int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);141if (wlen < 0) {142const wchar_t *err = L"[invalid]";143WriteConsoleW(console, err, wcslen(err), &dummy, NULL);144return;145}146
147/* write directly to console */148WriteConsoleW(console, wbuf, wlen, &dummy, NULL);149
150/* remember if non-ascii characters are printed */151if (wlen != len)152non_ascii_used = 1;153}
154
155#define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)156#define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)157
158static void set_console_attr(void)159{
160WORD attributes = attr;161if (negative) {162attributes &= ~FOREGROUND_ALL;163attributes &= ~BACKGROUND_ALL;164
165/* This could probably use a bitmask166instead of a series of ifs */
167if (attr & FOREGROUND_RED)168attributes |= BACKGROUND_RED;169if (attr & FOREGROUND_GREEN)170attributes |= BACKGROUND_GREEN;171if (attr & FOREGROUND_BLUE)172attributes |= BACKGROUND_BLUE;173
174if (attr & BACKGROUND_RED)175attributes |= FOREGROUND_RED;176if (attr & BACKGROUND_GREEN)177attributes |= FOREGROUND_GREEN;178if (attr & BACKGROUND_BLUE)179attributes |= FOREGROUND_BLUE;180}181SetConsoleTextAttribute(console, attributes);182}
183
184static void erase_in_line(void)185{
186CONSOLE_SCREEN_BUFFER_INFO sbi;187DWORD dummy; /* Needed for Windows 7 (or Vista) regression */188
189if (!console)190return;191
192GetConsoleScreenBufferInfo(console, &sbi);193FillConsoleOutputCharacterA(console, ' ',194sbi.dwSize.X - sbi.dwCursorPosition.X, sbi.dwCursorPosition,195&dummy);196}
197
198static void set_attr(char func, const int *params, int paramlen)199{
200int i;201switch (func) {202case 'm':203for (i = 0; i < paramlen; i++) {204switch (params[i]) {205case 0: /* reset */206attr = plain_attr;207negative = 0;208break;209case 1: /* bold */210attr |= FOREGROUND_INTENSITY;211break;212case 2: /* faint */213case 22: /* normal */214attr &= ~FOREGROUND_INTENSITY;215break;216case 3: /* italic */217/* Unsupported */218break;219case 4: /* underline */220case 21: /* double underline */221/* Wikipedia says this flag does nothing */222/* Furthermore, mingw doesn't define this flag223attr |= COMMON_LVB_UNDERSCORE; */
224break;225case 24: /* no underline */226/* attr &= ~COMMON_LVB_UNDERSCORE; */227break;228case 5: /* slow blink */229case 6: /* fast blink */230/* We don't have blink, but we do have231background intensity */
232attr |= BACKGROUND_INTENSITY;233break;234case 25: /* no blink */235attr &= ~BACKGROUND_INTENSITY;236break;237case 7: /* negative */238negative = 1;239break;240case 27: /* positive */241negative = 0;242break;243case 8: /* conceal */244case 28: /* reveal */245/* Unsupported */246break;247case 30: /* Black */248attr &= ~FOREGROUND_ALL;249break;250case 31: /* Red */251attr &= ~FOREGROUND_ALL;252attr |= FOREGROUND_RED;253break;254case 32: /* Green */255attr &= ~FOREGROUND_ALL;256attr |= FOREGROUND_GREEN;257break;258case 33: /* Yellow */259attr &= ~FOREGROUND_ALL;260attr |= FOREGROUND_RED | FOREGROUND_GREEN;261break;262case 34: /* Blue */263attr &= ~FOREGROUND_ALL;264attr |= FOREGROUND_BLUE;265break;266case 35: /* Magenta */267attr &= ~FOREGROUND_ALL;268attr |= FOREGROUND_RED | FOREGROUND_BLUE;269break;270case 36: /* Cyan */271attr &= ~FOREGROUND_ALL;272attr |= FOREGROUND_GREEN | FOREGROUND_BLUE;273break;274case 37: /* White */275attr |= FOREGROUND_RED |276FOREGROUND_GREEN |277FOREGROUND_BLUE;278break;279case 38: /* Unknown */280break;281case 39: /* reset */282attr &= ~FOREGROUND_ALL;283attr |= (plain_attr & FOREGROUND_ALL);284break;285case 40: /* Black */286attr &= ~BACKGROUND_ALL;287break;288case 41: /* Red */289attr &= ~BACKGROUND_ALL;290attr |= BACKGROUND_RED;291break;292case 42: /* Green */293attr &= ~BACKGROUND_ALL;294attr |= BACKGROUND_GREEN;295break;296case 43: /* Yellow */297attr &= ~BACKGROUND_ALL;298attr |= BACKGROUND_RED | BACKGROUND_GREEN;299break;300case 44: /* Blue */301attr &= ~BACKGROUND_ALL;302attr |= BACKGROUND_BLUE;303break;304case 45: /* Magenta */305attr &= ~BACKGROUND_ALL;306attr |= BACKGROUND_RED | BACKGROUND_BLUE;307break;308case 46: /* Cyan */309attr &= ~BACKGROUND_ALL;310attr |= BACKGROUND_GREEN | BACKGROUND_BLUE;311break;312case 47: /* White */313attr |= BACKGROUND_RED |314BACKGROUND_GREEN |315BACKGROUND_BLUE;316break;317case 48: /* Unknown */318break;319case 49: /* reset */320attr &= ~BACKGROUND_ALL;321attr |= (plain_attr & BACKGROUND_ALL);322break;323default:324/* Unsupported code */325break;326}327}328set_console_attr();329break;330case 'K':331erase_in_line();332break;333default:334/* Unsupported code */335break;336}337}
338
339enum {340TEXT = 0, ESCAPE = 033, BRACKET = '['341};342
343static DWORD WINAPI console_thread(LPVOID unused)344{
345unsigned char buffer[BUFFER_SIZE];346DWORD bytes;347int start, end = 0, c, parampos = 0, state = TEXT;348int params[MAX_PARAMS];349
350while (1) {351/* read next chunk of bytes from the pipe */352if (!ReadFile(hread, buffer + end, BUFFER_SIZE - end, &bytes,353NULL)) {354/* exit if pipe has been closed or disconnected */355if (GetLastError() == ERROR_PIPE_NOT_CONNECTED ||356GetLastError() == ERROR_BROKEN_PIPE)357break;358/* ignore other errors */359continue;360}361
362/* scan the bytes and handle ANSI control codes */363bytes += end;364start = end = 0;365while (end < bytes) {366c = buffer[end++];367switch (state) {368case TEXT:369if (c == ESCAPE) {370/* print text seen so far */371if (end - 1 > start)372write_console(buffer + start,373end - 1 - start);374
375/* then start parsing escape sequence */376start = end - 1;377memset(params, 0, sizeof(params));378parampos = 0;379state = ESCAPE;380}381break;382
383case ESCAPE:384/* continue if "\033[", otherwise bail out */385state = (c == BRACKET) ? BRACKET : TEXT;386break;387
388case BRACKET:389/* parse [0-9;]* into array of parameters */390if (c >= '0' && c <= '9') {391params[parampos] *= 10;392params[parampos] += c - '0';393} else if (c == ';') {394/*395* next parameter, bail out if out of
396* bounds
397*/
398parampos++;399if (parampos >= MAX_PARAMS)400state = TEXT;401} else {402/*403* end of escape sequence, change
404* console attributes
405*/
406set_attr(c, params, parampos + 1);407start = end;408state = TEXT;409}410break;411}412}413
414/* print remaining text unless parsing an escape sequence */415if (state == TEXT && end > start) {416/* check for incomplete UTF-8 sequences and fix end */417if (buffer[end - 1] >= 0x80) {418if (buffer[end -1] >= 0xc0)419end--;420else if (end - 1 > start &&421buffer[end - 2] >= 0xe0)422end -= 2;423else if (end - 2 > start &&424buffer[end - 3] >= 0xf0)425end -= 3;426}427
428/* print remaining complete UTF-8 sequences */429if (end > start)430write_console(buffer + start, end - start);431
432/* move remaining bytes to the front */433if (end < bytes)434memmove(buffer, buffer + end, bytes - end);435end = bytes - end;436} else {437/* all data has been consumed, mark buffer empty */438end = 0;439}440}441
442/* check if the console font supports unicode */443warn_if_raster_font();444
445CloseHandle(hread);446return 0;447}
448
449static void winansi_exit(void)450{
451/* flush all streams */452_flushall();453
454/* signal console thread to exit */455FlushFileBuffers(hwrite);456DisconnectNamedPipe(hwrite);457
458/* wait for console thread to copy remaining data */459WaitForSingleObject(hthread, INFINITE);460
461/* cleanup handles... */462CloseHandle(hwrite);463CloseHandle(hthread);464}
465
466static void die_lasterr(const char *fmt, ...)467{
468va_list params;469va_start(params, fmt);470errno = err_win_to_posix(GetLastError());471die_errno(fmt, params);472va_end(params);473}
474
475#undef dup2476int winansi_dup2(int oldfd, int newfd)477{
478int ret = dup2(oldfd, newfd);479
480if (!ret && newfd >= 0 && newfd <= 2)481fd_is_interactive[newfd] = oldfd < 0 || oldfd > 2 ?4820 : fd_is_interactive[oldfd];483
484return ret;485}
486
487static HANDLE duplicate_handle(HANDLE hnd)488{
489HANDLE hresult, hproc = GetCurrentProcess();490if (!DuplicateHandle(hproc, hnd, hproc, &hresult, 0, TRUE,491DUPLICATE_SAME_ACCESS))492die_lasterr("DuplicateHandle(%li) failed",493(long) (intptr_t) hnd);494return hresult;495}
496
497static HANDLE swap_osfhnd(int fd, HANDLE new_handle)498{
499/*500* Create a copy of the original handle associated with fd
501* because the original will get closed when we dup2().
502*/
503HANDLE handle = (HANDLE)_get_osfhandle(fd);504HANDLE duplicate = duplicate_handle(handle);505
506/* Create a temp fd associated with the already open "new_handle". */507int new_fd = _open_osfhandle((intptr_t)new_handle, O_BINARY);508
509assert((fd == 1) || (fd == 2));510
511/*512* Use stock dup2() to re-bind fd to the new handle. Note that
513* this will implicitly close(1) and close both fd=1 and the
514* originally associated handle. It will open a new fd=1 and
515* call DuplicateHandle() on the handle associated with new_fd.
516* It is because of this implicit close() that we created the
517* copy of the original.
518*
519* Note that we need to update the cached console handle to the
520* duplicated one because the dup2() call will implicitly close
521* the original one.
522*
523* Note that dup2() when given target := {0,1,2} will also
524* call SetStdHandle(), so we don't need to worry about that.
525*/
526if (console == handle)527console = duplicate;528dup2(new_fd, fd);529
530/* Close the temp fd. This explicitly closes "new_handle"531* (because it has been associated with it).
532*/
533close(new_fd);534
535if (fd == 2)536setvbuf(stderr, NULL, _IONBF, BUFSIZ);537fd_is_interactive[fd] |= FD_SWAPPED;538
539return duplicate;540}
541
542#ifdef DETECT_MSYS_TTY543
544#include <winternl.h>545
546#if defined(_MSC_VER)547
548typedef struct _OBJECT_NAME_INFORMATION549{
550UNICODE_STRING Name;551WCHAR NameBuffer[FLEX_ARRAY];552} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;553
554#define ObjectNameInformation 1555
556#else557#include <ntstatus.h>558#endif559
560static void detect_msys_tty(int fd)561{
562ULONG result;563BYTE buffer[1024];564POBJECT_NAME_INFORMATION nameinfo = (POBJECT_NAME_INFORMATION) buffer;565PWSTR name;566
567/* check if fd is a pipe */568HANDLE h = (HANDLE) _get_osfhandle(fd);569if (GetFileType(h) != FILE_TYPE_PIPE)570return;571
572/* get pipe name */573if (!NT_SUCCESS(NtQueryObject(h, ObjectNameInformation,574buffer, sizeof(buffer) - 2, &result)))575return;576name = nameinfo->Name.Buffer;577name[nameinfo->Name.Length / sizeof(*name)] = 0;578
579/*580* Check if this could be a MSYS2 pty pipe ('msys-XXXX-ptyN-XX')
581* or a cygwin pty pipe ('cygwin-XXXX-ptyN-XX')
582*/
583if ((!wcsstr(name, L"msys-") && !wcsstr(name, L"cygwin-")) ||584!wcsstr(name, L"-pty"))585return;586
587if (fd == 2)588setvbuf(stderr, NULL, _IONBF, BUFSIZ);589fd_is_interactive[fd] |= FD_MSYS;590}
591
592#endif593
594/*
595* Wrapper for isatty(). Most calls in the main git code
596* call isatty(1 or 2) to see if the instance is interactive
597* and should: be colored, show progress, paginate output.
598* We lie and give results for what the descriptor WAS at
599* startup (and ignore any pipe redirection we internally
600* do).
601*/
602#undef isatty603int winansi_isatty(int fd)604{
605if (fd >= 0 && fd <= 2)606return fd_is_interactive[fd] != 0;607return isatty(fd);608}
609
610void winansi_init(void)611{
612int con1, con2;613wchar_t name[32];614
615/* check if either stdout or stderr is a console output screen buffer */616con1 = is_console(1);617con2 = is_console(2);618
619/* Also compute console bit for fd 0 even though we don't need the result here. */620is_console(0);621
622if (!con1 && !con2) {623#ifdef DETECT_MSYS_TTY624/* check if stdin / stdout / stderr are MSYS2 pty pipes */625detect_msys_tty(0);626detect_msys_tty(1);627detect_msys_tty(2);628#endif629return;630}631
632/* create a named pipe to communicate with the console thread */633if (swprintf(name, ARRAY_SIZE(name) - 1, L"\\\\.\\pipe\\winansi%lu",634GetCurrentProcessId()) < 0)635die("Could not initialize winansi pipe name");636hwrite = CreateNamedPipeW(name, PIPE_ACCESS_OUTBOUND,637PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL);638if (hwrite == INVALID_HANDLE_VALUE)639die_lasterr("CreateNamedPipe failed");640
641hread = CreateFileW(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);642if (hread == INVALID_HANDLE_VALUE)643die_lasterr("CreateFile for named pipe failed");644
645/* start console spool thread on the pipe's read end */646hthread = CreateThread(NULL, 0, console_thread, NULL, 0, NULL);647if (!hthread)648die_lasterr("CreateThread(console_thread) failed");649
650/* schedule cleanup routine */651if (atexit(winansi_exit))652die_errno("atexit(winansi_exit) failed");653
654/* redirect stdout / stderr to the pipe */655if (con1)656hconsole1 = swap_osfhnd(1, duplicate_handle(hwrite));657if (con2)658hconsole2 = swap_osfhnd(2, duplicate_handle(hwrite));659}
660
661/*
662* Returns the real console handle if stdout / stderr is a pipe redirecting
663* to the console. Allows spawn / exec to pass the console to the next process.
664*/
665HANDLE winansi_get_osfhandle(int fd)666{
667HANDLE ret;668
669if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED))670return hconsole1;671if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED))672return hconsole2;673
674ret = (HANDLE)_get_osfhandle(fd);675
676/*677* There are obviously circumstances under which _get_osfhandle()
678* returns (HANDLE)-2. This is not documented anywhere, but that is so
679* clearly an invalid handle value that we can just work around this
680* and return the correct value for invalid handles.
681*/
682return ret == (HANDLE)-2 ? INVALID_HANDLE_VALUE : ret;683}
684