Merge "Add new fastbootd battery-soc variable" into main
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 6b6a982..0dc4e97 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -151,7 +151,8 @@
 }
 
 BootControlClient* FastbootDevice::boot1_1() const {
-    if (boot_control_hal_->GetVersion() >= android::hal::BootControlVersion::BOOTCTL_V1_1) {
+    if (boot_control_hal_ &&
+        boot_control_hal_->GetVersion() >= android::hal::BootControlVersion::BOOTCTL_V1_1) {
         return boot_control_hal_.get();
     }
     return nullptr;
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index b12e584..56b90b9 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -1414,21 +1414,6 @@
     }
 }
 
-bool is_retrofit_device(const ImageSource* source) {
-    // Does this device use dynamic partitions at all?
-    std::vector<char> contents;
-    if (!source->ReadFile("super_empty.img", &contents)) {
-        return false;
-    }
-    auto metadata = android::fs_mgr::ReadFromImageBlob(contents.data(), contents.size());
-    for (const auto& partition : metadata->partitions) {
-        if (partition.attributes & LP_PARTITION_ATTR_SLOT_SUFFIXED) {
-            return true;
-        }
-    }
-    return false;
-}
-
 // Fetch a partition from the device to a given fd. This is a wrapper over FetchToFd to fetch
 // the full image.
 static uint64_t fetch_partition(const std::string& partition, borrowed_fd fd,
@@ -1880,19 +1865,6 @@
 
     // Sync the super partition. This will reboot to userspace fastboot if needed.
     tasks.emplace_back(std::make_unique<UpdateSuperTask>(fp_));
-    for (const auto& [image, slot] : os_images_) {
-        // Retrofit devices have two super partitions, named super_a and super_b.
-        // On these devices, secondary slots must be flashed as physical
-        // partitions (otherwise they would not mount on first boot). To enforce
-        // this, we delete any logical partitions for the "other" slot.
-        if (is_retrofit_device(fp_->source.get())) {
-            std::string partition_name = image->part_name + "_" + slot;
-            if (image->IsSecondary() &&
-                should_flash_in_userspace(fp_->source.get(), partition_name)) {
-                tasks.emplace_back(std::make_unique<DeleteTask>(fp_, partition_name));
-            }
-        }
-    }
 
     AddFlashTasks(os_images_, tasks);
 
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index cd7bc2d..2c40890 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -188,7 +188,6 @@
 std::vector<SparsePtr> resparse_file(sparse_file* s, int64_t max_size);
 
 bool supports_AB(fastboot::IFastBootDriver* fb);
-bool is_retrofit_device(const ImageSource* source);
 bool is_logical(const std::string& partition);
 void fb_perform_format(const std::string& partition, int skip_if_not_supported,
                        const std::string& type_override, const std::string& size_override,
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 0131f73..6fad662 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -201,6 +201,7 @@
         "libsnapshot_cow/snapshot_reader.cpp",
         "libsnapshot_cow/writer_base.cpp",
         "libsnapshot_cow/writer_v2.cpp",
+        "libsnapshot_cow/writer_v3.cpp",
     ],
     export_include_dirs: ["include"],
     host_supported: true,
@@ -392,6 +393,7 @@
     srcs: [
         "libsnapshot_cow/snapshot_reader_test.cpp",
         "libsnapshot_cow/test_v2.cpp",
+        "libsnapshot_cow/test_v3.cpp",
     ],
     cflags: [
         "-D_FILE_OFFSET_BITS=64",
@@ -415,6 +417,9 @@
     test_options: {
         min_shipping_api_level: 30,
     },
+    data: [
+        "tools/testdata/cow_v2",
+    ],
     auto_gen_config: true,
     require_root: false,
     host_supported: true,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h
index fd12b84..8191d61 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h
@@ -29,12 +29,14 @@
 
     virtual ~ICompressor() {}
     // Factory methods for compression methods.
-    static std::unique_ptr<ICompressor> Gz(uint32_t compression_level, const int32_t BLOCK_SZ);
-    static std::unique_ptr<ICompressor> Brotli(uint32_t compression_level, const int32_t BLOCK_SZ);
-    static std::unique_ptr<ICompressor> Lz4(uint32_t compression_level, const int32_t BLOCK_SZ);
-    static std::unique_ptr<ICompressor> Zstd(uint32_t compression_level, const int32_t BLOCK_SZ);
+    static std::unique_ptr<ICompressor> Gz(uint32_t compression_level, const int32_t block_size);
+    static std::unique_ptr<ICompressor> Brotli(uint32_t compression_level,
+                                               const int32_t block_size);
+    static std::unique_ptr<ICompressor> Lz4(uint32_t compression_level, const int32_t block_size);
+    static std::unique_ptr<ICompressor> Zstd(uint32_t compression_level, const int32_t block_size);
 
-    static std::unique_ptr<ICompressor> Create(CowCompression compression, const int32_t BLOCK_SZ);
+    static std::unique_ptr<ICompressor> Create(CowCompression compression,
+                                               const int32_t block_size);
 
     uint32_t GetCompressionLevel() const { return compression_level_; }
     uint32_t GetBlockSize() const { return block_size_; }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 9359b9e..b271dad 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -22,6 +22,9 @@
 namespace android {
 namespace snapshot {
 
+struct CowOperationV3;
+typedef CowOperationV3 CowOperation;
+
 static constexpr uint64_t kCowMagicNumber = 0x436f77634f572121ULL;
 static constexpr uint32_t kCowVersionMajor = 2;
 static constexpr uint32_t kCowVersionMinor = 0;
@@ -88,6 +91,18 @@
 
     // Scratch space used during merge
     uint32_t buffer_size;
+
+} __attribute__((packed));
+
+struct CowHeaderV3 : public CowHeader {
+    // Location of sequence buffer in COW.
+    uint64_t sequence_buffer_offset;
+    // Size, in bytes, of the CowResumePoint buffer.
+    uint32_t resume_buffer_size;
+    // Size, in bytes, of the CowOperation buffer.
+    uint32_t op_buffer_size;
+    // Compression Algorithm
+    uint32_t compression_algorithm;
 } __attribute__((packed));
 
 // This structure is the same size of a normal Operation, but is repurposed for the footer.
@@ -109,43 +124,7 @@
     uint64_t num_ops;
 } __attribute__((packed));
 
-// Cow operations are currently fixed-size entries, but this may change if
-// needed.
-struct CowOperation {
-    // The operation code (see the constants and structures below).
-    uint8_t type;
-
-    // If this operation reads from the data section of the COW, this contains
-    // the compression type of that data (see constants below).
-    uint8_t compression;
-
-    // If this operation reads from the data section of the COW, this contains
-    // the length.
-    uint16_t data_length;
-
-    // The block of data in the new image that this operation modifies.
-    uint64_t new_block;
-
-    // The value of |source| depends on the operation code.
-    //
-    // For copy operations, this is a block location in the source image.
-    //
-    // For replace operations, this is a byte offset within the COW's data
-    // sections (eg, not landing within the header or metadata). It is an
-    // absolute position within the image.
-    //
-    // For zero operations (replace with all zeroes), this is unused and must
-    // be zero.
-    //
-    // For Label operations, this is the value of the applied label.
-    //
-    // For Cluster operations, this is the length of the following data region
-    //
-    // For Xor operations, this is the byte location in the source image.
-    uint64_t source;
-} __attribute__((packed));
-
-// The on disk format of cow (currently ==  CowOperation)
+// V2 version of COW. On disk format for older devices
 struct CowOperationV2 {
     // The operation code (see the constants and structures below).
     uint8_t type;
@@ -180,8 +159,32 @@
     uint64_t source;
 } __attribute__((packed));
 
-static_assert(sizeof(CowOperationV2) == sizeof(CowOperation));
-static_assert(sizeof(CowOperation) == sizeof(CowFooterOperation));
+// The on disk format of cow (currently ==  CowOperation)
+struct CowOperationV3 {
+    // If this operation reads from the data section of the COW, this contains
+    // the length.
+    uint16_t data_length;
+
+    // The block of data in the new image that this operation modifies.
+    uint32_t new_block;
+
+    // source_info with have the following layout
+    // |---4 bits ---| ---12 bits---| --- 48 bits ---|
+    // |--- type --- | -- unused -- | --- source --- |
+    //
+    // The value of |source| depends on the operation code.
+    //
+    // CopyOp: a 32-bit block location in the source image.
+    // ReplaceOp: an absolute byte offset within the COW's data section.
+    // XorOp: an absolute byte offset in the source image.
+    // ZeroOp: unused
+    // LabelOp: a 64-bit opaque identifier.
+    //
+    // A block is compressed if it’s data is < block_sz
+    uint64_t source_info;
+} __attribute__((packed));
+
+static_assert(sizeof(CowOperationV2) == sizeof(CowFooterOperation));
 
 static constexpr uint8_t kCowCopyOp = 1;
 static constexpr uint8_t kCowReplaceOp = 2;
@@ -208,11 +211,22 @@
 static constexpr uint8_t kCowReadAheadInProgress = 1;
 static constexpr uint8_t kCowReadAheadDone = 2;
 
-static inline uint64_t GetCowOpSourceInfoData(const CowOperation* op) {
-    return op->source;
+// this is a mask to grab the last 48 bits of a 64 bit integer
+static constexpr uint64_t kCowOpSourceInfoDataMask = (0x1ULL << 48) - 1;
+// this is a mask to grab the first 4 bits of a 64 bit integer
+static constexpr uint64_t kCowOpSourceInfoTypeBit = 60;
+static constexpr uint64_t kCowOpSourceInfoTypeNumBits = 4;
+static constexpr uint64_t kCowOpSourceInfoTypeMask = (1 << kCowOpSourceInfoTypeNumBits) - 1;
+
+static constexpr void SetCowOpSourceInfoType(CowOperation* op, const uint8_t type) {
+    op->source_info |= uint64_t(type) << kCowOpSourceInfoTypeBit;
 }
-static inline bool GetCowOpSourceInfoCompression(const CowOperation* op) {
-    return op->compression != kCowCompressNone;
+static constexpr uint64_t GetCowOpSourceInfoType(const CowOperation& op) {
+    return (op.source_info >> kCowOpSourceInfoTypeBit) & kCowOpSourceInfoTypeMask;
+}
+
+static constexpr uint64_t GetCowOpSourceInfoData(const CowOperation& op) {
+    return op.source_info & kCowOpSourceInfoDataMask;
 }
 
 struct CowFooter {
@@ -236,10 +250,12 @@
 // 2MB Scratch space used for read-ahead
 static constexpr uint64_t BUFFER_REGION_DEFAULT_SIZE = (1ULL << 21);
 
+std::ostream& operator<<(std::ostream& os, CowOperationV2 const& arg);
+
 std::ostream& operator<<(std::ostream& os, CowOperation const& arg);
 
-int64_t GetNextOpOffset(const CowOperation& op, uint32_t cluster_size);
-int64_t GetNextDataOffset(const CowOperation& op, uint32_t cluster_size);
+int64_t GetNextOpOffset(const CowOperationV2& op, uint32_t cluster_size);
+int64_t GetNextDataOffset(const CowOperationV2& op, uint32_t cluster_size);
 
 // Ops that are internal to the Cow Format and not OTA data
 bool IsMetadataOp(const CowOperation& op);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 67d301d..2721937 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -165,9 +165,10 @@
     void UpdateMergeOpsCompleted(int num_merge_ops) { header_.num_merge_ops += num_merge_ops; }
 
   private:
+    bool ParseV2(android::base::borrowed_fd fd, std::optional<uint64_t> label);
     bool PrepMergeOps();
     uint64_t FindNumCopyops();
-    uint8_t GetCompressionType(const CowOperation* op);
+    uint8_t GetCompressionType();
 
     android::base::unique_fd owned_fd_;
     android::base::borrowed_fd fd_;
@@ -184,7 +185,10 @@
     std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
     ReaderFlags reader_flag_;
     bool is_merge_{};
+    uint8_t compression_type_ = kCowCompressNone;
 };
 
+bool ReadCowHeader(android::base::borrowed_fd fd, CowHeader* header);
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
index ff3ccec..1be592d 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
@@ -14,11 +14,14 @@
 // limitations under the License.
 //
 
+#include <inttypes.h>
 #include <libsnapshot/cow_format.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <libsnapshot/cow_format.h>
 #include "writer_v2.h"
 
 namespace android {
@@ -26,45 +29,80 @@
 
 using android::base::unique_fd;
 
-std::ostream& operator<<(std::ostream& os, CowOperation const& op) {
-    os << "CowOperation(type:";
-    if (op.type == kCowCopyOp)
-        os << "kCowCopyOp,    ";
-    else if (op.type == kCowReplaceOp)
-        os << "kCowReplaceOp, ";
-    else if (op.type == kCowZeroOp)
-        os << "kZeroOp,       ";
-    else if (op.type == kCowFooterOp)
-        os << "kCowFooterOp,  ";
-    else if (op.type == kCowLabelOp)
-        os << "kCowLabelOp,   ";
-    else if (op.type == kCowClusterOp)
-        os << "kCowClusterOp  ";
-    else if (op.type == kCowXorOp)
-        os << "kCowXorOp      ";
-    else if (op.type == kCowSequenceOp)
-        os << "kCowSequenceOp ";
-    else if (op.type == kCowFooterOp)
-        os << "kCowFooterOp  ";
-    else
-        os << (int)op.type << "?,";
-    os << "compression:";
-    if (op.compression == kCowCompressNone)
-        os << "kCowCompressNone,   ";
-    else if (op.compression == kCowCompressGz)
-        os << "kCowCompressGz,     ";
-    else if (op.compression == kCowCompressBrotli)
-        os << "kCowCompressBrotli, ";
-    else
-        os << (int)op.compression << "?, ";
-    os << "data_length:" << op.data_length << ",\t";
-    os << "new_block:" << op.new_block << ",\t";
+std::ostream& EmitCowTypeString(std::ostream& os, uint8_t cow_type) {
+    switch (cow_type) {
+        case kCowCopyOp:
+            return os << "kCowCopyOp";
+        case kCowReplaceOp:
+            return os << "kCowReplaceOp";
+        case kCowZeroOp:
+            return os << "kZeroOp";
+        case kCowFooterOp:
+            return os << "kCowFooterOp";
+        case kCowLabelOp:
+            return os << "kCowLabelOp";
+        case kCowClusterOp:
+            return os << "kCowClusterOp";
+        case kCowXorOp:
+            return os << "kCowXorOp";
+        case kCowSequenceOp:
+            return os << "kCowSequenceOp";
+        default:
+            return os << (int)cow_type << "unknown";
+    }
+}
+
+std::ostream& operator<<(std::ostream& os, CowOperationV2 const& op) {
+    os << "CowOperationV2(";
+    EmitCowTypeString(os, op.type) << ", ";
+    switch (op.compression) {
+        case kCowCompressNone:
+            os << "uncompressed, ";
+            break;
+        case kCowCompressGz:
+            os << "gz, ";
+            break;
+        case kCowCompressBrotli:
+            os << "brotli, ";
+            break;
+        case kCowCompressLz4:
+            os << "lz4, ";
+            break;
+        case kCowCompressZstd:
+            os << "zstd, ";
+            break;
+    }
+    os << "data_length:" << op.data_length << ", ";
+    os << "new_block:" << op.new_block << ", ";
     os << "source:" << op.source;
     os << ")";
     return os;
 }
 
-int64_t GetNextOpOffset(const CowOperation& op, uint32_t cluster_ops) {
+std::ostream& operator<<(std::ostream& os, CowOperation const& op) {
+    os << "CowOperation(";
+    EmitCowTypeString(os, GetCowOpSourceInfoType(op));
+    if (GetCowOpSourceInfoType(op) == kCowReplaceOp || GetCowOpSourceInfoType(op) == kCowXorOp ||
+        GetCowOpSourceInfoType(op) == kCowSequenceOp) {
+        os << ", data_length:" << op.data_length;
+    }
+    if (GetCowOpSourceInfoType(op) != kCowClusterOp &&
+        GetCowOpSourceInfoType(op) != kCowSequenceOp && GetCowOpSourceInfoType(op) != kCowLabelOp) {
+        os << ", new_block:" << op.new_block;
+    }
+    if (GetCowOpSourceInfoType(op) == kCowXorOp || GetCowOpSourceInfoType(op) == kCowReplaceOp ||
+        GetCowOpSourceInfoType(op) == kCowCopyOp) {
+        os << ", source:" << (op.source_info & kCowOpSourceInfoDataMask);
+    } else if (GetCowOpSourceInfoType(op) == kCowClusterOp) {
+        os << ", cluster_data:" << (op.source_info & kCowOpSourceInfoDataMask);
+    } else {
+        os << ", label:0x" << android::base::StringPrintf("%" PRIx64, op.source_info);
+    }
+    os << ")";
+    return os;
+}
+
+int64_t GetNextOpOffset(const CowOperationV2& op, uint32_t cluster_ops) {
     if (op.type == kCowClusterOp) {
         return op.source;
     } else if ((op.type == kCowReplaceOp || op.type == kCowXorOp) && cluster_ops == 0) {
@@ -74,18 +112,18 @@
     }
 }
 
-int64_t GetNextDataOffset(const CowOperation& op, uint32_t cluster_ops) {
+int64_t GetNextDataOffset(const CowOperationV2& op, uint32_t cluster_ops) {
     if (op.type == kCowClusterOp) {
-        return cluster_ops * sizeof(CowOperation);
+        return cluster_ops * sizeof(CowOperationV2);
     } else if (cluster_ops == 0) {
-        return sizeof(CowOperation);
+        return sizeof(CowOperationV2);
     } else {
         return 0;
     }
 }
 
 bool IsMetadataOp(const CowOperation& op) {
-    switch (op.type) {
+    switch (GetCowOpSourceInfoType(op)) {
         case kCowLabelOp:
         case kCowClusterOp:
         case kCowFooterOp:
@@ -97,7 +135,7 @@
 }
 
 bool IsOrderedOp(const CowOperation& op) {
-    switch (op.type) {
+    switch (GetCowOpSourceInfoType(op)) {
         case kCowCopyOp:
         case kCowXorOp:
             return true;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 1b5d724..996037e 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -24,6 +24,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <libsnapshot/cow_format.h>
 #include <libsnapshot/cow_reader.h>
 #include <zlib.h>
 
@@ -33,6 +34,35 @@
 namespace android {
 namespace snapshot {
 
+bool ReadCowHeader(android::base::borrowed_fd fd, CowHeader* header) {
+    if (lseek(fd.get(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek header failed";
+        return false;
+    }
+
+    memset(header, 0, sizeof(*header));
+
+    if (!android::base::ReadFully(fd, &header->prefix, sizeof(header->prefix))) {
+        return false;
+    }
+    if (header->prefix.magic != kCowMagicNumber) {
+        LOG(ERROR) << "Header Magic corrupted. Magic: " << header->prefix.magic
+                   << "Expected: " << kCowMagicNumber;
+        return false;
+    }
+    if (header->prefix.header_size > sizeof(CowHeader)) {
+        LOG(ERROR) << "Unknown CowHeader size (got " << header->prefix.header_size
+                   << " bytes, expected at most " << sizeof(CowHeader) << " bytes)";
+        return false;
+    }
+
+    if (lseek(fd.get(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek header failed";
+        return false;
+    }
+    return android::base::ReadFully(fd, header, header->prefix.header_size);
+}
+
 CowReader::CowReader(ReaderFlags reader_flag, bool is_merge)
     : fd_(-1),
       header_(),
@@ -55,6 +85,7 @@
     cow->data_loc_ = data_loc_;
     cow->block_pos_index_ = block_pos_index_;
     cow->is_merge_ = is_merge_;
+    cow->compression_type_ = compression_type_;
     return cow;
 }
 
@@ -101,8 +132,43 @@
     footer_ = parser.footer();
     fd_size_ = parser.fd_size();
     last_label_ = parser.last_label();
-    ops_ = parser.ops();
     data_loc_ = parser.data_loc();
+    ops_ = std::make_shared<std::vector<CowOperation>>(parser.ops()->size());
+
+    // Translate the operation buffer from on disk to in memory
+    for (size_t i = 0; i < parser.ops()->size(); i++) {
+        const auto& v2_op = parser.ops()->at(i);
+
+        auto& new_op = ops_->at(i);
+        SetCowOpSourceInfoType(&new_op, v2_op.type);
+        new_op.data_length = v2_op.data_length;
+
+        if (v2_op.new_block > std::numeric_limits<uint32_t>::max()) {
+            LOG(ERROR) << "Out-of-range new block in COW op: " << v2_op;
+            return false;
+        }
+        new_op.new_block = v2_op.new_block;
+
+        uint64_t source_info = v2_op.source;
+        if (GetCowOpSourceInfoType(new_op) != kCowLabelOp) {
+            source_info &= kCowOpSourceInfoDataMask;
+            if (source_info != v2_op.source) {
+                LOG(ERROR) << "Out-of-range source value in COW op: " << v2_op;
+                return false;
+            }
+        }
+        if (v2_op.compression != kCowCompressNone) {
+            if (compression_type_ == kCowCompressNone) {
+                compression_type_ = v2_op.compression;
+            } else if (compression_type_ != v2_op.compression) {
+                LOG(ERROR) << "COW has mixed compression types which is not supported;"
+                           << " previously saw " << compression_type_ << ", got "
+                           << v2_op.compression << ", op: " << v2_op;
+                return false;
+            }
+        }
+        new_op.source_info |= source_info;
+    }
 
     // If we're resuming a write, we're not ready to merge
     if (label.has_value()) return true;
@@ -224,7 +290,7 @@
     for (size_t i = 0; i < ops_->size(); i++) {
         auto& current_op = ops_->data()[i];
 
-        if (current_op.type == kCowSequenceOp) {
+        if (GetCowOpSourceInfoType(current_op) == kCowSequenceOp) {
             size_t seq_len = current_op.data_length / sizeof(uint32_t);
 
             merge_op_blocks->resize(merge_op_blocks->size() + seq_len);
@@ -536,11 +602,11 @@
 }
 
 bool CowReader::GetRawBytes(const CowOperation* op, void* buffer, size_t len, size_t* read) {
-    switch (op->type) {
+    switch (GetCowOpSourceInfoType(*op)) {
         case kCowSequenceOp:
         case kCowReplaceOp:
         case kCowXorOp:
-            return GetRawBytes(GetCowOpSourceInfoData(op), buffer, len, read);
+            return GetRawBytes(GetCowOpSourceInfoData(*op), buffer, len, read);
         default:
             LOG(ERROR) << "Cannot get raw bytes of non-data op: " << *op;
             return false;
@@ -597,14 +663,14 @@
     size_t remaining_;
 };
 
-uint8_t CowReader::GetCompressionType(const CowOperation* op) {
-    return op->compression;
+uint8_t CowReader::GetCompressionType() {
+    return compression_type_;
 }
 
 ssize_t CowReader::ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
                             size_t ignore_bytes) {
     std::unique_ptr<IDecompressor> decompressor;
-    switch (GetCompressionType(op)) {
+    switch (GetCompressionType()) {
         case kCowCompressNone:
             break;
         case kCowCompressGz:
@@ -624,15 +690,15 @@
             }
             break;
         default:
-            LOG(ERROR) << "Unknown compression type: " << GetCompressionType(op);
+            LOG(ERROR) << "Unknown compression type: " << GetCompressionType();
             return -1;
     }
 
     uint64_t offset;
-    if (op->type == kCowXorOp) {
+    if (GetCowOpSourceInfoType(*op) == kCowXorOp) {
         offset = data_loc_->at(op->new_block);
     } else {
-        offset = GetCowOpSourceInfoData(op);
+        offset = GetCowOpSourceInfoData(*op);
     }
 
     if (!decompressor) {
@@ -646,12 +712,12 @@
 }
 
 bool CowReader::GetSourceOffset(const CowOperation* op, uint64_t* source_offset) {
-    switch (op->type) {
+    switch (GetCowOpSourceInfoType(*op)) {
         case kCowCopyOp:
-            *source_offset = GetCowOpSourceInfoData(op) * header_.block_size;
+            *source_offset = GetCowOpSourceInfoData(*op) * header_.block_size;
             return true;
         case kCowXorOp:
-            *source_offset = GetCowOpSourceInfoData(op);
+            *source_offset = GetCowOpSourceInfoData(*op);
             return true;
         default:
             return false;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
index 83b5a12..aaaf20c 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
@@ -26,6 +26,7 @@
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <gflags/gflags.h>
+#include <libsnapshot/cow_format.h>
 #include <libsnapshot/cow_reader.h>
 #include "parser_v2.h"
 
@@ -198,7 +199,7 @@
 
         if (!FLAGS_silent && FLAGS_show_ops) std::cout << *op << "\n";
 
-        if ((FLAGS_decompress || extract_to >= 0) && op->type == kCowReplaceOp) {
+        if ((FLAGS_decompress || extract_to >= 0) && GetCowOpSourceInfoType(*op) == kCowReplaceOp) {
             if (reader.ReadData(op, buffer.data(), buffer.size()) < 0) {
                 std::cerr << "Failed to decompress for :" << *op << "\n";
                 success = false;
@@ -212,12 +213,13 @@
                     return false;
                 }
             }
-        } else if (extract_to >= 0 && !IsMetadataOp(*op) && op->type != kCowZeroOp) {
+        } else if (extract_to >= 0 && !IsMetadataOp(*op) &&
+                   GetCowOpSourceInfoType(*op) != kCowZeroOp) {
             PLOG(ERROR) << "Cannot extract op yet: " << *op;
             return false;
         }
 
-        if (op->type == kCowSequenceOp && FLAGS_show_merge_sequence) {
+        if (GetCowOpSourceInfoType(*op) == kCowSequenceOp && FLAGS_show_merge_sequence) {
             size_t read;
             std::vector<uint32_t> merge_op_blocks;
             size_t seq_len = op->data_length / sizeof(uint32_t);
@@ -235,13 +237,13 @@
             }
         }
 
-        if (op->type == kCowCopyOp) {
+        if (GetCowOpSourceInfoType(*op) == kCowCopyOp) {
             copy_ops++;
-        } else if (op->type == kCowReplaceOp) {
+        } else if (GetCowOpSourceInfoType(*op) == kCowReplaceOp) {
             replace_ops++;
-        } else if (op->type == kCowZeroOp) {
+        } else if (GetCowOpSourceInfoType(*op) == kCowZeroOp) {
             zero_ops++;
-        } else if (op->type == kCowXorOp) {
+        } else if (GetCowOpSourceInfoType(*op) == kCowXorOp) {
             xor_ops++;
         }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
index fdb5c59..a7307bf 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
@@ -23,35 +23,6 @@
 
 using android::base::borrowed_fd;
 
-bool ReadCowHeader(android::base::borrowed_fd fd, CowHeader* header) {
-    if (lseek(fd.get(), 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek header failed";
-        return false;
-    }
-
-    memset(header, 0, sizeof(*header));
-
-    if (!android::base::ReadFully(fd, &header->prefix, sizeof(header->prefix))) {
-        return false;
-    }
-    if (header->prefix.magic != kCowMagicNumber) {
-        LOG(ERROR) << "Header Magic corrupted. Magic: " << header->prefix.magic
-                   << "Expected: " << kCowMagicNumber;
-        return false;
-    }
-    if (header->prefix.header_size > sizeof(CowHeader)) {
-        LOG(ERROR) << "Unknown CowHeader size (got " << header->prefix.header_size
-                   << " bytes, expected at most " << sizeof(CowHeader) << " bytes)";
-        return false;
-    }
-
-    if (lseek(fd.get(), 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek header failed";
-        return false;
-    }
-    return android::base::ReadFully(fd, header, header->prefix.header_size);
-}
-
 bool CowParserV2::Parse(borrowed_fd fd, const CowHeader& header, std::optional<uint64_t> label) {
     auto pos = lseek(fd.get(), 0, SEEK_END);
     if (pos < 0) {
@@ -66,18 +37,18 @@
                    << sizeof(CowFooter);
         return false;
     }
-    if (header_.op_size != sizeof(CowOperation)) {
+    if (header_.op_size != sizeof(CowOperationV2)) {
         LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
-                   << sizeof(CowOperation);
+                   << sizeof(CowOperationV2);
         return false;
     }
     if (header_.cluster_ops == 1) {
         LOG(ERROR) << "Clusters must contain at least two operations to function.";
         return false;
     }
-    if (header_.op_size != sizeof(CowOperation)) {
+    if (header_.op_size != sizeof(CowOperationV2)) {
         LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
-                   << sizeof(CowOperation);
+                   << sizeof(CowOperationV2);
         return false;
     }
     if (header_.cluster_ops == 1) {
@@ -123,23 +94,23 @@
     uint64_t data_pos = 0;
 
     if (header_.cluster_ops) {
-        data_pos = pos + header_.cluster_ops * sizeof(CowOperation);
+        data_pos = pos + header_.cluster_ops * sizeof(CowOperationV2);
     } else {
-        data_pos = pos + sizeof(CowOperation);
+        data_pos = pos + sizeof(CowOperationV2);
     }
 
-    auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
+    auto ops_buffer = std::make_shared<std::vector<CowOperationV2>>();
     uint64_t current_op_num = 0;
     uint64_t cluster_ops = header_.cluster_ops ?: 1;
     bool done = false;
 
     // Alternating op clusters and data
     while (!done) {
-        uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperation));
+        uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperationV2));
         if (to_add == 0) break;
         ops_buffer->resize(current_op_num + to_add);
         if (!android::base::ReadFully(fd, &ops_buffer->data()[current_op_num],
-                                      to_add * sizeof(CowOperation))) {
+                                      to_add * sizeof(CowOperationV2))) {
             PLOG(ERROR) << "read op failed";
             return false;
         }
@@ -150,7 +121,7 @@
             if (current_op.type == kCowXorOp) {
                 data_loc->insert({current_op.new_block, data_pos});
             }
-            pos += sizeof(CowOperation) + GetNextOpOffset(current_op, header_.cluster_ops);
+            pos += sizeof(CowOperationV2) + GetNextOpOffset(current_op, header_.cluster_ops);
             data_pos += current_op.data_length + GetNextDataOffset(current_op, header_.cluster_ops);
 
             if (current_op.type == kCowClusterOp) {
@@ -222,7 +193,7 @@
                        << ops_buffer->size();
             return false;
         }
-        if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) {
+        if (ops_buffer->size() * sizeof(CowOperationV2) != footer_->op.ops_size) {
             LOG(ERROR) << "ops size does not match ";
             return false;
         }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
index afcf687..f51ff88 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
@@ -33,7 +33,7 @@
 
     const CowHeader& header() const { return header_; }
     const std::optional<CowFooter>& footer() const { return footer_; }
-    std::shared_ptr<std::vector<CowOperation>> ops() { return ops_; }
+    std::shared_ptr<std::vector<CowOperationV2>> ops() { return ops_; }
     std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc() const { return data_loc_; }
     uint64_t fd_size() const { return fd_size_; }
     const std::optional<uint64_t>& last_label() const { return last_label_; }
@@ -43,13 +43,11 @@
 
     CowHeader header_ = {};
     std::optional<CowFooter> footer_;
-    std::shared_ptr<std::vector<CowOperation>> ops_;
+    std::shared_ptr<std::vector<CowOperationV2>> ops_;
     std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
     uint64_t fd_size_;
     std::optional<uint64_t> last_label_;
 };
 
-bool ReadCowHeader(android::base::borrowed_fd fd, CowHeader* header);
-
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
index a3e40d9..d7ee432 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/snapshot_reader.cpp
@@ -147,7 +147,7 @@
         op = ops_[chunk];
     }
 
-    if (!op || op->type == kCowCopyOp) {
+    if (!op || GetCowOpSourceInfoType(*op) == kCowCopyOp) {
         borrowed_fd fd = GetSourceFd();
         if (fd < 0) {
             // GetSourceFd sets errno.
@@ -169,15 +169,15 @@
             // ReadFullyAtOffset sets errno.
             return -1;
         }
-    } else if (op->type == kCowZeroOp) {
+    } else if (GetCowOpSourceInfoType(*op) == kCowZeroOp) {
         memset(buffer, 0, bytes_to_read);
-    } else if (op->type == kCowReplaceOp) {
+    } else if (GetCowOpSourceInfoType(*op) == kCowReplaceOp) {
         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;
         }
-    } else if (op->type == kCowXorOp) {
+    } else if (GetCowOpSourceInfoType(*op) == kCowXorOp) {
         borrowed_fd fd = GetSourceFd();
         if (fd < 0) {
             // GetSourceFd sets errno.
@@ -208,7 +208,8 @@
             ((char*)buffer)[i] ^= data[i];
         }
     } else {
-        LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << uint32_t(op->type);
+        LOG(ERROR) << "CompressedSnapshotReader unknown op type: "
+                   << uint32_t(GetCowOpSourceInfoType(*op));
         errno = EINVAL;
         return -1;
     }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
index e59bd92..d962875 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v2.cpp
@@ -25,6 +25,7 @@
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
 #include "cow_decompress.h"
+#include "libsnapshot/cow_format.h"
 #include "writer_v2.h"
 
 using android::base::unique_fd;
@@ -85,11 +86,10 @@
     size_t i = 0;
     while (!iter->AtEnd()) {
         auto op = iter->Get();
-        ASSERT_EQ(op->type, kCowCopyOp);
-        ASSERT_EQ(op->compression, kCowCompressNone);
+        ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowCopyOp);
         ASSERT_EQ(op->data_length, 0);
         ASSERT_EQ(op->new_block, 10 + i);
-        ASSERT_EQ(op->source, 1000 + i);
+        ASSERT_EQ(GetCowOpSourceInfoData(*op), 1000 + i);
         iter->Next();
         i += 1;
     }
@@ -132,11 +132,10 @@
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowCopyOp);
-    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowCopyOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 10);
-    ASSERT_EQ(op->source, 20);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 20);
 
     std::string sink(data.size(), '\0');
 
@@ -144,8 +143,7 @@
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_FALSE(GetCowOpSourceInfoCompression(op));
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowReplaceOp);
     ASSERT_EQ(op->data_length, 4096);
     ASSERT_EQ(op->new_block, 50);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -156,21 +154,19 @@
     op = iter->Get();
 
     // Note: the zero operation gets split into two blocks.
-    ASSERT_EQ(op->type, kCowZeroOp);
-    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowZeroOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 51);
-    ASSERT_EQ(op->source, 0);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowZeroOp);
-    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowZeroOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 52);
-    ASSERT_EQ(op->source, 0);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
 
     iter->Next();
     ASSERT_TRUE(iter->AtEnd());
@@ -211,11 +207,10 @@
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowCopyOp);
-    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowCopyOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 10);
-    ASSERT_EQ(op->source, 20);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 20);
 
     std::string sink(data.size(), '\0');
 
@@ -223,11 +218,10 @@
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowXorOp);
-    ASSERT_FALSE(GetCowOpSourceInfoCompression(op));
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowXorOp);
     ASSERT_EQ(op->data_length, 4096);
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_EQ(GetCowOpSourceInfoData(op), 98314);  // 4096 * 24 + 10
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314);  // 4096 * 24 + 10
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
@@ -236,21 +230,19 @@
     op = iter->Get();
 
     // Note: the zero operation gets split into two blocks.
-    ASSERT_EQ(op->type, kCowZeroOp);
-    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowZeroOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 51);
-    ASSERT_EQ(op->source, 0);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowZeroOp);
-    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowZeroOp);
     ASSERT_EQ(op->data_length, 0);
     ASSERT_EQ(op->new_block, 52);
-    ASSERT_EQ(op->source, 0);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
 
     iter->Next();
     ASSERT_TRUE(iter->AtEnd());
@@ -282,8 +274,7 @@
 
     std::string sink(data.size(), '\0');
 
-    ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(GetCowOpSourceInfoCompression(op));
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowReplaceOp);
     ASSERT_EQ(op->data_length, 56);  // compressed!
     ASSERT_EQ(op->new_block, 50);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -335,16 +326,16 @@
     while (!iter->AtEnd()) {
         auto op = iter->Get();
 
-        if (op->type == kCowXorOp) {
+        if (GetCowOpSourceInfoType(*op) == kCowXorOp) {
             total_blocks += 1;
             std::string sink(xor_data.size(), '\0');
             ASSERT_EQ(op->new_block, 50);
-            ASSERT_EQ(GetCowOpSourceInfoData(op), 98314);  // 4096 * 24 + 10
+            ASSERT_EQ(GetCowOpSourceInfoData(*op), 98314);  // 4096 * 24 + 10
             ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
             ASSERT_EQ(sink, xor_data);
         }
 
-        if (op->type == kCowReplaceOp) {
+        if (GetCowOpSourceInfoType(*op) == kCowReplaceOp) {
             total_blocks += 1;
             if (op->new_block == 100) {
                 data.resize(options.block_size);
@@ -409,7 +400,7 @@
     while (!iter->AtEnd()) {
         auto op = iter->Get();
 
-        if (op->type == kCowReplaceOp) {
+        if (GetCowOpSourceInfoType(*op) == kCowReplaceOp) {
             total_blocks += 1;
             if (op->new_block == 50) {
                 data.resize(options.block_size);
@@ -529,8 +520,7 @@
 
     std::string sink(data.size(), '\0');
 
-    ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(GetCowOpSourceInfoCompression(op));
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowReplaceOp);
     ASSERT_EQ(op->data_length, 56);  // compressed!
     ASSERT_EQ(op->new_block, 50);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -540,7 +530,7 @@
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowClusterOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowClusterOp);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
@@ -548,7 +538,6 @@
 
     sink = {};
     sink.resize(data2.size(), '\0');
-    ASSERT_TRUE(GetCowOpSourceInfoCompression(op));
     ASSERT_EQ(op->data_length, 41);  // compressed!
     ASSERT_EQ(op->new_block, 51);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
@@ -558,7 +547,7 @@
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
 
-    ASSERT_EQ(op->type, kCowClusterOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowClusterOp);
 
     iter->Next();
     ASSERT_TRUE(iter->AtEnd());
@@ -592,8 +581,7 @@
     std::string sink(options.block_size, '\0');
 
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(GetCowOpSourceInfoCompression(op));
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowReplaceOp);
     ASSERT_EQ(op->new_block, 51);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
 }
@@ -666,7 +654,7 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
@@ -676,14 +664,14 @@
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(op->source, 3);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowLabelOp);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 3);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data2);
 
@@ -729,14 +717,14 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(op->source, 0);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowLabelOp);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 0);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowZeroOp);
 
     iter->Next();
 
@@ -787,8 +775,8 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(op->source, 5);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowLabelOp);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 5);
 
     iter->Next();
     ASSERT_TRUE(iter->AtEnd());
@@ -838,7 +826,7 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(0, options.block_size));
 
@@ -848,7 +836,7 @@
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(options.block_size, 2 * options.block_size));
 
@@ -856,26 +844,26 @@
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(op->source, 4);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowLabelOp);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 4);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowZeroOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowZeroOp);
 
     iter->Next();
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(op->source, 5);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowLabelOp);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 5);
 
     iter->Next();
 
