mirror of
https://github.com/git/git
synced 2024-08-27 11:39:22 +00:00
![Jeff King](/assets/img/avatar_default.png)
We sometimes sprintf into fixed-size buffers when we know that the buffer is large enough to fit the input (either because it's a constant, or because it's numeric input that is bounded in size). Likewise with strcpy of constant strings. However, these sites make it hard to audit sprintf and strcpy calls for buffer overflows, as a reader has to cross-reference the size of the array with the input. Let's use xsnprintf instead, which communicates to a reader that we don't expect this to overflow (and catches the mistake in case we do). Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
580 lines
14 KiB
C
580 lines
14 KiB
C
/*
|
|
* Copyright 2008 Peter Harris <git@peter.is-a-geek.org>
|
|
*/
|
|
|
|
#undef NOGDI
|
|
#include "../git-compat-util.h"
|
|
#include <wingdi.h>
|
|
#include <winreg.h>
|
|
|
|
/*
|
|
ANSI codes used by git: m, K
|
|
|
|
This file is git-specific. Therefore, this file does not attempt
|
|
to implement any codes that are not used by git.
|
|
*/
|
|
|
|
static HANDLE console;
|
|
static WORD plain_attr;
|
|
static WORD attr;
|
|
static int negative;
|
|
static int non_ascii_used = 0;
|
|
static HANDLE hthread, hread, hwrite;
|
|
static HANDLE hconsole1, hconsole2;
|
|
|
|
#ifdef __MINGW32__
|
|
typedef struct _CONSOLE_FONT_INFOEX {
|
|
ULONG cbSize;
|
|
DWORD nFont;
|
|
COORD dwFontSize;
|
|
UINT FontFamily;
|
|
UINT FontWeight;
|
|
WCHAR FaceName[LF_FACESIZE];
|
|
} CONSOLE_FONT_INFOEX, *PCONSOLE_FONT_INFOEX;
|
|
#endif
|
|
|
|
typedef BOOL (WINAPI *PGETCURRENTCONSOLEFONTEX)(HANDLE, BOOL,
|
|
PCONSOLE_FONT_INFOEX);
|
|
|
|
static void warn_if_raster_font(void)
|
|
{
|
|
DWORD fontFamily = 0;
|
|
PGETCURRENTCONSOLEFONTEX pGetCurrentConsoleFontEx;
|
|
|
|
/* don't bother if output was ascii only */
|
|
if (!non_ascii_used)
|
|
return;
|
|
|
|
/* GetCurrentConsoleFontEx is available since Vista */
|
|
pGetCurrentConsoleFontEx = (PGETCURRENTCONSOLEFONTEX) GetProcAddress(
|
|
GetModuleHandle("kernel32.dll"),
|
|
"GetCurrentConsoleFontEx");
|
|
if (pGetCurrentConsoleFontEx) {
|
|
CONSOLE_FONT_INFOEX cfi;
|
|
cfi.cbSize = sizeof(cfi);
|
|
if (pGetCurrentConsoleFontEx(console, 0, &cfi))
|
|
fontFamily = cfi.FontFamily;
|
|
} else {
|
|
/* pre-Vista: check default console font in registry */
|
|
HKEY hkey;
|
|
if (ERROR_SUCCESS == RegOpenKeyExA(HKEY_CURRENT_USER, "Console",
|
|
0, KEY_READ, &hkey)) {
|
|
DWORD size = sizeof(fontFamily);
|
|
RegQueryValueExA(hkey, "FontFamily", NULL, NULL,
|
|
(LPVOID) &fontFamily, &size);
|
|
RegCloseKey(hkey);
|
|
}
|
|
}
|
|
|
|
if (!(fontFamily & TMPF_TRUETYPE)) {
|
|
const wchar_t *msg = L"\nWarning: Your console font probably "
|
|
L"doesn\'t support Unicode. If you experience strange "
|
|
L"characters in the output, consider switching to a "
|
|
L"TrueType font such as Consolas!\n";
|
|
DWORD dummy;
|
|
WriteConsoleW(console, msg, wcslen(msg), &dummy, NULL);
|
|
}
|
|
}
|
|
|
|
static int is_console(int fd)
|
|
{
|
|
CONSOLE_SCREEN_BUFFER_INFO sbi;
|
|
HANDLE hcon;
|
|
|
|
static int initialized = 0;
|
|
|
|
/* get OS handle of the file descriptor */
|
|
hcon = (HANDLE) _get_osfhandle(fd);
|
|
if (hcon == INVALID_HANDLE_VALUE)
|
|
return 0;
|
|
|
|
/* check if its a device (i.e. console, printer, serial port) */
|
|
if (GetFileType(hcon) != FILE_TYPE_CHAR)
|
|
return 0;
|
|
|
|
/* check if its a handle to a console output screen buffer */
|
|
if (!GetConsoleScreenBufferInfo(hcon, &sbi))
|
|
return 0;
|
|
|
|
/* initialize attributes */
|
|
if (!initialized) {
|
|
console = hcon;
|
|
attr = plain_attr = sbi.wAttributes;
|
|
negative = 0;
|
|
initialized = 1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#define BUFFER_SIZE 4096
|
|
#define MAX_PARAMS 16
|
|
|
|
static void write_console(unsigned char *str, size_t len)
|
|
{
|
|
/* only called from console_thread, so a static buffer will do */
|
|
static wchar_t wbuf[2 * BUFFER_SIZE + 1];
|
|
DWORD dummy;
|
|
|
|
/* convert utf-8 to utf-16 */
|
|
int wlen = xutftowcsn(wbuf, (char*) str, ARRAY_SIZE(wbuf), len);
|
|
|
|
/* write directly to console */
|
|
WriteConsoleW(console, wbuf, wlen, &dummy, NULL);
|
|
|
|
/* remember if non-ascii characters are printed */
|
|
if (wlen != len)
|
|
non_ascii_used = 1;
|
|
}
|
|
|
|
#define FOREGROUND_ALL (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
|
|
#define BACKGROUND_ALL (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE)
|
|
|
|
static void set_console_attr(void)
|
|
{
|
|
WORD attributes = attr;
|
|
if (negative) {
|
|
attributes &= ~FOREGROUND_ALL;
|
|
attributes &= ~BACKGROUND_ALL;
|
|
|
|
/* This could probably use a bitmask
|
|
instead of a series of ifs */
|
|
if (attr & FOREGROUND_RED)
|
|
attributes |= BACKGROUND_RED;
|
|
if (attr & FOREGROUND_GREEN)
|
|
attributes |= BACKGROUND_GREEN;
|
|
if (attr & FOREGROUND_BLUE)
|
|
attributes |= BACKGROUND_BLUE;
|
|
|
|
if (attr & BACKGROUND_RED)
|
|
attributes |= FOREGROUND_RED;
|
|
if (attr & BACKGROUND_GREEN)
|
|
attributes |= FOREGROUND_GREEN;
|
|
if (attr & BACKGROUND_BLUE)
|
|
attributes |= FOREGROUND_BLUE;
|
|
}
|
|
SetConsoleTextAttribute(console, attributes);
|
|
}
|
|
|
|
static void erase_in_line(void)
|
|
{
|
|
CONSOLE_SCREEN_BUFFER_INFO sbi;
|
|
DWORD dummy; /* Needed for Windows 7 (or Vista) regression */
|
|
|
|
if (!console)
|
|
return;
|
|
|
|
GetConsoleScreenBufferInfo(console, &sbi);
|
|
FillConsoleOutputCharacterA(console, ' ',
|
|
sbi.dwSize.X - sbi.dwCursorPosition.X, sbi.dwCursorPosition,
|
|
&dummy);
|
|
}
|
|
|
|
static void set_attr(char func, const int *params, int paramlen)
|
|
{
|
|
int i;
|
|
switch (func) {
|
|
case 'm':
|
|
for (i = 0; i < paramlen; i++) {
|
|
switch (params[i]) {
|
|
case 0: /* reset */
|
|
attr = plain_attr;
|
|
negative = 0;
|
|
break;
|
|
case 1: /* bold */
|
|
attr |= FOREGROUND_INTENSITY;
|
|
break;
|
|
case 2: /* faint */
|
|
case 22: /* normal */
|
|
attr &= ~FOREGROUND_INTENSITY;
|
|
break;
|
|
case 3: /* italic */
|
|
/* Unsupported */
|
|
break;
|
|
case 4: /* underline */
|
|
case 21: /* double underline */
|
|
/* Wikipedia says this flag does nothing */
|
|
/* Furthermore, mingw doesn't define this flag
|
|
attr |= COMMON_LVB_UNDERSCORE; */
|
|
break;
|
|
case 24: /* no underline */
|
|
/* attr &= ~COMMON_LVB_UNDERSCORE; */
|
|
break;
|
|
case 5: /* slow blink */
|
|
case 6: /* fast blink */
|
|
/* We don't have blink, but we do have
|
|
background intensity */
|
|
attr |= BACKGROUND_INTENSITY;
|
|
break;
|
|
case 25: /* no blink */
|
|
attr &= ~BACKGROUND_INTENSITY;
|
|
break;
|
|
case 7: /* negative */
|
|
negative = 1;
|
|
break;
|
|
case 27: /* positive */
|
|
negative = 0;
|
|
break;
|
|
case 8: /* conceal */
|
|
case 28: /* reveal */
|
|
/* Unsupported */
|
|
break;
|
|
case 30: /* Black */
|
|
attr &= ~FOREGROUND_ALL;
|
|
break;
|
|
case 31: /* Red */
|
|
attr &= ~FOREGROUND_ALL;
|
|
attr |= FOREGROUND_RED;
|
|
break;
|
|
case 32: /* Green */
|
|
attr &= ~FOREGROUND_ALL;
|
|
attr |= FOREGROUND_GREEN;
|
|
break;
|
|
case 33: /* Yellow */
|
|
attr &= ~FOREGROUND_ALL;
|
|
attr |= FOREGROUND_RED | FOREGROUND_GREEN;
|
|
break;
|
|
case 34: /* Blue */
|
|
attr &= ~FOREGROUND_ALL;
|
|
attr |= FOREGROUND_BLUE;
|
|
break;
|
|
case 35: /* Magenta */
|
|
attr &= ~FOREGROUND_ALL;
|
|
attr |= FOREGROUND_RED | FOREGROUND_BLUE;
|
|
break;
|
|
case 36: /* Cyan */
|
|
attr &= ~FOREGROUND_ALL;
|
|
attr |= FOREGROUND_GREEN | FOREGROUND_BLUE;
|
|
break;
|
|
case 37: /* White */
|
|
attr |= FOREGROUND_RED |
|
|
FOREGROUND_GREEN |
|
|
FOREGROUND_BLUE;
|
|
break;
|
|
case 38: /* Unknown */
|
|
break;
|
|
case 39: /* reset */
|
|
attr &= ~FOREGROUND_ALL;
|
|
attr |= (plain_attr & FOREGROUND_ALL);
|
|
break;
|
|
case 40: /* Black */
|
|
attr &= ~BACKGROUND_ALL;
|
|
break;
|
|
case 41: /* Red */
|
|
attr &= ~BACKGROUND_ALL;
|
|
attr |= BACKGROUND_RED;
|
|
break;
|
|
case 42: /* Green */
|
|
attr &= ~BACKGROUND_ALL;
|
|
attr |= BACKGROUND_GREEN;
|
|
break;
|
|
case 43: /* Yellow */
|
|
attr &= ~BACKGROUND_ALL;
|
|
attr |= BACKGROUND_RED | BACKGROUND_GREEN;
|
|
break;
|
|
case 44: /* Blue */
|
|
attr &= ~BACKGROUND_ALL;
|
|
attr |= BACKGROUND_BLUE;
|
|
break;
|
|
case 45: /* Magenta */
|
|
attr &= ~BACKGROUND_ALL;
|
|
attr |= BACKGROUND_RED | BACKGROUND_BLUE;
|
|
break;
|
|
case 46: /* Cyan */
|
|
attr &= ~BACKGROUND_ALL;
|
|
attr |= BACKGROUND_GREEN | BACKGROUND_BLUE;
|
|
break;
|
|
case 47: /* White */
|
|
attr |= BACKGROUND_RED |
|
|
BACKGROUND_GREEN |
|
|
BACKGROUND_BLUE;
|
|
break;
|
|
case 48: /* Unknown */
|
|
break;
|
|
case 49: /* reset */
|
|
attr &= ~BACKGROUND_ALL;
|
|
attr |= (plain_attr & BACKGROUND_ALL);
|
|
break;
|
|
default:
|
|
/* Unsupported code */
|
|
break;
|
|
}
|
|
}
|
|
set_console_attr();
|
|
break;
|
|
case 'K':
|
|
erase_in_line();
|
|
break;
|
|
default:
|
|
/* Unsupported code */
|
|
break;
|
|
}
|
|
}
|
|
|
|
enum {
|
|
TEXT = 0, ESCAPE = 033, BRACKET = '['
|
|
};
|
|
|
|
static DWORD WINAPI console_thread(LPVOID unused)
|
|
{
|
|
unsigned char buffer[BUFFER_SIZE];
|
|
DWORD bytes;
|
|
int start, end = 0, c, parampos = 0, state = TEXT;
|
|
int params[MAX_PARAMS];
|
|
|
|
while (1) {
|
|
/* read next chunk of bytes from the pipe */
|
|
if (!ReadFile(hread, buffer + end, BUFFER_SIZE - end, &bytes,
|
|
NULL)) {
|
|
/* exit if pipe has been closed or disconnected */
|
|
if (GetLastError() == ERROR_PIPE_NOT_CONNECTED ||
|
|
GetLastError() == ERROR_BROKEN_PIPE)
|
|
break;
|
|
/* ignore other errors */
|
|
continue;
|
|
}
|
|
|
|
/* scan the bytes and handle ANSI control codes */
|
|
bytes += end;
|
|
start = end = 0;
|
|
while (end < bytes) {
|
|
c = buffer[end++];
|
|
switch (state) {
|
|
case TEXT:
|
|
if (c == ESCAPE) {
|
|
/* print text seen so far */
|
|
if (end - 1 > start)
|
|
write_console(buffer + start,
|
|
end - 1 - start);
|
|
|
|
/* then start parsing escape sequence */
|
|
start = end - 1;
|
|
memset(params, 0, sizeof(params));
|
|
parampos = 0;
|
|
state = ESCAPE;
|
|
}
|
|
break;
|
|
|
|
case ESCAPE:
|
|
/* continue if "\033[", otherwise bail out */
|
|
state = (c == BRACKET) ? BRACKET : TEXT;
|
|
break;
|
|
|
|
case BRACKET:
|
|
/* parse [0-9;]* into array of parameters */
|
|
if (c >= '0' && c <= '9') {
|
|
params[parampos] *= 10;
|
|
params[parampos] += c - '0';
|
|
} else if (c == ';') {
|
|
/*
|
|
* next parameter, bail out if out of
|
|
* bounds
|
|
*/
|
|
parampos++;
|
|
if (parampos >= MAX_PARAMS)
|
|
state = TEXT;
|
|
} else {
|
|
/*
|
|
* end of escape sequence, change
|
|
* console attributes
|
|
*/
|
|
set_attr(c, params, parampos + 1);
|
|
start = end;
|
|
state = TEXT;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* print remaining text unless parsing an escape sequence */
|
|
if (state == TEXT && end > start) {
|
|
/* check for incomplete UTF-8 sequences and fix end */
|
|
if (buffer[end - 1] >= 0x80) {
|
|
if (buffer[end -1] >= 0xc0)
|
|
end--;
|
|
else if (end - 1 > start &&
|
|
buffer[end - 2] >= 0xe0)
|
|
end -= 2;
|
|
else if (end - 2 > start &&
|
|
buffer[end - 3] >= 0xf0)
|
|
end -= 3;
|
|
}
|
|
|
|
/* print remaining complete UTF-8 sequences */
|
|
if (end > start)
|
|
write_console(buffer + start, end - start);
|
|
|
|
/* move remaining bytes to the front */
|
|
if (end < bytes)
|
|
memmove(buffer, buffer + end, bytes - end);
|
|
end = bytes - end;
|
|
} else {
|
|
/* all data has been consumed, mark buffer empty */
|
|
end = 0;
|
|
}
|
|
}
|
|
|
|
/* check if the console font supports unicode */
|
|
warn_if_raster_font();
|
|
|
|
CloseHandle(hread);
|
|
return 0;
|
|
}
|
|
|
|
static void winansi_exit(void)
|
|
{
|
|
/* flush all streams */
|
|
_flushall();
|
|
|
|
/* signal console thread to exit */
|
|
FlushFileBuffers(hwrite);
|
|
DisconnectNamedPipe(hwrite);
|
|
|
|
/* wait for console thread to copy remaining data */
|
|
WaitForSingleObject(hthread, INFINITE);
|
|
|
|
/* cleanup handles... */
|
|
CloseHandle(hwrite);
|
|
CloseHandle(hthread);
|
|
}
|
|
|
|
static void die_lasterr(const char *fmt, ...)
|
|
{
|
|
va_list params;
|
|
va_start(params, fmt);
|
|
errno = err_win_to_posix(GetLastError());
|
|
die_errno(fmt, params);
|
|
va_end(params);
|
|
}
|
|
|
|
static HANDLE duplicate_handle(HANDLE hnd)
|
|
{
|
|
HANDLE hresult, hproc = GetCurrentProcess();
|
|
if (!DuplicateHandle(hproc, hnd, hproc, &hresult, 0, TRUE,
|
|
DUPLICATE_SAME_ACCESS))
|
|
die_lasterr("DuplicateHandle(%li) failed", (long) hnd);
|
|
return hresult;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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[];
|
|
|
|
static size_t sizeof_ioinfo = 0;
|
|
|
|
#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;
|
|
char name[32];
|
|
|
|
/* check if either stdout or stderr is a console output screen buffer */
|
|
con1 = is_console(1);
|
|
con2 = is_console(2);
|
|
if (!con1 && !con2)
|
|
return;
|
|
|
|
/* create a named pipe to communicate with the console thread */
|
|
xsnprintf(name, sizeof(name), "\\\\.\\pipe\\winansi%lu", GetCurrentProcessId());
|
|
hwrite = CreateNamedPipe(name, PIPE_ACCESS_OUTBOUND,
|
|
PIPE_TYPE_BYTE | PIPE_WAIT, 1, BUFFER_SIZE, 0, 0, NULL);
|
|
if (hwrite == INVALID_HANDLE_VALUE)
|
|
die_lasterr("CreateNamedPipe failed");
|
|
|
|
hread = CreateFile(name, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
|
|
if (hread == INVALID_HANDLE_VALUE)
|
|
die_lasterr("CreateFile for named pipe failed");
|
|
|
|
/* start console spool thread on the pipe's read end */
|
|
hthread = CreateThread(NULL, 0, console_thread, NULL, 0, NULL);
|
|
if (hthread == INVALID_HANDLE_VALUE)
|
|
die_lasterr("CreateThread(console_thread) failed");
|
|
|
|
/* schedule cleanup routine */
|
|
if (atexit(winansi_exit))
|
|
die_errno("atexit(winansi_exit) failed");
|
|
|
|
/* redirect stdout / stderr to the pipe */
|
|
if (con1)
|
|
hconsole1 = swap_osfhnd(1, duplicate_handle(hwrite));
|
|
if (con2)
|
|
hconsole2 = swap_osfhnd(2, duplicate_handle(hwrite));
|
|
}
|
|
|
|
/*
|
|
* Returns the real console handle if stdout / stderr is a pipe redirecting
|
|
* to the console. Allows spawn / exec to pass the console to the next process.
|
|
*/
|
|
HANDLE winansi_get_osfhandle(int 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;
|
|
}
|