Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 26d7d953 authored by Yifan Hong's avatar Yifan Hong
Browse files

libsnapshot: tests for public APIs.

Test: libsnapshot_test

Change-Id: I411ae32e77914845ed4037d7e67620598f8218cf
parent 0e13bbad
Loading
Loading
Loading
Loading
+77 −0
Original line number Diff line number Diff line
// 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
+2 −0
Original line number Diff line number Diff line
@@ -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;
+436 −13
Original line number Diff line number Diff line
@@ -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");
    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";
        }
    void DeleteDevice(const std::string& device) {
        if (dm_.GetState(device) != DmDeviceState::INVALID) {
            ASSERT_TRUE(dm_.DeleteDevice(device));
        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.
+57 −0
Original line number Diff line number Diff line
@@ -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
+6 −0
Original line number Diff line number Diff line
@@ -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