Re-map source partitions with RW permission during OTA

Test: th
Bug: 351928254
Change-Id: Ia58b62f038b3bd0d73b429f761f4ff05ab93e072
diff --git a/aosp/dynamic_partition_control_android.cc b/aosp/dynamic_partition_control_android.cc
index 78cda20..363a433 100644
--- a/aosp/dynamic_partition_control_android.cc
+++ b/aosp/dynamic_partition_control_android.cc
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <chrono>  // NOLINT(build/c++11) - using libsnapshot / liblp API
 #include <cstdint>
+#include <iterator>
 #include <map>
 #include <memory>
 #include <set>
@@ -100,7 +101,11 @@
 constexpr std::chrono::milliseconds kMapSnapshotTimeout{10000};
 
 DynamicPartitionControlAndroid::~DynamicPartitionControlAndroid() {
-  UnmapAllPartitions();
+  std::set<std::string> mapped = mapped_devices_;
+  LOG(INFO) << "Destroying [" << Join(mapped, ", ") << "] from device mapper";
+  for (const auto& device_name : mapped) {
+    ignore_result(UnmapPartitionOnDeviceMapper(device_name));
+  }
   metadata_device_.reset();
 }
 
@@ -184,18 +189,34 @@
   return false;
 }
 
+constexpr auto&& kRWSourcePartitionSuffix = "_ota";
+std::string DynamicPartitionControlAndroid::GetDeviceName(
+    std::string partition_name, uint32_t slot) const {
+  if (partition_name.ends_with(kRWSourcePartitionSuffix)) {
+    return partition_name;
+  }
+  if (!partition_name.ends_with("_a") && !partition_name.ends_with("_b")) {
+    partition_name += slot ? "_b" : "_a";
+  }
+  if (slot == source_slot_) {
+    return partition_name + kRWSourcePartitionSuffix;
+  }
+  return partition_name;
+}
+
 bool DynamicPartitionControlAndroid::MapPartitionInternal(
     const std::string& super_device,
     const std::string& target_partition_name,
     uint32_t slot,
     bool force_writable,
     std::string* path) {
+  auto device_name = GetDeviceName(target_partition_name, slot);
   CreateLogicalPartitionParams params = {
       .block_device = super_device,
       .metadata_slot = slot,
       .partition_name = target_partition_name,
       .force_writable = force_writable,
-  };
+      .device_name = device_name};
   bool success = false;
   if (GetVirtualAbFeatureFlag().IsEnabled() && target_supports_snapshot_ &&
       slot != source_slot_ && force_writable && ExpectMetadataMounted()) {
@@ -219,7 +240,7 @@
   LOG(INFO) << "Succesfully mapped " << target_partition_name
             << " to device mapper (force_writable = " << force_writable
             << "); device path at " << *path;
-  mapped_devices_.insert(target_partition_name);
+  mapped_devices_.insert(params.device_name);
   return true;
 }
 
