// // Copyright (C) 2020 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include #include #include #include #include #include #include #include #include "update_engine/payload_consumer/snapshot_extent_writer.h" #include "update_engine/payload_generator/delta_diff_generator.h" #include "update_engine/update_metadata.pb.h" namespace chromeos_update_engine { class FakeCowWriter : public android::snapshot::ICowWriter { public: struct CowOp { enum { COW_COPY, COW_REPLACE, COW_ZERO } type; std::vector data; union { size_t source_block; size_t num_blocks; }; }; using ICowWriter::ICowWriter; ~FakeCowWriter() = default; bool EmitCopy(uint64_t new_block, uint64_t old_block) override { operations_[new_block] = {.type = CowOp::COW_COPY, .source_block = static_cast(old_block)}; return true; } bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override { auto&& op = operations_[new_block_start]; const auto uint8_ptr = static_cast(data); op.data.insert(op.data.end(), uint8_ptr, uint8_ptr + size); return true; } bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override { operations_[new_block_start] = {.type = CowOp::COW_ZERO}; return true; } bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block, uint16_t offset) override { return false; } bool Finalize() override { finalize_called_ = true; return true; } bool EmitLabel(uint64_t label) { label_count_++; return true; } bool EmitSequenceData(size_t num_ops, const uint32_t* data) override { return false; } // Return number of bytes the cow image occupies on disk. uint64_t GetCowSize() override { return std::accumulate( operations_.begin(), operations_.end(), 0, [](auto&& acc, auto&& op) { return acc + op.second.data.size(); }); } bool Contains(size_t block) { return operations_.find(block) != operations_.end(); } bool finalize_called_ = true; size_t label_count_ = 0; std::map operations_; }; class SnapshotExtentWriterTest : public ::testing::Test { public: void SetUp() override {} protected: android::snapshot::CowOptions options_ = { .block_size = static_cast(kBlockSize)}; FakeCowWriter cow_writer_{options_}; SnapshotExtentWriter writer_{&cow_writer_}; }; void AddExtent(google::protobuf::RepeatedPtrField* extents, size_t start_block, size_t num_blocks) { auto&& extent = extents->Add(); extent->set_start_block(start_block); extent->set_num_blocks(num_blocks); } TEST_F(SnapshotExtentWriterTest, BufferWrites) { google::protobuf::RepeatedPtrField extents; AddExtent(&extents, 123, 1); writer_.Init(extents, kBlockSize); std::vector buf(kBlockSize, 0); buf[123] = 231; buf[231] = 123; buf[buf.size() - 1] = 255; writer_.Write(buf.data(), kBlockSize - 1); ASSERT_TRUE(cow_writer_.operations_.empty()) << "Haven't send data of a complete block yet, CowWriter should not be " "invoked."; writer_.Write(buf.data() + kBlockSize - 1, 1); ASSERT_TRUE(cow_writer_.Contains(123)) << "Once a block of data is sent to SnapshotExtentWriter, it should " "forward data to cow_writer."; ASSERT_EQ(cow_writer_.operations_.size(), 1U); ASSERT_EQ(buf, cow_writer_.operations_[123].data); } TEST_F(SnapshotExtentWriterTest, NonBufferedWrites) { google::protobuf::RepeatedPtrField extents; AddExtent(&extents, 123, 1); AddExtent(&extents, 125, 1); writer_.Init(extents, kBlockSize); std::vector buf(kBlockSize * 2, 0); buf[123] = 231; buf[231] = 123; buf[buf.size() - 1] = 255; writer_.Write(buf.data(), buf.size()); ASSERT_TRUE(cow_writer_.Contains(123)); ASSERT_TRUE(cow_writer_.Contains(125)); ASSERT_EQ(cow_writer_.operations_.size(), 2U); auto actual_data = cow_writer_.operations_[123].data; actual_data.insert(actual_data.end(), cow_writer_.operations_[125].data.begin(), cow_writer_.operations_[125].data.end()); ASSERT_EQ(buf, actual_data); } TEST_F(SnapshotExtentWriterTest, WriteAcrossBlockBoundary) { google::protobuf::RepeatedPtrField extents; AddExtent(&extents, 123, 1); AddExtent(&extents, 125, 2); writer_.Init(extents, kBlockSize); std::vector buf(kBlockSize * 3); std::memset(buf.data(), 0, buf.size()); buf[123] = 231; buf[231] = 123; buf[buf.size() - 1] = 255; buf[kBlockSize - 1] = 254; writer_.Write(buf.data(), kBlockSize - 1); ASSERT_TRUE(cow_writer_.operations_.empty()) << "Haven't send data of a complete block yet, CowWriter should not be " "invoked."; writer_.Write(buf.data() + kBlockSize - 1, 1 + kBlockSize * 2); ASSERT_TRUE(cow_writer_.Contains(123)); ASSERT_TRUE(cow_writer_.Contains(125)); ASSERT_EQ(cow_writer_.operations_.size(), 2U); auto actual_data = cow_writer_.operations_[123].data; actual_data.insert(actual_data.end(), cow_writer_.operations_[125].data.begin(), cow_writer_.operations_[125].data.end()); ASSERT_EQ(buf, actual_data); } } // namespace chromeos_update_engine