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 "update_engine/payload_generator/cow_size_estimator.h"
18
19 #include <algorithm>
20 #include <functional>
21 #include <string>
22 #include <utility>
23 #include <vector>
24
25 #include <android-base/unique_fd.h>
26 #include <libsnapshot/cow_writer.h>
27
28 #include "update_engine/common/cow_operation_convert.h"
29 #include "update_engine/common/utils.h"
30 #include "update_engine/payload_consumer/vabc_partition_writer.h"
31 #include "update_engine/payload_generator/extent_ranges.h"
32 #include "update_engine/payload_generator/extent_utils.h"
33 #include "update_engine/update_metadata.pb.h"
34
35 namespace chromeos_update_engine {
36 using android::snapshot::CowWriter;
37
CowDryRun(FileDescriptorPtr source_fd,FileDescriptorPtr target_fd,const google::protobuf::RepeatedPtrField<InstallOperation> & operations,const google::protobuf::RepeatedPtrField<CowMergeOperation> & merge_operations,const size_t block_size,android::snapshot::CowWriter * cow_writer,const size_t partition_size,const bool xor_enabled)38 bool CowDryRun(
39 FileDescriptorPtr source_fd,
40 FileDescriptorPtr target_fd,
41 const google::protobuf::RepeatedPtrField<InstallOperation>& operations,
42 const google::protobuf::RepeatedPtrField<CowMergeOperation>&
43 merge_operations,
44 const size_t block_size,
45 android::snapshot::CowWriter* cow_writer,
46 const size_t partition_size,
47 const bool xor_enabled) {
48 CHECK_NE(target_fd, nullptr);
49 CHECK(target_fd->IsOpen());
50 VABCPartitionWriter::WriteMergeSequence(merge_operations, cow_writer);
51 ExtentRanges visited;
52 for (const auto& op : merge_operations) {
53 if (op.type() == CowMergeOperation::COW_COPY) {
54 visited.AddExtent(op.dst_extent());
55 cow_writer->AddCopy(op.dst_extent().start_block(),
56 op.src_extent().start_block(),
57 op.dst_extent().num_blocks());
58 } else if (op.type() == CowMergeOperation::COW_XOR && xor_enabled) {
59 CHECK_NE(source_fd, nullptr) << "Source fd is required to enable XOR ops";
60 CHECK(source_fd->IsOpen());
61 visited.AddExtent(op.dst_extent());
62 // dst block count is used, because
63 // src block count is probably(if src_offset > 0) 1 block
64 // larger than dst extent. Using it might lead to intreseting out of bound
65 // disk reads.
66 std::vector<unsigned char> old_data(op.dst_extent().num_blocks() *
67 block_size);
68 ssize_t bytes_read = 0;
69 if (!utils::PReadAll(
70 source_fd,
71 old_data.data(),
72 old_data.size(),
73 op.src_extent().start_block() * block_size + op.src_offset(),
74 &bytes_read)) {
75 PLOG(ERROR) << "Failed to read source data at " << op.src_extent();
76 return false;
77 }
78 std::vector<unsigned char> new_data(op.dst_extent().num_blocks() *
79 block_size);
80 if (!utils::PReadAll(target_fd,
81 new_data.data(),
82 new_data.size(),
83 op.dst_extent().start_block() * block_size,
84 &bytes_read)) {
85 PLOG(ERROR) << "Failed to read target data at " << op.dst_extent();
86 return false;
87 }
88 CHECK_GT(old_data.size(), 0UL);
89 CHECK_GT(new_data.size(), 0UL);
90 std::transform(new_data.begin(),
91 new_data.end(),
92 old_data.begin(),
93 new_data.begin(),
94 std::bit_xor<unsigned char>{});
95 CHECK(cow_writer->AddXorBlocks(op.dst_extent().start_block(),
96 new_data.data(),
97 new_data.size(),
98 op.src_extent().start_block(),
99 op.src_offset()));
100 }
101 // The value of label doesn't really matter, we just want to write some
102 // labels to simulate bahvior of update_engine. As update_engine writes
103 // labels every once a while when installing OTA, it's important that we do
104 // the same to get accurate size estimation.
105 cow_writer->AddLabel(0);
106 }
107 for (const auto& op : operations) {
108 cow_writer->AddLabel(0);
109 if (op.type() == InstallOperation::ZERO) {
110 for (const auto& ext : op.dst_extents()) {
111 visited.AddExtent(ext);
112 cow_writer->AddZeroBlocks(ext.start_block(), ext.num_blocks());
113 }
114 }
115 }
116 cow_writer->AddLabel(0);
117 const size_t last_block = partition_size / block_size;
118 const auto unvisited_extents =
119 FilterExtentRanges({ExtentForRange(0, last_block)}, visited);
120 for (const auto& ext : unvisited_extents) {
121 std::vector<unsigned char> data(ext.num_blocks() * block_size);
122 ssize_t bytes_read = 0;
123 if (!utils::PReadAll(target_fd,
124 data.data(),
125 data.size(),
126 ext.start_block() * block_size,
127 &bytes_read)) {
128 PLOG(ERROR) << "Failed to read new block data at " << ext;
129 return false;
130 }
131 cow_writer->AddRawBlocks(ext.start_block(), data.data(), data.size());
132 cow_writer->AddLabel(0);
133 }
134
135 return cow_writer->Finalize();
136 }
137
EstimateCowSize(FileDescriptorPtr source_fd,FileDescriptorPtr target_fd,const google::protobuf::RepeatedPtrField<InstallOperation> & operations,const google::protobuf::RepeatedPtrField<CowMergeOperation> & merge_operations,const size_t block_size,std::string compression,const size_t partition_size,const bool xor_enabled)138 size_t EstimateCowSize(
139 FileDescriptorPtr source_fd,
140 FileDescriptorPtr target_fd,
141 const google::protobuf::RepeatedPtrField<InstallOperation>& operations,
142 const google::protobuf::RepeatedPtrField<CowMergeOperation>&
143 merge_operations,
144 const size_t block_size,
145 std::string compression,
146 const size_t partition_size,
147 const bool xor_enabled) {
148 android::snapshot::CowWriter cow_writer{
149 {.block_size = static_cast<uint32_t>(block_size),
150 .compression = std::move(compression)}};
151 // CowWriter treats -1 as special value, will discard all the data but still
152 // reports Cow size. Good for estimation purposes
153 cow_writer.Initialize(android::base::borrowed_fd{-1});
154 CHECK(CowDryRun(source_fd,
155 target_fd,
156 operations,
157 merge_operations,
158 block_size,
159 &cow_writer,
160 partition_size,
161 xor_enabled));
162 return cow_writer.GetCowSize();
163 }
164
165 } // namespace chromeos_update_engine
166