Merge "libsnapshot: Implement CowWriterV3::EmitzeroBlocks." into main
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 4cbcab1..755a3af 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -145,6 +145,7 @@
                      size_t ignore_bytes = 0) override;
 
     CowHeader& GetHeader() override { return header_; }
+    const CowHeaderV3& header_v3() const { return header_; }
 
     bool GetRawBytes(const CowOperation* op, void* buffer, size_t len, size_t* read);
     bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index 3016e93..5b1e56c 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -54,6 +54,9 @@
 
     // Batch write cluster ops
     bool batch_write = false;
+
+    // Size of the cow operation buffer; used in v3 only.
+    uint32_t op_count_max = 0;
 };
 
 // Interface for writing to a snapuserd COW. All operations are ordered; merges
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
index a07ced5..07c1d4f 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
@@ -89,5 +89,36 @@
     ASSERT_EQ(header.cluster_ops, 0);
 }
 
+TEST_F(CowTestV3, ZeroOp) {
+    CowOptions options;
+    options.op_count_max = 20;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    ASSERT_TRUE(writer->AddZeroBlocks(1, 2));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    ASSERT_EQ(reader.header_v3().op_count, 2);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    auto op = iter->Get();
+    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->data_length, 0);
+    ASSERT_EQ(op->new_block, 1);
+    ASSERT_EQ(op->source_info, 0);
+
+    iter->Next();
+    ASSERT_FALSE(iter->AtEnd());
+    op = iter->Get();
+
+    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->data_length, 0);
+    ASSERT_EQ(op->new_block, 2);
+    ASSERT_EQ(op->source_info, 0);
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
index 709b248..5274456 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_base.h
@@ -62,6 +62,8 @@
     bool InitFd();
     bool ValidateNewBlock(uint64_t new_block);
 
+    bool IsEstimating() const { return is_dev_null_; }
+
     CowOptions options_;
 
     android::base::unique_fd fd_;
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index d69254f..ecbf97e 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -155,6 +155,7 @@
             return false;
         }
     }
+    header_.op_count_max = options_.op_count_max;
 
     if (!Sync()) {
         LOG(ERROR) << "Header sync failed";
@@ -184,9 +185,17 @@
 }
 
 bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-    if (new_block_start && num_blocks) return false;
-    return false;
+    for (uint64_t i = 0; i < num_blocks; i++) {
+        CowOperationV3 op;
+        op.type = kCowZeroOp;
+        op.data_length = 0;
+        op.new_block = new_block_start + i;
+        op.source_info = 0;
+        if (!WriteOperation(op)) {
+            return false;
+        }
+    }
+    return true;
 }
 
 bool CowWriterV3::EmitLabel(uint64_t label) {
@@ -201,7 +210,33 @@
     return false;
 }
 
+bool CowWriterV3::WriteOperation(const CowOperationV3& op) {
+    if (IsEstimating()) {
+        header_.op_count++;
+        header_.op_count_max++;
+        return true;
+    }
+
+    if (header_.op_count + 1 > header_.op_count_max) {
+        LOG(ERROR) << "Maximum number of ops reached: " << header_.op_count_max;
+        return false;
+    }
+
+    const off_t offset = GetOpOffset(header_.op_count);
+    if (!android::base::WriteFullyAtOffset(fd_, &op, sizeof(op), offset)) {
+        return false;
+    }
+
+    header_.op_count++;
+    return true;
+}
+
 bool CowWriterV3::Finalize() {
+    CHECK_GE(header_.prefix.header_size, sizeof(CowHeaderV3));
+    CHECK_LE(header_.prefix.header_size, sizeof(header_));
+    if (!android::base::WriteFullyAtOffset(fd_, &header_, header_.prefix.header_size, 0)) {
+        return false;
+    }
     return Sync();
 }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index a0d7ab9..8717c01 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -14,7 +14,8 @@
 
 #pragma once
 
-#include <future>
+#include <android-base/logging.h>
+
 #include "writer_base.h"
 
 namespace android {
@@ -42,6 +43,13 @@
     void SetupHeaders();
     bool ParseOptions();
     bool OpenForWrite();
+    bool WriteOperation(const CowOperationV3& op);
+
+    off_t GetOpOffset(uint32_t op_index) const {
+        CHECK_LT(op_index, header_.op_count_max);
+        return header_.prefix.header_size + header_.buffer_size +
+               (op_index * sizeof(CowOperationV3));
+    }
 
   private:
     CowHeaderV3 header_{};