blob: 9bfd2e5bd7ab003bcdc0316e85c1193d8ec34201 [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 <launchpad/loader-service.h>
#include <fdio/debug.h>
#include <fdio/dispatcher.h>
#include <fdio/io.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdalign.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/compiler.h>
#include <zircon/device/dmctl.h>
#include <zircon/device/vfs.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/threads.h>
#include <zircon/types.h>
static void __PRINTFLIKE(2, 3) log_printf(zx_handle_t log,
const char* fmt, ...) {
if (log <= 0)
return;
char buf[128];
va_list ap;
va_start(ap, fmt);
// we allow partial writes.
int len = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (len < 0)
return;
len = (len > (int)sizeof(buf)) ? (int)sizeof(buf) : len;
zx_log_write(log, len, buf, 0u);
}
#define PREFIX_MAX 32
struct loader_service {
char name[ZX_MAX_NAME_LEN];
mtx_t dispatcher_lock;
fdio_dispatcher_t* dispatcher;
zx_handle_t dispatcher_log;
const loader_service_ops_t* ops;
void* ctx;
char config_prefix[PREFIX_MAX];
bool config_exclusive;
};
static const char* const libpaths[] = {
"/system/lib",
"/boot/lib",
};
zx_status_t loader_service_publish_data_sink_fs(const char* sink_name, zx_handle_t vmo) {
union {
vmo_create_config_t header;
struct {
alignas(vmo_create_config_t) char h[sizeof(vmo_create_config_t)];
char name[ZX_MAX_NAME_LEN];
};
} config;
zx_status_t status = zx_object_get_property(
vmo, ZX_PROP_NAME, config.name, sizeof(config.name));
if (status != ZX_OK)
return status;
if (config.name[0] == '\0') {
zx_info_handle_basic_t info;
status = zx_object_get_info(vmo, ZX_INFO_HANDLE_BASIC,
&info, sizeof(info), NULL, NULL);
if (status != ZX_OK)
return status;
snprintf(config.name, sizeof(config.name), "unnamed.%" PRIu64,
info.koid);
}
int tmp_dir_fd = open("/tmp", O_DIRECTORY);
if (tmp_dir_fd < 0) {
fprintf(stderr, "dlsvc: cannot open /tmp for data-sink \"%s\": %m\n",
sink_name);
close(tmp_dir_fd);
zx_handle_close(vmo);
return ZX_ERR_NOT_FOUND;
}
if (mkdirat(tmp_dir_fd, sink_name, 0777) != 0 && errno != EEXIST) {
fprintf(stderr, "dlsvc: cannot mkdir \"/tmp/%s\" for data-sink: %m\n",
sink_name);
close(tmp_dir_fd);
zx_handle_close(vmo);
return ZX_ERR_NOT_FOUND;
}
int sink_dir_fd = openat(tmp_dir_fd, sink_name, O_RDONLY | O_DIRECTORY);
close(tmp_dir_fd);
if (sink_dir_fd < 0) {
fprintf(stderr,
"dlsvc: cannot open data-sink directory \"/tmp/%s\": %m\n",
sink_name);
zx_handle_close(vmo);
return ZX_ERR_NOT_FOUND;
}
config.header.vmo = vmo;
ssize_t result = ioctl_vfs_vmo_create(
sink_dir_fd, &config.header,
sizeof(config.header) + strlen(config.name) + 1);
close(sink_dir_fd);
if (result < 0) {
fprintf(stderr,
"dlsvc: ioctl_vfs_vmo_create failed"
" for data-sink \"%s\" item \"%s\": %s\n",
sink_name, config.name, zx_status_get_string(result));
return result;
}
return ZX_OK;
}
// When loading a library object, search in the hard-coded locations.
static int open_from_libpath(const char* fn) {
int fd = -1;
for (size_t n = 0; fd < 0 && n < countof(libpaths); ++n) {
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/%s", libpaths[n], fn);
fd = open(path, O_RDONLY);
}
return fd;
}
// Always consumes the fd.
static zx_handle_t load_object_fd(int fd, const char* fn, zx_handle_t* out) {
zx_status_t status = fdio_get_vmo(fd, out);
close(fd);
if (status == ZX_OK)
zx_object_set_property(*out, ZX_PROP_NAME, fn, strlen(fn));
return status;
}
static zx_status_t fs_load_object(void *ctx, const char* name, zx_handle_t* out) {
int fd = open_from_libpath(name);
if (fd >= 0)
return load_object_fd(fd, name, out);
return ZX_ERR_NOT_FOUND;
}
static zx_status_t fs_load_abspath(void *ctx, const char* path, zx_handle_t* out) {
int fd = open(path, O_RDONLY);
if (fd >= 0)
return load_object_fd(fd, path, out);
return ZX_ERR_NOT_FOUND;
}
// For now, just publish data-sink VMOs as files under /tmp/<sink-name>/.
// The individual file is named by its VMO's name.
static zx_status_t fs_publish_data_sink(void* ctx, const char* name, zx_handle_t vmo) {
return loader_service_publish_data_sink_fs(name, vmo);
}
static const loader_service_ops_t fs_ops = {
.load_object = fs_load_object,
.load_abspath = fs_load_abspath,
.publish_data_sink = fs_publish_data_sink,
};
static zx_status_t default_load_fn(void* cookie, uint32_t load_op,
zx_handle_t request_handle,
const char* fn, zx_handle_t* out) {
loader_service_t* svc = cookie;
zx_status_t status;
switch (load_op) {
case LOADER_SVC_OP_CONFIG: {
size_t len = strlen(fn);
if (len < 2 || len >= sizeof(svc->config_prefix) - 1 ||
strchr(fn, '/') != NULL) {
status = ZX_ERR_INVALID_ARGS;
break;
}
strncpy(svc->config_prefix, fn, len + 1);
svc->config_exclusive = false;
if (svc->config_prefix[len - 1] == '!') {
--len;
svc->config_exclusive = true;
}
svc->config_prefix[len] = '/';
svc->config_prefix[len + 1] = '\0';
status = ZX_OK;
break;
}
case LOADER_SVC_OP_LOAD_OBJECT:
// If a prefix is configured, try loading with that prefix first
if (svc->config_prefix[0] != '\0') {
size_t maxlen = PREFIX_MAX + strlen(fn) + 1;
char pfn[maxlen];
snprintf(pfn, maxlen, "%s%s", svc->config_prefix, fn);
if (((status = svc->ops->load_object(svc->ctx, pfn, out)) == ZX_OK) ||
svc->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, fn, out);
break;
case LOADER_SVC_OP_LOAD_SCRIPT_INTERP:
case LOADER_SVC_OP_LOAD_DEBUG_CONFIG:
// When loading a script interpreter or debug configuration file,
// we expect an absolute path.
if (fn[0] != '/') {
fprintf(stderr, "dlsvc: invalid %s '%s' is not an absolute path\n",
load_op == LOADER_SVC_OP_LOAD_SCRIPT_INTERP ?
"script interpreter" : "debug config file",
fn);
return ZX_ERR_NOT_FOUND;
}
status = svc->ops->load_abspath(svc->ctx, fn, out);
break;
case LOADER_SVC_OP_PUBLISH_DATA_SINK:
status = svc->ops->publish_data_sink(svc->ctx, fn, request_handle);
request_handle = ZX_HANDLE_INVALID;
break;
case LOADER_SVC_OP_CLONE:
status = loader_service_attach(svc, request_handle);
request_handle = ZX_HANDLE_INVALID;
break;
default:
__builtin_trap();
}
if (request_handle != ZX_HANDLE_INVALID) {
fprintf(stderr, "dlsvc: unused handle (%#x) opcode=%#x data=\"%s\"\n",
request_handle, load_op, fn);
zx_handle_close(request_handle);
}
return status;
}
struct startup {
loader_service_fn_t loader;
void* loader_arg;
zx_handle_t pipe_handle;
zx_handle_t syslog_handle;
};
static zx_status_t handle_loader_rpc(zx_handle_t h,
loader_service_fn_t loader,
void* loader_arg, zx_handle_t sys_log) {
uint8_t data[1024];
zx_loader_svc_msg_t* msg = (void*) data;
uint32_t sz = sizeof(data);
zx_handle_t request_handle;
uint32_t nhandles;
zx_status_t r =
zx_channel_read(h, 0, msg, &request_handle, sz, 1, &sz, &nhandles);
if (r != ZX_OK) {
// This is the normal error for the other end going away,
// which happens when the process dies.
if (r != ZX_ERR_PEER_CLOSED)
fprintf(stderr, "dlsvc: msg read error %d: %s\n", r, zx_status_get_string(r));
return r;
}
if (nhandles == 0)
request_handle = ZX_HANDLE_INVALID;
if ((sz <= sizeof(zx_loader_svc_msg_t))) {
zx_handle_close(request_handle);
fprintf(stderr, "dlsvc: runt message\n");
return ZX_ERR_IO;
}
// forcibly null-terminate the message data argument
data[sz - 1] = 0;
zx_handle_t handle = ZX_HANDLE_INVALID;
switch (msg->opcode) {
case LOADER_SVC_OP_CONFIG:
case LOADER_SVC_OP_LOAD_OBJECT:
case LOADER_SVC_OP_LOAD_SCRIPT_INTERP:
case LOADER_SVC_OP_LOAD_DEBUG_CONFIG:
case LOADER_SVC_OP_PUBLISH_DATA_SINK:
case LOADER_SVC_OP_CLONE:
// TODO(MG-491): Use a threadpool for loading, and guard against
// other starvation attacks.
r = (*loader)(loader_arg, msg->opcode,
request_handle, (const char*) msg->data, &handle);
if (r == ZX_ERR_NOT_FOUND) {
fprintf(stderr, "dlsvc: could not open '%s'\n",
(const char*) msg->data);
}
request_handle = ZX_HANDLE_INVALID;
msg->arg = r;
break;
case LOADER_SVC_OP_DEBUG_PRINT:
log_printf(sys_log, "dlsvc: debug: %s\n", (const char*) msg->data);
msg->arg = ZX_OK;
break;
case LOADER_SVC_OP_DONE:
zx_handle_close(request_handle);
return ZX_ERR_PEER_CLOSED;
default:
fprintf(stderr, "dlsvc: invalid opcode 0x%x\n", msg->opcode);
msg->arg = ZX_ERR_INVALID_ARGS;
break;
}
if (request_handle != ZX_HANDLE_INVALID) {
fprintf(stderr, "dlsvc: unused handle (%#x) opcode=%#x data=\"%s\"\n",
request_handle, msg->opcode, msg->data);
zx_handle_close(request_handle);
}
// msg->txid returned as received from the client.
msg->opcode = LOADER_SVC_OP_STATUS;
msg->reserved0 = 0;
msg->reserved1 = 0;
if ((r = zx_channel_write(h, 0, msg, sizeof(zx_loader_svc_msg_t),
&handle, handle != ZX_HANDLE_INVALID ? 1 : 0)) < 0) {
fprintf(stderr, "dlsvc: msg write error: %d: %s\n", r, zx_status_get_string(r));
return r;
}
return ZX_OK;
}
static int loader_service_thread(void* arg) {
struct startup* startup = arg;
zx_handle_t h = startup->pipe_handle;
loader_service_fn_t loader = startup->loader;
void* loader_arg = startup->loader_arg;
zx_handle_t sys_log = startup->syslog_handle;
free(startup);
zx_status_t r;
for (;;) {
if ((r = zx_object_wait_one(h, ZX_CHANNEL_READABLE, ZX_TIME_INFINITE, NULL)) < 0) {
// This is the normal error for the other end going away,
// which happens when the process dies.
if (r != ZX_ERR_BAD_STATE)
fprintf(stderr, "dlsvc: wait error %d: %s\n", r, zx_status_get_string(r));
break;
}
if ((r = handle_loader_rpc(h, loader, loader_arg, sys_log)) < 0) {
break;
}
}
zx_handle_close(h);
return 0;
}
zx_status_t loader_service_create(const char* name,
const loader_service_ops_t* ops,
void* ctx,
loader_service_t** out) {
if (name == NULL || name[0] == '\0' || 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;
}
svc->ops = ops;
svc->ctx = ctx;
strncpy(svc->name, name, sizeof(svc->name) - 1);
*out = svc;
return ZX_OK;
}
zx_status_t loader_service_create_fs(const char* name,
loader_service_t** out) {
return loader_service_create(name, &fs_ops, NULL, out);
}
static zx_status_t multiloader_cb(zx_handle_t h, void* cb, void* cookie) {
if (h == 0) {
// close notification, which we can ignore
return 0;
}
// This uses svc->dispatcher_log without grabbing the lock, but
// it will never change once the dispatcher that called us is created.
loader_service_t* svc = cookie;
return handle_loader_rpc(h, default_load_fn, svc, svc->dispatcher_log);
}
zx_status_t loader_service_attach(loader_service_t* svc, zx_handle_t h) {
if (svc == NULL) {
return ZX_ERR_INVALID_ARGS;
}
mtx_lock(&svc->dispatcher_lock);
zx_status_t r;
if (svc->dispatcher == NULL) {
if ((r = fdio_dispatcher_create(&svc->dispatcher,
multiloader_cb)) < 0) {
goto done;
}
if ((r = fdio_dispatcher_start(svc->dispatcher, svc->name)) < 0) {
//TODO: destroy dispatcher once support exists
svc->dispatcher = NULL;
goto done;
}
if (zx_log_create(0, &svc->dispatcher_log) < 0) {
// unlikely to fail, but we'll keep going without it if so
svc->dispatcher_log = ZX_HANDLE_INVALID;
}
}
r = fdio_dispatcher_add(svc->dispatcher, h, NULL, svc);
done:
mtx_unlock(&svc->dispatcher_lock);
if (r != ZX_OK) {
zx_handle_close(r);
}
return r;
}
// TODO(dbort): Provide a name/id for the process that this handle will
// be used for, to make error messages more useful? Would need to pass
// the same through IOCTL_DMCTL_GET_LOADER_SERVICE_CHANNEL.
zx_status_t loader_service_connect(loader_service_t* svc, zx_handle_t* out) {
zx_handle_t h0, h1;
zx_status_t r;
if ((r = zx_channel_create(0, &h0, &h1)) != ZX_OK) {
return r;
}
if ((r = loader_service_attach(svc, h1)) != ZX_OK) {
zx_handle_close(h0);
return r;
}
*out = h0;
return ZX_OK;
}
static bool force_local_loader_service = false;
void loader_service_force_local(void) {
force_local_loader_service = true;
}
// Returns a channel to the system loader service.
zx_status_t loader_service_get_system(zx_handle_t* out) {
int fd = open("/dev/misc/dmctl", O_RDONLY);
if (fd < 0) {
return ZX_ERR_NOT_FOUND;
}
zx_handle_t h;
ssize_t s = ioctl_dmctl_get_loader_service_channel(fd, &h);
close(fd);
if (s != (ssize_t)sizeof(zx_handle_t)) {
return s < 0 ? s : ZX_ERR_INTERNAL;
}
*out = h;
return ZX_OK;
}
// In-process multiloader
static loader_service_t local_loader_svc = {
.name = "local-loader-svc",
.ops = &fs_ops,
};
zx_status_t loader_service_get_default(zx_handle_t* out) {
if (!force_local_loader_service) {
// Try to use the system loader service.
if (loader_service_get_system(out) == ZX_OK) {
return ZX_OK;
}
}
// Fall back to an in-process loader service.
return loader_service_connect(&local_loader_svc, out);
}
zx_status_t loader_service_simple(loader_service_fn_t loader, void* loader_arg,
zx_handle_t* out) {
struct startup *startup = malloc(sizeof(*startup));
if (startup == NULL)
return ZX_ERR_NO_MEMORY;
zx_handle_t h;
zx_status_t r;
if ((r = zx_channel_create(0, &h, &startup->pipe_handle)) < 0) {
free(startup);
return r;
}
zx_handle_t sys_log = ZX_HANDLE_INVALID;
if ((r = zx_log_create(0u, &sys_log)) < 0)
fprintf(stderr, "dlsvc: log creation failed: error %d: %s\n", r,
zx_status_get_string(r));
startup->loader = loader;
startup->loader_arg = loader_arg;
startup->syslog_handle = sys_log;
thrd_t t;
int ret = thrd_create_with_name(&t, loader_service_thread, startup,
"local-custom-loader");
if (ret != thrd_success) {
zx_handle_close(h);
zx_handle_close(startup->pipe_handle);
free(startup);
return thrd_status_to_zx_status(ret);
}
thrd_detach(t);
*out = h;
return ZX_OK;
}