// 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 #include #include #include #include #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* 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