1 //
2 // Copyright (C) 2020 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include <array>
18 #include <cstring>
19 #include <map>
20 #include <numeric>
21 #include <vector>
22
23 #include <gtest/gtest.h>
24 #include <google/protobuf/message_lite.h>
25 #include <libsnapshot/cow_writer.h>
26
27 #include "update_engine/payload_consumer/snapshot_extent_writer.h"
28 #include "update_engine/payload_generator/delta_diff_generator.h"
29 #include "update_engine/update_metadata.pb.h"
30
31 namespace chromeos_update_engine {
32
33 class FakeCowWriter : public android::snapshot::ICowWriter {
34 public:
35 struct CowOp {
36 enum { COW_COPY, COW_REPLACE, COW_ZERO } type;
37 std::vector<unsigned char> data;
38 union {
39 size_t source_block;
40 size_t num_blocks;
41 };
42 };
43 using ICowWriter::ICowWriter;
44 ~FakeCowWriter() = default;
45
EmitCopy(uint64_t new_block,uint64_t old_block)46 bool EmitCopy(uint64_t new_block, uint64_t old_block) override {
47 operations_[new_block] = {.type = CowOp::COW_COPY,
48 .source_block = static_cast<size_t>(old_block)};
49 return true;
50 }
EmitRawBlocks(uint64_t new_block_start,const void * data,size_t size)51 bool EmitRawBlocks(uint64_t new_block_start,
52 const void* data,
53 size_t size) override {
54 auto&& op = operations_[new_block_start];
55 const auto uint8_ptr = static_cast<const unsigned char*>(data);
56 op.data.insert(op.data.end(), uint8_ptr, uint8_ptr + size);
57 return true;
58 }
EmitZeroBlocks(uint64_t new_block_start,uint64_t num_blocks)59 bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override {
60 operations_[new_block_start] = {.type = CowOp::COW_ZERO};
61 return true;
62 }
Finalize()63 bool Finalize() override {
64 finalize_called_ = true;
65 return true;
66 }
67
EmitLabel(uint64_t label)68 bool EmitLabel(uint64_t label) {
69 label_count_++;
70 return true;
71 }
72
73 // Return number of bytes the cow image occupies on disk.
GetCowSize()74 uint64_t GetCowSize() override {
75 return std::accumulate(
76 operations_.begin(), operations_.end(), 0, [](auto&& acc, auto&& op) {
77 return acc + op.second.data.size();
78 });
79 }
Contains(size_t block)80 bool Contains(size_t block) {
81 return operations_.find(block) != operations_.end();
82 }
83 bool finalize_called_ = true;
84 size_t label_count_ = 0;
85 std::map<size_t, CowOp> operations_;
86 };
87
88 class SnapshotExtentWriterTest : public ::testing::Test {
89 public:
SetUp()90 void SetUp() override {}
91
92 protected:
93 android::snapshot::CowOptions options_ = {
94 .block_size = static_cast<uint32_t>(kBlockSize)};
95 FakeCowWriter cow_writer_{options_};
96 SnapshotExtentWriter writer_{&cow_writer_};
97 };
98
AddExtent(google::protobuf::RepeatedPtrField<Extent> * extents,size_t start_block,size_t num_blocks)99 void AddExtent(google::protobuf::RepeatedPtrField<Extent>* extents,
100 size_t start_block,
101 size_t num_blocks) {
102 auto&& extent = extents->Add();
103 extent->set_start_block(start_block);
104 extent->set_num_blocks(num_blocks);
105 }
106
TEST_F(SnapshotExtentWriterTest,BufferWrites)107 TEST_F(SnapshotExtentWriterTest, BufferWrites) {
108 google::protobuf::RepeatedPtrField<Extent> extents;
109 AddExtent(&extents, 123, 1);
110 writer_.Init(extents, kBlockSize);
111
112 std::vector<uint8_t> buf(kBlockSize, 0);
113 buf[123] = 231;
114 buf[231] = 123;
115 buf[buf.size() - 1] = 255;
116
117 writer_.Write(buf.data(), kBlockSize - 1);
118 ASSERT_TRUE(cow_writer_.operations_.empty())
119 << "Haven't send data of a complete block yet, CowWriter should not be "
120 "invoked.";
121 writer_.Write(buf.data() + kBlockSize - 1, 1);
122 ASSERT_TRUE(cow_writer_.Contains(123))
123 << "Once a block of data is sent to SnapshotExtentWriter, it should "
124 "forward data to cow_writer.";
125 ASSERT_EQ(cow_writer_.operations_.size(), 1U);
126 ASSERT_EQ(buf, cow_writer_.operations_[123].data);
127 }
128
TEST_F(SnapshotExtentWriterTest,NonBufferedWrites)129 TEST_F(SnapshotExtentWriterTest, NonBufferedWrites) {
130 google::protobuf::RepeatedPtrField<Extent> extents;
131 AddExtent(&extents, 123, 1);
132 AddExtent(&extents, 125, 1);
133 writer_.Init(extents, kBlockSize);
134
135 std::vector<uint8_t> buf(kBlockSize * 2, 0);
136 buf[123] = 231;
137 buf[231] = 123;
138 buf[buf.size() - 1] = 255;
139
140 writer_.Write(buf.data(), buf.size());
141 ASSERT_TRUE(cow_writer_.Contains(123));
142 ASSERT_TRUE(cow_writer_.Contains(125));
143
144 ASSERT_EQ(cow_writer_.operations_.size(), 2U);
145 auto actual_data = cow_writer_.operations_[123].data;
146 actual_data.insert(actual_data.end(),
147 cow_writer_.operations_[125].data.begin(),
148 cow_writer_.operations_[125].data.end());
149 ASSERT_EQ(buf, actual_data);
150 }
151
TEST_F(SnapshotExtentWriterTest,WriteAcrossBlockBoundary)152 TEST_F(SnapshotExtentWriterTest, WriteAcrossBlockBoundary) {
153 google::protobuf::RepeatedPtrField<Extent> extents;
154 AddExtent(&extents, 123, 1);
155 AddExtent(&extents, 125, 2);
156 writer_.Init(extents, kBlockSize);
157
158 std::vector<uint8_t> buf(kBlockSize * 3);
159 std::memset(buf.data(), 0, buf.size());
160 buf[123] = 231;
161 buf[231] = 123;
162 buf[buf.size() - 1] = 255;
163 buf[kBlockSize - 1] = 254;
164
165 writer_.Write(buf.data(), kBlockSize - 1);
166 ASSERT_TRUE(cow_writer_.operations_.empty())
167 << "Haven't send data of a complete block yet, CowWriter should not be "
168 "invoked.";
169 writer_.Write(buf.data() + kBlockSize - 1, 1 + kBlockSize * 2);
170 ASSERT_TRUE(cow_writer_.Contains(123));
171 ASSERT_TRUE(cow_writer_.Contains(125));
172
173 ASSERT_EQ(cow_writer_.operations_.size(), 2U);
174 auto actual_data = cow_writer_.operations_[123].data;
175 actual_data.insert(actual_data.end(),
176 cow_writer_.operations_[125].data.begin(),
177 cow_writer_.operations_[125].data.end());
178 ASSERT_EQ(buf, actual_data);
179 }
180 } // namespace chromeos_update_engine
181