| // 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 <fbl/auto_call.h> |
| #include <fuchsia/hardware/block/c/fidl.h> |
| #include <lib/fzl/fdio.h> |
| #include <ramdevice-client/ramdisk.h> |
| #include <unittest/unittest.h> |
| #include <zircon/assert.h> |
| |
| #include "gpt-tests.h" |
| #include <gpt/guid.h> |
| |
| extern bool gUseRamDisk; |
| extern char gDevPath[PATH_MAX]; |
| extern unsigned int gRandSeed; |
| |
| namespace { |
| |
| using gpt::GptDevice; |
| using gpt::guid_t; |
| using gpt::KnownGuid; |
| |
| constexpr guid_t kGuid = {0x0, 0x1, 0x2, {0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa}}; |
| constexpr uint64_t kHoleSize = 10; |
| |
| // 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; |
| } |
| |
| bool destroy_gpt(int fd, uint64_t block_size, uint64_t offset, uint64_t block_count) { |
| BEGIN_HELPER; |
| |
| 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; |
| |
| for (uint64_t i = first; i <= last; i++) { |
| ASSERT_EQ(pwrite(fd, zero, sizeof(zero), block_size * i), (ssize_t)sizeof(zero), |
| "Failed to pwrite"); |
| } |
| // fsync is not supported in rpc-server.cpp |
| // TODO(ZX-3294) to fix this |
| // ASSERT_EQ(fsync(fd), 0, "Failed to fsync"); |
| END_HELPER; |
| } |
| |
| // 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 { |
| public: |
| Partitions(uint32_t count, uint64_t first, uint64_t last); |
| |
| // Returns partition at index index. Returns null if index is out of range. |
| const gpt_partition_t* GetPartition(uint32_t index) const; |
| |
| // Returns number of parition created/removed. |
| uint32_t GetCount() const { return partition_count_; } |
| |
| // Marks a partition as created in GPT. |
| bool MarkCreated(uint32_t index); |
| |
| // Mark a partition as removed in GPT. |
| bool ClearCreated(uint32_t index); |
| |
| // Returns true if the GPT should have the partition. |
| bool IsCreated(uint32_t index) const; |
| |
| // Returns number of partition that should exist on GPT. |
| uint32_t CreatedCount() const; |
| |
| // Returns true if two partitions are the same. |
| bool Compare(const gpt_partition_t* in_mem_partition, |
| const gpt_partition_t* on_disk_partition) const; |
| |
| // Returns true if the partition p exists in partitions_. |
| bool Find(const gpt_partition_t* p, uint32_t* out_index) const; |
| |
| // Changes gpt_partition_t.type. One of the fields in the guid_t |
| // is increamented. |
| bool ChangePartitionType(uint32_t partition_index); |
| |
| // Changes gpt_partition_t.guid. One of the fields in the guid_t |
| // is increamented. |
| bool ChangePartitionGuid(uint32_t partition_index); |
| |
| // Sets the visibility attribute of the partition |
| bool SetPartitionVisibility(uint32_t partition_index, bool 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) |
| bool ChangePartitionRange(uint32_t partition_index, uint64_t start, uint64_t end); |
| |
| // Gets the current value of gpt_partition_t.flags |
| bool GetPartitionFlags(uint32_t partition_index, uint64_t* flags) const; |
| |
| // Sets the current value of gpt_partition_t.flags |
| bool SetPartitionFlags(uint32_t partition_index, uint64_t flags); |
| |
| 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_; |
| }; |
| |
| Partitions::Partitions(uint32_t count, uint64_t first, uint64_t last) { |
| ZX_ASSERT(count > 0); |
| ZX_ASSERT(count <= gpt::kPartitionCount); |
| partition_count_ = count; |
| guid_t guid = kGuid; |
| |
| 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); |
| guid.data1 = i; |
| memcpy(partitions_[i].type, &guid, sizeof(partitions_[i].type)); |
| memcpy(partitions_[i].guid, &guid, 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); |
| } |
| } |
| |
| const gpt_partition_t* Partitions::GetPartition(uint32_t index) const { |
| if (index >= partition_count_) { |
| return nullptr; |
| } |
| |
| return &partitions_[index]; |
| } |
| |
| bool Partitions::MarkCreated(uint32_t index) { |
| BEGIN_HELPER; |
| ASSERT_LT(index, partition_count_, "Index out of range"); |
| |
| created_[index] = true; |
| END_HELPER; |
| } |
| |
| bool Partitions::ClearCreated(uint32_t index) { |
| BEGIN_HELPER; |
| ASSERT_LT(index, partition_count_, "Index out of range"); |
| |
| created_[index] = false; |
| END_HELPER; |
| } |
| |
| bool Partitions::IsCreated(uint32_t index) const { |
| BEGIN_HELPER; |
| ASSERT_LT(index, partition_count_, "Index out of range"); |
| |
| return created_[index]; |
| END_HELPER; |
| } |
| |
| uint32_t Partitions::CreatedCount() const { |
| uint32_t created_count = 0; |
| |
| for (uint32_t i = 0; i < GetCount(); i++) { |
| if (IsCreated(i)) { |
| created_count++; |
| } |
| } |
| return (created_count); |
| } |
| |
| bool Partitions::Compare(const gpt_partition_t* in_mem_partition, |
| const gpt_partition_t* on_disk_partition) const { |
| 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, (const uint16_t*)on_disk_partition->name, GPT_NAME_LEN / 2); |
| |
| if (strncmp(name, reinterpret_cast<const char*>(in_mem_partition->name), GPT_NAME_LEN / 2) != |
| 0) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool Partitions::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; |
| } |
| |
| void IncrementGuid(guid_t* g) { |
| g->data3++; |
| } |
| |
| bool Partitions::ChangePartitionGuid(uint32_t partition_index) { |
| BEGIN_HELPER; |
| ASSERT_LT(partition_index, partition_count_, "Partition index out of range"); |
| IncrementGuid(reinterpret_cast<guid_t*>(partitions_[partition_index].guid)); |
| END_HELPER; |
| } |
| |
| bool Partitions::ChangePartitionType(uint32_t partition_index) { |
| BEGIN_HELPER; |
| ASSERT_LT(partition_index, partition_count_, "Partition index out of range"); |
| IncrementGuid(reinterpret_cast<guid_t*>(partitions_[partition_index].type)); |
| END_HELPER; |
| } |
| |
| bool Partitions::SetPartitionVisibility(uint32_t partition_index, bool visible) { |
| BEGIN_HELPER; |
| ASSERT_LT(partition_index, partition_count_, "Partition index out of range"); |
| gpt::SetPartitionVisibility(&partitions_[partition_index], visible); |
| END_HELPER; |
| } |
| |
| bool Partitions::ChangePartitionRange(uint32_t partition_index, uint64_t start, uint64_t end) { |
| BEGIN_HELPER; |
| ASSERT_LT(partition_index, partition_count_, "Partition index out of range"); |
| partitions_[partition_index].first = start; |
| partitions_[partition_index].last = end; |
| END_HELPER; |
| } |
| |
| bool Partitions::GetPartitionFlags(uint32_t partition_index, uint64_t* flags) const { |
| BEGIN_HELPER; |
| ASSERT_LT(partition_index, partition_count_, "Partition index out of range"); |
| *flags = partitions_[partition_index].flags; |
| END_HELPER; |
| } |
| |
| bool Partitions::SetPartitionFlags(uint32_t partition_index, uint64_t flags) { |
| BEGIN_HELPER; |
| ASSERT_LT(partition_index, partition_count_, "Partition index out of range"); |
| partitions_[partition_index].flags = flags; |
| END_HELPER; |
| } |
| |
| // Defines a libgpt test function which can be passed to the TestWrapper. |
| typedef bool (*TestFunction)(LibGptTest* libGptTest); |
| #define RUN_TEST_WRAP(test_name) RUN_TEST_MEDIUM(TestWrapper<test_name>) |
| |
| // A test wrapper which runs a libgpt test. |
| template <TestFunction TestFunc> |
| bool TestWrapper(void) { |
| BEGIN_TEST; |
| |
| LibGptTest libGptTest(gUseRamDisk); |
| ASSERT_TRUE(libGptTest.Init(), "Setting up the block device"); |
| // Run the test. This should pass. |
| ASSERT_TRUE(TestFunc(&libGptTest)); |
| ASSERT_TRUE(libGptTest.Teardown(), "Tearing down and cleaning up the block device"); |
| |
| END_TEST; |
| } |
| |
| bool LibGptTest::Reset() { |
| BEGIN_HELPER; |
| fbl::unique_ptr<GptDevice> gpt; |
| |
| // explicitly close the fd, if open, before we attempt to reopen it. |
| fd_.reset(); |
| |
| fd_.reset(open(disk_path_, O_RDWR)); |
| |
| ASSERT_TRUE(fd_.is_valid(), "Could not open block device\n"); |
| ASSERT_EQ(GptDevice::Create(fd_.get(), GetBlockSize(), GetBlockCount(), &gpt), ZX_OK); |
| gpt_ = std::move(gpt); |
| END_HELPER; |
| } |
| |
| bool LibGptTest::Finalize() { |
| BEGIN_HELPER; |
| ASSERT_FALSE(gpt_->Valid(), "Valid GPT on uninitialized disk"); |
| |
| ASSERT_EQ(gpt_->Finalize(), ZX_OK, "Failed to finalize"); |
| ASSERT_TRUE(gpt_->Valid(), "Invalid GPT after finalize"); |
| END_HELPER; |
| } |
| |
| bool LibGptTest::Sync() { |
| BEGIN_HELPER; |
| |
| ASSERT_EQ(gpt_->Sync(), ZX_OK, "Failed to sync"); |
| ASSERT_TRUE(gpt_->Valid(), "Invalid GPT after sync"); |
| |
| END_HELPER; |
| } |
| |
| bool LibGptTest::ReadRange() { |
| BEGIN_HELPER; |
| |
| ASSERT_EQ(gpt_->Range(&usable_start_block_, &usable_last_block_), ZX_OK, |
| "Retrieval of device range failed."); |
| |
| // 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"); |
| |
| END_HELPER; |
| } |
| |
| bool LibGptTest::PrepDisk(bool sync) { |
| BEGIN_HELPER; |
| |
| if (sync) { |
| Sync(); |
| } else { |
| Finalize(); |
| } |
| |
| ASSERT_TRUE(ReadRange(), "Read range failed"); |
| END_HELPER; |
| } |
| |
| bool LibGptTest::InitDisk(const char* disk_path) { |
| BEGIN_HELPER; |
| |
| use_ramdisk_ = false; |
| snprintf(disk_path_, PATH_MAX, "%s", disk_path); |
| fbl::unique_fd fd(open(disk_path_, O_RDWR)); |
| ASSERT_TRUE(fd.is_valid(), "Could not open block device to fetch info"); |
| fzl::UnownedFdioCaller disk_caller(fd.get()); |
| fuchsia_hardware_block_BlockInfo block_info; |
| zx_status_t status; |
| ASSERT_EQ(fuchsia_hardware_block_BlockGetInfo(disk_caller.borrow_channel(), &status, |
| &block_info), |
| ZX_OK); |
| ASSERT_EQ(status, ZX_OK); |
| |
| blk_size_ = block_info.block_size; |
| blk_count_ = block_info.block_count; |
| |
| ASSERT_GE(GetDiskSize(), kAccptableMinimumSize, "Insufficient disk space for tests"); |
| fd_ = std::move(fd); |
| |
| END_HELPER; |
| } |
| |
| bool LibGptTest::InitRamDisk() { |
| BEGIN_HELPER; |
| ASSERT_EQ(ramdisk_create(GetBlockSize(), GetBlockCount(), &ramdisk_), ZX_OK, |
| "Could not create ramdisk"); |
| strlcpy(disk_path_, ramdisk_get_path(ramdisk_), sizeof(disk_path_)); |
| fd_.reset(open(disk_path_, O_RDWR)); |
| if (!fd_) { |
| return false; |
| } |
| |
| END_HELPER; |
| } |
| |
| bool LibGptTest::Init() { |
| BEGIN_HELPER; |
| auto error = fbl::MakeAutoCall([this]() { Teardown(); }); |
| if (use_ramdisk_) { |
| ASSERT_TRUE(InitRamDisk()); |
| } else { |
| ASSERT_TRUE(InitDisk(gDevPath)); |
| } |
| |
| // 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 initalized state. |
| // |
| // To either uninitialize 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. |
| ASSERT_TRUE(destroy_gpt(fd_.get(), GetBlockSize(), 0, GptMetadataBlocksCount()), |
| "Failed to destroy gpt"); |
| |
| ASSERT_TRUE(Reset()); |
| error.cancel(); |
| END_HELPER; |
| } |
| |
| bool LibGptTest::TearDownDisk() { |
| BEGIN_HELPER; |
| ASSERT_FALSE(use_ramdisk_); |
| END_HELPER; |
| } |
| |
| bool LibGptTest::TearDownRamDisk() { |
| BEGIN_HELPER; |
| ASSERT_EQ(ramdisk_destroy(ramdisk_), ZX_OK); |
| END_HELPER; |
| } |
| |
| bool LibGptTest::Teardown() { |
| BEGIN_HELPER; |
| |
| if (use_ramdisk_) { |
| ASSERT_TRUE(TearDownRamDisk()); |
| } else { |
| ASSERT_TRUE(TearDownDisk()); |
| } |
| |
| END_HELPER; |
| } |
| |
| } // namespace |
| |
| // Creates "partitions->GetCount()"" number of partitions on GPT. |
| // The information needed to create a partitions is passed in "partitions". |
| bool AddPartitionHelper(LibGptTest* libGptTest, Partitions* partitions) { |
| BEGIN_HELPER; |
| |
| 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_EQ(libGptTest->AddPartition(reinterpret_cast<const char*>(p->name), p->type, p->guid, |
| p->first, partition_size(p), p->flags), |
| ZX_OK, "Add partition failed"); |
| partitions->MarkCreated(i); |
| } |
| |
| END_HELPER; |
| } |
| |
| // Removes randomly selected "remove_count" number of partitions. |
| bool RemovePartitionsHelper(LibGptTest* libGptTest, Partitions* partitions, uint32_t remove_count) { |
| BEGIN_HELPER; |
| 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_EQ(libGptTest->RemovePartition(p->guid), ZX_OK, "Failed to remove partition"); |
| partitions->ClearCreated(index); |
| } |
| END_HELPER; |
| } |
| |
| // Verifies all the partitions that exists on GPT are the ones that are created |
| // by the test and vice-versa. |
| bool PartitionVerify(LibGptTest* libGptTest, const Partitions* partitions) { |
| BEGIN_HELPER; |
| bool found[gpt::kPartitionCount] = {}; |
| uint32_t found_index; |
| |
| // Check what's found on disk is created by us |
| // iteratre 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++) { |
| gpt_partition_t* p = libGptTest->GetPartition(i); |
| |
| if (p == NULL) { |
| 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"); |
| } |
| } |
| |
| END_TEST; |
| } |
| |
| // Creates partitions and verifies them. |
| bool AddPartitions(LibGptTest* libGptTest, Partitions* partitions, bool sync) { |
| BEGIN_HELPER; |
| |
| ASSERT_TRUE(AddPartitionHelper(libGptTest, partitions), "AddPartitionHelper failed"); |
| |
| if (sync) { |
| ASSERT_TRUE(libGptTest->Sync(), "Sync failed"); |
| } |
| |
| ASSERT_TRUE(PartitionVerify(libGptTest, partitions), "Partition verify failed"); |
| ASSERT_EQ(partitions->GetCount(), partitions->CreatedCount(), |
| "Not as many created as we wanted to"); |
| |
| END_HELPER; |
| } |
| |
| // Removes partitions and verifies them. |
| bool RemovePartitions(LibGptTest* libGptTest, Partitions* partitions, uint32_t remove_count, |
| bool sync) { |
| BEGIN_HELPER; |
| |
| ASSERT_TRUE(RemovePartitionsHelper(libGptTest, partitions, remove_count), |
| "RemovePartitionsHelper failed"); |
| if (sync) { |
| ASSERT_TRUE(libGptTest->Sync(), "Sync failed"); |
| } |
| |
| ASSERT_TRUE(PartitionVerify(libGptTest, partitions), "Partition verify failed"); |
| ASSERT_EQ(partitions->GetCount() - partitions->CreatedCount(), remove_count, |
| "Not as many removed as we wanted to"); |
| |
| END_HELPER; |
| } |
| |
| // Removes all partitions and verifies them. |
| bool RemoveAllPartitions(LibGptTest* libGptTest, Partitions* partitions, bool sync) { |
| BEGIN_HELPER; |
| |
| ASSERT_LE(partitions->GetCount(), partitions->CreatedCount(), "Not all partitions populated"); |
| ASSERT_EQ(libGptTest->RemoveAllPartitions(), ZX_OK, "Failed to remove all partition"); |
| |
| for (uint32_t i = 0; i < partitions->GetCount(); i++) { |
| partitions->ClearCreated(i); |
| } |
| |
| ASSERT_TRUE(PartitionVerify(libGptTest, partitions), "Partition verify failed"); |
| ASSERT_EQ(partitions->CreatedCount(), 0, "Not as many removed as we wanted to"); |
| END_HELPER; |
| } |
| |
| // Tests if we can create a GptDevice. |
| bool CreateTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| |
| ASSERT_FALSE(libGptTest->IsGptValid(), "Valid GPT on uninitialized disk"); |
| ASSERT_TRUE(libGptTest->Reset(), "Failed to reset Test"); |
| ASSERT_FALSE(libGptTest->IsGptValid(), "Valid GPT after reset"); |
| END_TEST; |
| } |
| |
| // Tests Finalize initializes GPT in-memory only and doesn't commit to disk. |
| bool FinalizeTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| |
| ASSERT_TRUE(libGptTest->Finalize(), "Finalize failed"); |
| |
| // Finalize initializes GPT but doesn't write changes to disk. |
| // Resetting the Test should bring invalid gpt back. |
| ASSERT_TRUE(libGptTest->Reset(), "Failed to reset Test"); |
| ASSERT_FALSE(libGptTest->IsGptValid(), "Valid GPT after finalize and reset"); |
| END_TEST; |
| } |
| |
| // Tests Finalize initializes GPT and writes it to disk. |
| bool SyncTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| |
| ASSERT_FALSE(libGptTest->IsGptValid(), "Valid GPT on uninitialized disk"); |
| |
| // Sync should write changes to disk. Resetting should bring valid gpt back. |
| ASSERT_TRUE(libGptTest->Sync(), "Sync failed"); |
| ASSERT_TRUE(libGptTest->Reset(), "Failed to reset Test"); |
| ASSERT_TRUE(libGptTest->IsGptValid(), "Invalid GPT after sync and reset"); |
| END_TEST; |
| } |
| |
| // Tests the range the GPT blocks falls within disk. |
| bool RangeTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| |
| ASSERT_TRUE(libGptTest->Finalize(), "Finalize failed"); |
| ASSERT_TRUE(libGptTest->ReadRange(), "Failed to read range"); |
| |
| END_TEST; |
| } |
| |
| // Test adding total_partitions partitions to GPT w/o writing to disk |
| template <uint32_t total_partitions, bool sync> |
| bool AddPartitionTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| ASSERT_TRUE(libGptTest->PrepDisk(sync), "Failed to setup disk"); |
| |
| Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(), |
| libGptTest->GetUsableLastBlock()); |
| |
| ASSERT_TRUE(AddPartitions(libGptTest, &partitions, sync), "AddPartitions failed"); |
| END_TEST; |
| } |
| |
| // Test adding total_partitions partitions to GPT and test removing remove_count |
| // partitions later w/o writing to disk. |
| template <uint32_t total_partitions, uint32_t remove_count, bool sync> |
| bool RemovePartitionTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| ASSERT_TRUE(libGptTest->PrepDisk(sync), "Failed to setup disk"); |
| |
| Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(), |
| libGptTest->GetUsableLastBlock()); |
| |
| ASSERT_TRUE(AddPartitions(libGptTest, &partitions, sync), "AddPartitions failed"); |
| ASSERT_TRUE(RemovePartitions(libGptTest, &partitions, remove_count, sync), |
| "RemovePartitions failed"); |
| |
| END_TEST; |
| } |
| |
| // Test removing all total_partititions from GPT w/o syncing. |
| template <uint32_t total_partitions, bool sync> |
| bool RemovePartitionAllTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| |
| ASSERT_TRUE(libGptTest->PrepDisk(sync), "Failed to setup disk"); |
| |
| Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(), |
| libGptTest->GetUsableLastBlock()); |
| |
| ASSERT_TRUE(AddPartitions(libGptTest, &partitions, sync), "AddPartitions failed"); |
| ASSERT_TRUE(RemoveAllPartitions(libGptTest, &partitions, sync), "RemoveAllPartitions failed"); |
| |
| END_TEST; |
| } |
| |
| template <uint32_t total_partitions, bool sync> |
| bool SetPartitionTypeTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| guid_t before, after; |
| |
| ASSERT_TRUE(libGptTest->PrepDisk(sync), "Failed to setup disk"); |
| |
| Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(), |
| libGptTest->GetUsableLastBlock()); |
| |
| ASSERT_TRUE(AddPartitions(libGptTest, &partitions, sync), "AddPartitions failed"); |
| |
| // 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); |
| memcpy(&before, p->type, sizeof(before)); |
| |
| // Change the type in GptDevice |
| ASSERT_EQ(libGptTest->SetPartitionType(index, partitions.GetPartition(index)->type), ZX_OK, ""); |
| |
| // Get the changes |
| p = libGptTest->GetPartition(index); |
| memcpy(&after, p->type, sizeof(after)); |
| |
| // The type should have changed by now in GptDevice |
| ASSERT_NE(memcmp(&before, &after, sizeof(before)), 0, "Same type before and after"); |
| |
| ASSERT_TRUE(PartitionVerify(libGptTest, &partitions), ""); |
| END_TEST; |
| } |
| |
| template <uint32_t total_partitions, bool sync> |
| bool SetPartitionGuidTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| guid_t before, after; |
| |
| ASSERT_TRUE(libGptTest->PrepDisk(sync), "Failed to setup disk"); |
| |
| Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(), |
| libGptTest->GetUsableLastBlock()); |
| |
| ASSERT_TRUE(AddPartitions(libGptTest, &partitions, sync), "AddPartitions failed"); |
| |
| // 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); |
| memcpy(&before, p->guid, sizeof(before)); |
| |
| // Change the guid in GptDevice |
| ASSERT_EQ(libGptTest->SetPartitionGuid(index, partitions.GetPartition(index)->guid), ZX_OK, ""); |
| |
| // Get the changes |
| p = libGptTest->GetPartition(index); |
| memcpy(&after, p->guid, sizeof(after)); |
| |
| // The guid should have changed by now in GptDevice |
| ASSERT_NE(memcmp(&before, &after, sizeof(before)), 0, "Same guid before and after"); |
| |
| ASSERT_TRUE(PartitionVerify(libGptTest, &partitions), ""); |
| END_TEST; |
| } |
| |
| // 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 black. |
| typedef zx_status_t (*find_partition_t)(const Partitions* partitions, uint32_t* out_index, |
| uint64_t* out_first, uint64_t* out_last); |
| |
| template <uint32_t total_partitions, bool sync, find_partition_t find_part> |
| bool SetPartitionRangeTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| uint64_t new_last = 0, new_first = 0; |
| |
| ASSERT_GT(total_partitions, 1, "For range to test we need at least two partition"); |
| |
| ASSERT_TRUE(libGptTest->PrepDisk(sync), "Failed to setup disk"); |
| |
| Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(), |
| libGptTest->GetUsableLastBlock() - kHoleSize); |
| |
| ASSERT_TRUE(AddPartitions(libGptTest, &partitions, sync), "AddPartitions failed"); |
| |
| uint32_t index; |
| ASSERT_EQ(find_part(&partitions, &index, &new_first, &new_last), ZX_OK, ""); |
| |
| 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"); |
| |
| ASSERT_TRUE(partitions.ChangePartitionRange(index, new_first, new_last), ""); |
| |
| // Change the range in GptDevice |
| ASSERT_EQ(libGptTest->SetPartitionRange(index, new_first, new_last), ZX_OK, ""); |
| |
| // Get the changes |
| gpt_partition_t* p = libGptTest->GetPartition(index); |
| ASSERT_EQ(p->first, new_first, "First doesn't match after update"); |
| ASSERT_EQ(p->last, new_last, "Last doesn't match after update"); |
| |
| ASSERT_TRUE(PartitionVerify(libGptTest, &partitions), "Partition verify failed"); |
| END_TEST; |
| } |
| |
| bool PartitionVisibilityFlip(LibGptTest* libGptTest, Partitions* partitions, uint32_t index) { |
| BEGIN_HELPER; |
| |
| // 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 |
| ASSERT_EQ(libGptTest->SetPartitionVisibility(index, visible), ZX_OK, ""); |
| |
| // Get the changes and verify |
| p = libGptTest->GetPartition(index); |
| ASSERT_EQ(gpt::IsPartitionVisible(p), visible, "Changes not reflected"); |
| ASSERT_TRUE(PartitionVerify(libGptTest, partitions), ""); |
| |
| END_HELPER; |
| } |
| |
| template <uint32_t total_partitions, bool sync> |
| bool PartitionVisibilityTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| |
| ASSERT_TRUE(libGptTest->PrepDisk(sync), "Failed to setup disk"); |
| |
| Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(), |
| libGptTest->GetUsableLastBlock()); |
| |
| ASSERT_TRUE(AddPartitions(libGptTest, &partitions, sync), "AddPartitions failed"); |
| uint32_t index = static_cast<uint32_t>(rand_r(&gRandSeed) % total_partitions); |
| |
| ASSERT_TRUE(PartitionVisibilityFlip(libGptTest, &partitions, index), ""); |
| ASSERT_TRUE(PartitionVisibilityFlip(libGptTest, &partitions, index), "Flipping visibility"); |
| END_TEST; |
| } |
| |
| bool PartitionFlagsFlip(LibGptTest* libGptTest, Partitions* partitions, uint32_t index) { |
| BEGIN_HELPER; |
| |
| // Get the current flags |
| uint64_t old_flags, updated_flags; |
| ASSERT_EQ(libGptTest->GetPartitionFlags(index, &old_flags), ZX_OK, ""); |
| |
| uint64_t new_flags = ~old_flags; |
| partitions->SetPartitionFlags(index, new_flags); |
| |
| // Change the flags |
| ASSERT_EQ(libGptTest->SetPartitionFlags(index, new_flags), ZX_OK, ""); |
| |
| // Get the changes and verify |
| ASSERT_EQ(libGptTest->GetPartitionFlags(index, &updated_flags), ZX_OK, ""); |
| ASSERT_EQ(new_flags, updated_flags, "Flags update failed"); |
| ASSERT_TRUE(PartitionVerify(libGptTest, partitions), ""); |
| |
| END_HELPER; |
| } |
| |
| template <uint32_t total_partitions, bool sync> |
| bool PartitionFlagsTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| |
| ASSERT_TRUE(libGptTest->PrepDisk(sync), "Failed to setup disk"); |
| |
| Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(), |
| libGptTest->GetUsableLastBlock()); |
| |
| ASSERT_TRUE(AddPartitions(libGptTest, &partitions, sync), "AddPartitions failed"); |
| uint32_t index = static_cast<uint32_t>(rand_r(&gRandSeed) % total_partitions); |
| |
| ASSERT_TRUE(PartitionFlagsFlip(libGptTest, &partitions, index), ""); |
| ASSERT_TRUE(PartitionFlagsFlip(libGptTest, &partitions, index), "Flipping flags"); |
| END_TEST; |
| } |
| |
| // Test if Diffs after adding partitions reflect all the changes. |
| template <uint32_t total_partitions> |
| bool DiffsTest(LibGptTest* libGptTest) { |
| BEGIN_TEST; |
| uint32_t diffs; |
| |
| ASSERT_NE(libGptTest->GetDiffs(0, &diffs), ZX_OK, "GetDiffs should fail before PrepDisk"); |
| ASSERT_TRUE(libGptTest->PrepDisk(false), ""); |
| ASSERT_NE(libGptTest->GetDiffs(0, &diffs), ZX_OK, |
| "GetDiffs for non-existing partition should fail"); |
| |
| Partitions partitions(total_partitions, libGptTest->GetUsableStartBlock(), |
| libGptTest->GetUsableLastBlock()); |
| ASSERT_TRUE(AddPartitions(libGptTest, &partitions, false), ""); |
| ASSERT_EQ(libGptTest->GetDiffs(0, &diffs), ZX_OK, "Diffs zero after adding partition"); |
| |
| ASSERT_EQ(diffs, |
| gpt::kGptDiffType | gpt::kGptDiffGuid | gpt::kGptDiffFirst | gpt::kGptDiffLast | |
| gpt::kGptDiffName, |
| "Unexpected diff after creating partition"); |
| ASSERT_TRUE(libGptTest->Sync(), ""); |
| ASSERT_EQ(libGptTest->GetDiffs(0, &diffs), ZX_OK, ""); |
| ASSERT_EQ(diffs, 0, "Diffs not zero after syncing partition"); |
| |
| END_TEST; |
| } |
| |
| // KnownGuid is statically built. Verify that there are no double entries for |
| // human friendly GUID name. |
| bool KnownGuidUniqueNameTest() { |
| BEGIN_TEST; |
| for (auto i = KnownGuid::begin(); i != KnownGuid::end(); i++) { |
| for (auto j = i + 1; j != KnownGuid::end(); j++) { |
| ASSERT_NE(strcmp(i->name(), j->name()), 0, "Guid names not unique"); |
| } |
| } |
| END_TEST; |
| } |
| |
| // KnownGuid is statically built. Verify that there are no double entries for |
| // GUID. |
| bool KnownGuidUniqueGuidTest() { |
| BEGIN_TEST; |
| for (auto i = KnownGuid::begin(); i != KnownGuid::end(); i++) { |
| for (auto j = i + 1; j != KnownGuid::end(); j++) { |
| ASSERT_NE(memcmp(i->guid(), j->guid(), sizeof(guid_t)), 0, "Guid not unique"); |
| } |
| } |
| END_TEST; |
| } |
| |
| // KnownGuid is statically built. Verify that there are no double entries for |
| // human friendly GUID string. |
| bool KnownGuidUniqueStrTest() { |
| BEGIN_TEST; |
| for (auto i = KnownGuid::begin(); i != KnownGuid::end(); i++) { |
| for (auto j = i + 1; j != KnownGuid::end(); j++) { |
| ASSERT_NE(strcmp(i->str(), j->str()), 0, "Guid str not unique"); |
| } |
| } |
| END_TEST; |
| } |
| |
| // KnownGuid is statically built. Verify that there are no wrong entries for GUID to |
| // string conversion. |
| bool KnownGuidToStrTest() { |
| BEGIN_TEST; |
| char str[GPT_NAME_LEN]; |
| bool pass = true; |
| for (auto i = KnownGuid::begin(); i != KnownGuid::end(); i++) { |
| uint8_to_guid_string(str, i->guid()); |
| if (strcmp(i->str(), str) != 0) { |
| printf("for %s: %s and %s don't match\n", i->name(), i->str(), str); |
| pass = false; |
| } |
| } |
| |
| ASSERT_TRUE(pass, "test failed"); |
| END_TEST; |
| } |
| |
| // Litmus test for Guid to human friendly Name conversions |
| bool GuidToNameTest() { |
| BEGIN_TEST; |
| |
| uint8_t install[GPT_GUID_LEN] = GUID_INSTALL_VALUE; |
| auto res = KnownGuid::GuidToName(install); |
| ASSERT_EQ(strcmp(res, "fuchsia-install"), 0, "Could not lookup fuchsia-install"); |
| |
| uint8_t bl[GPT_GUID_LEN] = GUID_BOOTLOADER_VALUE; |
| res = KnownGuid::GuidToName(bl); |
| ASSERT_EQ(strcmp(res, "bootloader"), 0, "Could not lookup bootloader"); |
| |
| uint8_t zb[GPT_GUID_LEN] = GUID_ZIRCON_B_VALUE; |
| res = KnownGuid::GuidToName(zb); |
| ASSERT_EQ(strcmp(res, "zircon-b"), 0, "Could not lookup zircon-b"); |
| |
| END_TEST; |
| } |
| |
| // Litmus test for Guid to human friendly Name conversions |
| bool NameToGuidTest() { |
| BEGIN_TEST; |
| uint8_t guid[GPT_GUID_LEN]; |
| |
| uint8_t sys[GPT_GUID_LEN] = GUID_SYSTEM_VALUE; |
| ASSERT_TRUE(KnownGuid::NameToGuid("fuchsia-system", guid), "fuchsia-system not found"); |
| ASSERT_EQ(memcmp(guid, sys, GPT_GUID_LEN), 0, "Could not lookup fuchsia-system"); |
| |
| uint8_t factory[GPT_GUID_LEN] = GUID_FACTORY_CONFIG_VALUE; |
| ASSERT_TRUE(KnownGuid::NameToGuid("factory", guid), "factory not found"); |
| ASSERT_EQ(memcmp(guid, factory, GPT_GUID_LEN), 0, "Could not lookup factory"); |
| |
| uint8_t vbmeta[GPT_GUID_LEN] = GUID_VBMETA_B_VALUE; |
| ASSERT_TRUE(KnownGuid::NameToGuid("vbmeta_a", guid), "vbmeta_a not found"); |
| ASSERT_EQ(memcmp(guid, vbmeta, GPT_GUID_LEN), 0, "Could not lookup vbmeta_a"); |
| |
| END_TEST; |
| } |
| |
| // Litmus test for guid str to name conversions |
| bool GuidStrToNameTest() { |
| BEGIN_TEST; |
| |
| ASSERT_TRUE( |
| strcmp(KnownGuid::GuidStrToName("CAB6E88E-ABF3-4102-A07A-D4BB9BE3C1D3"), "cros-firmware"), |
| "No match for cros-firmware"); |
| |
| ASSERT_TRUE( |
| strcmp(KnownGuid::GuidStrToName("3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC"), "cros-rootfs"), |
| "No match for cros-rootfs"); |
| |
| ASSERT_TRUE( |
| strcmp(KnownGuid::GuidStrToName("41D0E340-57E3-954E-8C1E-17ECAC44CFF5"), "fuchsia-fvm"), |
| "No match for fuchsia-fvm"); |
| |
| END_TEST; |
| } |
| |
| BEGIN_TEST_CASE(libgpt_tests) |
| RUN_TEST_WRAP(CreateTest) |
| RUN_TEST_WRAP(FinalizeTest) |
| RUN_TEST_WRAP(SyncTest) |
| RUN_TEST_WRAP(RangeTest) |
| RUN_TEST_WRAP((AddPartitionTest<3, false>)) |
| RUN_TEST_WRAP((AddPartitionTest<20, true>)) |
| RUN_TEST_WRAP((RemovePartitionTest<12, 4, false>)) |
| RUN_TEST_WRAP((RemovePartitionTest<3, 2, true>)) |
| RUN_TEST_WRAP((RemovePartitionTest<11, 11, false>)) |
| RUN_TEST_WRAP((RemovePartitionAllTest<12, true>)) |
| RUN_TEST_WRAP((RemovePartitionAllTest<15, false>)) |
| RUN_TEST_WRAP((SetPartitionTypeTest<4, true>)) |
| RUN_TEST_WRAP((SetPartitionTypeTest<8, false>)) |
| RUN_TEST_WRAP((SetPartitionGuidTest<5, true>)) |
| RUN_TEST_WRAP((SetPartitionGuidTest<7, false>)) |
| RUN_TEST_WRAP((SetPartitionRangeTest<14, true, FindPartitionToExpand>)) |
| RUN_TEST_WRAP((SetPartitionRangeTest<18, false, FindPartitionToShrink>)) |
| RUN_TEST_WRAP((PartitionVisibilityTest<2, true>)) |
| RUN_TEST_WRAP((PartitionFlagsTest<1, true>)) |
| RUN_TEST_WRAP((DiffsTest<9>)) |
| RUN_TEST_MEDIUM(KnownGuidUniqueNameTest) |
| RUN_TEST_MEDIUM(KnownGuidUniqueGuidTest) |
| RUN_TEST_MEDIUM(KnownGuidUniqueStrTest) |
| RUN_TEST_MEDIUM(KnownGuidToStrTest) |
| END_TEST_CASE(libgpt_tests) |