libsnaphot: Refactor cow_snapuserd test

Refact cow_snapuserd unit test. This will
test code changes in daemon in isolation. Currently it tests

1: Reading snapshot device and validate data
2: Merge and validate data.

More tests related to merge will be added later; but this
should provide a baseline unit test for daemon.

BUG: 168311203
Test: cow_snapuserd_test

Signed-off-by: Akilesh Kailash <akailash@google.com>
Change-Id: I4d77c435a9f045204a4ff3ece524c857f9fba50f
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 34049d4..e32fde0 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -563,6 +563,7 @@
         "libsnapshot_snapuserd",
         "libcutils_sockets",
         "libz",
+	"libfs_mgr",
         "libdm",
     ],
     header_libs: [
diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
index 5483fd0..16d9313 100644
--- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
@@ -12,10 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <fcntl.h>
 #include <linux/fs.h>
+#include <linux/memfd.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
+#include <sys/syscall.h>
 #include <sys/types.h>
+#include <unistd.h>
 
 #include <chrono>
 #include <iostream>
@@ -24,7 +28,9 @@
 
 #include <android-base/file.h>
 #include <android-base/unique_fd.h>
+#include <fs_mgr/file_wait.h>
 #include <gtest/gtest.h>
+#include <libdm/dm.h>
 #include <libdm/loop_control.h>
 #include <libsnapshot/cow_writer.h>
 #include <libsnapshot/snapuserd_client.h>
@@ -37,111 +43,203 @@
 using android::base::unique_fd;
 using LoopDevice = android::dm::LoopDevice;
 using namespace std::chrono_literals;
+using namespace android::dm;
+using namespace std;
 
-class SnapuserdTest : public ::testing::Test {
-  protected:
-    void SetUp() override {
-        // TODO: Daemon started through first stage
-        // init does not have permission to read files
-        // from /data/nativetest.
-        system("setenforce 0");
-        cow_system_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_system_->fd, 0) << strerror(errno);
+static constexpr char kSnapuserdSocketTest[] = "snapuserdTest";
 
-        cow_product_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_product_->fd, 0) << strerror(errno);
+class TempDevice {
+  public:
+    TempDevice(const std::string& name, const DmTable& table)
+        : dm_(DeviceMapper::Instance()), name_(name), valid_(false) {
+        valid_ = dm_.CreateDevice(name, table, &path_, std::chrono::seconds(5));
+    }
+    TempDevice(TempDevice&& other) noexcept
+        : dm_(other.dm_), name_(other.name_), path_(other.path_), valid_(other.valid_) {
+        other.valid_ = false;
+    }
+    ~TempDevice() {
+        if (valid_) {
+            dm_.DeleteDevice(name_);
+        }
+    }
+    bool Destroy() {
+        if (!valid_) {
+            return false;
+        }
+        valid_ = false;
+        return dm_.DeleteDevice(name_);
+    }
+    const std::string& path() const { return path_; }
+    const std::string& name() const { return name_; }
+    bool valid() const { return valid_; }
 
-        cow_system_1_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_system_1_->fd, 0) << strerror(errno);
+    TempDevice(const TempDevice&) = delete;
+    TempDevice& operator=(const TempDevice&) = delete;
 
-        cow_product_1_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_product_1_->fd, 0) << strerror(errno);
-
-        std::string path = android::base::GetExecutableDirectory();
-
-        system_a_ = std::make_unique<TemporaryFile>(path);
-        ASSERT_GE(system_a_->fd, 0) << strerror(errno);
-
-        product_a_ = std::make_unique<TemporaryFile>(path);
-        ASSERT_GE(product_a_->fd, 0) << strerror(errno);
-
-        size_ = 100_MiB;
+    TempDevice& operator=(TempDevice&& other) noexcept {
+        name_ = other.name_;
+        valid_ = other.valid_;
+        other.valid_ = false;
+        return *this;
     }
 