@@ -229,9 +250,10 @@
     uint32_t slot,
     bool force_writable,
     std::string* path) {
-  DmDeviceState state = GetState(target_partition_name);
+  auto device_name = GetDeviceName(target_partition_name, slot);
+  DmDeviceState state = GetState(device_name);
   if (state == DmDeviceState::ACTIVE) {
-    if (mapped_devices_.find(target_partition_name) != mapped_devices_.end()) {
+    if (mapped_devices_.find(device_name) != mapped_devices_.end()) {
       if (GetDmDevicePathByName(target_partition_name, path)) {
         LOG(INFO) << target_partition_name
                   << " is mapped on device mapper: " << *path;
@@ -245,12 +267,13 @@
     // Note that for source partitions, if GetState() == ACTIVE, callers (e.g.
     // BootControlAndroid) should not call MapPartitionOnDeviceMapper, but
     // should directly call GetDmDevicePathByName.
-    if (!UnmapPartitionOnDeviceMapper(target_partition_name)) {
+    LOG(INFO) << "Destroying `" << device_name << "` from device mapper";
+    if (!UnmapPartitionOnDeviceMapper(device_name)) {
       LOG(ERROR) << target_partition_name
                  << " is mapped before the update, and it cannot be unmapped.";
       return false;
     }
-    state = GetState(target_partition_name);
+    state = GetState(device_name);
     if (state != DmDeviceState::INVALID) {
       LOG(ERROR) << target_partition_name << " is unmapped but state is "
                  << static_cast<std::underlying_type_t<DmDeviceState>>(state);
@@ -270,32 +293,38 @@
 
 bool DynamicPartitionControlAndroid::UnmapPartitionOnDeviceMapper(
     const std::string& target_partition_name) {
-  if (DeviceMapper::Instance().GetState(target_partition_name) !=
+  auto device_name = target_partition_name;
+  if (target_partition_name.ends_with("_a") ||
+      target_partition_name.ends_with("_b")) {
+    auto slot = target_partition_name.ends_with("_a") ? 0 : 1;
+    device_name = GetDeviceName(target_partition_name, slot);
+    return false;
+  }
+  if (DeviceMapper::Instance().GetState(device_name) !=
       DmDeviceState::INVALID) {
     // Partitions at target slot on non-Virtual A/B devices are mapped as
     // dm-linear. Also, on Virtual A/B devices, system_other may be mapped for
     // preopt apps as dm-linear.
     // Call DestroyLogicalPartition to handle these cases.
-    bool success = DestroyLogicalPartition(target_partition_name);
+    bool success = DestroyLogicalPartition(device_name);
 
     // On a Virtual A/B device, |target_partition_name| may be a leftover from
     // a paused update. Clean up any underlying devices.
-    if (ExpectMetadataMounted()) {
-      success &= snapshot_->UnmapUpdateSnapshot(target_partition_name);
+    if (ExpectMetadataMounted() &&
+        !device_name.ends_with(kRWSourcePartitionSuffix)) {
+      success &= snapshot_->UnmapUpdateSnapshot(device_name);
     } else {
-      LOG(INFO) << "Skip UnmapUpdateSnapshot(" << target_partition_name
-                << ") because metadata is not mounted";
+      LOG(INFO) << "Skip UnmapUpdateSnapshot(" << device_name << ")";
     }
 
     if (!success) {
-      LOG(ERROR) << "Cannot unmap " << target_partition_name
-                 << " from device mapper.";
+      LOG(ERROR) << "Cannot unmap " << device_name << " from device mapper.";
       return false;
     }
-    LOG(INFO) << "Successfully unmapped " << target_partition_name
+    LOG(INFO) << "Successfully unmapped " << device_name
               << " from device mapper.";
   }
-  mapped_devices_.erase(target_partition_name);
+  mapped_devices_.erase(device_name);
   return true;
 }
 
@@ -306,16 +335,27 @@
   }
   // UnmapPartitionOnDeviceMapper removes objects from mapped_devices_, hence
   // a copy is needed for the loop.
-  std::set<std::string> mapped = mapped_devices_;
+  std::set<std::string> mapped;
+  std::copy_if(mapped_devices_.begin(),
+               mapped_devices_.end(),
+               std::inserter(mapped, mapped.end()),
+               [](auto&& device_name) {
+                 return !std::string_view(device_name)
+                             .ends_with(kRWSourcePartitionSuffix);
+               });
   LOG(INFO) << "Destroying [" << Join(mapped, ", ") << "] from device mapper";
-  for (const auto& partition_name : mapped) {
-    ignore_result(UnmapPartitionOnDeviceMapper(partition_name));
+  for (const auto& device_name : mapped) {
+    ignore_result(UnmapPartitionOnDeviceMapper(device_name));
   }
   return true;
 }
 
 void DynamicPartitionControlAndroid::Cleanup() {
-  UnmapAllPartitions();
+  std::set<std::string> mapped = mapped_devices_;
+  LOG(INFO) << "Destroying [" << Join(mapped, ", ") << "] from device mapper";
+  for (const auto& device_name : mapped) {
+    ignore_result(UnmapPartitionOnDeviceMapper(device_name));
+  }
   LOG(INFO) << "UnmapAllPartitions done";
   metadata_device_.reset();
   if (GetVirtualAbFeatureFlag().IsEnabled()) {
@@ -772,6 +812,8 @@
   // 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.
+  LOG(INFO) << "Destroying `" << partition_name_suffix
+            << "` from device mapper";
   TEST_AND_RETURN_FALSE(UnmapPartitionOnDeviceMapper(partition_name_suffix));
   // Use CreateLogicalPartition directly to avoid mapping with existing
   // snapshots.
@@ -813,6 +855,8 @@
   // should be called. If DestroyLogicalPartition does fail, it is still okay
   // to skip the error here and let Prepare*() fail later.
   if (should_unmap) {
+    LOG(INFO) << "Destroying `" << partition_name_suffix
+              << "` from device mapper";
     TEST_AND_RETURN_FALSE(UnmapPartitionOnDeviceMapper(partition_name_suffix));
   }
 
@@ -1161,12 +1205,13 @@
   std::string device;
   if (GetDynamicPartitionsFeatureFlag().IsEnabled() &&
       (slot == current_slot || is_target_dynamic_)) {
-    switch (GetDynamicPartitionDevice(device_dir,
-                                      partition_name_suffix,
-                                      slot,
-                                      current_slot,
-                                      not_in_payload,
-                                      &device)) {
+    auto status = GetDynamicPartitionDevice(device_dir,
+                                            partition_name_suffix,
+                                            slot,
+                                            current_slot,
+                                            not_in_payload,
+                                            &device);
+    switch (status) {
       case DynamicPartitionDeviceStatus::SUCCESS:
         return {{.rw_device_path = device,
                  .readonly_device_path = device,
@@ -1176,6 +1221,7 @@
         break;
       case DynamicPartitionDeviceStatus::ERROR:  // fallthrough
       default:
+        LOG(ERROR) << "Unhandled dynamic partition status " << (int)status;
         return {};
     }
   }
@@ -1211,6 +1257,7 @@
     std::string* device) {
   std::string super_device =
       device_dir.Append(GetSuperPartitionName(slot)).value();
+  auto device_name = GetDeviceName(partition_name_suffix, slot);
 
   auto builder = LoadMetadataBuilder(super_device, slot);
   if (builder == nullptr) {
@@ -1233,13 +1280,12 @@
   }
 
   if (slot == current_slot) {
-    if (GetState(partition_name_suffix) != DmDeviceState::ACTIVE) {
-      LOG(WARNING) << partition_name_suffix << " is at current slot but it is "
+    if (GetState(device_name) != DmDeviceState::ACTIVE) {
+      LOG(WARNING) << device_name << " is at current slot but it is "
                    << "not mapped. Now try to map it.";
     } else {
-      if (GetDmDevicePathByName(partition_name_suffix, device)) {
-        LOG(INFO) << partition_name_suffix
-                  << " is mapped on device mapper: " << *device;
+      if (GetDmDevicePathByName(device_name, device)) {
+        LOG(INFO) << device_name << " is mapped on device mapper: " << *device;
         return DynamicPartitionDeviceStatus::SUCCESS;
       }
       LOG(ERROR) << partition_name_suffix << "is mapped but path is unknown.";
diff --git a/aosp/dynamic_partition_control_android.h b/aosp/dynamic_partition_control_android.h
index 176cf50..1f70184 100644
--- a/aosp/dynamic_partition_control_android.h
+++ b/aosp/dynamic_partition_control_android.h
@@ -338,6 +338,8 @@
   // target_supports_snapshot_ and is_target_dynamic_.
   bool SetTargetBuildVars(const DeltaArchiveManifest& manifest);
 
+  std::string GetDeviceName(std::string partition_name, uint32_t slot) const;
+
   std::set<std::string> mapped_devices_;
   const FeatureFlag dynamic_partitions_;
   const FeatureFlag virtual_ab_;
diff --git a/aosp/dynamic_partition_control_android_unittest.cc b/aosp/dynamic_partition_control_android_unittest.cc
index c93d4c3..6822394 100644
--- a/aosp/dynamic_partition_control_android_unittest.cc
+++ b/aosp/dynamic_partition_control_android_unittest.cc
@@ -16,9 +16,7 @@
 
 #include "update_engine/aosp/dynamic_partition_control_android.h"
 
-#include <algorithm>
 #include <set>
-#include <vector>
 
 #include <base/logging.h>
 #include <gmock/gmock.h>
@@ -26,6 +24,7 @@
 #include <libavb/libavb.h>
 #include <libsnapshot/mock_snapshot.h>
 
+#include "update_engine/aosp/boot_control_android.h"
 #include "update_engine/aosp/dynamic_partition_test_utils.h"
 #include "update_engine/aosp/mock_dynamic_partition_control_android.h"
 #include "update_engine/common/mock_prefs.h"
@@ -212,7 +211,7 @@
   }
 
   std::unique_ptr<DynamicPartitionControlAndroid> module_;
-  TestParam slots_;
+  TestParam slots_{};
 };
 
 class DynamicPartitionControlAndroidTestP
@@ -240,7 +239,7 @@
                                 {T("system"), 3_GiB},
                                 {T("vendor"), 1_GiB}};
   PartitionSizes update_metadata{{"system", 3_GiB}, {"vendor", 1_GiB}};
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       UpdatePartitionMetadata(source_metadata, update_metadata, expected));
 }
 
@@ -257,7 +256,7 @@
                                 {T("system"), 2_GiB},
                                 {T("vendor"), 150_MiB}};
   PartitionSizes update_metadata{{"system", 2_GiB}, {"vendor", 150_MiB}};
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       UpdatePartitionMetadata(source_metadata, update_metadata, expected));
 }
 
@@ -266,7 +265,7 @@
   PartitionSuffixSizes source_metadata{};
   PartitionSuffixSizes expected{{T("system"), 2_GiB}, {T("vendor"), 1_GiB}};
   PartitionSizes update_metadata{{"system", 2_GiB}, {"vendor", 1_GiB}};
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       UpdatePartitionMetadata(source_metadata, update_metadata, expected));
 }
 
@@ -277,7 +276,7 @@
   PartitionSuffixSizes expected{
       {S("system"), 2_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}};
   PartitionSizes update_metadata{{"system", 2_GiB}, {"vendor", 1_GiB}};
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       UpdatePartitionMetadata(source_metadata, update_metadata, expected));
 }
 
@@ -291,7 +290,7 @@
   PartitionSuffixSizes expected{
       {S("system"), 2_GiB}, {S("vendor"), 1_GiB}, {T("system"), 2_GiB}};
   PartitionSizes update_metadata{{"system", 2_GiB}};
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       UpdatePartitionMetadata(source_metadata, update_metadata, expected));
 }
 
@@ -303,7 +302,7 @@
                                        {T("vendor"), 1_GiB}};
   PartitionSuffixSizes expected{{S("system"), 2_GiB}, {S("vendor"), 1_GiB}};
   PartitionSizes update_metadata{};
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       UpdatePartitionMetadata(source_metadata, update_metadata, expected));
 }
 
