diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h index 6bc8b9b5a013c747b9a48d3e0f2851311b844658..e7b0020ead86a84671ce9667e6cab7191bbad06f 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h @@ -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& 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* 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); diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp index 51389a0dbf270d9684cf97ef578521ccca7a2110..4743a42a4b230541a6609d386509bb870c73ad58 100644 --- a/fs_mgr/libsnapshot/snapshot.cpp +++ b/fs_mgr/libsnapshot/snapshot.cpp @@ -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 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 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 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 diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp index 3b6d26aa11ff969cfdf67b3c7509e95df587102a..e506110b61a7b6d9bed57a1f07020664234c090d 100644 --- a/fs_mgr/libsnapshot/snapshot_test.cpp +++ b/fs_mgr/libsnapshot/snapshot_test.cpp @@ -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. diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp index 1323b0bc8a6407ee0a7410eac3aa40b7a504c614..ebaca2dbe92aa51ebeef740e8fa63e0a231e57a4 100644 --- a/fs_mgr/libsnapshot/snapshotctl.cpp +++ b/fs_mgr/libsnapshot/snapshotctl.cpp @@ -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 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 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& 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 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> kCmdMap = { {"map-snapshots", MapPrecreatedSnapshots}, {"unmap-snapshots", UnMapPrecreatedSnapshots}, {"delete-snapshots", DeletePrecreatedSnapshots}, + {"revert-snapshots", RemovePrecreatedSnapshots}, // clang-format on };