Merge "decompressor maintenence" into main
diff --git a/debuggerd/crasher/arm/crashglue.S b/debuggerd/crasher/arm/crashglue.S
index e4adf40..0def8ae 100644
--- a/debuggerd/crasher/arm/crashglue.S
+++ b/debuggerd/crasher/arm/crashglue.S
@@ -23,10 +23,11 @@
 	ldr lr, [lr]
 	b .
 	.cfi_endproc
+	.size crash1, .-crash1
 
-.globl crashnostack
-.type crashnostack, %function
-crashnostack:
+.globl crash_no_stack
+.type crash_no_stack, %function
+crash_no_stack:
 	.cfi_startproc
 	mov r1, sp
 	.cfi_def_cfa_register r1
@@ -35,3 +36,4 @@
 	ldr r0, [r0]
 	b .
 	.cfi_endproc
+	.size crash_no_stack, .-crash_no_stack
diff --git a/debuggerd/crasher/arm64/crashglue.S b/debuggerd/crasher/arm64/crashglue.S
index 97c824e..c56e19a 100644
--- a/debuggerd/crasher/arm64/crashglue.S
+++ b/debuggerd/crasher/arm64/crashglue.S
@@ -41,11 +41,12 @@
 	ldr x30, [x30]
 	b .
 	.cfi_endproc
+	.size crash1, .-crash1
 
 
-.globl crashnostack
-.type crashnostack, %function
-crashnostack:
+.globl crash_no_stack
+.type crash_no_stack, %function
+crash_no_stack:
 	.cfi_startproc
 	mov x1, sp
 	.cfi_def_cfa_register x1
@@ -54,3 +55,41 @@
 	ldr x0, [x0]
 	b .
 	.cfi_endproc
+	.size crash_no_stack, .-crash_no_stack
+
+
+.globl crash_bti
+.type crash_bti, %function
+crash_bti:
+	.cfi_startproc
+	adr x16, 1f
+	br x16
+1:	// Deliberatly not a bti instruction so we crash here.
+	b .
+	.cfi_endproc
+	.size crash_bti, .-crash_bti
+
+
+.globl crash_pac
+.type crash_pac, %function
+crash_pac:
+	.cfi_startproc
+	paciasp
+	// Since sp is a pac input, this ensures a mismatch.
+	sub sp, sp, #16
+	autiasp
+	b .
+	.cfi_endproc
+	.size crash_pac, .-crash_pac
+
+// Set the PAC and BTI bits for this object file.
+.section .note.gnu.property, "a"
+.balign 8
+.long 4
+.long 0x10
+.long 0x5
+.asciz "GNU"
+.long 0xc0000000
+.long 4
+.long 0x3
+.long 0
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index 12ba502..3b52776 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -19,6 +19,7 @@
 #include <assert.h>
 #include <dirent.h>
 #include <errno.h>
+#include <error.h>
 #include <fcntl.h>
 #include <pthread.h>
 #include <signal.h>
@@ -29,6 +30,9 @@
 #include <sys/prctl.h>
 #include <unistd.h>
 
+#include <android-base/file.h>
+#include <android-base/strings.h>
+
 // We test both kinds of logging.
 #include <android-base/logging.h>
 #include <log/log.h>
@@ -59,8 +63,10 @@
 // Avoid name mangling so that stacks are more readable.
 extern "C" {
 
-void crash1(void);
-void crashnostack(void);
+void crash1();
+void crash_no_stack();
+void crash_bti();
+void crash_pac();
 
 int do_action(const char* arg);
 
@@ -196,13 +202,6 @@
     fprintf(stderr, "  fdsan_file            close a file descriptor that's owned by a FILE*\n");
     fprintf(stderr, "  fdsan_dir             close a file descriptor that's owned by a DIR*\n");
     fprintf(stderr, "  seccomp               fail a seccomp check\n");
-#if defined(__arm__)
-    fprintf(stderr, "  kuser_helper_version  call kuser_helper_version\n");
-    fprintf(stderr, "  kuser_get_tls         call kuser_get_tls\n");
-    fprintf(stderr, "  kuser_cmpxchg         call kuser_cmpxchg\n");
-    fprintf(stderr, "  kuser_memory_barrier  call kuser_memory_barrier\n");
-    fprintf(stderr, "  kuser_cmpxchg64       call kuser_cmpxchg64\n");
-#endif
     fprintf(stderr, "  xom                   read execute-only memory\n");
     fprintf(stderr, "\n");
     fprintf(stderr, "  LOG_ALWAYS_FATAL      call liblog LOG_ALWAYS_FATAL\n");
@@ -223,6 +222,20 @@
     fprintf(stderr, "\n");
     fprintf(stderr, "  no_new_privs          set PR_SET_NO_NEW_PRIVS and then abort\n");
     fprintf(stderr, "\n");
+#if defined(__arm__)
+    fprintf(stderr, "Also, since this is an arm32 binary:\n");
+    fprintf(stderr, "  kuser_helper_version  call kuser_helper_version\n");
+    fprintf(stderr, "  kuser_get_tls         call kuser_get_tls\n");
+    fprintf(stderr, "  kuser_cmpxchg         call kuser_cmpxchg\n");
+    fprintf(stderr, "  kuser_memory_barrier  call kuser_memory_barrier\n");
+    fprintf(stderr, "  kuser_cmpxchg64       call kuser_cmpxchg64\n");
+#endif
+#if defined(__aarch64__)
+    fprintf(stderr, "Also, since this is an arm64 binary:\n");
+    fprintf(stderr, "  bti                   fail a branch target identification (BTI) check\n");
+    fprintf(stderr, "  pac                   fail a pointer authentication (PAC) check\n");
+#endif
+    fprintf(stderr, "\n");
     fprintf(stderr, "prefix any of the above with 'thread-' to run on a new thread\n");
     fprintf(stderr, "prefix any of the above with 'exhaustfd-' to exhaust\n");
     fprintf(stderr, "all available file descriptors before crashing.\n");
@@ -231,6 +244,21 @@
     return EXIT_FAILURE;
 }
 
