blob: 02a2de51317cf0b26c7e9950ec2c611bb145c4e6 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "zbi.h"
#include <lib/zbi-format/zbi.h>
#include <lib/zbitl/error-stdio.h>
#include <lib/zbitl/view.h>
#include <lib/zbitl/vmo.h>
#include <lib/zircon-internal/align.h>
#include <stdarg.h>
#include <zircon/status.h>
#include "option.h"
#include "util.h"
namespace {
using namespace std::string_view_literals;
using ZbiView = zbitl::View<zbitl::MapUnownedVmo>;
using ZbiError = ZbiView::Error;
using ZbiCopyError = ZbiView::CopyError<zbitl::MapOwnedVmo>;
constexpr const char kBootfsVmoName[] = "uncompressed-bootfs";
constexpr const char kScratchVmoName[] = "bootfs-decompression-scratch";
[[noreturn]] void FailFromZbiError(const ZbiError& error, const zx::debuglog& log) {
zbitl::PrintViewError(error, [&](const char* fmt, ...) {
va_list args;
va_start(args, fmt);
printl(log, fmt, args);
va_end(args);
});
zx_process_exit(-1);
}
[[noreturn]] void FailFromZbiCopyError(const ZbiCopyError& error, const zx::debuglog& log) {
zbitl::PrintViewCopyError(error, [&](const char* fmt, ...) {
va_list args;
va_start(args, fmt);
printl(log, fmt, args);
va_end(args);
});
zx_process_exit(-1);
}
// This is used as the zbitl::View::CopyStorageItem callback to allocate
// scratch memory used by decompression.
class ScratchAllocator {
public:
class Holder {
public:
Holder() = delete;
Holder(const Holder&) = delete;
Holder& operator=(const Holder&) = delete;
// Unlike the default move constructor and move assignment operators, these
// ensure that exactly one destructor cleans up the mapping.
Holder(Holder&& other) {
*this = std::move(other);
ZX_ASSERT(*vmar_);
ZX_ASSERT(*log_);
}
Holder& operator=(Holder&& other) {
std::swap(vmar_, other.vmar_);
std::swap(log_, other.log_);
std::swap(mapping_, other.mapping_);
std::swap(size_, other.size_);
ZX_ASSERT(*vmar_);
ZX_ASSERT(*log_);
return *this;
}
Holder(const zx::vmar& vmar, const zx::debuglog& log, size_t size)
: vmar_(vmar), log_(log), size_(size) {
ZX_ASSERT(*vmar_);
ZX_ASSERT(*log_);
zx::vmo vmo;
Do(zx::vmo::create(size, 0, &vmo), "allocate");
Do(vmar_->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo, 0, size, &mapping_), "map");
Do(vmo.set_property(ZX_PROP_NAME, kScratchVmoName, sizeof(kScratchVmoName) - 1), "name");
}
// zbitl::View::CopyStorageItem calls this get the scratch memory.
void* get() const { return reinterpret_cast<void*>(mapping_); }
~Holder() {
if (mapping_ != 0) {
Do(vmar_->unmap(mapping_, size_), "unmap");
}
}
private:
void Do(zx_status_t status, const char* what) {
check(*log_, status, "cannot %s %zu-byte VMO for %s", what, size_, kScratchVmoName);
printl(*log_, "OK %s %zu-byte VMO for %s", what, size_, kScratchVmoName);
}
zx::unowned_vmar vmar_;
zx::unowned_debuglog log_;
uintptr_t mapping_ = 0;
size_t size_ = 0;
};
ScratchAllocator() = delete;
ScratchAllocator(const zx::vmar& vmar_self, const zx::debuglog& log)
: vmar_(zx::unowned_vmar{vmar_self}), log_(zx::unowned_debuglog{log}) {
ZX_ASSERT(*vmar_);
ZX_ASSERT(*log_);
}
// zbitl::View::CopyStorageItem calls this to allocate scratch space.
fit::result<std::string_view, Holder> operator()(size_t size) const {
return fit::ok(Holder{*vmar_, *log_, size});
}
private:
zx::unowned_vmar vmar_;
zx::unowned_debuglog log_;
};
} // namespace
zx::vmo GetBootfsFromZbi(const zx::debuglog& log, const zx::vmar& vmar_self,
const zx::vmo& zbi_vmo) {
ZbiView zbi(zbitl::MapUnownedVmo{zx::unowned_vmo{zbi_vmo}, /*writable=*/true,
zx::unowned_vmar{vmar_self}});
for (auto it = zbi.begin(); it != zbi.end(); ++it) {
if (it->header->type == ZBI_TYPE_STORAGE_BOOTFS) {
auto result = zbi.CopyStorageItem(it, ScratchAllocator{vmar_self, log});
if (result.is_error()) {
printl(log, "cannot extract BOOTFS from ZBI: ");
FailFromZbiCopyError(result.error_value(), log);
}
zx::vmo bootfs_vmo = std::move(result).value().release();
check(log, bootfs_vmo.set_property(ZX_PROP_NAME, kBootfsVmoName, sizeof(kBootfsVmoName) - 1),
"cannot set name of uncompressed BOOTFS VMO");
// Signal that we've already processed this one.
// GCC's -Wmissing-field-initializers is buggy: it should allow
// designated initializers without all fields, but doesn't (in C++?).
zbi_header_t discard{};
discard.type = ZBI_TYPE_DISCARD;
if (auto ok = zbi.EditHeader(it, discard); ok.is_error()) {
check(log, ok.error_value(), "zx_vmo_write failed on ZBI VMO\n");
}
// Cancel error-checking since we're ending the iteration on purpose.
zbi.ignore_error();
return bootfs_vmo;
}
}
if (auto check = zbi.take_error(); check.is_error()) {
printl(log, "invalid ZBI: ");
FailFromZbiError(check.error_value(), log);
}
fail(log, "no '/boot' bootfs in bootstrap message\n");
__UNREACHABLE;
}
Options GetOptionsFromZbi(const zx::debuglog& log, const zx::vmar& vmar_self, const zx::vmo& zbi) {
ZbiView view(zbitl::MapUnownedVmo{
zx::unowned_vmo{zbi},
/*writable=*/false,
zx::unowned_vmar{vmar_self},
});
Options opts;
for (auto it = view.begin(); it != view.end(); ++it) {
auto [header, payload] = *it;
if (header->type != ZBI_TYPE_CMDLINE) {
continue;
}
// Map in and parse the CMDLINE payload. The strings referenced by `opts`
// will be owned by the mapped pages and will be valid within `vmar_self`'s
// lifetime (i.e., for the entirety of userboot's runtime).
const uint64_t previous_page_boundary = payload & -ZX_PAGE_SIZE;
const uint64_t next_page_boundary = ZX_PAGE_ALIGN(payload + header->length);
const size_t size = next_page_boundary - previous_page_boundary;
uintptr_t mapping;
if (zx_status_t status =
vmar_self.map(ZX_VM_PERM_READ, 0, zbi, previous_page_boundary, size, &mapping);
status != ZX_OK) {
fail(log, "failed to map CMDLINE item: %s", zx_status_get_string(status));
}
const uintptr_t mapped_payload = mapping + (payload % ZX_PAGE_SIZE);
std::string_view cmdline{reinterpret_cast<const char*>(mapped_payload), header->length};
printl(log, "CMDLINE %.*s\n", static_cast<int>(cmdline.size()), cmdline.data());
ParseCmdline(log, cmdline, opts);
}
if (auto result = view.take_error(); result.is_error()) {
printl(log, "invalid ZBI: ");
FailFromZbiError(result.error_value(), log);
}
return opts;
}