blob: a6e523377ec6131112272db485ad24f17d46fb1a [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 <ctype.h>
#include <fidl/fuchsia.hardware.block/cpp/wire.h>
#include <lib/cksum.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fdio/cpp/caller.h>
#include <zircon/assert.h>
#include <algorithm>
#include <functional>
#include <memory>
#include <optional>
#include <gpt/gpt.h>
#include <gpt/guid.h>
#include <ramdevice-client/ramdisk.h>
#include <zxtest/zxtest.h>
#include "src/storage/gpt/gpt.h"
#include "src/storage/lib/block_client/cpp/block_device.h"
#include "src/storage/lib/block_client/cpp/reader.h"
#include "src/storage/lib/block_client/cpp/remote_block_device.h"
#include "src/storage/lib/block_client/cpp/writer.h"
extern bool gUseRamDisk;
extern char gDevPath[PATH_MAX];
extern unsigned int gRandSeed;
namespace gpt {
namespace {
using gpt::GptDevice;
constexpr uint64_t kHoleSize = 10;
uuid::Uuid TestGuid(uint32_t id) {
return uuid::Uuid(uuid::RawUuid{id, 0x10, 0x20, 0x30, 0x40, {1, 2, 3, 4, 5, 6}});
}
// Return a copy of the input variable.
//
// Useful when the input value is a packed field and needs to be passed to
// a function taking a const reference. For example, given:
//
// void Bar(const int& b);
//
// struct Foo {
// char a;
// int b;
// } PACKED;
//
// we cannot directly call "Bar(foo->b)", but can call "Bar(Unpack(foo->b))".
template <typename T>
inline T Unpack(T val) {
return val;
}
// generate a random number between [0, max)
uint64_t random_length(uint64_t max) { return (rand_r(&gRandSeed) % max); }
constexpr uint64_t partition_size(const gpt_partition_t* p) { return p->last - p->first + 1; }
uint32_t CalculateCrc(const gpt_header_t& header) {
gpt_header_t new_header = header;
new_header.crc32 = 0;
return crc32(0, reinterpret_cast<const uint8_t*>(&new_header), kHeaderSize);
}
void UpdateHeaderCrcs(gpt_header_t* header, uint8_t* entries_array, size_t size) {
header->entries_crc = crc32(0, entries_array, size);
header->crc32 = CalculateCrc(*header);
}
void destroy_gpt(block_client::BlockDevice& device, uint64_t block_size, uint64_t offset,
uint64_t block_count) {
char zero[block_size];
memset(zero, 0, sizeof(zero));
ASSERT_GT(block_count, 0, "Block count should be greater than zero");
ASSERT_GT(block_size, 0, "Block count should be greater than zero");
uint64_t first = offset;
uint64_t last = offset + block_count - 1;
block_client::Writer writer(device);
for (uint64_t i = first; i <= last; i++) {
ASSERT_OK(writer.Write(block_size * i, block_size, zero), "Failed to write");
}
}
// This class keeps track of what we expect partitions to be on the
// GptDevice. Before making the change to GptDevice, we make tracking
// changes to this class so that we can verify a set of changes.
class Partitions {
private:
static void IncrementGuid(uint8_t* guid) { guid[6]++; }
public:
Partitions(uint32_t count, uint64_t first, uint64_t last) {
ZX_ASSERT(count > 0);
ZX_ASSERT(count <= gpt::kPartitionCount);
partition_count_ = count;
uint64_t part_first = first, part_last;
uint64_t part_max_len = (last - first) / partition_count_;
ZX_ASSERT(part_max_len > 0);
memset(partitions_, 0, sizeof(partitions_));
for (uint32_t i = 0; i < partition_count_; i++) {
part_last = part_first + random_length(part_max_len);
uuid::Uuid guid = TestGuid(i);
memcpy(partitions_[i].type, guid.bytes(), sizeof(partitions_[i].type));
memcpy(partitions_[i].guid, guid.bytes(), sizeof(partitions_[i].type));
partitions_[i].first = part_first;
partitions_[i].last = part_last;
partitions_[i].flags = 0;
memset(partitions_[i].name, 0, sizeof(partitions_[i].name));
snprintf(reinterpret_cast<char*>(partitions_[i].name), sizeof(partitions_[i].name), "%u_part",
i);
// Set next first block
part_first += part_max_len;
// Previous last block should be less than next first block
ZX_ASSERT(part_last < part_first);
}
}
// Returns partition at index index. Returns null if index is out of range.
const gpt_partition_t* GetPartition(uint32_t index) const {
if (index >= partition_count_) {
return nullptr;
}
return &partitions_[index];
}
// Returns number of partition created/removed.
uint32_t GetCount() const { return partition_count_; }
// Marks a partition as created in GPT.
void MarkCreated(uint32_t index) {
ASSERT_LT(index, partition_count_);
created_[index] = true;
}
// Mark a partition as removed in GPT.
void ClearCreated(uint32_t index) {
ASSERT_LT(index, partition_count_);
created_[index] = false;
}
// Returns true if the GPT should have the partition.
bool IsCreated(uint32_t index) const { return created_[index]; }
// Returns number of partition that should exist on GPT.
uint32_t CreatedCount() const {
uint32_t created_count = 0;
for (uint32_t i = 0; i < GetCount(); i++) {
if (IsCreated(i)) {
created_count++;
}
}
return (created_count);
}
// Returns true if the partition p exists in partitions_.
bool Find(const gpt_partition_t* p, uint32_t* out_index) const {
for (uint32_t i = 0; i < partition_count_; i++) {
if (Compare(GetPartition(i), p)) {
*out_index = i;
return true;
}
}
return false;
}
// Changes gpt_partition_t.type. One of the fields in the GUID
// is incremented.
void ChangePartitionType(uint32_t partition_index) {
ASSERT_LT(partition_index, partition_count_);
IncrementGuid(partitions_[partition_index].type);
}
// Changes gpt_partition_t.guid. One of the fields in the GUID
// is incremented.
void ChangePartitionGuid(uint32_t partition_index) {
ASSERT_LT(partition_index, partition_count_);
IncrementGuid(partitions_[partition_index].guid);
}
// Sets the visibility attribute of the partition
void SetPartitionVisibility(uint32_t partition_index, bool visible) {
ASSERT_LT(partition_index, partition_count_);
gpt::SetPartitionVisibility(&partitions_[partition_index], visible);
}
// Changes the range a partition covers. The function doesn't check if these
// changes are valid or not (whether they over lap with other partitions,
// cross device limits)
void ChangePartitionRange(uint32_t partition_index, uint64_t start, uint64_t end) {
ASSERT_LT(partition_index, partition_count_);
partitions_[partition_index].first = start;
partitions_[partition_index].last = end;
}
// Gets the current value of gpt_partition_t.flags
void GetPartitionFlags(uint32_t partition_index, uint64_t* flags) const {
ASSERT_LT(partition_index, partition_count_);
*flags = partitions_[partition_index].flags;
}
// Sets the current value of gpt_partition_t.flags
void SetPartitionFlags(uint32_t partition_index, uint64_t flags) {
ASSERT_LT(partition_index, partition_count_);
partitions_[partition_index].flags = flags;
}
// Returns true if two partitions are the same.
static bool Compare(const gpt_partition_t* in_mem_partition,
const gpt_partition_t* on_disk_partition) {
if (memcmp(in_mem_partition->type, on_disk_partition->type, sizeof(in_mem_partition->type)) !=
0) {
return false;
}
if (memcmp(in_mem_partition->guid, on_disk_partition->guid, sizeof(in_mem_partition->guid)) !=
0) {
return false;
}
if (in_mem_partition->first != on_disk_partition->first) {
return false;
}
if (in_mem_partition->last != on_disk_partition->last) {
return false;
}
if (in_mem_partition->flags != on_disk_partition->flags) {
return false;
}
// In mem partition name is a c-string whereas on-disk partition name
// is stored as UTF-16. We need to convert UTF-16 to c-string before we
// compare.
char name[GPT_NAME_LEN];
memset(name, 0, GPT_NAME_LEN);
utf16_to_cstring(name, reinterpret_cast<const uint16_t*>(on_disk_partition->name),
GPT_NAME_LEN / 2);
return strncmp(name, reinterpret_cast<const char*>(in_mem_partition->name), GPT_NAME_LEN / 2) ==
0;
}
private:
// List of partitions
gpt_partition_t partitions_[gpt::kPartitionCount];
// A variable to track whether a partition is created on GPT or not
bool created_[gpt::kPartitionCount] = {};
// Number of partitions_ that is populated with valid information
uint32_t partition_count_;
};
constexpr uint32_t kBlockSize = 512;
constexpr uint64_t kBlockCount = 1 << 20;
constexpr uint64_t kAcceptableMinimumSize = kBlockSize * kBlockCount;
constexpr uint64_t kGptMetadataSize = 1 << 18; // 256KiB for now. See comment in LibGptTest::Init
static_assert(kGptMetadataSize <= kAcceptableMinimumSize,
"GPT size greater than kAcceptableMinimumSize");
class LibGptTest {
public:
~LibGptTest() {
if (ramdisk_ != nullptr) {
ramdisk_destroy(ramdisk_);
}
}
// Test environment options.
struct Options {
// Path to the block device to use for the test. If empty, an internal ramdisk will
// be used for tests.
std::string disk_path;
// Block size and count for the test. Ignored if a block device is provided.
uint32_t block_size = kBlockSize;
uint64_t block_count = kBlockCount;
};
// Create a new test environment using the given options.
static std::unique_ptr<LibGptTest> Create(const Options& options) {
// Use "new" to access private constructor.
auto result = std::unique_ptr<LibGptTest>(new LibGptTest());
// Set up disk.
if (options.disk_path.empty()) {
result->InitRamDisk(options);
} else {
result->InitDisk(options.disk_path.c_str());
}
// TODO(auradkar): All tests assume that the disks don't have an initialized
// disk. If tests find an GPT initialized disk at the beginning of test,
// they fail. The tests leave disks in initialized state.
//
// To either uninitialized an initialized disk as a part of setup
// test needs to know where gpt lies on the disk. As of now libgpt doesn't
// export an api to get the location(s) of the gpt on disk. So, we assume
// here that gpt lies in first few (GptMetadataBlocksCount()) blocks on the
// device. We also ignore any backup copies on the device.
// Once there exists an api in libgpt to get size and location(s) of gpt,
// we can setup/cleanup before/after running tests in a better way.
destroy_gpt(result->gpt_->device(), result->GetBlockSize(), 0,
result->GptMetadataBlocksCount());
result->Reset();
return result;
}
// Returns total size of the disk under test.
uint64_t GetDiskSize() const { return blk_size_ * blk_count_; }
// Return block size of the disk under test.
uint32_t GetBlockSize() const { return blk_size_; }
// Returns total number of block in the disk.
uint64_t GetBlockCount() const { return blk_count_; }
// Return total number of block free in disk after GPT is created.
uint64_t GetUsableBlockCount() const { return usable_last_block_ - usable_start_block_; }
// First block that is free to use after GPT is created.
uint64_t GetUsableStartBlock() const { return usable_start_block_; }
// Last block that is free to use after GPT is created.
uint64_t GetUsableLastBlock() const { return usable_last_block_; }
// Returns number of block GPT uses.
uint64_t GptMetadataBlocksCount() const {
// See comment in Init
return kGptMetadataSize / blk_size_;
}
// Returns the full device path.
const char* GetDevicePath() const { return disk_path_; }
// Remove all partition from GPT and keep device in GPT initialized state.
void Reset() {
zx::result device = component::Connect<fuchsia_hardware_block_volume::Volume>(disk_path_);
ASSERT_OK(device);
std::string controller_path = std::string(disk_path_).append("/device_controller");
zx::result controller = component::Connect<fuchsia_device::Controller>(controller_path);
ASSERT_OK(controller);
zx::result remote_device = block_client::RemoteBlockDevice::Create(
std::move(device.value()), std::move(controller.value()));
ASSERT_OK(remote_device);
zx::result gpt_result =
GptDevice::Create(std::move(remote_device.value()), GetBlockSize(), GetBlockCount());
ASSERT_OK(gpt_result);
gpt_ = std::move(gpt_result.value());
}
// Finalize uninitialized disk and verify.
void Finalize() {
ASSERT_FALSE(gpt_->Valid(), "Valid GPT on uninitialized disk");
ASSERT_OK(gpt_->Finalize(), "Failed to finalize");
ASSERT_TRUE(gpt_->Valid(), "Invalid GPT after finalize");
}
// Sync and verify.
void Sync() {
ASSERT_OK(gpt_->Sync(), "Failed to sync");
ASSERT_TRUE(gpt_->Valid(), "Invalid GPT after sync");
}
// Get the Range from GPT.
void ReadRange() {
ASSERT_OK(gpt_->Range(&usable_start_block_, &usable_last_block_));
// TODO(auradkar): GptDevice doesn't export api to get GPT-metadata size.
// If it does, we can keep better track of metadata size it says it needs
// and metadata it actually uses.
ASSERT_LT(GetUsableStartBlock(), GetBlockCount(), "Range starts after EOD");
ASSERT_LT(GetUsableStartBlock(), GetUsableLastBlock(), "Invalid range");
ASSERT_LT(GetUsableLastBlock(), GetBlockCount(), "Range end greater than block count");
ASSERT_GT(GetUsableBlockCount(), 0, "GPT occupied all available blocks");
}
// Read the MBR from the disk.
zx_status_t ReadMbr(mbr::Mbr* mbr) const {
ZX_ASSERT(sizeof(*mbr) <= blk_size_);
// Read the block containing the MBR.
char buff[blk_size_];
block_client::Reader reader(gpt_->device());
if (reader.Read(0, blk_size_, buff) != ZX_OK) {
return ZX_ERR_IO;
}
// Copy the result to "mbr".
memcpy(mbr, buff, sizeof(*mbr));
return ZX_OK;
}
// Prepare disk to run Add Partition tests.
// 1. initialize GPT
// 2. optionally sync
// 3. get the usable range
void PrepDisk(bool sync) {
if (sync) {
Sync();
} else {
Finalize();
}
ReadRange();
}
// gpt_ changes across Reset(). So we do not expose pointer to GptDevice to
// any of the test. Instead we expose following wrapper functions for
// a few GptDevice methods.
bool IsGptValid() const { return gpt_->Valid(); }
zx_status_t GetDiffs(uint32_t partition_index, uint32_t* diffs) const {
return gpt_->GetDiffs(partition_index, diffs);
}
// Gets a partition at index.
const gpt_partition_t* GetPartition(uint32_t index) const {
return gpt_->GetPartition(index).value_or(nullptr);
}
// Adds a partition
zx_status_t AddPartition(const char* name, const uint8_t* type, const uint8_t* guid,
uint64_t offset, uint64_t blocks, uint64_t flags) {
return gpt_->AddPartition(name, type, guid, offset, blocks, flags).status_value();
}
// removes a partition.
zx_status_t RemovePartition(const uint8_t* guid) { return gpt_->RemovePartition(guid); }
// removes all partitions.
zx_status_t RemoveAllPartitions() { return gpt_->RemoveAllPartitions(); }
// wrapper around GptDevice's SetPartitionType
zx_status_t SetPartitionType(uint32_t partition_index, const uint8_t* type) {
return gpt_->SetPartitionType(partition_index, type);
}
// wrapper around GptDevice's SetPartitionGuid
zx_status_t SetPartitionGuid(uint32_t partition_index, const uint8_t* guid) {
return gpt_->SetPartitionGuid(partition_index, guid);
}
// wrapper around GptDevice's SetPartitionRange
zx_status_t SetPartitionRange(uint32_t partition_index, uint64_t start, uint64_t end) {
return gpt_->SetPartitionRange(partition_index, start, end);
}
// wrapper around GptDevice's SetPartitionVisibility
zx_status_t SetPartitionVisibility(uint32_t index, bool visible) {
return gpt_->SetPartitionVisibility(index, visible);
}
// wrapper around GptDevice's GetPartitionFlags
zx_status_t GetPartitionFlags(uint32_t index, uint64_t* flags) {
return gpt_->GetPartitionFlags(index, flags);
}
// wrapper around GptDevice's SetPartitionFlags
zx_status_t SetPartitionFlags(uint32_t index, uint64_t flags) {
return gpt_->SetPartitionFlags(index, flags);
}
private:
LibGptTest() = default;
// Initialize a physical media.
void InitDisk(const char* disk_path) {
strlcpy(disk_path_, disk_path, sizeof(disk_path_));
zx::result device = component::Connect<fuchsia_hardware_block_volume::Volume>(disk_path_);
ASSERT_OK(device);
const fidl::WireResult result = fidl::WireCall(device.value())->GetInfo();
ASSERT_OK(result.status());
const fit::result response = result.value();
ASSERT_TRUE(response.is_ok(), "%s", zx_status_get_string(response.error_value()));
blk_size_ = response.value()->info.block_size;
blk_count_ = response.value()->info.block_count;
ASSERT_GE(GetDiskSize(), kAcceptableMinimumSize, "Insufficient disk space for tests");
std::string controller_path = std::string(disk_path_).append("/device_controller");
zx::result controller = component::Connect<fuchsia_device::Controller>(controller_path);
ASSERT_OK(controller);
zx::result remote_device = block_client::RemoteBlockDevice::Create(
std::move(device.value()), std::move(controller.value()));
ASSERT_OK(remote_device);
zx::result gpt_result =
GptDevice::Create(std::move(remote_device.value()), GetBlockSize(), GetBlockCount());
ASSERT_OK(gpt_result);
gpt_ = std::move(gpt_result.value());
}
// Create and initialize and ramdisk.
void InitRamDisk(const Options& options) {
ASSERT_OK(ramdisk_create(options.block_size, options.block_count, &ramdisk_));
InitDisk(ramdisk_get_path(ramdisk_));
}
// Block size of the device.
uint32_t blk_size_ = kBlockSize;
// Number of block in the disk.
uint64_t blk_count_ = kBlockCount;
// disk path
char disk_path_[PATH_MAX] = {};
// pointer to read GptDevice.
std::unique_ptr<gpt::GptDevice> gpt_;
// An optional ramdisk structure.
ramdisk_client_t* ramdisk_ = nullptr;
// usable start block offset.
uint64_t usable_start_block_ = UINT64_MAX;
// usable last block offset.
uint64_t usable_last_block_ = UINT64_MAX;
};
class LibGptTestFixture : public zxtest::Test {
public:
LibGptTest* GetLibGptTest() const { return lib_gpt_test_.get(); }
protected:
void SetUp() override {
lib_gpt_test_ = LibGptTest::Create({
.disk_path = gUseRamDisk ? std::string() : gDevPath,
});
}
private:
std::unique_ptr<LibGptTest> lib_gpt_test_ = {};
};
using GptDeviceTest = LibGptTestFixture;
uint64_t EntryArrayBlockCount(uint64_t block_size) {
return ((kMaxPartitionTableSize + block_size - 1) / block_size);
}
// Manually calculate the minimum block count.
uint64_t GptMinimumBlockCount(uint64_t block_size) {
uint64_t block_count = kPrimaryHeaderStartBlock;
// Two copies of gpt_header_t. A block for each.
block_count += (UINT64_C(2) * kHeaderBlocks);
// Two copies of entries array.
block_count += (UINT64_C(2) * EntryArrayBlockCount(block_size));
// We need at least one block as usable block.
return block_count + 1;
}
// Returns a copy of |str| with any hex values converted to uppercase.
std::string HexToUpper(std::string str) {
for (char& ch : str) {
if (ch >= 'a' && ch <= 'f') {
ch -= 'a';
ch += 'A';
}
}
return str;
}
} // namespace
// Creates "partitions->GetCount()"" number of partitions on GPT.
// The information needed to create a partitions is passed in "partitions".
void AddPartitionHelper(LibGptTest* libGptTest, Partitions* partitions) {
ASSERT_GT(partitions->GetCount(), 0, "At least one partition is required");
for (uint32_t i = 0; i < partitions->GetCount(); i++) {
const gpt_partition_t* p = partitions->GetPartition(i);
ASSERT_OK(libGptTest->AddPartition(reinterpret_cast<const char*>(p->name), p->type, p->guid,
p->first, partition_size(p), p->flags));
partitions->MarkCreated(i);
}
}
// Removes randomly selected "remove_count" number of partitions.
void RemovePartitionsHelper(LibGptTest* libGptTest, Partitions* partitions, uint32_t remove_count) {
uint32_t index;
ASSERT_LE(remove_count, partitions->GetCount(), "Remove count exceeds whats available");
ASSERT_LE(remove_count, partitions->CreatedCount(), "Cannot remove more partitions than created");
for (uint32_t i = 0; i < remove_count; i++) {
while (true) {
index = static_cast<uint32_t>(rand_r(&gRandSeed)) % partitions->GetCount();
if (partitions->IsCreated(index)) {
break;
}
}
ASSERT_TRUE(partitions->IsCreated(index), "Partition already removed");
const gpt_partition_t* p = partitions->GetPartition(index);
ASSERT_OK(libGptTest->RemovePartition(p->guid), "Failed to remove partition");
partitions->ClearCreated(index);
}
}
// Verifies all the partitions that exists on GPT are the ones that are created
// by the test and vice-versa.
void PartitionVerify(LibGptTest* libGptTest, const Partitions* partitions) {
bool found[gpt::kPartitionCount] = {};
uint32_t found_index = 0;
// Check what's found on disk is created by us
// iterate over all partition that are present on disk and make sure
// that we intended to create them.
// Note: The index of an entry/partition need not match with the index of
// the partition in "Partition* partition".
for (uint32_t i = 0; i < gpt::kPartitionCount; i++) {
const gpt_partition_t* p = libGptTest->GetPartition(i);
if (p == nullptr) {
continue;
}
ASSERT_TRUE(partitions->Find(p, &found_index), "Found an entry on GPT that we did not create");
ASSERT_TRUE(partitions->IsCreated(found_index), "Removed entry reincarnated");
found[found_index] = true;
}
// Check what's created is found on disk
for (uint32_t i = 0; i < partitions->GetCount(); i++) {
if (partitions->IsCreated(i)) {
ASSERT_TRUE(found[i], "Created partition is missing on disk");
}
}
}
// Creates partitions and verifies them.
void AddPartitions(LibGptTest* libGptTest, Partitions* partitions, bool sync) {
AddPartitionHelper(libGptTest, partitions);
if (sync) {
libGptTest->Sync();
}
PartitionVerify(libGptTest, partitions);
ASSERT_EQ(partitions->GetCount(), partitions->CreatedCount());
}
// Removes partitions and verifies them.
void RemovePartitions(LibGptTest* libGptTest, Partitions* partitions, uint32_t remove_count,
bool sync) {
RemovePartitionsHelper(libGptTest, partitions, remove_count);
if (sync) {
libGptTest->Sync();
}
PartitionVerify(libGptTest, partitions);
ASSERT_EQ(partitions->GetCount() - partitions->CreatedCount(), remove_count,
"Not as many removed as we wanted to");
}
// Removes all partitions and verifies them.
void RemoveAllPartitions(LibGptTest* libGptTest, Partitions* partitions, bool sync) {
ASSERT_LE(partitions->GetCount(), partitions->CreatedCount(), "Not all partitions populated");
ASSERT_OK(libGptTest->RemoveAllPartitions(), "Failed to remove all partition");
for (uint32_t i = 0; i < partitions->GetCount(); i++) {
partitions->ClearCreated(i);
}
PartitionVerify(libGptTest, partitions);
ASSERT_EQ(partitions->CreatedCount(), 0, "Not as many removed as we wanted to");
}
// Test adding total_partitions partitions to GPT w/o writing to disk
void AddPartitionTestHelper(LibGptTest* libGptTest, uint32_t total_partitions, bool sync) {
libGptTest->PrepDisk(sync);
Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(),
libGptTest->GetUsableLastBlock());
AddPartitions(libGptTest, &partitions, sync);
}
// Test adding total_partitions partitions to GPT and test removing remove_count
// partitions later w/o writing to disk.
void RemovePartition(LibGptTest* libGptTest, uint32_t total_partitions, uint32_t remove_count,
bool sync) {
libGptTest->PrepDisk(sync);
Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(),
libGptTest->GetUsableLastBlock());
AddPartitions(libGptTest, &partitions, sync);
RemovePartitions(libGptTest, &partitions, remove_count, sync);
}
// Test removing all total_partitions from GPT w/o syncing.
void RemoveAllPartitions(LibGptTest* libGptTest, uint32_t total_partitions, bool sync) {
libGptTest->PrepDisk(sync);
Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(),
libGptTest->GetUsableLastBlock());
AddPartitions(libGptTest, &partitions, sync);
RemoveAllPartitions(libGptTest, &partitions, sync);
}
void SetPartitionTypeTestHelper(LibGptTest* libGptTest, uint32_t total_partitions, bool sync) {
libGptTest->PrepDisk(sync);
Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(),
libGptTest->GetUsableLastBlock());
AddPartitions(libGptTest, &partitions, sync);
// Change partition type in cached copy so that we can verify the
// changes in GptDevice
uint32_t index = static_cast<uint32_t>(rand_r(&gRandSeed) % total_partitions);
partitions.ChangePartitionType(index);
// Keep a backup copy of GptDevice's partition type
const gpt_partition_t* p = libGptTest->GetPartition(index);
uuid::Uuid before(p->type);
// Change the type in GptDevice
EXPECT_OK(libGptTest->SetPartitionType(index, partitions.GetPartition(index)->type));
// Get the changes
p = libGptTest->GetPartition(index);
uuid::Uuid after(p->type);
// The type should have changed by now in GptDevice
EXPECT_NE(before, after);
PartitionVerify(libGptTest, &partitions);
}
void SetPartitionGuidTestHelper(LibGptTest* libGptTest, uint32_t total_partitions, bool sync) {
libGptTest->PrepDisk(sync);
Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(),
libGptTest->GetUsableLastBlock());
AddPartitions(libGptTest, &partitions, sync);
// Change partition id in cached copy so that we can verify the
// changes in GptDevice
uint32_t index = static_cast<uint32_t>(rand_r(&gRandSeed) % total_partitions);
partitions.ChangePartitionGuid(index);
// Keep a backup copy of GptDevice's partition ID
const gpt_partition_t* p = libGptTest->GetPartition(index);
uuid::Uuid before(p->guid);
// Change the guid in GptDevice
EXPECT_OK(libGptTest->SetPartitionGuid(index, partitions.GetPartition(index)->guid));
// Get the changes
p = libGptTest->GetPartition(index);
uuid::Uuid after(p->guid);
// The guid should have changed by now in GptDevice
EXPECT_NE(before, after);
PartitionVerify(libGptTest, &partitions);
}
// Find a partition that has a hole between it's end and start of next partition
zx_status_t FindPartitionToExpand(const Partitions* partitions, uint32_t* out_index,
uint64_t* out_first, uint64_t* out_last) {
for (uint32_t index = 0; index < partitions->GetCount(); index++) {
if (index == partitions->GetCount() - 1) {
*out_last = partitions->GetPartition(index)->last + kHoleSize;
*out_index = index;
*out_first = partitions->GetPartition(index)->first;
return ZX_OK;
}
// if delta between last block of this partition and first next
// partition is greater than one then we have found a hole
if ((partitions->GetPartition(index + 1)->first - partitions->GetPartition(index)->last) > 1) {
*out_last = partitions->GetPartition(index + 1)->first - 1;
*out_index = index;
*out_first = partitions->GetPartition(index)->first;
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
// Find a partition that has a hole between it's end and start of next partition
zx_status_t FindPartitionToShrink(const Partitions* partitions, uint32_t* out_index,
uint64_t* out_first, uint64_t* out_last) {
constexpr uint64_t kMinPartitionSize = 10;
for (uint32_t index = 0; index < partitions->GetCount(); index++) {
// The partition needs to have at least kMinPartitionSize to shrink
if ((partitions->GetPartition(index)->last - partitions->GetPartition(index)->first) >
kMinPartitionSize) {
*out_last = partitions->GetPartition(index)->last - 2;
*out_first = partitions->GetPartition(index)->first + 2;
*out_index = index;
return ZX_OK;
}
}
return ZX_ERR_NOT_FOUND;
}
// Find partition tries to find a partition that can be either expanded or shrunk.
// Partition first and last block number can either increase or decrease. So there are
// 8 combinations of how a partition boundary can change. We test two of those 8 cases.
// On successfully finding a partition that can be modified, function return ZX_OK
// On success The output parameters
// - out_index is the partition that can be modified.
// - out_first is new value of partition's first block.
// - out_last is the new value of partition's last block.
using FindPartitionFn = std::function<zx_status_t(const Partitions* partitions, uint32_t* out_index,
uint64_t* out_first, uint64_t* out_last)>;
void SetPartitionRangeTestHelper(LibGptTest* libGptTest, uint32_t total_partitions, bool sync,
const FindPartitionFn& find_part) {
uint64_t new_last = 0, new_first = 0;
ASSERT_GT(total_partitions, 1, "For range to test we need at least two partition");
libGptTest->PrepDisk(sync);
Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(),
libGptTest->GetUsableLastBlock() - kHoleSize);
AddPartitions(libGptTest, &partitions, sync);
uint32_t index;
EXPECT_OK(find_part(&partitions, &index, &new_first, &new_last));
ASSERT_NE(index, partitions.GetCount(), "Could not find a hole");
ASSERT_NE(new_first, 0, "Could not find a hole to change range");
ASSERT_NE(new_last, 0, "Could not find a hole to change range");
partitions.ChangePartitionRange(index, new_first, new_last);
// Change the range in GptDevice
EXPECT_OK(libGptTest->SetPartitionRange(index, new_first, new_last));
// Get the changes
const gpt_partition_t* p = libGptTest->GetPartition(index);
EXPECT_EQ(p->first, new_first, "First doesn't match after update");
EXPECT_EQ(p->last, new_last, "Last doesn't match after update");
PartitionVerify(libGptTest, &partitions);
}
void PartitionVisibilityFlip(LibGptTest* libGptTest, Partitions* partitions, uint32_t index) {
// Get the current visibility and flip it
const gpt_partition_t* p = libGptTest->GetPartition(index);
bool visible = gpt::IsPartitionVisible(p);
visible = !visible;
partitions->SetPartitionVisibility(index, visible);
// Change the guid in GptDevice
EXPECT_OK(libGptTest->SetPartitionVisibility(index, visible));
// Get the changes and verify
p = libGptTest->GetPartition(index);
EXPECT_EQ(gpt::IsPartitionVisible(p), visible);
PartitionVerify(libGptTest, partitions);
}
void PartitionVisibilityTestHelper(LibGptTest* libGptTest, uint32_t total_partitions, bool sync) {
libGptTest->PrepDisk(sync);
Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(),
libGptTest->GetUsableLastBlock());
AddPartitions(libGptTest, &partitions, sync);
uint32_t index = static_cast<uint32_t>(rand_r(&gRandSeed) % total_partitions);
PartitionVisibilityFlip(libGptTest, &partitions, index);
PartitionVisibilityFlip(libGptTest, &partitions, index);
}
void PartitionFlagsFlip(LibGptTest* libGptTest, Partitions* partitions, uint32_t index) {
// Get the current flags
uint64_t old_flags, updated_flags;
ASSERT_OK(libGptTest->GetPartitionFlags(index, &old_flags), "");
uint64_t new_flags = ~old_flags;
partitions->SetPartitionFlags(index, new_flags);
// Change the flags
ASSERT_OK(libGptTest->SetPartitionFlags(index, new_flags), "");
// Get the changes and verify
ASSERT_OK(libGptTest->GetPartitionFlags(index, &updated_flags), "");
ASSERT_EQ(new_flags, updated_flags, "Flags update failed");
PartitionVerify(libGptTest, partitions);
}
void PartitionFlagsTestHelper(LibGptTest* libGptTest, uint32_t total_partitions, bool sync) {
libGptTest->PrepDisk(sync);
Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(),
libGptTest->GetUsableLastBlock());
AddPartitions(libGptTest, &partitions, sync);
uint32_t index = static_cast<uint32_t>(rand_r(&gRandSeed) % total_partitions);
PartitionFlagsFlip(libGptTest, &partitions, index);
PartitionFlagsFlip(libGptTest, &partitions, index);
}
// Test if Diffs after adding partitions reflect all the changes.
void DiffsTestHelper(LibGptTest* libGptTest, uint32_t total_partitions) {
uint32_t diffs;
EXPECT_NE(libGptTest->GetDiffs(0, &diffs), ZX_OK, "GetDiffs should fail before PrepDisk");
libGptTest->PrepDisk(false);
EXPECT_NE(libGptTest->GetDiffs(0, &diffs), ZX_OK,
"GetDiffs for non-existing partition should fail");
Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(),
libGptTest->GetUsableLastBlock());
AddPartitions(libGptTest, &partitions, false);
EXPECT_OK(libGptTest->GetDiffs(0, &diffs), "Diffs zero after adding partition");
EXPECT_EQ(diffs,
gpt::kGptDiffType | gpt::kGptDiffGuid | gpt::kGptDiffFirst | gpt::kGptDiffLast |
gpt::kGptDiffName,
"Unexpected diff after creating partition");
libGptTest->Sync();
EXPECT_OK(libGptTest->GetDiffs(0, &diffs), "");
EXPECT_EQ(diffs, 0, "Diffs not zero after syncing partition");
}
constexpr uint64_t ComputePerCopySize(uint64_t block_size) {
return block_size + (static_cast<uint64_t>(kPartitionCount) * kEntrySize);
}
constexpr uint64_t ComputePerCopyBlockCount(uint64_t block_size) {
return (ComputePerCopySize(block_size) + block_size - 1) / block_size;
}
constexpr uint64_t ComputeMinimumBlockDeviceSize(uint64_t block_size) {
// One mbr block.
return 1 + (2 * ComputePerCopyBlockCount(block_size));
}
TEST(MinimumBytesPerCopyTest, BlockSizeTooSmall) {
ASSERT_STATUS(ZX_ERR_INVALID_ARGS, MinimumBytesPerCopy(kHeaderSize - 1).error());
}
TEST(MinimumBytesPerCopyTest, BlockSizeDefaultBlockSize) {
ASSERT_EQ(ComputePerCopySize(kBlockSize), MinimumBytesPerCopy(kBlockSize).value());
}
TEST(MinimumBytesPerCopyTest, BlockSize1Meg) {
ASSERT_EQ(ComputePerCopySize(1 << 20), MinimumBytesPerCopy(1 << 20).value());
}
TEST(MinimumBlocksPerCopyTest, BlockSizeTooSmall) {
ASSERT_STATUS(ZX_ERR_INVALID_ARGS, MinimumBlocksPerCopy(kHeaderSize - 1).error());
}
TEST(MinimumBlocksPerCopyTest, BlockSizeDefaultBlockSize) {
ASSERT_EQ(ComputePerCopyBlockCount(kBlockSize), MinimumBlocksPerCopy(kBlockSize).value());
}
TEST(MinimumBlocksPerCopyTest, BlockSize1Meg) {
ASSERT_EQ(ComputePerCopyBlockCount(1 << 20), MinimumBlocksPerCopy(1 << 20).value());
}
TEST(MinimumBlockDeviceSizeTest, BlockSizeTooSmall) {
ASSERT_STATUS(ZX_ERR_INVALID_ARGS, MinimumBlockDeviceSize(kHeaderSize - 1).error());
}
TEST(MinimumBlockDeviceSizeTest, BlockSizeDefaultBlockSize) {
ASSERT_EQ(ComputeMinimumBlockDeviceSize(kBlockSize), MinimumBlockDeviceSize(kBlockSize).value());
}
TEST(MinimumBlockDeviceSizeTest, BlockSize1Meg) {
ASSERT_EQ(ComputeMinimumBlockDeviceSize(1 << 20), MinimumBlockDeviceSize(1 << 20).value());
}
TEST(EntryBlockCountTest, ValidEntry) {
gpt_entry_t entry = {};
entry.guid[0] = 1;
entry.type[0] = 1;
entry.first = 10;
entry.last = 20;
ASSERT_EQ(EntryBlockCount(&entry).value(), 11);
}
TEST(EntryBlockCountTest, UninitializedEntry) {
gpt_entry_t entry = {};
ASSERT_STATUS(ZX_ERR_NOT_FOUND, EntryBlockCount(&entry).error());
}
TEST(EntryBlockCountTest, NullPointer) {
ASSERT_STATUS(ZX_ERR_INVALID_ARGS, EntryBlockCount(nullptr).error());
}
TEST(EntryBlockCountTest, UninitializedGuid) {
gpt_entry_t entry = {};
entry.guid[0] = 0;
entry.type[0] = 1;
entry.first = 10;
entry.last = 20;
ASSERT_STATUS(ZX_ERR_BAD_STATE, EntryBlockCount(&entry).error());
}
TEST(EntryBlockCountTest, UninitializedType) {
gpt_entry_t entry = {};
entry.guid[0] = 1;
entry.type[0] = 0;
entry.first = 10;
entry.last = 20;
ASSERT_STATUS(ZX_ERR_BAD_STATE, EntryBlockCount(&entry).error());
}
TEST(EntryBlockCountTest, BadRange) {
gpt_entry_t entry = {};
entry.guid[0] = 1;
entry.type[0] = 1;
entry.first = 20;
entry.last = 10;
ASSERT_STATUS(ZX_ERR_BAD_STATE, EntryBlockCount(&entry).error());
}
// Tests if we can create a GptDevice.
TEST_F(GptDeviceTest, ValidGptOnUninitilizedDisk) {
LibGptTest* libGptTest = GetLibGptTest();
EXPECT_FALSE(libGptTest->IsGptValid(), "Valid GPT on uninitialized disk");
}
TEST_F(GptDeviceTest, ValidGptAfterResetOnUninitilized) {
LibGptTest* libGptTest = GetLibGptTest();
libGptTest->Reset();
EXPECT_FALSE(libGptTest->IsGptValid(), "Valid GPT after reset");
}
// Tests Finalize initializes GPT in-memory only and doesn't commit to disk.
TEST_F(GptDeviceTest, FinalizeNoSync) {
LibGptTest* libGptTest = GetLibGptTest();
libGptTest->Finalize();
// Finalize initializes GPT but doesn't write changes to disk.
// Resetting the Test should bring invalid gpt back.
libGptTest->Reset();
EXPECT_FALSE(libGptTest->IsGptValid(), "Valid GPT after finalize and reset");
}
// Tests Finalize initializes GPT and writes it to disk.
TEST_F(GptDeviceTest, FinalizeAndSync) {
auto libGptTest = GetLibGptTest();
EXPECT_FALSE(libGptTest->IsGptValid());
// Sync should write changes to disk. Resetting should bring valid gpt back.
libGptTest->Sync();
libGptTest->Reset();
EXPECT_TRUE(libGptTest->IsGptValid());
// Check the protective MBR that was written to disk.
mbr::Mbr mbr;
zx_status_t status = libGptTest->ReadMbr(&mbr);
ASSERT_OK(status, "Failed to read MBR");
EXPECT_EQ(mbr::kMbrBootSignature, mbr.boot_signature, "Invalid MBR boot signature");
mbr::MbrPartitionEntry expected{
.status = 0,
.chs_address_start = {0, 1, 0},
.type = mbr::kPartitionTypeGptProtective,
.chs_address_end = {0xff, 0xff, 0xff},
.start_sector_lba = 1,
.num_sectors =
static_cast<uint32_t>(std::min(0xffff'ffffUL, libGptTest->GetBlockCount() - 1)),
};
EXPECT_BYTES_EQ(&expected, mbr.partitions, sizeof(expected), "Invalid protective MBR");
}
// Tests the range the GPT blocks falls within disk.
TEST_F(GptDeviceTest, Range) {
auto libGptTest = GetLibGptTest();
libGptTest->Finalize();
libGptTest->ReadRange();
}
TEST_F(GptDeviceTest, AddPartitionNoSync) { AddPartitionTestHelper(GetLibGptTest(), 3, false); }
TEST_F(GptDeviceTest, AddPartition) { AddPartitionTestHelper(GetLibGptTest(), 20, true); }
TEST_F(GptDeviceTest, RemovePartitionNoSync) { RemovePartition(GetLibGptTest(), 12, 4, false); }
TEST_F(GptDeviceTest, RemovePartition) { RemovePartition(GetLibGptTest(), 3, 2, true); }
TEST_F(GptDeviceTest, RemovePartitionRemoveAllOneAtATime) {
RemovePartition(GetLibGptTest(), 11, 11, false);
}
TEST_F(GptDeviceTest, RemoveAllPartitions) { RemoveAllPartitions(GetLibGptTest(), 12, true); }
TEST_F(GptDeviceTest, RemoveAllPartitionsNoSync) {
RemoveAllPartitions(GetLibGptTest(), 15, false);
}
TEST_F(GptDeviceTest, SetPartitionType) { SetPartitionTypeTestHelper(GetLibGptTest(), 4, true); }
TEST_F(GptDeviceTest, SetPartitionTypeNoSync) {
SetPartitionTypeTestHelper(GetLibGptTest(), 8, false);
}
TEST_F(GptDeviceTest, SetPartitionGuidSync) {
SetPartitionGuidTestHelper(GetLibGptTest(), 5, true);
}
TEST_F(GptDeviceTest, SetPartitionGuidNoSync) {
SetPartitionGuidTestHelper(GetLibGptTest(), 7, false);
}
TEST_F(GptDeviceTest, ExpandPartitionSync) {
SetPartitionRangeTestHelper(GetLibGptTest(), 3, true, FindPartitionToExpand);
}
TEST_F(GptDeviceTest, ExpandPartitionNoSync) {
SetPartitionRangeTestHelper(GetLibGptTest(), 3, false, FindPartitionToExpand);
}
TEST_F(GptDeviceTest, ShrinkPartitionSync) {
SetPartitionRangeTestHelper(GetLibGptTest(), 3, true, FindPartitionToShrink);
}
TEST_F(GptDeviceTest, ShrinkPartitionNoSync) {
SetPartitionRangeTestHelper(GetLibGptTest(), 3, false, FindPartitionToShrink);
}
TEST_F(GptDeviceTest, PartitionVisibilityOnSyncTest) {
PartitionVisibilityTestHelper(GetLibGptTest(), 5, true);
}
TEST_F(GptDeviceTest, PartitionVisibilityNoSyncTest) {
PartitionVisibilityTestHelper(GetLibGptTest(), 3, false);
}
TEST_F(GptDeviceTest, UpdatePartitionFlagsSync) {
PartitionFlagsTestHelper(GetLibGptTest(), 9, true);
}
TEST_F(GptDeviceTest, UpdatePartitionFlagsNoSync) {
PartitionFlagsTestHelper(GetLibGptTest(), 1, false);
}
TEST_F(GptDeviceTest, GetDiffsForAddingOnePartition) { DiffsTestHelper(GetLibGptTest(), 1); }
TEST_F(GptDeviceTest, GetDiffsForAddingMultiplePartition) { DiffsTestHelper(GetLibGptTest(), 9); }
TEST(GptDeviceLoad, ValidHeader) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
uint8_t blocks[MinimumBytesPerCopy(kBlockSize).value()] = {};
UpdateHeaderCrcs(&header, &blocks[kBlockSize],
MinimumBytesPerCopy(kBlockSize).value() - kBlockSize);
memcpy(blocks, &header, sizeof(header));
ASSERT_OK(
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount));
}
TEST(GptDeviceLoad, SmallBlockSize) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
uint8_t blocks[MinimumBytesPerCopy(kBlockSize).value()] = {};
memcpy(blocks, &header, sizeof(header));
ASSERT_STATUS(
ZX_ERR_INVALID_ARGS,
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kHeaderSize - 1, kBlockCount)
.status_value());
}
TEST(GptDeviceLoad, NullBuffer) {
ASSERT_STATUS(
ZX_ERR_INVALID_ARGS,
GptDevice::Load(nullptr, MinimumBytesPerCopy(kBlockSize).value(), kHeaderSize, kBlockCount)
.status_value());
}
// It is ok to have gpt with no partitions.
TEST(GptDeviceLoadEntries, NoValidEntries) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
uint8_t blocks[MinimumBytesPerCopy(kBlockSize).value()] = {};
UpdateHeaderCrcs(&header, &blocks[kBlockSize],
MinimumBytesPerCopy(kBlockSize).value() - kBlockSize);
memcpy(blocks, &header, sizeof(header));
ASSERT_OK(
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount));
}
TEST(GptDeviceLoadEntries, SmallEntryArray) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
uint8_t blocks[MinimumBytesPerCopy(kBlockSize).value()] = {};
UpdateHeaderCrcs(&header, &blocks[kBlockSize],
MinimumBytesPerCopy(kBlockSize).value() - kBlockSize);
memcpy(blocks, &header, sizeof(header));
ASSERT_STATUS(
ZX_ERR_BUFFER_TOO_SMALL,
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value() - 1, kBlockSize, kBlockCount)
.status_value());
}
TEST(GptDeviceLoadEntries, EntryFirstSmallerThanFirstUsable) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[MinimumBytesPerCopy(kBlockSize).value()]());
uint8_t* blocks = buffer.get();
gpt_entry_t* entry = reinterpret_cast<gpt_entry_t*>(&blocks[kBlockSize]);
// first is less than first usable block.
entry->guid[0] = 1;
entry->type[0] = 1;
entry->first = header.first - 1;
entry->last = header.last;
UpdateHeaderCrcs(&header, &blocks[kBlockSize],
MinimumBytesPerCopy(kBlockSize).value() - kBlockSize);
memcpy(blocks, &header, sizeof(header));
ASSERT_STATUS(
ZX_ERR_ALREADY_EXISTS,
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount)
.status_value());
}
TEST(GptDeviceLoadEntries, EntryLastLargerThanLastUsable) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[MinimumBytesPerCopy(kBlockSize).value()]());
uint8_t* blocks = buffer.get();
gpt_entry_t* entry = reinterpret_cast<gpt_entry_t*>(&blocks[kBlockSize]);
// last is greater than last usable block.
entry->guid[0] = 1;
entry->type[0] = 1;
entry->first = header.first;
entry->last = header.last + 1;
UpdateHeaderCrcs(&header, &blocks[kBlockSize],
MinimumBytesPerCopy(kBlockSize).value() - kBlockSize);
memcpy(blocks, &header, sizeof(header));
ASSERT_STATUS(
ZX_ERR_ALREADY_EXISTS,
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount)
.status_value());
}
TEST(GptDeviceLoadEntries, EntryFirstLargerThanEntryLast) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[MinimumBytesPerCopy(kBlockSize).value()]());
uint8_t* blocks = buffer.get();
gpt_entry_t* entry = reinterpret_cast<gpt_entry_t*>(&blocks[kBlockSize]);
// last is greater than last usable block.
entry->guid[0] = 1;
entry->type[0] = 1;
entry->first = header.last;
entry->last = header.first;
UpdateHeaderCrcs(&header, &blocks[kBlockSize],
MinimumBytesPerCopy(kBlockSize).value() - kBlockSize);
memcpy(blocks, &header, sizeof(header));
ASSERT_STATUS(
ZX_ERR_OUT_OF_RANGE,
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount)
.status_value());
}
TEST(GptDeviceLoadEntries, EntriesOverlap) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[MinimumBytesPerCopy(kBlockSize).value()]());
uint8_t* blocks = buffer.get();
gpt_entry_t* entry1 = reinterpret_cast<gpt_entry_t*>(&blocks[kBlockSize]);
entry1->guid[0] = 1;
entry1->type[0] = 1;
entry1->first = header.first;
entry1->last = kBlockCount / 3;
EXPECT_TRUE(entry1->first <= entry1->last);
gpt_entry_t* entry2 = entry1 + 1;
entry2->guid[0] = 2;
entry2->type[0] = 2;
entry2->first = 2 * kBlockCount / 3; // Block shared with entry1
entry2->last = header.last;
EXPECT_TRUE(entry2->first <= entry2->last);
gpt_entry_t* entry3 = entry2 + 1;
entry3->guid[0] = 3;
entry3->type[0] = 3;
entry3->first = entry1->last; // Block shared with entry1
entry3->last = entry2->first - 1;
EXPECT_TRUE(entry3->first <= entry3->last);
UpdateHeaderCrcs(&header, &blocks[kBlockSize],
MinimumBytesPerCopy(kBlockSize).value() - kBlockSize);
memcpy(blocks, &header, sizeof(header));
ASSERT_STATUS(
ZX_ERR_OUT_OF_RANGE,
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount)
.status_value());
}
TEST(GptDeviceLoadEntries, EntryOverlapsWithLastEntry) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[MinimumBytesPerCopy(kBlockSize).value()]);
uint8_t* blocks = buffer.get();
gpt_entry_t* entry1 = reinterpret_cast<gpt_entry_t*>(&blocks[kBlockSize]);
entry1->guid[0] = 1;
entry1->type[0] = 1;
entry1->first = header.first;
entry1->last = kBlockCount / 3;
EXPECT_TRUE(entry1->first <= entry1->last);
gpt_entry_t* entry2 = entry1 + 1;
entry2->guid[0] = 2;
entry2->type[0] = 2;
entry2->first = 2 * kBlockCount / 3; // Block shared with entry1
entry2->last = header.last;
EXPECT_TRUE(entry2->first <= entry2->last);
gpt_entry_t* entry3 = entry2 + 1;
entry3->guid[0] = 3;
entry3->type[0] = 3;
entry3->first = entry1->last + 1;
entry3->last = entry2->first; // Block shared with entry2
EXPECT_TRUE(entry3->first <= entry3->last);
UpdateHeaderCrcs(&header, &blocks[kBlockSize],
MinimumBytesPerCopy(kBlockSize).value() - kBlockSize);
memcpy(blocks, &header, sizeof(header));
ASSERT_STATUS(
ZX_ERR_OUT_OF_RANGE,
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount)
.status_value());
}
TEST(GptDeviceLoadEntries, EntriesCrcIncludesPadding) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[MinimumBytesPerCopy(kBlockSize).value()]());
uint8_t* blocks = buffer.get();
uint32_t entry_count = 4;
header.entries_count = entry_count;
UpdateHeaderCrcs(&header, &blocks[kBlockSize], kMaxPartitionTableSize);
memcpy(blocks, &header, sizeof(header));
zx::result gpt =
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount);
ASSERT_OK(gpt);
ASSERT_EQ(gpt.value()->EntryCount(), entry_count);
}
TEST(GptDeviceLoadEntries, BadEntriesCrc) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[MinimumBytesPerCopy(kBlockSize).value()]());
uint8_t* blocks = buffer.get();
uint32_t entry_count = 4;
header.entries_count = entry_count;
UpdateHeaderCrcs(&header, &blocks[kBlockSize], static_cast<uint64_t>(kEntrySize) * entry_count);
header.entries_crc = ~header.entries_crc;
header.crc32 = CalculateCrc(header);
memcpy(blocks, &header, sizeof(header));
zx::result gpt =
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount);
EXPECT_STATUS(gpt, ZX_ERR_BAD_STATE);
}
TEST(GptDeviceLoadEntries, MaxEntryCount) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[MinimumBytesPerCopy(kBlockSize).value()]());
uint8_t* blocks = buffer.get();
uint32_t entry_count = kPartitionCount;
header.entries_count = entry_count;
UpdateHeaderCrcs(&header, &blocks[kBlockSize], static_cast<uint64_t>(kEntrySize) * entry_count);
memcpy(blocks, &header, sizeof(header));
zx::result gpt =
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount);
ASSERT_OK(gpt);
ASSERT_EQ(gpt.value()->EntryCount(), entry_count);
}
TEST(GptDeviceEntryCountTest, DefaultValue) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[MinimumBytesPerCopy(kBlockSize).value()]());
uint8_t* blocks = buffer.get();
UpdateHeaderCrcs(&header, &blocks[kBlockSize],
MinimumBytesPerCopy(kBlockSize).value() - kBlockSize);
memcpy(blocks, &header, sizeof(header));
zx::result gpt =
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount);
ASSERT_OK(gpt);
ASSERT_EQ(gpt.value()->EntryCount(), kPartitionCount);
}
TEST(GptDeviceEntryCountTest, FewerEntries) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[MinimumBytesPerCopy(kBlockSize).value()]());
uint8_t* blocks = buffer.get();
uint32_t entry_count = 4;
header.entries_count = entry_count;
UpdateHeaderCrcs(&header, &blocks[kBlockSize],
static_cast<uint64_t>(entry_count) * header.entries_size);
memcpy(blocks, &header, sizeof(header));
zx::result gpt =
GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, kBlockCount);
ASSERT_OK(gpt);
ASSERT_EQ(gpt.value()->EntryCount(), entry_count);
}
TEST(GptDeviceMbr, LargerSectorSizes) {
auto libGptTest = LibGptTest::Create({
.block_size = 4096,
});
// Disk starts off uninitialized.
EXPECT_FALSE(libGptTest->IsGptValid());
// Sync should write changes to disk. Resetting should bring valid gpt back.
libGptTest->Sync();
libGptTest->Reset();
EXPECT_TRUE(libGptTest->IsGptValid());
// Check the protective MBR that was written to disk.
mbr::Mbr mbr;
zx_status_t status = libGptTest->ReadMbr(&mbr);
ASSERT_OK(status, "Failed to read MBR");
EXPECT_EQ(mbr::kMbrBootSignature, Unpack(mbr.boot_signature), "Invalid MBR boot signature");
}
TEST(GptDeviceMbr, DiskSize) {
auto libGptTest = LibGptTest::Create({
.block_count = 0x10'0000,
});
// Write changes to disk.
libGptTest->Sync();
EXPECT_TRUE(libGptTest->IsGptValid());
// Check the protective MBR that was written to disk, and covers the size of the disk,
// minus one (the MBR sector is not counted).
mbr::Mbr mbr;
zx_status_t status = libGptTest->ReadMbr(&mbr);
ASSERT_OK(status, "Failed to read MBR");
EXPECT_EQ(Unpack(mbr.partitions[0].num_sectors), 0x10'0000 - 1);
}
TEST(MakeProtectiveMbr, PartitionSize) {
// Protective MBR should create a partition of size min(UINT32_MAX, num_sectors - 1).
EXPECT_EQ(Unpack(MakeProtectiveMbr(100).partitions[0].num_sectors), 99);
EXPECT_EQ(Unpack(MakeProtectiveMbr(0xffff'ffff).partitions[0].num_sectors), 0xffff'fffe);
EXPECT_EQ(Unpack(MakeProtectiveMbr(0x10'abcd'1234).partitions[0].num_sectors), 0xffff'ffff);
}
// KnownGuid is statically built. Verify name uniqueness within each scheme.
TEST(KnownGuidTest, CheckNames) {
for (auto i = KnownGuid::begin(); i != KnownGuid::end(); i++) {
for (auto j = i + 1; j != KnownGuid::end(); j++) {
// Old and new partition scheme sometimes share names, but in this case
// the type GUID and scheme must be different.
if (i->name() == j->name()) {
EXPECT_NE(i->type_guid(), j->type_guid());
EXPECT_NE(i->scheme(), j->scheme());
}
}
}
}
// KnownGuid is statically built. Verify type GUID uniqueness except when
// shared by slotted partitions.
TEST(KnownGuidTest, CheckTypeGuids) {
for (auto i = KnownGuid::begin(); i != KnownGuid::end(); i++) {
for (auto j = i + 1; j != KnownGuid::end(); j++) {
// Slotted partitions can share a type GUID.
if (i->type_guid() == j->type_guid()) {
// Names should differ only by the slot suffix character.
EXPECT_EQ(i->name().substr(0, i->name().size() - 1),
j->name().substr(0, j->name().size() - 1));
// Only the new partitioning scheme uses shared type GUIDs.
EXPECT_EQ(i->scheme(), PartitionScheme::kNew);
EXPECT_EQ(j->scheme(), PartitionScheme::kNew);
}
}
}
}
TEST(KnownGuidTest, FindByTypeGuid) {
std::list<const GuidProperties*> matches;
matches = KnownGuid::Find(std::nullopt, uuid::Uuid(GUID_BOOTLOADER_VALUE), std::nullopt);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(matches.front()->name(), "bootloader");
matches = KnownGuid::Find(std::nullopt, uuid::Uuid(GUID_ZIRCON_B_VALUE), std::nullopt);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(matches.front()->name(), "zircon-b");
// New partition scheme.
matches = KnownGuid::Find(std::nullopt, uuid::Uuid(GPT_DURABLE_BOOT_TYPE_GUID), std::nullopt);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(matches.front()->name(), "durable_boot");
matches = KnownGuid::Find(std::nullopt, uuid::Uuid(GPT_FVM_TYPE_GUID), std::nullopt);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(matches.front()->name(), "fvm");
matches = KnownGuid::Find(std::nullopt, uuid::Uuid(GPT_BOOTLOADER_ABR_TYPE_GUID), std::nullopt);
EXPECT_EQ(matches.size(), 3);
EXPECT_EQ(matches.front()->name(), "bootloader_a");
EXPECT_EQ((++matches.front())->name(), "bootloader_b");
EXPECT_EQ(matches.back()->name(), "bootloader_r");
// Unknown type GUID.
matches = KnownGuid::Find(std::nullopt, TestGuid(0), std::nullopt);
EXPECT_TRUE(matches.empty());
}
TEST(KnownGuidTest, FindByName) {
std::list<const GuidProperties*> matches;
// Legacy partition scheme.
matches = KnownGuid::Find("fuchsia-system", std::nullopt, std::nullopt);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(matches.front()->type_guid(), uuid::Uuid(GUID_SYSTEM_VALUE));
matches = KnownGuid::Find("misc", std::nullopt, std::nullopt);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(matches.front()->type_guid(), uuid::Uuid(GUID_ABR_META_VALUE));
// vbmeta_{a,b,r} partitions exist in both legacy and new schemes.
matches = KnownGuid::Find("vbmeta_a", std::nullopt, std::nullopt);
EXPECT_EQ(matches.size(), 2);
EXPECT_EQ(matches.front()->type_guid(), uuid::Uuid(GUID_VBMETA_A_VALUE));
EXPECT_EQ(matches.front()->scheme(), PartitionScheme::kLegacy);
EXPECT_EQ(matches.back()->type_guid(), uuid::Uuid(GPT_VBMETA_ABR_TYPE_GUID));
EXPECT_EQ(matches.back()->scheme(), PartitionScheme::kNew);
// New partition scheme.
matches = KnownGuid::Find("factory", std::nullopt, std::nullopt);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(matches.front()->type_guid(), uuid::Uuid(GPT_FACTORY_TYPE_GUID));
// Unknown partition name.
matches = KnownGuid::Find("unknown_name", std::nullopt, std::nullopt);
EXPECT_TRUE(matches.empty());
}
TEST(KnownGuidTest, FindByPartitionScheme) {
ASSERT_EQ(KnownGuid::Find(std::nullopt, std::nullopt, PartitionScheme::kLegacy).size(), 26);
ASSERT_EQ(KnownGuid::Find(std::nullopt, std::nullopt, PartitionScheme::kNew).size(), 13);
}
TEST(KnownGuidTest, FindByAll) {
std::list<const GuidProperties*> matches;
// Legacy partition scheme.
matches =
KnownGuid::Find("fuchsia-system", uuid::Uuid(GUID_SYSTEM_VALUE), PartitionScheme::kLegacy);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(matches.front()->name(), "fuchsia-system");
EXPECT_EQ(matches.front()->type_guid(), uuid::Uuid(GUID_SYSTEM_VALUE));
EXPECT_EQ(matches.front()->scheme(), PartitionScheme::kLegacy);
// New partition scheme.
matches = KnownGuid::Find("factory", uuid::Uuid(GPT_FACTORY_TYPE_GUID), PartitionScheme::kNew);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(matches.front()->name(), "factory");
EXPECT_EQ(matches.front()->type_guid(), uuid::Uuid(GPT_FACTORY_TYPE_GUID));
EXPECT_EQ(matches.front()->scheme(), PartitionScheme::kNew);
// Both schemes have a "factory" partition, but mix-and-match of the new type
// GUID and old scheme should return nothing.
matches = KnownGuid::Find("factory", uuid::Uuid(GPT_FACTORY_TYPE_GUID), PartitionScheme::kLegacy);
EXPECT_TRUE(matches.empty());
}
// Type GUID string validation to make sure endianness is handled correctly.
TEST(KnownGuidTest, TypeGuidStrings) {
std::list<const GuidProperties*> matches;
// Legacy partition scheme.
matches = KnownGuid::Find("cros-firmware", std::nullopt, std::nullopt);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(HexToUpper(matches.front()->type_guid().ToString()),
"CAB6E88E-ABF3-4102-A07A-D4BB9BE3C1D3");
matches = KnownGuid::Find("fuchsia-fvm", std::nullopt, std::nullopt);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(HexToUpper(matches.front()->type_guid().ToString()),
"41D0E340-57E3-954E-8C1E-17ECAC44CFF5");
// New partition scheme.
matches = KnownGuid::Find("factory_boot", std::nullopt, std::nullopt);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(HexToUpper(matches.front()->type_guid().ToString()),
"10B8DBAA-D2BF-42A9-98C6-A7C5DB3701E7");
matches = KnownGuid::Find("bootloader_r", std::nullopt, std::nullopt);
EXPECT_EQ(matches.size(), 1);
EXPECT_EQ(HexToUpper(matches.front()->type_guid().ToString()),
"FE8A2634-5E2E-46BA-99E3-3A192091A350");
}
TEST(KnownGuidTest, TypeDescription) {
// Legacy partition scheme.
EXPECT_EQ(KnownGuid::TypeDescription(GUID_ZIRCON_A_VALUE), "zircon-a");
EXPECT_EQ(KnownGuid::TypeDescription(GUID_FVM_VALUE), "fuchsia-fvm");
// New partition scheme.
EXPECT_EQ(KnownGuid::TypeDescription(GPT_FACTORY_BOOT_TYPE_GUID), "factory_boot");
EXPECT_EQ(KnownGuid::TypeDescription(GPT_ZIRCON_ABR_TYPE_GUID), "zircon_*");
// Unknown partition.
EXPECT_EQ(KnownGuid::TypeDescription(TestGuid(0)), "");
}
// Make sure all known GUID entries have a valid TypeDescription.
TEST(KnownGuidTest, AllHaveTypeDescription) {
for (auto i = KnownGuid::begin(); i != KnownGuid::end(); i++) {
std::string description = KnownGuid::TypeDescription(i->type_guid());
// Shouldn't be empty, and shouldn't just be "*" which would indicate
// something went wrong with the prefix matcher.
EXPECT_FALSE(description.empty());
EXPECT_NE(description, "*");
}
}
TEST(InitializePrimaryHeader, BlockSizeTooSmall) {
ASSERT_EQ(InitializePrimaryHeader(sizeof(gpt_header_t) - 1, kBlockCount).error(),
ZX_ERR_INVALID_ARGS);
}
TEST(InitializePrimaryHeader, BlockCountOne) {
ASSERT_EQ(InitializePrimaryHeader(kBlockSize, 1).error(), ZX_ERR_BUFFER_TOO_SMALL);
}
TEST(InitializePrimaryHeader, BlockCountOneLessThanRequired) {
uint64_t block_count = GptMinimumBlockCount(kBlockSize) - 1;
ASSERT_EQ(InitializePrimaryHeader(kBlockSize, block_count).error(), ZX_ERR_BUFFER_TOO_SMALL);
}
TEST(InitializePrimaryHeader, BlockCountEqualsMinimumRequired) {
uint64_t block_count = GptMinimumBlockCount(kBlockSize);
ASSERT_TRUE(InitializePrimaryHeader(kBlockSize, block_count).is_ok());
}
TEST(InitializePrimaryHeader, CheckFields) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
ASSERT_EQ(header.magic, kMagicNumber);
ASSERT_EQ(header.revision, kRevision);
ASSERT_EQ(header.size, kHeaderSize);
ASSERT_EQ(header.reserved0, 0);
ASSERT_EQ(header.current, kPrimaryHeaderStartBlock);
ASSERT_EQ(header.backup, kBlockCount - 1);
ASSERT_EQ(header.first, kPrimaryHeaderStartBlock + 1 + EntryArrayBlockCount(kBlockSize));
ASSERT_EQ(header.last, header.backup - EntryArrayBlockCount(kBlockSize) - 1);
// Guid can be anything but all zeros
ASSERT_NE(uuid::Uuid(header.guid), uuid::Uuid());
ASSERT_EQ(header.entries, header.current + 1);
ASSERT_EQ(header.entries_count, kPartitionCount);
ASSERT_EQ(header.entries_size, kEntrySize);
ASSERT_EQ(header.entries_crc, 0);
ASSERT_EQ(header.crc32, CalculateCrc(header));
}
TEST(ValidateHeaderTest, ValidHeader) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
ASSERT_OK(ValidateHeader(&header, kBlockCount));
}
TEST(ValidateHeaderTest, BadMagic) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
header.magic = ~header.magic;
ASSERT_STATUS(ValidateHeader(&header, kBlockCount), ZX_ERR_BAD_STATE);
}
TEST(ValidateHeaderTest, InvalidSize) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
header.size = header.size + 1;
ASSERT_STATUS(ValidateHeader(&header, kBlockCount), ZX_ERR_INVALID_ARGS);
header.size = header.size - 2;
ASSERT_STATUS(ValidateHeader(&header, kBlockCount), ZX_ERR_INVALID_ARGS);
}
TEST(ValidateHeaderTest, BadCrc) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
header.crc32 = ~header.crc32;
ASSERT_STATUS(ValidateHeader(&header, kBlockCount), ZX_ERR_IO_DATA_INTEGRITY);
}
TEST(ValidateHeaderTest, TooManyPartitions) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
header.entries_count = kPartitionCount + 1;
header.crc32 = CalculateCrc(header);
ASSERT_STATUS(ValidateHeader(&header, kBlockCount), ZX_ERR_IO_OVERRUN);
}
TEST(ValidateHeaderTest, EntrySizeMismatch) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
header.entries_size = kEntrySize - 1;
header.crc32 = CalculateCrc(header);
ASSERT_STATUS(ValidateHeader(&header, kBlockCount), ZX_ERR_FILE_BIG);
header.entries_size = kEntrySize + 1;
header.crc32 = CalculateCrc(header);
ASSERT_STATUS(ValidateHeader(&header, kBlockCount), ZX_ERR_FILE_BIG);
}
TEST(ValidateHeaderTest, BlockDeviceShrunk) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
ASSERT_EQ(ValidateHeader(&header, kBlockCount - 1), ZX_ERR_BUFFER_TOO_SMALL);
}
TEST(ValidateHeaderTest, FirstUsableBlockLargerThanLast) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
header.first = header.last + 1;
header.crc32 = CalculateCrc(header);
ASSERT_STATUS(ZX_ERR_IO_DATA_INTEGRITY, ValidateHeader(&header, kBlockCount));
header.first = header.last;
header.crc32 = CalculateCrc(header);
ASSERT_OK(ValidateHeader(&header, kBlockCount));
}
TEST(ValidateHeaderTest, UsableRangeIntersectsPrimaryHeader) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
header.first = kPrimaryEntriesStartBlock - 1;
header.crc32 = CalculateCrc(header);
ASSERT_STATUS(ZX_ERR_IO_DATA_INTEGRITY, ValidateHeader(&header, kBlockCount));
header.first = 0;
header.last = kPrimaryEntriesStartBlock - 1;
header.crc32 = CalculateCrc(header);
ASSERT_STATUS(ZX_ERR_IO_DATA_INTEGRITY, ValidateHeader(&header, kBlockCount));
}
TEST(ValidateHeaderTest, UsableRangeIntersectsBackupHeader) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
header.last = kBlockCount - 1;
header.crc32 = CalculateCrc(header);
ASSERT_STATUS(ZX_ERR_IO_DATA_INTEGRITY, ValidateHeader(&header, kBlockCount));
header.last = kBlockCount - 2;
header.crc32 = CalculateCrc(header);
ASSERT_OK(ValidateHeader(&header, kBlockCount));
}
TEST(ValidateEntryTest, UninitializedEntry) {
gpt_entry_t entry = {};
ASSERT_FALSE(ValidateEntry(&entry).value());
}
TEST(ValidateEntryTest, ValidEntry) {
gpt_entry_t entry = {};
entry.guid[0] = 1;
entry.type[0] = 1;
entry.first = 10;
entry.last = 20;
ASSERT_TRUE(ValidateEntry(&entry).value());
}
TEST(ValidateEntryTest, UninitializedGuid) {
gpt_entry_t entry = {};
entry.guid[0] = 0;
entry.type[0] = 1;
entry.first = 10;
entry.last = 20;
ASSERT_STATUS(ZX_ERR_BAD_STATE, ValidateEntry(&entry).error());
}
TEST(ValidateEntryTest, UninitializedType) {
gpt_entry_t entry = {};
entry.guid[0] = 1;
entry.type[0] = 0;
entry.first = 10;
entry.last = 20;
ASSERT_STATUS(ZX_ERR_BAD_STATE, ValidateEntry(&entry).error());
}
TEST(ValidateEntryTest, BadRange) {
gpt_entry_t entry = {};
entry.guid[0] = 1;
entry.type[0] = 1;
entry.first = 20;
entry.last = 10;
ASSERT_STATUS(ZX_ERR_OUT_OF_RANGE, ValidateEntry(&entry).error());
}
TEST(GetPartitionNameTest, BufferTooSmall) {
std::array<char, kMaxUtf8NameLen> partition_name{};
gpt_entry_t partition_entry = {};
std::fill(std::begin(partition_entry.name), std::end(partition_entry.name), 0xFF);
ASSERT_STATUS(
ZX_ERR_BUFFER_TOO_SMALL,
gpt::GetPartitionName(partition_entry, partition_name.data(), partition_name.size() - 1)
.status_value());
}
TEST(GetPartitionNameTest, LongName) {
// Result of converting 36 0xFFFF's in UTF-16 to UTF-8 is a pattern
// of 0xEF, 0xBF, 0xBF repeated.
std::array<char, kMaxUtf8NameLen> expected_result{};
for (size_t i = 0; i < expected_result.size() - 1; ++i)
expected_result[i] = static_cast<char>((i % 3) ? 0xBF : 0xEF);
std::array<char, kMaxUtf8NameLen> partition_name{};
gpt_entry_t partition_entry = {};
std::fill(std::begin(partition_entry.name), std::end(partition_entry.name), 0xFF);
ASSERT_OK(gpt::GetPartitionName(partition_entry, partition_name.data(), partition_name.size())
.status_value());
ASSERT_EQ(expected_result, partition_name);
}
TEST(GptDeviceLoadEntries, FindFreeRange) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
size_t len = MinimumBytesPerCopy(kBlockSize).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[len]);
uint8_t* blocks = buffer.get();
memset(blocks, 0, len);
// Add three entries covering [START..n], [n + 402..END], [n + 100..n + 101], where `n` is
// arbitrary. This should leave room for allocations up to 300 blocks long.
gpt_entry_t* entry1 = reinterpret_cast<gpt_entry_t*>(&blocks[kBlockSize]);
entry1->guid[0] = 1;
entry1->type[0] = 1;
entry1->first = header.first;
entry1->last = kBlockCount / 4;
EXPECT_TRUE(entry1->first <= entry1->last);
gpt_entry_t* entry2 = entry1 + 1;
entry2->guid[0] = 2;
entry2->type[0] = 2;
entry2->first = entry1->last + 402;
entry2->last = header.last;
EXPECT_TRUE(entry2->first <= entry2->last);
gpt_entry_t* entry3 = entry2 + 1;
entry3->guid[0] = 3;
entry3->type[0] = 3;
entry3->first = entry1->last + 100;
entry3->last = entry3->first + 1;
EXPECT_TRUE(entry2->first <= entry2->last);
UpdateHeaderCrcs(&header, &blocks[kBlockSize], len - kBlockSize);
memcpy(blocks, &header, sizeof(header));
zx::result gpt_result = GptDevice::Load(blocks, len, kBlockSize, kBlockCount);
ASSERT_OK(gpt_result);
GptDevice& gpt = *gpt_result.value();
uint64_t offset;
ASSERT_OK(gpt.FindFreeRange(10, &offset));
ASSERT_OK(gpt.FindFreeRange(100, &offset));
ASSERT_OK(gpt.FindFreeRange(300, &offset));
ASSERT_STATUS(ZX_ERR_NO_SPACE, gpt.FindFreeRange(301, &offset), "Found offset %lu", offset);
}
TEST(GptDeviceLoadEntries, FindFreeRangeEmptyGpt) {
gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value();
size_t len = MinimumBytesPerCopy(kBlockSize).value();
std::unique_ptr<uint8_t[]> buffer(new uint8_t[len]);
uint8_t* blocks = buffer.get();
memset(blocks, 0, len);
UpdateHeaderCrcs(&header, &blocks[kBlockSize], len - kBlockSize);
memcpy(blocks, &header, sizeof(header));
zx::result gpt_result = GptDevice::Load(blocks, len, kBlockSize, kBlockCount);
ASSERT_OK(gpt_result);
GptDevice& gpt = *gpt_result.value();
uint64_t start = 0;
uint64_t end = std::numeric_limits<uint64_t>::max();
ASSERT_OK(gpt.Range(&start, &end));
uint64_t offset;
ASSERT_OK(gpt.FindFreeRange(10, &offset));
ASSERT_OK(gpt.FindFreeRange(end - start + 1, &offset));
ASSERT_STATUS(ZX_ERR_NO_SPACE, gpt.FindFreeRange(end - start + 2, &offset), "Found offset %lu",
offset);
}
} // namespace gpt