libsnapshot: Integrate userspace snapshots APIs

dm-user block device will be the snapshot device; thus, no
more explicit call to MapSnapshot(). Additionally, block device
name for dm-user will be the snapshot name so that mount works
seamlessly.

API's to query the snapshot status, merge progress has been
integrated. Since daemon requires base device for merge, we pass
additional parameter during initialization.

Add a new virtual a/b property flag to enable/disable
user-snapshots feature. Propagate this flag to init layer
for first stage mount during boot process.

Some minor cleanup and renaming of variables.

Bug: 193863443
Test: 1: Full OTA on CF and pixel and verify the merge completion.
Tested merge-resume path by rebooting device during merge.
2: Incremental OTA on CF and pixel

Signed-off-by: Akilesh Kailash <akailash@google.com>
Change-Id: I5088f40a55807946cd044b3987678ead3696d996
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index e2abdba..532f66d 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -194,6 +194,9 @@
 
     // Source build fingerprint.
     string source_build_fingerprint = 8;
+
+    // user-space snapshots
+    bool userspace_snapshots = 9;
 }
 
 // Next: 10
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index ec58cca..ba62330 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -35,6 +35,7 @@
                 (override));
     MOCK_METHOD(UpdateState, GetUpdateState, (double* progress), (override));
     MOCK_METHOD(bool, UpdateUsesCompression, (), (override));
+    MOCK_METHOD(bool, UpdateUsesUserSnapshots, (), (override));
     MOCK_METHOD(Return, CreateUpdateSnapshots,
                 (const chromeos_update_engine::DeltaArchiveManifest& manifest), (override));
     MOCK_METHOD(bool, MapUpdateSnapshot,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index a49b026..08c3920 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -193,6 +193,9 @@
     // UpdateState is None, or no snapshots have been created.
     virtual bool UpdateUsesCompression() = 0;
 
+    // Returns true if userspace snapshots is enabled for the current update.
+    virtual bool UpdateUsesUserSnapshots() = 0;
+
     // Create necessary COW device / files for OTA clients. New logical partitions will be added to
     // group "cow" in target_metadata. Regions of partitions of current_metadata will be
     // "write-protected" and snapshotted.
@@ -352,6 +355,7 @@
                                    const std::function<bool()>& before_cancel = {}) override;
     UpdateState GetUpdateState(double* progress = nullptr) override;
     bool UpdateUsesCompression() override;
+    bool UpdateUsesUserSnapshots() override;
     Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) override;
     bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
                            std::string* snapshot_path) override;
@@ -387,6 +391,11 @@
     // first-stage to decide whether to launch snapuserd.
     bool IsSnapuserdRequired();
 
+    enum class SnapshotDriver {
+        DM_SNAPSHOT,
+        DM_USER,
+    };
+
   private:
     FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
     FRIEND_TEST(SnapshotTest, CreateSnapshot);
@@ -456,6 +465,8 @@
     };
     static std::unique_ptr<LockedFile> OpenFile(const std::string& file, int lock_flags);
 
+    SnapshotDriver GetSnapshotDriver(LockedFile* lock);
+
     // Create a new snapshot record. This creates the backing COW store and
     // persists information needed to map the device. The device can be mapped
     // with MapSnapshot().
@@ -491,8 +502,8 @@
 
     // Create a dm-user device for a given snapshot.
     bool MapDmUserCow(LockedFile* lock, const std::string& name, const std::string& cow_file,
-                      const std::string& base_device, const std::chrono::milliseconds& timeout_ms,
-                      std::string* path);
+                      const std::string& base_device, const std::string& base_path_merge,
+                      const std::chrono::milliseconds& timeout_ms, std::string* path);
 
     // Map the source device used for dm-user.
     bool MapSourceDevice(LockedFile* lock, const std::string& name,
@@ -591,7 +602,8 @@
     // Internal callback for when merging is complete.
     bool OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
                                  const SnapshotStatus& status);
-    bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status);
+    bool CollapseSnapshotDevice(LockedFile* lock, const std::string& name,
+                                const SnapshotStatus& status);
 
     struct MergeResult {
         explicit MergeResult(UpdateState state,
@@ -689,7 +701,10 @@
     bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name);
 
     // Unmap a dm-user device through snapuserd.
-    bool UnmapDmUserDevice(const std::string& snapshot_name);
+    bool UnmapDmUserDevice(const std::string& dm_user_name);
+
+    // Unmap a dm-user device for user space snapshots
+    bool UnmapUserspaceSnapshotDevice(LockedFile* lock, const std::string& snapshot_name);
 
     // If there isn't a previous update, return true. |needs_merge| is set to false.
     // If there is a previous update but the device has not boot into it, tries to cancel the
@@ -778,6 +793,8 @@
 
     // Helper of UpdateUsesCompression
     bool UpdateUsesCompression(LockedFile* lock);
+    // Helper of UpdateUsesUsersnapshots
+    bool UpdateUsesUserSnapshots(LockedFile* lock);
 
     // Wrapper around libdm, with diagnostics.
     bool DeleteDeviceIfExists(const std::string& name,
@@ -792,6 +809,7 @@
     std::function<bool(const std::string&)> uevent_regen_callback_;
     std::unique_ptr<SnapuserdClient> snapuserd_client_;
     std::unique_ptr<LpMetadata> old_partition_metadata_;
+    std::optional<bool> is_snapshot_userspace_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index 74b78c5..318e525 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -35,6 +35,7 @@
                                    const std::function<bool()>& before_cancel = {}) override;
     UpdateState GetUpdateState(double* progress = nullptr) override;
     bool UpdateUsesCompression() override;
+    bool UpdateUsesUserSnapshots() override;
     Return CreateUpdateSnapshots(
             const chromeos_update_engine::DeltaArchiveManifest& manifest) override;
     bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 3d8ae29..f4584d2 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -95,6 +95,7 @@
     if (!info) {
         info = new DeviceInfo();
     }
+
     return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
 }
 
@@ -121,8 +122,34 @@
     return snapshot_name + "-cow";
 }
 
-static std::string GetDmUserCowName(const std::string& snapshot_name) {
-    return snapshot_name + "-user-cow";
+SnapshotManager::SnapshotDriver SnapshotManager::GetSnapshotDriver(LockedFile* lock) {
+    if (UpdateUsesUserSnapshots(lock)) {
+        return SnapshotManager::SnapshotDriver::DM_USER;
+    } else {
+        return SnapshotManager::SnapshotDriver::DM_SNAPSHOT;
+    }
+}
+
+static std::string GetDmUserCowName(const std::string& snapshot_name,
+                                    SnapshotManager::SnapshotDriver driver) {
+    // dm-user block device will act as a snapshot device. We identify it with
+    // the same partition name so that when partitions can be mounted off
+    // dm-user.
+
+    switch (driver) {
+        case SnapshotManager::SnapshotDriver::DM_USER: {
+            return snapshot_name;
+        }
+
+        case SnapshotManager::SnapshotDriver::DM_SNAPSHOT: {
+            return snapshot_name + "-user-cow";
+        }
+
+        default: {
+            LOG(ERROR) << "Invalid snapshot driver";
+            return "";
+        }
+    }
 }
 
 static std::string GetCowImageDeviceName(const std::string& snapshot_name) {
@@ -398,9 +425,33 @@
 
 bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
                                    const std::string& cow_file, const std::string& base_device,
+                                   const std::string& base_path_merge,
                                    const std::chrono::milliseconds& timeout_ms, std::string* path) {
     CHECK(lock);
 
+    if (UpdateUsesUserSnapshots(lock)) {
+        SnapshotStatus status;
+        if (!ReadSnapshotStatus(lock, name, &status)) {
+            LOG(ERROR) << "MapDmUserCow: ReadSnapshotStatus failed...";
+            return false;
+        }
+
+        if (status.state() == SnapshotState::NONE ||
+            status.state() == SnapshotState::MERGE_COMPLETED) {
+            LOG(ERROR) << "Should not create a snapshot device for " << name
+                       << " after merging has completed.";
+            return false;
+        }
+
+        SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+        if (update_status.state() == UpdateState::MergeCompleted ||
+            update_status.state() == UpdateState::MergeNeedsReboot) {
+            LOG(ERROR) << "Should not create a snapshot device for " << name
+                       << " after global merging has completed.";
+            return false;
+        }
+    }
+
     // Use an extra decoration for first-stage init, so we can transition
     // to a new table entry in second-stage.
     std::string misc_name = name;
@@ -412,18 +463,41 @@
         return false;
     }
 
-    uint64_t base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
-    if (base_sectors == 0) {
-        LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
-        return false;
+    uint64_t base_sectors = 0;
+    if (!UpdateUsesUserSnapshots(lock)) {
+        base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
+        if (base_sectors == 0) {
+            LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
+            return false;
+        }
+    } 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
+        // created in the daemon, we no longer need to rely on the daemon for
+        // sizing the dm-user block device.
+        unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge.c_str(), O_RDONLY | O_CLOEXEC)));
+        if (fd < 0) {
+            LOG(ERROR) << "Cannot open block device: " << base_path_merge;
+            return false;
+        }
+
+        uint64_t dev_sz = get_block_device_size(fd.get());
+        if (!dev_sz) {
+            LOG(ERROR) << "Failed to find block device size: " << base_path_merge;
+            return false;
+        }
+
+        base_sectors = dev_sz >> 9;
     }
 
     DmTable table;
     table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
     if (!dm_.CreateDevice(name, table, path, timeout_ms)) {
+        LOG(ERROR) << " dm-user: CreateDevice failed... ";
         return false;
     }
     if (!WaitForDevice(*path, timeout_ms)) {
+        LOG(ERROR) << " dm-user: timeout: Failed to create block device for: " << name;
         return false;
     }
 
