update_engine resize dynamic partitions during OTA.
update_engine uses device mapper to resize dynamic partitions
before opening the devices to apply the update.
* DeltaPerformer calls BootControlInterface::InitPartitionMetadata
when parsing the update manifest. The implementation for
BootControlAndroid::InitPartitionMetadata does the following
if sizes for dynamic partitions are incorrect (assuming updating
from slot A to B):
* Load metadata from metadata slot A
* Delete all extents of partitions at slot B (with _b suffix)
* Add extents for partitions at slot B
* Write metadata to metadata slot B
* Re-map all partitions at slot B using metadata slot B with
force_writable = true
* BootControlAndroid::GetPartitionDevice() checks device-mapper
before returning static partitions.
* PostinstallRunnerAction::Cleanup calls BootControlInterface::Cleanup
which unmaps all partitions at slot B.
A partition "foo" is considered dynamic if foo_a exists as a dynamic
partition OR foo_b does NOT exist as a static partition.
Bug: 110717529
Test: manual ota
Test: update_engine_unittests --gtest_filter=*BootControlAndroid*
Change-Id: I50f410b486a874242663624801c3694151bdda18
diff --git a/boot_control_android.cc b/boot_control_android.cc
index 12a3a10..abf898e 100644
--- a/boot_control_android.cc
+++ b/boot_control_android.cc
@@ -20,22 +20,32 @@
#include <utility>
#include <base/bind.h>
-#include <base/files/file_util.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 android::hardware::hidl_string;
+using Slot = chromeos_update_engine::BootControlInterface::Slot;
+using PartitionSizes =
+ chromeos_update_engine::BootControlInterface::PartitionSizes;
namespace {
+
+constexpr char kZeroGuid[] = "00000000-0000-0000-0000-000000000000";
+
auto StoreResultCallback(CommandResult* dest) {
return [dest](const CommandResult& result) { *dest = result; };
}
@@ -47,7 +57,7 @@
// Factory defined in boot_control.h.
std::unique_ptr<BootControlInterface> CreateBootControl() {
- std::unique_ptr<BootControlAndroid> boot_control(new BootControlAndroid());
+ auto boot_control = std::make_unique<BootControlAndroid>();
if (!boot_control->Init()) {
return nullptr;
}
@@ -65,9 +75,15 @@
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();
}
@@ -76,43 +92,9 @@
return module_->getCurrentSlot();
}
-bool BootControlAndroid::GetPartitionDevice(const string& partition_name,
- Slot slot,
- string* device) const {
- // We can't use fs_mgr to look up |partition_name| because fstab
- // doesn't list every slot partition (it uses the slotselect option
- // to mask the suffix).
- //
- // We can however assume that there's an entry for the /misc mount
- // point and use that to get the device file for the misc
- // partition. This helps us locate the disk that |partition_name|
- // resides on. From there we'll assume that a by-name scheme is used
- // so we can just replace the trailing "misc" by the given
- // |partition_name| and suffix corresponding to |slot|, e.g.
- //
- // /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
- // /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
- //
- // If needed, it's possible to relax the by-name assumption in the
- // future by trawling /sys/block looking for the appropriate sibling
- // of misc and then finding an entry in /dev matching the sysfs
- // entry.
-
- string err, misc_device = get_bootloader_message_blk_device(&err);
- if (misc_device.empty()) {
- LOG(ERROR) << "Unable to get misc block device: " << err;
- return false;
- }
-
- if (!utils::IsSymlink(misc_device.c_str())) {
- LOG(ERROR) << "Device file " << misc_device << " for /misc "
- << "is not a symlink.";
- return false;
- }
-
- string suffix;
+bool BootControlAndroid::GetSuffix(Slot slot, string* suffix) const {
auto store_suffix_cb = [&suffix](hidl_string cb_suffix) {
- suffix = cb_suffix.c_str();
+ *suffix = cb_suffix.c_str();
};
Return<void> ret = module_->getSuffix(slot, store_suffix_cb);
@@ -121,10 +103,56 @@
<< 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(misc_device).DirName().Append(partition_name + suffix);
- if (!base::PathExists(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;
}
@@ -196,4 +224,250 @@
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,
+ kZeroGuid,
+ 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