1 //
2 // Copyright (C) 2015 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/boot_control_android.h"
18
19 #include <memory>
20 #include <utility>
21 #include <vector>
22
23 #include <base/bind.h>
24 #include <base/logging.h>
25 #include <base/strings/string_util.h>
26 #include <bootloader_message/bootloader_message.h>
27 #include <brillo/message_loops/message_loop.h>
28 #include <fs_mgr.h>
29 #include <fs_mgr_overlayfs.h>
30
31 #include "update_engine/common/utils.h"
32 #include "update_engine/dynamic_partition_control_android.h"
33
34 using std::string;
35
36 using android::dm::DmDeviceState;
37 using android::fs_mgr::Partition;
38 using android::hardware::hidl_string;
39 using android::hardware::Return;
40 using android::hardware::boot::V1_0::BoolResult;
41 using android::hardware::boot::V1_0::CommandResult;
42 using android::hardware::boot::V1_0::IBootControl;
43 using Slot = chromeos_update_engine::BootControlInterface::Slot;
44 using PartitionMetadata =
45 chromeos_update_engine::BootControlInterface::PartitionMetadata;
46
47 namespace {
48
StoreResultCallback(CommandResult * dest)49 auto StoreResultCallback(CommandResult* dest) {
50 return [dest](const CommandResult& result) { *dest = result; };
51 }
52 } // namespace
53
54 namespace chromeos_update_engine {
55
56 namespace boot_control {
57
58 // Factory defined in boot_control.h.
CreateBootControl()59 std::unique_ptr<BootControlInterface> CreateBootControl() {
60 auto boot_control = std::make_unique<BootControlAndroid>();
61 if (!boot_control->Init()) {
62 return nullptr;
63 }
64 return std::move(boot_control);
65 }
66
67 } // namespace boot_control
68
Init()69 bool BootControlAndroid::Init() {
70 module_ = IBootControl::getService();
71 if (module_ == nullptr) {
72 LOG(ERROR) << "Error getting bootctrl HIDL module.";
73 return false;
74 }
75
76 LOG(INFO) << "Loaded boot control hidl hal.";
77
78 dynamic_control_ = std::make_unique<DynamicPartitionControlAndroid>();
79
80 return true;
81 }
82
Cleanup()83 void BootControlAndroid::Cleanup() {
84 dynamic_control_->Cleanup();
85 }
86
GetNumSlots() const87 unsigned int BootControlAndroid::GetNumSlots() const {
88 return module_->getNumberSlots();
89 }
90
GetCurrentSlot() const91 BootControlInterface::Slot BootControlAndroid::GetCurrentSlot() const {
92 return module_->getCurrentSlot();
93 }
94
GetSuffix(Slot slot,string * suffix) const95 bool BootControlAndroid::GetSuffix(Slot slot, string* suffix) const {
96 auto store_suffix_cb = [&suffix](hidl_string cb_suffix) {
97 *suffix = cb_suffix.c_str();
98 };
99 Return<void> ret = module_->getSuffix(slot, store_suffix_cb);
100
101 if (!ret.isOk()) {
102 LOG(ERROR) << "boot_control impl returned no suffix for slot "
103 << SlotName(slot);
104 return false;
105 }
106 return true;
107 }
108
IsSuperBlockDevice(const base::FilePath & device_dir,Slot slot,const string & partition_name_suffix) const109 bool BootControlAndroid::IsSuperBlockDevice(
110 const base::FilePath& device_dir,
111 Slot slot,
112 const string& partition_name_suffix) const {
113 string source_device =
114 device_dir.Append(fs_mgr_get_super_partition_name(slot)).value();
115 auto source_metadata = dynamic_control_->LoadMetadataBuilder(
116 source_device, slot, BootControlInterface::kInvalidSlot);
117 return source_metadata->HasBlockDevice(partition_name_suffix);
118 }
119
120 BootControlAndroid::DynamicPartitionDeviceStatus
GetDynamicPartitionDevice(const base::FilePath & device_dir,const string & partition_name_suffix,Slot slot,string * device) const121 BootControlAndroid::GetDynamicPartitionDevice(
122 const base::FilePath& device_dir,
123 const string& partition_name_suffix,
124 Slot slot,
125 string* device) const {
126 string super_device =
127 device_dir.Append(fs_mgr_get_super_partition_name(slot)).value();
128
129 auto builder = dynamic_control_->LoadMetadataBuilder(
130 super_device, slot, BootControlInterface::kInvalidSlot);
131
132 if (builder == nullptr) {
133 LOG(ERROR) << "No metadata in slot "
134 << BootControlInterface::SlotName(slot);
135 return DynamicPartitionDeviceStatus::ERROR;
136 }
137
138 Slot current_slot = GetCurrentSlot();
139 if (builder->FindPartition(partition_name_suffix) == nullptr) {
140 LOG(INFO) << partition_name_suffix
141 << " is not in super partition metadata.";
142
143 if (IsSuperBlockDevice(device_dir, current_slot, partition_name_suffix)) {
144 LOG(ERROR) << "The static partition " << partition_name_suffix
145 << " is a block device for current metadata ("
146 << fs_mgr_get_super_partition_name(current_slot) << ", slot "
147 << BootControlInterface::SlotName(current_slot)
148 << "). It cannot be used as a logical partition.";
149 return DynamicPartitionDeviceStatus::ERROR;
150 }
151
152 return DynamicPartitionDeviceStatus::TRY_STATIC;
153 }
154
155 if (slot == current_slot) {
156 if (dynamic_control_->GetState(partition_name_suffix) !=
157 DmDeviceState::ACTIVE) {
158 LOG(WARNING) << partition_name_suffix << " is at current slot but it is "
159 << "not mapped. Now try to map it.";
160 } else {
161 if (dynamic_control_->GetDmDevicePathByName(partition_name_suffix,
162 device)) {
163 LOG(INFO) << partition_name_suffix
164 << " is mapped on device mapper: " << *device;
165 return DynamicPartitionDeviceStatus::SUCCESS;
166 }
167 LOG(ERROR) << partition_name_suffix << "is mapped but path is unknown.";
168 return DynamicPartitionDeviceStatus::ERROR;
169 }
170 }
171
172 bool force_writable = slot != current_slot;
173 if (dynamic_control_->MapPartitionOnDeviceMapper(
174 super_device, partition_name_suffix, slot, force_writable, device)) {
175 return DynamicPartitionDeviceStatus::SUCCESS;
176 }
177 return DynamicPartitionDeviceStatus::ERROR;
178 }
179
GetPartitionDevice(const string & partition_name,Slot slot,string * device) const180 bool BootControlAndroid::GetPartitionDevice(const string& partition_name,
181 Slot slot,
182 string* device) const {
183 string suffix;
184 if (!GetSuffix(slot, &suffix)) {
185 return false;
186 }
187 const string partition_name_suffix = partition_name + suffix;
188
189 string device_dir_str;
190 if (!dynamic_control_->GetDeviceDir(&device_dir_str)) {
191 return false;
192 }
193 base::FilePath device_dir(device_dir_str);
194
195 // When looking up target partition devices, treat them as static if the
196 // current payload doesn't encode them as dynamic partitions. This may happen
197 // when applying a retrofit update on top of a dynamic-partitions-enabled
198 // build.
199 if (dynamic_control_->IsDynamicPartitionsEnabled() &&
200 (slot == GetCurrentSlot() || is_target_dynamic_)) {
201 switch (GetDynamicPartitionDevice(
202 device_dir, partition_name_suffix, slot, device)) {
203 case DynamicPartitionDeviceStatus::SUCCESS:
204 return true;
205 case DynamicPartitionDeviceStatus::TRY_STATIC:
206 break;
207 case DynamicPartitionDeviceStatus::ERROR: // fallthrough
208 default:
209 return false;
210 }
211 }
212
213 base::FilePath path = device_dir.Append(partition_name_suffix);
214 if (!dynamic_control_->DeviceExists(path.value())) {
215 LOG(ERROR) << "Device file " << path.value() << " does not exist.";
216 return false;
217 }
218
219 *device = path.value();
220 return true;
221 }
222
IsSlotBootable(Slot slot) const223 bool BootControlAndroid::IsSlotBootable(Slot slot) const {
224 Return<BoolResult> ret = module_->isSlotBootable(slot);
225 if (!ret.isOk()) {
226 LOG(ERROR) << "Unable to determine if slot " << SlotName(slot)
227 << " is bootable: " << ret.description();
228 return false;
229 }
230 if (ret == BoolResult::INVALID_SLOT) {
231 LOG(ERROR) << "Invalid slot: " << SlotName(slot);
232 return false;
233 }
234 return ret == BoolResult::TRUE;
235 }
236
MarkSlotUnbootable(Slot slot)237 bool BootControlAndroid::MarkSlotUnbootable(Slot slot) {
238 CommandResult result;
239 auto ret = module_->setSlotAsUnbootable(slot, StoreResultCallback(&result));
240 if (!ret.isOk()) {
241 LOG(ERROR) << "Unable to call MarkSlotUnbootable for slot "
242 << SlotName(slot) << ": " << ret.description();
243 return false;
244 }
245 if (!result.success) {
246 LOG(ERROR) << "Unable to mark slot " << SlotName(slot)
247 << " as unbootable: " << result.errMsg.c_str();
248 }
249 return result.success;
250 }
251
SetActiveBootSlot(Slot slot)252 bool BootControlAndroid::SetActiveBootSlot(Slot slot) {
253 CommandResult result;
254 auto ret = module_->setActiveBootSlot(slot, StoreResultCallback(&result));
255 if (!ret.isOk()) {
256 LOG(ERROR) << "Unable to call SetActiveBootSlot for slot " << SlotName(slot)
257 << ": " << ret.description();
258 return false;
259 }
260 if (!result.success) {
261 LOG(ERROR) << "Unable to set the active slot to slot " << SlotName(slot)
262 << ": " << result.errMsg.c_str();
263 }
264 return result.success;
265 }
266
MarkBootSuccessfulAsync(base::Callback<void (bool)> callback)267 bool BootControlAndroid::MarkBootSuccessfulAsync(
268 base::Callback<void(bool)> callback) {
269 CommandResult result;
270 auto ret = module_->markBootSuccessful(StoreResultCallback(&result));
271 if (!ret.isOk()) {
272 LOG(ERROR) << "Unable to call MarkBootSuccessful: " << ret.description();
273 return false;
274 }
275 if (!result.success) {
276 LOG(ERROR) << "Unable to mark boot successful: " << result.errMsg.c_str();
277 }
278 return brillo::MessageLoop::current()->PostTask(
279 FROM_HERE, base::Bind(callback, result.success)) !=
280 brillo::MessageLoop::kTaskIdNull;
281 }
282
283 namespace {
284
UpdatePartitionMetadata(DynamicPartitionControlInterface * dynamic_control,Slot source_slot,Slot target_slot,const string & target_suffix,const PartitionMetadata & partition_metadata)285 bool UpdatePartitionMetadata(DynamicPartitionControlInterface* dynamic_control,
286 Slot source_slot,
287 Slot target_slot,
288 const string& target_suffix,
289 const PartitionMetadata& partition_metadata) {
290 string device_dir_str;
291 if (!dynamic_control->GetDeviceDir(&device_dir_str)) {
292 return false;
293 }
294 base::FilePath device_dir(device_dir_str);
295 auto source_device =
296 device_dir.Append(fs_mgr_get_super_partition_name(source_slot)).value();
297
298 auto builder = dynamic_control->LoadMetadataBuilder(
299 source_device, source_slot, target_slot);
300 if (builder == nullptr) {
301 // TODO(elsk): allow reconstructing metadata from partition_metadata
302 // in recovery sideload.
303 LOG(ERROR) << "No metadata at "
304 << BootControlInterface::SlotName(source_slot);
305 return false;
306 }
307
308 std::vector<string> groups = builder->ListGroups();
309 for (const auto& group_name : groups) {
310 if (base::EndsWith(
311 group_name, target_suffix, base::CompareCase::SENSITIVE)) {
312 LOG(INFO) << "Removing group " << group_name;
313 builder->RemoveGroupAndPartitions(group_name);
314 }
315 }
316
317 uint64_t total_size = 0;
318 for (const auto& group : partition_metadata.groups) {
319 total_size += group.size;
320 }
321
322 string expr;
323 uint64_t allocatable_space = builder->AllocatableSpace();
324 if (!dynamic_control->IsDynamicPartitionsRetrofit()) {
325 allocatable_space /= 2;
326 expr = "half of ";
327 }
328 if (total_size > allocatable_space) {
329 LOG(ERROR) << "The maximum size of all groups with suffix " << target_suffix
330 << " (" << total_size << ") has exceeded " << expr
331 << " allocatable space for dynamic partitions "
332 << allocatable_space << ".";
333 return false;
334 }
335
336 for (const auto& group : partition_metadata.groups) {
337 auto group_name_suffix = group.name + target_suffix;
338 if (!builder->AddGroup(group_name_suffix, group.size)) {
339 LOG(ERROR) << "Cannot add group " << group_name_suffix << " with size "
340 << group.size;
341 return false;
342 }
343 LOG(INFO) << "Added group " << group_name_suffix << " with size "
344 << group.size;
345
346 for (const auto& partition : group.partitions) {
347 auto partition_name_suffix = partition.name + target_suffix;
348 Partition* p = builder->AddPartition(
349 partition_name_suffix, group_name_suffix, LP_PARTITION_ATTR_READONLY);
350 if (!p) {
351 LOG(ERROR) << "Cannot add partition " << partition_name_suffix
352 << " to group " << group_name_suffix;
353 return false;
354 }
355 if (!builder->ResizePartition(p, partition.size)) {
356 LOG(ERROR) << "Cannot resize partition " << partition_name_suffix
357 << " to size " << partition.size << ". Not enough space?";
358 return false;
359 }
360 LOG(INFO) << "Added partition " << partition_name_suffix << " to group "
361 << group_name_suffix << " with size " << partition.size;
362 }
363 }
364
365 auto target_device =
366 device_dir.Append(fs_mgr_get_super_partition_name(target_slot)).value();
367 return dynamic_control->StoreMetadata(
368 target_device, builder.get(), target_slot);
369 }
370
UnmapTargetPartitions(DynamicPartitionControlInterface * dynamic_control,const string & target_suffix,const PartitionMetadata & partition_metadata)371 bool UnmapTargetPartitions(DynamicPartitionControlInterface* dynamic_control,
372 const string& target_suffix,
373 const PartitionMetadata& partition_metadata) {
374 for (const auto& group : partition_metadata.groups) {
375 for (const auto& partition : group.partitions) {
376 if (!dynamic_control->UnmapPartitionOnDeviceMapper(
377 partition.name + target_suffix, true /* wait */)) {
378 return false;
379 }
380 }
381 }
382 return true;
383 }
384
385 } // namespace
386
InitPartitionMetadata(Slot target_slot,const PartitionMetadata & partition_metadata,bool update_metadata)387 bool BootControlAndroid::InitPartitionMetadata(
388 Slot target_slot,
389 const PartitionMetadata& partition_metadata,
390 bool update_metadata) {
391 if (fs_mgr_overlayfs_is_setup()) {
392 // Non DAP devices can use overlayfs as well.
393 LOG(WARNING)
394 << "overlayfs overrides are active and can interfere with our "
395 "resources.\n"
396 << "run adb enable-verity to deactivate if required and try again.";
397 }
398 if (!dynamic_control_->IsDynamicPartitionsEnabled()) {
399 return true;
400 }
401
402 auto source_slot = GetCurrentSlot();
403 if (target_slot == source_slot) {
404 LOG(ERROR) << "Cannot call InitPartitionMetadata on current slot.";
405 return false;
406 }
407
408 // Although the current build supports dynamic partitions, the given payload
409 // doesn't use it for target partitions. This could happen when applying a
410 // retrofit update. Skip updating the partition metadata for the target slot.
411 is_target_dynamic_ = !partition_metadata.groups.empty();
412 if (!is_target_dynamic_) {
413 return true;
414 }
415
416 if (!update_metadata) {
417 return true;
418 }
419
420 string target_suffix;
421 if (!GetSuffix(target_slot, &target_suffix)) {
422 return false;
423 }
424
425 // Unmap all the target dynamic partitions because they would become
426 // inconsistent with the new metadata.
427 if (!UnmapTargetPartitions(
428 dynamic_control_.get(), target_suffix, partition_metadata)) {
429 return false;
430 }
431
432 return UpdatePartitionMetadata(dynamic_control_.get(),
433 source_slot,
434 target_slot,
435 target_suffix,
436 partition_metadata);
437 }
438
439 } // namespace chromeos_update_engine
440