Allow to skip mounting metadata in recovery.
After factory data reset, metadata has no valid ext4 fs, and it is not
formatted when recovery is started. Hence, it is possible that recovery
can't mount metadata. Use fallback path for sideloading full OTAs on
Virtual A/B devices in this case.
Test: the following:
fastboot reboot fastboot -w
fastboot reboot recovery
adb root
adb shell mount -t ext4 /dev/block/by-name/metadata /metadata # fails
adb reboot sideload
adb sideload ota.zip # successful
Bug: 152352037
Change-Id: I51ae3e5918b0c00054f309832c45823d80e46c69
diff --git a/dynamic_partition_control_android.cc b/dynamic_partition_control_android.cc
index 1e92f45..a310f20 100644
--- a/dynamic_partition_control_android.cc
+++ b/dynamic_partition_control_android.cc
@@ -152,10 +152,12 @@
};
bool success = false;
if (GetVirtualAbFeatureFlag().IsEnabled() && target_supports_snapshot_ &&
- force_writable) {
+ force_writable && ExpectMetadataMounted()) {
// Only target partitions are mapped with force_writable. On Virtual
// A/B devices, target partitions may overlap with source partitions, so
// they must be mapped with snapshot.
+ // One exception is when /metadata is not mounted. Fallback to
+ // CreateLogicalPartition as snapshots are not created in the first place.
params.timeout_ms = kMapSnapshotTimeout;
success = snapshot_->MapUpdateSnapshot(params, path);
} else {
@@ -232,8 +234,11 @@
// On a Virtual A/B device, |target_partition_name| may be a leftover from
// a paused update. Clean up any underlying devices.
- if (GetVirtualAbFeatureFlag().IsEnabled()) {
+ if (ExpectMetadataMounted()) {
success &= snapshot_->UnmapUpdateSnapshot(target_partition_name);
+ } else {
+ LOG(INFO) << "Skip UnmapUpdateSnapshot(" << target_partition_name
+ << ") because metadata is not mounted";
}
if (!success) {
@@ -405,10 +410,10 @@
<< "run adb enable-verity to deactivate if required and try again.";
}
- if (GetVirtualAbFeatureFlag().IsEnabled() && metadata_device_ == nullptr) {
- metadata_device_ = snapshot_->EnsureMetadataMounted();
- TEST_AND_RETURN_FALSE(metadata_device_ != nullptr);
- }
+ // If metadata is erased but not formatted, it is possible to not mount
+ // it in recovery. It is acceptable to skip mounting and choose fallback path
+ // (PrepareDynamicPartitionsForUpdate) when sideloading full OTAs.
+ TEST_AND_RETURN_FALSE(EnsureMetadataMounted() || IsRecovery());
if (update) {
TEST_AND_RETURN_FALSE(EraseSystemOtherAvbFooter(source_slot, target_slot));
@@ -469,9 +474,18 @@
<< "snapshots.";
}
- if (!snapshot_->CancelUpdate()) {
- LOG(ERROR) << "Cannot cancel previous update.";
- return false;
+ // In recovery, if /metadata is not mounted, it is likely that metadata
+ // partition is erased and not formatted yet. After sideloading, when
+ // rebooting into the new version, init will erase metadata partition,
+ // hence the failure of CancelUpdate() can be ignored here.
+ // However, if metadata is mounted and CancelUpdate fails, sideloading
+ // should not proceed because during next boot, snapshots will overlay on
+ // the devices incorrectly.
+ if (ExpectMetadataMounted()) {
+ TEST_AND_RETURN_FALSE(snapshot_->CancelUpdate());
+ } else {
+ LOG(INFO) << "Skip canceling previous update because metadata is not "
+ << "mounted";
}
}
@@ -632,6 +646,9 @@
// Delete any pre-existing device with name |partition_name_suffix| and
// also remove it from |mapped_devices_|.
+ // In recovery, metadata might not be mounted, and
+ // UnmapPartitionOnDeviceMapper might fail. However,
+ // it is unusual that system_other has already been mapped. Hence, just skip.
TEST_AND_RETURN_FALSE(UnmapPartitionOnDeviceMapper(partition_name_suffix));
// Use CreateLogicalPartition directly to avoid mapping with existing
// snapshots.
@@ -668,6 +685,10 @@
// Delete |partition_name_suffix| from device mapper and from
// |mapped_devices_| again so that it does not interfere with update process.
+ // In recovery, metadata might not be mounted, and
+ // UnmapPartitionOnDeviceMapper might fail. However, DestroyLogicalPartition
+ // should be called. If DestroyLogicalPartition does fail, it is still okay
+ // to skip the error here and let Prepare*() fail later.
if (should_unmap) {
TEST_AND_RETURN_FALSE(UnmapPartitionOnDeviceMapper(partition_name_suffix));
}
@@ -726,6 +747,7 @@
uint32_t target_slot,
const DeltaArchiveManifest& manifest,
uint64_t* required_size) {
+ TEST_AND_RETURN_FALSE(ExpectMetadataMounted());
if (!snapshot_->BeginUpdate()) {
LOG(ERROR) << "Cannot begin new update.";
return false;
@@ -829,10 +851,14 @@
}
bool DynamicPartitionControlAndroid::FinishUpdate(bool powerwash_required) {
- if (GetVirtualAbFeatureFlag().IsEnabled() &&
- snapshot_->GetUpdateState() == UpdateState::Initiated) {
- LOG(INFO) << "Snapshot writes are done.";
- return snapshot_->FinishedSnapshotWrites(powerwash_required);
+ if (ExpectMetadataMounted()) {
+ if (snapshot_->GetUpdateState() == UpdateState::Initiated) {
+ LOG(INFO) << "Snapshot writes are done.";
+ return snapshot_->FinishedSnapshotWrites(powerwash_required);
+ }
+ } else {
+ LOG(INFO) << "Skip FinishedSnapshotWrites() because /metadata is not "
+ << "mounted";
}
return true;
}
@@ -1006,9 +1032,41 @@
TEST_AND_RETURN_FALSE(DeltaPerformer::ResetUpdateProgress(
prefs, false /* quick */, false /* skip dynamic partitions metadata */));
- TEST_AND_RETURN_FALSE(snapshot_->CancelUpdate());
+ if (ExpectMetadataMounted()) {
+ TEST_AND_RETURN_FALSE(snapshot_->CancelUpdate());
+ } else {
+ LOG(INFO) << "Skip cancelling update in ResetUpdate because /metadata is "
+ << "not mounted";
+ }
return true;
}
+bool DynamicPartitionControlAndroid::ExpectMetadataMounted() {
+ // No need to mount metadata for non-Virtual A/B devices.
+ if (!GetVirtualAbFeatureFlag().IsEnabled()) {
+ return false;
+ }
+ // Intentionally not checking |metadata_device_| in Android mode.
+ // /metadata should always be mounted in Android mode. If it isn't, let caller
+ // fails when calling into SnapshotManager.
+ if (!IsRecovery()) {
+ return true;
+ }
+ // In recovery mode, explicitly check |metadata_device_|.
+ return metadata_device_ != nullptr;
+}
+
+bool DynamicPartitionControlAndroid::EnsureMetadataMounted() {
+ // No need to mount metadata for non-Virtual A/B devices.
+ if (!GetVirtualAbFeatureFlag().IsEnabled()) {
+ return true;
+ }
+
+ if (metadata_device_ == nullptr) {
+ metadata_device_ = snapshot_->EnsureMetadataMounted();
+ }
+ return metadata_device_ != nullptr;
+}
+
} // namespace chromeos_update_engine