Merge "init: Fix ramdump when enabling shutdown animations."
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index bec4ef1..7994065 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -396,7 +396,7 @@
 
     ConnectedDevicesStorage storage;
     std::set<std::string> devices;
-    {
+    if (storage.Exists()) {
         FileLock lock = storage.Lock();
         devices = storage.ReadDevices(lock);
     }
@@ -2583,10 +2583,13 @@
         if (fp->force_flash) {
             CancelSnapshotIfNeeded();
         }
+        std::vector<std::unique_ptr<Task>> wipe_tasks;
         std::vector<std::string> partitions = {"userdata", "cache", "metadata"};
         for (const auto& partition : partitions) {
-            tasks.emplace_back(std::make_unique<WipeTask>(fp.get(), partition));
+            wipe_tasks.emplace_back(std::make_unique<WipeTask>(fp.get(), partition));
         }
+        tasks.insert(tasks.begin(), std::make_move_iterator(wipe_tasks.begin()),
+                     std::make_move_iterator(wipe_tasks.end()));
     }
     if (fp->wants_set_active) {
         fb->SetActive(next_active);
diff --git a/fastboot/filesystem.h b/fastboot/filesystem.h
index 5f41fbc..c5f9c94 100644
--- a/fastboot/filesystem.h
+++ b/fastboot/filesystem.h
@@ -31,6 +31,7 @@
 #endif
 
 std::string GetHomeDirPath();
+bool FileExists(const std::string& path);
 bool EnsureDirectoryExists(const std::string& directory_path);
 
 class FileLock {
diff --git a/fastboot/storage.cpp b/fastboot/storage.cpp
index d6e00cf..dc733b9 100644
--- a/fastboot/storage.cpp
+++ b/fastboot/storage.cpp
@@ -23,24 +23,19 @@
 #include "util.h"
 
 ConnectedDevicesStorage::ConnectedDevicesStorage() {
-    const std::string home_path = GetHomeDirPath();
-    if (home_path.empty()) {
-        return;
-    }
-
-    const std::string home_fastboot_path = home_path + kPathSeparator + ".fastboot";
-
-    if (!EnsureDirectoryExists(home_fastboot_path)) {
-        LOG(FATAL) << "Cannot create directory: " << home_fastboot_path;
-    }
+    home_fastboot_path_ = GetHomeDirPath() + kPathSeparator + ".fastboot";
+    devices_path_ = home_fastboot_path_ + kPathSeparator + "devices";
 
     // We're using a separate file for locking because the Windows LockFileEx does not
     // permit opening a file stream for the locked file, even within the same process. So,
     // we have to use fd or handle API to manipulate the storage files, which makes it
     // nearly impossible to fully rewrite a file content without having to recreate it.
     // Unfortunately, this is not an option during holding a lock.
-    devices_path_ = home_fastboot_path + kPathSeparator + "devices";
-    devices_lock_path_ = home_fastboot_path + kPathSeparator + "devices.lock";
+    devices_lock_path_ = home_fastboot_path_ + kPathSeparator + "devices.lock";
+}
+
+bool ConnectedDevicesStorage::Exists() const {
+    return FileExists(devices_path_);
 }
 
 void ConnectedDevicesStorage::WriteDevices(const FileLock&, const std::set<std::string>& devices) {
@@ -63,5 +58,8 @@
 }
 
 FileLock ConnectedDevicesStorage::Lock() const {
+    if (!EnsureDirectoryExists(home_fastboot_path_)) {
+        LOG(FATAL) << "Cannot create directory: " << home_fastboot_path_;
+    }
     return FileLock(devices_lock_path_);
 }
\ No newline at end of file
diff --git a/fastboot/storage.h b/fastboot/storage.h
index 0cc3d86..ae9d846 100644
--- a/fastboot/storage.h
+++ b/fastboot/storage.h
@@ -24,13 +24,15 @@
 class ConnectedDevicesStorage {
   public:
     ConnectedDevicesStorage();
+
+    bool Exists() const;
     void WriteDevices(const FileLock&, const std::set<std::string>& devices);
     std::set<std::string> ReadDevices(const FileLock&);
     void Clear(const FileLock&);
-
     FileLock Lock() const;
 
   private:
+    std::string home_fastboot_path_;
     std::string devices_path_;
     std::string devices_lock_path_;
 };
\ No newline at end of file
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
index 9ce2cfd..03f9b89 100644
--- a/fastboot/task.cpp
+++ b/fastboot/task.cpp
@@ -46,6 +46,14 @@
     do_for_partitions(pname_, slot_, flash, true);
 }
 
+std::string FlashTask::ToString() {
+    std::string apply_vbmeta_string = "";
+    if (apply_vbmeta_) {
+        apply_vbmeta_string = " --apply_vbmeta";
+    }
+    return "flash" + apply_vbmeta_string + " " + pname_ + " " + fname_;
+}
+
 std::string FlashTask::GetPartitionAndSlot() {
     auto slot = slot_;
     if (slot.empty()) {
@@ -84,6 +92,10 @@
     }
 }
 
+std::string RebootTask::ToString() {
+    return "reboot " + reboot_target_;
+}
+
 FlashSuperLayoutTask::FlashSuperLayoutTask(const std::string& super_name,
                                            std::unique_ptr<SuperFlashHelper> helper,
                                            SparsePtr sparse_layout, uint64_t super_size)
@@ -106,6 +118,9 @@
     // Send the data to the device.
     flash_partition_files(super_name_, files);
 }
+std::string FlashSuperLayoutTask::ToString() {
+    return "optimized-flash-super";
+}
 
 std::unique_ptr<FlashSuperLayoutTask> FlashSuperLayoutTask::Initialize(
         const FlashingPlan* fp, std::vector<ImageEntry>& os_images) {
@@ -263,6 +278,9 @@
     }
     fp_->fb->RawCommand(command, "Updating super partition");
 }
+std::string UpdateSuperTask::ToString() {
+    return "update-super";
+}
 
 ResizeTask::ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size,
                        const std::string& slot)
@@ -277,12 +295,20 @@
     do_for_partitions(pname_, slot_, resize_partition, false);
 }
 
+std::string ResizeTask::ToString() {
+    return "resize " + pname_;
+}
+
 DeleteTask::DeleteTask(const FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
 
 void DeleteTask::Run() {
     fp_->fb->DeletePartition(pname_);
 }
 
+std::string DeleteTask::ToString() {
+    return "delete " + pname_;
+}
+
 WipeTask::WipeTask(const FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
 
 void WipeTask::Run() {
@@ -298,3 +324,7 @@
     }
     fb_perform_format(pname_, 1, partition_type, "", fp_->fs_options);
 }
+
+std::string WipeTask::ToString() {
+    return "erase " + pname_;
+}
diff --git a/fastboot/task.h b/fastboot/task.h
index 82e8ebf..500655d 100644
--- a/fastboot/task.h
+++ b/fastboot/task.h
@@ -35,6 +35,8 @@
   public:
     Task() = default;
     virtual void Run() = 0;
+    virtual std::string ToString() = 0;
+
     virtual FlashTask* AsFlashTask() { return nullptr; }
     virtual RebootTask* AsRebootTask() { return nullptr; }
     virtual UpdateSuperTask* AsUpdateSuperTask() { return nullptr; }
@@ -49,11 +51,12 @@
               const bool apply_vbmeta, const FlashingPlan* fp);
     virtual FlashTask* AsFlashTask() override { return this; }
 
+    void Run() override;
+    std::string ToString() override;
     std::string GetPartition() { return pname_; }
     std::string GetImageName() { return fname_; }
-    std::string GetPartitionAndSlot();
     std::string GetSlot() { return slot_; }
-    void Run() override;
+    std::string GetPartitionAndSlot();
 
   private:
     const std::string pname_;
@@ -69,6 +72,7 @@
     RebootTask(const FlashingPlan* fp, const std::string& reboot_target);
     virtual RebootTask* AsRebootTask() override { return this; }
     void Run() override;
+    std::string ToString() override;
 
   private:
     const std::string reboot_target_ = "";
@@ -85,6 +89,7 @@
             const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks);
     using ImageEntry = std::pair<const Image*, std::string>;
     void Run() override;
+    std::string ToString() override;
 
   private:
     const std::string super_name_;
@@ -99,6 +104,7 @@
     virtual UpdateSuperTask* AsUpdateSuperTask() override { return this; }
 
     void Run() override;
+    std::string ToString() override;
 
   private:
     const FlashingPlan* fp_;
@@ -109,6 +115,7 @@
     ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size,
                const std::string& slot);
     void Run() override;
+    std::string ToString() override;
 
   private:
     const FlashingPlan* fp_;
