2015-01-28 14:49:42 -08:00
|
|
|
// 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.
|
|
|
|
|
test: Move util/test to its own top-level directory, test.
After 9e79ea1da719, it no longer makes sense for crashpad_util_test_lib
to “hide” in util/util_test.gyp. All of util/test is moved to its own
top-level directory, test, which all other test code is allowed to
depend on. test, too, is allowed to depend on all other non-test code.
In a future change, when crashpad_util_test_lib gains a dependency on
crashpad_client, it won’t look so weird for something in util (even
though it’s in util/test) to depend on something in client, because the
thing that needs to depend on client will live in test, not util.
BUG=crashpad:33
R=scottmg@chromium.org
Review URL: https://codereview.chromium.org/1051533002
2015-03-31 17:44:14 -04:00
|
|
|
#include "test/multiprocess_exec.h"
|
2015-01-28 14:49:42 -08:00
|
|
|
|
2016-01-06 12:22:50 -05:00
|
|
|
#include <sys/types.h>
|
|
|
|
|
2015-01-28 14:49:42 -08:00
|
|
|
#include "base/logging.h"
|
|
|
|
#include "base/strings/utf_string_conversions.h"
|
|
|
|
#include "gtest/gtest.h"
|
2015-11-02 13:59:36 -05:00
|
|
|
#include "util/win/command_line.h"
|
2015-01-28 14:49:42 -08:00
|
|
|
|
|
|
|
namespace crashpad {
|
|
|
|
namespace test {
|
|
|
|
|
|
|
|
namespace internal {
|
|
|
|
|
|
|
|
struct MultiprocessInfo {
|
|
|
|
MultiprocessInfo() {}
|
|
|
|
ScopedFileHANDLE pipe_c2p_read;
|
|
|
|
ScopedFileHANDLE pipe_c2p_write;
|
|
|
|
ScopedFileHANDLE pipe_p2c_read;
|
|
|
|
ScopedFileHANDLE pipe_p2c_write;
|
|
|
|
PROCESS_INFORMATION process_info;
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace internal
|
|
|
|
|
|
|
|
Multiprocess::Multiprocess()
|
|
|
|
: info_(nullptr),
|
|
|
|
code_(EXIT_SUCCESS),
|
|
|
|
reason_(kTerminationNormal) {
|
|
|
|
}
|
|
|
|
|
|
|
|
void Multiprocess::Run() {
|
|
|
|
// Set up and spawn the child process.
|
|
|
|
ASSERT_NO_FATAL_FAILURE(PreFork());
|
|
|
|
RunChild();
|
|
|
|
|
|
|
|
// And then run the parent actions in this process.
|
|
|
|
RunParent();
|
|
|
|
|
|
|
|
// Reap the child.
|
|
|
|
WaitForSingleObject(info_->process_info.hProcess, INFINITE);
|
|
|
|
CloseHandle(info_->process_info.hThread);
|
|
|
|
CloseHandle(info_->process_info.hProcess);
|
|
|
|
}
|
|
|
|
|
|
|
|
Multiprocess::~Multiprocess() {
|
|
|
|
delete info_;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Multiprocess::PreFork() {
|
|
|
|
NOTREACHED();
|
|
|
|
}
|
|
|
|
|
|
|
|
FileHandle Multiprocess::ReadPipeHandle() const {
|
|
|
|
// This is the parent case, it's stdin in the child.
|
|
|
|
return info_->pipe_c2p_read.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
FileHandle Multiprocess::WritePipeHandle() const {
|
|
|
|
// This is the parent case, it's stdout in the child.
|
|
|
|
return info_->pipe_p2c_write.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Multiprocess::CloseReadPipe() {
|
|
|
|
info_->pipe_c2p_read.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Multiprocess::CloseWritePipe() {
|
|
|
|
info_->pipe_p2c_write.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Multiprocess::RunParent() {
|
|
|
|
MultiprocessParent();
|
|
|
|
|
|
|
|
info_->pipe_c2p_read.reset();
|
|
|
|
info_->pipe_p2c_write.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Multiprocess::RunChild() {
|
|
|
|
MultiprocessChild();
|
|
|
|
|
|
|
|
info_->pipe_c2p_write.reset();
|
|
|
|
info_->pipe_p2c_read.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
MultiprocessExec::MultiprocessExec()
|
|
|
|
: Multiprocess(), command_(), arguments_(), command_line_() {
|
|
|
|
}
|
|
|
|
|
|
|
|
void MultiprocessExec::SetChildCommand(
|
|
|
|
const std::string& command,
|
|
|
|
const std::vector<std::string>* arguments) {
|
|
|
|
command_ = command;
|
|
|
|
if (arguments) {
|
|
|
|
arguments_ = *arguments;
|
|
|
|
} else {
|
|
|
|
arguments_.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MultiprocessExec::~MultiprocessExec() {
|
|
|
|
}
|
|
|
|
|
|
|
|
void MultiprocessExec::PreFork() {
|
|
|
|
ASSERT_FALSE(command_.empty());
|
|
|
|
|
|
|
|
command_line_.clear();
|
|
|
|
AppendCommandLineArgument(base::UTF8ToUTF16(command_), &command_line_);
|
|
|
|
for (size_t i = 0; i < arguments_.size(); ++i) {
|
|
|
|
AppendCommandLineArgument(base::UTF8ToUTF16(arguments_[i]), &command_line_);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make pipes for child-to-parent and parent-to-child communication. Mark them
|
|
|
|
// as inheritable via the SECURITY_ATTRIBUTES, but use SetHandleInformation to
|
|
|
|
// ensure that the parent sides are not inherited.
|
|
|
|
ASSERT_EQ(nullptr, info());
|
|
|
|
set_info(new internal::MultiprocessInfo());
|
|
|
|
|
|
|
|
SECURITY_ATTRIBUTES security_attributes = {0};
|
|
|
|
security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
|
|
security_attributes.bInheritHandle = TRUE;
|
|
|
|
|
|
|
|
HANDLE c2p_read, c2p_write;
|
|
|
|
PCHECK(CreatePipe(&c2p_read, &c2p_write, &security_attributes, 0));
|
|
|
|
PCHECK(SetHandleInformation(c2p_read, HANDLE_FLAG_INHERIT, 0));
|
|
|
|
info()->pipe_c2p_read.reset(c2p_read);
|
|
|
|
info()->pipe_c2p_write.reset(c2p_write);
|
|
|
|
|
|
|
|
HANDLE p2c_read, p2c_write;
|
|
|
|
PCHECK(CreatePipe(&p2c_read, &p2c_write, &security_attributes, 0));
|
|
|
|
PCHECK(SetHandleInformation(p2c_write, HANDLE_FLAG_INHERIT, 0));
|
|
|
|
info()->pipe_p2c_read.reset(p2c_read);
|
|
|
|
info()->pipe_p2c_write.reset(p2c_write);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MultiprocessExec::MultiprocessChild() {
|
|
|
|
STARTUPINFO startup_info = {0};
|
|
|
|
startup_info.cb = sizeof(startup_info);
|
|
|
|
startup_info.hStdInput = info()->pipe_p2c_read.get();
|
|
|
|
startup_info.hStdOutput = info()->pipe_c2p_write.get();
|
|
|
|
startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
|
|
|
startup_info.dwFlags = STARTF_USESTDHANDLES;
|
|
|
|
PCHECK(CreateProcess(base::UTF8ToUTF16(command_).c_str(),
|
|
|
|
&command_line_[0], // This cannot be constant, per MSDN.
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
TRUE,
|
|
|
|
0,
|
|
|
|
nullptr,
|
|
|
|
nullptr,
|
|
|
|
&startup_info,
|
|
|
|
&info()->process_info));
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace test
|
|
|
|
} // namespace crashpad
|