+[[maybe_unused]] static void CheckCpuFeature(const std::string& name) {
+    std::string cpuinfo;
+    if (!android::base::ReadFileToString("/proc/cpuinfo", &cpuinfo)) {
+        error(1, errno, "couldn't read /proc/cpuinfo");
+    }
+    std::vector<std::string> lines = android::base::Split(cpuinfo, "\n");
+    for (std::string_view line : lines) {
+        if (!android::base::ConsumePrefix(&line, "Features\t:")) continue;
+        std::vector<std::string> features = android::base::Split(std::string(line), " ");
+        if (std::find(features.begin(), features.end(), name) == features.end()) {
+          error(1, 0, "/proc/cpuinfo does not report feature '%s'", name.c_str());
+        }
+    }
+}
+
 noinline int do_action(const char* arg) {
     // Prefixes.
     if (!strncmp(arg, "wait-", strlen("wait-"))) {
@@ -256,7 +284,7 @@
     } else if (!strcasecmp(arg, "stack-overflow")) {
       overflow_stack(nullptr);
     } else if (!strcasecmp(arg, "nostack")) {
-      crashnostack();
+      crash_no_stack();
     } else if (!strcasecmp(arg, "exit")) {
       exit(1);
     } else if (!strcasecmp(arg, "call-null")) {
@@ -350,6 +378,14 @@
     } else if (!strcasecmp(arg, "kuser_cmpxchg64")) {
         return __kuser_cmpxchg64(0, 0, 0);
 #endif
+#if defined(__aarch64__)
+    } else if (!strcasecmp(arg, "bti")) {
+        CheckCpuFeature("bti");
+        crash_bti();
+    } else if (!strcasecmp(arg, "pac")) {
+        CheckCpuFeature("paca");
+        crash_pac();
+#endif
     } else if (!strcasecmp(arg, "no_new_privs")) {
         if (prctl(PR_SET_NO_NEW_PRIVS, 1) != 0) {
           fprintf(stderr, "prctl(PR_SET_NO_NEW_PRIVS, 1) failed: %s\n", strerror(errno));
diff --git a/debuggerd/crasher/riscv64/crashglue.S b/debuggerd/crasher/riscv64/crashglue.S
index 42f59b3..f179e33 100644
--- a/debuggerd/crasher/riscv64/crashglue.S
+++ b/debuggerd/crasher/riscv64/crashglue.S
@@ -43,10 +43,11 @@
 	ld t2, 0(zero)
 	j .
 	.cfi_endproc
+	.size crash1, .-crash1
 
 
-.globl crashnostack
-crashnostack:
+.globl crash_no_stack
+crash_no_stack:
 	.cfi_startproc
 	mv t1, sp
 	.cfi_def_cfa_register t1
@@ -54,3 +55,4 @@
 	ld t2, 0(zero)
 	j .
 	.cfi_endproc
+	.size crash_no_stack, .-crash_no_stack
diff --git a/debuggerd/crasher/x86/crashglue.S b/debuggerd/crasher/x86/crashglue.S
index e8eb3a7..453035b 100644
--- a/debuggerd/crasher/x86/crashglue.S
+++ b/debuggerd/crasher/x86/crashglue.S
@@ -6,13 +6,15 @@
 
 	movl $0, %edx
 	jmp *%edx
+	.size crash1, .-crash1
 
 
-.globl crashnostack
-crashnostack:
+.globl crash_no_stack
+crash_no_stack:
 	.cfi_startproc
 	movl %esp, %eax
 	.cfi_def_cfa_register %eax
 	movl $0, %esp
 	movl (%esp), %ebx
 	.cfi_endproc
+	.size crash_no_stack, .-crash_no_stack
diff --git a/debuggerd/crasher/x86_64/crashglue.S b/debuggerd/crasher/x86_64/crashglue.S
index 8f67214..c3d39c4 100644
--- a/debuggerd/crasher/x86_64/crashglue.S
+++ b/debuggerd/crasher/x86_64/crashglue.S
@@ -6,13 +6,15 @@
 
 	movl $0, %edx
 	jmp *%rdx
+	.size crash1, .-crash1
 
 
-.globl crashnostack
-crashnostack:
+.globl crash_no_stack
+crash_no_stack:
 	.cfi_startproc
 	movq %rsp, %rax
 	.cfi_def_cfa_register %rax
 	movq $0, %rsp
 	movq (%rsp), %rbx
 	.cfi_endproc
+	.size crash_no_stack, .-crash_no_stack
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 3644d95..71a228e 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -1801,6 +1801,7 @@
     CancelSnapshotIfNeeded();
 
     tasks_ = CollectTasks();
+
     for (auto& task : tasks_) {
         task->Run();
     }
@@ -1815,7 +1816,18 @@
     } else {
         tasks = CollectTasksFromImageList();
     }
-
+    if (fp_->exclude_dynamic_partitions) {
+        auto is_non_static_flash_task = [](const auto& task) -> bool {
+            if (auto flash_task = task->AsFlashTask()) {
+                if (!should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
+                    return false;
+                }
+            }
+            return true;
+        };
+        tasks.erase(std::remove_if(tasks.begin(), tasks.end(), is_non_static_flash_task),
+                    tasks.end());
+    }
     return tasks;
 }
 
@@ -2219,6 +2231,7 @@
                                       {"disable-verification", no_argument, 0, 0},
                                       {"disable-verity", no_argument, 0, 0},
                                       {"disable-super-optimization", no_argument, 0, 0},
+                                      {"exclude-dynamic-partitions", no_argument, 0, 0},
                                       {"disable-fastboot-info", no_argument, 0, 0},
                                       {"force", no_argument, 0, 0},
                                       {"fs-options", required_argument, 0, 0},
@@ -2260,6 +2273,9 @@
                 g_disable_verity = true;
             } else if (name == "disable-super-optimization") {
                 fp->should_optimize_flash_super = false;
+            } else if (name == "exclude-dynamic-partitions") {
+                fp->exclude_dynamic_partitions = true;
+                fp->should_optimize_flash_super = false;
             } else if (name == "disable-fastboot-info") {
                 fp->should_use_fastboot_info = false;
             } else if (name == "force") {
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index dc57149..75b8d29 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -99,6 +99,7 @@
     bool force_flash = false;
     bool should_optimize_flash_super = true;
     bool should_use_fastboot_info = true;
+    bool exclude_dynamic_partitions = false;
     uint64_t sparse_limit = 0;
 
     std::string slot_override;
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 6251e5a..6548cc8 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -90,6 +90,7 @@
     ramdisk_available: true,
     vendor_ramdisk_available: true,
     recovery_available: true,
+    host_supported: true,
 }
 
 cc_defaults {
@@ -221,6 +222,8 @@
     ],
     srcs: [
         "testing/dm_user_harness.cpp",
+        "testing/harness.cpp",
+        "testing/host_harness.cpp",
         "user-space-merge/snapuserd_test.cpp",
     ],
     shared_libs: [
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
index 0d83f47..7ab75dc 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
@@ -14,6 +14,8 @@
 
 #pragma once
 
+#include <linux/types.h>
+
 namespace android {
 namespace snapshot {
 
@@ -70,7 +72,7 @@
 
     /* In sectors */
     uint32_t chunk_size;
-} __packed;
+} __attribute__((packed));
 
 // A disk exception is a mapping of old_chunk to new_chunk
 // old_chunk is the chunk ID of a dm-snapshot device.
@@ -78,7 +80,7 @@
 struct disk_exception {
     uint64_t old_chunk;
     uint64_t new_chunk;
-} __packed;
+} __attribute__((packed));
 
 // Control structures to communicate with dm-user
 // It comprises of header and a payload
