| // 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); |
| } |