// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/process/launch.h" #include #include #include #include #include #include "base/logging.h" #include "base/posix/eintr_wrapper.h" #include "base/threading/thread_restrictions.h" namespace base { namespace { // DPSXCHECK is a Debug Posix Spawn Check macro. The posix_spawn* family of // functions return an errno value, as opposed to setting errno directly. This // macro emulates a DPCHECK(). #define DPSXCHECK(expr) \ do { \ int rv = (expr); \ DCHECK_EQ(rv, 0) << #expr << ": -" << rv << " " << strerror(rv); \ } while (0) class PosixSpawnAttr { public: PosixSpawnAttr() { DPSXCHECK(posix_spawnattr_init(&attr_)); } ~PosixSpawnAttr() { DPSXCHECK(posix_spawnattr_destroy(&attr_)); } posix_spawnattr_t* get() { return &attr_; } private: posix_spawnattr_t attr_; }; class PosixSpawnFileActions { public: PosixSpawnFileActions() { DPSXCHECK(posix_spawn_file_actions_init(&file_actions_)); } ~PosixSpawnFileActions() { DPSXCHECK(posix_spawn_file_actions_destroy(&file_actions_)); } void Open(int filedes, const char* path, int mode) { DPSXCHECK(posix_spawn_file_actions_addopen(&file_actions_, filedes, path, mode, 0)); } void Dup2(int filedes, int newfiledes) { DPSXCHECK( posix_spawn_file_actions_adddup2(&file_actions_, filedes, newfiledes)); } void Inherit(int filedes) { DPSXCHECK(posix_spawn_file_actions_addinherit_np(&file_actions_, filedes)); } const posix_spawn_file_actions_t* get() const { return &file_actions_; } private: posix_spawn_file_actions_t file_actions_; DISALLOW_COPY_AND_ASSIGN(PosixSpawnFileActions); }; } // namespace void RestoreDefaultExceptionHandler() { // This function is tailored to remove the Breakpad exception handler. // exception_mask matches s_exception_mask in // third_party/breakpad/breakpad/src/client/mac/handler/exception_handler.cc const exception_mask_t exception_mask = EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT; // Setting the exception port to MACH_PORT_NULL may not be entirely // kosher to restore the default exception handler, but in practice, // it results in the exception port being set to Apple Crash Reporter, // the desired behavior. task_set_exception_ports(mach_task_self(), exception_mask, MACH_PORT_NULL, EXCEPTION_DEFAULT, THREAD_STATE_NONE); } Process LaunchProcessPosixSpawn(const std::vector& argv, const LaunchOptions& options) { DCHECK(!options.pre_exec_delegate) << "LaunchProcessPosixSpawn does not support PreExecDelegate"; DCHECK(options.current_directory.empty()) << "LaunchProcessPosixSpawn does not support current_directory"; PosixSpawnAttr attr; short flags = POSIX_SPAWN_CLOEXEC_DEFAULT; if (options.new_process_group) { flags |= POSIX_SPAWN_SETPGROUP; DPSXCHECK(posix_spawnattr_setpgroup(attr.get(), 0)); } DPSXCHECK(posix_spawnattr_setflags(attr.get(), flags)); PosixSpawnFileActions file_actions; // Process file descriptors for the child. By default, LaunchProcess will // open stdin to /dev/null and inherit stdout and stderr. bool inherit_stdout = true, inherit_stderr = true; bool null_stdin = true; for (const auto& dup2_pair : options.fds_to_remap) { if (dup2_pair.second == STDIN_FILENO) { null_stdin = false; } else if (dup2_pair.second == STDOUT_FILENO) { inherit_stdout = false; } else if (dup2_pair.second == STDERR_FILENO) { inherit_stderr = false; } if (dup2_pair.first == dup2_pair.second) { file_actions.Inherit(dup2_pair.second); } else { file_actions.Dup2(dup2_pair.first, dup2_pair.second); } } if (null_stdin) { file_actions.Open(STDIN_FILENO, "/dev/null", O_RDONLY); } if (inherit_stdout) { file_actions.Inherit(STDOUT_FILENO); } if (inherit_stderr) { file_actions.Inherit(STDERR_FILENO); } std::vector argv_cstr; argv_cstr.reserve(argv.size() + 1); for (const auto& arg : argv) argv_cstr.push_back(const_cast(arg.c_str())); argv_cstr.push_back(nullptr); std::unique_ptr owned_environ; char** new_environ = options.clear_environ ? nullptr : *_NSGetEnviron(); if (!options.environ.empty()) { owned_environ = AlterEnvironment(new_environ, options.environ); new_environ = owned_environ.get(); } const char* executable_path = !options.real_path.empty() ? options.real_path.value().c_str() : argv_cstr[0]; // Use posix_spawnp as some callers expect to have PATH consulted. pid_t pid; int rv = posix_spawnp(&pid, executable_path, file_actions.get(), attr.get(), &argv_cstr[0], new_environ); if (rv != 0) { DLOG(ERROR) << "posix_spawnp(" << executable_path << "): -" << rv << " " << strerror(rv); return Process(); } if (options.wait) { // While this isn't strictly disk IO, waiting for another process to // finish is the sort of thing ThreadRestrictions is trying to prevent. base::ThreadRestrictions::AssertIOAllowed(); pid_t ret = HANDLE_EINTR(waitpid(pid, nullptr, 0)); DPCHECK(ret > 0); } return Process(pid); } } // namespace base