@@ -432,6 +506,15 @@
         return false;
     }
 
+    if (UpdateUsesUserSnapshots(lock)) {
+        // Now that the dm-user device is created, initialize the daemon and
+        // spin up the worker threads.
+        if (!snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device, base_path_merge)) {
+            LOG(ERROR) << "InitDmUserCow failed";
+            return false;
+        }
+    }
+
     return snapuserd_client_->AttachDmUser(misc_name);
 }
 
@@ -698,13 +781,15 @@
 
     DmTargetSnapshot::Status initial_target_values = {};
     for (const auto& snapshot : snapshots) {
-        DmTargetSnapshot::Status current_status;
-        if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) {
-            return false;
+        if (!UpdateUsesUserSnapshots(lock.get())) {
+            DmTargetSnapshot::Status current_status;
+            if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) {
+                return false;
+            }
+            initial_target_values.sectors_allocated += current_status.sectors_allocated;
+            initial_target_values.total_sectors += current_status.total_sectors;
+            initial_target_values.metadata_sectors += current_status.metadata_sectors;
         }
-        initial_target_values.sectors_allocated += current_status.sectors_allocated;
-        initial_target_values.total_sectors += current_status.total_sectors;
-        initial_target_values.metadata_sectors += current_status.metadata_sectors;
 
         SnapshotStatus snapshot_status;
         if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) {
@@ -719,11 +804,14 @@
 
     SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get());
     initial_status.set_state(UpdateState::Merging);
-    initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
-    initial_status.set_total_sectors(initial_target_values.total_sectors);
-    initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
     initial_status.set_compression_enabled(compression_enabled);
 
+    if (!UpdateUsesUserSnapshots(lock.get())) {
+        initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
+        initial_status.set_total_sectors(initial_target_values.total_sectors);
+        initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
+    }
+
     // If any partitions shrunk, we need to merge them before we merge any other
     // partitions (see b/177935716). Otherwise, a merge from another partition
     // may overwrite the source block of a copy operation.
@@ -777,20 +865,36 @@
                      << " has unexpected state: " << SnapshotState_Name(status.state());
     }
 
-    // After this, we return true because we technically did switch to a merge
-    // target. Everything else we do here is just informational.
-    if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
-        return code;
+    if (UpdateUsesUserSnapshots(lock)) {
+        if (EnsureSnapuserdConnected()) {
+            // This is the point where we inform the daemon to initiate/resume
+            // the merge
+            if (!snapuserd_client_->InitiateMerge(name)) {
+                return MergeFailureCode::UnknownTable;
+            }
+        } else {
+            LOG(ERROR) << "Failed to connect to snapuserd daemon to initiate merge";
+            return MergeFailureCode::UnknownTable;
+        }
+    } else {
+        // After this, we return true because we technically did switch to a merge
+        // target. Everything else we do here is just informational.
+        if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
+            return code;
+        }
     }
 
     status.set_state(SnapshotState::MERGING);
 
-    DmTargetSnapshot::Status dm_status;
-    if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
-        LOG(ERROR) << "Could not query merge status for snapshot: " << name;
+    if (!UpdateUsesUserSnapshots(lock)) {
+        DmTargetSnapshot::Status dm_status;
+        if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
+            LOG(ERROR) << "Could not query merge status for snapshot: " << name;
+        }
+        status.set_sectors_allocated(dm_status.sectors_allocated);
+        status.set_metadata_sectors(dm_status.metadata_sectors);
     }
-    status.set_sectors_allocated(dm_status.sectors_allocated);
-    status.set_metadata_sectors(dm_status.metadata_sectors);
+
     if (!WriteSnapshotStatus(lock, status)) {
         LOG(ERROR) << "Could not update status file for snapshot: " << name;
     }
@@ -856,9 +960,15 @@
         return false;
     }
     auto type = DeviceMapper::GetTargetType(snap_target.spec);
-    if (type != "snapshot" && type != "snapshot-merge") {
-        return false;
+
+    // If this is not a user-snapshot device then it should either
+    // be a dm-snapshot or dm-snapshot-merge target
+    if (type != "user") {
+        if (type != "snapshot" && type != "snapshot-merge") {
+            return false;
+        }
     }
+
     if (target) {
         *target = std::move(snap_target);
     }
@@ -1094,34 +1204,86 @@
     DCHECK((current_metadata = ReadCurrentMetadata()) &&
            GetMetadataPartitionState(*current_metadata, name) == MetadataPartitionState::Updated);
 
-    std::string target_type;
-    DmTargetSnapshot::Status status;
-    if (!QuerySnapshotStatus(name, &target_type, &status)) {
-        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
-    }
-    if (target_type == "snapshot" &&
-        DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
-        update_status.merge_phase() == MergePhase::FIRST_PHASE) {
-        // The snapshot is not being merged because it's in the wrong phase.
-        return MergeResult(UpdateState::None);
-    }
-    if (target_type != "snapshot-merge") {
-        // We can get here if we failed to rewrite the target type in
-        // InitiateMerge(). If we failed to create the target in first-stage
-        // init, boot would not succeed.
-        LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
-        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
+    if (UpdateUsesUserSnapshots(lock)) {
+        std::string merge_status;
+        if (EnsureSnapuserdConnected()) {
+            // Query the snapshot status from the daemon
+            merge_status = snapuserd_client_->QuerySnapshotStatus(name);
+        } else {
+            MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
+        }
+
+        if (merge_status == "snapshot-merge-failed") {
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
+        }
+
+        // This is the case when device reboots during merge. Once the device boots,
+        // snapuserd daemon will not resume merge immediately in first stage init.
+        // This is slightly different as compared to dm-snapshot-merge; In this
+        // case, metadata file will have "MERGING" state whereas the daemon will be
+        // waiting to resume the merge. Thus, we resume the merge at this point.
+        if (merge_status == "snapshot" && snapshot_status.state() == SnapshotState::MERGING) {
+            if (!snapuserd_client_->InitiateMerge(name)) {
+                return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
+            }
+            return MergeResult(UpdateState::Merging);
+        }
+
+        if (merge_status == "snapshot" &&
+            DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
+            update_status.merge_phase() == MergePhase::FIRST_PHASE) {
+            // The snapshot is not being merged because it's in the wrong phase.
+            return MergeResult(UpdateState::None);
+        }
+
+        if (merge_status == "snapshot-merge") {
+            if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
+                LOG(ERROR) << "Snapshot " << name
+                           << " is merging after being marked merge-complete.";
+                return MergeResult(UpdateState::MergeFailed,
+                                   MergeFailureCode::UnmergedSectorsAfterCompletion);
+            }
+            return MergeResult(UpdateState::Merging);
+        }
+
+        if (merge_status != "snapshot-merge-complete") {
+            LOG(ERROR) << "Snapshot " << name << " has incorrect status: " << merge_status;
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
+        }
+    } else {
+        // dm-snapshot in the kernel
+        std::string target_type;
+        DmTargetSnapshot::Status status;
+        if (!QuerySnapshotStatus(name, &target_type, &status)) {
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
+        }
+        if (target_type == "snapshot" &&
+            DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
+            update_status.merge_phase() == MergePhase::FIRST_PHASE) {
+            // The snapshot is not being merged because it's in the wrong phase.
+            return MergeResult(UpdateState::None);
+        }
+        if (target_type != "snapshot-merge") {
+            // We can get here if we failed to rewrite the target type in
+            // InitiateMerge(). If we failed to create the target in first-stage
+            // init, boot would not succeed.
+            LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
+            return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
+        }
+
+        // These two values are equal when merging is complete.
+        if (status.sectors_allocated != status.metadata_sectors) {
+            if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
+                LOG(ERROR) << "Snapshot " << name
+                           << " is merging after being marked merge-complete.";
+                return MergeResult(UpdateState::MergeFailed,
+                                   MergeFailureCode::UnmergedSectorsAfterCompletion);
+            }
+            return MergeResult(UpdateState::Merging);
+        }
     }
 
