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_consumer/partition_update_generator_android.h"
18 
19 #include <filesystem>
20 #include <memory>
21 #include <set>
22 #include <string_view>
23 #include <utility>
24 
25 #include <android-base/strings.h>
26 #include <base/logging.h>
27 
28 #include "update_engine/common/hash_calculator.h"
29 #include "update_engine/common/utils.h"
30 
31 namespace {
32 // TODO(xunchang) use definition in fs_mgr, e.g. fs_mgr_get_slot_suffix
33 const char* SUFFIX_A = "_a";
34 const char* SUFFIX_B = "_b";
35 }  // namespace
36 
37 namespace chromeos_update_engine {
38 
PartitionUpdateGeneratorAndroid(BootControlInterface * boot_control,std::string device_dir,size_t block_size)39 PartitionUpdateGeneratorAndroid::PartitionUpdateGeneratorAndroid(
40     BootControlInterface* boot_control,
41     std::string device_dir,
42     size_t block_size)
43     : boot_control_(boot_control),
44       block_device_dir_(std::move(device_dir)),
45       block_size_(block_size) {}
46 
47 bool PartitionUpdateGeneratorAndroid::
GenerateOperationsForPartitionsNotInPayload(BootControlInterface::Slot source_slot,BootControlInterface::Slot target_slot,const std::set<std::string> & partitions_in_payload,std::vector<PartitionUpdate> * update_list)48     GenerateOperationsForPartitionsNotInPayload(
49         BootControlInterface::Slot source_slot,
50         BootControlInterface::Slot target_slot,
51         const std::set<std::string>& partitions_in_payload,
52         std::vector<PartitionUpdate>* update_list) {
53   auto ab_partitions = GetStaticAbPartitionsOnDevice();
54   if (!ab_partitions.has_value()) {
55     LOG(ERROR) << "Failed to load static a/b partitions";
56     return false;
57   }
58 
59   std::vector<PartitionUpdate> partition_updates;
60   for (const auto& partition_name : ab_partitions.value()) {
61     if (partitions_in_payload.find(partition_name) !=
62         partitions_in_payload.end()) {
63       LOG(INFO) << partition_name << " has included in payload";
64       continue;
65     }
66 
67     auto partition_update =
68         CreatePartitionUpdate(partition_name, source_slot, target_slot);
69     if (!partition_update.has_value()) {
70       LOG(ERROR) << "Failed to create partition update for " << partition_name;
71       return false;
72     }
73     partition_updates.push_back(partition_update.value());
74   }
75   *update_list = std::move(partition_updates);
76   return true;
77 }
78 
79 std::optional<std::set<std::string>>
GetStaticAbPartitionsOnDevice()80 PartitionUpdateGeneratorAndroid::GetStaticAbPartitionsOnDevice() {
81   if (std::error_code error_code;
82       !std::filesystem::exists(block_device_dir_, error_code) || error_code) {
83     LOG(ERROR) << "Failed to find " << block_device_dir_ << " "
84                << error_code.message();
85     return std::nullopt;
86   }
87 
88   std::error_code error_code;
89   auto it = std::filesystem::directory_iterator(block_device_dir_, error_code);
90   if (error_code) {
91     LOG(ERROR) << "Failed to iterate " << block_device_dir_ << " "
92                << error_code.message();
93     return std::nullopt;
94   }
95 
96   std::set<std::string> partitions_with_suffix;
97   for (const auto& entry : it) {
98     auto partition_name = entry.path().filename().string();
99     if (android::base::EndsWith(partition_name, SUFFIX_A) ||
100         android::base::EndsWith(partition_name, SUFFIX_B)) {
101       partitions_with_suffix.insert(partition_name);
102     }
103   }
104 
105   // Second iteration to add the partition name without suffixes.
106   std::set<std::string> ab_partitions;
107   for (std::string_view name : partitions_with_suffix) {
108     if (!android::base::ConsumeSuffix(&name, SUFFIX_A)) {
109       continue;
110     }
111 
112     // Add to the output list if the partition exist for both slot a and b.
113     auto base_name = std::string(name);
114     if (partitions_with_suffix.find(base_name + SUFFIX_B) !=
115         partitions_with_suffix.end()) {
116       ab_partitions.insert(base_name);
117     } else {
118       LOG(WARNING) << "Failed to find the b partition for " << base_name;
119     }
120   }
121 
122   return ab_partitions;
123 }
124 
125 std::optional<PartitionUpdate>
CreatePartitionUpdate(const std::string & partition_name,BootControlInterface::Slot source_slot,BootControlInterface::Slot target_slot)126 PartitionUpdateGeneratorAndroid::CreatePartitionUpdate(
127     const std::string& partition_name,
128     BootControlInterface::Slot source_slot,
129     BootControlInterface::Slot target_slot) {
130   bool is_source_dynamic = false;
131   std::string source_device;
132   if (!boot_control_->GetPartitionDevice(partition_name,
133                                          source_slot,
134                                          true, /* not_in_payload */
135                                          &source_device,
136                                          &is_source_dynamic)) {
137     LOG(ERROR) << "Failed to load source " << partition_name;
138     return std::nullopt;
139   }
140   bool is_target_dynamic = false;
141   std::string target_device;
142   if (!boot_control_->GetPartitionDevice(partition_name,
143                                          target_slot,
144                                          true,
145                                          &target_device,
146                                          &is_target_dynamic)) {
147     LOG(ERROR) << "Failed to load target " << partition_name;
148     return std::nullopt;
149   }
150 
151   if (is_source_dynamic || is_target_dynamic) {
152     LOG(ERROR) << "Partition " << partition_name << " is expected to be a"
153                << " static partition. source slot is "
154                << (is_source_dynamic ? "" : "not")
155                << " dynamic, and target slot " << target_slot << " is "
156                << (is_target_dynamic ? "" : "not") << " dynamic.";
157     return std::nullopt;
158   }
159 
160   auto source_size = utils::FileSize(source_device);
161   auto target_size = utils::FileSize(target_device);
162   if (source_size == -1 || target_size == -1 || source_size != target_size ||
163       source_size % block_size_ != 0) {
164     LOG(ERROR) << "Invalid partition size. source size " << source_size
165                << ", target size " << target_size;
166     return std::nullopt;
167   }
168 
169   return CreatePartitionUpdate(
170       partition_name, source_device, target_device, source_size);
171 }
172 
173 std::optional<PartitionUpdate>
CreatePartitionUpdate(const std::string & partition_name,const std::string & source_device,const std::string & target_device,int64_t partition_size)174 PartitionUpdateGeneratorAndroid::CreatePartitionUpdate(
175     const std::string& partition_name,
176     const std::string& source_device,
177     const std::string& target_device,
178     int64_t partition_size) {
179   PartitionUpdate partition_update;
180   partition_update.set_partition_name(partition_name);
181   auto old_partition_info = partition_update.mutable_old_partition_info();
182   old_partition_info->set_size(partition_size);
183 
184   auto raw_hash = CalculateHashForPartition(source_device, partition_size);
185   if (!raw_hash.has_value()) {
186     return {};
187   }
188   old_partition_info->set_hash(raw_hash->data(), raw_hash->size());
189   auto new_partition_info = partition_update.mutable_new_partition_info();
190   new_partition_info->set_size(partition_size);
191   new_partition_info->set_hash(raw_hash->data(), raw_hash->size());
192 
193   auto copy_operation = partition_update.add_operations();
194   copy_operation->set_type(InstallOperation::SOURCE_COPY);
195   Extent copy_extent;
196   copy_extent.set_start_block(0);
197   copy_extent.set_num_blocks(partition_size / block_size_);
198 
199   *copy_operation->add_src_extents() = copy_extent;
200   *copy_operation->add_dst_extents() = copy_extent;
201 
202   return partition_update;
203 }
204 
205 std::optional<brillo::Blob>
CalculateHashForPartition(const std::string & block_device,int64_t partition_size)206 PartitionUpdateGeneratorAndroid::CalculateHashForPartition(
207     const std::string& block_device, int64_t partition_size) {
208   // TODO(xunchang) compute the hash with ecc partitions first, the hashing
209   // behavior should match the one in SOURCE_COPY. Also, we don't have the
210   // correct hash for source partition.
211   // An alternative way is to verify the written bytes match the read bytes
212   // during filesystem verification. This could probably save us a read of
213   // partitions here.
214   brillo::Blob raw_hash;
215   if (HashCalculator::RawHashOfFile(block_device, partition_size, &raw_hash) !=
216       partition_size) {
217     LOG(ERROR) << "Failed to calculate hash for " << block_device;
218     return std::nullopt;
219   }
220 
221   return raw_hash;
222 }
223 
224 namespace partition_update_generator {
Create(BootControlInterface * boot_control,size_t block_size)225 std::unique_ptr<PartitionUpdateGeneratorInterface> Create(
226     BootControlInterface* boot_control, size_t block_size) {
227   CHECK(boot_control);
228   auto dynamic_control = boot_control->GetDynamicPartitionControl();
229   CHECK(dynamic_control);
230   std::string dir_path;
231   if (!dynamic_control->GetDeviceDir(&dir_path)) {
232     return nullptr;
233   }
234 
235   return std::unique_ptr<PartitionUpdateGeneratorInterface>(
236       new PartitionUpdateGeneratorAndroid(
237           boot_control, std::move(dir_path), block_size));
238 }
239 }  // namespace partition_update_generator
240 
241 }  // namespace chromeos_update_engine
242