|  | // | 
|  | // Copyright (C) 2015 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 "update_engine/boot_control_android.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <utility> | 
|  |  | 
|  | #include <base/bind.h> | 
|  | #include <base/logging.h> | 
|  | #include <bootloader_message/bootloader_message.h> | 
|  | #include <brillo/message_loops/message_loop.h> | 
|  |  | 
|  | #include "update_engine/common/utils.h" | 
|  | #include "update_engine/dynamic_partition_control_android.h" | 
|  |  | 
|  | using std::string; | 
|  |  | 
|  | using android::dm::DmDeviceState; | 
|  | using android::fs_mgr::MetadataBuilder; | 
|  | using android::fs_mgr::Partition; | 
|  | using android::fs_mgr::UpdatePartitionTable; | 
|  | using android::hardware::hidl_string; | 
|  | using android::hardware::Return; | 
|  | using android::hardware::boot::V1_0::BoolResult; | 
|  | using android::hardware::boot::V1_0::CommandResult; | 
|  | using android::hardware::boot::V1_0::IBootControl; | 
|  | using Slot = chromeos_update_engine::BootControlInterface::Slot; | 
|  | using PartitionSizes = | 
|  | chromeos_update_engine::BootControlInterface::PartitionSizes; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | auto StoreResultCallback(CommandResult* dest) { | 
|  | return [dest](const CommandResult& result) { *dest = result; }; | 
|  | } | 
|  | }  // namespace | 
|  |  | 
|  | namespace chromeos_update_engine { | 
|  |  | 
|  | namespace boot_control { | 
|  |  | 
|  | // Factory defined in boot_control.h. | 
|  | std::unique_ptr<BootControlInterface> CreateBootControl() { | 
|  | auto boot_control = std::make_unique<BootControlAndroid>(); | 
|  | if (!boot_control->Init()) { | 
|  | return nullptr; | 
|  | } | 
|  | return std::move(boot_control); | 
|  | } | 
|  |  | 
|  | }  // namespace boot_control | 
|  |  | 
|  | bool BootControlAndroid::Init() { | 
|  | module_ = IBootControl::getService(); | 
|  | if (module_ == nullptr) { | 
|  | LOG(ERROR) << "Error getting bootctrl HIDL module."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | LOG(INFO) << "Loaded boot control hidl hal."; | 
|  |  | 
|  | dynamic_control_ = std::make_unique<DynamicPartitionControlAndroid>(); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void BootControlAndroid::Cleanup() { | 
|  | dynamic_control_->Cleanup(); | 
|  | } | 
|  |  | 
|  | unsigned int BootControlAndroid::GetNumSlots() const { | 
|  | return module_->getNumberSlots(); | 
|  | } | 
|  |  | 
|  | BootControlInterface::Slot BootControlAndroid::GetCurrentSlot() const { | 
|  | return module_->getCurrentSlot(); | 
|  | } | 
|  |  | 
|  | bool BootControlAndroid::GetSuffix(Slot slot, string* suffix) const { | 
|  | auto store_suffix_cb = [&suffix](hidl_string cb_suffix) { | 
|  | *suffix = cb_suffix.c_str(); | 
|  | }; | 
|  | Return<void> ret = module_->getSuffix(slot, store_suffix_cb); | 
|  |  | 
|  | if (!ret.isOk()) { | 
|  | LOG(ERROR) << "boot_control impl returned no suffix for slot " | 
|  | << SlotName(slot); | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool BootControlAndroid::GetPartitionDevice(const string& partition_name, | 
|  | Slot slot, | 
|  | string* device) const { | 
|  | string suffix; | 
|  | if (!GetSuffix(slot, &suffix)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const string target_partition_name = partition_name + suffix; | 
|  |  | 
|  | // DeltaPerformer calls InitPartitionMetadata before calling | 
|  | // InstallPlan::LoadPartitionsFromSlots. After InitPartitionMetadata, | 
|  | // the partition must be re-mapped with force_writable == true. Hence, | 
|  | // we only need to check device mapper. | 
|  | if (dynamic_control_->IsDynamicPartitionsEnabled()) { | 
|  | switch (dynamic_control_->GetState(target_partition_name)) { | 
|  | case DmDeviceState::ACTIVE: | 
|  | if (dynamic_control_->GetDmDevicePathByName(target_partition_name, | 
|  | device)) { | 
|  | LOG(INFO) << target_partition_name | 
|  | << " is mapped on device mapper: " << *device; | 
|  | return true; | 
|  | } | 
|  | LOG(ERROR) << target_partition_name | 
|  | << " is mapped but path is unknown."; | 
|  | return false; | 
|  |  | 
|  | case DmDeviceState::INVALID: | 
|  | // Try static partitions. | 
|  | break; | 
|  |  | 
|  | case DmDeviceState::SUSPENDED:  // fallthrough | 
|  | default: | 
|  | LOG(ERROR) << target_partition_name | 
|  | << " is mapped on device mapper but state is unknown"; | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | string device_dir_str; | 
|  | if (!dynamic_control_->GetDeviceDir(&device_dir_str)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | base::FilePath path = | 
|  | base::FilePath(device_dir_str).Append(target_partition_name); | 
|  | if (!dynamic_control_->DeviceExists(path.value())) { | 
|  | LOG(ERROR) << "Device file " << path.value() << " does not exist."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | *device = path.value(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool BootControlAndroid::IsSlotBootable(Slot slot) const { | 
|  | Return<BoolResult> ret = module_->isSlotBootable(slot); | 
|  | if (!ret.isOk()) { | 
|  | LOG(ERROR) << "Unable to determine if slot " << SlotName(slot) | 
|  | << " is bootable: " | 
|  | << ret.description(); | 
|  | return false; | 
|  | } | 
|  | if (ret == BoolResult::INVALID_SLOT) { | 
|  | LOG(ERROR) << "Invalid slot: " << SlotName(slot); | 
|  | return false; | 
|  | } | 
|  | return ret == BoolResult::TRUE; | 
|  | } | 
|  |  | 
|  | bool BootControlAndroid::MarkSlotUnbootable(Slot slot) { | 
|  | CommandResult result; | 
|  | auto ret = module_->setSlotAsUnbootable(slot, StoreResultCallback(&result)); | 
|  | if (!ret.isOk()) { | 
|  | LOG(ERROR) << "Unable to call MarkSlotUnbootable for slot " | 
|  | << SlotName(slot) << ": " | 
|  | << ret.description(); | 
|  | return false; | 
|  | } | 
|  | if (!result.success) { | 
|  | LOG(ERROR) << "Unable to mark slot " << SlotName(slot) | 
|  | << " as unbootable: " << result.errMsg.c_str(); | 
|  | } | 
|  | return result.success; | 
|  | } | 
|  |  | 
|  | bool BootControlAndroid::SetActiveBootSlot(Slot slot) { | 
|  | CommandResult result; | 
|  | auto ret = module_->setActiveBootSlot(slot, StoreResultCallback(&result)); | 
|  | if (!ret.isOk()) { | 
|  | LOG(ERROR) << "Unable to call SetActiveBootSlot for slot " << SlotName(slot) | 
|  | << ": " << ret.description(); | 
|  | return false; | 
|  | } | 
|  | if (!result.success) { | 
|  | LOG(ERROR) << "Unable to set the active slot to slot " << SlotName(slot) | 
|  | << ": " << result.errMsg.c_str(); | 
|  | } | 
|  | return result.success; | 
|  | } | 
|  |  | 
|  | bool BootControlAndroid::MarkBootSuccessfulAsync( | 
|  | base::Callback<void(bool)> callback) { | 
|  | CommandResult result; | 
|  | auto ret = module_->markBootSuccessful(StoreResultCallback(&result)); | 
|  | if (!ret.isOk()) { | 
|  | LOG(ERROR) << "Unable to call MarkBootSuccessful: " | 
|  | << ret.description(); | 
|  | return false; | 
|  | } | 
|  | if (!result.success) { | 
|  | LOG(ERROR) << "Unable to mark boot successful: " << result.errMsg.c_str(); | 
|  | } | 
|  | return brillo::MessageLoop::current()->PostTask( | 
|  | FROM_HERE, base::Bind(callback, result.success)) != | 
|  | brillo::MessageLoop::kTaskIdNull; | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Resize |partition_name|_|slot| to the given |size|. | 
|  | bool ResizePartition(MetadataBuilder* builder, | 
|  | const string& target_partition_name, | 
|  | uint64_t size) { | 
|  | Partition* partition = builder->FindPartition(target_partition_name); | 
|  | if (partition == nullptr) { | 
|  | LOG(ERROR) << "Cannot find " << target_partition_name << " in metadata."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | uint64_t old_size = partition->size(); | 
|  | const string action = "resize " + target_partition_name + " in super (" + | 
|  | std::to_string(old_size) + " -> " + | 
|  | std::to_string(size) + " bytes)"; | 
|  | if (!builder->ResizePartition(partition, size)) { | 
|  | LOG(ERROR) << "Cannot " << action << "; see previous log messages."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (partition->size() != size) { | 
|  | LOG(ERROR) << "Cannot " << action | 
|  | << "; value is misaligned and partition should have been " | 
|  | << partition->size(); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | LOG(INFO) << "Successfully " << action; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool ResizePartitions(DynamicPartitionControlInterface* dynamic_control, | 
|  | const string& super_device, | 
|  | Slot target_slot, | 
|  | const string& target_suffix, | 
|  | const PartitionSizes& logical_sizes, | 
|  | MetadataBuilder* builder) { | 
|  | // Delete all extents to ensure that each partition has enough space to | 
|  | // grow. | 
|  | for (const auto& pair : logical_sizes) { | 
|  | const string target_partition_name = pair.first + target_suffix; | 
|  | if (builder->FindPartition(target_partition_name) == nullptr) { | 
|  | // Use constant GUID because it is unused. | 
|  | LOG(INFO) << "Adding partition " << target_partition_name << " to slot " | 
|  | << BootControlInterface::SlotName(target_slot) << " in " | 
|  | << super_device; | 
|  | if (builder->AddPartition(target_partition_name, | 
|  | LP_PARTITION_ATTR_READONLY) == nullptr) { | 
|  | LOG(ERROR) << "Cannot add partition " << target_partition_name; | 
|  | return false; | 
|  | } | 
|  | } | 
|  | if (!ResizePartition(builder, pair.first + target_suffix, 0 /* size */)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | for (const auto& pair : logical_sizes) { | 
|  | if (!ResizePartition(builder, pair.first + target_suffix, pair.second)) { | 
|  | LOG(ERROR) << "Not enough space?"; | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!dynamic_control->StoreMetadata(super_device, builder, target_slot)) { | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Assume upgrading from slot A to B. A partition foo is considered dynamic | 
|  | // iff one of the following: | 
|  | // 1. foo_a exists as a dynamic partition (so it should continue to be a | 
|  | //    dynamic partition) | 
|  | // 2. foo_b does not exist as a static partition (in which case we may be | 
|  | //    adding a new partition). | 
|  | bool IsDynamicPartition(DynamicPartitionControlInterface* dynamic_control, | 
|  | const base::FilePath& device_dir, | 
|  | MetadataBuilder* source_metadata, | 
|  | const string& partition_name, | 
|  | const string& source_suffix, | 
|  | const string& target_suffix) { | 
|  | bool dynamic_source_exist = | 
|  | source_metadata->FindPartition(partition_name + source_suffix) != nullptr; | 
|  | bool static_target_exist = dynamic_control->DeviceExists( | 
|  | device_dir.Append(partition_name + target_suffix).value()); | 
|  |  | 
|  | return dynamic_source_exist || !static_target_exist; | 
|  | } | 
|  |  | 
|  | bool FilterPartitionSizes(DynamicPartitionControlInterface* dynamic_control, | 
|  | const base::FilePath& device_dir, | 
|  | const PartitionSizes& partition_sizes, | 
|  | MetadataBuilder* source_metadata, | 
|  | const string& source_suffix, | 
|  | const string& target_suffix, | 
|  | PartitionSizes* logical_sizes) { | 
|  | for (const auto& pair : partition_sizes) { | 
|  | if (!IsDynamicPartition(dynamic_control, | 
|  | device_dir, | 
|  | source_metadata, | 
|  | pair.first, | 
|  | source_suffix, | 
|  | target_suffix)) { | 
|  | // In the future we can check static partition sizes, but skip for now. | 
|  | LOG(INFO) << pair.first << " is static; assume its size is " | 
|  | << pair.second << " bytes."; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | logical_sizes->insert(pair); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Return false if partition sizes are all correct in metadata slot | 
|  | // |target_slot|. If so, no need to resize. |logical_sizes| have format like | 
|  | // {vendor: size, ...}, and fail if a partition is not found. | 
|  | bool NeedResizePartitions(DynamicPartitionControlInterface* dynamic_control, | 
|  | const string& super_device, | 
|  | Slot target_slot, | 
|  | const string& suffix, | 
|  | const PartitionSizes& logical_sizes) { | 
|  | auto target_metadata = | 
|  | dynamic_control->LoadMetadataBuilder(super_device, target_slot); | 
|  | if (target_metadata == nullptr) { | 
|  | LOG(INFO) << "Metadata slot " << BootControlInterface::SlotName(target_slot) | 
|  | << " in " << super_device | 
|  | << " is corrupted; attempt to recover from source slot."; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | for (const auto& pair : logical_sizes) { | 
|  | Partition* partition = target_metadata->FindPartition(pair.first + suffix); | 
|  | if (partition == nullptr) { | 
|  | LOG(INFO) << "Cannot find " << pair.first << suffix << " at slot " | 
|  | << BootControlInterface::SlotName(target_slot) << " in " | 
|  | << super_device << ". Need to resize."; | 
|  | return true; | 
|  | } | 
|  | if (partition->size() != pair.second) { | 
|  | LOG(INFO) << super_device << ":" | 
|  | << BootControlInterface::SlotName(target_slot) << ":" | 
|  | << pair.first << suffix << ": size == " << partition->size() | 
|  | << " but requested " << pair.second << ". Need to resize."; | 
|  | return true; | 
|  | } | 
|  | LOG(INFO) << super_device << ":" | 
|  | << BootControlInterface::SlotName(target_slot) << ":" | 
|  | << pair.first << suffix << ": size == " << partition->size() | 
|  | << " as requested."; | 
|  | } | 
|  | LOG(INFO) << "No need to resize at metadata slot " | 
|  | << BootControlInterface::SlotName(target_slot) << " in " | 
|  | << super_device; | 
|  | return false; | 
|  | } | 
|  | }  // namespace | 
|  |  | 
|  | bool BootControlAndroid::InitPartitionMetadata( | 
|  | Slot target_slot, const PartitionSizes& partition_sizes) { | 
|  | if (!dynamic_control_->IsDynamicPartitionsEnabled()) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | string device_dir_str; | 
|  | if (!dynamic_control_->GetDeviceDir(&device_dir_str)) { | 
|  | return false; | 
|  | } | 
|  | base::FilePath device_dir(device_dir_str); | 
|  | string super_device = device_dir.Append(LP_METADATA_PARTITION_NAME).value(); | 
|  |  | 
|  | Slot current_slot = GetCurrentSlot(); | 
|  | if (target_slot == current_slot) { | 
|  | LOG(ERROR) << "Cannot call InitPartitionMetadata on current slot."; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | string current_suffix; | 
|  | if (!GetSuffix(current_slot, ¤t_suffix)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | string target_suffix; | 
|  | if (!GetSuffix(target_slot, &target_suffix)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | auto builder = | 
|  | dynamic_control_->LoadMetadataBuilder(super_device, current_slot); | 
|  | if (builder == nullptr) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Read metadata from current slot to determine which partitions are logical | 
|  | // and may be resized. Do not read from target slot because metadata at | 
|  | // target slot may be corrupted. | 
|  | PartitionSizes logical_sizes; | 
|  | if (!FilterPartitionSizes(dynamic_control_.get(), | 
|  | device_dir, | 
|  | partition_sizes, | 
|  | builder.get() /* source metadata */, | 
|  | current_suffix, | 
|  | target_suffix, | 
|  | &logical_sizes)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Read metadata from target slot to determine if the sizes are correct. Only | 
|  | // test logical partitions. | 
|  | if (NeedResizePartitions(dynamic_control_.get(), | 
|  | super_device, | 
|  | target_slot, | 
|  | target_suffix, | 
|  | logical_sizes)) { | 
|  | if (!ResizePartitions(dynamic_control_.get(), | 
|  | super_device, | 
|  | target_slot, | 
|  | target_suffix, | 
|  | logical_sizes, | 
|  | builder.get())) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Unmap all partitions, and remap partitions if size is non-zero. | 
|  | for (const auto& pair : logical_sizes) { | 
|  | if (!dynamic_control_->UnmapPartitionOnDeviceMapper( | 
|  | pair.first + target_suffix, true /* wait */)) { | 
|  | return false; | 
|  | } | 
|  | if (pair.second == 0) { | 
|  | continue; | 
|  | } | 
|  | string map_path; | 
|  | if (!dynamic_control_->MapPartitionOnDeviceMapper( | 
|  | super_device, pair.first + target_suffix, target_slot, &map_path)) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | }  // namespace chromeos_update_engine |