liblp: Make it easier to test UpdatePartitionTable.
This change makes the internal UpdatePartitionTable function more testable by parameterizing its write functions. It also adds two tests, one of which exposes a flaw in the current implementation. Bug: 79173901 Test: liblp_test gtest Change-Id: I3c4112794b97d577a27f035baeac2d42ac75f552
This commit is contained in:
parent
21671eda3e
commit
451694e29d
4 changed files with 105 additions and 6 deletions
|
@ -46,6 +46,9 @@ cc_library_static {
|
|||
cc_test {
|
||||
name: "liblp_test",
|
||||
defaults: ["fs_mgr_defaults"],
|
||||
cppflags: [
|
||||
"-Wno-unused-parameter",
|
||||
],
|
||||
static_libs: [
|
||||
"libbase",
|
||||
"liblog",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#ifndef LIBLP_WRITER_H
|
||||
#define LIBLP_WRITER_H
|
||||
|
||||
#include <functional>
|
||||
#include "metadata_format.h"
|
||||
|
||||
namespace android {
|
||||
|
@ -43,6 +44,9 @@ bool UpdatePartitionTable(const std::string& block_device, const LpMetadata& met
|
|||
bool FlashPartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number);
|
||||
bool UpdatePartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number);
|
||||
|
||||
bool UpdatePartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number,
|
||||
std::function<bool(int, const std::string&)> writer);
|
||||
|
||||
// Helper function to serialize geometry and metadata to a normal file, for
|
||||
// flashing or debugging.
|
||||
bool WriteToImageFile(const char* file, const LpMetadata& metadata);
|
||||
|
|
|
@ -393,3 +393,85 @@ TEST(liblp, ImageFiles) {
|
|||
unique_ptr<LpMetadata> imported = ReadFromImageFile(fd);
|
||||
ASSERT_NE(imported, nullptr);
|
||||
}
|
||||
|
||||
class BadWriter {
|
||||
public:
|
||||
// When requested, write garbage instead of the requested bytes, then
|
||||
// return false.
|
||||
bool operator()(int fd, const std::string& blob) {
|
||||
if (++write_count_ == fail_on_write_) {
|
||||
std::unique_ptr<char[]> new_data = std::make_unique<char[]>(blob.size());
|
||||
memset(new_data.get(), 0xe5, blob.size());
|
||||
EXPECT_TRUE(android::base::WriteFully(fd, new_data.get(), blob.size()));
|
||||
return false;
|
||||
} else {
|
||||
return android::base::WriteFully(fd, blob.data(), blob.size());
|
||||
}
|
||||
}
|
||||
void FailOnWrite(int number) {
|
||||
fail_on_write_ = number;
|
||||
write_count_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
int fail_on_write_ = 0;
|
||||
int write_count_ = 0;
|
||||
};
|
||||
|
||||
// Test that an interrupted flash operation on the "primary" copy of metadata
|
||||
// is not fatal.
|
||||
TEST(liblp, FlashPrimaryMetadataFailure) {
|
||||
// Initial state.
|
||||
unique_fd fd = CreateFlashedDisk();
|
||||
ASSERT_GE(fd, 0);
|
||||
|
||||
BadWriter writer;
|
||||
|
||||
// Read and write it back.
|
||||
writer.FailOnWrite(1);
|
||||
unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
|
||||
ASSERT_NE(imported, nullptr);
|
||||
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 0, writer));
|
||||
|
||||
// We should still be able to read the backup copy.
|
||||
imported = ReadMetadata(fd, 0);
|
||||
ASSERT_NE(imported, nullptr);
|
||||
|
||||
// Flash again, this time fail the backup copy. We should still be able
|
||||
// to read the primary.
|
||||
writer.FailOnWrite(2);
|
||||
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 0, writer));
|
||||
imported = ReadMetadata(fd, 0);
|
||||
ASSERT_NE(imported, nullptr);
|
||||
}
|
||||
|
||||
// Test that an interrupted flash operation on the "backup" copy of metadata
|
||||
// is not fatal.
|
||||
TEST(liblp, FlashBackupMetadataFailure) {
|
||||
// Initial state.
|
||||
unique_fd fd = CreateFlashedDisk();
|
||||
ASSERT_GE(fd, 0);
|
||||
|
||||
BadWriter writer;
|
||||
|
||||
// Read and write it back.
|
||||
writer.FailOnWrite(2);
|
||||
unique_ptr<LpMetadata> imported = ReadMetadata(fd, 0);
|
||||
ASSERT_NE(imported, nullptr);
|
||||
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 0, writer));
|
||||
|
||||
// We should still be able to read the primary copy.
|
||||
imported = ReadMetadata(fd, 0);
|
||||
ASSERT_NE(imported, nullptr);
|
||||
|
||||
// Flash again, this time fail the primary copy. We should still be able
|
||||
// to read the primary.
|
||||
//
|
||||
// TODO(dvander): This is currently not handled correctly. liblp does not
|
||||
// guarantee both copies are in sync before the update. The ASSERT_EQ
|
||||
// will change to an ASSERT_NE when this is fixed.
|
||||
writer.FailOnWrite(1);
|
||||
ASSERT_FALSE(UpdatePartitionTable(fd, *imported.get(), 0, writer));
|
||||
imported = ReadMetadata(fd, 0);
|
||||
ASSERT_EQ(imported, nullptr);
|
||||
}
|
||||
|
|
|
@ -130,8 +130,13 @@ static bool ValidateAndSerializeMetadata(int fd, const LpMetadata& metadata, std
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool DefaultWriter(int fd, const std::string& blob) {
|
||||
return android::base::WriteFully(fd, blob.data(), blob.size());
|
||||
}
|
||||
|
||||
static bool WriteMetadata(int fd, const LpMetadataGeometry& geometry, uint32_t slot_number,
|
||||
const std::string& blob) {
|
||||
const std::string& blob,
|
||||
std::function<bool(int, const std::string&)> writer) {
|
||||
// Make sure we're writing to a valid metadata slot.
|
||||
if (slot_number >= geometry.metadata_slot_count) {
|
||||
LERROR << "Invalid logical partition metadata slot number.";
|
||||
|
@ -144,7 +149,7 @@ static bool WriteMetadata(int fd, const LpMetadataGeometry& geometry, uint32_t s
|
|||
PERROR << __PRETTY_FUNCTION__ << "lseek failed: offset " << primary_offset;
|
||||
return false;
|
||||
}
|
||||
if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
|
||||
if (!writer(fd, blob)) {
|
||||
PERROR << __PRETTY_FUNCTION__ << "write " << blob.size() << " bytes failed";
|
||||
return false;
|
||||
}
|
||||
|
@ -161,7 +166,7 @@ static bool WriteMetadata(int fd, const LpMetadataGeometry& geometry, uint32_t s
|
|||
<< " is within logical partition bounds, sector " << geometry.last_logical_sector;
|
||||
return false;
|
||||
}
|
||||
if (!android::base::WriteFully(fd, blob.data(), blob.size())) {
|
||||
if (!writer(fd, blob)) {
|
||||
PERROR << __PRETTY_FUNCTION__ << "backup write " << blob.size() << " bytes failed";
|
||||
return false;
|
||||
}
|
||||
|
@ -197,10 +202,11 @@ bool FlashPartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_numbe
|
|||
}
|
||||
|
||||
// Write metadata to the correct slot, now that geometry is in place.
|
||||
return WriteMetadata(fd, metadata.geometry, slot_number, metadata_blob);
|
||||
return WriteMetadata(fd, metadata.geometry, slot_number, metadata_blob, DefaultWriter);
|
||||
}
|
||||
|
||||
bool UpdatePartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number) {
|
||||
bool UpdatePartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number,
|
||||
std::function<bool(int, const std::string&)> writer) {
|
||||
// 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.
|
||||
|
@ -221,7 +227,7 @@ bool UpdatePartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_numb
|
|||
LERROR << "Incompatible geometry in new logical partition metadata";
|
||||
return false;
|
||||
}
|
||||
return WriteMetadata(fd, geometry, slot_number, blob);
|
||||
return WriteMetadata(fd, geometry, slot_number, blob, writer);
|
||||
}
|
||||
|
||||
bool FlashPartitionTable(const std::string& block_device, const LpMetadata& metadata,
|
||||
|
@ -244,6 +250,10 @@ bool UpdatePartitionTable(const std::string& block_device, const LpMetadata& met
|
|||
return UpdatePartitionTable(fd, metadata, slot_number);
|
||||
}
|
||||
|
||||
bool UpdatePartitionTable(int fd, const LpMetadata& metadata, uint32_t slot_number) {
|
||||
return UpdatePartitionTable(fd, metadata, slot_number, DefaultWriter);
|
||||
}
|
||||
|
||||
bool WriteToImageFile(int fd, const LpMetadata& input) {
|
||||
std::string geometry = SerializeGeometry(input.geometry);
|
||||
std::string padding(LP_METADATA_GEOMETRY_SIZE - geometry.size(), '\0');
|
||||
|
|
Loading…
Reference in a new issue