libsnapshot: tests for public APIs.

Test: libsnapshot_test

Change-Id: I411ae32e77914845ed4037d7e67620598f8218cf
This commit is contained in:
Yifan Hong 2019-09-05 14:29:17 -07:00
parent 0e13bbade3
commit 26d7d95391
5 changed files with 579 additions and 14 deletions

View File

@ -0,0 +1,77 @@
// Copyright (C) 2019 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.
#pragma once
#include <stdint.h>
#include <stdlib.h>
namespace android {
namespace digital_storage {
template <size_t Power>
struct Size {
static constexpr size_t power = Power;
constexpr Size(uint64_t count) : value_(count) {}
constexpr uint64_t bytes() const { return value_ << (Power * 10); }
constexpr uint64_t count() const { return value_; }
operator uint64_t() const { return bytes(); }
private:
uint64_t value_;
};
using B = Size<0>;
using KiB = Size<1>;
using MiB = Size<2>;
using GiB = Size<3>;
constexpr B operator""_B(unsigned long long v) { // NOLINT
return B{v};
}
constexpr KiB operator""_KiB(unsigned long long v) { // NOLINT
return KiB{v};
}
constexpr MiB operator""_MiB(unsigned long long v) { // NOLINT
return MiB{v};
}
constexpr GiB operator""_GiB(unsigned long long v) { // NOLINT
return GiB{v};
}
template <typename Dest, typename Src>
constexpr Dest size_cast(Src src) {
if (Src::power < Dest::power) {
return Dest(src.count() >> ((Dest::power - Src::power) * 10));
}
if (Src::power > Dest::power) {
return Dest(src.count() << ((Src::power - Dest::power) * 10));
}
return Dest(src.count());
}
static_assert((1_B).bytes() == 1);
static_assert((1_KiB).bytes() == 1 << 10);
static_assert((1_MiB).bytes() == 1 << 20);
static_assert((1_GiB).bytes() == 1 << 30);
static_assert(size_cast<KiB>(1_B).count() == 0);
static_assert(size_cast<KiB>(1024_B).count() == 1);
static_assert(size_cast<KiB>(1_MiB).count() == 1024);
} // namespace digital_storage
} // namespace android

View File

@ -200,7 +200,9 @@ class SnapshotManager final {
FRIEND_TEST(SnapshotTest, Merge);
FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow);
FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
friend class SnapshotTest;
friend class SnapshotUpdateTest;
friend struct AutoDeleteCowImage;
friend struct AutoDeleteSnapshot;
friend struct PartitionCowCreator;

View File

