Merge "Allow to skip mounting metadata in recovery." into rvc-dev
diff --git a/cleanup_previous_update_action.cc b/cleanup_previous_update_action.cc
index ee68947..26cc6be 100644
--- a/cleanup_previous_update_action.cc
+++ b/cleanup_previous_update_action.cc
@@ -160,7 +160,10 @@
 
   if (metadata_device_ == nullptr) {
     LOG(ERROR) << "Failed to mount /metadata.";
-    processor_->ActionComplete(this, ErrorCode::kError);
+    // If metadata is erased but not formatted, it is possible to not mount
+    // it in recovery. It is safe to skip CleanupPreviousUpdateAction.
+    processor_->ActionComplete(
+        this, kIsRecovery ? ErrorCode::kSuccess : ErrorCode::kError);
     return;
   }
 
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
diff --git a/dynamic_partition_control_android.h b/dynamic_partition_control_android.h
index 9dcdcf1..8ad7593 100644
--- a/dynamic_partition_control_android.h
+++ b/dynamic_partition_control_android.h
@@ -233,6 +233,28 @@
                               uint32_t source_slot,
                               const DeltaArchiveManifest& manifest);
 
+  // Returns true if metadata is expected to be mounted, false otherwise.
+  // Note that it returns false on non-Virtual A/B devices.
+  //
+  // Almost all functions of SnapshotManager depends on metadata being mounted.
+  // - In Android mode for Virtual A/B devices, assume it is mounted. If not,
+  //   let caller fails when calling into SnapshotManager.
+  // - In recovery for Virtual A/B devices, it is possible that metadata is not
+  //   formatted, hence it cannot be mounted. Caller should not call into
+  //   SnapshotManager.
+  // - On non-Virtual A/B devices, updates do not depend on metadata partition.
+  //   Caller should not call into SnapshotManager.
+  //
+  // This function does NOT mount metadata partition. Use EnsureMetadataMounted
+  // to mount metadata partition.
+  bool ExpectMetadataMounted();
+
+  // Ensure /metadata is mounted. Returns true if successful, false otherwise.
+  //
+  // Note that this function returns true on non-Virtual A/B devices without
+  // doing anything.
+  bool EnsureMetadataMounted();
+
   std::set<std::string> mapped_devices_;
   const FeatureFlag dynamic_partitions_;
   const FeatureFlag virtual_ab_;