gecko-dev/browser/app/winlauncher/test/TestSameBinary.cpp

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;
}