blob: 84e166d94afeba25677bda11903e612ecc8f649b [file] [log] [blame]
// Copyright 2016 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 <loader-service/loader-service.h>
#include <errno.h>
#include <fcntl.h>
#include <lib/fdio/io.h>
#include <inttypes.h>
#include <ldmsg/ldmsg.h>
#include <lib/async-loop/loop.h>
#include <lib/async/wait.h>
#include <limits.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/compiler.h>
#include <zircon/device/vfs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#define PREFIX_MAX 32
// State of a loader service instance.
typedef struct instance_state instance_state_t;
struct instance_state {
int root_dir_fd;
// NULL-terminated list of paths from which objects will loaded.
const char* const* lib_paths;
};
// This represents an instance of the loader service. Each session in an
// instance has a session_state_t pointing to this. All sessions in
// the same instance behave the same.
struct loader_service {
atomic_int refcount;
async_dispatcher_t* dispatcher;
const loader_service_ops_t* ops;
void* ctx;
};
// Per-session state of a loader service instance.
typedef struct session_state session_state_t;
struct session_state {
async_wait_t wait; // Must be first.
char config_prefix[PREFIX_MAX];
bool config_exclusive;
loader_service_t* svc;
};
static void loader_service_addref(loader_service_t* svc) {
atomic_fetch_add(&svc->refcount, 1);
}
static void loader_service_deref(loader_service_t* svc) {
if (atomic_fetch_sub(&svc->refcount, 1) == 1) {
if (svc->ops->finalizer)
svc->ops->finalizer(svc->ctx);
free(svc);
}
}
// When loading a library object, search in the locations provided in
// |lib_paths|, which is required to be NULL-terminated.
static int open_from_lib_paths(int root_dir_fd, const char* const* lib_paths,
const char* fn) {
int fd = -1;
for (size_t n = 0; fd < 0 && lib_paths[n]; ++n) {
char path[PATH_MAX];
if (snprintf(path, sizeof(path), "%s/%s", lib_paths[n], fn) < 0) {
return -1;
}
fd = openat(root_dir_fd, path, O_RDONLY);
}
return fd;
}
// Always consumes the |fd|.
static zx_handle_t vmo_from_fd(int fd, const char* fn, zx_handle_t* out) {
zx_handle_t vmo;
zx_handle_t exec_vmo;
zx_status_t status = fdio_get_vmo_clone(fd, &vmo);
close(fd);
if (status != ZX_OK) {
return status;
}
status = zx_vmo_replace_as_executable(vmo, ZX_HANDLE_INVALID, &exec_vmo);
if (status != ZX_OK) {
zx_handle_close(vmo);
return status;
}
status = zx_object_set_property(exec_vmo, ZX_PROP_NAME, fn, strlen(fn));
if (status != ZX_OK) {
zx_handle_close(exec_vmo);
return status;
}
*out = exec_vmo;
return ZX_OK;
}
static zx_status_t fd_load_object(void* ctx, const char* name, zx_handle_t* out) {
int root_dir_fd = ((instance_state_t*)ctx)->root_dir_fd;
const char* const* lib_paths = ((instance_state_t*)ctx)->lib_paths;
int fd = open_from_lib_paths(root_dir_fd, lib_paths, name);
if (fd >= 0) {
return vmo_from_fd(fd, name, out);
}
return ZX_ERR_NOT_FOUND;
}
static zx_status_t fd_load_abspath(void* ctx, const char* path, zx_handle_t* out) {
int root_dir_fd = ((instance_state_t*)ctx)->root_dir_fd;
int fd = openat(root_dir_fd, path, O_RDONLY);
if (fd >= 0) {
return vmo_from_fd(fd, path, out);
}
return ZX_ERR_NOT_FOUND;
}
zx_status_t fd_publish_data_sink(void* ctx, const char* sink_name, zx_handle_t vmo) {
zx_handle_close(vmo);
return ZX_ERR_NOT_SUPPORTED;
}
void fd_finalizer(void* ctx) {
instance_state_t* instance_state = (instance_state_t*)ctx;
int root_dir_fd = instance_state->root_dir_fd;
close(root_dir_fd);
free(instance_state);
}
static const loader_service_ops_t fd_ops = {
.load_object = fd_load_object,
.load_abspath = fd_load_abspath,
.publish_data_sink = fd_publish_data_sink,
.finalizer = fd_finalizer,
};
static zx_status_t loader_service_rpc(zx_handle_t h, session_state_t* session_state) {
loader_service_t* svc = session_state->svc;
ldmsg_req_t req;
uint32_t req_len = sizeof(req);
zx_handle_t req_handle = ZX_HANDLE_INVALID;
uint32_t req_handle_len;
zx_status_t status =
zx_channel_read(h, 0, &req, &req_handle, req_len, 1, &req_len, &req_handle_len);
if (status != ZX_OK) {
// This is the normal error for the other end going away,
// which happens when the process dies.
if (status != ZX_ERR_PEER_CLOSED)
fprintf(stderr, "dlsvc: msg read error %d: %s\n", status, zx_status_get_string(status));
return status;
}
const char* data = NULL;
size_t len = 0;
status = ldmsg_req_decode(&req, req_len, &data, &len);
if (status != ZX_OK) {
zx_handle_close(req_handle);
fprintf(stderr, "dlsvc: invalid message\n");
return ZX_ERR_IO;
}
zx_handle_t rsp_handle = ZX_HANDLE_INVALID;
switch (req.header.ordinal) {
case LDMSG_OP_CONFIG_OLD:
case LDMSG_OP_CONFIG: {
size_t len = strlen(data);
if (len < 2 || len >= sizeof(session_state->config_prefix) - 1 || strchr(data, '/') != NULL) {
status = ZX_ERR_INVALID_ARGS;
break;
}
memcpy(session_state->config_prefix, data, len + 1);
session_state->config_exclusive = false;
if (session_state->config_prefix[len - 1] == '!') {
--len;
session_state->config_exclusive = true;
}
session_state->config_prefix[len] = '/';
session_state->config_prefix[len + 1] = '\0';
status = ZX_OK;
break;
}
case LDMSG_OP_LOAD_OBJECT_OLD:
case LDMSG_OP_LOAD_OBJECT:
// If a prefix is configured, try loading with that prefix first
if (session_state->config_prefix[0] != '\0') {
size_t maxlen = PREFIX_MAX + strlen(data) + 1;
char prefixed_name[maxlen];
snprintf(prefixed_name, maxlen, "%s%s", session_state->config_prefix, data);
if (((status = svc->ops->load_object(svc->ctx, prefixed_name, &rsp_handle)) == ZX_OK) ||
session_state->config_exclusive) {
// if loading with prefix succeeds, or loading
// with prefix is configured to be exclusive of
// non-prefix loading, stop here
break;
}
// otherwise, if non-exclusive, try loading without the prefix
}
status = svc->ops->load_object(svc->ctx, data, &rsp_handle);
break;
case LDMSG_OP_LOAD_SCRIPT_INTERPRETER_OLD:
case LDMSG_OP_DEBUG_LOAD_CONFIG_OLD:
case LDMSG_OP_LOAD_SCRIPT_INTERPRETER:
case LDMSG_OP_DEBUG_LOAD_CONFIG:
// When loading a script interpreter or debug configuration file,
// we expect an absolute path.
if (data[0] != '/') {
fprintf(stderr, "dlsvc: invalid %s '%s' is not an absolute path\n",
req.header.ordinal == LDMSG_OP_LOAD_SCRIPT_INTERPRETER_OLD ||
req.header.ordinal == LDMSG_OP_LOAD_SCRIPT_INTERPRETER ? "script interpreter" : "debug config file",
data);
status = ZX_ERR_NOT_FOUND;
break;
}
status = svc->ops->load_abspath(svc->ctx, data, &rsp_handle);
break;
case LDMSG_OP_DEBUG_PUBLISH_DATA_SINK_OLD:
case LDMSG_OP_DEBUG_PUBLISH_DATA_SINK:
status = svc->ops->publish_data_sink(svc->ctx, data, req_handle);
req_handle = ZX_HANDLE_INVALID;
break;
case LDMSG_OP_CLONE_OLD:
case LDMSG_OP_CLONE:
status = loader_service_attach(svc, req_handle);
req_handle = ZX_HANDLE_INVALID;
break;
case LDMSG_OP_DONE_OLD:
case LDMSG_OP_DONE:
zx_handle_close(req_handle);
return ZX_ERR_PEER_CLOSED;
default:
// This case cannot happen because ldmsg_req_decode will return an
// error for invalid ordinals.
__builtin_trap();
}
if (status == ZX_ERR_NOT_FOUND) {
fprintf(stderr, "dlsvc: could not open '%s'\n", data);
}
if (req_handle != ZX_HANDLE_INVALID) {
fprintf(stderr, "dlsvc: unused handle (%#x) opcode=%#x data=\"%s\"\n",
req_handle, req.header.ordinal, data);
zx_handle_close(req_handle);
}
ldmsg_rsp_t rsp;
memset(&rsp, 0, sizeof(rsp));
rsp.header.txid = req.header.txid;
rsp.header.ordinal = req.header.ordinal;
rsp.rv = status;
rsp.object = rsp_handle == ZX_HANDLE_INVALID ? FIDL_HANDLE_ABSENT : FIDL_HANDLE_PRESENT;
if ((status = zx_channel_write(h, 0, &rsp, ldmsg_rsp_get_size(&rsp),
&rsp_handle, rsp_handle != ZX_HANDLE_INVALID ? 1 : 0)) < 0) {
fprintf(stderr, "dlsvc: msg write error: %d: %s\n", status, zx_status_get_string(status));
return status;
}
return ZX_OK;
}
zx_status_t loader_service_create(async_dispatcher_t* dispatcher,
const loader_service_ops_t* ops,
void* ctx,
loader_service_t** out) {
if (out == NULL || ops == NULL) {
return ZX_ERR_INVALID_ARGS;
}
loader_service_t* svc = calloc(1, sizeof(loader_service_t));
if (svc == NULL) {
return ZX_ERR_NO_MEMORY;
}
if (!dispatcher) {
async_loop_t* loop;
zx_status_t status = async_loop_create(&kAsyncLoopConfigNoAttachToThread, &loop);
if (status != ZX_OK) {
free(svc);
return status;
}
status = async_loop_start_thread(loop, "loader-service", NULL);
if (status != ZX_OK) {
free(svc);
async_loop_destroy(loop);
return status;
}
dispatcher = async_loop_get_dispatcher(loop);
}
svc->dispatcher = dispatcher;
svc->ops = ops;
svc->ctx = ctx;
// When we create the loader service, we initialize the refcount to 1, which
// causes the loader service to stay alive at least until someone calls
// |loader_service_release|, at which point the service will be destroyed
// once the last client goes away.
loader_service_addref(svc);
*out = svc;
return ZX_OK;
}
// Default library paths for the fd- and fs- loader service implementations.
static const char* const fd_lib_paths[] = {"lib", NULL};
static const char* const fs_lib_paths[] = {"system/lib", "boot/lib", NULL};
// Create the default implementation of a loader service for which
// paths are loaded relative to |root_dir_fd| and among the array of
// subdirectories given by |lib_paths| (NULL-terminated).
static zx_status_t loader_service_create_default(async_dispatcher_t* dispatcher,
int root_dir_fd,
const char* const* lib_paths,
loader_service_t** out) {
instance_state_t* instance_state = calloc(1, sizeof(loader_service_t));
if (instance_state == NULL) {
return ZX_ERR_NO_MEMORY;
}
instance_state->root_dir_fd = root_dir_fd;
instance_state->lib_paths = lib_paths? lib_paths : fd_lib_paths;
loader_service_t* svc;
zx_status_t status = loader_service_create(dispatcher, &fd_ops, NULL, &svc);
if (status == ZX_OK) {
svc->ctx = instance_state;
*out = svc;
} else {
free(instance_state);
}
return status;
}
zx_status_t loader_service_create_fs(async_dispatcher_t* dispatcher, loader_service_t** out) {
int root_dir_fd = open("/", O_RDONLY | O_DIRECTORY);
if (root_dir_fd < 0){
return ZX_ERR_NOT_FOUND;
}
return loader_service_create_default(dispatcher, root_dir_fd, fs_lib_paths, out);
}
zx_status_t loader_service_create_fd(async_dispatcher_t* dispatcher,
int root_dir_fd,
loader_service_t** out) {
return loader_service_create_default(dispatcher, root_dir_fd, fd_lib_paths, out);
}
zx_status_t loader_service_release(loader_service_t* svc) {
// This call to |loader_service_deref| balances the |loader_service_addref|
// call in |loader_service_create|. This reference prevents the loader
// service from being destroyed before its creator is done with it.
loader_service_deref(svc);
return ZX_OK;
}
static void loader_service_handler(async_dispatcher_t* dispatcher,
async_wait_t* wait,
zx_status_t status,
const zx_packet_signal_t* signal) {
session_state_t* session_state = (session_state_t*)wait;
if (status != ZX_OK)
goto stop;
status = loader_service_rpc(wait->object, session_state);
if (status != ZX_OK)
goto stop;
status = async_begin_wait(dispatcher, wait);
if (status != ZX_OK)
goto stop;
return;
stop:
zx_handle_close(wait->object);
loader_service_t* svc = session_state->svc;
free(session_state);
loader_service_deref(svc); // Balanced in |loader_service_attach|.
}
zx_status_t loader_service_attach(loader_service_t* svc, zx_handle_t h) {
zx_status_t status = ZX_OK;
session_state_t* session_state = NULL;
if (svc == NULL) {
status = ZX_ERR_INVALID_ARGS;
goto done;
}
session_state = calloc(1, sizeof(session_state_t));
if (session_state == NULL) {
status = ZX_ERR_NO_MEMORY;
goto done;
}
session_state->wait.handler = loader_service_handler;
session_state->wait.object = h;
session_state->wait.trigger = ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED;
session_state->svc = svc;
status = async_begin_wait(svc->dispatcher, &session_state->wait);
if (status == ZX_OK) {
loader_service_addref(svc); // Balanced in |loader_service_handler|.
}
done:
if (status != ZX_OK) {
zx_handle_close(h);
free(session_state);
}
return status;
}
zx_status_t loader_service_connect(loader_service_t* svc, zx_handle_t* out) {
zx_handle_t h0, h1;
zx_status_t status;
if ((status = zx_channel_create(0, &h0, &h1)) != ZX_OK) {
return status;
}
if ((status = loader_service_attach(svc, h1)) != ZX_OK) {
zx_handle_close(h0);
return status;
}
*out = h0;
return ZX_OK;
}