@ -33,6 +33,7 @@
#include <liblp/builder.h>
#include <liblp/mock_property_fetcher.h>
#include "digital_storage.h"
#include "test_helpers.h"
#include "utility.h"
@ -46,10 +47,12 @@ using android::fiemap::IImageManager;
using android::fs_mgr::BlockDeviceInfo;
using android::fs_mgr::CreateLogicalPartitionParams;
using android::fs_mgr::DestroyLogicalPartition;
using android::fs_mgr::GetPartitionGroupName;
using android::fs_mgr::GetPartitionName;
using android::fs_mgr::MetadataBuilder;
using namespace ::testing;
using namespace android::fs_mgr::testing;
using namespace android::digital_storage;
using namespace std::chrono_literals;
using namespace std::string_literals;
@ -59,7 +62,7 @@ std::unique_ptr<SnapshotManager> sm;
TestDeviceInfo* test_device = nullptr;
std::string fake_super;
static constexpr uint64_t kSuperSize = 16 * 1024 * 1024;
static constexpr uint64_t kSuperSize = (16_MiB).bytes();
class SnapshotTest : public ::testing::Test {
public:
@ -106,7 +109,7 @@ class SnapshotTest : public ::testing::Test {
std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a",
"test_partition_b"};
for (const auto& snapshot : snapshots) {
DeleteSnapshotDevice(snapshot);
ASSERT_TRUE(DeleteSnapshotDevice(snapshot));
DeleteBackingImage(image_manager_, snapshot + "-cow-img");
auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
@ -212,17 +215,23 @@ class SnapshotTest : public ::testing::Test {
return true;
}
void DeleteSnapshotDevice(const std::string& snapshot) {
DeleteDevice(snapshot);
DeleteDevice(snapshot + "-inner");
DeleteDevice(snapshot + "-cow");
ASSERT_TRUE(image_manager_->UnmapImageIfExists(snapshot + "-cow-img"));
DeleteDevice(snapshot + "-base");
}
void DeleteDevice(const std::string& device) {
if (dm_.GetState(device) != DmDeviceState::INVALID) {
ASSERT_TRUE(dm_.DeleteDevice(device));
AssertionResult DeleteSnapshotDevice(const std::string& snapshot) {
AssertionResult res = AssertionSuccess();
if (!(res = DeleteDevice(snapshot))) return res;
if (!(res = DeleteDevice(snapshot + "-inner"))) return res;
if (!(res = DeleteDevice(snapshot + "-cow"))) return res;
if (!image_manager_->UnmapImageIfExists(snapshot + "-cow-img")) {
return AssertionFailure() << "Cannot unmap image " << snapshot << "-cow-img";
}
if (!(res = DeleteDevice(snapshot + "-base"))) return res;
return AssertionSuccess();
}
AssertionResult DeleteDevice(const std::string& device) {
if (!dm_.DeleteDeviceIfExists(device)) {
return AssertionFailure() << "Can't delete " << device;
}
return AssertionSuccess();
}
AssertionResult CreateCowImage(const std::string& name) {
@ -465,7 +474,7 @@ TEST_F(SnapshotTest, MergeCannotRemoveCow) {
// Wait 1s, otherwise DeleteSnapshotDevice may fail with EBUSY.
sleep(1);
// Forcefully delete the snapshot device, so it looks like we just rebooted.
DeleteSnapshotDevice("test-snapshot");
ASSERT_TRUE(DeleteSnapshotDevice("test-snapshot"));
// Map snapshot should fail now, because we're in a merge-complete state.
ASSERT_TRUE(AcquireLock());
@ -602,7 +611,7 @@ TEST_F(SnapshotTest, FlashSuperDuringMerge) {
// Now, reflash super. Note that we haven't called ProcessUpdateState, so the
// status is still Merging.
DeleteSnapshotDevice("test_partition_b");
ASSERT_TRUE(DeleteSnapshotDevice("test_partition_b"));
ASSERT_TRUE(init->image_manager()->UnmapImageIfExists("test_partition_b-cow-img"));
FormatFakeSuper();
ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize));
@ -615,6 +624,419 @@ TEST_F(SnapshotTest, FlashSuperDuringMerge) {
ASSERT_EQ(sm->GetUpdateState(), UpdateState::None);
}
class SnapshotUpdateTest : public SnapshotTest {
public:
void SetUp() override {
SnapshotTest::SetUp();
Cleanup();
// Cleanup() changes slot suffix, so initialize it again.
test_device->set_slot_suffix("_a");
ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
.WillByDefault(Return(true));
opener = std::make_unique<TestPartitionOpener>(fake_super);
// Initialize source partition metadata. sys_b is similar to system_other.
// Not using full name "system", "vendor", "product" because these names collide with the
// mapped partitions on the running device.
src = MetadataBuilder::New(*opener, "super", 0);
ASSERT_NE(nullptr, src);
auto partition = src->AddPartition("sys_a", 0);
ASSERT_NE(nullptr, partition);
ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
partition = src->AddPartition("vnd_a", 0);
ASSERT_NE(nullptr, partition);
ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
partition = src->AddPartition("prd_a", 0);
ASSERT_NE(nullptr, partition);
ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
partition = src->AddPartition("sys_b", 0);
ASSERT_NE(nullptr, partition);
ASSERT_TRUE(src->ResizePartition(partition, 1_MiB));
auto metadata = src->Export();
ASSERT_NE(nullptr, metadata);
ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 0));
// Map source partitions. Additionally, map sys_b to simulate system_other after flashing.
std::string path;
for (const auto& name : {"sys_a", "vnd_a", "prd_a", "sys_b"}) {
ASSERT_TRUE(CreateLogicalPartition(
CreateLogicalPartitionParams{
.block_device = fake_super,
.metadata_slot = 0,
.partition_name = name,
.timeout_ms = 1s,
.partition_opener = opener.get(),
},
&path));
ASSERT_TRUE(WriteRandomData(path));
auto hash = GetHash(path);
ASSERT_TRUE(hash.has_value());
hashes_[name] = *hash;
}
}
void TearDown() override {
Cleanup();
SnapshotTest::TearDown();
}
void Cleanup() {
if (!image_manager_) {
InitializeState();
}
for (const auto& suffix : {"_a", "_b"}) {
test_device->set_slot_suffix(suffix);
EXPECT_TRUE(sm->CancelUpdate()) << suffix;
}
EXPECT_TRUE(UnmapAll());
}
static AssertionResult ResizePartitions(
MetadataBuilder* builder,
const std::vector<std::pair<std::string, uint64_t>>& partition_sizes) {
for (auto&& [name, size] : partition_sizes) {
auto partition = builder->FindPartition(name);
if (!partition) {
return AssertionFailure() << "Cannot find partition in metadata " << name;
}
if (!builder->ResizePartition(partition, size)) {
return AssertionFailure()
<< "Cannot resize partition " << name << " to " << size << " bytes";
}
}
return AssertionSuccess();
}
AssertionResult IsPartitionUnchanged(const std::string& name) {
std::string path;
if (!dm_.GetDmDevicePathByName(name, &path)) {
return AssertionFailure() << "Path of " << name << " cannot be determined";
}
auto hash = GetHash(path);
if (!hash.has_value()) {
return AssertionFailure() << "Cannot read partition " << name << ": " << path;
}
if (hashes_[name] != *hash) {
return AssertionFailure() << "Content of " << name << " has changed after the merge";
}
return AssertionSuccess();
}
std::optional<uint64_t> GetSnapshotSize(const std::string& name) {
if (!AcquireLock()) {
return std::nullopt;
}
auto local_lock = std::move(lock_);
SnapshotManager::SnapshotStatus status;
if (!sm->ReadSnapshotStatus(local_lock.get(), name, &status)) {
return std::nullopt;
}
return status.snapshot_size;
}
AssertionResult UnmapAll() {
for (const auto& name : {"sys", "vnd", "prd"}) {
if (!dm_.DeleteDeviceIfExists(name + "_a"s)) {
return AssertionFailure() << "Cannot unmap " << name << "_a";
}
if (!DeleteSnapshotDevice(name + "_b"s)) {
return AssertionFailure() << "Cannot delete snapshot " << name << "_b";
}
}
return AssertionSuccess();
}
std::unique_ptr<TestPartitionOpener> opener;
std::unique_ptr<MetadataBuilder> src;
std::map<std::string, std::string> hashes_;
};
// Test full update flow executed by update_engine. Some partitions uses super empty space,
// some uses images, and some uses both.
// Also test UnmapUpdateSnapshot unmaps everything.
// Also test first stage mount and merge after this.
TEST_F(SnapshotUpdateTest, FullUpdateFlow) {
// OTA client calls CancelUpdate then BeginUpdate before doing anything.
ASSERT_TRUE(sm->CancelUpdate());
ASSERT_TRUE(sm->BeginUpdate());
// OTA client blindly unmaps all partitions that are possibly mapped.
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
}
// OTA client adjusts the partition sizes before giving it to CreateUpdateSnapshots.
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
ASSERT_NE(nullptr, tgt);
// clang-format off
ASSERT_TRUE(ResizePartitions(tgt.get(), {
{"sys_b", 4_MiB}, // grows
{"vnd_b", 4_MiB}, // grows
{"prd_b", 4_MiB}, // grows
}));
// clang-format on
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
// Test that partitions prioritize using space in super.
ASSERT_NE(nullptr, tgt->FindPartition("sys_b-cow"));
ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow"));
ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow"));
// The metadata slot 1 is now updated.
auto metadata = tgt->Export();
ASSERT_NE(nullptr, metadata);
ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
// Write some data to target partitions.
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
std::string path;
ASSERT_TRUE(sm->MapUpdateSnapshot(
CreateLogicalPartitionParams{
.block_device = fake_super,
.metadata_slot = 1,
.partition_name = name,
.timeout_ms = 10s,
.partition_opener = opener.get(),
},
&path))
<< name;
ASSERT_TRUE(WriteRandomData(path));
auto hash = GetHash(path);
ASSERT_TRUE(hash.has_value());
hashes_[name] = *hash;
}
// Assert that source partitions aren't affected.
for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
ASSERT_TRUE(IsPartitionUnchanged(name));
}
ASSERT_TRUE(sm->FinishedSnapshotWrites());
// Simulate shutting down the device.
ASSERT_TRUE(UnmapAll());
// After reboot, init does first stage mount.
auto rebooted = new TestDeviceInfo(fake_super);
rebooted->set_slot_suffix("_b");
auto init = SnapshotManager::NewForFirstStageMount(rebooted);
ASSERT_NE(init, nullptr);
ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
// Check that the target partitions have the same content.
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
ASSERT_TRUE(IsPartitionUnchanged(name));
}
// Initiate the merge and wait for it to be completed.
ASSERT_TRUE(init->InitiateMerge());
ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
// Check that the target partitions have the same content after the merge.
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
ASSERT_TRUE(IsPartitionUnchanged(name))
<< "Content of " << name << " changes after the merge";
}
}
// Test that if new system partitions uses empty space in super, that region is not snapshotted.
TEST_F(SnapshotUpdateTest, DirectWriteEmptySpace) {
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
ASSERT_NE(nullptr, tgt);
// clang-format off
ASSERT_TRUE(ResizePartitions(tgt.get(), {
{"sys_b", 4_MiB}, // grows
// vnd_b and prd_b are unchanged
}));
// clang-format on
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
ASSERT_EQ(3_MiB, GetSnapshotSize("sys_b").value_or(0));
}
// Test that if new system partitions uses space of old vendor partition, that region is
// snapshotted.
TEST_F(SnapshotUpdateTest, SnapshotOldPartitions) {
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
ASSERT_NE(nullptr, tgt);
// clang-format off
ASSERT_TRUE(ResizePartitions(tgt.get(), {
{"vnd_b", 2_MiB}, // shrinks
{"sys_b", 4_MiB}, // grows
// prd_b is unchanged
}));
// clang-format on
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
ASSERT_EQ(4_MiB, GetSnapshotSize("sys_b").value_or(0));
}
// Test that even if there seem to be empty space in target metadata, COW partition won't take
// it because they are used by old partitions.
TEST_F(SnapshotUpdateTest, CowPartitionDoNotTakeOldPartitions) {
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
ASSERT_NE(nullptr, tgt);
// clang-format off
ASSERT_TRUE(ResizePartitions(tgt.get(), {
{"sys_b", 2_MiB}, // shrinks
// vnd_b and prd_b are unchanged
}));
// clang-format on
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
auto metadata = tgt->Export();
ASSERT_NE(nullptr, metadata);
std::vector<std::string> written;
// Write random data to all COW partitions in super
for (auto p : metadata->partitions) {
if (GetPartitionGroupName(metadata->groups[p.group_index]) != kCowGroupName) {
continue;
}
std::string path;
ASSERT_TRUE(CreateLogicalPartition(
CreateLogicalPartitionParams{
.block_device = fake_super,
.metadata = metadata.get(),
.partition = &p,
.timeout_ms = 1s,
.partition_opener = opener.get(),
},
&path));
ASSERT_TRUE(WriteRandomData(path));
written.push_back(GetPartitionName(p));
}
ASSERT_FALSE(written.empty())
<< "No COW partitions are created even if there are empty space in super partition";
// Make sure source partitions aren't affected.
for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
ASSERT_TRUE(IsPartitionUnchanged(name));
}
}
// Test that it crashes after creating snapshot status file but before creating COW image, then
// calling CreateUpdateSnapshots again works.
TEST_F(SnapshotUpdateTest, SnapshotStatusFileWithoutCow) {
// Write some trash snapshot files to simulate leftovers from previous runs.
{
ASSERT_TRUE(AcquireLock());
auto local_lock = std::move(lock_);
ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), "sys_b",
SnapshotManager::SnapshotStatus{}));
ASSERT_TRUE(image_manager_->CreateBackingImage("sys_b-cow-img", 1_MiB,
IImageManager::CREATE_IMAGE_DEFAULT));
}
// Redo the update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
ASSERT_NE(nullptr, tgt);
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
// The metadata slot 1 is now updated.
auto metadata = tgt->Export();
ASSERT_NE(nullptr, metadata);
ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
// Check that target partitions can be mapped.
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
std::string path;
EXPECT_TRUE(sm->MapUpdateSnapshot(
CreateLogicalPartitionParams{
.block_device = fake_super,
.metadata_slot = 1,
.partition_name = name,
.timeout_ms = 10s,
.partition_opener = opener.get(),
},
&path))
<< name;
}
}
// Test that the old partitions are not modified.
TEST_F(SnapshotUpdateTest, TestRollback) {
// Execute the update.
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
ASSERT_NE(nullptr, tgt);
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
// The metadata slot 1 is now updated.
auto metadata = tgt->Export();
ASSERT_NE(nullptr, metadata);
ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
// Write some data to target partitions.
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
std::string path;
ASSERT_TRUE(sm->MapUpdateSnapshot(
CreateLogicalPartitionParams{
.block_device = fake_super,
.metadata_slot = 1,
.partition_name = name,
.timeout_ms = 10s,
.partition_opener = opener.get(),
},
&path))
<< name;
ASSERT_TRUE(WriteRandomData(path));
auto hash = GetHash(path);
ASSERT_TRUE(hash.has_value());
hashes_[name] = *hash;
}
// Assert that source partitions aren't affected.
for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
ASSERT_TRUE(IsPartitionUnchanged(name));
}
ASSERT_TRUE(sm->FinishedSnapshotWrites());
// Simulate shutting down the device.
ASSERT_TRUE(UnmapAll());
// After reboot, init does first stage mount.
auto rebooted = new TestDeviceInfo(fake_super);
rebooted->set_slot_suffix("_b");
auto init = SnapshotManager::NewForFirstStageMount(rebooted);
ASSERT_NE(init, nullptr);
ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
// Check that the target partitions have the same content.
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
ASSERT_TRUE(IsPartitionUnchanged(name));
}
// Simulate shutting down the device again.
ASSERT_TRUE(UnmapAll());
rebooted = new TestDeviceInfo(fake_super);
rebooted->set_slot_suffix("_a");
init = SnapshotManager::NewForFirstStageMount(rebooted);
ASSERT_NE(init, nullptr);
ASSERT_FALSE(init->NeedSnapshotsInFirstStageMount());
ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
// Assert that the source partitions aren't affected.
for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
ASSERT_TRUE(IsPartitionUnchanged(name));
}
}
// Test that if an update is applied but not booted into, it can be canceled.
TEST_F(SnapshotUpdateTest, CancelAfterApply) {
ASSERT_TRUE(sm->BeginUpdate());
ASSERT_TRUE(sm->FinishedSnapshotWrites());
ASSERT_TRUE(sm->CancelUpdate());
}
} // namespace snapshot
} // namespace android
@ -656,6 +1078,7 @@ int main(int argc, char** argv) {
}
// Clean up previous run.
SnapshotUpdateTest().Cleanup();
SnapshotTest().Cleanup();
// Use a separate image manager for our fake super partition.