diff --git a/fs_mgr/libsnapshot/snapuserd/testing/harness.cpp b/fs_mgr/libsnapshot/snapuserd/testing/harness.cpp
new file mode 100644
index 0000000..02ae549
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/testing/harness.cpp
@@ -0,0 +1,116 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "harness.h"
+
+#ifdef __ANDROID__
+#include <linux/memfd.h>
+#endif
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <ext4_utils/ext4_utils.h>
+#include <libdm/loop_control.h>
+#include "snapuserd_logging.h"
+
+namespace android {
+namespace snapshot {
+
+using namespace std::chrono_literals;
+using android::base::unique_fd;
+using android::dm::LoopDevice;
+
+#ifdef __ANDROID__
+// Prefer this on device since it is a real block device, which is more similar
+// to how we use snapuserd.
+class MemoryBackedDevice final : public IBackingDevice {
+  public:
+    bool Init(uint64_t size) {
+        memfd_.reset(memfd_create("snapuserd_test", MFD_ALLOW_SEALING));
+        if (memfd_ < 0) {
+            PLOG(ERROR) << "memfd_create failed";
+            return false;
+        }
+        if (ftruncate(memfd_.get(), size) < 0) {
+            PLOG(ERROR) << "ftruncate failed";
+            return false;
+        }
+        if (fcntl(memfd_.get(), F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
+            PLOG(ERROR) << "fcntl seal failed";
+            return false;
+        }
+        dev_ = std::make_unique<LoopDevice>(memfd_, 10s);
+        return dev_->valid();
+    }
+    const std::string& GetPath() override { return dev_->device(); }
+    uint64_t GetSize() override {
+        unique_fd fd(open(GetPath().c_str(), O_RDONLY | O_CLOEXEC));
+        if (fd < 0) {
+            PLOG(ERROR) << "open failed: " << GetPath();
+            return 0;
+        }
+        return get_block_device_size(fd.get());
+    }
+
+  private:
+    unique_fd memfd_;
+    std::unique_ptr<LoopDevice> dev_;
+};
+#endif
+
+class FileBackedDevice final : public IBackingDevice {
+  public:
+    bool Init(uint64_t size) {
+        if (temp_.fd < 0) {
+            return false;
+        }
+        if (ftruncate(temp_.fd, size) < 0) {
+            PLOG(ERROR) << "ftruncate failed: " << temp_.path;
+            return false;
+        }
+        path_ = temp_.path;
+        return true;
+    }
+
+    const std::string& GetPath() override { return path_; }
+    uint64_t GetSize() override {
+        off_t off = lseek(temp_.fd, 0, SEEK_END);
+        if (off < 0) {
+            PLOG(ERROR) << "lseek failed: " << temp_.path;
+            return 0;
+        }
+        return off;
+    }
+
+  private:
+    TemporaryFile temp_;
+    std::string path_;
+};
+
+std::unique_ptr<IBackingDevice> ITestHarness::CreateBackingDevice(uint64_t size) {
+#ifdef __ANDROID__
+    auto dev = std::make_unique<MemoryBackedDevice>();
+#else
+    auto dev = std::make_unique<FileBackedDevice>();
+#endif
+    if (!dev->Init(size)) {
+        return nullptr;
+    }
+    return dev;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/testing/harness.h b/fs_mgr/libsnapshot/snapuserd/testing/harness.h
index c0d528f..ffe9f7b 100644
--- a/fs_mgr/libsnapshot/snapuserd/testing/harness.h
+++ b/fs_mgr/libsnapshot/snapuserd/testing/harness.h
@@ -33,6 +33,14 @@
     virtual bool Destroy() = 0;
 };
 
+// Interface for an fd/temp file that is a block device when possible.
+class IBackingDevice {
+  public:
+    virtual ~IBackingDevice() {}
+    virtual const std::string& GetPath() = 0;
+    virtual uint64_t GetSize() = 0;
+};
+
 class ITestHarness {
   public:
     virtual ~ITestHarness() {}
@@ -41,6 +49,7 @@
                                                           uint64_t num_sectors) = 0;
     virtual IBlockServerFactory* GetBlockServerFactory() = 0;
     virtual bool HasUserDevice() = 0;
+    virtual std::unique_ptr<IBackingDevice> CreateBackingDevice(uint64_t size);
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/testing/host_harness.cpp b/fs_mgr/libsnapshot/snapuserd/testing/host_harness.cpp
new file mode 100644
index 0000000..0d230ad
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/testing/host_harness.cpp
@@ -0,0 +1,112 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "host_harness.h"
+
+#include "snapuserd_logging.h"
+
+namespace android {
+namespace snapshot {
+
+void TestBlockServerQueue::WaitForShutdown() {
+    std::unique_lock lock(m_);
+    if (shutdown_) {
+        return;
+    }
+    cv_.wait(lock, [this]() -> bool { return shutdown_; });
+}
+
+void TestBlockServerQueue::Shutdown() {
+    std::unique_lock lock(m_);
+    shutdown_ = true;
+    cv_.notify_all();
+}
+
+TestBlockServer::TestBlockServer(std::shared_ptr<TestBlockServerQueue> queue,
+                                 const std::string& misc_name)
+    : queue_(queue), misc_name_(misc_name) {}
+
+bool TestBlockServer::ProcessRequests() {
+    queue_->WaitForShutdown();
+    return false;
+}
+
+void* TestBlockServer::GetResponseBuffer(size_t size, size_t to_write) {
+    std::string buffer(size, '\0');
+    buffered_.emplace_back(std::move(buffer), to_write);
+    return buffered_.back().first.data();
+}
+
+bool TestBlockServer::SendBufferedIo() {
+    for (const auto& [data, to_write] : buffered_) {
+        sent_io_ += data.substr(0, to_write);
+    }
+    buffered_.clear();
+    return true;
+}
+
+TestBlockServerOpener::TestBlockServerOpener(std::shared_ptr<TestBlockServerQueue> queue,
+                                             const std::string& misc_name)
+    : queue_(queue), misc_name_(misc_name) {}
+
+std::unique_ptr<IBlockServer> TestBlockServerOpener::Open(IBlockServer::Delegate*, size_t) {
+    return std::make_unique<TestBlockServer>(queue_, misc_name_);
+}
+
+std::shared_ptr<TestBlockServerOpener> TestBlockServerFactory::CreateTestOpener(
+        const std::string& misc_name) {
+    if (queues_.count(misc_name)) {
+        LOG(ERROR) << "Cannot create opener for " << misc_name << ", already exists";
+        return nullptr;
+    }
+    auto queue = std::make_shared<TestBlockServerQueue>();
+    queues_.emplace(misc_name, queue);
+    return std::make_shared<TestBlockServerOpener>(queue, misc_name);
+}
+
+std::shared_ptr<IBlockServerOpener> TestBlockServerFactory::CreateOpener(
+        const std::string& misc_name) {
+    return CreateTestOpener(misc_name);
+}
+
+bool TestBlockServerFactory::DeleteQueue(const std::string& misc_name) {
+    auto iter = queues_.find(misc_name);
+    if (iter == queues_.end()) {
+        LOG(ERROR) << "Cannot delete queue " << misc_name << ", not found";
+        return false;
+    }
+    iter->second->Shutdown();
+    queues_.erase(iter);
+    return true;
+}
+
+HostUserDevice::HostUserDevice(TestBlockServerFactory* factory, const std::string& misc_name)
+    : factory_(factory), misc_name_(misc_name) {}
+
+bool HostUserDevice::Destroy() {
+    return factory_->DeleteQueue(misc_name_);
+}
+
+std::unique_ptr<IUserDevice> HostTestHarness::CreateUserDevice(const std::string&,
+                                                               const std::string& misc_name,
+                                                               uint64_t) {
+    return std::make_unique<HostUserDevice>(&factory_, misc_name);
+}
+
+IBlockServerFactory* HostTestHarness::GetBlockServerFactory() {
+    return &factory_;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/testing/host_harness.h b/fs_mgr/libsnapshot/snapuserd/testing/host_harness.h
new file mode 100644
index 0000000..ec0bd29
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/testing/host_harness.h
@@ -0,0 +1,105 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <condition_variable>
+#include <mutex>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "harness.h"
+
+namespace android {
+namespace snapshot {
+
+class TestBlockServerQueue final {
+  public:
+    void WaitForShutdown();
+    void Shutdown();
+
+  private:
+    std::mutex m_;
+    std::condition_variable cv_;
+    bool shutdown_ = false;
+};
+
+class TestBlockServer final : public IBlockServer {
+  public:
+    TestBlockServer(std::shared_ptr<TestBlockServerQueue> queue, const std::string& misc_name);
+    bool ProcessRequests() override;
+    void* GetResponseBuffer(size_t size, size_t to_write) override;
+    bool SendBufferedIo() override;
+
+    std::string&& sent_io() { return std::move(sent_io_); }
+
+  private:
+    std::shared_ptr<TestBlockServerQueue> queue_;
+    std::string misc_name_;
+    std::string sent_io_;
+    std::vector<std::pair<std::string, size_t>> buffered_;
+};
+
+class TestBlockServerOpener final : public IBlockServerOpener {
+  public:
+    TestBlockServerOpener(std::shared_ptr<TestBlockServerQueue> queue,
+                          const std::string& misc_name);
+    std::unique_ptr<IBlockServer> Open(IBlockServer::Delegate* delegate,
+                                       size_t buffer_size) override;
+
+  private:
+    std::shared_ptr<TestBlockServerQueue> queue_;
+    std::string misc_name_;
+};
+
+class TestBlockServerFactory final : public IBlockServerFactory {
+  public:
+    std::shared_ptr<IBlockServerOpener> CreateOpener(const std::string& misc_name) override;
+    std::shared_ptr<TestBlockServerOpener> CreateTestOpener(const std::string& misc_name);
+    bool DeleteQueue(const std::string& misc_name);
+
+  private:
+    std::unordered_map<std::string, std::shared_ptr<TestBlockServerQueue>> queues_;
+};
+
+class TestBlockServerFactory;
+
+class HostUserDevice final : public IUserDevice {
+  public:
+    HostUserDevice(TestBlockServerFactory* factory, const std::string& misc_name);
+    const std::string& GetPath() override { return empty_path_; }
+    bool Destroy();
+
+  private:
+    TestBlockServerFactory* factory_;
+    std::string misc_name_;
+    std::string empty_path_;
+};
+
+class HostTestHarness final : public ITestHarness {
+  public:
+    std::unique_ptr<IUserDevice> CreateUserDevice(const std::string& dev_name,
+                                                  const std::string& misc_name,
+                                                  uint64_t num_sectors) override;
+    IBlockServerFactory* GetBlockServerFactory() override;
+    bool HasUserDevice() override { return false; }
+
+  private:
+    TestBlockServerFactory factory_;
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
index fd41cd4..d979e20 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
@@ -14,6 +14,7 @@
 
 #include "handler_manager.h"
 
+#include <pthread.h>
 #include <sys/eventfd.h>
 
 #include <android-base/logging.h>
@@ -132,6 +133,8 @@
 void SnapshotHandlerManager::RunThread(std::shared_ptr<HandlerThread> handler) {
     LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
 
+    pthread_setname_np(pthread_self(), "Handler");
+
     if (!handler->snapuserd()->Start()) {
         LOG(ERROR) << " Failed to launch all worker threads";
     }
@@ -201,9 +204,8 @@
 
     handler->snapuserd()->MonitorMerge();
 
-    if (!is_merge_monitor_started_) {
-        std::thread(&SnapshotHandlerManager::MonitorMerge, this).detach();
-        is_merge_monitor_started_ = true;
+    if (!merge_monitor_.joinable()) {
+        merge_monitor_ = std::thread(&SnapshotHandlerManager::MonitorMerge, this);
     }
 
     merge_handlers_.push(handler);
@@ -220,6 +222,7 @@
 }
 
 void SnapshotHandlerManager::MonitorMerge() {
+    pthread_setname_np(pthread_self(), "Merge Monitor");
     while (!stop_monitor_merge_thread_) {
         uint64_t testVal;
         ssize_t ret =
@@ -357,8 +360,12 @@
         if (th.joinable()) th.join();
     }
 
-    stop_monitor_merge_thread_ = true;
-    WakeupMonitorMergeThread();
+    if (merge_monitor_.joinable()) {
+        stop_monitor_merge_thread_ = true;
+        WakeupMonitorMergeThread();
+
+        merge_monitor_.join();
+    }
 }
 
 auto SnapshotHandlerManager::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
index 2a6da96..b1605f0 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
@@ -122,9 +122,9 @@
     std::mutex lock_;
     HandlerList dm_users_;
 
-    bool is_merge_monitor_started_ = false;
     bool stop_monitor_merge_thread_ = false;
     int active_merge_threads_ = 0;
+    std::thread merge_monitor_;
     int num_partitions_merge_complete_ = 0;
     std::queue<std::shared_ptr<HandlerThread>> merge_handlers_;
     android::base::unique_fd monitor_merge_event_fd_;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
index 2d69721..11b8d7c 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
@@ -15,6 +15,8 @@
  */
 #include "merge_worker.h"
 
+#include <pthread.h>
+
 #include "snapuserd_core.h"
 #include "utility.h"
 
@@ -198,6 +200,7 @@
         // Wait for RA thread to notify that the merge window
         // is ready for merging.
         if (!snapuserd_->WaitForMergeBegin()) {
+            SNAP_LOG(ERROR) << "Failed waiting for merge to begin";
             return false;
         }
 
@@ -303,7 +306,7 @@
                     // will fallback to synchronous I/O.
                     ret = io_uring_wait_cqe(ring_.get(), &cqe);
                     if (ret) {
-                        SNAP_LOG(ERROR) << "Merge: io_uring_wait_cqe failed: " << ret;
+                        SNAP_LOG(ERROR) << "Merge: io_uring_wait_cqe failed: " << strerror(-ret);
                         status = false;
                         break;
                     }
@@ -546,6 +549,9 @@
 
 bool MergeWorker::Run() {
     SNAP_LOG(DEBUG) << "Waiting for merge begin...";
+
+    pthread_setname_np(pthread_self(), "MergeWorker");
+
     if (!snapuserd_->WaitForMergeBegin()) {
         SNAP_LOG(ERROR) << "Merge terminated early...";
         return true;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
index e2c292b..b9ecfa5 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
@@ -16,6 +16,8 @@
 
 #include "read_worker.h"
 
+#include <pthread.h>
+
 #include "snapuserd_core.h"
 #include "utility.h"
 
@@ -209,6 +211,8 @@
 bool ReadWorker::Run() {
     SNAP_LOG(INFO) << "Processing snapshot I/O requests....";
 
+    pthread_setname_np(pthread_self(), "ReadWorker");
+
     if (!SetThreadPriority(kNiceValueForMergeThreads)) {
         SNAP_PLOG(ERROR) << "Failed to set thread priority";
     }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
index a6a3eb8..6dbae81 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.h
@@ -33,9 +33,11 @@
     bool Run();
     bool Init() override;
     void CloseFds() override;
+    bool RequestSectors(uint64_t sector, uint64_t size) override;
+
+    IBlockServer* block_server() const { return block_server_.get(); }
 
   private:
-    bool RequestSectors(uint64_t sector, uint64_t size) override;
     bool SendBufferedIo();
 
     bool ProcessCowOp(const CowOperation* cow_op, void* buffer);
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 5a82ca1..c295851 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -295,8 +295,6 @@
     if (ra_thread_) {
         ra_thread_status =
                 std::async(std::launch::async, &ReadAhead::RunThread, read_ahead_thread_.get());
-
-        SNAP_LOG(INFO) << "Read-ahead thread started";
     }
 
     // Launch worker threads
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 a45e0bc..622fc50 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -28,6 +28,7 @@
 #include <iostream>
 #include <limits>
 #include <mutex>
+#include <ostream>
 #include <string>
 #include <thread>
 #include <unordered_map>
@@ -68,12 +69,13 @@
 #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
 
 enum class MERGE_IO_TRANSITION {
+    INVALID,
     MERGE_READY,
     MERGE_BEGIN,
     MERGE_FAILED,
     MERGE_COMPLETE,
     IO_TERMINATED,
-    READ_AHEAD_FAILURE,
+    READ_AHEAD_FAILURE
 };
 
 class MergeWorker;
@@ -220,7 +222,7 @@
     bool populate_data_from_cow_ = false;
     bool ra_thread_ = false;
     int total_ra_blocks_merged_ = 0;
-    MERGE_IO_TRANSITION io_state_;
+    MERGE_IO_TRANSITION io_state_ = MERGE_IO_TRANSITION::INVALID;
     std::unique_ptr<ReadAhead> read_ahead_thread_;
     std::unordered_map<uint64_t, void*> read_ahead_buffer_map_;
 
@@ -246,5 +248,7 @@
     std::shared_ptr<IBlockServerOpener> block_server_opener_;
 };
 
+std::ostream& operator<<(std::ostream& os, MERGE_IO_TRANSITION value);
+
 }  // namespace snapshot
 }  // namespace android
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 8b799ea..d2128c5 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -16,6 +16,8 @@
 
 #include "snapuserd_readahead.h"
 
+#include <pthread.h>
+
 #include "snapuserd_core.h"
 #include "utility.h"
 
@@ -428,7 +430,7 @@
         // will fallback to synchronous I/O.
         int ret = io_uring_wait_cqe(ring_.get(), &cqe);
         if (ret) {
-            SNAP_LOG(ERROR) << "Read-ahead - io_uring_wait_cqe failed: " << ret;
+            SNAP_LOG(ERROR) << "Read-ahead - io_uring_wait_cqe failed: " << strerror(-ret);
             status = false;
             break;
         }
@@ -691,6 +693,7 @@
     // window. If there is a crash during this time frame, merge should resume
     // based on the contents of the scratch space.
     if (!snapuserd_->WaitForMergeReady()) {
+        SNAP_LOG(ERROR) << "ReadAhead failed to wait for merge ready";
         return false;
     }
 
@@ -752,6 +755,10 @@
 }
 
 bool ReadAhead::RunThread() {
+    SNAP_LOG(INFO) << "ReadAhead thread started.";
+
+    pthread_setname_np(pthread_self(), "ReadAhead");
+
     if (!InitializeFds()) {
         return false;
     }
@@ -770,6 +777,7 @@
         SNAP_PLOG(ERROR) << "Failed to set thread priority";
     }
 
+    SNAP_LOG(INFO) << "ReadAhead processing.";
     while (!RAIterDone()) {
         if (!ReadAheadIOStart()) {
             break;
@@ -780,7 +788,7 @@
     CloseFds();
     reader_->CloseCowFd();
 
-    SNAP_LOG(INFO) << " ReadAhead thread terminating....";
+    SNAP_LOG(INFO) << " ReadAhead thread terminating.";
     return true;
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index c407834..01fe06f 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -17,7 +17,6 @@
 
 #include <fcntl.h>
 #include <linux/fs.h>
-#include <linux/memfd.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/syscall.h>
@@ -40,8 +39,11 @@
 #include <snapuserd/dm_user_block_server.h>
 #include <storage_literals/storage_literals.h>
 #include "handler_manager.h"
+#include "merge_worker.h"
+#include "read_worker.h"
 #include "snapuserd_core.h"
 #include "testing/dm_user_harness.h"
+#include "testing/host_harness.h"
 #include "testing/temp_device.h"
 #include "utility.h"
 
@@ -56,147 +58,54 @@
 using namespace std::chrono_literals;
 using namespace android::dm;
 using namespace std;
+using testing::AssertionFailure;
+using testing::AssertionResult;
+using testing::AssertionSuccess;
 
-class SnapuserdTest : public ::testing::Test {
-  public:
-    bool SetupDefault();
-    bool SetupOrderedOps();
-    bool SetupOrderedOpsInverted();
-    bool SetupCopyOverlap_1();
-    bool SetupCopyOverlap_2();
-    bool Merge();
-    void ValidateMerge();
-    void ReadSnapshotDeviceAndValidate();
-    void Shutdown();
-    void MergeInterrupt();
-    void MergeInterruptFixed(int duration);
-    void MergeInterruptRandomly(int max_duration);
-    void StartMerge();
-    void CheckMergeCompletion();
-
-    static const uint64_t kSectorSize = 512;
-
+class SnapuserdTestBase : public ::testing::Test {
   protected:
     void SetUp() override;
-    void TearDown() override { Shutdown(); }
-
-    void SetupImpl();
-
-    void SimulateDaemonRestart();
-
-    std::unique_ptr<ICowWriter> CreateCowDeviceInternal();
-    void CreateCowDevice();
-    void CreateCowDeviceOrderedOps();
-    void CreateCowDeviceOrderedOpsInverted();
-    void CreateCowDeviceWithCopyOverlap_1();
-    void CreateCowDeviceWithCopyOverlap_2();
-    bool SetupDaemon();
+    void TearDown() override;
     void CreateBaseDevice();
-    void InitCowDevice();
+    void CreateCowDevice();
     void SetDeviceControlName();
-    void InitDaemon();
-    void CreateUserDevice();
+    std::unique_ptr<ICowWriter> CreateCowDeviceInternal();
 
-    unique_ptr<LoopDevice> base_loop_;
-    unique_ptr<IUserDevice> dmuser_dev_;
-
+    std::unique_ptr<ITestHarness> harness_;
+    size_t size_ = 10_MiB;
+    int total_base_size_ = 0;
     std::string system_device_ctrl_name_;
     std::string system_device_name_;
 
+    unique_ptr<IBackingDevice> base_dev_;
     unique_fd base_fd_;
+
     std::unique_ptr<TemporaryFile> cow_system_;
+
     std::unique_ptr<uint8_t[]> orig_buffer_;
-    std::unique_ptr<uint8_t[]> merged_buffer_;
-    SnapshotHandlerManager handlers_;
-    bool setup_ok_ = false;
-    bool merge_ok_ = false;
-    size_t size_ = 100_MiB;
-    int cow_num_sectors_;
-    int total_base_size_;
-    std::unique_ptr<ITestHarness> harness_;
 };
 
-static unique_fd CreateTempFile(const std::string& name, size_t size) {
-    unique_fd fd(syscall(__NR_memfd_create, name.c_str(), MFD_ALLOW_SEALING));
-    if (fd < 0) {
-        return {};
-    }
-    if (size) {
-        if (ftruncate(fd, size) < 0) {
-            perror("ftruncate");
-            return {};
-        }
-        if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
-            perror("fcntl");
-            return {};
-        }
-    }
-    return fd;
-}
-
-void SnapuserdTest::SetUp() {
+void SnapuserdTestBase::SetUp() {
+#if __ANDROID__
     harness_ = std::make_unique<DmUserTestHarness>();
+#else
+    harness_ = std::make_unique<HostTestHarness>();
+#endif
 }
 
-void SnapuserdTest::Shutdown() {
-    ASSERT_TRUE(dmuser_dev_->Destroy());
+void SnapuserdTestBase::TearDown() {}
 
-    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
-    ASSERT_TRUE(handlers_.DeleteHandler(system_device_ctrl_name_));
-    ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s));
-    handlers_.TerminateMergeThreads();
-}
-
-bool SnapuserdTest::SetupDefault() {
-    SetupImpl();
-    return setup_ok_;
-}
-
-bool SnapuserdTest::SetupOrderedOps() {
-    CreateBaseDevice();
-    CreateCowDeviceOrderedOps();
-    return SetupDaemon();
-}
-
-bool SnapuserdTest::SetupOrderedOpsInverted() {
-    CreateBaseDevice();
-    CreateCowDeviceOrderedOpsInverted();
-    return SetupDaemon();
-}
-
-bool SnapuserdTest::SetupCopyOverlap_1() {
-    CreateBaseDevice();
-    CreateCowDeviceWithCopyOverlap_1();
-    return SetupDaemon();
-}
-
-bool SnapuserdTest::SetupCopyOverlap_2() {
-    CreateBaseDevice();
-    CreateCowDeviceWithCopyOverlap_2();
-    return SetupDaemon();
-}
-
-bool SnapuserdTest::SetupDaemon() {
-    SetDeviceControlName();
-
-    CreateUserDevice();
-    InitCowDevice();
-    InitDaemon();
-
-    setup_ok_ = true;
-
-    return setup_ok_;
-}
-
-void SnapuserdTest::CreateBaseDevice() {
-    unique_fd rnd_fd;
-
+void SnapuserdTestBase::CreateBaseDevice() {
     total_base_size_ = (size_ * 5);
-    base_fd_ = CreateTempFile("base_device", total_base_size_);
+
+    base_dev_ = harness_->CreateBackingDevice(total_base_size_);
+    ASSERT_NE(base_dev_, nullptr);
+
+    base_fd_.reset(open(base_dev_->GetPath().c_str(), O_RDWR | O_CLOEXEC));
     ASSERT_GE(base_fd_, 0);
 
-    rnd_fd.reset(open("/dev/random", O_RDONLY));
-    ASSERT_TRUE(rnd_fd > 0);
+    unique_fd rnd_fd(open("/dev/random", O_RDONLY));
+    ASSERT_GE(rnd_fd, 0);
 
     std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
 
@@ -206,9 +115,216 @@
     }
 
     ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
+}
 
-    base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
-    ASSERT_TRUE(base_loop_->valid());
+std::unique_ptr<ICowWriter> SnapuserdTestBase::CreateCowDeviceInternal() {
+    cow_system_ = std::make_unique<TemporaryFile>();
+
+    CowOptions options;
+    options.compression = "gz";
+
+    unique_fd fd(cow_system_->fd);
+    cow_system_->fd = -1;
+
+    return CreateCowWriter(kDefaultCowVersion, options, std::move(fd));
+}
+
+void SnapuserdTestBase::CreateCowDevice() {
+    unique_fd rnd_fd;
+    loff_t offset = 0;
+
+    auto writer = CreateCowDeviceInternal();
+    ASSERT_NE(writer, nullptr);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
+
+    // Fill random data
+    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
+                  true);
+
+        offset += 1_MiB;
+    }
+
+    size_t num_blocks = size_ / writer->GetBlockSize();
+    size_t blk_end_copy = num_blocks * 2;
+    size_t source_blk = num_blocks - 1;
+    size_t blk_src_copy = blk_end_copy - 1;
+
+    uint32_t sequence[num_blocks * 2];
+    // Sequence for Copy ops
+    for (int i = 0; i < num_blocks; i++) {
+        sequence[i] = num_blocks - 1 - i;
+    }
+    // Sequence for Xor ops
+    for (int i = 0; i < num_blocks; i++) {
+        sequence[num_blocks + i] = 5 * num_blocks - 1 - i;
+    }
+    ASSERT_TRUE(writer->AddSequenceData(2 * num_blocks, sequence));
+
+    size_t x = num_blocks;
+    while (1) {
+        ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy));
+        x -= 1;
+        if (x == 0) {
+            break;
+        }
+        source_blk -= 1;
+        blk_src_copy -= 1;
+    }
+
+    source_blk = num_blocks;
+    blk_src_copy = blk_end_copy;
+
+    ASSERT_TRUE(writer->AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
+
+    size_t blk_zero_copy_start = source_blk + num_blocks;
+    size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks;
+
+    ASSERT_TRUE(writer->AddZeroBlocks(blk_zero_copy_start, num_blocks));
+
+    size_t blk_random2_replace_start = blk_zero_copy_end;
+
+    ASSERT_TRUE(writer->AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
+
+    size_t blk_xor_start = blk_random2_replace_start + num_blocks;
+    size_t xor_offset = BLOCK_SZ / 2;
+    ASSERT_TRUE(writer->AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks,
+                                     xor_offset));
+
+    // Flush operations
+    ASSERT_TRUE(writer->Finalize());
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    std::string zero_buffer(size_, 0);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), size_, size_), true);
+    memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_);
+    memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_);
+    memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_1_.get(), size_);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, &orig_buffer_.get()[size_ * 4], size_,
+                                               size_ + xor_offset),
+              true);
+    for (int i = 0; i < size_; i++) {
+        orig_buffer_.get()[(size_ * 4) + i] =
+                (uint8_t)(orig_buffer_.get()[(size_ * 4) + i] ^ random_buffer_1_.get()[i]);
+    }
+}
+
+void SnapuserdTestBase::SetDeviceControlName() {
+    system_device_name_.clear();
+    system_device_ctrl_name_.clear();
+
+    std::string str(cow_system_->path);
+    std::size_t found = str.find_last_of("/\\");
+    ASSERT_NE(found, std::string::npos);
+    system_device_name_ = str.substr(found + 1);
+
+    system_device_ctrl_name_ = system_device_name_ + "-ctrl";
+}
+
+class SnapuserdTest : public SnapuserdTestBase {
+  public:
+    void SetupDefault();
+    void SetupOrderedOps();
+    void SetupOrderedOpsInverted();
+    void SetupCopyOverlap_1();
+    void SetupCopyOverlap_2();
+    bool Merge();
+    void ValidateMerge();
+    void ReadSnapshotDeviceAndValidate();
+    void Shutdown();
+    void MergeInterrupt();
+    void MergeInterruptFixed(int duration);
+    void MergeInterruptRandomly(int max_duration);
+    bool StartMerge();
+    void CheckMergeCompletion();
+
+    static const uint64_t kSectorSize = 512;
+
+  protected:
+    void SetUp() override;
+    void TearDown() override;
+
+    void SetupImpl();
+
+    void SimulateDaemonRestart();
+
+    void CreateCowDeviceOrderedOps();
+    void CreateCowDeviceOrderedOpsInverted();
+    void CreateCowDeviceWithCopyOverlap_1();
+    void CreateCowDeviceWithCopyOverlap_2();
+    void SetupDaemon();
+    void InitCowDevice();
+    void InitDaemon();
+    void CreateUserDevice();
+
+    unique_ptr<IUserDevice> dmuser_dev_;
+
+    std::unique_ptr<uint8_t[]> merged_buffer_;
+    std::unique_ptr<SnapshotHandlerManager> handlers_;
+    int cow_num_sectors_;
+};
+
+void SnapuserdTest::SetUp() {
+    ASSERT_NO_FATAL_FAILURE(SnapuserdTestBase::SetUp());
+    handlers_ = std::make_unique<SnapshotHandlerManager>();
+}
+
+void SnapuserdTest::TearDown() {
+    SnapuserdTestBase::TearDown();
+    Shutdown();
+}
+
+void SnapuserdTest::Shutdown() {
+    if (dmuser_dev_) {
+        ASSERT_TRUE(dmuser_dev_->Destroy());
+    }
+
+    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
+    ASSERT_TRUE(handlers_->DeleteHandler(system_device_ctrl_name_));
+    ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s));
+    handlers_->TerminateMergeThreads();
+    handlers_->JoinAllThreads();
+    handlers_ = std::make_unique<SnapshotHandlerManager>();
+}
+
+void SnapuserdTest::SetupDefault() {
+    ASSERT_NO_FATAL_FAILURE(SetupImpl());
+}
+
+void SnapuserdTest::SetupOrderedOps() {
+    ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
+    ASSERT_NO_FATAL_FAILURE(CreateCowDeviceOrderedOps());
+    ASSERT_NO_FATAL_FAILURE(SetupDaemon());
+}
+
+void SnapuserdTest::SetupOrderedOpsInverted() {
+    ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
+    ASSERT_NO_FATAL_FAILURE(CreateCowDeviceOrderedOpsInverted());
+    ASSERT_NO_FATAL_FAILURE(SetupDaemon());
+}
+
+void SnapuserdTest::SetupCopyOverlap_1() {
+    ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
+    ASSERT_NO_FATAL_FAILURE(CreateCowDeviceWithCopyOverlap_1());
+    ASSERT_NO_FATAL_FAILURE(SetupDaemon());
+}
+
+void SnapuserdTest::SetupCopyOverlap_2() {
+    ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
+    ASSERT_NO_FATAL_FAILURE(CreateCowDeviceWithCopyOverlap_2());
+    ASSERT_NO_FATAL_FAILURE(SetupDaemon());
+}
+
+void SnapuserdTest::SetupDaemon() {
+    SetDeviceControlName();
+
+    ASSERT_NO_FATAL_FAILURE(CreateUserDevice());
+    ASSERT_NO_FATAL_FAILURE(InitCowDevice());
+    ASSERT_NO_FATAL_FAILURE(InitDaemon());
 }
 
 void SnapuserdTest::ReadSnapshotDeviceAndValidate() {
@@ -242,19 +358,6 @@
     ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0);
 }
 
