Support batch writes for V3 cow format

Test: th
Bug: 313962438
Change-Id: If3a01ab85a1a649b7476bee2d56b732f04d0509a
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
index 4df0e76..de097f5 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.cpp
@@ -38,6 +38,7 @@
 #include <linux/fs.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
+#include <numeric>
 
 // The info messages here are spammy, but as useful for update_engine. Disable
 // them when running on the host.
@@ -55,7 +56,7 @@
 using android::base::unique_fd;
 
 CowWriterV3::CowWriterV3(const CowOptions& options, unique_fd&& fd)
-    : CowWriterBase(options, std::move(fd)) {
+    : CowWriterBase(options, std::move(fd)), batch_size_(std::max<size_t>(options.cluster_ops, 1)) {
     SetupHeaders();
 }
 
@@ -70,6 +71,9 @@
     header_.block_size = options_.block_size;
     header_.num_merge_ops = options_.num_merge_ops;
     header_.cluster_ops = 0;
+    if (batch_size_ > 1) {
+        LOG(INFO) << "Batch writes enabled with batch size of " << batch_size_;
+    }
     if (options_.scratch_space) {
         header_.buffer_size = BUFFER_REGION_DEFAULT_SIZE;
     }
@@ -258,40 +262,49 @@
             }
             op.data_length = header_.block_size;
         }
-        return WriteOperation({ops.data(), ops.size()},
-                              {reinterpret_cast<const uint8_t*>(data), size});
+        return WriteOperation({ops.data(), ops.size()}, data, size);
     }
 
