blob: ab3d77d86554d7a642546ff9fdf4bfbb17dc0318 [file] [log] [blame]
// Copyright 2017 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 "devmgr.h"
#include "memfs-private.h"
#include <bootdata/decompress.h>
#include <fdio/namespace.h>
#include <fdio/util.h>
#include <launchpad/launchpad.h>
#include <launchpad/loader-service.h>
#include <zircon/boot/bootdata.h>
#include <zircon/dlfcn.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
// When adding VMOs to the boot filesystem, add them under the directory
// /boot/VMO_SUBDIR. This constant must end, but not start, with a slash.
#define VMO_SUBDIR "kernel/"
#define VMO_SUBDIR_LEN (sizeof(VMO_SUBDIR) - 1)
struct callback_data {
zx_handle_t vmo;
unsigned int file_count;
zx_status_t (*add_file)(const char* path, zx_handle_t vmo, zx_off_t off, size_t len);
};
static zx_status_t callback(void* arg, const bootfs_entry_t* entry) {
struct callback_data* cd = arg;
//printf("bootfs: %s @%zd (%zd bytes)\n", path, off, len);
cd->add_file(entry->name, cd->vmo, entry->data_off, entry->data_len);
++cd->file_count;
return ZX_OK;
}
static bool has_secondary_bootfs = false;
bool secondary_bootfs_ready(void) {
return has_secondary_bootfs;
}
static ssize_t setup_bootfs_vmo(uint32_t n, uint32_t type, zx_handle_t vmo) {
uint64_t size;
zx_status_t status = zx_vmo_get_size(vmo, &size);
if (status != ZX_OK) {
printf("devmgr: failed to get bootfs#%u size (%d)\n", n, status);
return status;
}
if (size == 0) {
return 0;
}
// map the vmo so that ps will account for it
// NOTE: will leak the mapping in case the bootfs is thrown away later
uintptr_t address;
zx_vmar_map(zx_vmar_root_self(), 0, vmo, 0, size, ZX_VM_FLAG_PERM_READ, &address);
struct callback_data cd = {
.vmo = vmo,
.add_file = (type == BOOTDATA_BOOTFS_SYSTEM) ? systemfs_add_file : bootfs_add_file,
};
if ((type == BOOTDATA_BOOTFS_SYSTEM) && !has_secondary_bootfs) {
has_secondary_bootfs = true;
status = memfs_mount(vfs_create_global_root(), "system", systemfs_get_root());
if (status != ZX_OK) {
printf("devmgr: failed to mount /system (%d)\n", status);
return status;
}
}
bootfs_t bfs;
if (bootfs_create(&bfs, vmo) == ZX_OK) {
bootfs_parse(&bfs, callback, &cd);
bootfs_destroy(&bfs);
}
if (type == BOOTDATA_BOOTFS_SYSTEM) {
// TODO(abarth): Uncomment this line when we're ready to make /system
// read only.
// systemfs_set_readonly(getenv("zircon.system.writable") == NULL);
}
return cd.file_count;
}
static void setup_last_crashlog(zx_handle_t vmo_in, uint64_t off_in, size_t sz) {
printf("devmgr: last crashlog is %zu bytes\n", sz);
zx_handle_t vmo;
if (copy_vmo(vmo_in, off_in, sz, &vmo) != ZX_OK) {
return;
}
bootfs_add_file("log/last-panic.txt", vmo, 0, sz);
}
#define HND_BOOTFS(n) PA_HND(PA_VMO_BOOTFS, n)
#define HND_BOOTDATA(n) PA_HND(PA_VMO_BOOTDATA, n)
static void setup_bootfs(void) {
zx_handle_t vmo;
unsigned idx = 0;
if ((vmo = zx_get_startup_handle(HND_BOOTFS(0)))) {
setup_bootfs_vmo(idx++, BOOTDATA_BOOTFS_BOOT, vmo);
} else {
printf("devmgr: missing primary bootfs?!\n");
}
for (unsigned n = 0; (vmo = zx_get_startup_handle(HND_BOOTDATA(n))); n++) {
bootdata_t bootdata;
size_t actual;
zx_status_t status = zx_vmo_read(vmo, &bootdata, 0, sizeof(bootdata), &actual);
if ((status < 0) || (actual != sizeof(bootdata))) {
goto done;
}
if ((bootdata.type != BOOTDATA_CONTAINER) || (bootdata.extra != BOOTDATA_MAGIC)) {
printf("devmgr: bootdata item does not contain bootdata\n");
goto done;
}
if (!(bootdata.flags & BOOTDATA_FLAG_V2)) {
printf("devmgr: bootdata v1 no longer supported\n");
goto done;
}
size_t len = bootdata.length;
size_t off = sizeof(bootdata);
while (len > sizeof(bootdata)) {
zx_status_t status = zx_vmo_read(vmo, &bootdata, off, sizeof(bootdata), &actual);
if ((status < 0) || (actual != sizeof(bootdata))) {
break;
}
size_t itemlen = BOOTDATA_ALIGN(sizeof(bootdata_t) + bootdata.length);
if (itemlen > len) {
printf("devmgr: bootdata item too large (%zd > %zd)\n", itemlen, len);
break;
}
switch (bootdata.type) {
case BOOTDATA_CONTAINER:
printf("devmgr: unexpected bootdata container header\n");
goto done;
case BOOTDATA_BOOTFS_DISCARD:
// this was already unpacked for us by userboot
break;
case BOOTDATA_BOOTFS_BOOT:
case BOOTDATA_BOOTFS_SYSTEM: {
const char* errmsg;
zx_handle_t bootfs_vmo;
status = decompress_bootdata(zx_vmar_root_self(), vmo,
off, bootdata.length + sizeof(bootdata_t),
&bootfs_vmo, &errmsg);
if (status < 0) {
printf("devmgr: failed to decompress bootdata: %s\n", errmsg);
} else {
setup_bootfs_vmo(idx++, bootdata.type, bootfs_vmo);
}
break;
}
case BOOTDATA_LAST_CRASHLOG:
setup_last_crashlog(vmo, off + sizeof(bootdata_t), bootdata.length);
break;
default:
break;
}
off += itemlen;
len -= itemlen;
}
done:
zx_handle_close(vmo);
}
}
ssize_t devmgr_add_systemfs_vmo(zx_handle_t vmo) {
ssize_t added = setup_bootfs_vmo(100, BOOTDATA_BOOTFS_SYSTEM, vmo);
if (added > 0) {
fuchsia_start();
}
return added;
}
// Look for VMOs passed as startup handles of PA_HND_TYPE type, and add them to
// the filesystem under the path /boot/VMO_SUBDIR_LEN/<vmo-name>.
static void fetch_vmos(uint_fast8_t type, const char* debug_type_name) {
for (uint_fast16_t i = 0; true; ++i) {
zx_handle_t vmo = zx_get_startup_handle(PA_HND(type, i));
if (vmo == ZX_HANDLE_INVALID)
break;
if (type == PA_VMO_VDSO && i == 0) {
// The first vDSO is the default vDSO. Since we've stolen
// the startup handle, launchpad won't find it on its own.
// So point launchpad at it.
launchpad_set_vdso_vmo(vmo);
}
// The vDSO VMOs have names like "vdso/default", so those
// become VMO files at "/boot/kernel/vdso/default".
char name[VMO_SUBDIR_LEN + ZX_MAX_NAME_LEN] = VMO_SUBDIR;
size_t size;
zx_status_t status = zx_object_get_property(vmo, ZX_PROP_NAME,
name + VMO_SUBDIR_LEN, sizeof(name) - VMO_SUBDIR_LEN);
if (status != ZX_OK) {
printf("devmgr: zx_object_get_property on %s %u: %s\n",
debug_type_name, i, zx_status_get_string(status));
continue;
}
status = zx_vmo_get_size(vmo, &size);
if (status != ZX_OK) {
printf("devmgr: zx_vmo_get_size on %s %u: %s\n",
debug_type_name, i, zx_status_get_string(status));
continue;
}
if (size == 0) {
// empty vmos do not get installed
zx_handle_close(vmo);
continue;
}
if (!strcmp(name + VMO_SUBDIR_LEN, "crashlog")) {
// the crashlog has a special home
strcpy(name, "log/last-panic.txt");
}
status = bootfs_add_file(name, vmo, 0, size);
if (status != ZX_OK) {
printf("devmgr: failed to add %s %u to filesystem: %s\n",
debug_type_name, i, zx_status_get_string(status));
}
}
}
void fshost_start(void) {
setup_bootfs();
vfs_global_init(vfs_create_global_root());
fetch_vmos(PA_VMO_VDSO, "PA_VMO_VDSO");
fetch_vmos(PA_VMO_KERNEL_FILE, "PA_VMO_KERNEL_FILE");
// if we have a /system ramdisk, start higher level services
if (has_secondary_bootfs) {
fuchsia_start();
}
}
zx_handle_t fs_root_clone(void) {
return vfs_create_global_root_handle();
}
zx_handle_t devmgr_load_file(const char* path) {
return ZX_HANDLE_INVALID;
}
static zx_handle_t devfs_root;
static zx_handle_t svc_root;
static zx_handle_t fuchsia_event;
zx_handle_t devfs_root_clone(void) {
return fdio_service_clone(devfs_root);
}
zx_handle_t svc_root_clone(void) {
return fdio_service_clone(svc_root);
}
void fuchsia_start(void) {
zx_object_signal(fuchsia_event, 0, ZX_USER_SIGNAL_0);
}
static loader_service_t* loader_service;
int main(int argc, char** argv) {
printf("fshost: started.\n");
bool netboot = false;
while (argc > 1) {
if (!strcmp(argv[1], "--netboot")) {
netboot = true;
} else {
printf("fshost: unknown option '%s'\n", argv[1]);
}
argc--;
argv++;
}
zx_handle_t fs_root = zx_get_startup_handle(PA_HND(PA_USER0, 0));
devfs_root = zx_get_startup_handle(PA_HND(PA_USER0, 1));
svc_root = zx_get_startup_handle(PA_HND(PA_USER0, 2));
zx_handle_t devmgr_loader = zx_get_startup_handle(PA_HND(PA_USER0, 3));
fuchsia_event = zx_get_startup_handle(PA_HND(PA_USER1, 0));
fshost_start();
vfs_connect_global_root_handle(fs_root);
fdio_ns_t* ns;
zx_status_t r;
if ((r = fdio_ns_create(&ns)) != ZX_OK) {
printf("fshost: cannot create namespace: %d\n", r);
return -1;
}
if ((r = fdio_ns_bind(ns, "/", fs_root_clone())) != ZX_OK) {
printf("fshost: cannot bind / to namespace: %d\n", r);
}
if ((r = fdio_ns_bind(ns, "/dev", devfs_root_clone())) != ZX_OK) {
printf("fshost: cannot bind /dev to namespace: %d\n", r);
}
if ((r = fdio_ns_install(ns)) != ZX_OK) {
printf("fshost: cannot install namespace: %d\n", r);
}
if ((r = loader_service_create_fs("system-loader", &loader_service)) != ZX_OK) {
printf("fshost: failed to create loader service: %d\n", r);
} else {
loader_service_attach(loader_service, devmgr_loader);
zx_handle_t svc;
if ((r = loader_service_connect(loader_service, &svc)) != ZX_OK) {
printf("fshost: failed to connect to loader service: %d\n", r);
} else {
// switch from bootfs-loader to system-loader
zx_handle_close(dl_set_loader_service(svc));
}
}
block_device_watcher(zx_job_default(), netboot);
}