libsnapshot: Parser v3 implementation

Also adds a test for CowWriterV3 + CowParserV3.

Bug: 307452468
Test: read a header written by v3 writer
Change-Id: I77cf048604c82a010cfdbfb38d0f8beef597d112
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index c9777a3..91e0425 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -99,8 +99,10 @@
     uint64_t sequence_buffer_offset;
     // Size, in bytes, of the CowResumePoint buffer.
     uint32_t resume_buffer_size;
-    // Size, in bytes, of the CowOperation buffer.
-    uint32_t op_buffer_size;
+    // Number of CowOperationV3 structs in the operation buffer, currently and total
+    // region size.
+    uint32_t op_count;
+    uint32_t op_count_max;
     // Compression Algorithm
     uint32_t compression_algorithm;
 } __attribute__((packed));
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index 6f1854d..c20194d 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -29,6 +29,7 @@
 
 #include "cow_decompress.h"
 #include "parser_v2.h"
+#include "parser_v3.h"
 
 namespace android {
 namespace snapshot {
@@ -132,6 +133,7 @@
             parser = std::make_unique<CowParserV2>();
             break;
         case 3:
+            parser = std::make_unique<CowParserV3>();
             break;
         default:
             LOG(ERROR) << "Unknown version: " << header_.prefix.major_version;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
index 923ab64..23ddaae 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/parser_v3.cpp
@@ -24,15 +24,58 @@
 using android::base::borrowed_fd;
 
 bool CowParserV3::Parse(borrowed_fd fd, const CowHeaderV3& header, std::optional<uint64_t> label) {
-    LOG(ERROR) << "this function should never be called";
-    if (fd.get() || sizeof(header) > 0 || label) return false;
-    return false;
+    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 != 0) {
+        LOG(ERROR) << "Footer size isn't 0, read " << header_.footer_size;
+        return false;
+    }
+    if (header_.op_size != sizeof(CowOperationV3)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperationV3);
+        return false;
+    }
+    if (header_.cluster_ops != 0) {
+        LOG(ERROR) << "Cluster ops not supported in v3";
+        return false;
+    }
+
+    if (header_.prefix.major_version != 3 || header_.prefix.minor_version != 0) {
+        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 CowParserV3::ParseOps(android::base::borrowed_fd fd, std::optional<uint64_t> label) {
-    LOG(ERROR) << "this function should never be called";
-    if (fd.get() || label) return false;
-    return false;
+bool CowParserV3::ParseOps(borrowed_fd fd, std::optional<uint64_t> label) {
+    ops_ = std::make_shared<std::vector<CowOperationV3>>();
+    ops_->resize(header_.op_count);
+
+    const off_t offset = header_.prefix.header_size + header_.buffer_size;
+    if (!android::base::ReadFullyAtOffset(fd, ops_->data(), ops_->size() * sizeof(CowOperationV3),
+                                          offset)) {
+        PLOG(ERROR) << "read ops failed";
+        return false;
+    }
+
+    // :TODO: sequence buffer & resume buffer follow
+    // Once we implement labels, we'll have to discard unused ops and adjust
+    // the header as needed.
+    CHECK(!label);
+
+    ops_->shrink_to_fit();
+    return true;
 }
 
 bool CowParserV3::Translate(TranslatedCowOps* out) {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
index cc8dd83..a07ced5 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
@@ -37,7 +37,7 @@
 namespace android {
 namespace snapshot {
 
-class CowOperationV3Test : public ::testing::Test {
+class CowTestV3 : public ::testing::Test {
   protected:
     virtual void SetUp() override {
         cow_ = std::make_unique<TemporaryFile>();
@@ -51,7 +51,7 @@
     std::unique_ptr<TemporaryFile> cow_;
 };
 
-TEST_F(CowOperationV3Test, CowHeaderV2Test) {
+TEST_F(CowTestV3, CowHeaderV2Test) {
     CowOptions options;
     options.cluster_ops = 5;
     options.num_merge_ops = 1;
@@ -67,11 +67,27 @@
 
     const auto& header = reader.GetHeader();
     ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
-    ASSERT_EQ(header.prefix.major_version, kCowVersionMajor);
-    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.prefix.major_version, 2);
+    ASSERT_EQ(header.prefix.minor_version, 0);
     ASSERT_EQ(header.block_size, options.block_size);
     ASSERT_EQ(header.cluster_ops, options.cluster_ops);
 }
 
+TEST_F(CowTestV3, Header) {
+    CowOptions options;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.GetHeader();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, 0);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(header.cluster_ops, 0);
+}
+
 }  // namespace snapshot
-}  // namespace android
\ No newline at end of file
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index 5ae5f19..d69254f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -78,7 +78,8 @@
     // during COW size estimation
     header_.sequence_buffer_offset = 0;
     header_.resume_buffer_size = 0;
-    header_.op_buffer_size = 0;
+    header_.op_count = 0;
+    header_.op_count_max = 0;
     header_.compression_algorithm = kCowCompressNone;
     return;
 }
@@ -159,10 +160,6 @@
         LOG(ERROR) << "Header sync failed";
         return false;
     }
-
-    next_op_pos_ = 0;
-    next_data_pos_ = 0;
-
     return true;
 }
 
@@ -205,8 +202,7 @@
 }
 
 bool CowWriterV3::Finalize() {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    return false;
+    return Sync();
 }
 
 uint64_t CowWriterV3::GetCowSize() {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index 2a88a12..a0d7ab9 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -46,12 +46,9 @@
   private:
     CowHeaderV3 header_{};
     CowCompression compression_;
+
     // in the case that we are using one thread for compression, we can store and re-use the same
     // compressor
-
-    uint64_t next_op_pos_ = 0;
-    uint64_t next_data_pos_ = 0;
-
     int num_compress_threads_ = 1;
 };