| // 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 <cerrno> |
| #include <fcntl.h> |
| #include <string> |
| #include <sys/stat.h> |
| |
| #include <fuchsia/hardware/skipblock/c/fidl.h> |
| #include <zircon/boot/image.h> |
| #include <zircon/process.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| |
| #include <bootdata/decompress.h> |
| #include <fbl/macros.h> |
| #include <fbl/unique_fd.h> |
| #include <fbl/vector.h> |
| #include <lib/bootfs/parser.h> |
| #include <lib/fzl/fdio.h> |
| #include <lib/fzl/vmo-mapper.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/vmar.h> |
| #include <lib/zx/vmo.h> |
| |
| #include <zbi-bootfs/zbi-bootfs.h> |
| |
| namespace zbi_bootfs { |
| |
| bool ZbiBootfsParser::IsSkipBlock(const char* path, |
| fuchsia_hardware_skipblock_PartitionInfo* partition_info) { |
| fbl::unique_fd fd(open(path, O_RDONLY)); |
| if (!fd) { |
| return false; |
| } |
| |
| fzl::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::ProcessZbi(const char* filename, Entry* entry) { |
| zbi_header_t hdr; |
| zx::vmo bootfs_vmo; |
| |
| zx_status_t status = zbi_vmo.read(&hdr, 0, sizeof(hdr)); |
| if (status != ZX_OK) { |
| fprintf(stderr, "VMO read error\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| printf("ZBI Container Header\n"); |
| printf("ZBI type = %08x\n", hdr.type); |
| printf("ZBI Magic = %08x\n", hdr.magic); |
| printf("ZBI extra = %08x\n", hdr.extra); |
| printf("ZBI Length = %u\n", hdr.length); |
| printf("ZBI Flags = %08x\n", hdr.flags); |
| |
| if ((hdr.type != ZBI_TYPE_CONTAINER) || (hdr.extra != ZBI_CONTAINER_MAGIC)) { |
| printf("ZBI item does not have a container header\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| uint32_t len = hdr.length; |
| uint32_t off = sizeof(zbi_header_t); |
| |
| while (len > sizeof(zbi_header_t)) { |
| status = zbi_vmo.read(&hdr, off, sizeof(hdr)); |
| if (status != ZX_OK) { |
| fprintf(stderr, "VMO read error\n"); |
| break; |
| } |
| printf("ZBI Payload Header\n"); |
| printf("ZBI type = %08x\n", hdr.type); |
| printf("ZBI Magic = %08x\n", hdr.magic); |
| printf("ZBI extra = %08x\n", hdr.extra); |
| printf("ZBI Length = %u\n", hdr.length); |
| printf("ZBI Flags = %08x\n", hdr.flags); |
| |
| uint32_t item_len = ZBI_ALIGN(static_cast<uint32_t>(sizeof(zbi_header_t)) + hdr.length); |
| if (item_len > len) { |
| fprintf(stderr, "ZBI item too large (%u > %u)\n", item_len, len); |
| break; |
| } |
| switch (hdr.type) { |
| case ZBI_TYPE_CONTAINER: |
| fprintf(stderr, "Unexpected ZBI container header\n"); |
| break; |
| case ZBI_TYPE_STORAGE_BOOTFS: { |
| if (hdr.flags & ZBI_FLAG_STORAGE_COMPRESSED) { |
| const char* err_msg; |
| |
| status = decompress_bootdata(zx_vmar_root_self(), zbi_vmo.get(), off, |
| hdr.length + sizeof(zbi_header_t), |
| bootfs_vmo.reset_and_get_address(), &err_msg); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Failed to decompress bootfs: %s\n", err_msg); |
| break; |
| } |
| |
| } else { |
| fprintf(stderr, |
| "Processing an uncompressed ZBI image is not currently supported\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| bootfs::Parser parser; |
| status = parser.Init(zx::unowned_vmo(bootfs_vmo)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // TODO(joeljacob): Consider making the vector a class member |
| // This will prevent unnecessarily re-reading the VMO |
| fbl::Vector<const bootfs_entry_t*> parsed_entries; |
| parser.Parse([&](const bootfs_entry_t* entry) { |
| parsed_entries.push_back(entry); |
| |
| return ZX_OK; |
| }); |
| |
| bool found = false; |
| |
| for (const auto& parsed_entry : parsed_entries) { |
| printf("Entry = %s\n ", parsed_entry->name); |
| if (!(strcmp(parsed_entry->name, filename))) { |
| 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); |
| auto buffer = std::make_unique<uint8_t[]>(parsed_entry->data_len); |
| |
| size_t data_len = parsed_entry->data_len; |
| bootfs_vmo.read(buffer.get(), parsed_entry->data_off, data_len); |
| |
| zx::vmo vmo; |
| zx::vmo::create(parsed_entry->data_len, 0, &vmo); |
| *entry = Entry { parsed_entry->data_len, std::move(vmo) }; |
| |
| entry->vmo.write(buffer.get(), 0, data_len); |
| found = true; |
| break; |
| } |
| } |
| |
| status = found ? ZX_OK : ZX_ERR_NOT_FOUND; |
| break; |
| } |
| default: |
| printf("Unknown payload type, processing will stop\n"); |
| status = ZX_ERR_NOT_SUPPORTED; |
| break; |
| } |
| off += item_len; |
| len -= item_len; |
| } |
| return status; |
| } |
| |
| zx_status_t ZbiBootfsParser::Init(const char* input, size_t byte_offset) { |
| |
| zx_status_t status = LoadZbi(input, byte_offset); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Error loading ZBI. Error code: %d\n", status); |
| } |
| return status; |
| } |
| |
| zx_status_t ZbiBootfsParser::LoadZbi(const char* input, size_t byte_offset) { |
| |
| // 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; |
| |
| // Check byte_offset validity |
| if ((byte_offset % input_bs) != 0) { |
| fprintf(stderr, "Byte Offset must be a multiple of %lu (block-size)\n", input_bs); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // 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 = static_cast<uint32_t>((byte_offset / partition_info.block_size_bytes)), |
| .block_count = block_count, |
| }; |
| |
| fzl::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(hdr)); |
| if (status != ZX_OK) { |
| fprintf(stderr, "VMO read error\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 = static_cast<uint32_t>((byte_offset / partition_info.block_size_bytes)), |
| .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)]; |
| |
| read(fd.get(), buf, sizeof(buf)); |
| 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); |
| buf_size = hdr->length + sizeof(zbi_header_t); |
| |
| if (buf_size == 0) { |
| fprintf(stderr, "Buffer size must be greater than zero\n"); |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| |
| 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(), byte_offset, SEEK_SET) != static_cast<uint32_t>(byte_offset)) { |
| fprintf(stderr, "Failed to read at offset = %zu\n", byte_offset); |
| return ZX_ERR_IO; |
| } |
| |
| // Read in input file (on disk) into buffer |
| read(fd.get(), mapping.start(), mapping.size()); |
| } |
| |
| zbi_vmo = std::move(vmo); |
| return ZX_OK; |
| } |
| } // namespace zbi_bootfs |