blob: b4d29660f4d02a88f4b9ac9e28669c5e8476e820 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 <fcntl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <fuchsia/process/llcpp/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/io.h>
#include <lib/fdio/limits.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/spawn.h>
#include <lib/fidl/txn_header.h>
#include <lib/zx/channel.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/dlfcn.h>
#include <zircon/errors.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/utc.h>
#include <bitset>
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <list>
#include <string>
#include <utility>
#include <vector>
#include <fbl/unique_fd.h>
#include "internal.h"
namespace fio = fuchsia_io;
namespace fprocess = fuchsia_process;
#define FDIO_RESOLVE_PREFIX "#!resolve "
#define FDIO_RESOLVE_PREFIX_LEN 10
// It is possible to setup an infinite loop of interpreters. We want to avoid this being a common
// abuse vector, but also stay out of the way of any complex user setups.
#define FDIO_SPAWN_MAX_INTERPRETER_DEPTH 255
// Maximum allowed length of a #! shebang directive.
// This applies to both types of #! directives - both the '#!resolve' special case and the general
// '#!' case with an arbitrary interpreter - but we use the fuchsia.process/Resolver limit rather
// than define a separate arbitrary limit.
#define FDIO_SPAWN_MAX_INTERPRETER_LINE_LEN \
(fprocess::wire::MAX_RESOLVE_NAME_SIZE + FDIO_RESOLVE_PREFIX_LEN)
static_assert(FDIO_SPAWN_MAX_INTERPRETER_LINE_LEN < PAGE_SIZE,
"max #! interpreter line length must be less than page size");
#define FDIO_SPAWN_LAUNCH_HANDLE_EXECUTABLE ((size_t)0u)
#define FDIO_SPAWN_LAUNCH_HANDLE_JOB ((size_t)1u)
#define FDIO_SPAWN_LAUNCH_HANDLE_COUNT ((size_t)2u)
#define FDIO_SPAWN_LAUNCH_REPLY_HANDLE_COUNT ((size_t)1u)
// The fdio_spawn_action_t is replicated in various ffi interfaces, including
// the rust and golang standard libraries.
static_assert(sizeof(fdio_spawn_action_t) == 24, "fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, action) == 0,
"fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, fd) == 8, "fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, fd.local_fd) == 8,
"fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, fd.target_fd) == 12,
"fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, ns) == 8, "fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, ns.prefix) == 8,
"fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, ns.handle) == 16,
"fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, h) == 8, "fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, h.id) == 8,
"fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, h.handle) == 12,
"fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, name) == 8,
"fdio_spawn_action_t must have a stable ABI");
static_assert(offsetof(fdio_spawn_action_t, name.data) == 8,
"fdio_spawn_action_t must have a stable ABI");
static void report_error(char* err_msg, const char* format, ...) {
if (!err_msg)
return;
va_list args;
va_start(args, format);
vsnprintf(err_msg, FDIO_SPAWN_ERR_MSG_MAX_LENGTH, format, args);
va_end(args);
}
static zx_status_t load_path(const char* path, zx::vmo* out_vmo, char* err_msg) {
fbl::unique_fd fd;
zx_status_t status =
fdio_open_fd(path, fio::wire::OPEN_RIGHT_READABLE | fio::wire::OPEN_RIGHT_EXECUTABLE,
fd.reset_and_get_address());
if (status != ZX_OK) {
report_error(err_msg, "Could not open file");
return status;
}
zx::vmo vmo;
status = fdio_get_vmo_exec(fd.get(), vmo.reset_and_get_address());
if (status != ZX_OK) {
report_error(err_msg, "Could not clone VMO for file");
return status;
}
if (strlen(path) >= ZX_MAX_NAME_LEN) {
const char* p = strrchr(path, '/');
if (p != nullptr) {
path = p + 1;
}
}
status = vmo.set_property(ZX_PROP_NAME, path, strlen(path));
if (status != ZX_OK) {
report_error(err_msg, "Could not associate pathname with VMO");
return status;
}
*out_vmo = std::move(vmo);
return status;
}
// resolve_name makes a call to the fuchsia.process.Resolver service and may return a vmo and
// associated loader service, if the name resolves within the current realm.
static zx_status_t resolve_name(const char* name, size_t name_len, zx::vmo* out_executable,
zx::channel* out_ldsvc, char* err_msg) {
fidl::WireSyncClient<fprocess::Resolver> resolver;
zx::channel request;
zx_status_t status = zx::channel::create(0, &request, resolver.mutable_channel());
if (status != ZX_OK) {
report_error(err_msg, "failed to create channel for resolver service: %d", status);
return ZX_ERR_INTERNAL;
}
status = fdio_service_connect_by_name(fidl::DiscoverableProtocolName<fprocess::Resolver>,
request.release());
if (status != ZX_OK) {
report_error(err_msg, "failed to connect to resolver service: %d", status);
return ZX_ERR_INTERNAL;
}
auto response = resolver.Resolve(fidl::StringView::FromExternal(name, name_len));
status = response.status();
if (status != ZX_OK) {
report_error(err_msg, "failed to send resolver request: %d", status);
return ZX_ERR_INTERNAL;
}
status = response->status;
if (status != ZX_OK) {
report_error(err_msg, "failed to resolve %.*s: %d", name_len, name, status);
return status;
}
*out_executable = std::move(response->executable);
*out_ldsvc = std::move(response->ldsvc.channel());
return ZX_OK;
}
// Find the starting point of the interpreter and the interpreter arguments in a #! script header.
// Note that the input buffer (line) will be modified to add a NUL after the interpreter name.
static zx_status_t parse_interp_spec(char* line, char** interp_start, char** args_start) {
*args_start = nullptr;
// Skip the '#!' prefix
char* next_char = line + 2;
// Skip whitespace
next_char += strspn(next_char, " \t");
// No interpreter specified
if (*next_char == '\0')
return ZX_ERR_INVALID_ARGS;
*interp_start = next_char;
// Skip the interpreter name
next_char += strcspn(next_char, " \t");
if (*next_char == '\0')
return ZX_OK;
// Add a NUL after the interpreter name
*next_char++ = '\0';
// Look for the args
next_char += strspn(next_char, " \t");
if (*next_char == '\0')
return ZX_OK;
*args_start = next_char;
return ZX_OK;
}
// handle_interpreters checks whether the provided vmo starts with a '#!' directive, and handles
// appropriately if it does.
//
// If a '#!' directive is present, we check whether it is either:
// 1) a specific '#!resolve' directive, in which case resolve_name is used to resolve the given
// executable name into a new executable vmo and appropriate loader service through the
// fuchsia.process.Resolver service, or
// 2) a general '#!' shebang interpreter directive, in which case the given interpreter is loaded
// via the current loader service and executable is updated. extra_args will also be appended
// to, and these arguments should be added to the front of argv.
//
// Directives will be resolved until none are detected, an error is encountered, or a resolution
// limit is reached. Also, mixing the two types is unsupported.
//
// The executable and ldsvc paramters are both inputs to and outputs from this function, and are
// updated based on the resolved directives. executable must always be valid, and ldsvc must be
// valid at minimum for the 2nd case above, though it should generally always be valid as well when
// calling this.
static zx_status_t handle_interpreters(zx::vmo* executable, zx::channel* ldsvc,
std::list<std::string>* extra_args, char* err_msg) {
extra_args->clear();
// Mixing #!resolve and general #! within a single spawn is unsupported so that the #!
// interpreters can simply be loaded from the current namespace.
bool handled_resolve = false;
bool handled_shebang = false;
for (size_t depth = 0; true; ++depth) {
// VMO sizes are page aligned and MAX_INTERPRETER_LINE_LEN < PAGE_SIZE (asserted above), so
// there's no use in checking VMO size explicitly here. Either the read fails because the VMO is
// zero-sized, and we handle it, or sizeof(line) < vmo_size.
char line[FDIO_SPAWN_MAX_INTERPRETER_LINE_LEN];
memset(line, 0, sizeof(line));
zx_status_t status = executable->read(line, 0, sizeof(line));
if (status != ZX_OK) {
report_error(err_msg, "error reading executable vmo: %d", status);
return status;
}
// If no "#!" prefix is present, we're done; treat this as an ELF file and continue loading.
if (line[0] != '#' || line[1] != '!') {
break;
}
// Interpreter resolution is not allowed to carry on forever.
if (depth == FDIO_SPAWN_MAX_INTERPRETER_DEPTH) {
report_error(err_msg, "hit recursion limit resolving interpreters");
return ZX_ERR_IO_INVALID;
}
// Find the end of the first line and NUL-terminate it to aid in parsing.
char* line_end = reinterpret_cast<char*>(memchr(line, '\n', sizeof(line)));
if (line_end) {
*line_end = '\0';
} else {
// If there's no newline, then the script may be a single line and lack a trailing newline.
// Look for the actual end of the script.
line_end = reinterpret_cast<char*>(memchr(line, '\0', sizeof(line)));
if (line_end == nullptr) {
// This implies that the first line is longer than MAX_INTERPRETER_LINE_LEN.
report_error(err_msg, "first line of script is too long");
return ZX_ERR_OUT_OF_RANGE;
}
}
size_t line_len = line_end - line;
if (memcmp(FDIO_RESOLVE_PREFIX, line, FDIO_RESOLVE_PREFIX_LEN) == 0) {
// This is a "#!resolve" directive; use fuchsia.process.Resolve to resolve the name into a new
// executable and appropriate loader.
handled_resolve = true;
if (handled_shebang) {
report_error(err_msg, "already resolved a #! directive, mixing #!resolve is unsupported");
return ZX_ERR_NOT_SUPPORTED;
}
char* name = &line[FDIO_RESOLVE_PREFIX_LEN];
size_t name_len = line_len - FDIO_RESOLVE_PREFIX_LEN;
status = resolve_name(name, name_len, executable, ldsvc, err_msg);
if (status != ZX_OK) {
return status;
}
} else {
// This is a general "#!" interpreter directive.
handled_shebang = true;
if (handled_resolve) {
report_error(err_msg, "already resolved a #!resolve directive, mixing #! is unsupported");
return ZX_ERR_NOT_SUPPORTED;
}
// Parse the interpreter spec to find the interpreter name and any args, and add those to
// extra_args.
char* interp_start;
char* args_start;
status = parse_interp_spec(line, &interp_start, &args_start);
if (status != ZX_OK) {
report_error(err_msg, "invalid #! interpreter spec");
return status;
}
// args_start and interp_start are safe to treat as NUL terminated because parse_interp_spec
// adds a NUL at the end of the interpreter name and we added an overall line NUL terminator
// above when finding the line end.
if (args_start != nullptr) {
extra_args->emplace_front(args_start);
}
extra_args->emplace_front(interp_start);
// Load the specified interpreter from the current namespace.
char path_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
status = load_path(interp_start, executable, path_msg);
if (status != ZX_OK) {
report_error(err_msg, "failed to load script interpreter '%s': %s", interp_start, path_msg);
return status;
}
}
}
return ZX_OK;
}
static zx_status_t send_handles(fidl::WireSyncClient<fprocess::Launcher>* launcher,
size_t handle_capacity, uint32_t flags, zx_handle_t job,
zx::channel ldsvc, zx_handle_t utc_clock, size_t action_count,
const fdio_spawn_action_t* actions, char* err_msg) {
// TODO(abarth): In principle, we should chunk array into separate
// messages if we exceed ZX_CHANNEL_MAX_MSG_HANDLES.
fprocess::wire::HandleInfo handle_infos[handle_capacity];
// VLAs cannot be initialized.
memset(handle_infos, 0, sizeof(handle_infos));
zx_status_t status = ZX_OK;
uint32_t h = 0;
size_t a = 0;
std::bitset<FDIO_MAX_FD> fds_in_use;
auto check_fd = [&fds_in_use](int fd) -> zx_status_t {
fd &= ~FDIO_FLAG_USE_FOR_STDIO;
if (fd < 0 || fd >= FDIO_MAX_FD) {
return ZX_ERR_OUT_OF_RANGE;
}
if (fds_in_use.test(fd)) {
return ZX_ERR_ALREADY_EXISTS;
}
fds_in_use.set(fd);
return ZX_OK;
};
if ((flags & FDIO_SPAWN_CLONE_JOB) != 0) {
auto* handle_info = &handle_infos[h++];
handle_info->id = PA_JOB_DEFAULT;
status =
zx_handle_duplicate(job, ZX_RIGHT_SAME_RIGHTS, handle_info->handle.reset_and_get_address());
if (status != ZX_OK) {
report_error(err_msg, "failed to duplicate job: %d", status);
goto cleanup;
}
}
// ldsvc may be valid if flags contains FDIO_SPAWN_DEFAULT_LDSVC or if a ldsvc was obtained
// through handling a '#!resolve' directive.
if (ldsvc.is_valid()) {
auto* handle_info = &handle_infos[h++];
handle_info->id = PA_LDSVC_LOADER;
handle_info->handle = std::move(ldsvc);
}
for (; a < action_count; ++a) {
zx_handle_t fd_handle = ZX_HANDLE_INVALID;
switch (actions[a].action) {
case FDIO_SPAWN_ACTION_CLONE_FD: {
status = check_fd(actions[a].fd.target_fd);
if (status != ZX_OK) {
report_error(err_msg, "invalid target %d to clone fd %d (action index %zu): %d",
actions[a].fd.target_fd, actions[a].fd.local_fd, a, status);
goto cleanup;
}
status = fdio_fd_clone(actions[a].fd.local_fd, &fd_handle);
if (status != ZX_OK) {
report_error(err_msg, "failed to clone fd %d (action index %zu): %d",
actions[a].fd.local_fd, a, status);
goto cleanup;
}
break;
}
case FDIO_SPAWN_ACTION_TRANSFER_FD: {
status = check_fd(actions[a].fd.target_fd);
if (status != ZX_OK) {
report_error(err_msg, "invalid target %d to transfer fd %d (action index %zu): %d",
actions[a].fd.target_fd, actions[a].fd.local_fd, a, status);
goto cleanup;
}
status = fdio_fd_transfer(actions[a].fd.local_fd, &fd_handle);
if (status != ZX_OK) {
report_error(err_msg, "failed to transfer fd %d (action index %zu): %d",
actions[a].fd.local_fd, a, status);
goto cleanup;
}
break;
}
case FDIO_SPAWN_ACTION_ADD_HANDLE: {
if (PA_HND_TYPE(actions[a].h.id) == PA_FD) {
int fd = PA_HND_ARG(actions[a].h.id) & ~FDIO_FLAG_USE_FOR_STDIO;
status = check_fd(fd);
if (status != ZX_OK) {
report_error(err_msg, "add-handle action has invalid fd %d (action index %zu): %d", fd,
a, status);
goto cleanup;
}
}
auto* handle_info = &handle_infos[h++];
handle_info->id = actions[a].h.id;
handle_info->handle.reset(actions[a].h.handle);
continue;
}
default: {
continue;
}
}
auto* handle_info = &handle_infos[h++];
handle_info->id = PA_HND(PA_FD, actions[a].fd.target_fd);
handle_info->handle.reset(fd_handle);
}
// Do these after generic actions so that actions can set these fds first.
if ((flags & FDIO_SPAWN_CLONE_STDIO) != 0) {
for (int fd = 0; fd < 3; ++fd) {
if (fds_in_use.test(fd)) {
// Skip a standard fd that was explicitly set by an action.
continue;
}
zx_handle_t fd_handle = ZX_HANDLE_INVALID;
status = fdio_fd_clone(fd, &fd_handle);
if (status == ZX_ERR_INVALID_ARGS || status == ZX_ERR_NOT_SUPPORTED) {
// This file descriptor is either closed, or something that doesn't
// support cloning into a handle (e.g. a null fdio object).
// We just skip it rather than generating an error.
continue;
}
if (status != ZX_OK) {
report_error(err_msg, "failed to clone fd %d: %d", fd, status);
goto cleanup;
}
auto* handle_info = &handle_infos[h++];
handle_info->id = PA_HND(PA_FD, fd);
handle_info->handle.reset(fd_handle);
}
}
if ((flags & FDIO_SPAWN_CLONE_UTC_CLOCK) != 0) {
if (utc_clock != ZX_HANDLE_INVALID) {
auto* handle_info = &handle_infos[h++];
handle_info->id = PA_CLOCK_UTC;
status = zx_handle_duplicate(
utc_clock, ZX_RIGHT_READ | ZX_RIGHT_WAIT | ZX_RIGHT_DUPLICATE | ZX_RIGHT_TRANSFER,
handle_info->handle.reset_and_get_address());
if (status != ZX_OK) {
report_error(err_msg, "failed to clone UTC clock: %d", status);
goto cleanup;
}
}
}
ZX_DEBUG_ASSERT(h <= handle_capacity);
status =
launcher
->AddHandles(fidl::VectorView<fprocess::wire::HandleInfo>::FromExternal(handle_infos, h))
.status();
if (status != ZX_OK)
report_error(err_msg, "failed to send handles: %d", status);
return status;
cleanup:
// If |a| is less than |action_count|, that means we encountered an error
// before we processed all the actions. We need to iterate through the rest
// of the table and close the file descriptors and handles that we're
// supposed to consume.
for (size_t i = a; i < action_count; ++i) {
switch (actions[i].action) {
case FDIO_SPAWN_ACTION_TRANSFER_FD:
close(actions[i].fd.local_fd);
break;
case FDIO_SPAWN_ACTION_ADD_HANDLE:
zx_handle_close(actions[i].h.handle);
break;
}
}
return status;
}
static zx_status_t send_namespace(fidl::WireSyncClient<fprocess::Launcher>* launcher,
size_t name_count, fdio_flat_namespace_t* flat,
size_t action_count, const fdio_spawn_action_t* actions,
char* err_msg) {
fprocess::wire::NameInfo names[name_count];
// VLAs cannot be initialized.
memset(names, 0, sizeof(names));
size_t n = 0;
if (flat) {
while (n < flat->count) {
auto* name = &names[n];
auto path = flat->path[n];
name->path = fidl::StringView::FromExternal(path);
name->directory = fidl::ClientEnd<fio::Directory>(zx::channel(flat->handle[n]));
flat->handle[n] = ZX_HANDLE_INVALID;
n++;
}
}
for (size_t i = 0; i < action_count; ++i) {
if (actions[i].action == FDIO_SPAWN_ACTION_ADD_NS_ENTRY) {
auto* name = &names[n];
auto path = actions[i].ns.prefix;
name->path = fidl::StringView::FromExternal(path);
name->directory = fidl::ClientEnd<fio::Directory>(zx::channel(actions[i].ns.handle));
n++;
}
}
ZX_DEBUG_ASSERT(n == name_count);
zx_status_t status =
launcher->AddNames(fidl::VectorView<fprocess::wire::NameInfo>::FromExternal(names, n))
.status();
if (status != ZX_OK)
report_error(err_msg, "failed send namespace: %d", status);
return status;
}
__EXPORT
zx_status_t fdio_spawn(zx_handle_t job, uint32_t flags, const char* path, const char* const* argv,
zx_handle_t* process_out) {
return fdio_spawn_etc(job, flags, path, argv, nullptr, 0, nullptr, process_out, nullptr);
}
__EXPORT
zx_status_t fdio_spawn_etc(zx_handle_t job, uint32_t flags, const char* path,
const char* const* argv, const char* const* explicit_environ,
size_t action_count, const fdio_spawn_action_t* actions,
zx_handle_t* process_out, char* err_msg) {
zx::vmo executable;
char path_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
zx_status_t status = load_path(path, &executable, path_msg);
if (status != ZX_OK) {
report_error(err_msg, "failed to load executable from %s: %s", path, path_msg);
// Set |err_msg| to nullptr to prevent |fdio_spawn_vmo| from generating
// a less useful error message.
err_msg = nullptr;
}
// Always call fdio_spawn_vmo to clean up arguments. If |executable| is
// |ZX_HANDLE_INVALID|, then |fdio_spawn_vmo| will generate an error.
zx_status_t spawn_status =
fdio_spawn_vmo(job, flags, executable.release(), argv, explicit_environ, action_count,
actions, process_out, err_msg);
// Use |status| if we already had an error before calling |fdio_spawn_vmo|.
// Otherwise, we'll always return |ZX_ERR_INVALID_ARGS| rather than the more
// useful status from |load_path|.
return status != ZX_OK ? status : spawn_status;
}
static bool should_clone_namespace(std::string_view path,
const std::vector<std::string_view>& prefixes) {
for (const auto& prefix : prefixes) {
// Only share path if there is a directory prefix in |prefixes| that matches the path.
// Also take care to not match partial directory names. Ex, /foo should not match
// /foobar.
if (path.compare(0, prefix.size(), prefix) == 0 &&
(path.size() == prefix.size() || path[prefix.size()] == '/')) {
return true;
}
}
return false;
}
static void filter_flat_namespace(fdio_flat_namespace_t* flat,
const std::vector<std::string_view>& prefixes) {
size_t read, write;
for (read = 0, write = 0; read < flat->count; ++read) {
if (should_clone_namespace(flat->path[read], prefixes)) {
if (read != write) {
flat->handle[write] = flat->handle[read];
flat->type[write] = flat->type[read];
const_cast<const char**>(flat->path)[write] = flat->path[read];
}
write++;
} else {
zx_handle_close(flat->handle[read]);
flat->handle[read] = ZX_HANDLE_INVALID;
}
}
flat->count = write;
}
__EXPORT
zx_status_t fdio_spawn_vmo(zx_handle_t job, uint32_t flags, zx_handle_t executable_vmo,
const char* const* argv, const char* const* explicit_environ,
size_t action_count, const fdio_spawn_action_t* actions,
zx_handle_t* process_out, char* err_msg) {
zx_status_t status = ZX_OK;
fdio_flat_namespace_t* flat = nullptr;
size_t name_count = 0;
size_t handle_capacity = 0;
std::vector<std::string_view> shared_dirs;
fidl::WireSyncClient<fprocess::Launcher> launcher;
zx::channel request;
zx::channel ldsvc;
const char* process_name = nullptr;
size_t process_name_size = 0;
std::list<std::string> extra_args;
zx_handle_t utc_clock = ZX_HANDLE_INVALID;
fprocess::wire::LaunchInfo launch_info = {
.executable = zx::vmo(executable_vmo),
};
executable_vmo = ZX_HANDLE_INVALID;
if (err_msg)
err_msg[0] = '\0';
// We intentionally don't fill in |err_msg| for invalid args.
if (!launch_info.executable.is_valid() || !argv || (action_count != 0 && !actions)) {
status = ZX_ERR_INVALID_ARGS;
goto cleanup;
}
if (job == ZX_HANDLE_INVALID)
job = zx_job_default();
process_name = argv[0];
for (size_t i = 0; i < action_count; ++i) {
switch (actions[i].action) {
case FDIO_SPAWN_ACTION_CLONE_FD:
case FDIO_SPAWN_ACTION_TRANSFER_FD:
++handle_capacity;
break;
case FDIO_SPAWN_ACTION_ADD_NS_ENTRY:
if (actions[i].ns.handle == ZX_HANDLE_INVALID || !actions[i].ns.prefix) {
status = ZX_ERR_INVALID_ARGS;
goto cleanup;
}
++name_count;
break;
case FDIO_SPAWN_ACTION_ADD_HANDLE:
if (actions[i].h.handle == ZX_HANDLE_INVALID) {
status = ZX_ERR_INVALID_ARGS;
goto cleanup;
}
if (actions[i].h.id == PA_CLOCK_UTC) {
// A UTC Clock handle is explicitly passed in.
if ((flags & FDIO_SPAWN_CLONE_UTC_CLOCK) != 0) {
status = ZX_ERR_INVALID_ARGS;
report_error(err_msg, "cannot clone global UTC clock and send explicit clock");
goto cleanup;
}
}
++handle_capacity;
break;
case FDIO_SPAWN_ACTION_SET_NAME:
if (actions[i].name.data == nullptr) {
status = ZX_ERR_INVALID_ARGS;
goto cleanup;
}
process_name = actions[i].name.data;
break;
case FDIO_SPAWN_ACTION_CLONE_DIR: {
if (!actions[i].dir.prefix) {
status = ZX_ERR_INVALID_ARGS;
goto cleanup;
}
// The path must be absolute (rooted at '/') and not contain a trailing '/', but do
// allow the root namespace to be specified as "/".
size_t len = strlen(actions[i].dir.prefix);
if (len == 0 || actions[i].dir.prefix[0] != '/' ||
(len > 1 && actions[i].dir.prefix[len - 1] == '/')) {
status = ZX_ERR_INVALID_ARGS;
goto cleanup;
} else if (len == 1 && actions[i].dir.prefix[0] == '/') {
flags |= FDIO_SPAWN_CLONE_NAMESPACE;
} else {
shared_dirs.push_back(actions[i].dir.prefix);
}
} break;
default:
break;
}
}
if (!process_name) {
status = ZX_ERR_INVALID_ARGS;
goto cleanup;
}
if ((flags & FDIO_SPAWN_CLONE_JOB) != 0)
++handle_capacity;
// Need to clone ldsvc here so it's available for handle_interpreters.
if ((flags & FDIO_SPAWN_DEFAULT_LDSVC) != 0) {
status = dl_clone_loader_service(ldsvc.reset_and_get_address());
if (status != ZX_OK) {
report_error(err_msg, "failed to clone library loader service: %d", status);
goto cleanup;
}
}
if ((flags & FDIO_SPAWN_CLONE_STDIO) != 0)
handle_capacity += 3;
if ((flags & FDIO_SPAWN_CLONE_UTC_CLOCK) != 0) {
utc_clock = zx_utc_reference_get();
if (utc_clock != ZX_HANDLE_INVALID) {
++handle_capacity;
}
}
if (!shared_dirs.empty() || (flags & FDIO_SPAWN_CLONE_NAMESPACE) != 0) {
status = fdio_ns_export_root(&flat);
if (status != ZX_OK) {
report_error(err_msg, "Could not make copy of root namespace: %d", status);
goto cleanup;
}
// If we don't clone the entire namespace, we need to filter down to only the
// directories that are prefixed by paths in FDIO_SPAWN_ACTION_CLONE_DIR actions.
if ((flags & FDIO_SPAWN_CLONE_NAMESPACE) == 0) {
filter_flat_namespace(flat, shared_dirs);
}
name_count += flat->count;
}
// resolve any '#!' directives that are present, updating executable and ldsvc as needed
status = handle_interpreters(&launch_info.executable, &ldsvc, &extra_args, err_msg);
if (status != ZX_OK) {
goto cleanup;
}
if (ldsvc.is_valid()) {
++handle_capacity;
}
status = zx::channel::create(0, &request, launcher.mutable_channel());
if (status != ZX_OK) {
report_error(err_msg, "failed to create channel for launcher service: %d", status);
goto cleanup;
}
status = fdio_service_connect_by_name(fidl::DiscoverableProtocolName<fprocess::Launcher>,
request.release());
if (status != ZX_OK) {
report_error(err_msg, "failed to connect to launcher service: %d", status);
goto cleanup;
}
// send any extra arguments from handle_interpreters, then the normal arguments.
{
size_t capacity = extra_args.size();
for (auto it = argv; *it; ++it) {
++capacity;
}
std::vector<fidl::VectorView<uint8_t>> args;
args.reserve(capacity);
for (const auto& arg : extra_args) {
auto ptr = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(arg.data()));
args.emplace_back(fidl::VectorView<uint8_t>::FromExternal(ptr, arg.length()));
}
for (auto it = argv; *it; ++it) {
auto ptr = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(*it));
args.emplace_back(fidl::VectorView<uint8_t>::FromExternal(ptr, strlen(*it)));
}
status =
launcher.AddArgs(fidl::VectorView<fidl::VectorView<uint8_t>>::FromExternal(args)).status();
if (status != ZX_OK) {
report_error(err_msg, "failed to send argument vector: %d", status);
goto cleanup;
}
}
if (explicit_environ) {
size_t capacity = 0;
for (auto it = explicit_environ; *it; ++it) {
++capacity;
}
std::vector<fidl::VectorView<uint8_t>> env;
env.reserve(capacity);
for (auto it = explicit_environ; *it; ++it) {
auto ptr = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(*it));
env.emplace_back(fidl::VectorView<uint8_t>::FromExternal(ptr, strlen(*it)));
}
status = launcher.AddEnvirons(fidl::VectorView<fidl::VectorView<uint8_t>>::FromExternal(env))
.status();
if (status != ZX_OK) {
report_error(err_msg, "failed to send environment: %d", status);
goto cleanup;
}
} else if ((flags & FDIO_SPAWN_CLONE_ENVIRON) != 0) {
size_t capacity = 0;
for (auto it = environ; *it; ++it) {
++capacity;
}
std::vector<fidl::VectorView<uint8_t>> env;
env.reserve(capacity);
for (auto it = environ; *it; ++it) {
auto ptr = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(*it));
env.emplace_back(fidl::VectorView<uint8_t>::FromExternal(ptr, strlen(*it)));
}
status = launcher.AddEnvirons(fidl::VectorView<fidl::VectorView<uint8_t>>::FromExternal(env))
.status();
if (status != ZX_OK) {
report_error(err_msg, "failed to send environment clone with FDIO_SPAWN_CLONE_ENVIRON: %d",
status);
goto cleanup;
}
}
if (handle_capacity) {
status = send_handles(&launcher, handle_capacity, flags, job, std::move(ldsvc), utc_clock,
action_count, actions, err_msg);
if (status != ZX_OK) {
// When |send_handles| fails, it consumes all the action handles
// that it knows about, but it doesn't consume the handles used for
// |FDIO_SPAWN_ACTION_ADD_NS_ENTRY|.
for (size_t i = 0; i < action_count; ++i) {
switch (actions[i].action) {
case FDIO_SPAWN_ACTION_ADD_NS_ENTRY:
zx_handle_close(actions[i].ns.handle);
break;
default:
break;
}
}
action_count = 0; // We've now consumed all the handles.
goto cleanup;
}
}
if (name_count) {
status = send_namespace(&launcher, name_count, flat, action_count, actions, err_msg);
if (status != ZX_OK) {
action_count = 0;
goto cleanup;
}
}
action_count = 0; // We've consumed all the actions at this point.
process_name_size = strlen(process_name);
if (process_name_size >= ZX_MAX_NAME_LEN)
process_name_size = ZX_MAX_NAME_LEN - 1;
launch_info.name = fidl::StringView::FromExternal(process_name, process_name_size);
status = zx_handle_duplicate(job, ZX_RIGHT_SAME_RIGHTS, launch_info.job.reset_and_get_address());
if (status != ZX_OK) {
report_error(err_msg, "failed to duplicate job handle: %d", status);
goto cleanup;
}
{
auto reply = launcher.Launch(std::move(launch_info));
status = reply.status();
if (status != ZX_OK) {
report_error(err_msg, "failed to send launch message: %d", status);
goto cleanup;
}
status = reply->status;
if (status == ZX_OK) {
// The launcher claimed to succeed but didn't actually give us a
// process handle. Something is wrong with the launcher.
if (!reply->process.is_valid()) {
status = ZX_ERR_BAD_HANDLE;
report_error(err_msg, "failed receive process handle");
// This jump skips over closing the process handle, but that's
// fine because we didn't receive a process handle.
goto cleanup;
}
if (process_out) {
*process_out = reply->process.release();
}
} else {
report_error(err_msg, "fuchsia.process.Launcher failed");
}
}
cleanup:
if (actions) {
for (size_t i = 0; i < action_count; ++i) {
switch (actions[i].action) {
case FDIO_SPAWN_ACTION_ADD_NS_ENTRY:
zx_handle_close(actions[i].ns.handle);
break;
case FDIO_SPAWN_ACTION_ADD_HANDLE:
zx_handle_close(actions[i].h.handle);
break;
default:
break;
}
}
}
if (flat) {
fdio_ns_free_flat_ns(flat);
}
// If we observe ZX_ERR_NOT_FOUND in the VMO spawn, it really means a
// dependency of launching could not be fulfilled, but clients of spawn_etc
// and friends could misinterpret this to mean the binary was not found.
// Instead we remap that specific case to ZX_ERR_INTERNAL.
if (status == ZX_ERR_NOT_FOUND) {
return ZX_ERR_INTERNAL;
}
return status;
}