@@ -121,6 +128,7 @@
   public:
     DeleteTask(const FlashingPlan* fp, const std::string& pname);
     void Run() override;
+    std::string ToString() override;
 
   private:
     const FlashingPlan* fp_;
@@ -131,8 +139,8 @@
   public:
     WipeTask(const FlashingPlan* fp, const std::string& pname);
     virtual WipeTask* AsWipeTask() override { return this; }
-
     void Run() override;
+    std::string ToString() override;
 
   private:
     const FlashingPlan* fp_;
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 598a3d2..c3c10ba 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -242,7 +242,9 @@
                     LWARNING << "Warning: zramsize= flag malformed: " << arg;
                 }
             }
-        } else if (StartsWith(flag, "fileencryption=")) {
+        } else if (StartsWith(flag, "fileencryption=") || flag == "fileencryption") {
+            // "fileencryption" enables file-based encryption.  It's normally followed by an = and
+            // then the encryption options.  But that can be omitted to use the default options.
             ParseFileEncryption(arg, entry);
         } else if (StartsWith(flag, "max_comp_streams=")) {
             if (!ParseInt(arg, &entry->max_comp_streams)) {
diff --git a/fs_mgr/libfiemap/Android.bp b/fs_mgr/libfiemap/Android.bp
index 5deba65..ddda648 100644
--- a/fs_mgr/libfiemap/Android.bp
+++ b/fs_mgr/libfiemap/Android.bp
@@ -93,6 +93,9 @@
     test_options: {
         min_shipping_api_level: 29,
     },
+    header_libs: [
+        "libstorage_literals_headers",
+    ],
     require_root: true,
 }
 
diff --git a/fs_mgr/libfiemap/fiemap_writer_test.cpp b/fs_mgr/libfiemap/fiemap_writer_test.cpp
index c65481b..bd97a78 100644
--- a/fs_mgr/libfiemap/fiemap_writer_test.cpp
+++ b/fs_mgr/libfiemap/fiemap_writer_test.cpp
@@ -22,21 +22,25 @@
 #include <string.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
+#include <sys/statvfs.h>
 #include <sys/types.h>
 #include <sys/vfs.h>
 #include <unistd.h>
 
 #include <string>
+#include <utility>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
+#include <fstab/fstab.h>
 #include <gtest/gtest.h>
 #include <libdm/loop_control.h>
 #include <libfiemap/fiemap_writer.h>
 #include <libfiemap/split_fiemap_writer.h>
 #include <libgsi/libgsi.h>
+#include <storage_literals/storage_literals.h>
 
 #include "utility.h"
 
@@ -46,6 +50,7 @@
 using namespace std;
 using namespace std::string_literals;
 using namespace android::fiemap;
+using namespace android::storage_literals;
 using unique_fd = android::base::unique_fd;
 using LoopDevice = android::dm::LoopDevice;
 
@@ -427,90 +432,123 @@
     ASSERT_FALSE(ptr->Write(buffer.get(), kSize));
 }
 
-class VerifyBlockWritesExt4 : public ::testing::Test {
+// Get max file size and free space.
+std::pair<uint64_t, uint64_t> GetBigFileLimit(const std::string& mount_point) {
+    struct statvfs fs;
+    if (statvfs(mount_point.c_str(), &fs) < 0) {
+        PLOG(ERROR) << "statfs failed";
+        return {0, 0};
+    }
+
+    auto fs_limit = static_cast<uint64_t>(fs.f_blocks) * (fs.f_bsize - 1);
+    auto fs_free = static_cast<uint64_t>(fs.f_bfree) * fs.f_bsize;
+
+    LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free;
+
+    return {fs_limit, fs_free};
+}
+
+class FsTest : public ::testing::Test {
+  protected:
     // 2GB Filesystem and 4k block size by default
     static constexpr uint64_t block_size = 4096;
-    static constexpr uint64_t fs_size = 2147483648;
+    static constexpr uint64_t fs_size = 64 * 1024 * 1024;
 
-  protected:
-    void SetUp() override {
-        fs_path = std::string(getenv("TMPDIR")) + "/ext4_2G.img";
+    void SetUp() {
+        android::fs_mgr::Fstab fstab;
+        ASSERT_TRUE(android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab));
+
+        ASSERT_EQ(access(tmpdir_.path, F_OK), 0);
+        fs_path_ = tmpdir_.path + "/fs_image"s;
+        mntpoint_ = tmpdir_.path + "/mnt_point"s;
+
+        auto entry = android::fs_mgr::GetEntryForMountPoint(&fstab, "/data");
+        ASSERT_NE(entry, nullptr);
+        if (entry->fs_type == "ext4") {
+            SetUpExt4();
+        } else if (entry->fs_type == "f2fs") {
+            SetUpF2fs();
+        } else {
+            FAIL() << "Unrecognized fs_type: " << entry->fs_type;
+        }
+    }
+
+    void SetUpExt4() {
         uint64_t count = fs_size / block_size;
         std::string dd_cmd =
                 ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
                                               " count=%" PRIu64 " > /dev/null 2>&1",
-                                              fs_path.c_str(), block_size, count);
+                                              fs_path_.c_str(), block_size, count);
         std::string mkfs_cmd =
-                ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path.c_str());
+                ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path_.c_str());
         // create mount point
-        mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
-        ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
+        ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0);
         // create file for the file system
         int ret = system(dd_cmd.c_str());
         ASSERT_EQ(ret, 0);
         // Get and attach a loop device to the filesystem we created
-        LoopDevice loop_dev(fs_path, 10s);
+        LoopDevice loop_dev(fs_path_, 10s);
         ASSERT_TRUE(loop_dev.valid());
         // create file system
         ret = system(mkfs_cmd.c_str());
         ASSERT_EQ(ret, 0);
 
         // mount the file system
-        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "ext4", 0, nullptr), 0);
+        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "ext4", 0, nullptr), 0);
     }
 
-    void TearDown() override {
-        umount(mntpoint.c_str());
-        rmdir(mntpoint.c_str());
-        unlink(fs_path.c_str());
-    }
-
-    std::string mntpoint;
-    std::string fs_path;
-};
-
-class VerifyBlockWritesF2fs : public ::testing::Test {
-    // 2GB Filesystem and 4k block size by default
-    static constexpr uint64_t block_size = 4096;
-    static constexpr uint64_t fs_size = 2147483648;
-
-  protected:
-    void SetUp() override {
-        fs_path = std::string(getenv("TMPDIR")) + "/f2fs_2G.img";
+    void SetUpF2fs() {
         uint64_t count = fs_size / block_size;
         std::string dd_cmd =
                 ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64
                                               " count=%" PRIu64 " > /dev/null 2>&1",
-                                              fs_path.c_str(), block_size, count);
+                                              fs_path_.c_str(), block_size, count);
         std::string mkfs_cmd =
-                ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path.c_str());
+                ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path_.c_str());
         // create mount point
-        mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt";
-        ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0);
+        ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0);
         // create file for the file system
         int ret = system(dd_cmd.c_str());
         ASSERT_EQ(ret, 0);
         // Get and attach a loop device to the filesystem we created
-        LoopDevice loop_dev(fs_path, 10s);
+        LoopDevice loop_dev(fs_path_, 10s);
         ASSERT_TRUE(loop_dev.valid());
         // create file system
         ret = system(mkfs_cmd.c_str());
         ASSERT_EQ(ret, 0);
 
         // mount the file system
-        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "f2fs", 0, nullptr), 0);
+        ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "f2fs", 0, nullptr), 0);
     }
 
     void TearDown() override {
-        umount(mntpoint.c_str());
-        rmdir(mntpoint.c_str());
-        unlink(fs_path.c_str());
+        umount(mntpoint_.c_str());
+        rmdir(mntpoint_.c_str());
+        unlink(fs_path_.c_str());
     }
 
-    std::string mntpoint;
-    std::string fs_path;
+    TemporaryDir tmpdir_;
+    std::string mntpoint_;
+    std::string fs_path_;
 };
 