-    void TearDown() override {
-        system("setenforce 1");
-        cow_system_ = nullptr;
-        cow_product_ = nullptr;
-
-        cow_system_1_ = nullptr;
-        cow_product_1_ = nullptr;
-    }
-
-    std::unique_ptr<TemporaryFile> system_a_;
-    std::unique_ptr<TemporaryFile> product_a_;
-
-    std::unique_ptr<LoopDevice> system_a_loop_;
-    std::unique_ptr<LoopDevice> product_a_loop_;
-
-    std::unique_ptr<TemporaryFile> cow_system_;
-    std::unique_ptr<TemporaryFile> cow_product_;
-
-    std::unique_ptr<TemporaryFile> cow_system_1_;
-    std::unique_ptr<TemporaryFile> cow_product_1_;
-
-    unique_fd sys_fd_;
-    unique_fd product_fd_;
-    size_t size_;
-
-    int system_blksize_;
-    int product_blksize_;
-    std::string system_device_name_;
-    std::string product_device_name_;
-
-    std::string system_device_ctrl_name_;
-    std::string product_device_ctrl_name_;
-
-    std::unique_ptr<uint8_t[]> random_buffer_1_;
-    std::unique_ptr<uint8_t[]> random_buffer_2_;
-    std::unique_ptr<uint8_t[]> zero_buffer_;
-    std::unique_ptr<uint8_t[]> system_buffer_;
-    std::unique_ptr<uint8_t[]> product_buffer_;
-
-    void Init();
-    void InitCowDevices();
-    void InitDaemon();
-    void CreateCowDevice(std::unique_ptr<TemporaryFile>& cow);
-    void CreateSystemDmUser(std::unique_ptr<TemporaryFile>& cow);
-    void CreateProductDmUser(std::unique_ptr<TemporaryFile>& cow);
-    void DeleteDmUser(std::unique_ptr<TemporaryFile>& cow, std::string snapshot_device);
-    void StartSnapuserdDaemon();
-    void CreateSnapshotDevices();
-    void SwitchSnapshotDevices();
-
-    std::string GetSystemControlPath() {
-        return std::string("/dev/dm-user/") + system_device_ctrl_name_;
-    }
-    std::string GetProductControlPath() {
-        return std::string("/dev/dm-user/") + product_device_ctrl_name_;
-    }
-
-    void TestIO(unique_fd& snapshot_fd, std::unique_ptr<uint8_t[]>& buffer);
-    std::unique_ptr<SnapuserdClient> client_;
+  private:
+    DeviceMapper& dm_;
+    std::string name_;
+    std::string path_;
+    bool valid_;
 };
 
-void SnapuserdTest::Init() {
+class CowSnapuserdTest final {
+  public:
+    bool Setup();
+    bool Merge();
+    void ValidateMerge();
+    void ReadSnapshotDeviceAndValidate();
+    void Shutdown();
+
+    std::string snapshot_dev() const { return snapshot_dev_->path(); }
+
+    static const uint64_t kSectorSize = 512;
+
+  private:
+    void SetupImpl();
+    void MergeImpl();
+    void CreateCowDevice();
+    void CreateBaseDevice();
+    void InitCowDevice();
+    void SetDeviceControlName();
+    void InitDaemon();
+    void CreateDmUserDevice();
+    void StartSnapuserdDaemon();
+    void CreateSnapshotDevice();
+    unique_fd CreateTempFile(const std::string& name, size_t size);
+
+    unique_ptr<LoopDevice> base_loop_;
+    unique_ptr<TempDevice> dmuser_dev_;
+    unique_ptr<TempDevice> snapshot_dev_;
+
+    std::string system_device_ctrl_name_;
+    std::string system_device_name_;
+
+    unique_fd base_fd_;
+    std::unique_ptr<TemporaryFile> cow_system_;
+    std::unique_ptr<SnapuserdClient> client_;
+    std::unique_ptr<uint8_t[]> orig_buffer_;
+    std::unique_ptr<uint8_t[]> merged_buffer_;
+    bool setup_ok_ = false;
+    bool merge_ok_ = false;
+    size_t size_ = 1_MiB;
+    int cow_num_sectors_;
+    int total_base_size_;
+};
+
+unique_fd CowSnapuserdTest::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 CowSnapuserdTest::Shutdown() {
+    ASSERT_TRUE(client_->StopSnapuserd());
+    ASSERT_TRUE(snapshot_dev_->Destroy());
+    ASSERT_TRUE(dmuser_dev_->Destroy());
+}
+
+bool CowSnapuserdTest::Setup() {
+    SetupImpl();
+    return setup_ok_;
+}
+
+void CowSnapuserdTest::StartSnapuserdDaemon() {
+    pid_t pid = fork();
+    ASSERT_GE(pid, 0);
+    if (pid == 0) {
+        std::string arg0 = "/system/bin/snapuserd";
+        std::string arg1 = kSnapuserdSocketTest;
+        char* const argv[] = {arg0.data(), arg1.data(), nullptr};
+        ASSERT_GE(execv(arg0.c_str(), argv), 0);
+    } else {
+        client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s);
+        ASSERT_NE(client_, nullptr);
+    }
+}
+
+void CowSnapuserdTest::CreateBaseDevice() {
     unique_fd rnd_fd;
-    loff_t offset = 0;
-    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
+
+    total_base_size_ = (size_ * 4);
+    base_fd_ = CreateTempFile("base_device", total_base_size_);
+    ASSERT_GE(base_fd_, 0);
 
     rnd_fd.reset(open("/dev/random", O_RDONLY));
     ASSERT_TRUE(rnd_fd > 0);
 
-    random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
-    random_buffer_2_ = std::make_unique<uint8_t[]>(size_);
-    system_buffer_ = std::make_unique<uint8_t[]>(size_);
-    product_buffer_ = std::make_unique<uint8_t[]>(size_);
-    zero_buffer_ = std::make_unique<uint8_t[]>(size_);
+    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
+
+    for (size_t j = 0; j < ((total_base_size_) / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
+        ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), 1_MiB), true);
+    }
+
+    ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
+
+    base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
+    ASSERT_TRUE(base_loop_->valid());
+}
+
+void CowSnapuserdTest::ReadSnapshotDeviceAndValidate() {
+    unique_fd snapshot_fd(open(snapshot_dev_->path().c_str(), O_RDONLY));
+    ASSERT_TRUE(snapshot_fd > 0);
+
+    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
+
+    // COPY
+    loff_t offset = 0;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0);
+
+    // ZERO
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
+}
+
+void CowSnapuserdTest::CreateCowDevice() {
+    unique_fd rnd_fd;
+    loff_t offset = 0;
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    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_);
+    std::unique_ptr<uint8_t[]> random_buffer_2_ = std::make_unique<uint8_t[]>(size_);
 
     // Fill random data
     for (size_t j = 0; j < (size_ / 1_MiB); j++) {
@@ -154,77 +252,32 @@
         offset += 1_MiB;
     }
 
