TestSameBinary.cpp (8428B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 6 7 #define MOZ_USE_LAUNCHER_ERROR 8 9 #include <stdio.h> 10 #include <stdlib.h> 11 12 #include "SameBinary.h" 13 #include "mozilla/Assertions.h" 14 #include "mozilla/CmdLineAndEnvUtils.h" 15 #include "mozilla/NativeNt.h" 16 #include "mozilla/WinHeaderOnlyUtils.h" 17 #include "nsWindowsHelpers.h" 18 19 #define EXPECT_SAMEBINARY_IS(expected, option, message) \ 20 do { \ 21 mozilla::LauncherResult<bool> isSame = \ 22 mozilla::IsSameBinaryAsParentProcess(option); \ 23 if (isSame.isErr()) { \ 24 PrintLauncherError(isSame, \ 25 "IsSameBinaryAsParentProcess returned error " \ 26 "when we were expecting success."); \ 27 return 1; \ 28 } \ 29 if (isSame.unwrap() != expected) { \ 30 PrintErrorMsg(message); \ 31 return 1; \ 32 } \ 33 } while (0) 34 35 /** 36 * This test involves three processes: 37 * 1. The "Monitor" process, which is executed by |MonitorMain|. This process 38 * is responsible for integrating with the test harness, so it spawns the 39 * "Parent" process (2), and then waits for the other two processes to 40 * finish. 41 * 2. The "Parent" process, which is executed by |ParentMain|. This process 42 * creates the "Child" process (3) and then waits indefinitely. 43 * 3. The "Child" process, which is executed by |ChildMain| and carries out 44 * the actual test. It terminates the Parent process during its execution, 45 * using the Child PID as the Parent process's exit code. This serves as a 46 * hacky yet effective way to signal to the Monitor process which PID it 47 * should wait on to ensure that the Child process has exited. 48 */ 49 50 static const char kMsgStart[] = "TEST-FAILED | SameBinary | "; 51 52 inline void PrintErrorMsg(const char* aMsg) { 53 printf("%s%s\n", kMsgStart, aMsg); 54 } 55 56 inline void PrintWinError(const char* aMsg) { 57 mozilla::WindowsError err(mozilla::WindowsError::FromLastError()); 58 printf("%s%s: %S\n", kMsgStart, aMsg, err.AsString().get()); 59 } 60 61 template <typename T> 62 inline void PrintLauncherError(const mozilla::LauncherResult<T>& aResult, 63 const char* aMsg = nullptr) { 64 const char* const kSep = aMsg ? ": " : ""; 65 const char* msg = aMsg ? aMsg : ""; 66 const mozilla::LauncherError& err = aResult.inspectErr(); 67 printf("%s%s%s%S (%s:%d)\n", kMsgStart, msg, kSep, 68 err.mError.AsString().get(), err.mFile, err.mLine); 69 } 70 71 static int ChildMain(DWORD aExpectedParentPid) { 72 mozilla::LauncherResult<DWORD> parentPid = mozilla::nt::GetParentProcessId(); 73 if (parentPid.isErr()) { 74 PrintLauncherError(parentPid); 75 return 1; 76 } 77 78 if (parentPid.inspect() != aExpectedParentPid) { 79 PrintErrorMsg("Unexpected mismatch of parent PIDs"); 80 return 1; 81 } 82 83 const DWORD kAccess = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE; 84 nsAutoHandle parentProcess( 85 ::OpenProcess(kAccess, FALSE, parentPid.inspect())); 86 if (!parentProcess) { 87 PrintWinError("Unexpectedly failed to call OpenProcess on parent"); 88 return 1; 89 } 90 91 EXPECT_SAMEBINARY_IS( 92 true, mozilla::ImageFileCompareOption::Default, 93 "IsSameBinaryAsParentProcess returned incorrect result for identical " 94 "binaries"); 95 EXPECT_SAMEBINARY_IS( 96 true, mozilla::ImageFileCompareOption::CompareNtPathsOnly, 97 "IsSameBinaryAsParentProcess(CompareNtPathsOnly) returned incorrect " 98 "result for identical binaries"); 99 100 // Total hack, but who cares? We'll set the parent's exit code as our PID 101 // so that the monitor process knows who to wait for! 102 if (!::TerminateProcess(parentProcess.get(), ::GetCurrentProcessId())) { 103 PrintWinError("Unexpected failure in TerminateProcess"); 104 return 1; 105 } 106 107 // Close our handle to the parent process so that no references are held. 108 ::CloseHandle(parentProcess.disown()); 109 110 // Querying a pid on a terminated process may still succeed some time after 111 // that process has been terminated. For the purposes of this test, we'll poll 112 // the OS until we cannot succesfully open the parentPid anymore. 113 const uint32_t kMaxAttempts = 100; 114 uint32_t curAttempt = 0; 115 while (HANDLE p = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, 116 parentPid.inspect())) { 117 ::CloseHandle(p); 118 ::Sleep(100); 119 ++curAttempt; 120 if (curAttempt >= kMaxAttempts) { 121 PrintErrorMsg( 122 "Exhausted retry attempts waiting for parent pid to become invalid"); 123 return 1; 124 } 125 } 126 127 EXPECT_SAMEBINARY_IS( 128 false, mozilla::ImageFileCompareOption::Default, 129 "IsSameBinaryAsParentProcess returned incorrect result for dead parent " 130 "process"); 131 EXPECT_SAMEBINARY_IS( 132 false, mozilla::ImageFileCompareOption::CompareNtPathsOnly, 133 "IsSameBinaryAsParentProcess(CompareNtPathsOnly) returned incorrect " 134 "result for dead parent process"); 135 136 return 0; 137 } 138 139 static nsReturnRef<HANDLE> CreateSelfProcess(int argc, wchar_t* argv[]) { 140 nsAutoHandle empty; 141 142 DWORD myPid = ::GetCurrentProcessId(); 143 144 wchar_t strPid[11] = {}; 145 #if defined(__MINGW32__) 146 _ultow(myPid, strPid, 16); 147 #else 148 if (_ultow_s(myPid, strPid, 16)) { 149 PrintErrorMsg("_ultow_s failed"); 150 return empty.out(); 151 } 152 #endif // defined(__MINGW32__) 153 154 wchar_t* extraArgs[] = {strPid}; 155 156 auto cmdLine = 157 mozilla::MakeCommandLine(argc, argv, std::size(extraArgs), extraArgs); 158 if (!cmdLine) { 159 PrintErrorMsg("MakeCommandLine failed"); 160 return empty.out(); 161 } 162 163 STARTUPINFOW si = {sizeof(si)}; 164 PROCESS_INFORMATION pi; 165 BOOL ok = 166 ::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr, FALSE, 167 CREATE_UNICODE_ENVIRONMENT, nullptr, nullptr, &si, &pi); 168 if (!ok) { 169 PrintWinError("CreateProcess failed"); 170 return empty.out(); 171 } 172 173 nsAutoHandle proc(pi.hProcess); 174 nsAutoHandle thd(pi.hThread); 175 176 return proc.out(); 177 } 178 179 static int ParentMain(int argc, wchar_t* argv[]) { 180 nsAutoHandle childProc(CreateSelfProcess(argc, argv)); 181 if (!childProc) { 182 return 1; 183 } 184 185 if (::WaitForSingleObject(childProc.get(), INFINITE) != WAIT_OBJECT_0) { 186 PrintWinError( 187 "Unexpected result from WaitForSingleObject on child process"); 188 return 1; 189 } 190 191 MOZ_CRASH("This process should be terminated by now"); 192 } 193 194 static int MonitorMain(int argc, wchar_t* argv[]) { 195 // In this process, "parent" means the process that will be running 196 // ParentMain, which is our child process (confusing, I know...) 197 nsAutoHandle parentProc(CreateSelfProcess(argc, argv)); 198 if (!parentProc) { 199 return 1; 200 } 201 202 if (::WaitForSingleObject(parentProc.get(), 60000) != WAIT_OBJECT_0) { 203 PrintWinError("Unexpected result from WaitForSingleObject on parent"); 204 return 1; 205 } 206 207 DWORD childPid; 208 if (!::GetExitCodeProcess(parentProc.get(), &childPid)) { 209 PrintWinError("GetExitCodeProcess failed"); 210 return 1; 211 } 212 213 nsAutoHandle childProc(::OpenProcess(SYNCHRONIZE, FALSE, childPid)); 214 if (!childProc) { 215 // Nothing to wait on anymore, which is OK. 216 return 0; 217 } 218 219 // We want no more references to parentProc 220 ::CloseHandle(parentProc.disown()); 221 222 if (::WaitForSingleObject(childProc.get(), 60000) != WAIT_OBJECT_0) { 223 PrintWinError("Unexpected result from WaitForSingleObject on child"); 224 return 1; 225 } 226 227 return 0; 228 } 229 230 extern "C" int wmain(int argc, wchar_t* argv[]) { 231 if (argc == 3) { 232 return ChildMain(wcstoul(argv[2], nullptr, 16)); 233 } 234 235 if (!mozilla::SetArgv0ToFullBinaryPath(argv)) { 236 return 1; 237 } 238 239 if (argc == 1) { 240 return MonitorMain(argc, argv); 241 } 242 243 if (argc == 2) { 244 return ParentMain(argc, argv); 245 } 246 247 PrintErrorMsg("Unexpected argc"); 248 return 1; 249 }