1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
|
// Copyright (C) 2019 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 "partition_cow_creator.h"
#include <math.h>
#include <android-base/logging.h>
#include <android/snapshot/snapshot.pb.h>
#include <storage_literals/storage_literals.h>
#include "dm_snapshot_internals.h"
#include "utility.h"
using android::dm::kSectorSize;
using android::fs_mgr::Extent;
using android::fs_mgr::Interval;
using android::fs_mgr::kDefaultBlockSize;
using android::fs_mgr::Partition;
using chromeos_update_engine::InstallOperation;
template <typename T>
using RepeatedPtrField = google::protobuf::RepeatedPtrField<T>;
namespace android {
namespace snapshot {
static constexpr uint64_t kBlockSize = 4096;
using namespace android::storage_literals;
// Intersect two linear extents. If no intersection, return an extent with length 0.
static std::unique_ptr<Extent> Intersect(Extent* target_extent, Extent* existing_extent) {
// Convert target_extent and existing_extent to linear extents. Zero extents
// doesn't matter and doesn't result in any intersection.
auto existing_linear_extent = existing_extent->AsLinearExtent();
if (!existing_linear_extent) return nullptr;
auto target_linear_extent = target_extent->AsLinearExtent();
if (!target_linear_extent) return nullptr;
return Interval::Intersect(target_linear_extent->AsInterval(),
existing_linear_extent->AsInterval())
.AsExtent();
}
// Check that partition |p| contains |e| fully. Both of them should
// be from |target_metadata|.
// Returns true as long as |e| is a subrange of any extent of |p|.
bool PartitionCowCreator::HasExtent(Partition* p, Extent* e) {
for (auto& partition_extent : p->extents()) {
auto intersection = Intersect(partition_extent.get(), e);
if (intersection != nullptr && intersection->num_sectors() == e->num_sectors()) {
return true;
}
}
return false;
}
bool OptimizeSourceCopyOperation(const InstallOperation& operation, InstallOperation* optimized) {
if (operation.type() != InstallOperation::SOURCE_COPY) {
return false;
}
optimized->Clear();
optimized->set_type(InstallOperation::SOURCE_COPY);
const auto& src_extents = operation.src_extents();
const auto& dst_extents = operation.dst_extents();
// If input is empty, skip by returning an empty result.
if (src_extents.empty() && dst_extents.empty()) {
return true;
}
auto s_it = src_extents.begin();
auto d_it = dst_extents.begin();
uint64_t s_offset = 0; // offset within *s_it
uint64_t d_offset = 0; // offset within *d_it
bool is_optimized = false;
while (s_it != src_extents.end() || d_it != dst_extents.end()) {
if (s_it == src_extents.end() || d_it == dst_extents.end()) {
LOG(ERROR) << "number of blocks do not equal in src_extents and dst_extents";
return false;
}
if (s_it->num_blocks() <= s_offset || d_it->num_blocks() <= d_offset) {
LOG(ERROR) << "Offset goes out of bounds.";
return false;
}
// Check the next |step| blocks, where |step| is the min of remaining blocks in the current
// source extent and current destination extent.
auto s_step = s_it->num_blocks() - s_offset;
auto d_step = d_it->num_blocks() - d_offset;
auto step = std::min(s_step, d_step);
bool moved = s_it->start_block() + s_offset != d_it->start_block() + d_offset;
if (moved) {
// If the next |step| blocks are not copied to the same location, add them to result.
AppendExtent(optimized->mutable_src_extents(), s_it->start_block() + s_offset, step);
AppendExtent(optimized->mutable_dst_extents(), d_it->start_block() + d_offset, step);
} else {
// The next |step| blocks are optimized out.
is_optimized = true;
}
// Advance offsets by |step|, and go to the next non-empty extent if the current extent is
// depleted.
s_offset += step;
d_offset += step;
while (s_it != src_extents.end() && s_offset >= s_it->num_blocks()) {
++s_it;
s_offset = 0;
}
while (d_it != dst_extents.end() && d_offset >= d_it->num_blocks()) {
++d_it;
d_offset = 0;
}
}
return is_optimized;
}
void WriteExtent(DmSnapCowSizeCalculator* sc, const chromeos_update_engine::Extent& de,
unsigned int sectors_per_block) {
const auto block_boundary = de.start_block() + de.num_blocks();
for (auto b = de.start_block(); b < block_boundary; ++b) {
for (unsigned int s = 0; s < sectors_per_block; ++s) {
const auto sector_id = b * sectors_per_block + s;
sc->WriteSector(sector_id);
}
}
}
std::optional<uint64_t> PartitionCowCreator::GetCowSize() {
if (compression_enabled) {
if (update == nullptr || !update->has_estimate_cow_size()) {
LOG(ERROR) << "Update manifest does not include a COW size";
return std::nullopt;
}
// Add an extra 2MB of wiggle room for any minor differences in labels/metadata
// that might come up.
auto size = update->estimate_cow_size() + 2_MiB;
// Align to nearest block.
size += kBlockSize - 1;
size &= ~(kBlockSize - 1);
return size;
}
// WARNING: The origin partition should be READ-ONLY
const uint64_t logical_block_size = current_metadata->logical_block_size();
const unsigned int sectors_per_block = logical_block_size / kSectorSize;
DmSnapCowSizeCalculator sc(kSectorSize, kSnapshotChunkSize);
// Allocate space for extra extents (if any). These extents are those that can be
// used for error corrections or to store verity hash trees.
for (const auto& de : extra_extents) {
WriteExtent(&sc, de, sectors_per_block);
}
if (update == nullptr) return sc.cow_size_bytes();
for (const auto& iop : update->operations()) {
const InstallOperation* written_op = &iop;
InstallOperation buf;
// Do not allocate space for extents that are going to be skipped
// during OTA application.
if (iop.type() == InstallOperation::SOURCE_COPY && OptimizeSourceCopyOperation(iop, &buf)) {
written_op = &buf;
}
for (const auto& de : written_op->dst_extents()) {
WriteExtent(&sc, de, sectors_per_block);
}
}
return sc.cow_size_bytes();
}
std::optional<PartitionCowCreator::Return> PartitionCowCreator::Run() {
CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME &&
target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME);
const uint64_t logical_block_size = current_metadata->logical_block_size();
CHECK(logical_block_size != 0 && !(logical_block_size & (logical_block_size - 1)))
<< "logical_block_size is not power of 2";
Return ret;
ret.snapshot_status.set_name(target_partition->name());
ret.snapshot_status.set_device_size(target_partition->size());
ret.snapshot_status.set_snapshot_size(target_partition->size());
if (update && update->has_estimate_cow_size()) {
ret.snapshot_status.set_estimated_cow_size(update->estimate_cow_size());
}
if (ret.snapshot_status.snapshot_size() == 0) {
LOG(INFO) << "Not creating snapshot for partition " << ret.snapshot_status.name();
ret.snapshot_status.set_cow_partition_size(0);
ret.snapshot_status.set_cow_file_size(0);
return ret;
}
// Being the COW partition virtual, its size doesn't affect the storage
// memory that will be occupied by the target.
// The actual storage space is affected by the COW file, whose size depends
// on the chunks that diverged between |current| and |target|.
// If the |target| partition is bigger than |current|, the data that is
// modified outside of |current| can be written directly to |current|.
// This because the data that will be written outside of |current| would
// not invalidate any useful information of |current|, thus:
// - if the snapshot is accepted for merge, this data would be already at
// the right place and should not be copied;
// - in the unfortunate case of the snapshot to be discarded, the regions
// modified by this data can be set as free regions and reused.
// Compute regions that are free in both current and target metadata. These are the regions
// we can use for COW partition.
auto target_free_regions = target_metadata->GetFreeRegions();
auto current_free_regions = current_metadata->GetFreeRegions();
auto free_regions = Interval::Intersect(target_free_regions, current_free_regions);
uint64_t free_region_length = 0;
for (const auto& interval : free_regions) {
free_region_length += interval.length();
}
free_region_length *= kSectorSize;
LOG(INFO) << "Remaining free space for COW: " << free_region_length << " bytes";
auto cow_size = GetCowSize();
if (!cow_size) {
return {};
}
// Compute the COW partition size.
uint64_t cow_partition_size = std::min(cow_size.value(), free_region_length);
// Round it down to the nearest logical block. Logical partitions must be a multiple
// of logical blocks.
cow_partition_size &= ~(logical_block_size - 1);
ret.snapshot_status.set_cow_partition_size(cow_partition_size);
// Assign cow_partition_usable_regions to indicate what regions should the COW partition uses.
ret.cow_partition_usable_regions = std::move(free_regions);
auto cow_file_size = cow_size.value() - cow_partition_size;
// Round it up to the nearest sector.
cow_file_size += kSectorSize - 1;
cow_file_size &= ~(kSectorSize - 1);
ret.snapshot_status.set_cow_file_size(cow_file_size);
return ret;
}
} // namespace snapshot
} // namespace android
|