@@ -314,7 +313,7 @@
       .WillOnce(Invoke([](auto, auto, auto) { return nullptr; }));
   ExpectUnmap({T("system")});
 
-  EXPECT_FALSE(PreparePartitionsForUpdate({{"system", 1_GiB}}))
+  ASSERT_FALSE(PreparePartitionsForUpdate({{"system", 1_GiB}}))
       << "Should not be able to continue with corrupt source metadata";
 }
 
@@ -327,7 +326,7 @@
                                        {T("vendor"), 0}};
   PartitionSizes update_metadata{{"system", 3_GiB}, {"vendor", 3_GiB}};
 
-  EXPECT_FALSE(UpdatePartitionMetadata(source_metadata, update_metadata, {}))
+  ASSERT_FALSE(UpdatePartitionMetadata(source_metadata, update_metadata, {}))
       << "Should not be able to fit 11GiB data into 10GiB space";
 }
 
@@ -337,7 +336,7 @@
                                        {T("system"), 0},
                                        {T("vendor"), 0}};
   PartitionSizes update_metadata{{"system", 3_GiB}, {"vendor", 3_GiB}};
-  EXPECT_FALSE(UpdatePartitionMetadata(source_metadata, update_metadata, {}))
+  ASSERT_FALSE(UpdatePartitionMetadata(source_metadata, update_metadata, {}))
       << "Should not be able to grow over size of super / 2";
 }
 