+TEST_F(FsTest, LowSpaceError) {
+    auto limits = GetBigFileLimit(mntpoint_);
+    ASSERT_GE(limits.first, 0);
+
+    FiemapUniquePtr ptr;
+
+    auto test_file = mntpoint_ + "/big_file";
+    auto status = FiemapWriter::Open(test_file, limits.first, &ptr);
+    ASSERT_FALSE(status.is_ok());
+    ASSERT_EQ(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE);
+
+    // Also test for EFBIG.
+    status = FiemapWriter::Open(test_file, 16_TiB, &ptr);
+    ASSERT_FALSE(status.is_ok());
+    ASSERT_NE(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE);
+}
+
 bool DetermineBlockSize() {
     struct statfs s;
     if (statfs(gTestDir.c_str(), &s)) {
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index d3bd904..cba6844 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -179,6 +179,7 @@
         "libsnapshot_cow/cow_writer.cpp",
         "libsnapshot_cow/cow_format.cpp",
         "libsnapshot_cow/cow_compress.cpp",
+        "libsnapshot_cow/parser_v2.cpp",
     ],
     host_supported: true,
     recovery_available: true,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index c3ca00a..b228dff 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -28,6 +28,9 @@
 
 static constexpr uint32_t kCowVersionManifest = 2;
 
+static constexpr uint32_t kMinCowVersion = 1;
+static constexpr uint32_t kMaxCowVersion = 2;
+
 // 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:
 //
@@ -52,13 +55,15 @@
 // between writing the last operation/data pair, or the footer itself. In this
 // case, the safest way to proceed is to assume the last operation is faulty.
 
-struct CowHeader {
+struct CowHeaderPrefix {
     uint64_t magic;
     uint16_t major_version;
     uint16_t minor_version;
+    uint16_t header_size;  // size of CowHeader.
+} __attribute__((packed));
 
-    // Size of this struct.
-    uint16_t header_size;
+struct CowHeader {
+    CowHeaderPrefix prefix;
 
     // Size of footer struct
     uint16_t footer_size;
@@ -88,7 +93,7 @@
     // the compression type of that data (see constants below).
     uint8_t compression;
 
-    // Length of Footer Data. Currently 64 for both checksums
+    // Length of Footer Data. Currently 64.
     uint16_t data_length;
 
     // The amount of file space used by Cow operations
@@ -98,14 +103,6 @@
     uint64_t num_ops;
 } __attribute__((packed));
 
-struct CowFooterData {
-    // SHA256 checksums of Footer op
-    uint8_t footer_checksum[32];
-
-    // SHA256 of the operation sequence.
-    uint8_t ops_checksum[32];
-} __attribute__((packed));
-
 // Cow operations are currently fixed-size entries, but this may change if
 // needed.
 struct CowOperation {
@@ -167,7 +164,7 @@
 
 struct CowFooter {
     CowFooterOperation op;
-    CowFooterData data;
+    uint8_t unused[64];
 } __attribute__((packed));
 
 struct ScratchMetadata {
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index c7b83a8..7881f35 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -93,9 +93,6 @@
     // Return number of bytes the cow image occupies on disk.
     virtual uint64_t GetCowSize() = 0;
 
-    // Returns true if AddCopy() operations are supported.
-    virtual bool SupportsCopyOperation() const { return true; }
-
     const CowOptions& options() { return options_; }
 
   protected:
@@ -171,8 +168,6 @@
 
     uint64_t GetCowSize() override;
 
-    uint32_t GetCowVersion() { return header_.major_version; }
-
   protected:
     virtual bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
     virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
index 29828bc..d798e25 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_writer.h
@@ -31,9 +31,6 @@
     // Return number of bytes the cow image occupies on disk.
     MOCK_METHOD(uint64_t, GetCowSize, (), (override));
 
-    // Returns true if AddCopy() operations are supported.
-    MOCK_METHOD(bool, SupportsCopyOperation, (), (const override));
-
     MOCK_METHOD(bool, EmitCopy, (uint64_t, uint64_t, uint64_t), (override));
     MOCK_METHOD(bool, EmitRawBlocks, (uint64_t, const void*, size_t), (override));
     MOCK_METHOD(bool, EmitXorBlocks, (uint32_t, const void*, size_t, uint32_t, uint16_t),
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 9eb89b6..ecf1d15 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -697,10 +697,6 @@
             LockedFile* lock, const std::optional<std::string>& source_device,
             const std::string& partition_name, const SnapshotStatus& status,
             const SnapshotPaths& paths);
-    std::unique_ptr<ISnapshotWriter> OpenKernelSnapshotWriter(
-            LockedFile* lock, const std::optional<std::string>& source_device,
-            const std::string& partition_name, const SnapshotStatus& status,
-            const SnapshotPaths& paths);
 
     // Map the base device, COW devices, and snapshot device.
     bool MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
index 0e3b1db..8f6344c 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
@@ -89,38 +89,5 @@
     std::unique_ptr<CowWriter> cow_;
 };
 
-// Write directly to a dm-snapshot device.
-class OnlineKernelSnapshotWriter final : public ISnapshotWriter {
-  public:
-    OnlineKernelSnapshotWriter(const CowOptions& options);
-
-    // Set the device used for all writes.
-    void SetSnapshotDevice(android::base::unique_fd&& snapshot_fd, uint64_t cow_size);
-
-    bool Initialize() override { return true; }
-    bool InitializeAppend(uint64_t) override { return true; }
-
-    bool Finalize() override;
-    uint64_t GetCowSize() override { return cow_size_; }
-    std::unique_ptr<FileDescriptor> OpenReader() override;
-
-    // Online kernel snapshot writer doesn't care about merge sequences.
-    // So ignore.
-    bool VerifyMergeOps() const noexcept override { return true; }
-
-  protected:
-    bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
-    bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
-    bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block,
-                       uint16_t offset) override;
-    bool EmitCopy(uint64_t new_block, uint64_t old_block, uint64_t num_blocks = 1) override;
-    bool EmitLabel(uint64_t label) override;
-    bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
-
-  private:
-    android::base::unique_fd snapshot_fd_;
-    uint64_t cow_size_ = 0;
-};
-
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
index edc9d65..8f80bb3 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_api_test.cpp
@@ -65,9 +65,9 @@
     ASSERT_TRUE(reader.Parse(cow_->fd));
 
     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.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
 
     CowFooter footer;
@@ -114,9 +114,9 @@
     ASSERT_TRUE(reader.Parse(cow_->fd));
 
     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.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
 
     CowFooter footer;
@@ -193,9 +193,9 @@
     ASSERT_TRUE(reader.Parse(cow_->fd));
 
     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.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
     ASSERT_EQ(header.block_size, options.block_size);
 
     CowFooter footer;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 6749cf1..c2a7fdb 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -30,7 +30,7 @@
 #include <zlib.h>
 
 #include "cow_decompress.h"
-#include "libsnapshot/cow_format.h"
+#include "parser_v2.h"
 
 namespace android {
 namespace snapshot {
@@ -43,15 +43,6 @@
       reader_flag_(reader_flag),
       is_merge_(is_merge) {}
 
-static void SHA256(const void*, size_t, uint8_t[]) {
-#if 0
-    SHA256_CTX c;
-    SHA256_Init(&c);
-    SHA256_Update(&c, data, length);
-    SHA256_Final(out, &c);
-#endif
-}
-
 std::unique_ptr<CowReader> CowReader::CloneCowReader() {
     auto cow = std::make_unique<CowReader>();
     cow->owned_fd_.reset();
@@ -63,7 +54,6 @@
     cow->merge_op_start_ = merge_op_start_;
     cow->num_total_data_ops_ = num_total_data_ops_;
     cow->num_ordered_ops_to_merge_ = num_ordered_ops_to_merge_;
-    cow->has_seq_ops_ = has_seq_ops_;
     cow->data_loc_ = data_loc_;
     cow->block_pos_index_ = block_pos_index_;
     cow->is_merge_ = is_merge_;
@@ -101,217 +91,26 @@
 bool CowReader::Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label) {
     fd_ = fd;
 
-    auto pos = lseek(fd_.get(), 0, SEEK_END);
-    if (pos < 0) {
-        PLOG(ERROR) << "lseek end failed";
-        return false;
-    }
-    fd_size_ = pos;
-
-    if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek header failed";
-        return false;
-    }
-    if (!android::base::ReadFully(fd_, &header_, sizeof(header_))) {
-        PLOG(ERROR) << "read header failed";
+    if (!ReadCowHeader(fd, &header_)) {
         return false;
     }
 
-    if (header_.magic != kCowMagicNumber) {
-        LOG(ERROR) << "Header Magic corrupted. Magic: " << header_.magic
-                   << "Expected: " << kCowMagicNumber;
-        return false;
-    }
-    if (header_.footer_size != sizeof(CowFooter)) {
-        LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected "
-                   << sizeof(CowFooter);
-        return false;
-    }
-    if (header_.op_size != sizeof(CowOperation)) {
-        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
-                   << sizeof(CowOperation);
-        return false;
-    }
-    if (header_.cluster_ops == 1) {
-        LOG(ERROR) << "Clusters must contain at least two operations to function.";
-        return false;
-    }
-    if (header_.op_size != sizeof(CowOperation)) {
-        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
-                   << sizeof(CowOperation);
-        return false;
-    }
-    if (header_.cluster_ops == 1) {
-        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+    CowParserV2 parser;
+    if (!parser.Parse(fd, header_, label)) {
         return false;
     }
 
-    if ((header_.major_version > kCowVersionMajor) || (header_.minor_version != kCowVersionMinor)) {
-        LOG(ERROR) << "Header version mismatch";
-        LOG(ERROR) << "Major version: " << header_.major_version
-                   << "Expected: " << kCowVersionMajor;
-        LOG(ERROR) << "Minor version: " << header_.minor_version
-                   << "Expected: " << kCowVersionMinor;
-        return false;
-    }
+    footer_ = parser.footer();
+    fd_size_ = parser.fd_size();
+    last_label_ = parser.last_label();
+    ops_ = std::move(parser.ops());
+    data_loc_ = parser.data_loc();
 
-    if (!ParseOps(label)) {
-        return false;
-    }
     // If we're resuming a write, we're not ready to merge
     if (label.has_value()) return true;
     return PrepMergeOps();
 }
 
-bool CowReader::ParseOps(std::optional<uint64_t> label) {
-    uint64_t pos;
-    auto data_loc = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
-
-    // Skip the scratch space
-    if (header_.major_version >= 2 && (header_.buffer_size > 0)) {
-        LOG(DEBUG) << " Scratch space found of size: " << header_.buffer_size;
-        size_t init_offset = header_.header_size + header_.buffer_size;
-        pos = lseek(fd_.get(), init_offset, SEEK_SET);
-        if (pos != init_offset) {
-            PLOG(ERROR) << "lseek ops failed";
-            return false;
-        }
-    } else {
-        pos = lseek(fd_.get(), header_.header_size, SEEK_SET);
-        if (pos != header_.header_size) {
-            PLOG(ERROR) << "lseek ops failed";
-            return false;
-        }
-        // Reading a v1 version of COW which doesn't have buffer_size.
-        header_.buffer_size = 0;
-    }
-    uint64_t data_pos = 0;
-
-    if (header_.cluster_ops) {
-        data_pos = pos + header_.cluster_ops * sizeof(CowOperation);
-    } else {
-        data_pos = pos + sizeof(CowOperation);
-    }
-
-    auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
-    uint64_t current_op_num = 0;
-    uint64_t cluster_ops = header_.cluster_ops ?: 1;
-    bool done = false;
-
-    // Alternating op clusters and data
-    while (!done) {
-        uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperation));
-        if (to_add == 0) break;
-        ops_buffer->resize(current_op_num + to_add);
-        if (!android::base::ReadFully(fd_, &ops_buffer->data()[current_op_num],
-                                      to_add * sizeof(CowOperation))) {
-            PLOG(ERROR) << "read op failed";
-            return false;
-        }
-        // Parse current cluster to find start of next cluster
-        while (current_op_num < ops_buffer->size()) {
-            auto& current_op = ops_buffer->data()[current_op_num];
-            current_op_num++;
-            if (current_op.type == kCowXorOp) {
-                data_loc->insert({current_op.new_block, data_pos});
-            }
-            pos += sizeof(CowOperation) + GetNextOpOffset(current_op, header_.cluster_ops);
-            data_pos += current_op.data_length + GetNextDataOffset(current_op, header_.cluster_ops);
-
-            if (current_op.type == kCowClusterOp) {
-                break;
-            } else if (current_op.type == kCowLabelOp) {
-                last_label_ = {current_op.source};
-
-                // If we reach the requested label, stop reading.
-                if (label && label.value() == current_op.source) {
-                    done = true;
-                    break;
-                }
-            } else if (current_op.type == kCowFooterOp) {
-                footer_.emplace();
-                CowFooter* footer = &footer_.value();
-                memcpy(&footer_->op, &current_op, sizeof(footer->op));
-                off_t offs = lseek(fd_.get(), pos, SEEK_SET);
-                if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
-                    PLOG(ERROR) << "lseek next op failed " << offs;
-                    return false;
-                }
-                if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
-                    LOG(ERROR) << "Could not read COW footer";
-                    return false;
-                }
-
-                // Drop the footer from the op stream.
-                current_op_num--;
-                done = true;
-                break;
-            } else if (current_op.type == kCowSequenceOp) {
-                has_seq_ops_ = true;
-            }
-        }
-
-        // Position for next cluster read
-        off_t offs = lseek(fd_.get(), pos, SEEK_SET);
-        if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
-            PLOG(ERROR) << "lseek next op failed " << offs;
-            return false;
-        }
-        ops_buffer->resize(current_op_num);
-    }
-
-    LOG(DEBUG) << "COW file read complete. Total ops: " << ops_buffer->size();
-    // To successfully parse a COW file, we need either:
-    //  (1) a label to read up to, and for that label to be found, or
-    //  (2) a valid footer.
-    if (label) {
-        if (!last_label_) {
-            LOG(ERROR) << "Did not find label " << label.value()
-                       << " while reading COW (no labels found)";
-            return false;
-        }
-        if (last_label_.value() != label.value()) {
-            LOG(ERROR) << "Did not find label " << label.value()
-                       << ", last label=" << last_label_.value();
-            return false;
-        }
-    } else if (!footer_) {
-        LOG(ERROR) << "No COW footer found";
-        return false;
-    }
-
-    uint8_t csum[32];
-    memset(csum, 0, sizeof(uint8_t) * 32);
-
-    if (footer_) {
-        if (ops_buffer->size() != footer_->op.num_ops) {
-            LOG(ERROR) << "num ops does not match, expected " << footer_->op.num_ops << ", found "
-                       << ops_buffer->size();
-            return false;
-        }
-        if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) {
-            LOG(ERROR) << "ops size does not match ";
-            return false;
-        }
-        SHA256(&footer_->op, sizeof(footer_->op), footer_->data.footer_checksum);
-        if (memcmp(csum, footer_->data.ops_checksum, sizeof(csum)) != 0) {
-            LOG(ERROR) << "ops checksum does not match";
-            return false;
-        }
-        SHA256(ops_buffer->data(), footer_->op.ops_size, csum);
-        if (memcmp(csum, footer_->data.ops_checksum, sizeof(csum)) != 0) {
-            LOG(ERROR) << "ops checksum does not match";
-            return false;
-        }
-    }
-
-    ops_ = ops_buffer;
-    ops_->shrink_to_fit();
-    data_loc_ = data_loc;
-
-    return true;
-}
-
 //
 // This sets up the data needed for MergeOpIter. MergeOpIter presents
 // data in the order we intend to merge in.
