// Copyright 2017 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/command_line.h" #include "base/files/file_util.h" #include "base/fuchsia/default_job.h" #include "base/logging.h" namespace base { namespace { bool GetAppOutputInternal(const CommandLine& cmd_line, bool include_stderr, std::string* output, int* exit_code) { DCHECK(exit_code); LaunchOptions options; // LaunchProcess will automatically clone any stdio fd we do not explicitly // map. int pipe_fd[2]; if (pipe(pipe_fd) < 0) return false; options.fds_to_remap.emplace_back(pipe_fd[1], STDOUT_FILENO); if (include_stderr) options.fds_to_remap.emplace_back(pipe_fd[1], STDERR_FILENO); Process process = LaunchProcess(cmd_line, options); close(pipe_fd[1]); if (!process.IsValid()) { close(pipe_fd[0]); return false; } output->clear(); for (;;) { char buffer[256]; ssize_t bytes_read = read(pipe_fd[0], buffer, sizeof(buffer)); if (bytes_read <= 0) break; output->append(buffer, bytes_read); } close(pipe_fd[0]); return process.WaitForExit(exit_code); } } // namespace Process LaunchProcess(const CommandLine& cmdline, const LaunchOptions& options) { return LaunchProcess(cmdline.argv(), options); } // TODO(768416): Investigate whether we can make LaunchProcess() create // unprivileged processes by default (no implicit capabilities are granted). Process LaunchProcess(const std::vector& argv, const LaunchOptions& options) { std::vector argv_cstr; argv_cstr.reserve(argv.size() + 1); for (const auto& arg : argv) argv_cstr.push_back(arg.c_str()); argv_cstr.push_back(nullptr); // Note that per launchpad.h, the intention is that launchpad_ functions are // used in a "builder" style. From launchpad_create() to launchpad_go() the // status is tracked in the launchpad_t object, and launchpad_go() reports on // the final status, and cleans up |lp| (assuming it was even created). launchpad_t* lp = nullptr; zx_handle_t job = options.job_handle != ZX_HANDLE_INVALID ? options.job_handle : GetDefaultJob(); DCHECK_NE(ZX_HANDLE_INVALID, job); launchpad_create(job, argv_cstr[0], &lp); launchpad_load_from_file(lp, argv_cstr[0]); launchpad_set_args(lp, argv.size(), argv_cstr.data()); uint32_t to_clone = options.clone_flags; std::unique_ptr new_environ; char* const empty_environ = nullptr; char* const* old_environ = environ; if (options.clear_environ) old_environ = &empty_environ; EnvironmentMap environ_modifications = options.environ; if (!options.current_directory.empty()) { environ_modifications["PWD"] = options.current_directory.value(); } else { FilePath cwd; base::GetCurrentDirectory(&cwd); environ_modifications["PWD"] = cwd.value(); } if (to_clone & LP_CLONE_DEFAULT_JOB) { // Override Fuchsia's built in default job cloning behavior with our own // logic which uses |job| instead of zx_job_default(). // This logic is based on the launchpad implementation. zx_handle_t job_duplicate = ZX_HANDLE_INVALID; zx_status_t status = zx_handle_duplicate(job, ZX_RIGHT_SAME_RIGHTS, &job_duplicate); if (status != ZX_OK) { LOG(ERROR) << "zx_handle_duplicate(job): " << zx_status_get_string(status); return Process(); } launchpad_add_handle(lp, job_duplicate, PA_HND(PA_JOB_DEFAULT, 0)); to_clone &= ~LP_CLONE_DEFAULT_JOB; } if (!environ_modifications.empty()) new_environ = AlterEnvironment(old_environ, environ_modifications); if (!environ_modifications.empty() || options.clear_environ) launchpad_set_environ(lp, new_environ.get()); else to_clone |= LP_CLONE_ENVIRON; launchpad_clone(lp, to_clone); // Clone the mapped file-descriptors, plus any of the stdio descriptors // which were not explicitly specified. bool stdio_already_mapped[3] = {false}; for (const auto& src_target : options.fds_to_remap) { if (static_cast(src_target.second) < arraysize(stdio_already_mapped)) { stdio_already_mapped[src_target.second] = true; } launchpad_clone_fd(lp, src_target.first, src_target.second); } if (to_clone & LP_CLONE_FDIO_STDIO) { for (size_t stdio_fd = 0; stdio_fd < arraysize(stdio_already_mapped); ++stdio_fd) { if (!stdio_already_mapped[stdio_fd]) launchpad_clone_fd(lp, stdio_fd, stdio_fd); } to_clone &= ~LP_CLONE_FDIO_STDIO; } for (const auto& id_and_handle : options.handles_to_transfer) { launchpad_add_handle(lp, id_and_handle.handle, id_and_handle.id); } zx_handle_t proc; const char* errmsg; zx_status_t status = launchpad_go(lp, &proc, &errmsg); if (status != ZX_OK) { LOG(ERROR) << "launchpad_go failed: " << errmsg << ", status=" << zx_status_get_string(status); return Process(); } return Process(proc); } bool GetAppOutput(const CommandLine& cl, std::string* output) { int exit_code; bool result = GetAppOutputInternal(cl, false, output, &exit_code); return result && exit_code == EXIT_SUCCESS; } bool GetAppOutput(const std::vector& argv, std::string* output) { return GetAppOutput(CommandLine(argv), output); } bool GetAppOutputAndError(const CommandLine& cl, std::string* output) { int exit_code; bool result = GetAppOutputInternal(cl, true, output, &exit_code); return result && exit_code == EXIT_SUCCESS; } bool GetAppOutputAndError(const std::vector& argv, std::string* output) { return GetAppOutputAndError(CommandLine(argv), output); } bool GetAppOutputWithExitCode(const CommandLine& cl, std::string* output, int* exit_code) { // Contrary to GetAppOutput(), |true| return here means that the process was // launched and the exit code was waited upon successfully, but not // necessarily that the exit code was EXIT_SUCCESS. return GetAppOutputInternal(cl, false, output, exit_code); } } // namespace base