View File

@ -14,11 +14,18 @@
#include "test_helpers.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <gtest/gtest.h>
#include <openssl/sha.h>
namespace android {
namespace snapshot {
using android::base::ReadFully;
using android::base::unique_fd;
using android::base::WriteFully;
using android::fiemap::IImageManager;
void DeleteBackingImage(IImageManager* manager, const std::string& name) {
@ -53,5 +60,55 @@ 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');
char* outp = out.data();
for (; len > 0; len--, buf++) {
*outp++ = (char)lookup[*buf >> 4];
*outp++ = (char)lookup[*buf & 0xf];
}
return out;
}
std::optional<std::string> GetHash(const std::string& path) {
unique_fd fd(open(path.c_str(), O_RDONLY));
char buf[4096];
SHA256_CTX ctx;
SHA256_Init(&ctx);
while (true) {
ssize_t n = TEMP_FAILURE_RETRY(read(fd.get(), buf, sizeof(buf)));
if (n < 0) {
PLOG(ERROR) << "Cannot read " << path;
return std::nullopt;
}
if (n == 0) {
break;
}
SHA256_Update(&ctx, buf, n);
}
uint8_t out[32];
SHA256_Final(out, &ctx);
return ToHexString(out, sizeof(out));
}
} // namespace snapshot
} // namespace android

View File

@ -14,6 +14,7 @@
#pragma once
#include <optional>
#include <string>
#include <libfiemap/image_manager.h>
@ -66,5 +67,10 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
// 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);
std::optional<std::string> GetHash(const std::string& path);
} // namespace snapshot
} // namespace android