| // 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 |