Merge "Added defaults for storaged fuzzers"
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index cdcd036..f09616a 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -95,6 +95,8 @@
 using namespace std::string_literals;
 using namespace std::placeholders;
 
+#define FASTBOOT_INFO_VERSION 1
+
 static const char* serial = nullptr;
 
 static bool g_long_listing = false;
@@ -1663,20 +1665,6 @@
     return;
 }
 
-static bool IsNumber(const std::string& s) {
-    bool period = false;
-    for (size_t i = 0; i < s.length(); i++) {
-        if (!isdigit(s[i])) {
-            if (!period && s[i] == '.' && i != 0 && i != s.length() - 1) {
-                period = true;
-            } else {
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
 static bool IsIgnore(const std::vector<std::string>& command) {
     if (command[0][0] == '#') {
         return true;
@@ -1684,7 +1672,8 @@
     return false;
 }
 
-bool CheckFastbootInfoRequirements(const std::vector<std::string>& command) {
+bool CheckFastbootInfoRequirements(const std::vector<std::string>& command,
+                                   uint32_t host_tool_version) {
     if (command.size() != 2) {
         LOG(ERROR) << "unknown characters in version info in fastboot-info.txt -> "
                    << android::base::Join(command, " ");
@@ -1696,18 +1685,20 @@
         return false;
     }
 
-    if (!IsNumber(command[1])) {
-        LOG(ERROR) << "version number contains non-numeric values in fastboot-info.txt -> "
+    uint32_t fastboot_info_version;
+    if (!android::base::ParseUint(command[1], &fastboot_info_version)) {
+        LOG(ERROR) << "version number contains non-numeric characters in fastboot-info.txt -> "
                    << android::base::Join(command, " ");
         return false;
     }
 
     LOG(VERBOSE) << "Checking 'fastboot-info.txt version'";
-    if (command[1] < PLATFORM_TOOLS_VERSION) {
+    if (fastboot_info_version <= host_tool_version) {
         return true;
     }
+
     LOG(ERROR) << "fasboot-info.txt version: " << command[1]
-               << " not compatible with host tool version --> " << PLATFORM_TOOLS_VERSION;
+               << " not compatible with host tool version --> " << host_tool_version;
     return false;
 }
 
@@ -1721,7 +1712,7 @@
             continue;
         }
         if (command.size() > 1 && command[0] == "version") {
-            if (!CheckFastbootInfoRequirements(command)) {
+            if (!CheckFastbootInfoRequirements(command, FASTBOOT_INFO_VERSION)) {
                 return {};
             }
             continue;
@@ -1733,8 +1724,6 @@
         }
         auto task = ParseFastbootInfoLine(fp, command);
         if (!task) {
-            LOG(ERROR) << "Error when parsing fastboot-info.txt, falling back on Hardcoded list: "
-                       << text;
             return {};
         }
         tasks.emplace_back(std::move(task));
@@ -1760,8 +1749,6 @@
 }
 
 std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp, std::ifstream& fs) {
-    if (!fs || fs.eof()) return {};
-
     std::string text;
     std::vector<std::string> file;
     // Get os_partitions that need to be resized
@@ -1792,13 +1779,17 @@
 
     std::string path = find_item_given_name("fastboot-info.txt");
     std::ifstream stream(path);
-    std::vector<std::unique_ptr<Task>> tasks = ParseFastbootInfo(fp_, stream);
-    if (tasks.empty()) {
+    if (!stream || stream.eof()) {
         LOG(VERBOSE) << "Flashing from hardcoded images. fastboot-info.txt is empty or does not "
                         "exist";
         HardcodedFlash();
         return;
     }
+
+    std::vector<std::unique_ptr<Task>> tasks = ParseFastbootInfo(fp_, stream);
+    if (tasks.empty()) {
+        LOG(FATAL) << "Invalid fastboot-info.txt file.";
+    }
     LOG(VERBOSE) << "Flashing from fastboot-info.txt";
     for (auto& task : tasks) {
         task->Run();
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index d6afb9e..80b77a0 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -133,7 +133,8 @@
 std::string get_current_slot();
 
 // Code for Parsing fastboot-info.txt
-bool CheckFastbootInfoRequirements(const std::vector<std::string>& command);
+bool CheckFastbootInfoRequirements(const std::vector<std::string>& command,
+                                   uint32_t host_tool_version);
 std::unique_ptr<FlashTask> ParseFlashCommand(const FlashingPlan* fp,
                                              const std::vector<std::string>& parts);
 std::unique_ptr<RebootTask> ParseRebootCommand(const FlashingPlan* fp,
diff --git a/fastboot/task_test.cpp b/fastboot/task_test.cpp
index 6fc2056..b4e139b 100644
--- a/fastboot/task_test.cpp
+++ b/fastboot/task_test.cpp
@@ -89,20 +89,27 @@
 }
 
 TEST_F(ParseTest, VersionCheckCorrect) {
-    std::vector<std::string> correct_versions = {
-            "version 1.0",
-            "version 22.00",
-    };
+    std::vector<std::string> correct_versions = {"version 1", "version 22", "version 5",
+                                                 "version 17"};
 
-    std::vector<std::string> bad_versions = {"version",        "version .01", "version x1",
-                                             "version 1.0.1",  "version 1.",  "s 1.0",
-                                             "version 1.0 2.0"};
+    std::vector<std::string> bad_versions = {"version",         "version .01",    "version x1",
+                                             "version 1.0.1",   "version 1.",     "s 1.0",
+                                             "version 1.0 2.0", "version 100.00", "version 1 2"};
 
     for (auto& version : correct_versions) {
-        ASSERT_TRUE(CheckFastbootInfoRequirements(android::base::Split(version, " "))) << version;
+        ASSERT_TRUE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 26))
+                << version;
     }
+
+    // returning False for failing version check
+    for (auto& version : correct_versions) {
+        ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 0))
+                << version;
+    }
+    // returning False for bad format
     for (auto& version : bad_versions) {
-        ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "))) << version;
+        ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "), 100))
+                << version;
     }
 }
 
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index db27cf0..d357e45 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -24,9 +24,8 @@
     {
       "name": "vab_legacy_tests"
     },
-    {
-      "name": "vabc_legacy_tests"
-    },
+    // TODO: b/279009697
+    //{"name": "vabc_legacy_tests"},
     {
       "name": "cow_api_test"
     }
@@ -43,9 +42,8 @@
     },
     {
       "name": "vab_legacy_tests"
-    },
-    {
-      "name": "vabc_legacy_tests"
     }
+    // TODO: b/279009697
+    //{"name": "vabc_legacy_tests"}
   ]
 }
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 6349c20..ef436e5 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -1083,7 +1083,7 @@
         return 0;
     }
 
-    auto ideal_size = std::min(super_info.size, (uint64_t(s.f_frsize) * s.f_bfree) / 2);
+    auto ideal_size = std::min(super_info.size, uint64_t(s.f_frsize * s.f_bfree * 0.85));
 
     // Align up to the filesystem block size.
     if (auto remainder = ideal_size % s.f_bsize; remainder > 0) {
diff --git a/fs_mgr/libdm/dm_table.cpp b/fs_mgr/libdm/dm_table.cpp
index efe03ab..b546995 100644
--- a/fs_mgr/libdm/dm_table.cpp
+++ b/fs_mgr/libdm/dm_table.cpp
@@ -38,11 +38,11 @@
 bool DmTable::valid() const {
     if (targets_.empty()) {
         LOG(ERROR) << "Device-mapper table must have at least one target.";
-        return "";
+        return false;
     }
     if (targets_[0]->start() != 0) {
         LOG(ERROR) << "Device-mapper table must start at logical sector 0.";
-        return "";
+        return false;
     }
     return true;
 }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 69079cb..6baf4be 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -28,9 +28,6 @@
 
 static constexpr uint32_t kCowVersionManifest = 2;
 
-static constexpr size_t BLOCK_SZ = 4096;
-static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
-
 // This header appears as the first sequence of bytes in the COW. All fields
 // in the layout are little-endian encoded. The on-disk layout is:
 //
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 8e61a21..95a1270 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -29,42 +29,12 @@
 
 class ICowOpIter;
 
-// A ByteSink object handles requests for a buffer of a specific size. It
-// always owns the underlying buffer. It's designed to minimize potential
-// copying as we parse or decompress the COW.
-class IByteSink {
-  public:
-    virtual ~IByteSink() {}
-
-    // Called when the reader has data. The size of the request is given. The
-    // sink must return a valid pointer (or null on failure), and return the
-    // maximum number of bytes that can be written to the returned buffer.
-    //
-    // The returned buffer is owned by IByteSink, but must remain valid until
-    // the read operation has completed (or the entire buffer has been
-    // covered by calls to ReturnData).
-    //
-    // After calling GetBuffer(), all previous buffers returned are no longer
-    // valid.
-    //
-    // GetBuffer() is intended to be sequential. A returned size of N indicates
-    // that the output stream will advance by N bytes, and the ReturnData call
-    // indicates that those bytes have been fulfilled. Therefore, it is
-    // possible to have ReturnBuffer do nothing, if the implementation doesn't
-    // care about incremental writes.
-    virtual void* GetBuffer(size_t requested, size_t* actual) = 0;
-
-    // Called when a section returned by |GetBuffer| has been filled with data.
-    virtual bool ReturnData(void* buffer, size_t length) = 0;
-};
-
 // Interface for reading from a snapuserd COW.
 class ICowReader {
   public:
     virtual ~ICowReader() {}
 
     // Return the file header.
-    virtual bool GetHeader(CowHeader* header) = 0;
     virtual CowHeader& GetHeader() = 0;
 
     // Return the file footer.
@@ -84,10 +54,6 @@
     virtual std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress) = 0;
 
     // Get decoded bytes from the data section, handling any decompression.
-    // All retrieved data is passed to the sink.
-    virtual bool ReadData(const CowOperation& op, IByteSink* sink) = 0;
-
-    // Get decoded bytes from the data section, handling any decompression.
     //
     // If ignore_bytes is non-zero, it specifies the initial number of bytes
     // to skip writing to |buffer|.
@@ -97,29 +63,34 @@
     //
     // Partial reads are not possible unless |buffer_size| is less than the
     // operation block size.
-    virtual ssize_t ReadData(const CowOperation& op, void* buffer, size_t buffer_size,
+    //
+    // The operation pointer must derive from ICowOpIter::Get().
+    virtual ssize_t ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
                              size_t ignore_bytes = 0) = 0;
 };
 
-// Iterate over a sequence of COW operations.
+// Iterate over a sequence of COW operations. The iterator is bidirectional.
 class ICowOpIter {
   public:
     virtual ~ICowOpIter() {}
 
-    // True if there are no more items to read forward, false otherwise.
-    virtual bool Done() = 0;
+    // Returns true if the iterator is at the end of the operation list.
+    // If true, Get() and Next() must not be called.
+    virtual bool AtEnd() = 0;
 
     // Read the current operation.
-    virtual const CowOperation& Get() = 0;
+    virtual const CowOperation* Get() = 0;
 
     // Advance to the next item.
     virtual void Next() = 0;
 
+    // Returns true if the iterator is at the beginning of the operation list.
+    // If true, Prev() must not be called; Get() however will be valid if
+    // AtEnd() is not true.
+    virtual bool AtBegin() = 0;
+
     // Advance to the previous item.
     virtual void Prev() = 0;
-
-    // True if there are no more items to read backwards, false otherwise
-    virtual bool RDone() = 0;
 };
 
 class CowReader final : public ICowReader {
@@ -140,7 +111,6 @@
     bool InitForMerge(android::base::unique_fd&& fd);
     bool VerifyMergeOps() override;
 
-    bool GetHeader(CowHeader* header) override;
     bool GetFooter(CowFooter* footer) override;
 
     bool GetLastLabel(uint64_t* label) override;
@@ -153,8 +123,7 @@
     std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress = false) override;
     std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress = false) override;
 
-    bool ReadData(const CowOperation& op, IByteSink* sink) override;
-    ssize_t ReadData(const CowOperation& op, void* buffer, size_t buffer_size,
+    ssize_t ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
                      size_t ignore_bytes = 0) override;
 
     CowHeader& GetHeader() override { return header_; }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
index f05aeb2..edc9d65 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
@@ -46,7 +46,7 @@
 };
 
 // Helper to check read sizes.
