306 lines
11 KiB
C++
306 lines
11 KiB
C++
// Copyright 2017 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 <tuple>
|
|
|
|
#include <lib/fdio/limits.h>
|
|
#include <lib/fdio/namespace.h>
|
|
#include <lib/fdio/spawn.h>
|
|
#include <lib/zx/job.h>
|
|
#include <stdint.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/process/environment_internal.h"
|
|
#include "base/scoped_generic.h"
|
|
#include "base/threading/scoped_blocking_call.h"
|
|
#include "base/trace_event/base_tracing.h"
|
|
|
|
namespace base {
|
|
|
|
namespace {
|
|
|
|
bool GetAppOutputInternal(const CommandLine& cmd_line,
|
|
bool include_stderr,
|
|
std::string* output,
|
|
int* exit_code) {
|
|
DCHECK(exit_code);
|
|
TRACE_EVENT0("base", "GetAppOutput");
|
|
|
|
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, static_cast<size_t>(bytes_read));
|
|
}
|
|
close(pipe_fd[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;
|
|
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;
|
|
}
|
|
|
|
fdio_spawn_action_t FdioSpawnActionSetName(const char* name) {
|
|
fdio_spawn_action_t action = FdioSpawnAction(FDIO_SPAWN_ACTION_SET_NAME);
|
|
action.name.data = name;
|
|
return action;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// static
|
|
uint32_t LaunchOptions::AddHandleToTransfer(
|
|
HandlesToTransferVector* handles_to_transfer,
|
|
zx_handle_t handle) {
|
|
CHECK_LE(handles_to_transfer->size(), std::numeric_limits<uint16_t>::max())
|
|
<< "Number of handles to transfer exceeds total allowed";
|
|
auto handle_id =
|
|
static_cast<uint32_t>(PA_HND(PA_USER1, handles_to_transfer->size()));
|
|
handles_to_transfer->push_back({handle_id, handle});
|
|
return handle_id;
|
|
}
|
|
|
|
Process LaunchProcess(const CommandLine& cmdline,
|
|
const LaunchOptions& options) {
|
|
return LaunchProcess(cmdline.argv(), options);
|
|
}
|
|
|
|
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. |fdio_spawn_action_t| does not own any values assigned to its
|
|
// members, so strings assigned to members must be valid through the
|
|
// fdio_spawn_etc() call.
|
|
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);
|
|
|
|
// If |environment| is set then it contains values to set/replace to create
|
|
// the new process' environment.
|
|
EnvironmentMap environ_modifications = options.environment;
|
|
|
|
// "PWD" is set in the new process' environment, to one of:
|
|
// 1. The value of |current_directory|, if set.
|
|
// 2. The value specified in |environment|, if any.
|
|
// 3. The current process' current working directory, if known.
|
|
if (!options.current_directory.empty()) {
|
|
environ_modifications["PWD"] = options.current_directory.value();
|
|
} else if (environ_modifications.find("PWD") == environ_modifications.end()) {
|
|
FilePath cwd;
|
|
if (GetCurrentDirectory(&cwd)) {
|
|
environ_modifications["PWD"] = cwd.value();
|
|
}
|
|
}
|
|
|
|
// By default the calling process' environment is copied, and the collated
|
|
// modifications applied, to create the new process' environment. If
|
|
// |clear_environment| is set then only the collated modifications are used.
|
|
char* const kEmptyEnviron = nullptr;
|
|
char* const* old_environ =
|
|
options.clear_environment ? &kEmptyEnviron : environ;
|
|
std::unique_ptr<char*[]> new_environ =
|
|
internal::AlterEnvironment(old_environ, environ_modifications);
|
|
|
|
// Always clone the library loader service and UTC clock to new processes,
|
|
// in addition to any flags specified by the caller.
|
|
uint32_t spawn_flags = FDIO_SPAWN_DEFAULT_LDSVC | FDIO_SPAWN_CLONE_UTC_CLOCK |
|
|
options.spawn_flags;
|
|
|
|
// 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) {
|
|
fidl::InterfaceHandle<::fuchsia::io::Directory> directory =
|
|
base::OpenDirectoryHandle(path_to_clone);
|
|
if (!directory) {
|
|
LOG(WARNING) << "Could not open handle for path: " << path_to_clone;
|
|
return base::Process();
|
|
}
|
|
|
|
zx::handle handle = directory.TakeChannel();
|
|
|
|
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));
|
|
}
|
|
|
|
// If |process_name_suffix| is specified then set process name as
|
|
// "<file_name><suffix>", otherwise leave the default value.
|
|
std::string process_name; // Must outlive the fdio_spawn_etc() call.
|
|
if (!options.process_name_suffix.empty()) {
|
|
process_name = base::FilePath(argv[0]).BaseName().value() +
|
|
options.process_name_suffix;
|
|
spawn_actions.push_back(FdioSpawnActionSetName(process_name.c_str()));
|
|
}
|
|
|
|
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)
|
|
std::ignore = 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
|