123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- // 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 <crt_externs.h>
- #include <mach/mach.h>
- #include <os/availability.h>
- #include <spawn.h>
- #include <string.h>
- #include <sys/wait.h>
- #include "base/command_line.h"
- #include "base/files/scoped_file.h"
- #include "base/logging.h"
- #include "base/posix/eintr_wrapper.h"
- #include "base/process/environment_internal.h"
- #include "base/threading/scoped_blocking_call.h"
- #include "base/threading/thread_restrictions.h"
- #include "base/trace_event/base_tracing.h"
- extern "C" {
- // Changes the current thread's directory to a path or directory file
- // descriptor. libpthread only exposes a syscall wrapper starting in
- // macOS 10.12, but the system call dates back to macOS 10.5. On older OSes,
- // the syscall is issued directly.
- int pthread_chdir_np(const char* dir) API_AVAILABLE(macosx(10.12));
- int pthread_fchdir_np(int fd) API_AVAILABLE(macosx(10.12));
- int responsibility_spawnattrs_setdisclaim(posix_spawnattr_t attrs, int disclaim)
- API_AVAILABLE(macosx(10.14));
- } // extern "C"
- 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(const PosixSpawnFileActions&) = delete;
- PosixSpawnFileActions& operator=(const PosixSpawnFileActions&) = delete;
- ~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));
- }
- void Chdir(const char* path) API_AVAILABLE(macos(10.15)) {
- DPSXCHECK(posix_spawn_file_actions_addchdir_np(&file_actions_, path));
- }
- const posix_spawn_file_actions_t* get() const { return &file_actions_; }
- private:
- posix_spawn_file_actions_t file_actions_;
- };
- int ChangeCurrentThreadDirectory(const char* path) {
- return pthread_chdir_np(path);
- }
- // The recommended way to unset a per-thread cwd is to set a new value to an
- // invalid file descriptor, per libpthread-218.1.3/private/private.h.
- int ResetCurrentThreadDirectory() {
- return pthread_fchdir_np(-1);
- }
- struct GetAppOutputOptions {
- // Whether to pipe stderr to stdout in |output|.
- bool include_stderr = false;
- // Caller-supplied string poiter for the output.
- std::string* output = nullptr;
- // Result exit code of Process::Wait().
- int exit_code = 0;
- };
- bool GetAppOutputInternal(const std::vector<std::string>& argv,
- GetAppOutputOptions* gao_options) {
- TRACE_EVENT0("base", "GetAppOutput");
- ScopedFD read_fd, write_fd;
- {
- int pipefds[2];
- if (pipe(pipefds) != 0) {
- DPLOG(ERROR) << "pipe";
- return false;
- }
- read_fd.reset(pipefds[0]);
- write_fd.reset(pipefds[1]);
- }
- LaunchOptions launch_options;
- launch_options.fds_to_remap.emplace_back(write_fd.get(), STDOUT_FILENO);
- if (gao_options->include_stderr) {
- launch_options.fds_to_remap.emplace_back(write_fd.get(), STDERR_FILENO);
- }
- Process process = LaunchProcess(argv, launch_options);
- // Close the parent process' write descriptor, so that EOF is generated in
- // read loop below.
- write_fd.reset();
- // Read the child's output before waiting for its exit, otherwise the pipe
- // buffer may fill up if the process is producing a lot of output.
- std::string* output = gao_options->output;
- output->clear();
- const size_t kBufferSize = 1024;
- size_t total_bytes_read = 0;
- ssize_t read_this_pass = 0;
- do {
- output->resize(output->size() + kBufferSize);
- read_this_pass = HANDLE_EINTR(
- read(read_fd.get(), &(*output)[total_bytes_read], kBufferSize));
- if (read_this_pass >= 0) {
- total_bytes_read += static_cast<size_t>(read_this_pass);
- output->resize(total_bytes_read);
- }
- } while (read_this_pass > 0);
- // It is okay to allow this process to wait on the launched process as a
- // process launched with GetAppOutput*() shouldn't wait back on the process
- // that launched it.
- internal::GetAppOutputScopedAllowBaseSyncPrimitives allow_wait;
- if (!process.WaitForExit(&gao_options->exit_code)) {
- return false;
- }
- return read_this_pass == 0;
- }
- } // namespace
- Process LaunchProcess(const CommandLine& cmdline,
- const LaunchOptions& options) {
- return LaunchProcess(cmdline.argv(), options);
- }
- Process LaunchProcess(const std::vector<std::string>& argv,
- const LaunchOptions& options) {
- TRACE_EVENT0("base", "LaunchProcess");
- 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);
- }
- if (options.disclaim_responsibility) {
- if (__builtin_available(macOS 10.14, *)) {
- DPSXCHECK(responsibility_spawnattrs_setdisclaim(attr.get(), 1));
- }
- }
- std::vector<char*> argv_cstr;
- argv_cstr.reserve(argv.size() + 1);
- for (const auto& arg : argv)
- argv_cstr.push_back(const_cast<char*>(arg.c_str()));
- argv_cstr.push_back(nullptr);
- std::unique_ptr<char*[]> owned_environ;
- char* empty_environ = nullptr;
- char** new_environ =
- options.clear_environment ? &empty_environ : *_NSGetEnviron();
- if (!options.environment.empty()) {
- owned_environ =
- internal::AlterEnvironment(new_environ, options.environment);
- new_environ = owned_environ.get();
- }
- const char* executable_path = !options.real_path.empty()
- ? options.real_path.value().c_str()
- : argv_cstr[0];
- if (__builtin_available(macOS 11.0, *)) {
- if (options.enable_cpu_security_mitigations) {
- DPSXCHECK(posix_spawnattr_set_csm_np(attr.get(), POSIX_SPAWN_NP_CSM_ALL));
- }
- }
- if (!options.current_directory.empty()) {
- const char* chdir_str = options.current_directory.value().c_str();
- if (__builtin_available(macOS 10.15, *)) {
- file_actions.Chdir(chdir_str);
- } else {
- // If the chdir posix_spawn_file_actions extension is not available,
- // change the thread-specific working directory. The new process will
- // inherit it during posix_spawnp().
- int rv = ChangeCurrentThreadDirectory(chdir_str);
- if (rv != 0) {
- DPLOG(ERROR) << "pthread_chdir_np";
- return Process();
- }
- }
- }
- int rv;
- pid_t pid;
- {
- // If |options.mach_ports_for_rendezvous| is specified : the server's lock
- // must be held for the duration of posix_spawnp() so that new child's PID
- // can be recorded with the set of ports.
- const bool has_mach_ports_for_rendezvous =
- !options.mach_ports_for_rendezvous.empty();
- AutoLockMaybe rendezvous_lock(
- has_mach_ports_for_rendezvous
- ? &MachPortRendezvousServer::GetInstance()->GetLock()
- : nullptr);
- // Use posix_spawnp as some callers expect to have PATH consulted.
- rv = posix_spawnp(&pid, executable_path, file_actions.get(), attr.get(),
- &argv_cstr[0], new_environ);
- if (has_mach_ports_for_rendezvous) {
- if (rv == 0) {
- MachPortRendezvousServer::GetInstance()->GetLock().AssertAcquired();
- MachPortRendezvousServer::GetInstance()->RegisterPortsForPid(
- pid, options.mach_ports_for_rendezvous);
- } else {
- // Because |options| is const-ref, the collection has to be copied here.
- // The caller expects to relinquish ownership of any strong rights if
- // LaunchProcess() were to succeed, so these rights should be manually
- // destroyed on failure.
- MachPortsForRendezvous ports = options.mach_ports_for_rendezvous;
- for (auto& port : ports) {
- port.second.Destroy();
- }
- }
- }
- }
- // Restore the thread's working directory if it was changed.
- if (!options.current_directory.empty()) {
- if (__builtin_available(macOS 10.15, *)) {
- // Nothing to do because no global state was changed, but
- // __builtin_available is special and cannot be negated.
- } else {
- ResetCurrentThreadDirectory();
- }
- }
- 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.
- ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
- pid_t ret = HANDLE_EINTR(waitpid(pid, nullptr, 0));
- DPCHECK(ret > 0);
- }
- return Process(pid);
- }
- bool GetAppOutput(const CommandLine& cl, std::string* output) {
- return GetAppOutput(cl.argv(), output);
- }
- bool GetAppOutputAndError(const CommandLine& cl, std::string* output) {
- return GetAppOutputAndError(cl.argv(), output);
- }
- bool GetAppOutputWithExitCode(const CommandLine& cl,
- std::string* output,
- int* exit_code) {
- GetAppOutputOptions options;
- options.output = output;
- bool rv = GetAppOutputInternal(cl.argv(), &options);
- *exit_code = options.exit_code;
- return rv;
- }
- bool GetAppOutput(const std::vector<std::string>& argv, std::string* output) {
- GetAppOutputOptions options;
- options.output = output;
- return GetAppOutputInternal(argv, &options) &&
- options.exit_code == EXIT_SUCCESS;
- }
- bool GetAppOutputAndError(const std::vector<std::string>& argv,
- std::string* output) {
- GetAppOutputOptions options;
- options.include_stderr = true;
- options.output = output;
- return GetAppOutputInternal(argv, &options) &&
- options.exit_code == EXIT_SUCCESS;
- }
- void RaiseProcessToHighPriority() {
- // Historically this has not been implemented on POSIX and macOS. This could
- // influence the Mach task policy in the future.
- }
- } // namespace base
|