-    // These two values are equal when merging is complete.
-    if (status.sectors_allocated != status.metadata_sectors) {
-        if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
-            LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
-            return MergeResult(UpdateState::MergeFailed,
-                               MergeFailureCode::UnmergedSectorsAfterCompletion);
-        }
-        return MergeResult(UpdateState::Merging);
-    }
+    // Merge is complete at this point
 
     auto code = CheckMergeConsistency(lock, name, snapshot_status);
     if (code != MergeFailureCode::Ok) {
@@ -1311,30 +1473,40 @@
 
 bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
                                               const SnapshotStatus& status) {
-    if (IsSnapshotDevice(name)) {
-        // We are extra-cautious here, to avoid deleting the wrong table.
-        std::string target_type;
-        DmTargetSnapshot::Status dm_status;
-        if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
-            return false;
+    if (!UpdateUsesUserSnapshots(lock)) {
+        if (IsSnapshotDevice(name)) {
+            // We are extra-cautious here, to avoid deleting the wrong table.
+            std::string target_type;
+            DmTargetSnapshot::Status dm_status;
+            if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
+                return false;
+            }
+            if (target_type != "snapshot-merge") {
+                LOG(ERROR) << "Unexpected target type " << target_type
+                           << " for snapshot device: " << name;
+                return false;
+            }
+            if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
+                LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
+                return false;
+            }
+            if (!CollapseSnapshotDevice(lock, name, status)) {
+                LOG(ERROR) << "Unable to collapse snapshot: " << name;
+                return false;
+            }
         }
-        if (target_type != "snapshot-merge") {
-            LOG(ERROR) << "Unexpected target type " << target_type
-                       << " for snapshot device: " << name;
-            return false;
-        }
-        if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
-            LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
-            return false;
-        }
-        if (!CollapseSnapshotDevice(name, status)) {
+    } else {
+        // Just collapse the device - no need to query again as we just did
+        // prior to calling this function
+        if (!CollapseSnapshotDevice(lock, name, status)) {
             LOG(ERROR) << "Unable to collapse snapshot: " << name;
             return false;
         }
-        // Note that collapsing is implicitly an Unmap, so we don't need to
-        // unmap the snapshot.
     }
 
+    // Note that collapsing is implicitly an Unmap, so we don't need to
+    // unmap the snapshot.
+
     if (!DeleteSnapshot(lock, name)) {
         LOG(ERROR) << "Could not delete snapshot: " << name;
         return false;
@@ -1342,23 +1514,26 @@
     return true;
 }
 
-bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
+bool SnapshotManager::CollapseSnapshotDevice(LockedFile* lock, const std::string& name,
                                              const SnapshotStatus& status) {
-    // Verify we have a snapshot-merge device.
-    DeviceMapper::TargetInfo target;
-    if (!GetSingleTarget(name, TableQuery::Table, &target)) {
-        return false;
-    }
-    if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
-        // This should be impossible, it was checked earlier.
-        LOG(ERROR) << "Snapshot device has invalid target type: " << name;
-        return false;
-    }
+    if (!UpdateUsesUserSnapshots(lock)) {
+        // Verify we have a snapshot-merge device.
+        DeviceMapper::TargetInfo target;
+        if (!GetSingleTarget(name, TableQuery::Table, &target)) {
+            return false;
+        }
+        if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
+            // This should be impossible, it was checked earlier.
+            LOG(ERROR) << "Snapshot device has invalid target type: " << name;
+            return false;
+        }
 
-    std::string base_device, cow_device;
-    if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
-        LOG(ERROR) << "Could not parse snapshot device " << name << " parameters: " << target.data;
-        return false;
+        std::string base_device, cow_device;
+        if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
+            LOG(ERROR) << "Could not parse snapshot device " << name
+                       << " parameters: " << target.data;
+            return false;
+        }
     }
 
     uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
@@ -1386,14 +1561,32 @@
         return false;
     }
 
-    // Attempt to delete the snapshot device if one still exists. Nothing
-    // should be depending on the device, and device-mapper should have
-    // flushed remaining I/O. We could in theory replace with dm-zero (or
-    // re-use the table above), but for now it's better to know why this
-    // would fail.
-    if (status.compression_enabled()) {
-        UnmapDmUserDevice(name);
+    if (!UpdateUsesUserSnapshots(lock)) {
+        // Attempt to delete the snapshot device if one still exists. Nothing
+        // should be depending on the device, and device-mapper should have
+        // flushed remaining I/O. We could in theory replace with dm-zero (or
+        // re-use the table above), but for now it's better to know why this
+        // would fail.
+        //
+        // Furthermore, we should not be trying to unmap for userspace snapshot
+        // as unmap will fail since dm-user itself was a snapshot device prior
+        // to switching of tables. Unmap will fail as the device will be mounted
+        // by system partitions
+        if (status.compression_enabled()) {
+            auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
+            UnmapDmUserDevice(dm_user_name);
+        }
     }
+
+    // We can't delete base device immediately as daemon holds a reference.
+    // Make sure we wait for all the worker threads to terminate and release
+    // the reference
+    if (UpdateUsesUserSnapshots(lock) && EnsureSnapuserdConnected()) {
+        if (!snapuserd_client_->WaitForDeviceDelete(name)) {
+            LOG(ERROR) << "Failed to wait for " << name << " control device to delete";
+        }
+    }
+
     auto base_name = GetBaseDeviceName(name);
     if (!DeleteDeviceIfExists(base_name)) {
         LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name;
@@ -1464,10 +1657,15 @@
         return false;
     }
 
+    if (UpdateUsesUserSnapshots(lock.get()) && transition == InitTransition::SELINUX_DETACH) {
+        snapuserd_argv->emplace_back("-user_snapshot");
+    }
+
     size_t num_cows = 0;
     size_t ok_cows = 0;
     for (const auto& snapshot : snapshots) {
-        std::string user_cow_name = GetDmUserCowName(snapshot);
+        std::string user_cow_name = GetDmUserCowName(snapshot, GetSnapshotDriver(lock.get()));
+
         if (dm_.GetState(user_cow_name) == DmDeviceState::INVALID) {
             continue;
         }
@@ -1513,6 +1711,12 @@
             continue;
         }
 
+        std::string base_path_merge;
+        if (!dm_.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &base_path_merge)) {
+            LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot);
+            continue;
+        }
+
         std::string cow_image_name = GetMappedCowDeviceName(snapshot, snapshot_status);
 
         std::string cow_image_device;
