text: // 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 <fuchsia/boot/c/fidl.h>
  #include <lib/async-loop/cpp/loop.h>
  #include <lib/async-loop/default.h>
  #include <lib/fdio/fdio.h>
  #include <lib/zx/debuglog.h>
  #include <lib/zx/job.h>
  #include <lib/zx/process.h>
  #include <lib/zx/resource.h>
  #include <lib/zx/time.h>
  #include <stdio.h>
  #include <zircon/boot/image.h>
  #include <zircon/dlfcn.h>
  #include <zircon/process.h>
  #include <zircon/processargs.h>
  #include <zircon/status.h>
  #include <zircon/syscalls/clock.h>
  #include <zircon/syscalls/resource.h>
  #include <zircon/syscalls/system.h>
  #include <zircon/utc.h>
  
  #include <iterator>
  #include <memory>
  #include <sstream>
  #include <thread>
  #include <utility>
  #include <vector>
  
  #include <fbl/string.h>
  #include <fbl/vector.h>
  #include <launchpad/launchpad.h>
  
  #include "bootfs-loader-service.h"
  #include "bootfs-service.h"
  #include "svcfs-service.h"
  #include "util.h"
  
  namespace {
  
  static constexpr const char kBootfsVmexName[] = "bootfs_vmex";
  
  struct Resources {
    // TODO(smpham): Remove root resource.
    zx::resource root;
    zx::resource mmio;
    zx::resource irq;
    zx::resource system;
  #if __x86_64__
    zx::resource ioport;
  #elif __aarch64__
    zx::resource smc;
  #endif
  };
  
  // Wire up stdout so that printf() and friends work.
  zx_status_t SetupStdout(const zx::debuglog& log) {
    zx::debuglog dup;
    zx_status_t status = log.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup);
    if (status != ZX_OK) {
      return status;
    }
    fdio_t* logger = nullptr;
    status = fdio_create(dup.release(), &logger);
    if (status != ZX_OK) {
      return status;
    }
    int fd = fdio_bind_to_fd(logger, 1, 0);
    if (fd != 1) {
      return ZX_ERR_BAD_STATE;
    }
    return ZX_OK;
  }
  
  // Create and install a userspace UTC clock that will remain fixed at zero.
  zx_status_t InitializeClock() {
    zx_clock_create_args_v1_t args = {};
    zx_handle_t clock, prev_clock;
    zx_status_t status = zx_clock_create(ZX_CLOCK_ARGS_VERSION(1), &args, &clock);
    if (status != ZX_OK) {
      return status;
    }
    status = zx_utc_reference_swap(clock, &prev_clock);
    if (status != ZX_OK) {
      return status;
    }
    if (prev_clock != ZX_HANDLE_INVALID) {
      printf("bootsvc: UTC clock was not empty during initialization\n");
      zx_handle_close(prev_clock);
    }
    return ZX_OK;
  }
  
  // Parse ZBI_TYPE_IMAGE_ARGS item into boot args buffer
  zx_status_t ExtractBootArgsFromImage(fbl::Vector<char>* buf, const zx::vmo& image_vmo,
                                       bootsvc::ItemMap* item_map) {
    auto it = item_map->find(bootsvc::ItemKey{ZBI_TYPE_IMAGE_ARGS, 0});
    if (it == item_map->end()) {
      return ZX_ERR_NOT_FOUND;
    }
  
    auto cfg = std::make_unique<char[]>(it->second.length);
  
    // read cfg data
    zx_status_t status = image_vmo.read(cfg.get(), it->second.offset, it->second.length);
    if (status != ZX_OK) {
      return status;
    }
  
    // Parse boot arguments file from bootdata.
    std::string_view str(cfg.get(), it->second.length);
    status = bootsvc::ParseBootArgs(std::move(str), buf);
    if (status != ZX_OK) {
      return status;
    }
  
    item_map->erase(it);
    return ZX_OK;
  }
  
  zx_status_t ExtractBootArgsFromBootfs(fbl::Vector<char>* buf,
                                        const fbl::RefPtr<bootsvc::BootfsService>& bootfs) {
    // TODO(teisenbe): Rename this file
    const char* config_path = "config/devmgr";
  
    // This config file may not be present depending on the device, but errors besides NOT_FOUND
    // should not be ignored.
    zx::vmo config_vmo;
    uint64_t file_size;
    zx_status_t status = bootfs->Open(config_path, /*executable=*/false, &config_vmo, &file_size);
    if (status == ZX_ERR_NOT_FOUND) {
      printf("bootsvc: No boot config found in bootfs, skipping\n");
      return ZX_OK;
    } else if (status != ZX_OK) {
      return status;
    }
  
    auto config = std::make_unique<char[]>(file_size);
    status = config_vmo.read(config.get(), 0, file_size);
    if (status != ZX_OK) {
      return status;
    }
  
    // Parse boot arguments file from bootfs.
    std::string_view str(config.get(), file_size);
    return bootsvc::ParseBootArgs(std::move(str), buf);
  }
  
  // Load the boot arguments from bootfs/ZBI_TYPE_IMAGE_ARGS and environment variables.
  zx_status_t LoadBootArgs(const fbl::RefPtr<bootsvc::BootfsService>& bootfs,
                           const zx::vmo& image_vmo, bootsvc::ItemMap* item_map, zx::vmo* out,
                           uint64_t* size) {
    fbl::Vector<char> boot_args;
    zx_status_t status;
  
    status = ExtractBootArgsFromImage(&boot_args, image_vmo, item_map);
    ZX_ASSERT_MSG(((status == ZX_OK) || (status == ZX_ERR_NOT_FOUND)),
                  "Retrieving boot args failed: %s\n", zx_status_get_string(status));
  
    status = ExtractBootArgsFromBootfs(&boot_args, bootfs);
    ZX_ASSERT_MSG(status == ZX_OK, "Retrieving boot config failed: %s\n",
                  zx_status_get_string(status));
  
    // Add boot arguments from environment variables.
    for (char** e = environ; *e != nullptr; e++) {
      for (const char* x = *e; *x != 0; x++) {
        boot_args.push_back(*x);
      }
      boot_args.push_back(0);
    }
  
    // Copy boot arguments into VMO.
    zx::vmo args_vmo;
    status = zx::vmo::create(boot_args.size(), 0, &args_vmo);
    if (status != ZX_OK) {
      return status;
    }
    status = args_vmo.write(boot_args.data(), 0, boot_args.size());
    if (status != ZX_OK) {
      return status;
    }
    status = args_vmo.replace(ZX_DEFAULT_VMO_RIGHTS & ~ZX_RIGHT_WRITE, &args_vmo);
    if (status != ZX_OK) {
      return status;
    }
    *out = std::move(args_vmo);
    *size = boot_args.size();
    return ZX_OK;
  }
  
  // Launch the next process in the boot chain.
  // It will receive:
  // - stdout & stderr wired up to a debuglog handle
  // - A namespace containing:
  //   - A /boot directory, connected to the bootfs service hosted by bootsvc
  //   - A /svc directory, containing other services hosted by bootsvc, including:
  //     - fuchsia.boot.FactoryItems, to access factory-related ZBI items
  //     - fuchsia.boot.Items, to access ZBI items including bootloader files
  // - A loader that can load libraries from /boot, hosted by bootsvc
  //
  // If the next process terminates, bootsvc will quit.
  void LaunchNextProcess(fbl::RefPtr<bootsvc::BootfsService> bootfs,
                         fbl::RefPtr<bootsvc::SvcfsService> svcfs,
                         std::shared_ptr<bootsvc::BootfsLoaderService> loader_svc,
                         Resources& resources, const zx::debuglog& log, async::Loop& loop,
                         const zx::vmo& bootargs_vmo, const uint64_t bootargs_size) {
    const char* bootsvc_next = getenv("bootsvc.next");
    if (bootsvc_next == nullptr) {
      // Note that arguments are comma-delimited.
      bootsvc_next =
          "bin/component_manager,"
          "fuchsia-boot:///#meta/root.cm,"
          "--config,"
          "/boot/config/component_manager";
    }
  
    // Split the bootsvc.next value into 1 or more arguments using ',' as a
    // delimiter.
    printf("bootsvc: bootsvc.next = %s\n", bootsvc_next);
    fbl::Vector<fbl::String> next_args = bootsvc::SplitString(bootsvc_next, ',');
  
    // Open the executable we will start next
    zx::vmo program;
    uint64_t file_size;
    const char* next_program = next_args[0].c_str();
    zx_status_t status = bootfs->Open(next_program, /*executable=*/true, &program, &file_size);
    ZX_ASSERT_MSG(status == ZX_OK, "bootsvc: failed to open '%s': %s\n", next_program,
                  zx_status_get_string(status));
  
    // 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 = bootfs->CreateRootConnection(&bootfs_conn);
    ZX_ASSERT_MSG(status == ZX_OK, "bootfs conn creation failed: %s\n", zx_status_get_string(status));
  
    zx::channel svcfs_conn;
    status = svcfs->CreateRootConnection(&svcfs_conn);
    ZX_ASSERT_MSG(status == ZX_OK, "svcfs conn creation failed: %s\n", zx_status_get_string(status));
  
    const char* nametable[2] = {};
    uint32_t count = 0;
  
    launchpad_t* lp;
    launchpad_create(0, next_program, &lp);
    {
      // Use the local loader service backed directly by the primary BOOTFS.
      auto loader_conn = loader_svc->Connect();
      ZX_ASSERT_MSG(loader_conn.is_ok(), "failed to connect to BootfsLoaderService : %s\n",
                    loader_conn.status_string());
      zx_handle_t old = launchpad_use_loader_service(lp, loader_conn->TakeChannel().release());
      ZX_ASSERT(old == ZX_HANDLE_INVALID);
    }
    launchpad_load_from_vmo(lp, program.release());
    launchpad_clone(lp, LP_CLONE_DEFAULT_JOB);
  
    launchpad_add_handle(lp, bootfs_conn.release(), PA_HND(PA_NS_DIR, count));
    nametable[count++] = "/boot";
    launchpad_add_handle(lp, svcfs_conn.release(), PA_HND(PA_NS_DIR, count));
    nametable[count++] = "/svc";
  
    // Pass on resources to the next process.
    launchpad_add_handle(lp, resources.mmio.release(), PA_HND(PA_MMIO_RESOURCE, 0));
    launchpad_add_handle(lp, resources.irq.release(), PA_HND(PA_IRQ_RESOURCE, 0));
    launchpad_add_handle(lp, resources.system.release(), PA_HND(PA_SYSTEM_RESOURCE, 0));
  #if __x86_64__
    launchpad_add_handle(lp, resources.ioport.release(), PA_HND(PA_IOPORT_RESOURCE, 0));
  #elif __aarch64__
    launchpad_add_handle(lp, resources.smc.release(), PA_HND(PA_SMC_RESOURCE, 0));
  #endif
  
    // Duplicate the root resource to pass to the next process.
    zx::resource root_rsrc_dup;
    status = resources.root.duplicate(ZX_RIGHT_SAME_RIGHTS, &root_rsrc_dup);
    ZX_ASSERT_MSG(status == ZX_OK && root_rsrc_dup.is_valid(), "Failed to duplicate root resource");
    launchpad_add_handle(lp, root_rsrc_dup.release(), PA_HND(PA_RESOURCE, 0));
  
    int argc = static_cast<int>(next_args.size());
    const char* argv[argc];
    for (int i = 0; i < argc; ++i) {
      argv[i] = next_args[i].c_str();
    }
    launchpad_set_args(lp, argc, argv);
  
    ZX_ASSERT(count <= std::size(nametable));
    launchpad_set_nametable(lp, count, nametable);
  
    // Set up the environment table for launchpad.
    std::vector<char> env(bootargs_size);
    std::vector<char*> envp;
    status = bootargs_vmo.read(env.data(), 0, bootargs_size);
    ZX_ASSERT_MSG(status == ZX_OK, "failed to read bootargs from vmo: %s",
                  zx_status_get_string(status));
  
    uint64_t last_env_start = 0;
    uint64_t i;
    for (i = 0; i < bootargs_size; i++) {
      if (env[i] == 0) {
        envp.push_back(&env[last_env_start]);
        last_env_start = i + 1;
      }
    }
    envp.push_back(nullptr);
  
    status = launchpad_set_environ(lp, envp.data());
    if (status != ZX_OK) {
      launchpad_abort(lp, status, "bootsvc: cannot set up environment");
    }
  
    zx::debuglog log_dup;
    status = log.duplicate(ZX_RIGHT_SAME_RIGHTS, &log_dup);
    if (status != ZX_OK) {
      launchpad_abort(lp, status, "bootsvc: cannot duplicate debuglog handle");
    } else {
      launchpad_add_handle(lp, log_dup.release(), PA_HND(PA_FD, FDIO_FLAG_USE_FOR_STDIO));
    }
  
    const char* errmsg;
    zx::process proc_handle;
    status = launchpad_go(lp, proc_handle.reset_and_get_address(), &errmsg);
    if (status != ZX_OK) {
      printf("bootsvc: launchpad %s failed: %s: %s\n", next_program, errmsg,
             zx_status_get_string(status));
      return;
    }
    printf("bootsvc: Launched %s\n", next_program);
  
    // wait for termination and then reboot or power off the system
    zx_signals_t observed;
    zx_status_t termination_result =
        proc_handle.wait_one(ZX_TASK_TERMINATED, zx::time::basic_time::infinite(), &observed);
    if (termination_result != ZX_OK) {
      printf("bootsvc: failure waiting for next process termination %i\n", termination_result);
    }
  
    // If the next process terminated, quit the main loop.
    loop.Quit();
  }
  
  }  // namespace
  
  int main(int argc, char** argv) {
    // 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));
  
    // NOTE: This will be the only source of zx::debuglog in the system.
    // Eventually, we will receive this through a startup handle from userboot.
    zx::resource root_resource(zx_take_startup_handle(PA_HND(PA_RESOURCE, 0)));
    ZX_ASSERT(root_resource.is_valid());
    zx::debuglog log;
    zx_status_t status = zx::debuglog::create(root_resource, 0, &log);
    ZX_ASSERT(status == ZX_OK);
  
    status = SetupStdout(log);
    ZX_ASSERT(status == ZX_OK);
  
    printf("bootsvc: Starting...\n");
  
    status = zx::job::default_job()->set_critical(0, *zx::process::self());
    ZX_ASSERT_MSG(status == ZX_OK, "Failed to set bootsvc as critical to root-job: %s\n",
                  zx_status_get_string(status));
  
    async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
  
    zx::vmo bootfs_vmo(zx_take_startup_handle(PA_HND(PA_VMO_BOOTFS, 0)));
    ZX_ASSERT(bootfs_vmo.is_valid());
  
    // Take the resources.
    printf("bootsvc: Taking resources...\n");
    Resources resources;
    resources.mmio.reset(zx_take_startup_handle(PA_HND(PA_MMIO_RESOURCE, 0)));
    ZX_ASSERT_MSG(resources.mmio.is_valid(), "Invalid MMIO root resource handle\n");
    resources.irq.reset(zx_take_startup_handle(PA_HND(PA_IRQ_RESOURCE, 0)));
    ZX_ASSERT_MSG(resources.irq.is_valid(), "Invalid IRQ root resource handle\n");
    resources.system.reset(zx_take_startup_handle(PA_HND(PA_SYSTEM_RESOURCE, 0)));
    ZX_ASSERT_MSG(resources.system.is_valid(), "Invalid system root resource handle\n");
  #if __x86_64__
    resources.ioport.reset(zx_take_startup_handle(PA_HND(PA_IOPORT_RESOURCE, 0)));
    ZX_ASSERT_MSG(resources.ioport.is_valid(), "Invalid IOPORT root resource handle\n");
  #elif __aarch64__
    resources.smc.reset(zx_take_startup_handle(PA_HND(PA_SMC_RESOURCE, 0)));
    ZX_ASSERT_MSG(resources.smc.is_valid(), "Invalid SMC root resource handle\n");
  #endif
    resources.root = std::move(root_resource);
    ZX_ASSERT_MSG(resources.root.is_valid(), "Invalid root resource handle\n");
  
    // Memfs attempts to read the UTC clock to set file times, but bootsvc starts
    // before component_manager has created the standard UTC clock. Create a
    // process-local clock fixed to zero to avoid clock read errors.
    status = InitializeClock();
    ZX_ASSERT_MSG(status == ZX_OK, "Failed to create clock: %s\n", zx_status_get_string(status));
  
    // Create a VMEX resource object to provide the bootfs service.
    // TODO(smpham): Pass VMEX resource from kernel.
    zx::resource bootfs_vmex_rsrc;
    status = zx::resource::create(resources.system, ZX_RSRC_KIND_SYSTEM, ZX_RSRC_SYSTEM_VMEX_BASE, 1,
                                  kBootfsVmexName, sizeof(kBootfsVmexName), &bootfs_vmex_rsrc);
    ZX_ASSERT_MSG(status == ZX_OK, "Failed to create VMEX resource");
  
    // Set up the bootfs service
    printf("bootsvc: Creating bootfs service...\n");
    fbl::RefPtr<bootsvc::BootfsService> bootfs_svc;
    status =
        bootsvc::BootfsService::Create(loop.dispatcher(), std::move(bootfs_vmex_rsrc), &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 ZBI boot image
    printf("bootsvc: Retrieving boot image...\n");
    zx::vmo image_vmo;
    bootsvc::ItemMap item_map;
    bootsvc::FactoryItemMap factory_item_map;
    bootsvc::BootloaderFileMap bootloader_file_map;
    status =
        bootsvc::RetrieveBootImage(&image_vmo, &item_map, &factory_item_map, &bootloader_file_map);
    ZX_ASSERT_MSG(status == ZX_OK, "Retrieving boot image failed: %s\n",
                  zx_status_get_string(status));
  
    // Load boot arguments into VMO
    printf("bootsvc: Loading boot arguments...\n");
    zx::vmo args_vmo;
    uint64_t args_size = 0;
    status = LoadBootArgs(bootfs_svc, image_vmo, &item_map, &args_vmo, &args_size);
    ZX_ASSERT_MSG(status == ZX_OK, "Loading boot arguments failed: %s\n",
                  zx_status_get_string(status));
  
    // Set up the svcfs service
    printf("bootsvc: Creating svcfs service...\n");
    fbl::RefPtr<bootsvc::SvcfsService> svcfs_svc = bootsvc::SvcfsService::Create(loop.dispatcher());
    svcfs_svc->AddService(
        fuchsia_boot_Items_Name,
        bootsvc::CreateItemsService(loop.dispatcher(), std::move(image_vmo), std::move(item_map),
                                    std::move(bootloader_file_map)));
    svcfs_svc->AddService(
        fuchsia_boot_FactoryItems_Name,
        bootsvc::CreateFactoryItemsService(loop.dispatcher(), std::move(factory_item_map)));
  
    // 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");
    auto loader_svc = bootsvc::BootfsLoaderService::Create(loop.dispatcher(), bootfs_svc);
  
    // 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");
    std::thread(LaunchNextProcess, bootfs_svc, svcfs_svc, loader_svc, std::ref(resources),
                std::cref(log), std::ref(loop), std::cref(args_vmo), args_size)
        .detach();
  
    // Begin serving the bootfs fileystem and loader
    loop.Run();
    printf("bootsvc: Exiting\n");
    return 0;
  }
  