libsnapshot: Implement CowWriterV3:EmitBlocks

Add in replace ops to WriterV3 without compression support (will be
added in later). CowReader also has to be changed since we no longer
have a CowFooter (our checks need to be updated correspondingly).
We need next_data_pos_ to be pointed to after the operation buffer. This
section is appended to incrementally as we write replace + xor
operations

Test: cow_api_test
Change-Id: Ie979c72f842edd04337d900fd43dac8031207517
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
index c20194d..93c31bb 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/cow_reader.cpp
@@ -601,8 +601,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_.prefix.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_ || offset + len > fd_size_ ||
+        len >= fd_size_) {
         LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
         return false;
     }
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
index 07c1d4f..214eb1c 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/test_v3.cpp
@@ -51,6 +51,11 @@
     std::unique_ptr<TemporaryFile> cow_;
 };
 
+// Helper to check read sizes.
+static inline bool ReadData(CowReader& reader, const CowOperation* op, void* buffer, size_t size) {
+    return reader.ReadData(op, buffer, size) == size;
+}
+
 TEST_F(CowTestV3, CowHeaderV2Test) {
     CowOptions options;
     options.cluster_ops = 5;
@@ -120,5 +125,86 @@
     ASSERT_EQ(op->source_info, 0);
 }
 
+TEST_F(CowTestV3, ReplaceOp) {
+    CowOptions options;
+    options.op_count_max = 20;
+    options.scratch_space = false;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+
+    ASSERT_TRUE(writer->AddRawBlocks(5, data.data(), data.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(header.op_count, 1);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    auto op = iter->Get();
+    std::string sink(data.size(), '\0');
+
+    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->data_length, 4096);
+    ASSERT_EQ(op->new_block, 5);
+    ASSERT_TRUE(ReadData(reader, op, sink.data(), sink.size()));
+    ASSERT_EQ(sink, data);
+}
+
+TEST_F(CowTestV3, ConsecutiveReplaceOp) {
+    CowOptions options;
+    options.op_count_max = 20;
+    options.scratch_space = false;
+    auto writer = CreateCowWriter(3, options, GetCowFd());
+    std::string data;
+    data.resize(options.block_size * 5);
+    for (int i = 0; i < data.size(); i++) {
+        data[i] = char(rand() % 256);
+    }
+
+    ASSERT_TRUE(writer->AddRawBlocks(5, data.data(), data.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    const auto& header = reader.header_v3();
+    ASSERT_EQ(header.prefix.magic, kCowMagicNumber);
+    ASSERT_EQ(header.prefix.major_version, 3);
+    ASSERT_EQ(header.prefix.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(header.op_count, 5);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->AtEnd());
+
+    size_t i = 0;
+    std::string sink(data.size(), '\0');
+
+    while (!iter->AtEnd()) {
+        auto op = iter->Get();
+        ASSERT_EQ(op->type, kCowReplaceOp);
+        ASSERT_EQ(op->data_length, options.block_size);
+        ASSERT_EQ(op->new_block, 5 + i);
+        ASSERT_TRUE(
+                ReadData(reader, op, sink.data() + (i * options.block_size), options.block_size));
+        iter->Next();
+        i++;
+    }
+    ASSERT_EQ(sink, data);
+
+    ASSERT_EQ(i, 5);
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index ecbf97e..d7aee13 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -161,6 +161,9 @@
         LOG(ERROR) << "Header sync failed";
         return false;
     }
+    next_data_pos_ =
+            sizeof(CowHeaderV3) + header_.buffer_size + header_.op_count_max * sizeof(CowOperation);
+
     return true;
 }
 
@@ -171,10 +174,7 @@
 }
 
 bool CowWriterV3::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
-    LOG(ERROR) << __LINE__ << " " << __FILE__ << " <- function here should never be called";
-
-    if (new_block_start || data || size) return false;
-    return false;
+    return EmitBlocks(new_block_start, data, size, 0, 0, kCowReplaceOp);
 }
 
 bool CowWriterV3::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
@@ -184,6 +184,33 @@
     return false;
 }
 
+bool CowWriterV3::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
+                             uint64_t old_block, uint16_t offset, uint8_t type) {
+    const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
+
+    // Placing here until we support XOR ops
+    CHECK_EQ(old_block, 0);
+    CHECK_EQ(offset, 0);
+
+    const size_t num_blocks = (size / header_.block_size);
+
+    for (size_t i = 0; i < num_blocks; i++) {
+        CowOperation op = {};
+        op.new_block = new_block_start + i;
+
+        op.type = type;
+        op.source_info = next_data_pos_;
+        op.data_length = static_cast<uint16_t>(header_.block_size);
+        if (!WriteOperation(op, iter, header_.block_size)) {
+            LOG(ERROR) << "AddRawBlocks: write failed";
+            return false;
+        }
+        iter += header_.block_size;
+    }
+
+    return true;
+}
+
 bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
     for (uint64_t i = 0; i < num_blocks; i++) {
         CowOperationV3 op;
@@ -210,7 +237,7 @@
     return false;
 }
 
-bool CowWriterV3::WriteOperation(const CowOperationV3& op) {
+bool CowWriterV3::WriteOperation(const CowOperationV3& op, const void* data, size_t size) {
     if (IsEstimating()) {
         header_.op_count++;
         header_.op_count_max++;
@@ -224,10 +251,20 @@
 
     const off_t offset = GetOpOffset(header_.op_count);
     if (!android::base::WriteFullyAtOffset(fd_, &op, sizeof(op), offset)) {
+        PLOG(ERROR) << "write failed for " << op << " at " << offset;
         return false;
     }
-
+    if (data && size > 0) {
+        if (!android::base::WriteFullyAtOffset(fd_, data, size, next_data_pos_)) {
+            PLOG(ERROR) << "write failed for data of size: " << size
+                        << " at offset: " << next_data_pos_;
+            return false;
+        }
+    }
     header_.op_count++;
+    next_data_pos_ += op.data_length;
+    next_op_pos_ += sizeof(CowOperationV3);
+
     return true;
 }
 
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index 8717c01..af71a03 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -43,7 +43,9 @@
     void SetupHeaders();
     bool ParseOptions();
     bool OpenForWrite();
-    bool WriteOperation(const CowOperationV3& op);
+    bool WriteOperation(const CowOperationV3& op, const void* data = nullptr, size_t size = 0);
+    bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
+                    uint16_t offset, uint8_t type);
 
     off_t GetOpOffset(uint32_t op_index) const {
         CHECK_LT(op_index, header_.op_count_max);
@@ -55,6 +57,9 @@
     CowHeaderV3 header_{};
     CowCompression compression_;
 
+    uint64_t next_op_pos_ = 0;
+    uint64_t next_data_pos_ = 0;
+
     // in the case that we are using one thread for compression, we can store and re-use the same
     // compressor
     int num_compress_threads_ = 1;