blob: 7b4c3eafe6de11e8aff66d8338aa084f822043cf [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/zbitl/view.h>
#include <lib/zbitl/vmo.h>
#include <zircon/boot/image.h>
#include <zircon/status.h>
#include "util.h"
namespace {
constexpr const char kBootfsVmoName[] = "uncompressed-bootfs";
constexpr const char kScratchVmoName[] = "bootfs-decompression-scratch";
// 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.
fitx::result<std::string_view, Holder> operator()(size_t size) const {
return fitx::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) {
zbitl::View<zbitl::MapUnownedVmo> zbi(
zbitl::MapUnownedVmo{zx::unowned_vmo{zbi_vmo}, 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()) {
if (result.error_value().read_error) {
fail(log, "cannot extract BOOTFS from ZBI: %.*s: read error: %s\n",
static_cast<int>(result.error_value().zbi_error.size()),
result.error_value().zbi_error.data(),
zx_status_get_string(*result.error_value().read_error));
} else if (result.error_value().write_error) {
fail(log, "cannot extract BOOTFS from ZBI: %.*s: write error: %s\n",
static_cast<int>(result.error_value().zbi_error.size()),
result.error_value().zbi_error.data(),
zx_status_get_string(*result.error_value().write_error));
} else {
fail(log, "cannot extract BOOTFS from ZBI: %.*s\n",
static_cast<int>(result.error_value().zbi_error.size()),
result.error_value().zbi_error.data());
}
}
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()) {
auto error = check.error_value();
if (error.storage_error) {
fail(log, "invalid ZBI: %.*s at offset %#x\n", static_cast<int>(error.zbi_error.size()),
error.zbi_error.data(), error.item_offset);
} else {
fail(log, "invalid ZBI: %.*s at offset %#x: %s\n", static_cast<int>(error.zbi_error.size()),
error.zbi_error.data(), error.item_offset, zx_status_get_string(*error.storage_error));
}
} else {
fail(log, "no '/boot' bootfs in bootstrap message\n");
}
}