| // 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 <fdio/limits.h> |
| #include <fdio/namespace.h> |
| #include <fdio/util.h> |
| #include <launchpad/launchpad.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/fuchsia/default_job.h" |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/scoped_generic.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); |
| } |
| |
| bool MapPathsToLaunchpad(const std::vector<FilePath>& paths_to_map, |
| launchpad_t* lp) { |
| zx_status_t status; |
| |
| // Build a array of null terminated strings, which which will be used as an |
| // argument for launchpad_set_nametable(). |
| std::vector<const char*> paths_c_str; |
| paths_c_str.reserve(paths_to_map.size()); |
| |
| for (size_t paths_idx = 0; paths_idx < paths_to_map.size(); ++paths_idx) { |
| const FilePath& next_path = paths_to_map[paths_idx]; |
| if (!PathExists(next_path)) { |
| DLOG(ERROR) << "Path does not exist: " << next_path; |
| return false; |
| } |
| |
| File dir(next_path, File::FLAG_OPEN | File::FLAG_READ); |
| ScopedPlatformFile scoped_fd(dir.TakePlatformFile()); |
| zx_handle_t handles[FDIO_MAX_HANDLES] = {}; |
| uint32_t types[FDIO_MAX_HANDLES] = {}; |
| zx_status_t num_handles = |
| fdio_transfer_fd(scoped_fd.get(), 0, handles, types); |
| // fdio_transfer_fd() returns number of transferred handles, or negative |
| // error. |
| if (num_handles <= 0) { |
| DCHECK_LT(num_handles, 0); |
| ZX_LOG(ERROR, num_handles) << "fdio_transfer_fd"; |
| return false; |
| } |
| ScopedZxHandle scoped_handle(handles[0]); |
| ignore_result(scoped_fd.release()); |
| |
| // Close the handles that we won't use. |
| for (int i = 1; i < num_handles; ++i) { |
| zx_handle_close(handles[i]); |
| } |
| |
| if (types[0] != PA_FDIO_REMOTE) { |
| LOG(ERROR) << "Handle type for " << next_path.AsUTF8Unsafe() |
| << " is not PA_FDIO_REMOTE: " << types[0]; |
| return false; |
| } |
| |
| // Add the handle to the child's nametable. |
| // We use the macro PA_HND(..., <index>) to relate the handle to its |
| // position in the nametable, which is stored as an array of path strings |
| // |paths_str|. |
| status = launchpad_add_handle(lp, scoped_handle.release(), |
| PA_HND(PA_NS_DIR, paths_idx)); |
| if (status != ZX_OK) { |
| ZX_LOG(ERROR, status) << "launchpad_add_handle"; |
| return false; |
| } |
| paths_c_str.push_back(next_path.value().c_str()); |
| } |
| |
| if (!paths_c_str.empty()) { |
| status = |
| launchpad_set_nametable(lp, paths_c_str.size(), paths_c_str.data()); |
| if (status != ZX_OK) { |
| ZX_LOG(ERROR, status) << "launchpad_set_nametable"; |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| struct LaunchpadScopedTraits { |
| static launchpad_t* InvalidValue() { return nullptr; } |
| |
| static void Free(launchpad_t* lp) { launchpad_destroy(lp); } |
| }; |
| |
| using ScopedLaunchpad = ScopedGeneric<launchpad_t*, LaunchpadScopedTraits>; |
| |
| } // 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<std::string>& argv, |
| const LaunchOptions& options) { |
| std::vector<const char*> 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). |
| zx_handle_t job = options.job_handle != ZX_HANDLE_INVALID ? options.job_handle |
| : GetDefaultJob(); |
| DCHECK_NE(ZX_HANDLE_INVALID, job); |
| ScopedLaunchpad lp; |
| zx_status_t status; |
| if ((status = launchpad_create(job, argv_cstr[0], lp.receive())) != ZX_OK) { |
| ZX_LOG(ERROR, status) << "launchpad_create(job)"; |
| return Process(); |
| } |
| |
| if ((status = launchpad_load_from_file(lp.get(), argv_cstr[0])) != ZX_OK) { |
| ZX_LOG(ERROR, status) << "launchpad_load_from_file(" << argv_cstr[0] << ")"; |
| return Process(); |
| } |
| |
| if ((status = launchpad_set_args(lp.get(), argv.size(), argv_cstr.data())) != |
| ZX_OK) { |
| ZX_LOG(ERROR, status) << "launchpad_set_args"; |
| return Process(); |
| } |
| |
| uint32_t to_clone = options.clone_flags; |
| |
| std::unique_ptr<char* []> 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; |
| 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; |
| if ((status = zx_handle_duplicate(job, ZX_RIGHT_SAME_RIGHTS, |
| &job_duplicate)) != ZX_OK) { |
| ZX_LOG(ERROR, status) << "zx_handle_duplicate"; |
| return Process(); |
| } |
| launchpad_add_handle(lp.get(), 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.get(), new_environ.get()); |
| else |
| to_clone |= LP_CLONE_ENVIRON; |
| |
| if (!options.paths_to_map.empty()) { |
| DCHECK(!(to_clone & LP_CLONE_FDIO_NAMESPACE)); |
| if (!MapPathsToLaunchpad(options.paths_to_map, lp.get())) { |
| return Process(); |
| } |
| } |
| |
| launchpad_clone(lp.get(), 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<size_t>(src_target.second) < |
| arraysize(stdio_already_mapped)) { |
| stdio_already_mapped[src_target.second] = true; |
| } |
| launchpad_clone_fd(lp.get(), 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.get(), stdio_fd, stdio_fd); |
| } |
| to_clone &= ~LP_CLONE_FDIO_STDIO; |
| } |
| |
| for (const auto& id_and_handle : options.handles_to_transfer) { |
| launchpad_add_handle(lp.get(), id_and_handle.handle, id_and_handle.id); |
| } |
| |
| zx_handle_t process_handle; |
| const char* errmsg; |
| if ((status = launchpad_go(lp.get(), &process_handle, &errmsg)) != ZX_OK) { |
| ZX_LOG(ERROR, status) << "launchpad_go failed: " << errmsg; |
| return Process(); |
| } |
| ignore_result(lp.release()); // launchpad_go() took ownership. |
| |
| Process process(process_handle); |
| if (options.wait) { |
| status = zx_object_wait_one(process.Handle(), ZX_TASK_TERMINATED, |
| ZX_TIME_INFINITE, nullptr); |
| DCHECK(status == ZX_OK) |
| << "zx_object_wait_one: " << zx_status_get_string(status); |
| } |
| |
| return process; |
| } |
| |
| 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<std::string>& 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<std::string>& 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); |
| } |
| |
| void RaiseProcessToHighPriority() { |
| // Fuchsia doesn't provide an API to change process priority. |
| } |
| |
| } // namespace base |