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

Commit 014d68dd authored by Akilesh Kailash's avatar Akilesh Kailash Committed by Automerger Merge Worker
Browse files

Merge "libsnapshot_cow: Prepare device to boot from pre-created snapshots"...

Merge "libsnapshot_cow: Prepare device to boot from pre-created snapshots" into main am: 6b78aac2 am: eaf919aa am: 87a38fab

Original change: https://android-review.googlesource.com/c/platform/system/core/+/2746258



Change-Id: I95cf525ae1a11bb1f538653d578937d1c4b8e5b3
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents dda3ffb1 87a38fab
Loading
Loading
Loading
Loading
+17 −1
Original line number Diff line number Diff line
@@ -73,6 +73,9 @@ class ISnapshotMergeStats;
class SnapshotMergeStats;
class SnapshotStatus;

using std::chrono::duration_cast;
using namespace std::chrono_literals;

static constexpr const std::string_view kCowGroupName = "cow";
static constexpr char kVirtualAbCompressionProp[] = "ro.virtual_ab.compression.enabled";

@@ -424,6 +427,7 @@ class SnapshotManager final : public ISnapshotManager {
    FRIEND_TEST(SnapshotTest, MergeFailureCode);
    FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
    FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
    FRIEND_TEST(SnapshotTest, BootSnapshotWithoutSlotSwitch);
    FRIEND_TEST(SnapshotUpdateTest, AddPartition);
    FRIEND_TEST(SnapshotUpdateTest, ConsistencyCheckResume);
    FRIEND_TEST(SnapshotUpdateTest, DaemonTransition);
@@ -436,6 +440,7 @@ class SnapshotManager final : public ISnapshotManager {
    FRIEND_TEST(SnapshotUpdateTest, QueryStatusError);
    FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
    FRIEND_TEST(SnapshotUpdateTest, SpaceSwapUpdate);
    FRIEND_TEST(SnapshotUpdateTest, MapAllSnapshotsWithoutSlotSwitch);
    friend class SnapshotTest;
    friend class SnapshotUpdateTest;
    friend class FlashAfterUpdateTest;
@@ -456,7 +461,7 @@ class SnapshotManager final : public ISnapshotManager {
    bool EnsureImageManager();

    // Ensure we're connected to snapuserd.
    bool EnsureSnapuserdConnected();
    bool EnsureSnapuserdConnected(std::chrono::milliseconds timeout_ms = 10s);

    // Helpers for first-stage init.
    const std::unique_ptr<IDeviceInfo>& device() const { return device_; }
@@ -549,6 +554,16 @@ class SnapshotManager final : public ISnapshotManager {
    // Unmap and remove all known snapshots.
    bool RemoveAllSnapshots(LockedFile* lock);

    // Boot device off snapshots without slot switch
    bool BootFromSnapshotsWithoutSlotSwitch();

    // Remove kBootSnapshotsWithoutSlotSwitch so that device can boot
    // without snapshots on the current slot
    bool PrepareDeviceToBootWithoutSnapshot();

    // Is the kBootSnapshotsWithoutSlotSwitch present
    bool IsSnapshotWithoutSlotSwitch();

    // List the known snapshot names.
    bool ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
                       const std::string& suffix = "");
@@ -663,6 +678,7 @@ class SnapshotManager final : public ISnapshotManager {
    std::string GetRollbackIndicatorPath();
    std::string GetForwardMergeIndicatorPath();
    std::string GetOldPartitionMetadataPath();
    std::string GetBootSnapshotsWithoutSlotSwitchPath();

    const LpMetadata* ReadOldPartitionMetadata(LockedFile* lock);

+114 −8
Original line number Diff line number Diff line
@@ -83,6 +83,8 @@ using std::chrono::duration_cast;
using namespace std::chrono_literals;
using namespace std::string_literals;

static constexpr char kBootSnapshotsWithoutSlotSwitch[] =
        "/metadata/ota/snapshot-boot-without-slot-switch";
static constexpr char kBootIndicatorPath[] = "/metadata/ota/snapshot-boot";
static constexpr char kRollbackIndicatorPath[] = "/metadata/ota/rollback-indicator";
static constexpr auto kUpdateStateCheckInterval = 2s;
@@ -217,6 +219,12 @@ bool SnapshotManager::TryCancelUpdate(bool* needs_merge) {
    auto file = LockExclusive();
    if (!file) return false;

    if (IsSnapshotWithoutSlotSwitch()) {
        LOG(ERROR) << "Cannot cancel the snapshots as partitions are mounted off the snapshots on "
                      "current slot.";
        return false;
    }

    UpdateState state = ReadUpdateState(file.get());
    if (state == UpdateState::None) {
        RemoveInvalidSnapshots(file.get());
@@ -299,10 +307,9 @@ bool SnapshotManager::RemoveAllUpdateState(LockedFile* lock, const std::function
    // - For ForwardMerge, FinishedSnapshotWrites asserts that the existence of the indicator
    // matches the incoming update.
    std::vector<std::string> files = {
            GetSnapshotBootIndicatorPath(),
            GetRollbackIndicatorPath(),
            GetForwardMergeIndicatorPath(),
            GetOldPartitionMetadataPath(),
            GetSnapshotBootIndicatorPath(),          GetRollbackIndicatorPath(),
            GetForwardMergeIndicatorPath(),          GetOldPartitionMetadataPath(),
            GetBootSnapshotsWithoutSlotSwitchPath(),
    };
    for (const auto& file : files) {
        RemoveFileIfExists(file);
@@ -483,6 +490,32 @@ bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
            LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
            return false;
        }
    } else if (IsSnapshotWithoutSlotSwitch()) {
        // When snapshots are on current slot, we determine the size
        // of block device based on the number of COW operations. We cannot
        // use base device as it will be from older image.
        size_t num_ops = 0;
        uint64_t dev_sz = 0;
        unique_fd fd(open(cow_file.c_str(), O_RDONLY | O_CLOEXEC));
        if (fd < 0) {
            PLOG(ERROR) << "Failed to open " << cow_file;
            return false;
        }

        CowReader reader;
        if (!reader.Parse(std::move(fd))) {
            LOG(ERROR) << "Failed to parse cow " << cow_file;
            return false;
        }

        const auto& header = reader.GetHeader();
        if (header.prefix.major_version > 2) {
            LOG(ERROR) << "COW format not supported";
            return false;
        }
        num_ops = reader.get_num_total_data_ops();
        dev_sz = (num_ops * header.block_size);
        base_sectors = dev_sz >> 9;
    } else {
        // For userspace snapshots, the size of the base device is taken as the
        // size of the dm-user block device. Since there is no pseudo mapping
@@ -1479,6 +1512,10 @@ MergeFailureCode SnapshotManager::MergeSecondPhaseSnapshots(LockedFile* lock) {
    return result;
}

std::string SnapshotManager::GetBootSnapshotsWithoutSlotSwitchPath() {
    return metadata_dir_ + "/" + android::base::Basename(kBootSnapshotsWithoutSlotSwitch);
}

std::string SnapshotManager::GetSnapshotBootIndicatorPath() {
    return metadata_dir_ + "/" + android::base::Basename(kBootIndicatorPath);
}
@@ -2120,6 +2157,10 @@ UpdateState SnapshotManager::GetUpdateState(double* progress) {
    return state;
}

bool SnapshotManager::IsSnapshotWithoutSlotSwitch() {
    return (access(GetBootSnapshotsWithoutSlotSwitchPath().c_str(), F_OK) == 0);
}

bool SnapshotManager::UpdateUsesCompression() {
    auto lock = LockShared();
    if (!lock) return false;
@@ -2212,6 +2253,13 @@ std::string SnapshotManager::GetGlobalRollbackIndicatorPath() {
}

bool SnapshotManager::NeedSnapshotsInFirstStageMount() {
    if (IsSnapshotWithoutSlotSwitch()) {
        if (GetCurrentSlot() != Slot::Source) {
            LOG(ERROR) << "Snapshots marked to boot without slot switch; but slot is wrong";
            return false;
        }
        return true;
    }
    // If we fail to read, we'll wind up using CreateLogicalPartitions, which
    // will create devices that look like the old slot, except with extra
    // content at the end of each device. This will confuse dm-verity, and
@@ -2347,7 +2395,8 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
    // completed, live_snapshot_status is set to nullopt.
    std::optional<SnapshotStatus> live_snapshot_status;
    do {
        if (!(params.partition->attributes & LP_PARTITION_ATTR_UPDATED)) {
        if (!IsSnapshotWithoutSlotSwitch() &&
            !(params.partition->attributes & LP_PARTITION_ATTR_UPDATED)) {
            LOG(INFO) << "Detected re-flashing of partition, will skip snapshot: "
                      << params.GetPartitionName();
            break;
@@ -2703,7 +2752,7 @@ bool SnapshotManager::UnmapUserspaceSnapshotDevice(LockedFile* lock,
    // to unmap; hence, we can't be deleting the device
    // as the table would be mounted off partitions and will fail.
    if (snapshot_status.state() != SnapshotState::MERGE_COMPLETED) {
        if (!DeleteDeviceIfExists(dm_user_name)) {
        if (!DeleteDeviceIfExists(dm_user_name, 4000ms)) {
            LOG(ERROR) << "Cannot unmap " << dm_user_name;
            return false;
        }
@@ -3098,7 +3147,7 @@ bool SnapshotManager::EnsureImageManager() {
    return true;
}

bool SnapshotManager::EnsureSnapuserdConnected() {
bool SnapshotManager::EnsureSnapuserdConnected(std::chrono::milliseconds timeout_ms) {
    if (snapuserd_client_) {
        return true;
    }
@@ -3107,7 +3156,7 @@ bool SnapshotManager::EnsureSnapuserdConnected() {
        return false;
    }

    snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
    snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, timeout_ms);
    if (!snapuserd_client_) {
        LOG(ERROR) << "Unable to connect to snapuserd";
        return false;
@@ -4372,13 +4421,70 @@ std::string SnapshotManager::ReadSourceBuildFingerprint() {
bool SnapshotManager::IsUserspaceSnapshotUpdateInProgress() {
    auto slot = GetCurrentSlot();
    if (slot == Slot::Target) {
        // Merge in-progress
        if (IsSnapuserdRequired()) {
            return true;
        }
    }

    // Let's check more deeper to see if snapshots are mounted
    auto lock = LockExclusive();
    if (!lock) {
        return false;
    }

    std::vector<std::string> snapshots;
    if (!ListSnapshots(lock.get(), &snapshots)) {
        return false;
    }

    for (const auto& snapshot : snapshots) {
        // Active snapshot and daemon is alive
        if (IsSnapshotDevice(snapshot) && EnsureSnapuserdConnected(2s)) {
            return true;
        }
    }

    return false;
}

bool SnapshotManager::BootFromSnapshotsWithoutSlotSwitch() {
    auto lock = LockExclusive();
    if (!lock) return false;

    auto contents = device_->GetSlotSuffix();
    // This is the indicator which tells first-stage init
    // to boot from snapshots even though there was no slot-switch
    auto boot_file = GetBootSnapshotsWithoutSlotSwitchPath();
    if (!WriteStringToFileAtomic(contents, boot_file)) {
        PLOG(ERROR) << "write failed: " << boot_file;
        return false;
    }

    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock.get());
    update_status.set_state(UpdateState::Initiated);
    update_status.set_userspace_snapshots(true);
    update_status.set_using_snapuserd(true);
    if (!WriteSnapshotUpdateStatus(lock.get(), update_status)) {
        return false;
    }
    return true;
}

bool SnapshotManager::PrepareDeviceToBootWithoutSnapshot() {
    auto lock = LockExclusive();
    if (!lock) return false;

    android::base::RemoveFileIfExists(GetSnapshotBootIndicatorPath());
    android::base::RemoveFileIfExists(GetBootSnapshotsWithoutSlotSwitchPath());

    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock.get());
    update_status.set_state(UpdateState::Cancelled);
    if (!WriteSnapshotUpdateStatus(lock.get(), update_status)) {
        return false;
    }
    return true;
}

}  // namespace snapshot
}  // namespace android
+50 −0
Original line number Diff line number Diff line
@@ -2559,6 +2559,56 @@ TEST_F(SnapshotUpdateTest, DaemonTransition) {
    }
}

TEST_F(SnapshotUpdateTest, MapAllSnapshotsWithoutSlotSwitch) {
    MountMetadata();
    AddOperationForPartitions();
    // Execute the update.
    ASSERT_TRUE(sm->BeginUpdate());
    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));

    if (!sm->UpdateUsesUserSnapshots()) {
        GTEST_SKIP() << "Test does not apply as UserSnapshots aren't enabled.";
    }

    ASSERT_TRUE(WriteSnapshots());
    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));

    if (ShouldSkipLegacyMerging()) {
        GTEST_SKIP() << "Skipping legacy merge test";
    }
    // Mark the indicator
    ASSERT_TRUE(sm->BootFromSnapshotsWithoutSlotSwitch());

    ASSERT_TRUE(sm->EnsureSnapuserdConnected());
    sm->set_use_first_stage_snapuserd(true);

    ASSERT_TRUE(sm->NeedSnapshotsInFirstStageMount());

    // Map snapshots
    ASSERT_TRUE(sm->MapAllSnapshots(10s));

    // New updates should fail
    ASSERT_FALSE(sm->BeginUpdate());

    // Snapshots cannot be cancelled
    ASSERT_FALSE(sm->CancelUpdate());

    // Merge cannot start
    ASSERT_FALSE(sm->InitiateMerge());

    // Read bytes back and verify they match the cache.
    ASSERT_TRUE(IsPartitionUnchanged("sys_b"));

    // Remove the indicators
    ASSERT_TRUE(sm->PrepareDeviceToBootWithoutSnapshot());

    // Ensure snapshots are still mounted
    ASSERT_TRUE(sm->IsUserspaceSnapshotUpdateInProgress());

    // Cleanup snapshots
    ASSERT_TRUE(sm->UnmapAllSnapshots());
}

TEST_F(SnapshotUpdateTest, MapAllSnapshots) {
    AddOperationForPartitions();
    // Execute the update.
+69 −23
Original line number Diff line number Diff line
@@ -75,7 +75,11 @@ int Usage() {
                 "  unmap-snapshots\n"
                 "    Unmap all pre-created snapshots\n"
                 "  delete-snapshots\n"
                 "    Delete all pre-created snapshots\n";
                 "    Delete all pre-created snapshots\n"
                 "  revert-snapshots\n"
                 "    Prepares devices to boot without snapshots on next boot.\n"
                 "    This does not delete the snapshot. It only removes the indicators\n"
                 "    so that first stage init will not mount from snapshots.\n";
    return EX_USAGE;
}

@@ -87,9 +91,11 @@ class MapSnapshots {
    MapSnapshots(std::string path = "");
    bool CreateSnapshotDevice(std::string& partition_name, std::string& patch);
    bool InitiateThreadedSnapshotWrite(std::string& pname, std::string& snapshot_patch);
    bool WaitForSnapshotWritesToComplete();
    bool FinishSnapshotWrites();
    bool UnmapCowImagePath(std::string& name);
    bool DeleteCowImage(std::string& name);
    bool DeleteSnapshots();
    bool CleanupSnapshot() { return sm_->PrepareDeviceToBootWithoutSnapshot(); }
    bool BeginUpdate();

  private:
    std::optional<std::string> GetCowImagePath(std::string& name);
@@ -107,7 +113,24 @@ MapSnapshots::MapSnapshots(std::string path) {
        exit(1);
    }
    snapshot_dir_path_ = path + "/";
}

bool MapSnapshots::BeginUpdate() {
    lock_ = sm_->LockExclusive();
    std::vector<std::string> snapshots;
    sm_->ListSnapshots(lock_.get(), &snapshots);
    if (!snapshots.empty()) {
        // Snapshots are already present.
        return true;
    }

    lock_ = nullptr;
    if (!sm_->BeginUpdate()) {
        LOG(ERROR) << "BeginUpdate failed";
        return false;
    }
    lock_ = sm_->LockExclusive();
    return true;
}

bool MapSnapshots::CreateSnapshotDevice(std::string& partition_name, std::string& patchfile) {
@@ -130,6 +153,9 @@ bool MapSnapshots::CreateSnapshotDevice(std::string& partition_name, std::string
    dev_sz &= ~(block_sz - 1);

    SnapshotStatus status;
    status.set_state(SnapshotState::CREATED);
    status.set_using_snapuserd(true);
    status.set_old_partition_size(0);
    status.set_name(partition_name);
    status.set_cow_file_size(dev_sz);
    status.set_cow_partition_size(0);
@@ -216,27 +242,33 @@ bool MapSnapshots::InitiateThreadedSnapshotWrite(std::string& pname, std::string
    return true;
}

bool MapSnapshots::WaitForSnapshotWritesToComplete() {
bool MapSnapshots::FinishSnapshotWrites() {
    bool ret = true;
    for (auto& t : threads_) {
        ret = t.get() && ret;
    }

    lock_ = nullptr;
    if (ret) {
        LOG(INFO) << "Pre-created snapshots successfully copied";
    } else {
        LOG(ERROR) << "Snapshot copy failed";
        if (!sm_->FinishedSnapshotWrites(false)) {
            return false;
        }
        return sm_->BootFromSnapshotsWithoutSlotSwitch();
    }
    return ret;

    LOG(ERROR) << "Snapshot copy failed";
    return false;
}

bool MapSnapshots::UnmapCowImagePath(std::string& name) {
    return sm_->UnmapCowImage(name);
}

bool MapSnapshots::DeleteCowImage(std::string& name) {
    if (!sm_->DeleteSnapshot(lock_.get(), name)) {
        LOG(ERROR) << "Delete snapshot failed";
bool MapSnapshots::DeleteSnapshots() {
    lock_ = sm_->LockExclusive();
    if (!sm_->RemoveAllUpdateState(lock_.get())) {
        LOG(ERROR) << "Remove All Update State failed";
        return false;
    }
    return true;
@@ -281,7 +313,8 @@ bool GetVerityPartitions(std::vector<std::string>& partitions) {
    return true;
}

bool UnMapPrecreatedSnapshots(int, char**) {
bool UnMapPrecreatedSnapshots(int, char** argv) {
    android::base::InitLogging(argv, &android::base::KernelLogger);
    // Make sure we are root.
    if (::getuid() != 0) {
        LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
@@ -302,29 +335,36 @@ bool UnMapPrecreatedSnapshots(int, char**) {
    return true;
}

bool DeletePrecreatedSnapshots(int, char**) {
bool RemovePrecreatedSnapshots(int, char** argv) {
    android::base::InitLogging(argv, &android::base::KernelLogger);
    // Make sure we are root.
    if (::getuid() != 0) {
        LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
        return EXIT_FAILURE;
    }

    std::vector<std::string> partitions;
    if (!GetVerityPartitions(partitions)) {
        return false;
    }

    MapSnapshots snapshot;
    for (auto partition : partitions) {
        if (!snapshot.DeleteCowImage(partition)) {
            LOG(ERROR) << "DeleteCowImage failed: " << partition;
        }
    if (!snapshot.CleanupSnapshot()) {
        LOG(ERROR) << "CleanupSnapshot failed";
        return false;
    }
    return true;
}

bool DeletePrecreatedSnapshots(int, char** argv) {
    android::base::InitLogging(argv, &android::base::KernelLogger);
    // Make sure we are root.
    if (::getuid() != 0) {
        LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
        return EXIT_FAILURE;
    }

    MapSnapshots snapshot;
    return snapshot.DeleteSnapshots();
}

bool MapPrecreatedSnapshots(int argc, char** argv) {
    android::base::InitLogging(argv, &android::base::StderrLogger);
    android::base::InitLogging(argv, &android::base::KernelLogger);

    // Make sure we are root.
    if (::getuid() != 0) {
@@ -365,6 +405,11 @@ bool MapPrecreatedSnapshots(int argc, char** argv) {
    }

    MapSnapshots cow(path);
    if (!cow.BeginUpdate()) {
        LOG(ERROR) << "BeginUpdate failed";
        return false;
    }

    for (auto& pair : partitions) {
        if (!cow.CreateSnapshotDevice(pair.first, pair.second)) {
            LOG(ERROR) << "CreateSnapshotDevice failed for: " << pair.first;
@@ -376,7 +421,7 @@ bool MapPrecreatedSnapshots(int argc, char** argv) {
        }
    }

    return cow.WaitForSnapshotWritesToComplete();
    return cow.FinishSnapshotWrites();
}

#ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
@@ -508,6 +553,7 @@ static std::map<std::string, std::function<bool(int, char**)>> kCmdMap = {
        {"map-snapshots", MapPrecreatedSnapshots},
        {"unmap-snapshots", UnMapPrecreatedSnapshots},
        {"delete-snapshots", DeletePrecreatedSnapshots},
        {"revert-snapshots", RemovePrecreatedSnapshots},
        // clang-format on
};