blob: 11d155fe57a414be038b95f675d0f87e7f86cd70 [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 <lib/fdio/io.h>
#include <lib/fdio/limits.h>
#include <lib/fdio/namespace.h>
#include <lib/fdio/util.h>
#include <fuchsia/process/c/fidl.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/dlfcn.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
#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)
// Even though FDIO_MAX_HANDLES is 3, the clone and transfer operations can only
// ever generate 2 handles.
#define FDIO_MAX_HANDLES_FOR_CLONE_OR_TRANSFER ((size_t)2u)
// 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_handle_t* vmo) {
int fd = open(path, O_RDONLY);
if (fd < 0)
return ZX_ERR_IO;
zx_status_t status = fdio_get_vmo_clone(fd, vmo);
close(fd);
if (status == ZX_OK) {
if (strlen(path) >= ZX_MAX_NAME_LEN) {
const char* p = strrchr(path, '/');
if (p != NULL) {
path = p + 1;
}
}
zx_object_set_property(*vmo, ZX_PROP_NAME, path, strlen(path));
}
return status;
}
static void measure_string_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);
}
static zx_status_t send_string_array(zx_handle_t 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_string_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_string_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_string_t* strings = (fidl_string_t*)(vector + 1);
uint8_t* payload = (uint8_t*)(strings + 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]);
strings[i].size = size;
strings[i].data = (void*)FIDL_ALLOC_PRESENT;
memcpy(payload + offset, array[i], size);
offset += FIDL_ALIGN(size);
}
return zx_channel_write(launcher, 0, msg, msg_len, NULL, 0);
}
static zx_status_t send_handles(zx_handle_t launcher, size_t handle_capacity,
uint32_t flags, zx_handle_t job, 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;
size_t h = 0;
size_t a = 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_CLONE_LDSVC) != 0) {
handle_infos[h].handle = FIDL_HANDLE_PRESENT;
handle_infos[h].id = PA_LDSVC_LOADER;
status = dl_clone_loader_service(&handles[h++]);
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) {
for (int fd = 0; fd < 3; ++fd) {
zx_handle_t fdio_handles[FDIO_MAX_HANDLES];
uint32_t fdio_types[FDIO_MAX_HANDLES];
status = fdio_clone_fd(fd, fd, fdio_handles, fdio_types);
if (status == ZX_ERR_BAD_HANDLE) {
// 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;
}
ZX_ASSERT((size_t)status <= FDIO_MAX_HANDLES_FOR_CLONE_OR_TRANSFER);
for (int i = 0; i < status; ++i) {
handle_infos[h].handle = FIDL_HANDLE_PRESENT;
handle_infos[h].id = fdio_types[i];
handles[h++] = fdio_handles[i];
}
}
}
for ( ; a < action_count; ++a) {
zx_handle_t fdio_handles[FDIO_MAX_HANDLES];
uint32_t fdio_types[FDIO_MAX_HANDLES];
switch (actions[a].action) {
case FDIO_SPAWN_ACTION_CLONE_FD:
status = fdio_clone_fd(actions[a].fd.local_fd, actions[a].fd.target_fd, fdio_handles, fdio_types);
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_transfer_fd(actions[a].fd.local_fd, actions[a].fd.target_fd, fdio_handles, fdio_types);
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;
}
ZX_ASSERT((size_t)status <= FDIO_MAX_HANDLES_FOR_CLONE_OR_TRANSFER);
for (int j = 0; j < status; ++j) {
handle_infos[h].handle = FIDL_HANDLE_PRESENT;
handle_infos[h].id = fdio_types[j];
handles[h++] = fdio_handles[j];
}
}
req->handles.count = h;
req->handles.data = (void*)FIDL_ALLOC_PRESENT;
ZX_DEBUG_ASSERT(h <= handle_capacity);
size_t msg_len = sizeof(fuchsia_process_LauncherAddHandlesRequest) + FIDL_ALIGN(h * sizeof(fuchsia_process_HandleInfo));
status = zx_channel_write(launcher, 0, msg, msg_len, handles, h);
if (status == ZX_OK)
return status;
report_error(err_msg, "failed send handles: %d", status);
cleanup:
for (size_t i = 0; i < h; ++i) {
zx_handle_close(handles[i]);
}
// 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(zx_handle_t 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 = (void*)FIDL_ALLOC_PRESENT;
size_t n = 0;
size_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 = (void*)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 = (void*)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 = zx_channel_write(launcher, 0, msg, msg_len, handles, h);
if (status != ZX_OK) {
report_error(err_msg, "failed send namespace: %d", status);
for (size_t i = 0; i < h; ++i) {
zx_handle_close(handles[i]);
}
}
return status;
}
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);
}
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_handle_t executable_vmo = ZX_HANDLE_INVALID;
zx_status_t status = load_path(path, &executable_vmo);
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_vmo| is
// |ZX_HANDLE_INVALID|, then |fdio_spawn_vmo| will generate an error.
zx_status_t spawn_status = fdio_spawn_vmo(job, flags, executable_vmo, 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;
}
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_handle_t launcher = ZX_HANDLE_INVALID;
zx_handle_t launcher_request = ZX_HANDLE_INVALID;
zx_handle_t msg_handles[FDIO_SPAWN_LAUNCH_HANDLE_COUNT];
memset(msg_handles, 0, sizeof(msg_handles));
if (err_msg)
err_msg[0] = '\0';
if (executable_vmo == ZX_HANDLE_INVALID || !argv || (action_count != 0 && !actions)) {
status = ZX_ERR_INVALID_ARGS;
goto cleanup;
}
if (job == ZX_HANDLE_INVALID)
job = zx_job_default();
const char* 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 += FDIO_MAX_HANDLES_FOR_CLONE_OR_TRANSFER;
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 == ZX_HANDLE_INVALID) {
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_CLONE_LDSVC) != 0)
++handle_capacity;
if ((flags & FDIO_SPAWN_CLONE_STDIO) != 0)
handle_capacity += 3 * FDIO_MAX_HANDLES_FOR_CLONE_OR_TRANSFER;
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]));
}
}
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);
launcher_request = ZX_HANDLE_INVALID;
if (status != ZX_OK) {
report_error(err_msg, "failed to connect to launcher service: %d", status);
goto cleanup;
}
status = send_string_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_string_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_string_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, 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.
size_t 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 = (void*)FIDL_ALLOC_PRESENT;
memcpy(msg.process_name, process_name, process_name_size);
msg_handles[FDIO_SPAWN_LAUNCH_HANDLE_EXECUTABLE] = executable_vmo;
executable_vmo = ZX_HANDLE_INVALID;
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;
}
struct {
FIDL_ALIGNDECL
fuchsia_process_LauncherLaunchResponse rsp;
uint8_t err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
} 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 = 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;
zx_status_t read_status = ZX_OK;
status = zx_channel_call(launcher, 0, ZX_TIME_INFINITE, &args,
&actual_bytes, &actual_handles, &read_status);
if (status == ZX_OK || status == ZX_ERR_CALL_FAILED)
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.rsp.result.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 {
if (err_msg) {
size_t n = reply.rsp.result.error_message.size;
if (n >= FDIO_SPAWN_ERR_MSG_MAX_LENGTH)
n = FDIO_SPAWN_ERR_MSG_MAX_LENGTH - 1;
memcpy(err_msg, reply.err_msg, n);
err_msg[n] = '\0';
}
}
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 (executable_vmo != ZX_HANDLE_INVALID)
zx_handle_close(executable_vmo);
if (launcher != ZX_HANDLE_INVALID)
zx_handle_close(launcher);
if (launcher_request != ZX_HANDLE_INVALID)
zx_handle_close(launcher_request);
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]);
return status;
}