blob: 9fb661bef47d38b5314c4d3e56a8b3a991ebef61 [file] [log] [blame]
// Copyright 2019 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 <fcntl.h>
#include <fuchsia/hardware/skipblock/c/fidl.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/zx/channel.h>
#include <lib/zx/vmar.h>
#include <lib/zx/vmo.h>
#include <string.h>
#include <sys/stat.h>
#include <zircon/boot/bootfs.h>
#include <zircon/boot/image.h>
#include <zircon/errors.h>
#include <zircon/process.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>
#include <fbl/macros.h>
#include <fbl/unique_fd.h>
#include <fbl/vector.h>
#include <lz4/lz4frame.h>
#include <zbi-bootfs/zbi-bootfs.h>
#include <zstd/zstd.h>
#include "src/lib/bootfs/parser.h"
namespace zbi_bootfs {
const uint32_t kMaxDecompressedZbiSize = (1 << 30); // 1 GiB
namespace {
zx_status_t FindEntry(zx::unowned_vmo vmo, const char* filename, zbi_bootfs_dirent_t* entry) {
bootfs::Parser parser;
zx_status_t status = parser.Init(std::move(vmo));
if (status != ZX_OK) {
fprintf(stderr, "Failed to init bootfs::Parser: %s\n", zx_status_get_string(status));
return status;
}
// TODO(joeljacob): Consider making the vector a class member
// This will prevent unnecessarily re-reading the VMO
fbl::Vector<const zbi_bootfs_dirent_t*> parsed_entries;
parser.Parse([&](const zbi_bootfs_dirent_t* entry) {
parsed_entries.push_back(entry);
return ZX_OK;
});
for (const auto& parsed_entry : parsed_entries) {
printf("Entry = %s\n ", parsed_entry->name);
// This is not the entry we are looking for.
if (strcmp(parsed_entry->name, filename) != 0) {
continue;
}
printf("Filename = %s\n ", parsed_entry->name);
printf("File name length = %d\n", parsed_entry->name_len);
printf("File data length = %d\n", parsed_entry->data_len);
printf("File data offset = %d\n", parsed_entry->data_off);
memcpy(entry, parsed_entry, sizeof(zbi_bootfs_dirent_t));
return ZX_OK;
}
return ZX_ERR_NOT_FOUND;
}
} // namespace
bool ZbiBootfsParser::IsSkipBlock(const char* path,
fuchsia_hardware_skipblock_PartitionInfo* partition_info) {
fbl::unique_fd fd(open(path, O_RDONLY));
if (!fd) {
return false;
}
fdio_cpp::FdioCaller caller(std::move(fd));
// |status| is used for the status of the whole FIDL request. We expect
// |status| to be ZX_OK if the channel connects to a skip-block driver.
// |op_status| refers to the status of the underlying read/write operation
// and will be ZX_OK only if the read/write succeeds. It is NOT set if
// the channel is not connected to a skip-block driver.
zx_status_t op_status;
zx_status_t status = fuchsia_hardware_skipblock_SkipBlockGetPartitionInfo(
caller.borrow_channel(), &op_status, partition_info);
return status == ZX_OK;
}
zx_status_t ZbiBootfsParser::FindBootZbi(uint32_t* read_offset, zbi_header_t* header) {
zbi_header_t container_header;
zx_status_t status = zbi_vmo_.read(&container_header, 0, sizeof(zbi_header_t));
if (status != ZX_OK) {
fprintf(stderr, "Failed to read ZBI header from vmo.\n");
return ZX_ERR_BAD_STATE;
}
printf("ZBI Container Header\n");
printf("ZBI Type = %08x\n", container_header.type);
printf("ZBI Magic = %08x\n", container_header.magic);
printf("ZBI Extra = %08x\n", container_header.extra);
printf("ZBI Length = %08x (%u)\n", container_header.length, container_header.length);
printf("ZBI Flags = %08x\n", container_header.flags);
if ((container_header.type != ZBI_TYPE_CONTAINER) ||
(container_header.extra != ZBI_CONTAINER_MAGIC)) {
printf("ZBI item does not have a container header\n");
return ZX_ERR_BAD_STATE;
}
uint64_t bytes_to_read = container_header.length;
uint64_t current_offset = sizeof(zbi_header_t);
zbi_header_t item_header;
while (bytes_to_read > 0) {
status = zbi_vmo_.read(&item_header, current_offset, sizeof(zbi_header_t));
if (status != ZX_OK) {
fprintf(stderr, "Failed to read ZBI header from vmo.\n");
return status;
}
printf("ZBI Payload Header\n");
printf("ZBI Type = %08x\n", item_header.type);
printf("ZBI Magic = %08x\n", item_header.magic);
printf("ZBI Extra = %08x\n", item_header.extra);
printf("ZBI Length = %08x (%u)\n", item_header.length, item_header.length);
printf("ZBI Flags = %08x\n", item_header.flags);
uint64_t item_len = static_cast<uint64_t>(sizeof(zbi_header_t)) + item_header.length;
// ZBI_ALIGN(uint32_t::max()) = 0 so the last ZBI_ALIGNMENT is excluded
if (item_len > std::numeric_limits<uint32_t>::max() - ZBI_ALIGNMENT) {
fprintf(stderr, "ZBI item exceeds uint32_t capacity\n");
return ZX_ERR_INVALID_ARGS;
}
item_len = ZBI_ALIGN(item_len);
if (item_len > bytes_to_read) {
fprintf(stderr, "ZBI item too large (%lu > %lu)\n", item_len, bytes_to_read);
return ZX_ERR_BAD_STATE;
}
switch (item_header.type) {
case ZBI_TYPE_CONTAINER:
fprintf(stderr, "Unexpected ZBI container header\n");
status = ZX_ERR_INVALID_ARGS;
break;
case ZBI_TYPE_STORAGE_BOOTFS: {
if (!(item_header.flags & ZBI_FLAG_STORAGE_COMPRESSED)) {
fprintf(stderr, "Processing an uncompressed ZBI image is not currently supported\n");
return ZX_ERR_NOT_SUPPORTED;
}
*read_offset = current_offset;
memcpy(header, &item_header, sizeof(zbi_header_t));
return ZX_OK;
}
default:
printf("Unknown payload type, processing will stop\n");
status = ZX_ERR_NOT_SUPPORTED;
break;
}
current_offset += item_len;
bytes_to_read -= item_len;
}
return status;
}
__EXPORT zx_status_t ZbiBootfsParser::ProcessZbi(const char* filename, Entry* entry) {
uint32_t read_offset;
zbi_header_t boot_header;
zx_status_t status = FindBootZbi(&read_offset, &boot_header);
if (status != ZX_OK) {
return status;
}
zx::vmo boot_vmo;
uint32_t decompressed_size = boot_header.extra;
if (decompressed_size > kMaxDecompressedZbiSize) {
fprintf(stderr, "ZBI Decompressed size too large: %u > %u\n", decompressed_size,
kMaxDecompressedZbiSize);
return ZX_ERR_FILE_BIG;
}
status = zx::vmo::create(decompressed_size, 0, &boot_vmo);
if (status != ZX_OK) {
fprintf(stderr, "Failed to create boot vmo: %s\n", zx_status_get_string(status));
return status;
}
status = Decompress(zbi_vmo_, read_offset + sizeof(zbi_header_t), boot_header.length, boot_vmo, 0,
decompressed_size);
if (status != ZX_OK) {
fprintf(stderr, "Failed to decompress bootfs: %s\n", zx_status_get_string(status));
return status;
}
zbi_bootfs_dirent_t parsed_entry;
status = FindEntry(zx::unowned_vmo(boot_vmo), filename, &parsed_entry);
if (status != ZX_OK) {
return status;
}
size_t data_len = parsed_entry.data_len;
auto buffer = std::make_unique<uint8_t[]>(data_len);
boot_vmo.read(buffer.get(), parsed_entry.data_off, data_len);
zx::vmo vmo;
zx::vmo::create(data_len, 0, &vmo);
*entry = Entry{data_len, std::move(vmo)};
entry->vmo.write(buffer.get(), 0, data_len);
return ZX_OK;
}
__EXPORT zx_status_t ZbiBootfsParser::Init(const char* input) {
zx_status_t status = LoadZbi(input);
if (status != ZX_OK) {
fprintf(stderr, "Error loading ZBI. Error code: %s\n", zx_status_get_string(status));
}
return status;
}
__EXPORT zx_status_t ZbiBootfsParser::LoadZbi(const char* input) {
// Logic for skip-block devices.
fuchsia_hardware_skipblock_PartitionInfo partition_info = {};
zx::vmo vmo;
fzl::VmoMapper mapping;
size_t buf_size = 0;
size_t input_bs = 0;
fbl::unique_fd fd(open(input, O_RDONLY));
if (!fd) {
fprintf(stderr, "Couldn't open input file %s : %d\n", input, errno);
return ZX_ERR_IO;
}
if ((IsSkipBlock(input, &partition_info))) {
// Grab Block size for the partition we'd like to access
input_bs = partition_info.block_size_bytes;
// Set buffer size
buf_size = partition_info.block_size_bytes;
if (buf_size == 0) {
fprintf(stderr, "Buffer size must be greater than zero\n");
return ZX_ERR_BUFFER_TOO_SMALL;
}
zx_status_t status = zx::vmo::create(buf_size, ZX_VMO_RESIZABLE, &vmo);
if (status != ZX_OK) {
fprintf(stderr, "Error creating VMO\n");
return status;
}
zx::vmo dup;
status = vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup);
if (status != ZX_OK) {
fprintf(stderr, "Cannot duplicate handle\n");
return status;
}
const uint32_t block_count = static_cast<uint32_t>(input_bs / partition_info.block_size_bytes);
fuchsia_hardware_skipblock_ReadWriteOperation op = {
.vmo = dup.release(),
.vmo_offset = 0,
.block = 0,
.block_count = block_count,
};
fdio_cpp::FdioCaller caller(std::move(fd));
fuchsia_hardware_skipblock_SkipBlockRead(caller.borrow_channel(), &op, &status);
if (status != ZX_OK) {
fprintf(stderr, "Failed to read skip-block partition. Error code: %d\n", status);
return status;
}
// Check ZBI header for content length and set buffer size
// accordingly
zbi_header_t hdr;
status = vmo.read(&hdr, 0, sizeof(zbi_header_t));
if (status != ZX_OK) {
fprintf(stderr, "Failed to read ZBI header from vmo.\n");
return status;
}
printf("ZBI container type = %08x\n", hdr.type);
printf("ZBI payload length = %u\n", hdr.length);
// Check if ZBI contents are larger than the size of one block
// Resize the VMO accordingly
if ((hdr.length + sizeof(zbi_header_t)) > buf_size) {
vmo.set_size(buf_size + (hdr.length + sizeof(zbi_header_t)));
uint64_t vmo_size;
status = vmo.get_size(&vmo_size);
if (status != ZX_OK || vmo_size == 0) {
printf("Error resizing VMO\n");
return status;
}
zx::vmo dup;
status = vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup);
if (status != ZX_OK) {
fprintf(stderr, "Cannot duplicate handle\n");
return status;
}
const uint32_t block_count =
static_cast<uint32_t>(input_bs / partition_info.block_size_bytes);
fuchsia_hardware_skipblock_ReadWriteOperation op = {
.vmo = dup.release(),
.vmo_offset = 0,
.block = 0,
.block_count = block_count,
};
zx_status_t status;
fuchsia_hardware_skipblock_SkipBlockRead(caller.borrow_channel(), &op, &status);
if (status != ZX_OK) {
fprintf(stderr, "Failed to read skip-block partition. Error code: %d\n", status);
return status;
}
}
} else {
// Check ZBI header for content length and set buffer size
// accordingly
char buf[sizeof(zbi_header_t)];
if (read(fd.get(), buf, sizeof(zbi_header_t)) != sizeof(zbi_header_t)) {
fprintf(stderr, "Failed to read header from zbi.\n");
return ZX_ERR_IO;
}
zbi_header_t* hdr = reinterpret_cast<zbi_header_t*>(&buf);
printf("ZBI container type = %08x\n", hdr->type);
printf("ZBI payload length = %u\n", hdr->length);
if (hdr->length == 0) {
fprintf(stderr, "Payload length must be greater than zero\n");
return ZX_ERR_BUFFER_TOO_SMALL;
}
buf_size = hdr->length + sizeof(zbi_header_t);
zx_status_t status = mapping.CreateAndMap(buf_size, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr,
&vmo, ZX_RIGHT_SAME_RIGHTS, 0);
if (status != ZX_OK) {
fprintf(stderr, "Error creating and mapping VMO\n");
return status;
}
if (lseek(fd.get(), 0, SEEK_SET) != 0) {
fprintf(stderr, "Failed to reset to beginning of fd\n");
return ZX_ERR_IO;
}
// Read in input file (on disk) into buffer
if (read(fd.get(), mapping.start(), mapping.size()) != static_cast<ssize_t>(mapping.size())) {
fprintf(stderr, "Failed to read input file into buffer\n");
return ZX_ERR_IO;
}
}
zbi_vmo_ = std::move(vmo);
return ZX_OK;
}
static zx_status_t DecompressZstd(zx::vmo& input, uint64_t input_offset, size_t input_size,
zx::vmo& output, uint64_t output_offset, size_t output_size) {
auto input_buffer = std::make_unique<std::byte[]>(input_size);
zx_status_t status = input.read(input_buffer.get(), input_offset, input_size);
if (status != ZX_OK) {
return status;
}
auto output_buffer = std::make_unique<std::byte[]>(output_size);
auto rc = ZSTD_decompress(output_buffer.get(), output_size, input_buffer.get(), input_size);
if (ZSTD_isError(rc) || rc != output_size) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
status = output.write(output_buffer.get(), output_offset, output_size);
if (status != ZX_OK) {
return status;
}
return ZX_OK;
}
static zx_status_t DecompressLz4f(zx::vmo& input, uint64_t input_offset, size_t input_size,
zx::vmo& output, uint64_t output_offset, size_t output_size) {
auto input_buffer = std::make_unique<std::byte[]>(input_size);
zx_status_t status = input.read(input_buffer.get(), input_offset, input_size);
if (status != ZX_OK) {
return status;
}
auto output_buffer = std::make_unique<std::byte[]>(output_size);
LZ4F_decompressionContext_t ctx;
LZ4F_errorCode_t result = LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION);
if (LZ4F_isError(result)) {
return ZX_ERR_INTERNAL;
}
// Calls freeDecompressionContext when cleanup goes out of scope
auto cleanup = fbl::MakeAutoCall([&]() { LZ4F_freeDecompressionContext(ctx); });
std::byte* dst = output_buffer.get();
size_t dst_size = output_size;
auto src = input_buffer.get();
size_t src_size = input_size;
do {
if (dst_size == 0) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
size_t nwritten = dst_size, nread = src_size;
static constexpr const LZ4F_decompressOptions_t kDecompressOpt{};
result = LZ4F_decompress(ctx, dst, &nwritten, src, &nread, &kDecompressOpt);
if (LZ4F_isError(result)) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
if (nread > src_size) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
src += nread;
src_size -= nread;
if (nwritten > dst_size) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
dst += nwritten;
dst_size -= nwritten;
} while (src_size > 0);
if (dst_size > 0) {
return ZX_ERR_IO_DATA_INTEGRITY;
}
status = output.write(output_buffer.get(), output_offset, output_size);
if (status != ZX_OK) {
return status;
}
return ZX_OK;
}
static constexpr uint32_t kLz4fMagic = 0x184D2204;
static constexpr uint32_t kZstdMagic = 0xFD2FB528;
zx_status_t Decompress(zx::vmo& input, uint64_t input_offset, size_t input_size, zx::vmo& output,
uint64_t output_offset, size_t output_size) {
uint32_t magic;
zx_status_t status = input.read(&magic, input_offset, sizeof(magic));
if (status != ZX_OK) {
return status;
}
if (magic == kLz4fMagic) {
return DecompressLz4f(input, input_offset, input_size, output, output_offset, output_size);
}
if (magic == kZstdMagic) {
return DecompressZstd(input, input_offset, input_size, output, output_offset, output_size);
}
return ZX_ERR_NOT_SUPPORTED;
}
} // namespace zbi_bootfs