blob: 71c2dce2154e6a11f7848ff85398c597698f6978 [file] [log] [blame]
// 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