@@ -919,7 +907,7 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(0, options.block_size));
 
@@ -927,52 +915,52 @@
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(op->source, 4);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowLabelOp);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 4);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowZeroOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowClusterOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowClusterOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowZeroOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(op->source, 5);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowLabelOp);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 5);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowCopyOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowCopyOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowClusterOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowClusterOp);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(op->source, 6);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowLabelOp);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 6);
 
     iter->Next();
 
@@ -1018,14 +1006,14 @@
 
     ASSERT_FALSE(iter->AtEnd());
     auto op = iter->Get();
-    ASSERT_EQ(op->type, kCowLabelOp);
-    ASSERT_EQ(op->source, 50);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowLabelOp);
+    ASSERT_EQ(GetCowOpSourceInfoData(*op), 50);
 
     iter->Next();
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowReplaceOp);
     ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data2);
 
@@ -1033,7 +1021,7 @@
 
     ASSERT_FALSE(iter->AtEnd());
     op = iter->Get();
-    ASSERT_EQ(op->type, kCowClusterOp);
+    ASSERT_EQ(GetCowOpSourceInfoType(*op), kCowClusterOp);
 
     iter->Next();
 
@@ -1130,12 +1118,12 @@
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
 
-        if (op->type == kCowReplaceOp) {
+        if (GetCowOpSourceInfoType(*op) == kCowReplaceOp) {
             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 (GetCowOpSourceInfoType(*op) == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1191,12 +1179,12 @@
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
 
-        if (op->type == kCowReplaceOp) {
+        if (GetCowOpSourceInfoType(*op) == kCowReplaceOp) {
             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 (GetCowOpSourceInfoType(*op) == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1242,12 +1230,12 @@
 
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
-        if (op->type == kCowReplaceOp) {
+        if (GetCowOpSourceInfoType(*op) == kCowReplaceOp) {
             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 (GetCowOpSourceInfoType(*op) == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1504,6 +1492,37 @@
     ASSERT_FALSE(reader.VerifyMergeOps());
 }
 
+unique_fd OpenTestFile(const std::string& file, int flags) {
+    std::string path = "tools/testdata/" + file;
+
+    unique_fd fd(open(path.c_str(), flags));
+    if (fd >= 0) {
+        return fd;
+    }
+
+    path = android::base::GetExecutableDirectory() + "/" + path;
+    return unique_fd{open(path.c_str(), flags)};
+}
+
+TEST_F(CowTest, CompatibilityTest) {
+    std::string filename = "cow_v2";
+    auto fd = OpenTestFile(filename, O_RDONLY);
+    if (fd.get() == -1) {
+        LOG(ERROR) << filename << " not found";
+        GTEST_SKIP();
+    }
+    CowReader reader;
+    reader.Parse(fd);
+
+    const auto& header = reader.GetHeader();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+
+    CowFooter footer;
+    ASSERT_TRUE(reader.GetFooter(&footer));
+}
+
 }  // namespace snapshot
 }  // namespace android
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
new file mode 100644
index 0000000..a4d617f
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sys/stat.h>
+
+#include <cstdio>
+#include <iostream>
+#include <memory>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <libsnapshot/cow_reader.h>
+#include <libsnapshot/cow_writer.h>
+#include "cow_decompress.h"
+#include "libsnapshot/cow_format.h"
+#include "writer_v3.h"
+using android::base::unique_fd;
+using testing::AssertionFailure;
+using testing::AssertionResult;
+using testing::AssertionSuccess;
+
+namespace android {
+namespace snapshot {
+
+class CowOperationV3Test : public ::testing::Test {
+  protected:
+    virtual void SetUp() override {
+        cow_ = std::make_unique<TemporaryFile>();
+        ASSERT_GE(cow_->fd, 0) << strerror(errno);
+    }
+
+    virtual void TearDown() override { cow_ = nullptr; }
+
+    unique_fd GetCowFd() { return unique_fd{dup(cow_->fd)}; }
+
+    std::unique_ptr<TemporaryFile> cow_;
+};
+
+TEST_F(CowOperationV3Test, CowTypeTest) {
+    CowOperationV3 new_op;
+    CowOperationV2 old_op;
+    old_op.type = kCowReplaceOp;
+    ASSERT_NE(GetCowOpSourceInfoType(new_op), old_op.type);
+    SetCowOpSourceInfoType(&new_op, old_op.type);
+    ASSERT_EQ(GetCowOpSourceInfoType(new_op), old_op.type);
+}
+
+}  // namespace snapshot
+}  // namespace android
\ No newline at end of file
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.cpp
index ff34c59..2ffc37b 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.cpp
@@ -191,5 +191,16 @@
                                                       block_dev_size);
 }
 
+bool CowWriterBase::Sync() {
+    if (is_dev_null_) {
+        return true;
+    }
+    if (fsync(fd_.get()) < 0) {
+        PLOG(ERROR) << "fsync failed";
+        return false;
+    }
+    return true;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
index c8b4772..709b248 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
@@ -34,6 +34,7 @@
     // If the given label is not found, Initialize will fail.
     virtual bool Initialize(std::optional<uint64_t> label = {}) = 0;
 
+    bool Sync();
     bool AddCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
     bool AddRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
     bool AddXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block,
@@ -62,7 +63,6 @@
     bool ValidateNewBlock(uint64_t new_block);
 
     CowOptions options_;
-    CowHeader header_{};
 
     android::base::unique_fd fd_;
     bool is_dev_null_ = false;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
index 699529b..a6f449f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.cpp
@@ -110,7 +110,7 @@
     header_.prefix.minor_version = kCowVersionMinor;
     header_.prefix.header_size = sizeof(CowHeader);
     header_.footer_size = sizeof(CowFooter);
-    header_.op_size = sizeof(CowOperation);
+    header_.op_size = sizeof(CowOperationV2);
     header_.block_size = options_.block_size;
     header_.num_merge_ops = options_.num_merge_ops;
     header_.cluster_ops = options_.cluster_ops;
@@ -159,9 +159,9 @@
         struct iovec* cowop_ptr = cowop_vec_.get();
         struct iovec* data_ptr = data_vec_.get();
         for (size_t i = 0; i < header_.cluster_ops; i++) {
-            std::unique_ptr<CowOperation> op = std::make_unique<CowOperation>();
+            std::unique_ptr<CowOperationV2> op = std::make_unique<CowOperationV2>();
             cowop_ptr[i].iov_base = op.get();
-            cowop_ptr[i].iov_len = sizeof(CowOperation);
+            cowop_ptr[i].iov_len = sizeof(CowOperationV2);
             opbuffer_vec_.push_back(std::move(op));
 
             std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(header_.block_size * 2);
@@ -214,19 +214,19 @@
 }
 
 void CowWriterV2::InitPos() {
-    next_op_pos_ = sizeof(header_) + header_.buffer_size;
-    cluster_size_ = header_.cluster_ops * sizeof(CowOperation);
+    next_op_pos_ = sizeof(CowHeader) + header_.buffer_size;
+    cluster_size_ = header_.cluster_ops * sizeof(CowOperationV2);
     if (header_.cluster_ops) {
         next_data_pos_ = next_op_pos_ + cluster_size_;
     } else {
-        next_data_pos_ = next_op_pos_ + sizeof(CowOperation);
+        next_data_pos_ = next_op_pos_ + sizeof(CowOperationV2);
     }
     current_cluster_size_ = 0;
     current_data_size_ = 0;
 }
 
 bool CowWriterV2::OpenForWrite() {
-    // This limitation is tied to the data field size in CowOperation.
+    // This limitation is tied to the data field size in CowOperationV2.
     if (header_.block_size > std::numeric_limits<uint16_t>::max()) {
         LOG(ERROR) << "Block size is too large";
         return false;
@@ -243,7 +243,7 @@
 
     // Headers are not complete, but this ensures the file is at the right
     // position.
-    if (!android::base::WriteFully(fd_, &header_, sizeof(header_))) {
+    if (!android::base::WriteFully(fd_, &header_, sizeof(CowHeader))) {
         PLOG(ERROR) << "write failed";
         return false;
     }
@@ -262,7 +262,7 @@
         return false;
     }
 
-    if (lseek(fd_.get(), sizeof(header_) + header_.buffer_size, SEEK_SET) < 0) {
+    if (lseek(fd_.get(), sizeof(CowHeader) + header_.buffer_size, SEEK_SET) < 0) {
         PLOG(ERROR) << "lseek failed";
         return false;
     }
@@ -313,7 +313,7 @@
     CHECK(!merge_in_progress_);
 
     for (size_t i = 0; i < num_blocks; i++) {
-        CowOperation op = {};
+        CowOperationV2 op = {};
         op.type = kCowCopyOp;
         op.new_block = new_block + i;
         op.source = old_block + i;
@@ -399,7 +399,7 @@
         num_blocks -= pending_blocks;
 
         while (i < size / header_.block_size && pending_blocks) {
-            CowOperation op = {};
+            CowOperationV2 op = {};
             op.new_block = new_block_start + i;
             op.type = type;
             if (type == kCowXorOp) {
@@ -451,7 +451,7 @@
 bool CowWriterV2::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
     CHECK(!merge_in_progress_);
     for (uint64_t i = 0; i < num_blocks; i++) {
-        CowOperation op = {};
+        CowOperationV2 op = {};
         op.type = kCowZeroOp;
         op.new_block = new_block_start + i;
         op.source = 0;
@@ -462,7 +462,7 @@
 
 bool CowWriterV2::EmitLabel(uint64_t label) {
     CHECK(!merge_in_progress_);
-    CowOperation op = {};
+    CowOperationV2 op = {};
     op.type = kCowLabelOp;
     op.source = label;
     return WriteOperation(op) && Sync();
@@ -473,7 +473,7 @@
     size_t to_add = 0;
     size_t max_ops = (header_.block_size * 2) / sizeof(uint32_t);
     while (num_ops > 0) {
-        CowOperation op = {};
+        CowOperationV2 op = {};
         op.type = kCowSequenceOp;
         op.source = next_data_pos_;
         to_add = std::min(num_ops, max_ops);
@@ -489,16 +489,16 @@
 }
 
 bool CowWriterV2::EmitCluster() {
-    CowOperation op = {};
+    CowOperationV2 op = {};
     op.type = kCowClusterOp;
     // Next cluster starts after remainder of current cluster and the next data block.
-    op.source = current_data_size_ + cluster_size_ - current_cluster_size_ - sizeof(CowOperation);
+    op.source = current_data_size_ + cluster_size_ - current_cluster_size_ - sizeof(CowOperationV2);
     return WriteOperation(op);
 }
 
 bool CowWriterV2::EmitClusterIfNeeded() {
     // If there isn't room for another op and the cluster end op, end the current cluster
-    if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperation)) {
+    if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperationV2)) {
         if (!EmitCluster()) return false;
     }
     return true;
@@ -539,7 +539,7 @@
         extra_cluster = true;
     }
 
-    footer_.op.ops_size = footer_.op.num_ops * sizeof(CowOperation);
+    footer_.op.ops_size = footer_.op.num_ops * sizeof(CowOperationV2);
     if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
         PLOG(ERROR) << "Failed to seek to footer position.";
         return false;
@@ -611,9 +611,9 @@
 
     if (op_vec_index_) {
         ret = pwritev(fd_.get(), cowop_vec_.get(), op_vec_index_, current_op_pos_);
-        if (ret != (op_vec_index_ * sizeof(CowOperation))) {
-            PLOG(ERROR) << "pwritev failed for CowOperation. Expected: "
-                        << (op_vec_index_ * sizeof(CowOperation));
+        if (ret != (op_vec_index_ * sizeof(CowOperationV2))) {
+            PLOG(ERROR) << "pwritev failed for CowOperationV2. Expected: "
+                        << (op_vec_index_ * sizeof(CowOperationV2));
             return false;
         }
     }
@@ -635,15 +635,16 @@
     return true;
 }
 
-bool CowWriterV2::WriteOperation(const CowOperation& op, const void* data, size_t size) {
+bool CowWriterV2::WriteOperation(const CowOperationV2& op, const void* data, size_t size) {
     if (!EnsureSpaceAvailable(next_op_pos_ + sizeof(op)) ||
         !EnsureSpaceAvailable(next_data_pos_ + size)) {
         return false;
     }
 
     if (batch_write_) {
-        CowOperation* cow_op = reinterpret_cast<CowOperation*>(cowop_vec_[op_vec_index_].iov_base);
-        std::memcpy(cow_op, &op, sizeof(CowOperation));
+        CowOperationV2* cow_op =
+                reinterpret_cast<CowOperationV2*>(cowop_vec_[op_vec_index_].iov_base);
+        std::memcpy(cow_op, &op, sizeof(CowOperationV2));
         op_vec_index_ += 1;
 
         if (data != nullptr && size > 0) {
@@ -681,7 +682,7 @@
     return EmitClusterIfNeeded();
 }
 
-void CowWriterV2::AddOperation(const CowOperation& op) {
+void CowWriterV2::AddOperation(const CowOperationV2& op) {
     footer_.op.num_ops++;
 
     if (op.type == kCowClusterOp) {
@@ -693,7 +694,7 @@
     }
 
     next_data_pos_ += op.data_length + GetNextDataOffset(op, header_.cluster_ops);
-    next_op_pos_ += sizeof(CowOperation) + GetNextOpOffset(op, header_.cluster_ops);
+    next_op_pos_ += sizeof(CowOperationV2) + GetNextOpOffset(op, header_.cluster_ops);
 }
 
 bool CowWriterV2::WriteRawData(const void* data, const size_t size) {
@@ -703,17 +704,6 @@
     return true;
 }
 
-bool CowWriterV2::Sync() {
-    if (is_dev_null_) {
-        return true;
-    }
-    if (fsync(fd_.get()) < 0) {
-        PLOG(ERROR) << "fsync failed";
-        return false;
-    }
-    return true;
-}
-
 bool CowWriterV2::Truncate(off_t length) {
     if (is_dev_null_ || is_block_device_) {
         return true;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
index 131a068..24170eb 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v2.h
@@ -50,20 +50,20 @@
     bool OpenForAppend(uint64_t label);
     bool GetDataPos(uint64_t* pos);
     bool WriteRawData(const void* data, size_t size);
-    bool WriteOperation(const CowOperation& op, const void* data = nullptr, size_t size = 0);
-    void AddOperation(const CowOperation& op);
+    bool WriteOperation(const CowOperationV2& op, const void* data = nullptr, size_t size = 0);
+    void AddOperation(const CowOperationV2& op);
     void InitPos();
     void InitBatchWrites();
     void InitWorkers();
     bool FlushCluster();
 
     bool CompressBlocks(size_t num_blocks, const void* data);
-    bool Sync();
     bool Truncate(off_t length);
     bool EnsureSpaceAvailable(const uint64_t bytes_needed) const;
 
   private:
     CowFooter footer_{};
+    CowHeader header_{};
     CowCompression compression_;
     // in the case that we are using one thread for compression, we can store and re-use the same
     // compressor
@@ -84,7 +84,7 @@
     std::vector<std::basic_string<uint8_t>> compressed_buf_;
     std::vector<std::basic_string<uint8_t>>::iterator buf_iter_;
 
-    std::vector<std::unique_ptr<CowOperation>> opbuffer_vec_;
+    std::vector<std::unique_ptr<CowOperationV2>> opbuffer_vec_;
     std::vector<std::unique_ptr<uint8_t[]>> databuffer_vec_;
     std::unique_ptr<struct iovec[]> cowop_vec_;
     int op_vec_index_ = 0;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
new file mode 100644
index 0000000..2b9867e
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -0,0 +1,117 @@
+//
+// Copyright (C) 2020 The Android Open Source_info Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "writer_v3.h"
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <brotli/encode.h>
+#include <libsnapshot/cow_format.h>
+#include <libsnapshot/cow_reader.h>
+#include <libsnapshot/cow_writer.h>
+#include <lz4.h>
+#include <zlib.h>
+
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+// The info messages here are spammy, but as useful for update_engine. Disable
+// them when running on the host.
+#ifdef __ANDROID__
+#define LOG_INFO LOG(INFO)
+#else
+#define LOG_INFO LOG(VERBOSE)
+#endif
+
+namespace android {
+namespace snapshot {
+
+static_assert(sizeof(off_t) == sizeof(uint64_t));
+
+using android::base::unique_fd;
+
+CowWriterV3::CowWriterV3(const CowOptions& options, unique_fd&& fd)
+    : CowWriterBase(options, std::move(fd)) {}
+
+CowWriterV3::~CowWriterV3() {}
+
+bool CowWriterV3::Initialize(std::optional<uint64_t> label) {
+    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
+    if (label) return false;
+    return false;
+}
+
+bool CowWriterV3::EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks) {
+    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
+    if (new_block || old_block || num_blocks) return false;
+    return false;
+}
+
+bool CowWriterV3::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
+    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
+
+    if (new_block_start || data || size) return false;
+    return false;
+}
+
+bool CowWriterV3::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
+                                uint32_t old_block, uint16_t offset) {
+    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
+    if (new_block_start || old_block || offset || data || size) return false;
+    return false;
+}
+
+bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
+    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
+    if (new_block_start && num_blocks) return false;
+    return false;
+}
+
+bool CowWriterV3::EmitLabel(uint64_t label) {
+    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
+    if (label) return false;
+    return false;
+}
+
+bool CowWriterV3::EmitSequenceData(size_t num_ops, const uint32_t* data) {
+    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
+    if (num_ops && data) return false;
+    return false;
+}
+
+bool CowWriterV3::Finalize() {
+    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
+    return false;
+}
+
+uint64_t CowWriterV3::GetCowSize() {
+    LOG(ERROR) << __LINE__ << " " << __FILE__
+               << " <- Get Cow Size function here should never be called";
+    return 0;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
new file mode 100644
index 0000000..ddd7287
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <future>
+#include "writer_base.h"
+
+namespace android {
+namespace snapshot {
+
+class CowWriterV3 : public CowWriterBase {
+  public:
+    explicit CowWriterV3(const CowOptions& options, android::base::unique_fd&& fd);
+    ~CowWriterV3() override;
+
+    bool Initialize(std::optional<uint64_t> label = {}) override;
+    bool Finalize() override;
+    uint64_t GetCowSize() override;
+
+  protected:
+    virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
+    virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
+    virtual bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
+                               uint32_t old_block, uint16_t offset) override;
+    virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
+    virtual bool EmitLabel(uint64_t label) override;
+    virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index 7057223..5bc7e65 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -131,15 +131,28 @@
     return is_optimized;
 }
 
-void WriteExtent(DmSnapCowSizeCalculator* sc, const chromeos_update_engine::Extent& de,
+bool WriteExtent(DmSnapCowSizeCalculator* sc, const chromeos_update_engine::Extent& de,
                  unsigned int sectors_per_block) {
     const auto block_boundary = de.start_block() + de.num_blocks();
     for (auto b = de.start_block(); b < block_boundary; ++b) {
         for (unsigned int s = 0; s < sectors_per_block; ++s) {
-            const auto sector_id = b * sectors_per_block + s;
+            // sector_id = b * sectors_per_block + s;
+            uint64_t block_start_sector_id;
+            if (__builtin_mul_overflow(b, sectors_per_block, &block_start_sector_id)) {
+                LOG(ERROR) << "Integer overflow when calculating sector id (" << b << " * "
+                           << sectors_per_block << ")";
+                return false;
+            }
+            uint64_t sector_id;
+            if (__builtin_add_overflow(block_start_sector_id, s, &sector_id)) {
+                LOG(ERROR) << "Integer overflow when calculating sector id ("
+                           << block_start_sector_id << " + " << s << ")";
+                return false;
+            }
             sc->WriteSector(sector_id);
         }
     }
+    return true;
 }
 
 std::optional<uint64_t> PartitionCowCreator::GetCowSize() {
@@ -167,7 +180,7 @@
     // Allocate space for extra extents (if any). These extents are those that can be
     // used for error corrections or to store verity hash trees.
     for (const auto& de : extra_extents) {
-        WriteExtent(&sc, de, sectors_per_block);
+        if (!WriteExtent(&sc, de, sectors_per_block)) return std::nullopt;
     }
 
     if (update == nullptr) return sc.cow_size_bytes();
@@ -182,7 +195,7 @@
         }
 
         for (const auto& de : written_op->dst_extents()) {
-            WriteExtent(&sc, de, sectors_per_block);
+            if (!WriteExtent(&sc, de, sectors_per_block)) return std::nullopt;
         }
     }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index 71664bf..0be6ff5 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -25,6 +25,7 @@
 #include <csignal>
 #include <optional>
 #include <set>
+#include "libsnapshot/cow_format.h"
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -406,9 +407,9 @@
             break;
         }
 
-        if (cow_op->type == kCowReplaceOp) {
+        if (GetCowOpSourceInfoType(*cow_op) == kCowReplaceOp) {
             replace_ops++;
-        } else if (cow_op->type == kCowZeroOp) {
+        } else if (GetCowOpSourceInfoType(*cow_op) == kCowZeroOp) {
             zero_ops++;
         }
 
@@ -508,7 +509,7 @@
             // the merge of operations are done based on the ops present
             // in the file.
             //===========================================================
-            uint64_t block_source = GetCowOpSourceInfoData(cow_op);
+            uint64_t block_source = GetCowOpSourceInfoData(*cow_op);
             if (prev_id.has_value()) {
                 if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source)) {
                     break;
@@ -540,7 +541,7 @@
             chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), cow_op));
             offset += sizeof(struct disk_exception);
             num_ops += 1;
-            if (cow_op->type == kCowCopyOp) {
+            if (GetCowOpSourceInfoType(*cow_op) == kCowCopyOp) {
                 copy_ops++;
             }
 
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 d5fbe91..b2a4f2c 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "libsnapshot/cow_format.h"
 #include "snapuserd.h"
 
 #include <csignal>
@@ -172,7 +173,7 @@
 }
 
 void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
-    uint64_t source_block = GetCowOpSourceInfoData(cow_op);
+    uint64_t source_block = GetCowOpSourceInfoData(*cow_op);
     if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block)) {
         overlap_ = true;
     }
@@ -191,8 +192,8 @@
         // Get the first block with offset
         const CowOperation* cow_op = GetRAOpIter();
         CHECK_NE(cow_op, nullptr);
-        *source_offset = GetCowOpSourceInfoData(cow_op);
-        if (cow_op->type == kCowCopyOp) {
+        *source_offset = GetCowOpSourceInfoData(*cow_op);
+        if (GetCowOpSourceInfoData(*cow_op) == kCowCopyOp) {
             *source_offset *= BLOCK_SZ;
         }
         RAIterNext();
@@ -210,8 +211,8 @@
         while (!RAIterDone() && num_ops) {
             const CowOperation* op = GetRAOpIter();
             CHECK_NE(op, nullptr);
-            uint64_t next_offset = GetCowOpSourceInfoData(op);
-            if (op->type == kCowCopyOp) {
+            uint64_t next_offset = GetCowOpSourceInfoData(*op);
+            if (GetCowOpSourceInfoData(*op) == kCowCopyOp) {
                 next_offset *= BLOCK_SZ;
             }
             if (next_offset + nr_consecutive * BLOCK_SZ != *source_offset) {
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 571b352..0930a5d 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "libsnapshot/cow_format.h"
 #include "snapuserd.h"
 
 #include <csignal>
@@ -177,7 +178,7 @@
         return false;
     }
 
-    switch (cow_op->type) {
+    switch (GetCowOpSourceInfoType(*cow_op)) {
         case kCowReplaceOp: {
             return ProcessReplaceOp(cow_op);
         }
@@ -191,7 +192,8 @@
         }
 
         default: {
-            SNAP_LOG(ERROR) << "Unsupported operation-type found: " << cow_op->type;
+            SNAP_LOG(ERROR) << "Unsupported operation-type found: "
+                            << GetCowOpSourceInfoType(*cow_op);
         }
     }
     return false;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
index 11b8d7c..d06ba58 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/merge_worker.cpp
@@ -17,6 +17,7 @@
 
 #include <pthread.h>
 
+#include "libsnapshot/cow_format.h"
 #include "snapuserd_core.h"
 #include "utility.h"
 
@@ -114,13 +115,13 @@
                 SNAP_LOG(ERROR) << "AcquireBuffer failed in MergeReplaceOps";
                 return false;
             }
-            if (cow_op->type == kCowReplaceOp) {
+            if (GetCowOpSourceInfoType(*cow_op) == kCowReplaceOp) {
                 if (!reader_->ReadData(cow_op, buffer, BLOCK_SZ)) {
                     SNAP_LOG(ERROR) << "Failed to read COW in merge";
                     return false;
                 }
             } else {
-                CHECK(cow_op->type == kCowZeroOp);
+                CHECK(GetCowOpSourceInfoType(*cow_op) == kCowZeroOp);
                 memset(buffer, 0, BLOCK_SZ);
             }
         }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
index 5cb13e8..ffa23ea 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/read_worker.cpp
@@ -18,6 +18,7 @@
 
 #include <pthread.h>
 
+#include "libsnapshot/cow_format.h"
 #include "snapuserd_core.h"
 #include "utility.h"
 
@@ -63,7 +64,7 @@
                     << " Op: " << *cow_op;
     if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) {
         std::string op;
-        if (cow_op->type == kCowCopyOp)
+        if (GetCowOpSourceInfoType(*cow_op) == kCowCopyOp)
             op = "Copy-op";
         else {
             op = "Xor-op";
@@ -133,7 +134,7 @@
         }
         case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: {
             bool ret;
-            if (cow_op->type == kCowCopyOp) {
+            if (GetCowOpSourceInfoType(*cow_op) == kCowCopyOp) {
                 ret = ProcessCopyOp(cow_op, buffer);
             } else {
                 ret = ProcessXorOp(cow_op, buffer);
@@ -167,7 +168,7 @@
         return false;
     }
 
-    switch (cow_op->type) {
+    switch (GetCowOpSourceInfoType(*cow_op)) {
         case kCowReplaceOp: {
             return ProcessReplaceOp(cow_op, buffer);
         }
@@ -183,7 +184,7 @@
         }
 
         default: {
-            SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+            SNAP_LOG(ERROR) << "Unknown operation-type found: " << GetCowOpSourceInfoType(*cow_op);
         }
     }
     return false;
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 c295851..536ff2f 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -24,6 +24,7 @@
 #include <android-base/strings.h>
 #include <snapuserd/dm_user_block_server.h>
 
+#include "libsnapshot/cow_format.h"
 #include "merge_worker.h"
 #include "read_worker.h"
 
@@ -173,6 +174,10 @@
     }
 
     SNAP_LOG(INFO) << "Merge-ops: " << header.num_merge_ops;
+    if (header.num_merge_ops) {
+        resume_merge_ = true;
+        SNAP_LOG(INFO) << "Resume Snapshot-merge";
+    }
 
     if (!MmapMetadata()) {
         SNAP_LOG(ERROR) << "mmap failed";
@@ -192,13 +197,13 @@
     while (!cowop_iter->AtEnd()) {
         const CowOperation* cow_op = cowop_iter->Get();
 
-        if (cow_op->type == kCowCopyOp) {
+        if (GetCowOpSourceInfoType(*cow_op) == kCowCopyOp) {
             copy_ops += 1;
-        } else if (cow_op->type == kCowReplaceOp) {
+        } else if (GetCowOpSourceInfoType(*cow_op) == kCowReplaceOp) {
             replace_ops += 1;
-        } else if (cow_op->type == kCowZeroOp) {
+        } else if (GetCowOpSourceInfoType(*cow_op) == kCowZeroOp) {
             zero_ops += 1;
-        } else if (cow_op->type == kCowXorOp) {
+        } else if (GetCowOpSourceInfoType(*cow_op) == kCowXorOp) {
             xor_ops += 1;
         }
 
@@ -295,6 +300,11 @@
     if (ra_thread_) {
         ra_thread_status =
                 std::async(std::launch::async, &ReadAhead::RunThread, read_ahead_thread_.get());
+        // If this is a merge-resume path, wait until RA thread is fully up as
+        // the data has to be re-constructed from the scratch space.
+        if (resume_merge_ && ShouldReconstructDataFromCow()) {
+            WaitForRaThreadToStart();
+        }
     }
 
     // Launch worker threads
@@ -307,7 +317,9 @@
             std::async(std::launch::async, &MergeWorker::Run, merge_thread_.get());
 
     // Now that the worker threads are up, scan the partitions.
-    if (perform_verification_) {
+    // If the snapshot-merge is being resumed, there is no need to scan as the
+    // current slot is already marked as boot complete.
+    if (perform_verification_ && !resume_merge_) {
         update_verify_->VerifyUpdatePartition();
     }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
index e401c11..f88406d 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -147,6 +147,8 @@
     void WakeupMonitorMergeThread();
     void WaitForMergeComplete();
     bool WaitForMergeBegin();
+    void RaThreadStarted();
+    void WaitForRaThreadToStart();
     void NotifyRAForMergeReady();
     bool WaitForMergeReady();
     void MergeFailed();
@@ -221,6 +223,7 @@
     // Read-ahead related
     bool populate_data_from_cow_ = false;
     bool ra_thread_ = false;
+    bool ra_thread_started_ = false;
     int total_ra_blocks_merged_ = 0;
     MERGE_IO_TRANSITION io_state_ = MERGE_IO_TRANSITION::INVALID;
     std::unique_ptr<ReadAhead> read_ahead_thread_;
@@ -242,6 +245,7 @@
     bool scratch_space_ = false;
     int num_worker_threads_ = kNumWorkerThreads;
     bool perform_verification_ = true;
+    bool resume_merge_ = false;
 
     std::unique_ptr<struct io_uring> ring_;
     std::unique_ptr<UpdateVerify> update_verify_;
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 d2128c5..d7d43a6 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -18,6 +18,7 @@
 
 #include <pthread.h>
 
+#include "libsnapshot/cow_format.h"
 #include "snapuserd_core.h"
 #include "utility.h"
 
@@ -77,7 +78,7 @@
         SNAP_LOG(ERROR) << "PrepareNextReadAhead operation has no source offset: " << *cow_op;
         return nr_consecutive;
     }
-    if (cow_op->type == kCowXorOp) {
+    if (GetCowOpSourceInfoType(*cow_op) == kCowXorOp) {
         xor_op_vec.push_back(cow_op);
     }
 
@@ -106,7 +107,7 @@
             break;
         }
 
-        if (op->type == kCowXorOp) {
+        if (GetCowOpSourceInfoType(*op) == kCowXorOp) {
             xor_op_vec.push_back(op);
         }
 
@@ -206,6 +207,7 @@
         return false;
     }
 
+    snapuserd_->RaThreadStarted();
     SNAP_LOG(INFO) << "ReconstructDataFromCow success";
     notify_read_ahead_failed.Cancel();
     return true;
@@ -716,9 +718,13 @@
     total_ra_blocks_completed_ += total_blocks_merged_;
     snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged_);
 
-    // Flush the data only if we have a overlapping blocks in the region
+    // Flush the scratch data - Technically, we should flush only for overlapping
+    // blocks; However, since this region is mmap'ed, the dirty pages can still
+    // get flushed to disk at any random point in time. Instead, make sure
+    // the data in scratch is in the correct state before merge thread resumes.
+    //
     // Notify the Merge thread to resume merging this window
-    if (!snapuserd_->ReadAheadIOCompleted(overlap_)) {
+    if (!snapuserd_->ReadAheadIOCompleted(true)) {
         SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
         snapuserd_->ReadAheadIOFailed();
         return false;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index 620ecbd..65f31cf 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -235,9 +235,11 @@
     bool Merge();
     void ValidateMerge();
     void ReadSnapshotDeviceAndValidate();
+    void ReadSnapshotAndValidateOverlappingBlocks();
     void Shutdown();
     void MergeInterrupt();
     void MergeInterruptFixed(int duration);
+    void MergeInterruptAndValidate(int duration);
     void MergeInterruptRandomly(int max_duration);
     bool StartMerge();
     void CheckMergeCompletion();
@@ -358,6 +360,76 @@
     ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0);
 }
 
+void SnapuserdTest::ReadSnapshotAndValidateOverlappingBlocks() {
+    // Open COW device
+    unique_fd fd(open(cow_system_->path, O_RDONLY));
+    ASSERT_GE(fd, 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(fd));
+
+    const auto& header = reader.GetHeader();
+    size_t total_mapped_addr_length = header.prefix.header_size + BUFFER_REGION_DEFAULT_SIZE;
+
+    ASSERT_GE(header.prefix.major_version, 2);
+
+    void* mapped_addr = mmap(NULL, total_mapped_addr_length, PROT_READ, MAP_SHARED, fd.get(), 0);
+    ASSERT_NE(mapped_addr, MAP_FAILED);
+
+    bool populate_data_from_scratch = false;
+    struct BufferState* ra_state =
+            reinterpret_cast<struct BufferState*>((char*)mapped_addr + header.prefix.header_size);
+    if (ra_state->read_ahead_state == kCowReadAheadDone) {
+        populate_data_from_scratch = true;
+    }
+
+    size_t num_merge_ops = header.num_merge_ops;
+    // We have some partial merge operations completed.
+    // To test the merge-resume path, forcefully corrupt the data of the base
+    // device for the offsets where the merge is still pending.
+    if (num_merge_ops && populate_data_from_scratch) {
+        std::string corrupt_buffer(4096, 0);
+        // Corrupt two blocks from the point where the merge has to be resumed by
+        // writing down zeroe's.
+        //
+        // Now, since this is a merge-resume path, the "correct" data should be
+        // in the scratch space of the COW device. When there is an I/O request
+        // from the snapshot device, the data has to be retrieved from the
+        // scratch space. If not and I/O is routed to the base device, we
+        // may end up with corruption.
+        off_t corrupt_offset = (num_merge_ops + 2) * 4096;
+
+        if (corrupt_offset < size_) {
+            ASSERT_EQ(android::base::WriteFullyAtOffset(base_fd_, (void*)corrupt_buffer.c_str(),
+                                                        4096, corrupt_offset),
+                      true);
+            corrupt_offset -= 4096;
+            ASSERT_EQ(android::base::WriteFullyAtOffset(base_fd_, (void*)corrupt_buffer.c_str(),
+                                                        4096, corrupt_offset),
+                      true);
+            fsync(base_fd_.get());
+        }
+    }
+
+    // Time to read the snapshot device.
+    unique_fd snapshot_fd(open(dmuser_dev_->GetPath().c_str(), O_RDONLY | O_DIRECT | O_SYNC));
+    ASSERT_GE(snapshot_fd, 0);
+
+    void* buff_addr;
+    ASSERT_EQ(posix_memalign(&buff_addr, 4096, size_), 0);
+
+    std::unique_ptr<void, decltype(&::free)> snapshot_buffer(buff_addr, ::free);
+
+    // Scan the entire snapshot device and read the data and verify data
+    // integrity. Since the base device was forcefully corrupted, the data from
+    // this scan should be retrieved from scratch space of the COW partition.
+    //
+    // Furthermore, after the merge is complete, base device data is again
+    // verified as the aforementioned corrupted blocks aren't persisted.
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapshot_buffer.get(), size_, 0), true);
+    ASSERT_EQ(memcmp(snapshot_buffer.get(), orig_buffer_.get(), size_), 0);
+}
+
 void SnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
     auto writer = CreateCowDeviceInternal();
     ASSERT_NE(writer, nullptr);
@@ -665,6 +737,20 @@
     ASSERT_TRUE(Merge());
 }
 
+void SnapuserdTest::MergeInterruptAndValidate(int duration) {
+    ASSERT_TRUE(StartMerge());
+
+    for (int i = 0; i < 15; i++) {
+        std::this_thread::sleep_for(std::chrono::milliseconds(duration));
+        ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
+        ReadSnapshotAndValidateOverlappingBlocks();
+        ASSERT_TRUE(StartMerge());
+    }
+
+    ASSERT_NO_FATAL_FAILURE(SimulateDaemonRestart());
+    ASSERT_TRUE(Merge());
+}
+
 void SnapuserdTest::MergeInterrupt() {
     // Interrupt merge at various intervals
     ASSERT_TRUE(StartMerge());
@@ -761,6 +847,15 @@
     ValidateMerge();
 }
 
+TEST_F(SnapuserdTest, Snapshot_COPY_Overlap_Merge_Resume_IO_Validate_TEST) {
+    if (!harness_->HasUserDevice()) {
+        GTEST_SKIP() << "Skipping snapshot read; not supported";
+    }
+    ASSERT_NO_FATAL_FAILURE(SetupCopyOverlap_2());
+    ASSERT_NO_FATAL_FAILURE(MergeInterruptAndValidate(2));
+    ValidateMerge();
+}
+
 TEST_F(SnapuserdTest, Snapshot_Merge_Crash_Fixed_Ordered) {
     ASSERT_NO_FATAL_FAILURE(SetupOrderedOps());
     ASSERT_NO_FATAL_FAILURE(MergeInterruptFixed(300));
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
index f3e0019..8d090bf 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_transitions.cpp
@@ -366,6 +366,26 @@
     }
 }
 
+void SnapshotHandler::RaThreadStarted() {
+    std::unique_lock<std::mutex> lock(lock_);
+    ra_thread_started_ = true;
+}
+
+void SnapshotHandler::WaitForRaThreadToStart() {
+    auto now = std::chrono::system_clock::now();
+    auto deadline = now + 3s;
+    {
+        std::unique_lock<std::mutex> lock(lock_);
+        while (!ra_thread_started_) {
+            auto status = cv.wait_until(lock, deadline);
+            if (status == std::cv_status::timeout) {
+                SNAP_LOG(ERROR) << "Read-ahead thread did not start";
+                return;
+            }
+        }
+    }
+}
+
 std::string SnapshotHandler::GetMergeStatus() {
     bool merge_not_initiated = false;
     bool merge_monitored = false;
@@ -618,7 +638,6 @@
     std::unordered_map<uint64_t, void*>::iterator it = read_ahead_buffer_map_.find(block);
 
     if (it == read_ahead_buffer_map_.end()) {
-        SNAP_LOG(ERROR) << "Block: " << block << " not found in RA buffer";
         return false;
     }
 
@@ -642,6 +661,13 @@
         MERGE_GROUP_STATE state = blk_state->merge_state_;
         switch (state) {
             case MERGE_GROUP_STATE::GROUP_MERGE_PENDING: {
+                // If this is a merge-resume path, check if the data is
+                // available from scratch space. Data from scratch space takes
+                // higher precedence than from source device for overlapping
+                // blocks.
+                if (resume_merge_ && GetRABuffer(&lock, new_block, buffer)) {
+                    return (MERGE_GROUP_STATE::GROUP_MERGE_IN_PROGRESS);
+                }
                 blk_state->num_ios_in_progress += 1;  // ref count
                 [[fallthrough]];
             }
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 2064fae..cdd0afe 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -1461,8 +1461,6 @@
             work_.pop_front();
         }
 
-        std::this_thread::sleep_for(1s);
-
         // Perform write/fsync outside the lock.
         WritePersistentProperty(std::get<0>(item), std::get<1>(item));
         NotifyPropertyChange(std::get<0>(item), std::get<1>(item));
diff --git a/libcutils/socket_local_unix.h b/libcutils/socket_local_unix.h
index 45b9856..ea98c08 100644
--- a/libcutils/socket_local_unix.h
+++ b/libcutils/socket_local_unix.h
@@ -17,6 +17,8 @@
 #ifndef __SOCKET_LOCAL_H
 #define __SOCKET_LOCAL_H
 
+#include <sys/socket.h>
+
 #define FILESYSTEM_SOCKET_PREFIX "/tmp/" 
 #define ANDROID_RESERVED_SOCKET_PREFIX "/dev/socket/"
 
diff --git a/libcutils/sockets_windows.cpp b/libcutils/sockets_windows.cpp
index 4adb796..99a2e2d 100644
--- a/libcutils/sockets_windows.cpp
+++ b/libcutils/sockets_windows.cpp
@@ -35,7 +35,7 @@
 // can be extremely tricky and cause deadlock when using threads or atexit().
 //
 // Both adb (1) and Chrome (2) purposefully avoid WSACleanup() with no issues.
-// (1) https://android.googlesource.com/platform/system/core.git/+/master/adb/sysdeps_win32.cpp
+// (1) https://android.googlesource.com/platform/packages/modules/adb.git/+/main/sysdeps_win32.cpp
 // (2) https://code.google.com/p/chromium/codesearch#chromium/src/net/base/winsock_init.cc
 bool initialize_windows_sockets() {
     // There's no harm in calling WSAStartup() multiple times but no benefit
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 4506439..cc2565f 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -219,7 +219,7 @@
 
     while (retries--) {
         ret = rmdir(uid_pid_path.c_str());
-        if (!ret || errno != EBUSY) break;
+        if (!ret || errno != EBUSY || !retries) break;
         std::this_thread::sleep_for(5ms);
     }
 
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index d013ec8..3e4393d 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -1,6 +1,13 @@
 {
   "Cgroups": [
     {
+      "Controller": "blkio",
+      "Path": "/dev/blkio",
+      "Mode": "0775",
+      "UID": "system",
+      "GID": "system"
+    },
+    {
       "Controller": "cpu",
       "Path": "/dev/cpuctl",
       "Mode": "0755",
@@ -32,12 +39,6 @@
       {
         "Controller": "freezer",
         "Path": "."
-      },
-      {
-        "Controller": "io",
-        "Path": ".",
-        "NeedsActivation": true,
-        "Optional": true
       }
     ]
   }
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 12f7b44..1fc66ba 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -76,24 +76,6 @@
       "Name": "FreezerState",
       "Controller": "freezer",
       "File": "cgroup.freeze"
-    },
-    {
-      "Name": "BfqWeight",
-      "Controller": "io",
-      "File": "blkio.bfq.weight",
-      "FileV2": "io.bfq.weight"
-    },
-    {
-      "Name": "CfqGroupIdle",
-      "Controller": "io",
-      "File": "blkio.group_idle",
-      "FileV2": "io.group_idle"
-    },
-    {
-      "Name": "CfqWeight",
-      "Controller": "io",
-      "File": "blkio.weight",
-      "FileV2": "io.weight"
     }
   ],
 
@@ -457,30 +439,11 @@
       "Name": "LowIoPriority",
       "Actions": [
         {
-          "Name": "SetAttribute",
+          "Name": "JoinCgroup",
           "Params":
           {
-            "Name": "BfqWeight",
-            "Value": "10",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqGroupIdle",
-            "Value": "0",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqWeight",
-            "Value": "200",
-            "Optional": "true"
+            "Controller": "blkio",
+            "Path": "background"
           }
         }
       ]
@@ -489,30 +452,11 @@
       "Name": "NormalIoPriority",
       "Actions": [
         {
-          "Name": "SetAttribute",
+          "Name": "JoinCgroup",
           "Params":
           {
-            "Name": "BfqWeight",
-            "Value": "100",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqGroupIdle",
-            "Value": "0",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqWeight",
-            "Value": "1000",
-            "Optional": "true"
+            "Controller": "blkio",
+            "Path": ""
           }
         }
       ]
@@ -521,30 +465,11 @@
       "Name": "HighIoPriority",
       "Actions": [
         {
-          "Name": "SetAttribute",
+          "Name": "JoinCgroup",
           "Params":
           {
-            "Name": "BfqWeight",
-            "Value": "100",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqGroupIdle",
-            "Value": "0",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqWeight",
-            "Value": "1000",
-            "Optional": "true"
+            "Controller": "blkio",
+            "Path": ""
           }
         }
       ]
@@ -553,30 +478,11 @@
       "Name": "MaxIoPriority",
       "Actions": [
         {
-          "Name": "SetAttribute",
+          "Name": "JoinCgroup",
           "Params":
           {
-            "Name": "BfqWeight",
-            "Value": "100",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqGroupIdle",
-            "Value": "0",
-            "Optional": "true"
-          }
-        },
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "CfqWeight",
-            "Value": "1000",
-            "Optional": "true"
+            "Controller": "blkio",
+            "Path": ""
           }
         }
       ]
diff --git a/rootdir/etc/public.libraries.android.txt b/rootdir/etc/public.libraries.android.txt
index cacc47c..9be2ef6 100644
--- a/rootdir/etc/public.libraries.android.txt
+++ b/rootdir/etc/public.libraries.android.txt
@@ -1,4 +1,4 @@
-# See https://android.googlesource.com/platform/ndk/+/master/docs/PlatformApis.md
+# See https://android.googlesource.com/platform/ndk/+/main/docs/PlatformApis.md
 libandroid.so
 libaaudio.so
 libamidi.so
diff --git a/rootdir/etc/public.libraries.iot.txt b/rootdir/etc/public.libraries.iot.txt
index 77f8bb8..0513968 100644
--- a/rootdir/etc/public.libraries.iot.txt
+++ b/rootdir/etc/public.libraries.iot.txt
@@ -1,4 +1,4 @@
-# See https://android.googlesource.com/platform/ndk/+/master/docs/PlatformApis.md
+# See https://android.googlesource.com/platform/ndk/+/main/docs/PlatformApis.md
 libandroid.so
 libandroidthings.so
 libaaudio.so
diff --git a/rootdir/etc/public.libraries.wear.txt b/rootdir/etc/public.libraries.wear.txt
index ea1e234..5915dcb 100644
--- a/rootdir/etc/public.libraries.wear.txt
+++ b/rootdir/etc/public.libraries.wear.txt
@@ -1,4 +1,4 @@
-# See https://android.googlesource.com/platform/ndk/+/master/docs/PlatformApis.md
+# See https://android.googlesource.com/platform/ndk/+/main/docs/PlatformApis.md
 libandroid.so
 libaaudio.so
 libamidi.so
diff --git a/rootdir/init.rc b/rootdir/init.rc
index fb64736..317f809 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -219,6 +219,26 @@
     write /dev/stune/nnapi-hal/schedtune.boost 1
     write /dev/stune/nnapi-hal/schedtune.prefer_idle 1
 
+    # Create blkio group and apply initial settings.
+    # This feature needs kernel to support it, and the
+    # device's init.rc must actually set the correct values.
+    mkdir /dev/blkio/background
+    chown system system /dev/blkio
+    chown system system /dev/blkio/background
+    chown system system /dev/blkio/tasks
+    chown system system /dev/blkio/background/tasks
+    chown system system /dev/blkio/cgroup.procs
+    chown system system /dev/blkio/background/cgroup.procs
+    chmod 0664 /dev/blkio/tasks
+    chmod 0664 /dev/blkio/background/tasks
+    chmod 0664 /dev/blkio/cgroup.procs
+    chmod 0664 /dev/blkio/background/cgroup.procs
+    write /dev/blkio/blkio.weight 1000
+    write /dev/blkio/background/blkio.weight 200
+    write /dev/blkio/background/blkio.bfq.weight 10
+    write /dev/blkio/blkio.group_idle 0
+    write /dev/blkio/background/blkio.group_idle 0
+
     restorecon_recursive /mnt
 
     mount configfs none /config nodev noexec nosuid
diff --git a/trusty/fuzz/tipc_fuzzer.cpp b/trusty/fuzz/tipc_fuzzer.cpp
index f265ced..edc2a79 100644
--- a/trusty/fuzz/tipc_fuzzer.cpp
+++ b/trusty/fuzz/tipc_fuzzer.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <android-base/result.h>
+#include <fuzzer/FuzzedDataProvider.h>
 #include <stdlib.h>
 #include <trusty/coverage/coverage.h>
 #include <trusty/coverage/uuid.h>
@@ -23,6 +25,7 @@
 #include <iostream>
 #include <memory>
 
+using android::base::Result;
 using android::trusty::coverage::CoverageRecord;
 using android::trusty::fuzz::ExtraCounters;
 using android::trusty::fuzz::TrustyApp;
@@ -41,7 +44,14 @@
 #error "Binary file name must be parameterized using -DTRUSTY_APP_FILENAME."
 #endif
 
-static TrustyApp kTrustyApp(TIPC_DEV, TRUSTY_APP_PORT);
+#ifdef TRUSTY_APP_MAX_CONNECTIONS
+constexpr size_t MAX_CONNECTIONS = TRUSTY_APP_MAX_CONNECTIONS;
+#else
+constexpr size_t MAX_CONNECTIONS = 1;
+#endif
+
+static_assert(MAX_CONNECTIONS >= 1);
+
 static std::unique_ptr<CoverageRecord> record;
 
 extern "C" int LLVMFuzzerInitialize(int* /* argc */, char*** /* argv */) {
@@ -53,7 +63,8 @@
     }
 
     /* Make sure lazy-loaded TAs have started and connected to coverage service. */
-    auto ret = kTrustyApp.Connect();
+    TrustyApp ta(TIPC_DEV, TRUSTY_APP_PORT);
+    auto ret = ta.Connect();
     if (!ret.ok()) {
         std::cerr << ret.error() << std::endl;
         exit(-1);
@@ -73,24 +84,56 @@
     return 0;
 }
 
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    static uint8_t buf[TIPC_MAX_MSG_SIZE];
+Result<void> testOneInput(FuzzedDataProvider& provider) {
+    std::vector<TrustyApp> trustyApps;
 
+    while (provider.remaining_bytes() > 0) {
+        if (trustyApps.size() < MAX_CONNECTIONS && provider.ConsumeBool()) {
+            auto& ta = trustyApps.emplace_back(TIPC_DEV, TRUSTY_APP_PORT);
+            const auto result = ta.Connect();
+            if (!result.ok()) {
+                return result;
+            }
+        } else {
+            const auto i = provider.ConsumeIntegralInRange<size_t>(0, trustyApps.size());
+            std::swap(trustyApps[i], trustyApps.back());
+
+            if (provider.ConsumeBool()) {
+                auto& ta = trustyApps.back();
+
+                const auto data = provider.ConsumeRandomLengthString();
+                auto result = ta.Write(data.data(), data.size());
+                if (!result.ok()) {
+                    return result;
+                }
+
+                std::array<uint8_t, TIPC_MAX_MSG_SIZE> buf;
+                result = ta.Read(buf.data(), buf.size());
+                if (!result.ok()) {
+                    return result;
+                }
+
+                // Reconnect to ensure that the service is still up.
+                ta.Disconnect();
+                result = ta.Connect();
+                if (!result.ok()) {
+                    std::cerr << result.error() << std::endl;
+                    android::trusty::fuzz::Abort();
+                    return result;
+                }
+            } else {
+                trustyApps.pop_back();
+            }
+        }
+    }
+    return {};
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     ExtraCounters counters(record.get());
     counters.Reset();
 
-    auto ret = kTrustyApp.Write(data, size);
-    if (ret.ok()) {
-        ret = kTrustyApp.Read(&buf, sizeof(buf));
-    }
-
-    // Reconnect to ensure that the service is still up
-    kTrustyApp.Disconnect();
-    ret = kTrustyApp.Connect();
-    if (!ret.ok()) {
-        std::cerr << ret.error() << std::endl;
-        android::trusty::fuzz::Abort();
-    }
-
-    return ret.ok() ? 0 : -1;
+    FuzzedDataProvider provider(data, size);
+    const auto result = testOneInput(provider);
+    return result.ok() ? 0 : -1;
 }
diff --git a/trusty/stats/test/README.md b/trusty/stats/test/README.md
index 45e6af8..175409e 100644
--- a/trusty/stats/test/README.md
+++ b/trusty/stats/test/README.md
@@ -1,8 +1,8 @@
 # Development Notes
 
-*    First get [repo_pull.py and gerrit.py](https://android.googlesource.com/platform/development/+/master/tools/repo_pull/) from aosp.
+*    First get [repo_pull.py and gerrit.py](https://android.googlesource.com/platform/development/+/main/tools/repo_pull/) from aosp.
 
-*    Although this repo is not currently in Trusty’s manifest, it’s sufficient to copy these two python scripts to the root of the Trusty project and run them from there. Make sure to follow the [repo_pull installation](https://android.googlesource.com/platform/development/+/master/tools/repo_pull/#installation) steps if necessary.
+*    Although this repo is not currently in Trusty’s manifest, it’s sufficient to copy these two python scripts to the root of the Trusty project and run them from there. Make sure to follow the [repo_pull installation](https://android.googlesource.com/platform/development/+/main/tools/repo_pull/#installation) steps if necessary.
 
 ## Build
 
diff --git a/trusty/utils/acvp/trusty_modulewrapper.cpp b/trusty/utils/acvp/trusty_modulewrapper.cpp
index 70ffb52..85b7159 100644
--- a/trusty/utils/acvp/trusty_modulewrapper.cpp
+++ b/trusty/utils/acvp/trusty_modulewrapper.cpp
@@ -21,15 +21,16 @@
 #include <android-base/result.h>
 #include <android-base/unique_fd.h>
 #include <errno.h>
+#include <iostream>
 #include <log/log.h>
 #include <modulewrapper.h>
 #include <openssl/span.h>
 #include <stdint.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/mman.h>
 #include <trusty/tipc.h>
 #include <unistd.h>
-#include <iostream>
 
 #include "acvp_ipc.h"
 
@@ -208,6 +209,11 @@
     return {};
 }
 
+static bool EqString(bssl::Span<const uint8_t> cmd, const char *str) {
+    return cmd.size() == strlen(str) &&
+           memcmp(str, cmd.data(), cmd.size()) == 0;
+}
+
 int main() {
     for (;;) {
         auto buffer = bssl::acvp::RequestBuffer::New();
@@ -217,17 +223,24 @@
             return EXIT_FAILURE;
         }
 
-        ModuleWrapper wrapper;
-        auto res = wrapper.SendMessage(args);
-        if (!res.ok()) {
-            std::cerr << res.error() << std::endl;
-            return EXIT_FAILURE;
-        }
+        if (EqString(args[0], "flush")) {
+            if (!bssl::acvp::FlushBuffer(STDOUT_FILENO)) {
+                ALOGE("Could not flush the buffer to stdout\n");
+                return EXIT_FAILURE;
+            }
+        } else {
+            ModuleWrapper wrapper;
+            auto res = wrapper.SendMessage(args);
+            if (!res.ok()) {
+                std::cerr << res.error() << std::endl;
+                return EXIT_FAILURE;
+            }
 
-        res = wrapper.ForwardResponse();
-        if (!res.ok()) {
-            std::cerr << res.error() << std::endl;
-            return EXIT_FAILURE;
+            res = wrapper.ForwardResponse();
+            if (!res.ok()) {
+                std::cerr << res.error() << std::endl;
+                return EXIT_FAILURE;
+            }
         }
     }