| // Copyright 2018 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/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 |