Merge "init_kill_services_test: log state"
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index 844357c..3b8866e 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -459,6 +459,8 @@
     {"reboot,sys_ldo_ok,pmic,main", 227},
     {"reboot,sys_ldo_ok,pmic,sub", 228},
     {"reboot,smpl_timeout,pmic,main", 229},
+    {"reboot,ota,.*", 230},
+    {"reboot,periodic,.*", 231},
 };
 
 // Converts a string value representing the reason the system booted to an
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 7794c4b..56cac88 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -196,6 +196,7 @@
         "libfastbootshim",
         "libsnapshot_cow",
         "liblz4",
+        "libzstd",
         "libsnapshot_nobinder",
         "update_metadata-protos",
         "liburing",
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index cdcd036..f09616a 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -95,6 +95,8 @@
 using namespace std::string_literals;
 using namespace std::placeholders;
 
+#define FASTBOOT_INFO_VERSION 1
+
 static const char* serial = nullptr;
 
 static bool g_long_listing = false;
@@ -1663,20 +1665,6 @@
     return;
 }
 
-static bool IsNumber(const std::string& s) {
-    bool period = false;
-    for (size_t i = 0; i < s.length(); i++) {
-        if (!isdigit(s[i])) {
-            if (!period && s[i] == '.' && i != 0 && i != s.length() - 1) {
-                period = true;
-            } else {
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
 static bool IsIgnore(const std::vector<std::string>& command) {
     if (command[0][0] == '#') {
         return true;
@@ -1684,7 +1672,8 @@
     return false;
 }
 
-bool CheckFastbootInfoRequirements(const std::vector<std::string>& command) {
+bool CheckFastbootInfoRequirements(const std::vector<std::string>& command,
+                                   uint32_t host_tool_version) {
     if (command.size() != 2) {
         LOG(ERROR) << "unknown characters in version info in fastboot-info.txt -> "
                    << android::base::Join(command, " ");
@@ -1696,18 +1685,20 @@
         return false;
     }
 
-    if (!IsNumber(command[1])) {
-        LOG(ERROR) << "version number contains non-numeric values in fastboot-info.txt -> "
+    uint32_t fastboot_info_version;
+    if (!android::base::ParseUint(command[1], &fastboot_info_version)) {
+        LOG(ERROR) << "version number contains non-numeric characters in fastboot-info.txt -> "
                    << android::base::Join(command, " ");
         return false;
     }
 
     LOG(VERBOSE) << "Checking 'fastboot-info.txt version'";
-    if (command[1] < PLATFORM_TOOLS_VERSION) {
+    if (fastboot_info_version <= host_tool_version) {
         return true;
     }
+
     LOG(ERROR) << "fasboot-info.txt version: " << command[1]
-               << " not compatible with host tool version --> " << PLATFORM_TOOLS_VERSION;
+               << " not compatible with host tool version --> " << host_tool_version;
     return false;
 }
 
@@ -1721,7 +1712,7 @@
             continue;
         }
         if (command.size() > 1 && command[0] == "version") {
-            if (!CheckFastbootInfoRequirements(command)) {
+            if (!CheckFastbootInfoRequirements(command, FASTBOOT_INFO_VERSION)) {
                 return {};
             }
             continue;
@@ -1733,8 +1724,6 @@
         }
         auto task = ParseFastbootInfoLine(fp, command);
         if (!task) {
-            LOG(ERROR) << "Error when parsing fastboot-info.txt, falling back on Hardcoded list: "
-                       << text;
             return {};
         }
         tasks.emplace_back(std::move(task));
@@ -1760,8 +1749,6 @@
 }
 
 std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp, std::ifstream& fs) {
-    if (!fs || fs.eof()) return {};
-
     std::string text;
     std::vector<std::string> file;
     // Get os_partitions that need to be resized
@@ -1792,13 +1779,17 @@
 
     std::string path = find_item_given_name("fastboot-info.txt");
     std::ifstream stream(path);
-    std::vector<std::unique_ptr<Task>> tasks = ParseFastbootInfo(fp_, stream);
-    if (tasks.empty()) {
+    if (!stream || stream.eof()) {
         LOG(VERBOSE) << "Flashing from hardcoded images. fastboot-info.txt is empty or does not "
                         "exist";
         HardcodedFlash();
         return;
     }
+
+    std::vector<std::unique_ptr<Task>> tasks = ParseFastbootInfo(fp_, stream);
+    if (tasks.empty()) {
+        LOG(FATAL) << "Invalid fastboot-info.txt file.";
+    }
     LOG(VERBOSE) << "Flashing from fastboot-info.txt";
     for (auto& task : tasks) {
         task->Run();
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index d6afb9e..80b77a0 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -133,7 +133,8 @@
 std::string get_current_slot();
 
 // Code for Parsing fastboot-info.txt
-bool CheckFastbootInfoRequirements(const std::vector<std::string>& command);
+bool CheckFastbootInfoRequirements(const std::vector<std::string>& command,
+                                   uint32_t host_tool_version);
 std::unique_ptr<FlashTask> ParseFlashCommand(const FlashingPlan* fp,
                                              const std::vector<std::string>& parts);
 std::unique_ptr<RebootTask> ParseRebootCommand(const FlashingPlan* fp,
diff --git a/fastboot/task_test.cpp b/fastboot/task_test.cpp
index 6fc2056..b4e139b 100644
--- a/fastboot/task_test.cpp
+++ b/fastboot/task_test.cpp
@@ -89,20 +89,27 @@
 }
 
 TEST_F(ParseTest, VersionCheckCorrect) {
-    std::vector<std::string> correct_versions = {
-            "version 1.0",
-            "version 22.00",
-    };
+    std::vector<std::string> correct_versions = {"version 1", "version 22", "version 5",
+                                                 "version 17"};
 
-    std::vector<std::string> bad_versions = {"version",        "version .01", "version x1",
-                                             "version 1.0.1",  "version 1.",  "s 1.0",
-                                             "version 1.0 2.0"};
+    std::vector<std::string> bad_versions = {"version",         "version .01",    "version x1",
+                                             "version 1.0.1",   "version 1.",     "s 1.0",
+                                             "version 1.0 2.0", "version 100.00", "version 1 2"};
 
     for (auto& version : correct_versions) {
-        ASSERT_TRUE(CheckFastbootInfoRequirements(android::base::Split(version, " "))) << version;
+        ASSERT_TRUE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 26))
+                << version;
     }
+
+    // returning False for failing version check
+    for (auto& version : correct_versions) {
+        ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 0))
+                << version;
+    }
+    // returning False for bad format
     for (auto& version : bad_versions) {
-        ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "))) << version;
+        ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 100))
+                << version;
     }
 }
 
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 6349c20..ef436e5 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -1083,7 +1083,7 @@
         return 0;
     }
 
-    auto ideal_size = std::min(super_info.size, (uint64_t(s.f_frsize) * s.f_bfree) / 2);
+    auto ideal_size = std::min(super_info.size, uint64_t(s.f_frsize * s.f_bfree * 0.85));
 
     // Align up to the filesystem block size.
     if (auto remainder = ideal_size % s.f_bsize; remainder > 0) {
diff --git a/fs_mgr/libdm/dm_table.cpp b/fs_mgr/libdm/dm_table.cpp
index efe03ab..b546995 100644
--- a/fs_mgr/libdm/dm_table.cpp
+++ b/fs_mgr/libdm/dm_table.cpp
@@ -38,11 +38,11 @@
 bool DmTable::valid() const {
     if (targets_.empty()) {
         LOG(ERROR) << "Device-mapper table must have at least one target.";
-        return "";
+        return false;
     }
     if (targets_[0]->start() != 0) {
         LOG(ERROR) << "Device-mapper table must start at logical sector 0.";
-        return "";
+        return false;
     }
     return true;
 }
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 3dd1f1a..d3bd904 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -163,6 +163,7 @@
         "libbrotli",
         "libz",
         "liblz4",
+        "libzstd",
     ],
     export_include_dirs: ["include"],
 }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 69079cb..c3ca00a 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -28,9 +28,6 @@
 
 static constexpr uint32_t kCowVersionManifest = 2;
 
-static constexpr size_t BLOCK_SZ = 4096;
-static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
-
 // This header appears as the first sequence of bytes in the COW. All fields
 // in the layout are little-endian encoded. The on-disk layout is:
 //
@@ -160,7 +157,8 @@
     kCowCompressNone = 0,
     kCowCompressGz = 1,
     kCowCompressBrotli = 2,
-    kCowCompressLz4 = 3
+    kCowCompressLz4 = 3,
+    kCowCompressZstd = 4,
 };
 
 static constexpr uint8_t kCowReadAheadNotStarted = 0;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 73429e1..95a1270 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -63,7 +63,9 @@
     //
     // Partial reads are not possible unless |buffer_size| is less than the
     // operation block size.
-    virtual ssize_t ReadData(const CowOperation& op, void* buffer, size_t buffer_size,
+    //
+    // The operation pointer must derive from ICowOpIter::Get().
+    virtual ssize_t ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
                              size_t ignore_bytes = 0) = 0;
 };
 
@@ -77,7 +79,7 @@
     virtual bool AtEnd() = 0;
 
     // Read the current operation.
-    virtual const CowOperation& Get() = 0;
+    virtual const CowOperation* Get() = 0;
 
     // Advance to the next item.
     virtual void Next() = 0;
@@ -121,7 +123,7 @@
     std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress = false) override;
     std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress = false) override;
 