@@ -446,7 +245,8 @@
             continue;
         }
 
-        if (!has_seq_ops_ && IsOrderedOp(current_op)) {
+        // Sequence ops must be the first ops in the stream.
+        if (seq_ops_set.empty() && IsOrderedOp(current_op)) {
             merge_op_blocks->emplace_back(current_op.new_block);
         } else if (seq_ops_set.count(current_op.new_block) == 0) {
             other_ops.push_back(current_op.new_block);
@@ -718,8 +518,8 @@
 
 bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
     // Validate the offset, taking care to acknowledge possible overflow of offset+len.
-    if (offset < header_.header_size || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
-        offset + len > fd_size_ - sizeof(CowFooter)) {
+    if (offset < header_.prefix.header_size || offset >= fd_size_ - sizeof(CowFooter) ||
+        len >= fd_size_ || offset + len > fd_size_ - sizeof(CowFooter)) {
         LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
         return false;
     }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
index 0e18979..1eaa038 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_writer.cpp
@@ -186,10 +186,10 @@
 
 void CowWriter::SetupHeaders() {
     header_ = {};
-    header_.magic = kCowMagicNumber;
-    header_.major_version = kCowVersionMajor;
-    header_.minor_version = kCowVersionMinor;
-    header_.header_size = sizeof(CowHeader);
+    header_.prefix.magic = kCowMagicNumber;
+    header_.prefix.major_version = kCowVersionMajor;
+    header_.prefix.minor_version = kCowVersionMinor;
+    header_.prefix.header_size = sizeof(CowHeader);
     header_.footer_size = sizeof(CowFooter);
     header_.op_size = sizeof(CowOperation);
     header_.block_size = options_.block_size;
@@ -614,17 +614,6 @@
     return true;
 }
 
-// TODO: Fix compilation issues when linking libcrypto library
-// when snapuserd is compiled as part of ramdisk.
-static void SHA256(const void*, size_t, uint8_t[]) {
-#if 0
-    SHA256_CTX c;
-    SHA256_Init(&c);
-    SHA256_Update(&c, data, length);
-    SHA256_Final(out, &c);
-#endif
-}
-
 bool CowWriter::Finalize() {
     if (!FlushCluster()) {
         LOG(ERROR) << "Finalize: FlushCluster() failed";
@@ -665,10 +654,8 @@
         PLOG(ERROR) << "Failed to seek to footer position.";
         return false;
     }
-    memset(&footer_.data.ops_checksum, 0, sizeof(uint8_t) * 32);
-    memset(&footer_.data.footer_checksum, 0, sizeof(uint8_t) * 32);
+    memset(&footer_.unused, 0, sizeof(footer_.unused));
 
-    SHA256(&footer_.op, sizeof(footer_.op), footer_.data.footer_checksum);
     // Write out footer at end of file
     if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&footer_),
                                    sizeof(footer_))) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
index 917cec4..c2c86ee 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/inspect_cow.cpp
@@ -104,8 +104,9 @@
     if (reader.GetFooter(&footer)) has_footer = true;
 
     if (!opt.silent) {
-        std::cout << "Version: " << header.major_version << "." << header.minor_version << "\n";
-        std::cout << "Header size: " << header.header_size << "\n";
+        std::cout << "Version: " << header.prefix.major_version << "."
+                  << header.prefix.minor_version << "\n";
+        std::cout << "Header size: " << header.prefix.header_size << "\n";
         std::cout << "Footer size: " << header.footer_size << "\n";
         std::cout << "Block size: " << header.block_size << "\n";
         std::cout << "Merge ops: " << header.num_merge_ops << "\n";
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
new file mode 100644
index 0000000..fdb5c59
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.cpp
@@ -0,0 +1,238 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "parser_v2.h"
+
+#include <unistd.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::borrowed_fd;
+
+bool ReadCowHeader(android::base::borrowed_fd fd, CowHeader* header) {
+    if (lseek(fd.get(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek header failed";
+        return false;
+    }
+
+    memset(header, 0, sizeof(*header));
+
+    if (!android::base::ReadFully(fd, &header->prefix, sizeof(header->prefix))) {
+        return false;
+    }
+    if (header->prefix.magic != kCowMagicNumber) {
+        LOG(ERROR) << "Header Magic corrupted. Magic: " << header->prefix.magic
+                   << "Expected: " << kCowMagicNumber;
+        return false;
+    }
+    if (header->prefix.header_size > sizeof(CowHeader)) {
+        LOG(ERROR) << "Unknown CowHeader size (got " << header->prefix.header_size
+                   << " bytes, expected at most " << sizeof(CowHeader) << " bytes)";
+        return false;
+    }
+
+    if (lseek(fd.get(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek header failed";
+        return false;
+    }
+    return android::base::ReadFully(fd, header, header->prefix.header_size);
+}
+
+bool CowParserV2::Parse(borrowed_fd fd, const CowHeader& header, std::optional<uint64_t> label) {
+    auto pos = lseek(fd.get(), 0, SEEK_END);
+    if (pos < 0) {
+        PLOG(ERROR) << "lseek end failed";
+        return false;
+    }
+    fd_size_ = pos;
+    header_ = header;
+
+    if (header_.footer_size != sizeof(CowFooter)) {
+        LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected "
+                   << sizeof(CowFooter);
+        return false;
+    }
+    if (header_.op_size != sizeof(CowOperation)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperation);
+        return false;
+    }
+    if (header_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
+    if (header_.op_size != sizeof(CowOperation)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperation);
+        return false;
+    }
+    if (header_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
+
+    if ((header_.prefix.major_version > kCowVersionMajor) ||
+        (header_.prefix.minor_version != kCowVersionMinor)) {
+        LOG(ERROR) << "Header version mismatch, "
+                   << "major version: " << header_.prefix.major_version
+                   << ", expected: " << kCowVersionMajor
+                   << ", minor version: " << header_.prefix.minor_version
+                   << ", expected: " << kCowVersionMinor;
+        return false;
+    }
+
+    return ParseOps(fd, label);
+}
+
+bool CowParserV2::ParseOps(borrowed_fd fd, std::optional<uint64_t> label) {
+    uint64_t pos;
+    auto data_loc = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
+
+    // Skip the scratch space
+    if (header_.prefix.major_version >= 2 && (header_.buffer_size > 0)) {
+        LOG(DEBUG) << " Scratch space found of size: " << header_.buffer_size;
+        size_t init_offset = header_.prefix.header_size + header_.buffer_size;
+        pos = lseek(fd.get(), init_offset, SEEK_SET);
+        if (pos != init_offset) {
+            PLOG(ERROR) << "lseek ops failed";
+            return false;
+        }
+    } else {
+        pos = lseek(fd.get(), header_.prefix.header_size, SEEK_SET);
+        if (pos != header_.prefix.header_size) {
+            PLOG(ERROR) << "lseek ops failed";
+            return false;
+        }
+        // Reading a v1 version of COW which doesn't have buffer_size.
+        header_.buffer_size = 0;
+    }
+    uint64_t data_pos = 0;
+
+    if (header_.cluster_ops) {
+        data_pos = pos + header_.cluster_ops * sizeof(CowOperation);
+    } else {
+        data_pos = pos + sizeof(CowOperation);
+    }
+
+    auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
+    uint64_t current_op_num = 0;
+    uint64_t cluster_ops = header_.cluster_ops ?: 1;
+    bool done = false;
+
+    // Alternating op clusters and data
+    while (!done) {
+        uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperation));
+        if (to_add == 0) break;
+        ops_buffer->resize(current_op_num + to_add);
+        if (!android::base::ReadFully(fd, &ops_buffer->data()[current_op_num],
+                                      to_add * sizeof(CowOperation))) {
+            PLOG(ERROR) << "read op failed";
+            return false;
+        }
+        // Parse current cluster to find start of next cluster
+        while (current_op_num < ops_buffer->size()) {
+            auto& current_op = ops_buffer->data()[current_op_num];
+            current_op_num++;
+            if (current_op.type == kCowXorOp) {
+                data_loc->insert({current_op.new_block, data_pos});
+            }
+            pos += sizeof(CowOperation) + GetNextOpOffset(current_op, header_.cluster_ops);
+            data_pos += current_op.data_length + GetNextDataOffset(current_op, header_.cluster_ops);
+
+            if (current_op.type == kCowClusterOp) {
+                break;
+            } else if (current_op.type == kCowLabelOp) {
+                last_label_ = {current_op.source};
+
+                // If we reach the requested label, stop reading.
+                if (label && label.value() == current_op.source) {
+                    done = true;
+                    break;
+                }
+            } else if (current_op.type == kCowFooterOp) {
+                footer_.emplace();
+                CowFooter* footer = &footer_.value();
+                memcpy(&footer_->op, &current_op, sizeof(footer->op));
+                off_t offs = lseek(fd.get(), pos, SEEK_SET);
+                if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
+                    PLOG(ERROR) << "lseek next op failed " << offs;
+                    return false;
+                }
+                if (!android::base::ReadFully(fd, &footer->unused, sizeof(footer->unused))) {
+                    LOG(ERROR) << "Could not read COW footer";
+                    return false;
+                }
+
+                // Drop the footer from the op stream.
+                current_op_num--;
+                done = true;
+                break;
+            }
+        }
+
+        // Position for next cluster read
+        off_t offs = lseek(fd.get(), pos, SEEK_SET);
+        if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
+            PLOG(ERROR) << "lseek next op failed " << offs;
+            return false;
+        }
+        ops_buffer->resize(current_op_num);
+    }
+
+    LOG(DEBUG) << "COW file read complete. Total ops: " << ops_buffer->size();
+    // To successfully parse a COW file, we need either:
+    //  (1) a label to read up to, and for that label to be found, or
+    //  (2) a valid footer.
+    if (label) {
+        if (!last_label_) {
+            LOG(ERROR) << "Did not find label " << label.value()
+                       << " while reading COW (no labels found)";
+            return false;
+        }
+        if (last_label_.value() != label.value()) {
+            LOG(ERROR) << "Did not find label " << label.value()
+                       << ", last label=" << last_label_.value();
+            return false;
+        }
+    } else if (!footer_) {
+        LOG(ERROR) << "No COW footer found";
+        return false;
+    }
+
+    uint8_t csum[32];
+    memset(csum, 0, sizeof(uint8_t) * 32);
+
+    if (footer_) {
+        if (ops_buffer->size() != footer_->op.num_ops) {
+            LOG(ERROR) << "num ops does not match, expected " << footer_->op.num_ops << ", found "
+                       << ops_buffer->size();
+            return false;
+        }
+        if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) {
+            LOG(ERROR) << "ops size does not match ";
+            return false;
+        }
+    }
+
+    ops_ = ops_buffer;
+    ops_->shrink_to_fit();
+    data_loc_ = data_loc;
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
new file mode 100644
index 0000000..afcf687
--- /dev/null
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v2.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#pragma once
+
+#include <stdint.h>
+
+#include <memory>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <libsnapshot/cow_format.h>
+
+namespace android {
+namespace snapshot {
+
+class CowParserV2 {
+  public:
+    bool Parse(android::base::borrowed_fd fd, const CowHeader& header,
+               std::optional<uint64_t> label = {});
+
+    const CowHeader& header() const { return header_; }
+    const std::optional<CowFooter>& footer() const { return footer_; }
+    std::shared_ptr<std::vector<CowOperation>> ops() { return ops_; }
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc() const { return data_loc_; }
+    uint64_t fd_size() const { return fd_size_; }
+    const std::optional<uint64_t>& last_label() const { return last_label_; }
+
+  private:
+    bool ParseOps(android::base::borrowed_fd fd, std::optional<uint64_t> label);
+
+    CowHeader header_ = {};
+    std::optional<CowFooter> footer_;
+    std::shared_ptr<std::vector<CowOperation>> ops_;
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
+    uint64_t fd_size_;
+    std::optional<uint64_t> last_label_;
+};
+
+bool ReadCowHeader(android::base::borrowed_fd fd, CowHeader* header);
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 2661482..e114d25 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -3202,39 +3202,20 @@
     CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME &&
           target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME);
 
-    std::map<std::string, SnapshotStatus> all_snapshot_status;
-
-    // In case of error, automatically delete devices that are created along the way.
-    // Note that "lock" is destroyed after "created_devices", so it is safe to use |lock| for
-    // these devices.
-    AutoDeviceList created_devices;
-
     const auto& dap_metadata = manifest.dynamic_partition_metadata();
-    CowOptions options;
-    CowWriter writer(options);
-    bool cow_format_support = true;
-    if (dap_metadata.cow_version() < writer.GetCowVersion()) {
-        cow_format_support = false;
-    }
-
-    LOG(INFO) << " dap_metadata.cow_version(): " << dap_metadata.cow_version()
-              << " writer.GetCowVersion(): " << writer.GetCowVersion();
-
-    // Deduce supported features.
-    bool userspace_snapshots = CanUseUserspaceSnapshots();
-    bool legacy_compression = GetLegacyCompressionEnabledProperty();
 
     std::string vabc_disable_reason;
     if (!dap_metadata.vabc_enabled()) {
         vabc_disable_reason = "not enabled metadata";
     } else if (device_->IsRecovery()) {
         vabc_disable_reason = "recovery";
-    } else if (!cow_format_support) {
-        vabc_disable_reason = "cow format not supported";
     } else if (!KernelSupportsCompressedSnapshots()) {
         vabc_disable_reason = "kernel missing userspace block device support";
     }
 
+    // Deduce supported features.
+    bool userspace_snapshots = CanUseUserspaceSnapshots();
+    bool legacy_compression = GetLegacyCompressionEnabledProperty();
     if (!vabc_disable_reason.empty()) {
         if (userspace_snapshots) {
             LOG(INFO) << "Userspace snapshots disabled: " << vabc_disable_reason;
@@ -3246,6 +3227,16 @@
         legacy_compression = false;
     }
 
+    if (legacy_compression || userspace_snapshots) {
+        if (dap_metadata.cow_version() < kMinCowVersion ||
+            dap_metadata.cow_version() > kMaxCowVersion) {
+            LOG(ERROR) << "Manifest cow version is out of bounds (got: "
+                       << dap_metadata.cow_version() << ", min: " << kMinCowVersion
+                       << ", max: " << kMaxCowVersion << ")";
+            return Return::Error();
+        }
+    }
+
     const bool using_snapuserd = userspace_snapshots || legacy_compression;
     if (!using_snapuserd) {
         LOG(INFO) << "Using legacy Virtual A/B (dm-snapshot)";
@@ -3278,6 +3269,11 @@
         cow_creator.batched_writes = dap_metadata.vabc_feature_set().batch_writes();
     }
 
+    // In case of error, automatically delete devices that are created along the way.
+    // Note that "lock" is destroyed after "created_devices", so it is safe to use |lock| for
+    // these devices.
+    AutoDeviceList created_devices;
+    std::map<std::string, SnapshotStatus> all_snapshot_status;
     auto ret = CreateUpdateSnapshotsInternal(lock.get(), manifest, &cow_creator, &created_devices,
                                              &all_snapshot_status);
     if (!ret.is_ok()) {
@@ -3651,12 +3647,13 @@
         return nullptr;
     }
 
-    if (status.using_snapuserd()) {
-        return OpenCompressedSnapshotWriter(lock.get(), source_device, params.GetPartitionName(),
-                                            status, paths);
+    if (!status.using_snapuserd()) {
+        LOG(ERROR) << "Can only create snapshot writers with userspace or compressed snapshots";
+        return nullptr;
     }
-    return OpenKernelSnapshotWriter(lock.get(), source_device, params.GetPartitionName(), status,
-                                    paths);
+
+    return OpenCompressedSnapshotWriter(lock.get(), source_device, params.GetPartitionName(),
+                                        status, paths);
 #endif
 }
 
@@ -3704,34 +3701,6 @@
 
     return writer;
 }
-
-std::unique_ptr<ISnapshotWriter> SnapshotManager::OpenKernelSnapshotWriter(
-        LockedFile* lock, const std::optional<std::string>& source_device,
-        [[maybe_unused]] const std::string& partition_name, const SnapshotStatus& status,
-        const SnapshotPaths& paths) {
-    CHECK(lock);
-
-    CowOptions cow_options;
-    cow_options.max_blocks = {status.device_size() / cow_options.block_size};
-
-    auto writer = std::make_unique<OnlineKernelSnapshotWriter>(cow_options);
-
-    std::string path = paths.snapshot_device.empty() ? paths.target_device : paths.snapshot_device;
-    unique_fd fd(open(path.c_str(), O_RDWR | O_CLOEXEC));
-    if (fd < 0) {
-        PLOG(ERROR) << "open failed: " << path;
-        return nullptr;
-    }
-
-    if (source_device) {
-        writer->SetSourceDevice(*source_device);
-    }
-
-    uint64_t cow_size = status.cow_partition_size() + status.cow_file_size();
-    writer->SetSnapshotDevice(std::move(fd), cow_size);
-
-    return writer;
-}
 #endif  // !defined(LIBSNAPSHOT_NO_COW_WRITE)
 
 bool SnapshotManager::UnmapUpdateSnapshot(const std::string& target_partition_name) {
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 22731e7..757f6f1 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -643,21 +643,38 @@
 TEST_F(SnapshotTest, Merge) {
     ASSERT_TRUE(AcquireLock());
 
-    static const uint64_t kDeviceSize = 1024 * 1024;
-
-    std::unique_ptr<ISnapshotWriter> writer;
-    ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer));
-
-    bool userspace_snapshots = sm->UpdateUsesUserSnapshots(lock_.get());
-
-    // Release the lock.
-    lock_ = nullptr;
+    static constexpr uint64_t kDeviceSize = 1024 * 1024;
+    static constexpr uint32_t kBlockSize = 4096;
 
     std::string test_string = "This is a test string.";
-    test_string.resize(writer->options().block_size);
-    ASSERT_TRUE(writer->AddRawBlocks(0, test_string.data(), test_string.size()));
-    ASSERT_TRUE(writer->Finalize());
-    writer = nullptr;
+    test_string.resize(kBlockSize);
+
+    bool userspace_snapshots = false;
+    if (snapuserd_required_) {
+        std::unique_ptr<ISnapshotWriter> writer;
+        ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer));
+
+        userspace_snapshots = sm->UpdateUsesUserSnapshots(lock_.get());
+
+        // Release the lock.
+        lock_ = nullptr;
+
+        ASSERT_TRUE(writer->AddRawBlocks(0, test_string.data(), test_string.size()));
+        ASSERT_TRUE(writer->Finalize());
+        writer = nullptr;
+    } else {
+        ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
+
+        // Release the lock.
+        lock_ = nullptr;
+
+        std::string path;
+        ASSERT_TRUE(dm_.GetDmDevicePathByName("test_partition_b", &path));
+
+        unique_fd fd(open(path.c_str(), O_WRONLY));
+        ASSERT_GE(fd, 0);
+        ASSERT_TRUE(android::base::WriteFully(fd, test_string.data(), test_string.size()));
+    }
 
     // Done updating.
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
@@ -2372,60 +2389,6 @@
             << "FinishedSnapshotWrites should detect overflow of CoW device.";
 }
 
