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;
}