-    ssize_t ReadData(const CowOperation& op, void* buffer, size_t buffer_size,
+    ssize_t ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
                      size_t ignore_bytes = 0) override;
 
     CowHeader& GetHeader() override { return header_; }
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 24c91a8..f45d4ed 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -214,29 +214,6 @@
 // Get partition size from update package metadata.
 uint64_t GetSize(PartitionUpdate* partition_update);
 
-// Util class for test cases on low space scenario. These tests assumes image manager
-// uses /data as backup device.
-class LowSpaceUserdata {
-  public:
-    // Set the maximum free space allowed for this test. If /userdata has more space than the given
-    // number, a file is allocated to consume space.
-    AssertionResult Init(uint64_t max_free_space);
-
-    uint64_t free_space() const;
-    uint64_t available_space() const;
-    uint64_t bsize() const;
-
-  private:
-    AssertionResult ReadUserdataStats();
-
-    static constexpr const char* kUserDataDevice = "/data";
-    std::unique_ptr<TemporaryFile> big_file_;
-    bool initialized_ = false;
-    uint64_t free_space_ = 0;
-    uint64_t available_space_ = 0;
-    uint64_t bsize_ = 0;
-};
-
 bool IsVirtualAbEnabled();
 
 #define SKIP_IF_NON_VIRTUAL_AB()                                                        \
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
index 7d1b7ba..edc9d65 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
@@ -46,7 +46,7 @@
 };
 
 // Helper to check read sizes.
-static inline bool ReadData(CowReader& reader, const CowOperation& op, void* buffer, size_t size) {
+static inline bool ReadData(CowReader& reader, const CowOperation* op, void* buffer, size_t size) {
     return reader.ReadData(op, buffer, size) == size;
 }
 
@@ -80,7 +80,7 @@
 
     size_t i = 0;
     while (!iter->AtEnd()) {
-        auto op = &iter->Get();
+        auto op = iter->Get();
         ASSERT_EQ(op->type, kCowCopyOp);
         ASSERT_EQ(op->compression, kCowCompressNone);
         ASSERT_EQ(op->data_length, 0);
@@ -126,7 +126,7 @@
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
     ASSERT_FALSE(iter->AtEnd());
-    auto op = &iter->Get();
+    auto op = iter->Get();
 
     ASSERT_EQ(op->type, kCowCopyOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -138,18 +138,18 @@
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowReplaceOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
     ASSERT_EQ(op->data_length, 4096);
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
 
     // Note: the zero operation gets split into two blocks.
     ASSERT_EQ(op->type, kCowZeroOp);
@@ -160,7 +160,7 @@
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowZeroOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -205,7 +205,7 @@
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
     ASSERT_FALSE(iter->AtEnd());
-    auto op = &iter->Get();
+    auto op = iter->Get();
 
     ASSERT_EQ(op->type, kCowCopyOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -217,19 +217,19 @@
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowXorOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
     ASSERT_EQ(op->data_length, 4096);
     ASSERT_EQ(op->new_block, 50);
     ASSERT_EQ(op->source, 98314);  // 4096 * 24 + 10
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
 
     // Note: the zero operation gets split into two blocks.
     ASSERT_EQ(op->type, kCowZeroOp);
@@ -240,7 +240,7 @@
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowZeroOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -274,7 +274,7 @@
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
     ASSERT_FALSE(iter->AtEnd());
-    auto op = &iter->Get();
+    auto op = iter->Get();
 
     std::string sink(data.size(), '\0');
 
@@ -282,7 +282,7 @@
     ASSERT_EQ(op->compression, kCowCompressGz);
     ASSERT_EQ(op->data_length, 56);  // compressed!
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
     iter->Next();
@@ -329,14 +329,14 @@
 
     int total_blocks = 0;
     while (!iter->AtEnd()) {
-        auto op = &iter->Get();
+        auto op = iter->Get();
 
         if (op->type == kCowXorOp) {
             total_blocks += 1;
             std::string sink(xor_data.size(), '\0');
             ASSERT_EQ(op->new_block, 50);
             ASSERT_EQ(op->source, 98314);  // 4096 * 24 + 10
-            ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+            ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
             ASSERT_EQ(sink, xor_data);
         }
 
@@ -345,19 +345,19 @@
             if (op->new_block == 100) {
                 data.resize(options.block_size);
                 std::string sink(data.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink.size(), data.size());
                 ASSERT_EQ(sink, data);
             }
             if (op->new_block == 6000) {
                 data2.resize(options.block_size);
                 std::string sink(data2.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink, data2);
             }
             if (op->new_block == 9000) {
                 std::string sink(data3.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink, data3);
             }
         }
@@ -403,25 +403,25 @@
 
     int total_blocks = 0;
     while (!iter->AtEnd()) {
-        auto op = &iter->Get();
+        auto op = iter->Get();
 
         if (op->type == kCowReplaceOp) {
             total_blocks += 1;
             if (op->new_block == 50) {
                 data.resize(options.block_size);
                 std::string sink(data.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink, data);
             }
             if (op->new_block == 3000) {
                 data2.resize(options.block_size);
                 std::string sink(data2.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink, data2);
             }
             if (op->new_block == 5000) {
                 std::string sink(data3.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink, data3);
             }
         }
@@ -519,7 +519,7 @@
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
     ASSERT_FALSE(iter->AtEnd());
-    auto op = &iter->Get();
+    auto op = iter->Get();
 
     std::string sink(data.size(), '\0');
 
@@ -527,30 +527,30 @@
     ASSERT_EQ(op->compression, kCowCompressGz);
     ASSERT_EQ(op->data_length, 56);  // compressed!
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
 
     sink = {};
     sink.resize(data2.size(), '\0');
     ASSERT_EQ(op->compression, kCowCompressGz);
     ASSERT_EQ(op->data_length, 41);  // compressed!
     ASSERT_EQ(op->new_block, 51);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data2);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowClusterOp);
 
@@ -585,11 +585,11 @@
 
     std::string sink(options.block_size, '\0');
 
-    auto op = &iter->Get();
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
     ASSERT_EQ(op->compression, kCowCompressGz);
     ASSERT_EQ(op->new_block, 51);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
 }
 
 TEST_F(CowTest, GetSize) {
@@ -659,9 +659,9 @@
     ASSERT_NE(iter, nullptr);
 
     ASSERT_FALSE(iter->AtEnd());
-    auto op = &iter->Get();
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
     iter->Next();
@@ -669,16 +669,16 @@
     sink.resize(data2.size(), '\0');
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 3);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data2);
 
     iter->Next();
@@ -722,14 +722,14 @@
     ASSERT_NE(iter, nullptr);
 
     ASSERT_FALSE(iter->AtEnd());
-    auto op = &iter->Get();
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
@@ -780,7 +780,7 @@
     ASSERT_NE(iter, nullptr);
 
     ASSERT_FALSE(iter->AtEnd());
-    auto op = &iter->Get();
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 5);
 
@@ -831,9 +831,9 @@
     ASSERT_NE(iter, nullptr);
 
     ASSERT_FALSE(iter->AtEnd());
-    auto op = &iter->Get();
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(0, options.block_size));
 
     iter->Next();
@@ -841,33 +841,33 @@
     sink.resize(options.block_size, '\0');
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(options.block_size, 2 * options.block_size));
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 4);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 5);
 
@@ -912,59 +912,59 @@
     ASSERT_NE(iter, nullptr);
 
     ASSERT_FALSE(iter->AtEnd());
-    auto op = &iter->Get();
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(0, options.block_size));
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 4);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 5);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowCopyOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 6);
 
@@ -1011,22 +1011,22 @@
     ASSERT_NE(iter, nullptr);
 
     ASSERT_FALSE(iter->AtEnd());
-    auto op = &iter->Get();
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 50);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data2);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
-    op = &iter->Get();
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
@@ -1066,7 +1066,7 @@
     return AssertionSuccess();
 }
 
-AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op,
+AssertionResult CompareDataBlock(CowReader* reader, const CowOperation* op,
                                  const std::string& data) {
     const auto& header = reader->GetHeader();
 
@@ -1124,12 +1124,12 @@
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
 
-        if (op.type == kCowReplaceOp) {
+        if (op->type == kCowReplaceOp) {
             num_replace++;
 
-            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_EQ(op->new_block, num_replace);
             ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
-        } else if (op.type == kCowClusterOp) {
+        } else if (op->type == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1185,12 +1185,12 @@
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
 
-        if (op.type == kCowReplaceOp) {
+        if (op->type == kCowReplaceOp) {
             num_replace++;
 
-            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_EQ(op->new_block, num_replace);
             ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
-        } else if (op.type == kCowClusterOp) {
+        } else if (op->type == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1236,12 +1236,12 @@
 
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
-        if (op.type == kCowReplaceOp) {
+        if (op->type == kCowReplaceOp) {
             num_replace++;
 
-            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_EQ(op->new_block, num_replace);
             ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
-        } else if (op.type == kCowClusterOp) {
+        } else if (op->type == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1278,7 +1278,7 @@
         ASSERT_TRUE(!iter->AtEnd());
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, seq_len - i);
+        ASSERT_EQ(op->new_block, seq_len - i);
 
         iter->Next();
     }
@@ -1345,7 +1345,7 @@
         ASSERT_FALSE(iter->AtEnd());
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, expected_block);
+        ASSERT_EQ(op->new_block, expected_block);
 
         iter->Next();
         expected_block--;
@@ -1395,7 +1395,7 @@
     while (!iter->AtEnd() && expected_new_block != revMergeOpSequence.end()) {
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, *expected_new_block);
+        ASSERT_EQ(op->new_block, *expected_new_block);
 
         iter->Next();
         expected_new_block++;
@@ -1444,7 +1444,7 @@
     while (!iter->AtEnd() && expected_new_block != revMergeOpSequence.end()) {
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, *expected_new_block);
+        ASSERT_EQ(op->new_block, *expected_new_block);
 
         iter->Next();
         expected_new_block++;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
index d06c904..a4a0ad6 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_compress.cpp
@@ -29,6 +29,7 @@
 #include <libsnapshot/cow_writer.h>
 #include <lz4.h>
 #include <zlib.h>
+#include <zstd.h>
 
 namespace android {
 namespace snapshot {
@@ -40,6 +41,8 @@
         return {kCowCompressBrotli};
     } else if (name == "lz4") {
         return {kCowCompressLz4};
+    } else if (name == "zstd") {
+        return {kCowCompressZstd};
     } else if (name == "none" || name.empty()) {
         return {kCowCompressNone};
     } else {
@@ -112,6 +115,23 @@
             }
             return buffer;
         }
+        case kCowCompressZstd: {
+            std::basic_string<uint8_t> buffer(ZSTD_compressBound(length), '\0');
+            const auto compressed_size =
+                    ZSTD_compress(buffer.data(), buffer.size(), data, length, 0);
+            if (compressed_size <= 0) {
+                LOG(ERROR) << "ZSTD compression failed " << compressed_size;
+                return {};
+            }
+            // Don't run compression if the compressed output is larger
+            if (compressed_size >= length) {
+                buffer.resize(length);
+                memcpy(buffer.data(), data, length);
+            } else {
+                buffer.resize(compressed_size);
+            }
+            return buffer;
+        }
         default:
             LOG(ERROR) << "unhandled compression type: " << compression;
             break;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
index 3d34413..da90cc0 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
@@ -17,12 +17,15 @@
 #include "cow_decompress.h"
 
 #include <array>
+#include <cstring>
 #include <utility>
+#include <vector>
 
 #include <android-base/logging.h>
 #include <brotli/decode.h>
 #include <lz4.h>
 #include <zlib.h>
+#include <zstd.h>
 
 namespace android {
 namespace snapshot {
@@ -336,9 +339,56 @@
     }
 };
 
+class ZstdDecompressor final : public IDecompressor {
+  public:
+    ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size,
+                       size_t ignore_bytes = 0) override {
+        if (buffer_size < decompressed_size - ignore_bytes) {
+            LOG(INFO) << "buffer size " << buffer_size
+                      << " is not large enough to hold decompressed data. Decompressed size "
+                      << decompressed_size << ", ignore_bytes " << ignore_bytes;
+            return -1;
+        }
+        if (ignore_bytes == 0) {
+            if (!Decompress(buffer, decompressed_size)) {
+                return -1;
+            }
+            return decompressed_size;
+        }
+        std::vector<unsigned char> ignore_buf(decompressed_size);
+        if (!Decompress(buffer, decompressed_size)) {
+            return -1;
+        }
+        memcpy(buffer, ignore_buf.data() + ignore_bytes, buffer_size);
+        return decompressed_size;
+    }
+    bool Decompress(void* output_buffer, const size_t output_size) {
+        std::string input_buffer;
+        input_buffer.resize(stream_->Size());
+        size_t bytes_read = stream_->Read(input_buffer.data(), input_buffer.size());
+        if (bytes_read != input_buffer.size()) {
+            LOG(ERROR) << "Failed to read all input at once. Expected: " << input_buffer.size()
+                       << " actual: " << bytes_read;
+            return false;
+        }
+        const auto bytes_decompressed = ZSTD_decompress(output_buffer, output_size,
+                                                        input_buffer.data(), input_buffer.size());
+        if (bytes_decompressed != output_size) {
+            LOG(ERROR) << "Failed to decompress ZSTD block, expected output size: " << output_size
+                       << ", actual: " << bytes_decompressed;
+            return false;
+        }
+        return true;
+    }
+};
+
 std::unique_ptr<IDecompressor> IDecompressor::Lz4() {
     return std::make_unique<Lz4Decompressor>();
 }
 
+std::unique_ptr<IDecompressor> IDecompressor::Zstd() {
+    return std::make_unique<ZstdDecompressor>();
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
index 9e83f3c..52b3d76 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
@@ -47,6 +47,7 @@
     static std::unique_ptr<IDecompressor> Gz();
     static std::unique_ptr<IDecompressor> Brotli();
     static std::unique_ptr<IDecompressor> Lz4();
+    static std::unique_ptr<IDecompressor> Zstd();
 
     static std::unique_ptr<IDecompressor> FromString(std::string_view compressor);
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
index 94c4109..2157d0f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
@@ -57,8 +57,6 @@
     os << "data_length:" << op.data_length << ",\t";
     os << "new_block:" << op.new_block << ",\t";
     os << "source:" << op.source;
-    if (op.type == kCowXorOp)
-        os << " (block:" << op.source / BLOCK_SZ << " offset:" << op.source % BLOCK_SZ << ")";
     os << ")";
     return os;
 }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 893d95f..6749cf1 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -30,6 +30,7 @@
 #include <zlib.h>
 
 #include "cow_decompress.h"
+#include "libsnapshot/cow_format.h"
 
 namespace android {
 namespace snapshot {
@@ -508,37 +509,37 @@
 
 bool CowReader::VerifyMergeOps() {
     auto itr = GetMergeOpIter(true);
-    std::unordered_map<uint64_t, CowOperation> overwritten_blocks;
+    std::unordered_map<uint64_t, const CowOperation*> overwritten_blocks;
     while (!itr->AtEnd()) {
-        CowOperation op = itr->Get();
+        const auto& op = itr->Get();
         uint64_t block;
         bool offset;
-        if (op.type == kCowCopyOp) {
-            block = op.source;
+        if (op->type == kCowCopyOp) {
+            block = op->source;
             offset = false;
-        } else if (op.type == kCowXorOp) {
-            block = op.source / BLOCK_SZ;
-            offset = (op.source % BLOCK_SZ) != 0;
+        } else if (op->type == kCowXorOp) {
+            block = op->source / header_.block_size;
+            offset = (op->source % header_.block_size) != 0;
         } else {
             itr->Next();
             continue;
         }
 
-        CowOperation* overwrite = nullptr;
+        const CowOperation* overwrite = nullptr;
         if (overwritten_blocks.count(block)) {
-            overwrite = &overwritten_blocks[block];
+            overwrite = overwritten_blocks[block];
             LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
                        << op << "\noverwritten by previously merged op:\n"
                        << *overwrite;
         }
         if (offset && overwritten_blocks.count(block + 1)) {
-            overwrite = &overwritten_blocks[block + 1];
+            overwrite = overwritten_blocks[block + 1];
             LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
                        << op << "\noverwritten by previously merged op:\n"
                        << *overwrite;
         }
         if (overwrite != nullptr) return false;
-        overwritten_blocks[op.new_block] = op;
+        overwritten_blocks[op->new_block] = op;
         itr->Next();
     }
     return true;
@@ -561,7 +562,7 @@
     CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops, uint64_t start);
 
     bool AtEnd() override;
-    const CowOperation& Get() override;
+    const CowOperation* Get() override;
     void Next() override;
 
     void Prev() override;
@@ -595,9 +596,9 @@
     op_iter_++;
 }
 
-const CowOperation& CowOpIter::Get() {
+const CowOperation* CowOpIter::Get() {
     CHECK(!AtEnd());
-    return (*op_iter_);
+    return &(*op_iter_);
 }
 
 class CowRevMergeOpIter final : public ICowOpIter {
@@ -606,7 +607,7 @@
                                std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
 
     bool AtEnd() override;
-    const CowOperation& Get() override;
+    const CowOperation* Get() override;
     void Next() override;
 
     void Prev() override;
@@ -625,7 +626,7 @@
                             std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
 
     bool AtEnd() override;
-    const CowOperation& Get() override;
+    const CowOperation* Get() override;
     void Next() override;
 
     void Prev() override;
@@ -664,9 +665,9 @@
     block_iter_++;
 }
 
-const CowOperation& CowMergeOpIter::Get() {
+const CowOperation* CowMergeOpIter::Get() {
     CHECK(!AtEnd());
-    return ops_->data()[*block_iter_];
+    return &ops_->data()[*block_iter_];
 }
 
 CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
@@ -696,9 +697,9 @@
     block_riter_++;
 }
 
-const CowOperation& CowRevMergeOpIter::Get() {
+const CowOperation* CowRevMergeOpIter::Get() {
     CHECK(!AtEnd());
-    return ops_->data()[*block_riter_];
+    return &ops_->data()[*block_riter_];
 }
 
 std::unique_ptr<ICowOpIter> CowReader::GetOpIter(bool merge_progress) {
@@ -765,10 +766,10 @@
     size_t remaining_;
 };
 
-ssize_t CowReader::ReadData(const CowOperation& op, void* buffer, size_t buffer_size,
+ssize_t CowReader::ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
                             size_t ignore_bytes) {
     std::unique_ptr<IDecompressor> decompressor;
-    switch (op.compression) {
+    switch (op->compression) {
         case kCowCompressNone:
             break;
         case kCowCompressGz:
@@ -777,29 +778,34 @@
         case kCowCompressBrotli:
             decompressor = IDecompressor::Brotli();
             break;
+        case kCowCompressZstd:
+            if (header_.block_size != op->data_length) {
+                decompressor = IDecompressor::Zstd();
+            }
+            break;
         case kCowCompressLz4:
-            if (header_.block_size != op.data_length) {
+            if (header_.block_size != op->data_length) {
                 decompressor = IDecompressor::Lz4();
             }
             break;
         default:
-            LOG(ERROR) << "Unknown compression type: " << op.compression;
+            LOG(ERROR) << "Unknown compression type: " << op->compression;
             return -1;
     }
 
     uint64_t offset;
-    if (op.type == kCowXorOp) {
-        offset = data_loc_->at(op.new_block);
+    if (op->type == kCowXorOp) {
+        offset = data_loc_->at(op->new_block);
     } else {
-        offset = op.source;
+        offset = op->source;
     }
 
     if (!decompressor) {
-        CowDataStream stream(this, offset + ignore_bytes, op.data_length - ignore_bytes);
+        CowDataStream stream(this, offset + ignore_bytes, op->data_length - ignore_bytes);
         return stream.ReadFully(buffer, buffer_size);
     }
 
-    CowDataStream stream(this, offset, op.data_length);
+    CowDataStream stream(this, offset, op->data_length);
     decompressor->set_stream(&stream);
     return decompressor->Decompress(buffer, buffer_size, header_.block_size, ignore_bytes);
 }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
index cb20c6f..0e18979 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
@@ -407,7 +407,7 @@
     auto iter = reader->GetOpIter();
 
     while (!iter->AtEnd()) {
-        AddOperation(iter->Get());
+        AddOperation(*iter->Get());
         iter->Next();
     }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
index 4090162..917cec4 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
@@ -64,19 +64,19 @@
     bool include_merged;
 };
 
-static void ShowBad(CowReader& reader, const struct CowOperation& op) {
+static void ShowBad(CowReader& reader, const struct CowOperation* op) {
     size_t count;
-    auto buffer = std::make_unique<uint8_t[]>(op.data_length);
+    auto buffer = std::make_unique<uint8_t[]>(op->data_length);
 
-    if (!reader.GetRawBytes(op.source, buffer.get(), op.data_length, &count)) {
+    if (!reader.GetRawBytes(op->source, buffer.get(), op->data_length, &count)) {
         std::cerr << "Failed to read at all!\n";
     } else {
         std::cout << "The Block data is:\n";
-        for (int i = 0; i < op.data_length; i++) {
+        for (int i = 0; i < op->data_length; i++) {
             std::cout << std::hex << (int)buffer[i];
         }
         std::cout << std::dec << "\n\n";
-        if (op.data_length >= sizeof(CowOperation)) {
+        if (op->data_length >= sizeof(CowOperation)) {
             std::cout << "The start, as an op, would be " << *(CowOperation*)buffer.get() << "\n";
         }
     }
@@ -145,29 +145,29 @@
     bool success = true;
     uint64_t xor_ops = 0, copy_ops = 0, replace_ops = 0, zero_ops = 0;
     while (!iter->AtEnd()) {
-        const CowOperation& op = iter->Get();
+        const CowOperation* op = iter->Get();
 
-        if (!opt.silent && opt.show_ops) std::cout << op << "\n";
+        if (!opt.silent && opt.show_ops) std::cout << *op << "\n";
 
-        if (opt.decompress && op.type == kCowReplaceOp && op.compression != kCowCompressNone) {
+        if (opt.decompress && op->type == kCowReplaceOp && op->compression != kCowCompressNone) {
             if (reader.ReadData(op, buffer.data(), buffer.size()) < 0) {
-                std::cerr << "Failed to decompress for :" << op << "\n";
+                std::cerr << "Failed to decompress for :" << *op << "\n";
                 success = false;
                 if (opt.show_bad) ShowBad(reader, op);
             }
         }
 
-        if (op.type == kCowSequenceOp && opt.show_seq) {
+        if (op->type == kCowSequenceOp && opt.show_seq) {
             size_t read;
             std::vector<uint32_t> merge_op_blocks;
-            size_t seq_len = op.data_length / sizeof(uint32_t);
+            size_t seq_len = op->data_length / sizeof(uint32_t);
             merge_op_blocks.resize(seq_len);
-            if (!reader.GetRawBytes(op.source, merge_op_blocks.data(), op.data_length, &read)) {
+            if (!reader.GetRawBytes(op->source, merge_op_blocks.data(), op->data_length, &read)) {
                 PLOG(ERROR) << "Failed to read sequence op!";
                 return false;
             }
             if (!opt.silent) {
-                std::cout << "Sequence for " << op << " is :\n";
+                std::cout << "Sequence for " << *op << " is :\n";
                 for (size_t i = 0; i < seq_len; i++) {
                     std::cout << std::setfill('0') << std::setw(6) << merge_op_blocks[i] << ", ";
                     if ((i + 1) % 10 == 0 || i + 1 == seq_len) std::cout << "\n";
@@ -175,13 +175,13 @@
             }
         }
 
-        if (op.type == kCowCopyOp) {
+        if (op->type == kCowCopyOp) {
             copy_ops++;
-        } else if (op.type == kCowReplaceOp) {
+        } else if (op->type == kCowReplaceOp) {
             replace_ops++;
-        } else if (op.type == kCowZeroOp) {
+        } else if (op->type == kCowZeroOp) {
             zero_ops++;
-        } else if (op.type == kCowXorOp) {
+        } else if (op->type == kCowXorOp) {
             xor_ops++;
         }
 
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 64637c2..2661482 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -3133,6 +3133,7 @@
     for (auto&& [name, status] : all_snapshot_status) {
         sum += status.cow_file_size();
     }
+    LOG(INFO) << "Calculated needed COW space: " << sum << " bytes";
     return Return::NoSpace(sum);
 }
 
@@ -3279,7 +3280,10 @@
 
     auto ret = CreateUpdateSnapshotsInternal(lock.get(), manifest, &cow_creator, &created_devices,
                                              &all_snapshot_status);
-    if (!ret.is_ok()) return ret;
+    if (!ret.is_ok()) {
+        LOG(ERROR) << "CreateUpdateSnapshotsInternal failed: " << ret.string();
+        return ret;
+    }
 
     auto exported_target_metadata = target_metadata->Export();
     if (exported_target_metadata == nullptr) {
@@ -3496,7 +3500,10 @@
         // Create the backing COW image if necessary.
         if (snapshot_status.cow_file_size() > 0) {
             auto ret = CreateCowImage(lock, name);
-            if (!ret.is_ok()) return AddRequiredSpace(ret, *all_snapshot_status);
+            if (!ret.is_ok()) {
+                LOG(ERROR) << "CreateCowImage failed: " << ret.string();
+                return AddRequiredSpace(ret, *all_snapshot_status);
+            }
         }
 
         LOG(INFO) << "Successfully created snapshot for " << name;
diff --git a/fs_mgr/libsnapshot/snapshot_reader.cpp b/fs_mgr/libsnapshot/snapshot_reader.cpp
index 48efae0..4e75ba7 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader.cpp
@@ -86,7 +86,7 @@
     // Populate the operation map.
     op_iter_ = cow_->GetOpIter();
     while (!op_iter_->AtEnd()) {
-        const CowOperation* op = &op_iter_->Get();
+        const CowOperation* op = op_iter_->Get();
         if (IsMetadataOp(*op)) {
             op_iter_->Next();
             continue;
@@ -205,7 +205,7 @@
     } else if (op->type == kCowZeroOp) {
         memset(buffer, 0, bytes_to_read);
     } else if (op->type == kCowReplaceOp) {
-        if (cow_->ReadData(*op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
+        if (cow_->ReadData(op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
             LOG(ERROR) << "CompressedSnapshotReader failed to read replace op";
             errno = EIO;
             return -1;
@@ -226,7 +226,7 @@
             return -1;
         }
 
-        if (cow_->ReadData(*op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
+        if (cow_->ReadData(op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
             LOG(ERROR) << "CompressedSnapshotReader failed to read xor op";
             errno = EIO;
             return -1;
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 1fbfaf7..22731e7 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -19,6 +19,7 @@
 #include <signal.h>
 #include <sys/file.h>
 #include <sys/stat.h>
+#include <sys/statvfs.h>
 #include <sys/types.h>
 
 #include <chrono>
@@ -2371,20 +2372,44 @@
             << "FinishedSnapshotWrites should detect overflow of CoW device.";
 }
 
-TEST_F(SnapshotUpdateTest, LowSpace) {
-    static constexpr auto kMaxFree = 10_MiB;
-    auto userdata = std::make_unique<LowSpaceUserdata>();
-    ASSERT_TRUE(userdata->Init(kMaxFree));
+// Get max file size and free space.
+std::pair<uint64_t, uint64_t> GetBigFileLimit() {
+    struct statvfs fs;
+    if (statvfs("/data", &fs) < 0) {
+        PLOG(ERROR) << "statfs failed";
+        return {0, 0};
+    }
 
-    // Grow all partitions to 10_MiB, total 30_MiB. This requires 30 MiB of CoW space. After
-    // using the empty space in super (< 1 MiB), it uses 30 MiB of /userdata space.
+    auto fs_limit = static_cast<uint64_t>(fs.f_blocks) * (fs.f_bsize - 1);
+    auto fs_free = static_cast<uint64_t>(fs.f_bfree) * fs.f_bsize;
+
+    LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free;
+
+    return {fs_limit, fs_free};
+}
+
+TEST_F(SnapshotUpdateTest, LowSpace) {
+    // To make the low space test more reliable, we force a large cow estimate.
+    // However legacy VAB ignores the COW estimate and uses InstallOperations
+    // to compute the exact size required for dm-snapshot. It's difficult to
+    // make this work reliably (we'd need to somehow fake an extremely large
+    // super partition, and we don't have that level of dependency injection).
+    //
+    // For now, just skip this test on legacy VAB.
+    if (!snapuserd_required_) {
+        GTEST_SKIP() << "Skipping test on legacy VAB";
+    }
+
+    auto fs = GetBigFileLimit();
+    ASSERT_NE(fs.first, 0);
+
     constexpr uint64_t partition_size = 10_MiB;
     SetSize(sys_, partition_size);
     SetSize(vnd_, partition_size);
     SetSize(prd_, partition_size);
-    sys_->set_estimate_cow_size(partition_size);
-    vnd_->set_estimate_cow_size(partition_size);
-    prd_->set_estimate_cow_size(partition_size);
+    sys_->set_estimate_cow_size(fs.first);
+    vnd_->set_estimate_cow_size(fs.first);
+    prd_->set_estimate_cow_size(fs.first);
 
     AddOperationForPartitions();
 
@@ -2393,8 +2418,12 @@
     auto res = sm->CreateUpdateSnapshots(manifest_);
     ASSERT_FALSE(res);
     ASSERT_EQ(Return::ErrorCode::NO_SPACE, res.error_code());
-    ASSERT_GE(res.required_size(), 14_MiB);
-    ASSERT_LT(res.required_size(), 40_MiB);
+
+    // It's hard to predict exactly how much free space is needed, since /data
+    // is writable and the test is not the only process running. Divide by two
+    // as a rough lower bound, and adjust this in the future as necessary.
+    auto expected_delta = fs.first - fs.second;
+    ASSERT_GE(res.required_size(), expected_delta / 2);
 }
 
 TEST_F(SnapshotUpdateTest, AddPartition) {
@@ -2776,6 +2805,7 @@
     void TearDown() override {
         RETURN_IF_NON_VIRTUAL_AB();
         CleanUp();
+        SnapshotTest::TearDown();
     }
     void CleanUp() {
         if (!image_manager_) {
@@ -2789,26 +2819,13 @@
 };
 
 TEST_F(ImageManagerTest, CreateImageNoSpace) {
-    bool at_least_one_failure = false;
-    for (uint64_t size = 1_MiB; size <= 512_MiB; size *= 2) {
-        auto userdata = std::make_unique<LowSpaceUserdata>();
-        ASSERT_TRUE(userdata->Init(size));
+    auto fs = GetBigFileLimit();
+    ASSERT_NE(fs.first, 0);
 
-        uint64_t to_allocate = userdata->free_space() + userdata->bsize();
-
-        auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
-                                                      IImageManager::CREATE_IMAGE_DEFAULT);
-        if (!res) {
-            at_least_one_failure = true;
-        } else {
-            ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string();
-        }
-
-        CleanUp();
-    }
-
-    ASSERT_TRUE(at_least_one_failure)
-            << "We should have failed to allocate at least one over-sized image";
+    auto res = image_manager_->CreateBackingImage(kImageName, fs.first,
+                                                  IImageManager::CREATE_IMAGE_DEFAULT);
+    ASSERT_FALSE(res);
+    ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string();
 }
 
 bool Mkdir(const std::string& path) {
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 1e03683..9fe567a 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -112,6 +112,7 @@
         "liblz4",
         "libext4_utils",
         "liburing",
+        "libzstd",
     ],
 
     header_libs: [
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index b6e00ea..efa43b7 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -391,7 +391,7 @@
     memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
 
     while (!cowop_rm_iter->AtEnd()) {
-        const CowOperation* cow_op = &cowop_rm_iter->Get();
+        const CowOperation* cow_op = cowop_rm_iter->Get();
         struct disk_exception* de =
                 reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
 
@@ -459,7 +459,7 @@
 
     while (!cowop_rm_iter->AtEnd()) {
         do {
-            const CowOperation* cow_op = &cowop_rm_iter->Get();
+            const CowOperation* cow_op = cowop_rm_iter->Get();
 
             // We have two cases specific cases:
             //
@@ -509,10 +509,8 @@
             // in the file.
             //===========================================================
             uint64_t block_source = cow_op->source;
-            uint64_t block_offset = 0;
             if (prev_id.has_value()) {
-                if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source) ||
-                    (block_offset > 0 && source_blocks.count(block_source + 1))) {
+                if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source)) {
                     break;
                 }
             }
@@ -520,9 +518,6 @@
             pending_ordered_ops -= 1;
             vec.push_back(cow_op);
             dest_blocks.insert(block_source);
-            if (block_offset > 0) {
-                dest_blocks.insert(block_source + 1);
-            }
             source_blocks.insert(cow_op->new_block);
             prev_id = cow_op->new_block;
             cowop_rm_iter->Next();
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index 01123f8..a32c2bf 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -173,16 +173,11 @@
 
 void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
     uint64_t source_block = cow_op->source;
-    uint64_t source_offset = 0;
-    if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block) ||
-        (source_offset > 0 && source_blocks_.count(source_block + 1))) {
+    if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block)) {
         overlap_ = true;
     }
 
     dest_blocks_.insert(source_block);
-    if (source_offset > 0) {
-        dest_blocks_.insert(source_block + 1);
-    }
     source_blocks_.insert(cow_op->new_block);
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index 31a80a3..922df34 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -100,7 +100,7 @@
         SNAP_LOG(ERROR) << "No space in buffer sink";
         return false;
     }
-    ssize_t rv = reader_->ReadData(*cow_op, buffer, BLOCK_SZ);
+    ssize_t rv = reader_->ReadData(cow_op, buffer, BLOCK_SZ);
     if (rv != BLOCK_SZ) {
         SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block
                         << ", return = " << rv;
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
index c592257..46952c4 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
@@ -41,6 +41,9 @@
  */
 static constexpr uint32_t SECTOR_SHIFT = 9;
 
+static constexpr size_t BLOCK_SZ = 4096;
+static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+
 typedef __u64 sector_t;
 typedef sector_t chunk_t;
 
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 9df8cf9..a519639 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -186,7 +186,7 @@
     size_t copy_ops = 0, replace_ops = 0, zero_ops = 0, xor_ops = 0;
 
     while (!cowop_iter->AtEnd()) {
-        const CowOperation* cow_op = &cowop_iter->Get();
+        const CowOperation* cow_op = cowop_iter->Get();
 
         if (cow_op->type == kCowCopyOp) {
             copy_ops += 1;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
index 3bc02d4..7858216 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
@@ -81,7 +81,7 @@
         SNAP_LOG(ERROR) << "ProcessReplaceOp failed to allocate buffer";
         return false;
     }
-    if (!reader_->ReadData(*cow_op, buffer, BLOCK_SZ)) {
+    if (!reader_->ReadData(cow_op, buffer, BLOCK_SZ)) {
         SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
         return false;
     }
@@ -139,7 +139,7 @@
                         << actual;
         return false;
     }
-    ssize_t size = reader_->ReadData(*cow_op, buffer, BLOCK_SZ);
+    ssize_t size = reader_->ReadData(cow_op, buffer, BLOCK_SZ);
     if (size != BLOCK_SZ) {
         SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block
                         << ", return value: " << size;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
index 4d91ff3..ce95b76 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -31,7 +31,7 @@
 
     do {
         if (!cowop_iter_->AtEnd() && num_ops) {
-            const CowOperation* cow_op = &cowop_iter_->Get();
+            const CowOperation* cow_op = cowop_iter_->Get();
             if (checkOrderedOp && !IsOrderedOp(*cow_op)) {
                 break;
             }
@@ -46,7 +46,7 @@
             nr_consecutive = 1;
 
             while (!cowop_iter_->AtEnd() && num_ops) {
-                const CowOperation* op = &cowop_iter_->Get();
+                const CowOperation* op = cowop_iter_->Get();
                 if (checkOrderedOp && !IsOrderedOp(*op)) {
                     break;
                 }
@@ -181,7 +181,7 @@
     SNAP_LOG(INFO) << "MergeOrderedOpsAsync started....";
 
     while (!cowop_iter_->AtEnd()) {
-        const CowOperation* cow_op = &cowop_iter_->Get();
+        const CowOperation* cow_op = cowop_iter_->Get();
         if (!IsOrderedOp(*cow_op)) {
             break;
         }
@@ -362,7 +362,7 @@
     SNAP_LOG(INFO) << "MergeOrderedOps started....";
 
     while (!cowop_iter_->AtEnd()) {
-        const CowOperation* cow_op = &cowop_iter_->Get();
+        const CowOperation* cow_op = cowop_iter_->Get();
         if (!IsOrderedOp(*cow_op)) {
             break;
         }
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 4aad7a6..17f1f0e 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -492,7 +492,7 @@
                                     << xor_op->new_block;
                     return false;
                 }
-                if (ssize_t rv = reader_->ReadData(*xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) {
+                if (ssize_t rv = reader_->ReadData(xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) {
                     SNAP_LOG(ERROR)
                             << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block
                             << ", return value: " << rv;
@@ -592,7 +592,7 @@
                     SNAP_LOG(ERROR) << "ReadAhead - failed to allocate buffer";
                     return false;
                 }
-                if (ssize_t rv = reader_->ReadData(*xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) {
+                if (ssize_t rv = reader_->ReadData(xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) {
                     SNAP_LOG(ERROR)
                             << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block
                             << ", return value: " << rv;
@@ -834,8 +834,7 @@
 }
 
 const CowOperation* ReadAhead::GetRAOpIter() {
-    const CowOperation* cow_op = &cowop_iter_->Get();
-    return cow_op;
+    return cowop_iter_->Get();
 }
 
 void ReadAhead::InitializeBuffer() {
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index 9f1d676..a224f6b 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -214,68 +214,6 @@
     return partition_update->mutable_new_partition_info()->size();
 }
 
-AssertionResult LowSpaceUserdata::Init(uint64_t max_free_space) {
-    auto res = ReadUserdataStats();
-    if (!res) return res;
-
-    // Try to fill up the disk as much as possible until free_space_ <= max_free_space.
-    big_file_ = std::make_unique<TemporaryFile>();
-    if (big_file_->fd == -1) {
-        return AssertionFailure() << strerror(errno);
-    }
-    if (!android::base::StartsWith(big_file_->path, kUserDataDevice)) {
-        return AssertionFailure() << "Temp file allocated to " << big_file_->path << ", not in "
-                                  << kUserDataDevice;
-    }
-    uint64_t next_consume = std::min(std::max(available_space_, max_free_space) - max_free_space,
-                                     (uint64_t)std::numeric_limits<off_t>::max());
-    off_t allocated = 0;
-    while (next_consume > 0 && free_space_ > max_free_space) {
-        int status = fallocate(big_file_->fd, 0, allocated, next_consume);
-        if (status == -1 && errno == ENOSPC) {
-            next_consume /= 2;
-            continue;
-        }
-        if (status == -1) {
-            return AssertionFailure() << strerror(errno);
-        }
-        allocated += next_consume;
-
-        res = ReadUserdataStats();
-        if (!res) return res;
-    }
-
-    LOG(INFO) << allocated << " bytes allocated to " << big_file_->path;
-    initialized_ = true;
-    return AssertionSuccess();
-}
-
-AssertionResult LowSpaceUserdata::ReadUserdataStats() {
-    struct statvfs buf;
-    if (statvfs(kUserDataDevice, &buf) == -1) {
-        return AssertionFailure() << strerror(errno);
-    }
-    bsize_ = buf.f_bsize;
-    free_space_ = bsize_ * buf.f_bfree;
-    available_space_ = bsize_ * buf.f_bavail;
-    return AssertionSuccess();
-}
-
-uint64_t LowSpaceUserdata::free_space() const {
-    CHECK(initialized_);
-    return free_space_;
-}
-
-uint64_t LowSpaceUserdata::available_space() const {
-    CHECK(initialized_);
-    return available_space_;
-}
-
-uint64_t LowSpaceUserdata::bsize() const {
-    CHECK(initialized_);
-    return bsize_;
-}
-
 bool IsVirtualAbEnabled() {
     return android::base::GetBoolProperty("ro.virtual_ab.enabled", false);
 }
diff --git a/gatekeeperd/gatekeeperd.cpp b/gatekeeperd/gatekeeperd.cpp
index e5241b5..7987167 100644
--- a/gatekeeperd/gatekeeperd.cpp
+++ b/gatekeeperd/gatekeeperd.cpp
@@ -144,14 +144,22 @@
     }
 }
 
-uint32_t GateKeeperProxy::adjust_userId(uint32_t userId) {
+Status GateKeeperProxy::adjust_userId(uint32_t userId, uint32_t* hw_userId) {
     static constexpr uint32_t kGsiOffset = 1000000;
-    CHECK(userId < kGsiOffset);
-    CHECK((aidl_hw_device != nullptr) || (hw_device != nullptr));
-    if (is_running_gsi) {
-        return userId + kGsiOffset;
+    if (userId >= kGsiOffset) {
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT);
     }
-    return userId;
+
+    if ((aidl_hw_device == nullptr) && (hw_device == nullptr)) {
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+    }
+
+    if (is_running_gsi) {
+        *hw_userId = userId + kGsiOffset;
+        return Status::ok();
+    }
+    *hw_userId = userId;
+    return Status::ok();
 }
 
 #define GK_ERROR *gkResponse = GKResponse::error(), Status::ok()
@@ -201,7 +209,12 @@
     android::hardware::hidl_vec<uint8_t> newPwd;
     newPwd.setToExternal(const_cast<uint8_t*>(desiredPassword.data()), desiredPassword.size());
 
-    uint32_t hw_userId = adjust_userId(userId);
+    uint32_t hw_userId = 0;
+    Status result = adjust_userId(userId, &hw_userId);
+    if (!result.isOk()) {
+        return result;
+    }
+
     uint64_t secureUserId = 0;
     if (aidl_hw_device) {
         // AIDL gatekeeper service
@@ -300,7 +313,12 @@
         }
     }
 
-    uint32_t hw_userId = adjust_userId(userId);
+    uint32_t hw_userId = 0;
+    Status result = adjust_userId(userId, &hw_userId);
+    if (!result.isOk()) {
+        return result;
+    }
+
     android::hardware::hidl_vec<uint8_t> curPwdHandle;
     curPwdHandle.setToExternal(const_cast<uint8_t*>(enrolledPasswordHandle.data()),
                                enrolledPasswordHandle.size());
@@ -410,7 +428,12 @@
     }
     clear_sid(userId);
 
-    uint32_t hw_userId = adjust_userId(userId);
+    uint32_t hw_userId = 0;
+    Status result = adjust_userId(userId, &hw_userId);
+    if (!result.isOk()) {
+        return result;
+    }
+
     if (aidl_hw_device) {
         aidl_hw_device->deleteUser(hw_userId);
     } else if (hw_device) {
diff --git a/gatekeeperd/gatekeeperd.h b/gatekeeperd/gatekeeperd.h
index 29873da..b1f08c6 100644
--- a/gatekeeperd/gatekeeperd.h
+++ b/gatekeeperd/gatekeeperd.h
@@ -47,7 +47,7 @@
 
     // This should only be called on userIds being passed to the GateKeeper HAL. It ensures that
     // secure storage shared across a GSI image and a host image will not overlap.
-    uint32_t adjust_userId(uint32_t userId);
+    Status adjust_userId(uint32_t userId, uint32_t* hw_userId);
 
 #define GK_ERROR *gkResponse = GKResponse::error(), Status::ok()
 
diff --git a/init/Android.bp b/init/Android.bp
index 7b52903..41c7a95 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -169,6 +169,7 @@
         "libfsverity_init",
         "liblmkd_utils",
         "liblz4",
+        "libzstd",
         "libmini_keyctl_static",
         "libmodprobe",
         "libprocinfo",
@@ -370,6 +371,7 @@
         "libprotobuf-cpp-lite",
         "libsnapshot_cow",
         "liblz4",
+        "libzstd",
         "libsnapshot_init",
         "update_metadata-protos",
         "libprocinfo",
diff --git a/init/builtins.cpp b/init/builtins.cpp
index bc23972..585eca2 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -60,6 +60,8 @@
 #include <cutils/android_reboot.h>
 #include <fs_mgr.h>
 #include <fscrypt/fscrypt.h>
+#include <libdm/dm.h>
+#include <libdm/loop_control.h>
 #include <libgsi/libgsi.h>
 #include <logwrap/logwrap.h>
 #include <private/android_filesystem_config.h>
@@ -506,29 +508,29 @@
 
     if (android::base::StartsWith(source, "loop@")) {
         int mode = (flags & MS_RDONLY) ? O_RDONLY : O_RDWR;
-        unique_fd fd(TEMP_FAILURE_RETRY(open(source + 5, mode | O_CLOEXEC)));
-        if (fd < 0) return ErrnoError() << "open(" << source + 5 << ", " << mode << ") failed";
+        const char* file_path = source + strlen("loop@");
 
-        for (size_t n = 0;; n++) {
-            std::string tmp = android::base::StringPrintf("/dev/block/loop%zu", n);
-            unique_fd loop(TEMP_FAILURE_RETRY(open(tmp.c_str(), mode | O_CLOEXEC)));
-            if (loop < 0) return ErrnoError() << "open(" << tmp << ", " << mode << ") failed";
-
-            loop_info info;
-            /* if it is a blank loop device */
-            if (ioctl(loop.get(), LOOP_GET_STATUS, &info) < 0 && errno == ENXIO) {
-                /* if it becomes our loop device */
-                if (ioctl(loop.get(), LOOP_SET_FD, fd.get()) >= 0) {
-                    if (mount(tmp.c_str(), target, system, flags, options) < 0) {
-                        ioctl(loop.get(), LOOP_CLR_FD, 0);
-                        return ErrnoError() << "mount() failed";
-                    }
-                    return {};
-                }
-            }
+        // Open source file
+        if (wait) {
+            wait_for_file(file_path, kCommandRetryTimeout);
         }
 
-        return Error() << "out of loopback devices";
+        unique_fd fd(TEMP_FAILURE_RETRY(open(file_path, mode | O_CLOEXEC)));
+        if (fd < 0) {
+            return ErrnoError() << "open(" << file_path << ", " << mode << ") failed";
+        }
+
+        // Allocate loop device and attach it to file_path.
+        android::dm::LoopControl loop_control;
+        std::string loop_device;
+        if (!loop_control.Attach(fd.get(), 5s, &loop_device)) {
+            return ErrnoError() << "loop_control.Attach " << file_path << " failed";
+        }
+
+        if (mount(loop_device.c_str(), target, system, flags, options) < 0) {
+            loop_control.Detach(loop_device);
+            return ErrnoError() << "mount() failed";
+        }
     } else {
         if (wait)
             wait_for_file(source, kCommandRetryTimeout);
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 107e99a..bff80c5 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -58,6 +58,12 @@
 
 namespace {
 
+enum class BootMode {
+    NORMAL_MODE,
+    RECOVERY_MODE,
+    CHARGER_MODE,
+};
+
 void FreeRamdisk(DIR* dir, dev_t dev) {
     int dfd = dirfd(dir);
 
@@ -149,13 +155,27 @@
 }
 }  // namespace
 
-std::string GetModuleLoadList(bool recovery, const std::string& dir_path) {
-    auto module_load_file = "modules.load";
-    if (recovery) {
-        struct stat fileStat;
-        std::string recovery_load_path = dir_path + "/modules.load.recovery";
-        if (!stat(recovery_load_path.c_str(), &fileStat)) {
+std::string GetModuleLoadList(BootMode boot_mode, const std::string& dir_path) {
+    std::string module_load_file;
+
+    switch (boot_mode) {
+        case BootMode::NORMAL_MODE:
+            module_load_file = "modules.load";
+            break;
+        case BootMode::RECOVERY_MODE:
             module_load_file = "modules.load.recovery";
+            break;
+        case BootMode::CHARGER_MODE:
+            module_load_file = "modules.load.charger";
+            break;
+    }
+
+    if (module_load_file != "modules.load") {
+        struct stat fileStat;
+        std::string load_path = dir_path + "/" + module_load_file;
+        // Fall back to modules.load if the other files aren't accessible
+        if (stat(load_path.c_str(), &fileStat)) {
+            module_load_file = "modules.load";
         }
     }
 
@@ -163,7 +183,8 @@
 }
 
 #define MODULE_BASE_DIR "/lib/modules"
-bool LoadKernelModules(bool recovery, bool want_console, bool want_parallel, int& modules_loaded) {
+bool LoadKernelModules(BootMode boot_mode, bool want_console, bool want_parallel,
+                       int& modules_loaded) {
     struct utsname uts;
     if (uname(&uts)) {
         LOG(FATAL) << "Failed to get kernel version.";
@@ -203,7 +224,7 @@
     for (const auto& module_dir : module_dirs) {
         std::string dir_path = MODULE_BASE_DIR "/";
         dir_path.append(module_dir);
-        Modprobe m({dir_path}, GetModuleLoadList(recovery, dir_path));
+        Modprobe m({dir_path}, GetModuleLoadList(boot_mode, dir_path));
         bool retval = m.LoadListedModules(!want_console);
         modules_loaded = m.GetModuleCount();
         if (modules_loaded > 0) {
@@ -211,7 +232,7 @@
         }
     }
 
-    Modprobe m({MODULE_BASE_DIR}, GetModuleLoadList(recovery, MODULE_BASE_DIR));
+    Modprobe m({MODULE_BASE_DIR}, GetModuleLoadList(boot_mode, MODULE_BASE_DIR));
     bool retval = (want_parallel) ? m.LoadModulesParallel(std::thread::hardware_concurrency())
                                   : m.LoadListedModules(!want_console);
     modules_loaded = m.GetModuleCount();
@@ -221,6 +242,21 @@
     return true;
 }
 
+static bool IsChargerMode(const std::string& cmdline, const std::string& bootconfig) {
+    return bootconfig.find("androidboot.mode = \"charger\"") != std::string::npos ||
+            cmdline.find("androidboot.mode=charger") != std::string::npos;
+}
+
+static BootMode GetBootMode(const std::string& cmdline, const std::string& bootconfig)
+{
+    if (IsChargerMode(cmdline, bootconfig))
+        return BootMode::CHARGER_MODE;
+    else if (IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig))
+        return BootMode::RECOVERY_MODE;
+
+    return BootMode::NORMAL_MODE;
+}
+
 int FirstStageMain(int argc, char** argv) {
     if (REBOOT_BOOTLOADER_ON_PANIC) {
         InstallRebootSignalHandlers();
@@ -328,7 +364,8 @@
 
     boot_clock::time_point module_start_time = boot_clock::now();
     int module_count = 0;
-    if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console,
+    BootMode boot_mode = GetBootMode(cmdline, bootconfig);
+    if (!LoadKernelModules(boot_mode, want_console,
                            want_parallel, module_count)) {
         if (want_console != FirstStageConsoleParam::DISABLED) {
             LOG(ERROR) << "Failed to load kernel modules, starting console";
diff --git a/init/init.cpp b/init/init.cpp
index be1ebee..da63fdc 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -1043,6 +1043,12 @@
     SetProperty(gsi::kGsiBootedProp, is_running);
     auto is_installed = android::gsi::IsGsiInstalled() ? "1" : "0";
     SetProperty(gsi::kGsiInstalledProp, is_installed);
+    if (android::gsi::IsGsiRunning()) {
+        std::string dsu_slot;
+        if (android::gsi::GetActiveDsu(&dsu_slot)) {
+            SetProperty(gsi::kDsuSlotProp, dsu_slot);
+        }
+    }
 
     am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
     am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 79d79dd..f90a1bc 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -84,14 +84,12 @@
     { 00777, AID_ROOT,         AID_ROOT,         0, "sdcard" },
     { 00751, AID_ROOT,         AID_SDCARD_R,     0, "storage" },
     { 00750, AID_ROOT,         AID_SYSTEM,       0, "system/apex/com.android.tethering/bin/for-system" },
-    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system/apex/com.android.tethering.inprocess/bin/for-system" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/bin" },
     { 00755, AID_ROOT,         AID_ROOT,         0, "system/etc/ppp" },
     { 00755, AID_ROOT,         AID_SHELL,        0, "system/vendor" },
     { 00750, AID_ROOT,         AID_SHELL,        0, "system/xbin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/apex/*/bin" },
     { 00750, AID_ROOT,         AID_SYSTEM,       0, "system_ext/apex/com.android.tethering/bin/for-system" },
-    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/apex/*/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "vendor/bin" },
@@ -199,9 +197,7 @@
     // the following files have enhanced capabilities and ARE included
     // in user builds.
     { 06755, AID_CLAT,      AID_CLAT,      0, "system/apex/com.android.tethering/bin/for-system/clatd" },
-    { 06755, AID_CLAT,      AID_CLAT,      0, "system/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
     { 06755, AID_CLAT,      AID_CLAT,      0, "system_ext/apex/com.android.tethering/bin/for-system/clatd" },
-    { 06755, AID_CLAT,      AID_CLAT,      0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
     { 00700, AID_SYSTEM,    AID_SHELL,     CAP_MASK_LONG(CAP_BLOCK_SUSPEND),
                                               "system/bin/inputflinger" },
     { 00750, AID_ROOT,      AID_SHELL,     CAP_MASK_LONG(CAP_SETUID) |
diff --git a/storaged/Android.bp b/storaged/Android.bp
index 04f5d79..fe8c1f3 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -137,26 +137,36 @@
     path: "binder",
 }
 
-cc_fuzz {
-    name: "storaged_service_fuzzer",
+cc_defaults {
+    name: "storaged_service_fuzzer_defaults",
     defaults: [
         "storaged_defaults",
         "service_fuzzer_defaults",
+        "fuzzer_disable_leaks",
     ],
-    srcs: ["tests/fuzzers/storaged_service_fuzzer.cpp"],
     static_libs: [
         "libstoraged",
     ],
+    fuzz_config: {
+        cc: [
+            "dvander@google.com",
+        ],
+        triage_assignee: "waghpawan@google.com",
+    },
+}
+
+cc_fuzz {
+    name: "storaged_service_fuzzer",
+    defaults: [
+        "storaged_service_fuzzer_defaults",
+    ],
+    srcs: ["tests/fuzzers/storaged_service_fuzzer.cpp"],
 }
 
 cc_fuzz {
     name: "storaged_private_service_fuzzer",
     defaults: [
-        "storaged_defaults",
-        "service_fuzzer_defaults",
+        "storaged_service_fuzzer_defaults",
     ],
     srcs: ["tests/fuzzers/storaged_private_service_fuzzer.cpp"],
-    static_libs: [
-        "libstoraged",
-    ],
-}
\ No newline at end of file
+}
diff --git a/trusty/confirmationui/fuzz/Android.bp b/trusty/confirmationui/fuzz/Android.bp
index 4780943..96804bd 100644
--- a/trusty/confirmationui/fuzz/Android.bp
+++ b/trusty/confirmationui/fuzz/Android.bp
@@ -26,7 +26,8 @@
         "-DTRUSTY_APP_FILENAME=\"confirmationui.syms.elf\"",
     ],
     fuzz_config: {
-       cc: ["trong@google.com"],
+       cc: ["mikemcternan@google.com"],
+       componentid: 1290237,
     },
 
 }
@@ -40,7 +41,8 @@
         "libdmabufheap",
     ],
     fuzz_config: {
-       cc: ["trong@google.com"],
+       cc: ["mikemcternan@google.com"],
+       componentid: 1290237,
     },
 
     // The initial corpus for this fuzzer was derived by dumping messages from/to
diff --git a/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp b/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp
index e944167..6b8f90f 100644
--- a/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp
+++ b/trusty/keymaster/set_attestation_ids/set_attestation_ids.cpp
@@ -17,8 +17,10 @@
 #include <getopt.h>
 
 #include <string>
+#include <vector>
 
 #include <android-base/properties.h>
+#include <android-base/strings.h>
 #include <trusty_keymaster/ipc/trusty_keymaster_ipc.h>
 
 namespace {
@@ -34,14 +36,66 @@
         {"model", required_argument, nullptr, 'm'},
         {"imei", required_argument, nullptr, 'i'},
         {"meid", required_argument, nullptr, 'c'},
+        {"imei2", required_argument, nullptr, '2'},
         {0, 0, 0, 0},
 };
 
+std::string TELEPHONY_CMD_GET_IMEI = "cmd phone get-imei ";
+
+// Run a shell command and collect the output of it. If any error, set an empty string as the
+// output.
+std::string exec_command(const std::string& command) {
+    char buffer[128];
+    std::string result = "";
+
+    FILE* pipe = popen(command.c_str(), "r");
+    if (!pipe) {
+        fprintf(stderr, "popen('%s') failed\n", command.c_str());
+        return result;
+    }
+
+    while (!feof(pipe)) {
+        if (fgets(buffer, 128, pipe) != NULL) {
+            result += buffer;
+        }
+    }
+
+    pclose(pipe);
+    return result;
+}
+
+// Get IMEI using Telephony service shell command. If any error while executing the command
+// then empty string will be returned as output.
+std::string get_imei(int slot) {
+    std::string cmd = TELEPHONY_CMD_GET_IMEI + std::to_string(slot);
+    std::string output = exec_command(cmd);
+
+    if (output.empty()) {
+        fprintf(stderr, "Retrieve IMEI command ('%s') failed\n", cmd.c_str());
+        return "";
+    }
+
+    std::vector<std::string> out =
+            ::android::base::Tokenize(::android::base::Trim(output), "Device IMEI:");
+
+    if (out.size() != 1) {
+        fprintf(stderr, "Error parsing command ('%s') output '%s'\n", cmd.c_str(), output.c_str());
+        return "";
+    }
+
+    std::string imei = ::android::base::Trim(out[0]);
+    if (imei.compare("null") == 0) {
+        fprintf(stderr, "IMEI value from command ('%s') is null, skipping", cmd.c_str());
+        return "";
+    }
+    return imei;
+}
+
 std::string buf2string(const keymaster::Buffer& buf) {
     return std::string(reinterpret_cast<const char*>(buf.peek_read()), buf.available_read());
 }
 
-void print_usage(const char* prog, const keymaster::SetAttestationIdsRequest& req) {
+void print_usage(const char* prog, const keymaster::SetAttestationIdsKM3Request& req) {
     fprintf(stderr,
             "Usage: %s [options]\n"
             "\n"
@@ -55,34 +109,53 @@
             "  -m, --model <val>          set model (default '%s')\n"
             "  -i, --imei <val>           set IMEI (default '%s')\n"
             "  -c, --meid <val>           set MEID (default '%s')\n"
+            "  -2, --imei2 <val>          set second IMEI (default '%s')\n"
             "\n",
-            prog, buf2string(req.brand).c_str(), buf2string(req.device).c_str(),
-            buf2string(req.product).c_str(), buf2string(req.serial).c_str(),
-            buf2string(req.manufacturer).c_str(), buf2string(req.model).c_str(),
-            buf2string(req.imei).c_str(), buf2string(req.meid).c_str());
+            prog, buf2string(req.base.brand).c_str(), buf2string(req.base.device).c_str(),
+            buf2string(req.base.product).c_str(), buf2string(req.base.serial).c_str(),
+            buf2string(req.base.manufacturer).c_str(), buf2string(req.base.model).c_str(),
+            buf2string(req.base.imei).c_str(), buf2string(req.base.meid).c_str(),
+            buf2string(req.second_imei).c_str());
+}
+
+void set_to(keymaster::Buffer* buf, const std::string& value) {
+    if (!value.empty()) {
+        buf->Reinitialize(value.data(), value.size());
+    }
 }
 
 void set_from_prop(keymaster::Buffer* buf, const std::string& prop) {
     std::string prop_value = ::android::base::GetProperty(prop, /* default_value = */ "");
-    if (!prop_value.empty()) {
-        buf->Reinitialize(prop_value.data(), prop_value.size());
-    }
+    set_to(buf, prop_value);
 }
 
-void populate_ids(keymaster::SetAttestationIdsRequest* req) {
+void populate_base_ids(keymaster::SetAttestationIdsRequest* req) {
     set_from_prop(&req->brand, "ro.product.brand");
     set_from_prop(&req->device, "ro.product.device");
     set_from_prop(&req->product, "ro.product.name");
     set_from_prop(&req->serial, "ro.serialno");
     set_from_prop(&req->manufacturer, "ro.product.manufacturer");
     set_from_prop(&req->model, "ro.product.model");
+    std::string imei = get_imei(0);
+    set_to(&req->imei, imei);
+}
+
+void populate_ids(keymaster::SetAttestationIdsKM3Request* req) {
+    populate_base_ids(&req->base);
+
+    // - "What about IMEI?"
+    // - "You've already had it."
+    // - "We've had one, yes. What about second IMEI?"
+    // - "I don't think he knows about second IMEI, Pip."
+    std::string imei2 = get_imei(1);
+    set_to(&req->second_imei, imei2);
 }
 
 }  // namespace
 
 int main(int argc, char** argv) {
     // By default, set attestation IDs to the values in userspace properties.
-    keymaster::SetAttestationIdsRequest req(/* ver = */ 4);
+    keymaster::SetAttestationIdsKM3Request req(/* ver = */ 4);
     populate_ids(&req);
 
     while (true) {
@@ -94,28 +167,31 @@
 
         switch (c) {
             case 'b':
-                req.brand.Reinitialize(optarg, strlen(optarg));
+                req.base.brand.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'd':
-                req.device.Reinitialize(optarg, strlen(optarg));
+                req.base.device.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'p':
-                req.product.Reinitialize(optarg, strlen(optarg));
+                req.base.product.Reinitialize(optarg, strlen(optarg));
                 break;
             case 's':
-                req.serial.Reinitialize(optarg, strlen(optarg));
+                req.base.serial.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'M':
-                req.manufacturer.Reinitialize(optarg, strlen(optarg));
+                req.base.manufacturer.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'm':
-                req.model.Reinitialize(optarg, strlen(optarg));
+                req.base.model.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'i':
-                req.imei.Reinitialize(optarg, strlen(optarg));
+                req.base.imei.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'c':
-                req.meid.Reinitialize(optarg, strlen(optarg));
+                req.base.meid.Reinitialize(optarg, strlen(optarg));
+                break;
+            case '2':
+                req.second_imei.Reinitialize(optarg, strlen(optarg));
                 break;
             case 'h':
                 print_usage(argv[0], req);
@@ -144,19 +220,33 @@
            "  manufacturer: %s\n"
            "  model:        %s\n"
            "  IMEI:         %s\n"
-           "  MEID:         %s\n",
-           buf2string(req.brand).c_str(), buf2string(req.device).c_str(),
-           buf2string(req.product).c_str(), buf2string(req.serial).c_str(),
-           buf2string(req.manufacturer).c_str(), buf2string(req.model).c_str(),
-           buf2string(req.imei).c_str(), buf2string(req.meid).c_str());
+           "  MEID:         %s\n"
+           "  SECOND_IMEI:  %s\n\n",
+           buf2string(req.base.brand).c_str(), buf2string(req.base.device).c_str(),
+           buf2string(req.base.product).c_str(), buf2string(req.base.serial).c_str(),
+           buf2string(req.base.manufacturer).c_str(), buf2string(req.base.model).c_str(),
+           buf2string(req.base.imei).c_str(), buf2string(req.base.meid).c_str(),
+           buf2string(req.second_imei).c_str());
+    fflush(stdout);
 
     keymaster::EmptyKeymasterResponse rsp(/* ver = */ 4);
-    ret = trusty_keymaster_send(KM_SET_ATTESTATION_IDS, req, &rsp);
-    if (ret) {
-        fprintf(stderr, "SET_ATTESTATION_IDS failed: %d\n", ret);
-        trusty_keymaster_disconnect();
-        return EXIT_FAILURE;
+    const char* msg;
+    if (req.second_imei.available_read() == 0) {
+        // No SECOND_IMEI set, use base command.
+        ret = trusty_keymaster_send(KM_SET_ATTESTATION_IDS, req.base, &rsp);
+        msg = "SET_ATTESTATION_IDS";
+    } else {
+        // SECOND_IMEI is set, use updated command.
+        ret = trusty_keymaster_send(KM_SET_ATTESTATION_IDS_KM3, req, &rsp);
+        msg = "SET_ATTESTATION_IDS_KM3";
     }
+    trusty_keymaster_disconnect();
 
-    return EXIT_SUCCESS;
+    if (ret) {
+        fprintf(stderr, "%s failed: %d\n", msg, ret);
+        return EXIT_FAILURE;
+    } else {
+        printf("done\n");
+        return EXIT_SUCCESS;
+    }
 }