-static inline bool ReadData(CowReader& reader, const CowOperation& op, void* buffer, size_t size) {
+static inline bool ReadData(CowReader& reader, const CowOperation* op, void* buffer, size_t size) {
     return reader.ReadData(op, buffer, size) == size;
 }
 
@@ -62,24 +62,25 @@
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
     CowReader reader;
-    CowHeader header;
-    CowFooter footer;
     ASSERT_TRUE(reader.Parse(cow_->fd));
-    ASSERT_TRUE(reader.GetHeader(&header));
-    ASSERT_TRUE(reader.GetFooter(&footer));
+
+    const auto& header = reader.GetHeader();
     ASSERT_EQ(header.magic, kCowMagicNumber);
     ASSERT_EQ(header.major_version, kCowVersionMajor);
     ASSERT_EQ(header.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
+
+    CowFooter footer;
+    ASSERT_TRUE(reader.GetFooter(&footer));
     ASSERT_EQ(footer.op.num_ops, 100);
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
+    ASSERT_FALSE(iter->AtEnd());
 
     size_t i = 0;
-    while (!iter->Done()) {
-        auto op = &iter->Get();
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
         ASSERT_EQ(op->type, kCowCopyOp);
         ASSERT_EQ(op->compression, kCowCompressNone);
         ASSERT_EQ(op->data_length, 0);
@@ -110,21 +111,22 @@
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
     CowReader reader;
-    CowHeader header;
-    CowFooter footer;
     ASSERT_TRUE(reader.Parse(cow_->fd));
-    ASSERT_TRUE(reader.GetHeader(&header));
-    ASSERT_TRUE(reader.GetFooter(&footer));
+
+    const auto& header = reader.GetHeader();
     ASSERT_EQ(header.magic, kCowMagicNumber);
     ASSERT_EQ(header.major_version, kCowVersionMajor);
     ASSERT_EQ(header.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
+
+    CowFooter footer;
+    ASSERT_TRUE(reader.GetFooter(&footer));
     ASSERT_EQ(footer.op.num_ops, 4);
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
 
     ASSERT_EQ(op->type, kCowCopyOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -135,19 +137,19 @@
     std::string sink(data.size(), '\0');
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowReplaceOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
     ASSERT_EQ(op->data_length, 4096);
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     // Note: the zero operation gets split into two blocks.
     ASSERT_EQ(op->type, kCowZeroOp);
@@ -157,8 +159,8 @@
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowZeroOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -167,7 +169,7 @@
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, ReadWriteXor) {
@@ -188,21 +190,22 @@
     ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
 
     CowReader reader;
-    CowHeader header;
-    CowFooter footer;
     ASSERT_TRUE(reader.Parse(cow_->fd));
-    ASSERT_TRUE(reader.GetHeader(&header));
-    ASSERT_TRUE(reader.GetFooter(&footer));
+
+    const auto& header = reader.GetHeader();
     ASSERT_EQ(header.magic, kCowMagicNumber);
     ASSERT_EQ(header.major_version, kCowVersionMajor);
     ASSERT_EQ(header.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
+
+    CowFooter footer;
+    ASSERT_TRUE(reader.GetFooter(&footer));
     ASSERT_EQ(footer.op.num_ops, 4);
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
 
     ASSERT_EQ(op->type, kCowCopyOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -213,20 +216,20 @@
     std::string sink(data.size(), '\0');
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowXorOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
     ASSERT_EQ(op->data_length, 4096);
     ASSERT_EQ(op->new_block, 50);
     ASSERT_EQ(op->source, 98314);  // 4096 * 24 + 10
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     // Note: the zero operation gets split into two blocks.
     ASSERT_EQ(op->type, kCowZeroOp);
@@ -236,8 +239,8 @@
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowZeroOp);
     ASSERT_EQ(op->compression, kCowCompressNone);
@@ -246,7 +249,7 @@
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, CompressGz) {
@@ -270,8 +273,8 @@
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
 
     std::string sink(data.size(), '\0');
 
@@ -279,11 +282,11 @@
     ASSERT_EQ(op->compression, kCowCompressGz);
     ASSERT_EQ(op->data_length, 56);  // compressed!
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 class CompressionTest : public CowTest, public testing::WithParamInterface<const char*> {};
@@ -325,15 +328,15 @@
     ASSERT_NE(iter, nullptr);
 
     int total_blocks = 0;
-    while (!iter->Done()) {
-        auto op = &iter->Get();
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
 
         if (op->type == kCowXorOp) {
             total_blocks += 1;
             std::string sink(xor_data.size(), '\0');
             ASSERT_EQ(op->new_block, 50);
             ASSERT_EQ(op->source, 98314);  // 4096 * 24 + 10
-            ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+            ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
             ASSERT_EQ(sink, xor_data);
         }
 
@@ -342,19 +345,19 @@
             if (op->new_block == 100) {
                 data.resize(options.block_size);
                 std::string sink(data.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink.size(), data.size());
                 ASSERT_EQ(sink, data);
             }
             if (op->new_block == 6000) {
                 data2.resize(options.block_size);
                 std::string sink(data2.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink, data2);
             }
             if (op->new_block == 9000) {
                 std::string sink(data3.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink, data3);
             }
         }
@@ -399,26 +402,26 @@
     ASSERT_NE(iter, nullptr);
 
     int total_blocks = 0;
-    while (!iter->Done()) {
-        auto op = &iter->Get();
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
 
         if (op->type == kCowReplaceOp) {
             total_blocks += 1;
             if (op->new_block == 50) {
                 data.resize(options.block_size);
                 std::string sink(data.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink, data);
             }
             if (op->new_block == 3000) {
                 data2.resize(options.block_size);
                 std::string sink(data2.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink, data2);
             }
             if (op->new_block == 5000) {
                 std::string sink(data3.size(), '\0');
-                ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+                ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
                 ASSERT_EQ(sink, data3);
             }
         }
@@ -515,8 +518,8 @@
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
 
     std::string sink(data.size(), '\0');
 
@@ -524,35 +527,35 @@
     ASSERT_EQ(op->compression, kCowCompressGz);
     ASSERT_EQ(op->data_length, 56);  // compressed!
     ASSERT_EQ(op->new_block, 50);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     sink = {};
     sink.resize(data2.size(), '\0');
     ASSERT_EQ(op->compression, kCowCompressGz);
     ASSERT_EQ(op->data_length, 41);  // compressed!
     ASSERT_EQ(op->new_block, 51);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data2);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
 
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, CompressTwoBlocks) {
@@ -576,17 +579,17 @@
 
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
-    ASSERT_FALSE(iter->Done());
+    ASSERT_FALSE(iter->AtEnd());
     iter->Next();
-    ASSERT_FALSE(iter->Done());
+    ASSERT_FALSE(iter->AtEnd());
 
     std::string sink(options.block_size, '\0');
 
-    auto op = &iter->Get();
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
     ASSERT_EQ(op->compression, kCowCompressGz);
     ASSERT_EQ(op->new_block, 51);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
 }
 
 TEST_F(CowTest, GetSize) {
@@ -655,31 +658,31 @@
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data);
 
     iter->Next();
     sink = {};
     sink.resize(data2.size(), '\0');
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 3);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data2);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, AppendLabelMissing) {
@@ -718,20 +721,20 @@
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 0);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
 
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, AppendExtendedCorrupted) {
@@ -776,13 +779,13 @@
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 5);
 
     iter->Next();
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, AppendbyLabel) {
@@ -827,50 +830,50 @@
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(0, options.block_size));
 
     iter->Next();
     sink = {};
     sink.resize(options.block_size, '\0');
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(options.block_size, 2 * options.block_size));
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 4);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 5);
 
     iter->Next();
 
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, ClusterTest) {
@@ -908,66 +911,66 @@
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data.substr(0, options.block_size));
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 4);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowZeroOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 5);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowCopyOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 6);
 
     iter->Next();
 
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, ClusterAppendTest) {
@@ -1007,28 +1010,28 @@
     auto iter = reader.GetOpIter();
     ASSERT_NE(iter, nullptr);
 
-    ASSERT_FALSE(iter->Done());
-    auto op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    auto op = iter->Get();
     ASSERT_EQ(op->type, kCowLabelOp);
     ASSERT_EQ(op->source, 50);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowReplaceOp);
-    ASSERT_TRUE(ReadData(reader, *op, sink.data(), sink.size()));
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
     ASSERT_EQ(sink, data2);
 
     iter->Next();
 
-    ASSERT_FALSE(iter->Done());
-    op = &iter->Get();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
     ASSERT_EQ(op->type, kCowClusterOp);
 
     iter->Next();
 
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, AppendAfterFinalize) {
@@ -1063,10 +1066,9 @@
     return AssertionSuccess();
 }
 
-AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op,
+AssertionResult CompareDataBlock(CowReader* reader, const CowOperation* op,
                                  const std::string& data) {
-    CowHeader header;
-    reader->GetHeader(&header);
+    const auto& header = reader->GetHeader();
 
     std::string cmp = data;
     cmp.resize(header.block_size, '\0');
@@ -1116,18 +1118,18 @@
     size_t max_in_cluster = 0;
     size_t num_in_cluster = 0;
     size_t num_clusters = 0;
-    while (!iter->Done()) {
+    while (!iter->AtEnd()) {
         const auto& op = iter->Get();
 
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
 
-        if (op.type == kCowReplaceOp) {
+        if (op->type == kCowReplaceOp) {
             num_replace++;
 
-            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_EQ(op->new_block, num_replace);
             ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
-        } else if (op.type == kCowClusterOp) {
+        } else if (op->type == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1177,18 +1179,18 @@
     size_t max_in_cluster = 0;
     size_t num_in_cluster = 0;
     size_t num_clusters = 0;
-    while (!iter->Done()) {
+    while (!iter->AtEnd()) {
         const auto& op = iter->Get();
 
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
 
-        if (op.type == kCowReplaceOp) {
+        if (op->type == kCowReplaceOp) {
             num_replace++;
 
-            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_EQ(op->new_block, num_replace);
             ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
-        } else if (op.type == kCowClusterOp) {
+        } else if (op->type == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1229,17 +1231,17 @@
     size_t max_in_cluster = 0;
     size_t num_in_cluster = 0;
     size_t num_clusters = 0;
-    while (!iter->Done()) {
+    while (!iter->AtEnd()) {
         const auto& op = iter->Get();
 
         num_in_cluster++;
         max_in_cluster = std::max(max_in_cluster, num_in_cluster);
-        if (op.type == kCowReplaceOp) {
+        if (op->type == kCowReplaceOp) {
             num_replace++;
 
-            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_EQ(op->new_block, num_replace);
             ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
-        } else if (op.type == kCowClusterOp) {
+        } else if (op->type == kCowClusterOp) {
             num_in_cluster = 0;
             num_clusters++;
         }
@@ -1273,14 +1275,14 @@
     auto iter = reader.GetRevMergeOpIter();
 
     for (int i = 0; i < seq_len; i++) {
-        ASSERT_TRUE(!iter->Done());
+        ASSERT_TRUE(!iter->AtEnd());
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, seq_len - i);
+        ASSERT_EQ(op->new_block, seq_len - i);
 
         iter->Next();
     }
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, MissingSeqOp) {
@@ -1324,7 +1326,7 @@
     auto reader = std::make_unique<CowReader>();
     ASSERT_TRUE(reader->Parse(cow_->fd, 1));
     auto itr = reader->GetRevMergeOpIter();
-    ASSERT_TRUE(itr->Done());
+    ASSERT_TRUE(itr->AtEnd());
 
     writer = std::make_unique<CowWriter>(options);
     ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
@@ -1339,17 +1341,17 @@
     auto iter = reader->GetRevMergeOpIter();
 
     uint64_t expected_block = 10;
-    while (!iter->Done() && expected_block > 0) {
-        ASSERT_FALSE(iter->Done());
+    while (!iter->AtEnd() && expected_block > 0) {
+        ASSERT_FALSE(iter->AtEnd());
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, expected_block);
+        ASSERT_EQ(op->new_block, expected_block);
 
         iter->Next();
         expected_block--;
     }
     ASSERT_EQ(expected_block, 0);
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, RevMergeOpItrTest) {
@@ -1390,16 +1392,16 @@
     auto iter = reader.GetRevMergeOpIter();
     auto expected_new_block = revMergeOpSequence.begin();
 
-    while (!iter->Done() && expected_new_block != revMergeOpSequence.end()) {
+    while (!iter->AtEnd() && expected_new_block != revMergeOpSequence.end()) {
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, *expected_new_block);
+        ASSERT_EQ(op->new_block, *expected_new_block);
 
         iter->Next();
         expected_new_block++;
     }
     ASSERT_EQ(expected_new_block, revMergeOpSequence.end());
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, LegacyRevMergeOpItrTest) {
@@ -1439,16 +1441,16 @@
     auto iter = reader.GetRevMergeOpIter();
     auto expected_new_block = revMergeOpSequence.begin();
 
-    while (!iter->Done() && expected_new_block != revMergeOpSequence.end()) {
+    while (!iter->AtEnd() && expected_new_block != revMergeOpSequence.end()) {
         const auto& op = iter->Get();
 
-        ASSERT_EQ(op.new_block, *expected_new_block);
+        ASSERT_EQ(op->new_block, *expected_new_block);
 
         iter->Next();
         expected_new_block++;
     }
     ASSERT_EQ(expected_new_block, revMergeOpSequence.end());
-    ASSERT_TRUE(iter->Done());
+    ASSERT_TRUE(iter->AtEnd());
 }
 
 TEST_F(CowTest, InvalidMergeOrderTest) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
index 483d559..3d34413 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.cpp
@@ -64,69 +64,17 @@
     }
 }
 
-class NoDecompressor final : public IDecompressor {
-  public:
-    bool Decompress(size_t) override;
-    ssize_t Decompress(void*, size_t, size_t, size_t) override {
-        LOG(ERROR) << "Not supported";
-        return -1;
-    }
-};
-
-bool NoDecompressor::Decompress(size_t) {
-    size_t stream_remaining = stream_->Size();
-    while (stream_remaining) {
-        size_t buffer_size = stream_remaining;
-        uint8_t* buffer = reinterpret_cast<uint8_t*>(sink_->GetBuffer(buffer_size, &buffer_size));
-        if (!buffer) {
-            LOG(ERROR) << "Could not acquire buffer from sink";
-            return false;
-        }
-
-        // Read until we can fill the buffer.
-        uint8_t* buffer_pos = buffer;
-        size_t bytes_to_read = std::min(buffer_size, stream_remaining);
-        while (bytes_to_read) {
-            ssize_t read = stream_->Read(buffer_pos, bytes_to_read);
-            if (read < 0) {
-                return false;
-            }
-            if (!read) {
-                LOG(ERROR) << "Stream ended prematurely";
-                return false;
-            }
-            if (!sink_->ReturnData(buffer_pos, read)) {
-                LOG(ERROR) << "Could not return buffer to sink";
-                return false;
-            }
-            buffer_pos += read;
-            bytes_to_read -= read;
-            stream_remaining -= read;
-        }
-    }
-    return true;
-}
-
-std::unique_ptr<IDecompressor> IDecompressor::Uncompressed() {
-    return std::unique_ptr<IDecompressor>(new NoDecompressor());
-}
-
 // Read chunks of the COW and incrementally stream them to the decoder.
 class StreamDecompressor : public IDecompressor {
   public:
-    bool Decompress(size_t output_bytes) override;
     ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size,
                        size_t ignore_bytes) override;
 
     virtual bool Init() = 0;
-    virtual bool DecompressInput(const uint8_t* data, size_t length) = 0;
     virtual bool PartialDecompress(const uint8_t* data, size_t length) = 0;
     bool OutputFull() const { return !ignore_bytes_ && !output_buffer_remaining_; }
 
   protected:
-    bool GetFreshBuffer();
-
-    size_t output_bytes_;
     size_t stream_remaining_;
     uint8_t* output_buffer_ = nullptr;
     size_t output_buffer_remaining_ = 0;
@@ -136,43 +84,6 @@
 
 static constexpr size_t kChunkSize = 4096;
 
-bool StreamDecompressor::Decompress(size_t output_bytes) {
-    if (!Init()) {
-        return false;
-    }
-
-    stream_remaining_ = stream_->Size();
-    output_bytes_ = output_bytes;
-
-    uint8_t chunk[kChunkSize];
-    while (stream_remaining_) {
-        size_t max_read = std::min(stream_remaining_, sizeof(chunk));
-        ssize_t read = stream_->Read(chunk, max_read);
-        if (read < 0) {
-            return false;
-        }
-        if (!read) {
-            LOG(ERROR) << "Stream ended prematurely";
-            return false;
-        }
-        if (!DecompressInput(chunk, read)) {
-            return false;
-        }
-
-        stream_remaining_ -= read;
-
-        if (stream_remaining_ && decompressor_ended_) {
-            LOG(ERROR) << "Decompressor terminated early";
-            return false;
-        }
-    }
-    if (!decompressor_ended_) {
-        LOG(ERROR) << "Decompressor expected more bytes";
-        return false;
-    }
-    return true;
-}
-
 ssize_t StreamDecompressor::Decompress(void* buffer, size_t buffer_size, size_t,
                                        size_t ignore_bytes) {
     if (!Init()) {
@@ -220,23 +131,11 @@
     return buffer_size - output_buffer_remaining_;
 }
 
-bool StreamDecompressor::GetFreshBuffer() {
-    size_t request_size = std::min(output_bytes_, kChunkSize);
-    output_buffer_ =
-            reinterpret_cast<uint8_t*>(sink_->GetBuffer(request_size, &output_buffer_remaining_));
-    if (!output_buffer_) {
-        LOG(ERROR) << "Could not acquire buffer from sink";
-        return false;
-    }
-    return true;
-}
-
 class GzDecompressor final : public StreamDecompressor {
   public:
     ~GzDecompressor();
 
     bool Init() override;
-    bool DecompressInput(const uint8_t* data, size_t length) override;
     bool PartialDecompress(const uint8_t* data, size_t length) override;
 
   private:
@@ -255,50 +154,6 @@
     inflateEnd(&z_);
 }
 
-bool GzDecompressor::DecompressInput(const uint8_t* data, size_t length) {
-    z_.next_in = reinterpret_cast<Bytef*>(const_cast<uint8_t*>(data));
-    z_.avail_in = length;
-
-    while (z_.avail_in) {
-        // If no more output buffer, grab a new buffer.
-        if (z_.avail_out == 0) {
-            if (!GetFreshBuffer()) {
-                return false;
-            }
-            z_.next_out = reinterpret_cast<Bytef*>(output_buffer_);
-            z_.avail_out = output_buffer_remaining_;
-        }
-
-        // Remember the position of the output buffer so we can call ReturnData.
-        auto avail_out = z_.avail_out;
-
-        // Decompress.
-        int rv = inflate(&z_, Z_NO_FLUSH);
-        if (rv != Z_OK && rv != Z_STREAM_END) {
-            LOG(ERROR) << "inflate returned error code " << rv;
-            return false;
-        }
-
-        size_t returned = avail_out - z_.avail_out;
-        if (!sink_->ReturnData(output_buffer_, returned)) {
-            LOG(ERROR) << "Could not return buffer to sink";
-            return false;
-        }
-        output_buffer_ += returned;
-        output_buffer_remaining_ -= returned;
-
-        if (rv == Z_STREAM_END) {
-            if (z_.avail_in) {
-                LOG(ERROR) << "Gz stream ended prematurely";
-                return false;
-            }
-            decompressor_ended_ = true;
-            return true;
-        }
-    }
-    return true;
-}
-
 bool GzDecompressor::PartialDecompress(const uint8_t* data, size_t length) {
     z_.next_in = reinterpret_cast<Bytef*>(const_cast<uint8_t*>(data));
     z_.avail_in = length;
@@ -362,7 +217,6 @@
     ~BrotliDecompressor();
 
     bool Init() override;
-    bool DecompressInput(const uint8_t* data, size_t length) override;
     bool PartialDecompress(const uint8_t* data, size_t length) override;
 
   private:
@@ -380,31 +234,6 @@
     }
 }
 
-bool BrotliDecompressor::DecompressInput(const uint8_t* data, size_t length) {
-    size_t available_in = length;
-    const uint8_t* next_in = data;
-
-    bool needs_more_output = false;
-    while (available_in || needs_more_output) {
-        if (!output_buffer_remaining_ && !GetFreshBuffer()) {
-            return false;
-        }
-
-        auto output_buffer = output_buffer_;
-        auto r = BrotliDecoderDecompressStream(decoder_, &available_in, &next_in,
-                                               &output_buffer_remaining_, &output_buffer_, nullptr);
-        if (r == BROTLI_DECODER_RESULT_ERROR) {
-            LOG(ERROR) << "brotli decode failed";
-            return false;
-        }
-        if (!sink_->ReturnData(output_buffer, output_buffer_ - output_buffer)) {
-            return false;
-        }
-        needs_more_output = (r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT);
-    }
-    return true;
-}
-
 bool BrotliDecompressor::PartialDecompress(const uint8_t* data, size_t length) {
     size_t available_in = length;
     const uint8_t* next_in = data;
@@ -451,45 +280,6 @@
   public:
     ~Lz4Decompressor() override = default;
 
-    bool Decompress(const size_t output_size) override {
-        size_t actual_buffer_size = 0;
-        auto&& output_buffer = sink_->GetBuffer(output_size, &actual_buffer_size);
-        if (actual_buffer_size != output_size) {
-            LOG(ERROR) << "Failed to allocate buffer of size " << output_size << " only got "
-                       << actual_buffer_size << " bytes";
-            return false;
-        }
-        // If input size is same as output size, then input is uncompressed.
-        if (stream_->Size() == output_size) {
-            ssize_t bytes_read = stream_->ReadFully(output_buffer, output_size);
-            if (bytes_read != output_size) {
-                LOG(ERROR) << "Failed to read all input at once. Expected: " << output_size
-                           << " actual: " << bytes_read;
-                return false;
-            }
-            sink_->ReturnData(output_buffer, output_size);
-            return true;
-        }
-        std::string input_buffer;
-        input_buffer.resize(stream_->Size());
-        ssize_t bytes_read = stream_->ReadFully(input_buffer.data(), input_buffer.size());
-        if (bytes_read != input_buffer.size()) {
-            LOG(ERROR) << "Failed to read all input at once. Expected: " << input_buffer.size()
-                       << " actual: " << bytes_read;
-            return false;
-        }
-        const int bytes_decompressed =
-                LZ4_decompress_safe(input_buffer.data(), static_cast<char*>(output_buffer),
-                                    input_buffer.size(), output_size);
-        if (bytes_decompressed != output_size) {
-            LOG(ERROR) << "Failed to decompress LZ4 block, expected output size: " << output_size
-                       << ", actual: " << bytes_decompressed;
-            return false;
-        }
-        sink_->ReturnData(output_buffer, output_size);
-        return true;
-    }
-
     ssize_t Decompress(void* buffer, size_t buffer_size, size_t decompressed_size,
                        size_t ignore_bytes) override {
         std::string input_buffer(stream_->Size(), '\0');
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
index 09164d3..9e83f3c 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_decompress.h
@@ -50,9 +50,6 @@
 
     static std::unique_ptr<IDecompressor> FromString(std::string_view compressor);
 
-    // |output_bytes| is the expected total number of bytes to sink.
-    virtual bool Decompress(size_t output_bytes) = 0;
-
     // Decompress at most |buffer_size| bytes, ignoring the first |ignore_bytes|
     // of the decoded stream. |buffer_size| must be at least one byte.
     // |decompressed_size| is the expected total size if the entire stream were
@@ -64,11 +61,9 @@
                                size_t ignore_bytes = 0) = 0;
 
     void set_stream(IByteStream* stream) { stream_ = stream; }
-    void set_sink(IByteSink* sink) { sink_ = sink; }
 
   protected:
     IByteStream* stream_ = nullptr;
-    IByteSink* sink_ = nullptr;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
index 94c4109..2157d0f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_format.cpp
@@ -57,8 +57,6 @@
     os << "data_length:" << op.data_length << ",\t";
     os << "new_block:" << op.new_block << ",\t";
     os << "source:" << op.source;
-    if (op.type == kCowXorOp)
-        os << " (block:" << op.source / BLOCK_SZ << " offset:" << op.source % BLOCK_SZ << ")";
     os << ")";
     return os;
 }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index e583ff0..4c5a8bf 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -508,47 +508,42 @@
 
 bool CowReader::VerifyMergeOps() {
     auto itr = GetMergeOpIter(true);
-    std::unordered_map<uint64_t, CowOperation> overwritten_blocks;
-    while (!itr->Done()) {
-        CowOperation op = itr->Get();
+    std::unordered_map<uint64_t, const CowOperation*> overwritten_blocks;
+    while (!itr->AtEnd()) {
+        const auto& op = itr->Get();
         uint64_t block;
         bool offset;
-        if (op.type == kCowCopyOp) {
-            block = op.source;
+        if (op->type == kCowCopyOp) {
+            block = op->source;
             offset = false;
-        } else if (op.type == kCowXorOp) {
-            block = op.source / BLOCK_SZ;
-            offset = (op.source % BLOCK_SZ) != 0;
+        } else if (op->type == kCowXorOp) {
+            block = op->source / header_.block_size;
+            offset = (op->source % header_.block_size) != 0;
         } else {
             itr->Next();
             continue;
         }
 
-        CowOperation* overwrite = nullptr;
+        const CowOperation* overwrite = nullptr;
         if (overwritten_blocks.count(block)) {
-            overwrite = &overwritten_blocks[block];
+            overwrite = overwritten_blocks[block];
             LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
                        << op << "\noverwritten by previously merged op:\n"
                        << *overwrite;
         }
         if (offset && overwritten_blocks.count(block + 1)) {
-            overwrite = &overwritten_blocks[block + 1];
+            overwrite = overwritten_blocks[block + 1];
             LOG(ERROR) << "Invalid Sequence! Block needed for op:\n"
                        << op << "\noverwritten by previously merged op:\n"
                        << *overwrite;
         }
         if (overwrite != nullptr) return false;
-        overwritten_blocks[op.new_block] = op;
+        overwritten_blocks[op->new_block] = op;
         itr->Next();
     }
     return true;
 }
 
-bool CowReader::GetHeader(CowHeader* header) {
-    *header = header_;
-    return true;
-}
-
 bool CowReader::GetFooter(CowFooter* footer) {
     if (!footer_) return false;
     *footer = footer_.value();
@@ -565,12 +560,12 @@
   public:
     CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops, uint64_t start);
 
-    bool Done() override;
-    const CowOperation& Get() override;
+    bool AtEnd() override;
+    const CowOperation* Get() override;
     void Next() override;
 
     void Prev() override;
-    bool RDone() override;
+    bool AtBegin() override;
 
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
@@ -582,27 +577,27 @@
     op_iter_ = ops_->begin() + start;
 }
 
-bool CowOpIter::RDone() {
+bool CowOpIter::AtBegin() {
     return op_iter_ == ops_->begin();
 }
 
 void CowOpIter::Prev() {
-    CHECK(!RDone());
+    CHECK(!AtBegin());
     op_iter_--;
 }
 
-bool CowOpIter::Done() {
+bool CowOpIter::AtEnd() {
     return op_iter_ == ops_->end();
 }
 
 void CowOpIter::Next() {
-    CHECK(!Done());
+    CHECK(!AtEnd());
     op_iter_++;
 }
 
-const CowOperation& CowOpIter::Get() {
-    CHECK(!Done());
-    return (*op_iter_);
+const CowOperation* CowOpIter::Get() {
+    CHECK(!AtEnd());
+    return &(*op_iter_);
 }
 
 class CowRevMergeOpIter final : public ICowOpIter {
@@ -610,12 +605,12 @@
     explicit CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
                                std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
 
-    bool Done() override;
-    const CowOperation& Get() override;
+    bool AtEnd() override;
+    const CowOperation* Get() override;
     void Next() override;
 
     void Prev() override;
-    bool RDone() override;
+    bool AtBegin() override;
 
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
@@ -629,12 +624,12 @@
     explicit CowMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
                             std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
 
-    bool Done() override;
-    const CowOperation& Get() override;
+    bool AtEnd() override;
+    const CowOperation* Get() override;
     void Next() override;
 
     void Prev() override;
-    bool RDone() override;
+    bool AtBegin() override;
 
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
@@ -651,27 +646,27 @@
     block_iter_ = cow_op_index_vec_->begin() + start;
 }
 
-bool CowMergeOpIter::RDone() {
+bool CowMergeOpIter::AtBegin() {
     return block_iter_ == cow_op_index_vec_->begin();
 }
 
 void CowMergeOpIter::Prev() {
-    CHECK(!RDone());
+    CHECK(!AtBegin());
     block_iter_--;
 }
 
-bool CowMergeOpIter::Done() {
+bool CowMergeOpIter::AtEnd() {
     return block_iter_ == cow_op_index_vec_->end();
 }
 
 void CowMergeOpIter::Next() {
-    CHECK(!Done());
+    CHECK(!AtEnd());
     block_iter_++;
 }
 
-const CowOperation& CowMergeOpIter::Get() {
-    CHECK(!Done());
-    return ops_->data()[*block_iter_];
+const CowOperation* CowMergeOpIter::Get() {
+    CHECK(!AtEnd());
+    return &ops_->data()[*block_iter_];
 }
 
 CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
@@ -683,27 +678,27 @@
     block_riter_ = cow_op_index_vec_->rbegin();
 }
 
-bool CowRevMergeOpIter::RDone() {
+bool CowRevMergeOpIter::AtBegin() {
     return block_riter_ == cow_op_index_vec_->rbegin();
 }
 
 void CowRevMergeOpIter::Prev() {
-    CHECK(!RDone());
+    CHECK(!AtBegin());
     block_riter_--;
 }
 
-bool CowRevMergeOpIter::Done() {
+bool CowRevMergeOpIter::AtEnd() {
     return block_riter_ == cow_op_index_vec_->rend() - start_;
 }
 
 void CowRevMergeOpIter::Next() {
-    CHECK(!Done());
+    CHECK(!AtEnd());
     block_riter_++;
 }
 
-const CowOperation& CowRevMergeOpIter::Get() {
-    CHECK(!Done());
-    return ops_->data()[*block_riter_];
+const CowOperation* CowRevMergeOpIter::Get() {
+    CHECK(!AtEnd());
+    return &ops_->data()[*block_riter_];
 }
 
 std::unique_ptr<ICowOpIter> CowReader::GetOpIter(bool merge_progress) {
@@ -770,42 +765,10 @@
     size_t remaining_;
 };
 
-bool CowReader::ReadData(const CowOperation& op, IByteSink* sink) {
-    std::unique_ptr<IDecompressor> decompressor;
-    switch (op.compression) {
-        case kCowCompressNone:
-            decompressor = IDecompressor::Uncompressed();
-            break;
-        case kCowCompressGz:
-            decompressor = IDecompressor::Gz();
-            break;
-        case kCowCompressBrotli:
-            decompressor = IDecompressor::Brotli();
-            break;
-        case kCowCompressLz4:
-            decompressor = IDecompressor::Lz4();
-            break;
-        default:
-            LOG(ERROR) << "Unknown compression type: " << op.compression;
-            return false;
-    }
-
-    uint64_t offset;
-    if (op.type == kCowXorOp) {
-        offset = data_loc_->at(op.new_block);
-    } else {
-        offset = op.source;
-    }
-    CowDataStream stream(this, offset, op.data_length);
-    decompressor->set_stream(&stream);
-    decompressor->set_sink(sink);
-    return decompressor->Decompress(header_.block_size);
-}
-
-ssize_t CowReader::ReadData(const CowOperation& op, void* buffer, size_t buffer_size,
+ssize_t CowReader::ReadData(const CowOperation* op, void* buffer, size_t buffer_size,
                             size_t ignore_bytes) {
     std::unique_ptr<IDecompressor> decompressor;
-    switch (op.compression) {
+    switch (op->compression) {
         case kCowCompressNone:
             break;
         case kCowCompressGz:
@@ -815,28 +778,28 @@
             decompressor = IDecompressor::Brotli();
             break;
         case kCowCompressLz4:
-            if (header_.block_size != op.data_length) {
+            if (header_.block_size != op->data_length) {
                 decompressor = IDecompressor::Lz4();
             }
             break;
         default:
-            LOG(ERROR) << "Unknown compression type: " << op.compression;
+            LOG(ERROR) << "Unknown compression type: " << op->compression;
             return -1;
     }
 
     uint64_t offset;
-    if (op.type == kCowXorOp) {
-        offset = data_loc_->at(op.new_block);
+    if (op->type == kCowXorOp) {
+        offset = data_loc_->at(op->new_block);
     } else {
-        offset = op.source;
+        offset = op->source;
     }
 
     if (!decompressor) {
-        CowDataStream stream(this, offset + ignore_bytes, op.data_length - ignore_bytes);
+        CowDataStream stream(this, offset + ignore_bytes, op->data_length - ignore_bytes);
         return stream.ReadFully(buffer, buffer_size);
     }
 
-    CowDataStream stream(this, offset, op.data_length);
+    CowDataStream stream(this, offset, op->data_length);
     decompressor->set_stream(&stream);
     return decompressor->Decompress(buffer, buffer_size, header_.block_size, ignore_bytes);
 }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
index 042ffb4..0e18979 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
@@ -392,10 +392,11 @@
     auto reader = std::make_unique<CowReader>();
     std::queue<CowOperation> toAdd;
 
-    if (!reader->Parse(fd_, {label}) || !reader->GetHeader(&header_)) {
+    if (!reader->Parse(fd_, {label})) {
         return false;
     }
 
+    header_ = reader->GetHeader();
     options_.block_size = header_.block_size;
     options_.cluster_ops = header_.cluster_ops;
 
@@ -405,8 +406,8 @@
 
     auto iter = reader->GetOpIter();
 
-    while (!iter->Done()) {
-        AddOperation(iter->Get());
+    while (!iter->AtEnd()) {
+        AddOperation(*iter->Get());
         iter->Next();
     }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
index 2716156..917cec4 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
@@ -16,6 +16,7 @@
 #include <stdio.h>
 #include <unistd.h>
 
+#include <chrono>
 #include <iomanip>
 #include <iostream>
 #include <string>
@@ -38,16 +39,16 @@
 }
 
 static void usage(void) {
-    LOG(ERROR) << "Usage: inspect_cow [-sd] <COW_FILE>";
-    LOG(ERROR) << "\t -s Run Silent";
-    LOG(ERROR) << "\t -d Attempt to decompress";
-    LOG(ERROR) << "\t -b Show data for failed decompress";
-    LOG(ERROR) << "\t -l Show ops";
-    LOG(ERROR) << "\t -m Show ops in reverse merge order";
-    LOG(ERROR) << "\t -n Show ops in merge order";
-    LOG(ERROR) << "\t -a Include merged ops in any merge order listing";
-    LOG(ERROR) << "\t -o Shows sequence op block order";
-    LOG(ERROR) << "\t -v Verifies merge order has no conflicts\n";
+    std::cerr << "Usage: inspect_cow [-sd] <COW_FILE>\n";
+    std::cerr << "\t -s Run Silent\n";
+    std::cerr << "\t -d Attempt to decompress\n";
+    std::cerr << "\t -b Show data for failed decompress\n";
+    std::cerr << "\t -l Show ops\n";
+    std::cerr << "\t -m Show ops in reverse merge order\n";
+    std::cerr << "\t -n Show ops in merge order\n";
+    std::cerr << "\t -a Include merged ops in any merge order listing\n";
+    std::cerr << "\t -o Shows sequence op block order\n";
+    std::cerr << "\t -v Verifies merge order has no conflicts\n";
 }
 
 enum OpIter { Normal, RevMerge, Merge };
@@ -63,19 +64,19 @@
     bool include_merged;
 };
 
-static void ShowBad(CowReader& reader, const struct CowOperation& op) {
+static void ShowBad(CowReader& reader, const struct CowOperation* op) {
     size_t count;
-    auto buffer = std::make_unique<uint8_t[]>(op.data_length);
+    auto buffer = std::make_unique<uint8_t[]>(op->data_length);
 
-    if (!reader.GetRawBytes(op.source, buffer.get(), op.data_length, &count)) {
+    if (!reader.GetRawBytes(op->source, buffer.get(), op->data_length, &count)) {
         std::cerr << "Failed to read at all!\n";
     } else {
         std::cout << "The Block data is:\n";
-        for (int i = 0; i < op.data_length; i++) {
+        for (int i = 0; i < op->data_length; i++) {
             std::cout << std::hex << (int)buffer[i];
         }
         std::cout << std::dec << "\n\n";
-        if (op.data_length >= sizeof(CowOperation)) {
+        if (op->data_length >= sizeof(CowOperation)) {
             std::cout << "The start, as an op, would be " << *(CowOperation*)buffer.get() << "\n";
         }
     }
@@ -89,37 +90,40 @@
     }
 
     CowReader reader;
+
+    auto start_time = std::chrono::steady_clock::now();
     if (!reader.Parse(fd)) {
         LOG(ERROR) << "parse failed: " << path;
         return false;
     }
+    std::chrono::duration<double> parse_time = std::chrono::steady_clock::now() - start_time;
 
-    CowHeader header;
-    if (!reader.GetHeader(&header)) {
-        LOG(ERROR) << "could not get header: " << path;
-        return false;
-    }
+    const CowHeader& header = reader.GetHeader();
     CowFooter footer;
     bool has_footer = false;
     if (reader.GetFooter(&footer)) has_footer = true;
 
     if (!opt.silent) {
-        std::cout << "Major version: " << header.major_version << "\n";
-        std::cout << "Minor version: " << header.minor_version << "\n";
+        std::cout << "Version: " << header.major_version << "." << header.minor_version << "\n";
         std::cout << "Header size: " << header.header_size << "\n";
         std::cout << "Footer size: " << header.footer_size << "\n";
         std::cout << "Block size: " << header.block_size << "\n";
-        std::cout << "Num merge ops: " << header.num_merge_ops << "\n";
-        std::cout << "RA buffer size: " << header.buffer_size << "\n";
-        std::cout << "\n";
+        std::cout << "Merge ops: " << header.num_merge_ops << "\n";
+        std::cout << "Readahead buffer: " << header.buffer_size << " bytes\n";
         if (has_footer) {
-            std::cout << "Total Ops size: " << footer.op.ops_size << "\n";
-            std::cout << "Number of Ops: " << footer.op.num_ops << "\n";
-            std::cout << "\n";
+            std::cout << "Footer: ops usage: " << footer.op.ops_size << " bytes\n";
+            std::cout << "Footer: op count: " << footer.op.num_ops << "\n";
+        } else {
+            std::cout << "Footer: none\n";
         }
     }
 
+    if (!opt.silent) {
+        std::cout << "Parse time: " << (parse_time.count() * 1000) << "ms\n";
+    }
+
     if (opt.verify_sequence) {
+        std::cout << "\n";
         if (reader.VerifyMergeOps()) {
             std::cout << "\nMerge sequence is consistent.\n";
         } else {
@@ -140,30 +144,30 @@
 
     bool success = true;
     uint64_t xor_ops = 0, copy_ops = 0, replace_ops = 0, zero_ops = 0;
-    while (!iter->Done()) {
-        const CowOperation& op = iter->Get();
+    while (!iter->AtEnd()) {
+        const CowOperation* op = iter->Get();
 
-        if (!opt.silent && opt.show_ops) std::cout << op << "\n";
+        if (!opt.silent && opt.show_ops) std::cout << *op << "\n";
 
-        if (opt.decompress && op.type == kCowReplaceOp && op.compression != kCowCompressNone) {
+        if (opt.decompress && op->type == kCowReplaceOp && op->compression != kCowCompressNone) {
             if (reader.ReadData(op, buffer.data(), buffer.size()) < 0) {
-                std::cerr << "Failed to decompress for :" << op << "\n";
+                std::cerr << "Failed to decompress for :" << *op << "\n";
                 success = false;
                 if (opt.show_bad) ShowBad(reader, op);
             }
         }
 
-        if (op.type == kCowSequenceOp && opt.show_seq) {
+        if (op->type == kCowSequenceOp && opt.show_seq) {
             size_t read;
             std::vector<uint32_t> merge_op_blocks;
-            size_t seq_len = op.data_length / sizeof(uint32_t);
+            size_t seq_len = op->data_length / sizeof(uint32_t);
             merge_op_blocks.resize(seq_len);
-            if (!reader.GetRawBytes(op.source, merge_op_blocks.data(), op.data_length, &read)) {
+            if (!reader.GetRawBytes(op->source, merge_op_blocks.data(), op->data_length, &read)) {
                 PLOG(ERROR) << "Failed to read sequence op!";
                 return false;
             }
             if (!opt.silent) {
-                std::cout << "Sequence for " << op << " is :\n";
+                std::cout << "Sequence for " << *op << " is :\n";
                 for (size_t i = 0; i < seq_len; i++) {
                     std::cout << std::setfill('0') << std::setw(6) << merge_op_blocks[i] << ", ";
                     if ((i + 1) % 10 == 0 || i + 1 == seq_len) std::cout << "\n";
@@ -171,13 +175,13 @@
             }
         }
 
-        if (op.type == kCowCopyOp) {
+        if (op->type == kCowCopyOp) {
             copy_ops++;
-        } else if (op.type == kCowReplaceOp) {
+        } else if (op->type == kCowReplaceOp) {
             replace_ops++;
-        } else if (op.type == kCowZeroOp) {
+        } else if (op->type == kCowZeroOp) {
             zero_ops++;
-        } else if (op.type == kCowXorOp) {
+        } else if (op->type == kCowXorOp) {
             xor_ops++;
         }
 
@@ -186,9 +190,11 @@
 
     if (!opt.silent) {
         auto total_ops = replace_ops + zero_ops + copy_ops + xor_ops;
-        std::cout << "Total-data-ops: " << total_ops << "Replace-ops: " << replace_ops
-                  << " Zero-ops: " << zero_ops << " Copy-ops: " << copy_ops
-                  << " Xor_ops: " << xor_ops << std::endl;
+        std::cout << "Data ops: " << total_ops << "\n";
+        std::cout << "Replace ops: " << replace_ops << "\n";
+        std::cout << "Zero ops: " << zero_ops << "\n";
+        std::cout << "Copy ops: " << copy_ops << "\n";
+        std::cout << "Xor ops: " << xor_ops << "\n";
     }
 
     return success;
@@ -237,15 +243,17 @@
                 break;
             default:
                 android::snapshot::usage();
+                return 1;
         }
     }
-    android::base::InitLogging(argv, android::snapshot::MyLogger);
 
     if (argc < optind + 1) {
         android::snapshot::usage();
         return 1;
     }
 
+    android::base::InitLogging(argv, android::snapshot::MyLogger);
+
     if (!android::snapshot::Inspect(argv[optind], opt)) {
         return 1;
     }
diff --git a/fs_mgr/libsnapshot/snapshot_reader.cpp b/fs_mgr/libsnapshot/snapshot_reader.cpp
index 6546c2a..4e75ba7 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader.cpp
@@ -80,16 +80,13 @@
 bool CompressedSnapshotReader::SetCow(std::unique_ptr<CowReader>&& cow) {
     cow_ = std::move(cow);
 
-    CowHeader header;
-    if (!cow_->GetHeader(&header)) {
-        return false;
-    }
+    const auto& header = cow_->GetHeader();
     block_size_ = header.block_size;
 
     // Populate the operation map.
     op_iter_ = cow_->GetOpIter();
-    while (!op_iter_->Done()) {
-        const CowOperation* op = &op_iter_->Get();
+    while (!op_iter_->AtEnd()) {
+        const CowOperation* op = op_iter_->Get();
         if (IsMetadataOp(*op)) {
             op_iter_->Next();
             continue;
@@ -128,37 +125,6 @@
     return source_fd_;
 }
 
-class MemoryByteSink : public IByteSink {
-  public:
-    MemoryByteSink(void* buf, size_t count) {
-        buf_ = reinterpret_cast<uint8_t*>(buf);
-        pos_ = buf_;
-        end_ = buf_ + count;
-    }
-
-    void* GetBuffer(size_t requested, size_t* actual) override {
-        *actual = std::min(remaining(), requested);
-        if (!*actual) {
-            return nullptr;
-        }
-
-        uint8_t* start = pos_;
-        pos_ += *actual;
-        return start;
-    }
-
-    bool ReturnData(void*, size_t) override { return true; }
-
-    uint8_t* buf() const { return buf_; }
-    uint8_t* pos() const { return pos_; }
-    size_t remaining() const { return end_ - pos_; }
-
-  private:
-    uint8_t* buf_;
-    uint8_t* pos_;
-    uint8_t* end_;
-};
-
 ssize_t CompressedSnapshotReader::Read(void* buf, size_t count) {
     // Find the start and end chunks, inclusive.
     uint64_t start_chunk = offset_ / block_size_;
@@ -167,69 +133,48 @@
     // Chop off the first N bytes if the position is not block-aligned.
     size_t start_offset = offset_ % block_size_;
 
-    MemoryByteSink sink(buf, count);
+    uint8_t* buf_pos = reinterpret_cast<uint8_t*>(buf);
+    size_t buf_remaining = count;
 
-    size_t initial_bytes = std::min(block_size_ - start_offset, sink.remaining());
-    ssize_t rv = ReadBlock(start_chunk, &sink, start_offset, initial_bytes);
+    size_t initial_bytes = std::min(block_size_ - start_offset, buf_remaining);
+    ssize_t rv = ReadBlock(start_chunk, start_offset, buf_pos, initial_bytes);
     if (rv < 0) {
         return -1;
     }
     offset_ += rv;
+    buf_pos += rv;
+    buf_remaining -= rv;
 
     for (uint64_t chunk = start_chunk + 1; chunk < end_chunk; chunk++) {
-        ssize_t rv = ReadBlock(chunk, &sink, 0);
+        ssize_t rv = ReadBlock(chunk, 0, buf_pos, buf_remaining);
         if (rv < 0) {
             return -1;
         }
         offset_ += rv;
+        buf_pos += rv;
+        buf_remaining -= rv;
     }
 
-    if (sink.remaining()) {
-        ssize_t rv = ReadBlock(end_chunk, &sink, 0, {sink.remaining()});
+    if (buf_remaining) {
+        ssize_t rv = ReadBlock(end_chunk, 0, buf_pos, buf_remaining);
         if (rv < 0) {
             return -1;
         }
         offset_ += rv;
+        buf_pos += rv;
+        buf_remaining -= rv;
     }
 
+    CHECK_EQ(buf_pos - reinterpret_cast<uint8_t*>(buf), count);
+    CHECK_EQ(buf_remaining, 0);
+
     errno = 0;
-
-    DCHECK(sink.pos() - sink.buf() == count);
     return count;
 }
 
-// Discard the first N bytes of a sink request, or any excess bytes.
-class PartialSink : public MemoryByteSink {
-  public:
-    PartialSink(void* buffer, size_t size, size_t ignore_start)
-        : MemoryByteSink(buffer, size), ignore_start_(ignore_start) {}
-
-    void* GetBuffer(size_t requested, size_t* actual) override {
-        // Throw away the first N bytes if needed.
-        if (ignore_start_) {
-            *actual = std::min({requested, ignore_start_, sizeof(discard_)});
-            ignore_start_ -= *actual;
-            return discard_;
-        }
-        // Throw away any excess bytes if needed.
-        if (remaining() == 0) {
-            *actual = std::min(requested, sizeof(discard_));
-            return discard_;
-        }
-        return MemoryByteSink::GetBuffer(requested, actual);
-    }
-
-  private:
-    size_t ignore_start_;
-    char discard_[BLOCK_SZ];
-};
-
-ssize_t CompressedSnapshotReader::ReadBlock(uint64_t chunk, IByteSink* sink, size_t start_offset,
-                                            const std::optional<uint64_t>& max_bytes) {
-    size_t bytes_to_read = block_size_;
-    if (max_bytes) {
-        bytes_to_read = *max_bytes;
-    }
+ssize_t CompressedSnapshotReader::ReadBlock(uint64_t chunk, size_t start_offset, void* buffer,
+                                            size_t buffer_size) {
+    size_t bytes_to_read = std::min(static_cast<size_t>(block_size_), buffer_size);
 
     // The offset is relative to the chunk; we should be reading no more than
     // one chunk.
@@ -240,17 +185,6 @@
         op = ops_[chunk];
     }
 
-    size_t actual;
-    void* buffer = sink->GetBuffer(bytes_to_read, &actual);
-    if (!buffer || actual < bytes_to_read) {
-        // This should never happen unless we calculated the read size wrong
-        // somewhere. MemoryByteSink always fulfills the entire requested
-        // region unless there's not enough buffer remaining.
-        LOG(ERROR) << "Asked for buffer of size " << bytes_to_read << ", got " << actual;
-        errno = EINVAL;
-        return -1;
-    }
-
     if (!op || op->type == kCowCopyOp) {
         borrowed_fd fd = GetSourceFd();
         if (fd < 0) {
@@ -271,8 +205,7 @@
     } else if (op->type == kCowZeroOp) {
         memset(buffer, 0, bytes_to_read);
     } else if (op->type == kCowReplaceOp) {
-        PartialSink partial_sink(buffer, bytes_to_read, start_offset);
-        if (!cow_->ReadData(*op, &partial_sink)) {
+        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;
@@ -285,18 +218,20 @@
         }
 
         off64_t offset = op->source + start_offset;
-        char data[BLOCK_SZ];
-        if (!android::base::ReadFullyAtOffset(fd, &data, bytes_to_read, offset)) {
+
+        std::string data(bytes_to_read, '\0');
+        if (!android::base::ReadFullyAtOffset(fd, data.data(), data.size(), offset)) {
             PLOG(ERROR) << "read " << *source_device_;
             // ReadFullyAtOffset sets errno.
             return -1;
         }
-        PartialSink partial_sink(buffer, bytes_to_read, start_offset);
-        if (!cow_->ReadData(*op, &partial_sink)) {
+
+        if (cow_->ReadData(op, buffer, bytes_to_read, start_offset) < bytes_to_read) {
             LOG(ERROR) << "CompressedSnapshotReader failed to read xor op";
             errno = EIO;
             return -1;
         }
+
         for (size_t i = 0; i < bytes_to_read; i++) {
             ((char*)buffer)[i] ^= data[i];
         }
diff --git a/fs_mgr/libsnapshot/snapshot_reader.h b/fs_mgr/libsnapshot/snapshot_reader.h
index a3e7e22..5e19c62 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.h
+++ b/fs_mgr/libsnapshot/snapshot_reader.h
@@ -65,8 +65,7 @@
     bool Flush() override;
 
   private:
-    ssize_t ReadBlock(uint64_t chunk, IByteSink* sink, size_t start_offset,
-                      const std::optional<uint64_t>& max_bytes = {});
+    ssize_t ReadBlock(uint64_t chunk, size_t start_offset, void* buffer, size_t size);
     android::base::borrowed_fd GetSourceFd();
 
     std::unique_ptr<CowReader> cow_;
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
index 9adc655..f25023d 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -140,6 +140,18 @@
         ASSERT_EQ(reader->Seek(offset, SEEK_SET), offset);
         ASSERT_EQ(reader->Read(&value, sizeof(value)), sizeof(value));
         ASSERT_EQ(value, MakeNewBlockString()[1000]);
+
+        // Test a sequence of one byte reads.
+        offset = 5 * kBlockSize + 10;
+        std::string expected = MakeNewBlockString().substr(10, 20);
+        ASSERT_EQ(reader->Seek(offset, SEEK_SET), offset);
+
+        std::string got;
+        while (got.size() < expected.size()) {
+            ASSERT_EQ(reader->Read(&value, sizeof(value)), sizeof(value));
+            got.push_back(value);
+        }
+        ASSERT_EQ(got, expected);
     }
 
     void TestReads(ISnapshotWriter* writer) {
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index f472bab..1fbfaf7 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -2646,6 +2646,11 @@
     ASSERT_TRUE(init->InitiateMerge());
     ASSERT_EQ(UpdateState::MergeFailed, init->ProcessUpdateState());
 
+    if (ShouldSkipLegacyMerging()) {
+        LOG(INFO) << "Skipping legacy merge in test";
+        return;
+    }
+
     // Simulate a reboot that tries the merge again, with the non-failing dm.
     ASSERT_TRUE(UnmapAll());
     init = NewManagerForFirstStageMount("_b");
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index 8926030..efa43b7 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -347,7 +347,6 @@
  */
 bool Snapuserd::ReadMetadata() {
     reader_ = std::make_unique<CowReader>();
-    CowHeader header;
     CowOptions options;
     bool metadata_found = false;
     int replace_ops = 0, zero_ops = 0, copy_ops = 0;
@@ -359,11 +358,7 @@
         return false;
     }
 
-    if (!reader_->GetHeader(&header)) {
-        SNAP_LOG(ERROR) << "Failed to get header";
-        return false;
-    }
-
+    const auto& header = reader_->GetHeader();
     if (!(header.block_size == BLOCK_SZ)) {
         SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
         return false;
@@ -395,8 +390,8 @@
     // this memset will ensure that metadata read is completed.
     memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
 
-    while (!cowop_rm_iter->Done()) {
-        const CowOperation* cow_op = &cowop_rm_iter->Get();
+    while (!cowop_rm_iter->AtEnd()) {
+        const CowOperation* cow_op = cowop_rm_iter->Get();
         struct disk_exception* de =
                 reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
 
@@ -442,7 +437,7 @@
                                                  sizeof(struct disk_exception));
             memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
 
-            if (cowop_rm_iter->Done()) {
+            if (cowop_rm_iter->AtEnd()) {
                 vec_.push_back(std::move(de_ptr));
             }
         }
@@ -462,9 +457,9 @@
                     << " Number of replace/zero ops completed in this area: " << num_ops
                     << " Pending copy ops for this area: " << pending_ordered_ops;
 
-    while (!cowop_rm_iter->Done()) {
+    while (!cowop_rm_iter->AtEnd()) {
         do {
-            const CowOperation* cow_op = &cowop_rm_iter->Get();
+            const CowOperation* cow_op = cowop_rm_iter->Get();
 
             // We have two cases specific cases:
             //
@@ -514,10 +509,8 @@
             // in the file.
             //===========================================================
             uint64_t block_source = cow_op->source;
-            uint64_t block_offset = 0;
             if (prev_id.has_value()) {
-                if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source) ||
-                    (block_offset > 0 && source_blocks.count(block_source + 1))) {
+                if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source)) {
                     break;
                 }
             }
@@ -525,13 +518,10 @@
             pending_ordered_ops -= 1;
             vec.push_back(cow_op);
             dest_blocks.insert(block_source);
-            if (block_offset > 0) {
-                dest_blocks.insert(block_source + 1);
-            }
             source_blocks.insert(cow_op->new_block);
             prev_id = cow_op->new_block;
             cowop_rm_iter->Next();
-        } while (!cowop_rm_iter->Done() && pending_ordered_ops);
+        } while (!cowop_rm_iter->AtEnd() && pending_ordered_ops);
 
         data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
         SNAP_LOG(DEBUG) << "Batch Merge copy-ops of size: " << vec.size()
@@ -574,7 +564,7 @@
                                                      sizeof(struct disk_exception));
                 memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
 
-                if (cowop_rm_iter->Done()) {
+                if (cowop_rm_iter->AtEnd()) {
                     vec_.push_back(std::move(de_ptr));
                     SNAP_LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
                 }
@@ -636,8 +626,7 @@
 }
 
 bool Snapuserd::MmapMetadata() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     if (header.major_version >= 2 && header.buffer_size > 0) {
         total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
@@ -832,8 +821,7 @@
 }
 
 uint64_t Snapuserd::GetBufferMetadataOffset() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     size_t size = header.header_size + sizeof(BufferState);
     return size;
@@ -848,16 +836,14 @@
  *
  */
 size_t Snapuserd::GetBufferMetadataSize() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     size_t metadata_bytes = (header.buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ;
     return metadata_bytes;
 }
 
 size_t Snapuserd::GetBufferDataOffset() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     return (header.header_size + GetBufferMetadataSize());
 }
@@ -866,16 +852,14 @@
  * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
  */
 size_t Snapuserd::GetBufferDataSize() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     size_t size = header.buffer_size - GetBufferMetadataSize();
     return size;
 }
 
 struct BufferState* Snapuserd::GetBufferState() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     struct BufferState* ra_state =
             reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index 01123f8..a32c2bf 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -173,16 +173,11 @@
 
 void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
     uint64_t source_block = cow_op->source;
-    uint64_t source_offset = 0;
-    if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block) ||
-        (source_offset > 0 && source_blocks_.count(source_block + 1))) {
+    if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block)) {
         overlap_ = true;
     }
 
     dest_blocks_.insert(source_block);
-    if (source_offset > 0) {
-        dest_blocks_.insert(source_block + 1);
-    }
     source_blocks_.insert(cow_op->new_block);
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index 965af40..922df34 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -95,11 +95,17 @@
 // internal COW format and if the block is compressed,
 // it will be de-compressed.
 bool WorkerThread::ProcessReplaceOp(const CowOperation* cow_op) {
-    if (!reader_->ReadData(*cow_op, &bufsink_)) {
-        SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (!buffer) {
+        SNAP_LOG(ERROR) << "No space in buffer sink";
         return false;
     }
-
+    ssize_t rv = reader_->ReadData(cow_op, buffer, BLOCK_SZ);
+    if (rv != BLOCK_SZ) {
+        SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block
+                        << ", return = " << rv;
+        return false;
+    }
     return true;
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
index 2e4cac6..a6b6a7f 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_buffer.h
@@ -25,16 +25,15 @@
 namespace android {
 namespace snapshot {
 
-class BufferSink : public IByteSink {
+class BufferSink final {
   public:
     void Initialize(size_t size);
     void* GetBufPtr() { return buffer_.get(); }
     void Clear() { memset(GetBufPtr(), 0, buffer_size_); }
     void* GetPayloadBuffer(size_t size);
-    void* GetBuffer(size_t requested, size_t* actual) override;
+    void* GetBuffer(size_t requested, size_t* actual);
     void UpdateBufferOffset(size_t size) { buffer_offset_ += size; }
     struct dm_user_header* GetHeaderPtr();
-    bool ReturnData(void*, size_t) override { return true; }
     void ResetBufferOffset() { buffer_offset_ = 0; }
     void* GetPayloadBufPtr();
 
@@ -44,12 +43,12 @@
     size_t buffer_size_;
 };
 
-class XorSink : public IByteSink {
+class XorSink final {
   public:
     void Initialize(BufferSink* sink, size_t size);
     void Reset();
-    void* GetBuffer(size_t requested, size_t* actual) override;
-    bool ReturnData(void* buffer, size_t len) override;
+    void* GetBuffer(size_t requested, size_t* actual);
+    bool ReturnData(void* buffer, size_t len);
 
   private:
     BufferSink* bufsink_;
diff --git a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
index c592257..46952c4 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
@@ -41,6 +41,9 @@
  */
 static constexpr uint32_t SECTOR_SHIFT = 9;
 
+static constexpr size_t BLOCK_SZ = 4096;
+static constexpr size_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+
 typedef __u64 sector_t;
 typedef sector_t chunk_t;
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 25ce0ae..a519639 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -98,8 +98,7 @@
         }
     } else {
         reader_->UpdateMergeOpsCompleted(num_merge_ops);
-        CowHeader header;
-        reader_->GetHeader(&header);
+        const auto& header = reader_->GetHeader();
 
         if (lseek(cow_fd_.get(), 0, SEEK_SET) < 0) {
             SNAP_PLOG(ERROR) << "lseek failed";
@@ -154,7 +153,6 @@
 
 bool SnapshotHandler::ReadMetadata() {
     reader_ = std::make_unique<CowReader>(CowReader::ReaderFlags::USERSPACE_MERGE, true);
-    CowHeader header;
     CowOptions options;
 
     SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file";
@@ -164,11 +162,7 @@
         return false;
     }
 
-    if (!reader_->GetHeader(&header)) {
-        SNAP_LOG(ERROR) << "Failed to get header";
-        return false;
-    }
-
+    const auto& header = reader_->GetHeader();
     if (!(header.block_size == BLOCK_SZ)) {
         SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
         return false;
@@ -191,8 +185,8 @@
 
     size_t copy_ops = 0, replace_ops = 0, zero_ops = 0, xor_ops = 0;
 
-    while (!cowop_iter->Done()) {
-        const CowOperation* cow_op = &cowop_iter->Get();
+    while (!cowop_iter->AtEnd()) {
+        const CowOperation* cow_op = cowop_iter->Get();
 
         if (cow_op->type == kCowCopyOp) {
             copy_ops += 1;
@@ -244,8 +238,7 @@
 }
 
 bool SnapshotHandler::MmapMetadata() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
 
@@ -367,8 +360,7 @@
 }
 
 uint64_t SnapshotHandler::GetBufferMetadataOffset() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     return (header.header_size + sizeof(BufferState));
 }
@@ -383,8 +375,7 @@
  *
  */
 size_t SnapshotHandler::GetBufferMetadataSize() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
     size_t buffer_size = header.buffer_size;
 
     // If there is no scratch space, then just use the
@@ -397,8 +388,7 @@
 }
 
 size_t SnapshotHandler::GetBufferDataOffset() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     return (header.header_size + GetBufferMetadataSize());
 }
@@ -407,8 +397,7 @@
  * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
  */
 size_t SnapshotHandler::GetBufferDataSize() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
     size_t buffer_size = header.buffer_size;
 
     // If there is no scratch space, then just use the
@@ -421,8 +410,7 @@
 }
 
 struct BufferState* SnapshotHandler::GetBufferState() {
-    CowHeader header;
-    reader_->GetHeader(&header);
+    const auto& header = reader_->GetHeader();
 
     struct BufferState* ra_state =
             reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
index 0d0f711..7858216 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_dm_user.cpp
@@ -76,11 +76,15 @@
 // internal COW format and if the block is compressed,
 // it will be de-compressed.
 bool Worker::ProcessReplaceOp(const CowOperation* cow_op) {
-    if (!reader_->ReadData(*cow_op, &bufsink_)) {
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (!buffer) {
+        SNAP_LOG(ERROR) << "ProcessReplaceOp failed to allocate buffer";
+        return false;
+    }
+    if (!reader_->ReadData(cow_op, buffer, BLOCK_SZ)) {
         SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
         return false;
     }
-
     return true;
 }
 
@@ -125,12 +129,26 @@
     if (!ReadFromSourceDevice(cow_op)) {
         return false;
     }
+
     xorsink_.Reset();
-    if (!reader_->ReadData(*cow_op, &xorsink_)) {
-        SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block;
+
+    size_t actual = 0;
+    void* buffer = xorsink_.GetBuffer(BLOCK_SZ, &actual);
+    if (!buffer || actual < BLOCK_SZ) {
+        SNAP_LOG(ERROR) << "ProcessXorOp failed to get buffer of " << BLOCK_SZ << " size, got "
+                        << actual;
         return false;
     }
-
+    ssize_t size = reader_->ReadData(cow_op, buffer, BLOCK_SZ);
+    if (size != BLOCK_SZ) {
+        SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block
+                        << ", return value: " << size;
+        return false;
+    }
+    if (!xorsink_.ReturnData(buffer, size)) {
+        SNAP_LOG(ERROR) << "ProcessXorOp failed to return data";
+        return false;
+    }
     return true;
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
index d57f434..ce95b76 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -30,8 +30,8 @@
     bool checkOrderedOp = (replace_zero_vec == nullptr);
 
     do {
-        if (!cowop_iter_->Done() && num_ops) {
-            const CowOperation* cow_op = &cowop_iter_->Get();
+        if (!cowop_iter_->AtEnd() && num_ops) {
+            const CowOperation* cow_op = cowop_iter_->Get();
             if (checkOrderedOp && !IsOrderedOp(*cow_op)) {
                 break;
             }
@@ -45,8 +45,8 @@
             num_ops -= 1;
             nr_consecutive = 1;
 
-            while (!cowop_iter_->Done() && num_ops) {
-                const CowOperation* op = &cowop_iter_->Get();
+            while (!cowop_iter_->AtEnd() && num_ops) {
+                const CowOperation* op = cowop_iter_->Get();
                 if (checkOrderedOp && !IsOrderedOp(*op)) {
                     break;
                 }
@@ -85,7 +85,7 @@
 
     SNAP_LOG(INFO) << "MergeReplaceZeroOps started....";
 
-    while (!cowop_iter_->Done()) {
+    while (!cowop_iter_->AtEnd()) {
         int num_ops = PAYLOAD_BUFFER_SZ / BLOCK_SZ;
         std::vector<const CowOperation*> replace_zero_vec;
         uint64_t source_offset;
@@ -93,7 +93,7 @@
         int linear_blocks = PrepareMerge(&source_offset, &num_ops, &replace_zero_vec);
         if (linear_blocks == 0) {
             // Merge complete
-            CHECK(cowop_iter_->Done());
+            CHECK(cowop_iter_->AtEnd());
             break;
         }
 
@@ -180,8 +180,8 @@
 
     SNAP_LOG(INFO) << "MergeOrderedOpsAsync started....";
 
-    while (!cowop_iter_->Done()) {
-        const CowOperation* cow_op = &cowop_iter_->Get();
+    while (!cowop_iter_->AtEnd()) {
+        const CowOperation* cow_op = cowop_iter_->Get();
         if (!IsOrderedOp(*cow_op)) {
             break;
         }
@@ -361,8 +361,8 @@
 
     SNAP_LOG(INFO) << "MergeOrderedOps started....";
 
-    while (!cowop_iter_->Done()) {
-        const CowOperation* cow_op = &cowop_iter_->Get();
+    while (!cowop_iter_->AtEnd()) {
+        const CowOperation* cow_op = cowop_iter_->Get();
         if (!IsOrderedOp(*cow_op)) {
             break;
         }
@@ -443,7 +443,7 @@
     if (!MergeOrderedOpsAsync()) {
         SNAP_LOG(ERROR) << "MergeOrderedOpsAsync failed - Falling back to synchronous I/O";
         // Reset the iter so that we retry the merge
-        while (blocks_merged_in_group_ && !cowop_iter_->RDone()) {
+        while (blocks_merged_in_group_ && !cowop_iter_->AtBegin()) {
             cowop_iter_->Prev();
             blocks_merged_in_group_ -= 1;
         }
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 fbe57d2..17f1f0e 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -114,6 +114,24 @@
     return nr_consecutive;
 }
 
+class [[nodiscard]] AutoNotifyReadAheadFailed {
+  public:
+    AutoNotifyReadAheadFailed(std::shared_ptr<SnapshotHandler> snapuserd) : snapuserd_(snapuserd) {}
+
+    ~AutoNotifyReadAheadFailed() {
+        if (cancelled_) {
+            return;
+        }
+        snapuserd_->ReadAheadIOFailed();
+    }
+
+    void Cancel() { cancelled_ = true; }
+
+  private:
+    std::shared_ptr<SnapshotHandler> snapuserd_;
+    bool cancelled_ = false;
+};
+
 bool ReadAhead::ReconstructDataFromCow() {
     std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
     loff_t metadata_offset = 0;
@@ -145,6 +163,8 @@
         metadata_offset += sizeof(struct ScratchMetadata);
     }
 
+    AutoNotifyReadAheadFailed notify_read_ahead_failed(snapuserd_);
+
     // We are done re-constructing the mapping; however, we need to make sure
     // all the COW operations to-be merged are present in the re-constructed
     // mapping.
@@ -162,7 +182,6 @@
         if (!(num_ops == 0)) {
             SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd "
                             << " Pending ops: " << num_ops;
-            snapuserd_->ReadAheadIOFailed();
             return false;
         }
 
@@ -175,11 +194,11 @@
 
     if (!snapuserd_->ReadAheadIOCompleted(true)) {
         SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
-        snapuserd_->ReadAheadIOFailed();
         return false;
     }
 
     SNAP_LOG(INFO) << "ReconstructDataFromCow success";
+    notify_read_ahead_failed.Cancel();
     return true;
 }
 
@@ -467,9 +486,16 @@
         if (xor_op_index < xor_op_vec.size()) {
             const CowOperation* xor_op = xor_op_vec[xor_op_index];
             if (xor_op->new_block == new_block) {
-                if (!reader_->ReadData(*xor_op, &bufsink_)) {
+                void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+                if (!buffer) {
+                    SNAP_LOG(ERROR) << "ReadAhead - failed to allocate buffer for block: "
+                                    << xor_op->new_block;
+                    return false;
+                }
+                if (ssize_t rv = reader_->ReadData(xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) {
                     SNAP_LOG(ERROR)
-                            << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block;
+                            << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block
+                            << ", return value: " << rv;
                     return false;
                 }
 
@@ -492,6 +518,8 @@
     blocks_.clear();
     std::vector<const CowOperation*> xor_op_vec;
 
+    AutoNotifyReadAheadFailed notify_read_ahead_failed(snapuserd_);
+
     bufsink_.ResetBufferOffset();
 
     // Number of ops to be merged in this window. This is a fixed size
@@ -518,8 +546,6 @@
                              << " offset :" << source_offset % BLOCK_SZ
                              << " buffer_offset : " << buffer_offset << " io_size : " << io_size
                              << " buf-addr : " << read_ahead_buffer_;
-
-            snapuserd_->ReadAheadIOFailed();
             return false;
         }
 
@@ -530,6 +556,7 @@
 
     // Done with merging ordered ops
     if (RAIterDone() && total_blocks_merged_ == 0) {
+        notify_read_ahead_failed.Cancel();
         return true;
     }
 
@@ -560,20 +587,25 @@
             // Check if this block is an XOR op
             if (xor_op->new_block == new_block) {
                 // Read the xor'ed data from COW
-                if (!reader_->ReadData(*xor_op, &bufsink)) {
+                void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+                if (!buffer) {
+                    SNAP_LOG(ERROR) << "ReadAhead - failed to allocate buffer";
+                    return false;
+                }
+                if (ssize_t rv = reader_->ReadData(xor_op, buffer, BLOCK_SZ); rv != BLOCK_SZ) {
                     SNAP_LOG(ERROR)
-                            << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block;
-                    snapuserd_->ReadAheadIOFailed();
+                            << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block
+                            << ", return value: " << rv;
                     return false;
                 }
                 // Pointer to the data read from base device
-                uint8_t* buffer = reinterpret_cast<uint8_t*>(bufptr);
+                uint8_t* read_buffer = reinterpret_cast<uint8_t*>(bufptr);
                 // Get the xor'ed data read from COW device
                 uint8_t* xor_data = reinterpret_cast<uint8_t*>(bufsink.GetPayloadBufPtr());
 
                 // Retrieve the original data
                 for (size_t byte_offset = 0; byte_offset < BLOCK_SZ; byte_offset++) {
-                    buffer[byte_offset] ^= xor_data[byte_offset];
+                    read_buffer[byte_offset] ^= xor_data[byte_offset];
                 }
 
                 // Move to next XOR op
@@ -604,6 +636,7 @@
     bm->new_block = 0;
     bm->file_offset = 0;
 
+    notify_read_ahead_failed.Cancel();
     return true;
 }
 
@@ -776,7 +809,7 @@
 }
 
 bool ReadAhead::RAIterDone() {
-    if (cowop_iter_->Done()) {
+    if (cowop_iter_->AtEnd()) {
         return true;
     }
 
@@ -794,15 +827,14 @@
 }
 
 void ReadAhead::RAResetIter(uint64_t num_blocks) {
-    while (num_blocks && !cowop_iter_->RDone()) {
+    while (num_blocks && !cowop_iter_->AtBegin()) {
         cowop_iter_->Prev();
         num_blocks -= 1;
     }
 }
 
 const CowOperation* ReadAhead::GetRAOpIter() {
-    const CowOperation* cow_op = &cowop_iter_->Get();
-    return cow_op;
+    return cowop_iter_->Get();
 }
 
 void ReadAhead::InitializeBuffer() {
diff --git a/gatekeeperd/main.cpp b/gatekeeperd/main.cpp
index a05584a..a01f9de 100644
--- a/gatekeeperd/main.cpp
+++ b/gatekeeperd/main.cpp
@@ -38,7 +38,7 @@
             android::String16("android.service.gatekeeper.IGateKeeperService"), proxy);
     if (ret != android::OK) {
         ALOGE("Couldn't register binder service!");
-        return -1;
+        return 1;
     }
 
     /*
@@ -46,5 +46,5 @@
      * Binder transaction as a single-threaded program.
      */
     android::IPCThreadState::self()->joinThreadPool();
-    return 0;
+    return 1;
 }
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 0fc3ffc..7e8513b 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -180,9 +180,11 @@
     std::string init_script = R"init(
 service A something
     class first
+    user nobody
 
 service A something
     class second
+    user nobody
     override
 
 )init";
@@ -610,6 +612,31 @@
     EXPECT_EQ(2, num_executed);
 }
 
+TEST(init, RejectsNoUserStartingInV) {
+    std::string init_script =
+            R"init(
+service A something
+    class first
+)init";
+
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    ASSERT_TRUE(android::base::WriteStringToFd(init_script, tf.fd));
+
+    ServiceList service_list;
+    Parser parser;
+    parser.AddSectionParser("service",
+                            std::make_unique<ServiceParser>(&service_list, nullptr, std::nullopt));
+
+    ASSERT_TRUE(parser.ParseConfig(tf.path));
+
+    if (GetIntProperty("ro.vendor.api_level", 0) > __ANDROID_API_U__) {
+        ASSERT_EQ(1u, parser.parse_error_count());
+    } else {
+        ASSERT_EQ(0u, parser.parse_error_count());
+    }
+}
+
 TEST(init, RejectsCriticalAndOneshotService) {
     if (GetIntProperty("ro.product.first_api_level", 10000) < 30) {
         GTEST_SKIP() << "Test only valid for devices launching with R or later";
@@ -619,6 +646,7 @@
             R"init(
 service A something
   class first
+  user root
   critical
   oneshot
 )init";
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index d89664c..d46e1f7 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -25,6 +25,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <hidl-util/FQName.h>
 #include <processgroup/processgroup.h>
@@ -678,8 +679,13 @@
     }
 
     if (service_->proc_attr_.parsed_uid == std::nullopt) {
-        LOG(WARNING) << "No user specified for service '" << service_->name()
-                     << "'. Defaults to root.";
+        if (android::base::GetIntProperty("ro.vendor.api_level", 0) > __ANDROID_API_U__) {
+            return Error() << "No user specified for service '" << service_->name()
+                           << "'. Defaults to root.";
+        } else {
+            LOG(WARNING) << "No user specified for service '" << service_->name()
+                         << "'. Defaults to root.";
+        }
     }
 
     if (interface_inheritance_hierarchy_) {
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 79d79dd..f90a1bc 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -84,14 +84,12 @@
     { 00777, AID_ROOT,         AID_ROOT,         0, "sdcard" },
     { 00751, AID_ROOT,         AID_SDCARD_R,     0, "storage" },
     { 00750, AID_ROOT,         AID_SYSTEM,       0, "system/apex/com.android.tethering/bin/for-system" },
-    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system/apex/com.android.tethering.inprocess/bin/for-system" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/bin" },
     { 00755, AID_ROOT,         AID_ROOT,         0, "system/etc/ppp" },
     { 00755, AID_ROOT,         AID_SHELL,        0, "system/vendor" },
     { 00750, AID_ROOT,         AID_SHELL,        0, "system/xbin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/apex/*/bin" },
     { 00750, AID_ROOT,         AID_SYSTEM,       0, "system_ext/apex/com.android.tethering/bin/for-system" },
-    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/apex/*/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "vendor/bin" },
@@ -199,9 +197,7 @@
     // the following files have enhanced capabilities and ARE included
     // in user builds.
     { 06755, AID_CLAT,      AID_CLAT,      0, "system/apex/com.android.tethering/bin/for-system/clatd" },
-    { 06755, AID_CLAT,      AID_CLAT,      0, "system/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
     { 06755, AID_CLAT,      AID_CLAT,      0, "system_ext/apex/com.android.tethering/bin/for-system/clatd" },
-    { 06755, AID_CLAT,      AID_CLAT,      0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
     { 00700, AID_SYSTEM,    AID_SHELL,     CAP_MASK_LONG(CAP_BLOCK_SUSPEND),
                                               "system/bin/inputflinger" },
     { 00750, AID_ROOT,      AID_SHELL,     CAP_MASK_LONG(CAP_SETUID) |
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 48bc0b7..dbaeb93 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -96,6 +96,10 @@
 // Returns false in case of error, true in case of success
 bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
 
+// Check if a profile can be applied without failing.
+// Returns true if it can be applied without failing, false otherwise
+bool isProfileValidForProcess(const std::string& profile_name, int uid, int pid);
+
 #endif // __ANDROID_VNDK__
 
 __END_DECLS
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index e294260..1f29040 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -656,3 +656,13 @@
 bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path) {
     return CgroupGetAttributePathForTask(attr_name, tid, path);
 }
+
+bool isProfileValidForProcess(const std::string& profile_name, int uid, int pid) {
+    const TaskProfile* tp = TaskProfiles::GetInstance().GetProfile(profile_name);
+
+    if (tp == nullptr) {
+        return false;
+    }
+
+    return tp->IsValidForProcess(uid, pid);
+}
\ No newline at end of file
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 1731828..44dba2a 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -259,6 +259,31 @@
     return true;
 }
 
+bool SetAttributeAction::IsValidForProcess(uid_t, pid_t pid) const {
+    return IsValidForTask(pid);
+}
+
+bool SetAttributeAction::IsValidForTask(int tid) const {
+    std::string path;
+
+    if (!attribute_->GetPathForTask(tid, &path)) {
+        return false;
+    }
+
+    if (!access(path.c_str(), W_OK)) {
+        // operation will succeed
+        return true;
+    }
+
+    if (!access(path.c_str(), F_OK)) {
+        // file exists but not writable
+        return false;
+    }
+
+    // file does not exist, ignore if optional
+    return optional_;
+}
+
 SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
     : controller_(c), path_(p) {
     FdCacheHelper::Init(controller_.GetTasksFilePath(path_), fd_[ProfileAction::RCT_TASK]);
@@ -396,6 +421,39 @@
     FdCacheHelper::Drop(fd_[cache_type]);
 }
 
+bool SetCgroupAction::IsValidForProcess(uid_t uid, pid_t pid) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_PROCESS])) {
+        return true;
+    }
+
+    if (fd_[ProfileAction::RCT_PROCESS] == FdCacheHelper::FDS_INACCESSIBLE) {
+        return false;
+    }
+
+    std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid);
+    return access(procs_path.c_str(), W_OK) == 0;
+}
+
+bool SetCgroupAction::IsValidForTask(int) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_TASK])) {
+        return true;
+    }
+
+    if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_INACCESSIBLE) {
+        return false;
+    }
+
+    if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_APP_DEPENDENT) {
+        // application-dependent path can't be used with tid
+        return false;
+    }
+
+    std::string tasks_path = controller()->GetTasksFilePath(path_);
+    return access(tasks_path.c_str(), W_OK) == 0;
+}
+
 WriteFileAction::WriteFileAction(const std::string& task_path, const std::string& proc_path,
                                  const std::string& value, bool logfailures)
     : task_path_(task_path), proc_path_(proc_path), value_(value), logfailures_(logfailures) {
@@ -532,6 +590,37 @@
     FdCacheHelper::Drop(fd_[cache_type]);
 }
 
+bool WriteFileAction::IsValidForProcess(uid_t, pid_t) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_PROCESS])) {
+        return true;
+    }
+
+    if (fd_[ProfileAction::RCT_PROCESS] == FdCacheHelper::FDS_INACCESSIBLE) {
+        return false;
+    }
+
+    return access(proc_path_.empty() ? task_path_.c_str() : proc_path_.c_str(), W_OK) == 0;
+}
+
+bool WriteFileAction::IsValidForTask(int) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    if (FdCacheHelper::IsCached(fd_[ProfileAction::RCT_TASK])) {
+        return true;
+    }
+
+    if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_INACCESSIBLE) {
+        return false;
+    }
+
+    if (fd_[ProfileAction::RCT_TASK] == FdCacheHelper::FDS_APP_DEPENDENT) {
+        // application-dependent path can't be used with tid
+        return false;
+    }
+
+    return access(task_path_.c_str(), W_OK) == 0;
+}
+
 bool ApplyProfileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
     for (const auto& profile : profiles_) {
         profile->ExecuteForProcess(uid, pid);
@@ -558,6 +647,24 @@
     }
 }
 
+bool ApplyProfileAction::IsValidForProcess(uid_t uid, pid_t pid) const {
+    for (const auto& profile : profiles_) {
+        if (!profile->IsValidForProcess(uid, pid)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool ApplyProfileAction::IsValidForTask(int tid) const {
+    for (const auto& profile : profiles_) {
+        if (!profile->IsValidForTask(tid)) {
+            return false;
+        }
+    }
+    return true;
+}
+
 void TaskProfile::MoveTo(TaskProfile* profile) {
     profile->elements_ = std::move(elements_);
     profile->res_cached_ = res_cached_;
@@ -620,6 +727,20 @@
     res_cached_ = false;
 }
 
+bool TaskProfile::IsValidForProcess(uid_t uid, pid_t pid) const {
+    for (const auto& element : elements_) {
+        if (!element->IsValidForProcess(uid, pid)) return false;
+    }
+    return true;
+}
+
+bool TaskProfile::IsValidForTask(int tid) const {
+    for (const auto& element : elements_) {
+        if (!element->IsValidForTask(tid)) return false;
+    }
+    return true;
+}
+
 void TaskProfiles::DropResourceCaching(ProfileAction::ResourceCacheType cache_type) const {
     for (auto& iter : profiles_) {
         iter.second->DropResourceCaching(cache_type);
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index a8ecb87..a62c5b0 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -72,12 +72,14 @@
     virtual const char* Name() const = 0;
 
     // Default implementations will fail
-    virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; };
-    virtual bool ExecuteForTask(int) const { return false; };
-    virtual bool ExecuteForUID(uid_t) const { return false; };
+    virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; }
+    virtual bool ExecuteForTask(int) const { return false; }
+    virtual bool ExecuteForUID(uid_t) const { return false; }
 
     virtual void EnableResourceCaching(ResourceCacheType) {}
     virtual void DropResourceCaching(ResourceCacheType) {}
+    virtual bool IsValidForProcess(uid_t uid, pid_t pid) const { return false; }
+    virtual bool IsValidForTask(int tid) const { return false; }
 
   protected:
     enum CacheUseResult { SUCCESS, FAIL, UNUSED };
@@ -103,6 +105,8 @@
 
     const char* Name() const override { return "SetTimerSlack"; }
     bool ExecuteForTask(int tid) const override;
+    bool IsValidForProcess(uid_t uid, pid_t pid) const override { return true; }
+    bool IsValidForTask(int tid) const override { return true; }
 
   private:
     unsigned long slack_;
@@ -120,6 +124,8 @@
     bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
     bool ExecuteForTask(int tid) const override;
     bool ExecuteForUID(uid_t uid) const override;
+    bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+    bool IsValidForTask(int tid) const override;
 
   private:
     const IProfileAttribute* attribute_;
@@ -137,6 +143,8 @@
     bool ExecuteForTask(int tid) const override;
     void EnableResourceCaching(ResourceCacheType cache_type) override;
     void DropResourceCaching(ResourceCacheType cache_type) override;
+    bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+    bool IsValidForTask(int tid) const override;
 
     const CgroupController* controller() const { return &controller_; }
 
@@ -161,6 +169,8 @@
     bool ExecuteForTask(int tid) const override;
     void EnableResourceCaching(ResourceCacheType cache_type) override;
     void DropResourceCaching(ResourceCacheType cache_type) override;
+    bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+    bool IsValidForTask(int tid) const override;
 
   private:
     std::string task_path_, proc_path_, value_;
@@ -186,6 +196,8 @@
     bool ExecuteForUID(uid_t uid) const;
     void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type);
     void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
+    bool IsValidForProcess(uid_t uid, pid_t pid) const;
+    bool IsValidForTask(int tid) const;
 
   private:
     const std::string name_;
@@ -204,6 +216,8 @@
     bool ExecuteForTask(int tid) const override;
     void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
     void DropResourceCaching(ProfileAction::ResourceCacheType cache_type) override;
+    bool IsValidForProcess(uid_t uid, pid_t pid) const override;
+    bool IsValidForTask(int tid) const override;
 
   private:
     std::vector<std::shared_ptr<TaskProfile>> profiles_;
diff --git a/libprocessgroup/task_profiles_test.cpp b/libprocessgroup/task_profiles_test.cpp
index 6a5b48b..eadbe76 100644
--- a/libprocessgroup/task_profiles_test.cpp
+++ b/libprocessgroup/task_profiles_test.cpp
@@ -175,6 +175,32 @@
     }
 }
 
+class TaskProfileFixture : public TestWithParam<TestParam> {
+  public:
+    ~TaskProfileFixture() = default;
+};
+
+TEST_P(TaskProfileFixture, TaskProfile) {
+    // Treehugger runs host tests inside a container without cgroupv2 support.
+    if (!IsCgroupV2MountedRw()) {
+        GTEST_SKIP();
+        return;
+    }
+    const TestParam params = GetParam();
+    ProfileAttributeMock pa(params.attr_name);
+    // Test simple profile with one action
+    std::shared_ptr<TaskProfile> tp = std::make_shared<TaskProfile>("test_profile");
+    tp->Add(std::make_unique<SetAttributeAction>(&pa, params.attr_value, params.optional_attr));
+    EXPECT_EQ(tp->IsValidForProcess(getuid(), getpid()), params.result);
+    EXPECT_EQ(tp->IsValidForTask(getpid()), params.result);
+    // Test aggregate profile
+    TaskProfile tp2("meta_profile");
+    std::vector<std::shared_ptr<TaskProfile>> profiles = {tp};
+    tp2.Add(std::make_unique<ApplyProfileAction>(profiles));
+    EXPECT_EQ(tp2.IsValidForProcess(getuid(), getpid()), params.result);
+    EXPECT_EQ(tp2.IsValidForTask(getpid()), params.result);
+}
+
 // Test the four combinations of optional_attr {false, true} and cgroup attribute { does not exist,
 // exists }.
 INSTANTIATE_TEST_SUITE_P(
@@ -215,4 +241,28 @@
                         .log_prefix = "Failed to write",
                         .log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"}));
 
+// Test TaskProfile IsValid calls.
+INSTANTIATE_TEST_SUITE_P(
+        TaskProfileTestSuite, TaskProfileFixture,
+        Values(
+                // Test operating on non-existing cgroup attribute fails.
+                TestParam{.attr_name = "no-such-attribute",
+                          .attr_value = ".",
+                          .optional_attr = false,
+                          .result = false},
+                // Test operating on optional non-existing cgroup attribute succeeds.
+                TestParam{.attr_name = "no-such-attribute",
+                          .attr_value = ".",
+                          .optional_attr = true,
+                          .result = true},
+                // Test operating on existing cgroup attribute succeeds.
+                TestParam{.attr_name = "cgroup.procs",
+                          .attr_value = ".",
+                          .optional_attr = false,
+                          .result = true},
+                // Test operating on optional existing cgroup attribute succeeds.
+                TestParam{.attr_name = "cgroup.procs",
+                          .attr_value = ".",
+                          .optional_attr = true,
+                          .result = true}));
 }  // namespace
diff --git a/rootdir/init.usb.rc b/rootdir/init.usb.rc
index 0730cce..dde784e 100644
--- a/rootdir/init.usb.rc
+++ b/rootdir/init.usb.rc
@@ -18,6 +18,7 @@
     disabled
     updatable
     seclabel u:r:adbd:s0
+    user root
 
 on property:vendor.sys.usb.adb.disabled=*
     setprop sys.usb.adb.disabled ${vendor.sys.usb.adb.disabled}
diff --git a/rootdir/ramdisk_node_list b/rootdir/ramdisk_node_list
index d3ab8a6..4f45faa 100644
--- a/rootdir/ramdisk_node_list
+++ b/rootdir/ramdisk_node_list
@@ -1,3 +1,4 @@
 dir dev 0755 0 0
 nod dev/null 0600 0 0 c 1 3
 nod dev/console 0600 0 0 c 5 1
+nod dev/urandom 0600 0 0 c 1 9