Merge "liblp: Refactor the partition table update API."

This commit is contained in:
Treehugger Robot 2018-07-11 23:44:13 +00:00 committed by Gerrit Code Review
commit 32960e54b4
3 changed files with 112 additions and 91 deletions

View File

@ -22,28 +22,26 @@
namespace android {
namespace fs_mgr {
// When flashing the initial logical partition layout, we also write geometry
// information at the start and end of the big physical partition. This helps
// locate metadata and backup metadata in the case of corruption or a failed
// update. For normal changes to the metadata, we never modify the geometry.
enum class SyncMode {
// Write geometry information.
Flash,
// Normal update of a single slot.
Update
};
// Place an initial partition table on the device. This will overwrite the
// existing geometry, and should not be used for normal partition table
// updates. False can be returned if the geometry is incompatible with the
// block device or an I/O error occurs.
bool FlashPartitionTable(const std::string& block_device, const LpMetadata& metadata,
uint32_t slot_number);
// Write the given partition table to the given block device, writing only
// copies according to the given sync mode.
//
// This will perform some verification, such that the device has enough space
// to store the metadata as well as all of its extents.
//
// 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);
// Update the partition table for a given metadata slot number. False is
// returned if an error occurs, which can include:
// - Invalid slot number.
// - I/O error.
// - Corrupt or missing metadata geometry on disk.
// - Incompatible geometry.
bool UpdatePartitionTable(const std::string& block_device, const LpMetadata& metadata,
uint32_t slot_number);
// These variants are for testing only. The path-based functions should be used
// for actual operation, so that open() is called with the correct flags.
bool FlashPartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number);
bool UpdatePartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number);
// Helper function to serialize geometry and metadata to a normal file, for
// flashing or debugging.

View File

