blob: f70024d0f5aad5c7fac9f7b628f328ca5b34c56e [file] [log] [blame]
// 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;
}