+++ /dev/null
-//
-// Windows versions tested
-//
-// Vista Enterprise SP2 32-bit
-// - ver reports [Version 6.0.6002]
-// - kernel32.dll product/file versions are 6.0.6002.19381
-//
-// Windows 7 Ultimate SP1 32-bit
-// - ver reports [Version 6.1.7601]
-// - conhost.exe product/file versions are 6.1.7601.18847
-// - kernel32.dll product/file versions are 6.1.7601.18847
-//
-// Windows Server 2008 R2 Datacenter SP1 64-bit
-// - ver reports [Version 6.1.7601]
-// - conhost.exe product/file versions are 6.1.7601.23153
-// - kernel32.dll product/file versions are 6.1.7601.23153
-//
-// Windows 8 Enterprise 32-bit
-// - ver reports [Version 6.2.9200]
-// - conhost.exe product/file versions are 6.2.9200.16578
-// - kernel32.dll product/file versions are 6.2.9200.16859
-//
-
-//
-// Specific version details on working Server 2008 R2:
-//
-// dwMajorVersion = 6
-// dwMinorVersion = 1
-// dwBuildNumber = 7601
-// dwPlatformId = 2
-// szCSDVersion = Service Pack 1
-// wServicePackMajor = 1
-// wServicePackMinor = 0
-// wSuiteMask = 0x190
-// wProductType = 0x3
-//
-// Specific version details on broken Win7:
-//
-// dwMajorVersion = 6
-// dwMinorVersion = 1
-// dwBuildNumber = 7601
-// dwPlatformId = 2
-// szCSDVersion = Service Pack 1
-// wServicePackMajor = 1
-// wServicePackMinor = 0
-// wSuiteMask = 0x100
-// wProductType = 0x1
-//
-
-#include <windows.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "TestUtil.cc"
-
-const char *g_prefix = "";
-
-static void dumpHandles() {
- trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x",
- g_prefix,
- (long long)GetStdHandle(STD_INPUT_HANDLE),
- (long long)GetStdHandle(STD_OUTPUT_HANDLE),
- (long long)GetStdHandle(STD_ERROR_HANDLE));
-}
-
-static const char *successOrFail(BOOL ret) {
- return ret ? "ok" : "FAILED";
-}
-
-static void startChildInSameConsole(const wchar_t *args, BOOL
- bInheritHandles=FALSE) {
- wchar_t program[1024];
- wchar_t cmdline[1024];
- GetModuleFileNameW(NULL, program, 1024);
- swprintf(cmdline, L"\"%ls\" %ls", program, args);
-
- STARTUPINFOW sui;
- PROCESS_INFORMATION pi;
- memset(&sui, 0, sizeof(sui));
- memset(&pi, 0, sizeof(pi));
- sui.cb = sizeof(sui);
-
- CreateProcessW(program, cmdline,
- NULL, NULL,
- /*bInheritHandles=*/bInheritHandles,
- /*dwCreationFlags=*/0,
- NULL, NULL,
- &sui, &pi);
-}
-
-static void closeHandle(HANDLE h) {
- trace("%sClosing handle 0x%I64x...", g_prefix, (long long)h);
- trace("%sClosing handle 0x%I64x... %s", g_prefix, (long long)h, successOrFail(CloseHandle(h)));
-}
-
-static HANDLE createBuffer() {
-
- // If sa isn't provided, the handle defaults to not-inheritable.
- SECURITY_ATTRIBUTES sa = {0};
- sa.nLength = sizeof(sa);
- sa.bInheritHandle = TRUE;
-
- trace("%sCreating a new buffer...", g_prefix);
- HANDLE conout = CreateConsoleScreenBuffer(
- GENERIC_READ | GENERIC_WRITE,
- FILE_SHARE_READ | FILE_SHARE_WRITE,
- &sa,
- CONSOLE_TEXTMODE_BUFFER, NULL);
-
- trace("%sCreating a new buffer... 0x%I64x", g_prefix, (long long)conout);
- return conout;
-}
-
-static HANDLE openConout() {
-
- // If sa isn't provided, the handle defaults to not-inheritable.
- SECURITY_ATTRIBUTES sa = {0};
- sa.nLength = sizeof(sa);
- sa.bInheritHandle = TRUE;
-
- trace("%sOpening CONOUT...", g_prefix);
- HANDLE conout = CreateFileW(L"CONOUT$",
- GENERIC_READ | GENERIC_WRITE,
- FILE_SHARE_READ | FILE_SHARE_WRITE,
- &sa,
- OPEN_EXISTING, 0, NULL);
- trace("%sOpening CONOUT... 0x%I64x", g_prefix, (long long)conout);
- return conout;
-}
-
-static void setConsoleActiveScreenBuffer(HANDLE conout) {
- trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...",
- g_prefix, (long long)conout);
- trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s",
- g_prefix, (long long)conout,
- successOrFail(SetConsoleActiveScreenBuffer(conout)));
-}
-
-static void writeTest(HANDLE conout, const char *msg) {
- char writeData[256];
- sprintf(writeData, "%s%s\n", g_prefix, msg);
-
- trace("%sWriting to 0x%I64x: '%s'...",
- g_prefix, (long long)conout, msg);
- DWORD actual = 0;
- BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL);
- trace("%sWriting to 0x%I64x: '%s'... %s",
- g_prefix, (long long)conout, msg,
- successOrFail(ret && actual == strlen(writeData)));
-}
-
-static void writeTest(const char *msg) {
- writeTest(GetStdHandle(STD_OUTPUT_HANDLE), msg);
-}
-
-
-
-///////////////////////////////////////////////////////////////////////////////
-// TEST 1 -- create new buffer, activate it, and close the handle. The console
-// automatically switches the screen buffer back to the original.
-//
-// This test passes everywhere.
-//
-
-static void test1(int argc, char *argv[]) {
- if (!strcmp(argv[1], "1")) {
- startChildProcess(L"1:child");
- return;
- }
-
- HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
- writeTest(origBuffer, "<-- origBuffer -->");
-
- HANDLE newBuffer = createBuffer();
- writeTest(newBuffer, "<-- newBuffer -->");
- setConsoleActiveScreenBuffer(newBuffer);
- Sleep(2000);
-
- writeTest(origBuffer, "TEST PASSED!");
-
- // Closing the handle w/o switching the active screen buffer automatically
- // switches the console back to the original buffer.
- closeHandle(newBuffer);
-
- while (true) {
- Sleep(1000);
- }
-}
-
-
-
-///////////////////////////////////////////////////////////////////////////////
-// TEST 2 -- Test program that creates and activates newBuffer, starts a child
-// process, then closes its newBuffer handle. newBuffer remains activated,
-// because the child keeps it active. (Also see TEST D.)
-//
-
-static void test2(int argc, char *argv[]) {
- if (!strcmp(argv[1], "2")) {
- startChildProcess(L"2:parent");
- return;
- }
-
- if (!strcmp(argv[1], "2:parent")) {
- g_prefix = "parent: ";
- dumpHandles();
- HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
- writeTest(origBuffer, "<-- origBuffer -->");
-
- HANDLE newBuffer = createBuffer();
- writeTest(newBuffer, "<-- newBuffer -->");
- setConsoleActiveScreenBuffer(newBuffer);
-
- Sleep(1000);
- writeTest(newBuffer, "bInheritHandles=FALSE:");
- startChildInSameConsole(L"2:child", FALSE);
- Sleep(1000);
- writeTest(newBuffer, "bInheritHandles=TRUE:");
- startChildInSameConsole(L"2:child", TRUE);
-
- Sleep(1000);
- trace("parent:----");
-
- // Close the new buffer. The active screen buffer doesn't automatically
- // switch back to origBuffer, because the child process has a handle open
- // to the original buffer.
- closeHandle(newBuffer);
-
- Sleep(600 * 1000);
- return;
- }
-
- if (!strcmp(argv[1], "2:child")) {
- g_prefix = "child: ";
- dumpHandles();
- // The child's output isn't visible, because it's still writing to
- // origBuffer.
- trace("child:----");
- writeTest("writing to STDOUT");
-
- // Handle inheritability is curious. The console handles this program
- // creates are inheritable, but CreateProcess is called with both
- // bInheritHandles=TRUE and bInheritHandles=FALSE.
- //
- // Vista and Windows 7: bInheritHandles has no effect. The child and
- // parent processes have the same STDIN/STDOUT/STDERR handles:
- // 0x3, 0x7, and 0xB. The parent has a 0xF handle for newBuffer.
- // The child can only write to 0x7, 0xB, and 0xF. Only the writes to
- // 0xF are visible (i.e. they touch newBuffer).
- //
- // Windows 8 or Windows 10 (legacy or non-legacy): the lowest 2 bits of
- // the HANDLE to WriteConsole seem to be ignored. The new process'
- // console handles always refer to the buffer that was active when they
- // started, but the values of the handles depend upon bInheritHandles.
- // With bInheritHandles=TRUE, the child has the same
- // STDIN/STDOUT/STDERR/newBuffer handles as the parent, and the three
- // output handles all work, though their output is all visible. With
- // bInheritHandles=FALSE, the child has different STDIN/STDOUT/STDERR
- // handles, and only the new STDOUT/STDERR handles work.
- //
- for (unsigned int i = 0x1; i <= 0xB0; ++i) {
- char msg[256];
- sprintf(msg, "Write to handle 0x%x", i);
- HANDLE h = reinterpret_cast<HANDLE>(i);
- writeTest(h, msg);
- }
-
- Sleep(600 * 1000);
- return;
- }
-}
-
-
-
-///////////////////////////////////////////////////////////////////////////////
-// TEST A -- demonstrate an apparent Windows bug with screen buffers
-//
-// Steps:
-// - The parent starts a child process.
-// - The child process creates and activates newBuffer
-// - The parent opens CONOUT$ and writes to it.
-// - The parent closes CONOUT$.
-// - At this point, broken Windows reactivates origBuffer.
-// - The child writes to newBuffer again.
-// - The child activates origBuffer again, then closes newBuffer.
-//
-// Test passes if the message "TEST PASSED!" is visible.
-// Test commonly fails if conhost.exe crashes.
-//
-// Results:
-// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
-// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
-// - Windows 8 Enterprise 32-bit: PASS
-// - Windows 10 64-bit (legacy and non-legacy): PASS
-//
-
-static void testA_parentWork() {
- // Open an extra CONOUT$ handle so that the HANDLE values in parent and
- // child don't collide. I think it's OK if they collide, but since we're
- // trying to track down a Windows bug, it's best to avoid unnecessary
- // complication.
- HANDLE dummy = openConout();
-
- Sleep(3000);
-
- // Step 2: Open CONOUT$ in the parent. This opens the active buffer, which
- // was just created in the child. It's handle 0x13. Write to it.
-
- HANDLE newBuffer = openConout();
- writeTest(newBuffer, "step2: writing to newBuffer");
-
- Sleep(3000);
-
- // Step 3: Close handle 0x13. With Windows 7, the console switches back to
- // origBuffer, and (unless I'm missing something) it shouldn't.
-
- closeHandle(newBuffer);
-}
-
-static void testA_childWork() {
- HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
-
- //
- // Step 1: Create the new screen buffer in the child process and make it
- // active. (Typically, it's handle 0x0F.)
- //
-
- HANDLE newBuffer = createBuffer();
-
- setConsoleActiveScreenBuffer(newBuffer);
- writeTest(newBuffer, "<-- newBuffer -->");
-
- Sleep(9000);
- trace("child:----");
-
- // Step 4: write to the newBuffer again.
- writeTest(newBuffer, "TEST PASSED!");
-
- //
- // Step 5: Switch back to the original screen buffer and close the new
- // buffer. The switch call succeeds, but the CloseHandle call freezes for
- // several seconds, because conhost.exe crashes.
- //
- Sleep(3000);
-
- setConsoleActiveScreenBuffer(origBuffer);
- writeTest(origBuffer, "writing to origBuffer");
-
- closeHandle(newBuffer);
-
- // The console HWND is NULL.
- trace("child: console HWND=0x%I64x", (long long)GetConsoleWindow());
-
- // At this point, the console window has closed, but the parent/child
- // processes are still running. Calling AllocConsole would fail, but
- // calling FreeConsole followed by AllocConsole would both succeed, and a
- // new console would appear.
-}
-
-static void testA(int argc, char *argv[]) {
-
- if (!strcmp(argv[1], "A")) {
- startChildProcess(L"A:parent");
- return;
- }
-
- if (!strcmp(argv[1], "A:parent")) {
- g_prefix = "parent: ";
- trace("parent:----");
- dumpHandles();
- writeTest("<-- origBuffer -->");
- startChildInSameConsole(L"A:child");
- testA_parentWork();
- Sleep(120000);
- return;
- }
-
- if (!strcmp(argv[1], "A:child")) {
- g_prefix = "child: ";
- dumpHandles();
- testA_childWork();
- Sleep(120000);
- return;
- }
-}
-
-
-
-///////////////////////////////////////////////////////////////////////////////
-// TEST B -- invert TEST A -- also crashes conhost on Windows 7
-//
-// Test passes if the message "TEST PASSED!" is visible.
-// Test commonly fails if conhost.exe crashes.
-//
-// Results:
-// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
-// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
-// - Windows 8 Enterprise 32-bit: PASS
-// - Windows 10 64-bit (legacy and non-legacy): PASS
-//
-
-static void testB(int argc, char *argv[]) {
- if (!strcmp(argv[1], "B")) {
- startChildProcess(L"B:parent");
- return;
- }
-
- if (!strcmp(argv[1], "B:parent")) {
- g_prefix = "parent: ";
- startChildInSameConsole(L"B:child");
- writeTest("<-- origBuffer -->");
- HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
-
- //
- // Step 1: Create the new buffer and make it active.
- //
- trace("%s----", g_prefix);
- HANDLE newBuffer = createBuffer();
- setConsoleActiveScreenBuffer(newBuffer);
- writeTest(newBuffer, "<-- newBuffer -->");
-
- //
- // Step 4: Attempt to write again to the new buffer.
- //
- Sleep(9000);
- trace("%s----", g_prefix);
- writeTest(newBuffer, "TEST PASSED!");
-
- //
- // Step 5: Switch back to the original buffer.
- //
- Sleep(3000);
- trace("%s----", g_prefix);
- setConsoleActiveScreenBuffer(origBuffer);
- closeHandle(newBuffer);
- writeTest(origBuffer, "writing to the initial buffer");
-
- Sleep(60000);
- return;
- }
-
- if (!strcmp(argv[1], "B:child")) {
- g_prefix = "child: ";
- Sleep(3000);
- trace("%s----", g_prefix);
-
- //
- // Step 2: Open the newly active buffer and write to it.
- //
- HANDLE newBuffer = openConout();
- writeTest(newBuffer, "writing to newBuffer");
-
- //
- // Step 3: Close the newly active buffer.
- //
- Sleep(3000);
- closeHandle(newBuffer);
-
- Sleep(60000);
- return;
- }
-}
-
-
-
-///////////////////////////////////////////////////////////////////////////////
-// TEST C -- Interleaving open/close of console handles also seems to break on
-// Windows 7.
-//
-// Test:
-// - child creates and activates newBuf1
-// - parent opens newBuf1
-// - child creates and activates newBuf2
-// - parent opens newBuf2, then closes newBuf1
-// - child switches back to newBuf1
-// * At this point, the console starts malfunctioning.
-// - parent and child close newBuf2
-// - child closes newBuf1
-//
-// Test passes if the message "TEST PASSED!" is visible.
-// Test commonly fails if conhost.exe crashes.
-//
-// Results:
-// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
-// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
-// - Windows 8 Enterprise 32-bit: PASS
-// - Windows 10 64-bit (legacy and non-legacy): PASS
-//
-
-static void testC(int argc, char *argv[]) {
- if (!strcmp(argv[1], "C")) {
- startChildProcess(L"C:parent");
- return;
- }
-
- if (!strcmp(argv[1], "C:parent")) {
- startChildInSameConsole(L"C:child");
- writeTest("<-- origBuffer -->");
- g_prefix = "parent: ";
-
- // At time=4, open newBuffer1.
- Sleep(4000);
- trace("%s---- t=4", g_prefix);
- const HANDLE newBuffer1 = openConout();
-
- // At time=8, open newBuffer2, and close newBuffer1.
- Sleep(4000);
- trace("%s---- t=8", g_prefix);
- const HANDLE newBuffer2 = openConout();
- closeHandle(newBuffer1);
-
- // At time=25, cleanup of newBuffer2.
- Sleep(17000);
- trace("%s---- t=25", g_prefix);
- closeHandle(newBuffer2);
-
- Sleep(240000);
- return;
- }
-
- if (!strcmp(argv[1], "C:child")) {
- g_prefix = "child: ";
-
- // At time=2, create newBuffer1 and activate it.
- Sleep(2000);
- trace("%s---- t=2", g_prefix);
- const HANDLE newBuffer1 = createBuffer();
- setConsoleActiveScreenBuffer(newBuffer1);
- writeTest(newBuffer1, "<-- newBuffer1 -->");
-
- // At time=6, create newBuffer2 and activate it.
- Sleep(4000);
- trace("%s---- t=6", g_prefix);
- const HANDLE newBuffer2 = createBuffer();
- setConsoleActiveScreenBuffer(newBuffer2);
- writeTest(newBuffer2, "<-- newBuffer2 -->");
-
- // At time=10, attempt to switch back to newBuffer1. The parent process
- // has opened and closed its handle to newBuffer1, so does it still exist?
- Sleep(4000);
- trace("%s---- t=10", g_prefix);
- setConsoleActiveScreenBuffer(newBuffer1);
- writeTest(newBuffer1, "write to newBuffer1: TEST PASSED!");
-
- // At time=25, cleanup of newBuffer2.
- Sleep(15000);
- trace("%s---- t=25", g_prefix);
- closeHandle(newBuffer2);
-
- // At time=35, cleanup of newBuffer1. The console should switch to the
- // initial buffer again.
- Sleep(10000);
- trace("%s---- t=35", g_prefix);
- closeHandle(newBuffer1);
-
- Sleep(240000);
- return;
- }
-}
-
-
-
-///////////////////////////////////////////////////////////////////////////////
-// TEST D -- parent creates a new buffer, child launches, writes,
-// closes it output handle, then parent writes again. (Also see TEST 2.)
-//
-// On success, this will appear:
-//
-// parent: <-- newBuffer -->
-// child: writing to newBuffer
-// parent: TEST PASSED!
-//
-// If this appears, it indicates that the child's closing its output handle did
-// not destroy newBuffer.
-//
-// Results:
-// - Windows 7 Ultimate SP1 32-bit: PASS
-// - Windows 8 Enterprise 32-bit: PASS
-// - Windows 10 64-bit (legacy and non-legacy): PASS
-//
-
-static void testD(int argc, char *argv[]) {
- if (!strcmp(argv[1], "D")) {
- startChildProcess(L"D:parent");
- return;
- }
-
- if (!strcmp(argv[1], "D:parent")) {
- g_prefix = "parent: ";
- HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
- writeTest(origBuffer, "<-- origBuffer -->");
-
- HANDLE newBuffer = createBuffer();
- writeTest(newBuffer, "<-- newBuffer -->");
- setConsoleActiveScreenBuffer(newBuffer);
-
- // At t=2, start a child process, explicitly forcing it to use
- // newBuffer for its standard handles. These calls are apparently
- // redundant on Windows 8 and up.
- Sleep(2000);
- trace("parent:----");
- trace("parent: starting child process");
- SetStdHandle(STD_OUTPUT_HANDLE, newBuffer);
- SetStdHandle(STD_ERROR_HANDLE, newBuffer);
- startChildInSameConsole(L"D:child");
- SetStdHandle(STD_OUTPUT_HANDLE, origBuffer);
- SetStdHandle(STD_ERROR_HANDLE, origBuffer);
-
- // At t=6, write again to newBuffer.
- Sleep(4000);
- trace("parent:----");
- writeTest(newBuffer, "TEST PASSED!");
-
- // At t=8, close the newBuffer. In earlier versions of windows
- // (including Server 2008 R2), the console then switches back to
- // origBuffer. As of Windows 8, it doesn't, because somehow the child
- // process is keeping the console on newBuffer, even though the child
- // process closed its STDIN/STDOUT/STDERR handles. Killing the child
- // process by hand after the test finishes *does* force the console
- // back to origBuffer.
- Sleep(2000);
- closeHandle(newBuffer);
-
- Sleep(120000);
- return;
- }
-
- if (!strcmp(argv[1], "D:child")) {
- g_prefix = "child: ";
- // At t=2, the child starts.
- trace("child:----");
- dumpHandles();
- writeTest("writing to newBuffer");
-
- // At t=4, the child explicitly closes its handle.
- Sleep(2000);
- trace("child:----");
- if (GetStdHandle(STD_ERROR_HANDLE) != GetStdHandle(STD_OUTPUT_HANDLE)) {
- closeHandle(GetStdHandle(STD_ERROR_HANDLE));
- }
- closeHandle(GetStdHandle(STD_OUTPUT_HANDLE));
- closeHandle(GetStdHandle(STD_INPUT_HANDLE));
-
- Sleep(120000);
- return;
- }
-}
-
-
-
-int main(int argc, char *argv[]) {
- if (argc == 1) {
- printf("USAGE: %s testnum\n", argv[0]);
- return 0;
- }
-
- if (argv[1][0] == '1') {
- test1(argc, argv);
- } else if (argv[1][0] == '2') {
- test2(argc, argv);
- } else if (argv[1][0] == 'A') {
- testA(argc, argv);
- } else if (argv[1][0] == 'B') {
- testB(argc, argv);
- } else if (argv[1][0] == 'C') {
- testC(argc, argv);
- } else if (argv[1][0] == 'D') {
- testD(argc, argv);
- }
- return 0;
-}