-std::unique_ptr<ICowWriter> SnapuserdTest::CreateCowDeviceInternal() {
-    std::string path = android::base::GetExecutableDirectory();
-    cow_system_ = std::make_unique<TemporaryFile>(path);
-
-    CowOptions options;
-    options.compression = "gz";
-
-    unique_fd fd(cow_system_->fd);
-    cow_system_->fd = -1;
-
-    return CreateCowWriter(kDefaultCowVersion, options, std::move(fd));
-}
-
 void SnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
     auto writer = CreateCowDeviceInternal();
     ASSERT_NE(writer, nullptr);
@@ -451,90 +554,6 @@
     }
 }
 
-void SnapuserdTest::CreateCowDevice() {
-    unique_fd rnd_fd;
-    loff_t offset = 0;
-
-    auto writer = CreateCowDeviceInternal();
-    ASSERT_NE(writer, nullptr);
-
-    rnd_fd.reset(open("/dev/random", O_RDONLY));
-    ASSERT_TRUE(rnd_fd > 0);
-
-    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
-
-    // Fill random data
-    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
-                  true);
-
-        offset += 1_MiB;
-    }
-
-    size_t num_blocks = size_ / writer->GetBlockSize();
-    size_t blk_end_copy = num_blocks * 2;
-    size_t source_blk = num_blocks - 1;
-    size_t blk_src_copy = blk_end_copy - 1;
-
-    uint32_t sequence[num_blocks * 2];
-    // Sequence for Copy ops
-    for (int i = 0; i < num_blocks; i++) {
-        sequence[i] = num_blocks - 1 - i;
-    }
-    // Sequence for Xor ops
-    for (int i = 0; i < num_blocks; i++) {
-        sequence[num_blocks + i] = 5 * num_blocks - 1 - i;
-    }
-    ASSERT_TRUE(writer->AddSequenceData(2 * num_blocks, sequence));
-
-    size_t x = num_blocks;
-    while (1) {
-        ASSERT_TRUE(writer->AddCopy(source_blk, blk_src_copy));
-        x -= 1;
-        if (x == 0) {
-            break;
-        }
-        source_blk -= 1;
-        blk_src_copy -= 1;
-    }
-
-    source_blk = num_blocks;
-    blk_src_copy = blk_end_copy;
-
-    ASSERT_TRUE(writer->AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
-
-    size_t blk_zero_copy_start = source_blk + num_blocks;
-    size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks;
-
-    ASSERT_TRUE(writer->AddZeroBlocks(blk_zero_copy_start, num_blocks));
-
-    size_t blk_random2_replace_start = blk_zero_copy_end;
-
-    ASSERT_TRUE(writer->AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
-
-    size_t blk_xor_start = blk_random2_replace_start + num_blocks;
-    size_t xor_offset = BLOCK_SZ / 2;
-    ASSERT_TRUE(writer->AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks,
-                                     xor_offset));
-
-    // Flush operations
-    ASSERT_TRUE(writer->Finalize());
-    // Construct the buffer required for validation
-    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
-    std::string zero_buffer(size_, 0);
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), size_, size_), true);
-    memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_);
-    memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_);
-    memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_1_.get(), size_);
-    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, &orig_buffer_.get()[size_ * 4], size_,
-                                               size_ + xor_offset),
-              true);
-    for (int i = 0; i < size_; i++) {
-        orig_buffer_.get()[(size_ * 4) + i] =
-                (uint8_t)(orig_buffer_.get()[(size_ * 4) + i] ^ random_buffer_1_.get()[i]);
-    }
-}
-
 void SnapuserdTest::InitCowDevice() {
     bool use_iouring = true;
     if (FLAGS_force_config == "iouring_disabled") {
@@ -544,8 +563,8 @@
     auto factory = harness_->GetBlockServerFactory();
     auto opener = factory->CreateOpener(system_device_ctrl_name_);
     auto handler =
-            handlers_.AddHandler(system_device_ctrl_name_, cow_system_->path, base_loop_->device(),
-                                 base_loop_->device(), opener, 1, use_iouring, false);
+            handlers_->AddHandler(system_device_ctrl_name_, cow_system_->path, base_dev_->GetPath(),
+                                  base_dev_->GetPath(), opener, 1, use_iouring, false);
     ASSERT_NE(handler, nullptr);
     ASSERT_NE(handler->snapuserd(), nullptr);
 #ifdef __ANDROID__
@@ -553,24 +572,9 @@
 #endif
 }
 
-void SnapuserdTest::SetDeviceControlName() {
-    system_device_name_.clear();
-    system_device_ctrl_name_.clear();
-
-    std::string str(cow_system_->path);
-    std::size_t found = str.find_last_of("/\\");
-    ASSERT_NE(found, std::string::npos);
-    system_device_name_ = str.substr(found + 1);
-
-    system_device_ctrl_name_ = system_device_name_ + "-ctrl";
-}
-
 void SnapuserdTest::CreateUserDevice() {
-    unique_fd fd(TEMP_FAILURE_RETRY(open(base_loop_->device().c_str(), O_RDONLY | O_CLOEXEC)));
-    ASSERT_TRUE(fd > 0);
-
-    uint64_t dev_sz = get_block_device_size(fd.get());
-    ASSERT_TRUE(dev_sz > 0);
+    auto dev_sz = base_dev_->GetSize();
+    ASSERT_NE(dev_sz, 0);
 
     cow_num_sectors_ = dev_sz >> 9;
 
@@ -580,12 +584,12 @@
 }
 
 void SnapuserdTest::InitDaemon() {
-    ASSERT_TRUE(handlers_.StartHandler(system_device_ctrl_name_));
+    ASSERT_TRUE(handlers_->StartHandler(system_device_ctrl_name_));
 }
 
 void SnapuserdTest::CheckMergeCompletion() {
     while (true) {
-        double percentage = handlers_.GetMergePercentage();
+        double percentage = handlers_->GetMergePercentage();
         if ((int)percentage == 100) {
             break;
         }
@@ -595,27 +599,26 @@
 }
 
 void SnapuserdTest::SetupImpl() {
-    CreateBaseDevice();
-    CreateCowDevice();
+    ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
+    ASSERT_NO_FATAL_FAILURE(CreateCowDevice());
 
     SetDeviceControlName();
 
-    CreateUserDevice();
-    InitCowDevice();
-    InitDaemon();
-
-    setup_ok_ = true;
+    ASSERT_NO_FATAL_FAILURE(CreateUserDevice());
+    ASSERT_NO_FATAL_FAILURE(InitCowDevice());
+    ASSERT_NO_FATAL_FAILURE(InitDaemon());
 }
 
 bool SnapuserdTest::Merge() {
-    StartMerge();
+    if (!StartMerge()) {
+        return false;
+    }
     CheckMergeCompletion();
-    merge_ok_ = true;
-    return merge_ok_;
+    return true;
 }
 
-void SnapuserdTest::StartMerge() {
-    ASSERT_TRUE(handlers_.InitiateMerge(system_device_ctrl_name_));
+bool SnapuserdTest::StartMerge() {
+    return handlers_->InitiateMerge(system_device_ctrl_name_);
 }
 
 void SnapuserdTest::ValidateMerge() {
@@ -626,67 +629,67 @@
 }
 
 void SnapuserdTest::SimulateDaemonRestart() {
-    Shutdown();
+    ASSERT_NO_FATAL_FAILURE(Shutdown());
     std::this_thread::sleep_for(500ms);
     SetDeviceControlName();
-    CreateUserDevice();
-    InitCowDevice();
-    InitDaemon();
+    ASSERT_NO_FATAL_FAILURE(CreateUserDevice());
+    ASSERT_NO_FATAL_FAILURE(InitCowDevice());
+    ASSERT_NO_FATAL_FAILURE(InitDaemon());
 }
 
 void SnapuserdTest::MergeInterruptRandomly(int max_duration) {
     std::srand(std::time(nullptr));
-    StartMerge();
+    ASSERT_TRUE(StartMerge());
 
     for (int i = 0; i < 20; i++) {
         int duration = std::rand() % max_duration;
         std::this_thread::sleep_for(std::chrono::milliseconds(duration));
-        SimulateDaemonRestart();
-        StartMerge();
+        ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
+        ASSERT_TRUE(StartMerge());
     }
 
-    SimulateDaemonRestart();
+    ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
     ASSERT_TRUE(Merge());
 }
 
 void SnapuserdTest::MergeInterruptFixed(int duration) {
-    StartMerge();
+    ASSERT_TRUE(StartMerge());
 
     for (int i = 0; i < 25; i++) {
         std::this_thread::sleep_for(std::chrono::milliseconds(duration));
-        SimulateDaemonRestart();
-        StartMerge();
+        ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
+        ASSERT_TRUE(StartMerge());
     }
 
-    SimulateDaemonRestart();
+    ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
     ASSERT_TRUE(Merge());
 }
 
 void SnapuserdTest::MergeInterrupt() {
     // Interrupt merge at various intervals
-    StartMerge();
+    ASSERT_TRUE(StartMerge());
     std::this_thread::sleep_for(250ms);
-    SimulateDaemonRestart();
+    ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
 
-    StartMerge();
+    ASSERT_TRUE(StartMerge());
     std::this_thread::sleep_for(250ms);
-    SimulateDaemonRestart();
+    ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
 
-    StartMerge();
+    ASSERT_TRUE(StartMerge());
     std::this_thread::sleep_for(150ms);
-    SimulateDaemonRestart();
+    ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
 
-    StartMerge();
+    ASSERT_TRUE(StartMerge());
     std::this_thread::sleep_for(100ms);
-    SimulateDaemonRestart();
+    ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
 
-    StartMerge();
+    ASSERT_TRUE(StartMerge());
     std::this_thread::sleep_for(800ms);
-    SimulateDaemonRestart();
+    ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
 
-    StartMerge();
+    ASSERT_TRUE(StartMerge());
     std::this_thread::sleep_for(600ms);
-    SimulateDaemonRestart();
+    ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
 
     ASSERT_TRUE(Merge());
 }
@@ -695,98 +698,191 @@
     if (!harness_->HasUserDevice()) {
         GTEST_SKIP() << "Skipping snapshot read; not supported";
     }
-    ASSERT_TRUE(SetupDefault());
+    ASSERT_NO_FATAL_FAILURE(SetupDefault());
     // I/O before merge
-    ReadSnapshotDeviceAndValidate();
+    ASSERT_NO_FATAL_FAILURE(ReadSnapshotDeviceAndValidate());
     ASSERT_TRUE(Merge());
     ValidateMerge();
     // I/O after merge - daemon should read directly
     // from base device
-    ReadSnapshotDeviceAndValidate();
-    Shutdown();
+    ASSERT_NO_FATAL_FAILURE(ReadSnapshotDeviceAndValidate());
 }
 
 TEST_F(SnapuserdTest, Snapshot_MERGE_IO_TEST) {
     if (!harness_->HasUserDevice()) {
         GTEST_SKIP() << "Skipping snapshot read; not supported";
     }
-    ASSERT_TRUE(SetupDefault());
+    ASSERT_NO_FATAL_FAILURE(SetupDefault());
     // Issue I/O before merge begins
     std::async(std::launch::async, &SnapuserdTest::ReadSnapshotDeviceAndValidate, this);
     // Start the merge
     ASSERT_TRUE(Merge());
     ValidateMerge();
-    Shutdown();
 }
 
 TEST_F(SnapuserdTest, Snapshot_MERGE_IO_TEST_1) {
     if (!harness_->HasUserDevice()) {
         GTEST_SKIP() << "Skipping snapshot read; not supported";
     }
-    ASSERT_TRUE(SetupDefault());
+    ASSERT_NO_FATAL_FAILURE(SetupDefault());
     // Start the merge
-    StartMerge();
+    ASSERT_TRUE(StartMerge());
     // Issue I/O in parallel when merge is in-progress
     std::async(std::launch::async, &SnapuserdTest::ReadSnapshotDeviceAndValidate, this);
     CheckMergeCompletion();
     ValidateMerge();
-    Shutdown();
 }
 
 TEST_F(SnapuserdTest, Snapshot_Merge_Resume) {
-    ASSERT_TRUE(SetupDefault());
-    MergeInterrupt();
+    ASSERT_NO_FATAL_FAILURE(SetupDefault());
+    ASSERT_NO_FATAL_FAILURE(MergeInterrupt());
     ValidateMerge();
-    Shutdown();
 }
 
 TEST_F(SnapuserdTest, Snapshot_COPY_Overlap_TEST_1) {
-    ASSERT_TRUE(SetupCopyOverlap_1());
+    ASSERT_NO_FATAL_FAILURE(SetupCopyOverlap_1());
     ASSERT_TRUE(Merge());
     ValidateMerge();
-    Shutdown();
 }
 
 TEST_F(SnapuserdTest, Snapshot_COPY_Overlap_TEST_2) {
-    ASSERT_TRUE(SetupCopyOverlap_2());
+    ASSERT_NO_FATAL_FAILURE(SetupCopyOverlap_2());
     ASSERT_TRUE(Merge());
     ValidateMerge();
-    Shutdown();
 }
 
 TEST_F(SnapuserdTest, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
-    ASSERT_TRUE(SetupCopyOverlap_1());
-    MergeInterrupt();
+    ASSERT_NO_FATAL_FAILURE(SetupCopyOverlap_1());
+    ASSERT_NO_FATAL_FAILURE(MergeInterrupt());
     ValidateMerge();
-    Shutdown();
 }
 
 TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Fixed_Ordered) {
-    ASSERT_TRUE(SetupOrderedOps());
-    MergeInterruptFixed(300);
+    ASSERT_NO_FATAL_FAILURE(SetupOrderedOps());
+    ASSERT_NO_FATAL_FAILURE(MergeInterruptFixed(300));
     ValidateMerge();
-    Shutdown();
 }
 
 TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Random_Ordered) {
-    ASSERT_TRUE(SetupOrderedOps());
-    MergeInterruptRandomly(500);
+    ASSERT_NO_FATAL_FAILURE(SetupOrderedOps());
+    ASSERT_NO_FATAL_FAILURE(MergeInterruptRandomly(500));
     ValidateMerge();
-    Shutdown();
 }
 
 TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Fixed_Inverted) {
-    ASSERT_TRUE(SetupOrderedOpsInverted());
-    MergeInterruptFixed(50);
+    ASSERT_NO_FATAL_FAILURE(SetupOrderedOpsInverted());
+    ASSERT_NO_FATAL_FAILURE(MergeInterruptFixed(50));
     ValidateMerge();
-    Shutdown();
 }
 
 TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Random_Inverted) {
-    ASSERT_TRUE(SetupOrderedOpsInverted());
-    MergeInterruptRandomly(50);
+    ASSERT_NO_FATAL_FAILURE(SetupOrderedOpsInverted());
+    ASSERT_NO_FATAL_FAILURE(MergeInterruptRandomly(50));
     ValidateMerge();
-    Shutdown();
+}
+
+class HandlerTest : public SnapuserdTestBase {
+  protected:
+    void SetUp() override;
+    void TearDown() override;
+
+    AssertionResult ReadSectors(sector_t sector, uint64_t size, void* buffer);
+
+    TestBlockServerFactory factory_;
+    std::shared_ptr<TestBlockServerOpener> opener_;
+    std::shared_ptr<SnapshotHandler> handler_;
+    std::unique_ptr<ReadWorker> read_worker_;
+    TestBlockServer* block_server_;
+    std::future<bool> handler_thread_;
+};
+
+void HandlerTest::SetUp() {
+    ASSERT_NO_FATAL_FAILURE(SnapuserdTestBase::SetUp());
+    ASSERT_NO_FATAL_FAILURE(CreateBaseDevice());
+    ASSERT_NO_FATAL_FAILURE(CreateCowDevice());
+    ASSERT_NO_FATAL_FAILURE(SetDeviceControlName());
+
+    opener_ = factory_.CreateTestOpener(system_device_ctrl_name_);
+    ASSERT_NE(opener_, nullptr);
+
+    handler_ = std::make_shared<SnapshotHandler>(system_device_ctrl_name_, cow_system_->path,
+                                                 base_dev_->GetPath(), base_dev_->GetPath(),
+                                                 opener_, 1, false, false);
+    ASSERT_TRUE(handler_->InitCowDevice());
+    ASSERT_TRUE(handler_->InitializeWorkers());
+
+    read_worker_ = std::make_unique<ReadWorker>(cow_system_->path, base_dev_->GetPath(),
+                                                system_device_ctrl_name_, base_dev_->GetPath(),
+                                                handler_->GetSharedPtr(), opener_);
+    ASSERT_TRUE(read_worker_->Init());
+    block_server_ = static_cast<TestBlockServer*>(read_worker_->block_server());
+
+    handler_thread_ = std::async(std::launch::async, &SnapshotHandler::Start, handler_.get());
+}
+
+void HandlerTest::TearDown() {
+    ASSERT_TRUE(factory_.DeleteQueue(system_device_ctrl_name_));
+    ASSERT_TRUE(handler_thread_.get());
+    SnapuserdTestBase::TearDown();
+}
+
+AssertionResult HandlerTest::ReadSectors(sector_t sector, uint64_t size, void* buffer) {
+    if (!read_worker_->RequestSectors(sector, size)) {
+        return AssertionFailure() << "request sectors failed";
+    }
+
+    std::string result = std::move(block_server_->sent_io());
+    if (result.size() != size) {
+        return AssertionFailure() << "size mismatch in result, got " << result.size()
+                                  << ", expected " << size;
+    }
+
+    memcpy(buffer, result.data(), size);
+    return AssertionSuccess();
+}
+
+// This test mirrors ReadSnapshotDeviceAndValidate.
+TEST_F(HandlerTest, Read) {
+    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
+
+    // COPY
+    loff_t offset = 0;
+    ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, size_, snapuserd_buffer.get()));
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, size_, snapuserd_buffer.get()));
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0);
+
+    // ZERO
+    offset += size_;
+    ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, size_, snapuserd_buffer.get()));
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, size_, snapuserd_buffer.get()));
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
+
+    // XOR
+    offset += size_;
+    ASSERT_TRUE(ReadSectors(offset / SECTOR_SIZE, size_, snapuserd_buffer.get()));
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0);
+}
+
+TEST_F(HandlerTest, ReadUnalignedSector) {
+    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(BLOCK_SZ);
+
+    ASSERT_TRUE(ReadSectors(1, BLOCK_SZ, snapuserd_buffer.get()));
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get() + SECTOR_SIZE, BLOCK_SZ), 0);
+}
+
+TEST_F(HandlerTest, ReadUnalignedSize) {
+    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(SECTOR_SIZE);
+
+    ASSERT_TRUE(ReadSectors(0, SECTOR_SIZE, snapuserd_buffer.get()));
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), SECTOR_SIZE), 0);
 }
 
 }  // namespace snapshot
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 28c9f68..52e4f89 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
@@ -199,6 +199,7 @@
 
             if (io_state_ == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE ||
                 io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+                SNAP_LOG(ERROR) << "WaitForMergeBegin failed with state: " << io_state_;
                 return false;
             }
         }