@@ -362,35 +361,35 @@
   // Not calling through
   // DynamicPartitionControlAndroidTest::PreparePartitionsForUpdate(), since we
   // don't want any default group in the PartitionMetadata.
-  EXPECT_TRUE(dynamicControl().PreparePartitionsForUpdate(
+  ASSERT_TRUE(dynamicControl().PreparePartitionsForUpdate(
       source(), target(), {}, true, nullptr, nullptr));
 
   // Should use dynamic source partitions.
-  EXPECT_CALL(dynamicControl(), GetState(S("system")))
+  EXPECT_CALL(dynamicControl(), GetState(S("system") + "_ota"))
       .Times(1)
       .WillOnce(Return(DmDeviceState::ACTIVE));
   string system_device;
-  EXPECT_TRUE(dynamicControl().GetPartitionDevice(
+  ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "system", source(), source(), &system_device));
-  EXPECT_EQ(GetDmDevice(S("system")), system_device);
+  ASSERT_EQ(GetDmDevice(S("system") + "_ota"), system_device);
 
   // Should use static target partitions without querying dynamic control.
   EXPECT_CALL(dynamicControl(), GetState(T("system"))).Times(0);
-  EXPECT_TRUE(dynamicControl().GetPartitionDevice(
+  ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "system", target(), source(), &system_device));
-  EXPECT_EQ(GetDevice(T("system")), system_device);
+  ASSERT_EQ(GetDevice(T("system")), system_device);
 
   // Static partition "bar".
   EXPECT_CALL(dynamicControl(), GetState(S("bar"))).Times(0);
   std::string bar_device;
-  EXPECT_TRUE(dynamicControl().GetPartitionDevice(
+  ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "bar", source(), source(), &bar_device));
-  EXPECT_EQ(GetDevice(S("bar")), bar_device);
+  ASSERT_EQ(GetDevice(S("bar")), bar_device);
 
   EXPECT_CALL(dynamicControl(), GetState(T("bar"))).Times(0);
-  EXPECT_TRUE(dynamicControl().GetPartitionDevice(
+  ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "bar", target(), source(), &bar_device));
-  EXPECT_EQ(GetDevice(T("bar")), bar_device);
+  ASSERT_EQ(GetDevice(T("bar")), bar_device);
 }
 
 TEST_P(DynamicPartitionControlAndroidTestP, GetMountableDevicePath) {
@@ -411,9 +410,9 @@
                                  GetDevice(S("system")),
                                  GetDevice(T("system")))))
       .WillRepeatedly(Return(true));
-  EXPECT_CALL(
-      dynamicControl(),
-      GetState(AnyOf(S("vendor"), T("vendor"), S("system"), T("system"))))
+  EXPECT_CALL(dynamicControl(),
+              GetState(AnyOf(
+                  S("vendor"), T("vendor"), S("system") + "_ota", T("system"))))
       .WillRepeatedly(Return(DmDeviceState::ACTIVE));
 
   SetMetadata(source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}});
