blob: 904d878ce8146c6c84a2d225ff852930175f6b0d [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 <lib/fdio/spawn.h>
#include <fcntl.h>
#include <fuchsia/process/c/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/zx/channel.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.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 <utility>
#include "private.h"
#define FDIO_RESOLVE_PREFIX "#!resolve "
#define FDIO_RESOLVE_PREFIX_LEN 10
// It is possible to setup an infinite loop of resolvers. 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_RESOLVE_DEPTH 255
#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 zx_status_t load_path(const char* path, zx::vmo* out_vmo) {
int fd = open(path, O_RDONLY);
if (fd < 0)
return ZX_ERR_NOT_FOUND;
zx::vmo vmo;
zx::vmo exec_vmo;
zx_status_t status = fdio_get_vmo_clone(fd, vmo.reset_and_get_address());
close(fd);
if (status != ZX_OK) {
return status;
}
status = vmo.replace_as_executable(zx::handle(), &exec_vmo);
if (status != ZX_OK) {
return status;
}
if (strlen(path) >= ZX_MAX_NAME_LEN) {
const char* p = strrchr(path, '/');
if (p != NULL) {
path = p + 1;
}
}
status = exec_vmo.set_property(ZX_PROP_NAME, path, strlen(path));
if (status != ZX_OK) {
return status;
}
*out_vmo = std::move(exec_vmo);
return status;
}
static void measure_cstring_array(const char* const* array, size_t* count_out, size_t* len_out) {
size_t i = 0;
size_t len = 0;
while (array[i]) {
len += FIDL_ALIGN(strlen(array[i]));
++i;
}
*count_out = i;
*len_out = len;
}
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);
}
// 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) {
zx::channel resolver, resolver_request;
zx_status_t status = zx::channel::create(0, &resolver, &resolver_request);
if (status != ZX_OK) {
report_error(err_msg, "failed to create channel: %d", status);
return ZX_ERR_INTERNAL;
}
status = fdio_service_connect("/svc/fuchsia.process.Resolver", resolver_request.release());
if (status != ZX_OK) {
report_error(err_msg, "failed to connect to resolver service: %d", status);
return ZX_ERR_INTERNAL;
}
zx_status_t io_status = fuchsia_process_ResolverResolve(
resolver.get(), name, name_len, &status,
out_executable->reset_and_get_address(),
out_ldsvc->reset_and_get_address());
if (io_status != ZX_OK) {
report_error(err_msg, "failed to send resolver request: %d", io_status);
return ZX_ERR_INTERNAL;
}
if (status != ZX_OK) {
report_error(err_msg, "failed to resolve %.*s", name_len, name);
}
return status;
}
static zx_status_t send_cstring_array(const zx::channel& launcher, int ordinal, const char* const* array) {
size_t count = 0;
size_t len = 0;
// TODO(abarth): In principle, we should chunk array into separate
// messages if we exceed ZX_CHANNEL_MAX_MSG_BYTES.
measure_cstring_array(array, &count, &len);
if (count == 0)
return ZX_OK;
size_t msg_len = sizeof(fidl_message_header_t) + sizeof(fidl_vector_t) + count * sizeof(fidl_vector_t) + FIDL_ALIGN(len);
uint8_t msg[msg_len];
memset(msg, 0, msg_len);
fidl_message_header_t* hdr = (fidl_message_header_t*)msg;
fidl_vector_t* vector = (fidl_vector_t*)hdr + 1;
fidl_vector_t* bytes = (fidl_vector_t*)(vector + 1);
uint8_t* payload = (uint8_t*)(bytes + count);
hdr->ordinal = ordinal;
vector->count = count;
vector->data = (void*)FIDL_ALLOC_PRESENT;
size_t offset = 0;
for (size_t i = 0; i < count; ++i) {
size_t size = strlen(array[i]);
bytes[i].count = size;
bytes[i].data = (void*)FIDL_ALLOC_PRESENT;
memcpy(payload + offset, array[i], size);
offset += FIDL_ALIGN(size);
}
return launcher.write(0, msg, static_cast<uint32_t>(msg_len), NULL, 0);
}
static zx_status_t send_handles(const zx::channel& launcher, size_t handle_capacity,
uint32_t flags, zx_handle_t job,
zx::channel ldsvc, 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.
size_t msg_capacity = sizeof(fuchsia_process_LauncherAddHandlesRequest) + FIDL_ALIGN(handle_capacity * sizeof(fuchsia_process_HandleInfo));
uint8_t msg[msg_capacity];
memset(msg, 0, msg_capacity);
fuchsia_process_LauncherAddHandlesRequest* req = (fuchsia_process_LauncherAddHandlesRequest*)msg;
fuchsia_process_HandleInfo* handle_infos = (fuchsia_process_HandleInfo*)(req + 1);
zx_handle_t handles[handle_capacity];
memset(handles, 0, sizeof(handles));
req->hdr.ordinal = fuchsia_process_LauncherAddHandlesOrdinal;
zx_status_t status = ZX_OK;
uint32_t h = 0;
size_t a = 0;
size_t msg_len = 0;
if ((flags & FDIO_SPAWN_CLONE_JOB) != 0) {
handle_infos[h].handle = FIDL_HANDLE_PRESENT;
handle_infos[h].id = PA_JOB_DEFAULT;
status = zx_handle_duplicate(job, ZX_RIGHT_SAME_RIGHTS, &handles[h++]);
if (status != ZX_OK) {
report_error(err_msg, "failed to duplicate job: %d", status);
goto cleanup;
}
}
if ((flags & FDIO_SPAWN_DEFAULT_LDSVC) != 0) {
handle_infos[h].handle = FIDL_HANDLE_PRESENT;
handle_infos[h].id = PA_LDSVC_LOADER;
if (!ldsvc.is_valid()) {
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;
}
}
handles[h++] = ldsvc.release();
} else if (ldsvc.is_valid()) {
ldsvc.reset();
}
if ((flags & FDIO_SPAWN_CLONE_STDIO) != 0) {
for (int fd = 0; fd < 3; ++fd) {
zx_handle_t fd_handle = ZX_HANDLE_INVALID;
status = fdio_fd_clone(fd, &fd_handle);
if (status == ZX_ERR_INVALID_ARGS) {
// This file descriptor is closed. 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;
}
handle_infos[h].handle = FIDL_HANDLE_PRESENT;
handle_infos[h].id = PA_HND(PA_FD, fd);
handles[h++] = fd_handle;
}
}
for (; a < action_count; ++a) {
zx_handle_t fd_handle = ZX_HANDLE_INVALID;
switch (actions[a].action) {
case FDIO_SPAWN_ACTION_CLONE_FD:
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 = 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:
handle_infos[h].handle = FIDL_HANDLE_PRESENT;
handle_infos[h].id = actions[a].h.id;
handles[h++] = actions[a].h.handle;
continue;
default:
continue;
}
handle_infos[h].handle = FIDL_HANDLE_PRESENT;
handle_infos[h].id = PA_HND(PA_FD, actions[a].fd.target_fd);
handles[h++] = fd_handle;
}
req->handles.count = h;
req->handles.data = (void*)FIDL_ALLOC_PRESENT;
ZX_DEBUG_ASSERT(h <= handle_capacity);
msg_len = sizeof(fuchsia_process_LauncherAddHandlesRequest) + FIDL_ALIGN(h * sizeof(fuchsia_process_HandleInfo));
status = launcher.write(0, msg, static_cast<uint32_t>(msg_len), handles, h);
if (status != ZX_OK)
report_error(err_msg, "failed send handles: %d", status);
return status;
cleanup:
zx_handle_close_many(handles, h);
// 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(const zx::channel& launcher, size_t name_count, size_t name_len,
fdio_flat_namespace_t* flat, size_t action_count,
const fdio_spawn_action_t* actions, char* err_msg) {
size_t msg_len = sizeof(fuchsia_process_LauncherAddNamesRequest) + FIDL_ALIGN(name_count * sizeof(fuchsia_process_NameInfo)) + FIDL_ALIGN(name_len);
uint8_t msg[msg_len];
memset(msg, 0, msg_len);
fuchsia_process_LauncherAddNamesRequest* req = (fuchsia_process_LauncherAddNamesRequest*)msg;
fuchsia_process_NameInfo* names = (fuchsia_process_NameInfo*)(req + 1);
uint8_t* payload = (uint8_t*)(names + name_count);
zx_handle_t handles[name_count];
memset(handles, 0, sizeof(handles));
req->hdr.ordinal = fuchsia_process_LauncherAddNamesOrdinal;
req->names.count = name_count;
req->names.data = reinterpret_cast<void*>(FIDL_ALLOC_PRESENT);
size_t n = 0;
uint32_t h = 0;
size_t offset = 0;
if (flat) {
while (n < flat->count) {
size_t size = strlen(flat->path[n]);
names[n].path.size = size;
names[n].path.data = reinterpret_cast<char*>(FIDL_ALLOC_PRESENT);
names[n].directory = FIDL_HANDLE_PRESENT;
memcpy(payload + offset, flat->path[n], size);
offset += FIDL_ALIGN(size);
handles[h++] = flat->handle[n];
n++;
}
}
for (size_t i = 0; i < action_count; ++i) {
if (actions[i].action == FDIO_SPAWN_ACTION_ADD_NS_ENTRY) {
size_t size = strlen(actions[i].ns.prefix);
names[n].path.size = size;
names[n].path.data = reinterpret_cast<char*>(FIDL_ALLOC_PRESENT);
names[n].directory = FIDL_HANDLE_PRESENT;
memcpy(payload + offset, actions[i].ns.prefix, size);
offset += FIDL_ALIGN(size);
handles[h++] = actions[i].ns.handle;
n++;
}
}
ZX_DEBUG_ASSERT(n == name_count);
ZX_DEBUG_ASSERT(h == name_count);
zx_status_t status = launcher.write(0, msg, static_cast<uint32_t>(msg_len), handles, h);
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, NULL, 0, NULL, process_out, NULL);
}
__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;
zx_status_t status = load_path(path, &executable);
if (status != ZX_OK) {
report_error(err_msg, "failed to load executable from %s", path);
// Set |err_msg| to NULL to prevent |fdio_spawn_vmo| from generating
// a less useful error message.
err_msg = NULL;
}
// 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;
}
__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 = NULL;
size_t name_count = 0;
size_t name_len = 0;
size_t handle_capacity = 0;
zx::channel launcher;
zx::channel launcher_request;
zx_handle_t msg_handles[FDIO_SPAWN_LAUNCH_HANDLE_COUNT];
zx::channel ldsvc;
const char* process_name = NULL;
size_t process_name_size = 0;
zx::vmo executable(executable_vmo);
executable_vmo = ZX_HANDLE_INVALID;
memset(msg_handles, 0, sizeof(msg_handles));
if (err_msg)
err_msg[0] = '\0';
// We intentionally don't fill in |err_msg| for invalid args.
if (!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;
name_len += FIDL_ALIGN(strlen(actions[i].ns.prefix));
break;
case FDIO_SPAWN_ACTION_ADD_HANDLE:
if (actions[i].h.handle == ZX_HANDLE_INVALID) {
status = ZX_ERR_INVALID_ARGS;
goto cleanup;
}
++handle_capacity;
break;
case FDIO_SPAWN_ACTION_SET_NAME:
if (actions[i].name.data == NULL) {
status = ZX_ERR_INVALID_ARGS;
goto cleanup;
}
process_name = actions[i].name.data;
break;
default:
break;
}
}
if (!process_name) {
status = ZX_ERR_INVALID_ARGS;
goto cleanup;
}
if ((flags & FDIO_SPAWN_CLONE_JOB) != 0)
++handle_capacity;
if ((flags & FDIO_SPAWN_DEFAULT_LDSVC) != 0)
++handle_capacity;
if ((flags & FDIO_SPAWN_CLONE_STDIO) != 0)
handle_capacity += 3;
if ((flags & FDIO_SPAWN_CLONE_NAMESPACE) != 0) {
status = fdio_ns_export_root(&flat);
name_count += flat->count;
for (size_t i = 0; i < flat->count; ++i) {
name_len += FIDL_ALIGN(strlen(flat->path[i]));
}
}
// resolve vmos containing #!resolve, updating the vmo & ldsvc
for (size_t i = 0; true; ++i) {
char head[fuchsia_process_MAX_RESOLVE_NAME_SIZE + FDIO_RESOLVE_PREFIX_LEN];
ZX_ASSERT(sizeof(head) < PAGE_SIZE);
memset(head, 0, sizeof(head));
status = executable.read(head, 0, sizeof(head));
if (status != ZX_OK) {
report_error(err_msg, "error reading executable vmo: %d", status);
goto cleanup;
}
if (memcmp(FDIO_RESOLVE_PREFIX, head, FDIO_RESOLVE_PREFIX_LEN) != 0) {
break;
}
// resolves are not allowed to carry on forever.
if (i == FDIO_SPAWN_MAX_RESOLVE_DEPTH) {
status = ZX_ERR_IO_INVALID;
report_error(err_msg, "hit recursion limit resolving name");
goto cleanup;
}
char* name = &head[FDIO_RESOLVE_PREFIX_LEN];
size_t len = fuchsia_process_MAX_RESOLVE_NAME_SIZE;
char* end = reinterpret_cast<char*>(memchr(name, '\n', len));
if (end != NULL) {
len = end - name;
}
status = resolve_name(name, len, &executable, &ldsvc, err_msg);
if (status != ZX_OK) {
goto cleanup;
}
}
status = zx::channel::create(0, &launcher, &launcher_request);
if (status != ZX_OK) {
report_error(err_msg, "failed to create channel for process launcher: %d", status);
goto cleanup;
}
status = fdio_service_connect("/svc/fuchsia.process.Launcher", launcher_request.release());
if (status != ZX_OK) {
report_error(err_msg, "failed to connect to launcher service: %d", status);
goto cleanup;
}
status = send_cstring_array(launcher, fuchsia_process_LauncherAddArgsOrdinal, argv);
if (status != ZX_OK) {
report_error(err_msg, "failed to send argument vector: %d", status);
goto cleanup;
}
if (explicit_environ) {
status = send_cstring_array(launcher, fuchsia_process_LauncherAddEnvironsOrdinal, explicit_environ);
if (status != ZX_OK) {
report_error(err_msg, "failed to send environment: %d", status);
goto cleanup;
}
} else if ((flags & FDIO_SPAWN_CLONE_ENVIRON) != 0) {
status = send_cstring_array(launcher, fuchsia_process_LauncherAddEnvironsOrdinal, (const char* const*)environ);
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), 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, name_len, 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;
{
struct {
FIDL_ALIGNDECL
fuchsia_process_LauncherLaunchRequest req;
uint8_t process_name[FIDL_ALIGN(ZX_MAX_NAME_LEN)];
} msg;
memset(&msg, 0, sizeof(msg));
size_t msg_len = sizeof(fuchsia_process_LauncherLaunchRequest) + FIDL_ALIGN(process_name_size);
msg.req.hdr.ordinal = fuchsia_process_LauncherLaunchOrdinal;
msg.req.info.executable = FIDL_HANDLE_PRESENT;
msg.req.info.job = FIDL_HANDLE_PRESENT;
msg.req.info.name.size = process_name_size;
msg.req.info.name.data = reinterpret_cast<char*>(FIDL_ALLOC_PRESENT);
memcpy(msg.process_name, process_name, process_name_size);
msg_handles[FDIO_SPAWN_LAUNCH_HANDLE_EXECUTABLE] = executable.release();
status = zx_handle_duplicate(job, ZX_RIGHT_SAME_RIGHTS, &msg_handles[FDIO_SPAWN_LAUNCH_HANDLE_JOB]);
if (status != ZX_OK) {
report_error(err_msg, "failed to duplicate job handle: %d", status);
goto cleanup;
}
fuchsia_process_LauncherLaunchResponse reply;
zx_handle_t process = ZX_HANDLE_INVALID;
memset(&reply, 0, sizeof(reply));
zx_channel_call_args_t args;
args.wr_bytes = &msg;
args.wr_handles = msg_handles;
args.rd_bytes = &reply;
args.rd_handles = &process;
args.wr_num_bytes = static_cast<uint32_t>(msg_len);
args.wr_num_handles = FDIO_SPAWN_LAUNCH_HANDLE_COUNT;
args.rd_num_bytes = sizeof(reply);
args.rd_num_handles = FDIO_SPAWN_LAUNCH_REPLY_HANDLE_COUNT;
uint32_t actual_bytes = 0;
uint32_t actual_handles = 0;
status = launcher.call(0, zx::time::infinite(), &args, &actual_bytes,
&actual_handles);
// zx_channel_call always consumes handles.
memset(msg_handles, 0, sizeof(msg_handles));
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 (process == ZX_HANDLE_INVALID) {
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 = process;
process = ZX_HANDLE_INVALID;
}
} else {
report_error(err_msg, "fuchsia.process.Launcher failed");
}
if (process != ZX_HANDLE_INVALID)
zx_handle_close(process);
}
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;
}
}
}
free(flat);
if (msg_handles[FDIO_SPAWN_LAUNCH_HANDLE_EXECUTABLE] != ZX_HANDLE_INVALID)
zx_handle_close(msg_handles[FDIO_SPAWN_LAUNCH_HANDLE_EXECUTABLE]);
if (msg_handles[FDIO_SPAWN_LAUNCH_HANDLE_JOB] != ZX_HANDLE_INVALID)
zx_handle_close(msg_handles[FDIO_SPAWN_LAUNCH_HANDLE_JOB]);
// 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;
}