Loading fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +7 −0 Original line number Diff line number Diff line Loading @@ -529,6 +529,13 @@ class SnapshotManager final { // allow forward merge on FDR. bool UpdateForwardMergeIndicator(bool wipe); // Helper for HandleImminentDataWipe. // Call ProcessUpdateState and handle states with special rules before data wipe. Specifically, // if |allow_forward_merge| and allow-forward-merge indicator exists, initiate merge if // necessary. bool ProcessUpdateStateOnDataWipe(bool allow_forward_merge, const std::function<bool()>& callback); std::string gsid_dir_; std::string metadata_dir_; std::unique_ptr<IDeviceInfo> device_; Loading fs_mgr/libsnapshot/snapshot.cpp +37 −8 Original line number Diff line number Diff line Loading @@ -2522,19 +2522,39 @@ bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callba return false; } UpdateState state = ProcessUpdateState([&]() -> bool { auto process_callback = [&]() -> bool { if (callback) { callback(); } return true; }); }; if (!ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback)) { return false; } // Nothing should be depending on partitions now, so unmap them all. if (!UnmapAllPartitions()) { LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash."; } return true; } bool SnapshotManager::ProcessUpdateStateOnDataWipe(bool allow_forward_merge, const std::function<bool()>& callback) { auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); UpdateState state = ProcessUpdateState(callback); LOG(INFO) << "Update state in recovery: " << state; switch (state) { case UpdateState::MergeFailed: LOG(ERROR) << "Unrecoverable merge failure detected."; return false; case UpdateState::Unverified: { // If an OTA was just applied but has not yet started merging, we // If an OTA was just applied but has not yet started merging: // // - if forward merge is allowed, initiate merge and call // ProcessUpdateState again. // // - if forward merge is not allowed, we // have no choice but to revert slots, because the current slot will // immediately become unbootable. Rather than wait for the device // to reboot N times until a rollback, we proactively disable the Loading @@ -2544,8 +2564,17 @@ bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callba // as an error here. auto slot = GetCurrentSlot(); if (slot == Slot::Target) { if (allow_forward_merge && access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0) { LOG(INFO) << "Forward merge allowed, initiating merge now."; return InitiateMerge() && ProcessUpdateStateOnDataWipe(false /* allow_forward_merge */, callback); } LOG(ERROR) << "Reverting to old slot since update will be deleted."; device_->SetSlotAsUnbootable(slot_number); } else { LOG(INFO) << "Booting from " << slot << " slot, no action is taken."; } break; } Loading @@ -2557,11 +2586,6 @@ bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callba default: break; } // Nothing should be depending on partitions now, so unmap them all. if (!UnmapAllPartitions()) { LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash."; } return true; } Loading Loading @@ -2643,10 +2667,15 @@ CreateResult SnapshotManager::RecoveryCreateSnapshotDevices( } bool SnapshotManager::UpdateForwardMergeIndicator(bool wipe) { auto path = GetForwardMergeIndicatorPath(); if (!wipe) { LOG(INFO) << "Wipe is not scheduled. Deleting forward merge indicator."; return RemoveFileIfExists(path); } // TODO(b/152094219): Don't forward merge if no CoW file is allocated. LOG(INFO) << "Wipe will be scheduled. Allowing forward merge of snapshots."; if (!android::base::WriteStringToFile("1", path)) { PLOG(ERROR) << "Unable to write forward merge indicator: " << path; Loading fs_mgr/libsnapshot/snapshot_test.cpp +39 −0 Original line number Diff line number Diff line Loading @@ -1501,6 +1501,45 @@ TEST_F(SnapshotUpdateTest, DataWipeAfterRollback) { EXPECT_FALSE(test_device->IsSlotUnbootable(1)); } // Test update package that requests data wipe. TEST_F(SnapshotUpdateTest, DataWipeRequiredInPackage) { AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Write some data to target partitions. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(WriteSnapshotAndHash(name)) << name; } ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // Simulate a reboot into recovery. auto test_device = new TestDeviceInfo(fake_super, "_b"); test_device->set_recovery(true); auto new_sm = SnapshotManager::NewForFirstStageMount(test_device); ASSERT_TRUE(new_sm->HandleImminentDataWipe()); // Manually mount metadata so that we can call GetUpdateState() below. MountMetadata(); EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None); ASSERT_FALSE(test_device->IsSlotUnbootable(1)); ASSERT_FALSE(test_device->IsSlotUnbootable(0)); // Now reboot into new slot. test_device = new TestDeviceInfo(fake_super, "_b"); auto init = SnapshotManager::NewForFirstStageMount(test_device); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Verify that we are on the downgraded build. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)) << name; } } TEST_F(SnapshotUpdateTest, Hashtree) { constexpr auto partition_size = 4_MiB; constexpr auto data_size = 3_MiB; Loading Loading
fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +7 −0 Original line number Diff line number Diff line Loading @@ -529,6 +529,13 @@ class SnapshotManager final { // allow forward merge on FDR. bool UpdateForwardMergeIndicator(bool wipe); // Helper for HandleImminentDataWipe. // Call ProcessUpdateState and handle states with special rules before data wipe. Specifically, // if |allow_forward_merge| and allow-forward-merge indicator exists, initiate merge if // necessary. bool ProcessUpdateStateOnDataWipe(bool allow_forward_merge, const std::function<bool()>& callback); std::string gsid_dir_; std::string metadata_dir_; std::unique_ptr<IDeviceInfo> device_; Loading
fs_mgr/libsnapshot/snapshot.cpp +37 −8 Original line number Diff line number Diff line Loading @@ -2522,19 +2522,39 @@ bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callba return false; } UpdateState state = ProcessUpdateState([&]() -> bool { auto process_callback = [&]() -> bool { if (callback) { callback(); } return true; }); }; if (!ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback)) { return false; } // Nothing should be depending on partitions now, so unmap them all. if (!UnmapAllPartitions()) { LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash."; } return true; } bool SnapshotManager::ProcessUpdateStateOnDataWipe(bool allow_forward_merge, const std::function<bool()>& callback) { auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); UpdateState state = ProcessUpdateState(callback); LOG(INFO) << "Update state in recovery: " << state; switch (state) { case UpdateState::MergeFailed: LOG(ERROR) << "Unrecoverable merge failure detected."; return false; case UpdateState::Unverified: { // If an OTA was just applied but has not yet started merging, we // If an OTA was just applied but has not yet started merging: // // - if forward merge is allowed, initiate merge and call // ProcessUpdateState again. // // - if forward merge is not allowed, we // have no choice but to revert slots, because the current slot will // immediately become unbootable. Rather than wait for the device // to reboot N times until a rollback, we proactively disable the Loading @@ -2544,8 +2564,17 @@ bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callba // as an error here. auto slot = GetCurrentSlot(); if (slot == Slot::Target) { if (allow_forward_merge && access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0) { LOG(INFO) << "Forward merge allowed, initiating merge now."; return InitiateMerge() && ProcessUpdateStateOnDataWipe(false /* allow_forward_merge */, callback); } LOG(ERROR) << "Reverting to old slot since update will be deleted."; device_->SetSlotAsUnbootable(slot_number); } else { LOG(INFO) << "Booting from " << slot << " slot, no action is taken."; } break; } Loading @@ -2557,11 +2586,6 @@ bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callba default: break; } // Nothing should be depending on partitions now, so unmap them all. if (!UnmapAllPartitions()) { LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash."; } return true; } Loading Loading @@ -2643,10 +2667,15 @@ CreateResult SnapshotManager::RecoveryCreateSnapshotDevices( } bool SnapshotManager::UpdateForwardMergeIndicator(bool wipe) { auto path = GetForwardMergeIndicatorPath(); if (!wipe) { LOG(INFO) << "Wipe is not scheduled. Deleting forward merge indicator."; return RemoveFileIfExists(path); } // TODO(b/152094219): Don't forward merge if no CoW file is allocated. LOG(INFO) << "Wipe will be scheduled. Allowing forward merge of snapshots."; if (!android::base::WriteStringToFile("1", path)) { PLOG(ERROR) << "Unable to write forward merge indicator: " << path; Loading
fs_mgr/libsnapshot/snapshot_test.cpp +39 −0 Original line number Diff line number Diff line Loading @@ -1501,6 +1501,45 @@ TEST_F(SnapshotUpdateTest, DataWipeAfterRollback) { EXPECT_FALSE(test_device->IsSlotUnbootable(1)); } // Test update package that requests data wipe. TEST_F(SnapshotUpdateTest, DataWipeRequiredInPackage) { AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); // Write some data to target partitions. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(WriteSnapshotAndHash(name)) << name; } ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // Simulate a reboot into recovery. auto test_device = new TestDeviceInfo(fake_super, "_b"); test_device->set_recovery(true); auto new_sm = SnapshotManager::NewForFirstStageMount(test_device); ASSERT_TRUE(new_sm->HandleImminentDataWipe()); // Manually mount metadata so that we can call GetUpdateState() below. MountMetadata(); EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None); ASSERT_FALSE(test_device->IsSlotUnbootable(1)); ASSERT_FALSE(test_device->IsSlotUnbootable(0)); // Now reboot into new slot. test_device = new TestDeviceInfo(fake_super, "_b"); auto init = SnapshotManager::NewForFirstStageMount(test_device); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); // Verify that we are on the downgraded build. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { ASSERT_TRUE(IsPartitionUnchanged(name)) << name; } } TEST_F(SnapshotUpdateTest, Hashtree) { constexpr auto partition_size = 4_MiB; constexpr auto data_size = 3_MiB; Loading