Merge "libsnapshot: Track the source slot across reboots."
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 52aad12..746987e 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -33,6 +33,7 @@
         "libext2_uuid",
         "libext4_utils",
         "libfiemap",
+        "libfstab",
     ],
     export_include_dirs: ["include"],
 }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index f7608dc..4fb6808 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -71,11 +71,7 @@
         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();
@@ -93,6 +89,11 @@
     // 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();
@@ -261,6 +262,9 @@
     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.
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 63a01f3..75a1f26 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -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>
 
@@ -48,18 +49,15 @@
 // 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() {}
@@ -115,6 +113,27 @@
     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) {
@@ -339,8 +358,16 @@
         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;
     }
 
@@ -676,7 +703,23 @@
     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;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 4903224..34ea331 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -43,12 +43,12 @@
   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;
@@ -60,7 +60,7 @@
 
   protected:
     void SetUp() override {
-        test_device->set_is_running_snapshot(false);
+        test_device->set_slot_suffix("_a");
 
         if (sm->GetUpdateState() != UpdateState::None) {
             CleanupTestArtifacts();
@@ -189,15 +189,9 @@
 }
 
 TEST_F(SnapshotTest, NoMergeBeforeReboot) {
-    ASSERT_TRUE(AcquireLock());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
 
-    // Set the state to Unverified, as if we finished an update.
-    ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::Unverified));
-
-    // 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());
 }
 
@@ -231,7 +225,7 @@
     // 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.
@@ -273,13 +267,12 @@
     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.