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