| // 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 <lib/fdio/limits.h> |
| #include <lib/fdio/namespace.h> |
| #include <lib/fdio/spawn.h> |
| #include <lib/fdio/util.h> |
| #include <lib/zx/job.h> |
| #include <unistd.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/file_utils.h" |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/scoped_generic.h" |
| #include "starboard/types.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); |
| } |
| |
| fdio_spawn_action_t FdioSpawnAction(uint32_t action) { |
| fdio_spawn_action_t new_action = {}; |
| new_action.action = action; |
| return new_action; |
| } |
| |
| fdio_spawn_action_t FdioSpawnActionCloneFd(int local_fd, int target_fd) { |
| fdio_spawn_action_t action = FdioSpawnAction(FDIO_SPAWN_ACTION_CLONE_FD); |
| action.fd.local_fd = local_fd; |
| action.fd.target_fd = target_fd; |
| return action; |
| } |
| |
| fdio_spawn_action_t FdioSpawnActionAddNamespaceEntry(const char* prefix, |
| zx_handle_t handle) { |
| fdio_spawn_action_t action = FdioSpawnAction(FDIO_SPAWN_ACTION_ADD_NS_ENTRY); |
| action.ns.prefix = prefix; |
| action.ns.handle = handle; |
| return action; |
| } |
| |
| fdio_spawn_action_t FdioSpawnActionAddHandle(uint32_t id, zx_handle_t handle) { |
| fdio_spawn_action_t action = FdioSpawnAction(FDIO_SPAWN_ACTION_ADD_HANDLE); |
| action.h.id = id; |
| action.h.handle = handle; |
| return action; |
| } |
| |
| } // 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) { |
| // fdio_spawn_etc() accepts an array of |fdio_spawn_action_t|, describing |
| // namespace entries, descriptors and handles to launch the child process |
| // with. |
| std::vector<fdio_spawn_action_t> spawn_actions; |
| |
| // Handles to be transferred to the child are owned by this vector, so that |
| // they they are closed on early-exit, and can be release()d otherwise. |
| std::vector<zx::handle> transferred_handles; |
| |
| // Add caller-supplied handles for transfer. We must do this first to ensure |
| // that the handles are consumed even if some later step fails. |
| for (const auto& id_and_handle : options.handles_to_transfer) { |
| spawn_actions.push_back( |
| FdioSpawnActionAddHandle(id_and_handle.id, id_and_handle.handle)); |
| transferred_handles.emplace_back(id_and_handle.handle); |
| } |
| |
| // Determine the job under which to launch the new process. |
| zx::unowned_job job = options.job_handle != ZX_HANDLE_INVALID |
| ? zx::unowned_job(options.job_handle) |
| : GetDefaultJob(); |
| DCHECK(job->is_valid()); |
| |
| // Construct an |argv| array of C-strings from the supplied std::strings. |
| 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); |
| |
| // Determine the environment to pass to the new process. |
| // If |clear_environ|, |environ| or |current_directory| are set then we |
| // construct a new (possibly empty) environment, otherwise we let fdio_spawn() |
| // clone the caller's environment into the new process. |
| uint32_t spawn_flags = FDIO_SPAWN_CLONE_LDSVC | options.spawn_flags; |
| |
| 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(); |
| } |
| |
| std::unique_ptr<char* []> new_environ; |
| if (!environ_modifications.empty()) { |
| char* const empty_environ = nullptr; |
| char* const* old_environ = options.clear_environ ? &empty_environ : environ; |
| new_environ = AlterEnvironment(old_environ, environ_modifications); |
| } else if (!options.clear_environ) { |
| spawn_flags |= FDIO_SPAWN_CLONE_ENVIRON; |
| } |
| |
| // Add actions to clone handles for any specified paths into the new process' |
| // namespace. |
| if (!options.paths_to_clone.empty() || !options.paths_to_transfer.empty()) { |
| DCHECK((options.spawn_flags & FDIO_SPAWN_CLONE_NAMESPACE) == 0); |
| transferred_handles.reserve(transferred_handles.size() + |
| options.paths_to_clone.size() + |
| options.paths_to_transfer.size()); |
| |
| for (const auto& path_to_transfer : options.paths_to_transfer) { |
| zx::handle handle(path_to_transfer.handle); |
| spawn_actions.push_back(FdioSpawnActionAddNamespaceEntry( |
| path_to_transfer.path.value().c_str(), handle.get())); |
| transferred_handles.push_back(std::move(handle)); |
| } |
| |
| for (const auto& path_to_clone : options.paths_to_clone) { |
| zx::handle handle = fuchsia::GetHandleFromFile( |
| base::File(base::FilePath(path_to_clone), |
| base::File::FLAG_OPEN | base::File::FLAG_READ)); |
| if (!handle) { |
| LOG(WARNING) << "Could not open handle for path: " << path_to_clone; |
| return base::Process(); |
| } |
| |
| spawn_actions.push_back(FdioSpawnActionAddNamespaceEntry( |
| path_to_clone.value().c_str(), handle.get())); |
| transferred_handles.push_back(std::move(handle)); |
| } |
| } |
| |
| // Add any file-descriptors to be cloned into the new process. |
| // Note that if FDIO_SPAWN_CLONE_STDIO is set, then any stdio entries in |
| // |fds_to_remap| will be used in place of the parent process' descriptors. |
| for (const auto& src_target : options.fds_to_remap) { |
| spawn_actions.push_back( |
| FdioSpawnActionCloneFd(src_target.first, src_target.second)); |
| } |
| |
| zx::process process_handle; |
| // fdio_spawn_etc() will write a null-terminated scring to |error_message| in |
| // case of failure, so we avoid unnecessarily initializing it here. |
| char error_message[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; |
| zx_status_t status = fdio_spawn_etc( |
| job->get(), spawn_flags, argv_cstr[0], argv_cstr.data(), |
| new_environ.get(), spawn_actions.size(), spawn_actions.data(), |
| process_handle.reset_and_get_address(), error_message); |
| |
| // fdio_spawn_etc() will close all handles specified in add-handle actions, |
| // regardless of whether it succeeds or fails, so release our copies. |
| for (auto& transferred_handle : transferred_handles) |
| ignore_result(transferred_handle.release()); |
| |
| if (status != ZX_OK) { |
| ZX_LOG(ERROR, status) << "fdio_spawn: " << error_message; |
| return Process(); |
| } |
| |
| // Wrap the handle into a Process, and wait for it to terminate, if requested. |
| Process process(process_handle.release()); |
| if (options.wait) { |
| status = zx_object_wait_one(process.Handle(), ZX_TASK_TERMINATED, |
| ZX_TIME_INFINITE, nullptr); |
| ZX_DCHECK(status == ZX_OK, status) << "zx_object_wait_one"; |
| } |
| |
| 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 |