Loading fs_mgr/libsnapshot/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ cc_defaults { "libext2_uuid", "libext4_utils", "libfiemap", "libfstab", ], export_include_dirs: ["include"], } Loading fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +9 −5 Original line number Diff line number Diff line Loading @@ -71,11 +71,7 @@ class SnapshotManager final { virtual ~IDeviceInfo() {} virtual std::string GetGsidDir() const = 0; virtual std::string GetMetadataDir() const = 0; // Return true if the device is currently running off snapshot devices, // indicating that we have booted after applying (but not merging) an // OTA. virtual bool IsRunningSnapshot() const = 0; virtual std::string GetSlotSuffix() const = 0; }; ~SnapshotManager(); Loading @@ -93,6 +89,11 @@ class SnapshotManager final { // state != Initiated or None. bool CancelUpdate(); // Mark snapshot writes as having completed. After this, new snapshots cannot // be created, and the device must either cancel the OTA (either before // rebooting or after rolling back), or merge the OTA. bool FinishedSnapshotWrites(); // Initiate a merge on all snapshot devices. This should only be used after an // update has been marked successful after booting. bool InitiateMerge(); Loading Loading @@ -261,6 +262,9 @@ class SnapshotManager final { bool ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status); std::string GetSnapshotStatusFilePath(const std::string& name); std::string GetSnapshotBootIndicatorPath(); void RemoveSnapshotBootIndicator(); // Return the name of the device holding the "snapshot" or "snapshot-merge" // target. This may not be the final device presented via MapSnapshot(), if // for example there is a linear segment. Loading fs_mgr/libsnapshot/snapshot.cpp +51 −8 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <ext4_utils/ext4_utils.h> #include <fstab/fstab.h> #include <libdm/dm.h> #include <libfiemap/image_manager.h> Loading @@ -48,18 +49,15 @@ using namespace std::string_literals; // Unit is sectors, this is a 4K chunk. static constexpr uint32_t kSnapshotChunkSize = 8; static constexpr char kSnapshotBootIndicatorFile[] = "snapshot-boot"; class DeviceInfo final : public SnapshotManager::IDeviceInfo { public: std::string GetGsidDir() const override { return "ota"s; } std::string GetMetadataDir() const override { return "/metadata/ota"s; } bool IsRunningSnapshot() const override; std::string GetSlotSuffix() const override { return fs_mgr_get_slot_suffix(); } }; bool DeviceInfo::IsRunningSnapshot() const { // :TODO: implement this check. return true; } // Note: IIMageManager is an incomplete type in the header, so the default // destructor doesn't work. SnapshotManager::~SnapshotManager() {} Loading Loading @@ -115,6 +113,27 @@ bool SnapshotManager::CancelUpdate() { return true; } bool SnapshotManager::FinishedSnapshotWrites() { auto lock = LockExclusive(); if (!lock) return false; if (ReadUpdateState(lock.get()) != UpdateState::Initiated) { LOG(ERROR) << "Can only transition to the Unverified state from the Initiated state."; return false; } // This file acts as both a quick indicator for init (it can use access(2) // to decide how to do first-stage mounts), and it stores the old slot, so // we can tell whether or not we performed a rollback. auto contents = device_->GetSlotSuffix(); auto boot_file = GetSnapshotBootIndicatorPath(); if (!android::base::WriteStringToFile(contents, boot_file)) { PLOG(ERROR) << "write failed: " << boot_file; return false; } return WriteUpdateState(lock.get(), UpdateState::Unverified); } bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name, uint64_t device_size, uint64_t snapshot_size, uint64_t cow_size) { Loading Loading @@ -339,8 +358,16 @@ bool SnapshotManager::InitiateMerge() { LOG(ERROR) << "Cannot begin a merge if an update has not been verified"; return false; } if (!device_->IsRunningSnapshot()) { LOG(ERROR) << "Cannot begin a merge if the device is not booted off a snapshot"; std::string old_slot; auto boot_file = GetSnapshotBootIndicatorPath(); if (!android::base::ReadFileToString(boot_file, &old_slot)) { LOG(ERROR) << "Could not determine the previous slot; aborting merge"; return false; } auto new_slot = device_->GetSlotSuffix(); if (new_slot == old_slot) { LOG(ERROR) << "Device cannot merge while booting off old slot " << old_slot; return false; } Loading Loading @@ -676,7 +703,23 @@ UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std:: return UpdateState::MergeCompleted; } std::string SnapshotManager::GetSnapshotBootIndicatorPath() { return metadata_dir_ + "/" + kSnapshotBootIndicatorFile; } void SnapshotManager::RemoveSnapshotBootIndicator() { // It's okay if this fails - first-stage init performs a deeper check after // reading the indicator file, so it's not a problem if it still exists // after the update completes. auto boot_file = GetSnapshotBootIndicatorPath(); if (unlink(boot_file.c_str()) == -1 && errno != ENOENT) { PLOG(ERROR) << "unlink " << boot_file; } } void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) { RemoveSnapshotBootIndicator(); if (!WriteUpdateState(lock, UpdateState::None)) { // We'll try again next reboot, ad infinitum. return; Loading fs_mgr/libsnapshot/snapshot_test.cpp +10 −17 Original line number Diff line number Diff line Loading @@ -43,12 +43,12 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo { public: std::string GetGsidDir() const override { return "ota/test"s; } std::string GetMetadataDir() const override { return "/metadata/ota/test"s; } bool IsRunningSnapshot() const override { return is_running_snapshot_; } std::string GetSlotSuffix() const override { return slot_suffix_; } void set_is_running_snapshot(bool value) { is_running_snapshot_ = value; } void set_slot_suffix(const std::string& suffix) { slot_suffix_ = suffix; } private: bool is_running_snapshot_; std::string slot_suffix_; }; std::unique_ptr<SnapshotManager> sm; Loading @@ -60,7 +60,7 @@ class SnapshotTest : public ::testing::Test { protected: void SetUp() override { test_device->set_is_running_snapshot(false); test_device->set_slot_suffix("_a"); if (sm->GetUpdateState() != UpdateState::None) { CleanupTestArtifacts(); Loading Loading @@ -189,15 +189,9 @@ TEST_F(SnapshotTest, MapPartialSnapshot) { } TEST_F(SnapshotTest, NoMergeBeforeReboot) { ASSERT_TRUE(AcquireLock()); // Set the state to Unverified, as if we finished an update. ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified)); ASSERT_TRUE(sm->FinishedSnapshotWrites()); // Release the lock. lock_ = nullptr; // Merge should fail, since we didn't mark the device as rebooted. // Merge should fail, since the slot hasn't changed. ASSERT_FALSE(sm->InitiateMerge()); } Loading Loading @@ -231,7 +225,7 @@ TEST_F(SnapshotTest, Merge) { // Release the lock. lock_ = nullptr; test_device->set_is_running_snapshot(true); test_device->set_slot_suffix("_b"); ASSERT_TRUE(sm->InitiateMerge()); // The device should have been switched to a snapshot-merge target. Loading Loading @@ -273,13 +267,12 @@ TEST_F(SnapshotTest, MergeCannotRemoveCow) { unique_fd fd(open(cow_path.c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_GE(fd, 0); // Set the state to Unverified, as if we finished an update. ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified)); // Release the lock. lock_ = nullptr; test_device->set_is_running_snapshot(true); ASSERT_TRUE(sm->FinishedSnapshotWrites()); test_device->set_slot_suffix("_b"); ASSERT_TRUE(sm->InitiateMerge()); // COW cannot be removed due to open fd, so expect a soft failure. Loading Loading
fs_mgr/libsnapshot/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ cc_defaults { "libext2_uuid", "libext4_utils", "libfiemap", "libfstab", ], export_include_dirs: ["include"], } Loading
fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +9 −5 Original line number Diff line number Diff line Loading @@ -71,11 +71,7 @@ class SnapshotManager final { virtual ~IDeviceInfo() {} virtual std::string GetGsidDir() const = 0; virtual std::string GetMetadataDir() const = 0; // Return true if the device is currently running off snapshot devices, // indicating that we have booted after applying (but not merging) an // OTA. virtual bool IsRunningSnapshot() const = 0; virtual std::string GetSlotSuffix() const = 0; }; ~SnapshotManager(); Loading @@ -93,6 +89,11 @@ class SnapshotManager final { // state != Initiated or None. bool CancelUpdate(); // Mark snapshot writes as having completed. After this, new snapshots cannot // be created, and the device must either cancel the OTA (either before // rebooting or after rolling back), or merge the OTA. bool FinishedSnapshotWrites(); // Initiate a merge on all snapshot devices. This should only be used after an // update has been marked successful after booting. bool InitiateMerge(); Loading Loading @@ -261,6 +262,9 @@ class SnapshotManager final { bool ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status); std::string GetSnapshotStatusFilePath(const std::string& name); std::string GetSnapshotBootIndicatorPath(); void RemoveSnapshotBootIndicator(); // Return the name of the device holding the "snapshot" or "snapshot-merge" // target. This may not be the final device presented via MapSnapshot(), if // for example there is a linear segment. Loading
fs_mgr/libsnapshot/snapshot.cpp +51 −8 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <ext4_utils/ext4_utils.h> #include <fstab/fstab.h> #include <libdm/dm.h> #include <libfiemap/image_manager.h> Loading @@ -48,18 +49,15 @@ using namespace std::string_literals; // Unit is sectors, this is a 4K chunk. static constexpr uint32_t kSnapshotChunkSize = 8; static constexpr char kSnapshotBootIndicatorFile[] = "snapshot-boot"; class DeviceInfo final : public SnapshotManager::IDeviceInfo { public: std::string GetGsidDir() const override { return "ota"s; } std::string GetMetadataDir() const override { return "/metadata/ota"s; } bool IsRunningSnapshot() const override; std::string GetSlotSuffix() const override { return fs_mgr_get_slot_suffix(); } }; bool DeviceInfo::IsRunningSnapshot() const { // :TODO: implement this check. return true; } // Note: IIMageManager is an incomplete type in the header, so the default // destructor doesn't work. SnapshotManager::~SnapshotManager() {} Loading Loading @@ -115,6 +113,27 @@ bool SnapshotManager::CancelUpdate() { return true; } bool SnapshotManager::FinishedSnapshotWrites() { auto lock = LockExclusive(); if (!lock) return false; if (ReadUpdateState(lock.get()) != UpdateState::Initiated) { LOG(ERROR) << "Can only transition to the Unverified state from the Initiated state."; return false; } // This file acts as both a quick indicator for init (it can use access(2) // to decide how to do first-stage mounts), and it stores the old slot, so // we can tell whether or not we performed a rollback. auto contents = device_->GetSlotSuffix(); auto boot_file = GetSnapshotBootIndicatorPath(); if (!android::base::WriteStringToFile(contents, boot_file)) { PLOG(ERROR) << "write failed: " << boot_file; return false; } return WriteUpdateState(lock.get(), UpdateState::Unverified); } bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name, uint64_t device_size, uint64_t snapshot_size, uint64_t cow_size) { Loading Loading @@ -339,8 +358,16 @@ bool SnapshotManager::InitiateMerge() { LOG(ERROR) << "Cannot begin a merge if an update has not been verified"; return false; } if (!device_->IsRunningSnapshot()) { LOG(ERROR) << "Cannot begin a merge if the device is not booted off a snapshot"; std::string old_slot; auto boot_file = GetSnapshotBootIndicatorPath(); if (!android::base::ReadFileToString(boot_file, &old_slot)) { LOG(ERROR) << "Could not determine the previous slot; aborting merge"; return false; } auto new_slot = device_->GetSlotSuffix(); if (new_slot == old_slot) { LOG(ERROR) << "Device cannot merge while booting off old slot " << old_slot; return false; } Loading Loading @@ -676,7 +703,23 @@ UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std:: return UpdateState::MergeCompleted; } std::string SnapshotManager::GetSnapshotBootIndicatorPath() { return metadata_dir_ + "/" + kSnapshotBootIndicatorFile; } void SnapshotManager::RemoveSnapshotBootIndicator() { // It's okay if this fails - first-stage init performs a deeper check after // reading the indicator file, so it's not a problem if it still exists // after the update completes. auto boot_file = GetSnapshotBootIndicatorPath(); if (unlink(boot_file.c_str()) == -1 && errno != ENOENT) { PLOG(ERROR) << "unlink " << boot_file; } } void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) { RemoveSnapshotBootIndicator(); if (!WriteUpdateState(lock, UpdateState::None)) { // We'll try again next reboot, ad infinitum. return; Loading
fs_mgr/libsnapshot/snapshot_test.cpp +10 −17 Original line number Diff line number Diff line Loading @@ -43,12 +43,12 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo { public: std::string GetGsidDir() const override { return "ota/test"s; } std::string GetMetadataDir() const override { return "/metadata/ota/test"s; } bool IsRunningSnapshot() const override { return is_running_snapshot_; } std::string GetSlotSuffix() const override { return slot_suffix_; } void set_is_running_snapshot(bool value) { is_running_snapshot_ = value; } void set_slot_suffix(const std::string& suffix) { slot_suffix_ = suffix; } private: bool is_running_snapshot_; std::string slot_suffix_; }; std::unique_ptr<SnapshotManager> sm; Loading @@ -60,7 +60,7 @@ class SnapshotTest : public ::testing::Test { protected: void SetUp() override { test_device->set_is_running_snapshot(false); test_device->set_slot_suffix("_a"); if (sm->GetUpdateState() != UpdateState::None) { CleanupTestArtifacts(); Loading Loading @@ -189,15 +189,9 @@ TEST_F(SnapshotTest, MapPartialSnapshot) { } TEST_F(SnapshotTest, NoMergeBeforeReboot) { ASSERT_TRUE(AcquireLock()); // Set the state to Unverified, as if we finished an update. ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified)); ASSERT_TRUE(sm->FinishedSnapshotWrites()); // Release the lock. lock_ = nullptr; // Merge should fail, since we didn't mark the device as rebooted. // Merge should fail, since the slot hasn't changed. ASSERT_FALSE(sm->InitiateMerge()); } Loading Loading @@ -231,7 +225,7 @@ TEST_F(SnapshotTest, Merge) { // Release the lock. lock_ = nullptr; test_device->set_is_running_snapshot(true); test_device->set_slot_suffix("_b"); ASSERT_TRUE(sm->InitiateMerge()); // The device should have been switched to a snapshot-merge target. Loading Loading @@ -273,13 +267,12 @@ TEST_F(SnapshotTest, MergeCannotRemoveCow) { unique_fd fd(open(cow_path.c_str(), O_RDONLY | O_CLOEXEC)); ASSERT_GE(fd, 0); // Set the state to Unverified, as if we finished an update. ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified)); // Release the lock. lock_ = nullptr; test_device->set_is_running_snapshot(true); ASSERT_TRUE(sm->FinishedSnapshotWrites()); test_device->set_slot_suffix("_b"); ASSERT_TRUE(sm->InitiateMerge()); // COW cannot be removed due to open fd, so expect a soft failure. Loading