// 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 #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& 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(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& 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 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* 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& argv, std::string* output) { GetAppOutputOptions options; options.output = output; return GetAppOutputInternal(argv, &options) && options.exit_code == EXIT_SUCCESS; } bool GetAppOutputAndError(const std::vector& 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