-    for (size_t j = 0; j < (800_MiB / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
-        ASSERT_EQ(android::base::WriteFully(system_a_->fd, random_buffer.get(), 1_MiB), true);
-    }
-
-    for (size_t j = 0; j < (800_MiB / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
-        ASSERT_EQ(android::base::WriteFully(product_a_->fd, random_buffer.get(), 1_MiB), true);
-    }
-
-    // Create loopback devices
-    system_a_loop_ = std::make_unique<LoopDevice>(std::string(system_a_->path), 10s);
-    ASSERT_TRUE(system_a_loop_->valid());
-
-    product_a_loop_ = std::make_unique<LoopDevice>(std::string(product_a_->path), 10s);
-    ASSERT_TRUE(product_a_loop_->valid());
-
-    sys_fd_.reset(open(system_a_loop_->device().c_str(), O_RDONLY));
-    ASSERT_TRUE(sys_fd_ > 0);
-
-    product_fd_.reset(open(product_a_loop_->device().c_str(), O_RDONLY));
-    ASSERT_TRUE(product_fd_ > 0);
-
-    // Read from system partition from offset 0 of size 100MB
-    ASSERT_EQ(ReadFullyAtOffset(sys_fd_, system_buffer_.get(), size_, 0), true);
-
-    // Read from product partition from offset 0 of size 100MB
-    ASSERT_EQ(ReadFullyAtOffset(product_fd_, product_buffer_.get(), size_, 0), true);
-}
-
-void SnapuserdTest::CreateCowDevice(std::unique_ptr<TemporaryFile>& cow) {
-    //================Create a COW file with the following operations===========
-    //
-    // Create COW file which is gz compressed
-    //
-    // 0-100 MB of replace operation with random data
-    // 100-200 MB of copy operation
-    // 200-300 MB of zero operation
-    // 300-400 MB of replace operation with random data
-
     CowOptions options;
     options.compression = "gz";
     CowWriter writer(options);
 
-    ASSERT_TRUE(writer.Initialize(cow->fd));
-
-    // Write 100MB random data to COW file which is gz compressed from block 0
-    ASSERT_TRUE(writer.AddRawBlocks(0, random_buffer_1_.get(), size_));
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
 
     size_t num_blocks = size_ / options.block_size;
-    size_t blk_start_copy = num_blocks;
-    size_t blk_end_copy = blk_start_copy + num_blocks;
+    size_t blk_src_copy = num_blocks;
+    size_t blk_end_copy = blk_src_copy + num_blocks;
     size_t source_blk = 0;
 
-    // Copy blocks - source_blk starts from 0 as snapuserd
-    // has to read from block 0 in system_a partition
-    //
-    // This initializes copy operation from block 0 of size 100 MB from
-    // /dev/block/mapper/system_a or product_a
-    for (size_t i = blk_start_copy; i < blk_end_copy; i++) {
-        ASSERT_TRUE(writer.AddCopy(i, source_blk));
+    while (source_blk < num_blocks) {
+        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
         source_blk += 1;
+        blk_src_copy += 1;
     }
 
-    size_t blk_zero_copy_start = blk_end_copy;
+    ASSERT_EQ(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;
 
-    // 100 MB filled with zeroes
     ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks));
 
-    // Final 100MB filled with random data which is gz compressed
     size_t blk_random2_replace_start = blk_zero_copy_end;
 
     ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_2_.get(), size_));