@@ -421,7 +420,7 @@
   std::string device;
   ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "system", source(), source(), &device));
-  ASSERT_EQ(GetDmDevice(S("system")), device);
+  ASSERT_EQ(GetDmDevice(S("system") + "_ota"), device);
 
   ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "system", target(), source(), &device));
@@ -453,9 +452,9 @@
                                  GetDevice(S("system")),
                                  GetDevice(T("system")))))
       .WillRepeatedly(Return(true));
-  EXPECT_CALL(
-      dynamicControl(),
-      GetState(AnyOf(S("vendor"), T("vendor"), S("system"), T("system"))))
+  EXPECT_CALL(dynamicControl(),
+              GetState(AnyOf(
+                  S("vendor"), T("vendor"), S("system") + "_ota", T("system"))))
       .WillRepeatedly(Return(DmDeviceState::ACTIVE));
 
   SetMetadata(source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}});
@@ -464,7 +463,7 @@
   std::string device;
   ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "system", source(), source(), &device));
-  ASSERT_EQ(GetDmDevice(S("system")), device);
+  ASSERT_EQ(GetDmDevice(S("system") + "_ota"), device);
 
   ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "system", target(), source(), &device));
@@ -499,7 +498,7 @@
                {T("system"), 2_GiB},
                {T("vendor"), 1_GiB}});
 
-  EXPECT_TRUE(dynamicControl().PreparePartitionsForUpdate(
+  ASSERT_TRUE(dynamicControl().PreparePartitionsForUpdate(
       source(),
       target(),
       PartitionSizesToManifest({{"system", 2_GiB}, {"vendor", 1_GiB}}),
@@ -508,13 +507,13 @@
       nullptr));
 
   // Dynamic partition "system".
-  EXPECT_CALL(dynamicControl(), GetState(S("system")))
+  EXPECT_CALL(dynamicControl(), GetState(S("system") + "_ota"))
       .Times(1)
       .WillOnce(Return(DmDeviceState::ACTIVE));
   string system_device;
-  EXPECT_TRUE(dynamicControl().GetPartitionDevice(
+  ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "system", source(), source(), &system_device));
-  EXPECT_EQ(GetDmDevice(S("system")), system_device);
+  ASSERT_EQ(GetDmDevice(S("system") + "_ota"), system_device);
 
   EXPECT_CALL(dynamicControl(), GetState(T("system")))
       .Times(AnyNumber())
@@ -528,21 +527,21 @@
             *device = "/fake/remapped/" + name;
             return true;
           }));
-  EXPECT_TRUE(dynamicControl().GetPartitionDevice(
+  ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "system", target(), source(), &system_device));
-  EXPECT_EQ("/fake/remapped/" + T("system"), system_device);
+  ASSERT_EQ("/fake/remapped/" + T("system"), system_device);
 
   // Static partition "bar".
   EXPECT_CALL(dynamicControl(), GetState(S("bar"))).Times(0);
   std::string bar_device;
-  EXPECT_TRUE(dynamicControl().GetPartitionDevice(
+  ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "bar", source(), source(), &bar_device));
-  EXPECT_EQ(GetDevice(S("bar")), bar_device);
+  ASSERT_EQ(GetDevice(S("bar")), bar_device);
 
   EXPECT_CALL(dynamicControl(), GetState(T("bar"))).Times(0);
-  EXPECT_TRUE(dynamicControl().GetPartitionDevice(
+  ASSERT_TRUE(dynamicControl().GetPartitionDevice(
       "bar", target(), source(), &bar_device));
-  EXPECT_EQ(GetDevice(T("bar")), bar_device);
+  ASSERT_EQ(GetDevice(T("bar")), bar_device);
 }
 
 INSTANTIATE_TEST_CASE_P(DynamicPartitionControlAndroidTest,
@@ -582,7 +581,7 @@
   AddGroupAndPartition(&update_manifest, "android", 3_GiB, "system", 3_GiB);
   AddGroupAndPartition(&update_manifest, "oem", 2_GiB, "vendor", 2_GiB);
 
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       UpdatePartitionMetadata(source_manifest, update_manifest, expected));
 }
 
@@ -590,7 +589,7 @@
   DeltaArchiveManifest update_manifest;
   AddGroupAndPartition(&update_manifest, "android", 3_GiB, "system", 1_GiB),
       AddGroupAndPartition(&update_manifest, "oem", 2_GiB, "vendor", 3_GiB);
-  EXPECT_FALSE(UpdatePartitionMetadata(source_manifest, update_manifest, {}))
+  ASSERT_FALSE(UpdatePartitionMetadata(source_manifest, update_manifest, {}))
       << "Should not be able to grow over maximum size of group";
 }
 
