Loading fs_mgr/liblp/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ cc_test { ], srcs: [ "builder_test.cpp", "io_test.cpp", "utility_test.cpp", ], } fs_mgr/liblp/builder_test.cpp +18 −0 Original line number Diff line number Diff line Loading @@ -306,3 +306,21 @@ TEST(liblp, ExportInvalidGuid) { unique_ptr<LpMetadata> exported = builder->Export(); EXPECT_EQ(exported, nullptr); } TEST(liblp, MetadataTooLarge) { static const size_t kDiskSize = 128 * 1024; static const size_t kMetadataSize = 64 * 1024; // No space to store metadata + geometry. unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // No space to store metadata + geometry + one free sector. builder = MetadataBuilder::New(kDiskSize + LP_METADATA_GEOMETRY_SIZE * 2, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // Space for metadata + geometry + one free sector. builder = MetadataBuilder::New(kDiskSize + LP_METADATA_GEOMETRY_SIZE * 2 + LP_SECTOR_SIZE, kMetadataSize, 1); EXPECT_NE(builder, nullptr); } fs_mgr/liblp/include/liblp/reader.h +3 −0 Original line number Diff line number Diff line Loading @@ -29,13 +29,16 @@ namespace fs_mgr { // Read logical partition metadata from its predetermined location on a block // device. If readback fails, we also attempt to load from a backup copy. std::unique_ptr<LpMetadata> ReadMetadata(const char* block_device, uint32_t slot_number); std::unique_ptr<LpMetadata> ReadMetadata(int fd, uint32_t slot_number); // Read and validate the logical partition geometry from a block device. bool ReadLogicalPartitionGeometry(const char* block_device, LpMetadataGeometry* geometry); bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry); // Read logical partition metadata from an image file that was created with // WriteToImageFile(). std::unique_ptr<LpMetadata> ReadFromImageFile(const char* file); std::unique_ptr<LpMetadata> ReadFromImageFile(int fd); } // namespace fs_mgr } // namespace android Loading fs_mgr/liblp/include/liblp/writer.h +3 −0 Original line number Diff line number Diff line Loading @@ -42,10 +42,13 @@ enum class SyncMode { // The slot number indicates which metadata slot to use. bool WritePartitionTable(const char* block_device, const LpMetadata& metadata, SyncMode sync_mode, uint32_t slot_number); bool WritePartitionTable(int fd, const LpMetadata& metadata, SyncMode sync_mode, uint32_t slot_number); // Helper function to serialize geometry and metadata to a normal file, for // flashing or debugging. bool WriteToImageFile(const char* file, const LpMetadata& metadata); bool WriteToImageFile(int fd, const LpMetadata& metadata); } // namespace fs_mgr } // namespace android Loading fs_mgr/liblp/io_test.cpp 0 → 100644 +395 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <fcntl.h> #include <linux/memfd.h> #include <stdio.h> #include <sys/syscall.h> #include <android-base/file.h> #include <android-base/unique_fd.h> #include <gtest/gtest.h> #include <liblp/builder.h> #include <liblp/reader.h> #include <liblp/writer.h> #include "utility.h" using namespace std; using namespace android::fs_mgr; using unique_fd = android::base::unique_fd; // Our tests assume a 128KiB disk with two 512 byte metadata slots. static const size_t kDiskSize = 131072; static const size_t kMetadataSize = 512; static const size_t kMetadataSlots = 2; static const char* TEST_GUID_BASE = "A799D1D6-669F-41D8-A3F0-EBB7572D830"; static const char* TEST_GUID = "A799D1D6-669F-41D8-A3F0-EBB7572D8302"; // Helper function for creating an in-memory file descriptor. This lets us // simulate read/writing logical partition metadata as if we had a block device // for a physical partition. static unique_fd CreateFakeDisk(off_t size) { unique_fd fd(syscall(__NR_memfd_create, "fake_disk", MFD_ALLOW_SEALING)); if (fd < 0) { perror("memfd_create"); return {}; } if (ftruncate(fd, size) < 0) { perror("ftruncate"); return {}; } // Prevent anything from accidentally growing/shrinking the file, as it // would not be allowed on an actual partition. if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) { perror("fcntl"); return {}; } // Write garbage to the "disk" so we can tell what has been zeroed or not. unique_ptr<uint8_t[]> buffer = make_unique<uint8_t[]>(size); memset(buffer.get(), 0xcc, size); if (!android::base::WriteFully(fd, buffer.get(), size)) { return {}; } return fd; } // Create a disk of the default size. static unique_fd CreateFakeDisk() { return CreateFakeDisk(kDiskSize); } // Create a MetadataBuilder around some default sizes. static unique_ptr<MetadataBuilder> CreateDefaultBuilder() { unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize, kMetadataSize, kMetadataSlots); return builder; } static bool AddDefaultPartitions(MetadataBuilder* builder) { Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_NONE); if (!system) { return false; } return builder->GrowPartition(system, 24 * 1024); } // Create a temporary disk and flash it with the default partition setup. static unique_fd CreateFlashedDisk() { unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder(); if (!builder || !AddDefaultPartitions(builder.get())) { return {}; } unique_fd fd = CreateFakeDisk(); if (fd < 0) { return {}; } // Export and flash. unique_ptr<LpMetadata> exported = builder->Export(); if (!exported) { return {}; } if (!WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0)) { return {}; } return fd; } // Test that our CreateFakeDisk() function works. TEST(liblp, CreateFakeDisk) { unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); uint64_t size; ASSERT_TRUE(GetDescriptorSize(fd, &size)); ASSERT_EQ(size, kDiskSize); } // Flashing metadata should not work if the metadata was created for a larger // disk than the destination disk. TEST(liblp, ExportDiskTooSmall) { unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize + 1024, 512, 2); ASSERT_NE(builder, nullptr); unique_ptr<LpMetadata> exported = builder->Export(); ASSERT_NE(exported, nullptr); // A larger geometry should fail to flash, since there won't be enough // space to store the logical partition range that was specified. unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); EXPECT_FALSE(WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0)); } // Test the basics of flashing a partition and reading it back. TEST(liblp, FlashAndReadback) { unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); ASSERT_TRUE(AddDefaultPartitions(builder.get())); unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); // Export and flash. unique_ptr<LpMetadata> exported = builder->Export(); ASSERT_NE(exported, nullptr); ASSERT_TRUE(WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0)); // Read back. Note that some fields are only filled in during // serialization, so exported and imported will not be identical. For // example, table sizes and checksums are computed in WritePartitionTable. // Therefore we check on a field-by-field basis. unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); // Check geometry and header. EXPECT_EQ(exported->geometry.metadata_max_size, imported->geometry.metadata_max_size); EXPECT_EQ(exported->geometry.metadata_slot_count, imported->geometry.metadata_slot_count); EXPECT_EQ(exported->geometry.first_logical_sector, imported->geometry.first_logical_sector); EXPECT_EQ(exported->geometry.last_logical_sector, imported->geometry.last_logical_sector); EXPECT_EQ(exported->header.major_version, imported->header.major_version); EXPECT_EQ(exported->header.minor_version, imported->header.minor_version); EXPECT_EQ(exported->header.header_size, imported->header.header_size); // Check partition tables. ASSERT_EQ(exported->partitions.size(), imported->partitions.size()); EXPECT_EQ(GetPartitionName(exported->partitions[0]), GetPartitionName(imported->partitions[0])); EXPECT_EQ(GetPartitionGuid(exported->partitions[0]), GetPartitionGuid(imported->partitions[0])); EXPECT_EQ(exported->partitions[0].attributes, imported->partitions[0].attributes); EXPECT_EQ(exported->partitions[0].first_extent_index, imported->partitions[0].first_extent_index); EXPECT_EQ(exported->partitions[0].num_extents, imported->partitions[0].num_extents); // Check extent tables. ASSERT_EQ(exported->extents.size(), imported->extents.size()); EXPECT_EQ(exported->extents[0].num_sectors, imported->extents[0].num_sectors); EXPECT_EQ(exported->extents[0].target_type, imported->extents[0].target_type); EXPECT_EQ(exported->extents[0].target_data, imported->extents[0].target_data); } // Test that we can update metadata slots without disturbing others. TEST(liblp, UpdateAnyMetadataSlot) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); ASSERT_EQ(imported->partitions.size(), 1); EXPECT_EQ(GetPartitionName(imported->partitions[0]), "system"); // Verify that we can't read unwritten metadata. ASSERT_EQ(ReadMetadata(fd, 1), nullptr); // Change the name before writing to the next slot. strncpy(imported->partitions[0].name, "vendor", sizeof(imported->partitions[0].name)); ASSERT_TRUE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); // Read back the original slot, make sure it hasn't changed. imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); ASSERT_EQ(imported->partitions.size(), 1); EXPECT_EQ(GetPartitionName(imported->partitions[0]), "system"); // Now read back the new slot, and verify that it has a different name. imported = ReadMetadata(fd, 1); ASSERT_NE(imported, nullptr); ASSERT_EQ(imported->partitions.size(), 1); EXPECT_EQ(GetPartitionName(imported->partitions[0]), "vendor"); // Verify that we didn't overwrite anything in the logical paritition area. // We expect the disk to be filled with 0xcc on creation so we can read // this back and compare it. char expected[LP_SECTOR_SIZE]; memset(expected, 0xcc, sizeof(expected)); for (uint64_t i = imported->geometry.first_logical_sector; i <= imported->geometry.last_logical_sector; i++) { char buffer[LP_SECTOR_SIZE]; ASSERT_GE(lseek(fd, i * LP_SECTOR_SIZE, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer))); ASSERT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0); } } TEST(liblp, InvalidMetadataSlot) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); // Make sure all slots are filled. unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0); ASSERT_NE(metadata, nullptr); for (uint32_t i = 1; i < kMetadataSlots; i++) { ASSERT_TRUE(WritePartitionTable(fd, *metadata.get(), SyncMode::Update, i)); } // Verify that we can't read unavailable slots. EXPECT_EQ(ReadMetadata(fd, kMetadataSlots), nullptr); } // Test that updating a metadata slot does not allow it to be computed based // on mismatching geometry. TEST(liblp, NoChangingGeometry) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); ASSERT_TRUE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); imported->geometry.metadata_max_size += LP_SECTOR_SIZE; ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); imported->geometry.metadata_slot_count++; ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); imported->geometry.first_logical_sector++; ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); imported->geometry.last_logical_sector--; ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); } // Test that changing one bit of metadata is enough to break the checksum. TEST(liblp, BitFlipGeometry) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); LpMetadataGeometry geometry; ASSERT_GE(lseek(fd, 0, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fd, &geometry, sizeof(geometry))); LpMetadataGeometry bad_geometry = geometry; bad_geometry.metadata_slot_count++; ASSERT_TRUE(android::base::WriteFully(fd, &bad_geometry, sizeof(bad_geometry))); unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0); ASSERT_NE(metadata, nullptr); EXPECT_EQ(metadata->geometry.metadata_slot_count, 2); } TEST(liblp, ReadBackupGeometry) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); char corruption[LP_METADATA_GEOMETRY_SIZE]; memset(corruption, 0xff, sizeof(corruption)); // Corrupt the first 4096 bytes of the disk. ASSERT_GE(lseek(fd, 0, SEEK_SET), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_NE(ReadMetadata(fd, 0), nullptr); // Corrupt the last 4096 bytes too. ASSERT_GE(lseek(fd, -LP_METADATA_GEOMETRY_SIZE, SEEK_END), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_EQ(ReadMetadata(fd, 0), nullptr); } TEST(liblp, ReadBackupMetadata) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0); char corruption[kMetadataSize]; memset(corruption, 0xff, sizeof(corruption)); ASSERT_GE(lseek(fd, LP_METADATA_GEOMETRY_SIZE, SEEK_SET), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_NE(ReadMetadata(fd, 0), nullptr); off_t offset = LP_METADATA_GEOMETRY_SIZE + kMetadataSize * 2; // Corrupt the backup metadata. ASSERT_GE(lseek(fd, -offset, SEEK_END), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_EQ(ReadMetadata(fd, 0), nullptr); } // Test that we don't attempt to write metadata if it would overflow its // reserved space. TEST(liblp, TooManyPartitions) { unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); // Compute the maximum number of partitions we can fit in 1024 bytes of metadata. size_t max_partitions = (kMetadataSize - sizeof(LpMetadataHeader)) / sizeof(LpMetadataPartition); EXPECT_LT(max_partitions, 10); // Add this number of partitions. Partition* partition = nullptr; for (size_t i = 0; i < max_partitions; i++) { std::string guid = std::string(TEST_GUID) + to_string(i); partition = builder->AddPartition(to_string(i), TEST_GUID, LP_PARTITION_ATTR_NONE); ASSERT_NE(partition, nullptr); } ASSERT_NE(partition, nullptr); // Add one extent to any partition to fill up more space - we're at 508 // bytes after this, out of 512. ASSERT_TRUE(builder->GrowPartition(partition, 1024)); unique_ptr<LpMetadata> exported = builder->Export(); ASSERT_NE(exported, nullptr); unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); // Check that we are able to write our table. ASSERT_TRUE(WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0)); ASSERT_TRUE(WritePartitionTable(fd, *exported.get(), SyncMode::Update, 1)); // Check that adding one more partition overflows the metadata allotment. partition = builder->AddPartition("final", TEST_GUID, LP_PARTITION_ATTR_NONE); EXPECT_NE(partition, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); // The new table should be too large to be written. ASSERT_FALSE(WritePartitionTable(fd, *exported.get(), SyncMode::Update, 1)); // Check that the first and last logical sectors weren't touched when we // wrote this almost-full metadata. char expected[LP_SECTOR_SIZE]; memset(expected, 0xcc, sizeof(expected)); char buffer[LP_SECTOR_SIZE]; ASSERT_GE(lseek(fd, exported->geometry.first_logical_sector * LP_SECTOR_SIZE, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer))); EXPECT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0); ASSERT_GE(lseek(fd, exported->geometry.last_logical_sector * LP_SECTOR_SIZE, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer))); EXPECT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0); } // Test that we can read and write image files. TEST(liblp, ImageFiles) { unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); ASSERT_TRUE(AddDefaultPartitions(builder.get())); unique_ptr<LpMetadata> exported = builder->Export(); unique_fd fd(syscall(__NR_memfd_create, "image_file", 0)); ASSERT_GE(fd, 0); ASSERT_TRUE(WriteToImageFile(fd, *exported.get())); unique_ptr<LpMetadata> imported = ReadFromImageFile(fd); ASSERT_NE(imported, nullptr); } Loading
fs_mgr/liblp/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ cc_test { ], srcs: [ "builder_test.cpp", "io_test.cpp", "utility_test.cpp", ], }
fs_mgr/liblp/builder_test.cpp +18 −0 Original line number Diff line number Diff line Loading @@ -306,3 +306,21 @@ TEST(liblp, ExportInvalidGuid) { unique_ptr<LpMetadata> exported = builder->Export(); EXPECT_EQ(exported, nullptr); } TEST(liblp, MetadataTooLarge) { static const size_t kDiskSize = 128 * 1024; static const size_t kMetadataSize = 64 * 1024; // No space to store metadata + geometry. unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // No space to store metadata + geometry + one free sector. builder = MetadataBuilder::New(kDiskSize + LP_METADATA_GEOMETRY_SIZE * 2, kMetadataSize, 1); EXPECT_EQ(builder, nullptr); // Space for metadata + geometry + one free sector. builder = MetadataBuilder::New(kDiskSize + LP_METADATA_GEOMETRY_SIZE * 2 + LP_SECTOR_SIZE, kMetadataSize, 1); EXPECT_NE(builder, nullptr); }
fs_mgr/liblp/include/liblp/reader.h +3 −0 Original line number Diff line number Diff line Loading @@ -29,13 +29,16 @@ namespace fs_mgr { // Read logical partition metadata from its predetermined location on a block // device. If readback fails, we also attempt to load from a backup copy. std::unique_ptr<LpMetadata> ReadMetadata(const char* block_device, uint32_t slot_number); std::unique_ptr<LpMetadata> ReadMetadata(int fd, uint32_t slot_number); // Read and validate the logical partition geometry from a block device. bool ReadLogicalPartitionGeometry(const char* block_device, LpMetadataGeometry* geometry); bool ReadLogicalPartitionGeometry(int fd, LpMetadataGeometry* geometry); // Read logical partition metadata from an image file that was created with // WriteToImageFile(). std::unique_ptr<LpMetadata> ReadFromImageFile(const char* file); std::unique_ptr<LpMetadata> ReadFromImageFile(int fd); } // namespace fs_mgr } // namespace android Loading
fs_mgr/liblp/include/liblp/writer.h +3 −0 Original line number Diff line number Diff line Loading @@ -42,10 +42,13 @@ enum class SyncMode { // The slot number indicates which metadata slot to use. bool WritePartitionTable(const char* block_device, const LpMetadata& metadata, SyncMode sync_mode, uint32_t slot_number); bool WritePartitionTable(int fd, const LpMetadata& metadata, SyncMode sync_mode, uint32_t slot_number); // Helper function to serialize geometry and metadata to a normal file, for // flashing or debugging. bool WriteToImageFile(const char* file, const LpMetadata& metadata); bool WriteToImageFile(int fd, const LpMetadata& metadata); } // namespace fs_mgr } // namespace android Loading
fs_mgr/liblp/io_test.cpp 0 → 100644 +395 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <fcntl.h> #include <linux/memfd.h> #include <stdio.h> #include <sys/syscall.h> #include <android-base/file.h> #include <android-base/unique_fd.h> #include <gtest/gtest.h> #include <liblp/builder.h> #include <liblp/reader.h> #include <liblp/writer.h> #include "utility.h" using namespace std; using namespace android::fs_mgr; using unique_fd = android::base::unique_fd; // Our tests assume a 128KiB disk with two 512 byte metadata slots. static const size_t kDiskSize = 131072; static const size_t kMetadataSize = 512; static const size_t kMetadataSlots = 2; static const char* TEST_GUID_BASE = "A799D1D6-669F-41D8-A3F0-EBB7572D830"; static const char* TEST_GUID = "A799D1D6-669F-41D8-A3F0-EBB7572D8302"; // Helper function for creating an in-memory file descriptor. This lets us // simulate read/writing logical partition metadata as if we had a block device // for a physical partition. static unique_fd CreateFakeDisk(off_t size) { unique_fd fd(syscall(__NR_memfd_create, "fake_disk", MFD_ALLOW_SEALING)); if (fd < 0) { perror("memfd_create"); return {}; } if (ftruncate(fd, size) < 0) { perror("ftruncate"); return {}; } // Prevent anything from accidentally growing/shrinking the file, as it // would not be allowed on an actual partition. if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) { perror("fcntl"); return {}; } // Write garbage to the "disk" so we can tell what has been zeroed or not. unique_ptr<uint8_t[]> buffer = make_unique<uint8_t[]>(size); memset(buffer.get(), 0xcc, size); if (!android::base::WriteFully(fd, buffer.get(), size)) { return {}; } return fd; } // Create a disk of the default size. static unique_fd CreateFakeDisk() { return CreateFakeDisk(kDiskSize); } // Create a MetadataBuilder around some default sizes. static unique_ptr<MetadataBuilder> CreateDefaultBuilder() { unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize, kMetadataSize, kMetadataSlots); return builder; } static bool AddDefaultPartitions(MetadataBuilder* builder) { Partition* system = builder->AddPartition("system", TEST_GUID, LP_PARTITION_ATTR_NONE); if (!system) { return false; } return builder->GrowPartition(system, 24 * 1024); } // Create a temporary disk and flash it with the default partition setup. static unique_fd CreateFlashedDisk() { unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder(); if (!builder || !AddDefaultPartitions(builder.get())) { return {}; } unique_fd fd = CreateFakeDisk(); if (fd < 0) { return {}; } // Export and flash. unique_ptr<LpMetadata> exported = builder->Export(); if (!exported) { return {}; } if (!WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0)) { return {}; } return fd; } // Test that our CreateFakeDisk() function works. TEST(liblp, CreateFakeDisk) { unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); uint64_t size; ASSERT_TRUE(GetDescriptorSize(fd, &size)); ASSERT_EQ(size, kDiskSize); } // Flashing metadata should not work if the metadata was created for a larger // disk than the destination disk. TEST(liblp, ExportDiskTooSmall) { unique_ptr<MetadataBuilder> builder = MetadataBuilder::New(kDiskSize + 1024, 512, 2); ASSERT_NE(builder, nullptr); unique_ptr<LpMetadata> exported = builder->Export(); ASSERT_NE(exported, nullptr); // A larger geometry should fail to flash, since there won't be enough // space to store the logical partition range that was specified. unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); EXPECT_FALSE(WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0)); } // Test the basics of flashing a partition and reading it back. TEST(liblp, FlashAndReadback) { unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); ASSERT_TRUE(AddDefaultPartitions(builder.get())); unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); // Export and flash. unique_ptr<LpMetadata> exported = builder->Export(); ASSERT_NE(exported, nullptr); ASSERT_TRUE(WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0)); // Read back. Note that some fields are only filled in during // serialization, so exported and imported will not be identical. For // example, table sizes and checksums are computed in WritePartitionTable. // Therefore we check on a field-by-field basis. unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); // Check geometry and header. EXPECT_EQ(exported->geometry.metadata_max_size, imported->geometry.metadata_max_size); EXPECT_EQ(exported->geometry.metadata_slot_count, imported->geometry.metadata_slot_count); EXPECT_EQ(exported->geometry.first_logical_sector, imported->geometry.first_logical_sector); EXPECT_EQ(exported->geometry.last_logical_sector, imported->geometry.last_logical_sector); EXPECT_EQ(exported->header.major_version, imported->header.major_version); EXPECT_EQ(exported->header.minor_version, imported->header.minor_version); EXPECT_EQ(exported->header.header_size, imported->header.header_size); // Check partition tables. ASSERT_EQ(exported->partitions.size(), imported->partitions.size()); EXPECT_EQ(GetPartitionName(exported->partitions[0]), GetPartitionName(imported->partitions[0])); EXPECT_EQ(GetPartitionGuid(exported->partitions[0]), GetPartitionGuid(imported->partitions[0])); EXPECT_EQ(exported->partitions[0].attributes, imported->partitions[0].attributes); EXPECT_EQ(exported->partitions[0].first_extent_index, imported->partitions[0].first_extent_index); EXPECT_EQ(exported->partitions[0].num_extents, imported->partitions[0].num_extents); // Check extent tables. ASSERT_EQ(exported->extents.size(), imported->extents.size()); EXPECT_EQ(exported->extents[0].num_sectors, imported->extents[0].num_sectors); EXPECT_EQ(exported->extents[0].target_type, imported->extents[0].target_type); EXPECT_EQ(exported->extents[0].target_data, imported->extents[0].target_data); } // Test that we can update metadata slots without disturbing others. TEST(liblp, UpdateAnyMetadataSlot) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); ASSERT_EQ(imported->partitions.size(), 1); EXPECT_EQ(GetPartitionName(imported->partitions[0]), "system"); // Verify that we can't read unwritten metadata. ASSERT_EQ(ReadMetadata(fd, 1), nullptr); // Change the name before writing to the next slot. strncpy(imported->partitions[0].name, "vendor", sizeof(imported->partitions[0].name)); ASSERT_TRUE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); // Read back the original slot, make sure it hasn't changed. imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); ASSERT_EQ(imported->partitions.size(), 1); EXPECT_EQ(GetPartitionName(imported->partitions[0]), "system"); // Now read back the new slot, and verify that it has a different name. imported = ReadMetadata(fd, 1); ASSERT_NE(imported, nullptr); ASSERT_EQ(imported->partitions.size(), 1); EXPECT_EQ(GetPartitionName(imported->partitions[0]), "vendor"); // Verify that we didn't overwrite anything in the logical paritition area. // We expect the disk to be filled with 0xcc on creation so we can read // this back and compare it. char expected[LP_SECTOR_SIZE]; memset(expected, 0xcc, sizeof(expected)); for (uint64_t i = imported->geometry.first_logical_sector; i <= imported->geometry.last_logical_sector; i++) { char buffer[LP_SECTOR_SIZE]; ASSERT_GE(lseek(fd, i * LP_SECTOR_SIZE, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer))); ASSERT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0); } } TEST(liblp, InvalidMetadataSlot) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); // Make sure all slots are filled. unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0); ASSERT_NE(metadata, nullptr); for (uint32_t i = 1; i < kMetadataSlots; i++) { ASSERT_TRUE(WritePartitionTable(fd, *metadata.get(), SyncMode::Update, i)); } // Verify that we can't read unavailable slots. EXPECT_EQ(ReadMetadata(fd, kMetadataSlots), nullptr); } // Test that updating a metadata slot does not allow it to be computed based // on mismatching geometry. TEST(liblp, NoChangingGeometry) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); ASSERT_TRUE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); imported->geometry.metadata_max_size += LP_SECTOR_SIZE; ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); imported->geometry.metadata_slot_count++; ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); imported->geometry.first_logical_sector++; ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); imported = ReadMetadata(fd, 0); ASSERT_NE(imported, nullptr); imported->geometry.last_logical_sector--; ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1)); } // Test that changing one bit of metadata is enough to break the checksum. TEST(liblp, BitFlipGeometry) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); LpMetadataGeometry geometry; ASSERT_GE(lseek(fd, 0, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fd, &geometry, sizeof(geometry))); LpMetadataGeometry bad_geometry = geometry; bad_geometry.metadata_slot_count++; ASSERT_TRUE(android::base::WriteFully(fd, &bad_geometry, sizeof(bad_geometry))); unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0); ASSERT_NE(metadata, nullptr); EXPECT_EQ(metadata->geometry.metadata_slot_count, 2); } TEST(liblp, ReadBackupGeometry) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); char corruption[LP_METADATA_GEOMETRY_SIZE]; memset(corruption, 0xff, sizeof(corruption)); // Corrupt the first 4096 bytes of the disk. ASSERT_GE(lseek(fd, 0, SEEK_SET), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_NE(ReadMetadata(fd, 0), nullptr); // Corrupt the last 4096 bytes too. ASSERT_GE(lseek(fd, -LP_METADATA_GEOMETRY_SIZE, SEEK_END), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_EQ(ReadMetadata(fd, 0), nullptr); } TEST(liblp, ReadBackupMetadata) { unique_fd fd = CreateFlashedDisk(); ASSERT_GE(fd, 0); unique_ptr<LpMetadata> metadata = ReadMetadata(fd, 0); char corruption[kMetadataSize]; memset(corruption, 0xff, sizeof(corruption)); ASSERT_GE(lseek(fd, LP_METADATA_GEOMETRY_SIZE, SEEK_SET), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_NE(ReadMetadata(fd, 0), nullptr); off_t offset = LP_METADATA_GEOMETRY_SIZE + kMetadataSize * 2; // Corrupt the backup metadata. ASSERT_GE(lseek(fd, -offset, SEEK_END), 0); ASSERT_TRUE(android::base::WriteFully(fd, corruption, sizeof(corruption))); EXPECT_EQ(ReadMetadata(fd, 0), nullptr); } // Test that we don't attempt to write metadata if it would overflow its // reserved space. TEST(liblp, TooManyPartitions) { unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); // Compute the maximum number of partitions we can fit in 1024 bytes of metadata. size_t max_partitions = (kMetadataSize - sizeof(LpMetadataHeader)) / sizeof(LpMetadataPartition); EXPECT_LT(max_partitions, 10); // Add this number of partitions. Partition* partition = nullptr; for (size_t i = 0; i < max_partitions; i++) { std::string guid = std::string(TEST_GUID) + to_string(i); partition = builder->AddPartition(to_string(i), TEST_GUID, LP_PARTITION_ATTR_NONE); ASSERT_NE(partition, nullptr); } ASSERT_NE(partition, nullptr); // Add one extent to any partition to fill up more space - we're at 508 // bytes after this, out of 512. ASSERT_TRUE(builder->GrowPartition(partition, 1024)); unique_ptr<LpMetadata> exported = builder->Export(); ASSERT_NE(exported, nullptr); unique_fd fd = CreateFakeDisk(); ASSERT_GE(fd, 0); // Check that we are able to write our table. ASSERT_TRUE(WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0)); ASSERT_TRUE(WritePartitionTable(fd, *exported.get(), SyncMode::Update, 1)); // Check that adding one more partition overflows the metadata allotment. partition = builder->AddPartition("final", TEST_GUID, LP_PARTITION_ATTR_NONE); EXPECT_NE(partition, nullptr); exported = builder->Export(); ASSERT_NE(exported, nullptr); // The new table should be too large to be written. ASSERT_FALSE(WritePartitionTable(fd, *exported.get(), SyncMode::Update, 1)); // Check that the first and last logical sectors weren't touched when we // wrote this almost-full metadata. char expected[LP_SECTOR_SIZE]; memset(expected, 0xcc, sizeof(expected)); char buffer[LP_SECTOR_SIZE]; ASSERT_GE(lseek(fd, exported->geometry.first_logical_sector * LP_SECTOR_SIZE, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer))); EXPECT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0); ASSERT_GE(lseek(fd, exported->geometry.last_logical_sector * LP_SECTOR_SIZE, SEEK_SET), 0); ASSERT_TRUE(android::base::ReadFully(fd, buffer, sizeof(buffer))); EXPECT_EQ(memcmp(expected, buffer, LP_SECTOR_SIZE), 0); } // Test that we can read and write image files. TEST(liblp, ImageFiles) { unique_ptr<MetadataBuilder> builder = CreateDefaultBuilder(); ASSERT_NE(builder, nullptr); ASSERT_TRUE(AddDefaultPartitions(builder.get())); unique_ptr<LpMetadata> exported = builder->Export(); unique_fd fd(syscall(__NR_memfd_create, "image_file", 0)); ASSERT_GE(fd, 0); ASSERT_TRUE(WriteToImageFile(fd, *exported.get())); unique_ptr<LpMetadata> imported = ReadFromImageFile(fd); ASSERT_NE(imported, nullptr); }