зеркало из https://github.com/mozilla/gecko-dev.git
256 строки
8.4 KiB
C++
256 строки
8.4 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
#define MOZ_USE_LAUNCHER_ERROR
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <utility>
|
|
|
|
#include "SameBinary.h"
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/CmdLineAndEnvUtils.h"
|
|
#include "mozilla/NativeNt.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/Vector.h"
|
|
#include "mozilla/WinHeaderOnlyUtils.h"
|
|
#include "nsWindowsHelpers.h"
|
|
|
|
#define EXPECT_SAMEBINARY_IS(expected, option, message) \
|
|
do { \
|
|
mozilla::LauncherResult<bool> isSame = \
|
|
mozilla::IsSameBinaryAsParentProcess(option); \
|
|
if (isSame.isErr()) { \
|
|
PrintLauncherError(isSame, \
|
|
"IsSameBinaryAsParentProcess returned error " \
|
|
"when we were expecting success."); \
|
|
return 1; \
|
|
} \
|
|
if (isSame.unwrap() != expected) { \
|
|
PrintErrorMsg(message); \
|
|
return 1; \
|
|
} \
|
|
} while (0)
|
|
|
|
/**
|
|
* This test involves three processes:
|
|
* 1. The "Monitor" process, which is executed by |MonitorMain|. This process
|
|
* is responsible for integrating with the test harness, so it spawns the
|
|
* "Parent" process (2), and then waits for the other two processes to
|
|
* finish.
|
|
* 2. The "Parent" process, which is executed by |ParentMain|. This process
|
|
* creates the "Child" process (3) and then waits indefinitely.
|
|
* 3. The "Child" process, which is executed by |ChildMain| and carries out
|
|
* the actual test. It terminates the Parent process during its execution,
|
|
* using the Child PID as the Parent process's exit code. This serves as a
|
|
* hacky yet effective way to signal to the Monitor process which PID it
|
|
* should wait on to ensure that the Child process has exited.
|
|
*/
|
|
|
|
static const char kMsgStart[] = "TEST-FAILED | SameBinary | ";
|
|
|
|
inline void PrintErrorMsg(const char* aMsg) {
|
|
printf("%s%s\n", kMsgStart, aMsg);
|
|
}
|
|
|
|
inline void PrintWinError(const char* aMsg) {
|
|
mozilla::WindowsError err(mozilla::WindowsError::FromLastError());
|
|
printf("%s%s: %S\n", kMsgStart, aMsg, err.AsString().get());
|
|
}
|
|
|
|
template <typename T>
|
|
inline void PrintLauncherError(const mozilla::LauncherResult<T>& aResult,
|
|
const char* aMsg = nullptr) {
|
|
const char* const kSep = aMsg ? ": " : "";
|
|
const char* msg = aMsg ? aMsg : "";
|
|
const mozilla::LauncherError& err = aResult.inspectErr();
|
|
printf("%s%s%s%S (%s:%d)\n", kMsgStart, msg, kSep,
|
|
err.mError.AsString().get(), err.mFile, err.mLine);
|
|
}
|
|
|
|
static int ChildMain(DWORD aExpectedParentPid) {
|
|
mozilla::LauncherResult<DWORD> parentPid = mozilla::nt::GetParentProcessId();
|
|
if (parentPid.isErr()) {
|
|
PrintLauncherError(parentPid);
|
|
return 1;
|
|
}
|
|
|
|
if (parentPid.inspect() != aExpectedParentPid) {
|
|
PrintErrorMsg("Unexpected mismatch of parent PIDs");
|
|
return 1;
|
|
}
|
|
|
|
const DWORD kAccess = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE;
|
|
nsAutoHandle parentProcess(
|
|
::OpenProcess(kAccess, FALSE, parentPid.inspect()));
|
|
if (!parentProcess) {
|
|
PrintWinError("Unexpectedly failed to call OpenProcess on parent");
|
|
return 1;
|
|
}
|
|
|
|
EXPECT_SAMEBINARY_IS(
|
|
true, mozilla::ImageFileCompareOption::Default,
|
|
"IsSameBinaryAsParentProcess returned incorrect result for identical "
|
|
"binaries");
|
|
EXPECT_SAMEBINARY_IS(
|
|
true, mozilla::ImageFileCompareOption::CompareNtPathsOnly,
|
|
"IsSameBinaryAsParentProcess(CompareNtPathsOnly) returned incorrect "
|
|
"result for identical binaries");
|
|
|
|
// Total hack, but who cares? We'll set the parent's exit code as our PID
|
|
// so that the monitor process knows who to wait for!
|
|
if (!::TerminateProcess(parentProcess.get(), ::GetCurrentProcessId())) {
|
|
PrintWinError("Unexpected failure in TerminateProcess");
|
|
return 1;
|
|
}
|
|
|
|
// Close our handle to the parent process so that no references are held.
|
|
::CloseHandle(parentProcess.disown());
|
|
|
|
// Querying a pid on a terminated process may still succeed some time after
|
|
// that process has been terminated. For the purposes of this test, we'll poll
|
|
// the OS until we cannot succesfully open the parentPid anymore.
|
|
const uint32_t kMaxAttempts = 100;
|
|
uint32_t curAttempt = 0;
|
|
while (HANDLE p = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
|
|
parentPid.inspect())) {
|
|
::CloseHandle(p);
|
|
::Sleep(100);
|
|
++curAttempt;
|
|
if (curAttempt >= kMaxAttempts) {
|
|
PrintErrorMsg(
|
|
"Exhausted retry attempts waiting for parent pid to become invalid");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
EXPECT_SAMEBINARY_IS(
|
|
false, mozilla::ImageFileCompareOption::Default,
|
|
"IsSameBinaryAsParentProcess returned incorrect result for dead parent "
|
|
"process");
|
|
EXPECT_SAMEBINARY_IS(
|
|
false, mozilla::ImageFileCompareOption::CompareNtPathsOnly,
|
|
"IsSameBinaryAsParentProcess(CompareNtPathsOnly) returned incorrect "
|
|
"result for dead parent process");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static nsReturnRef<HANDLE> CreateSelfProcess(int argc, wchar_t* argv[]) {
|
|
nsAutoHandle empty;
|
|
|
|
DWORD myPid = ::GetCurrentProcessId();
|
|
|
|
wchar_t strPid[11] = {};
|
|
#if defined(__MINGW32__)
|
|
_ultow(myPid, strPid, 16);
|
|
#else
|
|
if (_ultow_s(myPid, strPid, 16)) {
|
|
PrintErrorMsg("_ultow_s failed");
|
|
return empty.out();
|
|
}
|
|
#endif // defined(__MINGW32__)
|
|
|
|
wchar_t* extraArgs[] = {strPid};
|
|
|
|
auto cmdLine = mozilla::MakeCommandLine(
|
|
argc, argv, mozilla::ArrayLength(extraArgs), extraArgs);
|
|
if (!cmdLine) {
|
|
PrintErrorMsg("MakeCommandLine failed");
|
|
return empty.out();
|
|
}
|
|
|
|
STARTUPINFOW si = {sizeof(si)};
|
|
PROCESS_INFORMATION pi;
|
|
BOOL ok =
|
|
::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr, FALSE,
|
|
CREATE_UNICODE_ENVIRONMENT, nullptr, nullptr, &si, &pi);
|
|
if (!ok) {
|
|
PrintWinError("CreateProcess failed");
|
|
return empty.out();
|
|
}
|
|
|
|
nsAutoHandle proc(pi.hProcess);
|
|
nsAutoHandle thd(pi.hThread);
|
|
|
|
return proc.out();
|
|
}
|
|
|
|
static int ParentMain(int argc, wchar_t* argv[]) {
|
|
nsAutoHandle childProc(CreateSelfProcess(argc, argv));
|
|
if (!childProc) {
|
|
return 1;
|
|
}
|
|
|
|
if (::WaitForSingleObject(childProc.get(), INFINITE) != WAIT_OBJECT_0) {
|
|
PrintWinError(
|
|
"Unexpected result from WaitForSingleObject on child process");
|
|
return 1;
|
|
}
|
|
|
|
MOZ_ASSERT_UNREACHABLE("This process should be terminated by now");
|
|
return 0;
|
|
}
|
|
|
|
static int MonitorMain(int argc, wchar_t* argv[]) {
|
|
// In this process, "parent" means the process that will be running
|
|
// ParentMain, which is our child process (confusing, I know...)
|
|
nsAutoHandle parentProc(CreateSelfProcess(argc, argv));
|
|
if (!parentProc) {
|
|
return 1;
|
|
}
|
|
|
|
if (::WaitForSingleObject(parentProc.get(), 60000) != WAIT_OBJECT_0) {
|
|
PrintWinError("Unexpected result from WaitForSingleObject on parent");
|
|
return 1;
|
|
}
|
|
|
|
DWORD childPid;
|
|
if (!::GetExitCodeProcess(parentProc.get(), &childPid)) {
|
|
PrintWinError("GetExitCodeProcess failed");
|
|
return 1;
|
|
}
|
|
|
|
nsAutoHandle childProc(::OpenProcess(SYNCHRONIZE, FALSE, childPid));
|
|
if (!childProc) {
|
|
// Nothing to wait on anymore, which is OK.
|
|
return 0;
|
|
}
|
|
|
|
// We want no more references to parentProc
|
|
::CloseHandle(parentProc.disown());
|
|
|
|
if (::WaitForSingleObject(childProc.get(), 60000) != WAIT_OBJECT_0) {
|
|
PrintWinError("Unexpected result from WaitForSingleObject on child");
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
extern "C" int wmain(int argc, wchar_t* argv[]) {
|
|
if (argc == 3) {
|
|
return ChildMain(wcstoul(argv[2], nullptr, 16));
|
|
}
|
|
|
|
if (!mozilla::SetArgv0ToFullBinaryPath(argv)) {
|
|
return 1;
|
|
}
|
|
|
|
if (argc == 1) {
|
|
return MonitorMain(argc, argv);
|
|
}
|
|
|
|
if (argc == 2) {
|
|
return ParentMain(argc, argv);
|
|
}
|
|
|
|
PrintErrorMsg("Unexpected argc");
|
|
return 1;
|
|
}
|