@@ -598,7 +597,7 @@
   DeltaArchiveManifest update_manifest;
   AddGroup(&update_manifest, "android", 3_GiB);
   AddGroup(&update_manifest, "oem", 3_GiB);
-  EXPECT_FALSE(UpdatePartitionMetadata(source_manifest, update_manifest, {}))
+  ASSERT_FALSE(UpdatePartitionMetadata(source_manifest, update_manifest, {}))
       << "Should not be able to grow over size of super / 2";
 }
 
@@ -614,7 +613,7 @@
   AddPartition(&update_manifest, g, "system_ext", 1_GiB);
   AddGroupAndPartition(&update_manifest, "oem", 2_GiB, "vendor", 2_GiB);
 
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       UpdatePartitionMetadata(source_manifest, update_manifest, expected));
 }
 
@@ -626,7 +625,7 @@
   AddGroup(&update_manifest, "android", 3_GiB);
   AddGroupAndPartition(&update_manifest, "oem", 2_GiB, "vendor", 2_GiB);
 
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       UpdatePartitionMetadata(source_manifest, update_manifest, expected));
 }
 
@@ -640,7 +639,7 @@
   AddGroupAndPartition(&update_manifest, "oem", 1_GiB, "vendor", 1_GiB);
   AddGroupAndPartition(
       &update_manifest, "new_group", 2_GiB, "new_partition", 2_GiB);
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       UpdatePartitionMetadata(source_manifest, update_manifest, expected));
 }
 
@@ -648,7 +647,7 @@
   DeltaArchiveManifest update_manifest;
   AddGroupAndPartition(&update_manifest, "android", 2_GiB, "system", 2_GiB);
 
-  EXPECT_TRUE(UpdatePartitionMetadata(
+  ASSERT_TRUE(UpdatePartitionMetadata(
       source_manifest, update_manifest, Not(HasGroup(T("oem")))));
 }
 
@@ -659,7 +658,7 @@
   DeltaArchiveManifest update_manifest;
   AddGroupAndPartition(&update_manifest, "android", 2_GiB, "system", 2_GiB),
       AddGroupAndPartition(&update_manifest, "oem", 3_GiB, "vendor", 3_GiB);
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       UpdatePartitionMetadata(source_manifest, update_manifest, expected));
 }
 
@@ -725,7 +724,7 @@
   ExpectStoreMetadata(update_sizes_1());
   ExpectUnmap({"grown_b", "shrunk_b", "same_b", "added_b"});
 