-    const auto saved_op_count = header_.op_count;
-    const auto saved_data_pos = next_data_pos_;
-    for (size_t i = 0; i < num_blocks; i++) {
-        const uint8_t* const iter =
-                reinterpret_cast<const uint8_t*>(data) + (header_.block_size * i);
+    for (size_t i = 0; i < num_blocks; i += batch_size_) {
+        const auto blocks_to_write = std::min<size_t>(batch_size_, num_blocks - i);
+        std::vector<std::basic_string<uint8_t>> compressed_blocks(blocks_to_write);
+        std::vector<CowOperationV3> ops(blocks_to_write);
+        std::vector<struct iovec> vec(blocks_to_write);
+        size_t compressed_bytes = 0;
+        for (size_t j = 0; j < blocks_to_write; j++) {
+            const uint8_t* const iter =
+                    reinterpret_cast<const uint8_t*>(data) + (header_.block_size * (i + j));
 
-        CowOperation op{};
-        op.new_block = new_block_start + i;
+            CowOperation& op = ops[j];
+            op.new_block = new_block_start + i + j;
 
-        op.set_type(type);
-        if (type == kCowXorOp) {
-            op.set_source((old_block + i) * header_.block_size + offset);
-        } else {
-            op.set_source(next_data_pos_);
+            op.set_type(type);
+            if (type == kCowXorOp) {
+                op.set_source((old_block + i + j) * header_.block_size + offset);
+            } else {
+                op.set_source(next_data_pos_ + compressed_bytes);
+            }
+
+            std::basic_string<uint8_t> compressed_data =
+                    compressor_->Compress(iter, header_.block_size);
+            if (compressed_data.empty()) {
+                LOG(ERROR) << "Compression failed during EmitBlocks(" << new_block_start << ", "
+                           << num_blocks << ");";
+                return false;
+            }
+            if (compressed_data.size() >= header_.block_size) {
+                compressed_data.resize(header_.block_size);
+                std::memcpy(compressed_data.data(), iter, header_.block_size);
+            }
+            compressed_blocks[j] = std::move(compressed_data);
+            vec[j] = {.iov_base = compressed_blocks[j].data(),
+                      .iov_len = compressed_blocks[j].size()};
+            op.data_length = vec[j].iov_len;
+            compressed_bytes += op.data_length;
         }
-        const void* out_data = iter;
-
-        op.data_length = header_.block_size;
-
-        const std::basic_string<uint8_t> compressed_data =
-                compressor_->Compress(out_data, header_.block_size);
-        if (compressed_data.size() < op.data_length) {
-            out_data = compressed_data.data();
-            op.data_length = compressed_data.size();
-        }
-        if (!WriteOperation(op, out_data, op.data_length)) {
+        if (!WriteOperation({ops.data(), ops.size()}, {vec.data(), vec.size()})) {
             PLOG(ERROR) << "AddRawBlocks with compression: write failed. new block: "
                         << new_block_start << " compression: " << compression_.algorithm;
-            header_.op_count = saved_op_count;
-            next_data_pos_ = saved_data_pos;
             return false;
         }
     }
@@ -299,14 +312,14 @@
     return true;
 }
 
-bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
+bool CowWriterV3::EmitZeroBlocks(uint64_t new_block_start, const uint64_t num_blocks) {
     std::vector<CowOperationV3> ops(num_blocks);
-    for (uint64_t i = 0; i < num_blocks; i++) {
-        CowOperationV3& op = ops[i];
+    for (uint64_t i = 0; i < ops.size(); i++) {
+        auto& op = ops[i];
         op.set_type(kCowZeroOp);
         op.new_block = new_block_start + i;
     }
-    if (!WriteOperation({ops.data(), ops.size()}, {})) {
+    if (!WriteOperation({ops.data(), ops.size()})) {
         return false;
     }
     return true;
@@ -353,8 +366,23 @@
     return true;
 }
 
+bool CowWriterV3::WriteOperation(std::basic_string_view<CowOperationV3> op, const void* data,
+                                 size_t size) {
+    struct iovec vec {
+        .iov_len = size
+    };
+    // Dear C++ god, this is effectively a const_cast. I had to do this because
+    // pwritev()'s struct iovec requires a non-const pointer. The input data
+    // will not be modified, as the iovec is only used for a write operation.
+    std::memcpy(&vec.iov_base, &data, sizeof(data));
+    return WriteOperation(op, {&vec, 1});
+}
+
 bool CowWriterV3::WriteOperation(std::basic_string_view<CowOperationV3> ops,
-                                 std::basic_string_view<uint8_t> data) {
+                                 std::basic_string_view<struct iovec> data) {
+    const auto total_data_size =
+            std::transform_reduce(data.begin(), data.end(), 0, std::plus<size_t>{},
+                                  [](const struct iovec& a) { return a.iov_len; });
     if (IsEstimating()) {
         header_.op_count += ops.size();
         if (header_.op_count > header_.op_count_max) {
@@ -363,7 +391,7 @@
             next_data_pos_ += (header_.op_count - header_.op_count_max) * sizeof(CowOperationV3);
             header_.op_count_max = header_.op_count;
         }
-        next_data_pos_ += data.size();
+        next_data_pos_ += total_data_size;
         return true;
     }
 
@@ -379,20 +407,21 @@
         return false;
     }
     if (!data.empty()) {
-        if (!android::base::WriteFullyAtOffset(fd_, data.data(), data.size(), next_data_pos_)) {
+        const auto ret = pwritev(fd_, data.data(), data.size(), next_data_pos_);
+        if (ret != total_data_size) {
             PLOG(ERROR) << "write failed for data of size: " << data.size()
-                        << " at offset: " << next_data_pos_;
+                        << " at offset: " << next_data_pos_ << " " << ret;
             return false;
         }
     }
     header_.op_count += ops.size();
-    next_data_pos_ += data.size();
+    next_data_pos_ += total_data_size;
 
     return true;
 }
 
 bool CowWriterV3::WriteOperation(const CowOperationV3& op, const void* data, size_t size) {
-    return WriteOperation({&op, 1}, {reinterpret_cast<const uint8_t*>(data), size});
+    return WriteOperation({&op, 1}, data, size);
 }
 
 bool CowWriterV3::Finalize() {
diff --git a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
index 02b4e61..93f1d24 100644
--- a/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
+++ b/fs_mgr/libsnapshot/libsnapshot_cow/writer_v3.h
@@ -46,7 +46,9 @@
     bool OpenForWrite();
     bool OpenForAppend(uint64_t label);
     bool WriteOperation(std::basic_string_view<CowOperationV3> op,
-                        std::basic_string_view<uint8_t> data);
+                        std::basic_string_view<struct iovec> data);
+    bool WriteOperation(std::basic_string_view<CowOperationV3> op, const void* data = nullptr,
+                        size_t size = 0);
     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, CowOperationType type);
@@ -68,6 +70,7 @@
     // 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;
+    size_t batch_size_ = 0;
 };
 
 }  // namespace snapshot