204 lines
5.7 KiB
C++
204 lines
5.7 KiB
C++
// Copyright 2018 The Crashpad Authors
|
|
//
|
|
// 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/multiprocess_exec.h"
|
|
|
|
#include <lib/fdio/io.h>
|
|
#include <lib/fdio/spawn.h>
|
|
#include <lib/zx/process.h>
|
|
#include <lib/zx/time.h>
|
|
#include <zircon/processargs.h>
|
|
|
|
#include "base/files/scoped_file.h"
|
|
#include "base/fuchsia/fuchsia_logging.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace crashpad {
|
|
namespace test {
|
|
|
|
namespace {
|
|
|
|
void AddPipe(fdio_spawn_action_t* action, int target_fd, int* fd_out) {
|
|
zx_handle_t handle = ZX_HANDLE_INVALID;
|
|
zx_status_t status = fdio_pipe_half(fd_out, &handle);
|
|
ZX_CHECK(status == ZX_OK, status) << "fdio_pipe_half";
|
|
action->action = FDIO_SPAWN_ACTION_ADD_HANDLE;
|
|
action->h.id = PA_HND(PA_FD, target_fd);
|
|
action->h.handle = handle;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace internal {
|
|
|
|
struct MultiprocessInfo {
|
|
MultiprocessInfo() {}
|
|
base::ScopedFD stdin_write;
|
|
base::ScopedFD stdout_read;
|
|
zx::process child;
|
|
};
|
|
|
|
} // 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();
|
|
|
|
// Wait until the child exits.
|
|
zx_signals_t signals;
|
|
ASSERT_EQ(
|
|
info_->child.wait_one(ZX_TASK_TERMINATED, zx::time::infinite(), &signals),
|
|
ZX_OK);
|
|
ASSERT_EQ(signals, ZX_TASK_TERMINATED);
|
|
|
|
// Get the child's exit code.
|
|
zx_info_process_t proc_info;
|
|
zx_status_t status = info_->child.get_info(
|
|
ZX_INFO_PROCESS, &proc_info, sizeof(proc_info), nullptr, nullptr);
|
|
if (status != ZX_OK) {
|
|
ZX_LOG(ERROR, status) << "zx_object_get_info";
|
|
ADD_FAILURE() << "Unable to get exit code of child";
|
|
} else {
|
|
if (code_ != proc_info.return_code) {
|
|
ADD_FAILURE() << "Child exited with code " << proc_info.return_code
|
|
<< ", expected exit with code " << code_;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Multiprocess::SetExpectedChildTermination(TerminationReason reason,
|
|
ReturnCodeType code) {
|
|
EXPECT_EQ(info_, nullptr)
|
|
<< "SetExpectedChildTermination() must be called before Run()";
|
|
reason_ = reason;
|
|
code_ = code;
|
|
}
|
|
|
|
void Multiprocess::SetExpectedChildTerminationBuiltinTrap() {
|
|
constexpr ReturnCodeType kExpectedReturnCode = ZX_TASK_RETCODE_EXCEPTION_KILL;
|
|
SetExpectedChildTermination(kTerminationNormal, kExpectedReturnCode);
|
|
}
|
|
|
|
Multiprocess::~Multiprocess() {
|
|
delete info_;
|
|
}
|
|
|
|
FileHandle Multiprocess::ReadPipeHandle() const {
|
|
return info_->stdout_read.get();
|
|
}
|
|
|
|
FileHandle Multiprocess::WritePipeHandle() const {
|
|
return info_->stdin_write.get();
|
|
}
|
|
|
|
void Multiprocess::CloseReadPipe() {
|
|
info_->stdout_read.reset();
|
|
}
|
|
|
|
void Multiprocess::CloseWritePipe() {
|
|
info_->stdin_write.reset();
|
|
}
|
|
|
|
void Multiprocess::RunParent() {
|
|
MultiprocessParent();
|
|
|
|
info_->stdout_read.reset();
|
|
info_->stdin_write.reset();
|
|
}
|
|
|
|
void Multiprocess::RunChild() {
|
|
MultiprocessChild();
|
|
}
|
|
|
|
MultiprocessExec::MultiprocessExec()
|
|
: Multiprocess(), command_(), arguments_(), argv_() {}
|
|
|
|
void MultiprocessExec::SetChildCommand(
|
|
const base::FilePath& 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());
|
|
|
|
ASSERT_TRUE(argv_.empty());
|
|
|
|
argv_.push_back(command_.value().c_str());
|
|
for (const std::string& argument : arguments_) {
|
|
argv_.push_back(argument.c_str());
|
|
}
|
|
argv_.push_back(nullptr);
|
|
|
|
ASSERT_EQ(info(), nullptr);
|
|
set_info(new internal::MultiprocessInfo());
|
|
}
|
|
|
|
void MultiprocessExec::MultiprocessChild() {
|
|
constexpr size_t kActionCount = 3;
|
|
fdio_spawn_action_t actions[kActionCount];
|
|
|
|
int stdin_parent_side = -1;
|
|
AddPipe(&actions[0], STDIN_FILENO, &stdin_parent_side);
|
|
info()->stdin_write.reset(stdin_parent_side);
|
|
|
|
int stdout_parent_side = -1;
|
|
AddPipe(&actions[1], STDOUT_FILENO, &stdout_parent_side);
|
|
info()->stdout_read.reset(stdout_parent_side);
|
|
|
|
actions[2].action = FDIO_SPAWN_ACTION_CLONE_FD;
|
|
actions[2].fd.local_fd = STDERR_FILENO;
|
|
actions[2].fd.target_fd = STDERR_FILENO;
|
|
|
|
// Pass the filesystem namespace, parent environment, and default job to the
|
|
// child, but don't include any other file handles, preferring to set them
|
|
// up explicitly below.
|
|
uint32_t flags = FDIO_SPAWN_CLONE_ALL & ~FDIO_SPAWN_CLONE_STDIO;
|
|
|
|
char error_message[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
|
|
zx::process child;
|
|
zx_status_t status = fdio_spawn_etc(ZX_HANDLE_INVALID,
|
|
flags,
|
|
command_.value().c_str(),
|
|
argv_.data(),
|
|
nullptr,
|
|
kActionCount,
|
|
actions,
|
|
child.reset_and_get_address(),
|
|
error_message);
|
|
ZX_CHECK(status == ZX_OK, status) << "fdio_spawn_etc: " << error_message;
|
|
info()->child = std::move(child);
|
|
}
|
|
|
|
ProcessType MultiprocessExec::ChildProcess() {
|
|
return zx::unowned_process(info()->child);
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace crashpad
|