| Index: test/win/win_child_process.cc
|
| diff --git a/test/win/win_child_process.cc b/test/win/win_child_process.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..f6156d159ec60cab9b8760180635d24d1287139b
|
| --- /dev/null
|
| +++ b/test/win/win_child_process.cc
|
| @@ -0,0 +1,238 @@
|
| +// Copyright 2015 The Crashpad Authors. All rights reserved.
|
| +//
|
| +// Licensed under the Apache License, Version 2.0 (the "License");
|
| +// you may not use this file except in compliance with the License.
|
| +// You may obtain a copy of the License at
|
| +//
|
| +// http://www.apache.org/licenses/LICENSE-2.0
|
| +//
|
| +// Unless required by applicable law or agreed to in writing, software
|
| +// distributed under the License is distributed on an "AS IS" BASIS,
|
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| +// See the License for the specific language governing permissions and
|
| +// limitations under the License.
|
| +
|
| +#include "test/win/win_child_process.h"
|
| +
|
| +#include <windows.h>
|
| +#include <shellapi.h>
|
| +
|
| +#include <string>
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/strings/stringprintf.h"
|
| +#include "base/strings/utf_string_conversions.h"
|
| +#include "gtest/gtest.h"
|
| +#include "util/stdlib/string_number_conversion.h"
|
| +#include "util/string/split_string.h"
|
| +#include "test/paths.h"
|
| +
|
| +namespace crashpad {
|
| +namespace test {
|
| +
|
| +namespace {
|
| +
|
| +const char kIsMultiprocessChild[] = "--is-multiprocess-child";
|
| +struct LocalFreeTraits {
|
| + static HLOCAL InvalidValue() { return nullptr; }
|
| + static void Free(HLOCAL mem) {
|
| + if (LocalFree(mem) != nullptr)
|
| + PLOG(ERROR) << "LocalFree";
|
| + }
|
| +};
|
| +
|
| +using ScopedLocalFree = base::ScopedGeneric<HLOCAL, LocalFreeTraits>;
|
| +
|
| +bool GetSwitch(const char* switch_name, std::string* value) {
|
| + int num_args;
|
| + wchar_t** args = CommandLineToArgvW(GetCommandLine(), &num_args);
|
| + ScopedLocalFree scoped_args(args); // Take ownership.
|
| + if (!args) {
|
| + PLOG(FATAL) << "CommandLineToArgvW";
|
| + return false;
|
| + }
|
| +
|
| + std::string switch_name_with_equals(switch_name);
|
| + switch_name_with_equals += "=";
|
| + for (size_t i = 1; i < num_args; ++i) {
|
| + const wchar_t* arg = args[i];
|
| + std::string arg_as_utf8 = base::UTF16ToUTF8(arg);
|
| + if (arg_as_utf8.compare(
|
| + 0, switch_name_with_equals.size(), switch_name_with_equals) == 0) {
|
| + if (value)
|
| + *value = arg_as_utf8.substr(switch_name_with_equals.size());
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +ScopedKernelHANDLE LaunchCommandLine(wchar_t* command_line) {
|
| + STARTUPINFO startup_info = {0};
|
| + startup_info.cb = sizeof(startup_info);
|
| + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
| + startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
|
| + startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
| + startup_info.dwFlags = STARTF_USESTDHANDLES;
|
| + PROCESS_INFORMATION process_info;
|
| + if (!CreateProcess(Paths::Executable().value().c_str(),
|
| + &command_line[0], // This cannot be constant, per MSDN.
|
| + nullptr,
|
| + nullptr,
|
| + true, // Inherit handles.
|
| + 0,
|
| + nullptr,
|
| + nullptr,
|
| + &startup_info,
|
| + &process_info)) {
|
| + PLOG(ERROR) << "CreateProcess";
|
| + return ScopedKernelHANDLE();
|
| + }
|
| + if (!CloseHandle(process_info.hThread)) {
|
| + PLOG(ERROR) << "CloseHandle";
|
| + if (!CloseHandle(process_info.hProcess))
|
| + PLOG(ERROR) << "CloseHandle";
|
| + return ScopedKernelHANDLE();
|
| + }
|
| + return ScopedKernelHANDLE(process_info.hProcess);
|
| +}
|
| +
|
| +bool UnsetHandleInheritance(HANDLE handle) {
|
| + if (!SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0)) {
|
| + PLOG(ERROR) << "SetHandleInformation";
|
| + ADD_FAILURE() << "SetHandleInformation";
|
| + return false;
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +bool CreateInheritablePipe(ScopedFileHANDLE* read_handle,
|
| + bool read_inheritable,
|
| + ScopedFileHANDLE* write_handle,
|
| + bool write_inheritable) {
|
| + // Mark both sides as inheritable via the SECURITY_ATTRIBUTES and use
|
| + // SetHandleInformation as necessary to restrict inheritance of either side.
|
| + SECURITY_ATTRIBUTES security_attributes = {0};
|
| + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
|
| + security_attributes.bInheritHandle = true;
|
| +
|
| + HANDLE read, write;
|
| + BOOL result = CreatePipe(&read, &write, &security_attributes, 0);
|
| + if (!result) {
|
| + PLOG(ERROR) << "CreatePipe";
|
| + ADD_FAILURE() << "CreatePipe failed";
|
| + return false;
|
| + }
|
| + ScopedFileHANDLE temp_read(read);
|
| + ScopedFileHANDLE temp_write(write);
|
| +
|
| + if (!read_inheritable && !UnsetHandleInheritance(temp_read.get()))
|
| + return false;
|
| + if (!write_inheritable && !UnsetHandleInheritance(temp_write.get()))
|
| + return false;
|
| +
|
| + *read_handle = temp_read.Pass();
|
| + *write_handle = temp_write.Pass();
|
| +
|
| + return true;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +WinChildProcess::WinChildProcess() {
|
| + std::string switch_value;
|
| + CHECK(GetSwitch(kIsMultiprocessChild, &switch_value));
|
| +
|
| + // Set up the handles we inherited from the parent. These are inherited from
|
| + // the parent and so are open and have the same value as in the parent. The
|
| + // values are passed to the child on the command line.
|
| + std::string left, right;
|
| + CHECK(SplitString(switch_value, '|', &left, &right));
|
| + unsigned int write, read;
|
| + CHECK(StringToNumber(left, &write));
|
| + CHECK(StringToNumber(right, &read));
|
| + pipe_write_.reset(reinterpret_cast<HANDLE>(write));
|
| + pipe_read_.reset(reinterpret_cast<HANDLE>(read));
|
| +
|
| + // Notify the parent that it's OK to proceed. We only need to wait to get to
|
| + // the process entry point, but this is the easiest place we can notify.
|
| + char c = ' ';
|
| + CheckedWriteFile(WritePipeHandle(), &c, sizeof(c));
|
| +}
|
| +
|
| +// static
|
| +bool WinChildProcess::IsChildProcess() {
|
| + return GetSwitch(kIsMultiprocessChild, nullptr);
|
| +}
|
| +
|
| +// static
|
| +scoped_ptr<WinChildProcess::Handles> WinChildProcess::Launch() {
|
| + // Make pipes for child-to-parent and parent-to-child communication.
|
| + scoped_ptr<Handles> handles_for_parent(new Handles);
|
| + ScopedFileHANDLE read_for_child;
|
| + ScopedFileHANDLE write_for_child;
|
| +
|
| + if (!CreateInheritablePipe(
|
| + &handles_for_parent->read, false, &write_for_child, true)) {
|
| + return scoped_ptr<Handles>();
|
| + }
|
| +
|
| + if (!CreateInheritablePipe(
|
| + &read_for_child, true, &handles_for_parent->write, false)) {
|
| + return scoped_ptr<Handles>();
|
| + }
|
| +
|
| + // Build a command line for the child process that tells it only to run the
|
| + // current test, and to pass down the values of the pipe handles.
|
| + const ::testing::TestInfo* const test_info =
|
| + ::testing::UnitTest::GetInstance()->current_test_info();
|
| + std::wstring command_line =
|
| + Paths::Executable().value() + L" " +
|
| + base::UTF8ToUTF16(base::StringPrintf("--gtest_filter=%s.%s %s=0x%x|0x%x",
|
| + test_info->test_case_name(),
|
| + test_info->name(),
|
| + kIsMultiprocessChild,
|
| + write_for_child,
|
| + read_for_child.get()));
|
| +
|
| + // Command-line buffer cannot be constant, per CreateProcess signature.
|
| + handles_for_parent->process = LaunchCommandLine(&command_line[0]);
|
| + if (!handles_for_parent->process.is_valid())
|
| + return scoped_ptr<Handles>();
|
| +
|
| + // Block until the child process has launched. CreateProcess() returns
|
| + // immediately, and test code expects process initialization to have
|
| + // completed so it can, for example, read the process memory.
|
| + char c;
|
| + if (!LoggingReadFile(handles_for_parent->read.get(), &c, sizeof(c))) {
|
| + ADD_FAILURE() << "LoggedReadFile";
|
| + return scoped_ptr<Handles>();
|
| + }
|
| +
|
| + if (c != ' ') {
|
| + ADD_FAILURE() << "invalid data read from child";
|
| + return scoped_ptr<Handles>();
|
| + }
|
| +
|
| + return handles_for_parent.Pass();
|
| +}
|
| +
|
| +FileHandle WinChildProcess::ReadPipeHandle() const {
|
| + return pipe_read_.get();
|
| +}
|
| +
|
| +FileHandle WinChildProcess::WritePipeHandle() const {
|
| + return pipe_write_.get();
|
| +}
|
| +
|
| +void WinChildProcess::CloseReadPipe() {
|
| + pipe_read_.reset();
|
| +}
|
| +
|
| +void WinChildProcess::CloseWritePipe() {
|
| + pipe_write_.reset();
|
| +}
|
| +
|
| +} // namespace test
|
| +} // namespace crashpad
|
|
|