Loading fastboot/device/commands.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -625,7 +625,7 @@ bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector<std::string if (!sm) { return device->WriteFail("Unable to create SnapshotManager"); } if (!sm->HandleImminentDataWipe()) { if (!sm->FinishMergeInRecovery()) { return device->WriteFail("Unable to finish snapshot merge"); } } else { Loading fs_mgr/libfiemap/image_test.cpp +0 −126 Original line number Diff line number Diff line Loading @@ -131,132 +131,6 @@ TEST_F(NativeTest, GetMappedImageDevice) { ASSERT_TRUE(manager_->UnmapImageDevice(base_name_)); } // This fixture is for tests against a simulated device environment. Rather // than use /data, we create an image and then layer a new filesystem within // it. Each test then decides how to mount and create layered images. This // allows us to test FBE vs FDE configurations. class ImageTest : public ::testing::Test { public: ImageTest() : dm_(DeviceMapper::Instance()) {} void SetUp() override { manager_ = ImageManager::Open(kMetadataPath, gDataPath); ASSERT_NE(manager_, nullptr); manager_->set_partition_opener(std::make_unique<TestPartitionOpener>()); submanager_ = ImageManager::Open(kMetadataPath + "/mnt"s, gDataPath + "/mnt"s); ASSERT_NE(submanager_, nullptr); submanager_->set_partition_opener(std::make_unique<TestPartitionOpener>()); // Ensure that metadata is cleared in between runs. submanager_->RemoveAllImages(); manager_->RemoveAllImages(); const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); base_name_ = tinfo->name(); test_image_name_ = base_name_ + "-base"; wrapper_device_name_ = base_name_ + "-wrapper"; ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize * 16, false, nullptr)); ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &base_device_)); } void TearDown() override { submanager_->UnmapImageDevice(test_image_name_); umount(gDataMountPath.c_str()); dm_.DeleteDeviceIfExists(wrapper_device_name_); manager_->UnmapImageDevice(base_name_); manager_->DeleteBackingImage(base_name_); } protected: bool DoFormat(const std::string& device) { // clang-format off std::vector<std::string> mkfs_args = { "/system/bin/mke2fs", "-F", "-b 4096", "-t ext4", "-m 0", "-O has_journal", device, ">/dev/null", "2>/dev/null", "</dev/null", }; // clang-format on auto command = android::base::Join(mkfs_args, " "); return system(command.c_str()) == 0; } std::unique_ptr<ImageManager> manager_; std::unique_ptr<ImageManager> submanager_; DeviceMapper& dm_; std::string base_name_; std::string base_device_; std::string test_image_name_; std::string wrapper_device_name_; }; TEST_F(ImageTest, DirectMount) { ASSERT_TRUE(DoFormat(base_device_)); ASSERT_EQ(mount(base_device_.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0); ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr)); std::string path; ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path)); ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/loop")); } TEST_F(ImageTest, IndirectMount) { #ifdef SKIP_TEST_IN_PRESUBMIT GTEST_SKIP() << "WIP failure b/148874852"; #endif // Create a simple wrapper around the base device that we'll mount from // instead. This will simulate the code paths for dm-crypt/default-key/bow // and force us to use device-mapper rather than loop devices. uint64_t device_size = 0; { unique_fd fd(open(base_device_.c_str(), O_RDWR | O_CLOEXEC)); ASSERT_GE(fd, 0); device_size = get_block_device_size(fd); ASSERT_EQ(device_size, kTestImageSize * 16); } uint64_t num_sectors = device_size / 512; auto& dm = DeviceMapper::Instance(); DmTable table; table.Emplace<DmTargetLinear>(0, num_sectors, base_device_, 0); ASSERT_TRUE(dm.CreateDevice(wrapper_device_name_, table)); // Format and mount. std::string wrapper_device; ASSERT_TRUE(dm.GetDmDevicePathByName(wrapper_device_name_, &wrapper_device)); ASSERT_TRUE(WaitForFile(wrapper_device, 5s)); ASSERT_TRUE(DoFormat(wrapper_device)); ASSERT_EQ(mount(wrapper_device.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0); ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr)); std::set<std::string> backing_devices; auto init = [&](std::set<std::string> devices) -> bool { backing_devices = std::move(devices); return true; }; std::string path; ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path)); ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/dm-")); ASSERT_TRUE(submanager_->UnmapImageDevice(test_image_name_)); ASSERT_TRUE(submanager_->MapAllImages(init)); ASSERT_FALSE(backing_devices.empty()); ASSERT_TRUE(submanager_->UnmapImageDevice(test_image_name_)); } bool Mkdir(const std::string& path) { if (mkdir(path.c_str(), 0700) && errno != EEXIST) { std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl; Loading fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +5 −0 Original line number Diff line number Diff line Loading @@ -223,6 +223,10 @@ class SnapshotManager final { // optional callback fires periodically to query progress via GetUpdateState. bool HandleImminentDataWipe(const std::function<void()>& callback = {}); // Force a merge to complete in recovery. This is similar to HandleImminentDataWipe // but does not expect a data wipe after. bool FinishMergeInRecovery(); // This method is only allowed in recovery and is used as a helper to // initialize the snapshot devices as a requirement to mount a snapshotted // /system in recovery. Loading Loading @@ -541,6 +545,7 @@ class SnapshotManager final { std::unique_ptr<IDeviceInfo> device_; std::unique_ptr<IImageManager> images_; bool has_local_image_manager_ = false; bool in_factory_data_reset_ = false; }; } // namespace snapshot Loading fs_mgr/libsnapshot/snapshot.cpp +54 −1 Original line number Diff line number Diff line Loading @@ -577,8 +577,16 @@ bool SnapshotManager::InitiateMerge() { return false; } auto other_suffix = device_->GetOtherSlotSuffix(); auto& dm = DeviceMapper::Instance(); for (const auto& snapshot : snapshots) { if (android::base::EndsWith(snapshot, other_suffix)) { // Allow the merge to continue, but log this unexpected case. LOG(ERROR) << "Unexpected snapshot found during merge: " << snapshot; continue; } // The device has to be mapped, since everything should be merged at // the same time. This is a fairly serious error. We could forcefully // map everything here, but it should have been mapped during first- Loading Loading @@ -1008,6 +1016,15 @@ std::string SnapshotManager::GetForwardMergeIndicatorPath() { } void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) { // It's not possible to remove update state in recovery, so write an // indicator that cleanup is needed on reboot. If a factory data reset // was requested, it doesn't matter, everything will get wiped anyway. // To make testing easier we consider a /data wipe as cleaned up. if (device_->IsRecovery() && !in_factory_data_reset_) { WriteUpdateState(lock, UpdateState::MergeCompleted); return; } RemoveAllUpdateState(lock); } Loading Loading @@ -2528,7 +2545,43 @@ bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callba } return true; }; if (!ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback)) { in_factory_data_reset_ = true; bool ok = ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback); in_factory_data_reset_ = false; if (!ok) { 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::FinishMergeInRecovery() { if (!device_->IsRecovery()) { LOG(ERROR) << "Data wipes are only allowed in recovery."; return false; } auto mount = EnsureMetadataMounted(); if (!mount || !mount->HasDevice()) { return false; } auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); auto super_path = device_->GetSuperDevice(slot_number); if (!CreateLogicalAndSnapshotPartitions(super_path)) { LOG(ERROR) << "Unable to map partitions to complete merge."; return false; } UpdateState state = ProcessUpdateState(); if (state != UpdateState::MergeCompleted) { LOG(ERROR) << "Merge returned unexpected status: " << state; return false; } Loading fs_mgr/libsnapshot/snapshot_test.cpp +46 −0 Original line number Diff line number Diff line Loading @@ -1454,6 +1454,52 @@ TEST_F(SnapshotUpdateTest, MergeInRecovery) { ASSERT_EQ(new_sm->GetUpdateState(), UpdateState::None); } // Test that a merge does not clear the snapshot state in fastboot. TEST_F(SnapshotUpdateTest, MergeInFastboot) { // Execute the first update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_TRUE(MapUpdateSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b")); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); init = nullptr; // Initiate the merge and then immediately stop it to simulate a reboot. auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); ASSERT_TRUE(new_sm->InitiateMerge()); ASSERT_TRUE(UnmapAll()); // Simulate a reboot into recovery. auto test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b"); test_device->set_recovery(true); new_sm = SnapshotManager::NewForFirstStageMount(test_device.release()); ASSERT_TRUE(new_sm->FinishMergeInRecovery()); auto mount = new_sm->EnsureMetadataMounted(); ASSERT_TRUE(mount && mount->HasDevice()); ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted); // Finish the merge in a normal boot. test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b"); init = SnapshotManager::NewForFirstStageMount(test_device.release()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); init = nullptr; test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b"); new_sm = SnapshotManager::NewForFirstStageMount(test_device.release()); ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted); ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::None); } // Test that after an OTA, before a merge, we can wipe data in recovery. TEST_F(SnapshotUpdateTest, DataWipeRollbackInRecovery) { // Execute the first update. Loading Loading
fastboot/device/commands.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -625,7 +625,7 @@ bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector<std::string if (!sm) { return device->WriteFail("Unable to create SnapshotManager"); } if (!sm->HandleImminentDataWipe()) { if (!sm->FinishMergeInRecovery()) { return device->WriteFail("Unable to finish snapshot merge"); } } else { Loading
fs_mgr/libfiemap/image_test.cpp +0 −126 Original line number Diff line number Diff line Loading @@ -131,132 +131,6 @@ TEST_F(NativeTest, GetMappedImageDevice) { ASSERT_TRUE(manager_->UnmapImageDevice(base_name_)); } // This fixture is for tests against a simulated device environment. Rather // than use /data, we create an image and then layer a new filesystem within // it. Each test then decides how to mount and create layered images. This // allows us to test FBE vs FDE configurations. class ImageTest : public ::testing::Test { public: ImageTest() : dm_(DeviceMapper::Instance()) {} void SetUp() override { manager_ = ImageManager::Open(kMetadataPath, gDataPath); ASSERT_NE(manager_, nullptr); manager_->set_partition_opener(std::make_unique<TestPartitionOpener>()); submanager_ = ImageManager::Open(kMetadataPath + "/mnt"s, gDataPath + "/mnt"s); ASSERT_NE(submanager_, nullptr); submanager_->set_partition_opener(std::make_unique<TestPartitionOpener>()); // Ensure that metadata is cleared in between runs. submanager_->RemoveAllImages(); manager_->RemoveAllImages(); const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); base_name_ = tinfo->name(); test_image_name_ = base_name_ + "-base"; wrapper_device_name_ = base_name_ + "-wrapper"; ASSERT_TRUE(manager_->CreateBackingImage(base_name_, kTestImageSize * 16, false, nullptr)); ASSERT_TRUE(manager_->MapImageDevice(base_name_, 5s, &base_device_)); } void TearDown() override { submanager_->UnmapImageDevice(test_image_name_); umount(gDataMountPath.c_str()); dm_.DeleteDeviceIfExists(wrapper_device_name_); manager_->UnmapImageDevice(base_name_); manager_->DeleteBackingImage(base_name_); } protected: bool DoFormat(const std::string& device) { // clang-format off std::vector<std::string> mkfs_args = { "/system/bin/mke2fs", "-F", "-b 4096", "-t ext4", "-m 0", "-O has_journal", device, ">/dev/null", "2>/dev/null", "</dev/null", }; // clang-format on auto command = android::base::Join(mkfs_args, " "); return system(command.c_str()) == 0; } std::unique_ptr<ImageManager> manager_; std::unique_ptr<ImageManager> submanager_; DeviceMapper& dm_; std::string base_name_; std::string base_device_; std::string test_image_name_; std::string wrapper_device_name_; }; TEST_F(ImageTest, DirectMount) { ASSERT_TRUE(DoFormat(base_device_)); ASSERT_EQ(mount(base_device_.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0); ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr)); std::string path; ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path)); ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/loop")); } TEST_F(ImageTest, IndirectMount) { #ifdef SKIP_TEST_IN_PRESUBMIT GTEST_SKIP() << "WIP failure b/148874852"; #endif // Create a simple wrapper around the base device that we'll mount from // instead. This will simulate the code paths for dm-crypt/default-key/bow // and force us to use device-mapper rather than loop devices. uint64_t device_size = 0; { unique_fd fd(open(base_device_.c_str(), O_RDWR | O_CLOEXEC)); ASSERT_GE(fd, 0); device_size = get_block_device_size(fd); ASSERT_EQ(device_size, kTestImageSize * 16); } uint64_t num_sectors = device_size / 512; auto& dm = DeviceMapper::Instance(); DmTable table; table.Emplace<DmTargetLinear>(0, num_sectors, base_device_, 0); ASSERT_TRUE(dm.CreateDevice(wrapper_device_name_, table)); // Format and mount. std::string wrapper_device; ASSERT_TRUE(dm.GetDmDevicePathByName(wrapper_device_name_, &wrapper_device)); ASSERT_TRUE(WaitForFile(wrapper_device, 5s)); ASSERT_TRUE(DoFormat(wrapper_device)); ASSERT_EQ(mount(wrapper_device.c_str(), gDataMountPath.c_str(), "ext4", 0, nullptr), 0); ASSERT_TRUE(submanager_->CreateBackingImage(test_image_name_, kTestImageSize, false, nullptr)); std::set<std::string> backing_devices; auto init = [&](std::set<std::string> devices) -> bool { backing_devices = std::move(devices); return true; }; std::string path; ASSERT_TRUE(submanager_->MapImageDevice(test_image_name_, 5s, &path)); ASSERT_TRUE(android::base::StartsWith(path, "/dev/block/dm-")); ASSERT_TRUE(submanager_->UnmapImageDevice(test_image_name_)); ASSERT_TRUE(submanager_->MapAllImages(init)); ASSERT_FALSE(backing_devices.empty()); ASSERT_TRUE(submanager_->UnmapImageDevice(test_image_name_)); } bool Mkdir(const std::string& path) { if (mkdir(path.c_str(), 0700) && errno != EEXIST) { std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl; Loading
fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +5 −0 Original line number Diff line number Diff line Loading @@ -223,6 +223,10 @@ class SnapshotManager final { // optional callback fires periodically to query progress via GetUpdateState. bool HandleImminentDataWipe(const std::function<void()>& callback = {}); // Force a merge to complete in recovery. This is similar to HandleImminentDataWipe // but does not expect a data wipe after. bool FinishMergeInRecovery(); // This method is only allowed in recovery and is used as a helper to // initialize the snapshot devices as a requirement to mount a snapshotted // /system in recovery. Loading Loading @@ -541,6 +545,7 @@ class SnapshotManager final { std::unique_ptr<IDeviceInfo> device_; std::unique_ptr<IImageManager> images_; bool has_local_image_manager_ = false; bool in_factory_data_reset_ = false; }; } // namespace snapshot Loading
fs_mgr/libsnapshot/snapshot.cpp +54 −1 Original line number Diff line number Diff line Loading @@ -577,8 +577,16 @@ bool SnapshotManager::InitiateMerge() { return false; } auto other_suffix = device_->GetOtherSlotSuffix(); auto& dm = DeviceMapper::Instance(); for (const auto& snapshot : snapshots) { if (android::base::EndsWith(snapshot, other_suffix)) { // Allow the merge to continue, but log this unexpected case. LOG(ERROR) << "Unexpected snapshot found during merge: " << snapshot; continue; } // The device has to be mapped, since everything should be merged at // the same time. This is a fairly serious error. We could forcefully // map everything here, but it should have been mapped during first- Loading Loading @@ -1008,6 +1016,15 @@ std::string SnapshotManager::GetForwardMergeIndicatorPath() { } void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) { // It's not possible to remove update state in recovery, so write an // indicator that cleanup is needed on reboot. If a factory data reset // was requested, it doesn't matter, everything will get wiped anyway. // To make testing easier we consider a /data wipe as cleaned up. if (device_->IsRecovery() && !in_factory_data_reset_) { WriteUpdateState(lock, UpdateState::MergeCompleted); return; } RemoveAllUpdateState(lock); } Loading Loading @@ -2528,7 +2545,43 @@ bool SnapshotManager::HandleImminentDataWipe(const std::function<void()>& callba } return true; }; if (!ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback)) { in_factory_data_reset_ = true; bool ok = ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback); in_factory_data_reset_ = false; if (!ok) { 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::FinishMergeInRecovery() { if (!device_->IsRecovery()) { LOG(ERROR) << "Data wipes are only allowed in recovery."; return false; } auto mount = EnsureMetadataMounted(); if (!mount || !mount->HasDevice()) { return false; } auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix()); auto super_path = device_->GetSuperDevice(slot_number); if (!CreateLogicalAndSnapshotPartitions(super_path)) { LOG(ERROR) << "Unable to map partitions to complete merge."; return false; } UpdateState state = ProcessUpdateState(); if (state != UpdateState::MergeCompleted) { LOG(ERROR) << "Merge returned unexpected status: " << state; return false; } Loading
fs_mgr/libsnapshot/snapshot_test.cpp +46 −0 Original line number Diff line number Diff line Loading @@ -1454,6 +1454,52 @@ TEST_F(SnapshotUpdateTest, MergeInRecovery) { ASSERT_EQ(new_sm->GetUpdateState(), UpdateState::None); } // Test that a merge does not clear the snapshot state in fastboot. TEST_F(SnapshotUpdateTest, MergeInFastboot) { // Execute the first update. ASSERT_TRUE(sm->BeginUpdate()); ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_)); ASSERT_TRUE(MapUpdateSnapshots()); ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); // Simulate shutting down the device. ASSERT_TRUE(UnmapAll()); // After reboot, init does first stage mount. auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b")); ASSERT_NE(init, nullptr); ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); init = nullptr; // Initiate the merge and then immediately stop it to simulate a reboot. auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b")); ASSERT_TRUE(new_sm->InitiateMerge()); ASSERT_TRUE(UnmapAll()); // Simulate a reboot into recovery. auto test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b"); test_device->set_recovery(true); new_sm = SnapshotManager::NewForFirstStageMount(test_device.release()); ASSERT_TRUE(new_sm->FinishMergeInRecovery()); auto mount = new_sm->EnsureMetadataMounted(); ASSERT_TRUE(mount && mount->HasDevice()); ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted); // Finish the merge in a normal boot. test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b"); init = SnapshotManager::NewForFirstStageMount(test_device.release()); ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_)); init = nullptr; test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b"); new_sm = SnapshotManager::NewForFirstStageMount(test_device.release()); ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted); ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::None); } // Test that after an OTA, before a merge, we can wipe data in recovery. TEST_F(SnapshotUpdateTest, DataWipeRollbackInRecovery) { // Execute the first update. Loading