// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. #include "precomp.h" // Exit macros #define ConExitOnLastError(x, s, ...) ExitOnLastErrorSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__) #define ConExitOnLastErrorDebugTrace(x, s, ...) ExitOnLastErrorDebugTraceSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__) #define ConExitWithLastError(x, s, ...) ExitWithLastErrorSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__) #define ConExitOnFailure(x, s, ...) ExitOnFailureSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__) #define ConExitOnRootFailure(x, s, ...) ExitOnRootFailureSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__) #define ConExitOnFailureDebugTrace(x, s, ...) ExitOnFailureDebugTraceSource(DUTIL_SOURCE_CONUTIL, x, s, __VA_ARGS__) #define ConExitOnNull(p, x, e, s, ...) ExitOnNullSource(DUTIL_SOURCE_CONUTIL, p, x, e, s, __VA_ARGS__) #define ConExitOnNullWithLastError(p, x, s, ...) ExitOnNullWithLastErrorSource(DUTIL_SOURCE_CONUTIL, p, x, s, __VA_ARGS__) #define ConExitOnNullDebugTrace(p, x, e, s, ...) ExitOnNullDebugTraceSource(DUTIL_SOURCE_CONUTIL, p, x, e, s, __VA_ARGS__) #define ConExitOnInvalidHandleWithLastError(p, x, s, ...) ExitOnInvalidHandleWithLastErrorSource(DUTIL_SOURCE_CONUTIL, p, x, s, __VA_ARGS__) #define ConExitOnWin32Error(e, x, s, ...) ExitOnWin32ErrorSource(DUTIL_SOURCE_CONUTIL, e, x, s, __VA_ARGS__) #define ConExitOnGdipFailure(g, x, s, ...) ExitOnGdipFailureSource(DUTIL_SOURCE_CONUTIL, g, x, s, __VA_ARGS__) LPCSTR NEWLINE = "\r\n"; static HANDLE vhStdIn = INVALID_HANDLE_VALUE; static HANDLE vhStdOut = INVALID_HANDLE_VALUE; static BOOL vfStdInInteractive = FALSE; static BOOL vfStdOutInteractive = FALSE; static BOOL vfConsoleOut = FALSE; static CONSOLE_SCREEN_BUFFER_INFO vcsbiInfo; static HRESULT DAPI ReadInteractiveStdIn( __deref_out_z LPWSTR* ppwzBuffer ); static HRESULT ReadRedirectedStdIn( __deref_out_ecount_opt(*pcchSize) LPWSTR* ppwzBuffer, __out DWORD* pcchSize, BOOL fReadLine, DWORD dwMaxRead ); extern "C" HRESULT DAPI ConsoleInitialize() { Assert(INVALID_HANDLE_VALUE == vhStdOut); HRESULT hr = S_OK; DWORD dwStdInMode = 0; DWORD dwStdOutMode = 0; vhStdIn = ::GetStdHandle(STD_INPUT_HANDLE); if (INVALID_HANDLE_VALUE == vhStdIn) { ConExitOnLastError(hr, "failed to open stdin"); } vfStdInInteractive = ::GetFileType(vhStdIn) == FILE_TYPE_CHAR && ::GetConsoleMode(vhStdIn, &dwStdInMode); vhStdOut = ::GetStdHandle(STD_OUTPUT_HANDLE); if (INVALID_HANDLE_VALUE == vhStdOut) { ConExitOnLastError(hr, "failed to open stdout"); } vfStdOutInteractive = ::GetFileType(vhStdOut) == FILE_TYPE_CHAR && ::GetConsoleMode(vhStdOut, &dwStdOutMode); if (::GetConsoleScreenBufferInfo(vhStdOut, &vcsbiInfo)) { vfConsoleOut = TRUE; } if (!::SetConsoleCP(CP_UTF8) || !::SetConsoleOutputCP(CP_UTF8)) { ConExitWithLastError(hr, "failed to set console codepage to UTF-8"); } LExit: if (FAILED(hr)) { if (INVALID_HANDLE_VALUE != vhStdOut) { ::CloseHandle(vhStdOut); } if (INVALID_HANDLE_VALUE != vhStdIn && vhStdOut != vhStdIn) { ::CloseHandle(vhStdIn); } vhStdOut = INVALID_HANDLE_VALUE; vhStdIn = INVALID_HANDLE_VALUE; } return hr; } extern "C" void DAPI ConsoleUninitialize() { BOOL fOutEqualsIn = vhStdOut == vhStdIn; memset(&vcsbiInfo, 0, sizeof(vcsbiInfo)); if (INVALID_HANDLE_VALUE != vhStdOut) { ::CloseHandle(vhStdOut); } if (INVALID_HANDLE_VALUE != vhStdIn && !fOutEqualsIn) { ::CloseHandle(vhStdIn); } vhStdOut = INVALID_HANDLE_VALUE; vhStdIn = INVALID_HANDLE_VALUE; } extern "C" void DAPI ConsoleGreen() { AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called"); if (vfConsoleOut) { ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_GREEN | FOREGROUND_INTENSITY); } } extern "C" void DAPI ConsoleRed() { AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called"); if (vfConsoleOut) { ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_RED | FOREGROUND_INTENSITY); } } extern "C" void DAPI ConsoleYellow() { AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called"); if (vfConsoleOut) { ::SetConsoleTextAttribute(vhStdOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY); } } extern "C" void DAPI ConsoleNormal() { AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called"); if (vfConsoleOut) { ::SetConsoleTextAttribute(vhStdOut, vcsbiInfo.wAttributes); } } extern "C" HRESULT DAPI ConsoleWrite( CONSOLE_COLOR cc, __in_z __format_string LPCSTR szFormat, ... ) { AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called"); HRESULT hr = S_OK; LPSTR pszOutput = NULL; DWORD cchOutput = 0; DWORD cbWrote = 0; DWORD cbTotal = 0; // set the color switch (cc) { case CONSOLE_COLOR_NORMAL: break; // do nothing case CONSOLE_COLOR_RED: ConsoleRed(); break; case CONSOLE_COLOR_YELLOW: ConsoleYellow(); break; case CONSOLE_COLOR_GREEN: ConsoleGreen(); break; } va_list args; va_start(args, szFormat); hr = StrAnsiAllocFormattedArgs(&pszOutput, szFormat, args); va_end(args); ConExitOnFailure(hr, "failed to format message: \"%s\"", szFormat); cchOutput = lstrlenA(pszOutput); while (cbTotal < (sizeof(*pszOutput) * cchOutput)) { if (!::WriteFile(vhStdOut, reinterpret_cast(pszOutput) + cbTotal, cchOutput * sizeof(*pszOutput) - cbTotal, &cbWrote, NULL)) { ConExitOnLastError(hr, "failed to write output to console with format: %s", szFormat); } cbTotal += cbWrote; } // reset the color to normal if (CONSOLE_COLOR_NORMAL != cc) { ConsoleNormal(); } LExit: ReleaseStr(pszOutput); return hr; } extern "C" HRESULT DAPI ConsoleWriteW( __in CONSOLE_COLOR cc, __in_z LPCWSTR wzData ) { AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called"); HRESULT hr = S_OK; LPSTR pszOutput = NULL; DWORD cchOutput = 0; DWORD cbWrote = 0; DWORD cbTotal = 0; // set the color switch (cc) { case CONSOLE_COLOR_NORMAL: break; // do nothing case CONSOLE_COLOR_RED: ConsoleRed(); break; case CONSOLE_COLOR_YELLOW: ConsoleYellow(); break; case CONSOLE_COLOR_GREEN: ConsoleGreen(); break; } hr = StrAnsiAllocString(&pszOutput, wzData, 0, CP_UTF8); ExitOnFailure(hr, "failed to convert console output to utf-8: %ls", wzData); cchOutput = lstrlenA(pszOutput); while (cbTotal < (sizeof(*pszOutput) * cchOutput)) { if (!::WriteFile(vhStdOut, reinterpret_cast(pszOutput) + cbTotal, cchOutput * sizeof(*pszOutput) - cbTotal, &cbWrote, NULL)) { ConExitOnLastError(hr, "failed to write output to console with format: %ls", wzData); } cbTotal += cbWrote; } // reset the color to normal if (CONSOLE_COLOR_NORMAL != cc) { ConsoleNormal(); } LExit: ReleaseStr(pszOutput); return hr; } extern "C" HRESULT DAPI ConsoleWriteLine( CONSOLE_COLOR cc, __in_z __format_string LPCSTR szFormat, ... ) { AssertSz(INVALID_HANDLE_VALUE != vhStdOut, "ConsoleInitialize() has not been called"); HRESULT hr = S_OK; LPSTR pszOutput = NULL; DWORD cchOutput = 0; DWORD cbWrote = 0; DWORD cbTotal = 0; // set the color switch (cc) { case CONSOLE_COLOR_NORMAL: break; // do nothing case CONSOLE_COLOR_RED: ConsoleRed(); break; case CONSOLE_COLOR_YELLOW: ConsoleYellow(); break; case CONSOLE_COLOR_GREEN: ConsoleGreen(); break; } va_list args; va_start(args, szFormat); hr = StrAnsiAllocFormattedArgs(&pszOutput, szFormat, args); va_end(args); ConExitOnFailure(hr, "failed to format message: \"%s\"", szFormat); // // write the string // cchOutput = lstrlenA(pszOutput); while (cbTotal < (sizeof(*pszOutput) * cchOutput)) { if (!::WriteFile(vhStdOut, reinterpret_cast(pszOutput) + cbTotal, cchOutput * sizeof(*pszOutput) - cbTotal, &cbWrote, NULL)) ConExitOnLastError(hr, "failed to write output to console: %s", pszOutput); cbTotal += cbWrote; } // // write the newline // if (!::WriteFile(vhStdOut, reinterpret_cast(NEWLINE), 2, &cbWrote, NULL)) { ConExitOnLastError(hr, "failed to write newline to console"); } // reset the color to normal if (CONSOLE_COLOR_NORMAL != cc) { ConsoleNormal(); } LExit: ReleaseStr(pszOutput); return hr; } HRESULT ConsoleWriteError( HRESULT hrError, CONSOLE_COLOR cc, __in_z __format_string LPCSTR szFormat, ... ) { HRESULT hr = S_OK; LPSTR pszMessage = NULL; va_list args; va_start(args, szFormat); hr = StrAnsiAllocFormattedArgs(&pszMessage, szFormat, args); va_end(args); ConExitOnFailure(hr, "failed to format error message: \"%s\"", szFormat); if (FAILED(hrError)) { hr = ConsoleWriteLine(cc, "Error 0x%x: %s", hrError, pszMessage); } else { hr = ConsoleWriteLine(cc, "Error: %s", pszMessage); } LExit: ReleaseStr(pszMessage); return hr; } extern "C" HRESULT DAPI ConsoleReadW( __deref_out_z LPWSTR* ppwzBuffer ) { AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called"); Assert(ppwzBuffer); HRESULT hr = S_OK; DWORD cchSize = 0; if (vfStdInInteractive) { hr = ReadInteractiveStdIn(ppwzBuffer); ExitOnFailure(hr, "failed to read from interactive console"); } else { hr = ReadRedirectedStdIn(ppwzBuffer, &cchSize, TRUE, 0); ExitOnFailure(hr, "failed to read from redirected console"); } LExit: return hr; } extern "C" HRESULT DAPI ConsoleReadNonBlockingW( __deref_out_ecount_opt(*pcchSize) LPWSTR* ppwzBuffer, __out DWORD* pcchSize, BOOL fReadLine ) { Assert(INVALID_HANDLE_VALUE != vhStdIn && pcchSize); HRESULT hr = S_OK; LPSTR psz = NULL; ConExitOnNull(ppwzBuffer, hr, E_INVALIDARG, "Failed to read from console because buffer was not provided"); DWORD dwRead; DWORD dwNumInput; DWORD cchTotal = 0; DWORD cch = 8; DWORD dwIndex = 0; DWORD er; INPUT_RECORD ir; WCHAR chIn; *ppwzBuffer = NULL; *pcchSize = 0; // If we really have a handle to stdin, and not the end of a pipe if (!PeekNamedPipe(vhStdIn, NULL, 0, NULL, &dwRead, NULL)) { er = ::GetLastError(); if (ERROR_INVALID_HANDLE != er) { ExitFunction1(hr = HRESULT_FROM_WIN32(er)); } if (!GetNumberOfConsoleInputEvents(vhStdIn, &dwRead)) { ConExitOnLastError(hr, "failed to peek at console input"); } if (0 == dwRead) { ExitFunction1(hr = S_FALSE); } for (/* dwRead from num of input events */; dwRead > 0; dwRead--) { if (!ReadConsoleInputW(vhStdIn, &ir, 1, &dwNumInput)) { ConExitOnLastError(hr, "Failed to read input from console"); } // If what we have is a KEY_EVENT, and that event signifies keyUp, we're interested if (KEY_EVENT == ir.EventType && FALSE == ir.Event.KeyEvent.bKeyDown) { chIn = ir.Event.KeyEvent.uChar.UnicodeChar; if (0 == cchTotal) { cchTotal = cch; cch *= 2; StrAlloc(ppwzBuffer, cch); } (*ppwzBuffer)[dwIndex] = chIn; if (fReadLine && (L'\r' == (*ppwzBuffer)[dwIndex - 1] && L'\n' == (*ppwzBuffer)[dwIndex])) { *ppwzBuffer[dwIndex - 1] = L'\0'; dwIndex -= 1; break; } ++dwIndex; cchTotal--; } } *pcchSize = dwIndex; } else { // otherwise, the peek worked, and we have the end of a pipe if (0 == dwRead) { ExitFunction1(hr = S_FALSE); } hr = ReadRedirectedStdIn(ppwzBuffer, pcchSize, fReadLine, dwRead); } LExit: ReleaseStr(psz); return hr; } extern "C" HRESULT DAPI ConsoleReadStringA( __deref_inout_ecount_part(cchCharBuffer,*pcchNumCharReturn) LPSTR* ppszCharBuffer, CONST DWORD cchCharBuffer, __out DWORD* pcchNumCharReturn ) { AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called"); HRESULT hr = S_OK; if (ppszCharBuffer && (pcchNumCharReturn || cchCharBuffer < 2)) { DWORD iRead = 1; DWORD iReadCharTotal = 0; if (ppszCharBuffer && *ppszCharBuffer == NULL) { do { hr = StrAnsiAlloc(ppszCharBuffer, cchCharBuffer * iRead); ConExitOnFailure(hr, "failed to allocate memory for ConsoleReadStringA"); // ReadConsoleW will not return until , the last two chars are 13 and 10. if (!::ReadConsoleA(vhStdIn, *ppszCharBuffer + iReadCharTotal, cchCharBuffer, pcchNumCharReturn, NULL) || *pcchNumCharReturn == 0) { ConExitOnLastError(hr, "failed to read string from console"); } iReadCharTotal += *pcchNumCharReturn; iRead += 1; } while((*ppszCharBuffer)[iReadCharTotal - 1] != 10 || (*ppszCharBuffer)[iReadCharTotal - 2] != 13); *pcchNumCharReturn = iReadCharTotal; } else { if (!::ReadConsoleA(vhStdIn, *ppszCharBuffer, cchCharBuffer, pcchNumCharReturn, NULL) || *pcchNumCharReturn > cchCharBuffer || *pcchNumCharReturn == 0) { ConExitOnLastError(hr, "failed to read string from console"); } if ((*ppszCharBuffer)[*pcchNumCharReturn - 1] != 10 || (*ppszCharBuffer)[*pcchNumCharReturn - 2] != 13) { // need read more hr = ERROR_MORE_DATA; } } } else { hr = E_INVALIDARG; } LExit: return hr; } extern "C" HRESULT DAPI ConsoleReadStringW( __deref_inout_ecount_part(cchCharBuffer,*pcchNumCharReturn) LPWSTR* ppwzCharBuffer, CONST DWORD cchCharBuffer, __out DWORD* pcchNumCharReturn ) { AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called"); HRESULT hr = S_OK; if (ppwzCharBuffer && (pcchNumCharReturn || cchCharBuffer < 2)) { DWORD iRead = 1; DWORD iReadCharTotal = 0; if (*ppwzCharBuffer == NULL) { do { hr = StrAlloc(ppwzCharBuffer, cchCharBuffer * iRead); ConExitOnFailure(hr, "failed to allocate memory for ConsoleReadStringW"); // ReadConsoleW will not return until , the last two chars are 13 and 10. if (!::ReadConsoleW(vhStdIn, *ppwzCharBuffer + iReadCharTotal, cchCharBuffer, pcchNumCharReturn, NULL) || *pcchNumCharReturn == 0) { ConExitOnLastError(hr, "failed to read string from console"); } iReadCharTotal += *pcchNumCharReturn; iRead += 1; } while((*ppwzCharBuffer)[iReadCharTotal - 1] != 10 || (*ppwzCharBuffer)[iReadCharTotal - 2] != 13); *pcchNumCharReturn = iReadCharTotal; } else { if (!::ReadConsoleW(vhStdIn, *ppwzCharBuffer, cchCharBuffer, pcchNumCharReturn, NULL) || *pcchNumCharReturn > cchCharBuffer || *pcchNumCharReturn == 0) { ConExitOnLastError(hr, "failed to read string from console"); } if ((*ppwzCharBuffer)[*pcchNumCharReturn - 1] != 10 || (*ppwzCharBuffer)[*pcchNumCharReturn - 2] != 13) { // need read more hr = ERROR_MORE_DATA; } } } else { hr = E_INVALIDARG; } LExit: return hr; } extern "C" HRESULT DAPI ConsoleSetReadHidden(void) { AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called"); HRESULT hr = S_OK; ::FlushConsoleInputBuffer(vhStdIn); if (!::SetConsoleMode(vhStdIn, ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)) { ConExitOnLastError(hr, "failed to set console input mode to be hidden"); } LExit: return hr; } extern "C" HRESULT DAPI ConsoleSetReadNormal(void) { AssertSz(INVALID_HANDLE_VALUE != vhStdIn, "ConsoleInitialize() has not been called"); HRESULT hr = S_OK; if (!::SetConsoleMode(vhStdIn, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_MOUSE_INPUT)) { ConExitOnLastError(hr, "failed to set console input mode to be normal"); } LExit: return hr; } static HRESULT DAPI ReadInteractiveStdIn( __deref_out_z LPWSTR* ppwzBuffer ) { HRESULT hr = S_OK; LPWSTR pwz = NULL; DWORD cch = 8; DWORD cchRead = 0; DWORD cchTotalRead = 0; hr = StrAlloc(&pwz, cch); ConExitOnFailure(hr, "failed to allocate memory to read from console"); // loop until we read the \r\n from the console for (;;) { // read one character at a time, since that seems to be the only way to make this work if (!::ReadConsoleW(vhStdIn, pwz + cchTotalRead, 1, &cchRead, NULL)) { ConExitOnLastError(hr, "failed to read string from console"); } cchTotalRead += cchRead; if (0 == cchRead) // nothing more was read { pwz[cchTotalRead] = L'\0'; // null terminate and bail break; } else if (0 < cchTotalRead && L'\n' == pwz[cchTotalRead - 1]) { if (1 < cchTotalRead && L'\r' == pwz[cchTotalRead - 2]) { pwz[cchTotalRead - 2] = L'\0'; // chop off the \r\n } else { pwz[cchTotalRead - 1] = L'\0'; // chop off the \n } break; } if (cchTotalRead == cch) { cch *= 2; // double everytime we run out of space hr = StrAlloc(&pwz, cch); ConExitOnFailure(hr, "failed to allocate memory to read from console"); } } hr = StrAllocString(ppwzBuffer, pwz, 0); ConExitOnFailure(hr, "failed to copy stdin buffer to return buffer"); LExit: ReleaseStr(pwz); return hr; } static HRESULT ReadRedirectedStdIn( __deref_out_ecount_opt(*pcchSize) LPWSTR* ppwzBuffer, __out DWORD* pcchSize, BOOL fReadLine, DWORD dwMaxRead ) { HRESULT hr = S_OK; LPSTR psz = NULL; DWORD cch = 8; DWORD cchRead = 0; DWORD cchTotalRead = 0; hr = StrAnsiAlloc(&psz, cch); ConExitOnFailure(hr, "failed to allocate memory to read from console"); while (dwMaxRead == 0 || cchTotalRead < dwMaxRead) { // read one character at a time, since that seems to be the only way to make this work if (!::ReadFile(vhStdIn, psz + cchTotalRead, 1, &cchRead, NULL)) { ConExitOnLastError(hr, "failed to read string from console"); } cchTotalRead += cchRead; if (0 == cchRead) // nothing more was read { psz[cchTotalRead] = '\0'; // null terminate and bail break; } else if (fReadLine && 0 < cchTotalRead && '\n' == psz[cchTotalRead - 1]) { if (1 < cchTotalRead && '\r' == psz[cchTotalRead - 2]) { psz[cchTotalRead - 2] = '\0'; // chop off the \r\n } else { psz[cchTotalRead - 1] = '\0'; // chop off the \n } break; } if (cchTotalRead == cch) { cch *= 2; // double everytime we run out of space hr = StrAnsiAlloc(&psz, cch); ConExitOnFailure(hr, "failed to allocate memory to read from console"); } } *pcchSize = cchTotalRead; hr = StrAllocStringAnsi(ppwzBuffer, psz, 0, CP_UTF8); ConExitOnFailure(hr, "failed to convert console data"); LExit: return hr; }