@@ -232,233 +285,133 @@
     // Flush operations
     ASSERT_TRUE(writer.Finalize());
 
-    ASSERT_EQ(lseek(cow->fd, 0, SEEK_SET), 0);
+    // 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_2_.get(), size_);
 }
 
-void SnapuserdTest::CreateSystemDmUser(std::unique_ptr<TemporaryFile>& cow) {
-    std::string cmd;
+void CowSnapuserdTest::InitCowDevice() {
+    cow_num_sectors_ = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
+                                              base_loop_->device());
+    ASSERT_NE(cow_num_sectors_, 0);
+}
+
+void CowSnapuserdTest::SetDeviceControlName() {
     system_device_name_.clear();
     system_device_ctrl_name_.clear();
 
-    std::string str(cow->path);
+    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);
 
-    // Create a control device
     system_device_ctrl_name_ = system_device_name_ + "-ctrl";
-    cmd = "dmctl create " + system_device_name_ + " user 0 " + std::to_string(system_blksize_);
-    cmd += " " + system_device_ctrl_name_;
-
-    system(cmd.c_str());
 }
 
-void SnapuserdTest::DeleteDmUser(std::unique_ptr<TemporaryFile>& cow, std::string snapshot_device) {
-    std::string cmd;
+void CowSnapuserdTest::CreateDmUserDevice() {
+    DmTable dmuser_table;
+    ASSERT_TRUE(dmuser_table.AddTarget(
+            std::make_unique<DmTargetUser>(0, cow_num_sectors_, system_device_ctrl_name_)));
+    ASSERT_TRUE(dmuser_table.valid());
 
-    cmd = "dmctl delete " + snapshot_device;
-    system(cmd.c_str());
+    dmuser_dev_ = std::make_unique<TempDevice>(system_device_name_, dmuser_table);
+    ASSERT_TRUE(dmuser_dev_->valid());
+    ASSERT_FALSE(dmuser_dev_->path().empty());
 
-    cmd.clear();
-
-    std::string str(cow->path);
-    std::size_t found = str.find_last_of("/\\");
-    ASSERT_NE(found, std::string::npos);
-    std::string device_name = str.substr(found + 1);
-
-    cmd = "dmctl delete " + device_name;
-
-    system(cmd.c_str());
+    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
+    ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s));
 }
 
