From f62a30e977f55b8986579e3f98957761f61875c7 Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Wed, 17 Jan 2018 14:03:39 -0800 Subject: [PATCH] fuchsia: Implementation of MultiprocessExec This supports multiprocess tests of the non-fork() variety. Also, improve directory finding so that the crashpad_test_test_multiprocess_exec_test_child binary can be located correctly on Fuchsia. Doc ref for launchpad: https://fuchsia.googlesource.com/zircon/+/master/system/ulib/launchpad/include/launchpad/launchpad.h#23 Also, roll mini_chromium to pick up ScopedZxHandle addition. Includes: a19ef08 Merge ScopedZxHandle from Chromium base f21c900 fuchsia: Move zircon libs dep to base, rather than global Bug: crashpad:196 Change-Id: Id01dee43f2d04e682e70c12777aff41f8dd848d6 Reviewed-on: https://chromium-review.googlesource.com/868967 Reviewed-by: Mark Mentovai Commit-Queue: Scott Graham --- DEPS | 2 +- test/BUILD.gn | 21 ++-- test/multiprocess.h | 14 ++- test/multiprocess_exec_fuchsia.cc | 155 ++++++++++++++++++++++++++++++ test/multiprocess_exec_win.cc | 4 - test/test_paths.cc | 8 +- util/misc/paths_fuchsia.cc | 5 +- 7 files changed, 185 insertions(+), 24 deletions(-) create mode 100644 test/multiprocess_exec_fuchsia.cc diff --git a/DEPS b/DEPS index d4bb9a65..7741afcd 100644 --- a/DEPS +++ b/DEPS @@ -28,7 +28,7 @@ deps = { '5e2b3ddde7cda5eb6bc09a5546a76b00e49d888f', 'crashpad/third_party/mini_chromium/mini_chromium': Var('chromium_git') + '/chromium/mini_chromium@' + - 'edfe51ce818e55eae73ab3f144a360e855533888', + 'a19ef08ada53345fd9277f3d9434bc35f0e13a55', 'crashpad/third_party/zlib/zlib': Var('chromium_git') + '/chromium/src/third_party/zlib@' + '13dc246a58e4b72104d35f9b1809af95221ebda7', diff --git a/test/BUILD.gn b/test/BUILD.gn index a937854e..e59cc2af 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -42,13 +42,13 @@ static_library("test") { ] if (crashpad_is_posix) { - sources += [ - "multiprocess_posix.cc", - "scoped_temp_dir_posix.cc", - ] + sources += [ "scoped_temp_dir_posix.cc" ] if (!crashpad_is_fuchsia) { - sources += [ "multiprocess_exec_posix.cc" ] + sources += [ + "multiprocess_exec_posix.cc", + "multiprocess_posix.cc", + ] } } @@ -91,6 +91,11 @@ static_library("test") { ] } + if (crashpad_is_fuchsia) { + sources += [ "multiprocess_exec_fuchsia.cc" ] + libs = [ "launchpad" ] + } + public_configs = [ "..:crashpad_config" ] configs += [ "../build:crashpad_is_in_chromium" ] @@ -121,6 +126,7 @@ source_set("test_test") { sources = [ "hex_string_test.cc", "main_arguments_test.cc", + "multiprocess_exec_test.cc", "scoped_temp_dir_test.cc", "test_paths_test.cc", ] @@ -141,11 +147,6 @@ source_set("test_test") { } if (!crashpad_is_fuchsia) { - sources += [ - # TODO(scottmg): A MultiprocessExecFuchsia is probably desirable, but - # hasn't been implemented yet. - "multiprocess_exec_test.cc", - ] } deps = [ diff --git a/test/multiprocess.h b/test/multiprocess.h index b0ff9870..35d18b2f 100644 --- a/test/multiprocess.h +++ b/test/multiprocess.h @@ -36,8 +36,8 @@ struct MultiprocessInfo; //! Subclasses are expected to implement the parent and child by overriding the //! appropriate methods. //! -//! On Windows, this class is only an internal implementation detail of -//! MultiprocessExec and all tests must use that class. +//! On Windows and Fuchsia, this class is only an internal implementation +//! detail of MultiprocessExec and all tests must use that class. class Multiprocess { public: //! \brief The termination type for a child process. @@ -110,14 +110,18 @@ class Multiprocess { //! //! Subclass implementations may signal failure by raising their own fatal //! gtest assertions. - virtual void PreFork(); + virtual void PreFork() +#if defined(OS_WIN) || defined(OS_FUCHSIA) + = 0 +#endif // OS_WIN || OS_FUCHSIA + ; -#if !defined(OS_WIN) +#if !defined(OS_WIN) && !defined(OS_FUCHSIA) //! \brief Returns the child process’ process ID. //! //! This method may only be called by the parent process. pid_t ChildPID() const; -#endif // !OS_WIN +#endif // !OS_WIN && !OS_FUCHSIA //! \brief Returns the read pipe’s file handle. //! diff --git a/test/multiprocess_exec_fuchsia.cc b/test/multiprocess_exec_fuchsia.cc new file mode 100644 index 00000000..324e7eb0 --- /dev/null +++ b/test/multiprocess_exec_fuchsia.cc @@ -0,0 +1,155 @@ +// 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 +#include +#include + +#include "base/files/scoped_file.h" +#include "base/fuchsia/fuchsia_logging.h" +#include "base/fuchsia/scoped_zx_handle.h" +#include "gtest/gtest.h" + +namespace crashpad { +namespace test { + +namespace internal { + +struct MultiprocessInfo { + MultiprocessInfo() {} + base::ScopedFD stdin_write; + base::ScopedFD stdout_read; + base::ScopedZxHandle 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(); + + // Reap the child. + zx_signals_t signals; + ASSERT_EQ( + zx_object_wait_one( + info_->child.get(), ZX_TASK_TERMINATED, ZX_TIME_INFINITE, &signals), + ZX_OK); + ASSERT_EQ(signals, ZX_TASK_TERMINATED); +} + +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()); + } + + ASSERT_EQ(info(), nullptr); + set_info(new internal::MultiprocessInfo()); +} + +void MultiprocessExec::MultiprocessChild() { + launchpad_t* lp; + launchpad_create(zx_job_default(), command_.value().c_str(), &lp); + launchpad_load_from_file(lp, command_.value().c_str()); + launchpad_set_args(lp, argv_.size(), &argv_[0]); + + // 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. + launchpad_clone( + lp, LP_CLONE_FDIO_NAMESPACE | LP_CLONE_ENVIRON | LP_CLONE_DEFAULT_JOB); + + int stdin_parent_side; + launchpad_add_pipe(lp, &stdin_parent_side, STDIN_FILENO); + info()->stdin_write.reset(stdin_parent_side); + + int stdout_parent_side; + launchpad_add_pipe(lp, &stdout_parent_side, STDOUT_FILENO); + info()->stdout_read.reset(stdout_parent_side); + + launchpad_clone_fd(lp, STDERR_FILENO, STDERR_FILENO); + + const char* error_message; + zx_handle_t child; + zx_status_t status = launchpad_go(lp, &child, &error_message); + ZX_CHECK(status == ZX_OK, status) << "launchpad_go: " << error_message; + info()->child.reset(child); +} + +} // namespace test +} // namespace crashpad diff --git a/test/multiprocess_exec_win.cc b/test/multiprocess_exec_win.cc index f3a10c42..98fd0ac1 100644 --- a/test/multiprocess_exec_win.cc +++ b/test/multiprocess_exec_win.cc @@ -61,10 +61,6 @@ 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(); diff --git a/test/test_paths.cc b/test/test_paths.cc index b3eee3c1..f6471d08 100644 --- a/test/test_paths.cc +++ b/test/test_paths.cc @@ -170,7 +170,7 @@ base::FilePath TestPaths::BuildArtifact( base::FilePath::StringType test_name = FILE_PATH_LITERAL("crashpad_") + module + FILE_PATH_LITERAL("_test"); -#if !defined(CRASHPAD_IS_IN_CHROMIUM) +#if !defined(CRASHPAD_IS_IN_CHROMIUM) && !defined(OS_FUCHSIA) CHECK(Executable().BaseName().RemoveFinalExtension().value() == test_name); #endif // !CRASHPAD_IS_IN_CHROMIUM @@ -182,6 +182,8 @@ base::FilePath TestPaths::BuildArtifact( case FileType::kExecutable: #if defined(OS_WIN) extension = FILE_PATH_LITERAL(".exe"); +#elif defined(OS_FUCHSIA) + directory = base::FilePath(FILE_PATH_LITERAL("/pkg/bin")); #endif // OS_WIN break; @@ -191,6 +193,10 @@ base::FilePath TestPaths::BuildArtifact( #else // OS_WIN extension = FILE_PATH_LITERAL(".so"); #endif // OS_WIN + +#if defined(OS_FUCHSIA) + directory = base::FilePath(FILE_PATH_LITERAL("/pkg/lib")); +#endif break; } diff --git a/util/misc/paths_fuchsia.cc b/util/misc/paths_fuchsia.cc index 8baa0d9f..09084483 100644 --- a/util/misc/paths_fuchsia.cc +++ b/util/misc/paths_fuchsia.cc @@ -26,9 +26,8 @@ namespace crashpad { bool Paths::Executable(base::FilePath* path) { // Assume the environment has been set up following // https://fuchsia.googlesource.com/docs/+/master/namespaces.md#typical-directory-structure - // . The actual executable name is not known, but it's conceptually in this - // location. - *path = base::FilePath("/pkg/bin/unknown"); + // . + *path = base::FilePath("/pkg/bin/app"); return true; }