@@ -1529,8 +1733,14 @@
         }
 
         if (transition == InitTransition::SELINUX_DETACH) {
-            auto message = misc_name + "," + cow_image_device + "," + source_device;
-            snapuserd_argv->emplace_back(std::move(message));
+            if (!UpdateUsesUserSnapshots(lock.get())) {
+                auto message = misc_name + "," + cow_image_device + "," + source_device;
+                snapuserd_argv->emplace_back(std::move(message));
+            } else {
+                auto message = misc_name + "," + cow_image_device + "," + source_device + "," +
+                               base_path_merge;
+                snapuserd_argv->emplace_back(std::move(message));
+            }
 
             // Do not attempt to connect to the new snapuserd yet, it hasn't
             // been started. We do however want to wait for the misc device
@@ -1539,8 +1749,15 @@
             continue;
         }
 
-        uint64_t base_sectors =
-                snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
+        uint64_t base_sectors;
+        if (!UpdateUsesUserSnapshots(lock.get())) {
+            base_sectors =
+                    snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
+        } else {
+            base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_image_device,
+                                                            source_device, base_path_merge);
+        }
+
         if (base_sectors == 0) {
             // Unrecoverable as metadata reads from cow device failed
             LOG(FATAL) << "Failed to retrieve base_sectors from Snapuserd";
@@ -1775,30 +1992,36 @@
         return state;
     }
 
-    // Sum all the snapshot states as if the system consists of a single huge
-    // snapshots device, then compute the merge completion percentage of that
-    // device.
-    std::vector<std::string> snapshots;
-    if (!ListSnapshots(lock.get(), &snapshots)) {
-        LOG(ERROR) << "Could not list snapshots";
-        return state;
+    if (!UpdateUsesUserSnapshots(lock.get())) {
+        // Sum all the snapshot states as if the system consists of a single huge
+        // snapshots device, then compute the merge completion percentage of that
+        // device.
+        std::vector<std::string> snapshots;
+        if (!ListSnapshots(lock.get(), &snapshots)) {
+            LOG(ERROR) << "Could not list snapshots";
+            return state;
+        }
+
+        DmTargetSnapshot::Status fake_snapshots_status = {};
+        for (const auto& snapshot : snapshots) {
+            DmTargetSnapshot::Status current_status;
+
+            if (!IsSnapshotDevice(snapshot)) continue;
+            if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
+
+            fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
+            fake_snapshots_status.total_sectors += current_status.total_sectors;
+            fake_snapshots_status.metadata_sectors += current_status.metadata_sectors;
+        }
+
+        *progress = DmTargetSnapshot::MergePercent(fake_snapshots_status,
+                                                   update_status.sectors_allocated());
+    } else {
+        if (EnsureSnapuserdConnected()) {
+            *progress = snapuserd_client_->GetMergePercent();
+        }
     }
 
-    DmTargetSnapshot::Status fake_snapshots_status = {};
-    for (const auto& snapshot : snapshots) {
-        DmTargetSnapshot::Status current_status;
-
-        if (!IsSnapshotDevice(snapshot)) continue;
-        if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
-
-        fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
-        fake_snapshots_status.total_sectors += current_status.total_sectors;
-        fake_snapshots_status.metadata_sectors += current_status.metadata_sectors;
-    }
-
-    *progress = DmTargetSnapshot::MergePercent(fake_snapshots_status,
-                                               update_status.sectors_allocated());
-
     return state;
 }
 
@@ -1813,6 +2036,38 @@
     return update_status.compression_enabled();
 }
 
+bool SnapshotManager::UpdateUsesUserSnapshots() {
+    // This and the following function is constantly
+    // invoked during snapshot merge. We want to avoid
+    // constantly reading from disk. Hence, store this
+    // value in memory.
+    //
+    // Furthermore, this value in the disk is set
+    // only when OTA is applied and doesn't change
+    // during merge phase. Hence, once we know that
+    // the value is read from disk the very first time,
+    // it is safe to read successive checks from memory.
+    if (is_snapshot_userspace_.has_value()) {
+        return is_snapshot_userspace_.value();
+    }
+
+    auto lock = LockShared();
+    if (!lock) return false;
+
+    return UpdateUsesUserSnapshots(lock.get());
+}
+
+bool SnapshotManager::UpdateUsesUserSnapshots(LockedFile* lock) {
+    // See UpdateUsesUserSnapshots()
+    if (is_snapshot_userspace_.has_value()) {
+        return is_snapshot_userspace_.value();
+    }
+
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+    is_snapshot_userspace_ = update_status.userspace_snapshots();
+    return is_snapshot_userspace_.value();
+}
+
 bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
                                     const std::string& suffix) {
     CHECK(lock);
@@ -2040,6 +2295,16 @@
         paths->target_device = base_path;
     }
 
+    auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
+    if (remaining_time.count() < 0) {
+        return false;
+    }
+
+    // Wait for the base device to appear
+    if (!WaitForDevice(base_path, remaining_time)) {
+        return false;
+    }
+
     if (!live_snapshot_status.has_value()) {
         created_devices.Release();
         return true;
@@ -2053,7 +2318,7 @@
         return false;
     }
 
-    auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
+    remaining_time = GetRemainingTime(params.timeout_ms, begin);
     if (remaining_time.count() < 0) return false;
 
     std::string cow_name;
@@ -2109,10 +2374,10 @@
             return false;
         }
 
-        auto name = GetDmUserCowName(params.GetPartitionName());
+        auto name = GetDmUserCowName(params.GetPartitionName(), GetSnapshotDriver(lock));
 
         std::string new_cow_device;
-        if (!MapDmUserCow(lock, name, cow_path, source_device_path, remaining_time,
+        if (!MapDmUserCow(lock, name, cow_path, source_device_path, base_path, remaining_time,
                           &new_cow_device)) {
             LOG(ERROR) << "Could not map dm-user device for partition "
                        << params.GetPartitionName();
@@ -2126,21 +2391,37 @@
         cow_device = new_cow_device;
     }
 
-    std::string path;
-    if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
-                     &path)) {
-        LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName();
-        return false;
-    }
-    // No need to add params.GetPartitionName() to created_devices since it is immediately released.
+    // For userspace snapshots, dm-user block device itself will act as a
+    // snapshot device. There is one subtle difference - MapSnapshot will create
+    // either snapshot target or snapshot-merge target based on the underlying
+    // state of the snapshot device. If snapshot-merge target is created, merge
+    // will immediately start in the kernel.
+    //
+    // This is no longer true with respect to userspace snapshots. When dm-user
+    // block device is created, we just have the snapshots ready but daemon in
+    // the user-space will not start the merge. We have to explicitly inform the
+    // daemon to resume the merge. Check ProcessUpdateState() call stack.
+    if (!UpdateUsesUserSnapshots(lock)) {
+        std::string path;
+        if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
+                         &path)) {
+            LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName();
+            return false;
+        }
+        // No need to add params.GetPartitionName() to created_devices since it is immediately
+        // released.
 
-    if (paths) {
-        paths->snapshot_device = path;
+        if (paths) {
+            paths->snapshot_device = path;
+        }
+        LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path;
+    } else {
+        LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at "
+                  << cow_device;
     }
 
     created_devices.Release();
 
-    LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path;
     return true;
 }
 