-void SnapuserdTest::CreateProductDmUser(std::unique_ptr<TemporaryFile>& cow) {
-    std::string cmd;
-    product_device_name_.clear();
-    product_device_ctrl_name_.clear();
-
-    std::string str(cow->path);
-    std::size_t found = str.find_last_of("/\\");
-    ASSERT_NE(found, std::string::npos);
-    product_device_name_ = str.substr(found + 1);
-    product_device_ctrl_name_ = product_device_name_ + "-ctrl";
-    cmd = "dmctl create " + product_device_name_ + " user 0 " + std::to_string(product_blksize_);
-    cmd += " " + product_device_ctrl_name_;
-
-    system(cmd.c_str());
-}
-
-void SnapuserdTest::InitCowDevices() {
-    system_blksize_ = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
-                                             system_a_loop_->device());
-    ASSERT_NE(system_blksize_, 0);
-
-    product_blksize_ = client_->InitDmUserCow(product_device_ctrl_name_, cow_product_->path,
-                                              product_a_loop_->device());
-    ASSERT_NE(product_blksize_, 0);
-}
-
-void SnapuserdTest::InitDaemon() {
+void CowSnapuserdTest::InitDaemon() {
     bool ok = client_->AttachDmUser(system_device_ctrl_name_);
     ASSERT_TRUE(ok);
-
-    ok = client_->AttachDmUser(product_device_ctrl_name_);
-    ASSERT_TRUE(ok);
 }
 
-void SnapuserdTest::StartSnapuserdDaemon() {
-    ASSERT_TRUE(EnsureSnapuserdStarted());
+void CowSnapuserdTest::CreateSnapshotDevice() {
+    DmTable snap_table;
+    ASSERT_TRUE(snap_table.AddTarget(std::make_unique<DmTargetSnapshot>(
+            0, total_base_size_ / kSectorSize, base_loop_->device(), dmuser_dev_->path(),
+            SnapshotStorageMode::Persistent, 8)));
+    ASSERT_TRUE(snap_table.valid());
 
-    client_ = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
-    ASSERT_NE(client_, nullptr);
+    snap_table.set_readonly(true);
+
+    snapshot_dev_ = std::make_unique<TempDevice>("cowsnapuserd-test-dm-snapshot", snap_table);
+    ASSERT_TRUE(snapshot_dev_->valid());
+    ASSERT_FALSE(snapshot_dev_->path().empty());
 }
 
-void SnapuserdTest::CreateSnapshotDevices() {
-    std::string cmd;
+void CowSnapuserdTest::SetupImpl() {
+    CreateBaseDevice();
+    CreateCowDevice();
 
-    cmd = "dmctl create system-snapshot -ro snapshot 0 " + std::to_string(system_blksize_);
-    cmd += " " + system_a_loop_->device();
-    cmd += " /dev/block/mapper/" + system_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-
-    cmd.clear();
-
-    cmd = "dmctl create product-snapshot -ro snapshot 0 " + std::to_string(product_blksize_);
-    cmd += " " + product_a_loop_->device();
-    cmd += " /dev/block/mapper/" + product_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-}
-
-void SnapuserdTest::SwitchSnapshotDevices() {
-    std::string cmd;
-
-    cmd = "dmctl create system-snapshot-1 -ro snapshot 0 " + std::to_string(system_blksize_);
-    cmd += " " + system_a_loop_->device();
-    cmd += " /dev/block/mapper/" + system_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-
-    cmd.clear();
-
-    cmd = "dmctl create product-snapshot-1 -ro snapshot 0 " + std::to_string(product_blksize_);
-    cmd += " " + product_a_loop_->device();
-    cmd += " /dev/block/mapper/" + product_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-}
-
-void SnapuserdTest::TestIO(unique_fd& snapshot_fd, std::unique_ptr<uint8_t[]>& buffer) {
-    loff_t offset = 0;
-    // std::unique_ptr<uint8_t[]> buffer = std::move(buf);
-
-    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
-
-    //================Start IO operation on dm-snapshot device=================
-    // This will test the following paths:
-    //
-    // 1: IO path for all three operations and interleaving of operations.
-    // 2: Merging of blocks in kernel during metadata read
-    // 3: Bulk IO issued by kernel duing merge operation
-
-    // Read from snapshot device of size 100MB from offset 0. This tests the
-    // 1st replace operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_compressed_cow (replace
-    // op)->decompress_cow->return
-
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Update the offset
-    offset += size_;
-
-    // Compare data with random_buffer_1_.
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_1_.get(), size_), 0);
-
-    // Clear the buffer
-    memset(snapuserd_buffer.get(), 0, size_);
-
-    // Read from snapshot device of size 100MB from offset 100MB. This tests the
-    // copy operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_from_(system_a/product_a) partition
-    // (copy op) -> return
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Update the offset
-    offset += size_;
-
-    // Compare data with buffer.
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), buffer.get(), size_), 0);
-
-    // Read from snapshot device of size 100MB from offset 200MB. This tests the
-    // zero operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->fill_memory_with_zero
-    // (zero op) -> return
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Compare data with zero filled buffer
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), zero_buffer_.get(), size_), 0);
-
-    // Update the offset
-    offset += size_;
-
-    // Read from snapshot device of size 100MB from offset 300MB. This tests the
-    // final replace operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_compressed_cow (replace
-    // op)->decompress_cow->return
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Compare data with random_buffer_2_.
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_2_.get(), size_), 0);
-}
-
-TEST_F(SnapuserdTest, ReadWrite) {
-    unique_fd snapshot_fd;
-
-    Init();
-
-    CreateCowDevice(cow_system_);
-    CreateCowDevice(cow_product_);
+    SetDeviceControlName();
 
     StartSnapuserdDaemon();
-    InitCowDevices();
+    InitCowDevice();
 
-    CreateSystemDmUser(cow_system_);
-    CreateProductDmUser(cow_product_);
-
+    CreateDmUserDevice();
     InitDaemon();
 
-    CreateSnapshotDevices();
+    CreateSnapshotDevice();
+    setup_ok_ = true;
+}
 
