libsnapshot: Add test for accounting for hash tree

Test: libsnapshot_Test
Bug: 145180464
Change-Id: If8546dea89fdd7ec7499522a232a777699c52d82
This commit is contained in:
Yifan Hong 2019-12-02 11:48:49 -08:00
parent 33836a6061
commit defcbb4b7f
3 changed files with 135 additions and 20 deletions

View file

@ -716,6 +716,45 @@ class SnapshotUpdateTest : public SnapshotTest {
return AssertionSuccess();
}
AssertionResult MapUpdateSnapshot(const std::string& name, std::string* path = nullptr) {
std::string real_path;
if (!sm->MapUpdateSnapshot(
CreateLogicalPartitionParams{
.block_device = fake_super,
.metadata_slot = 1,
.partition_name = name,
.timeout_ms = 10s,
.partition_opener = opener_.get(),
},
&real_path)) {
return AssertionFailure() << "Unable to map snapshot " << name;
}
if (path) {
*path = real_path;
}
return AssertionSuccess() << "Mapped snapshot " << name << " to " << real_path;
}
AssertionResult WriteSnapshotAndHash(const std::string& name,
std::optional<size_t> size = std::nullopt) {
std::string path;
auto res = MapUpdateSnapshot(name, &path);
if (!res) {
return res;
}
std::string size_string = size ? (std::to_string(*size) + " bytes") : "";
if (!WriteRandomData(path, size, &hashes_[name])) {
return AssertionFailure() << "Unable to write " << size_string << " to " << path
<< " for partition " << name;
}
return AssertionSuccess() << "Written " << size_string << " to " << path
<< " for snapshot partition " << name
<< ", hash: " << hashes_[name];
}
std::unique_ptr<TestPartitionOpener> opener_;
DeltaArchiveManifest manifest_;
std::unique_ptr<MetadataBuilder> src_;
@ -1313,6 +1352,56 @@ TEST_F(SnapshotUpdateTest, DataWipeAfterRollback) {
EXPECT_FALSE(test_device->IsSlotUnbootable(0));
}
TEST_F(SnapshotUpdateTest, Hashtree) {
constexpr auto partition_size = 4_MiB;
constexpr auto data_size = 3_MiB;
constexpr auto hashtree_size = 512_KiB;
constexpr auto fec_size = partition_size - data_size - hashtree_size;
const auto block_size = manifest_.block_size();
SetSize(sys_, partition_size);
auto e = sys_->add_operations()->add_dst_extents();
e->set_start_block(0);
e->set_num_blocks(data_size / block_size);
// Set hastree extents.
sys_->mutable_hash_tree_data_extent()->set_start_block(0);
sys_->mutable_hash_tree_data_extent()->set_num_blocks(data_size / block_size);
sys_->mutable_hash_tree_extent()->set_start_block(data_size / block_size);
sys_->mutable_hash_tree_extent()->set_num_blocks(hashtree_size / block_size);
// Set FEC extents.
sys_->mutable_fec_data_extent()->set_start_block(0);
sys_->mutable_fec_data_extent()->set_num_blocks((data_size + hashtree_size) / block_size);
sys_->mutable_fec_extent()->set_start_block((data_size + hashtree_size) / block_size);
sys_->mutable_fec_extent()->set_num_blocks(fec_size / block_size);
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
// Write some data to target partition.
ASSERT_TRUE(WriteSnapshotAndHash("sys_b", partition_size));
// Finish update.
ASSERT_TRUE(sm->FinishedSnapshotWrites());
// Simulate shutting down the device.
ASSERT_TRUE(UnmapAll());
// After reboot, init does first stage mount.
auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
ASSERT_NE(init, nullptr);
ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
// Check that the target partition have the same content. Hashtree and FEC extents
// should be accounted for.
ASSERT_TRUE(IsPartitionUnchanged("sys_b"));
}
class FlashAfterUpdateTest : public SnapshotUpdateTest,
public WithParamInterface<std::tuple<uint32_t, bool>> {
public:

View file

@ -62,24 +62,6 @@ std::string TestPartitionOpener::GetDeviceString(const std::string& partition_na
return PartitionOpener::GetDeviceString(partition_name);
}
bool WriteRandomData(const std::string& path) {
unique_fd rand(open("/dev/urandom", O_RDONLY));
unique_fd fd(open(path.c_str(), O_WRONLY));
char buf[4096];
while (true) {
ssize_t n = TEMP_FAILURE_RETRY(read(rand.get(), buf, sizeof(buf)));
if (n <= 0) return false;
if (!WriteFully(fd.get(), buf, n)) {
if (errno == ENOSPC) {
return true;
}
PLOG(ERROR) << "Cannot write " << path;
return false;
}
}
}
std::string ToHexString(const uint8_t* buf, size_t len) {
char lookup[] = "0123456789abcdef";
std::string out(len * 2 + 1, '\0');
@ -91,6 +73,47 @@ std::string ToHexString(const uint8_t* buf, size_t len) {
return out;
}
bool WriteRandomData(const std::string& path, std::optional<size_t> expect_size,
std::string* hash) {
unique_fd rand(open("/dev/urandom", O_RDONLY));
unique_fd fd(open(path.c_str(), O_WRONLY));
SHA256_CTX ctx;
if (hash) {
SHA256_Init(&ctx);
}
char buf[4096];
size_t total_written = 0;
while (!expect_size || total_written < *expect_size) {
ssize_t n = TEMP_FAILURE_RETRY(read(rand.get(), buf, sizeof(buf)));
if (n <= 0) return false;
if (!WriteFully(fd.get(), buf, n)) {
if (errno == ENOSPC) {
break;
}
PLOG(ERROR) << "Cannot write " << path;
return false;
}
total_written += n;
if (hash) {
SHA256_Update(&ctx, buf, n);
}
}
if (expect_size && total_written != *expect_size) {
PLOG(ERROR) << "Written " << total_written << " bytes, expected " << *expect_size;
return false;
}
if (hash) {
uint8_t out[32];
SHA256_Final(out, &ctx);
*hash = ToHexString(out, sizeof(out));
}
return true;
}
std::optional<std::string> GetHash(const std::string& path) {
std::string content;
if (!android::base::ReadFileToString(path, &content, true)) {

View file

@ -137,8 +137,11 @@ class SnapshotTestPropertyFetcher : public android::fs_mgr::testing::MockPropert
// Helper for error-spam-free cleanup.
void DeleteBackingImage(android::fiemap::IImageManager* manager, const std::string& name);
// Write some random data to the given device. Will write until reaching end of the device.
bool WriteRandomData(const std::string& device);
// Write some random data to the given device.
// If expect_size is not specified, will write until reaching end of the device.
// Expect space of |path| is multiple of 4K.
bool WriteRandomData(const std::string& path, std::optional<size_t> expect_size = std::nullopt,
std::string* hash = nullptr);
std::optional<std::string> GetHash(const std::string& path);