libsnapshot: Add cluster breaks after ops

Previously, we'd check if a new cluster was needed before we added a Cow
Operation. This would cause an op's associated data to go to the wrong
location, so instead we check if we'll need a new cluster after writing
each op.

Bug: 172026020
Test: cow_api_test (ClusterCompressGz)
Change-Id: Ia43afedcfd430961b34f5914da4265b89e6fadb9
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 35020f4..defe8d4 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -171,6 +171,70 @@
     ASSERT_TRUE(iter->Done());
 }
 
+TEST_F(CowTest, ClusterCompressGz) {
+    CowOptions options;
+    options.compression = "gz";
+    options.cluster_ops = 2;
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_->fd));
+
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size()));
+
+    std::string data2 = "More data!";
+    data2.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer.AddRawBlocks(51, data2.data(), data2.size()));
+
+    ASSERT_TRUE(writer.Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->Done());
+    auto op = &iter->Get();
+
+    StringSink sink;
+
+    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->compression, kCowCompressGz);
+    ASSERT_EQ(op->data_length, 56);  // compressed!
+    ASSERT_EQ(op->new_block, 50);
+    ASSERT_TRUE(reader.ReadData(*op, &sink));
+    ASSERT_EQ(sink.stream(), data);
+
+    iter->Next();
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+
+    ASSERT_EQ(op->type, kCowClusterOp);
+
+    iter->Next();
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+
+    sink.Reset();
+    ASSERT_EQ(op->compression, kCowCompressGz);
+    ASSERT_EQ(op->data_length, 41);  // compressed!
+    ASSERT_EQ(op->new_block, 51);
+    ASSERT_TRUE(reader.ReadData(*op, &sink));
+    ASSERT_EQ(sink.stream(), data2);
+
+    iter->Next();
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+
+    ASSERT_EQ(op->type, kCowClusterOp);
+
+    iter->Next();
+    ASSERT_TRUE(iter->Done());
+}
+
 TEST_F(CowTest, CompressTwoBlocks) {
     CowOptions options;
     options.compression = "gz";
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 8535252..c1a5f32 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -433,11 +433,6 @@
 }
 
 bool CowWriter::WriteOperation(const CowOperation& op, const void* data, size_t size) {
-    // If there isn't room for this op and the cluster end op, end the current cluster
-    if (cluster_size_ && op.type != kCowClusterOp &&
-        cluster_size_ < current_cluster_size_ + 2 * sizeof(op)) {
-        if (!EmitCluster()) return false;
-    }
     if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
         PLOG(ERROR) << "lseek failed for writing operation.";
         return false;
@@ -449,6 +444,11 @@
         if (!WriteRawData(data, size)) return false;
     }
     AddOperation(op);
+    // If there isn't room for another op and the cluster end op, end the current cluster
+    if (cluster_size_ && op.type != kCowClusterOp &&
+        cluster_size_ < current_cluster_size_ + 2 * sizeof(op)) {
+        if (!EmitCluster()) return false;
+    }
     return true;
 }