| // Copyright 2018 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 <ctype.h> |
| #include <stdio.h> |
| #include <utility> |
| |
| #include <bootdata/decompress.h> |
| #include <fbl/vector.h> |
| #include <launchpad/launchpad.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/bootsvc-protocol/processargs.h> |
| #include <lib/fdio/util.h> |
| #include <lib/zx/debuglog.h> |
| #include <zircon/boot/bootdata.h> |
| #include <zircon/dlfcn.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| |
| #include "bootfs-loader-service.h" |
| #include "bootfs-service.h" |
| #include "util.h" |
| |
| namespace { |
| |
| // Wire up stdout so that printf() and friends work. |
| void SetupStdout() { |
| zx::debuglog h; |
| if (zx::debuglog::create(zx::resource(), 0, &h) < 0) { |
| return; |
| } |
| fdio_t* logger; |
| if ((logger = fdio_logger_create(h.release())) == nullptr) { |
| return; |
| } |
| close(1); |
| fdio_bind_to_fd(logger, 1, 0); |
| } |
| |
| // Load the cmdline arguments overrides from the bootfs |
| void LoadCmdlineOverridesFromBootfs(const fbl::RefPtr<bootsvc::BootfsService>& bootfs) { |
| // TODO(teisenbe): Rename this file |
| const char* config_file = "/config/devmgr"; |
| |
| zx::vmo vmo; |
| uint64_t file_size; |
| zx_status_t status = bootfs->Open(config_file, &vmo, &file_size); |
| if (status != ZX_OK) { |
| return; |
| } |
| |
| auto cfg = fbl::make_unique<char[]>(file_size + 1); |
| ZX_ASSERT(cfg); |
| |
| status = vmo.read(cfg.get(), 0, file_size); |
| if (status != ZX_OK) { |
| printf("zx_vmo_read on /boot/config/devmgr BOOTFS VMO: %d (%s)\n", |
| status, zx_status_get_string(status)); |
| return; |
| } |
| cfg[file_size] = '\0'; |
| |
| // putenv() below takes ownership of pieces of this memory, so just release |
| // ownership of it now. |
| char* x = cfg.release(); |
| while (*x) { |
| // skip any leading whitespace |
| while (isspace(*x)) { |
| x++; |
| } |
| |
| // find the next line (seek for CR or NL) |
| char* next = x; |
| for (;;) { |
| // eof? we're all done then |
| if (*next == 0) { |
| return; |
| } |
| if ((*next == '\r') || (*next == '\n')) { |
| *next++ = 0; |
| break; |
| } |
| next++; |
| } |
| |
| // process line if not a comment and not a zero-length name |
| if ((*x != '#') && (*x != '=')) { |
| for (char* y = x; *y != 0; y++) { |
| // space in name is invalid, give up |
| if (isspace(*y)) { |
| break; |
| } |
| // valid looking env entry? store it |
| if (*y == '=') { |
| putenv(x); |
| break; |
| } |
| } |
| } |
| |
| x = next; |
| } |
| } |
| |
| // Set up the channel we will use for passing the root resource off. We |
| // embed the root resource in a channel to make it harder to accidentally |
| // leave a handle to it in some process on the way to devmgr. |
| zx::channel CreateResourceChannel() { |
| zx::resource resource(zx_take_startup_handle(PA_HND(PA_RESOURCE, 0))); |
| ZX_ASSERT_MSG(resource.is_valid(), "bootsvc: did not receive resource handle\n"); |
| |
| zx::channel client, server; |
| zx_status_t status = zx::channel::create(0, &server, &client); |
| ZX_ASSERT(status == ZX_OK); |
| |
| zx_handle_t handles[] = { resource.release() }; |
| status = server.write(0, nullptr, 0, handles, fbl::count_of(handles)); |
| ZX_ASSERT(status == ZX_OK); |
| return client; |
| } |
| |
| struct LaunchNextProcessArgs { |
| fbl::RefPtr<bootsvc::BootfsService> bootfs; |
| fbl::Vector<zx::vmo> bootdata; |
| }; |
| |
| // Launch the next process in the boot chain. |
| // It will receive: |
| // - stdout wired up via a debuglog handle |
| // - The boot cmdline arguments, via envp |
| // - A namespace containing a /boot, serviced by bootsvc |
| // - A loader that can load libraries from /boot, serviced by bootsvc |
| // - A handle to the root job |
| // - A handle to each of the bootdata VMOs the kernel provided |
| // - A handle to a channel containing the root resource, with type |
| // BOOTSVC_ROOT_RESOURCE_CHANNEL_HND |
| int LaunchNextProcess(void* raw_ctx) { |
| fbl::unique_ptr<LaunchNextProcessArgs> args(static_cast<LaunchNextProcessArgs*>(raw_ctx)); |
| |
| const char* next_program = getenv("bootsvc.next"); |
| if (next_program == nullptr) { |
| next_program = "bin/devmgr"; |
| } |
| |
| // Open the executable we will start next |
| zx::vmo program; |
| uint64_t file_size; |
| zx_status_t status = args->bootfs->Open(next_program, &program, &file_size); |
| ZX_ASSERT_MSG(status == ZX_OK, "bootsvc: failed to open '%s': %s\n", next_program, |
| zx_status_get_string(status)); |
| |
| zx::channel resource_client = CreateResourceChannel(); |
| |
| // Get the bootfs fuchsia.io.Node service channel that we will hand to the |
| // next process in the boot chain. |
| zx::channel bootfs_conn; |
| status = args->bootfs->CreateRootConnection(&bootfs_conn); |
| ZX_ASSERT_MSG(status == ZX_OK, "bootfs conn creation failed: %s\n", |
| zx_status_get_string(status)); |
| |
| const char* nametable[1] = { }; |
| uint32_t count = 0; |
| |
| launchpad_t* lp; |
| launchpad_create(0, next_program, &lp); |
| launchpad_load_from_vmo(lp, program.release()); |
| launchpad_clone(lp, LP_CLONE_ENVIRON | LP_CLONE_DEFAULT_JOB); |
| |
| launchpad_add_handle(lp, bootfs_conn.release(), PA_HND(PA_NS_DIR, count)); |
| nametable[count++] = "/boot"; |
| |
| ZX_ASSERT(count <= fbl::count_of(nametable)); |
| launchpad_set_nametable(lp, count, nametable); |
| |
| zx::debuglog debuglog; |
| status = zx::debuglog::create(zx::resource(), 0, &debuglog); |
| if (status != ZX_OK) { |
| launchpad_abort(lp, status, "bootsvc: cannot create debuglog handle"); |
| } else { |
| launchpad_add_handle(lp, debuglog.release(), PA_HND(PA_FDIO_LOGGER, |
| FDIO_FLAG_USE_FOR_STDIO | 0)); |
| } |
| |
| launchpad_add_handle(lp, resource_client.release(), BOOTSVC_ROOT_RESOURCE_CHANNEL_HND); |
| |
| unsigned bootdata_idx = 0; |
| for (zx::vmo& bootdata : args->bootdata) { |
| launchpad_add_handle(lp, bootdata.release(), PA_HND(PA_VMO_BOOTDATA, bootdata_idx++)); |
| } |
| |
| const char* errmsg; |
| if ((status = launchpad_go(lp, nullptr, &errmsg)) < 0) { |
| printf("bootsvc: launchpad %s failed: %s: %s\n", next_program, errmsg, |
| zx_status_get_string(status)); |
| } else { |
| printf("bootsvc: launched %s\n", next_program); |
| } |
| return 0; |
| } |
| |
| void StartLaunchNextProcessThread(const fbl::RefPtr<bootsvc::BootfsService>& bootfs, |
| fbl::Vector<zx::vmo> bootdata) { |
| auto args = fbl::make_unique<LaunchNextProcessArgs>(); |
| args->bootfs = bootfs; |
| args->bootdata = std::move(bootdata); |
| |
| thrd_t t; |
| int status = thrd_create(&t, LaunchNextProcess, args.release()); |
| ZX_ASSERT(status == thrd_success); |
| status = thrd_detach(t); |
| ZX_ASSERT(status == thrd_success); |
| } |
| |
| // Checks if there are any additions to the BOOT bootfs and if there is a |
| // crashlog from the bootloader. Modifies the bootdata_vmos vector as necessary |
| zx_status_t ProcessBootdata(const fbl::RefPtr<bootsvc::BootfsService>& bootfs, |
| const fbl::Vector<zx::vmo>& bootdata_vmos) { |
| for (const zx::vmo& vmo : bootdata_vmos) { |
| 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("bootsvc: bootdata item does not contain bootdata\n"); |
| continue; |
| } |
| if (!(bootdata.flags & BOOTDATA_FLAG_V2)) { |
| printf("bootsvc: 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("bootsvc: bootdata item too large (%zd > %zd)\n", itemlen, len); |
| break; |
| } |
| switch (bootdata.type) { |
| case BOOTDATA_CONTAINER: |
| printf("bootsvc: unexpected bootdata container header\n"); |
| break; |
| case BOOTDATA_BOOTFS_BOOT: { |
| const char* errmsg; |
| zx::vmo bootfs_vmo; |
| status = decompress_bootdata(zx_vmar_root_self(), vmo.get(), |
| off, bootdata.length + sizeof(bootdata_t), |
| bootfs_vmo.reset_and_get_address(), &errmsg); |
| if (status != ZX_OK) { |
| printf("bootsvc: failed to decompress bootfs: %s\n", errmsg); |
| break; |
| } |
| status = bootfs->AddBootfs(std::move(bootfs_vmo)); |
| if (status != ZX_OK) { |
| printf("bootsvc: failed to add bootfs: %s\n", errmsg); |
| break; |
| } |
| |
| // Mark that we've already processed this one. |
| bootdata.type = BOOTDATA_BOOTFS_DISCARD; |
| vmo.write(&bootdata.type, off + offsetof(bootdata_t, type), |
| sizeof(bootdata.type)); |
| break; |
| } |
| default: |
| break; |
| } |
| off += itemlen; |
| len -= itemlen; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| SetupStdout(); |
| printf("bootsvc: Starting...\n"); |
| |
| // Close the loader-service channel so the service can go away. |
| // We won't use it any more (no dlopen calls in this process). |
| zx_handle_close(dl_set_loader_service(ZX_HANDLE_INVALID)); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToThread); |
| |
| zx::vmo bootfs_vmo(zx_take_startup_handle(PA_HND(PA_VMO_BOOTFS, 0))); |
| ZX_ASSERT(bootfs_vmo.is_valid()); |
| |
| // Set up the bootfs service |
| printf("bootsvc: Creating bootfs service...\n"); |
| fbl::RefPtr<bootsvc::BootfsService> bootfs_svc; |
| zx_status_t status = bootsvc::BootfsService::Create(loop.dispatcher(), &bootfs_svc); |
| ZX_ASSERT_MSG(status == ZX_OK, "BootfsService creation failed: %s\n", |
| zx_status_get_string(status)); |
| status = bootfs_svc->AddBootfs(std::move(bootfs_vmo)); |
| ZX_ASSERT_MSG(status == ZX_OK, "bootfs add failed: %s\n", zx_status_get_string(status)); |
| |
| // Process the bootdata to get additional bootfs parts |
| printf("bootsvc: Processing bootdata...\n"); |
| fbl::Vector<zx::vmo> bootdata = bootsvc::RetrieveBootdata(); |
| status = ProcessBootdata(bootfs_svc, bootdata); |
| ZX_ASSERT_MSG(status == ZX_OK, "Processing bootdata failed: %s\n", |
| zx_status_get_string(status)); |
| |
| // Apply any cmdline overrides from bootfs |
| printf("bootsvc: Loading boot cmdline overrides...\n"); |
| LoadCmdlineOverridesFromBootfs(bootfs_svc); |
| |
| // Consume certain VMO types from the startup handle table |
| printf("bootsvc: Loading kernel VMOs...\n"); |
| bootfs_svc->PublishStartupVmos(PA_VMO_VDSO, "PA_VMO_VDSO"); |
| bootfs_svc->PublishStartupVmos(PA_VMO_KERNEL_FILE, "PA_VMO_KERNEL_FILE"); |
| |
| // Creating the loader service |
| printf("bootsvc: Creating loader service...\n"); |
| fbl::RefPtr<bootsvc::BootfsLoaderService> loader; |
| status = bootsvc::BootfsLoaderService::Create(bootfs_svc, loop.dispatcher(), &loader); |
| ZX_ASSERT_MSG(status == ZX_OK, "BootfsLoaderService creation failed: %s\n", |
| zx_status_get_string(status)); |
| |
| // Switch to the local loader service backed directly by the primary bootfs |
| // to allow us to load the next process. |
| zx::channel local_loader_conn; |
| status = loader->Connect(&local_loader_conn); |
| ZX_ASSERT_MSG(status == ZX_OK, "failed to connect to BootfsLoaderService : %s\n", |
| zx_status_get_string(status)); |
| zx_handle_close(dl_set_loader_service(local_loader_conn.release())); |
| |
| // Launch the next process in the chain. This must be in a thread, since |
| // it may issue requests to the loader, which runs in the async loop that |
| // starts running after this. |
| printf("bootsvc: Launching next process...\n"); |
| StartLaunchNextProcessThread(bootfs_svc, std::move(bootdata)); |
| |
| // Begin serving the bootfs fileystem and loader |
| loop.Run(); |
| return 0; |
| } |