| // 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 <bootdata/decompress.h> |
| |
| #include <fbl/function.h> |
| #include <fbl/intrusive_single_list.h> |
| #include <fbl/unique_fd.h> |
| #include <fs-management/ramdisk.h> |
| #include <launchpad/launchpad.h> |
| #include <lib/bootfs/parser.h> |
| #include <lib/fdio/namespace.h> |
| #include <lib/fdio/util.h> |
| #include <lib/fdio/watcher.h> |
| #include <lib/fit/defer.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/event.h> |
| #include <lib/zx/vmo.h> |
| #include <loader-service/loader-service.h> |
| |
| #include <zircon/boot/bootdata.h> |
| #include <zircon/device/vfs.h> |
| #include <zircon/dlfcn.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <threads.h> |
| |
| #include <utility> |
| |
| #include "fshost.h" |
| |
| namespace devmgr { |
| namespace { |
| |
| // 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) |
| |
| #define LAST_PANIC_FILEPATH "log/last-panic.txt" |
| |
| constexpr uint32_t kFsDirFlags = ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_ADMIN | |
| ZX_FS_FLAG_DIRECTORY | ZX_FS_FLAG_NOREMOTE; |
| |
| struct BootdataRamdisk : public fbl::SinglyLinkedListable<fbl::unique_ptr<BootdataRamdisk>> { |
| public: |
| explicit BootdataRamdisk(zx::vmo vmo) : vmo_(std::move(vmo)) {} |
| |
| zx::vmo TakeRamdisk() { |
| return std::move(vmo_); |
| } |
| private: |
| zx::vmo vmo_; |
| }; |
| |
| using RamdiskList = fbl::SinglyLinkedList<fbl::unique_ptr<BootdataRamdisk>>; |
| |
| // TODO: When the dependencies surrounding fs_clone are simplified, this global |
| // should be removed. fshost and devmgr each supply their own version of |
| // |fs_clone|, and devmgr-fdio relies on this function being present to |
| // implement |devmgr_launch|. |
| const FsManager* g_fshost = nullptr; |
| |
| zx_status_t SetupBootfsVmo(const fbl::unique_ptr<FsManager>& root, uint32_t n, 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 ZX_OK; |
| } |
| |
| // 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(), ZX_VM_PERM_READ, 0, vmo, 0, size, &address); |
| |
| if (!root->IsSystemMounted()) { |
| status = root->MountSystem(); |
| if (status != ZX_OK) { |
| printf("devmgr: failed to mount /system (%d)\n", status); |
| return status; |
| } |
| } |
| // We need to duplicate |vmo| because |bootfs::Create| takes ownership of the |
| // |vmo| and closes it during |bootfs::Destroy|. However, our |bootfs::Parse| |
| // callback will further store |vmo| in memfs. |
| zx::vmo bootfs_vmo; |
| status = zx_handle_duplicate(vmo, ZX_RIGHT_SAME_RIGHTS, bootfs_vmo.reset_and_get_address()); |
| if (status != ZX_OK) { |
| printf("devmgr: failed to duplicate vmo for /system (%d)\n", status); |
| return status; |
| } |
| bootfs::Parser bfs; |
| if (bfs.Init(zx::unowned_vmo(bootfs_vmo)) == ZX_OK) { |
| bfs.Parse([&root, vmo](const bootfs_entry_t *entry) -> zx_status_t { |
| // printf("bootfs: %s @%zd (%zd bytes)\n", path, off, len); |
| root.get()->SystemfsAddFile(entry->name, vmo, entry->data_off, |
| entry->data_len); |
| return ZX_OK; |
| }); |
| } |
| root->SystemfsSetReadonly(getenv("zircon.system.writable") == nullptr); |
| return ZX_OK; |
| } |
| |
| zx_status_t MiscDeviceAdded(int dirfd, int event, const char* fn, void* cookie) { |
| auto bootdata_ramdisk_list = static_cast<RamdiskList*>(cookie); |
| if (event != WATCH_EVENT_ADD_FILE || strcmp(fn, "ramctl") != 0) { |
| return ZX_OK; |
| } |
| |
| while (!bootdata_ramdisk_list->is_empty()) { |
| zx::vmo ramdisk_vmo = bootdata_ramdisk_list->pop_front()->TakeRamdisk(); |
| uint64_t size; |
| zx_status_t status = ramdisk_vmo.get_size(&size); |
| if (status != ZX_OK) { |
| printf("fshost: cannot get size of ramdisk_vmo: %d\n", status); |
| return status; |
| } |
| |
| struct ramdisk_client* client; |
| if (create_ramdisk_from_vmo(ramdisk_vmo.release(), &client) != ZX_OK) { |
| printf("fshost: failed to create ramdisk from BOOTDATA_RAMDISK\n"); |
| } else { |
| printf("fshost: BOOTDATA_RAMDISK attached\n"); |
| } |
| } |
| |
| return ZX_ERR_STOP; |
| } |
| |
| int RamctlWatcher(void* arg) { |
| fbl::unique_ptr<RamdiskList> ramdisk_list(static_cast<RamdiskList*>(arg)); |
| fbl::unique_fd dirfd(open("/dev/misc", O_DIRECTORY | O_RDONLY)); |
| if (!dirfd) { |
| printf("fshost: failed to open /dev/misc: %s\n", strerror(errno)); |
| return -1; |
| } |
| fdio_watch_directory(dirfd.get(), &MiscDeviceAdded, ZX_TIME_INFINITE, ramdisk_list.get()); |
| return 0; |
| } |
| |
| #define HND_BOOTFS(n) PA_HND(PA_VMO_BOOTFS, n) |
| #define HND_BOOTDATA(n) PA_HND(PA_VMO_BOOTDATA, n) |
| |
| void SetupBootfs(const fbl::unique_ptr<FsManager>& root, |
| const fbl::unique_ptr<RamdiskList>& ramdisk_list) { |
| unsigned idx = 0; |
| |
| zx::vmo vmo; |
| for (unsigned n = 0; vmo.reset(zx_take_startup_handle(HND_BOOTDATA(n))), vmo.is_valid(); n++) { |
| bootdata_t bootdata; |
| zx_status_t status = vmo.read(&bootdata, 0, sizeof(bootdata)); |
| if (status < 0) { |
| continue; |
| } |
| if ((bootdata.type != BOOTDATA_CONTAINER) || (bootdata.extra != BOOTDATA_MAGIC)) { |
| printf("devmgr: bootdata item does not contain bootdata\n"); |
| continue; |
| } |
| if (!(bootdata.flags & BOOTDATA_FLAG_V2)) { |
| printf("devmgr: bootdata v1 no longer supported\n"); |
| continue; |
| } |
| |
| size_t len = bootdata.length; |
| size_t off = sizeof(bootdata); |
| |
| while (len > sizeof(bootdata)) { |
| zx_status_t status = vmo.read(&bootdata, off, sizeof(bootdata)); |
| if (status < 0) { |
| 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"); |
| continue; |
| case BOOTDATA_BOOTFS_DISCARD: |
| // this was already unpacked for us by userboot and bootsvc |
| break; |
| case BOOTDATA_BOOTFS_BOOT: |
| // These should have been consumed by userboot and bootsvc. |
| printf("devmgr: unexpected boot-type bootfs\n"); |
| break; |
| case BOOTDATA_BOOTFS_SYSTEM: { |
| const char* errmsg; |
| zx_handle_t bootfs_vmo; |
| status = decompress_bootdata(zx_vmar_root_self(), vmo.get(), |
| off, bootdata.length + sizeof(bootdata_t), |
| &bootfs_vmo, &errmsg); |
| if (status < 0) { |
| printf("devmgr: failed to decompress bootdata: %s\n", errmsg); |
| } else { |
| SetupBootfsVmo(root, idx++, bootfs_vmo); |
| } |
| break; |
| } |
| case BOOTDATA_RAMDISK: { |
| const char* errmsg; |
| zx_handle_t ramdisk_vmo; |
| status = decompress_bootdata( |
| zx_vmar_root_self(), vmo.get(), |
| off, bootdata.length + sizeof(bootdata_t), |
| &ramdisk_vmo, &errmsg); |
| if (status != ZX_OK) { |
| printf("fshost: failed to decompress bootdata: %s\n", |
| errmsg); |
| } else { |
| auto ramdisk = fbl::make_unique<BootdataRamdisk>(zx::vmo(ramdisk_vmo)); |
| ramdisk_list->push_front(std::move(ramdisk)); |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| off += itemlen; |
| len -= itemlen; |
| } |
| |
| // Close the VMO once we've finished processing it. |
| vmo.reset(); |
| } |
| } |
| |
| // Setup the loader service to be used by all processes spawned by devmgr. |
| void setup_loader_service(zx::channel devmgr_loader) { |
| loader_service_t* svc; |
| zx_status_t status = loader_service_create_fs(nullptr, &svc);; |
| if (status != ZX_OK) { |
| fprintf(stderr, "fshost: failed to create loader service %d\n", status); |
| } |
| auto defer = fit::defer([svc] { loader_service_release(svc); }); |
| status = loader_service_attach(svc, devmgr_loader.release()); |
| if (status != ZX_OK) { |
| fprintf(stderr, "fshost: failed to attach to loader service: %d\n", status); |
| return; |
| } |
| zx_handle_t fshost_loader; |
| status = loader_service_connect(svc, &fshost_loader); |
| if (status != ZX_OK) { |
| fprintf(stderr, "fshost: failed to connect to loader service: %d\n", status); |
| return; |
| } |
| zx_handle_close(dl_set_loader_service(fshost_loader)); |
| } |
| |
| } // namespace |
| |
| FshostConnections::FshostConnections(zx::channel devfs_root, zx::channel svc_root, |
| zx::channel fs_root, zx::event event) |
| : devfs_root_(std::move(devfs_root)), svc_root_(std::move(svc_root)), |
| fs_root_(std::move(fs_root)), event_(std::move(event)) {} |
| |
| zx_status_t FshostConnections::Open(const char* path, zx::channel* out_connection) const { |
| zx::channel connection; |
| zx_status_t status = ZX_OK; |
| if (!strcmp(path, "svc")) { |
| connection.reset(fdio_service_clone(svc_root_.get())); |
| } else if (!strcmp(path, "dev")) { |
| connection.reset(fdio_service_clone(devfs_root_.get())); |
| } else { |
| zx::channel server; |
| status = zx::channel::create(0, &connection, &server); |
| if (status == ZX_OK) { |
| status = fdio_open_at(fs_root_.get(), path, kFsDirFlags, server.release()); |
| } |
| } |
| *out_connection = std::move(connection); |
| return status; |
| } |
| |
| zx_status_t FshostConnections::CreateNamespace() { |
| fdio_ns_t* ns; |
| zx_status_t status; |
| if ((status = fdio_ns_get_installed(&ns)) != ZX_OK) { |
| printf("fshost: cannot get namespace: %d\n", status); |
| return status; |
| } |
| |
| if ((status = fdio_ns_bind(ns, "/fs", fs_root_.get())) != ZX_OK) { |
| printf("fshost: cannot bind /fs to namespace: %d\n", status); |
| return status; |
| } |
| zx::channel system_connection; |
| if ((status = Open("system", &system_connection)) != ZX_OK) { |
| printf("devmgr: cannot open connection to /system: %d\n", status); |
| return status; |
| } |
| if ((status = fdio_ns_bind(ns, "/system", system_connection.release())) != ZX_OK) { |
| printf("devmgr: cannot bind /system to namespace: %d\n", status); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| zx::channel fs_clone(const char* path) { |
| zx::channel connection; |
| if (g_fshost->GetConnections().Open(path, &connection) == ZX_OK) { |
| return connection; |
| } else { |
| return zx::channel(); |
| } |
| } |
| |
| } // namespace devmgr |
| |
| int main(int argc, char** argv) { |
| using namespace devmgr; |
| |
| 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::channel fs_root(zx_take_startup_handle(PA_HND(PA_USER0, 0))); |
| zx::channel devfs_root; |
| { |
| zx::channel devfs_root_remote; |
| zx_status_t status = zx::channel::create(0, &devfs_root, &devfs_root_remote); |
| ZX_ASSERT(status == ZX_OK); |
| |
| fdio_ns_t* ns; |
| status = fdio_ns_get_installed(&ns); |
| ZX_ASSERT(status == ZX_OK); |
| status = fdio_ns_connect(ns, "/dev", ZX_FS_RIGHT_READABLE, devfs_root_remote.release()); |
| ZX_ASSERT_MSG(status == ZX_OK, "fshost: failed to connect to /dev: %s\n", |
| zx_status_get_string(status)); |
| } |
| zx::channel svc_root(zx_take_startup_handle(PA_HND(PA_USER0, 2))); |
| zx::channel devmgr_loader(zx_take_startup_handle(PA_HND(PA_USER0, 3))); |
| zx::event fshost_event(zx_take_startup_handle(PA_HND(PA_USER1, 0))); |
| |
| // First, initialize the local filesystem in isolation. |
| fbl::unique_ptr<FsManager> root = fbl::make_unique<FsManager>(); |
| |
| // Populate the FsManager and RamdiskList with data supplied from |
| // startup handles passed to fshost. |
| fbl::unique_ptr<RamdiskList> bootdata_ramdisk_list = fbl::make_unique<RamdiskList>(); |
| SetupBootfs(root, bootdata_ramdisk_list); |
| |
| // Initialize connections to external service managers, and begin |
| // monitoring the |fshost_event| for a termination event. |
| root->InitializeConnections(std::move(fs_root), std::move(devfs_root), |
| std::move(svc_root), std::move(fshost_event)); |
| g_fshost = root.get(); |
| |
| // If we have a "/system" ramdisk, start higher level services. |
| if (root->IsSystemMounted()) { |
| root->FuchsiaStart(); |
| } |
| |
| // Setup the devmgr loader service. |
| setup_loader_service(std::move(devmgr_loader)); |
| |
| if (!bootdata_ramdisk_list->is_empty()) { |
| thrd_t th; |
| RamdiskList* ramdisk_list = bootdata_ramdisk_list.release(); |
| int err = thrd_create_with_name(&th, &RamctlWatcher, ramdisk_list, "ramctl-watcher"); |
| if (err != thrd_success) { |
| printf("fshost: failed to start ramctl-watcher: %d\n", err); |
| delete ramdisk_list; |
| } else { |
| thrd_detach(th); |
| } |
| } |
| |
| block_device_watcher(std::move(root), zx::job::default_job(), netboot); |
| |
| printf("fshost: terminating (block device watcher finished?)\n"); |
| return 0; |
| } |