| // Copyright 2012 The Chromium Authors |
| // 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/mac/mach_port_rendezvous.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)); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| void Chdir(const char* path) API_AVAILABLE(macos(10.15)) { |
| DPSXCHECK(posix_spawn_file_actions_addchdir_np(&file_actions_, path)); |
| } |
| #endif |
| |
| 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 BUILDFLAG(IS_MAC) |
| if (options.disclaim_responsibility) { |
| if (__builtin_available(macOS 10.14, *)) { |
| DPSXCHECK(responsibility_spawnattrs_setdisclaim(attr.get(), 1)); |
| } |
| } |
| #endif |
| |
| 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 BUILDFLAG(IS_MAC) |
| if (__builtin_available(macOS 10.15, *)) { |
| file_actions.Chdir(chdir_str); |
| } else |
| #endif |
| { |
| // 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 |