| // 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 <fcntl.h> |
| #include <fidl/fuchsia.hardware.block/cpp/wire.h> |
| #include <lib/cksum.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/lib/storage/block_client/cpp/remote_block_device.h" |
| #include "src/storage/gpt/gpt.h" |
| |
| extern bool gUseRamDisk; |
| extern char gDevPath[PATH_MAX]; |
| extern unsigned int gRandSeed; |
| |
| namespace gpt { |
| namespace { |
| |
| using gpt::GptDevice; |
| using gpt::KnownGuid; |
| |
| 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(int fd, 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; |
| |
| for (uint64_t i = first; i <= last; i++) { |
| ASSERT_OK(block_client::SingleWriteBytes(fd, zero, sizeof(zero), block_size * i), |
| "Failed to pwrite"); |
| } |
| // fsync is not supported in rpc-server.cpp |
| // TODO(fxbug.dev/33099) to fix this |
| // ASSERT_EQ(fsync(fd), 0, "Failed to fsync"); |
| } |
| |
| // 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: |
| 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, (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; |
| } |
| |
| 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->fd_.get(), 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() { |
| std::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"); |
| ASSERT_OK(GptDevice::Create(fd_.get(), GetBlockSize(), GetBlockCount(), &gpt)); |
| gpt_ = std::move(gpt); |
| } |
| |
| // 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_]; |
| if (block_client::SingleReadBytes(fd_.get(), buff, blk_size_, 0) != 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); |
| } |
| |
| // 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() {} |
| |
| // Initialize a physical media. |
| void InitDisk(const char* disk_path) { |
| strlcpy(disk_path_, disk_path, sizeof(disk_path_)); |
| fbl::unique_fd fd(open(disk_path_, O_RDWR)); |
| ASSERT_TRUE(fd.is_valid(), "Could not open block device to fetch info"); |
| fdio_cpp::UnownedFdioCaller disk_caller(fd.get()); |
| auto info_res = |
| fidl::WireCall(disk_caller.borrow_as<fuchsia_hardware_block::Block>())->GetInfo(); |
| ASSERT_OK(info_res.status()); |
| ASSERT_OK(info_res.value().status); |
| |
| blk_size_ = info_res.value().info->block_size; |
| blk_count_ = info_res.value().info->block_count; |
| |
| ASSERT_GE(GetDiskSize(), kAcceptableMinimumSize, "Insufficient disk space for tests"); |
| fd_ = std::move(fd); |
| } |
| |
| // 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_; |
| |
| // Open file descriptor to block device. |
| fbl::unique_fd fd_; |
| |
| // 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 += (2 * kHeaderBlocks); |
| |
| // Two copies of entries array. |
| block_count += (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, |
| 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"); |
| } |
| |
| uint64_t ComputePerCopySize(uint64_t block_size) { |
| return block_size + (kPartitionCount * kEntrySize); |
| } |
| |
| uint64_t ComputePerCopyBlockCount(uint64_t block_size) { |
| return (ComputePerCopySize(block_size) + block_size - 1) / block_size; |
| } |
| |
| 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)); |
| std::unique_ptr<GptDevice> gpt; |
| ASSERT_OK(GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, |
| kBlockCount, &gpt)); |
| } |
| |
| TEST(GptDeviceLoad, SmallBlockSize) { |
| gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value(); |
| uint8_t blocks[MinimumBytesPerCopy(kBlockSize).value()] = {}; |
| memcpy(blocks, &header, sizeof(header)); |
| std::unique_ptr<GptDevice> gpt; |
| ASSERT_STATUS(ZX_ERR_INVALID_ARGS, |
| GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kHeaderSize - 1, |
| kBlockCount, &gpt)); |
| } |
| |
| TEST(GptDeviceLoad, NullGpt) { |
| gpt_header_t header = InitializePrimaryHeader(kBlockSize, kBlockCount).value(); |
| uint8_t blocks[MinimumBytesPerCopy(kBlockSize).value()] = {}; |
| memcpy(blocks, &header, sizeof(header)); |
| std::unique_ptr<GptDevice> gpt; |
| ASSERT_STATUS(ZX_ERR_INVALID_ARGS, |
| GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kHeaderSize, |
| kBlockCount, nullptr)); |
| } |
| |
| TEST(GptDeviceLoad, NullBuffer) { |
| std::unique_ptr<GptDevice> gpt; |
| ASSERT_STATUS(ZX_ERR_INVALID_ARGS, |
| GptDevice::Load(nullptr, MinimumBytesPerCopy(kBlockSize).value(), kHeaderSize, |
| kBlockCount, &gpt)); |
| } |
| |
| // 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)); |
| std::unique_ptr<GptDevice> gpt; |
| ASSERT_OK(GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, |
| kBlockCount, &gpt)); |
| } |
| |
| 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)); |
| std::unique_ptr<GptDevice> gpt; |
| ASSERT_STATUS(ZX_ERR_BUFFER_TOO_SMALL, |
| GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value() - 1, kBlockSize, |
| kBlockCount, &gpt)); |
| } |
| |
| 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)); |
| std::unique_ptr<GptDevice> gpt; |
| |
| ASSERT_STATUS(ZX_ERR_ALREADY_EXISTS, |
| GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, |
| kBlockCount, &gpt)); |
| } |
| |
| 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)); |
| std::unique_ptr<GptDevice> gpt; |
| |
| ASSERT_STATUS(ZX_ERR_ALREADY_EXISTS, |
| GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, |
| kBlockCount, &gpt)); |
| } |
| |
| 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)); |
| std::unique_ptr<GptDevice> gpt; |
| |
| ASSERT_STATUS(ZX_ERR_OUT_OF_RANGE, |
| GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, |
| kBlockCount, &gpt)); |
| } |
| |
| 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)); |
| std::unique_ptr<GptDevice> gpt; |
| ASSERT_STATUS(ZX_ERR_OUT_OF_RANGE, |
| GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, |
| kBlockCount, &gpt)); |
| } |
| |
| 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)); |
| std::unique_ptr<GptDevice> gpt; |
| ASSERT_STATUS(ZX_ERR_OUT_OF_RANGE, |
| GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, |
| kBlockCount, &gpt)); |
| } |
| |
| 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)); |
| std::unique_ptr<GptDevice> gpt; |
| EXPECT_OK(GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, |
| kBlockCount, &gpt)); |
| ASSERT_EQ(gpt->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], |
| MinimumBytesPerCopy(kBlockSize).value() - kBlockSize); |
| memcpy(blocks, &header, sizeof(header)); |
| std::unique_ptr<GptDevice> gpt; |
| EXPECT_OK(GptDevice::Load(blocks, MinimumBytesPerCopy(kBlockSize).value(), kBlockSize, |
| kBlockCount, &gpt)); |
| ASSERT_EQ(gpt->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("durable", std::nullopt, std::nullopt); |
| EXPECT_EQ(matches.size(), 1); |
| EXPECT_EQ(matches.front()->type_guid(), uuid::Uuid(GPT_DURABLE_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(), 14); |
| } |
| |
| 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); |
| } |
| |
| } // namespace gpt |