-// Get max file size and free space.
-std::pair<uint64_t, uint64_t> GetBigFileLimit() {
-    struct statvfs fs;
-    if (statvfs("/data", &fs) < 0) {
-        PLOG(ERROR) << "statfs failed";
-        return {0, 0};
-    }
-
-    auto fs_limit = static_cast<uint64_t>(fs.f_blocks) * (fs.f_bsize - 1);
-    auto fs_free = static_cast<uint64_t>(fs.f_bfree) * fs.f_bsize;
-
-    LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free;
-
-    return {fs_limit, fs_free};
-}
-
-TEST_F(SnapshotUpdateTest, LowSpace) {
-    // To make the low space test more reliable, we force a large cow estimate.
-    // However legacy VAB ignores the COW estimate and uses InstallOperations
-    // to compute the exact size required for dm-snapshot. It's difficult to
-    // make this work reliably (we'd need to somehow fake an extremely large
-    // super partition, and we don't have that level of dependency injection).
-    //
-    // For now, just skip this test on legacy VAB.
-    if (!snapuserd_required_) {
-        GTEST_SKIP() << "Skipping test on legacy VAB";
-    }
-
-    auto fs = GetBigFileLimit();
-    ASSERT_NE(fs.first, 0);
-
-    constexpr uint64_t partition_size = 10_MiB;
-    SetSize(sys_, partition_size);
-    SetSize(vnd_, partition_size);
-    SetSize(prd_, partition_size);
-    sys_->set_estimate_cow_size(fs.first);
-    vnd_->set_estimate_cow_size(fs.first);
-    prd_->set_estimate_cow_size(fs.first);
-
-    AddOperationForPartitions();
-
-    // Execute the update.
-    ASSERT_TRUE(sm->BeginUpdate());
-    auto res = sm->CreateUpdateSnapshots(manifest_);
-    ASSERT_FALSE(res);
-    ASSERT_EQ(Return::ErrorCode::NO_SPACE, res.error_code());
-
-    // It's hard to predict exactly how much free space is needed, since /data
-    // is writable and the test is not the only process running. Divide by two
-    // as a rough lower bound, and adjust this in the future as necessary.
-    auto expected_delta = fs.first - fs.second;
-    ASSERT_GE(res.required_size(), expected_delta / 2);
-}
-
 TEST_F(SnapshotUpdateTest, AddPartition) {
     group_->add_partition_names("dlkm");
 
@@ -2688,6 +2651,24 @@
     ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
 }
 
