├── .gitattributes ├── README.md └── src ├── .gitignore ├── HandleTests ├── CreateProcess.cc ├── CreateProcess_Detached.cc ├── CreateProcess_Duplicate.cc ├── CreateProcess_Duplicate_PseudoHandleBug.cc ├── CreateProcess_Duplicate_XPPipeBug.cc ├── CreateProcess_InheritAllHandles.cc ├── CreateProcess_InheritList.cc ├── CreateProcess_NewConsole.cc ├── CreateProcess_UseStdHandles.cc ├── MiscTests.cc ├── Modern.cc ├── Traditional.cc ├── Win7_Conout_Crash.cc └── main.cc ├── LICENSE ├── Makefile ├── Test_GetConsoleTitleW.cc ├── Win7Bug_InheritHandles.cc ├── Win7Bug_RaceCondition.cc ├── configure ├── harness ├── Command.h ├── Event.cc ├── Event.h ├── FixedSizeString.h ├── NtHandleQuery.cc ├── NtHandleQuery.h ├── OsVersion.h ├── RemoteHandle.cc ├── RemoteHandle.h ├── RemoteWorker.cc ├── RemoteWorker.h ├── ShmemParcel.cc ├── ShmemParcel.h ├── Spawn.cc ├── Spawn.h ├── TestCommon.h ├── TestUtil.cc ├── TestUtil.h ├── UnicodeConversions.cc ├── UnicodeConversions.h ├── Util.cc ├── Util.h ├── WorkerProgram.cc └── pch.h ├── manifest.xml ├── shared ├── DebugClient.cc ├── DebugClient.h ├── OsModule.h ├── WinptyAssert.cc ├── WinptyAssert.h ├── c99_snprintf.h ├── winpty_wcsnlen.cc └── winpty_wcsnlen.h └── tests.mk /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.cc text 3 | *.h text 4 | *.md text 5 | *.xml text 6 | .gitignore text 7 | .gitattributes text 8 | Makefile text 9 | configure text 10 | 11 | configure eol=lf 12 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /src/HandleTests/CreateProcess.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | REGISTER(Test_CreateProcess_ModeCombos, always); 4 | static void Test_CreateProcess_ModeCombos() { 5 | // It is often unclear how (or whether) various combinations of 6 | // CreateProcess parameters work when combined. Try to test the ambiguous 7 | // combinations. 8 | 9 | SpawnFailure failure; 10 | 11 | { 12 | // CREATE_NEW_CONSOLE | DETACHED_PROCESS ==> call fails 13 | Worker p; 14 | auto c = p.tryChild({ false, CREATE_NEW_CONSOLE | DETACHED_PROCESS }, &failure); 15 | CHECK(!c.valid()); 16 | CHECK_EQ(failure.kind, SpawnFailure::CreateProcess); 17 | CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER); 18 | } 19 | { 20 | // CREATE_NO_WINDOW | CREATE_NEW_CONSOLE ==> CREATE_NEW_CONSOLE dominates 21 | Worker p; 22 | auto c = p.tryChild({ false, CREATE_NO_WINDOW | CREATE_NEW_CONSOLE }, &failure); 23 | CHECK(c.valid()); 24 | CHECK(c.consoleWindow() != nullptr); 25 | CHECK(IsWindowVisible(c.consoleWindow())); 26 | } 27 | { 28 | // CREATE_NO_WINDOW | DETACHED_PROCESS ==> DETACHED_PROCESS dominates 29 | Worker p; 30 | auto c = p.tryChild({ false, CREATE_NO_WINDOW | DETACHED_PROCESS }, &failure); 31 | CHECK(c.valid()); 32 | CHECK_EQ(c.newBuffer().value(), INVALID_HANDLE_VALUE); 33 | } 34 | } 35 | 36 | REGISTER(Test_CreateProcess_STARTUPINFOEX, isAtLeastVista); 37 | static void Test_CreateProcess_STARTUPINFOEX() { 38 | // STARTUPINFOEX tests. 39 | 40 | Worker p; 41 | SpawnFailure failure; 42 | auto pipe1 = newPipe(p, true); 43 | auto ph1 = std::get<0>(pipe1); 44 | auto ph2 = std::get<1>(pipe1); 45 | 46 | auto testSetup = [&](SpawnParams sp, size_t cb, HANDLE inherit) { 47 | sp.sui.cb = cb; 48 | sp.inheritCount = 1; 49 | sp.inheritList = { inherit }; 50 | return p.tryChild(sp, &failure); 51 | }; 52 | 53 | { 54 | // The STARTUPINFOEX parameter is ignored if 55 | // EXTENDED_STARTUPINFO_PRESENT isn't present. 56 | auto c = testSetup({true}, sizeof(STARTUPINFOEXW), ph1.value()); 57 | CHECK(c.valid()); 58 | auto ch2 = Handle::invent(ph2.value(), c); 59 | // i.e. ph2 was inherited, because ch2 identifies the same thing. 60 | CHECK(compareObjectHandles(ph2, ch2)); 61 | } 62 | { 63 | // If EXTENDED_STARTUPINFO_PRESENT is specified, but the cb value 64 | // is wrong, the API call fails. 65 | auto c = testSetup({true, EXTENDED_STARTUPINFO_PRESENT}, 66 | sizeof(STARTUPINFOW), ph1.value()); 67 | CHECK(!c.valid()); 68 | CHECK_EQ(failure.kind, SpawnFailure::CreateProcess); 69 | CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER); 70 | } 71 | } 72 | 73 | REGISTER(Test_CreateNoWindow_HiddenVsNothing, always); 74 | static void Test_CreateNoWindow_HiddenVsNothing() { 75 | 76 | Worker p; 77 | auto c = p.child({ false, CREATE_NO_WINDOW }); 78 | 79 | if (isAtLeastWin7()) { 80 | // As of Windows 7, GetConsoleWindow returns NULL. 81 | CHECK(c.consoleWindow() == nullptr); 82 | } else { 83 | // On earlier operating systems, GetConsoleWindow returns a handle 84 | // to an invisible window. 85 | CHECK(c.consoleWindow() != nullptr); 86 | CHECK(!IsWindowVisible(c.consoleWindow())); 87 | } 88 | } 89 | 90 | // MSDN's CreateProcess page currently has this note in it: 91 | // 92 | // Important The caller is responsible for ensuring that the standard 93 | // handle fields in STARTUPINFO contain valid handle values. These fields 94 | // are copied unchanged to the child process without validation, even when 95 | // the dwFlags member specifies STARTF_USESTDHANDLES. Incorrect values can 96 | // cause the child process to misbehave or crash. Use the Application 97 | // Verifier runtime verification tool to detect invalid handles. 98 | // 99 | // XXX: The word "even" here sticks out. Verify that the standard handle 100 | // fields in STARTUPINFO are ignored when STARTF_USESTDHANDLES is not 101 | // specified. 102 | -------------------------------------------------------------------------------- /src/HandleTests/CreateProcess_Detached.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Test CreateProcess called with dwCreationFlags containing DETACHED_PROCESS. 4 | 5 | // This macro will output nicer line information than a function if it fails. 6 | #define CHECK_NULL(proc) \ 7 | do { \ 8 | CHECK(handleInts(stdHandles(proc)) == \ 9 | (std::vector {0,0,0})); \ 10 | } while(0) 11 | 12 | REGISTER(Test_CreateProcess_Detached, always); 13 | static void Test_CreateProcess_Detached() { 14 | { 15 | Worker p; 16 | auto c1 = p.child({ true, DETACHED_PROCESS }); 17 | CHECK_NULL(c1); 18 | auto c2 = p.child({ false, DETACHED_PROCESS }); 19 | CHECK_NULL(c2); 20 | } 21 | { 22 | Worker p; 23 | auto c = p.child({ true, DETACHED_PROCESS, { 24 | p.getStdin(), 25 | p.getStdout(), 26 | p.getStderr(), 27 | }}); 28 | CHECK(handleValues(stdHandles(c)) == handleValues(stdHandles(p))); 29 | } 30 | { 31 | Worker p; 32 | auto c = p.child({ false, DETACHED_PROCESS, { 33 | p.getStdin(), 34 | p.getStdout(), 35 | p.getStderr(), 36 | }}); 37 | if (isTraditionalConio()) { 38 | CHECK(handleValues(stdHandles(c)) == handleValues(stdHandles(p))); 39 | } else{ 40 | CHECK_NULL(c); 41 | } 42 | } 43 | { 44 | Worker p({ false, DETACHED_PROCESS }); 45 | auto pipe = newPipe(p, true); 46 | std::get<0>(pipe).setStdin(); 47 | std::get<1>(pipe).setStdout().setStderr(); 48 | 49 | { 50 | auto c1 = p.child({ true, DETACHED_PROCESS }); 51 | CHECK_NULL(c1); 52 | auto c2 = p.child({ false, DETACHED_PROCESS }); 53 | CHECK_NULL(c2); 54 | } 55 | { 56 | // The worker p2 was started with STARTF_USESTDHANDLES and with 57 | // standard handles referring to a pipe. Nevertheless, its 58 | // children's standard handles are NULL. 59 | auto p2 = p.child({ true, DETACHED_PROCESS, stdHandles(p) }); 60 | auto c1 = p2.child({ true, DETACHED_PROCESS }); 61 | CHECK_NULL(c1); 62 | auto c2 = p2.child({ false, DETACHED_PROCESS }); 63 | CHECK_NULL(c2); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/HandleTests/CreateProcess_Duplicate.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // If CreateProcess is called with these parameters: 4 | // - bInheritHandles=FALSE 5 | // - STARTF_USESTDHANDLES is not specified 6 | // - the "CreationConsoleMode" is Inherit (see console-handles.md) 7 | // then Windows duplicates each of STDIN/STDOUT/STDERR to the child. 8 | // 9 | // There are variations between OS releases, especially with regards to 10 | // how console handles work. 11 | 12 | // This handle duplication seems to be broken in WOW64 mode. It affects 13 | // at least: 14 | // - Windows 7 SP1 15 | // For some reason, the problem apparently only affects the client operating 16 | // system, not the server OS. 17 | // 18 | // Export this function to the other duplicate tests. 19 | bool brokenDuplicationInWow64() { 20 | return isWin7() && isWorkstation() && isWow64(); 21 | } 22 | 23 | namespace { 24 | 25 | static bool handlesAreNull(Worker &p) { 26 | return handleInts(stdHandles(p)) == std::vector {0, 0, 0}; 27 | } 28 | 29 | static std::string testMessage(bool isNull) { 30 | return isNull ? "BUG(dup->NULL)" : "OK(dup)"; 31 | } 32 | 33 | template 34 | void Test_CreateProcess_Duplicate_Impl(T makeChild) { 35 | printTestName(__FUNCTION__); 36 | 37 | { 38 | // An inheritable pipe is still inherited. 39 | Worker p; 40 | auto pipe = newPipe(p, true); 41 | auto wh = std::get<1>(pipe).setStdin().setStdout().setStderr(); 42 | CHECK(wh.inheritable()); 43 | auto c = makeChild(p, { false }); 44 | 45 | const auto expect = testMessage(brokenDuplicationInWow64()); 46 | const auto actual = testMessage(handlesAreNull(c)); 47 | std::cout << __FUNCTION__ << ": expect: " << expect << std::endl; 48 | std::cout << __FUNCTION__ << ": actual: " << actual << std::endl; 49 | CHECK_EQ(actual, expect); 50 | 51 | if (c.getStdout().value() != nullptr) { 52 | { 53 | ObjectSnap snap; 54 | CHECK(snap.eq({ c.getStdin(), c.getStdout(), c.getStderr(), wh })); 55 | } 56 | for (auto h : stdHandles(c)) { 57 | CHECK(h.tryFlags()); 58 | if (!h.tryFlags()) { 59 | continue; 60 | } 61 | auto inheritMessage = [](bool inheritable) { 62 | return inheritable 63 | ? "OK(inherit)" 64 | : "BAD(dup->non-inheritable)"; 65 | }; 66 | const std::string expect = inheritMessage(isAtLeastVista()); 67 | const std::string actual = inheritMessage(h.inheritable()); 68 | if (expect == actual && isAtLeastVista()) { 69 | continue; // We'll just stay silent in this case. 70 | } 71 | std::cout << __FUNCTION__ << ": expect: " << expect << std::endl; 72 | std::cout << __FUNCTION__ << ": actual: " << actual << std::endl; 73 | CHECK_EQ(actual, expect); 74 | } 75 | } 76 | } 77 | { 78 | // A non-inheritable pipe is still inherited. 79 | Worker p; 80 | auto pipe = newPipe(p, false); 81 | auto wh = std::get<1>(pipe).setStdin().setStdout().setStderr(); 82 | auto c = makeChild(p, { false }); 83 | 84 | const auto expect = testMessage(brokenDuplicationInWow64()); 85 | const auto actual = testMessage(handlesAreNull(c)); 86 | std::cout << __FUNCTION__ << ": expect: " << expect << std::endl; 87 | std::cout << __FUNCTION__ << ": actual: " << actual << std::endl; 88 | CHECK_EQ(actual, expect); 89 | 90 | if (c.getStdout().value() != nullptr) { 91 | { 92 | ObjectSnap snap; 93 | CHECK(snap.eq({ c.getStdin(), c.getStdout(), c.getStderr(), wh })); 94 | } 95 | // CreateProcess makes separate handles for stdin/stdout/stderr, 96 | // even though the parent has the same handle for each of them. 97 | CHECK(c.getStdin().value() != c.getStdout().value()); 98 | CHECK(c.getStdout().value() != c.getStderr().value()); 99 | CHECK(c.getStdin().value() != c.getStderr().value()); 100 | for (auto h : stdHandles(c)) { 101 | CHECK(h.tryFlags() && !h.inheritable()); 102 | } 103 | // Calling FreeConsole in the child does not free the duplicated 104 | // handles. 105 | c.detach(); 106 | { 107 | ObjectSnap snap; 108 | CHECK(snap.eq({ c.getStdin(), c.getStdout(), c.getStderr(), wh })); 109 | } 110 | } 111 | } 112 | { 113 | // Bogus values are transformed into zero. 114 | Worker p; 115 | Handle::invent(0x10000ull, p).setStdin().setStdout(); 116 | Handle::invent(0x0ull, p).setStderr(); 117 | auto c = makeChild(p, { false }); 118 | CHECK(handleInts(stdHandles(c)) == (std::vector {0,0,0})); 119 | } 120 | 121 | if (isAtLeastWin8()) { 122 | // On Windows 8 and up, if a standard handle we duplicate just happens 123 | // to be a console handle, that isn't sufficient reason for FreeConsole 124 | // to close it. 125 | Worker p; 126 | auto c = makeChild(p, { false }); 127 | auto ph = stdHandles(p); 128 | auto ch = stdHandles(c); 129 | auto check = [&]() { 130 | ObjectSnap snap; 131 | for (int i = 0; i < 3; ++i) { 132 | CHECK(snap.eq(ph[i], ch[i])); 133 | CHECK(ph[i].tryFlags() && ch[i].tryFlags()); 134 | CHECK_EQ(ph[i].tryFlags() && ph[i].inheritable(), 135 | ch[i].tryFlags() && ch[i].inheritable()); 136 | } 137 | }; 138 | check(); 139 | c.detach(); 140 | check(); 141 | } 142 | 143 | { 144 | // Traditional console-like values are passed through as-is, 145 | // up to 0x0FFFFFFFull. 146 | Worker p; 147 | Handle::invent(0x0FFFFFFFull, p).setStdin(); 148 | Handle::invent(0x10000003ull, p).setStdout(); 149 | Handle::invent(0x00000003ull, p).setStderr(); 150 | auto c = makeChild(p, { false }); 151 | if (isAtLeastWin8()) { 152 | // These values are invalid on Windows 8 and turned into NULL. 153 | CHECK(handleInts(stdHandles(c)) == 154 | (std::vector { 0, 0, 0 })); 155 | } else { 156 | CHECK(handleInts(stdHandles(c)) == 157 | (std::vector { 0x0FFFFFFFull, 0, 3 })); 158 | } 159 | } 160 | 161 | { 162 | // Test setting STDIN/STDOUT/STDERR to non-inheritable console handles. 163 | // 164 | // Handle duplication does not apply to traditional console handles, 165 | // and a console handle is inherited if and only if it is inheritable. 166 | // 167 | // On new releases, this will Just Work. 168 | // 169 | Worker p; 170 | p.getStdout().setFirstChar('A'); 171 | p.openConin(false).setStdin(); 172 | p.newBuffer(false, 'B').setStdout().setStderr(); 173 | auto c = makeChild(p, { false }); 174 | 175 | if (!isAtLeastWin8()) { 176 | CHECK(handleValues(stdHandles(p)) == handleValues(stdHandles(c))); 177 | CHECK(!c.getStdin().tryFlags()); 178 | CHECK(!c.getStdout().tryFlags()); 179 | CHECK(!c.getStderr().tryFlags()); 180 | } else { 181 | // In Win8, a console handle works like all other handles. 182 | CHECK_EQ(c.getStdout().firstChar(), 'B'); 183 | ObjectSnap snap; 184 | CHECK(snap.eq({ p.getStdout(), p.getStderr(), 185 | c.getStdout(), c.getStderr() })); 186 | CHECK(!c.getStdout().inheritable()); 187 | CHECK(!c.getStderr().inheritable()); 188 | } 189 | } 190 | } 191 | 192 | } // anonymous namespace 193 | 194 | REGISTER(Test_CreateProcess_Duplicate, always); 195 | static void Test_CreateProcess_Duplicate() { 196 | Test_CreateProcess_Duplicate_Impl([](Worker &p, SpawnParams sp) { 197 | return p.child(sp); 198 | }); 199 | if (isModernConio()) { 200 | // With modern console I/O, calling CreateProcess with these 201 | // parameters also duplicates standard handles: 202 | // - bInheritHandles=TRUE 203 | // - STARTF_USESTDHANDLES not specified 204 | // - an inherit list (PROC_THREAD_ATTRIBUTE_HANDLE_LIST) is specified 205 | Test_CreateProcess_Duplicate_Impl([](Worker &p, SpawnParams sp) { 206 | return childWithDummyInheritList(p, sp, false); 207 | }); 208 | Test_CreateProcess_Duplicate_Impl([](Worker &p, SpawnParams sp) { 209 | return childWithDummyInheritList(p, sp, true); 210 | }); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/HandleTests/CreateProcess_Duplicate_PseudoHandleBug.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // With CreateProcess's default handle duplication behavior, the 4 | // GetCurrentProcess() psuedo-handle (i.e. INVALID_HANDLE_VALUE) is translated 5 | // to a real handle value for the child process. It is a handle to the parent 6 | // process. Naturally, this was unintended behavior, and as of Windows 8.1, 7 | // the handle is instead translated to NULL. On some older operating systems, 8 | // the WOW64 mode also translates it to NULL. 9 | 10 | const std::string bugParentProc = "BUG(parent-proc)"; 11 | const std::string okInvalid = "OK(INVALID)"; 12 | const std::string okNull = "OK(NULL)"; 13 | 14 | static std::string determineChildStdout(Worker &c, Worker &p) { 15 | if (c.getStdout().value() == nullptr) { 16 | return okNull; 17 | } else if (c.getStdout().value() == INVALID_HANDLE_VALUE) { 18 | return okInvalid; 19 | } else { 20 | auto handleToPInP = Handle::dup(p.processHandle(), p); 21 | CHECK(compareObjectHandles(c.getStdout(), handleToPInP)); 22 | return bugParentProc; 23 | } 24 | } 25 | 26 | REGISTER(Test_CreateProcess_Duplicate_PseudoHandleBug, always); 27 | static void Test_CreateProcess_Duplicate_PseudoHandleBug() { 28 | Worker p; 29 | Handle::invent(GetCurrentProcess(), p).setStdout(); 30 | auto c = p.child({ false }); 31 | 32 | const std::string expect = 33 | (isAtLeastWin8_1() || (isAtLeastVista() && isWow64())) 34 | ? okNull 35 | : bugParentProc; 36 | 37 | const std::string actual = determineChildStdout(c, p); 38 | 39 | trace("%s: actual: %s", __FUNCTION__, actual.c_str()); 40 | std::cout << __FUNCTION__ << ": expect: " << expect << std::endl; 41 | std::cout << __FUNCTION__ << ": actual: " << actual << std::endl; 42 | CHECK_EQ(actual, expect); 43 | } 44 | 45 | REGISTER(Test_CreateProcess_Duplicate_PseudoHandleBug_IL, isAtLeastVista); 46 | static void Test_CreateProcess_Duplicate_PseudoHandleBug_IL() { 47 | // As above, but use an inherit list. With an inherit list, standard 48 | // handles are duplicated, but only with Windows 8 and up. 49 | for (int useDummyPipe = 0; useDummyPipe <= 1; ++useDummyPipe) { 50 | Worker p; 51 | Handle::invent(INVALID_HANDLE_VALUE, p).setStdout(); 52 | auto c = childWithDummyInheritList(p, {}, useDummyPipe != 0); 53 | 54 | // Figure out what we expect to see. 55 | std::string expect; 56 | if (isAtLeastWin8_1()) { 57 | // Windows 8.1 turns INVALID_HANDLE_VALUE into NULL. 58 | expect = okNull; 59 | } else if (isAtLeastWin8()) { 60 | // Windows 8 tries to duplicate the handle. WOW64 seems to be 61 | // OK, though. 62 | if (isWow64()) { 63 | expect = okNull; 64 | } else { 65 | expect = bugParentProc; 66 | } 67 | } else { 68 | // Prior to Windows 8, duplication doesn't occur in this case, so 69 | // the bug isn't relevant. We run the test anyway, but it's less 70 | // interesting. 71 | expect = okInvalid; 72 | } 73 | 74 | const std::string actual = determineChildStdout(c, p); 75 | 76 | trace("%s: actual: %s", __FUNCTION__, actual.c_str()); 77 | std::cout << __FUNCTION__ << ": expect: " << expect << std::endl; 78 | std::cout << __FUNCTION__ << ": actual: " << actual << std::endl; 79 | CHECK_EQ(actual, expect); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/HandleTests/CreateProcess_Duplicate_XPPipeBug.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Windows XP bug: default inheritance doesn't work with the read end 4 | // of a pipe, even if it's inheritable. It works with the write end. 5 | 6 | bool brokenDuplicationInWow64(); 7 | 8 | REGISTER(Test_CreateProcess_Duplicate_XPPipeBug, always); 9 | static void Test_CreateProcess_Duplicate_XPPipeBug() { 10 | auto check = [](Worker &proc, Handle correct, bool expectNull) { 11 | CHECK_EQ((proc.getStdin().value() == nullptr), expectNull); 12 | CHECK_EQ((proc.getStdout().value() == nullptr), expectNull); 13 | CHECK_EQ((proc.getStderr().value() == nullptr), expectNull); 14 | if (proc.getStdout().value() != nullptr) { 15 | ObjectSnap snap; 16 | CHECK(snap.eq({ 17 | proc.getStdin(), proc.getStdout(), proc.getStderr(), correct 18 | })); 19 | } 20 | }; 21 | 22 | Worker p; 23 | 24 | auto pipe = newPipe(p, false); 25 | auto rh = std::get<0>(pipe).setStdin().setStdout().setStderr(); 26 | auto c1 = p.child({ false }); 27 | check(c1, rh, !isAtLeastVista() || brokenDuplicationInWow64()); 28 | 29 | // Marking the handle itself inheritable makes no difference. 30 | rh.setInheritable(true); 31 | auto c2 = p.child({ false }); 32 | check(c2, rh, !isAtLeastVista() || brokenDuplicationInWow64()); 33 | 34 | // If we enter bInheritHandles=TRUE mode, it works. 35 | auto c3 = p.child({ true }); 36 | check(c3, rh, false); 37 | 38 | // Using STARTF_USESTDHANDLES works too. 39 | Handle::invent(nullptr, p).setStdin().setStdout().setStderr(); 40 | auto c4 = p.child({ true, 0, { rh, rh, rh }}); 41 | check(c4, rh, false); 42 | 43 | // Also test the write end of the pipe. 44 | auto wh = std::get<1>(pipe).setStdin().setStdout().setStderr(); 45 | auto c5 = p.child({ false }); 46 | check(c5, wh, brokenDuplicationInWow64()); 47 | } 48 | -------------------------------------------------------------------------------- /src/HandleTests/CreateProcess_InheritAllHandles.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 4 | // Test CreateProcess when called with these parameters: 5 | // - STARTF_USESTDHANDLES is not specified 6 | // - bInheritHandles=TRUE 7 | // - CreationConsoleMode=Inherit 8 | // 9 | 10 | REGISTER(Test_CreateProcess_InheritAllHandles, always); 11 | static void Test_CreateProcess_InheritAllHandles() { 12 | auto &hv = handleValues; 13 | 14 | { 15 | // Simple case: the standard handles are left as-is. 16 | Worker p; 17 | auto pipe = newPipe(p, true); 18 | std::get<0>(pipe).setStdin(); 19 | std::get<1>(pipe).setStdout().setStderr(); 20 | auto c = p.child({ true }); 21 | CHECK(hv(stdHandles(c)) == hv(stdHandles(p))); 22 | } 23 | 24 | { 25 | // We can pass arbitrary values through. 26 | Worker p; 27 | Handle::invent(0x0ull, p).setStdin(); 28 | Handle::invent(0x10000ull, p).setStdout(); 29 | Handle::invent(INVALID_HANDLE_VALUE, p).setStderr(); 30 | auto c = p.child({ true }); 31 | CHECK(hv(stdHandles(c)) == hv(stdHandles(p))); 32 | } 33 | 34 | { 35 | // Passing through a non-inheritable handle produces an invalid child 36 | // handle. 37 | Worker p; 38 | p.openConin(false).setStdin(); 39 | p.openConout(false).setStdout().setStderr(); 40 | auto c = p.child({ true }); 41 | CHECK(hv(stdHandles(c)) == hv(stdHandles(p))); 42 | if (isTraditionalConio()) { 43 | CHECK(!c.getStdin().tryFlags()); 44 | CHECK(!c.getStdout().tryFlags()); 45 | CHECK(!c.getStderr().tryFlags()); 46 | } else { 47 | ObjectSnap snap; 48 | CHECK(!snap.eq(p.getStdin(), c.getStdin())); 49 | CHECK(!snap.eq(p.getStdout(), c.getStdout())); 50 | CHECK(!snap.eq(p.getStderr(), c.getStderr())); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/HandleTests/CreateProcess_InheritList.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 4 | // Test CreateProcess, using PROC_THREAD_ATTRIBUTE_HANDLE_LIST to restrict the 5 | // inherited handles. 6 | // 7 | // Ordinarily, standard handles are copied as-is. 8 | // 9 | // On Windows 8 and later, if a PROC_THREAD_ATTRIBUTE_HANDLE_LIST list is used, 10 | // then the standard handles are duplicated instead. 11 | // 12 | 13 | REGISTER(Test_CreateProcess_InheritList, isAtLeastVista); 14 | static void Test_CreateProcess_InheritList() { 15 | // Specifically test inherit lists. 16 | 17 | SpawnFailure failure; 18 | 19 | auto testSetup = [&](Worker &proc, 20 | SpawnParams sp, 21 | std::initializer_list inheritList) { 22 | sp.dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT; 23 | sp.sui.cb = sizeof(STARTUPINFOEXW); 24 | sp.inheritCount = inheritList.size(); 25 | std::copy(inheritList.begin(), inheritList.end(), 26 | sp.inheritList.begin()); 27 | return proc.tryChild(sp, &failure); 28 | }; 29 | 30 | Worker p; 31 | auto pipe1 = newPipe(p, true); 32 | auto ph1 = std::get<0>(pipe1); 33 | auto ph2 = std::get<1>(pipe1); 34 | 35 | auto pipe2 = newPipe(p, true); 36 | auto ph3 = std::get<0>(pipe2); 37 | auto ph4 = std::get<1>(pipe2); 38 | 39 | auto phNI = ph1.dup(false); 40 | 41 | // Add an extra console handle so we can verify that a child's console 42 | // handles didn't revert to the original default, but were inherited. 43 | p.openConout(true); 44 | 45 | auto testSetupStdHandles = [&](SpawnParams sp) { 46 | const auto in = sp.sui.hStdInput; 47 | const auto out = sp.sui.hStdOutput; 48 | const auto err = sp.sui.hStdError; 49 | sp.dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT; 50 | sp.sui.cb = sizeof(STARTUPINFOEXW); 51 | // This test case isn't interested in what 52 | // PROC_THREAD_ATTRIBUTE_HANDLE_LIST does when there are duplicate 53 | // handles in its list. 54 | ASSERT(in != out && out != err && in != err); 55 | sp.inheritCount = 3; 56 | sp.inheritList = { in, out, err }; 57 | return p.tryChild(sp, &failure); 58 | }; 59 | 60 | auto ch1 = [&](RemoteWorker &c) { return Handle::invent(ph1.value(), c); }; 61 | auto ch2 = [&](RemoteWorker &c) { return Handle::invent(ph2.value(), c); }; 62 | auto ch3 = [&](RemoteWorker &c) { return Handle::invent(ph3.value(), c); }; 63 | auto ch4 = [&](RemoteWorker &c) { return Handle::invent(ph4.value(), c); }; 64 | 65 | { 66 | // Use PROC_THREAD_ATTRIBUTE_HANDLE_LIST correctly. 67 | auto c = testSetup(p, {true}, {ph1.value()}); 68 | CHECK(c.valid()); 69 | // i.e. ph1 was inherited, because ch1 identifies the same thing. 70 | // ph2 was not inherited, because it wasn't listed. 71 | ObjectSnap snap; 72 | CHECK(snap.eq(ph1, ch1(c))); 73 | CHECK(!snap.eq(ph2, ch2(c))); 74 | 75 | if (!isAtLeastWin8()) { 76 | // The traditional console handles were all inherited, but they're 77 | // also the standard handles, so maybe that's an exception. We'll 78 | // test more aggressively below. 79 | CHECK(handleValues(c.scanForConsoleHandles()) == 80 | handleValues(p.scanForConsoleHandles())); 81 | } 82 | } 83 | { 84 | // UpdateProcThreadAttribute fails if the buffer size is zero. 85 | auto c = testSetup(p, {true}, {}); 86 | CHECK(!c.valid()); 87 | CHECK_EQ(failure.kind, SpawnFailure::UpdateProcThreadAttribute); 88 | CHECK_EQ(failure.errCode, (DWORD)ERROR_BAD_LENGTH); 89 | } 90 | { 91 | // Attempting to inherit the GetCurrentProcess pseudo-handle also 92 | // fails. (The MSDN docs point out that using GetCurrentProcess here 93 | // will fail.) 94 | auto c = testSetup(p, {true}, {GetCurrentProcess()}); 95 | CHECK(!c.valid()); 96 | CHECK_EQ(failure.kind, SpawnFailure::CreateProcess); 97 | CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER); 98 | } 99 | { 100 | // CreateProcess fails if the inherit list has a non-inheritable handle 101 | // in it. (STARTF_USESTDHANDLES not set.) 102 | auto c1 = testSetup(p, {true}, {phNI.value()}); 103 | CHECK(!c1.valid()); 104 | CHECK_EQ(failure.kind, SpawnFailure::CreateProcess); 105 | CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER); 106 | } 107 | { 108 | // CreateProcess fails if the inherit list has a non-inheritable handle 109 | // in it. (STARTF_USESTDHANDLES set.) 110 | auto c = testSetup(p, {true, 0, {phNI, phNI, phNI}}, {phNI.value()}); 111 | CHECK(!c.valid()); 112 | CHECK_EQ(failure.kind, SpawnFailure::CreateProcess); 113 | CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER); 114 | } 115 | { 116 | // If bInheritHandles=FALSE and PROC_THREAD_ATTRIBUTE_HANDLE_LIST are 117 | // combined, the API call fails. (STARTF_USESTDHANDLES not set.) 118 | auto c = testSetup(p, {false}, {ph1.value()}); 119 | CHECK(!c.valid()); 120 | CHECK_EQ(failure.kind, SpawnFailure::CreateProcess); 121 | CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER); 122 | } 123 | { 124 | // If bInheritHandles=FALSE and PROC_THREAD_ATTRIBUTE_HANDLE_LIST are 125 | // combined, the API call fails. (STARTF_USESTDHANDLES set.) 126 | auto c = testSetupStdHandles({false, 0, {ph1, ph2, ph4}}); 127 | CHECK(!c.valid()); 128 | CHECK_EQ(failure.kind, SpawnFailure::CreateProcess); 129 | CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER); 130 | } 131 | 132 | if (!isAtLeastWin8()) { 133 | // Attempt to restrict inheritance to just one of the three open 134 | // traditional console handles. 135 | auto c = testSetupStdHandles({true, 0, {ph1, ph2, p.getStderr()}}); 136 | if (isWin7()) { 137 | // On Windows 7, the CreateProcess call fails with a strange 138 | // error. 139 | CHECK(!c.valid()); 140 | CHECK_EQ(failure.kind, SpawnFailure::CreateProcess); 141 | CHECK_EQ(failure.errCode, (DWORD)ERROR_NO_SYSTEM_RESOURCES); 142 | } else { 143 | // On Vista, the CreateProcess call succeeds, but handle 144 | // inheritance is broken. All of the console handles are 145 | // inherited, not just the error screen buffer that was listed. 146 | // None of the pipe handles were inherited, even though two were 147 | // listed. 148 | c.dumpConsoleHandles(); 149 | CHECK(handleValues(c.scanForConsoleHandles()) == 150 | handleValues(p.scanForConsoleHandles())); 151 | { 152 | ObjectSnap snap; 153 | CHECK(!snap.eq(ph1, ch1(c))); 154 | CHECK(!snap.eq(ph2, ch2(c))); 155 | CHECK(!snap.eq(ph3, ch3(c))); 156 | CHECK(!snap.eq(ph4, ch4(c))); 157 | } 158 | } 159 | } 160 | 161 | if (!isAtLeastWin8()) { 162 | // Make a final valiant effort to find a 163 | // PROC_THREAD_ATTRIBUTE_HANDLE_LIST and console handle interaction. 164 | // We'll set all the standard handles to pipes. Nevertheless, all 165 | // console handles are inherited. 166 | auto c = testSetupStdHandles({true, 0, {ph1, ph2, ph4}}); 167 | CHECK(c.valid()); 168 | CHECK(handleValues(c.scanForConsoleHandles()) == 169 | handleValues(p.scanForConsoleHandles())); 170 | } 171 | 172 | // 173 | // What does it mean if the inherit list has a NULL handle in it? 174 | // 175 | 176 | { 177 | // CreateProcess apparently succeeds if the inherit list has a single 178 | // NULL in it. Inheritable handles unrelated to standard handles are 179 | // not inherited. 180 | auto c = testSetup(p, {true}, {NULL}); 181 | CHECK(c.valid()); 182 | // None of the inheritable handles were inherited. 183 | ObjectSnap snap; 184 | CHECK(!snap.eq(ph1, ch1(c))); 185 | CHECK(!snap.eq(ph2, ch2(c))); 186 | } 187 | { 188 | // {NULL, a handle} ==> nothing is inherited. 189 | auto c = testSetup(p, {true}, {NULL, ph2.value()}); 190 | CHECK(c.valid()); 191 | ObjectSnap snap; 192 | CHECK(!snap.eq(ph1, ch1(c))); 193 | CHECK(!snap.eq(ph2, ch2(c))); 194 | } 195 | { 196 | // {a handle, NULL} ==> nothing is inherited. (Apparently a NULL 197 | // anywhere in the list means "inherit nothing"? The attribute is not 198 | // ignored.) 199 | auto c = testSetup(p, {true}, {ph1.value(), NULL}); 200 | CHECK(c.valid()); 201 | ObjectSnap snap; 202 | CHECK(!snap.eq(ph1, ch1(c))); 203 | CHECK(!snap.eq(ph2, ch2(c))); 204 | } 205 | { 206 | // bInheritHandles=FALSE still fails. 207 | auto c = testSetup(p, {false}, {NULL}); 208 | CHECK(!c.valid()); 209 | CHECK_EQ(failure.kind, SpawnFailure::CreateProcess); 210 | CHECK_EQ(failure.errCode, (DWORD)ERROR_INVALID_PARAMETER); 211 | } 212 | { 213 | // Test whether inheritList={NULL} has an unexpected effect on the 214 | // standard handles. Everything seems consistent. 215 | auto q = testSetup(p, {true}, {ph1.value(), ph2.value()}); 216 | ch1(q).setStdin(); 217 | ch2(q).setStdout().setStderr(); 218 | auto c = testSetup(q, {true}, {NULL}); 219 | ObjectSnap snap; 220 | if (isAtLeastWin8()) { 221 | // In Windows 8, standard handles are duplicated if an inherit 222 | // list is specified. 223 | CHECK(snap.eq({c.getStdin(), q.getStdin(), ch1(q)})); 224 | CHECK(snap.eq({c.getStdout(), q.getStdout(), ch2(q)})); 225 | CHECK(snap.eq({c.getStderr(), q.getStderr(), ch2(q)})); 226 | CHECK(c.getStdout().value() != c.getStderr().value()); 227 | CHECK(c.getStdin().tryFlags() && c.getStdin().inheritable()); 228 | CHECK(c.getStdout().tryFlags() && c.getStdout().inheritable()); 229 | CHECK(c.getStderr().tryFlags() && c.getStderr().inheritable()); 230 | } else { 231 | // The standard handles were not successfully inherited. 232 | CHECK(handleValues(stdHandles(c)) == handleValues(stdHandles(q))); 233 | CHECK(!snap.eq(ch1(c), ch1(q))); 234 | CHECK(!snap.eq(ch2(c), ch2(q))); 235 | } 236 | } 237 | } 238 | 239 | REGISTER(Test_CreateProcess_InheritList_StdHandles, isAtLeastVista); 240 | static void Test_CreateProcess_InheritList_StdHandles() { 241 | // List one of the standard handles in the inherit list, and see what 242 | // happens to the standard list. 243 | 244 | auto check = [](Worker &p, RemoteHandle rh, RemoteHandle wh) { 245 | ASSERT(!rh.isTraditionalConsole()); 246 | ASSERT(!wh.isTraditionalConsole()); 247 | { 248 | // Test bInheritHandles=TRUE, STARTF_USESTDHANDLES, and the 249 | // PROC_THREAD_ATTRIBUTE_HANDLE_LIST attribute. Verify that the 250 | // standard handles are set to handles whose inheritability was 251 | // suppressed. 252 | SpawnParams sp { true, EXTENDED_STARTUPINFO_PRESENT, {rh, wh, wh} }; 253 | sp.sui.cb = sizeof(STARTUPINFOEXW); 254 | sp.inheritCount = 1; 255 | sp.inheritList = { wh.value() }; 256 | auto c = p.child(sp); 257 | ObjectSnap snap; 258 | CHECK(handleValues(stdHandles(c)) == 259 | handleValues(std::vector {rh, wh, wh})); 260 | CHECK(!snap.eq(rh, c.getStdin())); 261 | CHECK(snap.eq(wh, c.getStdout())); 262 | CHECK(snap.eq(wh, c.getStderr())); 263 | } 264 | 265 | { 266 | // Same as above, but use a single NULL in the inherit list. Now 267 | // none of the handles are inherited, but the standard values are 268 | // unchanged. 269 | SpawnParams sp { true, EXTENDED_STARTUPINFO_PRESENT, {rh, wh, wh} }; 270 | sp.sui.cb = sizeof(STARTUPINFOEXW); 271 | sp.inheritCount = 1; 272 | sp.inheritList = { NULL }; 273 | auto c = p.child(sp); 274 | ObjectSnap snap; 275 | CHECK(handleValues(stdHandles(c)) == 276 | handleValues(std::vector {rh, wh, wh})); 277 | CHECK(!snap.eq(rh, c.getStdin())); 278 | CHECK(!snap.eq(wh, c.getStdout())); 279 | CHECK(!snap.eq(wh, c.getStderr())); 280 | } 281 | 282 | if (!isAtLeastWin8()) { 283 | // Same as above, but avoid STARTF_USESTDHANDLES this time. The 284 | // behavior changed with Windows 8, which now appears to duplicate 285 | // handles in this case. 286 | rh.setStdin(); 287 | wh.setStdout().setStderr(); 288 | SpawnParams sp { true, EXTENDED_STARTUPINFO_PRESENT }; 289 | sp.sui.cb = sizeof(STARTUPINFOEXW); 290 | sp.inheritCount = 1; 291 | sp.inheritList = { wh.value() }; 292 | auto c = p.child(sp); 293 | ObjectSnap snap; 294 | CHECK(handleValues(stdHandles(p)) == handleValues(stdHandles(c))); 295 | CHECK(!snap.eq(p.getStdin(), c.getStdin())); 296 | CHECK(snap.eq(p.getStdout(), c.getStdout())); 297 | } 298 | }; 299 | 300 | { 301 | Worker p; 302 | auto pipe = newPipe(p, true); 303 | check(p, std::get<0>(pipe), std::get<1>(pipe)); 304 | } 305 | 306 | if (isModernConio()) { 307 | Worker p; 308 | check(p, p.openConin(true), p.openConout(true)); 309 | } 310 | } 311 | 312 | REGISTER(Test_CreateProcess_InheritList_ModernDuplication, isAtLeastVista); 313 | static void Test_CreateProcess_InheritList_ModernDuplication() { 314 | auto &hv = handleValues; 315 | 316 | for (int useDummyPipe = 0; useDummyPipe <= 1; ++useDummyPipe) { 317 | // Once we've specified an inherit list, non-inheritable standard 318 | // handles are duplicated. 319 | Worker p; 320 | auto pipe = newPipe(p); 321 | auto rh = std::get<0>(pipe).setStdin(); 322 | auto wh = std::get<1>(pipe).setStdout().setStderr(); 323 | auto c = childWithDummyInheritList(p, {}, useDummyPipe != 0); 324 | if (isModernConio()) { 325 | ObjectSnap snap; 326 | CHECK(snap.eq(rh, c.getStdin())); 327 | CHECK(snap.eq(wh, c.getStdout())); 328 | CHECK(snap.eq(wh, c.getStderr())); 329 | CHECK(c.getStdout().value() != c.getStderr().value()); 330 | for (auto h : stdHandles(c)) { 331 | CHECK(!h.inheritable()); 332 | } 333 | } else { 334 | CHECK(hv(stdHandles(c)) == hv(stdHandles(p))); 335 | CHECK(!c.getStdin().tryFlags()); 336 | CHECK(!c.getStdout().tryFlags()); 337 | CHECK(!c.getStderr().tryFlags()); 338 | } 339 | } 340 | 341 | for (int useDummyPipe = 0; useDummyPipe <= 1; ++useDummyPipe) { 342 | // Invalid handles are translated to 0x0. (For full details, see the 343 | // "duplicate" CreateProcess tests.) 344 | Worker p; 345 | Handle::invent(0x0ull, p).setStdin(); 346 | Handle::invent(0xdeadbeefull, p).setStdout(); 347 | auto c = childWithDummyInheritList(p, {}, useDummyPipe != 0); 348 | if (isModernConio()) { 349 | CHECK(c.getStdin().uvalue() == 0ull); 350 | CHECK(c.getStdout().uvalue() == 0ull); 351 | } else { 352 | CHECK(c.getStdin().uvalue() == 0ull); 353 | CHECK(c.getStdout().value() == 354 | Handle::invent(0xdeadbeefull, c).value()); 355 | } 356 | } 357 | } 358 | 359 | REGISTER(Test_CreateProcess_Duplicate_StdHandles, isModernConio); 360 | static void Test_CreateProcess_Duplicate_StdHandles() { 361 | // The default Unbound console handles should be inheritable, so with 362 | // bInheritHandles=TRUE and standard handles listed in the inherit list, 363 | // the child process should have six console handles, all usable. 364 | Worker p; 365 | 366 | SpawnParams sp { true, EXTENDED_STARTUPINFO_PRESENT }; 367 | sp.sui.cb = sizeof(STARTUPINFOEXW); 368 | sp.inheritCount = 3; 369 | sp.inheritList = { 370 | p.getStdin().value(), 371 | p.getStdout().value(), 372 | p.getStderr().value(), 373 | }; 374 | auto c = p.child(sp); 375 | 376 | std::vector expected; 377 | extendVector(expected, handleInts(stdHandles(p))); 378 | extendVector(expected, handleInts(stdHandles(c))); 379 | std::sort(expected.begin(), expected.end()); 380 | 381 | auto correct = handleInts(c.scanForConsoleHandles()); 382 | std::sort(correct.begin(), correct.end()); 383 | 384 | p.dumpConsoleHandles(); 385 | c.dumpConsoleHandles(); 386 | 387 | CHECK(expected == correct); 388 | } 389 | -------------------------------------------------------------------------------- /src/HandleTests/CreateProcess_NewConsole.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 4 | // Test CreateProcess when called with these parameters: 5 | // - STARTF_USESTDHANDLES is not specified 6 | // - bInheritHandles=FALSE or bInheritHandles=TRUE 7 | // - CreationConsoleMode=NewConsole 8 | // 9 | 10 | REGISTER(Test_CreateProcess_NewConsole, always); 11 | static void Test_CreateProcess_NewConsole() { 12 | auto check = [](Worker &p, bool inheritHandles) { 13 | auto c = p.child({ inheritHandles, Worker::defaultCreationFlags() }); 14 | if (isTraditionalConio()) { 15 | checkInitConsoleHandleSet(c); 16 | CHECK(handleInts(stdHandles(c)) == 17 | (std::vector {0x3, 0x7, 0xb})); 18 | } else { 19 | checkModernConsoleHandleInit(c, true, true, true); 20 | } 21 | return c; 22 | }; 23 | { 24 | Worker p; 25 | check(p, true); 26 | check(p, false); 27 | } 28 | { 29 | Worker p; 30 | p.openConin(false).setStdin(); 31 | p.newBuffer(false).setStdout().dup(true).setStderr(); 32 | check(p, true); 33 | check(p, false); 34 | } 35 | 36 | if (isModernConio()) { 37 | // The default Unbound console handles should be inheritable, so with 38 | // bInheritHandles=TRUE, the child process should have six console 39 | // handles, all usable. 40 | Worker p; 41 | auto c = check(p, true); 42 | 43 | std::vector expected; 44 | extendVector(expected, handleInts(stdHandles(p))); 45 | extendVector(expected, handleInts(stdHandles(c))); 46 | std::sort(expected.begin(), expected.end()); 47 | 48 | auto correct = handleInts(c.scanForConsoleHandles()); 49 | std::sort(correct.begin(), correct.end()); 50 | 51 | CHECK(expected == correct); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/HandleTests/CreateProcess_UseStdHandles.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 4 | // Test CreateProcess when called with these parameters: 5 | // - STARTF_USESTDHANDLES is specified 6 | // - bInheritHandles=FALSE or bInheritHandles=TRUE 7 | // - CreationConsoleMode=NewConsole or CreationConsoleMode=Inherit 8 | // 9 | // Verify: 10 | // - The resulting traditional ConsoleHandleSet is correct. 11 | // - Windows 8 creates Unbound handles when appropriate. 12 | // - Standard handles are set correctly. 13 | // 14 | // Before Windows 8, the child process has the standard handles specified 15 | // in STARTUPINFO, without exception. Starting with Windows 8, the STARTUPINFO 16 | // handles are ignored with bInheritHandles=FALSE, and even with 17 | // bInheritHandles=TRUE, a NULL hStd{Input,Output,Error} field is translated to 18 | // a new open handle if a new console is being created. 19 | 20 | template 21 | void checkVariousInputs(T check) { 22 | { 23 | // Specify the original std values. With CreationConsoleMode==Inherit 24 | // and bInheritHandles=FALSE, this code used to work (i.e. produce 25 | // valid standard handles in the child). As of Windows 8, the standard 26 | // handles are now NULL instead. 27 | Worker p; 28 | check(p, stdHandles(p)); 29 | } 30 | { 31 | Worker p; 32 | check(p, { 33 | p.getStdin().dup(), 34 | p.getStdout().dup(), 35 | p.getStderr().dup(), 36 | }); 37 | } 38 | { 39 | Worker p; 40 | check(p, { 41 | p.getStdin().dup(true), 42 | p.getStdout().dup(true), 43 | p.getStderr().dup(true), 44 | }); 45 | } 46 | { 47 | Worker p; 48 | check(p, { 49 | p.openConin(), 50 | p.openConout(), 51 | p.openConout(), 52 | }); 53 | } 54 | { 55 | Worker p; 56 | check(p, { 57 | p.openConin(true), 58 | p.openConout(true), 59 | p.openConout(true), 60 | }); 61 | } 62 | { 63 | // Invalid handles. 64 | Worker p; 65 | check(p, { 66 | Handle::invent(nullptr, p), 67 | Handle::invent(0x10000ull, p), 68 | Handle::invent(0xdeadbeecull, p), 69 | }); 70 | check(p, { 71 | Handle::invent(INVALID_HANDLE_VALUE, p), 72 | Handle::invent(nullptr, p), 73 | Handle::invent(nullptr, p), 74 | }); 75 | check(p, { 76 | Handle::invent(nullptr, p), 77 | Handle::invent(nullptr, p), 78 | Handle::invent(nullptr, p), 79 | }); 80 | } 81 | { 82 | // Try a non-inheritable pipe. 83 | Worker p; 84 | auto pipe = newPipe(p, false); 85 | check(p, { 86 | std::get<0>(pipe), 87 | std::get<1>(pipe), 88 | std::get<1>(pipe), 89 | }); 90 | } 91 | { 92 | // Try an inheritable pipe. 93 | Worker p; 94 | auto pipe = newPipe(p, true); 95 | check(p, { 96 | std::get<0>(pipe), 97 | std::get<1>(pipe), 98 | std::get<1>(pipe), 99 | }); 100 | } 101 | } 102 | 103 | REGISTER(Test_CreateProcess_UseStdHandles, always); 104 | static void Test_CreateProcess_UseStdHandles() { 105 | checkVariousInputs([](Worker &p, std::vector newHandles) { 106 | ASSERT(newHandles.size() == 3); 107 | auto check = [&](Worker &c, bool inheritHandles, bool newConsole) { 108 | trace("Test_CreateProcess_UseStdHandles: " 109 | "inheritHandles=%d newConsole=%d", 110 | inheritHandles, newConsole); 111 | auto childHandles = stdHandles(c); 112 | if (isTraditionalConio()) { 113 | CHECK(handleValues(stdHandles(c)) == handleValues(newHandles)); 114 | if (newConsole) { 115 | checkInitConsoleHandleSet(c); 116 | } else { 117 | checkInitConsoleHandleSet(c, p); 118 | } 119 | // The child handles have the same values as the parent. 120 | // Verify that the child standard handles point to the right 121 | // kernel objects. 122 | ObjectSnap snap; 123 | for (int i = 0; i < 3; ++i) { 124 | if (newHandles[i].value() == nullptr || 125 | newHandles[i].value() == INVALID_HANDLE_VALUE) { 126 | // Nothing to check. 127 | } else if (newHandles[i].isTraditionalConsole()) { 128 | // Everything interesting was already checked in 129 | // checkInitConsoleHandleSet. 130 | } else if (newHandles[i].tryFlags()) { 131 | // A handle is not inherited simply because it is 132 | // listed in STARTUPINFO. The new child standard 133 | // handle is valid iff: 134 | // - the parent handle was valid, AND 135 | // - the parent handle was inheritable, AND 136 | // - bInheritHandles is TRUE 137 | // 138 | // The logic below is not obviously true for all 139 | // possible handle values, but it will work for all 140 | // values we test for. (i.e. There could be some 141 | // handle H to object O that isn't inherited, but by 142 | // sheer conincidence, the child gets a handle H that 143 | // also refers to O. (e.g. Windows internal objects.) 144 | // This test case works because we know that Windows 145 | // won't create a reference to our test objects.) 146 | CHECK(snap.eq(newHandles[i], childHandles[i]) == 147 | (inheritHandles && newHandles[i].inheritable())); 148 | } 149 | } 150 | } else { 151 | ObjectSnap snap; 152 | bool consoleOpened[3] = {false, false, false}; 153 | for (int i = 0; i < 3; ++i) { 154 | if (inheritHandles && newHandles[i].value() != nullptr) { 155 | // The parent's standard handle is used, without 156 | // validation or duplication. It is not inherited 157 | // simply because it is listed in STARTUPINFO. 158 | CHECK(childHandles[i].value() == 159 | newHandles[i].value()); 160 | if (newHandles[i].value() == INVALID_HANDLE_VALUE) { 161 | // The test below does not work on the current 162 | // process pseudo-handle (aka 163 | // INVALID_HANDLE_VALUE). 164 | } else if (newHandles[i].tryFlags()) { 165 | CHECK(snap.eq(newHandles[i], childHandles[i]) == 166 | newHandles[i].inheritable()); 167 | } 168 | } else if (newConsole) { 169 | consoleOpened[i] = true; 170 | } else { 171 | CHECK(childHandles[i].value() == nullptr); 172 | } 173 | } 174 | checkModernConsoleHandleInit(c, 175 | consoleOpened[0], 176 | consoleOpened[1], 177 | consoleOpened[2]); 178 | } 179 | }; 180 | 181 | for (int inheritInt = 0; inheritInt <= 1; ++inheritInt) { 182 | const bool inherit = inheritInt != 0; 183 | auto c1 = p.child({inherit, 0, newHandles}); 184 | check(c1, inherit, false); 185 | auto c2 = p.child( 186 | {inherit, Worker::defaultCreationFlags(), newHandles}); 187 | check(c2, inherit, true); 188 | } 189 | }); 190 | } 191 | -------------------------------------------------------------------------------- /src/HandleTests/MiscTests.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | REGISTER(Test_CompareObjectHandles, always); 4 | static void Test_CompareObjectHandles() { 5 | // Verify that compareObjectHandles and ObjectSnap are working. 6 | 7 | Worker p; 8 | Worker other; 9 | auto pipe1 = newPipe(p, true); 10 | auto ph1 = std::get<0>(pipe1); 11 | auto ph2 = std::get<1>(pipe1); 12 | auto ph1dup = ph1.dup(); 13 | auto ph1other = ph1.dup(other); 14 | 15 | ObjectSnap snap; 16 | 17 | CHECK(!compareObjectHandles(ph1, ph2)); 18 | CHECK(compareObjectHandles(ph1, ph1dup)); 19 | CHECK(compareObjectHandles(ph1, ph1other)); 20 | 21 | CHECK(!snap.eq(ph1, ph2)); 22 | CHECK(snap.eq(ph1, ph1dup)); 23 | CHECK(snap.eq(ph1, ph1other)); 24 | CHECK(snap.eq({ ph1, ph1other, ph1dup })); 25 | 26 | CHECK(!snap.eq({ ph2, ph1, ph1other, ph1dup })); 27 | CHECK(!snap.eq({ ph1, ph2, ph1other, ph1dup })); 28 | CHECK(!snap.eq({ ph1, ph1other, ph2, ph1dup })); 29 | CHECK(!snap.eq({ ph1, ph1other, ph1dup, ph2 })); 30 | } 31 | 32 | REGISTER(Test_IntrinsicInheritFlags, always); 33 | static void Test_IntrinsicInheritFlags() { 34 | // Console handles have an inherit flag, just as kernel handles do. 35 | // 36 | // In Windows 7, there is a bug where DuplicateHandle(h, FALSE) makes the 37 | // new handle inheritable if the old handle was inheritable. 38 | 39 | Worker p; 40 | auto n = p.newBuffer(FALSE); 41 | auto y = p.newBuffer(TRUE); 42 | auto nn = n.dup(FALSE); 43 | auto yn = y.dup(FALSE); 44 | auto ny = n.dup(TRUE); 45 | auto yy = y.dup(TRUE); 46 | p.dumpConsoleHandles(); 47 | 48 | CHECK(n.inheritable() == false); 49 | CHECK(nn.inheritable() == false); 50 | CHECK(yn.inheritable() == isWin7()); 51 | CHECK(y.inheritable() == true); 52 | CHECK(ny.inheritable() == true); 53 | CHECK(yy.inheritable() == true); 54 | 55 | for (auto &h : (Handle[]){ n, y, nn, ny, yn, yy }) { 56 | const bool v = h.inheritable(); 57 | if (isWin7()) { 58 | // In Windows 7, the console handle inherit flags could not be 59 | // changed. 60 | CHECK(h.trySetInheritable(v) == false); 61 | CHECK(h.trySetInheritable(!v) == false); 62 | CHECK(h.inheritable() == v); 63 | } else { 64 | // With older and newer operating systems, the inheritability can 65 | // be changed. (In newer operating systems, i.e. Windows 8 and up, 66 | // the console handles are just normal kernel handles.) 67 | CHECK(h.trySetInheritable(!v) == true); 68 | CHECK(h.inheritable() == !v); 69 | } 70 | } 71 | p.dumpConsoleHandles(); 72 | 73 | // For sanity's sake, check that DuplicateHandle(h, FALSE) does the right 74 | // thing with an inheritable pipe handle, even on Windows 7. 75 | auto pipeY = std::get<0>(newPipe(p, TRUE)); 76 | auto pipeN = pipeY.dup(FALSE); 77 | CHECK(pipeY.inheritable() == true); 78 | CHECK(pipeN.inheritable() == false); 79 | } 80 | 81 | REGISTER(Test_Input_Vs_Output, always); 82 | static void Test_Input_Vs_Output() { 83 | // Ensure that APIs meant for the other kind of handle fail. 84 | Worker p; 85 | CHECK(!p.getStdin().tryScreenBufferInfo()); 86 | CHECK(!p.getStdout().tryNumberOfConsoleInputEvents()); 87 | } 88 | 89 | REGISTER(Test_Detach_Does_Not_Change_Standard_Handles, always); 90 | static void Test_Detach_Does_Not_Change_Standard_Handles() { 91 | // Detaching the current console does not affect the standard handles. 92 | auto check = [](Worker &p) { 93 | auto handles1 = handleValues(stdHandles(p)); 94 | p.detach(); 95 | auto handles2 = handleValues(stdHandles(p)); 96 | CHECK(handles1 == handles2); 97 | }; 98 | // Simplest form of the test. 99 | { 100 | Worker p1; 101 | check(p1); 102 | } 103 | // Also do a test with duplicated handles, just in case detaching resets 104 | // the handles to their defaults. 105 | { 106 | Worker p2; 107 | p2.getStdin().dup(TRUE).setStdin(); 108 | p2.getStdout().dup(TRUE).setStdout(); 109 | p2.getStderr().dup(TRUE).setStderr(); 110 | check(p2); 111 | } 112 | // Do another test with STARTF_USESTDHANDLES, just in case detaching resets 113 | // to the hStd{Input,Output,Error} values. 114 | { 115 | Worker p3; 116 | auto pipe = newPipe(p3, true); 117 | auto rh = std::get<0>(pipe); 118 | auto wh = std::get<1>(pipe); 119 | auto p3c = p3.child({true, 0, {rh, wh, wh.dup(true)}}); 120 | check(p3c); 121 | } 122 | } 123 | 124 | REGISTER(Test_Activate_Does_Not_Change_Standard_Handles, always); 125 | static void Test_Activate_Does_Not_Change_Standard_Handles() { 126 | // SetConsoleActiveScreenBuffer does not change the standard handles. 127 | // MSDN documents this fact on "Console Handles"[1] 128 | // 129 | // "Note that changing the active screen buffer does not affect the 130 | // handle returned by GetStdHandle. Similarly, using SetStdHandle to 131 | // change the STDOUT handle does not affect the active screen buffer." 132 | // 133 | // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/ms682075.aspx 134 | Worker p; 135 | auto handles1 = handleValues(stdHandles(p)); 136 | p.newBuffer(TRUE).activate(); 137 | auto handles2 = handleValues(stdHandles(p)); 138 | CHECK(handles1 == handles2); 139 | } 140 | 141 | REGISTER(Test_Active_ScreenBuffer_Order, always); 142 | static void Test_Active_ScreenBuffer_Order() { 143 | // SetActiveConsoleScreenBuffer does not increase a refcount on the 144 | // screen buffer. Instead, when the active screen buffer's refcount hits 145 | // zero, Windows activates the most-recently-activated buffer. 146 | 147 | auto firstChar = [](Worker &p) { 148 | auto h = p.openConout(); 149 | auto ret = h.firstChar(); 150 | h.close(); 151 | return ret; 152 | }; 153 | 154 | { 155 | // Simplest test 156 | Worker p; 157 | p.getStdout().setFirstChar('a'); 158 | auto h = p.newBuffer(false, 'b').activate(); 159 | h.close(); 160 | CHECK_EQ(firstChar(p), 'a'); 161 | } 162 | { 163 | // a -> b -> c -> b -> a 164 | Worker p; 165 | p.getStdout().setFirstChar('a'); 166 | auto b = p.newBuffer(false, 'b').activate(); 167 | auto c = p.newBuffer(false, 'c').activate(); 168 | c.close(); 169 | CHECK_EQ(firstChar(p), 'b'); 170 | b.close(); 171 | CHECK_EQ(firstChar(p), 'a'); 172 | } 173 | { 174 | // a -> b -> c -> b -> c -> a 175 | Worker p; 176 | p.getStdout().setFirstChar('a'); 177 | auto b = p.newBuffer(false, 'b').activate(); 178 | auto c = p.newBuffer(false, 'c').activate(); 179 | b.activate(); 180 | b.close(); 181 | CHECK_EQ(firstChar(p), 'c'); 182 | c.close(); 183 | CHECK_EQ(firstChar(p), 'a'); 184 | } 185 | } 186 | 187 | REGISTER(Test_GetStdHandle_SetStdHandle, always); 188 | static void Test_GetStdHandle_SetStdHandle() { 189 | // A commenter on the Old New Thing blog suggested that 190 | // GetStdHandle/SetStdHandle could have internally used CloseHandle and/or 191 | // DuplicateHandle, which would have changed the resource management 192 | // obligations of the callers to those APIs. In fact, the APIs are just 193 | // simple wrappers around global variables. Try to write tests for this 194 | // fact. 195 | // 196 | // http://blogs.msdn.com/b/oldnewthing/archive/2013/03/07/10399690.aspx#10400489 197 | auto &hv = handleValues; 198 | { 199 | // Set values and read them back. We get the same handles. 200 | Worker p; 201 | auto pipe = newPipe(p); 202 | auto rh = std::get<0>(pipe); 203 | auto wh1 = std::get<1>(pipe); 204 | auto wh2 = std::get<1>(pipe).dup(); 205 | setStdHandles({ rh, wh1, wh2 }); 206 | CHECK(hv(stdHandles(p)) == hv({ rh, wh1, wh2})); 207 | 208 | // Call again, and we still get the same handles. 209 | CHECK(hv(stdHandles(p)) == hv({ rh, wh1, wh2})); 210 | } 211 | { 212 | Worker p; 213 | p.getStdout().setFirstChar('a'); 214 | p.newBuffer(false, 'b').activate().setStdout().dup().setStderr(); 215 | std::get<1>(newPipe(p)).setStdout().dup().setStderr(); 216 | 217 | // SetStdHandle doesn't close its previous handle when it's given a new 218 | // handle. Therefore, the two handles given to SetStdHandle for STDOUT 219 | // and STDERR are still open, and the new screen buffer is still 220 | // active. 221 | CHECK_EQ(p.openConout().firstChar(), 'b'); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/HandleTests/Modern.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | REGISTER(Test_AttachConsole_AllocConsole_StdHandles, isModernConio); 4 | static void Test_AttachConsole_AllocConsole_StdHandles() { 5 | // Verify that AttachConsole does the right thing w.r.t. console handle 6 | // sets and standard handles. 7 | 8 | auto check = [](bool newConsole, bool useStdHandles, int nullIndex) { 9 | trace("checking: newConsole=%d useStdHandles=%d nullIndex=%d", 10 | newConsole, useStdHandles, nullIndex); 11 | Worker p; 12 | SpawnParams sp = useStdHandles 13 | ? SpawnParams { true, 0, stdHandles(p) } 14 | : SpawnParams { false, 0 }; 15 | 16 | auto c = p.child(sp); 17 | auto pipe = newPipe(c, true); 18 | std::get<0>(pipe).setStdin(); 19 | std::get<1>(pipe).setStdout().setStdout(); 20 | 21 | if (nullIndex == 0) { 22 | Handle::invent(nullptr, c).setStdin(); 23 | } else if (nullIndex == 1) { 24 | Handle::invent(nullptr, c).setStdout(); 25 | } else if (nullIndex == 2) { 26 | Handle::invent(nullptr, c).setStderr(); 27 | } 28 | 29 | auto origStdHandles = stdHandles(c); 30 | c.detach(); 31 | CHECK(handleValues(stdHandles(c)) == handleValues(origStdHandles)); 32 | 33 | if (newConsole) { 34 | c.alloc(); 35 | } else { 36 | Worker other; 37 | c.attach(other); 38 | } 39 | 40 | if (useStdHandles) { 41 | auto curHandles = stdHandles(c); 42 | for (int i = 0; i < 3; ++i) { 43 | if (i != nullIndex) { 44 | CHECK(curHandles[i].value() == origStdHandles[i].value()); 45 | } 46 | } 47 | checkModernConsoleHandleInit(c, 48 | nullIndex == 0, 49 | nullIndex == 1, 50 | nullIndex == 2); 51 | } else { 52 | checkModernConsoleHandleInit(c, true, true, true); 53 | } 54 | }; 55 | 56 | for (int i = -1; i < 3; ++i) { 57 | check(false, false, i); 58 | check(false, true, i); 59 | check(true, false, i); 60 | check(true, true, i); 61 | } 62 | } 63 | 64 | REGISTER(Test_Unbound_vs_Bound, isModernConio); 65 | static void Test_Unbound_vs_Bound() { 66 | { 67 | // An Unbound output handle refers to the initial buffer. 68 | Worker p; 69 | auto ob = p.getStdout().setFirstChar('O'); 70 | p.newBuffer(true, 'N').activate().setStdout().setStderr(); 71 | CHECK_EQ(ob.firstChar(), 'O'); 72 | 73 | // The handle can come from another process. 74 | Worker p2; 75 | CHECK_EQ(p2.getStdout().dup(p).firstChar(), 'O'); 76 | 77 | // CONOUT$ will use the new buffer, though. 78 | CHECK_EQ(p.openConout().firstChar(), 'N'); 79 | } 80 | { 81 | // A Bound handle from another process does not work. 82 | Worker wa; 83 | Worker wb; 84 | wa.getStdout().setFirstChar('a'); 85 | wb.getStdout().setFirstChar('b'); 86 | auto a_b = wb.openConout().dup(wa); 87 | auto a_c = wb.newBuffer(false, 'c').dup(wa); 88 | CHECK(a_b.tryFlags()); 89 | CHECK(a_c.tryFlags()); 90 | CHECK(!a_b.tryScreenBufferInfo()); 91 | CHECK(!a_c.tryScreenBufferInfo()); 92 | 93 | // We can *make* them work, though, if we reattach p to p2's console. 94 | wa.detach(); 95 | CHECK(a_b.tryFlags() && a_c.tryFlags()); 96 | wa.attach(wb); 97 | CHECK(a_b.tryScreenBufferInfo() && a_b.firstChar() == 'b'); 98 | CHECK(a_c.tryScreenBufferInfo() && a_c.firstChar() == 'c'); 99 | } 100 | } 101 | 102 | REGISTER(Test_Console_Without_Processes, isModernConio); 103 | static void Test_Console_Without_Processes() { 104 | auto waitForTitle = [](HWND hwnd, const std::string &title) { 105 | for (int i = 0; i < 100 && (windowText(hwnd) != title); ++i) { 106 | Sleep(20); 107 | } 108 | }; 109 | auto waitForNotTitle = [](HWND hwnd, const std::string &title) { 110 | for (int i = 0; i < 100 && (windowText(hwnd) == title); ++i) { 111 | Sleep(20); 112 | } 113 | }; 114 | { 115 | // It is possible to have a console with no attached process. Verify 116 | // that the console window has the expected title even after its only 117 | // process detaches. The window dies once the duplicated Bound handle 118 | // is closed. 119 | Worker p({ false, CREATE_NEW_CONSOLE }); 120 | auto bound = p.openConout(); 121 | auto hwnd = p.consoleWindow(); 122 | auto title = makeTempName(__FUNCTION__); 123 | p.setTitle(title); 124 | waitForTitle(hwnd, title); 125 | p.detach(); 126 | Sleep(200); 127 | CHECK_EQ(windowText(hwnd), title); 128 | bound.close(); 129 | waitForNotTitle(hwnd, title); 130 | CHECK(windowText(hwnd) != title); 131 | } 132 | } 133 | 134 | REGISTER(Test_Implicit_Buffer_Reference, isModernConio); 135 | static void Test_Implicit_Buffer_Reference() { 136 | // Test that a process attached to a console holds an implicit reference 137 | // to the screen buffer that was active at attachment. 138 | auto activeFirstChar = [](Worker &proc) { 139 | auto b = proc.openConout(); 140 | auto ret = b.firstChar(); 141 | b.close(); 142 | return ret; 143 | }; 144 | { 145 | Worker p; 146 | p.getStdout().setFirstChar('A'); 147 | auto b = p.newBuffer(false, 'B').activate(); 148 | auto pipe = newPipe(p, true); 149 | 150 | // Spawn a child process that has no console handles open. 151 | SpawnParams sp({ true, EXTENDED_STARTUPINFO_PRESENT, { 152 | std::get<0>(pipe), 153 | std::get<1>(pipe), 154 | std::get<1>(pipe), 155 | }}); 156 | sp.sui.cb = sizeof(STARTUPINFOEXW); 157 | sp.inheritCount = 2; 158 | sp.inheritList = { 159 | std::get<0>(pipe).value(), 160 | std::get<1>(pipe).value(), 161 | }; 162 | auto c = p.child(sp); 163 | CHECK_EQ(c.scanForConsoleHandles().size(), 0u); 164 | 165 | // Now close the only open handle to the B buffer. The active 166 | // buffer remains B, because the child implicitly references B. 167 | b.close(); 168 | CHECK_EQ(activeFirstChar(p), 'B'); 169 | c.detach(); 170 | 171 | // Once the child detaches, B is freed, and A activates. 172 | CHECK_EQ(activeFirstChar(p), 'A'); 173 | } 174 | } 175 | 176 | REGISTER(Test_FreeConsole_Closes_Handles, isModernConio); 177 | static void Test_FreeConsole_Closes_Handles() { 178 | auto check = [](Worker &proc, bool ineq, bool outeq, bool erreq) { 179 | auto dupin = proc.getStdin().dup(); 180 | auto dupout = proc.getStdout().dup(); 181 | auto duperr = proc.getStderr().dup(); 182 | proc.detach(); 183 | ObjectSnap snap; 184 | CHECK_EQ(snap.eq(proc.getStdin(), dupin), ineq); 185 | CHECK_EQ(snap.eq(proc.getStdout(), dupout), outeq); 186 | CHECK_EQ(snap.eq(proc.getStderr(), duperr), erreq); 187 | dupin.close(); 188 | dupout.close(); 189 | duperr.close(); 190 | }; 191 | { 192 | // The child opened three console handles, so FreeConsole closes all of 193 | // them. 194 | Worker p; 195 | check(p, false, false, false); 196 | } 197 | { 198 | // The child inherited the handles, so FreeConsole closes none of them. 199 | Worker p; 200 | auto c = p.child({ true }); 201 | check(c, true, true, true); 202 | } 203 | { 204 | // Duplicated console handles: still none of them are closed. 205 | Worker p; 206 | auto c = p.child({ false }); 207 | check(c, true, true, true); 208 | } 209 | { 210 | // FreeConsole doesn't close the current stdhandles; it closes the 211 | // handles it opened at attach-time. 212 | Worker p; 213 | p.openConout().setStderr(); 214 | check(p, false, false, true); 215 | } 216 | { 217 | // With UseStdHandles, handles aren't closed. 218 | Worker p; 219 | auto c = p.child({ true, 0, stdHandles(p) }); 220 | check(c, true, true, true); 221 | } 222 | { 223 | // Using StdHandles, AllocConsole sometimes only opens a few handles. 224 | // Only the handles it opens are closed. 225 | Worker p({ false, DETACHED_PROCESS }); 226 | auto pipe = newPipe(p, true); 227 | auto c = p.child({ true, DETACHED_PROCESS, { 228 | std::get<0>(pipe), 229 | std::get<1>(pipe), 230 | std::get<1>(pipe), 231 | }}); 232 | Handle::invent(0ull, c).setStderr(); 233 | c.alloc(); 234 | CHECK(c.getStdin().value() == std::get<0>(pipe).value()); 235 | CHECK(c.getStdout().value() == std::get<1>(pipe).value()); 236 | CHECK(c.getStderr().tryScreenBufferInfo()); 237 | check(c, true, true, false); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/HandleTests/Traditional.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | REGISTER(Test_HandleDuplication, isTraditionalConio); 4 | static void Test_HandleDuplication() { 5 | // A traditional console handle cannot be duplicated to another process, 6 | // and it must be duplicated using the GetConsoleProcess() pseudo-value. 7 | // (This tests targetProcess != psuedo-value, but it doesn't test 8 | // sourceProcess != pseudo-value. Not worth the trouble.) 9 | Worker p, other; 10 | p.getStdout().setFirstChar('x'); 11 | CHECK_EQ(p.getStdout().dup().firstChar(), 'x'); 12 | CHECK_EQ(p.getStdout().dup(p).value(), INVALID_HANDLE_VALUE); 13 | CHECK_EQ(p.getStdout().dup(other).value(), INVALID_HANDLE_VALUE); 14 | } 15 | 16 | REGISTER(Test_NewConsole_Resets_ConsoleHandleSet, isTraditionalConio); 17 | static void Test_NewConsole_Resets_ConsoleHandleSet() { 18 | // Test that creating a new console properly resets everything. 19 | Worker p; 20 | 21 | // Open some handles to demonstrate the "clean slate" outcome. 22 | auto orig = stdHandles(p); 23 | p.getStdin().dup(true).setStdin(); 24 | p.newBuffer(true).setStderr().dup(true).setStdout().activate(); 25 | for (auto &h : orig) { 26 | h.close(); 27 | } 28 | 29 | auto checkClean = [](Worker &proc) { 30 | proc.dumpConsoleHandles(); 31 | CHECK_EQ(proc.getStdin().uvalue(), 0x3u); 32 | CHECK_EQ(proc.getStdout().uvalue(), 0x7u); 33 | CHECK_EQ(proc.getStderr().uvalue(), 0xbu); 34 | auto handles = proc.scanForConsoleHandles(); 35 | CHECK(handleValues(handles) == (std::vector { 36 | proc.getStdin().value(), 37 | proc.getStdout().value(), 38 | proc.getStderr().value(), 39 | })); 40 | CHECK(allInheritable(handles)); 41 | }; 42 | 43 | // A child with a new console is reset to a blank slate. 44 | for (int inherit = 0; inherit <= 1; ++inherit) { 45 | auto c1 = p.child({ inherit != 0, CREATE_NEW_CONSOLE }); 46 | checkClean(c1); 47 | auto c2 = p.child({ inherit != 0, CREATE_NO_WINDOW }); 48 | checkClean(c2); 49 | 50 | // Starting a child from a DETACHED_PROCESS also produces a clean 51 | // configuration. 52 | Worker detachedParent({ false, DETACHED_PROCESS }); 53 | auto pipe = newPipe(detachedParent, true); 54 | std::get<0>(pipe).setStdin(); 55 | std::get<1>(pipe).setStdout().dup(true).setStdout(); 56 | Worker c3 = detachedParent.child({ inherit != 0, 0 }); 57 | checkClean(c3); 58 | } 59 | 60 | // Similarly, detaching and allocating a new console resets the 61 | // ConsoleHandleSet. 62 | p.detach(); 63 | p.alloc(); 64 | checkClean(p); 65 | } 66 | 67 | REGISTER(Test_CreateProcess_DetachedProcess, isTraditionalConio); 68 | static void Test_CreateProcess_DetachedProcess() { 69 | // A child with DETACHED_PROCESS has no console, and its standard handles 70 | // are set to 0 by default. 71 | Worker p; 72 | 73 | p.getStdin().dup(TRUE).setStdin(); 74 | p.getStdout().dup(TRUE).setStdout(); 75 | p.getStderr().dup(TRUE).setStderr(); 76 | 77 | auto c = p.child({ true, DETACHED_PROCESS }); 78 | 79 | CHECK(c.getStdin().uvalue() == 0); 80 | CHECK(c.getStdout().uvalue() == 0); 81 | CHECK(c.getStderr().uvalue() == 0); 82 | CHECK(c.scanForConsoleHandles().empty()); 83 | CHECK(c.consoleWindow() == NULL); 84 | 85 | // XXX: What do GetConsoleCP and GetConsoleOutputCP do when no console is attached? 86 | 87 | // Verify that we have a blank slate even with an implicit console 88 | // creation. 89 | auto c2 = c.child({ true }); 90 | auto c2h = c2.scanForConsoleHandles(); 91 | CHECK(handleValues(c2h) == (std::vector { 92 | c2.getStdin().value(), 93 | c2.getStdout().value(), 94 | c2.getStderr().value(), 95 | })); 96 | } 97 | 98 | REGISTER(Test_Creation_bInheritHandles_Flag, isTraditionalConio); 99 | static void Test_Creation_bInheritHandles_Flag() { 100 | // The bInheritHandles flags to CreateProcess has no effect on console 101 | // handles. 102 | Worker p; 103 | for (auto &h : (Handle[]){ 104 | p.getStdin(), 105 | p.getStdout(), 106 | p.getStderr(), 107 | p.newBuffer(false), 108 | p.newBuffer(true), 109 | }) { 110 | h.dup(false); 111 | h.dup(true); 112 | } 113 | auto cY = p.child({ true }); 114 | auto cN = p.child({ false }); 115 | auto &hv = handleValues; 116 | CHECK(hv(cY.scanForConsoleHandles()) == hv(inheritableHandles(p.scanForConsoleHandles()))); 117 | CHECK(hv(cN.scanForConsoleHandles()) == hv(inheritableHandles(p.scanForConsoleHandles()))); 118 | } 119 | 120 | REGISTER(Test_HandleAllocationOrder, isTraditionalConio); 121 | static void Test_HandleAllocationOrder() { 122 | // When a new handle is created, it always assumes the lowest unused value. 123 | Worker p; 124 | 125 | auto h3 = p.getStdin(); 126 | auto h7 = p.getStdout(); 127 | auto hb = p.getStderr(); 128 | auto hf = h7.dup(true); 129 | auto h13 = h3.dup(true); 130 | auto h17 = hb.dup(true); 131 | 132 | CHECK(h3.uvalue() == 0x3); 133 | CHECK(h7.uvalue() == 0x7); 134 | CHECK(hb.uvalue() == 0xb); 135 | CHECK(hf.uvalue() == 0xf); 136 | CHECK(h13.uvalue() == 0x13); 137 | CHECK(h17.uvalue() == 0x17); 138 | 139 | hf.close(); 140 | h13.close(); 141 | h7.close(); 142 | 143 | h7 = h3.dup(true); 144 | hf = h3.dup(true); 145 | h13 = h3.dup(true); 146 | auto h1b = h3.dup(true); 147 | 148 | CHECK(h7.uvalue() == 0x7); 149 | CHECK(hf.uvalue() == 0xf); 150 | CHECK(h13.uvalue() == 0x13); 151 | CHECK(h1b.uvalue() == 0x1b); 152 | } 153 | 154 | REGISTER(Test_InheritNothing, isTraditionalConio); 155 | static void Test_InheritNothing() { 156 | // It's possible for the standard handles to be non-inheritable. 157 | // 158 | // Avoid calling DuplicateHandle(h, FALSE), because it produces inheritable 159 | // console handles on Windows 7. 160 | Worker p; 161 | auto conin = p.openConin(); 162 | auto conout = p.openConout(); 163 | p.getStdin().close(); 164 | p.getStdout().close(); 165 | p.getStderr().close(); 166 | conin.setStdin(); 167 | conout.setStdout().dup().setStderr(); 168 | p.dumpConsoleHandles(); 169 | 170 | auto c = p.child({ true }); 171 | // The child has no open console handles. 172 | CHECK(c.scanForConsoleHandles().empty()); 173 | c.dumpConsoleHandles(); 174 | // The standard handle values are inherited, even though they're invalid. 175 | CHECK(c.getStdin().value() == p.getStdin().value()); 176 | CHECK(c.getStdout().value() == p.getStdout().value()); 177 | CHECK(c.getStderr().value() == p.getStderr().value()); 178 | // Verify a console is attached. 179 | CHECK(c.openConin().value() != INVALID_HANDLE_VALUE); 180 | CHECK(c.openConout().value() != INVALID_HANDLE_VALUE); 181 | CHECK(c.newBuffer().value() != INVALID_HANDLE_VALUE); 182 | } 183 | 184 | REGISTER(Test_AttachConsole_And_CreateProcess_Inheritance, isTraditionalConio); 185 | static void Test_AttachConsole_And_CreateProcess_Inheritance() { 186 | Worker p; 187 | Worker unrelated({ false, DETACHED_PROCESS }); 188 | 189 | auto conin = p.getStdin().dup(true); 190 | auto conout1 = p.getStdout().dup(true); 191 | auto conout2 = p.getStderr().dup(true); 192 | p.openConout(false); // an extra handle for checkInitConsoleHandleSet testing 193 | p.openConout(true); // an extra handle for checkInitConsoleHandleSet testing 194 | p.getStdin().close(); 195 | p.getStdout().close(); 196 | p.getStderr().close(); 197 | conin.setStdin(); 198 | conout1.setStdout(); 199 | conout2.setStderr(); 200 | 201 | auto c = p.child({ true }); 202 | 203 | auto c2 = c.child({ true }); 204 | c2.detach(); 205 | c2.attach(c); 206 | 207 | unrelated.attach(p); 208 | 209 | // The first child will have the same standard handles as the parent. 210 | CHECK(c.getStdin().value() == p.getStdin().value()); 211 | CHECK(c.getStdout().value() == p.getStdout().value()); 212 | CHECK(c.getStderr().value() == p.getStderr().value()); 213 | 214 | // AttachConsole sets the handles to (0x3, 0x7, 0xb) regardless of handle 215 | // validity. In this case, c2 initially had non-default handles, and it 216 | // attached to a process that has and also initially had non-default 217 | // handles. Nevertheless, the new standard handles are the defaults. 218 | for (auto proc : {&c2, &unrelated}) { 219 | CHECK(proc->getStdin().uvalue() == 0x3); 220 | CHECK(proc->getStdout().uvalue() == 0x7); 221 | CHECK(proc->getStderr().uvalue() == 0xb); 222 | } 223 | 224 | // The set of inheritable console handles in these processes exactly match 225 | // that of the parent. 226 | checkInitConsoleHandleSet(c, p); 227 | checkInitConsoleHandleSet(c2, p); 228 | checkInitConsoleHandleSet(unrelated, p); 229 | } 230 | 231 | REGISTER(Test_Detach_Implicitly_Closes_Handles, isTraditionalConio); 232 | static void Test_Detach_Implicitly_Closes_Handles() { 233 | // After detaching, calling GetHandleInformation fails on previous console 234 | // handles. 235 | 236 | Worker p; 237 | Handle orig[] = { 238 | p.getStdin(), 239 | p.getStdout(), 240 | p.getStderr(), 241 | p.getStdin().dup(TRUE), 242 | p.getStdout().dup(TRUE), 243 | p.getStderr().dup(TRUE), 244 | p.openConin(TRUE), 245 | p.openConout(TRUE), 246 | }; 247 | 248 | p.detach(); 249 | for (auto h : orig) { 250 | CHECK(!h.tryFlags()); 251 | } 252 | } 253 | 254 | REGISTER(Test_AttachConsole_AllocConsole_StdHandles, isTraditionalConio); 255 | static void Test_AttachConsole_AllocConsole_StdHandles() { 256 | // Verify that AttachConsole does the right thing w.r.t. console handle 257 | // sets and standard handles. 258 | 259 | auto check = [](bool newConsole, bool useStdHandles) { 260 | trace("checking: newConsole=%d useStdHandles=%d", 261 | newConsole, useStdHandles); 262 | Worker p; 263 | SpawnParams sp = useStdHandles 264 | ? SpawnParams { true, 0, stdHandles(p) } 265 | : SpawnParams { false, 0 }; 266 | p.openConout(false); // 0x0f 267 | p.openConout(true); // 0x13 268 | 269 | auto c = p.child(sp); 270 | auto pipe = newPipe(c, true); 271 | std::get<0>(pipe).setStdin(); 272 | std::get<1>(pipe).setStdout().setStdout(); 273 | auto origStdHandles = stdHandles(c); 274 | c.detach(); 275 | CHECK(handleValues(stdHandles(c)) == handleValues(origStdHandles)); 276 | 277 | if (newConsole) { 278 | c.alloc(); 279 | checkInitConsoleHandleSet(c); 280 | } else { 281 | Worker other; 282 | auto out = other.newBuffer(true, 'N'); // 0x0f 283 | other.openConin(false); // 0x13 284 | auto in = other.openConin(true); // 0x17 285 | out.activate(); // activate new buffer 286 | other.getStdin().close(); // close 0x03 287 | other.getStdout().close(); // close 0x07 288 | other.getStderr().close(); // close 0x0b 289 | in.setStdin(); // 0x17 290 | out.setStdout().dup(true).setStderr(); // 0x0f and 0x1b 291 | c.attach(other); 292 | checkInitConsoleHandleSet(c, other); 293 | } 294 | 295 | if (useStdHandles) { 296 | CHECK(handleValues(stdHandles(c)) == handleValues(origStdHandles)); 297 | } else { 298 | CHECK(handleInts(stdHandles(c)) == 299 | (std::vector { 0x3, 0x7, 0xb })); 300 | } 301 | }; 302 | 303 | check(false, false); 304 | check(false, true); 305 | check(true, false); 306 | check(true, true); 307 | } 308 | -------------------------------------------------------------------------------- /src/HandleTests/Win7_Conout_Crash.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Test for the Windows 7 win7_conout_crash bug. 4 | // 5 | // See console-handle.md, #win7_conout_crash, for theory. Basically, if a 6 | // process does not have a handle for a screen buffer, and it opens and closes 7 | // CONOUT$, then the buffer is destroyed, even though another process is still 8 | // using it. Closing the *other* handles crashes conhost.exe. 9 | // 10 | // The bug affects Windows 7 SP1, but does not affect 11 | // Windows Server 2008 R2 SP1, the server version of the OS. 12 | // 13 | 14 | REGISTER(Win7_RefCount_Bug, always); 15 | static void Win7_RefCount_Bug() { 16 | { 17 | // Simplest demonstration: 18 | // 19 | // We will have two screen buffers in this test, O and N. The parent opens 20 | // CONOUT$ to access N, but when it closes its handle, N is freed, 21 | // restoring O as the active buffer. 22 | // 23 | Worker p; 24 | p.getStdout().setFirstChar('O'); 25 | auto c = p.child(); 26 | c.newBuffer(false, 'N').activate(); 27 | auto conout = p.openConout(); 28 | CHECK_EQ(conout.firstChar(), 'N'); 29 | conout.close(); 30 | // At this point, Win7 is broken. Test for it and hope we don't crash. 31 | conout = p.openConout(); 32 | if (isWin7() && isWorkstation()) { 33 | CHECK_EQ(conout.firstChar(), 'O'); 34 | } else { 35 | CHECK_EQ(conout.firstChar(), 'N'); 36 | } 37 | } 38 | { 39 | // We can still "close" the handle by first importing it to another 40 | // process, then detaching that process from its console. 41 | Worker p; 42 | Worker assistant({ false, DETACHED_PROCESS }); 43 | p.getStdout().setFirstChar('O'); 44 | auto c = p.child(); 45 | c.newBuffer(false, 'N').activate(); 46 | 47 | // Do the read a few times for good measure. 48 | for (int i = 0; i < 5; ++i) { 49 | auto conout = p.openConout(true); // Must be inheritable! 50 | CHECK_EQ(conout.firstChar(), 'N'); 51 | assistant.attach(p); // The attach imports the CONOUT$ handle 52 | conout.close(); 53 | assistant.detach(); // Exiting would also work. 54 | } 55 | } 56 | { 57 | // If the child detaches, the screen buffer is still allocated. This 58 | // demonstrates that the CONOUT$ handle *did* increment a refcount on 59 | // the buffer. 60 | Worker p; 61 | p.getStdout().setFirstChar('O'); 62 | Worker c = p.child(); 63 | c.newBuffer(false, 'N').activate(); 64 | auto conout = p.openConout(); 65 | c.detach(); // The child must exit/detach *without* closing the handle. 66 | CHECK_EQ(conout.firstChar(), 'N'); 67 | auto conout2 = p.openConout(); 68 | CHECK_EQ(conout2.firstChar(), 'N'); 69 | // It is now safe to close the handles. There is no other "console 70 | // object" referencing the screen buffer. 71 | conout.close(); 72 | conout2.close(); 73 | } 74 | { 75 | // If there are multiple console objects, closing any of them frees 76 | // the screen buffer. 77 | Worker p; 78 | auto c1 = p.child(); 79 | auto c2 = p.child(); 80 | p.getStdout().setFirstChar('O'); 81 | p.newBuffer(false, 'N').activate(); 82 | auto ch1 = c1.openConout(); 83 | auto ch2 = c2.openConout(); 84 | CHECK_EQ(ch1.firstChar(), 'N'); 85 | CHECK_EQ(ch2.firstChar(), 'N'); 86 | ch1.close(); 87 | // At this point, Win7 is broken. Test for it and hope we don't crash. 88 | auto testHandle = c1.openConout(); 89 | if (isWin7() && isWorkstation()) { 90 | CHECK_EQ(testHandle.firstChar(), 'O'); 91 | } else { 92 | CHECK_EQ(testHandle.firstChar(), 'N'); 93 | } 94 | } 95 | 96 | if (isTraditionalConio()) { 97 | // Two processes can share a console object; in that case, CloseHandle 98 | // does not immediately fail. 99 | for (int i = 0; i < 2; ++i) { 100 | Worker p1; 101 | Worker p2 = p1.child(); 102 | Worker p3({false, DETACHED_PROCESS}); 103 | p1.getStdout().setFirstChar('O'); 104 | Worker observer = p1.child(); 105 | p1.newBuffer(false, 'N').activate(); 106 | auto objref1 = p2.openConout(true); 107 | p3.attach(p2); 108 | auto objref2 = Handle::invent(objref1.value(), p3); 109 | if (i == 0) { 110 | objref1.close(); 111 | } else { 112 | objref2.close(); 113 | } 114 | CHECK_EQ(observer.openConout().firstChar(), 'N'); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/HandleTests/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | for (DWORD flags : {CREATE_NEW_CONSOLE, CREATE_NO_WINDOW}) { 5 | if (flags == CREATE_NEW_CONSOLE) { 6 | printTestName("Using CREATE_NEW_CONSOLE as default creation mode"); 7 | } else { 8 | printTestName("Using CREATE_NO_WINDOW as default creation mode"); 9 | } 10 | Worker::setDefaultCreationFlags(flags); 11 | for (auto &test : registeredTests()) { 12 | std::string name; 13 | bool (*cond)(); 14 | void (*func)(); 15 | std::tie(name, cond, func) = test; 16 | if (cond()) { 17 | printTestName(name); 18 | func(); 19 | } 20 | } 21 | } 22 | std::cout << std::endl; 23 | const auto failures = failedTests(); 24 | if (failures.empty()) { 25 | std::cout << "All tests passed!" << std::endl; 26 | } else { 27 | std::cout << "Failed tests:" << std::endl; 28 | for (auto name : failures) { 29 | std::cout << " " << name << std::endl; 30 | } 31 | } 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /src/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2016 Ryan Prichard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to 7 | deal in the Software without restriction, including without limitation the 8 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | sell copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | include config.mk 2 | 3 | # Pass -Wno-format to disable format checking because gcc complains about 4 | # %I64x. I can't use %lld because it's broken on MinGW-on-WinXP, though it 5 | # works on later OSs. %I64x works everywhere with MinGW, at runtime. I 6 | # need to find a better way to do this int-to-string conversion. 7 | CXXFLAGS += \ 8 | -MMD \ 9 | -Wall \ 10 | -Wno-format \ 11 | -Iharness -Ishared \ 12 | -std=c++11 \ 13 | -DUNICODE \ 14 | -D_UNICODE \ 15 | -D_WIN32_WINNT=0x0600 16 | LDFLAGS += -static -static-libgcc -static-libstdc++ 17 | 18 | # To disable PCH, just comment out these two lines. 19 | PCHFLAGS = -include build/obj/pch.h 20 | PCHDEPS = build/obj/pch.h.gch 21 | 22 | # Use gmake -n to see the command-lines gmake would run. 23 | 24 | COMMON_OBJECTS = \ 25 | build/obj/harness/Event.o \ 26 | build/obj/harness/NtHandleQuery.o \ 27 | build/obj/harness/ShmemParcel.o \ 28 | build/obj/harness/Spawn.o \ 29 | build/obj/harness/UnicodeConversions.o \ 30 | build/obj/harness/Util.o \ 31 | build/obj/shared/DebugClient.o \ 32 | build/obj/shared/WinptyAssert.o \ 33 | build/obj/shared/winpty_wcsnlen.o 34 | 35 | WORKER_OBJECTS = \ 36 | build/obj/harness/WorkerProgram.o 37 | 38 | TEST_OBJECTS = \ 39 | build/obj/harness/RemoteHandle.o \ 40 | build/obj/harness/RemoteWorker.o \ 41 | build/obj/harness/TestUtil.o \ 42 | 43 | HANDLETESTS_OBJECTS = \ 44 | build/obj/HandleTests/CreateProcess.o \ 45 | build/obj/HandleTests/CreateProcess_Detached.o \ 46 | build/obj/HandleTests/CreateProcess_Duplicate.o \ 47 | build/obj/HandleTests/CreateProcess_Duplicate_PseudoHandleBug.o \ 48 | build/obj/HandleTests/CreateProcess_Duplicate_XPPipeBug.o \ 49 | build/obj/HandleTests/CreateProcess_InheritAllHandles.o \ 50 | build/obj/HandleTests/CreateProcess_InheritList.o \ 51 | build/obj/HandleTests/CreateProcess_NewConsole.o \ 52 | build/obj/HandleTests/CreateProcess_UseStdHandles.o \ 53 | build/obj/HandleTests/MiscTests.o \ 54 | build/obj/HandleTests/Modern.o \ 55 | build/obj/HandleTests/Traditional.o \ 56 | build/obj/HandleTests/Win7_Conout_Crash.o \ 57 | build/obj/HandleTests/main.o 58 | 59 | include tests.mk 60 | 61 | .PHONY : all 62 | all : \ 63 | $(TESTS) \ 64 | build/Worker.exe \ 65 | build/HandleTests.exe \ 66 | build/HandleTests.exe.manifest 67 | 68 | .PHONY : clean 69 | clean: 70 | rm -fr build 71 | 72 | build/obj/pch.h.gch : harness/pch.h 73 | @echo Compiling PCH $< 74 | @mkdir -p $$(dirname $@) 75 | @$(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< 76 | 77 | build/obj/%.o : %.cc $(PCHDEPS) 78 | @echo Compiling $< 79 | @mkdir -p $$(dirname $@) 80 | @$(CXX) $(PCHFLAGS) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< 81 | 82 | .PRECIOUS : build/obj/%.o 83 | 84 | build/Worker.exe : $(WORKER_OBJECTS) $(COMMON_OBJECTS) 85 | @echo Linking $@ 86 | @$(CXX) -o $@ $^ $(LDFLAGS) 87 | 88 | build/HandleTests.exe : $(HANDLETESTS_OBJECTS) $(TEST_OBJECTS) $(COMMON_OBJECTS) 89 | @echo Linking $@ 90 | @$(CXX) -o $@ $^ $(LDFLAGS) 91 | 92 | build/HandleTests.exe.manifest : manifest.xml 93 | @echo Copying $< to $@ 94 | @mkdir -p $$(dirname $@) 95 | @cp $< $@ 96 | 97 | build/%.exe : build/obj/%.o $(TEST_OBJECTS) $(COMMON_OBJECTS) 98 | @echo Linking $@ 99 | @$(CXX) -o $@ $^ $(LDFLAGS) 100 | 101 | -include $(COMMON_OBJECTS:.o=.d) 102 | -include $(WORKER_OBJECTS:.o=.d) 103 | -include $(TEST_OBJECTS:.o=.d) 104 | -include $(HANDLETESTS_OBJECTS:.o=.d) 105 | -include build/obj/*.d 106 | -------------------------------------------------------------------------------- /src/Test_GetConsoleTitleW.cc: -------------------------------------------------------------------------------- 1 | // Test GetConsoleTitleW. 2 | // 3 | // Each of these OS sets implements different semantics for the system call: 4 | // * Windows XP 5 | // * Vista and Windows 7 6 | // * Windows 8 and up (at least to Windows 10) 7 | // 8 | 9 | #include 10 | 11 | static void checkBuf(const std::array &actual, 12 | const std::array &expected, 13 | const char *filename, 14 | int line) { 15 | if (actual != expected) { 16 | for (size_t i = 0; i < actual.size(); ++i) { 17 | if (actual[i] != expected[i]) { 18 | std::cout << filename << ":" << line << ": " 19 | << "char mismatch: [" << i << "]: " 20 | << actual[i] << " != " << expected[i] 21 | << " ('" << static_cast(actual[i]) << "' != '" 22 | << static_cast(expected[i]) << "')" 23 | << std::endl; 24 | } 25 | } 26 | } 27 | } 28 | 29 | #define CHECK_BUF(actual, ...) (checkBuf((actual), __VA_ARGS__, __FILE__, __LINE__)) 30 | 31 | int main() { 32 | Worker w; 33 | 34 | std::array readBuf; 35 | const std::wstring kNul = std::wstring(L"", 1); 36 | const std::array kJunk = { 37 | '1', '2', '3', '4', '5', '6', '7', '8', 38 | '9', '0', 'A', 'B', 'C', 'D', 'E', 'F', 39 | }; 40 | 41 | for (auto inputStr : { 42 | std::wstring(L""), 43 | std::wstring(L"a"), 44 | std::wstring(L"ab"), 45 | std::wstring(L"abc"), 46 | std::wstring(L"abcd"), 47 | std::wstring(L"abcde"), 48 | }) { 49 | for (size_t readLen = 0; readLen < 12; ++readLen) { 50 | std::cout << "Testing \"" << narrowString(inputStr) << "\", " 51 | << "reading " << readLen << " chars" << std::endl; 52 | 53 | // Set the title and read it back. 54 | w.setTitle(narrowString(inputStr)); 55 | readBuf = kJunk; 56 | const DWORD retVal = w.titleInternal(readBuf, readLen); 57 | 58 | if (readLen == 0) { 59 | // When passing a buffer size 0, the API returns 0 and leaves 60 | // the buffer untouched. Every OS version does the same thing. 61 | CHECK_EQ(retVal, 0u); 62 | CHECK_BUF(readBuf, kJunk); 63 | continue; 64 | } 65 | 66 | std::wstring expectedWrite; 67 | 68 | if (isAtLeastWin8()) { 69 | expectedWrite = inputStr.substr(0, readLen - 1) + kNul; 70 | 71 | // The call returns the untruncated length. 72 | CHECK_EQ(retVal, inputStr.size()); 73 | } 74 | else if (isAtLeastVista()) { 75 | // Vista and Windows 7 have a bug where the title is instead 76 | // truncated to half the correct number of characters. (i.e. 77 | // The `readlen` is seemingly interpreted as a byte count 78 | // rather than a character count.) The bug isn't present on XP 79 | // or Windows 8. 80 | if (readLen == 1) { 81 | // There is not even room for a NUL terminator, so it's 82 | // just left off. The call still succeeds, though. 83 | expectedWrite = std::wstring(); 84 | } else { 85 | expectedWrite = 86 | inputStr.substr(0, (readLen / 2) - 1) + kNul; 87 | } 88 | 89 | // The call returns the untruncated length. 90 | CHECK_EQ(retVal, inputStr.size()); 91 | } 92 | else { 93 | // Unlike later OSs, XP returns a truncated title length. 94 | // Moreover, whenever it would return 0, either because: 95 | // * the title is blank, and/or 96 | // * the read length is 1 97 | // then XP does not NUL-terminate the buffer. 98 | const size_t truncatedLen = std::min(inputStr.size(), readLen - 1); 99 | if (truncatedLen == 0) { 100 | expectedWrite = std::wstring(); 101 | } else { 102 | expectedWrite = inputStr.substr(0, truncatedLen) + kNul; 103 | } 104 | CHECK_EQ(retVal, truncatedLen); 105 | } 106 | 107 | // I will assume that remaining characters have undefined values, 108 | // but I suspect they're actually unchanged. On the other hand, 109 | // the API must never modify the bytes beyond `readLen`. 110 | auto expected = kJunk; 111 | std::copy(&readBuf[0], &readBuf[readLen], expected.begin()); 112 | std::copy(expectedWrite.begin(), expectedWrite.end(), expected.begin()); 113 | 114 | CHECK_BUF(readBuf, expected); 115 | } 116 | } 117 | 118 | return 0; 119 | } 120 | -------------------------------------------------------------------------------- /src/Win7Bug_InheritHandles.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | trace("----------------------------------"); 5 | Worker p; 6 | p.getStdout().write("<-- origBuffer -->"); 7 | 8 | auto c = p.child(); 9 | auto cb = c.newBuffer(FALSE); 10 | cb.activate(); 11 | cb.write("<-- cb -->"); 12 | c.dumpConsoleHandles(TRUE); 13 | 14 | // Proposed fix: the agent somehow decides it should attach to this 15 | // particular child process. Does that fix the problem? 16 | // 17 | // No, because the child's new buffer was not marked inheritable. If it 18 | // were inheritable, then the parent would "inherit" the handle during 19 | // attach, and both processes would use the same refcount for 20 | // `CloseHandle`. 21 | p.detach(); 22 | p.attach(c); 23 | p.dumpConsoleHandles(TRUE); 24 | auto pb = p.openConout(); 25 | 26 | cb.close(); 27 | 28 | // Demonstrate that pb is an invalid handle. 29 | pb.close(); 30 | 31 | Sleep(300000); 32 | } 33 | -------------------------------------------------------------------------------- /src/Win7Bug_RaceCondition.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | const int SC_CONSOLE_MARK = 0xFFF2; 4 | const int SC_CONSOLE_SELECT_ALL = 0xFFF5; 5 | 6 | int main() { 7 | SpawnParams sp; 8 | sp.bInheritHandles = TRUE; 9 | 10 | trace("----------------------------------"); 11 | Worker p; 12 | p.getStdout().write("<-- origBuffer -->"); 13 | 14 | auto c = p.child(); 15 | auto cb = c.newBuffer(); 16 | cb.activate(); 17 | cb.write("<-- cb -->"); 18 | 19 | // This is what the winpty-agent would want to do: 20 | // - It tries to "freeze" the console with "Select All", which blocks 21 | // WriteConsole but little else. Closing a screen buffer is not 22 | // blocked. 23 | // - Then, winpty wants to get the buffer info, then read screen content. 24 | // - If the child process closes its special screen buffer during the 25 | // scraping, then on Windows 7, conhost can start reading freed memory 26 | // and crash. In this test case, `info2` is frequently garbage. 27 | // Somehow winpty-agent needs to avoid this situation, but options seem 28 | // scarce: 29 | // - The Windows 7 bug only happens with `CloseHandle` AFAICT. If a 30 | // buffer handle goes away implicitly from `FreeConsole` or process 31 | // exit, then the buffer is reference counted properly. If app 32 | // developers avoid closing their buffer handle, winpty can work. 33 | // - Be really careful about when to scrape. Pay close attention to 34 | // the kinds of WinEvents a full-screen app generates just before it 35 | // exits, and try to fast-path everything such that no scraping is 36 | // necessary. 37 | // - Start interfering with the user processes attached to the console. 38 | // - e.g. inject a DLL inside the processes and open CONOUT$, or 39 | // override APIs, etc. 40 | // - Attach to the right console process before opening CONOUT$. If 41 | // that console's buffer handle is inheritable, then opening CONOUT$ 42 | // will then produce a safe handle. 43 | // - Accept a certain amount of unreliability. 44 | SendMessage(p.consoleWindow(), WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0); 45 | auto scrape = p.openConout(); 46 | auto info1 = scrape.screenBufferInfo(); 47 | cb.close(); 48 | Sleep(200); // Helps the test fail more often. 49 | auto info2 = scrape.screenBufferInfo(); 50 | SendMessage(p.consoleWindow(), WM_CHAR, 27, 0x00010001); 51 | 52 | trace("%d %d %d %d", info1.srWindow.Left, info1.srWindow.Top, info1.srWindow.Right, info1.srWindow.Bottom); 53 | trace("%d %d %d %d", info2.srWindow.Left, info2.srWindow.Top, info2.srWindow.Right, info2.srWindow.Bottom); 54 | 55 | Sleep(300000); 56 | } 57 | -------------------------------------------------------------------------------- /src/configure: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (c) 2011-2016 Ryan Prichard 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to 7 | # deal in the Software without restriction, including without limitation the 8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | # sell copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | # IN THE SOFTWARE. 22 | 23 | # 24 | # findTool(desc, commandList) 25 | # 26 | # Searches commandLine for the first command in the PATH and returns it. 27 | # Prints an error and aborts the script if no match is found. 28 | # 29 | FINDTOOL_OUT="" 30 | function findTool { 31 | DESC=$1 32 | OPTIONS=$2 33 | for CMD in ${OPTIONS}; do 34 | if (which $CMD &>/dev/null) then 35 | echo "Found $DESC: $CMD" 36 | FINDTOOL_OUT="$CMD" 37 | return 38 | fi 39 | done 40 | echo "Error: could not find $DESC. One of these should be in your PATH:" 41 | for CMD in ${OPTIONS}; do 42 | echo " * $CMD" 43 | done 44 | exit 1 45 | } 46 | 47 | case $(uname -m) in 48 | i686) 49 | echo 'uname -m identifies an i686 environment.' 50 | CXX=i686-w64-mingw32-g++ 51 | ;; 52 | x86_64) 53 | echo 'uname -m identifies an x86_64 environment.' 54 | CXX=x86_64-w64-mingw32-g++ 55 | ;; 56 | *) 57 | echo 'Error: uname -m did not match either i686 or x86_64.' 58 | exit 1 59 | ;; 60 | esac 61 | 62 | # Search the PATH and pick the first match. 63 | findTool "MinGW G++ compiler" "$CXX" 64 | CXX=$FINDTOOL_OUT 65 | 66 | # Write config.mk. 67 | echo Writing config.mk 68 | echo CXX=$CXX > config.mk 69 | -------------------------------------------------------------------------------- /src/harness/Command.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "FixedSizeString.h" 4 | #include "Spawn.h" 5 | 6 | #include 7 | #include 8 | 9 | struct Command { 10 | enum Kind { 11 | AllocConsole, 12 | AttachConsole, 13 | Close, 14 | CloseQuietly, 15 | DumpConsoleHandles, 16 | DumpStandardHandles, 17 | Duplicate, 18 | Exit, 19 | FreeConsole, 20 | GetConsoleProcessList, 21 | GetConsoleScreenBufferInfo, 22 | GetConsoleSelectionInfo, 23 | GetConsoleTitle, 24 | GetConsoleWindow, 25 | GetHandleInformation, 26 | GetNumberOfConsoleInputEvents, 27 | GetStdin, 28 | GetStderr, 29 | GetStdout, 30 | Hello, 31 | LookupKernelObject, 32 | NewBuffer, 33 | OpenConin, 34 | OpenConout, 35 | ReadConsoleOutput, 36 | ScanForConsoleHandles, 37 | SetConsoleTitle, 38 | SetHandleInformation, 39 | SetScreenBufferSize, 40 | SetStdin, 41 | SetStderr, 42 | SetStdout, 43 | SetActiveBuffer, 44 | SpawnChild, 45 | System, 46 | WriteConsoleOutput, 47 | WriteText, 48 | }; 49 | 50 | // These fields must appear first so that the LookupKernelObject RPC will 51 | // work. This RPC occurs from 32-bit test programs to a 64-bit worker. 52 | // In that case, most of this struct's fields do not have the same 53 | // addresses or sizes. 54 | Kind kind; 55 | struct { 56 | uint32_t pid; 57 | uint32_t handle[2]; 58 | uint32_t kernelObject[2]; 59 | } lookupKernelObject; 60 | 61 | HANDLE handle; 62 | HANDLE targetProcess; 63 | DWORD dword; 64 | BOOL success; 65 | BOOL bInheritHandle; 66 | BOOL writeToEach; 67 | HWND hwnd; 68 | union { 69 | CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo; 70 | CONSOLE_SELECTION_INFO consoleSelectionInfo; 71 | struct { 72 | FixedSizeString<128> spawnName; 73 | SpawnParams spawnParams; 74 | SpawnFailure spawnFailure; 75 | } spawn; 76 | FixedSizeString<1024> writeText; 77 | FixedSizeString<1024> systemText; 78 | std::array consoleTitle; 79 | std::array processList; 80 | struct { 81 | DWORD mask; 82 | DWORD flags; 83 | } setFlags; 84 | struct { 85 | int count; 86 | std::array table; 87 | } scanForConsoleHandles; 88 | struct { 89 | std::array buffer; 90 | COORD bufferSize; 91 | COORD bufferCoord; 92 | SMALL_RECT ioRegion; 93 | } consoleIo; 94 | COORD screenBufferSize; 95 | } u; 96 | }; 97 | -------------------------------------------------------------------------------- /src/harness/Event.cc: -------------------------------------------------------------------------------- 1 | #include "Event.h" 2 | 3 | #include "UnicodeConversions.h" 4 | #include 5 | 6 | Event::Event(const std::string &name) { 7 | // Create manual-reset, not signaled initially. 8 | m_handle = CreateEventW(NULL, TRUE, FALSE, widenString(name).c_str()); 9 | ASSERT(m_handle != NULL); 10 | } 11 | 12 | Event::~Event() { 13 | if (m_handle != NULL) { 14 | CloseHandle(m_handle); 15 | } 16 | } 17 | 18 | void Event::set() { 19 | BOOL ret = SetEvent(m_handle); 20 | ASSERT(ret && "SetEvent failed"); 21 | } 22 | 23 | void Event::reset() { 24 | BOOL ret = ResetEvent(m_handle); 25 | ASSERT(ret && "ResetEvent failed"); 26 | } 27 | 28 | void Event::wait() { 29 | DWORD ret = WaitForSingleObject(m_handle, INFINITE); 30 | ASSERT(ret == WAIT_OBJECT_0 && "WaitForSingleObject failed"); 31 | } 32 | -------------------------------------------------------------------------------- /src/harness/Event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | class Event { 9 | public: 10 | explicit Event(const std::string &name); 11 | ~Event(); 12 | void set(); 13 | void reset(); 14 | void wait(); 15 | 16 | // no copying 17 | Event(const Event &other) = delete; 18 | Event &operator=(const Event &other) = delete; 19 | 20 | // moving is okay 21 | Event(Event &&other) { *this = std::move(other); } 22 | Event &operator=(Event &&other) { 23 | m_handle = other.m_handle; 24 | other.m_handle = NULL; 25 | return *this; 26 | } 27 | 28 | private: 29 | HANDLE m_handle; 30 | }; 31 | -------------------------------------------------------------------------------- /src/harness/FixedSizeString.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | template 10 | struct FixedSizeString { 11 | public: 12 | std::string str() const { 13 | ASSERT(strnlen(data, N) < N); 14 | return std::string(data); 15 | } 16 | 17 | const char *c_str() const { 18 | ASSERT(strnlen(data, N) < N); 19 | return data; 20 | } 21 | 22 | FixedSizeString &operator=(const char *from) { 23 | ASSERT(strlen(from) < N); 24 | strcpy(data, from); 25 | return *this; 26 | } 27 | 28 | FixedSizeString &operator=(const std::string &from) { 29 | ASSERT(from.size() < N); 30 | ASSERT(from.size() == strlen(from.c_str())); 31 | strcpy(data, from.c_str()); 32 | return *this; 33 | } 34 | 35 | private: 36 | char data[N]; 37 | }; 38 | -------------------------------------------------------------------------------- /src/harness/NtHandleQuery.cc: -------------------------------------------------------------------------------- 1 | #include "NtHandleQuery.h" 2 | 3 | #include 4 | #include 5 | 6 | // internal definitions copied from mingw-w64's winternl.h and ntstatus.h. 7 | 8 | #define STATUS_SUCCESS ((NTSTATUS)0x00000000) 9 | #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004) 10 | 11 | typedef enum _SYSTEM_INFORMATION_CLASS { 12 | SystemBasicInformation = 0, 13 | SystemProcessorInformation = 1, 14 | SystemPerformanceInformation = 2, 15 | SystemTimeOfDayInformation = 3, 16 | SystemProcessInformation = 5, 17 | SystemProcessorPerformanceInformation = 8, 18 | SystemHandleInformation = 16, 19 | SystemPagefileInformation = 18, 20 | SystemInterruptInformation = 23, 21 | SystemExceptionInformation = 33, 22 | SystemRegistryQuotaInformation = 37, 23 | SystemLookasideInformation = 45 24 | } SYSTEM_INFORMATION_CLASS; 25 | 26 | typedef NTSTATUS NTAPI NtQuerySystemInformation_Type( 27 | SYSTEM_INFORMATION_CLASS SystemInformationClass, 28 | PVOID SystemInformation, 29 | ULONG SystemInformationLength, 30 | PULONG ReturnLength); 31 | 32 | std::vector queryNtHandles() { 33 | OsModule ntdll(L"ntdll.dll"); 34 | auto funcPtr = ntdll.proc("NtQuerySystemInformation"); 35 | ASSERT(funcPtr != NULL && "NtQuerySystemInformation API is missing"); 36 | auto func = reinterpret_cast(funcPtr); 37 | static std::vector buf(1024); 38 | while (true) { 39 | ULONG returnLength = 0; 40 | auto ret = func( 41 | SystemHandleInformation, 42 | buf.data(), 43 | buf.size(), 44 | &returnLength); 45 | if (ret == STATUS_INFO_LENGTH_MISMATCH) { 46 | buf.resize(buf.size() * 2); 47 | continue; 48 | } else if (ret == STATUS_SUCCESS) { 49 | break; 50 | } else { 51 | trace("Could not query NT handles, status was 0x%x", 52 | static_cast(ret)); 53 | return {}; 54 | } 55 | } 56 | auto &info = *reinterpret_cast(buf.data()); 57 | std::vector ret(info.Count); 58 | std::copy(info.Handle, info.Handle + info.Count, ret.begin()); 59 | return ret; 60 | } 61 | 62 | // Get the ObjectPointer (underlying NT object) for the NT handle. 63 | void *ntHandlePointer(const std::vector &table, 64 | DWORD pid, HANDLE h) { 65 | HANDLE ret = nullptr; 66 | for (auto &entry : table) { 67 | if (entry.OwnerPid == pid && 68 | entry.HandleValue == reinterpret_cast(h)) { 69 | ASSERT(ret == nullptr); 70 | ret = entry.ObjectPointer; 71 | } 72 | } 73 | return ret; 74 | } 75 | -------------------------------------------------------------------------------- /src/harness/NtHandleQuery.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | typedef struct _SYSTEM_HANDLE_ENTRY { 8 | ULONG OwnerPid; 9 | BYTE ObjectType; 10 | BYTE HandleFlags; 11 | USHORT HandleValue; 12 | PVOID ObjectPointer; 13 | ULONG AccessMask; 14 | } SYSTEM_HANDLE_ENTRY, *PSYSTEM_HANDLE_ENTRY; 15 | 16 | typedef struct _SYSTEM_HANDLE_INFORMATION { 17 | ULONG Count; 18 | SYSTEM_HANDLE_ENTRY Handle[1]; 19 | } SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; 20 | 21 | std::vector queryNtHandles(); 22 | void *ntHandlePointer(const std::vector &table, 23 | DWORD pid, HANDLE h); 24 | -------------------------------------------------------------------------------- /src/harness/OsVersion.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | inline std::tuple osversion() { 10 | OSVERSIONINFOW info = { sizeof(info) }; 11 | ASSERT(GetVersionExW(&info)); 12 | return std::make_tuple(info.dwMajorVersion, info.dwMinorVersion); 13 | } 14 | 15 | inline bool isWorkstation() { 16 | OSVERSIONINFOEXW info = { sizeof(info) }; 17 | ASSERT(GetVersionExW(reinterpret_cast(&info))); 18 | return info.wProductType == VER_NT_WORKSTATION; 19 | } 20 | 21 | inline bool isWin7() { 22 | return osversion() == std::make_tuple(6, 1); 23 | } 24 | 25 | inline bool isAtLeastVista() { 26 | return osversion() >= std::make_tuple(6, 0); 27 | } 28 | 29 | inline bool isAtLeastWin7() { 30 | return osversion() >= std::make_tuple(6, 1); 31 | } 32 | 33 | inline bool isAtLeastWin8() { 34 | return osversion() >= std::make_tuple(6, 2); 35 | } 36 | 37 | inline bool isAtLeastWin8_1() { 38 | return osversion() >= std::make_tuple(6, 3); 39 | } 40 | 41 | inline bool isTraditionalConio() { 42 | return !isAtLeastWin8(); 43 | } 44 | 45 | inline bool isModernConio() { 46 | return isAtLeastWin8(); 47 | } 48 | -------------------------------------------------------------------------------- /src/harness/RemoteHandle.cc: -------------------------------------------------------------------------------- 1 | #include "RemoteHandle.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "RemoteWorker.h" 7 | 8 | #include 9 | #include 10 | 11 | RemoteHandle RemoteHandle::dup(HANDLE h, RemoteWorker &target, 12 | BOOL bInheritHandle) { 13 | HANDLE targetHandle; 14 | BOOL success = DuplicateHandle( 15 | GetCurrentProcess(), 16 | h, 17 | target.m_process, 18 | &targetHandle, 19 | 0, bInheritHandle, DUPLICATE_SAME_ACCESS); 20 | ASSERT(success && "DuplicateHandle failed"); 21 | return RemoteHandle(targetHandle, target); 22 | } 23 | 24 | RemoteHandle &RemoteHandle::activate() { 25 | worker().cmd().handle = m_value; 26 | worker().rpc(Command::SetActiveBuffer); 27 | return *this; 28 | } 29 | 30 | void RemoteHandle::write(const std::string &msg) { 31 | worker().cmd().handle = m_value; 32 | worker().cmd().u.writeText = msg; 33 | worker().rpc(Command::WriteText); 34 | } 35 | 36 | void RemoteHandle::close() { 37 | worker().cmd().handle = m_value; 38 | worker().rpc(Command::Close); 39 | } 40 | 41 | RemoteHandle &RemoteHandle::setStdin() { 42 | worker().cmd().handle = m_value; 43 | worker().rpc(Command::SetStdin); 44 | return *this; 45 | } 46 | 47 | RemoteHandle &RemoteHandle::setStdout() { 48 | worker().cmd().handle = m_value; 49 | worker().rpc(Command::SetStdout); 50 | return *this; 51 | } 52 | 53 | RemoteHandle &RemoteHandle::setStderr() { 54 | worker().cmd().handle = m_value; 55 | worker().rpc(Command::SetStderr); 56 | return *this; 57 | } 58 | 59 | RemoteHandle RemoteHandle::dupImpl(RemoteWorker *target, BOOL bInheritHandle) { 60 | HANDLE targetProcessFromSource; 61 | 62 | if (target == nullptr) { 63 | targetProcessFromSource = GetCurrentProcess(); 64 | } else { 65 | // Allow the source worker to see the target worker. 66 | targetProcessFromSource = INVALID_HANDLE_VALUE; 67 | BOOL success = DuplicateHandle( 68 | GetCurrentProcess(), 69 | target->m_process, 70 | worker().m_process, 71 | &targetProcessFromSource, 72 | 0, FALSE, DUPLICATE_SAME_ACCESS); 73 | ASSERT(success && "Process handle duplication failed"); 74 | } 75 | 76 | // Do the user-level duplication in the source process. 77 | worker().cmd().handle = m_value; 78 | worker().cmd().targetProcess = targetProcessFromSource; 79 | worker().cmd().bInheritHandle = bInheritHandle; 80 | worker().rpc(Command::Duplicate); 81 | HANDLE retHandle = worker().cmd().handle; 82 | 83 | if (target != nullptr) { 84 | // Cleanup targetProcessFromSource. 85 | worker().cmd().handle = targetProcessFromSource; 86 | worker().rpc(Command::CloseQuietly); 87 | ASSERT(worker().cmd().success && 88 | "Error closing remote process handle"); 89 | } 90 | 91 | return RemoteHandle(retHandle, target != nullptr ? *target : worker()); 92 | } 93 | 94 | CONSOLE_SCREEN_BUFFER_INFO RemoteHandle::screenBufferInfo() { 95 | CONSOLE_SCREEN_BUFFER_INFO ret; 96 | bool success = tryScreenBufferInfo(&ret); 97 | ASSERT(success && "GetConsoleScreenBufferInfo failed"); 98 | return ret; 99 | } 100 | 101 | bool RemoteHandle::tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info) { 102 | worker().cmd().handle = m_value; 103 | worker().rpc(Command::GetConsoleScreenBufferInfo); 104 | if (worker().cmd().success && info != nullptr) { 105 | *info = worker().cmd().u.consoleScreenBufferInfo; 106 | } 107 | return worker().cmd().success; 108 | } 109 | 110 | DWORD RemoteHandle::flags() { 111 | DWORD ret; 112 | bool success = tryFlags(&ret); 113 | ASSERT(success && "GetHandleInformation failed"); 114 | return ret; 115 | } 116 | 117 | bool RemoteHandle::tryFlags(DWORD *flags) { 118 | worker().cmd().handle = m_value; 119 | worker().rpc(Command::GetHandleInformation); 120 | if (worker().cmd().success && flags != nullptr) { 121 | *flags = worker().cmd().dword; 122 | } 123 | return worker().cmd().success; 124 | } 125 | 126 | void RemoteHandle::setFlags(DWORD mask, DWORD flags) { 127 | bool success = trySetFlags(mask, flags); 128 | ASSERT(success && "SetHandleInformation failed"); 129 | } 130 | 131 | bool RemoteHandle::trySetFlags(DWORD mask, DWORD flags) { 132 | worker().cmd().handle = m_value; 133 | worker().cmd().u.setFlags.mask = mask; 134 | worker().cmd().u.setFlags.flags = flags; 135 | worker().rpc(Command::SetHandleInformation); 136 | return worker().cmd().success; 137 | } 138 | 139 | wchar_t RemoteHandle::firstChar() { 140 | // The "first char" is useful for identifying which output buffer a handle 141 | // refers to. 142 | worker().cmd().handle = m_value; 143 | const SMALL_RECT region = {}; 144 | auto &io = worker().cmd().u.consoleIo; 145 | io.bufferSize = { 1, 1 }; 146 | io.bufferCoord = {}; 147 | io.ioRegion = region; 148 | worker().rpc(Command::ReadConsoleOutput); 149 | ASSERT(worker().cmd().success); 150 | ASSERT(!memcmp(&io.ioRegion, ®ion, sizeof(region))); 151 | return io.buffer[0].Char.UnicodeChar; 152 | } 153 | 154 | RemoteHandle &RemoteHandle::setFirstChar(wchar_t ch) { 155 | // The "first char" is useful for identifying which output buffer a handle 156 | // refers to. 157 | worker().cmd().handle = m_value; 158 | const SMALL_RECT region = {}; 159 | auto &io = worker().cmd().u.consoleIo; 160 | io.buffer[0].Char.UnicodeChar = ch; 161 | io.buffer[0].Attributes = 7; 162 | io.bufferSize = { 1, 1 }; 163 | io.bufferCoord = {}; 164 | io.ioRegion = region; 165 | worker().rpc(Command::WriteConsoleOutput); 166 | ASSERT(worker().cmd().success); 167 | ASSERT(!memcmp(&io.ioRegion, ®ion, sizeof(region))); 168 | return *this; 169 | } 170 | 171 | bool RemoteHandle::tryNumberOfConsoleInputEvents(DWORD *ret) { 172 | worker().cmd().handle = m_value; 173 | worker().rpc(Command::GetNumberOfConsoleInputEvents); 174 | if (worker().cmd().success && ret != nullptr) { 175 | *ret = worker().cmd().dword; 176 | } 177 | return worker().cmd().success; 178 | } 179 | 180 | bool RemoteHandle::trySetScreenBufferSize(COORD size) { 181 | worker().cmd().handle = m_value; 182 | worker().cmd().u.screenBufferSize = size; 183 | worker().rpc(Command::SetScreenBufferSize); 184 | return worker().cmd().success; 185 | } 186 | 187 | std::vector inheritableHandles( 188 | const std::vector &vec) { 189 | std::vector ret; 190 | for (auto h : vec) { 191 | if (h.inheritable()) { 192 | ret.push_back(h); 193 | } 194 | } 195 | return ret; 196 | } 197 | 198 | std::vector handleInts(const std::vector &vec) { 199 | std::vector ret; 200 | for (auto h : vec) { 201 | ret.push_back(reinterpret_cast(h.value())); 202 | } 203 | return ret; 204 | } 205 | 206 | std::vector handleValues(const std::vector &vec) { 207 | std::vector ret; 208 | for (auto h : vec) { 209 | ret.push_back(h.value()); 210 | } 211 | return ret; 212 | } 213 | 214 | // It would make more sense to use a std::tuple here, but it's inconvenient. 215 | std::vector stdHandles(RemoteWorker &worker) { 216 | return { 217 | worker.getStdin(), 218 | worker.getStdout(), 219 | worker.getStderr(), 220 | }; 221 | } 222 | 223 | // It would make more sense to use a std::tuple here, but it's inconvenient. 224 | void setStdHandles(std::vector handles) { 225 | ASSERT(handles.size() == 3); 226 | handles[0].setStdin(); 227 | handles[1].setStdout(); 228 | handles[2].setStderr(); 229 | } 230 | 231 | bool allInheritable(const std::vector &vec) { 232 | return handleValues(vec) == handleValues(inheritableHandles(vec)); 233 | } 234 | -------------------------------------------------------------------------------- /src/harness/RemoteHandle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | class RemoteWorker; 12 | 13 | class RemoteHandle { 14 | friend class RemoteWorker; 15 | 16 | private: 17 | RemoteHandle(HANDLE value, RemoteWorker &worker) : 18 | m_value(value), m_worker(&worker) 19 | { 20 | } 21 | 22 | public: 23 | static RemoteHandle invent(HANDLE h, RemoteWorker &worker) { 24 | return RemoteHandle(h, worker); 25 | } 26 | static RemoteHandle invent(uint64_t h, RemoteWorker &worker) { 27 | return RemoteHandle(reinterpret_cast(h), worker); 28 | } 29 | RemoteHandle &activate(); 30 | void write(const std::string &msg); 31 | void close(); 32 | RemoteHandle &setStdin(); 33 | RemoteHandle &setStdout(); 34 | RemoteHandle &setStderr(); 35 | private: 36 | RemoteHandle dupImpl(RemoteWorker *target, BOOL bInheritHandle); 37 | public: 38 | RemoteHandle dup(RemoteWorker &target, BOOL bInheritHandle=FALSE) { 39 | return dupImpl(&target, bInheritHandle); 40 | } 41 | RemoteHandle dup(BOOL bInheritHandle=FALSE) { 42 | return dupImpl(nullptr, bInheritHandle); 43 | } 44 | static RemoteHandle dup(HANDLE h, RemoteWorker &target, 45 | BOOL bInheritHandle=FALSE); 46 | CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo(); 47 | bool tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info=nullptr); 48 | DWORD flags(); 49 | bool tryFlags(DWORD *flags=nullptr); 50 | void setFlags(DWORD mask, DWORD flags); 51 | bool trySetFlags(DWORD mask, DWORD flags); 52 | bool inheritable() { 53 | return flags() & HANDLE_FLAG_INHERIT; 54 | } 55 | void setInheritable(bool inheritable) { 56 | auto success = trySetInheritable(inheritable); 57 | ASSERT(success && "setInheritable failed"); 58 | } 59 | bool trySetInheritable(bool inheritable) { 60 | return trySetFlags(HANDLE_FLAG_INHERIT, 61 | inheritable ? HANDLE_FLAG_INHERIT : 0); 62 | } 63 | wchar_t firstChar(); 64 | RemoteHandle &setFirstChar(wchar_t ch); 65 | bool tryNumberOfConsoleInputEvents(DWORD *ret=nullptr); 66 | bool trySetScreenBufferSize(COORD size); 67 | HANDLE value() const { return m_value; } 68 | uint64_t uvalue() const { return reinterpret_cast(m_value); } 69 | bool isTraditionalConsole() const { return (uvalue() & 3) == 3; } 70 | RemoteWorker &worker() const { return *m_worker; } 71 | 72 | private: 73 | HANDLE m_value; 74 | RemoteWorker *m_worker; 75 | }; 76 | 77 | std::vector inheritableHandles( 78 | const std::vector &vec); 79 | std::vector handleInts(const std::vector &vec); 80 | std::vector handleValues(const std::vector &vec); 81 | std::vector stdHandles(RemoteWorker &worker); 82 | void setStdHandles(std::vector handles); 83 | bool allInheritable(const std::vector &vec); 84 | -------------------------------------------------------------------------------- /src/harness/RemoteWorker.cc: -------------------------------------------------------------------------------- 1 | #include "RemoteWorker.h" 2 | 3 | #include 4 | 5 | #include "UnicodeConversions.h" 6 | #include "Util.h" 7 | 8 | #include 9 | #include 10 | 11 | DWORD RemoteWorker::dwDefaultCreationFlags = CREATE_NEW_CONSOLE; 12 | 13 | RemoteWorker::RemoteWorker(decltype(DoNotSpawn)) : 14 | m_name(makeTempName("WinptyBufferTests")), 15 | m_parcel(m_name + "-shmem", ShmemParcel::CreateNew), 16 | m_startEvent(m_name + "-start"), 17 | m_finishEvent(m_name + "-finish") 18 | { 19 | m_finishEvent.set(); 20 | } 21 | 22 | RemoteWorker::RemoteWorker(SpawnParams params) : RemoteWorker(DoNotSpawn) { 23 | SpawnFailure dummy; 24 | m_process = spawn(m_name, params, dummy); 25 | ASSERT(m_process != nullptr && "Could not create RemoteWorker"); 26 | m_valid = true; 27 | // Perform an RPC just to ensure that the worker process is ready, and 28 | // the console exists, before returning. 29 | rpc(Command::Hello); 30 | } 31 | 32 | RemoteWorker RemoteWorker::child(SpawnParams params) { 33 | auto ret = tryChild(params); 34 | ASSERT(ret.valid() && "Could not spawn child worker"); 35 | return ret; 36 | } 37 | 38 | RemoteWorker RemoteWorker::tryChild(SpawnParams params, SpawnFailure *failure) { 39 | RemoteWorker ret(DoNotSpawn); 40 | cmd().u.spawn.spawnName = ret.m_name; 41 | cmd().u.spawn.spawnParams = params; 42 | rpc(Command::SpawnChild); 43 | if (cmd().handle == nullptr) { 44 | if (failure != nullptr) { 45 | *failure = cmd().u.spawn.spawnFailure; 46 | } 47 | } else { 48 | BOOL dupSuccess = DuplicateHandle( 49 | m_process, 50 | cmd().handle, 51 | GetCurrentProcess(), 52 | &ret.m_process, 53 | 0, FALSE, DUPLICATE_SAME_ACCESS); 54 | ASSERT(dupSuccess && "RemoteWorker::child: DuplicateHandle failed"); 55 | rpc(Command::CloseQuietly); 56 | ASSERT(cmd().success && "RemoteWorker::child: CloseHandle failed"); 57 | ret.m_valid = true; 58 | // Perform an RPC just to ensure that the worker process is ready, and 59 | // the console exists, before returning. 60 | ret.rpc(Command::Hello); 61 | } 62 | return ret; 63 | } 64 | 65 | void RemoteWorker::exit() { 66 | cmd().dword = 0; 67 | rpcAsync(Command::Exit); 68 | DWORD result = WaitForSingleObject(m_process, INFINITE); 69 | ASSERT(result == WAIT_OBJECT_0 && 70 | "WaitForSingleObject failed while killing worker"); 71 | CloseHandle(m_process); 72 | m_valid = false; 73 | } 74 | 75 | CONSOLE_SELECTION_INFO RemoteWorker::selectionInfo() { 76 | rpc(Command::GetConsoleSelectionInfo); 77 | ASSERT(cmd().success); 78 | return cmd().u.consoleSelectionInfo; 79 | } 80 | 81 | void RemoteWorker::dumpConsoleHandles(BOOL writeToEach) { 82 | cmd().writeToEach = writeToEach; 83 | rpc(Command::DumpConsoleHandles); 84 | } 85 | 86 | std::vector RemoteWorker::scanForConsoleHandles() { 87 | rpc(Command::ScanForConsoleHandles); 88 | auto &rpcTable = cmd().u.scanForConsoleHandles; 89 | std::vector ret; 90 | for (int i = 0; i < rpcTable.count; ++i) { 91 | ret.push_back(RemoteHandle(rpcTable.table[i], *this)); 92 | } 93 | return ret; 94 | } 95 | 96 | bool RemoteWorker::setTitleInternal(const std::wstring &wstr) { 97 | ASSERT(wstr.size() < cmd().u.consoleTitle.size()); 98 | ASSERT(wstr.size() == wcslen(wstr.c_str())); 99 | wcscpy(cmd().u.consoleTitle.data(), wstr.c_str()); 100 | rpc(Command::SetConsoleTitle); 101 | return cmd().success; 102 | } 103 | 104 | std::string RemoteWorker::title() { 105 | std::array buf; 106 | DWORD ret = titleInternal(buf, buf.size()); 107 | ret = std::min(ret, buf.size() - 1); 108 | buf[std::min(buf.size() - 1, ret)] = L'\0'; 109 | return narrowString(std::wstring(buf.data())); 110 | } 111 | 112 | // This API is more low-level than typical, because GetConsoleTitleW is buggy 113 | // in older versions of Windows, and this method is used to test the bugs. 114 | DWORD RemoteWorker::titleInternal(std::array &buf, DWORD bufSize) { 115 | cmd().dword = bufSize; 116 | cmd().u.consoleTitle = buf; 117 | rpc(Command::GetConsoleTitle); 118 | buf = cmd().u.consoleTitle; 119 | return cmd().dword; 120 | } 121 | 122 | std::vector RemoteWorker::consoleProcessList() { 123 | rpc(Command::GetConsoleProcessList); 124 | DWORD count = cmd().dword; 125 | ASSERT(count <= cmd().u.processList.size()); 126 | return std::vector( 127 | &cmd().u.processList[0], 128 | &cmd().u.processList[count]); 129 | } 130 | 131 | uint64_t RemoteWorker::lookupKernelObject(DWORD pid, HANDLE h) { 132 | const uint64_t h64 = reinterpret_cast(h); 133 | cmd().lookupKernelObject.pid = pid; 134 | memcpy(&cmd().lookupKernelObject.handle, &h64, sizeof(h64)); 135 | rpc(Command::LookupKernelObject); 136 | uint64_t ret; 137 | memcpy(&ret, &cmd().lookupKernelObject.kernelObject, sizeof(ret)); 138 | return ret; 139 | } 140 | 141 | void RemoteWorker::rpc(Command::Kind kind) { 142 | rpcImpl(kind); 143 | m_finishEvent.wait(); 144 | } 145 | 146 | void RemoteWorker::rpcAsync(Command::Kind kind) { 147 | rpcImpl(kind); 148 | } 149 | 150 | void RemoteWorker::rpcImpl(Command::Kind kind) { 151 | ASSERT(m_valid && "Cannot perform an RPC on an invalid RemoteWorker"); 152 | m_finishEvent.wait(); 153 | m_finishEvent.reset(); 154 | cmd().kind = kind; 155 | m_startEvent.set(); 156 | } 157 | -------------------------------------------------------------------------------- /src/harness/RemoteWorker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "Command.h" 9 | #include "Event.h" 10 | #include "RemoteHandle.h" 11 | #include "ShmemParcel.h" 12 | #include "Spawn.h" 13 | #include "UnicodeConversions.h" 14 | 15 | class RemoteWorker { 16 | friend class RemoteHandle; 17 | friend uint64_t wow64LookupKernelObject(DWORD pid, HANDLE handle); 18 | static DWORD dwDefaultCreationFlags; 19 | 20 | public: 21 | static void setDefaultCreationFlags(DWORD flags) { 22 | dwDefaultCreationFlags = flags; 23 | } 24 | static DWORD defaultCreationFlags() { 25 | return dwDefaultCreationFlags; 26 | } 27 | 28 | public: 29 | struct {} static constexpr DoNotSpawn = {}; 30 | explicit RemoteWorker(decltype(DoNotSpawn)); 31 | explicit RemoteWorker() : RemoteWorker({false, dwDefaultCreationFlags}) {} 32 | explicit RemoteWorker(SpawnParams params); 33 | RemoteWorker child(SpawnParams params={}); 34 | RemoteWorker tryChild(SpawnParams params={}, SpawnFailure *failure=nullptr); 35 | ~RemoteWorker() { cleanup(); } 36 | bool valid() { return m_valid; } 37 | void exit(); 38 | private: 39 | void cleanup() { if (m_valid) { exit(); } } 40 | public: 41 | 42 | // basic worker info 43 | HANDLE processHandle() { return m_process; } 44 | DWORD pid() { return GetProcessId(m_process); } 45 | 46 | // allow moving 47 | RemoteWorker(RemoteWorker &&other) : 48 | m_valid(std::move(other.m_valid)), 49 | m_name(std::move(other.m_name)), 50 | m_parcel(std::move(other.m_parcel)), 51 | m_startEvent(std::move(other.m_startEvent)), 52 | m_finishEvent(std::move(other.m_finishEvent)), 53 | m_process(std::move(other.m_process)) 54 | { 55 | other.m_valid = false; 56 | other.m_process = nullptr; 57 | } 58 | RemoteWorker &operator=(RemoteWorker &&other) { 59 | cleanup(); 60 | m_valid = std::move(other.m_valid); 61 | m_name = std::move(other.m_name); 62 | m_parcel = std::move(other.m_parcel); 63 | m_startEvent = std::move(other.m_startEvent); 64 | m_finishEvent = std::move(other.m_finishEvent); 65 | m_process = std::move(other.m_process); 66 | other.m_valid = false; 67 | other.m_process = nullptr; 68 | return *this; 69 | } 70 | 71 | // Commands 72 | RemoteHandle getStdin() { rpc(Command::GetStdin); return RemoteHandle(cmd().handle, *this); } 73 | RemoteHandle getStdout() { rpc(Command::GetStdout); return RemoteHandle(cmd().handle, *this); } 74 | RemoteHandle getStderr() { rpc(Command::GetStderr); return RemoteHandle(cmd().handle, *this); } 75 | bool detach() { rpc(Command::FreeConsole); return cmd().success; } 76 | bool attach(RemoteWorker &worker) { cmd().dword = GetProcessId(worker.m_process); rpc(Command::AttachConsole); return cmd().success; } 77 | bool alloc() { rpc(Command::AllocConsole); return cmd().success; } 78 | void dumpStandardHandles() { rpc(Command::DumpStandardHandles); } 79 | int system(const std::string &arg) { cmd().u.systemText = arg; rpc(Command::System); return cmd().dword; } 80 | HWND consoleWindow() { rpc(Command::GetConsoleWindow); return cmd().hwnd; } 81 | 82 | CONSOLE_SELECTION_INFO selectionInfo(); 83 | void dumpConsoleHandles(BOOL writeToEach=FALSE); 84 | std::vector scanForConsoleHandles(); 85 | void setTitle(const std::string &str) { auto b = setTitleInternal(widenString(str)); ASSERT(b && "setTitle failed"); } 86 | bool setTitleInternal(const std::wstring &str); 87 | std::string title(); 88 | DWORD titleInternal(std::array &buf, DWORD bufSize); 89 | std::vector consoleProcessList(); 90 | 91 | RemoteHandle openConin(BOOL bInheritHandle=FALSE) { 92 | cmd().bInheritHandle = bInheritHandle; 93 | rpc(Command::OpenConin); 94 | return RemoteHandle(cmd().handle, *this); 95 | } 96 | 97 | RemoteHandle openConout(BOOL bInheritHandle=FALSE) { 98 | cmd().bInheritHandle = bInheritHandle; 99 | rpc(Command::OpenConout); 100 | return RemoteHandle(cmd().handle, *this); 101 | } 102 | 103 | RemoteHandle newBuffer(BOOL bInheritHandle=FALSE, wchar_t firstChar=L'\0') { 104 | cmd().bInheritHandle = bInheritHandle; 105 | rpc(Command::NewBuffer); 106 | auto h = RemoteHandle(cmd().handle, *this); 107 | if (firstChar != L'\0') { 108 | h.setFirstChar(firstChar); 109 | } 110 | return h; 111 | } 112 | 113 | private: 114 | uint64_t lookupKernelObject(DWORD pid, HANDLE h); 115 | 116 | private: 117 | Command &cmd() { return m_parcel.value()[0]; } 118 | void rpc(Command::Kind kind); 119 | void rpcAsync(Command::Kind kind); 120 | void rpcImpl(Command::Kind kind); 121 | 122 | private: 123 | bool m_valid = false; 124 | std::string m_name; 125 | 126 | // HACK: Use Command[2] instead of Command. To accommodate WOW64, we need 127 | // to have a 32-bit test program communicate with a 64-bit worker to query 128 | // kernel handles. The sizes of the parcels will not match, but it's 129 | // mostly OK as long as the creation size is larger than the open size, and 130 | // the 32-bit program creates the parcel. In principle, a better fix might 131 | // be to use parcels of different sizes or make the Command struct's size 132 | // independent of architecture, but those changes are hard. 133 | ShmemParcelTyped m_parcel; 134 | 135 | Event m_startEvent; 136 | Event m_finishEvent; 137 | HANDLE m_process = NULL; 138 | }; 139 | -------------------------------------------------------------------------------- /src/harness/ShmemParcel.cc: -------------------------------------------------------------------------------- 1 | #include "ShmemParcel.h" 2 | 3 | #include "UnicodeConversions.h" 4 | #include 5 | #include 6 | 7 | ShmemParcel::ShmemParcel( 8 | const std::string &name, 9 | CreationDisposition disposition, 10 | size_t size) 11 | { 12 | if (disposition == CreateNew) { 13 | SetLastError(0); 14 | m_hfile = CreateFileMappingW( 15 | INVALID_HANDLE_VALUE, 16 | NULL, 17 | PAGE_READWRITE, 18 | 0, 19 | size, 20 | widenString(name).c_str()); 21 | ASSERT(m_hfile != NULL && GetLastError() == 0 && 22 | "Failed to create shared memory"); 23 | } else if (disposition == OpenExisting) { 24 | m_hfile = OpenFileMappingW( 25 | FILE_MAP_ALL_ACCESS, 26 | FALSE, 27 | widenString(name).c_str()); 28 | ASSERT(m_hfile != NULL && "Failed to open shared memory"); 29 | } else { 30 | ASSERT(false && "Invalid disposition value"); 31 | } 32 | m_view = MapViewOfFile(m_hfile, FILE_MAP_ALL_ACCESS, 0, 0, size); 33 | ASSERT(m_view != NULL && "Failed to map view of shared memory to create it"); 34 | } 35 | 36 | ShmemParcel::~ShmemParcel() 37 | { 38 | if (m_hfile != NULL) { 39 | UnmapViewOfFile(m_view); 40 | CloseHandle(m_hfile); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/harness/ShmemParcel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | class ShmemParcel { 10 | public: 11 | enum CreationDisposition { 12 | CreateNew, 13 | OpenExisting, 14 | }; 15 | 16 | public: 17 | ShmemParcel( 18 | const std::string &name, 19 | CreationDisposition disposition, 20 | size_t size); 21 | 22 | ~ShmemParcel(); 23 | 24 | // no copying 25 | ShmemParcel(const ShmemParcel &other) = delete; 26 | ShmemParcel &operator=(const ShmemParcel &other) = delete; 27 | 28 | // moving is okay 29 | ShmemParcel(ShmemParcel &&other) { 30 | *this = std::move(other); 31 | } 32 | ShmemParcel &operator=(ShmemParcel &&other) { 33 | m_hfile = other.m_hfile; 34 | m_view = other.m_view; 35 | other.m_hfile = NULL; 36 | other.m_view = NULL; 37 | return *this; 38 | } 39 | 40 | void *view() { return m_view; } 41 | 42 | private: 43 | HANDLE m_hfile; 44 | void *m_view; 45 | }; 46 | 47 | template 48 | class ShmemParcelTyped { 49 | public: 50 | ShmemParcelTyped( 51 | const std::string &name, 52 | ShmemParcel::CreationDisposition disposition) : 53 | m_parcel(name, disposition, sizeof(T)) 54 | { 55 | } 56 | 57 | T &value() { return *static_cast(m_parcel.view()); } 58 | 59 | private: 60 | ShmemParcel m_parcel; 61 | }; 62 | -------------------------------------------------------------------------------- /src/harness/Spawn.cc: -------------------------------------------------------------------------------- 1 | #include "Spawn.h" 2 | 3 | #include 4 | 5 | #include "UnicodeConversions.h" 6 | #include "Util.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace { 13 | 14 | static std::vector wstrToWVector(const std::wstring &str) { 15 | std::vector ret; 16 | ret.resize(str.size() + 1); 17 | wmemcpy(ret.data(), str.c_str(), str.size() + 1); 18 | return ret; 19 | } 20 | 21 | } // anonymous namespace 22 | 23 | HANDLE spawn(const std::string &workerName, 24 | const SpawnParams ¶ms, 25 | SpawnFailure &error) { 26 | auto exeBaseName = (isWow64() && params.nativeWorkerBitness) 27 | ? "Worker64.exe" // always 64-bit binary, used to escape WOW64 environ 28 | : "Worker.exe"; // 32 or 64-bit binary, same arch as test program 29 | auto workerPath = 30 | pathDirName(getModuleFileName(NULL)) + "\\" + exeBaseName; 31 | const std::wstring workerPathWStr = widenString(workerPath); 32 | const std::string cmdLine = "\"" + workerPath + "\" " + workerName; 33 | auto cmdLineWVec = wstrToWVector(widenString(cmdLine)); 34 | 35 | STARTUPINFOEXW suix = { params.sui }; 36 | ASSERT(suix.StartupInfo.cb == sizeof(STARTUPINFOW) || 37 | suix.StartupInfo.cb == sizeof(STARTUPINFOEXW)); 38 | std::unique_ptr attrListBuffer; 39 | auto inheritList = params.inheritList; 40 | 41 | OsModule kernel32(L"kernel32.dll"); 42 | #define DECL_API_FUNC(name) decltype(name) *p##name = nullptr; 43 | DECL_API_FUNC(InitializeProcThreadAttributeList); 44 | DECL_API_FUNC(UpdateProcThreadAttribute); 45 | DECL_API_FUNC(DeleteProcThreadAttributeList); 46 | #undef DECL_API_FUNC 47 | 48 | struct AttrList { 49 | decltype(DeleteProcThreadAttributeList) *cleanup = nullptr; 50 | LPPROC_THREAD_ATTRIBUTE_LIST v = nullptr; 51 | ~AttrList() { 52 | if (v != nullptr) { 53 | ASSERT(cleanup != nullptr); 54 | cleanup(v); 55 | } 56 | } 57 | } attrList; 58 | 59 | if (params.inheritCount != SpawnParams::NoInheritList) { 60 | // Add PROC_THREAD_ATTRIBUTE_HANDLE_LIST to the STARTUPINFOEX. Use 61 | // dynamic binding, because this code must run on XP, which does not 62 | // have this functionality. 63 | ASSERT(params.inheritCount < params.inheritList.size()); 64 | #define GET_API_FUNC(name) p##name = reinterpret_cast(kernel32.proc(#name)); 65 | GET_API_FUNC(InitializeProcThreadAttributeList); 66 | GET_API_FUNC(UpdateProcThreadAttribute); 67 | GET_API_FUNC(DeleteProcThreadAttributeList); 68 | #undef GET_API_FUNC 69 | if (pInitializeProcThreadAttributeList == nullptr || 70 | pUpdateProcThreadAttribute == nullptr || 71 | pDeleteProcThreadAttributeList == nullptr) { 72 | trace("Error: skipping PROC_THREAD_ATTRIBUTE_HANDLE_LIST " 73 | "due to missing APIs"); 74 | } else { 75 | SIZE_T bufferSize = 0; 76 | auto success = pInitializeProcThreadAttributeList( 77 | NULL, 1, 0, &bufferSize); 78 | if (!success && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { 79 | // The InitializeProcThreadAttributeList API "fails" with 80 | // ERROR_INSUFFICIENT_BUFFER. 81 | success = TRUE; 82 | } 83 | ASSERT(success && 84 | "First InitializeProcThreadAttributeList call failed"); 85 | attrListBuffer = std::unique_ptr(new char[bufferSize]); 86 | attrList.cleanup = pDeleteProcThreadAttributeList; 87 | suix.lpAttributeList = attrList.v = 88 | reinterpret_cast( 89 | attrListBuffer.get()); 90 | success = pInitializeProcThreadAttributeList( 91 | suix.lpAttributeList, 1, 0, &bufferSize); 92 | ASSERT(success && 93 | "Second InitializeProcThreadAttributeList call failed"); 94 | success = pUpdateProcThreadAttribute( 95 | suix.lpAttributeList, 0, 96 | PROC_THREAD_ATTRIBUTE_HANDLE_LIST, 97 | inheritList.data(), 98 | params.inheritCount * sizeof(HANDLE), 99 | nullptr, nullptr); 100 | if (!success) { 101 | error.kind = SpawnFailure::UpdateProcThreadAttribute; 102 | error.errCode = GetLastError(); 103 | trace("UpdateProcThreadAttribute failed: %s", 104 | errorString(error.errCode).c_str()); 105 | return nullptr; 106 | } 107 | } 108 | } 109 | 110 | PROCESS_INFORMATION pi; 111 | memset(&pi, 0, sizeof(pi)); 112 | 113 | auto success = CreateProcessW(workerPathWStr.c_str(), cmdLineWVec.data(), 114 | NULL, NULL, 115 | /*bInheritHandles=*/params.bInheritHandles, 116 | /*dwCreationFlags=*/params.dwCreationFlags, 117 | NULL, NULL, 118 | &suix.StartupInfo, &pi); 119 | if (!success) { 120 | error.kind = SpawnFailure::CreateProcess; 121 | error.errCode = GetLastError(); 122 | trace("CreateProcessW failed: %s", 123 | errorString(error.errCode).c_str()); 124 | return nullptr; 125 | } 126 | 127 | error.kind = SpawnFailure::Success; 128 | error.errCode = 0; 129 | CloseHandle(pi.hThread); 130 | return pi.hProcess; 131 | } 132 | -------------------------------------------------------------------------------- /src/harness/Spawn.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "RemoteHandle.h" 8 | 9 | struct SpawnParams { 10 | BOOL bInheritHandles = FALSE; 11 | DWORD dwCreationFlags = 0; 12 | STARTUPINFOW sui = { sizeof(STARTUPINFOW), 0 }; 13 | static const size_t NoInheritList = static_cast(~0); 14 | size_t inheritCount = NoInheritList; 15 | std::array inheritList = {}; 16 | bool nativeWorkerBitness = false; 17 | 18 | SpawnParams(bool bInheritHandles=false, DWORD dwCreationFlags=0) : 19 | bInheritHandles(bInheritHandles), 20 | dwCreationFlags(dwCreationFlags) 21 | { 22 | } 23 | 24 | SpawnParams(bool bInheritHandles, DWORD dwCreationFlags, 25 | std::vector stdHandles) : 26 | bInheritHandles(bInheritHandles), 27 | dwCreationFlags(dwCreationFlags) 28 | { 29 | ASSERT(stdHandles.size() == 3); 30 | sui.dwFlags |= STARTF_USESTDHANDLES; 31 | sui.hStdInput = stdHandles[0].value(); 32 | sui.hStdOutput = stdHandles[1].value(); 33 | sui.hStdError = stdHandles[2].value(); 34 | } 35 | }; 36 | 37 | struct SpawnFailure { 38 | enum Kind { Success, CreateProcess, UpdateProcThreadAttribute }; 39 | Kind kind = Success; 40 | DWORD errCode = 0; 41 | }; 42 | 43 | HANDLE spawn(const std::string &workerName, 44 | const SpawnParams ¶ms, 45 | SpawnFailure &error); 46 | -------------------------------------------------------------------------------- /src/harness/TestCommon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "NtHandleQuery.h" 19 | #include "OsVersion.h" 20 | #include "RemoteHandle.h" 21 | #include "RemoteWorker.h" 22 | #include "TestUtil.h" 23 | #include "UnicodeConversions.h" 24 | #include "Util.h" 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | using Handle = RemoteHandle; 31 | using Worker = RemoteWorker; 32 | -------------------------------------------------------------------------------- /src/harness/TestUtil.cc: -------------------------------------------------------------------------------- 1 | #include "TestUtil.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "OsVersion.h" 10 | #include "NtHandleQuery.h" 11 | #include "RemoteHandle.h" 12 | #include "RemoteWorker.h" 13 | #include "UnicodeConversions.h" 14 | #include "Util.h" 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | static RegistrationTable *g_testFunctions; 21 | static std::unordered_set g_testFailures; 22 | 23 | void printTestName(const std::string &name) { 24 | trace("----------------------------------------------------------"); 25 | trace("%s", name.c_str()); 26 | printf("%s\n", name.c_str()); 27 | fflush(stdout); 28 | } 29 | 30 | void recordFailure(const std::string &name) { 31 | g_testFailures.insert(name); 32 | } 33 | 34 | std::vector failedTests() { 35 | std::vector ret(g_testFailures.begin(), g_testFailures.end()); 36 | std::sort(ret.begin(), ret.end()); 37 | return ret; 38 | } 39 | 40 | void registerTest(const std::string &name, bool (&cond)(), void (&func)()) { 41 | if (g_testFunctions == nullptr) { 42 | g_testFunctions = new RegistrationTable {}; 43 | } 44 | for (auto &entry : *g_testFunctions) { 45 | // I think the compiler catches duplicates already, but just in case. 46 | ASSERT(&cond != std::get<1>(entry) || &func != std::get<2>(entry)); 47 | } 48 | g_testFunctions->push_back(std::make_tuple(name, &cond, &func)); 49 | } 50 | 51 | RegistrationTable registeredTests() { 52 | return *g_testFunctions; 53 | } 54 | 55 | static bool hasBuiltinCompareObjectHandles() { 56 | static auto kernelbase = LoadLibraryW(L"KernelBase.dll"); 57 | if (kernelbase != nullptr) { 58 | static auto proc = GetProcAddress(kernelbase, "CompareObjectHandles"); 59 | if (proc != nullptr) { 60 | return true; 61 | } 62 | } 63 | return false; 64 | } 65 | 66 | static bool needsWow64HandleLookup() { 67 | // The Worker.exe and the test programs must always be the same bitness. 68 | // However, in WOW64 mode, prior to Windows 7 64-bit, the WOW64 version of 69 | // NtQuerySystemInformation returned almost no handle information. Even 70 | // in Windows 7, the pointers are truncated to 32-bits, so for maximum 71 | // reliability, use the RPC technique there too. Windows 10 has a proper 72 | // API. 73 | static bool value = isWow64(); 74 | return value; 75 | } 76 | 77 | static RemoteWorker makeLookupWorker() { 78 | SpawnParams sp(false, DETACHED_PROCESS); 79 | sp.nativeWorkerBitness = true; 80 | return RemoteWorker(sp); 81 | } 82 | 83 | uint64_t wow64LookupKernelObject(DWORD pid, HANDLE handle) { 84 | static auto lookupWorker = makeLookupWorker(); 85 | return lookupWorker.lookupKernelObject(pid, handle); 86 | } 87 | 88 | static bool builtinCompareObjectHandles(RemoteHandle h1, RemoteHandle h2) { 89 | static OsModule kernelbase(L"KernelBase.dll"); 90 | static auto comp = 91 | reinterpret_cast( 92 | kernelbase.proc("CompareObjectHandles")); 93 | ASSERT(comp != nullptr); 94 | HANDLE h1local = nullptr; 95 | HANDLE h2local = nullptr; 96 | bool dup1 = DuplicateHandle( 97 | h1.worker().processHandle(), 98 | h1.value(), 99 | GetCurrentProcess(), 100 | &h1local, 101 | 0, false, DUPLICATE_SAME_ACCESS); 102 | bool dup2 = DuplicateHandle( 103 | h2.worker().processHandle(), 104 | h2.value(), 105 | GetCurrentProcess(), 106 | &h2local, 107 | 0, false, DUPLICATE_SAME_ACCESS); 108 | bool ret = dup1 && dup2 && comp(h1local, h2local); 109 | if (dup1) { 110 | CloseHandle(h1local); 111 | } 112 | if (dup2) { 113 | CloseHandle(h2local); 114 | } 115 | return ret; 116 | } 117 | 118 | bool compareObjectHandles(RemoteHandle h1, RemoteHandle h2) { 119 | ObjectSnap snap; 120 | return snap.eq(h1, h2); 121 | } 122 | 123 | ObjectSnap::ObjectSnap() { 124 | if (!hasBuiltinCompareObjectHandles() && !needsWow64HandleLookup()) { 125 | m_table = queryNtHandles(); 126 | m_hasTable = true; 127 | } 128 | } 129 | 130 | uint64_t ObjectSnap::object(RemoteHandle h) { 131 | if (needsWow64HandleLookup()) { 132 | return wow64LookupKernelObject(h.worker().pid(), h.value()); 133 | } 134 | if (!m_hasTable) { 135 | m_table = queryNtHandles(); 136 | } 137 | return reinterpret_cast(ntHandlePointer( 138 | m_table, h.worker().pid(), h.value())); 139 | } 140 | 141 | bool ObjectSnap::eq(std::initializer_list handles) { 142 | if (handles.size() < 2) { 143 | return true; 144 | } 145 | if (hasBuiltinCompareObjectHandles()) { 146 | for (auto i = handles.begin() + 1; i < handles.end(); ++i) { 147 | if (!builtinCompareObjectHandles(*handles.begin(), *i)) { 148 | return false; 149 | } 150 | } 151 | } else { 152 | auto first = object(*handles.begin()); 153 | for (auto i = handles.begin() + 1; i < handles.end(); ++i) { 154 | if (first != object(*i)) { 155 | return false; 156 | } 157 | } 158 | } 159 | return true; 160 | } 161 | 162 | std::tuple newPipe( 163 | RemoteWorker &w, BOOL inheritable) { 164 | HANDLE readPipe, writePipe; 165 | auto ret = CreatePipe(&readPipe, &writePipe, NULL, 0); 166 | ASSERT(ret && "CreatePipe failed"); 167 | auto p1 = RemoteHandle::dup(readPipe, w, inheritable); 168 | auto p2 = RemoteHandle::dup(writePipe, w, inheritable); 169 | trace("Opened pipe in pid %u: rh=0x%I64x wh=0x%I64x", 170 | w.pid(), p1.uvalue(), p2.uvalue()); 171 | CloseHandle(readPipe); 172 | CloseHandle(writePipe); 173 | return std::make_tuple(p1, p2); 174 | } 175 | 176 | std::string windowText(HWND hwnd) { 177 | std::array buf; 178 | DWORD ret = GetWindowTextW(hwnd, buf.data(), buf.size()); 179 | ASSERT(ret >= 0 && ret <= buf.size() - 1); 180 | buf[ret] = L'\0'; 181 | return narrowString(std::wstring(buf.data())); 182 | } 183 | 184 | // Verify that the process' open console handle set is as expected from 185 | // attaching to a new console. 186 | // * The set of console handles is exactly (0x3, 0x7, 0xb). 187 | // * The console handles are inheritable. 188 | void checkInitConsoleHandleSet(RemoteWorker &proc) { 189 | CHECK(isTraditionalConio() && "checkInitConsoleHandleSet is not valid " 190 | "with modern conio"); 191 | auto actualHandles = proc.scanForConsoleHandles(); 192 | auto correctHandles = std::vector { 0x3, 0x7, 0xb }; 193 | if (handleInts(actualHandles) == correctHandles && 194 | allInheritable(actualHandles)) { 195 | return; 196 | } 197 | proc.dumpConsoleHandles(); 198 | CHECK(false && "checkInitConsoleHandleSet failed"); 199 | } 200 | 201 | // Verify that the child's open console handle set is as expected from having 202 | // just attached to or spawned from a source worker. 203 | // * The set of child handles should exactly match the set of inheritable 204 | // source handles. 205 | // * Every open child handle should be inheritable. 206 | void checkInitConsoleHandleSet(RemoteWorker &child, RemoteWorker &source) { 207 | ASSERT(isTraditionalConio() && "checkInitConsoleHandleSet is not valid " 208 | "with modern conio"); 209 | auto cvec = child.scanForConsoleHandles(); 210 | auto cvecInherit = inheritableHandles(cvec); 211 | auto svecInherit = inheritableHandles(source.scanForConsoleHandles()); 212 | auto hv = &handleValues; 213 | if (hv(cvecInherit) == hv(svecInherit) && allInheritable(cvec)) { 214 | return; 215 | } 216 | source.dumpConsoleHandles(); 217 | child.dumpConsoleHandles(); 218 | CHECK(false && "checkInitConsoleHandleSet failed"); 219 | } 220 | 221 | // Returns true if the handle is a "usable console handle": 222 | // * The handle must be open. 223 | // * It must be a console handle. 224 | // * The process must have an attached console. 225 | // * With modern conio, the handle must be "unbound" or bound to the 226 | // currently attached console. 227 | bool isUsableConsoleHandle(RemoteHandle h) { 228 | // XXX: It would be more efficient/elegant to use GetConsoleMode instead. 229 | return h.tryNumberOfConsoleInputEvents() || h.tryScreenBufferInfo(); 230 | } 231 | 232 | bool isUsableConsoleInputHandle(RemoteHandle h) { 233 | return h.tryNumberOfConsoleInputEvents(); 234 | } 235 | 236 | bool isUsableConsoleOutputHandle(RemoteHandle h) { 237 | return h.tryScreenBufferInfo(); 238 | } 239 | 240 | bool isUnboundConsoleObject(RemoteHandle h) { 241 | // XXX: Consider what happens here with NULL, INVALID_HANDLE_OBJECT, junk, 242 | // etc. I *think* it should work. 243 | ASSERT(isModernConio() && "isUnboundConsoleObject is not valid with " 244 | "traditional conio"); 245 | static RemoteWorker other{ SpawnParams {false, CREATE_NO_WINDOW} }; 246 | auto dup = h.dup(other); 247 | bool ret = isUsableConsoleHandle(dup); 248 | dup.close(); 249 | return ret; 250 | } 251 | 252 | // Verify that an optional subset of the STDIN/STDOUT/STDERR standard 253 | // handles are new handles referring to new Unbound console objects. 254 | void checkModernConsoleHandleInit(RemoteWorker &proc, 255 | bool in, bool out, bool err) { 256 | // List all the usable console handles that weren't just opened. 257 | std::vector preExistingHandles; 258 | for (auto h : proc.scanForConsoleHandles()) { 259 | if ((in && h.value() == proc.getStdin().value()) || 260 | (out && h.value() == proc.getStdout().value()) || 261 | (err && h.value() == proc.getStderr().value())) { 262 | continue; 263 | } 264 | preExistingHandles.push_back(h); 265 | } 266 | ObjectSnap snap; 267 | auto checkNonReuse = [&](RemoteHandle h) { 268 | // The Unbound console objects that were just opened should not be 269 | // inherited from anywhere else -- they should be brand new objects. 270 | for (auto other : preExistingHandles) { 271 | CHECK(!snap.eq(h, other)); 272 | } 273 | }; 274 | 275 | if (in) { 276 | CHECK(isUsableConsoleInputHandle(proc.getStdin())); 277 | CHECK(isUnboundConsoleObject(proc.getStdin())); 278 | checkNonReuse(proc.getStdin()); 279 | } 280 | if (out) { 281 | CHECK(isUsableConsoleOutputHandle(proc.getStdout())); 282 | CHECK(isUnboundConsoleObject(proc.getStdout())); 283 | checkNonReuse(proc.getStdout()); 284 | } 285 | if (err) { 286 | CHECK(isUsableConsoleOutputHandle(proc.getStderr())); 287 | CHECK(isUnboundConsoleObject(proc.getStderr())); 288 | checkNonReuse(proc.getStderr()); 289 | } 290 | if (out && err) { 291 | ObjectSnap snap; 292 | CHECK(proc.getStdout().value() != proc.getStderr().value()); 293 | CHECK(snap.eq(proc.getStdout(), proc.getStderr())); 294 | } 295 | } 296 | 297 | // Wrapper around RemoteWorker::child that does the bare minimum to use an 298 | // inherit list. 299 | // 300 | // If `dummyPipeInInheritList` is true, it also creates an inheritable pipe, 301 | // closes one end, and specifies the other end in an inherit list. It closes 302 | // the final pipe end in the parent and child before returning. 303 | // 304 | // This function is useful for testing the modern bInheritHandles=TRUE handle 305 | // duplication functionality. 306 | // 307 | RemoteWorker childWithDummyInheritList(RemoteWorker &p, SpawnParams sp, 308 | bool dummyPipeInInheritList) { 309 | sp.bInheritHandles = true; 310 | sp.dwCreationFlags |= EXTENDED_STARTUPINFO_PRESENT; 311 | sp.sui.cb = sizeof(STARTUPINFOEXW); 312 | sp.inheritCount = 1; 313 | 314 | if (dummyPipeInInheritList) { 315 | auto pipe = newPipe(p, true); 316 | std::get<0>(pipe).close(); 317 | auto dummy = std::get<1>(pipe); 318 | sp.inheritList = { dummy.value() }; 319 | auto c = p.child(sp); 320 | RemoteHandle::invent(dummy.value(), c).close(); 321 | dummy.close(); 322 | return c; 323 | } else { 324 | sp.inheritList = { NULL }; 325 | return p.child(sp); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/harness/TestUtil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "NtHandleQuery.h" 11 | #include "RemoteHandle.h" 12 | #include "Spawn.h" 13 | 14 | class RemoteWorker; 15 | 16 | #define CHECK(cond) \ 17 | do { \ 18 | if (!(cond)) { \ 19 | recordFailure(__FUNCTION__); \ 20 | trace("%s:%d: ERROR: check failed: " #cond, __FILE__, __LINE__); \ 21 | std::cout << __FILE__ << ":" << __LINE__ \ 22 | << (": ERROR: check failed: " #cond) \ 23 | << std::endl; \ 24 | } \ 25 | } while(0) 26 | 27 | #define CHECK_EQ(actual, expected) \ 28 | do { \ 29 | auto a = (actual); \ 30 | auto e = (expected); \ 31 | if (a != e) { \ 32 | recordFailure(__FUNCTION__); \ 33 | trace("%s:%d: ERROR: check failed " \ 34 | "(" #actual " != " #expected ")", __FILE__, __LINE__); \ 35 | std::cout << __FILE__ << ":" << __LINE__ \ 36 | << ": ERROR: check failed " \ 37 | << ("(" #actual " != " #expected "): ") \ 38 | << a << " != " << e \ 39 | << std::endl; \ 40 | } \ 41 | } while(0) 42 | 43 | #define REGISTER(name, cond) \ 44 | static void name(); \ 45 | int g_register_ ## cond ## _ ## name = (registerTest(#name, cond, name), 0) 46 | 47 | template 48 | static void extendVector(std::vector &base, const std::vector &to_add) { 49 | base.insert(base.end(), to_add.begin(), to_add.end()); 50 | } 51 | 52 | // Test registration 53 | void printTestName(const std::string &name); 54 | void recordFailure(const std::string &name); 55 | std::vector failedTests(); 56 | void registerTest(const std::string &name, bool(&cond)(), void(&func)()); 57 | using RegistrationTable = std::vector>; 58 | RegistrationTable registeredTests(); 59 | inline bool always() { return true; } 60 | 61 | bool compareObjectHandles(RemoteHandle h1, RemoteHandle h2); 62 | 63 | // NT kernel handle->object snapshot 64 | class ObjectSnap { 65 | public: 66 | ObjectSnap(); 67 | uint64_t object(RemoteHandle h); 68 | bool eq(std::initializer_list handles); 69 | bool eq(RemoteHandle h1, RemoteHandle h2) { return eq({h1, h2}); } 70 | private: 71 | bool m_hasTable = false; 72 | std::vector m_table; 73 | }; 74 | 75 | // Misc 76 | std::tuple newPipe( 77 | RemoteWorker &w, BOOL inheritable=FALSE); 78 | std::string windowText(HWND hwnd); 79 | 80 | // "domain-specific" routines: perhaps these belong outside the harness? 81 | void checkInitConsoleHandleSet(RemoteWorker &child); 82 | void checkInitConsoleHandleSet(RemoteWorker &child, RemoteWorker &source); 83 | bool isUsableConsoleHandle(RemoteHandle h); 84 | bool isUsableConsoleInputHandle(RemoteHandle h); 85 | bool isUsableConsoleOutputHandle(RemoteHandle h); 86 | bool isUnboundConsoleObject(RemoteHandle h); 87 | void checkModernConsoleHandleInit(RemoteWorker &proc, 88 | bool in, bool out, bool err); 89 | RemoteWorker childWithDummyInheritList(RemoteWorker &p, SpawnParams sp, 90 | bool dummyPipeInInheritList); 91 | -------------------------------------------------------------------------------- /src/harness/UnicodeConversions.cc: -------------------------------------------------------------------------------- 1 | #include "UnicodeConversions.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | std::string narrowString(const std::wstring &input) 10 | { 11 | int mblen = WideCharToMultiByte( 12 | CP_UTF8, 0, 13 | input.data(), input.size(), 14 | NULL, 0, NULL, NULL); 15 | if (mblen <= 0) { 16 | return std::string(); 17 | } 18 | std::vector tmp(mblen); 19 | int mblen2 = WideCharToMultiByte( 20 | CP_UTF8, 0, 21 | input.data(), input.size(), 22 | tmp.data(), tmp.size(), 23 | NULL, NULL); 24 | ASSERT(mblen2 == mblen); 25 | return std::string(tmp.data(), tmp.size()); 26 | } 27 | 28 | std::wstring widenString(const std::string &input) 29 | { 30 | int widelen = MultiByteToWideChar( 31 | CP_UTF8, 0, 32 | input.data(), input.size(), 33 | NULL, 0); 34 | if (widelen <= 0) { 35 | return std::wstring(); 36 | } 37 | std::vector tmp(widelen); 38 | int widelen2 = MultiByteToWideChar( 39 | CP_UTF8, 0, 40 | input.data(), input.size(), 41 | tmp.data(), tmp.size()); 42 | ASSERT(widelen2 == widelen); 43 | return std::wstring(tmp.data(), tmp.size()); 44 | } 45 | -------------------------------------------------------------------------------- /src/harness/UnicodeConversions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | std::string narrowString(const std::wstring &input); 6 | std::wstring widenString(const std::string &input); 7 | -------------------------------------------------------------------------------- /src/harness/Util.cc: -------------------------------------------------------------------------------- 1 | #include "Util.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "UnicodeConversions.h" 10 | 11 | #include 12 | #include 13 | 14 | namespace { 15 | 16 | static std::string timeString() { 17 | FILETIME fileTime; 18 | GetSystemTimeAsFileTime(&fileTime); 19 | auto ret = ((uint64_t)fileTime.dwHighDateTime << 32) | 20 | fileTime.dwLowDateTime; 21 | return std::to_string(ret); 22 | } 23 | 24 | } // anonymous namespace 25 | 26 | std::string pathDirName(const std::string &path) 27 | { 28 | std::string::size_type pos = path.find_last_of("\\/"); 29 | if (pos == std::string::npos) { 30 | return std::string(); 31 | } else { 32 | return path.substr(0, pos); 33 | } 34 | } 35 | 36 | // Wrapper for GetModuleFileNameW. Returns a UTF-8 string. Aborts on error. 37 | std::string getModuleFileName(HMODULE module) 38 | { 39 | const DWORD size = 4096; 40 | wchar_t filename[size]; 41 | DWORD actual = GetModuleFileNameW(module, filename, size); 42 | ASSERT(actual > 0 && actual < size); 43 | return narrowString(filename); 44 | } 45 | 46 | // Convert GetLastError()'s error code to a presentable message such as: 47 | // 48 | // <87:The parameter is incorrect.> 49 | // 50 | std::string errorString(DWORD errCode) { 51 | // MSDN has this note about "Windows 10": 52 | // 53 | // Windows 10: 54 | // 55 | // LocalFree is not in the modern SDK, so it cannot be used to free 56 | // the result buffer. Instead, use HeapFree (GetProcessHeap(), 57 | // allocatedMessage). In this case, this is the same as calling 58 | // LocalFree on memory. 59 | // 60 | // Important: LocalAlloc() has different options: LMEM_FIXED, and 61 | // LMEM_MOVABLE. FormatMessage() uses LMEM_FIXED, so HeapFree can be 62 | // used. If LMEM_MOVABLE is used, HeapFree cannot be used. 63 | // 64 | // My interpretation of this note is: 65 | // * "Windows 10" really just means, "the latest MS SDK", which supports 66 | // Windows 10, as well as older releases. 67 | // * In every NT kernel ever, HeapFree is perfectly fine to use with 68 | // LocalAlloc LMEM_FIXED allocations. 69 | // * In every NT kernel ever, the FormatMessage buffer can be freed with 70 | // HeapFree. 71 | // The note is clumsy, though. Without clarity, I can't safely use 72 | // HeapFree, but apparently LocalFree calls stop compiling in the newest 73 | // SDK. 74 | // 75 | // Instead, I'll use a fixed-size buffer. 76 | 77 | std::stringstream ss; 78 | ss << "<" << errCode << ":"; 79 | std::vector msgBuf(1024); 80 | DWORD ret = FormatMessageW( 81 | FORMAT_MESSAGE_FROM_SYSTEM | 82 | FORMAT_MESSAGE_IGNORE_INSERTS, 83 | nullptr, 84 | errCode, 85 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 86 | msgBuf.data(), 87 | msgBuf.size(), 88 | nullptr); 89 | if (ret == 0) { 90 | ss << "FormatMessageW failed:"; 91 | ss << GetLastError(); 92 | } else { 93 | msgBuf[msgBuf.size() - 1] = L'\0'; 94 | std::string msg = narrowString(std::wstring(msgBuf.data())); 95 | if (msg.size() >= 2 && msg.substr(msg.size() - 2) == "\r\n") { 96 | msg.resize(msg.size() - 2); 97 | } 98 | ss << msg; 99 | } 100 | ss << ">"; 101 | return ss.str(); 102 | } 103 | 104 | bool isWow64() { 105 | static bool valueInitialized = false; 106 | static bool value = false; 107 | if (!valueInitialized) { 108 | OsModule kernel32(L"kernel32.dll"); 109 | auto proc = reinterpret_cast( 110 | kernel32.proc("IsWow64Process")); 111 | BOOL isWow64 = FALSE; 112 | BOOL ret = proc(GetCurrentProcess(), &isWow64); 113 | value = ret && isWow64; 114 | valueInitialized = true; 115 | } 116 | return value; 117 | } 118 | 119 | std::string makeTempName(const std::string &baseName) { 120 | static int workerCounter = 0; 121 | static auto initialTimeString = timeString(); 122 | return baseName + "-" + 123 | std::to_string(static_cast(GetCurrentProcessId())) + "-" + 124 | initialTimeString + "-" + 125 | std::to_string(++workerCounter); 126 | } 127 | -------------------------------------------------------------------------------- /src/harness/Util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | std::string pathDirName(const std::string &path); 9 | std::string getModuleFileName(HMODULE module); 10 | std::string errorString(DWORD errCode); 11 | bool isWow64(); 12 | std::string makeTempName(const std::string &baseName); 13 | -------------------------------------------------------------------------------- /src/harness/WorkerProgram.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "Command.h" 10 | #include "Event.h" 11 | #include "NtHandleQuery.h" 12 | #include "OsVersion.h" 13 | #include "ShmemParcel.h" 14 | #include "Spawn.h" 15 | 16 | #include 17 | 18 | static const char *g_prefix = ""; 19 | 20 | static const char *successOrFail(BOOL ret) { 21 | return ret ? "ok" : "FAILED"; 22 | } 23 | 24 | static HANDLE openConHandle(const wchar_t *name, BOOL bInheritHandle) { 25 | // If sa isn't provided, the handle defaults to not-inheritable. 26 | SECURITY_ATTRIBUTES sa = {0}; 27 | sa.nLength = sizeof(sa); 28 | sa.bInheritHandle = bInheritHandle; 29 | 30 | trace("%sOpening %ls...", g_prefix, name); 31 | HANDLE conout = CreateFileW(name, 32 | GENERIC_READ | GENERIC_WRITE, 33 | FILE_SHARE_READ | FILE_SHARE_WRITE, 34 | &sa, 35 | OPEN_EXISTING, 0, NULL); 36 | trace("%sOpening %ls... 0x%I64x", g_prefix, name, (int64_t)conout); 37 | return conout; 38 | } 39 | 40 | static HANDLE createBuffer(BOOL bInheritHandle) { 41 | // If sa isn't provided, the handle defaults to not-inheritable. 42 | SECURITY_ATTRIBUTES sa = {0}; 43 | sa.nLength = sizeof(sa); 44 | sa.bInheritHandle = bInheritHandle; 45 | 46 | trace("%sCreating a new buffer...", g_prefix); 47 | HANDLE conout = CreateConsoleScreenBuffer( 48 | GENERIC_READ | GENERIC_WRITE, 49 | FILE_SHARE_READ | FILE_SHARE_WRITE, 50 | &sa, 51 | CONSOLE_TEXTMODE_BUFFER, NULL); 52 | 53 | trace("%sCreating a new buffer... 0x%I64x", g_prefix, (int64_t)conout); 54 | return conout; 55 | } 56 | 57 | static void writeTest(HANDLE conout, const char *msg) { 58 | char writeData[256]; 59 | sprintf(writeData, "%s%s\n", g_prefix, msg); 60 | 61 | trace("%sWriting to 0x%I64x: '%s'...", 62 | g_prefix, (int64_t)conout, msg); 63 | DWORD actual = 0; 64 | BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL); 65 | trace("%sWriting to 0x%I64x: '%s'... %s", 66 | g_prefix, (int64_t)conout, msg, 67 | successOrFail(ret && actual == strlen(writeData))); 68 | } 69 | 70 | static void setConsoleActiveScreenBuffer(HANDLE conout) { 71 | trace("SetConsoleActiveScreenBuffer(0x%I64x) called...", 72 | (int64_t)conout); 73 | trace("SetConsoleActiveScreenBuffer(0x%I64x) called... %s", 74 | (int64_t)conout, 75 | successOrFail(SetConsoleActiveScreenBuffer(conout))); 76 | } 77 | 78 | static void dumpStandardHandles() { 79 | trace("stdin=0x%I64x stdout=0x%I64x stderr=0x%I64x", 80 | (int64_t)GetStdHandle(STD_INPUT_HANDLE), 81 | (int64_t)GetStdHandle(STD_OUTPUT_HANDLE), 82 | (int64_t)GetStdHandle(STD_ERROR_HANDLE)); 83 | } 84 | 85 | static std::vector scanForConsoleHandles() { 86 | std::vector ret; 87 | if (isModernConio()) { 88 | // As of Windows 8, console handles are real kernel handles. 89 | for (unsigned int i = 0x4; i <= 0x1000; i += 4) { 90 | HANDLE h = reinterpret_cast(i); 91 | DWORD mode; 92 | if (GetConsoleMode(h, &mode)) { 93 | ret.push_back(h); 94 | } 95 | } 96 | } else { 97 | for (unsigned int i = 0x3; i < 0x3 + 100 * 4; i += 4) { 98 | HANDLE h = reinterpret_cast(i); 99 | DWORD mode; 100 | if (GetConsoleMode(h, &mode)) { 101 | ret.push_back(h); 102 | } 103 | } 104 | } 105 | return ret; 106 | } 107 | 108 | static void dumpConsoleHandles(bool writeToEach) { 109 | std::string dumpLine = ""; 110 | for (HANDLE h : scanForConsoleHandles()) { 111 | char buf[32]; 112 | sprintf(buf, "0x%I64x", (int64_t)h); 113 | dumpLine += buf; 114 | dumpLine.push_back('('); 115 | CONSOLE_SCREEN_BUFFER_INFO info; 116 | bool is_output = false; 117 | DWORD count; 118 | if (GetNumberOfConsoleInputEvents(h, &count)) { 119 | dumpLine.push_back('I'); 120 | } 121 | if (GetConsoleScreenBufferInfo(h, &info)) { 122 | is_output = true; 123 | dumpLine.push_back('O'); 124 | CHAR_INFO charInfo; 125 | SMALL_RECT readRegion = {}; 126 | if (ReadConsoleOutputW(h, &charInfo, {1,1}, {0,0}, &readRegion)) { 127 | wchar_t ch = charInfo.Char.UnicodeChar; 128 | if (ch != L' ') { 129 | dumpLine.push_back((char)ch); 130 | } 131 | } 132 | } 133 | { 134 | DWORD flags = 0; 135 | if (GetHandleInformation(h, &flags)) { 136 | dumpLine.push_back((flags & HANDLE_FLAG_INHERIT) ? '^' : '_'); 137 | } 138 | } 139 | dumpLine += ") "; 140 | if (writeToEach && is_output) { 141 | char msg[256]; 142 | sprintf(msg, "%d: Writing to 0x%I64x", 143 | (int)GetCurrentProcessId(), (int64_t)h); 144 | writeTest(h, msg); 145 | } 146 | } 147 | trace("Valid console handles:%s", dumpLine.c_str()); 148 | } 149 | 150 | template 151 | void handleConsoleIoCommand(Command &cmd, T func) { 152 | const auto sz = cmd.u.consoleIo.bufferSize; 153 | ASSERT(static_cast(sz.X) * sz.Y <= cmd.u.consoleIo.buffer.size()); 154 | cmd.success = func(cmd.handle, cmd.u.consoleIo.buffer.data(), 155 | cmd.u.consoleIo.bufferSize, cmd.u.consoleIo.bufferCoord, 156 | &cmd.u.consoleIo.ioRegion); 157 | } 158 | 159 | int main(int argc, char *argv[]) { 160 | std::string workerName = argv[1]; 161 | 162 | ShmemParcelTyped parcel(workerName + "-shmem", ShmemParcel::OpenExisting); 163 | Event startEvent(workerName + "-start"); 164 | Event finishEvent(workerName + "-finish"); 165 | Command &cmd = parcel.value(); 166 | 167 | dumpStandardHandles(); 168 | 169 | while (true) { 170 | startEvent.wait(); 171 | startEvent.reset(); 172 | switch (cmd.kind) { 173 | case Command::AllocConsole: 174 | trace("Calling AllocConsole..."); 175 | cmd.success = AllocConsole(); 176 | trace("Calling AllocConsole... %s", 177 | successOrFail(cmd.success)); 178 | break; 179 | case Command::AttachConsole: 180 | trace("Calling AttachConsole(%u)...", 181 | (unsigned int)cmd.dword); 182 | cmd.success = AttachConsole(cmd.dword); 183 | trace("Calling AttachConsole(%u)... %s", 184 | (unsigned int)cmd.dword, successOrFail(cmd.success)); 185 | break; 186 | case Command::Close: 187 | trace("closing 0x%I64x...", 188 | (int64_t)cmd.handle); 189 | cmd.success = CloseHandle(cmd.handle); 190 | trace("closing 0x%I64x... %s", 191 | (int64_t)cmd.handle, successOrFail(cmd.success)); 192 | break; 193 | case Command::CloseQuietly: 194 | cmd.success = CloseHandle(cmd.handle); 195 | break; 196 | case Command::DumpStandardHandles: 197 | dumpStandardHandles(); 198 | break; 199 | case Command::DumpConsoleHandles: 200 | dumpConsoleHandles(cmd.writeToEach); 201 | break; 202 | case Command::Duplicate: { 203 | HANDLE sourceHandle = cmd.handle; 204 | cmd.success = DuplicateHandle( 205 | GetCurrentProcess(), 206 | sourceHandle, 207 | cmd.targetProcess, 208 | &cmd.handle, 209 | 0, cmd.bInheritHandle, DUPLICATE_SAME_ACCESS); 210 | if (!cmd.success) { 211 | cmd.handle = INVALID_HANDLE_VALUE; 212 | } 213 | trace("dup 0x%I64x to pid %u... %s, 0x%I64x", 214 | (int64_t)sourceHandle, 215 | (unsigned int)GetProcessId(cmd.targetProcess), 216 | successOrFail(cmd.success), 217 | (int64_t)cmd.handle); 218 | break; 219 | } 220 | case Command::Exit: 221 | trace("exiting"); 222 | ExitProcess(cmd.dword); 223 | break; 224 | case Command::FreeConsole: 225 | trace("Calling FreeConsole..."); 226 | cmd.success = FreeConsole(); 227 | trace("Calling FreeConsole... %s", successOrFail(cmd.success)); 228 | break; 229 | case Command::GetConsoleProcessList: 230 | cmd.dword = GetConsoleProcessList(cmd.u.processList.data(), 231 | cmd.u.processList.size()); 232 | break; 233 | case Command::GetConsoleScreenBufferInfo: 234 | cmd.u.consoleScreenBufferInfo = {}; 235 | cmd.success = GetConsoleScreenBufferInfo( 236 | cmd.handle, &cmd.u.consoleScreenBufferInfo); 237 | break; 238 | case Command::GetConsoleSelectionInfo: 239 | cmd.u.consoleSelectionInfo = {}; 240 | cmd.success = GetConsoleSelectionInfo(&cmd.u.consoleSelectionInfo); 241 | break; 242 | case Command::GetConsoleTitle: 243 | // GetConsoleTitle is buggy, so make the worker API for it very 244 | // explicit so we can test its bugginess. 245 | ASSERT(cmd.dword <= cmd.u.consoleTitle.size()); 246 | cmd.dword = GetConsoleTitleW(cmd.u.consoleTitle.data(), cmd.dword); 247 | break; 248 | case Command::GetConsoleWindow: 249 | cmd.hwnd = GetConsoleWindow(); 250 | break; 251 | case Command::GetHandleInformation: 252 | cmd.success = GetHandleInformation(cmd.handle, &cmd.dword); 253 | break; 254 | case Command::GetNumberOfConsoleInputEvents: 255 | cmd.success = GetNumberOfConsoleInputEvents(cmd.handle, &cmd.dword); 256 | break; 257 | case Command::GetStdin: 258 | cmd.handle = GetStdHandle(STD_INPUT_HANDLE); 259 | break; 260 | case Command::GetStderr: 261 | cmd.handle = GetStdHandle(STD_ERROR_HANDLE); 262 | break; 263 | case Command::GetStdout: 264 | cmd.handle = GetStdHandle(STD_OUTPUT_HANDLE); 265 | break; 266 | case Command::Hello: 267 | // NOOP for Worker startup synchronization. 268 | break; 269 | case Command::LookupKernelObject: { 270 | uint64_t h64; 271 | memcpy(&h64, &cmd.lookupKernelObject.handle, sizeof(h64)); 272 | auto handles = queryNtHandles(); 273 | uint64_t result = 274 | reinterpret_cast( 275 | ntHandlePointer( 276 | handles, cmd.lookupKernelObject.pid, 277 | reinterpret_cast(h64))); 278 | memcpy(&cmd.lookupKernelObject.kernelObject, 279 | &result, sizeof(result)); 280 | trace("LOOKUP: p%d: 0x%I64x => 0x%I64x", 281 | (int)cmd.lookupKernelObject.pid, 282 | h64, 283 | result); 284 | break; 285 | } 286 | case Command::NewBuffer: 287 | cmd.handle = createBuffer(cmd.bInheritHandle); 288 | break; 289 | case Command::OpenConin: 290 | cmd.handle = openConHandle(L"CONIN$", cmd.bInheritHandle); 291 | break; 292 | case Command::OpenConout: 293 | cmd.handle = openConHandle(L"CONOUT$", cmd.bInheritHandle); 294 | break; 295 | case Command::ReadConsoleOutput: 296 | handleConsoleIoCommand(cmd, ReadConsoleOutputW); 297 | break; 298 | case Command::ScanForConsoleHandles: { 299 | auto ret = scanForConsoleHandles(); 300 | ASSERT(ret.size() <= cmd.u.scanForConsoleHandles.table.size()); 301 | cmd.u.scanForConsoleHandles.count = ret.size(); 302 | std::copy(ret.begin(), ret.end(), 303 | cmd.u.scanForConsoleHandles.table.begin()); 304 | break; 305 | } 306 | case Command::SetConsoleTitle: { 307 | auto nul = std::find(cmd.u.consoleTitle.begin(), 308 | cmd.u.consoleTitle.end(), L'\0'); 309 | ASSERT(nul != cmd.u.consoleTitle.end()); 310 | cmd.success = SetConsoleTitleW(cmd.u.consoleTitle.data()); 311 | break; 312 | } 313 | case Command::SetHandleInformation: 314 | cmd.success = SetHandleInformation( 315 | cmd.handle, cmd.u.setFlags.mask, cmd.u.setFlags.flags); 316 | break; 317 | case Command::SetStdin: 318 | SetStdHandle(STD_INPUT_HANDLE, cmd.handle); 319 | trace("setting stdin to 0x%I64x", (int64_t)cmd.handle); 320 | break; 321 | case Command::SetStderr: 322 | SetStdHandle(STD_ERROR_HANDLE, cmd.handle); 323 | trace("setting stderr to 0x%I64x", (int64_t)cmd.handle); 324 | break; 325 | case Command::SetStdout: 326 | SetStdHandle(STD_OUTPUT_HANDLE, cmd.handle); 327 | trace("setting stdout to 0x%I64x", (int64_t)cmd.handle); 328 | break; 329 | case Command::SetActiveBuffer: 330 | setConsoleActiveScreenBuffer(cmd.handle); 331 | break; 332 | case Command::SetScreenBufferSize: 333 | cmd.success = 334 | SetConsoleScreenBufferSize(cmd.handle, 335 | cmd.u.screenBufferSize); 336 | break; 337 | case Command::SpawnChild: 338 | trace("Spawning child..."); 339 | cmd.handle = spawn(cmd.u.spawn.spawnName.str(), 340 | cmd.u.spawn.spawnParams, 341 | cmd.u.spawn.spawnFailure); 342 | if (cmd.handle != nullptr) { 343 | trace("Spawning child... pid %u", 344 | (unsigned int)GetProcessId(cmd.handle)); 345 | } 346 | break; 347 | case Command::System: 348 | cmd.dword = system(cmd.u.systemText.c_str()); 349 | break; 350 | case Command::WriteConsoleOutput: 351 | handleConsoleIoCommand(cmd, WriteConsoleOutputW); 352 | break; 353 | case Command::WriteText: 354 | writeTest(cmd.handle, cmd.u.writeText.c_str()); 355 | break; 356 | } 357 | finishEvent.set(); 358 | } 359 | return 0; 360 | } 361 | -------------------------------------------------------------------------------- /src/harness/pch.h: -------------------------------------------------------------------------------- 1 | // Precompiled Headers. 2 | // 3 | // Unfortunate evil that speeds up compile times. In principle, compilation 4 | // should still work if PCH is turned off. 5 | // 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | -------------------------------------------------------------------------------- /src/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/shared/DebugClient.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011-2012 Ryan Prichard 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to 5 | // deal in the Software without restriction, including without limitation the 6 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | // sell copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | 21 | #include "DebugClient.h" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #include "c99_snprintf.h" 30 | 31 | void *volatile g_debugConfig; 32 | 33 | static void sendToDebugServer(const char *message) 34 | { 35 | char response[16]; 36 | DWORD responseSize; 37 | CallNamedPipeA( 38 | "\\\\.\\pipe\\DebugServer", 39 | (void*)message, strlen(message), 40 | response, sizeof(response), &responseSize, 41 | NMPWAIT_WAIT_FOREVER); 42 | } 43 | 44 | // Get the current UTC time as milliseconds from the epoch (ignoring leap 45 | // seconds). Use the Unix epoch for consistency with DebugClient.py. There 46 | // are 134774 days between 1601-01-01 (the Win32 epoch) and 1970-01-01 (the 47 | // Unix epoch). 48 | static long long unixTimeMillis() 49 | { 50 | FILETIME fileTime; 51 | GetSystemTimeAsFileTime(&fileTime); 52 | long long msTime = (((long long)fileTime.dwHighDateTime << 32) + 53 | fileTime.dwLowDateTime) / 10000; 54 | return msTime - 134774LL * 24 * 3600 * 1000; 55 | } 56 | 57 | static const char *getDebugConfig() 58 | { 59 | if (g_debugConfig == NULL) { 60 | const int bufSize = 256; 61 | char buf[bufSize]; 62 | DWORD actualSize = GetEnvironmentVariableA("WINPTY_DEBUG", buf, bufSize); 63 | if (actualSize == 0 || actualSize >= (DWORD)bufSize) 64 | buf[0] = '\0'; 65 | char *newConfig = new char[strlen(buf) + 1]; 66 | strcpy(newConfig, buf); 67 | void *oldValue = InterlockedCompareExchangePointer( 68 | &g_debugConfig, newConfig, NULL); 69 | if (oldValue != NULL) { 70 | delete [] newConfig; 71 | } 72 | } 73 | return static_cast(g_debugConfig); 74 | } 75 | 76 | bool isTracingEnabled() 77 | { 78 | static bool disabled, enabled; 79 | if (disabled) { 80 | return false; 81 | } else if (enabled) { 82 | return true; 83 | } else { 84 | // Recognize WINPTY_DEBUG=1 for backwards compatibility. 85 | bool value = hasDebugFlag("trace") || hasDebugFlag("1"); 86 | disabled = !value; 87 | enabled = value; 88 | return value; 89 | } 90 | } 91 | 92 | bool hasDebugFlag(const char *flag) 93 | { 94 | if (strchr(flag, ',') != NULL) { 95 | trace("INTERNAL ERROR: hasDebugFlag flag has comma: '%s'", flag); 96 | abort(); 97 | } 98 | std::string config(getDebugConfig()); 99 | std::string flagStr(flag); 100 | config = "," + config + ","; 101 | flagStr = "," + flagStr + ","; 102 | return config.find(flagStr) != std::string::npos; 103 | } 104 | 105 | void trace(const char *format, ...) 106 | { 107 | if (!isTracingEnabled()) 108 | return; 109 | 110 | char message[1024]; 111 | 112 | va_list ap; 113 | va_start(ap, format); 114 | c99_vsnprintf(message, sizeof(message), format, ap); 115 | message[sizeof(message) - 1] = '\0'; 116 | va_end(ap); 117 | 118 | const int currentTime = (int)(unixTimeMillis() % (100000 * 1000)); 119 | 120 | char moduleName[1024]; 121 | moduleName[0] = '\0'; 122 | GetModuleFileNameA(NULL, moduleName, sizeof(moduleName)); 123 | const char *baseName = strrchr(moduleName, '\\'); 124 | baseName = (baseName != NULL) ? baseName + 1 : moduleName; 125 | 126 | char fullMessage[1024]; 127 | c99_snprintf(fullMessage, sizeof(fullMessage), 128 | "[%05d.%03d %s,p%04d,t%04d]: %s", 129 | currentTime / 1000, currentTime % 1000, 130 | baseName, (int)GetCurrentProcessId(), (int)GetCurrentThreadId(), 131 | message); 132 | fullMessage[sizeof(fullMessage) - 1] = '\0'; 133 | 134 | sendToDebugServer(fullMessage); 135 | } 136 | -------------------------------------------------------------------------------- /src/shared/DebugClient.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011-2012 Ryan Prichard 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to 5 | // deal in the Software without restriction, including without limitation the 6 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | // sell copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | 21 | #ifndef DEBUGCLIENT_H 22 | #define DEBUGCLIENT_H 23 | 24 | bool isTracingEnabled(); 25 | bool hasDebugFlag(const char *flag); 26 | void trace(const char *format, ...); 27 | 28 | #endif // DEBUGCLIENT_H 29 | -------------------------------------------------------------------------------- /src/shared/OsModule.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Ryan Prichard 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to 5 | // deal in the Software without restriction, including without limitation the 6 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | // sell copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | 21 | #ifndef OS_MODULE_H 22 | #define OS_MODULE_H 23 | 24 | #include "DebugClient.h" 25 | #include "WinptyAssert.h" 26 | 27 | class OsModule { 28 | HMODULE m_module; 29 | public: 30 | OsModule(const wchar_t *fileName) { 31 | m_module = LoadLibraryW(fileName); 32 | ASSERT(m_module != NULL); 33 | } 34 | ~OsModule() { 35 | FreeLibrary(m_module); 36 | } 37 | HMODULE handle() const { return m_module; } 38 | FARPROC proc(const char *funcName) { 39 | FARPROC ret = GetProcAddress(m_module, funcName); 40 | if (ret == NULL) { 41 | trace("GetProcAddress: %s is missing", funcName); 42 | } 43 | return ret; 44 | } 45 | }; 46 | 47 | #endif // OS_MODULE_H 48 | -------------------------------------------------------------------------------- /src/shared/WinptyAssert.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011-2012 Ryan Prichard 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to 5 | // deal in the Software without restriction, including without limitation the 6 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | // sell copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | 21 | #include "WinptyAssert.h" 22 | #include "DebugClient.h" 23 | #include 24 | 25 | // Calling the standard assert() function does not work in the agent because 26 | // the error message would be printed to the console, and the only way the 27 | // user can see the console is via a working agent! This custom assert 28 | // function instead sends the message to the DebugServer. 29 | 30 | void assertFail(const char *file, int line, const char *cond) 31 | { 32 | trace("Assertion failed: %s, file %s, line %d", 33 | cond, file, line); 34 | abort(); 35 | } 36 | -------------------------------------------------------------------------------- /src/shared/WinptyAssert.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011-2012 Ryan Prichard 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to 5 | // deal in the Software without restriction, including without limitation the 6 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | // sell copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | 21 | #ifndef WINPTY_ASSERT_H 22 | #define WINPTY_ASSERT_H 23 | 24 | #define ASSERT(x) do { if (!(x)) assertFail(__FILE__, __LINE__, #x); } while(0) 25 | 26 | void assertFail(const char *file, int line, const char *cond); 27 | 28 | #endif // WINPTY_ASSERT_H 29 | -------------------------------------------------------------------------------- /src/shared/c99_snprintf.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Ryan Prichard 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to 5 | // deal in the Software without restriction, including without limitation the 6 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | // sell copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | 21 | #ifndef C99_SNPRINTF_H 22 | #define C99_SNPRINTF_H 23 | 24 | #include 25 | #include 26 | 27 | #ifdef _MSC_VER 28 | 29 | #if _MSC_VER < 1800 30 | /* MSVC does not define C99's va_copy, so define one for it. It appears that 31 | * with MSVC, a va_list is a char*, not an array type, so va_copy is a simple 32 | * assignment. On MSVC 2012, there is a VC/include/vadefs.h file defining 33 | * va_list and the va_XXX routines, which has code for x86, x86-64, and ARM. */ 34 | #define c99_va_copy(dest, src) ((dest) = (src)) 35 | #else 36 | /* Visual C++ 2013 added va_copy. */ 37 | #define c99_va_copy(dest, src) va_copy(dest, src) 38 | #endif 39 | 40 | static inline int c99_vsnprintf( 41 | char* str, 42 | size_t size, 43 | const char* format, 44 | va_list ap) 45 | { 46 | va_list apcopy; 47 | int count = -1; 48 | if (size != 0) { 49 | c99_va_copy(apcopy, ap); 50 | count = _vsnprintf_s(str, size, _TRUNCATE, format, apcopy); 51 | va_end(apcopy); 52 | } 53 | if (count == -1) { 54 | c99_va_copy(apcopy, ap); 55 | count = _vscprintf(format, apcopy); 56 | va_end(apcopy); 57 | } 58 | return count; 59 | } 60 | 61 | #else 62 | 63 | #define c99_va_copy(dest, src) (va_copy(dest, src)) 64 | 65 | static inline int c99_vsnprintf( 66 | char* str, 67 | size_t size, 68 | const char* format, 69 | va_list ap) 70 | { 71 | return vsnprintf(str, size, format, ap); 72 | } 73 | 74 | #endif /* _MSC_VER */ 75 | 76 | static inline int c99_snprintf( 77 | char* str, 78 | size_t size, 79 | const char* format, 80 | ...) 81 | { 82 | int count; 83 | va_list ap; 84 | 85 | va_start(ap, format); 86 | count = c99_vsnprintf(str, size, format, ap); 87 | va_end(ap); 88 | 89 | return count; 90 | } 91 | 92 | #endif /* C99_SNPRINTF_H */ 93 | -------------------------------------------------------------------------------- /src/shared/winpty_wcsnlen.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Ryan Prichard 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to 5 | // deal in the Software without restriction, including without limitation the 6 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | // sell copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | 21 | #include "winpty_wcsnlen.h" 22 | 23 | #include "WinptyAssert.h" 24 | 25 | // Workaround. MinGW (from mingw.org) does not have wcsnlen. MinGW-w64 *does* 26 | // have wcsnlen, but use this function for consistency. 27 | size_t winpty_wcsnlen(const wchar_t *s, size_t maxlen) { 28 | ASSERT(s != NULL); 29 | for (size_t i = 0; i < maxlen; ++i) { 30 | if (s[i] == L'\0') { 31 | return i; 32 | } 33 | } 34 | return maxlen; 35 | } 36 | -------------------------------------------------------------------------------- /src/shared/winpty_wcsnlen.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 Ryan Prichard 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to 5 | // deal in the Software without restriction, including without limitation the 6 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | // sell copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | // IN THE SOFTWARE. 20 | 21 | #ifndef WINPTY_WCSNLEN_H 22 | #define WINPTY_WCSNLEN_H 23 | 24 | #include 25 | #include 26 | 27 | size_t winpty_wcsnlen(const wchar_t *s, size_t maxlen); 28 | 29 | #endif // WINPTY_WCSNLEN_H 30 | -------------------------------------------------------------------------------- /src/tests.mk: -------------------------------------------------------------------------------- 1 | TESTS += \ 2 | build/Test_GetConsoleTitleW.exe \ 3 | build/Win7Bug_InheritHandles.exe \ 4 | build/Win7Bug_RaceCondition.exe 5 | 6 | # To add tests that aren't checked-in, create an mk file of this name: 7 | -include local_tests.mk 8 | --------------------------------------------------------------------------------