-  EXPECT_TRUE(PreparePartitionsForUpdate({{"grown", 3_GiB},
+  ASSERT_TRUE(PreparePartitionsForUpdate({{"grown", 3_GiB},
                                           {"shrunk", 150_MiB},
                                           {"same", 100_MiB},
                                           {"added", 150_MiB}}));
@@ -742,7 +741,7 @@
   ExpectStoreMetadata(update_sizes_2());
   ExpectUnmap({"grown_a", "shrunk_a", "same_a", "deleted_a"});
 
-  EXPECT_TRUE(PreparePartitionsForUpdate({{"grown", 4_GiB},
+  ASSERT_TRUE(PreparePartitionsForUpdate({{"grown", 4_GiB},
                                           {"shrunk", 100_MiB},
                                           {"same", 100_MiB},
                                           {"deleted", 64_MiB}}));
@@ -750,7 +749,7 @@
 
 TEST_F(DynamicPartitionControlAndroidTest, ApplyingToCurrentSlot) {
   SetSlots({1, 1});
-  EXPECT_FALSE(PreparePartitionsForUpdate({}))
+  ASSERT_FALSE(PreparePartitionsForUpdate({}))
       << "Should not be able to apply to current slot.";
 }
 
@@ -766,16 +765,16 @@
 
   InstallOperation iop;
   InstallOperation optimized;
-  Extent *se, *de;
+  Extent *se{}, *de{};
 
   // Not a SOURCE_COPY operation, cannot skip.
   iop.set_type(InstallOperation::REPLACE);
-  EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
+  ASSERT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
 
   iop.set_type(InstallOperation::SOURCE_COPY);
 
   // By default GetVirtualAbFeatureFlag is disabled. Cannot skip operation.
-  EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
+  ASSERT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
 
   // Enable GetVirtualAbFeatureFlag in the mock interface.
   ON_CALL(dynamicControl(), GetVirtualAbFeatureFlag())
@@ -783,21 +782,21 @@
 
   // By default target_supports_snapshot_ is set to false. Cannot skip
   // operation.
-  EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
+  ASSERT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
 
   SetSnapshotEnabled(true);
 
   // Empty source and destination. Skip.
-  EXPECT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
-  EXPECT_TRUE(optimized.src_extents().empty());
-  EXPECT_TRUE(optimized.dst_extents().empty());
+  ASSERT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
+  ASSERT_TRUE(optimized.src_extents().empty());
+  ASSERT_TRUE(optimized.dst_extents().empty());
 
   se = iop.add_src_extents();
   se->set_start_block(0);
   se->set_num_blocks(1);
 
   // There is something in sources, but destinations are empty. Cannot skip.
-  EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
+  ASSERT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
 
   InstallOperation iop2;
 
@@ -806,48 +805,48 @@
   de->set_num_blocks(1);
 
   // There is something in destinations, but sources are empty. Cannot skip.
-  EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop2, &optimized));
+  ASSERT_FALSE(dynamicControl().OptimizeOperation("foo", iop2, &optimized));
 
   de = iop.add_dst_extents();
   de->set_start_block(0);
   de->set_num_blocks(1);
 
   // Sources and destinations are identical. Skip.
-  EXPECT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
-  EXPECT_TRUE(optimized.src_extents().empty());
-  EXPECT_TRUE(optimized.dst_extents().empty());
+  ASSERT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
+  ASSERT_TRUE(optimized.src_extents().empty());
+  ASSERT_TRUE(optimized.dst_extents().empty());
 
   se = iop.add_src_extents();
   se->set_start_block(1);
   se->set_num_blocks(5);
 
   // There is something in source, but not in destination. Cannot skip.
-  EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
+  ASSERT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
 
   de = iop.add_dst_extents();
   de->set_start_block(1);
   de->set_num_blocks(5);
 
   // There is source and destination are equal. Skip.
-  EXPECT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
-  EXPECT_TRUE(optimized.src_extents().empty());
-  EXPECT_TRUE(optimized.dst_extents().empty());
+  ASSERT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
+  ASSERT_TRUE(optimized.src_extents().empty());
+  ASSERT_TRUE(optimized.dst_extents().empty());
 
   de = iop.add_dst_extents();
   de->set_start_block(6);
   de->set_num_blocks(5);
 
   // There is something extra in dest. Cannot skip.
-  EXPECT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
+  ASSERT_FALSE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
 
   se = iop.add_src_extents();
   se->set_start_block(6);
   se->set_num_blocks(5);
 
   // Source and dest are identical again. Skip.
-  EXPECT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
-  EXPECT_TRUE(optimized.src_extents().empty());
-  EXPECT_TRUE(optimized.dst_extents().empty());
+  ASSERT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
+  ASSERT_TRUE(optimized.src_extents().empty());
+  ASSERT_TRUE(optimized.dst_extents().empty());
 
   iop.Clear();
   iop.set_type(InstallOperation::SOURCE_COPY);
@@ -865,20 +864,20 @@
   de->set_num_blocks(5);
 
   // [1, 3, 4, 7, 8] -> [2, 3, 4, 5, 6] should return [1, 7, 8] -> [2, 5, 6]
-  EXPECT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
+  ASSERT_TRUE(dynamicControl().OptimizeOperation("foo", iop, &optimized));
   ASSERT_EQ(2, optimized.src_extents_size());
   ASSERT_EQ(2, optimized.dst_extents_size());
-  EXPECT_EQ(1u, optimized.src_extents(0).start_block());
-  EXPECT_EQ(1u, optimized.src_extents(0).num_blocks());
-  EXPECT_EQ(2u, optimized.dst_extents(0).start_block());
-  EXPECT_EQ(1u, optimized.dst_extents(0).num_blocks());
-  EXPECT_EQ(7u, optimized.src_extents(1).start_block());
-  EXPECT_EQ(2u, optimized.src_extents(1).num_blocks());
-  EXPECT_EQ(5u, optimized.dst_extents(1).start_block());
-  EXPECT_EQ(2u, optimized.dst_extents(1).num_blocks());
+  ASSERT_EQ(1u, optimized.src_extents(0).start_block());
+  ASSERT_EQ(1u, optimized.src_extents(0).num_blocks());
+  ASSERT_EQ(2u, optimized.dst_extents(0).start_block());
+  ASSERT_EQ(1u, optimized.dst_extents(0).num_blocks());
+  ASSERT_EQ(7u, optimized.src_extents(1).start_block());
+  ASSERT_EQ(2u, optimized.src_extents(1).num_blocks());
+  ASSERT_EQ(5u, optimized.dst_extents(1).start_block());
+  ASSERT_EQ(2u, optimized.dst_extents(1).num_blocks());
 
   // Don't skip for static partitions.
-  EXPECT_FALSE(dynamicControl().OptimizeOperation("bar", iop, &optimized));
+  ASSERT_FALSE(dynamicControl().OptimizeOperation("bar", iop, &optimized));
 }
 
 TEST_F(DynamicPartitionControlAndroidTest, ResetUpdate) {
@@ -920,7 +919,7 @@
       }));
   ON_CALL(dynamicControl(), IsAvbEnabledOnSystemOther())
       .WillByDefault(Return(false));
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       dynamicControl().RealEraseSystemOtherAvbFooter(source(), target()));
 }
 
