blob: 3bc75809f7235954fd50cddac4ae001812198e30 [file] [log] [blame]
// 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