@@ -2148,8 +2429,10 @@
                                                  const std::string& target_partition_name) {
     CHECK(lock);
 
-    if (!UnmapSnapshot(lock, target_partition_name)) {
-        return false;
+    if (!UpdateUsesUserSnapshots(lock)) {
+        if (!UnmapSnapshot(lock, target_partition_name)) {
+            return false;
+        }
     }
 
     if (!UnmapCowDevices(lock, target_partition_name)) {
@@ -2247,8 +2530,17 @@
     CHECK(lock);
     if (!EnsureImageManager()) return false;
 
-    if (UpdateUsesCompression(lock) && !UnmapDmUserDevice(name)) {
-        return false;
+    if (UpdateUsesCompression(lock)) {
+        if (UpdateUsesUserSnapshots(lock)) {
+            if (!UnmapUserspaceSnapshotDevice(lock, name)) {
+                return false;
+            }
+        } else {
+            auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
+            if (!UnmapDmUserDevice(dm_user_name)) {
+                return false;
+            }
+        }
     }
 
     if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) {
@@ -2264,8 +2556,7 @@
     return true;
 }
 
-bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) {
-    auto dm_user_name = GetDmUserCowName(snapshot_name);
+bool SnapshotManager::UnmapDmUserDevice(const std::string& dm_user_name) {
     if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
         return true;
     }
@@ -2291,6 +2582,46 @@
     return true;
 }
 
+bool SnapshotManager::UnmapUserspaceSnapshotDevice(LockedFile* lock,
+                                                   const std::string& snapshot_name) {
+    auto dm_user_name = GetDmUserCowName(snapshot_name, GetSnapshotDriver(lock));
+    if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
+        return true;
+    }
+
+    CHECK(lock);
+
+    SnapshotStatus snapshot_status;
+
+    if (!ReadSnapshotStatus(lock, snapshot_name, &snapshot_status)) {
+        return false;
+    }
+    // If the merge is complete, then we switch dm tables which is equivalent
+    // 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)) {
+            LOG(ERROR) << "Cannot unmap " << dm_user_name;
+            return false;
+        }
+    }
+
+    if (EnsureSnapuserdConnected()) {
+        if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) {
+            LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
+            return false;
+        }
+    }
+
+    // Ensure the control device is gone so we don't run into ABA problems.
+    auto control_device = "/dev/dm-user/" + dm_user_name;
+    if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) {
+        LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink";
+        return false;
+    }
+    return true;
+}
+
 bool SnapshotManager::MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) {
     auto lock = LockExclusive();
     if (!lock) return false;
@@ -2527,6 +2858,7 @@
         status.set_compression_enabled(old_status.compression_enabled());
         status.set_source_build_fingerprint(old_status.source_build_fingerprint());
         status.set_merge_phase(old_status.merge_phase());
+        status.set_userspace_snapshots(old_status.userspace_snapshots());
     }
     return WriteSnapshotUpdateStatus(lock, status);
 }
@@ -2844,6 +3176,43 @@
     SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
     status.set_state(update_state);
     status.set_compression_enabled(cow_creator.compression_enabled);
+    if (cow_creator.compression_enabled) {
+        if (!device()->IsTestDevice()) {
+            // Userspace snapshots is enabled only if compression is enabled
+            status.set_userspace_snapshots(IsUserspaceSnapshotsEnabled());
+            if (IsUserspaceSnapshotsEnabled()) {
+                is_snapshot_userspace_ = true;
+                LOG(INFO) << "User-space snapshots enabled";
+            } else {
+                is_snapshot_userspace_ = false;
+                LOG(INFO) << "User-space snapshots disabled";
+            }
+
+            // Terminate stale daemon if any
+            std::unique_ptr<SnapuserdClient> snapuserd_client =
+                    SnapuserdClient::Connect(kSnapuserdSocket, 10s);
+            if (snapuserd_client) {
+                snapuserd_client->DetachSnapuserd();
+                snapuserd_client->CloseConnection();
+                snapuserd_client = nullptr;
+            }
+
+            // Clear the cached client if any
+            if (snapuserd_client_) {
+                snapuserd_client_->CloseConnection();
+                snapuserd_client_ = nullptr;
+            }
+        } else {
+            status.set_userspace_snapshots(!IsDmSnapshotTestingEnabled());
+            if (IsDmSnapshotTestingEnabled()) {
+                is_snapshot_userspace_ = false;
+                LOG(INFO) << "User-space snapshots disabled for testing";
+            } else {
+                is_snapshot_userspace_ = true;
+                LOG(INFO) << "User-space snapshots enabled for testing";
+            }
+        }
+    }
     if (!WriteSnapshotUpdateStatus(lock.get(), status)) {
         LOG(ERROR) << "Unable to write new update state";
         return Return::Error();
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index a8d5b8a..4af5367 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -121,6 +121,11 @@
     return false;
 }
 
+bool SnapshotManagerStub::UpdateUsesUserSnapshots() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
 class SnapshotMergeStatsStub : public ISnapshotMergeStats {
     bool Start() override { return false; }
     void set_state(android::snapshot::UpdateState, bool) override {}
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index d78ba0a..f1d76e7 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -45,6 +45,8 @@
 #include "partition_cow_creator.h"
 #include "utility.h"
 
+#include <android-base/properties.h>
+
 // Mock classes are not used. Header included to ensure mocked class definition aligns with the
 // class itself.
 #include <libsnapshot/mock_device_info.h>
@@ -272,7 +274,7 @@
     AssertionResult DeleteSnapshotDevice(const std::string& snapshot) {
         AssertionResult res = AssertionSuccess();
         if (!(res = DeleteDevice(snapshot))) return res;
-        if (!sm->UnmapDmUserDevice(snapshot)) {
+        if (!sm->UnmapDmUserDevice(snapshot + "-user-cow")) {
             return AssertionFailure() << "Cannot delete dm-user device for " << snapshot;
         }
         if (!(res = DeleteDevice(snapshot + "-inner"))) return res;
@@ -2559,5 +2561,15 @@
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
     ::testing::AddGlobalTestEnvironment(new ::android::snapshot::SnapshotTestEnvironment());
-    return RUN_ALL_TESTS();
+
+    android::base::SetProperty("ctl.stop", "snapuserd");
+
+    if (!android::base::SetProperty("snapuserd.test.dm.snapshots", "1")) {
+        return testing::AssertionFailure()
+               << "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
+    }
+
+    int ret = RUN_ALL_TESTS();
+    android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
+    return ret;
 }
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
index 6ed55af..cebda1c 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_client.h
@@ -63,7 +63,8 @@
     // The misc_name must be the "misc_name" given to dm-user in step 2.
     //
     uint64_t InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
-                           const std::string& backing_device);
+                           const std::string& backing_device,
+                           const std::string& base_path_merge = "");
     bool AttachDmUser(const std::string& misc_name);
 
     // Wait for snapuserd to disassociate with a dm-user control device. This
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index e345269..7b1c7a3 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -195,8 +195,16 @@
 }
 
 uint64_t SnapuserdClient::InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
