| // |
| // 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 <fs_mgr.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(fs_mgr_get_super_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 |