| // 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 "src/storage/fvm/fvm_check.h" |
| |
| #include <inttypes.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <zircon/status.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include <fbl/array.h> |
| #include <fbl/unique_fd.h> |
| #include <fbl/vector.h> |
| #include <gpt/guid.h> |
| |
| #include "src/storage/fvm/format.h" |
| #include "src/storage/fvm/fvm.h" |
| |
| namespace fvm { |
| |
| Checker::Checker() = default; |
| |
| Checker::Checker(fbl::unique_fd fd, uint32_t block_size, bool silent) |
| : fd_(std::move(fd)), block_size_(block_size), logger_(silent) {} |
| |
| Checker::~Checker() = default; |
| |
| bool Checker::Validate() const { |
| if (!ValidateOptions()) { |
| return false; |
| } |
| |
| FvmInfo info; |
| if (!LoadFVM(&info)) { |
| return false; |
| } |
| |
| return CheckFVM(info); |
| } |
| |
| bool Checker::ValidateOptions() const { |
| if (!fd_) { |
| logger_.Error("FVM checker missing a device\n"); |
| return false; |
| } |
| if (block_size_ == 0) { |
| logger_.Error("Invalid block size\n"); |
| return false; |
| } |
| return true; |
| } |
| |
| bool Checker::LoadFVM(FvmInfo* out) const { |
| const off_t device_size = lseek(fd_.get(), 0, SEEK_END); |
| if (device_size < 0) { |
| logger_.Error("Unable to get file length\n"); |
| return false; |
| } |
| if (device_size % block_size_ != 0) { |
| logger_.Error("File size is not divisible by block size\n"); |
| return false; |
| } |
| const size_t block_count = device_size / block_size_; |
| |
| std::unique_ptr<uint8_t[]> header(new uint8_t[fvm::kBlockSize]); |
| if (pread(fd_.get(), header.get(), fvm::kBlockSize, 0) != static_cast<ssize_t>(fvm::kBlockSize)) { |
| logger_.Error("Could not read header\n"); |
| return false; |
| } |
| const fvm::Header* superblock = reinterpret_cast<fvm::Header*>(header.get()); |
| if (superblock->slice_size % block_size_ != 0) { |
| logger_.Error("Slice size not divisible by block size\n"); |
| return false; |
| } else if (superblock->slice_size == 0) { |
| logger_.Error("Slice size cannot be zero\n"); |
| return false; |
| } |
| |
| // Validate sizes to prevent allocating overlarge buffers for the metadata. Check the table |
| // sizes separately to prevent numeric overflow when combining them. |
| if (superblock->GetAllocationTableAllocatedByteSize() > fvm::kMaxAllocationTableByteSize) { |
| logger_.Error("Slice allocation table is too large."); |
| return false; |
| } |
| if (superblock->GetPartitionTableByteSize() > fvm::kMaxPartitionTableByteSize) { |
| logger_.Error("FVM header partition table is too large."); |
| return false; |
| } |
| |
| size_t metadata_allocated_bytes = superblock->GetMetadataAllocatedBytes(); |
| if (metadata_allocated_bytes > fvm::kMaxMetadataByteSize) { |
| logger_.Error("FVM metadata size exceeds maximum limit."); |
| return false; |
| } |
| |
| // The metadata buffer holds both primary and secondary copies of the metadata. |
| size_t metadata_buffer_size = metadata_allocated_bytes * 2; |
| std::unique_ptr<uint8_t[]> metadata(new uint8_t[metadata_buffer_size]); |
| if (pread(fd_.get(), metadata.get(), metadata_buffer_size, 0) != |
| static_cast<ssize_t>(metadata_buffer_size)) { |
| logger_.Error("Could not read metadata\n"); |
| return false; |
| } |
| |
| std::optional<fvm::SuperblockType> use_superblock = fvm::PickValidHeader( |
| metadata.get(), metadata.get() + metadata_allocated_bytes, metadata_allocated_bytes); |
| if (!use_superblock) { |
| logger_.Error("Invalid FVM metadata\n"); |
| return false; |
| } |
| |
| fvm::SuperblockType invalid_superblock = *use_superblock == fvm::SuperblockType::kPrimary |
| ? fvm::SuperblockType::kSecondary |
| : fvm::SuperblockType::kPrimary; |
| |
| const uint8_t* valid_metadata = metadata.get() + superblock->GetSuperblockOffset(*use_superblock); |
| const uint8_t* invalid_metadata = |
| metadata.get() + superblock->GetSuperblockOffset(invalid_superblock); |
| |
| FvmInfo info = { |
| fbl::Array<uint8_t>(metadata.release(), superblock->GetMetadataAllocatedBytes() * 2), |
| superblock->GetSuperblockOffset(*use_superblock), |
| valid_metadata, |
| invalid_metadata, |
| block_size_, |
| block_count, |
| static_cast<size_t>(device_size), |
| superblock->slice_size, |
| }; |
| |
| *out = std::move(info); |
| return true; |
| } |
| |
| bool Checker::LoadPartitions(const size_t slice_count, const fvm::SliceEntry* slice_table, |
| const fvm::VPartitionEntry* vpart_table, |
| fbl::Vector<Slice>* out_slices, |
| fbl::Array<Partition>* out_partitions) const { |
| fbl::Vector<Slice> slices; |
| fbl::Array<Partition> partitions(new Partition[fvm::kMaxVPartitions], fvm::kMaxVPartitions); |
| |
| bool valid = true; |
| |
| // Initialize all allocated partitions. |
| for (size_t i = 1; i < fvm::kMaxVPartitions; i++) { |
| const uint32_t slices = vpart_table[i].slices; |
| if (slices != 0) { |
| partitions[i].entry = &vpart_table[i]; |
| } |
| } |
| |
| // Initialize all slices, ensure they are used for allocated partitions. |
| for (size_t i = 1; i <= slice_count; i++) { |
| if (slice_table[i].IsAllocated()) { |
| const uint64_t vpart = slice_table[i].VPartition(); |
| if (vpart >= kMaxVPartitions) { |
| logger_.Error("Invalid vslice entry; claims vpart which is out of range.\n"); |
| valid = false; |
| } else if (!partitions[vpart].entry || partitions[vpart].entry->IsFree()) { |
| logger_.Error("Invalid slice entry; claims that it is allocated to unallocated "); |
| logger_.Error("partition %zu\n", vpart); |
| valid = false; |
| } |
| |
| Slice slice = {vpart, slice_table[i].VSlice(), i}; |
| |
| slices.push_back(slice); |
| partitions[vpart].slices.push_back(std::move(slice)); |
| } |
| } |
| |
| // Validate that all allocated partitions are correct about the number of slices used. |
| for (size_t i = 1; i < fvm::kMaxVPartitions; i++) { |
| if (partitions[i].Allocated()) { |
| const size_t claimed = partitions[i].entry->slices; |
| const size_t actual = partitions[i].slices.size(); |
| if (claimed != actual) { |
| logger_.Error("Disagreement about allocated slice count: "); |
| logger_.Error("Partition %zu claims %zu slices, has %zu\n", i, claimed, actual); |
| valid = false; |
| } |
| } |
| } |
| |
| *out_slices = std::move(slices); |
| *out_partitions = std::move(partitions); |
| return valid; |
| } |
| |
| void Checker::DumpSlices(const fbl::Vector<Slice>& slices) const { |
| logger_.Log("[ Slice Info ]\n"); |
| Slice* run_start = nullptr; |
| size_t run_length = 0; |
| |
| // Prints whatever information we can from the current contiguous range of |
| // virtual / physical slices, then reset the "run" information. |
| // |
| // A run is a contiguous set of virtual / physical slices, all allocated to the same |
| // virtual partition. Noncontiguity in either the virtual or physical range |
| // "breaks" the run, since these cases provide new information. |
| auto start_run = [&run_start, &run_length](Slice* slice) { |
| run_start = slice; |
| run_length = 1; |
| }; |
| auto end_run = [this, &run_start, &run_length]() { |
| if (run_length == 1) { |
| logger_.Log("Physical Slice %zu allocated\n", run_start->physical_slice); |
| logger_.Log(" Allocated as virtual slice %zu\n", run_start->virtual_slice); |
| logger_.Log(" Allocated to partition %zu\n", run_start->virtual_partition); |
| } else if (run_length > 1) { |
| logger_.Log("%zu Physical Slices [%" PRIu64 ", %" PRIu64 "] allocated\n", run_length, |
| run_start->physical_slice, run_start->physical_slice + run_length - 1); |
| logger_.Log(" Allocated as virtual slices [%zu, %zu]\n", run_start->virtual_slice, |
| run_start->virtual_slice + run_length - 1); |
| logger_.Log(" Allocated to partition %zu\n", run_start->virtual_partition); |
| } |
| run_start = nullptr; |
| run_length = 0; |
| }; |
| |
| if (!slices.is_empty()) { |
| start_run(&slices[0]); |
| } |
| for (size_t i = 1; i < slices.size(); i++) { |
| const auto& slice = slices[i]; |
| const size_t expected_pslice = run_start->physical_slice + run_length; |
| const size_t expected_vslice = run_start->virtual_slice + run_length; |
| if (slice.physical_slice == expected_pslice && slice.virtual_slice == expected_vslice && |
| slice.virtual_partition == run_start->virtual_partition) { |
| run_length++; |
| } else { |
| end_run(); |
| start_run(&slices[i]); |
| } |
| } |
| end_run(); |
| } |
| |
| bool Checker::CheckFVM(const FvmInfo& info) const { |
| auto superblock = reinterpret_cast<const fvm::Header*>(info.valid_metadata); |
| auto invalid_superblock = reinterpret_cast<const fvm::Header*>(info.invalid_metadata); |
| |
| logger_.Log("[ FVM Info ]\n"); |
| logger_.Log("Format version: %" PRIu64 "\n", superblock->format_version); |
| logger_.Log("Oldest revision: %" PRIu64 "\n", superblock->oldest_revision); |
| logger_.Log("Generation number: %" PRIu64 "\n", superblock->generation); |
| logger_.Log("Generation number: %" PRIu64 " (invalid copy)\n", invalid_superblock->generation); |
| logger_.Log("\n"); |
| |
| const size_t slice_count = superblock->GetAllocationTableUsedEntryCount(); |
| logger_.Log("[ Size Info ]\n"); |
| logger_.Log("%-15s %10zu\n", "Device Length:", info.device_size); |
| logger_.Log("%-15s %10zu\n", "Block size:", info.block_size); |
| logger_.Log("%-15s %10zu\n", "Slice size:", info.slice_size); |
| logger_.Log("%-15s %10zu\n", "Slice count:", slice_count); |
| logger_.Log("\n"); |
| |
| const size_t metadata_size = superblock->GetMetadataAllocatedBytes(); |
| const size_t metadata_count = 2; |
| const size_t metadata_end = metadata_size * metadata_count; |
| logger_.Log("[ Metadata ]\n"); |
| logger_.Log("%-25s 0x%016zx\n", "Valid metadata start:", info.valid_metadata_offset); |
| logger_.Log("%-25s 0x%016x\n", "Metadata start:", 0); |
| logger_.Log("%-25s %16zu (for each copy)\n", "Metadata size:", metadata_size); |
| logger_.Log("%-25s %16zu\n", "Metadata count:", metadata_count); |
| logger_.Log("%-25s 0x%016zx\n", "Metadata end:", metadata_end); |
| logger_.Log("\n"); |
| |
| logger_.Log("[ All Subsequent Offsets Relative to Valid Metadata Start ]\n"); |
| logger_.Log("\n"); |
| |
| const size_t vpart_table_start = superblock->GetPartitionTableOffset(); |
| const size_t vpart_entry_size = sizeof(fvm::VPartitionEntry); |
| const size_t vpart_table_size = superblock->GetPartitionTableByteSize(); |
| const size_t vpart_table_end = vpart_table_start + vpart_table_size; |
| logger_.Log("[ Virtual Partition Table ]\n"); |
| logger_.Log("%-25s 0x%016zx\n", "VPartition Entry Start:", vpart_table_start); |
| logger_.Log("%-25s %16zu\n", "VPartition entry size:", vpart_entry_size); |
| logger_.Log("%-25s %16zu\n", "VPartition table size:", vpart_table_size); |
| logger_.Log("%-25s 0x%016zx\n", "VPartition table end:", vpart_table_end); |
| logger_.Log("\n"); |
| |
| const size_t slice_table_start = superblock->GetAllocationTableOffset(); |
| const size_t slice_entry_size = sizeof(fvm::SliceEntry); |
| const size_t slice_table_size = superblock->GetAllocationTableUsedByteSize(); |
| const size_t slice_table_end = slice_table_start + slice_table_size; |
| logger_.Log("[ Slice Allocation Table ]\n"); |
| logger_.Log("%-25s 0x%016zx\n", "Slice table start:", slice_table_start); |
| logger_.Log("%-25s %16zu\n", "Slice entry size:", slice_entry_size); |
| logger_.Log("%-25s %16zu\n", "Slice table size:", slice_table_size); |
| logger_.Log("%-25s 0x%016zx\n", "Slice table end:", slice_table_end); |
| logger_.Log("\n"); |
| |
| const fvm::SliceEntry* slice_table = |
| reinterpret_cast<const fvm::SliceEntry*>(info.valid_metadata + slice_table_start); |
| const fvm::VPartitionEntry* vpart_table = |
| reinterpret_cast<const fvm::VPartitionEntry*>(info.valid_metadata + vpart_table_start); |
| |
| fbl::Vector<Slice> slices; |
| fbl::Array<Partition> partitions; |
| bool valid = true; |
| if (!LoadPartitions(slice_count, slice_table, vpart_table, &slices, &partitions)) { |
| valid = false; |
| logger_.Log("Partitions invalid; displaying info anyway...\n"); |
| } |
| |
| logger_.Log("[ Partition Info ]\n"); |
| for (size_t i = 1; i < fvm::kMaxVPartitions; i++) { |
| const uint32_t slices = vpart_table[i].slices; |
| if (slices != 0) { |
| logger_.Log("Partition %zu allocated\n", i); |
| logger_.Log(" Has %u slices allocated\n", slices); |
| logger_.Log(" Type: %s\n", gpt::KnownGuid::TypeDescription(vpart_table[i].type).c_str()); |
| logger_.Log(" Name: %s\n", vpart_table[i].name().c_str()); |
| } |
| } |
| logger_.Log("\n"); |
| |
| DumpSlices(slices); |
| return valid; |
| } |
| |
| } // namespace fvm |