-                                        const std::string& backing_device) {
-    std::vector<std::string> parts = {"init", misc_name, cow_device, backing_device};
+                                        const std::string& backing_device,
+                                        const std::string& base_path_merge) {
+    std::vector<std::string> parts;
+
+    if (base_path_merge.empty()) {
+        parts = {"init", misc_name, cow_device, backing_device};
+    } else {
+        // For userspace snapshots
+        parts = {"init", misc_name, cow_device, backing_device, base_path_merge};
+    }
     std::string msg = android::base::Join(parts, ",");
     if (!Sendmsg(msg)) {
         LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon";
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index 912884f..ddb1f79 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -14,25 +14,95 @@
  * limitations under the License.
  */
 
-#include "snapuserd_daemon.h"
-
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <gflags/gflags.h>
 #include <snapuserd/snapuserd_client.h>
 
+#include "snapuserd_daemon.h"
+
 DEFINE_string(socket, android::snapshot::kSnapuserdSocket, "Named socket or socket path.");
 DEFINE_bool(no_socket, false,
             "If true, no socket is used. Each additional argument is an INIT message.");
 DEFINE_bool(socket_handoff, false,
             "If true, perform a socket hand-off with an existing snapuserd instance, then exit.");
+DEFINE_bool(user_snapshot, false, "If true, user-space snapshots are used");
 
 namespace android {
 namespace snapshot {
 
-bool Daemon::StartServer(int argc, char** argv) {
+bool Daemon::IsUserspaceSnapshotsEnabled() {
+    return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
+}
+
+bool Daemon::IsDmSnapshotTestingEnabled() {
+    return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
+}
+
+bool Daemon::StartDaemon(int argc, char** argv) {
     int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, true);
 
+    // Daemon launched from first stage init and during selinux transition
+    // will have the command line "-user_snapshot" flag set if the user-space
+    // snapshots are enabled.
+    //
+    // Daemon launched as a init service during "socket-handoff" and when OTA
+    // is applied will check for the property. This is ok as the system
+    // properties are valid at this point. We can't do this during first
+    // stage init and hence use the command line flags to get the information.
+    if (!IsDmSnapshotTestingEnabled() && (FLAGS_user_snapshot || IsUserspaceSnapshotsEnabled())) {
+        LOG(INFO) << "Starting daemon for user-space snapshots.....";
+        return StartServerForUserspaceSnapshots(arg_start, argc, argv);
+    } else {
+        LOG(INFO) << "Starting daemon for dm-snapshots.....";
+        return StartServerForDmSnapshot(arg_start, argc, argv);
+    }
+}
+
+bool Daemon::StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv) {
+    sigfillset(&signal_mask_);
+    sigdelset(&signal_mask_, SIGINT);
+    sigdelset(&signal_mask_, SIGTERM);
+    sigdelset(&signal_mask_, SIGUSR1);
+
+    // Masking signals here ensure that after this point, we won't handle INT/TERM
+    // until after we call into ppoll()
+    signal(SIGINT, Daemon::SignalHandler);
+    signal(SIGTERM, Daemon::SignalHandler);
+    signal(SIGPIPE, Daemon::SignalHandler);
+    signal(SIGUSR1, Daemon::SignalHandler);
+
+    MaskAllSignalsExceptIntAndTerm();
+
+    if (FLAGS_socket_handoff) {
+        return user_server_.RunForSocketHandoff();
+    }
+    if (!FLAGS_no_socket) {
+        if (!user_server_.Start(FLAGS_socket)) {
+            return false;
+        }
+        return user_server_.Run();
+    }
+
+    for (int i = arg_start; i < argc; i++) {
+        auto parts = android::base::Split(argv[i], ",");
+        if (parts.size() != 4) {
+            LOG(ERROR) << "Malformed message, expected three sub-arguments.";
+            return false;
+        }
+        auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3]);
+        if (!handler || !user_server_.StartHandler(handler)) {
+            return false;
+        }
+    }
+
+    // Skip the accept() call to avoid spurious log spam. The server will still
+    // run until all handlers have completed.
+    return user_server_.WaitForSocket();
+}
+
+bool Daemon::StartServerForDmSnapshot(int arg_start, int argc, char** argv) {
     sigfillset(&signal_mask_);
     sigdelset(&signal_mask_, SIGINT);
     sigdelset(&signal_mask_, SIGTERM);
@@ -95,11 +165,19 @@
 }
 
 void Daemon::Interrupt() {
-    server_.Interrupt();
+    if (IsUserspaceSnapshotsEnabled()) {
+        user_server_.Interrupt();
+    } else {
+        server_.Interrupt();
+    }
 }
 
 void Daemon::ReceivedSocketSignal() {
-    server_.ReceivedSocketSignal();
+    if (IsUserspaceSnapshotsEnabled()) {
+        user_server_.ReceivedSocketSignal();
+    } else {
+        server_.ReceivedSocketSignal();
+    }
 }
 
 void Daemon::SignalHandler(int signal) {
@@ -133,9 +211,10 @@
 
     android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance();
 
-    if (!daemon.StartServer(argc, argv)) {
-        LOG(ERROR) << "Snapuserd daemon failed to start.";
+    if (!daemon.StartDaemon(argc, argv)) {
+        LOG(ERROR) << "Snapuserd daemon failed to start";
         exit(EXIT_FAILURE);
     }
+
     return 0;
 }
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
index fbf57d9..cf3b917 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "dm-snapshot-merge/snapuserd_server.h"
+#include "user-space-merge/snapuserd_server.h"
 
 namespace android {
 namespace snapshot {
@@ -35,9 +36,13 @@
         return instance;
     }
 
-    bool StartServer(int argc, char** argv);
+    bool StartServerForDmSnapshot(int arg_start, int argc, char** argv);
+    bool StartServerForUserspaceSnapshots(int arg_start, int argc, char** argv);
     void Interrupt();
     void ReceivedSocketSignal();
+    bool IsUserspaceSnapshotsEnabled();
+    bool IsDmSnapshotTestingEnabled();
+    bool StartDaemon(int argc, char** argv);
 
   private:
     // Signal mask used with ppoll()
@@ -47,6 +52,7 @@
     void operator=(Daemon const&) = delete;
 
     SnapuserdServer server_;
+    UserSnapshotServer user_server_;
     void MaskAllSignalsExceptIntAndTerm();
     void MaskAllSignals();
     static void SignalHandler(int signal);
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 57e47e7..95d95cd 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -35,7 +35,7 @@
 }
 
 bool SnapshotHandler::InitializeWorkers() {
-    for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) {
+    for (int i = 0; i < kNumWorkerThreads; i++) {
         std::unique_ptr<Worker> wt =
                 std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
                                          misc_name_, base_path_merge_, GetSharedPtr());
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
index 13b56fa..1953316 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -48,10 +48,10 @@
 using android::base::unique_fd;
 using namespace std::chrono_literals;
 
-static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
-static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
+static constexpr size_t PAYLOAD_BUFFER_SZ = (1UL << 20);
+static_assert(PAYLOAD_BUFFER_SZ >= BLOCK_SZ);
 
-static constexpr int NUM_THREADS_PER_PARTITION = 1;
+static constexpr int kNumWorkerThreads = 4;
 
 #define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
 #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
index bfbacf9..1e300d2 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
@@ -231,8 +231,8 @@
     // Allocate the buffer which is used to communicate between
     // daemon and dm-user. The buffer comprises of header and a fixed payload.
     // If the dm-user requests a big IO, the IO will be broken into chunks
-    // of PAYLOAD_SIZE.
-    size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
+    // of PAYLOAD_BUFFER_SZ.
+    size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_BUFFER_SZ;
     bufsink_.Initialize(buf_size);
 }
 
@@ -326,7 +326,7 @@
 
     do {
         // Process 1MB payload at a time
-        size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+        size_t read_size = std::min(PAYLOAD_BUFFER_SZ, remaining_size);
 
         header->type = DM_USER_RESP_SUCCESS;
         size_t total_bytes_read = 0;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
index 47fc7db..fa055b7 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -81,11 +81,11 @@
     // Why 2048 ops ? We can probably increase this to bigger value but just
     // need to ensure that merge makes forward progress if there are
     // crashes repeatedly which is highly unlikely.
-    int total_ops_merged_per_commit = (PAYLOAD_SIZE / BLOCK_SZ) * 8;
+    int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 8;
     int num_ops_merged = 0;
 
     while (!cowop_iter->Done()) {
-        int num_ops = PAYLOAD_SIZE / BLOCK_SZ;
+        int num_ops = PAYLOAD_BUFFER_SZ / BLOCK_SZ;
         std::vector<const CowOperation*> replace_zero_vec;
         uint64_t source_offset;
 
@@ -292,6 +292,7 @@
 
     if (!Init()) {
         SNAP_LOG(ERROR) << "Merge thread initialization failed...";
+        snapuserd_->MergeFailed();
         return false;
     }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
index 0bcf26e..40e7242 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -429,7 +429,7 @@
             static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
     read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
     // For xor ops
-    bufsink_.Initialize(PAYLOAD_SIZE);
+    bufsink_.Initialize(PAYLOAD_BUFFER_SZ);
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index a4fd5a0..a79e3e1 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -44,7 +44,7 @@
 using android::base::borrowed_fd;
 using android::base::unique_fd;
 
-DaemonOps SnapuserServer::Resolveop(std::string& input) {
+DaemonOps UserSnapshotServer::Resolveop(std::string& input) {
     if (input == "init") return DaemonOps::INIT;
     if (input == "start") return DaemonOps::START;
     if (input == "stop") return DaemonOps::STOP;
@@ -59,14 +59,14 @@
     return DaemonOps::INVALID;
 }
 
-SnapuserServer::~SnapuserServer() {
+UserSnapshotServer::~UserSnapshotServer() {
     // Close any client sockets that were added via AcceptClient().
     for (size_t i = 1; i < watched_fds_.size(); i++) {
         close(watched_fds_[i].fd);
     }
 }
 
-std::string SnapuserServer::GetDaemonStatus() {
+std::string UserSnapshotServer::GetDaemonStatus() {
     std::string msg = "";
 
     if (IsTerminating())
@@ -77,8 +77,8 @@
     return msg;
 }
 
-void SnapuserServer::Parsemsg(std::string const& msg, const char delim,
-                              std::vector<std::string>& out) {
+void UserSnapshotServer::Parsemsg(std::string const& msg, const char delim,
+                                  std::vector<std::string>& out) {
     std::stringstream ss(msg);
     std::string s;
 
@@ -87,15 +87,15 @@
     }
 }
 
-void SnapuserServer::ShutdownThreads() {
+void UserSnapshotServer::ShutdownThreads() {
     terminating_ = true;
     JoinAllThreads();
 }
 
-DmUserHandler::DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
+UserSnapshotDmUserHandler::UserSnapshotDmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
     : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
 
-bool SnapuserServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
+bool UserSnapshotServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
     ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
     if (ret < 0) {
         PLOG(ERROR) << "Snapuserd:server: send() failed";
@@ -109,8 +109,8 @@
     return true;
 }
 
-bool SnapuserServer::Recv(android::base::borrowed_fd fd, std::string* data) {
-    char msg[MAX_PACKET_SIZE];
+bool UserSnapshotServer::Recv(android::base::borrowed_fd fd, std::string* data) {
+    char msg[kMaxPacketSize];
     ssize_t rv = TEMP_FAILURE_RETRY(recv(fd.get(), msg, sizeof(msg), 0));
     if (rv < 0) {
         PLOG(ERROR) << "recv failed";
@@ -120,7 +120,7 @@
     return true;
 }
 
-bool SnapuserServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
+bool UserSnapshotServer::Receivemsg(android::base::borrowed_fd fd, const std::string& str) {
     const char delim = ',';
 
     std::vector<std::string> out;
@@ -290,7 +290,7 @@
     }
 }
 
-void SnapuserServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
+void UserSnapshotServer::RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler) {
     LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
 
     handler->snapuserd()->SetSocketPresent(is_socket_present_);
@@ -337,7 +337,7 @@
     }
 }
 
-bool SnapuserServer::Start(const std::string& socketname) {
+bool UserSnapshotServer::Start(const std::string& socketname) {
     bool start_listening = true;
 
     sockfd_.reset(android_get_control_socket(socketname.c_str()));
@@ -353,7 +353,7 @@
     return StartWithSocket(start_listening);
 }
 
-bool SnapuserServer::StartWithSocket(bool start_listening) {
+bool UserSnapshotServer::StartWithSocket(bool start_listening) {
     if (start_listening && listen(sockfd_.get(), 4) < 0) {
         PLOG(ERROR) << "listen socket failed";
         return false;
@@ -374,7 +374,7 @@
     return true;
 }
 
-bool SnapuserServer::Run() {
+bool UserSnapshotServer::Run() {
     LOG(INFO) << "Now listening on snapuserd socket";
 
     while (!IsTerminating()) {
@@ -406,9 +406,9 @@
     return true;
 }
 
-void SnapuserServer::JoinAllThreads() {
+void UserSnapshotServer::JoinAllThreads() {
     // Acquire the thread list within the lock.
-    std::vector<std::shared_ptr<DmUserHandler>> dm_users;
+    std::vector<std::shared_ptr<UserSnapshotDmUserHandler>> dm_users;
     {
         std::lock_guard<std::mutex> guard(lock_);
         dm_users = std::move(dm_users_);
@@ -421,14 +421,14 @@
     }
 }
 
-void SnapuserServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
+void UserSnapshotServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
     struct pollfd p = {};
     p.fd = fd.get();
     p.events = events;
     watched_fds_.emplace_back(std::move(p));
 }
 
-void SnapuserServer::AcceptClient() {
+void UserSnapshotServer::AcceptClient() {
     int fd = TEMP_FAILURE_RETRY(accept4(sockfd_.get(), nullptr, nullptr, SOCK_CLOEXEC));
     if (fd < 0) {
         PLOG(ERROR) << "accept4 failed";
@@ -438,7 +438,7 @@
     AddWatchedFd(fd, POLLIN);
 }
 
-bool SnapuserServer::HandleClient(android::base::borrowed_fd fd, int revents) {
+bool UserSnapshotServer::HandleClient(android::base::borrowed_fd fd, int revents) {
     if (revents & POLLHUP) {
         LOG(DEBUG) << "Snapuserd client disconnected";
         return false;
@@ -455,16 +455,15 @@
     return true;
 }
 
-void SnapuserServer::Interrupt() {
+void UserSnapshotServer::Interrupt() {
     // Force close the socket so poll() fails.
     sockfd_ = {};
     SetTerminating();
 }
 
-std::shared_ptr<DmUserHandler> SnapuserServer::AddHandler(const std::string& misc_name,
-                                                          const std::string& cow_device_path,
-                                                          const std::string& backing_device,
-                                                          const std::string& base_path_merge) {
+std::shared_ptr<UserSnapshotDmUserHandler> UserSnapshotServer::AddHandler(
+        const std::string& misc_name, const std::string& cow_device_path,
+        const std::string& backing_device, const std::string& base_path_merge) {
     auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
                                                        base_path_merge);
     if (!snapuserd->InitCowDevice()) {
@@ -477,7 +476,7 @@
         return nullptr;
     }
 
-    auto handler = std::make_shared<DmUserHandler>(snapuserd);
+    auto handler = std::make_shared<UserSnapshotDmUserHandler>(snapuserd);
     {
         std::lock_guard<std::mutex> lock(lock_);
         if (FindHandler(&lock, misc_name) != dm_users_.end()) {
@@ -489,7 +488,7 @@
     return handler;
 }
 
-bool SnapuserServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
+bool UserSnapshotServer::StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
     if (handler->snapuserd()->IsAttached()) {
         LOG(ERROR) << "Handler already attached";
         return false;
@@ -497,11 +496,11 @@
 
     handler->snapuserd()->AttachControlDevice();
 
-    handler->thread() = std::thread(std::bind(&SnapuserServer::RunThread, this, handler));
+    handler->thread() = std::thread(std::bind(&UserSnapshotServer::RunThread, this, handler));
     return true;
 }
 
-bool SnapuserServer::StartMerge(const std::shared_ptr<DmUserHandler>& handler) {
+bool UserSnapshotServer::StartMerge(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
     if (!handler->snapuserd()->IsAttached()) {
         LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
         return false;
@@ -511,8 +510,8 @@
     return true;
 }
 
-auto SnapuserServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
-                                 const std::string& misc_name) -> HandlerList::iterator {
+auto UserSnapshotServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+                                     const std::string& misc_name) -> HandlerList::iterator {
     CHECK(proof_of_lock);
 
     for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
@@ -523,7 +522,7 @@
     return dm_users_.end();
 }
 
-void SnapuserServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
+void UserSnapshotServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
     CHECK(proof_of_lock);
 
     for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
@@ -533,11 +532,12 @@
     }
 }
 
-std::string SnapuserServer::GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler) {
+std::string UserSnapshotServer::GetMergeStatus(
+        const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
     return handler->snapuserd()->GetMergeStatus();
 }
 
-double SnapuserServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
+double UserSnapshotServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
     CHECK(proof_of_lock);
     double percentage = 0.0;
     int n = 0;
@@ -567,8 +567,8 @@
     return percentage;
 }
 
-bool SnapuserServer::RemoveAndJoinHandler(const std::string& misc_name) {
-    std::shared_ptr<DmUserHandler> handler;
+bool UserSnapshotServer::RemoveAndJoinHandler(const std::string& misc_name) {
+    std::shared_ptr<UserSnapshotDmUserHandler> handler;
     {
         std::lock_guard<std::mutex> lock(lock_);
 
@@ -588,7 +588,7 @@
     return true;
 }
 
-bool SnapuserServer::WaitForSocket() {
+bool UserSnapshotServer::WaitForSocket() {
     auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
 
     auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
@@ -642,7 +642,7 @@
     return Run();
 }
 
-bool SnapuserServer::RunForSocketHandoff() {
+bool UserSnapshotServer::RunForSocketHandoff() {
     unique_fd proxy_fd(android_get_control_socket(kSnapuserdSocketProxy));
     if (proxy_fd < 0) {
         PLOG(FATAL) << "Proxy could not get android control socket " << kSnapuserdSocketProxy;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
index e93621c..c645456 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -33,7 +33,7 @@
 namespace android {
 namespace snapshot {
 
-static constexpr uint32_t MAX_PACKET_SIZE = 512;
+static constexpr uint32_t kMaxPacketSize = 512;
 
 enum class DaemonOps {
     INIT,
@@ -49,9 +49,9 @@
     INVALID,
 };
 
-class DmUserHandler {
+class UserSnapshotDmUserHandler {
   public:
-    explicit DmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
+    explicit UserSnapshotDmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
 
     void FreeResources() {
         // Each worker thread holds a reference to snapuserd.
@@ -76,7 +76,7 @@
     bool thread_terminated_ = false;
 };
 
-class SnapuserServer {
+class UserSnapshotServer {
   private:
     android::base::unique_fd sockfd_;
     bool terminating_;
@@ -87,7 +87,7 @@
 
     std::mutex lock_;
 
-    using HandlerList = std::vector<std::shared_ptr<DmUserHandler>>;
+    using HandlerList = std::vector<std::shared_ptr<UserSnapshotDmUserHandler>>;
     HandlerList dm_users_;
 
     void AddWatchedFd(android::base::borrowed_fd fd, int events);
@@ -105,11 +105,11 @@
 
     bool IsTerminating() { return terminating_; }
 
-    void RunThread(std::shared_ptr<DmUserHandler> handler);
+    void RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler);
     void JoinAllThreads();
     bool StartWithSocket(bool start_listening);
 
-    // Find a DmUserHandler within a lock.
+    // Find a UserSnapshotDmUserHandler within a lock.
     HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
                                       const std::string& misc_name);
 
@@ -117,8 +117,8 @@
     void TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock);
 
   public:
-    SnapuserServer() { terminating_ = false; }
-    ~SnapuserServer();
+    UserSnapshotServer() { terminating_ = false; }
+    ~UserSnapshotServer();
 
     bool Start(const std::string& socketname);
     bool Run();
@@ -126,13 +126,13 @@
     bool RunForSocketHandoff();
     bool WaitForSocket();
 
-    std::shared_ptr<DmUserHandler> AddHandler(const std::string& misc_name,
-                                              const std::string& cow_device_path,
-                                              const std::string& backing_device,
-                                              const std::string& base_path_merge);
-    bool StartHandler(const std::shared_ptr<DmUserHandler>& handler);
-    bool StartMerge(const std::shared_ptr<DmUserHandler>& handler);
-    std::string GetMergeStatus(const std::shared_ptr<DmUserHandler>& handler);
+    std::shared_ptr<UserSnapshotDmUserHandler> AddHandler(const std::string& misc_name,
+                                                          const std::string& cow_device_path,
+                                                          const std::string& backing_device,
+                                                          const std::string& base_path_merge);
+    bool StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
+    bool StartMerge(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
+    std::string GetMergeStatus(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
 
     void SetTerminating() { terminating_ = true; }
     void ReceivedSocketSignal() { received_socket_signal_ = true; }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
index 6c91fde..6dec1e2 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
@@ -570,7 +570,6 @@
     {
         std::unique_lock<std::mutex> lock(blk_state->m_lock);
 
-        CHECK(blk_state->merge_state_ == MERGE_GROUP_STATE::GROUP_MERGE_PENDING);
         blk_state->num_ios_in_progress -= 1;
         if (blk_state->num_ios_in_progress == 0) {
             pending_ios = false;
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index 4a2af1c..89d6145 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -22,6 +22,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <fs_mgr/roots.h>
@@ -187,6 +188,10 @@
     return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false);
 }
 
+bool IsUserspaceSnapshotsEnabled() {
+    return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
+}
+
 std::string GetOtherPartitionName(const std::string& name) {
     auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name);
     CHECK(suffix == "_a" || suffix == "_b");
@@ -195,5 +200,9 @@
     return name.substr(0, name.size() - suffix.size()) + other_suffix;
 }
 
+bool IsDmSnapshotTestingEnabled() {
+    return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index e97afed..a032b68 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -131,8 +131,11 @@
 
 bool IsCompressionEnabled();
 
+bool IsUserspaceSnapshotsEnabled();
+
+bool IsDmSnapshotTestingEnabled();
+
 // Swap the suffix of a partition name.
 std::string GetOtherPartitionName(const std::string& name);
-
 }  // namespace snapshot
 }  // namespace android
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index f5c10bb..10a07a1 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -391,7 +391,11 @@
 
     use_snapuserd_ = sm->IsSnapuserdRequired();
     if (use_snapuserd_) {
-        LaunchFirstStageSnapuserd();
+        if (sm->UpdateUsesUserSnapshots()) {
+            LaunchFirstStageSnapuserd(SnapshotDriver::DM_USER);
+        } else {
+            LaunchFirstStageSnapuserd(SnapshotDriver::DM_SNAPSHOT);
+        }
     }
 
     sm->SetUeventRegenCallback([this](const std::string& device) -> bool {
diff --git a/init/snapuserd_transition.cpp b/init/snapuserd_transition.cpp
index b8c2fd2..e11510e 100644
--- a/init/snapuserd_transition.cpp
+++ b/init/snapuserd_transition.cpp
@@ -58,7 +58,7 @@
 static constexpr char kSnapuserdLabel[] = "u:object_r:snapuserd_exec:s0";
 static constexpr char kSnapuserdSocketLabel[] = "u:object_r:snapuserd_socket:s0";
 
-void LaunchFirstStageSnapuserd() {
+void LaunchFirstStageSnapuserd(SnapshotDriver driver) {
     SocketDescriptor socket_desc;
     socket_desc.name = android::snapshot::kSnapuserdSocket;
     socket_desc.type = SOCK_STREAM;
@@ -80,12 +80,23 @@
     }
     if (pid == 0) {
         socket->Publish();
-        char arg0[] = "/system/bin/snapuserd";
-        char* const argv[] = {arg0, nullptr};
-        if (execv(arg0, argv) < 0) {
-            PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
+
+        if (driver == SnapshotDriver::DM_USER) {
+            char arg0[] = "/system/bin/snapuserd";
+            char arg1[] = "-user_snapshot";
+            char* const argv[] = {arg0, arg1, nullptr};
+            if (execv(arg0, argv) < 0) {
+                PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
+            }
+            _exit(127);
+        } else {
+            char arg0[] = "/system/bin/snapuserd";
+            char* const argv[] = {arg0, nullptr};
+            if (execv(arg0, argv) < 0) {
+                PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
+            }
+            _exit(127);
         }
-        _exit(127);
     }
 
     auto client = SnapuserdClient::Connect(android::snapshot::kSnapuserdSocket, 10s);
diff --git a/init/snapuserd_transition.h b/init/snapuserd_transition.h
index 62aee83..be22afd 100644
--- a/init/snapuserd_transition.h
+++ b/init/snapuserd_transition.h
@@ -29,8 +29,13 @@
 namespace android {
 namespace init {
 
+enum class SnapshotDriver {
+    DM_SNAPSHOT,
+    DM_USER,
+};
+
 // Fork and exec a new copy of snapuserd.
-void LaunchFirstStageSnapuserd();
+void LaunchFirstStageSnapuserd(SnapshotDriver driver);
 
 class SnapuserdSelinuxHelper final {
     using SnapshotManager = android::snapshot::SnapshotManager;