673 lines
20 KiB
C++
673 lines
20 KiB
C++
|
//
|
||
|
// 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"
|
||
|
#include "../shared/DebugClient.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;
|
||
|
}
|