Loading fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +17 −1 Original line number Diff line number Diff line Loading @@ -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"; Loading Loading @@ -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); Loading @@ -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; Loading @@ -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_; } Loading Loading @@ -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 = ""); Loading Loading @@ -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); Loading fs_mgr/libsnapshot/snapshot.cpp +114 −8 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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()); Loading Loading @@ -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); Loading Loading @@ -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 Loading Loading @@ -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); } Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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; Loading Loading @@ -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 fs_mgr/libsnapshot/snapshot_test.cpp +50 −0 Original line number Diff line number Diff line Loading @@ -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. Loading fs_mgr/libsnapshot/snapshotctl.cpp +69 −23 Original line number Diff line number Diff line Loading @@ -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; } Loading @@ -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); Loading @@ -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) { Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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."; Loading @@ -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) { Loading Loading @@ -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; Loading @@ -376,7 +421,7 @@ bool MapPrecreatedSnapshots(int argc, char** argv) { } } return cow.WaitForSnapshotWritesToComplete(); return cow.FinishSnapshotWrites(); } #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG Loading Loading @@ -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 }; Loading Loading
fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +17 −1 Original line number Diff line number Diff line Loading @@ -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"; Loading Loading @@ -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); Loading @@ -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; Loading @@ -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_; } Loading Loading @@ -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 = ""); Loading Loading @@ -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); Loading
fs_mgr/libsnapshot/snapshot.cpp +114 −8 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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()); Loading Loading @@ -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); Loading Loading @@ -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 Loading Loading @@ -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); } Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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; Loading Loading @@ -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
fs_mgr/libsnapshot/snapshot_test.cpp +50 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
fs_mgr/libsnapshot/snapshotctl.cpp +69 −23 Original line number Diff line number Diff line Loading @@ -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; } Loading @@ -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); Loading @@ -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) { Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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."; Loading @@ -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) { Loading Loading @@ -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; Loading @@ -376,7 +421,7 @@ bool MapPrecreatedSnapshots(int argc, char** argv) { } } return cow.WaitForSnapshotWritesToComplete(); return cow.FinishSnapshotWrites(); } #ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG Loading Loading @@ -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 }; Loading