1 | //
|
2 | // Windows versions tested
|
3 | //
|
4 | // Vista Enterprise SP2 32-bit
|
5 | // - ver reports [Version 6.0.6002]
|
6 | // - kernel32.dll product/file versions are 6.0.6002.19381
|
7 | //
|
8 | // Windows 7 Ultimate SP1 32-bit
|
9 | // - ver reports [Version 6.1.7601]
|
10 | // - conhost.exe product/file versions are 6.1.7601.18847
|
11 | // - kernel32.dll product/file versions are 6.1.7601.18847
|
12 | //
|
13 | // Windows Server 2008 R2 Datacenter SP1 64-bit
|
14 | // - ver reports [Version 6.1.7601]
|
15 | // - conhost.exe product/file versions are 6.1.7601.23153
|
16 | // - kernel32.dll product/file versions are 6.1.7601.23153
|
17 | //
|
18 | // Windows 8 Enterprise 32-bit
|
19 | // - ver reports [Version 6.2.9200]
|
20 | // - conhost.exe product/file versions are 6.2.9200.16578
|
21 | // - kernel32.dll product/file versions are 6.2.9200.16859
|
22 | //
|
23 |
|
24 | //
|
25 | // Specific version details on working Server 2008 R2:
|
26 | //
|
27 | // dwMajorVersion = 6
|
28 | // dwMinorVersion = 1
|
29 | // dwBuildNumber = 7601
|
30 | // dwPlatformId = 2
|
31 | // szCSDVersion = Service Pack 1
|
32 | // wServicePackMajor = 1
|
33 | // wServicePackMinor = 0
|
34 | // wSuiteMask = 0x190
|
35 | // wProductType = 0x3
|
36 | //
|
37 | // Specific version details on broken Win7:
|
38 | //
|
39 | // dwMajorVersion = 6
|
40 | // dwMinorVersion = 1
|
41 | // dwBuildNumber = 7601
|
42 | // dwPlatformId = 2
|
43 | // szCSDVersion = Service Pack 1
|
44 | // wServicePackMajor = 1
|
45 | // wServicePackMinor = 0
|
46 | // wSuiteMask = 0x100
|
47 | // wProductType = 0x1
|
48 | //
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | const char *g_prefix = "";
|
57 |
|
58 | static void dumpHandles() {
|
59 | trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x",
|
60 | g_prefix,
|
61 | (long long)GetStdHandle(STD_INPUT_HANDLE),
|
62 | (long long)GetStdHandle(STD_OUTPUT_HANDLE),
|
63 | (long long)GetStdHandle(STD_ERROR_HANDLE));
|
64 | }
|
65 |
|
66 | static const char *successOrFail(BOOL ret) {
|
67 | return ret ? "ok" : "FAILED";
|
68 | }
|
69 |
|
70 | static void startChildInSameConsole(const wchar_t *args, BOOL
|
71 | bInheritHandles=FALSE) {
|
72 | wchar_t program[1024];
|
73 | wchar_t cmdline[1024];
|
74 | GetModuleFileNameW(NULL, program, 1024);
|
75 | swprintf(cmdline, L"\"%ls\" %ls", program, args);
|
76 |
|
77 | STARTUPINFOW sui;
|
78 | PROCESS_INFORMATION pi;
|
79 | memset(&sui, 0, sizeof(sui));
|
80 | memset(&pi, 0, sizeof(pi));
|
81 | sui.cb = sizeof(sui);
|
82 |
|
83 | CreateProcessW(program, cmdline,
|
84 | NULL, NULL,
|
85 | /*bInheritHandles=*/bInheritHandles,
|
86 | /*dwCreationFlags=*/0,
|
87 | NULL, NULL,
|
88 | &sui, &pi);
|
89 | }
|
90 |
|
91 | static void closeHandle(HANDLE h) {
|
92 | trace("%sClosing handle 0x%I64x...", g_prefix, (long long)h);
|
93 | trace("%sClosing handle 0x%I64x... %s", g_prefix, (long long)h, successOrFail(CloseHandle(h)));
|
94 | }
|
95 |
|
96 | static HANDLE createBuffer() {
|
97 |
|
98 | // If sa isn't provided, the handle defaults to not-inheritable.
|
99 | SECURITY_ATTRIBUTES sa = {0};
|
100 | sa.nLength = sizeof(sa);
|
101 | sa.bInheritHandle = TRUE;
|
102 |
|
103 | trace("%sCreating a new buffer...", g_prefix);
|
104 | HANDLE conout = CreateConsoleScreenBuffer(
|
105 | GENERIC_READ | GENERIC_WRITE,
|
106 | FILE_SHARE_READ | FILE_SHARE_WRITE,
|
107 | &sa,
|
108 | CONSOLE_TEXTMODE_BUFFER, NULL);
|
109 |
|
110 | trace("%sCreating a new buffer... 0x%I64x", g_prefix, (long long)conout);
|
111 | return conout;
|
112 | }
|
113 |
|
114 | static HANDLE openConout() {
|
115 |
|
116 | // If sa isn't provided, the handle defaults to not-inheritable.
|
117 | SECURITY_ATTRIBUTES sa = {0};
|
118 | sa.nLength = sizeof(sa);
|
119 | sa.bInheritHandle = TRUE;
|
120 |
|
121 | trace("%sOpening CONOUT...", g_prefix);
|
122 | HANDLE conout = CreateFileW(L"CONOUT$",
|
123 | GENERIC_READ | GENERIC_WRITE,
|
124 | FILE_SHARE_READ | FILE_SHARE_WRITE,
|
125 | &sa,
|
126 | OPEN_EXISTING, 0, NULL);
|
127 | trace("%sOpening CONOUT... 0x%I64x", g_prefix, (long long)conout);
|
128 | return conout;
|
129 | }
|
130 |
|
131 | static void setConsoleActiveScreenBuffer(HANDLE conout) {
|
132 | trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...",
|
133 | g_prefix, (long long)conout);
|
134 | trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s",
|
135 | g_prefix, (long long)conout,
|
136 | successOrFail(SetConsoleActiveScreenBuffer(conout)));
|
137 | }
|
138 |
|
139 | static void writeTest(HANDLE conout, const char *msg) {
|
140 | char writeData[256];
|
141 | sprintf(writeData, "%s%s\n", g_prefix, msg);
|
142 |
|
143 | trace("%sWriting to 0x%I64x: '%s'...",
|
144 | g_prefix, (long long)conout, msg);
|
145 | DWORD actual = 0;
|
146 | BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL);
|
147 | trace("%sWriting to 0x%I64x: '%s'... %s",
|
148 | g_prefix, (long long)conout, msg,
|
149 | successOrFail(ret && actual == strlen(writeData)));
|
150 | }
|
151 |
|
152 | static void writeTest(const char *msg) {
|
153 | writeTest(GetStdHandle(STD_OUTPUT_HANDLE), msg);
|
154 | }
|
155 |
|
156 |
|
157 |
|
158 | ///////////////////////////////////////////////////////////////////////////////
|
159 | // TEST 1 -- create new buffer, activate it, and close the handle. The console
|
160 | // automatically switches the screen buffer back to the original.
|
161 | //
|
162 | // This test passes everywhere.
|
163 | //
|
164 |
|
165 | static void test1(int argc, char *argv[]) {
|
166 | if (!strcmp(argv[1], "1")) {
|
167 | startChildProcess(L"1:child");
|
168 | return;
|
169 | }
|
170 |
|
171 | HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
|
172 | writeTest(origBuffer, "<-- origBuffer -->");
|
173 |
|
174 | HANDLE newBuffer = createBuffer();
|
175 | writeTest(newBuffer, "<-- newBuffer -->");
|
176 | setConsoleActiveScreenBuffer(newBuffer);
|
177 | Sleep(2000);
|
178 |
|
179 | writeTest(origBuffer, "TEST PASSED!");
|
180 |
|
181 | // Closing the handle w/o switching the active screen buffer automatically
|
182 | // switches the console back to the original buffer.
|
183 | closeHandle(newBuffer);
|
184 |
|
185 | while (true) {
|
186 | Sleep(1000);
|
187 | }
|
188 | }
|
189 |
|
190 |
|
191 |
|
192 | ///////////////////////////////////////////////////////////////////////////////
|
193 | // TEST 2 -- Test program that creates and activates newBuffer, starts a child
|
194 | // process, then closes its newBuffer handle. newBuffer remains activated,
|
195 | // because the child keeps it active. (Also see TEST D.)
|
196 | //
|
197 |
|
198 | static void test2(int argc, char *argv[]) {
|
199 | if (!strcmp(argv[1], "2")) {
|
200 | startChildProcess(L"2:parent");
|
201 | return;
|
202 | }
|
203 |
|
204 | if (!strcmp(argv[1], "2:parent")) {
|
205 | g_prefix = "parent: ";
|
206 | dumpHandles();
|
207 | HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
|
208 | writeTest(origBuffer, "<-- origBuffer -->");
|
209 |
|
210 | HANDLE newBuffer = createBuffer();
|
211 | writeTest(newBuffer, "<-- newBuffer -->");
|
212 | setConsoleActiveScreenBuffer(newBuffer);
|
213 |
|
214 | Sleep(1000);
|
215 | writeTest(newBuffer, "bInheritHandles=FALSE:");
|
216 | startChildInSameConsole(L"2:child", FALSE);
|
217 | Sleep(1000);
|
218 | writeTest(newBuffer, "bInheritHandles=TRUE:");
|
219 | startChildInSameConsole(L"2:child", TRUE);
|
220 |
|
221 | Sleep(1000);
|
222 | trace("parent:----");
|
223 |
|
224 | // Close the new buffer. The active screen buffer doesn't automatically
|
225 | // switch back to origBuffer, because the child process has a handle open
|
226 | // to the original buffer.
|
227 | closeHandle(newBuffer);
|
228 |
|
229 | Sleep(600 * 1000);
|
230 | return;
|
231 | }
|
232 |
|
233 | if (!strcmp(argv[1], "2:child")) {
|
234 | g_prefix = "child: ";
|
235 | dumpHandles();
|
236 | // The child's output isn't visible, because it's still writing to
|
237 | // origBuffer.
|
238 | trace("child:----");
|
239 | writeTest("writing to STDOUT");
|
240 |
|
241 | // Handle inheritability is curious. The console handles this program
|
242 | // creates are inheritable, but CreateProcess is called with both
|
243 | // bInheritHandles=TRUE and bInheritHandles=FALSE.
|
244 | //
|
245 | // Vista and Windows 7: bInheritHandles has no effect. The child and
|
246 | // parent processes have the same STDIN/STDOUT/STDERR handles:
|
247 | // 0x3, 0x7, and 0xB. The parent has a 0xF handle for newBuffer.
|
248 | // The child can only write to 0x7, 0xB, and 0xF. Only the writes to
|
249 | // 0xF are visible (i.e. they touch newBuffer).
|
250 | //
|
251 | // Windows 8 or Windows 10 (legacy or non-legacy): the lowest 2 bits of
|
252 | // the HANDLE to WriteConsole seem to be ignored. The new process'
|
253 | // console handles always refer to the buffer that was active when they
|
254 | // started, but the values of the handles depend upon bInheritHandles.
|
255 | // With bInheritHandles=TRUE, the child has the same
|
256 | // STDIN/STDOUT/STDERR/newBuffer handles as the parent, and the three
|
257 | // output handles all work, though their output is all visible. With
|
258 | // bInheritHandles=FALSE, the child has different STDIN/STDOUT/STDERR
|
259 | // handles, and only the new STDOUT/STDERR handles work.
|
260 | //
|
261 | for (unsigned int i = 0x1; i <= 0xB0; ++i) {
|
262 | char msg[256];
|
263 | sprintf(msg, "Write to handle 0x%x", i);
|
264 | HANDLE h = reinterpret_cast<HANDLE>(i);
|
265 | writeTest(h, msg);
|
266 | }
|
267 |
|
268 | Sleep(600 * 1000);
|
269 | return;
|
270 | }
|
271 | }
|
272 |
|
273 |
|
274 |
|
275 | ///////////////////////////////////////////////////////////////////////////////
|
276 | // TEST A -- demonstrate an apparent Windows bug with screen buffers
|
277 | //
|
278 | // Steps:
|
279 | // - The parent starts a child process.
|
280 | // - The child process creates and activates newBuffer
|
281 | // - The parent opens CONOUT$ and writes to it.
|
282 | // - The parent closes CONOUT$.
|
283 | // - At this point, broken Windows reactivates origBuffer.
|
284 | // - The child writes to newBuffer again.
|
285 | // - The child activates origBuffer again, then closes newBuffer.
|
286 | //
|
287 | // Test passes if the message "TEST PASSED!" is visible.
|
288 | // Test commonly fails if conhost.exe crashes.
|
289 | //
|
290 | // Results:
|
291 | // - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
|
292 | // - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
|
293 | // - Windows 8 Enterprise 32-bit: PASS
|
294 | // - Windows 10 64-bit (legacy and non-legacy): PASS
|
295 | //
|
296 |
|
297 | static void testA_parentWork() {
|
298 | // Open an extra CONOUT$ handle so that the HANDLE values in parent and
|
299 | // child don't collide. I think it's OK if they collide, but since we're
|
300 | // trying to track down a Windows bug, it's best to avoid unnecessary
|
301 | // complication.
|
302 | HANDLE dummy = openConout();
|
303 |
|
304 | Sleep(3000);
|
305 |
|
306 | // Step 2: Open CONOUT$ in the parent. This opens the active buffer, which
|
307 | // was just created in the child. It's handle 0x13. Write to it.
|
308 |
|
309 | HANDLE newBuffer = openConout();
|
310 | writeTest(newBuffer, "step2: writing to newBuffer");
|
311 |
|
312 | Sleep(3000);
|
313 |
|
314 | // Step 3: Close handle 0x13. With Windows 7, the console switches back to
|
315 | // origBuffer, and (unless I'm missing something) it shouldn't.
|
316 |
|
317 | closeHandle(newBuffer);
|
318 | }
|
319 |
|
320 | static void testA_childWork() {
|
321 | HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
|
322 |
|
323 | //
|
324 | // Step 1: Create the new screen buffer in the child process and make it
|
325 | // active. (Typically, it's handle 0x0F.)
|
326 | //
|
327 |
|
328 | HANDLE newBuffer = createBuffer();
|
329 |
|
330 | setConsoleActiveScreenBuffer(newBuffer);
|
331 | writeTest(newBuffer, "<-- newBuffer -->");
|
332 |
|
333 | Sleep(9000);
|
334 | trace("child:----");
|
335 |
|
336 | // Step 4: write to the newBuffer again.
|
337 | writeTest(newBuffer, "TEST PASSED!");
|
338 |
|
339 | //
|
340 | // Step 5: Switch back to the original screen buffer and close the new
|
341 | // buffer. The switch call succeeds, but the CloseHandle call freezes for
|
342 | // several seconds, because conhost.exe crashes.
|
343 | //
|
344 | Sleep(3000);
|
345 |
|
346 | setConsoleActiveScreenBuffer(origBuffer);
|
347 | writeTest(origBuffer, "writing to origBuffer");
|
348 |
|
349 | closeHandle(newBuffer);
|
350 |
|
351 | // The console HWND is NULL.
|
352 | trace("child: console HWND=0x%I64x", (long long)GetConsoleWindow());
|
353 |
|
354 | // At this point, the console window has closed, but the parent/child
|
355 | // processes are still running. Calling AllocConsole would fail, but
|
356 | // calling FreeConsole followed by AllocConsole would both succeed, and a
|
357 | // new console would appear.
|
358 | }
|
359 |
|
360 | static void testA(int argc, char *argv[]) {
|
361 |
|
362 | if (!strcmp(argv[1], "A")) {
|
363 | startChildProcess(L"A:parent");
|
364 | return;
|
365 | }
|
366 |
|
367 | if (!strcmp(argv[1], "A:parent")) {
|
368 | g_prefix = "parent: ";
|
369 | trace("parent:----");
|
370 | dumpHandles();
|
371 | writeTest("<-- origBuffer -->");
|
372 | startChildInSameConsole(L"A:child");
|
373 | testA_parentWork();
|
374 | Sleep(120000);
|
375 | return;
|
376 | }
|
377 |
|
378 | if (!strcmp(argv[1], "A:child")) {
|
379 | g_prefix = "child: ";
|
380 | dumpHandles();
|
381 | testA_childWork();
|
382 | Sleep(120000);
|
383 | return;
|
384 | }
|
385 | }
|
386 |
|
387 |
|
388 |
|
389 | ///////////////////////////////////////////////////////////////////////////////
|
390 | // TEST B -- invert TEST A -- also crashes conhost on Windows 7
|
391 | //
|
392 | // Test passes if the message "TEST PASSED!" is visible.
|
393 | // Test commonly fails if conhost.exe crashes.
|
394 | //
|
395 | // Results:
|
396 | // - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
|
397 | // - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
|
398 | // - Windows 8 Enterprise 32-bit: PASS
|
399 | // - Windows 10 64-bit (legacy and non-legacy): PASS
|
400 | //
|
401 |
|
402 | static void testB(int argc, char *argv[]) {
|
403 | if (!strcmp(argv[1], "B")) {
|
404 | startChildProcess(L"B:parent");
|
405 | return;
|
406 | }
|
407 |
|
408 | if (!strcmp(argv[1], "B:parent")) {
|
409 | g_prefix = "parent: ";
|
410 | startChildInSameConsole(L"B:child");
|
411 | writeTest("<-- origBuffer -->");
|
412 | HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
|
413 |
|
414 | //
|
415 | // Step 1: Create the new buffer and make it active.
|
416 | //
|
417 | trace("%s----", g_prefix);
|
418 | HANDLE newBuffer = createBuffer();
|
419 | setConsoleActiveScreenBuffer(newBuffer);
|
420 | writeTest(newBuffer, "<-- newBuffer -->");
|
421 |
|
422 | //
|
423 | // Step 4: Attempt to write again to the new buffer.
|
424 | //
|
425 | Sleep(9000);
|
426 | trace("%s----", g_prefix);
|
427 | writeTest(newBuffer, "TEST PASSED!");
|
428 |
|
429 | //
|
430 | // Step 5: Switch back to the original buffer.
|
431 | //
|
432 | Sleep(3000);
|
433 | trace("%s----", g_prefix);
|
434 | setConsoleActiveScreenBuffer(origBuffer);
|
435 | closeHandle(newBuffer);
|
436 | writeTest(origBuffer, "writing to the initial buffer");
|
437 |
|
438 | Sleep(60000);
|
439 | return;
|
440 | }
|
441 |
|
442 | if (!strcmp(argv[1], "B:child")) {
|
443 | g_prefix = "child: ";
|
444 | Sleep(3000);
|
445 | trace("%s----", g_prefix);
|
446 |
|
447 | //
|
448 | // Step 2: Open the newly active buffer and write to it.
|
449 | //
|
450 | HANDLE newBuffer = openConout();
|
451 | writeTest(newBuffer, "writing to newBuffer");
|
452 |
|
453 | //
|
454 | // Step 3: Close the newly active buffer.
|
455 | //
|
456 | Sleep(3000);
|
457 | closeHandle(newBuffer);
|
458 |
|
459 | Sleep(60000);
|
460 | return;
|
461 | }
|
462 | }
|
463 |
|
464 |
|
465 |
|
466 | ///////////////////////////////////////////////////////////////////////////////
|
467 | // TEST C -- Interleaving open/close of console handles also seems to break on
|
468 | // Windows 7.
|
469 | //
|
470 | // Test:
|
471 | // - child creates and activates newBuf1
|
472 | // - parent opens newBuf1
|
473 | // - child creates and activates newBuf2
|
474 | // - parent opens newBuf2, then closes newBuf1
|
475 | // - child switches back to newBuf1
|
476 | // * At this point, the console starts malfunctioning.
|
477 | // - parent and child close newBuf2
|
478 | // - child closes newBuf1
|
479 | //
|
480 | // Test passes if the message "TEST PASSED!" is visible.
|
481 | // Test commonly fails if conhost.exe crashes.
|
482 | //
|
483 | // Results:
|
484 | // - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
|
485 | // - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
|
486 | // - Windows 8 Enterprise 32-bit: PASS
|
487 | // - Windows 10 64-bit (legacy and non-legacy): PASS
|
488 | //
|
489 |
|
490 | static void testC(int argc, char *argv[]) {
|
491 | if (!strcmp(argv[1], "C")) {
|
492 | startChildProcess(L"C:parent");
|
493 | return;
|
494 | }
|
495 |
|
496 | if (!strcmp(argv[1], "C:parent")) {
|
497 | startChildInSameConsole(L"C:child");
|
498 | writeTest("<-- origBuffer -->");
|
499 | g_prefix = "parent: ";
|
500 |
|
501 | // At time=4, open newBuffer1.
|
502 | Sleep(4000);
|
503 | trace("%s---- t=4", g_prefix);
|
504 | const HANDLE newBuffer1 = openConout();
|
505 |
|
506 | // At time=8, open newBuffer2, and close newBuffer1.
|
507 | Sleep(4000);
|
508 | trace("%s---- t=8", g_prefix);
|
509 | const HANDLE newBuffer2 = openConout();
|
510 | closeHandle(newBuffer1);
|
511 |
|
512 | // At time=25, cleanup of newBuffer2.
|
513 | Sleep(17000);
|
514 | trace("%s---- t=25", g_prefix);
|
515 | closeHandle(newBuffer2);
|
516 |
|
517 | Sleep(240000);
|
518 | return;
|
519 | }
|
520 |
|
521 | if (!strcmp(argv[1], "C:child")) {
|
522 | g_prefix = "child: ";
|
523 |
|
524 | // At time=2, create newBuffer1 and activate it.
|
525 | Sleep(2000);
|
526 | trace("%s---- t=2", g_prefix);
|
527 | const HANDLE newBuffer1 = createBuffer();
|
528 | setConsoleActiveScreenBuffer(newBuffer1);
|
529 | writeTest(newBuffer1, "<-- newBuffer1 -->");
|
530 |
|
531 | // At time=6, create newBuffer2 and activate it.
|
532 | Sleep(4000);
|
533 | trace("%s---- t=6", g_prefix);
|
534 | const HANDLE newBuffer2 = createBuffer();
|
535 | setConsoleActiveScreenBuffer(newBuffer2);
|
536 | writeTest(newBuffer2, "<-- newBuffer2 -->");
|
537 |
|
538 | // At time=10, attempt to switch back to newBuffer1. The parent process
|
539 | // has opened and closed its handle to newBuffer1, so does it still exist?
|
540 | Sleep(4000);
|
541 | trace("%s---- t=10", g_prefix);
|
542 | setConsoleActiveScreenBuffer(newBuffer1);
|
543 | writeTest(newBuffer1, "write to newBuffer1: TEST PASSED!");
|
544 |
|
545 | // At time=25, cleanup of newBuffer2.
|
546 | Sleep(15000);
|
547 | trace("%s---- t=25", g_prefix);
|
548 | closeHandle(newBuffer2);
|
549 |
|
550 | // At time=35, cleanup of newBuffer1. The console should switch to the
|
551 | // initial buffer again.
|
552 | Sleep(10000);
|
553 | trace("%s---- t=35", g_prefix);
|
554 | closeHandle(newBuffer1);
|
555 |
|
556 | Sleep(240000);
|
557 | return;
|
558 | }
|
559 | }
|
560 |
|
561 |
|
562 |
|
563 | ///////////////////////////////////////////////////////////////////////////////
|
564 | // TEST D -- parent creates a new buffer, child launches, writes,
|
565 | // closes it output handle, then parent writes again. (Also see TEST 2.)
|
566 | //
|
567 | // On success, this will appear:
|
568 | //
|
569 | // parent: <-- newBuffer -->
|
570 | // child: writing to newBuffer
|
571 | // parent: TEST PASSED!
|
572 | //
|
573 | // If this appears, it indicates that the child's closing its output handle did
|
574 | // not destroy newBuffer.
|
575 | //
|
576 | // Results:
|
577 | // - Windows 7 Ultimate SP1 32-bit: PASS
|
578 | // - Windows 8 Enterprise 32-bit: PASS
|
579 | // - Windows 10 64-bit (legacy and non-legacy): PASS
|
580 | //
|
581 |
|
582 | static void testD(int argc, char *argv[]) {
|
583 | if (!strcmp(argv[1], "D")) {
|
584 | startChildProcess(L"D:parent");
|
585 | return;
|
586 | }
|
587 |
|
588 | if (!strcmp(argv[1], "D:parent")) {
|
589 | g_prefix = "parent: ";
|
590 | HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
|
591 | writeTest(origBuffer, "<-- origBuffer -->");
|
592 |
|
593 | HANDLE newBuffer = createBuffer();
|
594 | writeTest(newBuffer, "<-- newBuffer -->");
|
595 | setConsoleActiveScreenBuffer(newBuffer);
|
596 |
|
597 | // At t=2, start a child process, explicitly forcing it to use
|
598 | // newBuffer for its standard handles. These calls are apparently
|
599 | // redundant on Windows 8 and up.
|
600 | Sleep(2000);
|
601 | trace("parent:----");
|
602 | trace("parent: starting child process");
|
603 | SetStdHandle(STD_OUTPUT_HANDLE, newBuffer);
|
604 | SetStdHandle(STD_ERROR_HANDLE, newBuffer);
|
605 | startChildInSameConsole(L"D:child");
|
606 | SetStdHandle(STD_OUTPUT_HANDLE, origBuffer);
|
607 | SetStdHandle(STD_ERROR_HANDLE, origBuffer);
|
608 |
|
609 | // At t=6, write again to newBuffer.
|
610 | Sleep(4000);
|
611 | trace("parent:----");
|
612 | writeTest(newBuffer, "TEST PASSED!");
|
613 |
|
614 | // At t=8, close the newBuffer. In earlier versions of windows
|
615 | // (including Server 2008 R2), the console then switches back to
|
616 | // origBuffer. As of Windows 8, it doesn't, because somehow the child
|
617 | // process is keeping the console on newBuffer, even though the child
|
618 | // process closed its STDIN/STDOUT/STDERR handles. Killing the child
|
619 | // process by hand after the test finishes *does* force the console
|
620 | // back to origBuffer.
|
621 | Sleep(2000);
|
622 | closeHandle(newBuffer);
|
623 |
|
624 | Sleep(120000);
|
625 | return;
|
626 | }
|
627 |
|
628 | if (!strcmp(argv[1], "D:child")) {
|
629 | g_prefix = "child: ";
|
630 | // At t=2, the child starts.
|
631 | trace("child:----");
|
632 | dumpHandles();
|
633 | writeTest("writing to newBuffer");
|
634 |
|
635 | // At t=4, the child explicitly closes its handle.
|
636 | Sleep(2000);
|
637 | trace("child:----");
|
638 | if (GetStdHandle(STD_ERROR_HANDLE) != GetStdHandle(STD_OUTPUT_HANDLE)) {
|
639 | closeHandle(GetStdHandle(STD_ERROR_HANDLE));
|
640 | }
|
641 | closeHandle(GetStdHandle(STD_OUTPUT_HANDLE));
|
642 | closeHandle(GetStdHandle(STD_INPUT_HANDLE));
|
643 |
|
644 | Sleep(120000);
|
645 | return;
|
646 | }
|
647 | }
|
648 |
|
649 |
|
650 |
|
651 | int main(int argc, char *argv[]) {
|
652 | if (argc == 1) {
|
653 | printf("USAGE: %s testnum\n", argv[0]);
|
654 | return 0;
|
655 | }
|
656 |
|
657 | if (argv[1][0] == '1') {
|
658 | test1(argc, argv);
|
659 | } else if (argv[1][0] == '2') {
|
660 | test2(argc, argv);
|
661 | } else if (argv[1][0] == 'A') {
|
662 | testA(argc, argv);
|
663 | } else if (argv[1][0] == 'B') {
|
664 | testB(argc, argv);
|
665 | } else if (argv[1][0] == 'C') {
|
666 | testC(argc, argv);
|
667 | } else if (argv[1][0] == 'D') {
|
668 | testD(argc, argv);
|
669 | }
|
670 | return 0;
|
671 | }
|