| // Copyright 2021 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 <lib/fdio/spawn.h> |
| #include <lib/zx/process.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <zircon/device/block.h> |
| |
| #include <algorithm> |
| #include <climits> |
| #include <cstdint> |
| #include <cstdio> |
| #include <cstdlib> |
| #include <iterator> |
| #include <memory> |
| #include <string> |
| |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/lib/storage/fs_management/cpp/fvm.h" |
| #include "src/storage/extractor/c/extractor.h" |
| #include "src/storage/extractor/cpp/extractor.h" |
| #include "src/storage/fvm/format.h" |
| #include "src/storage/lib/utils/topological_path.h" |
| #include "src/storage/testing/fvm.h" |
| #include "src/storage/testing/ram_disk.h" |
| |
| namespace extractor { |
| namespace { |
| |
| constexpr uint32_t kBlockCount = 1024 * 256; |
| constexpr uint32_t kBlockSize = 8192; |
| constexpr uint32_t kSliceSize = 32'768; |
| constexpr uint64_t kExtractedImageBlockCount = 2; |
| |
| void CreateInputAndOutputStream(storage::RamDisk& ramdisk, fbl::unique_fd& input, |
| fbl::unique_fd& output) { |
| input.reset(open(ramdisk.path().c_str(), O_RDWR)); |
| |
| ASSERT_EQ(fs_management::FvmInitPreallocated(input.get(), kBlockCount * kBlockSize, |
| kBlockCount * kBlockSize, kSliceSize), |
| ZX_OK); |
| ASSERT_TRUE(input); |
| char out_path[] = "/tmp/fvm-extraction.XXXXXX"; |
| output.reset(mkostemp(out_path, O_RDWR | O_CREAT | O_EXCL)); |
| ASSERT_TRUE(output); |
| } |
| |
| void Extract(const fbl::unique_fd& input_fd, const fbl::unique_fd& output_fd, bool corrupt) { |
| ExtractorOptions options = ExtractorOptions{.force_dump_pii = false, |
| .add_checksum = false, |
| .alignment = fvm::kBlockSize, |
| .compress = false}; |
| auto extractor_status = Extractor::Create(input_fd.duplicate(), options, output_fd.duplicate()); |
| ASSERT_EQ(extractor_status.status_value(), ZX_OK); |
| auto extractor = std::move(extractor_status.value()); |
| auto status = FvmExtract(input_fd.duplicate(), *extractor); |
| if (!corrupt) { |
| ASSERT_TRUE(status.is_ok()); |
| } |
| ASSERT_TRUE(extractor->Write().is_ok()); |
| } |
| |
| void VerifyInputSuperblock(const fbl::unique_fd& input_fd, fvm::Header* info) { |
| // First copy |
| char buffer[fvm::kBlockSize]; |
| ASSERT_EQ((ssize_t)fvm::kBlockSize, pread(input_fd.get(), buffer, fvm::kBlockSize, 0)); |
| memcpy(info, buffer, sizeof(*info)); |
| ASSERT_EQ(info->magic, fvm::kMagic); |
| |
| // Second copy |
| char second_buffer[fvm::kBlockSize]; |
| fvm::Header secondary_superblock; |
| ASSERT_EQ((ssize_t)fvm::kBlockSize, |
| pread(input_fd.get(), second_buffer, fvm::kBlockSize, |
| info->GetSuperblockOffset(fvm::SuperblockType::kSecondary))); |
| memcpy(&secondary_superblock, second_buffer, sizeof(*info)); |
| ASSERT_EQ(secondary_superblock.magic, fvm::kMagic); |
| } |
| |
| void VerifyOutputSuperblock(const fvm::Header& info, const fbl::unique_fd& output_fd) { |
| ssize_t superblock_offset = kExtractedImageBlockCount * fvm::kBlockSize; |
| char read_buffer[fvm::kBlockSize]; |
| ASSERT_EQ(pread(output_fd.get(), read_buffer, fvm::kBlockSize, superblock_offset), |
| static_cast<ssize_t>(fvm::kBlockSize)); |
| char info_buffer[fvm::kBlockSize]; |
| memcpy(info_buffer, &info, sizeof(info)); |
| ASSERT_EQ(memcmp(info_buffer, read_buffer, sizeof(info)), 0); |
| } |
| |
| TEST(FvmExtractorTest, TestSuperblock) { |
| fbl::unique_fd input_fd; |
| fbl::unique_fd output_fd; |
| auto ramdisk_or = storage::RamDisk::Create(kBlockSize, kBlockCount); |
| ASSERT_EQ(ramdisk_or.status_value(), ZX_OK); |
| |
| CreateInputAndOutputStream(ramdisk_or.value(), input_fd, output_fd); |
| Extract(input_fd, output_fd, /*corrupt=*/false); |
| |
| fvm::Header info; |
| VerifyInputSuperblock(input_fd, &info); |
| |
| struct stat stats; |
| ASSERT_EQ(fstat(output_fd.get(), &stats), 0); |
| VerifyOutputSuperblock(info, output_fd); |
| } |
| |
| TEST(FvmExtractorTest, TestCorruptedSuperblock) { |
| fbl::unique_fd input_fd; |
| fbl::unique_fd output_fd; |
| auto ramdisk_or = storage::RamDisk::Create(kBlockSize, kBlockCount); |
| ASSERT_EQ(ramdisk_or.status_value(), ZX_OK); |
| |
| CreateInputAndOutputStream(ramdisk_or.value(), input_fd, output_fd); |
| char corrupt_block[fvm::kBlockSize] = {'C'}; |
| ssize_t r = pwrite(input_fd.get(), corrupt_block, sizeof(corrupt_block), 0); |
| ASSERT_EQ(r, (ssize_t)sizeof(corrupt_block)); |
| Extract(input_fd, output_fd, /*corrupt=*/true); |
| |
| std::unique_ptr<char[]> superblock(new char[fvm::kBlockSize]); |
| ASSERT_EQ(pread(input_fd.get(), superblock.get(), fvm::kBlockSize, 0), (ssize_t)fvm::kBlockSize); |
| std::unique_ptr<char[]> read_buffer_sb(new char[fvm::kBlockSize]); |
| ASSERT_EQ(pread(output_fd.get(), read_buffer_sb.get(), fvm::kBlockSize, |
| kExtractedImageBlockCount * fvm::kBlockSize), |
| (ssize_t)fvm::kBlockSize); |
| ASSERT_EQ(memcmp(superblock.get(), corrupt_block, fvm::kBlockSize), 0); |
| ASSERT_EQ(memcmp(read_buffer_sb.get(), corrupt_block, fvm::kBlockSize), 0); |
| } |
| |
| TEST(FvmExtractorTest, TestMetadata) { |
| fbl::unique_fd input_fd; |
| fbl::unique_fd output_fd; |
| auto ramdisk_or = storage::RamDisk::Create(kBlockSize, kBlockCount); |
| ASSERT_EQ(ramdisk_or.status_value(), ZX_OK); |
| |
| CreateInputAndOutputStream(ramdisk_or.value(), input_fd, output_fd); |
| Extract(input_fd, output_fd, /*corrupt=*/false); |
| |
| fvm::Header info; |
| VerifyInputSuperblock(input_fd, &info); |
| VerifyOutputSuperblock(info, output_fd); |
| ssize_t used_size = static_cast<ssize_t>(2 * info.GetMetadataUsedBytes()); |
| ssize_t allocated_size = static_cast<ssize_t>(2 * info.GetMetadataAllocatedBytes()); |
| std::unique_ptr<char[]> metadata(new char[allocated_size]); |
| ASSERT_EQ(pread(input_fd.get(), metadata.get(), allocated_size, 0), allocated_size); |
| std::unique_ptr<char[]> read_buffer_nodemap(new char[used_size]); |
| ASSERT_EQ(pread(output_fd.get(), read_buffer_nodemap.get(), used_size, |
| kExtractedImageBlockCount * fvm::kBlockSize), |
| used_size); |
| ASSERT_EQ(memcmp(metadata.get(), read_buffer_nodemap.get(), info.GetMetadataUsedBytes()), 0); |
| ASSERT_EQ( |
| memcmp(metadata.get() + info.GetMetadataAllocatedBytes(), |
| read_buffer_nodemap.get() + info.GetMetadataUsedBytes(), info.GetMetadataUsedBytes()), |
| 0); |
| } |
| |
| TEST(FvmExtractorTest, TestPartitionTable) { |
| fbl::unique_fd input_fd; |
| fbl::unique_fd output_fd; |
| auto ramdisk_or = storage::RamDisk::Create(kBlockSize, kBlockCount); |
| ASSERT_EQ(ramdisk_or.status_value(), ZX_OK); |
| |
| CreateInputAndOutputStream(ramdisk_or.value(), input_fd, output_fd); |
| Extract(input_fd, output_fd, /*corrupt=*/false); |
| |
| fvm::Header info; |
| VerifyInputSuperblock(input_fd, &info); |
| VerifyOutputSuperblock(info, output_fd); |
| |
| // First copy |
| std::unique_ptr<char[]> partition_table(new char[info.GetPartitionTableByteSize()]); |
| ASSERT_EQ(pread(input_fd.get(), partition_table.get(), info.GetPartitionTableByteSize(), |
| info.GetPartitionTableOffset()), |
| (ssize_t)info.GetPartitionTableByteSize()); |
| std::unique_ptr<char[]> read_buffer_pt(new char[info.GetPartitionTableByteSize()]); |
| ASSERT_EQ(pread(output_fd.get(), read_buffer_pt.get(), info.GetPartitionTableByteSize(), |
| kExtractedImageBlockCount * fvm::kBlockSize + info.GetPartitionTableOffset()), |
| (ssize_t)info.GetPartitionTableByteSize()); |
| |
| ASSERT_EQ(memcmp(partition_table.get(), read_buffer_pt.get(), info.GetPartitionTableByteSize()), |
| 0); |
| |
| // Second copy |
| size_t secondarySuperblockOffset = info.GetSuperblockOffset(fvm::SuperblockType::kSecondary); |
| size_t used_allocated_diff = |
| info.GetAllocationTableAllocatedByteSize() - info.GetAllocationTableUsedByteSize(); |
| |
| std::unique_ptr<char[]> second_partition_table(new char[info.GetPartitionTableByteSize()]); |
| ASSERT_EQ(pread(input_fd.get(), second_partition_table.get(), info.GetPartitionTableByteSize(), |
| secondarySuperblockOffset + info.GetPartitionTableOffset()), |
| (ssize_t)info.GetPartitionTableByteSize()); |
| std::unique_ptr<char[]> read_buffer_pt_2(new char[info.GetPartitionTableByteSize()]); |
| ASSERT_EQ(pread(output_fd.get(), read_buffer_pt_2.get(), info.GetPartitionTableByteSize(), |
| kExtractedImageBlockCount * fvm::kBlockSize + secondarySuperblockOffset - |
| used_allocated_diff + info.GetPartitionTableOffset()), |
| (ssize_t)info.GetPartitionTableByteSize()); |
| |
| ASSERT_EQ(memcmp(second_partition_table.get(), read_buffer_pt_2.get(), |
| info.GetPartitionTableByteSize()), |
| 0); |
| } |
| |
| TEST(FvmExtractorTest, TestAllocationTable) { |
| fbl::unique_fd input_fd; |
| fbl::unique_fd output_fd; |
| auto ramdisk_or = storage::RamDisk::Create(kBlockSize, kBlockCount); |
| ASSERT_EQ(ramdisk_or.status_value(), ZX_OK); |
| |
| CreateInputAndOutputStream(ramdisk_or.value(), input_fd, output_fd); |
| Extract(input_fd, output_fd, /*corrupt=*/false); |
| |
| fvm::Header info; |
| VerifyInputSuperblock(input_fd, &info); |
| VerifyOutputSuperblock(info, output_fd); |
| |
| // First copy |
| std::unique_ptr<char[]> allocation_table(new char[info.GetAllocationTableUsedByteSize()]); |
| ASSERT_EQ(pread(input_fd.get(), allocation_table.get(), info.GetAllocationTableUsedByteSize(), |
| info.GetAllocationTableOffset()), |
| (ssize_t)info.GetAllocationTableUsedByteSize()); |
| std::unique_ptr<char[]> read_buffer_at(new char[info.GetAllocationTableUsedByteSize()]); |
| ASSERT_EQ(pread(output_fd.get(), read_buffer_at.get(), info.GetAllocationTableUsedByteSize(), |
| kExtractedImageBlockCount * fvm::kBlockSize + info.GetAllocationTableOffset()), |
| (ssize_t)info.GetAllocationTableUsedByteSize()); |
| |
| ASSERT_EQ( |
| memcmp(allocation_table.get(), read_buffer_at.get(), info.GetAllocationTableUsedByteSize()), |
| 0); |
| |
| // Second copy |
| size_t secondarySuperblockOffset = info.GetSuperblockOffset(fvm::SuperblockType::kSecondary); |
| size_t used_allocated_diff = |
| info.GetAllocationTableAllocatedByteSize() - info.GetAllocationTableUsedByteSize(); |
| std::unique_ptr<char[]> second_allocation_table(new char[info.GetAllocationTableUsedByteSize()]); |
| ASSERT_EQ( |
| pread(input_fd.get(), second_allocation_table.get(), info.GetAllocationTableUsedByteSize(), |
| secondarySuperblockOffset + info.GetAllocationTableOffset()), |
| (ssize_t)info.GetAllocationTableUsedByteSize()); |
| std::unique_ptr<char[]> read_buffer_at_2(new char[info.GetAllocationTableUsedByteSize()]); |
| ASSERT_EQ(pread(output_fd.get(), read_buffer_at_2.get(), info.GetAllocationTableUsedByteSize(), |
| kExtractedImageBlockCount * fvm::kBlockSize + secondarySuperblockOffset - |
| used_allocated_diff + info.GetAllocationTableOffset()), |
| (ssize_t)info.GetAllocationTableUsedByteSize()); |
| |
| ASSERT_EQ(memcmp(second_allocation_table.get(), read_buffer_at_2.get(), |
| info.GetAllocationTableUsedByteSize()), |
| 0); |
| } |
| |
| } // namespace |
| |
| } // namespace extractor |