Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 861e69a4 authored by Daniel Rosenberg's avatar Daniel Rosenberg
Browse files

libsnapshot: Zero out leftover ops in CowWriter

When opening in append mode, we could write less than what was present
before. This could result in data blocks referencing beyond the end of
the file, or partially written ops. Zeroing these out will prevent
invalid leftovers from potentially causing confusion.

Bug: 183985866
Test: cow_api_test
Change-Id: I56f0218f3ea5b83c0614d1b86e81a4ca885f5c5e
parent db25e8e3
Loading
Loading
Loading
Loading
+51 −0
Original line number Diff line number Diff line
@@ -869,6 +869,57 @@ TEST_F(CowTest, ResumeMidCluster) {
    ASSERT_EQ(num_clusters, 2);
}

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

+15 −0
Original line number Diff line number Diff line
@@ -375,6 +375,21 @@ bool CowWriter::Finalize() {
    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) {