Merge changes Ie93d09b3,I56f0218f,I3c9a3855 am: 4872b73b9e am: 5487958d34

Original change: https://android-review.googlesource.com/c/platform/system/core/+/1673893

Change-Id: I603124ccabb0f3dd858a1ff1f7d3811c815aa9bf
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 5d63220..b75b154 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -25,6 +25,10 @@
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
 
+using testing::AssertionFailure;
+using testing::AssertionResult;
+using testing::AssertionSuccess;
+
 namespace android {
 namespace snapshot {
 
@@ -781,6 +785,202 @@
     ASSERT_TRUE(reader.Parse(cow_->fd));
 }
 
+AssertionResult WriteDataBlock(CowWriter* writer, uint64_t new_block, std::string data) {
+    data.resize(writer->options().block_size, '\0');
+    if (!writer->AddRawBlocks(new_block, data.data(), data.size())) {
+        return AssertionFailure() << "Failed to add raw block";
+    }
+    return AssertionSuccess();
+}
+
+AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op,
+                                 const std::string& data) {
+    CowHeader header;
+    reader->GetHeader(&header);
+
+    std::string cmp = data;
+    cmp.resize(header.block_size, '\0');
+
+    StringSink sink;
+    if (!reader->ReadData(op, &sink)) {
+        return AssertionFailure() << "Failed to read data block";
+    }
+    if (cmp != sink.stream()) {
+        return AssertionFailure() << "Data blocks did not match, expected " << cmp << ", got "
+                                  << sink.stream();
+    }
+
+    return AssertionSuccess();
+}
+
+TEST_F(CowTest, ResumeMidCluster) {
+    CowOptions options;
+    options.cluster_ops = 7;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+    ASSERT_TRUE(writer->AddLabel(2));
+    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();
+    size_t num_replace = 0;
+    size_t max_in_cluster = 0;
+    size_t num_in_cluster = 0;
+    size_t num_clusters = 0;
+    while (!iter->Done()) {
+        const auto& op = iter->Get();
+
+        num_in_cluster++;
+        max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+
+        if (op.type == kCowReplaceOp) {
+            num_replace++;
+
+            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+        } else if (op.type == kCowClusterOp) {
+            num_in_cluster = 0;
+            num_clusters++;
+        }
+
+        iter->Next();
+    }
+    ASSERT_EQ(num_replace, 8);
+    ASSERT_EQ(max_in_cluster, 7);
+    ASSERT_EQ(num_clusters, 2);
+}
+
+TEST_F(CowTest, ResumeEndCluster) {
+    CowOptions options;
+    int cluster_ops = 5;
+    options.cluster_ops = cluster_ops;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+    ASSERT_TRUE(writer->AddLabel(2));
+    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();
+    size_t num_replace = 0;
+    size_t max_in_cluster = 0;
+    size_t num_in_cluster = 0;
+    size_t num_clusters = 0;
+    while (!iter->Done()) {
+        const auto& op = iter->Get();
+
+        num_in_cluster++;
+        max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+
+        if (op.type == kCowReplaceOp) {
+            num_replace++;
+
+            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+        } else if (op.type == kCowClusterOp) {
+            num_in_cluster = 0;
+            num_clusters++;
+        }
+
+        iter->Next();
+    }
+    ASSERT_EQ(num_replace, 8);
+    ASSERT_EQ(max_in_cluster, cluster_ops);
+    ASSERT_EQ(num_clusters, 3);
+}
+
+TEST_F(CowTest, DeleteMidCluster) {
+    CowOptions options;
+    options.cluster_ops = 7;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    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();
+    size_t num_replace = 0;
+    size_t max_in_cluster = 0;
+    size_t num_in_cluster = 0;
+    size_t num_clusters = 0;
+    while (!iter->Done()) {
+        const auto& op = iter->Get();
+
+        num_in_cluster++;
+        max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+        if (op.type == kCowReplaceOp) {
+            num_replace++;
+
+            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+        } else if (op.type == kCowClusterOp) {
+            num_in_cluster = 0;
+            num_clusters++;
+        }
+
+        iter->Next();
+    }
+    ASSERT_EQ(num_replace, 3);
+    ASSERT_EQ(max_in_cluster, 5);  // 3 data, 1 label, 1 cluster op
+    ASSERT_EQ(num_clusters, 1);
+}
+
 }  // namespace snapshot
 }  // namespace android
 
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 59f6d6f..645ae9d 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -232,15 +232,11 @@
     // Free reader so we own the descriptor position again.
     reader = nullptr;
 
-    // Remove excess data
-    if (!Truncate(next_op_pos_)) {
-        return false;
-    }
     if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
         PLOG(ERROR) << "lseek failed";
         return false;
     }
-    return true;
+    return EmitClusterIfNeeded();
 }
 
 bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block) {
@@ -319,6 +315,14 @@
     return WriteOperation(op);
 }
 
+bool CowWriter::EmitClusterIfNeeded() {
+    // If there isn't room for another op and the cluster end op, end the current cluster
+    if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperation)) {
+        if (!EmitCluster()) return false;
+    }
+    return true;
+}
+
 std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) {
     switch (compression_) {
         case kCowCompressGz: {
@@ -379,6 +383,21 @@
     auto continue_num_ops = footer_.op.num_ops;
     bool extra_cluster = false;
 
+    // Blank out extra ops, in case we're in append mode and dropped ops.
+    if (cluster_size_) {
+        auto unused_cluster_space = cluster_size_ - current_cluster_size_;
+        std::string clr;
+        clr.resize(unused_cluster_space, '\0');
+        if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
+            PLOG(ERROR) << "Failed to seek to footer position.";
+            return false;
+        }
+        if (!android::base::WriteFully(fd_, clr.data(), clr.size())) {
+            PLOG(ERROR) << "clearing unused cluster area failed";
+            return false;
+        }
+    }
+
     // Footer should be at the end of a file, so if there is data after the current block, end it
     // and start a new cluster.
     if (cluster_size_ && current_data_size_ > 0) {
@@ -403,6 +422,17 @@
         return false;
     }
 
+    // Remove excess data, if we're in append mode and threw away more data
+    // than we wrote before.
+    off_t offs = lseek(fd_.get(), 0, SEEK_CUR);
+    if (offs < 0) {
+        PLOG(ERROR) << "Failed to lseek to find current position";
+        return false;
+    }
+    if (!Truncate(offs)) {
+        return false;
+    }
+
     // Reposition for additional Writing
     if (extra_cluster) {
         current_cluster_size_ = continue_cluster_size;
@@ -445,12 +475,7 @@
         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;
+    return EmitClusterIfNeeded();
 }
 
 void CowWriter::AddOperation(const CowOperation& op) {
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index 6ffd5d8..a9efad8 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -115,6 +115,7 @@
 
   private:
     bool EmitCluster();
+    bool EmitClusterIfNeeded();
     void SetupHeaders();
     bool ParseOptions();
     bool OpenForWrite();