@@ -929,7 +928,7 @@
   ON_CALL(dynamicControl(), IsAvbEnabledOnSystemOther())
       .WillByDefault(Return(true));
   std::string path;
-  bool should_unmap;
+  bool should_unmap{};
   ASSERT_TRUE(dynamicControl().RealGetSystemOtherPath(
       source(), target(), T("system"), &path, &should_unmap));
   ASSERT_TRUE(path.empty()) << path;
@@ -943,7 +942,7 @@
         return dynamicControl().RealGetSystemOtherPath(
             source_slot, target_slot, name, path, should_unmap);
       }));
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       dynamicControl().RealEraseSystemOtherAvbFooter(source(), target()));
 }
 
@@ -953,7 +952,7 @@
   ON_CALL(dynamicControl(), IsAvbEnabledOnSystemOther())
       .WillByDefault(Return(true));
   std::string path;
-  bool should_unmap;
+  bool should_unmap{};
   ASSERT_TRUE(dynamicControl().RealGetSystemOtherPath(
       source(), target(), T("system"), &path, &should_unmap));
   ASSERT_TRUE(path.empty()) << path;
@@ -967,7 +966,7 @@
         return dynamicControl().RealGetSystemOtherPath(
             source_slot, target_slot, name, path, should_unmap);
       }));
-  EXPECT_TRUE(
+  ASSERT_TRUE(
       dynamicControl().RealEraseSystemOtherAvbFooter(source(), target()));
 }
 
@@ -1059,8 +1058,8 @@
   ExpectCreateUpdateSnapshots(android::snapshot::Return::Ok());
   SetMetadata(source(), {});
   uint64_t required_size = 0;
-  EXPECT_TRUE(PreparePartitionsForUpdate(&required_size));
-  EXPECT_EQ(0u, required_size);
+  ASSERT_TRUE(PreparePartitionsForUpdate(&required_size));
+  ASSERT_EQ(0u, required_size);
 }
 
 // Test that if not enough space, required size returned by SnapshotManager is
@@ -1070,8 +1069,8 @@
   uint64_t required_size = 0;
 
   SetMetadata(source(), {});
-  EXPECT_FALSE(PreparePartitionsForUpdate(&required_size));
-  EXPECT_EQ(1_GiB, required_size);
+  ASSERT_FALSE(PreparePartitionsForUpdate(&required_size));
+  ASSERT_EQ(1_GiB, required_size);
 }
 
 // Test that in recovery, use empty space in super partition for a snapshot
@@ -1088,8 +1087,8 @@
   EXPECT_CALL(dynamicControl(), PrepareDynamicPartitionsForUpdate(_, _, _, _))
       .Times(0);
   uint64_t required_size = 0;
-  EXPECT_TRUE(PreparePartitionsForUpdate(&required_size));
-  EXPECT_EQ(0u, required_size);
+  ASSERT_TRUE(PreparePartitionsForUpdate(&required_size));
+  ASSERT_EQ(0u, required_size);
 }
 
 // Test that in recovery, if CreateUpdateSnapshots throws an error, try
@@ -1124,12 +1123,32 @@
   ExpectStoreMetadata({{T("system"), 3_GiB}, {T("vendor"), 1_GiB}});
 
   uint64_t required_size = 0;
-  EXPECT_TRUE(PreparePartitionsForUpdate(&required_size));
-  EXPECT_EQ(0u, required_size);
+  ASSERT_TRUE(PreparePartitionsForUpdate(&required_size));
+  ASSERT_EQ(0u, required_size);
 }
 
 INSTANTIATE_TEST_CASE_P(DynamicPartitionControlAndroidTest,
                         SnapshotPartitionTestP,
                         testing::Values(TestParam{0, 1}, TestParam{1, 0}));
 
+TEST(SourcePartitionTest, MapSourceWritable) {
+  BootControlAndroid boot_control;
+  ASSERT_TRUE(boot_control.Init());
+  auto source_slot = boot_control.GetCurrentSlot();
+  DynamicPartitionControlAndroid dynamic_control(source_slot);
+  std::string device;
+  ASSERT_TRUE(dynamic_control.GetPartitionDevice(
+      "system", source_slot, source_slot, &device));
+  android::base::unique_fd fd(open(device.c_str(), O_RDWR | O_CLOEXEC));
+  ASSERT_TRUE(utils::SetBlockDeviceReadOnly(device, false));
+  ASSERT_GE(fd, 0) << android::base::ErrnoNumberAsString(errno);
+  std::array<char, 512> block{};
+  ASSERT_EQ(pread(fd.get(), block.data(), block.size(), 0),
+            (ssize_t)block.size())
+      << android::base::ErrnoNumberAsString(errno);
+  ASSERT_EQ(pwrite(fd.get(), block.data(), block.size(), 0),
+            (ssize_t)block.size())
+      << android::base::ErrnoNumberAsString(errno);
+}
+
 }  // namespace chromeos_update_engine