+TEST_F(SnapshotUpdateTest, BadCowVersion) {
+    if (!snapuserd_required_) {
+        GTEST_SKIP() << "VABC only";
+    }
+
+    ASSERT_TRUE(sm->BeginUpdate());
+
+    auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
+    dynamic_partition_metadata->set_cow_version(kMinCowVersion - 1);
+    ASSERT_FALSE(sm->CreateUpdateSnapshots(manifest_));
+
+    dynamic_partition_metadata->set_cow_version(kMaxCowVersion + 1);
+    ASSERT_FALSE(sm->CreateUpdateSnapshots(manifest_));
+
+    dynamic_partition_metadata->set_cow_version(kMaxCowVersion);
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+}
+
 class FlashAfterUpdateTest : public SnapshotUpdateTest,
                              public WithParamInterface<std::tuple<uint32_t, bool>> {
   public:
@@ -2796,38 +2777,6 @@
                                     "Merge"s;
                          });
 
-class ImageManagerTest : public SnapshotTest {
-  protected:
-    void SetUp() override {
-        SKIP_IF_NON_VIRTUAL_AB();
-        SnapshotTest::SetUp();
-    }
-    void TearDown() override {
-        RETURN_IF_NON_VIRTUAL_AB();
-        CleanUp();
-        SnapshotTest::TearDown();
-    }
-    void CleanUp() {
-        if (!image_manager_) {
-            return;
-        }
-        EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) ||
-                    image_manager_->DeleteBackingImage(kImageName));
-    }
-
-    static constexpr const char* kImageName = "my_image";
-};
-
-TEST_F(ImageManagerTest, CreateImageNoSpace) {
-    auto fs = GetBigFileLimit();
-    ASSERT_NE(fs.first, 0);
-
-    auto res = image_manager_->CreateBackingImage(kImageName, fs.first,
-                                                  IImageManager::CREATE_IMAGE_DEFAULT);
-    ASSERT_FALSE(res);
-    ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string();
-}
-
 bool Mkdir(const std::string& path) {
     if (mkdir(path.c_str(), 0700) && errno != EEXIST) {
         std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl;
diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp
index 82a7fd7..6a3906e 100644
--- a/fs_mgr/libsnapshot/snapshot_writer.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer.cpp
@@ -149,95 +149,5 @@
     return cow_->InitializeAppend(cow_device_, label);
 }
 
-OnlineKernelSnapshotWriter::OnlineKernelSnapshotWriter(const CowOptions& options)
-    : ISnapshotWriter(options) {}
-
-void OnlineKernelSnapshotWriter::SetSnapshotDevice(android::base::unique_fd&& snapshot_fd,
-                                                   uint64_t cow_size) {
-    snapshot_fd_ = std::move(snapshot_fd);
-    cow_size_ = cow_size;
-}
-
-bool OnlineKernelSnapshotWriter::Finalize() {
-    if (fsync(snapshot_fd_.get()) < 0) {
-        PLOG(ERROR) << "fsync";
-        return false;
-    }
-    return true;
-}
-
-bool OnlineKernelSnapshotWriter::EmitRawBlocks(uint64_t new_block_start, const void* data,
-                                               size_t size) {
-    uint64_t offset = new_block_start * options_.block_size;
-    if (lseek(snapshot_fd_.get(), offset, SEEK_SET) < 0) {
-        PLOG(ERROR) << "EmitRawBlocks lseek to offset " << offset;
-        return false;
-    }
-    if (!android::base::WriteFully(snapshot_fd_, data, size)) {
-        PLOG(ERROR) << "EmitRawBlocks write";
-        return false;
-    }
-    return true;
-}
-
-bool OnlineKernelSnapshotWriter::EmitXorBlocks(uint32_t, const void*, size_t, uint32_t, uint16_t) {
-    LOG(ERROR) << "EmitXorBlocks not implemented.";
-    return false;
-}
-
-bool OnlineKernelSnapshotWriter::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
-    std::string zeroes(options_.block_size, 0);
-    for (uint64_t i = 0; i < num_blocks; i++) {
-        if (!EmitRawBlocks(new_block_start + i, zeroes.data(), zeroes.size())) {
-            return false;
-        }
-    }
-    return true;
-}
-
-bool OnlineKernelSnapshotWriter::EmitCopy(uint64_t new_block, uint64_t old_block,
-                                          uint64_t num_blocks) {
-    auto source_fd = GetSourceFd();
-    if (source_fd < 0) {
-        return false;
-    }
-
-    CHECK(num_blocks != 0);
-
-    for (size_t i = 0; i < num_blocks; i++) {
-        std::string buffer(options_.block_size, 0);
-        uint64_t offset = (old_block + i) * options_.block_size;
-        if (!android::base::ReadFullyAtOffset(source_fd, buffer.data(), buffer.size(), offset)) {
-            PLOG(ERROR) << "EmitCopy read";
-            return false;
-        }
-        if (!EmitRawBlocks(new_block + i, buffer.data(), buffer.size())) {
-            PLOG(ERROR) << "EmitRawBlocks failed";
-            return false;
-        }
-    }
-
-    return true;
-}
-
-bool OnlineKernelSnapshotWriter::EmitLabel(uint64_t) {
-    // Not Needed
-    return true;
-}
-
-bool OnlineKernelSnapshotWriter::EmitSequenceData(size_t, const uint32_t*) {
-    // Not Needed
-    return true;
-}
-
-std::unique_ptr<FileDescriptor> OnlineKernelSnapshotWriter::OpenReader() {
-    unique_fd fd(dup(snapshot_fd_.get()));
-    if (fd < 0) {
-        PLOG(ERROR) << "dup2 failed in OpenReader";
-        return nullptr;
-    }
-    return std::make_unique<ReadFdFileDescriptor>(std::move(fd));
-}
-
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index efa43b7..da9bd11 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -628,8 +628,8 @@
 bool Snapuserd::MmapMetadata() {
     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;
+    if (header.prefix.major_version >= 2 && header.buffer_size > 0) {
+        total_mapped_addr_length_ = header.prefix.header_size + BUFFER_REGION_DEFAULT_SIZE;
         read_ahead_feature_ = true;
     } else {
         // mmap the first 4k page - older COW format
@@ -823,7 +823,7 @@
 uint64_t Snapuserd::GetBufferMetadataOffset() {
     const auto& header = reader_->GetHeader();
 
-    size_t size = header.header_size + sizeof(BufferState);
+    size_t size = header.prefix.header_size + sizeof(BufferState);
     return size;
 }
 
@@ -845,7 +845,7 @@
 size_t Snapuserd::GetBufferDataOffset() {
     const auto& header = reader_->GetHeader();
 
-    return (header.header_size + GetBufferMetadataSize());
+    return (header.prefix.header_size + GetBufferMetadataSize());
 }
 
 /*
@@ -862,7 +862,7 @@
     const auto& header = reader_->GetHeader();
 
     struct BufferState* ra_state =
-            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.prefix.header_size);
     return ra_state;
 }
 
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 a519639..c3343b8 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -240,9 +240,9 @@
 bool SnapshotHandler::MmapMetadata() {
     const auto& header = reader_->GetHeader();
 
-    total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+    total_mapped_addr_length_ = header.prefix.header_size + BUFFER_REGION_DEFAULT_SIZE;
 
-    if (header.major_version >= 2 && header.buffer_size > 0) {
+    if (header.prefix.major_version >= 2 && header.buffer_size > 0) {
         scratch_space_ = true;
     }
 
@@ -362,7 +362,7 @@
 uint64_t SnapshotHandler::GetBufferMetadataOffset() {
     const auto& header = reader_->GetHeader();
 
-    return (header.header_size + sizeof(BufferState));
+    return (header.prefix.header_size + sizeof(BufferState));
 }
 
 /*
@@ -390,7 +390,7 @@
 size_t SnapshotHandler::GetBufferDataOffset() {
     const auto& header = reader_->GetHeader();
 
-    return (header.header_size + GetBufferMetadataSize());
+    return (header.prefix.header_size + GetBufferMetadataSize());
 }
 
 /*
@@ -413,7 +413,7 @@
     const auto& header = reader_->GetHeader();
 
     struct BufferState* ra_state =
-            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.prefix.header_size);
     return ra_state;
 }
 
diff --git a/fs_mgr/libstorage_literals/storage_literals/storage_literals.h b/fs_mgr/libstorage_literals/storage_literals/storage_literals.h
index ac0dfbd..bbeabd5 100644
--- a/fs_mgr/libstorage_literals/storage_literals/storage_literals.h
+++ b/fs_mgr/libstorage_literals/storage_literals/storage_literals.h
@@ -37,6 +37,7 @@
 using KiB = Size<10>;
 using MiB = Size<20>;
 using GiB = Size<30>;
+using TiB = Size<40>;
 
 constexpr B operator""_B(unsigned long long v) {  // NOLINT
     return B{v};
@@ -54,6 +55,10 @@
     return GiB{v};
 }
 
+constexpr TiB operator""_TiB(unsigned long long v) {  // NOLINT
+    return TiB{v};
+}
+
 template <typename Dest, typename Src>
 constexpr Dest size_cast(Src src) {
     if (Src::power < Dest::power) {
@@ -69,6 +74,7 @@
 static_assert(1_KiB == 1 << 10);
 static_assert(1_MiB == 1 << 20);
 static_assert(1_GiB == 1 << 30);
+static_assert(1_TiB == 1ULL << 40);
 static_assert(size_cast<KiB>(1_B).count() == 0);
 static_assert(size_cast<KiB>(1024_B).count() == 1);
 static_assert(size_cast<KiB>(1_MiB).count() == 1024);
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index e33681c..5f889ca 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -497,6 +497,7 @@
     EXPECT_EQ("none0", entry->mount_point);
     {
         FstabEntry::FsMgrFlags flags = {};
+        flags.file_encryption = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
     EXPECT_EQ("", entry->metadata_key_dir);
diff --git a/fs_mgr/tests/vts_fs_test.cpp b/fs_mgr/tests/vts_fs_test.cpp
index bb2ceb9..4d771fa 100644
--- a/fs_mgr/tests/vts_fs_test.cpp
+++ b/fs_mgr/tests/vts_fs_test.cpp
@@ -55,6 +55,21 @@
 }
 
 TEST(fs, PartitionTypes) {
+    // Requirements only apply to Android 13+, 5.10+ devices.
+    int vsr_level = GetVsrLevel();
+    if (vsr_level < __ANDROID_API_T__) {
+        GTEST_SKIP();
+    }
+
+    struct utsname uts;
+    ASSERT_EQ(uname(&uts), 0);
+
+    unsigned int major, minor;
+    ASSERT_EQ(sscanf(uts.release, "%u.%u", &major, &minor), 2);
+    if (major < 5 || (major == 5 && minor < 10)) {
+        GTEST_SKIP();
+    }
+
     android::fs_mgr::Fstab fstab;
     ASSERT_TRUE(android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab));
 
@@ -64,12 +79,7 @@
     ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/super", &super_bdev));
     ASSERT_TRUE(android::base::Readlink("/dev/block/by-name/userdata", &userdata_bdev));
 
-    int vsr_level = GetVsrLevel();
-
-    std::vector<std::string> must_be_f2fs;
-    if (vsr_level >= __ANDROID_API_T__) {
-        must_be_f2fs.emplace_back("/data");
-    }
+    std::vector<std::string> must_be_f2fs = {"/data"};
     if (vsr_level >= __ANDROID_API_U__) {
         must_be_f2fs.emplace_back("/metadata");
     }
@@ -98,17 +108,13 @@
             continue;
         }
 
-        if (vsr_level < __ANDROID_API_T__) {
-            continue;
-        }
-        if (vsr_level == __ANDROID_API_T__ && parent_bdev != super_bdev) {
-            // Only check for dynamic partitions at this VSR level.
-            continue;
-        }
-
         if (entry.flags & MS_RDONLY) {
-            std::vector<std::string> allowed = {"erofs", "ext4", "f2fs"};
+            if (parent_bdev != super_bdev) {
+                // Ignore non-AOSP partitions (eg anything outside of super).
+                continue;
+            }
 
+            std::vector<std::string> allowed = {"erofs", "ext4", "f2fs"};
             EXPECT_NE(std::find(allowed.begin(), allowed.end(), entry.fs_type), allowed.end())
                     << entry.mount_point;
         } else {
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index b135e57..0b5c125 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -286,11 +286,14 @@
     ],
 }
 
+always_static_test_libraries = [
+    "libjsoncpp",
+]
+
 test_libraries = [
     "libcutils",
     "liblog",
     "libbase",
-    "libjsoncpp",
     "libprocessgroup",
     "libcgrouprc",
 ]
@@ -301,6 +304,7 @@
     defaults: ["libcutils_test_default"],
     host_supported: true,
     shared_libs: test_libraries,
+    static_libs: always_static_test_libraries,
     require_root: true,
 }
 
@@ -310,7 +314,7 @@
     static_libs: [
         "libc",
         "libcgrouprc_format",
-    ] + test_libraries,
+    ] + test_libraries + always_static_test_libraries,
     stl: "libc++_static",
     require_root: true,
 
diff --git a/libcutils/KernelLibcutilsTest.xml b/libcutils/KernelLibcutilsTest.xml
index 40e4ef4..9750cbf 100644
--- a/libcutils/KernelLibcutilsTest.xml
+++ b/libcutils/KernelLibcutilsTest.xml
@@ -22,11 +22,11 @@
 
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
-        <option name="push" value="KernelLibcutilsTest->/data/local/tmp/KernelLibcutilsTest" />
+        <option name="push" value="KernelLibcutilsTest->/data/local/tests/unrestricted/KernelLibcutilsTest" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="native-test-device-path" value="/data/local/tests/unrestricted" />
         <option name="module-name" value="KernelLibcutilsTest" />
         <option name="include-filter" value="*AshmemTest*" />
     </test>
diff --git a/libcutils/TEST_MAPPING b/libcutils/TEST_MAPPING
index eb63aa5..78b3e44 100644
--- a/libcutils/TEST_MAPPING
+++ b/libcutils/TEST_MAPPING
@@ -12,6 +12,9 @@
   "kernel-presubmit": [
     {
       "name": "libcutils_test"
+    },
+    {
+      "name": "KernelLibcutilsTest"
     }
   ]
 }
diff --git a/libutils/Errors.cpp b/libutils/Errors.cpp
index 74f3bef..dfb4d9b 100644
--- a/libutils/Errors.cpp
+++ b/libutils/Errors.cpp
@@ -15,6 +15,8 @@
  */
 #include <utils/Errors.h>
 
+#include <string.h>
+
 namespace android {
 
 std::string statusToString(status_t s) {