@ -102,7 +102,7 @@ static unique_fd CreateFlashedDisk() {
if (!exported) {
return {};
}
if (!WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0)) {
if (!FlashPartitionTable(fd, *exported.get(), 0)) {
return {};
}
return fd;
@ -131,7 +131,7 @@ TEST(liblp, ExportDiskTooSmall) {
unique_fd fd = CreateFakeDisk();
ASSERT_GE(fd, 0);
EXPECT_FALSE(WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0));
EXPECT_FALSE(FlashPartitionTable(fd, *exported.get(), 0));
}
// Test the basics of flashing a partition and reading it back.
@ -146,7 +146,7 @@ TEST(liblp, FlashAndReadback) {
// Export and flash.
unique_ptr<LpMetadata> exported = builder->Export();
ASSERT_NE(exported, nullptr);
ASSERT_TRUE(WritePartitionTable(fd, *exported.get(), SyncMode::Flash, 0));
ASSERT_TRUE(FlashPartitionTable(fd, *exported.get(), 0));
// Read back. Note that some fields are only filled in during
// serialization, so exported and imported will not be identical. For
@ -195,7 +195,7 @@ TEST(liblp, UpdateAnyMetadataSlot) {
// 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));
ASSERT_TRUE(UpdatePartitionTable(fd, *imported.get(), 1));
// Read back the original slot, make sure it hasn't changed.
imported = ReadMetadata(fd, 0);
@ -231,7 +231,7 @@ TEST(liblp, InvalidMetadataSlot) {
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));
ASSERT_TRUE(UpdatePartitionTable(fd, *metadata.get(), i));
}
// Verify that we can't read unavailable slots.
@ -246,25 +246,25 @@ TEST(liblp, NoChangingGeometry) {
unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
ASSERT_TRUE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1));
ASSERT_TRUE(UpdatePartitionTable(fd, *imported.get(), 1));
imported->geometry.metadata_max_size += LP_SECTOR_SIZE;
ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1));
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 1));
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
imported->geometry.metadata_slot_count++;
ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1));
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 1));
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
imported->geometry.first_logical_sector++;
ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1));
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 1));
imported = ReadMetadata(fd, 0);
ASSERT_NE(imported, nullptr);
imported->geometry.last_logical_sector--;
ASSERT_FALSE(WritePartitionTable(fd, *imported.get(), SyncMode::Update, 1));
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 1));
}
// Test that changing one bit of metadata is enough to break the checksum.
@ -353,8 +353,8 @@ TEST(liblp, TooManyPartitions) {
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));
ASSERT_TRUE(FlashPartitionTable(fd, *exported.get(), 0));
ASSERT_TRUE(UpdatePartitionTable(fd, *exported.get(), 1));
// Check that adding one more partition overflows the metadata allotment.
partition = builder->AddPartition("final", TEST_GUID, LP_PARTITION_ATTR_NONE);
@ -364,7 +364,7 @@ TEST(liblp, TooManyPartitions) {
ASSERT_NE(exported, nullptr);
// The new table should be too large to be written.
ASSERT_FALSE(WritePartitionTable(fd, *exported.get(), SyncMode::Update, 1));
ASSERT_FALSE(UpdatePartitionTable(fd, *exported.get(), 1));
// Check that the first and last logical sectors weren't touched when we
// wrote this almost-full metadata.

View File

@ -73,8 +73,14 @@ static std::string SerializeMetadata(const LpMetadata& input) {
// Perform sanity checks so we don't accidentally overwrite valid metadata
// with potentially invalid metadata, or random partition data with metadata.
static bool ValidateGeometryAndMetadata(const LpMetadata& metadata, uint64_t blockdevice_size,
uint64_t metadata_size) {
static bool ValidateAndSerializeMetadata(int fd, const LpMetadata& metadata, std::string* blob) {
uint64_t blockdevice_size;
if (!GetDescriptorSize(fd, &blockdevice_size)) {
return false;
}
*blob = SerializeMetadata(metadata);
const LpMetadataHeader& header = metadata.header;
const LpMetadataGeometry& geometry = metadata.geometry;
// Validate the usable sector range.
@ -83,7 +89,7 @@ static bool ValidateGeometryAndMetadata(const LpMetadata& metadata, uint64_t blo
return false;
}
// Make sure we're writing within the space reserved.
if (metadata_size > geometry.metadata_max_size) {
if (blob->size() > geometry.metadata_max_size) {
LERROR << "Logical partition metadata is too large.";
return false;
}
@ -124,63 +130,14 @@ static bool ValidateGeometryAndMetadata(const LpMetadata& metadata, uint64_t blo
return true;
}
bool WritePartitionTable(int fd, const LpMetadata& metadata, SyncMode sync_mode,
uint32_t slot_number) {
uint64_t size;
if (!GetDescriptorSize(fd, &size)) {
return false;
}
const LpMetadataGeometry& geometry = metadata.geometry;
if (sync_mode != SyncMode::Flash) {
// Verify that the old geometry is identical. If it's not, then we've
// based this new metadata on invalid assumptions.
LpMetadataGeometry old_geometry;
if (!ReadLogicalPartitionGeometry(fd, &old_geometry)) {
return false;
}
if (!CompareGeometry(geometry, old_geometry)) {
LERROR << "Incompatible geometry in new logical partition metadata";
return false;
}
}
static bool WriteMetadata(int fd, const LpMetadataGeometry& geometry, uint32_t slot_number,
const std::string& blob) {
// Make sure we're writing to a valid metadata slot.
if (slot_number >= geometry.metadata_slot_count) {
LERROR << "Invalid logical partition metadata slot number.";
return false;
}
// Before writing geometry and/or logical partition tables, perform some
// basic checks that the geometry and tables are coherent, and will fit
// on the given block device.
std::string blob = SerializeMetadata(metadata);
if (!ValidateGeometryAndMetadata(metadata, size, blob.size())) {
return false;
}
// First write geometry if this is a flash operation. It gets written to
// the first and last 4096-byte regions of the device.
if (sync_mode == SyncMode::Flash) {
std::string blob = SerializeGeometry(metadata.geometry);
if (SeekFile64(fd, 0, SEEK_SET) < 0) {
PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset 0";
return false;
}
if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
PERROR << __PRETTY_FUNCTION__ << "write " << blob.size() << " bytes failed";
return false;
}
if (SeekFile64(fd, -LP_METADATA_GEOMETRY_SIZE, SEEK_END) < 0) {
PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << -LP_METADATA_GEOMETRY_SIZE;
return false;
}
if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
PERROR << __PRETTY_FUNCTION__ << "backup write " << blob.size() << " bytes failed";
return false;
}
}
// Write the primary copy of the metadata.
int64_t primary_offset = GetPrimaryMetadataOffset(geometry, slot_number);
if (SeekFile64(fd, primary_offset, SEEK_SET) < 0) {
@ -211,14 +168,80 @@ bool WritePartitionTable(int fd, const LpMetadata& metadata, SyncMode sync_mode,
return true;
}
bool WritePartitionTable(const char* block_device, const LpMetadata& metadata, SyncMode sync_mode,
bool FlashPartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number) {
// Before writing geometry and/or logical partition tables, perform some
// basic checks that the geometry and tables are coherent, and will fit
// on the given block device.
std::string metadata_blob;
if (!ValidateAndSerializeMetadata(fd, metadata, &metadata_blob)) {
return false;
}
// Write geometry to the first and last 4096 bytes of the device.
std::string blob = SerializeGeometry(metadata.geometry);
if (SeekFile64(fd, 0, SEEK_SET) < 0) {
PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset 0";
return false;
}
if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
PERROR << __PRETTY_FUNCTION__ << "write " << blob.size() << " bytes failed";
return false;
}
if (SeekFile64(fd, -LP_METADATA_GEOMETRY_SIZE, SEEK_END) < 0) {
PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << -LP_METADATA_GEOMETRY_SIZE;
return false;
}
if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
PERROR << __PRETTY_FUNCTION__ << "backup write " << blob.size() << " bytes failed";
return false;
}
// Write metadata to the correct slot, now that geometry is in place.
return WriteMetadata(fd, metadata.geometry, slot_number, metadata_blob);
}
bool UpdatePartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number) {
// Before writing geometry and/or logical partition tables, perform some
// basic checks that the geometry and tables are coherent, and will fit
// on the given block device.
std::string blob;
if (!ValidateAndSerializeMetadata(fd, metadata, &blob)) {
return false;
}
// Verify that the old geometry is identical. If it's not, then we might be
// writing a table that was built for a different device, so we must reject
// it.
const LpMetadataGeometry& geometry = metadata.geometry;
LpMetadataGeometry old_geometry;
if (!ReadLogicalPartitionGeometry(fd, &old_geometry)) {
return false;
}
if (!CompareGeometry(geometry, old_geometry)) {
LERROR << "Incompatible geometry in new logical partition metadata";
return false;
}
return WriteMetadata(fd, geometry, slot_number, blob);
}
bool FlashPartitionTable(const std::string& block_device, const LpMetadata& metadata,
uint32_t slot_number) {
android::base::unique_fd fd(open(block_device, O_RDWR | O_SYNC));
android::base::unique_fd fd(open(block_device.c_str(), O_RDWR | O_SYNC));
if (fd < 0) {
PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device;
return false;
}
return WritePartitionTable(fd, metadata, sync_mode, slot_number);
return FlashPartitionTable(fd, metadata, slot_number);
}
bool UpdatePartitionTable(const std::string& block_device, const LpMetadata& metadata,
uint32_t slot_number) {
android::base::unique_fd fd(open(block_device.c_str(), O_RDWR | O_SYNC));
if (fd < 0) {
PERROR << __PRETTY_FUNCTION__ << "open failed: " << block_device;
return false;
}
return UpdatePartitionTable(fd, metadata, slot_number);
}
bool WriteToImageFile(int fd, const LpMetadata& input) {