-    snapshot_fd.reset(open("/dev/block/mapper/system-snapshot", O_RDONLY));
-    ASSERT_TRUE(snapshot_fd > 0);
-    TestIO(snapshot_fd, system_buffer_);
+bool CowSnapuserdTest::Merge() {
+    MergeImpl();
+    return merge_ok_;
+}
 
-    snapshot_fd.reset(open("/dev/block/mapper/product-snapshot", O_RDONLY));
-    ASSERT_TRUE(snapshot_fd > 0);
-    TestIO(snapshot_fd, product_buffer_);
+void CowSnapuserdTest::MergeImpl() {
+    DmTable merge_table;
+    ASSERT_TRUE(merge_table.AddTarget(std::make_unique<DmTargetSnapshot>(
+            0, total_base_size_ / kSectorSize, base_loop_->device(), dmuser_dev_->path(),
+            SnapshotStorageMode::Merge, 8)));
+    ASSERT_TRUE(merge_table.valid());
+    ASSERT_EQ(total_base_size_ / kSectorSize, merge_table.num_sectors());
 
-    snapshot_fd.reset(-1);
+    DeviceMapper& dm = DeviceMapper::Instance();
+    ASSERT_TRUE(dm.LoadTableAndActivate("cowsnapuserd-test-dm-snapshot", merge_table));
 
-    DeleteDmUser(cow_system_, "system-snapshot");
-    DeleteDmUser(cow_product_, "product-snapshot");
+    while (true) {
+        vector<DeviceMapper::TargetInfo> status;
+        ASSERT_TRUE(dm.GetTableStatus("cowsnapuserd-test-dm-snapshot", &status));
+        ASSERT_EQ(status.size(), 1);
+        ASSERT_EQ(strncmp(status[0].spec.target_type, "snapshot-merge", strlen("snapshot-merge")),
+                  0);
 
-    ASSERT_TRUE(client_->StopSnapuserd());
+        DmTargetSnapshot::Status merge_status;
+        ASSERT_TRUE(DmTargetSnapshot::ParseStatusText(status[0].data, &merge_status));
+        ASSERT_TRUE(merge_status.error.empty());
+        if (merge_status.sectors_allocated == merge_status.metadata_sectors) {
+            break;
+        }
+
+        std::this_thread::sleep_for(250ms);
+    }
+
+    merge_ok_ = true;
+}
+
+void CowSnapuserdTest::ValidateMerge() {
+    merged_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0),
+              true);
+    ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0);
+}
+
+TEST(Snapuserd_Test, Snapshot) {
+    CowSnapuserdTest harness;
+    ASSERT_TRUE(harness.Setup());
+    harness.ReadSnapshotDeviceAndValidate();
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp
index 7282bff..16d02e4 100644
--- a/fs_mgr/libsnapshot/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_client.cpp
@@ -57,7 +57,7 @@
 SnapuserdClient::SnapuserdClient(android::base::unique_fd&& sockfd) : sockfd_(std::move(sockfd)) {}
 
 static inline bool IsRetryErrno() {
-    return errno == ECONNREFUSED || errno == EINTR;
+    return errno == ECONNREFUSED || errno == EINTR || errno == ENOENT;
 }
 
 std::unique_ptr<SnapuserdClient> SnapuserdClient::Connect(const std::string& socket_name,
@@ -112,6 +112,7 @@
 }
 
 bool SnapuserdClient::Sendmsg(const std::string& msg) {
+    LOG(DEBUG) << "Sendmsg: msg " << msg << " sockfd: " << sockfd_;
     ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), 0));
     if (numBytesSent < 0) {
         PLOG(ERROR) << "Send failed";