@@ -211,6 +212,7 @@
 
         if (io_state_ == MERGE_IO_TRANSITION::READ_AHEAD_FAILURE ||
             io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+            SNAP_LOG(ERROR) << "WaitForMergeBegin failed with state: " << io_state_;
             return false;
         }
 
@@ -277,6 +279,7 @@
         if (io_state_ == MERGE_IO_TRANSITION::MERGE_FAILED ||
             io_state_ == MERGE_IO_TRANSITION::MERGE_COMPLETE ||
             io_state_ == MERGE_IO_TRANSITION::IO_TERMINATED) {
+            SNAP_LOG(ERROR) << "Wait for merge ready failed: " << io_state_;
             return false;
         }
         return true;
@@ -668,5 +671,26 @@
     }
 }
 
+std::ostream& operator<<(std::ostream& os, MERGE_IO_TRANSITION value) {
+    switch (value) {
+        case MERGE_IO_TRANSITION::INVALID:
+            return os << "INVALID";
+        case MERGE_IO_TRANSITION::MERGE_READY:
+            return os << "MERGE_READY";
+        case MERGE_IO_TRANSITION::MERGE_BEGIN:
+            return os << "MERGE_BEGIN";
+        case MERGE_IO_TRANSITION::MERGE_FAILED:
+            return os << "MERGE_FAILED";
+        case MERGE_IO_TRANSITION::MERGE_COMPLETE:
+            return os << "MERGE_COMPLETE";
+        case MERGE_IO_TRANSITION::IO_TERMINATED:
+            return os << "IO_TERMINATED";
+        case MERGE_IO_TRANSITION::READ_AHEAD_FAILURE:
+            return os << "READ_AHEAD_FAILURE";
+        default:
+            return os << "unknown";
+    }
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/libcutils/include/cutils/threads.h b/libcutils/include/cutils/threads.h
index 92564b8..9bc3429 100644
--- a/libcutils/include/cutils/threads.h
+++ b/libcutils/include/cutils/threads.h
@@ -13,13 +13,3 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-#pragma once
-
-#include  <sys/types.h>
-
-#if defined(_WIN32)
-#include <windows.h>
-#else
-#include <pthread.h>
-#endif