DO NOT MERGE - Merge ab/7272582

Bug: 190855093
Change-Id: Iec6dbb2c309ca96e03b505f5d7e642d5593a6052
diff --git a/Android.bp b/Android.bp
index 7cdd64c..d74e78f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -66,7 +66,7 @@
     ],
     include_dirs: ["system"],
     local_include_dirs: ["client_library/include"],
-    static_libs: ["libgtest_prod"],
+    header_libs: ["libgtest_prod_headers"],
     shared_libs: [
         "libbrillo-stream",
         "libbrillo",
@@ -348,6 +348,9 @@
         "libstatslog",
         "libutils",
     ],
+    whole_static_libs: [
+        "com.android.sysprop.apex",
+    ],
 }
 
 cc_library_static {
@@ -426,6 +429,7 @@
     // TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved
     // out of the DBus interface.
     include_dirs: ["external/cros/system_api/dbus"],
+    header_libs: ["libgtest_prod_headers"],
 
     srcs: [
         "aosp/hardware_android.cc",
@@ -460,7 +464,6 @@
         "gkiprops",
         "libevent",
         "libmodpb64",
-        "libgtest_prod",
         "libprotobuf-cpp-lite",
         "libbrillo-stream",
         "libbrillo",
@@ -789,6 +792,7 @@
         "libcurl_http_fetcher_unittest.cc",
         "payload_consumer/bzip_extent_writer_unittest.cc",
         "payload_consumer/cached_file_descriptor_unittest.cc",
+        "payload_consumer/cow_writer_file_descriptor_unittest.cc",
         "payload_consumer/certificate_parser_android_unittest.cc",
         "payload_consumer/delta_performer_integration_test.cc",
         "payload_consumer/delta_performer_unittest.cc",
@@ -896,4 +900,4 @@
         "libz",
         "update_metadata-protos",
     ],
-}
\ No newline at end of file
+}
diff --git a/aosp/apex_handler_android.cc b/aosp/apex_handler_android.cc
index 38ec410..8beef96 100644
--- a/aosp/apex_handler_android.cc
+++ b/aosp/apex_handler_android.cc
@@ -14,10 +14,13 @@
 // limitations under the License.
 //
 
+#include <memory>
 #include <utility>
 
 #include <base/files/file_util.h>
 
+#include <ApexProperties.sysprop.h>
+
 #include "update_engine/aosp/apex_handler_android.h"
 #include "update_engine/common/utils.h"
 
@@ -44,6 +47,14 @@
 
 }  // namespace
 
+std::unique_ptr<ApexHandlerInterface> CreateApexHandler() {
+  if (android::sysprop::ApexProperties::updatable().value_or(false)) {
+    return std::make_unique<ApexHandlerAndroid>();
+  } else {
+    return std::make_unique<FlattenedApexHandlerAndroid>();
+  }
+}
+
 android::base::Result<uint64_t> ApexHandlerAndroid::CalculateSize(
     const std::vector<ApexInfo>& apex_infos) const {
   // We might not need to decompress every APEX. Communicate with apexd to get
@@ -86,4 +97,14 @@
   return android::interface_cast<android::apex::IApexService>(binder);
 }
 
+android::base::Result<uint64_t> FlattenedApexHandlerAndroid::CalculateSize(
+    const std::vector<ApexInfo>& apex_infos) const {
+  return 0;
+}
+
+bool FlattenedApexHandlerAndroid::AllocateSpace(
+    const std::vector<ApexInfo>& apex_infos) const {
+  return true;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/aosp/apex_handler_android.h b/aosp/apex_handler_android.h
index 00f3a80..767f561 100644
--- a/aosp/apex_handler_android.h
+++ b/aosp/apex_handler_android.h
@@ -17,6 +17,7 @@
 #ifndef SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_ANDROID_H_
 #define SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_ANDROID_H_
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -28,6 +29,8 @@
 
 namespace chromeos_update_engine {
 
+std::unique_ptr<ApexHandlerInterface> CreateApexHandler();
+
 class ApexHandlerAndroid : virtual public ApexHandlerInterface {
  public:
   android::base::Result<uint64_t> CalculateSize(
@@ -38,6 +41,13 @@
   android::sp<android::apex::IApexService> GetApexService() const;
 };
 
+class FlattenedApexHandlerAndroid : virtual public ApexHandlerInterface {
+ public:
+  android::base::Result<uint64_t> CalculateSize(
+      const std::vector<ApexInfo>& apex_infos) const;
+  bool AllocateSpace(const std::vector<ApexInfo>& apex_infos) const;
+};
+
 }  // namespace chromeos_update_engine
 
 #endif  // SYSTEM_UPDATE_ENGINE_AOSP_APEX_HANDLER_ANDROID_H_
diff --git a/aosp/apex_handler_android_unittest.cc b/aosp/apex_handler_android_unittest.cc
index 981ae9d..847ccaa 100644
--- a/aosp/apex_handler_android_unittest.cc
+++ b/aosp/apex_handler_android_unittest.cc
@@ -41,7 +41,7 @@
   return std::move(result);
 }
 
-TEST(ApexHandlerAndroidTest, CalculateSize) {
+TEST(ApexHandlerAndroidTest, CalculateSizeUpdatableApex) {
   ApexHandlerAndroid apex_handler;
   std::vector<ApexInfo> apex_infos;
   ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1);
@@ -52,10 +52,10 @@
   apex_infos.push_back(uncompressed_apex);
   auto result = apex_handler.CalculateSize(apex_infos);
   ASSERT_TRUE(result.ok());
-  EXPECT_EQ(*result, 3u);
+  ASSERT_EQ(*result, 3u);
 }
 
-TEST(ApexHandlerAndroidTest, AllocateSpace) {
+TEST(ApexHandlerAndroidTest, AllocateSpaceUpdatableApex) {
   ApexHandlerAndroid apex_handler;
   std::vector<ApexInfo> apex_infos;
   ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1);
@@ -64,10 +64,39 @@
   apex_infos.push_back(compressed_apex_1);
   apex_infos.push_back(compressed_apex_2);
   apex_infos.push_back(uncompressed_apex);
-  EXPECT_TRUE(apex_handler.AllocateSpace(apex_infos));
+  ASSERT_TRUE(apex_handler.AllocateSpace(apex_infos));
 
   // Should be able to pass empty list
-  EXPECT_TRUE(apex_handler.AllocateSpace({}));
+  ASSERT_TRUE(apex_handler.AllocateSpace({}));
+}
+
+TEST(ApexHandlerAndroidTest, CalculateSizeFlattenedApex) {
+  FlattenedApexHandlerAndroid apex_handler;
+  std::vector<ApexInfo> apex_infos;
+  ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1);
+  ApexInfo compressed_apex_2 = CreateApexInfo("sample2", 2, true, 2);
+  ApexInfo uncompressed_apex = CreateApexInfo("uncompressed", 1, false, 4);
+  apex_infos.push_back(compressed_apex_1);
+  apex_infos.push_back(compressed_apex_2);
+  apex_infos.push_back(uncompressed_apex);
+  auto result = apex_handler.CalculateSize(apex_infos);
+  ASSERT_TRUE(result.ok());
+  ASSERT_EQ(*result, 0u);
+}
+
+TEST(ApexHandlerAndroidTest, AllocateSpaceFlattenedApex) {
+  FlattenedApexHandlerAndroid apex_handler;
+  std::vector<ApexInfo> apex_infos;
+  ApexInfo compressed_apex_1 = CreateApexInfo("sample1", 1, true, 1);
+  ApexInfo compressed_apex_2 = CreateApexInfo("sample2", 2, true, 2);
+  ApexInfo uncompressed_apex = CreateApexInfo("uncompressed", 1, false, 4);
+  apex_infos.push_back(compressed_apex_1);
+  apex_infos.push_back(compressed_apex_2);
+  apex_infos.push_back(uncompressed_apex);
+  ASSERT_TRUE(apex_handler.AllocateSpace(apex_infos));
+
+  // Should be able to pass empty list
+  ASSERT_TRUE(apex_handler.AllocateSpace({}));
 }
 
 }  // namespace chromeos_update_engine
diff --git a/aosp/cleanup_previous_update_action.cc b/aosp/cleanup_previous_update_action.cc
index 68954f6..51bb083 100644
--- a/aosp/cleanup_previous_update_action.cc
+++ b/aosp/cleanup_previous_update_action.cc
@@ -276,7 +276,17 @@
 void CleanupPreviousUpdateAction::WaitForMergeOrSchedule() {
   AcknowledgeTaskExecuted();
   TEST_AND_RETURN(running_);
+
   auto update_uses_compression = snapshot_->UpdateUsesCompression();
+
+  // Propagate the merge failure code to the merge stats. If we wait until
+  // after ProcessUpdateState, then a successful merge could overwrite the
+  // state of the previous failure.
+  auto failure_code = snapshot_->ReadMergeFailureCode();
+  if (failure_code != android::snapshot::MergeFailureCode::Ok) {
+    merge_stats_->set_merge_failure_code(failure_code);
+  }
+
   auto state = snapshot_->ProcessUpdateState(
       std::bind(&CleanupPreviousUpdateAction::OnMergePercentageUpdate, this),
       std::bind(&CleanupPreviousUpdateAction::BeforeCancel, this));
@@ -324,6 +334,7 @@
 
     case UpdateState::MergeFailed: {
       LOG(ERROR) << "Merge failed. Device may be corrupted.";
+      merge_stats_->set_merge_failure_code(snapshot_->ReadMergeFailureCode());
       processor_->ActionComplete(this, ErrorCode::kDeviceCorrupted);
       return;
     }
@@ -492,7 +503,8 @@
                              report.total_cow_size_bytes(),
                              report.estimated_cow_size_bytes(),
                              report.boot_complete_time_ms(),
-                             report.boot_complete_to_merge_start_time_ms());
+                             report.boot_complete_to_merge_start_time_ms(),
+                             static_cast<int32_t>(report.merge_failure_code()));
 #endif
 }
 
diff --git a/aosp/daemon_state_android.cc b/aosp/daemon_state_android.cc
index fc89d73..da49080 100644
--- a/aosp/daemon_state_android.cc
+++ b/aosp/daemon_state_android.cc
@@ -65,12 +65,11 @@
   certificate_checker_->Init();
 
   // Initialize the UpdateAttempter before the UpdateManager.
-  update_attempter_.reset(
-      new UpdateAttempterAndroid(this,
-                                 prefs_.get(),
-                                 boot_control_.get(),
-                                 hardware_.get(),
-                                 std::make_unique<ApexHandlerAndroid>()));
+  update_attempter_.reset(new UpdateAttempterAndroid(this,
+                                                     prefs_.get(),
+                                                     boot_control_.get(),
+                                                     hardware_.get(),
+                                                     CreateApexHandler()));
 
   return true;
 }
diff --git a/aosp/dynamic_partition_control_android.cc b/aosp/dynamic_partition_control_android.cc
index 444fe42..538b57c 100644
--- a/aosp/dynamic_partition_control_android.cc
+++ b/aosp/dynamic_partition_control_android.cc
@@ -32,6 +32,7 @@
 #include <base/files/file_util.h>
 #include <base/logging.h>
 #include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
 #include <bootloader_message/bootloader_message.h>
 #include <fs_mgr.h>
 #include <fs_mgr_dm_linear.h>
@@ -70,6 +71,7 @@
 using android::snapshot::SnapshotManager;
 using android::snapshot::SnapshotManagerStub;
 using android::snapshot::UpdateState;
+using base::StringPrintf;
 
 namespace chromeos_update_engine {
 
@@ -830,33 +832,90 @@
   return StoreMetadata(target_device, builder.get(), target_slot);
 }
 
+DynamicPartitionControlAndroid::SpaceLimit
+DynamicPartitionControlAndroid::GetSpaceLimit(bool use_snapshot) {
+  // On device retrofitting dynamic partitions, allocatable_space = "super",
+  // where "super" is the sum of all block devices for that slot. Since block
+  // devices are dedicated for the corresponding slot, there's no need to halve
+  // the allocatable space.
+  if (GetDynamicPartitionsFeatureFlag().IsRetrofit())
+    return SpaceLimit::ERROR_IF_EXCEEDED_SUPER;
+
+  // On device launching dynamic partitions w/o VAB, regardless of recovery
+  // sideload, super partition must be big enough to hold both A and B slots of
+  // groups. Hence,
+  // allocatable_space = super / 2
+  if (!GetVirtualAbFeatureFlag().IsEnabled())
+    return SpaceLimit::ERROR_IF_EXCEEDED_HALF_OF_SUPER;
+
+  // Source build supports VAB. Super partition must be big enough to hold
+  // one slot of groups (ERROR_IF_EXCEEDED_SUPER). However, there are cases
+  // where additional warning messages needs to be written.
+
+  // If using snapshot updates, implying that target build also uses VAB,
+  // allocatable_space = super
+  if (use_snapshot)
+    return SpaceLimit::ERROR_IF_EXCEEDED_SUPER;
+
+  // Source build supports VAB but not using snapshot updates. There are
+  // several cases, as listed below.
+  // Sideloading: allocatable_space = super.
+  if (IsRecovery())
+    return SpaceLimit::ERROR_IF_EXCEEDED_SUPER;
+
+  // On launch VAB device, this implies secondary payload.
+  // Technically, we don't have to check anything, but sum(groups) < super
+  // still applies.
+  if (!GetVirtualAbFeatureFlag().IsRetrofit())
+    return SpaceLimit::ERROR_IF_EXCEEDED_SUPER;
+
+  // On retrofit VAB device, either of the following:
+  // - downgrading: allocatable_space = super / 2
+  // - secondary payload: don't check anything
+  // These two cases are indistinguishable,
+  // hence emit warning if sum(groups) > super / 2
+  return SpaceLimit::WARN_IF_EXCEEDED_HALF_OF_SUPER;
+}
+
 bool DynamicPartitionControlAndroid::CheckSuperPartitionAllocatableSpace(
     android::fs_mgr::MetadataBuilder* builder,
     const DeltaArchiveManifest& manifest,
     bool use_snapshot) {
-  uint64_t total_size = 0;
+  uint64_t sum_groups = 0;
   for (const auto& group : manifest.dynamic_partition_metadata().groups()) {
-    total_size += group.size();
+    sum_groups += group.size();
   }
 
-  std::string expr;
-  uint64_t allocatable_space = builder->AllocatableSpace();
-  // On device retrofitting dynamic partitions, allocatable_space = super.
-  // On device launching dynamic partitions w/o VAB,
-  //   allocatable_space = super / 2.
-  // On device launching dynamic partitions with VAB, allocatable_space = super.
-  // For recovery sideload, allocatable_space = super.
-  if (!GetDynamicPartitionsFeatureFlag().IsRetrofit() && !use_snapshot &&
-      !IsRecovery()) {
-    allocatable_space /= 2;
-    expr = "half of ";
-  }
-  if (total_size > allocatable_space) {
-    LOG(ERROR) << "The maximum size of all groups for the target slot"
-               << " (" << total_size << ") has exceeded " << expr
-               << "allocatable space for dynamic partitions "
-               << allocatable_space << ".";
-    return false;
+  uint64_t full_space = builder->AllocatableSpace();
+  uint64_t half_space = full_space / 2;
+  constexpr const char* fmt =
+      "The maximum size of all groups for the target slot (%" PRIu64
+      ") has exceeded %sallocatable space for dynamic partitions %" PRIu64 ".";
+  switch (GetSpaceLimit(use_snapshot)) {
+    case SpaceLimit::ERROR_IF_EXCEEDED_HALF_OF_SUPER: {
+      if (sum_groups > half_space) {
+        LOG(ERROR) << StringPrintf(fmt, sum_groups, "HALF OF ", half_space);
+        return false;
+      }
+      // If test passes, it implies that the following two conditions also pass.
+      break;
+    }
+    case SpaceLimit::WARN_IF_EXCEEDED_HALF_OF_SUPER: {
+      if (sum_groups > half_space) {
+        LOG(WARNING) << StringPrintf(fmt, sum_groups, "HALF OF ", half_space)
+                     << " This is allowed for downgrade or secondary OTA on "
+                        "retrofit VAB device.";
+      }
+      // still check sum(groups) < super
+      [[fallthrough]];
+    }
+    case SpaceLimit::ERROR_IF_EXCEEDED_SUPER: {
+      if (sum_groups > full_space) {
+        LOG(ERROR) << base::StringPrintf(fmt, sum_groups, "", full_space);
+        return false;
+      }
+      break;
+    }
   }
 
   return true;
@@ -910,9 +969,16 @@
     uint32_t target_slot,
     const DeltaArchiveManifest& manifest) {
   // Check preconditions.
-  LOG_IF(WARNING, !GetVirtualAbFeatureFlag().IsEnabled() || IsRecovery())
-      << "UpdatePartitionMetadata is called on a Virtual A/B device "
-         "but source partitions is not deleted. This is not allowed.";
+  if (GetVirtualAbFeatureFlag().IsEnabled()) {
+    CHECK(!target_supports_snapshot_ || IsRecovery())
+        << "Must use snapshot on VAB device when target build supports VAB and "
+           "not sideloading.";
+    LOG_IF(INFO, !target_supports_snapshot_)
+        << "Not using snapshot on VAB device because target build does not "
+           "support snapshot. Secondary or downgrade OTA?";
+    LOG_IF(INFO, IsRecovery())
+        << "Not using snapshot on VAB device because sideloading.";
+  }
 
   // If applying downgrade from Virtual A/B to non-Virtual A/B, the left-over
   // COW group needs to be deleted to ensure there are enough space to create
@@ -1050,9 +1116,9 @@
   if (UpdateUsesSnapshotCompression() && slot != current_slot &&
       IsDynamicPartition(partition_name, slot)) {
     return {
-        {.mountable_device_path = base::FilePath{std::string{VABC_DEVICE_DIR}}
-                                      .Append(partition_name_suffix)
-                                      .value(),
+        {.readonly_device_path = base::FilePath{std::string{VABC_DEVICE_DIR}}
+                                     .Append(partition_name_suffix)
+                                     .value(),
          .is_dynamic = true}};
   }
 
@@ -1071,7 +1137,7 @@
                                       &device)) {
       case DynamicPartitionDeviceStatus::SUCCESS:
         return {{.rw_device_path = device,
-                 .mountable_device_path = device,
+                 .readonly_device_path = device,
                  .is_dynamic = true}};
 
       case DynamicPartitionDeviceStatus::TRY_STATIC:
@@ -1089,7 +1155,7 @@
   }
 
   return {{.rw_device_path = static_path,
-           .mountable_device_path = static_path,
+           .readonly_device_path = static_path,
            .is_dynamic = false}};
 }
 
@@ -1404,7 +1470,8 @@
 }
 
 bool DynamicPartitionControlAndroid::UpdateUsesSnapshotCompression() {
-  return snapshot_->UpdateUsesCompression();
+  return GetVirtualAbFeatureFlag().IsEnabled() &&
+         snapshot_->UpdateUsesCompression();
 }
 
 }  // namespace chromeos_update_engine
diff --git a/aosp/dynamic_partition_control_android.h b/aosp/dynamic_partition_control_android.h
index b7aa7ea..df91401 100644
--- a/aosp/dynamic_partition_control_android.h
+++ b/aosp/dynamic_partition_control_android.h
@@ -258,6 +258,18 @@
                                           const DeltaArchiveManifest& manifest,
                                           uint64_t* required_size);
 
+  enum SpaceLimit {
+    // Most restricted: if sum(groups) > super / 2, error
+    ERROR_IF_EXCEEDED_HALF_OF_SUPER,
+    // Implies ERROR_IF_EXCEEDED_SUPER; then, if sum(groups) > super / 2, warn
+    WARN_IF_EXCEEDED_HALF_OF_SUPER,
+    // Least restricted: if sum(groups) > super, error
+    ERROR_IF_EXCEEDED_SUPER,
+  };
+  // Helper of CheckSuperPartitionAllocatableSpace. Determine limit for groups
+  // and partitions.
+  SpaceLimit GetSpaceLimit(bool use_snapshot);
+
   // Returns true if the allocatable space in super partition is larger than
   // the size of dynamic partition groups in the manifest.
   bool CheckSuperPartitionAllocatableSpace(
diff --git a/aosp/dynamic_partition_control_android_unittest.cc b/aosp/dynamic_partition_control_android_unittest.cc
index 0bb8df7..6f1d4ef 100644
--- a/aosp/dynamic_partition_control_android_unittest.cc
+++ b/aosp/dynamic_partition_control_android_unittest.cc
@@ -431,7 +431,7 @@
   auto device_info =
       dynamicControl().GetPartitionDevice("system", target(), source(), false);
   ASSERT_TRUE(device_info.has_value());
-  ASSERT_EQ(device_info->mountable_device_path, device);
+  ASSERT_EQ(device_info->readonly_device_path, device);
 }
 
 TEST_P(DynamicPartitionControlAndroidTestP, GetMountableDevicePathVABC) {
@@ -475,7 +475,7 @@
   ASSERT_TRUE(device_info.has_value());
   base::FilePath vabc_device_dir{
       std::string{DynamicPartitionControlAndroid::VABC_DEVICE_DIR}};
-  ASSERT_EQ(device_info->mountable_device_path,
+  ASSERT_EQ(device_info->readonly_device_path,
             vabc_device_dir.Append(T("system")).value());
 }
 
diff --git a/aosp/update_attempter_android.cc b/aosp/update_attempter_android.cc
index ba61f25..c1e15c0 100644
--- a/aosp/update_attempter_android.cc
+++ b/aosp/update_attempter_android.cc
@@ -159,7 +159,7 @@
     SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
   } else {
     SetStatusAndNotify(UpdateStatus::IDLE);
-    UpdatePrefsAndReportUpdateMetricsOnReboot();
+    UpdateStateAfterReboot();
 #ifdef _UE_SIDELOAD
     LOG(INFO) << "Skip ScheduleCleanupPreviousUpdate in sideload because "
               << "ApplyPayload will call it later.";
@@ -364,6 +364,12 @@
   LOG(INFO) << "Attempting to reset state from "
             << UpdateStatusToString(status_) << " to UpdateStatus::IDLE";
 
+  if (apex_handler_android_ != nullptr) {
+    LOG(INFO) << "Cleaning up reserved space for compressed APEX (if any)";
+    std::vector<ApexInfo> apex_infos_blank;
+    apex_handler_android_->AllocateSpace(apex_infos_blank);
+  }
+
   switch (status_) {
     case UpdateStatus::IDLE: {
       if (!boot_control_->GetDynamicPartitionControl()->ResetUpdate(prefs_)) {
@@ -877,27 +883,32 @@
   }
 }
 
-void UpdateAttempterAndroid::UpdatePrefsAndReportUpdateMetricsOnReboot() {
+void UpdateAttempterAndroid::UpdateStateAfterReboot() {
   string current_boot_id;
   TEST_AND_RETURN(utils::GetBootId(&current_boot_id));
   // Example: [ro.build.version.incremental]: [4292972]
   string current_version =
       android::base::GetProperty("ro.build.version.incremental", "");
   TEST_AND_RETURN(!current_version.empty());
+  const auto current_slot = boot_control_->GetCurrentSlot();
 
   // If there's no record of previous version (e.g. due to a data wipe), we
   // save the info of current boot and skip the metrics report.
   if (!prefs_->Exists(kPrefsPreviousVersion)) {
     prefs_->SetString(kPrefsBootId, current_boot_id);
     prefs_->SetString(kPrefsPreviousVersion, current_version);
+    prefs_->SetInt64(std::string{kPrefsPreviousSlot},
+                     boot_control_->GetCurrentSlot());
     ClearMetricsPrefs();
     return;
   }
+  int64_t previous_slot = -1;
+  prefs_->GetInt64(kPrefsPreviousSlot, &previous_slot);
   string previous_version;
-  // update_engine restarted under the same build.
+  // update_engine restarted under the same build and same slot.
   // TODO(xunchang) identify and report rollback by checking UpdateMarker.
   if (prefs_->GetString(kPrefsPreviousVersion, &previous_version) &&
-      previous_version == current_version) {
+      previous_version == current_version && previous_slot == current_slot) {
     string last_boot_id;
     bool is_reboot = prefs_->Exists(kPrefsBootId) &&
                      (prefs_->GetString(kPrefsBootId, &last_boot_id) &&
@@ -917,6 +928,8 @@
   // TODO(xunchang) check the build version is larger than the previous one.
   prefs_->SetString(kPrefsBootId, current_boot_id);
   prefs_->SetString(kPrefsPreviousVersion, current_version);
+  prefs_->SetInt64(std::string{kPrefsPreviousSlot},
+                   boot_control_->GetCurrentSlot());
 
   bool previous_attempt_exists = prefs_->Exists(kPrefsPayloadAttemptNumber);
   // |kPrefsPayloadAttemptNumber| should be cleared upon successful update.
diff --git a/aosp/update_attempter_android.h b/aosp/update_attempter_android.h
index 70938bc..7a5a635 100644
--- a/aosp/update_attempter_android.h
+++ b/aosp/update_attempter_android.h
@@ -162,12 +162,16 @@
   //   |kPrefsSystemUpdatedMarker|
   void CollectAndReportUpdateMetricsOnUpdateFinished(ErrorCode error_code);
 
+  // This function is called after update_engine is started after device
+  // reboots. If update_engine is restarted w/o device reboot, this function
+  // would not be called.
+
   // Metrics report function to call:
   //   |ReportAbnormallyTerminatedUpdateAttemptMetrics|
   //   |ReportTimeToRebootMetrics|
   // Prefs to update:
   //   |kPrefsBootId|, |kPrefsPreviousVersion|
-  void UpdatePrefsAndReportUpdateMetricsOnReboot();
+  void UpdateStateAfterReboot();
 
   // Prefs to update:
   //   |kPrefsPayloadAttemptNumber|, |kPrefsUpdateTimestampStart|,
diff --git a/aosp/update_attempter_android_unittest.cc b/aosp/update_attempter_android_unittest.cc
index f799df3..f73df16 100644
--- a/aosp/update_attempter_android_unittest.cc
+++ b/aosp/update_attempter_android_unittest.cc
@@ -24,6 +24,7 @@
 #include <base/time/time.h>
 #include <gtest/gtest.h>
 
+#include "common/constants.h"
 #include "update_engine/aosp/daemon_state_android.h"
 #include "update_engine/common/fake_boot_control.h"
 #include "update_engine/common/fake_clock.h"
@@ -81,6 +82,8 @@
   prefs_.SetString(kPrefsPreviousVersion, build_version);
   prefs_.SetString(kPrefsBootId, "oldboot");
   prefs_.SetInt64(kPrefsNumReboots, 1);
+  prefs_.SetInt64(kPrefsPreviousSlot, 1);
+  boot_control_.SetCurrentSlot(1);
 
   EXPECT_CALL(*metrics_reporter_, ReportTimeToReboot(_)).Times(0);
   update_attempter_android_.Init();
@@ -88,15 +91,15 @@
   // Check that the boot_id and reboot_count are updated.
   std::string boot_id;
   utils::GetBootId(&boot_id);
-  EXPECT_TRUE(prefs_.Exists(kPrefsBootId));
+  ASSERT_TRUE(prefs_.Exists(kPrefsBootId));
   std::string prefs_boot_id;
-  EXPECT_TRUE(prefs_.GetString(kPrefsBootId, &prefs_boot_id));
-  EXPECT_EQ(boot_id, prefs_boot_id);
+  ASSERT_TRUE(prefs_.GetString(kPrefsBootId, &prefs_boot_id));
+  ASSERT_EQ(boot_id, prefs_boot_id);
 
-  EXPECT_TRUE(prefs_.Exists(kPrefsNumReboots));
+  ASSERT_TRUE(prefs_.Exists(kPrefsNumReboots));
   int64_t reboot_count;
-  EXPECT_TRUE(prefs_.GetInt64(kPrefsNumReboots, &reboot_count));
-  EXPECT_EQ(2, reboot_count);
+  ASSERT_TRUE(prefs_.GetInt64(kPrefsNumReboots, &reboot_count));
+  ASSERT_EQ(2, reboot_count);
 }
 
 TEST_F(UpdateAttempterAndroidTest, UpdatePrefsBuildVersionChangeOnInit) {
diff --git a/common/constants.cc b/common/constants.cc
index a9cf238..ff46755 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -16,132 +16,4 @@
 
 #include "update_engine/common/constants.h"
 
-namespace chromeos_update_engine {
-
-const char kExclusionPrefsSubDir[] = "exclusion";
-
-const char kDlcPrefsSubDir[] = "dlc";
-
-const char kPowerwashSafePrefsSubDirectory[] = "update_engine/prefs";
-
-const char kPrefsSubDirectory[] = "prefs";
-
-const char kStatefulPartition[] = "/mnt/stateful_partition";
-
-const char kPostinstallDefaultScript[] = "postinst";
-
-// Constants defining keys for the persisted state of update engine.
-const char kPrefsAttemptInProgress[] = "attempt-in-progress";
-const char kPrefsBackoffExpiryTime[] = "backoff-expiry-time";
-const char kPrefsBootId[] = "boot-id";
-const char kPrefsCurrentBytesDownloaded[] = "current-bytes-downloaded";
-const char kPrefsCurrentResponseSignature[] = "current-response-signature";
-const char kPrefsCurrentUrlFailureCount[] = "current-url-failure-count";
-const char kPrefsCurrentUrlIndex[] = "current-url-index";
-const char kPrefsDailyMetricsLastReportedAt[] =
-    "daily-metrics-last-reported-at";
-const char kPrefsDeltaUpdateFailures[] = "delta-update-failures";
-const char kPrefsDynamicPartitionMetadataUpdated[] =
-    "dynamic-partition-metadata-updated";
-const char kPrefsFullPayloadAttemptNumber[] = "full-payload-attempt-number";
-const char kPrefsInstallDateDays[] = "install-date-days";
-const char kPrefsLastActivePingDay[] = "last-active-ping-day";
-const char kPrefsLastRollCallPingDay[] = "last-roll-call-ping-day";
-const char kPrefsManifestMetadataSize[] = "manifest-metadata-size";
-const char kPrefsManifestSignatureSize[] = "manifest-signature-size";
-const char kPrefsMetricsAttemptLastReportingTime[] =
-    "metrics-attempt-last-reporting-time";
-const char kPrefsMetricsCheckLastReportingTime[] =
-    "metrics-check-last-reporting-time";
-const char kPrefsNoIgnoreBackoff[] = "no-ignore-backoff";
-const char kPrefsNumReboots[] = "num-reboots";
-const char kPrefsNumResponsesSeen[] = "num-responses-seen";
-const char kPrefsOmahaCohort[] = "omaha-cohort";
-const char kPrefsOmahaCohortHint[] = "omaha-cohort-hint";
-const char kPrefsOmahaCohortName[] = "omaha-cohort-name";
-const char kPrefsOmahaEolDate[] = "omaha-eol-date";
-const char kPrefsP2PEnabled[] = "p2p-enabled";
-const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
-const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
-const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number";
-const char kPrefsTestUpdateCheckIntervalTimeout[] =
-    "test-update-check-interval-timeout";
-// Keep |kPrefsPingActive| in sync with |kDlcMetadataFilePingActive| in
-// dlcservice.
-const char kPrefsPingActive[] = "active";
-const char kPrefsPingLastActive[] = "date_last_active";
-const char kPrefsPingLastRollcall[] = "date_last_rollcall";
-const char kPrefsLastFp[] = "last-fp";
-const char kPrefsPostInstallSucceeded[] = "post-install-succeeded";
-const char kPrefsPreviousVersion[] = "previous-version";
-const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
-const char kPrefsRollbackHappened[] = "rollback-happened";
-const char kPrefsRollbackVersion[] = "rollback-version";
-const char kPrefsChannelOnSlotPrefix[] = "channel-on-slot-";
-const char kPrefsSystemUpdatedMarker[] = "system-updated-marker";
-const char kPrefsTargetVersionAttempt[] = "target-version-attempt";
-const char kPrefsTargetVersionInstalledFrom[] = "target-version-installed-from";
-const char kPrefsTargetVersionUniqueId[] = "target-version-unique-id";
-const char kPrefsTotalBytesDownloaded[] = "total-bytes-downloaded";
-const char kPrefsUpdateCheckCount[] = "update-check-count";
-const char kPrefsUpdateCheckResponseHash[] = "update-check-response-hash";
-const char kPrefsUpdateCompletedBootTime[] = "update-completed-boot-time";
-const char kPrefsUpdateCompletedOnBootId[] = "update-completed-on-boot-id";
-const char kPrefsUpdateDurationUptime[] = "update-duration-uptime";
-const char kPrefsUpdateFirstSeenAt[] = "update-first-seen-at";
-const char kPrefsUpdateOverCellularPermission[] =
-    "update-over-cellular-permission";
-const char kPrefsUpdateOverCellularTargetVersion[] =
-    "update-over-cellular-target-version";
-const char kPrefsUpdateOverCellularTargetSize[] =
-    "update-over-cellular-target-size";
-const char kPrefsUpdateServerCertificate[] = "update-server-cert";
-const char kPrefsUpdateStateNextDataLength[] = "update-state-next-data-length";
-const char kPrefsUpdateStateNextDataOffset[] = "update-state-next-data-offset";
-const char kPrefsUpdateStateNextOperation[] = "update-state-next-operation";
-const char kPrefsUpdateStatePayloadIndex[] = "update-state-payload-index";
-const char kPrefsUpdateStateSHA256Context[] = "update-state-sha-256-context";
-const char kPrefsUpdateStateSignatureBlob[] = "update-state-signature-blob";
-const char kPrefsUpdateStateSignedSHA256Context[] =
-    "update-state-signed-sha-256-context";
-const char kPrefsUpdateBootTimestampStart[] = "update-boot-timestamp-start";
-const char kPrefsUpdateTimestampStart[] = "update-timestamp-start";
-const char kPrefsUrlSwitchCount[] = "url-switch-count";
-const char kPrefsVerityWritten[] = "verity-written";
-const char kPrefsWallClockScatteringWaitPeriod[] = "wall-clock-wait-period";
-const char kPrefsWallClockStagingWaitPeriod[] =
-    "wall-clock-staging-wait-period";
-const char kPrefsManifestBytes[] = "manifest-bytes";
-
-// These four fields are generated by scripts/brillo_update_payload.
-const char kPayloadPropertyFileSize[] = "FILE_SIZE";
-const char kPayloadPropertyFileHash[] = "FILE_HASH";
-const char kPayloadPropertyMetadataSize[] = "METADATA_SIZE";
-const char kPayloadPropertyMetadataHash[] = "METADATA_HASH";
-// The Authorization: HTTP header to be sent when downloading the payload.
-const char kPayloadPropertyAuthorization[] = "AUTHORIZATION";
-// The User-Agent HTTP header to be sent when downloading the payload.
-const char kPayloadPropertyUserAgent[] = "USER_AGENT";
-// Set "POWERWASH=1" to powerwash (factory data reset) the device after
-// applying the update.
-const char kPayloadPropertyPowerwash[] = "POWERWASH";
-// The network id to pass to android_setprocnetwork before downloading.
-// This can be used to zero-rate OTA traffic by sending it over the correct
-// network.
-const char kPayloadPropertyNetworkId[] = "NETWORK_ID";
-// Set "SWITCH_SLOT_ON_REBOOT=0" to skip marking the updated partitions active.
-// The default is 1 (always switch slot if update succeeded).
-const char kPayloadPropertySwitchSlotOnReboot[] = "SWITCH_SLOT_ON_REBOOT";
-// Set "RUN_POST_INSTALL=0" to skip running optional post install.
-// The default is 1 (always run post install).
-const char kPayloadPropertyRunPostInstall[] = "RUN_POST_INSTALL";
-
-const char kOmahaUpdaterVersion[] = "0.1.0.0";
-
-// X-Goog-Update headers.
-const char kXGoogleUpdateInteractivity[] = "X-Goog-Update-Interactivity";
-const char kXGoogleUpdateAppId[] = "X-Goog-Update-AppId";
-const char kXGoogleUpdateUpdater[] = "X-Goog-Update-Updater";
-const char kXGoogleUpdateSessionId[] = "X-Goog-SessionId";
-
-}  // namespace chromeos_update_engine
+namespace chromeos_update_engine {}  // namespace chromeos_update_engine
diff --git a/common/constants.h b/common/constants.h
index 64447ce..8c07fcf 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -20,115 +20,176 @@
 #include <cstdint>
 
 namespace chromeos_update_engine {
-
 // The root path of all exclusion prefs.
-extern const char kExclusionPrefsSubDir[];
+static constexpr const auto& kExclusionPrefsSubDir = "exclusion";
 
 // The root path of all DLC metadata.
-extern const char kDlcPrefsSubDir[];
+static constexpr const auto& kDlcPrefsSubDir = "dlc";
 
 // Directory for AU prefs that are preserved across powerwash.
-extern const char kPowerwashSafePrefsSubDirectory[];
+static constexpr const auto& kPowerwashSafePrefsSubDirectory =
+    "update_engine/prefs";
 
 // The location where we store the AU preferences (state etc).
-extern const char kPrefsSubDirectory[];
-
-// Path to the post install command, relative to the partition.
-extern const char kPostinstallDefaultScript[];
+static constexpr const auto& kPrefsSubDirectory = "prefs";
 
 // Path to the stateful partition on the root filesystem.
-extern const char kStatefulPartition[];
+static constexpr const auto& kStatefulPartition = "/mnt/stateful_partition";
+
+// Path to the post install command, relative to the partition.
+static constexpr const auto& kPostinstallDefaultScript = "postinst";
 
 // Constants related to preferences.
-extern const char kPrefsAttemptInProgress[];
-extern const char kPrefsBackoffExpiryTime[];
-extern const char kPrefsBootId[];
-extern const char kPrefsCurrentBytesDownloaded[];
-extern const char kPrefsCurrentResponseSignature[];
-extern const char kPrefsCurrentUrlFailureCount[];
-extern const char kPrefsCurrentUrlIndex[];
-extern const char kPrefsDailyMetricsLastReportedAt[];
-extern const char kPrefsDeltaUpdateFailures[];
-extern const char kPrefsDynamicPartitionMetadataUpdated[];
-extern const char kPrefsFullPayloadAttemptNumber[];
-extern const char kPrefsInstallDateDays[];
-extern const char kPrefsLastActivePingDay[];
-extern const char kPrefsLastRollCallPingDay[];
-extern const char kPrefsManifestMetadataSize[];
-extern const char kPrefsManifestSignatureSize[];
-extern const char kPrefsMetricsAttemptLastReportingTime[];
-extern const char kPrefsMetricsCheckLastReportingTime[];
-extern const char kPrefsNoIgnoreBackoff[];
-extern const char kPrefsNumReboots[];
-extern const char kPrefsNumResponsesSeen[];
-extern const char kPrefsOmahaCohort[];
-extern const char kPrefsOmahaCohortHint[];
-extern const char kPrefsOmahaCohortName[];
-extern const char kPrefsOmahaEolDate[];
-extern const char kPrefsP2PEnabled[];
-extern const char kPrefsP2PFirstAttemptTimestamp[];
-extern const char kPrefsP2PNumAttempts[];
-extern const char kPrefsPayloadAttemptNumber[];
-extern const char kPrefsTestUpdateCheckIntervalTimeout[];
-extern const char kPrefsPingActive[];
-extern const char kPrefsPingLastActive[];
-extern const char kPrefsPingLastRollcall[];
-extern const char kPrefsLastFp[];
-extern const char kPrefsPostInstallSucceeded[];
-extern const char kPrefsPreviousVersion[];
-extern const char kPrefsResumedUpdateFailures[];
-extern const char kPrefsRollbackHappened[];
-extern const char kPrefsRollbackVersion[];
-extern const char kPrefsChannelOnSlotPrefix[];
-extern const char kPrefsSystemUpdatedMarker[];
-extern const char kPrefsTargetVersionAttempt[];
-extern const char kPrefsTargetVersionInstalledFrom[];
-extern const char kPrefsTargetVersionUniqueId[];
-extern const char kPrefsTotalBytesDownloaded[];
-extern const char kPrefsUpdateCheckCount[];
-extern const char kPrefsUpdateCheckResponseHash[];
-extern const char kPrefsUpdateCompletedBootTime[];
-extern const char kPrefsUpdateCompletedOnBootId[];
-extern const char kPrefsUpdateDurationUptime[];
-extern const char kPrefsUpdateFirstSeenAt[];
-extern const char kPrefsUpdateOverCellularPermission[];
-extern const char kPrefsUpdateOverCellularTargetVersion[];
-extern const char kPrefsUpdateOverCellularTargetSize[];
-extern const char kPrefsUpdateServerCertificate[];
-extern const char kPrefsUpdateStateNextDataLength[];
-extern const char kPrefsUpdateStateNextDataOffset[];
-extern const char kPrefsUpdateStateNextOperation[];
-extern const char kPrefsUpdateStatePayloadIndex[];
-extern const char kPrefsUpdateStateSHA256Context[];
-extern const char kPrefsUpdateStateSignatureBlob[];
-extern const char kPrefsUpdateStateSignedSHA256Context[];
-extern const char kPrefsUpdateBootTimestampStart[];
-extern const char kPrefsUpdateTimestampStart[];
-extern const char kPrefsUrlSwitchCount[];
-extern const char kPrefsVerityWritten[];
-extern const char kPrefsWallClockScatteringWaitPeriod[];
-extern const char kPrefsWallClockStagingWaitPeriod[];
-extern const char kPrefsManifestBytes[];
+// Constants defining keys for the persisted state of update engine.
+static constexpr const auto& kPrefsAttemptInProgress = "attempt-in-progress";
+static constexpr const auto& kPrefsBackoffExpiryTime = "backoff-expiry-time";
+static constexpr const auto& kPrefsBootId = "boot-id";
+static constexpr const auto& kPrefsCurrentBytesDownloaded =
+    "current-bytes-downloaded";
+static constexpr const auto& kPrefsCurrentResponseSignature =
+    "current-response-signature";
+static constexpr const auto& kPrefsCurrentUrlFailureCount =
+    "current-url-failure-count";
+static constexpr const auto& kPrefsCurrentUrlIndex = "current-url-index";
+static constexpr const auto& kPrefsDailyMetricsLastReportedAt =
+    "daily-metrics-last-reported-at";
+static constexpr const auto& kPrefsDeltaUpdateFailures =
+    "delta-update-failures";
+static constexpr const auto& kPrefsDynamicPartitionMetadataUpdated =
+    "dynamic-partition-metadata-updated";
+static constexpr const auto& kPrefsFullPayloadAttemptNumber =
+    "full-payload-attempt-number";
+static constexpr const auto& kPrefsInstallDateDays = "install-date-days";
+static constexpr const auto& kPrefsLastActivePingDay = "last-active-ping-day";
+static constexpr const auto& kPrefsLastRollCallPingDay =
+    "last-roll-call-ping-day";
+static constexpr const auto& kPrefsManifestMetadataSize =
+    "manifest-metadata-size";
+static constexpr const auto& kPrefsManifestSignatureSize =
+    "manifest-signature-size";
+static constexpr const auto& kPrefsMetricsAttemptLastReportingTime =
+    "metrics-attempt-last-reporting-time";
+static constexpr const auto& kPrefsMetricsCheckLastReportingTime =
+    "metrics-check-last-reporting-time";
+static constexpr const auto& kPrefsNoIgnoreBackoff = "no-ignore-backoff";
+static constexpr const auto& kPrefsNumReboots = "num-reboots";
+static constexpr const auto& kPrefsNumResponsesSeen = "num-responses-seen";
+static constexpr const auto& kPrefsOmahaCohort = "omaha-cohort";
+static constexpr const auto& kPrefsOmahaCohortHint = "omaha-cohort-hint";
+static constexpr const auto& kPrefsOmahaCohortName = "omaha-cohort-name";
+static constexpr const auto& kPrefsOmahaEolDate = "omaha-eol-date";
+static constexpr const auto& kPrefsP2PEnabled = "p2p-enabled";
+static constexpr const auto& kPrefsP2PFirstAttemptTimestamp =
+    "p2p-first-attempt-timestamp";
+static constexpr const auto& kPrefsP2PNumAttempts = "p2p-num-attempts";
+static constexpr const auto& kPrefsPayloadAttemptNumber =
+    "payload-attempt-number";
+static constexpr const auto& kPrefsTestUpdateCheckIntervalTimeout =
+    "test-update-check-interval-timeout";
+// Keep |kPrefsPingActive| in sync with |kDlcMetadataFilePingActive| in
+// dlcservice.
+static constexpr const auto& kPrefsPingActive = "active";
+static constexpr const auto& kPrefsPingLastActive = "date_last_active";
+static constexpr const auto& kPrefsPingLastRollcall = "date_last_rollcall";
+static constexpr const auto& kPrefsLastFp = "last-fp";
+static constexpr const auto& kPrefsPostInstallSucceeded =
+    "post-install-succeeded";
+static constexpr const auto& kPrefsPreviousVersion = "previous-version";
+static constexpr const auto& kPrefsResumedUpdateFailures =
+    "resumed-update-failures";
+static constexpr const auto& kPrefsRollbackHappened = "rollback-happened";
+static constexpr const auto& kPrefsRollbackVersion = "rollback-version";
+static constexpr const auto& kPrefsChannelOnSlotPrefix = "channel-on-slot-";
+static constexpr const auto& kPrefsSystemUpdatedMarker =
+    "system-updated-marker";
+static constexpr const auto& kPrefsTargetVersionAttempt =
+    "target-version-attempt";
+static constexpr const auto& kPrefsTargetVersionInstalledFrom =
+    "target-version-installed-from";
+static constexpr const auto& kPrefsTargetVersionUniqueId =
+    "target-version-unique-id";
+static constexpr const auto& kPrefsTotalBytesDownloaded =
+    "total-bytes-downloaded";
+static constexpr const auto& kPrefsUpdateCheckCount = "update-check-count";
+static constexpr const auto& kPrefsUpdateCheckResponseHash =
+    "update-check-response-hash";
+static constexpr const auto& kPrefsUpdateCompletedBootTime =
+    "update-completed-boot-time";
+static constexpr const auto& kPrefsUpdateCompletedOnBootId =
+    "update-completed-on-boot-id";
+static constexpr const auto& kPrefsUpdateDurationUptime =
+    "update-duration-uptime";
+static constexpr const auto& kPrefsUpdateFirstSeenAt = "update-first-seen-at";
+static constexpr const auto& kPrefsUpdateOverCellularPermission =
+    "update-over-cellular-permission";
+static constexpr const auto& kPrefsUpdateOverCellularTargetVersion =
+    "update-over-cellular-target-version";
+static constexpr const auto& kPrefsUpdateOverCellularTargetSize =
+    "update-over-cellular-target-size";
+static constexpr const auto& kPrefsUpdateServerCertificate =
+    "update-server-cert";
+static constexpr const auto& kPrefsUpdateStateNextDataLength =
+    "update-state-next-data-length";
+static constexpr const auto& kPrefsUpdateStateNextDataOffset =
+    "update-state-next-data-offset";
+static constexpr const auto& kPrefsUpdateStateNextOperation =
+    "update-state-next-operation";
+static constexpr const auto& kPrefsUpdateStatePayloadIndex =
+    "update-state-payload-index";
+static constexpr const auto& kPrefsUpdateStateSHA256Context =
+    "update-state-sha-256-context";
+static constexpr const auto& kPrefsUpdateStateSignatureBlob =
+    "update-state-signature-blob";
+static constexpr const auto& kPrefsUpdateStateSignedSHA256Context =
+    "update-state-signed-sha-256-context";
+static constexpr const auto& kPrefsUpdateBootTimestampStart =
+    "update-boot-timestamp-start";
+static constexpr const auto& kPrefsUpdateTimestampStart =
+    "update-timestamp-start";
+static constexpr const auto& kPrefsUrlSwitchCount = "url-switch-count";
+static constexpr const auto& kPrefsVerityWritten = "verity-written";
+static constexpr const auto& kPrefsWallClockScatteringWaitPeriod =
+    "wall-clock-wait-period";
+static constexpr const auto& kPrefsWallClockStagingWaitPeriod =
+    "wall-clock-staging-wait-period";
+static constexpr const auto& kPrefsManifestBytes = "manifest-bytes";
+static constexpr const auto& kPrefsPreviousSlot = "previous-slot";
 
 // Keys used when storing and loading payload properties.
-extern const char kPayloadPropertyFileSize[];
-extern const char kPayloadPropertyFileHash[];
-extern const char kPayloadPropertyMetadataSize[];
-extern const char kPayloadPropertyMetadataHash[];
-extern const char kPayloadPropertyAuthorization[];
-extern const char kPayloadPropertyUserAgent[];
-extern const char kPayloadPropertyPowerwash[];
-extern const char kPayloadPropertyNetworkId[];
-extern const char kPayloadPropertySwitchSlotOnReboot[];
-extern const char kPayloadPropertyRunPostInstall[];
+// These four fields are generated by scripts/brillo_update_payload.
+static constexpr const auto& kPayloadPropertyFileSize = "FILE_SIZE";
+static constexpr const auto& kPayloadPropertyFileHash = "FILE_HASH";
+static constexpr const auto& kPayloadPropertyMetadataSize = "METADATA_SIZE";
+static constexpr const auto& kPayloadPropertyMetadataHash = "METADATA_HASH";
+// The Authorization: HTTP header to be sent when downloading the payload.
+static constexpr const auto& kPayloadPropertyAuthorization = "AUTHORIZATION";
+// The User-Agent HTTP header to be sent when downloading the payload.
+static constexpr const auto& kPayloadPropertyUserAgent = "USER_AGENT";
+// Set "POWERWASH=1" to powerwash (factory data reset) the device after
+// applying the update.
+static constexpr const auto& kPayloadPropertyPowerwash = "POWERWASH";
+// The network id to pass to android_setprocnetwork before downloading.
+// This can be used to zero-rate OTA traffic by sending it over the correct
+// network.
+static constexpr const auto& kPayloadPropertyNetworkId = "NETWORK_ID";
+// Set "SWITCH_SLOT_ON_REBOOT=0" to skip marking the updated partitions active.
+// The default is 1 (always switch slot if update succeeded).
+static constexpr const auto& kPayloadPropertySwitchSlotOnReboot =
+    "SWITCH_SLOT_ON_REBOOT";
+// Set "RUN_POST_INSTALL=0" to skip running optional post install.
+// The default is 1 (always run post install).
+static constexpr const auto& kPayloadPropertyRunPostInstall =
+    "RUN_POST_INSTALL";
 
-extern const char kOmahaUpdaterVersion[];
+static constexpr const auto& kOmahaUpdaterVersion = "0.1.0.0";
 
 // X-Goog-Update headers.
-extern const char kXGoogleUpdateInteractivity[];
-extern const char kXGoogleUpdateAppId[];
-extern const char kXGoogleUpdateUpdater[];
-extern const char kXGoogleUpdateSessionId[];
+// X-Goog-Update headers.
+static constexpr const auto& kXGoogleUpdateInteractivity =
+    "X-Goog-Update-Interactivity";
+static constexpr const auto& kXGoogleUpdateAppId = "X-Goog-Update-AppId";
+static constexpr const auto& kXGoogleUpdateUpdater = "X-Goog-Update-Updater";
+static constexpr const auto& kXGoogleUpdateSessionId = "X-Goog-SessionId";
 
 // A download source is any combination of protocol and server (that's of
 // interest to us when looking at UMA metrics) using which we may download
diff --git a/common/dynamic_partition_control_interface.h b/common/dynamic_partition_control_interface.h
index d5e1d8d..a5be6e1 100644
--- a/common/dynamic_partition_control_interface.h
+++ b/common/dynamic_partition_control_interface.h
@@ -39,7 +39,7 @@
 
 struct PartitionDevice {
   std::string rw_device_path;
-  std::string mountable_device_path;
+  std::string readonly_device_path;
   bool is_dynamic;
 };
 
diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h
index fc7839d..79e2139 100644
--- a/common/fake_boot_control.h
+++ b/common/fake_boot_control.h
@@ -137,7 +137,7 @@
     PartitionDevice device;
     device.is_dynamic = false;
     device.rw_device_path = device_path->second;
-    device.mountable_device_path = device.rw_device_path;
+    device.readonly_device_path = device.rw_device_path;
     return device;
   }
 
diff --git a/common/fake_prefs.cc b/common/fake_prefs.cc
index ea6ea60..e87e0ec 100644
--- a/common/fake_prefs.cc
+++ b/common/fake_prefs.cc
@@ -28,7 +28,7 @@
 
 namespace {
 
-void CheckNotNull(const string& key, void* ptr) {
+void CheckNotNull(std::string_view key, void* ptr) {
   EXPECT_NE(nullptr, ptr) << "Called Get*() for key \"" << key
                           << "\" with a null parameter.";
 }
@@ -63,41 +63,41 @@
 bool FakePrefs::PrefValue::*const FakePrefs::PrefConsts<bool>::member =
     &FakePrefs::PrefValue::as_bool;
 
-bool FakePrefs::GetString(const string& key, string* value) const {
+bool FakePrefs::GetString(std::string_view key, string* value) const {
   return GetValue(key, value);
 }
 
-bool FakePrefs::SetString(const string& key, std::string_view value) {
+bool FakePrefs::SetString(std::string_view key, std::string_view value) {
   SetValue(key, std::string(value));
   return true;
 }
 
-bool FakePrefs::GetInt64(const string& key, int64_t* value) const {
+bool FakePrefs::GetInt64(std::string_view key, int64_t* value) const {
   return GetValue(key, value);
 }
 
-bool FakePrefs::SetInt64(const string& key, const int64_t value) {
+bool FakePrefs::SetInt64(std::string_view key, const int64_t value) {
   SetValue(key, value);
   return true;
 }
 
-bool FakePrefs::GetBoolean(const string& key, bool* value) const {
+bool FakePrefs::GetBoolean(std::string_view key, bool* value) const {
   return GetValue(key, value);
 }
 
-bool FakePrefs::SetBoolean(const string& key, const bool value) {
+bool FakePrefs::SetBoolean(std::string_view key, const bool value) {
   SetValue(key, value);
   return true;
 }
 
-bool FakePrefs::Exists(const string& key) const {
+bool FakePrefs::Exists(std::string_view key) const {
   return values_.find(key) != values_.end();
 }
 
-bool FakePrefs::Delete(const string& key) {
+bool FakePrefs::Delete(std::string_view key) {
   if (values_.find(key) == values_.end())
     return false;
-  values_.erase(key);
+  values_.erase(std::string{key});
   const auto observers_for_key = observers_.find(key);
   if (observers_for_key != observers_.end()) {
     std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
@@ -107,7 +107,7 @@
   return true;
 }
 
-bool FakePrefs::Delete(const string& key, const vector<string>& nss) {
+bool FakePrefs::Delete(std::string_view key, const vector<string>& nss) {
   bool success = Delete(key);
   for (const auto& ns : nss) {
     vector<string> ns_keys;
@@ -123,7 +123,7 @@
   return success;
 }
 
-bool FakePrefs::GetSubKeys(const string& ns, vector<string>* keys) const {
+bool FakePrefs::GetSubKeys(std::string_view ns, vector<string>* keys) const {
   for (const auto& pr : values_)
     if (pr.first.compare(0, ns.length(), ns) == 0)
       keys->push_back(pr.first);
@@ -142,7 +142,7 @@
   return "Unknown";
 }
 
-void FakePrefs::CheckKeyType(const string& key, PrefType type) const {
+void FakePrefs::CheckKeyType(std::string_view key, PrefType type) const {
   auto it = values_.find(key);
   EXPECT_TRUE(it == values_.end() || it->second.type == type)
       << "Key \"" << key << "\" if defined as " << GetTypeName(it->second.type)
@@ -150,10 +150,11 @@
 }
 
 template <typename T>
-void FakePrefs::SetValue(const string& key, T value) {
+void FakePrefs::SetValue(std::string_view key, T value) {
+  std::string str_key{key};
   CheckKeyType(key, PrefConsts<T>::type);
-  values_[key].type = PrefConsts<T>::type;
-  values_[key].value.*(PrefConsts<T>::member) = std::move(value);
+  values_[str_key].type = PrefConsts<T>::type;
+  values_[str_key].value.*(PrefConsts<T>::member) = std::move(value);
   const auto observers_for_key = observers_.find(key);
   if (observers_for_key != observers_.end()) {
     std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
@@ -163,7 +164,7 @@
 }
 
 template <typename T>
-bool FakePrefs::GetValue(const string& key, T* value) const {
+bool FakePrefs::GetValue(std::string_view key, T* value) const {
   CheckKeyType(key, PrefConsts<T>::type);
   auto it = values_.find(key);
   if (it == values_.end())
@@ -173,12 +174,14 @@
   return true;
 }
 
-void FakePrefs::AddObserver(const string& key, ObserverInterface* observer) {
-  observers_[key].push_back(observer);
+void FakePrefs::AddObserver(std::string_view key, ObserverInterface* observer) {
+  observers_[string{key}].push_back(observer);
 }
 
-void FakePrefs::RemoveObserver(const string& key, ObserverInterface* observer) {
-  std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+void FakePrefs::RemoveObserver(std::string_view key,
+                               ObserverInterface* observer) {
+  string str_key{key};
+  std::vector<ObserverInterface*>& observers_for_key = observers_[str_key];
   auto observer_it =
       std::find(observers_for_key.begin(), observers_for_key.end(), observer);
   EXPECT_NE(observer_it, observers_for_key.end())
@@ -186,7 +189,7 @@
   if (observer_it != observers_for_key.end())
     observers_for_key.erase(observer_it);
   if (observers_for_key.empty())
-    observers_.erase(key);
+    observers_.erase(str_key);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/common/fake_prefs.h b/common/fake_prefs.h
index 430c291..7ae9fb9 100644
--- a/common/fake_prefs.h
+++ b/common/fake_prefs.h
@@ -17,6 +17,7 @@
 #ifndef UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
 #define UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
 
+#include <functional>
 #include <map>
 #include <string>
 #include <string_view>
@@ -40,24 +41,23 @@
   ~FakePrefs();
 
   // PrefsInterface methods.
-  bool GetString(const std::string& key, std::string* value) const override;
-  bool SetString(const std::string& key, std::string_view value) override;
-  bool GetInt64(const std::string& key, int64_t* value) const override;
-  bool SetInt64(const std::string& key, const int64_t value) override;
-  bool GetBoolean(const std::string& key, bool* value) const override;
-  bool SetBoolean(const std::string& key, const bool value) override;
+  bool GetString(std::string_view key, std::string* value) const override;
+  bool SetString(std::string_view key, std::string_view value) override;
+  bool GetInt64(std::string_view key, int64_t* value) const override;
+  bool SetInt64(std::string_view key, const int64_t value) override;
+  bool GetBoolean(std::string_view key, bool* value) const override;
+  bool SetBoolean(std::string_view key, const bool value) override;
 
-  bool Exists(const std::string& key) const override;
-  bool Delete(const std::string& key) override;
-  bool Delete(const std::string& key,
+  bool Exists(std::string_view key) const override;
+  bool Delete(std::string_view key) override;
+  bool Delete(std::string_view key,
               const std::vector<std::string>& nss) override;
 
-  bool GetSubKeys(const std::string& ns,
+  bool GetSubKeys(std::string_view ns,
                   std::vector<std::string>* keys) const override;
 
-  void AddObserver(const std::string& key,
-                   ObserverInterface* observer) override;
-  void RemoveObserver(const std::string& key,
+  void AddObserver(std::string_view key, ObserverInterface* observer) override;
+  void RemoveObserver(std::string_view key,
                       ObserverInterface* observer) override;
 
  private:
@@ -92,24 +92,25 @@
   static std::string GetTypeName(PrefType type);
 
   // Checks that the |key| is either not present or has the given |type|.
-  void CheckKeyType(const std::string& key, PrefType type) const;
+  void CheckKeyType(std::string_view key, PrefType type) const;
 
   // Helper function to set a value of the passed |key|. It sets the type based
   // on the template parameter T.
   template <typename T>
-  void SetValue(const std::string& key, T value);
+  void SetValue(std::string_view key, T value);
 
   // Helper function to get a value from the map checking for invalid calls.
   // The function fails the test if you attempt to read a value  defined as a
   // different type. Returns whether the get succeeded.
   template <typename T>
-  bool GetValue(const std::string& key, T* value) const;
+  bool GetValue(std::string_view key, T* value) const;
 
   // Container for all the key/value pairs.
-  std::map<std::string, PrefTypeValue> values_;
+  std::map<std::string, PrefTypeValue, std::less<>> values_;
 
   // The registered observers watching for changes.
-  std::map<std::string, std::vector<ObserverInterface*>> observers_;
+  std::map<std::string, std::vector<ObserverInterface*>, std::less<>>
+      observers_;
 
   DISALLOW_COPY_AND_ASSIGN(FakePrefs);
 };
diff --git a/common/hash_calculator.cc b/common/hash_calculator.cc
index d010a53..60812d5 100644
--- a/common/hash_calculator.cc
+++ b/common/hash_calculator.cc
@@ -95,6 +95,11 @@
   return RawHashOfBytes(data.data(), data.size(), out_hash);
 }
 
+bool HashCalculator::RawHashOfFile(const string& name, brillo::Blob* out_hash) {
+  const auto file_size = utils::FileSize(name);
+  return RawHashOfFile(name, file_size, out_hash) == file_size;
+}
+
 off_t HashCalculator::RawHashOfFile(const string& name,
                                     off_t length,
                                     brillo::Blob* out_hash) {
diff --git a/common/hash_calculator.h b/common/hash_calculator.h
index b7e4d86..4426128 100644
--- a/common/hash_calculator.h
+++ b/common/hash_calculator.h
@@ -75,6 +75,7 @@
   static off_t RawHashOfFile(const std::string& name,
                              off_t length,
                              brillo::Blob* out_hash);
+  static bool RawHashOfFile(const std::string& name, brillo::Blob* out_hash);
 
  private:
   // If non-empty, the final raw hash. Will only be set to non-empty when
diff --git a/common/mock_prefs.h b/common/mock_prefs.h
index 49431fb..f308074 100644
--- a/common/mock_prefs.h
+++ b/common/mock_prefs.h
@@ -29,27 +29,24 @@
 
 class MockPrefs : public PrefsInterface {
  public:
-  MOCK_CONST_METHOD2(GetString,
-                     bool(const std::string& key, std::string* value));
-  MOCK_METHOD2(SetString, bool(const std::string& key, std::string_view value));
-  MOCK_CONST_METHOD2(GetInt64, bool(const std::string& key, int64_t* value));
-  MOCK_METHOD2(SetInt64, bool(const std::string& key, const int64_t value));
+  MOCK_CONST_METHOD2(GetString, bool(std::string_view key, std::string* value));
+  MOCK_METHOD2(SetString, bool(std::string_view key, std::string_view value));
+  MOCK_CONST_METHOD2(GetInt64, bool(std::string_view key, int64_t* value));
+  MOCK_METHOD2(SetInt64, bool(std::string_view key, const int64_t value));
 
-  MOCK_CONST_METHOD2(GetBoolean, bool(const std::string& key, bool* value));
-  MOCK_METHOD2(SetBoolean, bool(const std::string& key, const bool value));
+  MOCK_CONST_METHOD2(GetBoolean, bool(std::string_view key, bool* value));
+  MOCK_METHOD2(SetBoolean, bool(std::string_view key, const bool value));
 
-  MOCK_CONST_METHOD1(Exists, bool(const std::string& key));
-  MOCK_METHOD1(Delete, bool(const std::string& key));
+  MOCK_CONST_METHOD1(Exists, bool(std::string_view key));
+  MOCK_METHOD1(Delete, bool(std::string_view key));
   MOCK_METHOD2(Delete,
-               bool(const std::string& key,
-                    const std::vector<std::string>& nss));
+               bool(std::string_view key, const std::vector<std::string>& nss));
 
   MOCK_CONST_METHOD2(GetSubKeys,
-                     bool(const std::string&, std::vector<std::string>*));
+                     bool(std::string_view, std::vector<std::string>*));
 
-  MOCK_METHOD2(AddObserver, void(const std::string& key, ObserverInterface*));
-  MOCK_METHOD2(RemoveObserver,
-               void(const std::string& key, ObserverInterface*));
+  MOCK_METHOD2(AddObserver, void(std::string_view key, ObserverInterface*));
+  MOCK_METHOD2(RemoveObserver, void(std::string_view key, ObserverInterface*));
 };
 
 }  // namespace chromeos_update_engine
diff --git a/common/prefs.cc b/common/prefs.cc
index 1e06be4..f33a8a9 100644
--- a/common/prefs.cc
+++ b/common/prefs.cc
@@ -51,11 +51,11 @@
 
 }  // namespace
 
-bool PrefsBase::GetString(const string& key, string* value) const {
+bool PrefsBase::GetString(const std::string_view key, string* value) const {
   return storage_->GetKey(key, value);
 }
 
-bool PrefsBase::SetString(const string& key, std::string_view value) {
+bool PrefsBase::SetString(std::string_view key, std::string_view value) {
   TEST_AND_RETURN_FALSE(storage_->SetKey(key, value));
   const auto observers_for_key = observers_.find(key);
   if (observers_for_key != observers_.end()) {
@@ -66,7 +66,7 @@
   return true;
 }
 
-bool PrefsBase::GetInt64(const string& key, int64_t* value) const {
+bool PrefsBase::GetInt64(const std::string_view key, int64_t* value) const {
   string str_value;
   if (!GetString(key, &str_value))
     return false;
@@ -75,11 +75,11 @@
   return true;
 }
 
-bool PrefsBase::SetInt64(const string& key, const int64_t value) {
+bool PrefsBase::SetInt64(std::string_view key, const int64_t value) {
   return SetString(key, base::NumberToString(value));
 }
 
-bool PrefsBase::GetBoolean(const string& key, bool* value) const {
+bool PrefsBase::GetBoolean(std::string_view key, bool* value) const {
   string str_value;
   if (!GetString(key, &str_value))
     return false;
@@ -95,15 +95,15 @@
   return false;
 }
 
-bool PrefsBase::SetBoolean(const string& key, const bool value) {
+bool PrefsBase::SetBoolean(std::string_view key, const bool value) {
   return SetString(key, value ? "true" : "false");
 }
 
-bool PrefsBase::Exists(const string& key) const {
+bool PrefsBase::Exists(std::string_view key) const {
   return storage_->KeyExists(key);
 }
 
-bool PrefsBase::Delete(const string& key) {
+bool PrefsBase::Delete(std::string_view key) {
   TEST_AND_RETURN_FALSE(storage_->DeleteKey(key));
   const auto observers_for_key = observers_.find(key);
   if (observers_for_key != observers_.end()) {
@@ -114,7 +114,7 @@
   return true;
 }
 
-bool PrefsBase::Delete(const string& pref_key, const vector<string>& nss) {
+bool PrefsBase::Delete(std::string_view pref_key, const vector<string>& nss) {
   // Delete pref key for platform.
   bool success = Delete(pref_key);
   // Delete pref key in each namespace.
@@ -132,16 +132,18 @@
   return success;
 }
 
-bool PrefsBase::GetSubKeys(const string& ns, vector<string>* keys) const {
+bool PrefsBase::GetSubKeys(std::string_view ns, vector<string>* keys) const {
   return storage_->GetSubKeys(ns, keys);
 }
 
-void PrefsBase::AddObserver(const string& key, ObserverInterface* observer) {
-  observers_[key].push_back(observer);
+void PrefsBase::AddObserver(std::string_view key, ObserverInterface* observer) {
+  observers_[std::string{key}].push_back(observer);
 }
 
-void PrefsBase::RemoveObserver(const string& key, ObserverInterface* observer) {
-  std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+void PrefsBase::RemoveObserver(std::string_view key,
+                               ObserverInterface* observer) {
+  std::vector<ObserverInterface*>& observers_for_key =
+      observers_[std::string{key}];
   auto observer_it =
       std::find(observers_for_key.begin(), observers_for_key.end(), observer);
   if (observer_it != observers_for_key.end())
@@ -165,7 +167,7 @@
   return true;
 }
 
-bool Prefs::FileStorage::GetKey(const string& key, string* value) const {
+bool Prefs::FileStorage::GetKey(std::string_view key, string* value) const {
   base::FilePath filename;
   TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
   if (!base::ReadFileToString(filename, value)) {
@@ -174,7 +176,7 @@
   return true;
 }
 
-bool Prefs::FileStorage::GetSubKeys(const string& ns,
+bool Prefs::FileStorage::GetSubKeys(std::string_view ns,
                                     vector<string>* keys) const {
   base::FilePath filename;
   TEST_AND_RETURN_FALSE(GetFileNameForKey(ns, &filename));
@@ -192,7 +194,7 @@
   return true;
 }
 
-bool Prefs::FileStorage::SetKey(const string& key, std::string_view value) {
+bool Prefs::FileStorage::SetKey(std::string_view key, std::string_view value) {
   base::FilePath filename;
   TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
   if (!base::DirectoryExists(filename.DirName())) {
@@ -205,13 +207,13 @@
   return true;
 }
 
-bool Prefs::FileStorage::KeyExists(const string& key) const {
+bool Prefs::FileStorage::KeyExists(std::string_view key) const {
   base::FilePath filename;
   TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
   return base::PathExists(filename);
 }
 
-bool Prefs::FileStorage::DeleteKey(const string& key) {
+bool Prefs::FileStorage::DeleteKey(std::string_view key) {
   base::FilePath filename;
   TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
 #if BASE_VER < 800000
@@ -222,20 +224,21 @@
   return true;
 }
 
-bool Prefs::FileStorage::GetFileNameForKey(const string& key,
+bool Prefs::FileStorage::GetFileNameForKey(std::string_view key,
                                            base::FilePath* filename) const {
   // Allows only non-empty keys containing [A-Za-z0-9_-/].
   TEST_AND_RETURN_FALSE(!key.empty());
   for (char c : key)
     TEST_AND_RETURN_FALSE(base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) ||
                           c == '_' || c == '-' || c == kKeySeparator);
-  *filename = prefs_dir_.Append(key);
+  *filename = prefs_dir_.Append(
+      base::FilePath::StringPieceType(key.data(), key.size()));
   return true;
 }
 
 // MemoryPrefs
 
-bool MemoryPrefs::MemoryStorage::GetKey(const string& key,
+bool MemoryPrefs::MemoryStorage::GetKey(std::string_view key,
                                         string* value) const {
   auto it = values_.find(key);
   if (it == values_.end())
@@ -244,15 +247,13 @@
   return true;
 }
 
-bool MemoryPrefs::MemoryStorage::GetSubKeys(const string& ns,
+bool MemoryPrefs::MemoryStorage::GetSubKeys(std::string_view ns,
                                             vector<string>* keys) const {
-  using value_type = decltype(values_)::value_type;
-  using key_type = decltype(values_)::key_type;
-  auto lower_comp = [](const value_type& pr, const key_type& ns) {
-    return pr.first.substr(0, ns.length()) < ns;
+  auto lower_comp = [](const auto& pr, const auto& ns) {
+    return std::string_view{pr.first.data(), ns.length()} < ns;
   };
-  auto upper_comp = [](const key_type& ns, const value_type& pr) {
-    return ns < pr.first.substr(0, ns.length());
+  auto upper_comp = [](const auto& ns, const auto& pr) {
+    return ns < std::string_view{pr.first.data(), ns.length()};
   };
   auto lower_it =
       std::lower_bound(begin(values_), end(values_), ns, lower_comp);
@@ -262,17 +263,17 @@
   return true;
 }
 
-bool MemoryPrefs::MemoryStorage::SetKey(const string& key,
+bool MemoryPrefs::MemoryStorage::SetKey(std::string_view key,
                                         std::string_view value) {
-  values_[key] = value;
+  values_[std::string{key}] = value;
   return true;
 }
 
-bool MemoryPrefs::MemoryStorage::KeyExists(const string& key) const {
+bool MemoryPrefs::MemoryStorage::KeyExists(std::string_view key) const {
   return values_.find(key) != values_.end();
 }
 
-bool MemoryPrefs::MemoryStorage::DeleteKey(const string& key) {
+bool MemoryPrefs::MemoryStorage::DeleteKey(std::string_view key) {
   auto it = values_.find(key);
   if (it != values_.end())
     values_.erase(it);
diff --git a/common/prefs.h b/common/prefs.h
index 93477dd..c3105c6 100644
--- a/common/prefs.h
+++ b/common/prefs.h
@@ -17,6 +17,7 @@
 #ifndef UPDATE_ENGINE_COMMON_PREFS_H_
 #define UPDATE_ENGINE_COMMON_PREFS_H_
 
+#include <functional>
 #include <map>
 #include <string>
 #include <string_view>
@@ -41,23 +42,23 @@
 
     // Get the key named |key| and store its value in the referenced |value|.
     // Returns whether the operation succeeded.
-    virtual bool GetKey(const std::string& key, std::string* value) const = 0;
+    virtual bool GetKey(std::string_view key, std::string* value) const = 0;
 
     // Get the keys stored within the namespace. If there are no keys in the
     // namespace, |keys| will be empty. Returns whether the operation succeeded.
-    virtual bool GetSubKeys(const std::string& ns,
+    virtual bool GetSubKeys(std::string_view ns,
                             std::vector<std::string>* keys) const = 0;
 
     // Set the value of the key named |key| to |value| regardless of the
     // previous value. Returns whether the operation succeeded.
-    virtual bool SetKey(const std::string& key, std::string_view value) = 0;
+    virtual bool SetKey(std::string_view key, std::string_view value) = 0;
 
     // Returns whether the key named |key| exists.
-    virtual bool KeyExists(const std::string& key) const = 0;
+    virtual bool KeyExists(std::string_view key) const = 0;
 
     // Deletes the value associated with the key name |key|. Returns whether the
     // key was deleted.
-    virtual bool DeleteKey(const std::string& key) = 0;
+    virtual bool DeleteKey(std::string_view key) = 0;
 
    private:
     DISALLOW_COPY_AND_ASSIGN(StorageInterface);
@@ -66,29 +67,29 @@
   explicit PrefsBase(StorageInterface* storage) : storage_(storage) {}
 
   // PrefsInterface methods.
-  bool GetString(const std::string& key, std::string* value) const override;
-  bool SetString(const std::string& key, std::string_view value) override;
-  bool GetInt64(const std::string& key, int64_t* value) const override;
-  bool SetInt64(const std::string& key, const int64_t value) override;
-  bool GetBoolean(const std::string& key, bool* value) const override;
-  bool SetBoolean(const std::string& key, const bool value) override;
+  bool GetString(std::string_view key, std::string* value) const override;
+  bool SetString(std::string_view key, std::string_view value) override;
+  bool GetInt64(std::string_view key, int64_t* value) const override;
+  bool SetInt64(std::string_view key, const int64_t value) override;
+  bool GetBoolean(std::string_view key, bool* value) const override;
+  bool SetBoolean(std::string_view key, const bool value) override;
 
-  bool Exists(const std::string& key) const override;
-  bool Delete(const std::string& key) override;
-  bool Delete(const std::string& pref_key,
+  bool Exists(std::string_view key) const override;
+  bool Delete(std::string_view key) override;
+  bool Delete(std::string_view pref_key,
               const std::vector<std::string>& nss) override;
 
-  bool GetSubKeys(const std::string& ns,
+  bool GetSubKeys(std::string_view ns,
                   std::vector<std::string>* keys) const override;
 
-  void AddObserver(const std::string& key,
-                   ObserverInterface* observer) override;
-  void RemoveObserver(const std::string& key,
+  void AddObserver(std::string_view key, ObserverInterface* observer) override;
+  void RemoveObserver(std::string_view key,
                       ObserverInterface* observer) override;
 
  private:
   // The registered observers watching for changes.
-  std::map<std::string, std::vector<ObserverInterface*>> observers_;
+  std::map<std::string, std::vector<ObserverInterface*>, std::less<>>
+      observers_;
 
   // The concrete implementation of the storage used for the keys.
   StorageInterface* storage_;
@@ -121,12 +122,12 @@
     bool Init(const base::FilePath& prefs_dir);
 
     // PrefsBase::StorageInterface overrides.
-    bool GetKey(const std::string& key, std::string* value) const override;
-    bool GetSubKeys(const std::string& ns,
+    bool GetKey(std::string_view key, std::string* value) const override;
+    bool GetSubKeys(std::string_view ns,
                     std::vector<std::string>* keys) const override;
-    bool SetKey(const std::string& key, std::string_view value) override;
-    bool KeyExists(const std::string& key) const override;
-    bool DeleteKey(const std::string& key) override;
+    bool SetKey(std::string_view key, std::string_view value) override;
+    bool KeyExists(std::string_view key) const override;
+    bool DeleteKey(std::string_view key) override;
 
    private:
     FRIEND_TEST(PrefsTest, GetFileNameForKey);
@@ -135,7 +136,7 @@
 
     // Sets |filename| to the full path to the file containing the data
     // associated with |key|. Returns true on success, false otherwise.
-    bool GetFileNameForKey(const std::string& key,
+    bool GetFileNameForKey(std::string_view key,
                            base::FilePath* filename) const;
 
     // Preference store directory.
@@ -161,16 +162,16 @@
     MemoryStorage() = default;
 
     // PrefsBase::StorageInterface overrides.
-    bool GetKey(const std::string& key, std::string* value) const override;
-    bool GetSubKeys(const std::string& ns,
+    bool GetKey(std::string_view, std::string* value) const override;
+    bool GetSubKeys(std::string_view ns,
                     std::vector<std::string>* keys) const override;
-    bool SetKey(const std::string& key, std::string_view value) override;
-    bool KeyExists(const std::string& key) const override;
-    bool DeleteKey(const std::string& key) override;
+    bool SetKey(std::string_view key, std::string_view value) override;
+    bool KeyExists(std::string_view key) const override;
+    bool DeleteKey(std::string_view key) override;
 
    private:
     // The std::map holding the values in memory.
-    std::map<std::string, std::string> values_;
+    std::map<std::string, std::string, std::less<>> values_;
   };
 
   // The concrete memory storage implementation.
diff --git a/common/prefs_interface.h b/common/prefs_interface.h
index e773a35..69ccf68 100644
--- a/common/prefs_interface.h
+++ b/common/prefs_interface.h
@@ -37,10 +37,10 @@
     virtual ~ObserverInterface() = default;
 
     // Called when the value is set for the observed |key|.
-    virtual void OnPrefSet(const std::string& key) = 0;
+    virtual void OnPrefSet(std::string_view key) = 0;
 
     // Called when the observed |key| is deleted.
-    virtual void OnPrefDeleted(const std::string& key) = 0;
+    virtual void OnPrefDeleted(std::string_view key) = 0;
   };
 
   virtual ~PrefsInterface() = default;
@@ -48,61 +48,61 @@
   // Gets a string |value| associated with |key|. Returns true on
   // success, false on failure (including when the |key| is not
   // present in the store).
-  virtual bool GetString(const std::string& key, std::string* value) const = 0;
+  virtual bool GetString(std::string_view key, std::string* value) const = 0;
 
   // Associates |key| with a string |value|. Returns true on success,
   // false otherwise.
-  virtual bool SetString(const std::string& key, std::string_view value) = 0;
+  virtual bool SetString(std::string_view key, std::string_view value) = 0;
 
   // Gets an int64_t |value| associated with |key|. Returns true on
   // success, false on failure (including when the |key| is not
   // present in the store).
-  virtual bool GetInt64(const std::string& key, int64_t* value) const = 0;
+  virtual bool GetInt64(std::string_view key, int64_t* value) const = 0;
 
   // Associates |key| with an int64_t |value|. Returns true on success,
   // false otherwise.
-  virtual bool SetInt64(const std::string& key, const int64_t value) = 0;
+  virtual bool SetInt64(std::string_view key, const int64_t value) = 0;
 
   // Gets a boolean |value| associated with |key|. Returns true on
   // success, false on failure (including when the |key| is not
   // present in the store).
-  virtual bool GetBoolean(const std::string& key, bool* value) const = 0;
+  virtual bool GetBoolean(std::string_view key, bool* value) const = 0;
 
   // Associates |key| with a boolean |value|. Returns true on success,
   // false otherwise.
-  virtual bool SetBoolean(const std::string& key, const bool value) = 0;
+  virtual bool SetBoolean(std::string_view key, const bool value) = 0;
 
   // Returns true if the setting exists (i.e. a file with the given key
   // exists in the prefs directory)
-  virtual bool Exists(const std::string& key) const = 0;
+  virtual bool Exists(std::string_view key) const = 0;
 
   // Returns true if successfully deleted the file corresponding to
   // this key. Calling with non-existent keys does nothing.
-  virtual bool Delete(const std::string& key) = 0;
+  virtual bool Delete(std::string_view key) = 0;
 
   // Deletes the pref key from platform and given namespace subdirectories.
   // Keys are matched against end of pref keys in each namespace.
   // Returns true if all deletes were successful.
-  virtual bool Delete(const std::string& pref_key,
+  virtual bool Delete(std::string_view pref_key,
                       const std::vector<std::string>& nss) = 0;
 
   // Creates a key which is part of a sub preference.
   static std::string CreateSubKey(const std::vector<std::string>& ns_with_key);
 
   // Returns a list of keys within the namespace.
-  virtual bool GetSubKeys(const std::string& ns,
+  virtual bool GetSubKeys(std::string_view ns,
                           std::vector<std::string>* keys) const = 0;
 
   // Add an observer to watch whenever the given |key| is modified. The
   // OnPrefSet() and OnPrefDelete() methods will be called whenever any of the
   // Set*() methods or the Delete() method are called on the given key,
   // respectively.
-  virtual void AddObserver(const std::string& key,
+  virtual void AddObserver(std::string_view key,
                            ObserverInterface* observer) = 0;
 
   // Remove an observer added with AddObserver(). The observer won't be called
   // anymore for future Set*() and Delete() method calls.
-  virtual void RemoveObserver(const std::string& key,
+  virtual void RemoveObserver(std::string_view key,
                               ObserverInterface* observer) = 0;
 
  protected:
diff --git a/common/prefs_unittest.cc b/common/prefs_unittest.cc
index a5f46e5..cef6d44 100644
--- a/common/prefs_unittest.cc
+++ b/common/prefs_unittest.cc
@@ -507,8 +507,8 @@
 
 class MockPrefsObserver : public PrefsInterface::ObserverInterface {
  public:
-  MOCK_METHOD1(OnPrefSet, void(const string&));
-  MOCK_METHOD1(OnPrefDeleted, void(const string& key));
+  MOCK_METHOD1(OnPrefSet, void(std::string_view));
+  MOCK_METHOD1(OnPrefDeleted, void(std::string_view));
 };
 
 TEST_F(PrefsTest, ObserversCalled) {
diff --git a/common/scoped_task_id.h b/common/scoped_task_id.h
new file mode 100644
index 0000000..91a2986
--- /dev/null
+++ b/common/scoped_task_id.h
@@ -0,0 +1,123 @@
+//
+// Copyright (C) 2021 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.
+//
+
+#ifndef UPDATE_ENGINE_SCOPED_TASK_ID_H_
+#define UPDATE_ENGINE_SCOPED_TASK_ID_H_
+
+#include <type_traits>
+#include <utility>
+
+#include <base/bind.h>
+#include <brillo/message_loops/message_loop.h>
+
+namespace chromeos_update_engine {
+
+// This class provides unique_ptr like semantic for |MessageLoop::TaskId|, when
+// instance of this class goes out of scope, underlying task will be cancelled.
+class ScopedTaskId {
+  using MessageLoop = brillo::MessageLoop;
+
+ public:
+  // Move only type similar to unique_ptr.
+  ScopedTaskId(const ScopedTaskId&) = delete;
+  ScopedTaskId& operator=(const ScopedTaskId&) = delete;
+
+  constexpr ScopedTaskId() = default;
+
+  constexpr ScopedTaskId(ScopedTaskId&& other) noexcept {
+    *this = std::move(other);
+  }
+
+  constexpr ScopedTaskId& operator=(ScopedTaskId&& other) noexcept {
+    std::swap(task_id_, other.task_id_);
+    return *this;
+  }
+
+  // Post a callback on current message loop, return true if succeeded, false if
+  // the previous callback hasn't run yet, or scheduling failed at MessageLoop
+  // side.
+  [[nodiscard]] bool PostTask(const base::Location& from_here,
+                              base::OnceClosure&& callback,
+                              base::TimeDelta delay = {}) noexcept {
+    return PostTask<decltype(callback)>(from_here, std::move(callback), delay);
+  }
+  [[nodiscard]] bool PostTask(const base::Location& from_here,
+                              std::function<void()>&& callback,
+                              base::TimeDelta delay = {}) noexcept {
+    return PostTask<decltype(callback)>(from_here, std::move(callback), delay);
+  }
+
+  ~ScopedTaskId() noexcept { Cancel(); }
+
+  // Cancel the underlying managed task, true if cancel successful. False if no
+  // task scheduled or task cancellation failed
+  bool Cancel() noexcept {
+    if (task_id_ != MessageLoop::kTaskIdNull) {
+      if (MessageLoop::current()->CancelTask(task_id_)) {
+        LOG(INFO) << "Cancelled task id " << task_id_;
+        task_id_ = MessageLoop::kTaskIdNull;
+        return true;
+      }
+    }
+    return false;
+  }
+
+  [[nodiscard]] constexpr bool IsScheduled() const noexcept {
+    return task_id_ != MessageLoop::kTaskIdNull;
+  }
+
+  [[nodiscard]] constexpr bool operator==(const ScopedTaskId& other) const
+      noexcept {
+    return other.task_id_ == task_id_;
+  }
+
+  [[nodiscard]] constexpr bool operator<(const ScopedTaskId& other) const
+      noexcept {
+    return task_id_ < other.task_id_;
+  }
+
+ private:
+  template <typename Callable>
+  [[nodiscard]] bool PostTask(const base::Location& from_here,
+                              Callable&& callback,
+                              base::TimeDelta delay) noexcept {
+    if (task_id_ != MessageLoop::kTaskIdNull) {
+      LOG(ERROR) << "Scheduling another task but task id " << task_id_
+                 << " isn't executed yet! This can cause the old task to leak.";
+      return false;
+    }
+    task_id_ = MessageLoop::current()->PostDelayedTask(
+        from_here,
+        base::BindOnce(&ScopedTaskId::ExecuteTask<decltype(callback)>,
+                       base::Unretained(this),
+                       std::move(callback)),
+        delay);
+    return task_id_ != MessageLoop::kTaskIdNull;
+  }
+  template <typename Callable>
+  void ExecuteTask(Callable&& callback) {
+    task_id_ = MessageLoop::kTaskIdNull;
+    if constexpr (std::is_same_v<Callable&&, base::OnceClosure&&>) {
+      std::move(callback).Run();
+    } else {
+      std::move(callback)();
+    }
+  }
+  MessageLoop::TaskId task_id_{MessageLoop::kTaskIdNull};
+};
+}  // namespace chromeos_update_engine
+
+#endif
diff --git a/common/utils.h b/common/utils.h
index 5f6e475..59f236e 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -399,13 +399,19 @@
 
   // If |open_fd| is true, a writable file descriptor will be opened for this
   // file.
-  explicit ScopedTempFile(const std::string& pattern, bool open_fd = false) {
+  // If |truncate_size| is non-zero, truncate file to that size on creation.
+  explicit ScopedTempFile(const std::string& pattern,
+                          bool open_fd = false,
+                          size_t truncate_size = 0) {
     CHECK(utils::MakeTempFile(pattern, &path_, open_fd ? &fd_ : nullptr));
     unlinker_.reset(new ScopedPathUnlinker(path_));
     if (open_fd) {
       CHECK_GE(fd_, 0);
       fd_closer_.reset(new ScopedFdCloser(&fd_));
     }
+    if (truncate_size > 0) {
+      CHECK_EQ(0, truncate(path_.c_str(), truncate_size));
+    }
   }
   virtual ~ScopedTempFile() = default;
 
diff --git a/metrics_utils.cc b/metrics_utils.cc
index 34da5a1..ade024a 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -294,7 +294,7 @@
   return metrics::ConnectionType::kUnknown;
 }
 
-int64_t GetPersistedValue(const std::string& key, PrefsInterface* prefs) {
+int64_t GetPersistedValue(std::string_view key, PrefsInterface* prefs) {
   CHECK(prefs);
   if (!prefs->Exists(key))
     return 0;
diff --git a/metrics_utils.h b/metrics_utils.h
index 3aac4e5..16e9eec 100644
--- a/metrics_utils.h
+++ b/metrics_utils.h
@@ -50,7 +50,7 @@
 
 // Returns the persisted value from prefs for the given key. It also
 // validates that the value returned is non-negative.
-int64_t GetPersistedValue(const std::string& key, PrefsInterface* prefs);
+int64_t GetPersistedValue(std::string_view key, PrefsInterface* prefs);
 
 // Persists the reboot count of the update attempt to |kPrefsNumReboots|.
 void SetNumReboots(int64_t num_reboots, PrefsInterface* prefs);
diff --git a/payload_consumer/bzip_extent_writer.cc b/payload_consumer/bzip_extent_writer.cc
index 0c25c71..26fdc5f 100644
--- a/payload_consumer/bzip_extent_writer.cc
+++ b/payload_consumer/bzip_extent_writer.cc
@@ -29,8 +29,7 @@
   TEST_AND_RETURN(input_buffer_.empty());
 }
 
-bool BzipExtentWriter::Init(FileDescriptorPtr fd,
-                            const RepeatedPtrField<Extent>& extents,
+bool BzipExtentWriter::Init(const RepeatedPtrField<Extent>& extents,
                             uint32_t block_size) {
   // Init bzip2 stream
   int rc = BZ2_bzDecompressInit(&stream_,
@@ -39,7 +38,7 @@
 
   TEST_AND_RETURN_FALSE(rc == BZ_OK);
 
-  return next_->Init(fd, extents, block_size);
+  return next_->Init(extents, block_size);
 }
 
 bool BzipExtentWriter::Write(const void* bytes, size_t count) {
diff --git a/payload_consumer/bzip_extent_writer.h b/payload_consumer/bzip_extent_writer.h
index ec181a7..38c041a 100644
--- a/payload_consumer/bzip_extent_writer.h
+++ b/payload_consumer/bzip_extent_writer.h
@@ -40,8 +40,7 @@
   }
   ~BzipExtentWriter() override;
 
-  bool Init(FileDescriptorPtr fd,
-            const google::protobuf::RepeatedPtrField<Extent>& extents,
+  bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents,
             uint32_t block_size) override;
   bool Write(const void* bytes, size_t count) override;
 
diff --git a/payload_consumer/bzip_extent_writer_unittest.cc b/payload_consumer/bzip_extent_writer_unittest.cc
index b587040..c93545a 100644
--- a/payload_consumer/bzip_extent_writer_unittest.cc
+++ b/payload_consumer/bzip_extent_writer_unittest.cc
@@ -29,7 +29,6 @@
 #include "update_engine/common/utils.h"
 #include "update_engine/payload_generator/extent_ranges.h"
 
-using google::protobuf::RepeatedPtrField;
 using std::min;
 using std::string;
 using std::vector;
@@ -64,9 +63,8 @@
       0x22, 0x9c, 0x28, 0x48, 0x66, 0x61, 0xb8, 0xea, 0x00,
   };
 
-  BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>());
-  EXPECT_TRUE(
-      bzip_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+  BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>(fd_));
+  EXPECT_TRUE(bzip_writer.Init({extents.begin(), extents.end()}, kBlockSize));
   EXPECT_TRUE(bzip_writer.Write(test, sizeof(test)));
 
   brillo::Blob buf;
@@ -97,9 +95,8 @@
 
   vector<Extent> extents = {ExtentForBytes(kBlockSize, 0, kDecompressedLength)};
 
-  BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>());
-  EXPECT_TRUE(
-      bzip_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+  BzipExtentWriter bzip_writer(std::make_unique<DirectExtentWriter>(fd_));
+  EXPECT_TRUE(bzip_writer.Init({extents.begin(), extents.end()}, kBlockSize));
 
   brillo::Blob original_compressed_data = compressed_data;
   for (brillo::Blob::size_type i = 0; i < compressed_data.size();
diff --git a/payload_consumer/cow_writer_file_descriptor.cc b/payload_consumer/cow_writer_file_descriptor.cc
index d8c7afb..2de6664 100644
--- a/payload_consumer/cow_writer_file_descriptor.cc
+++ b/payload_consumer/cow_writer_file_descriptor.cc
@@ -28,7 +28,10 @@
 CowWriterFileDescriptor::CowWriterFileDescriptor(
     std::unique_ptr<android::snapshot::ISnapshotWriter> cow_writer)
     : cow_writer_(std::move(cow_writer)),
-      cow_reader_(cow_writer_->OpenReader()) {}
+      cow_reader_(cow_writer_->OpenReader()) {
+  CHECK_NE(cow_writer_, nullptr);
+  CHECK_NE(cow_reader_, nullptr);
+}
 
 bool CowWriterFileDescriptor::Open(const char* path, int flags, mode_t mode) {
   LOG(ERROR) << "CowWriterFileDescriptor doesn't support Open()";
@@ -113,7 +116,17 @@
 
 bool CowWriterFileDescriptor::Close() {
   if (cow_writer_) {
-    TEST_AND_RETURN_FALSE(cow_writer_->Finalize());
+    // b/186196758
+    // When calling
+    // InitializeAppend(kEndOfInstall), the SnapshotWriter only reads up to the
+    // given label. But OpenReader() completely disregards the resume label and
+    // reads all ops. Therefore, update_engine sees the verity data. However,
+    // when calling SnapshotWriter::Finalize(), data after resume label are
+    // discarded, therefore verity data is gone. To prevent phantom reads, don't
+    // call Finalize() unless we actually write something.
+    if (dirty_) {
+      TEST_AND_RETURN_FALSE(cow_writer_->Finalize());
+    }
     cow_writer_ = nullptr;
   }
   if (cow_reader_) {
diff --git a/payload_consumer/cow_writer_file_descriptor_unittest.cc b/payload_consumer/cow_writer_file_descriptor_unittest.cc
new file mode 100644
index 0000000..c596e3b
--- /dev/null
+++ b/payload_consumer/cow_writer_file_descriptor_unittest.cc
@@ -0,0 +1,120 @@
+//
+// Copyright (C) 2021 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/payload_consumer/cow_writer_file_descriptor.h"
+
+#include <cstring>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <libsnapshot/snapshot_writer.h>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+constexpr size_t BLOCK_SIZE = 4096;
+constexpr size_t PARTITION_SIZE = BLOCK_SIZE * 10;
+
+using android::base::unique_fd;
+using android::snapshot::CompressedSnapshotWriter;
+using android::snapshot::CowOptions;
+using android::snapshot::ISnapshotWriter;
+
+class CowWriterFileDescriptorUnittest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    ASSERT_EQ(ftruncate64(cow_device_file_.fd(), PARTITION_SIZE), 0)
+        << "Failed to truncate cow_device file to " << PARTITION_SIZE
+        << strerror(errno);
+    ASSERT_EQ(ftruncate64(cow_source_file_.fd(), PARTITION_SIZE), 0)
+        << "Failed to truncate cow_source file to " << PARTITION_SIZE
+        << strerror(errno);
+  }
+
+  std::unique_ptr<CompressedSnapshotWriter> GetCowWriter() {
+    const CowOptions options{.block_size = BLOCK_SIZE, .compression = "gz"};
+    auto snapshot_writer = std::make_unique<CompressedSnapshotWriter>(options);
+    int fd = open(cow_device_file_.path().c_str(), O_RDWR);
+    EXPECT_NE(fd, -1);
+    EXPECT_TRUE(snapshot_writer->SetCowDevice(unique_fd{fd}));
+    snapshot_writer->SetSourceDevice(cow_source_file_.path());
+    return snapshot_writer;
+  }
+  CowWriterFileDescriptor GetCowFd() {
+    auto cow_writer = GetCowWriter();
+    return CowWriterFileDescriptor{std::move(cow_writer)};
+  }
+
+  ScopedTempFile cow_source_file_{"cow_source.XXXXXX", true};
+  ScopedTempFile cow_device_file_{"cow_device.XXXXXX", true};
+};
+
+TEST_F(CowWriterFileDescriptorUnittest, ReadAfterWrite) {
+  std::vector<unsigned char> buffer;
+  buffer.resize(BLOCK_SIZE);
+  std::fill(buffer.begin(), buffer.end(), 234);
+
+  std::vector<unsigned char> verity_data;
+  verity_data.resize(BLOCK_SIZE);
+  std::fill(verity_data.begin(), verity_data.end(), 0xAA);
+
+  auto cow_writer = GetCowWriter();
+  cow_writer->Initialize();
+
+  // Simulate Writing InstallOp data
+  ASSERT_TRUE(cow_writer->AddRawBlocks(0, buffer.data(), buffer.size()));
+  ASSERT_TRUE(cow_writer->AddZeroBlocks(1, 2));
+  ASSERT_TRUE(cow_writer->AddCopy(3, 1));
+  // Fake label to simulate "end of install"
+  ASSERT_TRUE(cow_writer->AddLabel(23));
+  ASSERT_TRUE(
+      cow_writer->AddRawBlocks(4, verity_data.data(), verity_data.size()));
+  ASSERT_TRUE(cow_writer->Finalize());
+
+  cow_writer = GetCowWriter();
+  ASSERT_NE(nullptr, cow_writer);
+  ASSERT_TRUE(cow_writer->InitializeAppend(23));
+  auto cow_fd =
+      std::make_unique<CowWriterFileDescriptor>(std::move(cow_writer));
+
+  ASSERT_EQ((ssize_t)BLOCK_SIZE * 4, cow_fd->Seek(BLOCK_SIZE * 4, SEEK_SET));
+  std::vector<unsigned char> read_back(4096);
+  ASSERT_EQ((ssize_t)read_back.size(),
+            cow_fd->Read(read_back.data(), read_back.size()));
+  ASSERT_EQ(verity_data, read_back);
+
+  // Since we didn't write anything to this instance of cow_fd, destructor
+  // should not call Finalize(). As finalize will drop ops after resume label,
+  // causing subsequent reads to fail.
+  cow_writer = GetCowWriter();
+  ASSERT_NE(nullptr, cow_writer);
+  ASSERT_TRUE(cow_writer->InitializeAppend(23));
+  cow_fd = std::make_unique<CowWriterFileDescriptor>(std::move(cow_writer));
+
+  ASSERT_EQ((ssize_t)BLOCK_SIZE * 4, cow_fd->Seek(BLOCK_SIZE * 4, SEEK_SET));
+  ASSERT_EQ((ssize_t)read_back.size(),
+            cow_fd->Read(read_back.data(), read_back.size()));
+  ASSERT_EQ(verity_data, read_back)
+      << "Could not read verity data afeter InitializeAppend() => Read() => "
+         "InitializeAppend() sequence. If no writes happened while CowWriterFd "
+         "is open, Finalize() should not be called.";
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
index 840ecf6..27f846e 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -654,7 +654,9 @@
   brillo::Blob payload_data =
       GeneratePayload(brillo::Blob(), {aop}, false, &old_part);
 
-  EXPECT_EQ(actual_data, ApplyPayload(payload_data, source.path(), false));
+  // When source hash mismatches, PartitionWriter will refuse to write anything.
+  // Therefore we should expect an empty blob.
+  EXPECT_EQ(brillo::Blob{}, ApplyPayload(payload_data, source.path(), false));
 }
 
 TEST_F(DeltaPerformerTest, ExtentsToByteStringTest) {
diff --git a/payload_consumer/extent_writer.h b/payload_consumer/extent_writer.h
index 9e53561..8b1b532 100644
--- a/payload_consumer/extent_writer.h
+++ b/payload_consumer/extent_writer.h
@@ -38,8 +38,7 @@
   virtual ~ExtentWriter() = default;
 
   // Returns true on success.
-  virtual bool Init(FileDescriptorPtr fd,
-                    const google::protobuf::RepeatedPtrField<Extent>& extents,
+  virtual bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents,
                     uint32_t block_size) = 0;
 
   // Returns true on success.
@@ -51,13 +50,11 @@
 
 class DirectExtentWriter : public ExtentWriter {
  public:
-  DirectExtentWriter() = default;
+  explicit DirectExtentWriter(FileDescriptorPtr fd) : fd_(fd) {}
   ~DirectExtentWriter() override = default;
 
-  bool Init(FileDescriptorPtr fd,
-            const google::protobuf::RepeatedPtrField<Extent>& extents,
+  bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents,
             uint32_t block_size) override {
-    fd_ = fd;
     block_size_ = block_size;
     extents_ = extents;
     cur_extent_ = extents_.begin();
diff --git a/payload_consumer/extent_writer_unittest.cc b/payload_consumer/extent_writer_unittest.cc
index afebb1a..5c67d3e 100644
--- a/payload_consumer/extent_writer_unittest.cc
+++ b/payload_consumer/extent_writer_unittest.cc
@@ -65,9 +65,8 @@
 TEST_F(ExtentWriterTest, SimpleTest) {
   vector<Extent> extents = {ExtentForRange(1, 1)};
   const string bytes = "1234";
-  DirectExtentWriter direct_writer;
-  EXPECT_TRUE(
-      direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+  DirectExtentWriter direct_writer{fd_};
+  EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize));
   EXPECT_TRUE(direct_writer.Write(bytes.data(), bytes.size()));
 
   EXPECT_EQ(static_cast<off_t>(kBlockSize + bytes.size()),
@@ -84,9 +83,8 @@
 
 TEST_F(ExtentWriterTest, ZeroLengthTest) {
   vector<Extent> extents = {ExtentForRange(1, 1)};
-  DirectExtentWriter direct_writer;
-  EXPECT_TRUE(
-      direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+  DirectExtentWriter direct_writer{fd_};
+  EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize));
   EXPECT_TRUE(direct_writer.Write(nullptr, 0));
 }
 
@@ -109,9 +107,8 @@
   brillo::Blob data(kBlockSize * 3);
   test_utils::FillWithData(&data);
 
-  DirectExtentWriter direct_writer;
-  EXPECT_TRUE(
-      direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+  DirectExtentWriter direct_writer{fd_};
+  EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize));
 
   size_t bytes_written = 0;
   while (bytes_written < data.size()) {
@@ -150,9 +147,8 @@
   brillo::Blob data(17);
   test_utils::FillWithData(&data);
 
-  DirectExtentWriter direct_writer;
-  EXPECT_TRUE(
-      direct_writer.Init(fd_, {extents.begin(), extents.end()}, kBlockSize));
+  DirectExtentWriter direct_writer{fd_};
+  EXPECT_TRUE(direct_writer.Init({extents.begin(), extents.end()}, kBlockSize));
 
   size_t bytes_written = 0;
   while (bytes_written < (block_count * kBlockSize)) {
diff --git a/payload_consumer/fake_extent_writer.h b/payload_consumer/fake_extent_writer.h
index 7b2b7ac..680b1b3 100644
--- a/payload_consumer/fake_extent_writer.h
+++ b/payload_consumer/fake_extent_writer.h
@@ -33,8 +33,7 @@
   ~FakeExtentWriter() override = default;
 
   // ExtentWriter overrides.
-  bool Init(FileDescriptorPtr /* fd */,
-            const google::protobuf::RepeatedPtrField<Extent>& /* extents */,
+  bool Init(const google::protobuf::RepeatedPtrField<Extent>& /* extents */,
             uint32_t /* block_size */) override {
     init_called_ = true;
     return true;
diff --git a/payload_consumer/file_descriptor.cc b/payload_consumer/file_descriptor.cc
index 7c69c1b..da76327 100644
--- a/payload_consumer/file_descriptor.cc
+++ b/payload_consumer/file_descriptor.cc
@@ -139,7 +139,9 @@
 }
 
 bool EintrSafeFileDescriptor::Close() {
-  CHECK_GE(fd_, 0);
+  if (fd_ < 0) {
+    return false;
+  }
   // https://stackoverflow.com/questions/705454/does-linux-guarantee-the-contents-of-a-file-is-flushed-to-disc-after-close
   // |close()| doesn't imply |fsync()|, we need to do it manually.
   fsync(fd_);
diff --git a/payload_consumer/file_descriptor_utils.cc b/payload_consumer/file_descriptor_utils.cc
index 846cbd7..9a6a601 100644
--- a/payload_consumer/file_descriptor_utils.cc
+++ b/payload_consumer/file_descriptor_utils.cc
@@ -82,8 +82,8 @@
                         const RepeatedPtrField<Extent>& tgt_extents,
                         uint64_t block_size,
                         brillo::Blob* hash_out) {
-  DirectExtentWriter writer;
-  TEST_AND_RETURN_FALSE(writer.Init(target, tgt_extents, block_size));
+  DirectExtentWriter writer{target};
+  TEST_AND_RETURN_FALSE(writer.Init(tgt_extents, block_size));
   TEST_AND_RETURN_FALSE(utils::BlocksInExtents(src_extents) ==
                         utils::BlocksInExtents(tgt_extents));
   TEST_AND_RETURN_FALSE(
diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc
index 09dc638..22c8e0b 100644
--- a/payload_consumer/filesystem_verifier_action.cc
+++ b/payload_consumer/filesystem_verifier_action.cc
@@ -35,6 +35,7 @@
 #include <brillo/secure_blob.h>
 #include <brillo/streams/file_stream.h>
 
+#include "common/error_code.h"
 #include "payload_generator/delta_diff_generator.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/payload_consumer/file_descriptor.h"
@@ -77,6 +78,7 @@
 
 namespace {
 const off_t kReadFileBufferSize = 128 * 1024;
+constexpr float kVerityProgressPercent = 0.6;
 }  // namespace
 
 void FilesystemVerifierAction::PerformAction() {
@@ -102,7 +104,6 @@
 }
 
 void FilesystemVerifierAction::TerminateProcessing() {
-  brillo::MessageLoop::current()->CancelTask(pending_task_id_);
   cancelled_ = true;
   Cleanup(ErrorCode::kSuccess);  // error code is ignored if canceled_ is true.
 }
@@ -112,6 +113,14 @@
   // This memory is not used anymore.
   buffer_.clear();
 
+  // If we didn't write verity, partitions were maped. Releaase resource now.
+  if (!install_plan_.write_verity &&
+      dynamic_control_->UpdateUsesSnapshotCompression()) {
+    LOG(INFO) << "Not writing verity and VABC is enabled, unmapping all "
+                 "partitions";
+    dynamic_control_->UnmapAllPartitions();
+  }
+
   if (cancelled_)
     return;
   if (code == ErrorCode::kSuccess && HasOutputPipe())
@@ -126,11 +135,42 @@
   }
 }
 
-bool FilesystemVerifierAction::InitializeFdVABC() {
+void FilesystemVerifierAction::UpdatePartitionProgress(double progress) {
+  // We don't consider sizes of each partition. Every partition
+  // has the same length on progress bar.
+  // TODO(b/186087589): Take sizes of each partition into account.
+  UpdateProgress((progress + partition_index_) /
+                 install_plan_.partitions.size());
+}
+
+bool FilesystemVerifierAction::InitializeFdVABC(bool should_write_verity) {
   const InstallPlan::Partition& partition =
       install_plan_.partitions[partition_index_];
 
-  // FilesystemVerifierAction need the read_fd_.
+  if (!should_write_verity) {
+    // In VABC, we cannot map/unmap partitions w/o first closing ALL fds first.
+    // Since this function might be called inside a ScheduledTask, the closure
+    // might have a copy of partition_fd_ when executing this function. Which
+    // means even if we do |partition_fd_.reset()| here, there's a chance that
+    // underlying fd isn't closed until we return. This is unacceptable, we need
+    // to close |partition_fd| right away.
+    if (partition_fd_) {
+      partition_fd_->Close();
+      partition_fd_.reset();
+    }
+    // In VABC, if we are not writing verity, just map all partitions,
+    // and read using regular fd on |postinstall_mount_device| .
+    // All read will go through snapuserd, which provides a consistent
+    // view: device will use snapuserd to read partition during boot.
+    // b/186196758
+    // Call UnmapAllPartitions() first, because if we wrote verity before, these
+    // writes won't be visible to previously opened snapuserd daemon. To ensure
+    // that we will see the most up to date data from partitions, call Unmap()
+    // then Map() to re-spin daemon.
+    dynamic_control_->UnmapAllPartitions();
+    dynamic_control_->MapAllPartitions();
+    return InitializeFd(partition.readonly_target_path);
+  }
   partition_fd_ =
       dynamic_control_->OpenCowFd(partition.name, partition.source_path, true);
   if (!partition_fd_) {
@@ -157,6 +197,112 @@
   return true;
 }
 
+void FilesystemVerifierAction::WriteVerityAndHashPartition(
+    FileDescriptorPtr fd,
+    const off64_t start_offset,
+    const off64_t end_offset,
+    void* buffer,
+    const size_t buffer_size) {
+  if (start_offset >= end_offset) {
+    LOG_IF(WARNING, start_offset > end_offset)
+        << "start_offset is greater than end_offset : " << start_offset << " > "
+        << end_offset;
+    if (!verity_writer_->Finalize(fd, fd)) {
+      LOG(ERROR) << "Failed to write verity data";
+      Cleanup(ErrorCode::kVerityCalculationError);
+      return;
+    }
+    if (dynamic_control_->UpdateUsesSnapshotCompression()) {
+      // Spin up snapuserd to read fs.
+      if (!InitializeFdVABC(false)) {
+        LOG(ERROR) << "Failed to map all partitions";
+        Cleanup(ErrorCode::kFilesystemVerifierError);
+        return;
+      }
+    }
+    HashPartition(partition_fd_, 0, partition_size_, buffer, buffer_size);
+    return;
+  }
+  const auto cur_offset = fd->Seek(start_offset, SEEK_SET);
+  if (cur_offset != start_offset) {
+    PLOG(ERROR) << "Failed to seek to offset: " << start_offset;
+    Cleanup(ErrorCode::kVerityCalculationError);
+    return;
+  }
+  const auto read_size =
+      std::min<size_t>(buffer_size, end_offset - start_offset);
+  const auto bytes_read = fd->Read(buffer, read_size);
+  if (bytes_read < 0 || static_cast<size_t>(bytes_read) != read_size) {
+    PLOG(ERROR) << "Failed to read offset " << start_offset << " expected "
+                << read_size << " bytes, actual: " << bytes_read;
+    Cleanup(ErrorCode::kVerityCalculationError);
+    return;
+  }
+  if (!verity_writer_->Update(
+          start_offset, static_cast<const uint8_t*>(buffer), read_size)) {
+    LOG(ERROR) << "VerityWriter::Update() failed";
+    Cleanup(ErrorCode::kVerityCalculationError);
+    return;
+  }
+  UpdatePartitionProgress((start_offset + bytes_read) * 1.0f / partition_size_ *
+                          kVerityProgressPercent);
+  CHECK(pending_task_id_.PostTask(
+      FROM_HERE,
+      base::BindOnce(&FilesystemVerifierAction::WriteVerityAndHashPartition,
+                     base::Unretained(this),
+                     fd,
+                     start_offset + bytes_read,
+                     end_offset,
+                     buffer,
+                     buffer_size)));
+}
+
+void FilesystemVerifierAction::HashPartition(FileDescriptorPtr fd,
+                                             const off64_t start_offset,
+                                             const off64_t end_offset,
+                                             void* buffer,
+                                             const size_t buffer_size) {
+  if (start_offset >= end_offset) {
+    LOG_IF(WARNING, start_offset > end_offset)
+        << "start_offset is greater than end_offset : " << start_offset << " > "
+        << end_offset;
+    FinishPartitionHashing();
+    return;
+  }
+  const auto cur_offset = fd->Seek(start_offset, SEEK_SET);
+  if (cur_offset != start_offset) {
+    PLOG(ERROR) << "Failed to seek to offset: " << start_offset;
+    Cleanup(ErrorCode::kFilesystemVerifierError);
+    return;
+  }
+  const auto read_size =
+      std::min<size_t>(buffer_size, end_offset - start_offset);
+  const auto bytes_read = fd->Read(buffer, read_size);
+  if (bytes_read < 0 || static_cast<size_t>(bytes_read) != read_size) {
+    PLOG(ERROR) << "Failed to read offset " << start_offset << " expected "
+                << read_size << " bytes, actual: " << bytes_read;
+    Cleanup(ErrorCode::kFilesystemVerifierError);
+    return;
+  }
+  if (!hasher_->Update(buffer, read_size)) {
+    LOG(ERROR) << "Hasher updated failed on offset" << start_offset;
+    Cleanup(ErrorCode::kFilesystemVerifierError);
+    return;
+  }
+  const auto progress = (start_offset + bytes_read) * 1.0f / partition_size_;
+  UpdatePartitionProgress(progress * (1 - kVerityProgressPercent) +
+                          kVerityProgressPercent);
+  CHECK(pending_task_id_.PostTask(
+      FROM_HERE,
+      base::BindOnce(&FilesystemVerifierAction::HashPartition,
+                     base::Unretained(this),
+                     fd,
+                     start_offset + bytes_read,
+                     end_offset,
+                     buffer,
+                     buffer_size)));
+}
+
 void FilesystemVerifierAction::StartPartitionHashing() {
   if (partition_index_ == install_plan_.partitions.size()) {
     if (!install_plan_.untouched_dynamic_partitions.empty()) {
@@ -178,26 +324,14 @@
   }
   const InstallPlan::Partition& partition =
       install_plan_.partitions[partition_index_];
-  string part_path;
-  switch (verifier_step_) {
-    case VerifierStep::kVerifySourceHash:
-      part_path = partition.source_path;
-      partition_size_ = partition.source_size;
-      break;
-    case VerifierStep::kVerifyTargetHash:
-      part_path = partition.target_path;
-      partition_size_ = partition.target_size;
-      break;
-  }
+  const auto& part_path = GetPartitionPath();
+  partition_size_ = GetPartitionSize();
 
   LOG(INFO) << "Hashing partition " << partition_index_ << " ("
             << partition.name << ") on device " << part_path;
   auto success = false;
-  if (dynamic_control_->UpdateUsesSnapshotCompression() &&
-      verifier_step_ == VerifierStep::kVerifyTargetHash &&
-      dynamic_control_->IsDynamicPartition(partition.name,
-                                           install_plan_.target_slot)) {
-    success = InitializeFdVABC();
+  if (IsVABC(partition)) {
+    success = InitializeFdVABC(ShouldWriteVerity());
   } else {
     if (part_path.empty()) {
       if (partition_size_ == 0) {
@@ -232,17 +366,53 @@
     filesystem_data_end_ = partition.fec_offset;
   }
   if (ShouldWriteVerity()) {
+    LOG(INFO) << "Verity writes enabled on partition " << partition.name;
     if (!verity_writer_->Init(partition)) {
       LOG(INFO) << "Verity writes enabled on partition " << partition.name;
       Cleanup(ErrorCode::kVerityCalculationError);
       return;
     }
+    WriteVerityAndHashPartition(
+        partition_fd_, 0, filesystem_data_end_, buffer_.data(), buffer_.size());
   } else {
     LOG(INFO) << "Verity writes disabled on partition " << partition.name;
+    HashPartition(
+        partition_fd_, 0, partition_size_, buffer_.data(), buffer_.size());
   }
+}
 
-  // Start the first read.
-  ScheduleFileSystemRead();
+bool FilesystemVerifierAction::IsVABC(
+    const InstallPlan::Partition& partition) const {
+  return dynamic_control_->UpdateUsesSnapshotCompression() &&
+         verifier_step_ == VerifierStep::kVerifyTargetHash &&
+         dynamic_control_->IsDynamicPartition(partition.name,
+                                              install_plan_.target_slot);
+}
+
+const std::string& FilesystemVerifierAction::GetPartitionPath() const {
+  const InstallPlan::Partition& partition =
+      install_plan_.partitions[partition_index_];
+  switch (verifier_step_) {
+    case VerifierStep::kVerifySourceHash:
+      return partition.source_path;
+    case VerifierStep::kVerifyTargetHash:
+      if (IsVABC(partition)) {
+        return partition.readonly_target_path;
+      } else {
+        return partition.target_path;
+      }
+  }
+}
+
+size_t FilesystemVerifierAction::GetPartitionSize() const {
+  const InstallPlan::Partition& partition =
+      install_plan_.partitions[partition_index_];
+  switch (verifier_step_) {
+    case VerifierStep::kVerifySourceHash:
+      return partition.source_size;
+    case VerifierStep::kVerifyTargetHash:
+      return partition.target_size;
+  }
 }
 
 bool FilesystemVerifierAction::ShouldWriteVerity() {
@@ -253,106 +423,6 @@
          (partition.hash_tree_size > 0 || partition.fec_size > 0);
 }
 
-void FilesystemVerifierAction::ReadVerityAndFooter() {
-  if (ShouldWriteVerity()) {
-    if (!verity_writer_->Finalize(partition_fd_, partition_fd_)) {
-      LOG(ERROR) << "Failed to write hashtree/FEC data.";
-      Cleanup(ErrorCode::kFilesystemVerifierError);
-      return;
-    }
-  }
-  // Since we handed our |read_fd_| to verity_writer_ during |Finalize()|
-  // call, fd's position could have been changed. Re-seek.
-  partition_fd_->Seek(filesystem_data_end_, SEEK_SET);
-  auto bytes_to_read = partition_size_ - filesystem_data_end_;
-  while (bytes_to_read > 0) {
-    const auto read_size = std::min<size_t>(buffer_.size(), bytes_to_read);
-    auto bytes_read = partition_fd_->Read(buffer_.data(), read_size);
-    if (bytes_read <= 0) {
-      PLOG(ERROR) << "Failed to read hash tree " << bytes_read;
-      Cleanup(ErrorCode::kFilesystemVerifierError);
-      return;
-    }
-    if (!hasher_->Update(buffer_.data(), bytes_read)) {
-      LOG(ERROR) << "Unable to update the hash.";
-      Cleanup(ErrorCode::kError);
-      return;
-    }
-    bytes_to_read -= bytes_read;
-  }
-  FinishPartitionHashing();
-}
-
-void FilesystemVerifierAction::ScheduleFileSystemRead() {
-  // We can only start reading anything past |hash_tree_offset| after we have
-  // already read all the data blocks that the hash tree covers. The same
-  // applies to FEC.
-
-  size_t bytes_to_read = std::min(static_cast<uint64_t>(buffer_.size()),
-                                  filesystem_data_end_ - offset_);
-  if (!bytes_to_read) {
-    ReadVerityAndFooter();
-    return;
-  }
-  partition_fd_->Seek(offset_, SEEK_SET);
-  auto bytes_read = partition_fd_->Read(buffer_.data(), bytes_to_read);
-  if (bytes_read < 0) {
-    LOG(ERROR) << "Unable to schedule an asynchronous read from the stream. "
-               << bytes_read;
-    Cleanup(ErrorCode::kError);
-  } else {
-    // We could just invoke |OnReadDoneCallback()|, it works. But |PostTask|
-    // is used so that users can cancel updates.
-    pending_task_id_ = brillo::MessageLoop::current()->PostTask(
-        base::Bind(&FilesystemVerifierAction::OnReadDone,
-                   base::Unretained(this),
-                   bytes_read));
-  }
-}
-
-void FilesystemVerifierAction::OnReadDone(size_t bytes_read) {
-  if (cancelled_) {
-    Cleanup(ErrorCode::kError);
-    return;
-  }
-  if (bytes_read == 0) {
-    LOG(ERROR) << "Failed to read the remaining " << partition_size_ - offset_
-               << " bytes from partition "
-               << install_plan_.partitions[partition_index_].name;
-    Cleanup(ErrorCode::kFilesystemVerifierError);
-    return;
-  }
-
-  if (!hasher_->Update(buffer_.data(), bytes_read)) {
-    LOG(ERROR) << "Unable to update the hash.";
-    Cleanup(ErrorCode::kError);
-    return;
-  }
-
-  // WE don't consider sizes of each partition. Every partition
-  // has the same length on progress bar.
-  // TODO(zhangkelvin) Take sizes of each partition into account
-
-  UpdateProgress(
-      (static_cast<double>(offset_) / partition_size_ + partition_index_) /
-      install_plan_.partitions.size());
-  if (ShouldWriteVerity()) {
-    if (!verity_writer_->Update(offset_, buffer_.data(), bytes_read)) {
-      LOG(ERROR) << "Unable to update verity";
-      Cleanup(ErrorCode::kVerityCalculationError);
-      return;
-    }
-  }
-
-  offset_ += bytes_read;
-  if (offset_ == filesystem_data_end_) {
-    ReadVerityAndFooter();
-    return;
-  }
-
-  ScheduleFileSystemRead();
-}
-
 void FilesystemVerifierAction::FinishPartitionHashing() {
   if (!hasher_->Finalize()) {
     LOG(ERROR) << "Unable to finalize the hash.";
@@ -424,6 +494,7 @@
   hasher_.reset();
   buffer_.clear();
   if (partition_fd_) {
+    partition_fd_->Close();
     partition_fd_.reset();
   }
   StartPartitionHashing();
diff --git a/payload_consumer/filesystem_verifier_action.h b/payload_consumer/filesystem_verifier_action.h
index 78634cb..850abda 100644
--- a/payload_consumer/filesystem_verifier_action.h
+++ b/payload_consumer/filesystem_verifier_action.h
@@ -22,12 +22,14 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <brillo/message_loops/message_loop.h>
 
 #include "update_engine/common/action.h"
 #include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/scoped_task_id.h"
 #include "update_engine/payload_consumer/file_descriptor.h"
 #include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/payload_consumer/verity_writer_interface.h"
@@ -84,6 +86,16 @@
 
  private:
   friend class FilesystemVerifierActionTestDelegate;
+  void WriteVerityAndHashPartition(FileDescriptorPtr fd,
+                                   const off64_t start_offset,
+                                   const off64_t end_offset,
+                                   void* buffer,
+                                   const size_t buffer_size);
+  void HashPartition(FileDescriptorPtr fd,
+                     const off64_t start_offset,
+                     const off64_t end_offset,
+                     void* buffer,
+                     const size_t buffer_size);
 
   // Return true if we need to write verity bytes.
   bool ShouldWriteVerity();
@@ -91,16 +103,11 @@
   // remaining to be hashed, it finishes the action.
   void StartPartitionHashing();
 
-  // Schedules the asynchronous read of the filesystem part of this
-  // partition(not including hashtree/verity).
-  void ScheduleFileSystemRead();
+  const std::string& GetPartitionPath() const;
 
-  // Read the verity part of this partition.(hash tree and FEC)
-  void ReadVerityAndFooter();
+  bool IsVABC(const InstallPlan::Partition& partition) const;
 
-  // Called from the main loop when a single read from |src_stream_| succeeds or
-  // fails, calling OnReadDoneCallback() and OnReadErrorCallback() respectively.
-  void OnReadDone(size_t bytes_read);
+  size_t GetPartitionSize() const;
 
   // When the read is done, finalize the hash checking of the current partition
   // and continue checking the next one.
@@ -114,9 +121,13 @@
   // Invoke delegate callback to report progress, if delegate is not null
   void UpdateProgress(double progress);
 
+  // Updates progress of current partition. |progress| should be in range [0,
+  // 1], and it will be scaled appropriately with # of partitions.
+  void UpdatePartitionProgress(double progress);
+
   // Initialize read_fd_ and write_fd_
   bool InitializeFd(const std::string& part_path);
-  bool InitializeFdVABC();
+  bool InitializeFdVABC(bool should_write_verity);
 
   // The type of the partition that we are verifying.
   VerifierStep verifier_step_ = VerifierStep::kVerifyTargetHash;
@@ -161,8 +172,7 @@
 
   // Callback that should be cancelled on |TerminateProcessing|. Usually this
   // points to pending read callbacks from async stream.
-  brillo::MessageLoop::TaskId pending_task_id_{
-      brillo::MessageLoop::kTaskIdNull};
+  ScopedTaskId pending_task_id_;
 
   DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction);
 };
diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc
index c100684..f2f2954 100644
--- a/payload_consumer/filesystem_verifier_action_unittest.cc
+++ b/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -16,6 +16,8 @@
 
 #include "update_engine/payload_consumer/filesystem_verifier_action.h"
 
+#include <algorithm>
+#include <cstring>
 #include <memory>
 #include <string>
 #include <utility>
@@ -25,8 +27,10 @@
 #include <brillo/message_loops/fake_message_loop.h>
 #include <brillo/message_loops/message_loop_utils.h>
 #include <brillo/secure_blob.h>
+#include <fec/ecc.h>
 #include <gtest/gtest.h>
 #include <libsnapshot/snapshot_writer.h>
+#include <sys/stat.h>
 
 #include "update_engine/common/dynamic_partition_control_stub.h"
 #include "update_engine/common/hash_calculator.h"
@@ -35,6 +39,7 @@
 #include "update_engine/common/utils.h"
 #include "update_engine/payload_consumer/fake_file_descriptor.h"
 #include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/payload_consumer/verity_writer_android.h"
 
 using brillo::MessageLoop;
 using std::string;
@@ -48,13 +53,57 @@
 namespace chromeos_update_engine {
 
 class FilesystemVerifierActionTest : public ::testing::Test {
+ public:
+  static constexpr size_t BLOCK_SIZE = 4096;
+  // We use SHA256 for testing, so hash size is 256bits / 8
+  static constexpr size_t HASH_SIZE = 256 / 8;
+  static constexpr size_t PARTITION_SIZE = BLOCK_SIZE * 1024;
+  static constexpr size_t HASH_TREE_START_OFFSET = 800 * BLOCK_SIZE;
+  size_t hash_tree_size = 0;
+  size_t fec_start_offset = 0;
+  size_t fec_data_size = 0;
+  static constexpr size_t FEC_ROOTS = 2;
+  size_t fec_rounds = 0;
+  size_t fec_size = 0;
+
  protected:
-  void SetUp() override { loop_.SetAsCurrent(); }
+  void SetUp() override {
+    hash_tree_size = HashTreeBuilder::CalculateSize(
+        HASH_TREE_START_OFFSET, BLOCK_SIZE, HASH_SIZE);
+    fec_start_offset = HASH_TREE_START_OFFSET + hash_tree_size;
+    fec_data_size = fec_start_offset;
+    static constexpr size_t FEC_ROOTS = 2;
+    fec_rounds =
+        utils::DivRoundUp(fec_data_size / BLOCK_SIZE, FEC_RSM - FEC_ROOTS);
+    fec_size = fec_rounds * FEC_ROOTS * BLOCK_SIZE;
+
+    fec_data_.resize(fec_size);
+    hash_tree_data_.resize(hash_tree_size);
+    // Globally readable writable, as we want to write data
+    ASSERT_EQ(0, fchmod(source_part_.fd(), 0666))
+        << " Failed to set " << source_part_.path() << " as writable "
+        << strerror(errno);
+    ASSERT_EQ(0, fchmod(target_part_.fd(), 0666))
+        << " Failed to set " << target_part_.path() << " as writable "
+        << strerror(errno);
+    brillo::Blob part_data(PARTITION_SIZE);
+    test_utils::FillWithData(&part_data);
+    ASSERT_TRUE(utils::WriteFile(
+        source_part_.path().c_str(), part_data.data(), part_data.size()));
+    // FillWithData() will fill with different data next call. We want
+    // source/target partitions to contain different data for testing.
+    test_utils::FillWithData(&part_data);
+    ASSERT_TRUE(utils::WriteFile(
+        target_part_.path().c_str(), part_data.data(), part_data.size()));
+    loop_.SetAsCurrent();
+  }
 
   void TearDown() override {
     EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1));
   }
 
+  void DoTestVABC(bool clear_target_hash, bool enable_verity);
+
   // Returns true iff test has completed successfully.
   bool DoTest(bool terminate_early, bool hash_fail);
 
@@ -62,11 +111,110 @@
   void BuildActions(const InstallPlan& install_plan,
                     DynamicPartitionControlInterface* dynamic_control);
 
+  InstallPlan::Partition* AddFakePartition(InstallPlan* install_plan,
+                                           std::string name = "fake_part") {
+    InstallPlan::Partition& part = install_plan->partitions.emplace_back();
+    part.name = name;
+    part.target_path = target_part_.path();
+    part.readonly_target_path = part.target_path;
+    part.target_size = PARTITION_SIZE;
+    part.block_size = BLOCK_SIZE;
+    part.source_path = source_part_.path();
+    part.source_size = PARTITION_SIZE;
+    EXPECT_TRUE(
+        HashCalculator::RawHashOfFile(source_part_.path(), &part.source_hash));
+    EXPECT_TRUE(
+        HashCalculator::RawHashOfFile(target_part_.path(), &part.target_hash));
+    return &part;
+  }
+  static void ZeroRange(FileDescriptorPtr fd,
+                        size_t start_block,
+                        size_t num_blocks) {
+    std::vector<unsigned char> buffer(BLOCK_SIZE);
+    ASSERT_EQ((ssize_t)(start_block * BLOCK_SIZE),
+              fd->Seek(start_block * BLOCK_SIZE, SEEK_SET));
+    for (size_t i = 0; i < num_blocks; i++) {
+      ASSERT_TRUE(utils::WriteAll(fd, buffer.data(), buffer.size()));
+    }
+  }
+
+  void SetHashWithVerity(InstallPlan::Partition* partition) {
+    partition->hash_tree_algorithm = "sha256";
+    partition->hash_tree_size = hash_tree_size;
+    partition->hash_tree_offset = HASH_TREE_START_OFFSET;
+    partition->hash_tree_data_offset = 0;
+    partition->hash_tree_data_size = HASH_TREE_START_OFFSET;
+    partition->fec_size = fec_size;
+    partition->fec_offset = fec_start_offset;
+    partition->fec_data_offset = 0;
+    partition->fec_data_size = fec_data_size;
+    partition->fec_roots = FEC_ROOTS;
+    VerityWriterAndroid verity_writer;
+    ASSERT_TRUE(verity_writer.Init(*partition));
+    LOG(INFO) << "Opening " << partition->readonly_target_path;
+    auto fd = std::make_shared<EintrSafeFileDescriptor>();
+    ASSERT_TRUE(fd->Open(partition->readonly_target_path.c_str(), O_RDWR))
+        << "Failed to open " << partition->target_path.c_str() << " "
+        << strerror(errno);
+    std::vector<unsigned char> buffer(BLOCK_SIZE);
+    // Only need to read up to hash tree
+    auto bytes_to_read = HASH_TREE_START_OFFSET;
+    auto offset = 0;
+    while (bytes_to_read > 0) {
+      const auto bytes_read = fd->Read(
+          buffer.data(), std::min<size_t>(buffer.size(), bytes_to_read));
+      ASSERT_GT(bytes_read, 0)
+          << "offset: " << offset << " bytes to read: " << bytes_to_read
+          << " error: " << strerror(errno);
+      ASSERT_TRUE(verity_writer.Update(offset, buffer.data(), bytes_read));
+      bytes_to_read -= bytes_read;
+      offset += bytes_read;
+    }
+    ASSERT_TRUE(verity_writer.Finalize(fd, fd));
+    ASSERT_TRUE(fd->IsOpen());
+    ASSERT_TRUE(HashCalculator::RawHashOfFile(target_part_.path(),
+                                              &partition->target_hash));
+
+    ASSERT_TRUE(fd->Seek(HASH_TREE_START_OFFSET, SEEK_SET));
+    ASSERT_EQ(fd->Read(hash_tree_data_.data(), hash_tree_data_.size()),
+              static_cast<ssize_t>(hash_tree_data_.size()))
+        << "Failed to read hashtree " << strerror(errno);
+    ASSERT_TRUE(fd->Seek(fec_start_offset, SEEK_SET));
+    ASSERT_EQ(fd->Read(fec_data_.data(), fec_data_.size()),
+              static_cast<ssize_t>(fec_data_.size()))
+        << "Failed to read FEC " << strerror(errno);
+    // Fs verification action is expected to write them, so clear verity data to
+    // ensure that they are re-created correctly.
+    ZeroRange(
+        fd, HASH_TREE_START_OFFSET / BLOCK_SIZE, hash_tree_size / BLOCK_SIZE);
+    ZeroRange(fd, fec_start_offset / BLOCK_SIZE, fec_size / BLOCK_SIZE);
+  }
+
   brillo::FakeMessageLoop loop_{nullptr};
   ActionProcessor processor_;
   DynamicPartitionControlStub dynamic_control_stub_;
+  std::vector<unsigned char> fec_data_;
+  std::vector<unsigned char> hash_tree_data_;
+  static ScopedTempFile source_part_;
+  static ScopedTempFile target_part_;
+  InstallPlan install_plan_;
 };
 
+ScopedTempFile FilesystemVerifierActionTest::source_part_{
+    "source_part.XXXXXX", true, PARTITION_SIZE};
+ScopedTempFile FilesystemVerifierActionTest::target_part_{
+    "target_part.XXXXXX", true, PARTITION_SIZE};
+
+static void EnableVABC(MockDynamicPartitionControl* dynamic_control,
+                       const std::string& part_name) {
+  ON_CALL(*dynamic_control, GetDynamicPartitionsFeatureFlag())
+      .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH)));
+  ON_CALL(*dynamic_control, UpdateUsesSnapshotCompression())
+      .WillByDefault(Return(true));
+  ON_CALL(*dynamic_control, IsDynamicPartition(part_name, _))
+      .WillByDefault(Return(true));
+}
+
 class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
  public:
   FilesystemVerifierActionTestDelegate()
@@ -132,9 +280,8 @@
   bool success = true;
 
   // Set up the action objects
-  InstallPlan install_plan;
-  install_plan.source_slot = 0;
-  install_plan.target_slot = 1;
+  install_plan_.source_slot = 0;
+  install_plan_.target_slot = 1;
   InstallPlan::Partition part;
   part.name = "part";
   part.target_size = kLoopFileSize - (hash_fail ? 1 : 0);
@@ -149,23 +296,19 @@
     ADD_FAILURE();
     success = false;
   }
-  install_plan.partitions = {part};
+  install_plan_.partitions = {part};
 
-  BuildActions(install_plan);
+  BuildActions(install_plan_);
 
   FilesystemVerifierActionTestDelegate delegate;
   processor_.set_delegate(&delegate);
 
-  loop_.PostTask(FROM_HERE,
-                 base::Bind(
-                     [](ActionProcessor* processor, bool terminate_early) {
-                       processor->StartProcessing();
-                       if (terminate_early) {
-                         processor->StopProcessing();
-                       }
-                     },
-                     base::Unretained(&processor_),
-                     terminate_early));
+  loop_.PostTask(base::Bind(&ActionProcessor::StartProcessing,
+                            base::Unretained(&processor_)));
+  if (terminate_early) {
+    loop_.PostTask(base::Bind(&ActionProcessor::StopProcessing,
+                              base::Unretained(&processor_)));
+  }
   loop_.Run();
 
   if (!terminate_early) {
@@ -194,7 +337,7 @@
   EXPECT_TRUE(is_a_file_reading_eq);
   success = success && is_a_file_reading_eq;
 
-  bool is_install_plan_eq = (*delegate.install_plan_ == install_plan);
+  bool is_install_plan_eq = (*delegate.install_plan_ == install_plan_);
   EXPECT_TRUE(is_install_plan_eq);
   success = success && is_install_plan_eq;
   return success;
@@ -259,14 +402,13 @@
 }
 
 TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) {
-  InstallPlan install_plan;
   InstallPlan::Partition part;
   part.name = "nope";
   part.source_path = "/no/such/file";
   part.target_path = "/no/such/file";
-  install_plan.partitions = {part};
+  install_plan_.partitions = {part};
 
-  BuildActions(install_plan);
+  BuildActions(install_plan_);
 
   FilesystemVerifierActionTest2Delegate delegate;
   processor_.set_delegate(&delegate);
@@ -307,7 +449,6 @@
   test_utils::ScopedLoopbackDeviceBinder target_device(
       part_file.path(), true, &target_path);
 
-  InstallPlan install_plan;
   InstallPlan::Partition part;
   part.name = "part";
   part.target_path = target_path;
@@ -338,9 +479,9 @@
   part.hash_tree_salt = {0x9e, 0xcb, 0xf8, 0xd5, 0x0b, 0xb4, 0x43,
                          0x0a, 0x7a, 0x10, 0xad, 0x96, 0xd7, 0x15,
                          0x70, 0xba, 0xed, 0x27, 0xe2, 0xae};
-  install_plan.partitions = {part};
+  install_plan_.partitions = {part};
 
-  BuildActions(install_plan);
+  BuildActions(install_plan_);
 
   FilesystemVerifierActionTestDelegate delegate;
   processor_.set_delegate(&delegate);
@@ -369,8 +510,7 @@
   test_utils::ScopedLoopbackDeviceBinder target_device(
       part_file.path(), true, &target_path);
 
-  InstallPlan install_plan;
-  install_plan.write_verity = false;
+  install_plan_.write_verity = false;
   InstallPlan::Partition part;
   part.name = "part";
   part.target_path = target_path;
@@ -385,9 +525,9 @@
   part.fec_offset = part.fec_data_size;
   part.fec_size = 2 * 4096;
   EXPECT_TRUE(HashCalculator::RawHashOfData(part_data, &part.target_hash));
-  install_plan.partitions = {part};
+  install_plan_.partitions = {part};
 
-  BuildActions(install_plan);
+  BuildActions(install_plan_);
 
   FilesystemVerifierActionTestDelegate delegate;
   processor_.set_delegate(&delegate);
@@ -404,85 +544,146 @@
   ASSERT_EQ(ErrorCode::kSuccess, delegate.code());
 }
 
-TEST_F(FilesystemVerifierActionTest, RunWithVABCNoVerity) {
-  InstallPlan install_plan;
-  InstallPlan::Partition& part = install_plan.partitions.emplace_back();
-  part.name = "fake_part";
-  part.target_path = "/dev/fake_target_path";
-  part.target_size = 4096 * 4096;
-  part.block_size = 4096;
-  part.source_path = "/dev/fake_source_path";
-  part.fec_size = 0;
-  part.hash_tree_size = 0;
-  part.target_hash.clear();
-  part.source_hash.clear();
+void FilesystemVerifierActionTest::DoTestVABC(bool clear_target_hash,
+                                              bool enable_verity) {
+  auto part_ptr = AddFakePartition(&install_plan_);
+  if (::testing::Test::HasFailure()) {
+    return;
+  }
+  ASSERT_NE(part_ptr, nullptr);
+  InstallPlan::Partition& part = *part_ptr;
+  part.target_path = "Shouldn't attempt to open this path";
+  if (enable_verity) {
+    install_plan_.write_verity = true;
+    ASSERT_NO_FATAL_FAILURE(SetHashWithVerity(&part));
+  }
+  if (clear_target_hash) {
+    part.target_hash.clear();
+  }
 
   NiceMock<MockDynamicPartitionControl> dynamic_control;
-  auto fake_fd = std::make_shared<FakeFileDescriptor>();
 
-  ON_CALL(dynamic_control, GetDynamicPartitionsFeatureFlag())
-      .WillByDefault(Return(FeatureFlag(FeatureFlag::Value::LAUNCH)));
-  ON_CALL(dynamic_control, UpdateUsesSnapshotCompression())
-      .WillByDefault(Return(true));
-  ON_CALL(dynamic_control, OpenCowFd(_, _, _)).WillByDefault(Return(fake_fd));
-  ON_CALL(dynamic_control, IsDynamicPartition(part.name, _))
-      .WillByDefault(Return(true));
+  EnableVABC(&dynamic_control, part.name);
+  auto open_cow = [part]() {
+    auto cow_fd = std::make_shared<EintrSafeFileDescriptor>();
+    EXPECT_TRUE(cow_fd->Open(part.readonly_target_path.c_str(), O_RDWR))
+        << "Failed to open part " << part.readonly_target_path
+        << strerror(errno);
+    return cow_fd;
+  };
 
   EXPECT_CALL(dynamic_control, UpdateUsesSnapshotCompression())
       .Times(AtLeast(1));
-  EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
-      .Times(1);
+  auto cow_fd = open_cow();
+  if (HasFailure()) {
+    return;
+  }
+
+  if (enable_verity) {
+    ON_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
+        .WillByDefault(open_cow);
+    EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
+        .Times(AtLeast(1));
+
+    // fs verification isn't supposed to write to |readonly_target_path|. All
+    // writes should go through fd returned by |OpenCowFd|. Therefore we set
+    // target part as read-only to make sure.
+    ASSERT_EQ(0, chmod(part.readonly_target_path.c_str(), 0444))
+        << " Failed to set " << part.readonly_target_path << " as read-only "
+        << strerror(errno);
+  } else {
+    // Since we are not writing verity, we should not attempt to OpenCowFd()
+    // reads should go through regular file descriptors on mapped partitions.
+    EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
+        .Times(0);
+    EXPECT_CALL(dynamic_control, MapAllPartitions()).Times(AtLeast(1));
+  }
   EXPECT_CALL(dynamic_control, ListDynamicPartitionsForSlot(_, _, _))
       .WillRepeatedly(
           DoAll(SetArgPointee<2, std::vector<std::string>>({part.name}),
                 Return(true)));
 
-  BuildActions(install_plan, &dynamic_control);
+  BuildActions(install_plan_, &dynamic_control);
 
   FilesystemVerifierActionTestDelegate delegate;
   processor_.set_delegate(&delegate);
 
-  loop_.PostTask(
-      FROM_HERE,
-      base::Bind(
-          [](ActionProcessor* processor) { processor->StartProcessing(); },
-          base::Unretained(&processor_)));
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&ActionProcessor::StartProcessing,
+                            base::Unretained(&processor_)));
   loop_.Run();
 
   ASSERT_FALSE(processor_.IsRunning());
   ASSERT_TRUE(delegate.ran());
-  // Filesystem verifier will fail, because we set an empty hash
-  ASSERT_EQ(ErrorCode::kNewRootfsVerificationError, delegate.code());
-  const auto& read_pos = fake_fd->GetReadOps();
-  size_t expected_offset = 0;
-  for (const auto& [off, size] : read_pos) {
-    ASSERT_EQ(off, expected_offset);
-    expected_offset += size;
+  if (enable_verity) {
+    std::vector<unsigned char> actual_fec(fec_size);
+    ssize_t bytes_read = 0;
+    ASSERT_TRUE(utils::PReadAll(cow_fd,
+                                actual_fec.data(),
+                                actual_fec.size(),
+                                fec_start_offset,
+                                &bytes_read));
+    ASSERT_EQ(actual_fec, fec_data_);
+    std::vector<unsigned char> actual_hash_tree(hash_tree_size);
+    ASSERT_TRUE(utils::PReadAll(cow_fd,
+                                actual_hash_tree.data(),
+                                actual_hash_tree.size(),
+                                HASH_TREE_START_OFFSET,
+                                &bytes_read));
+    ASSERT_EQ(actual_hash_tree, hash_tree_data_);
   }
-  const auto actual_read_size = expected_offset;
-  ASSERT_EQ(actual_read_size, part.target_size);
+  if (clear_target_hash) {
+    ASSERT_EQ(ErrorCode::kNewRootfsVerificationError, delegate.code());
+  } else {
+    ASSERT_EQ(ErrorCode::kSuccess, delegate.code());
+  }
 }
 
-TEST_F(FilesystemVerifierActionTest, ReadAfterWrite) {
-  constexpr auto BLOCK_SIZE = 4096;
-  ScopedTempFile cow_device_file("cow_device.XXXXXX", true);
-  android::snapshot::CompressedSnapshotWriter snapshot_writer{
-      {.block_size = BLOCK_SIZE}};
-  snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd()});
-  snapshot_writer.Initialize();
-  std::vector<unsigned char> buffer;
-  buffer.resize(BLOCK_SIZE);
-  std::fill(buffer.begin(), buffer.end(), 123);
+TEST_F(FilesystemVerifierActionTest, VABC_NoVerity_Success) {
+  DoTestVABC(false, false);
+}
 
-  ASSERT_TRUE(snapshot_writer.AddRawBlocks(0, buffer.data(), buffer.size()));
-  ASSERT_TRUE(snapshot_writer.Finalize());
-  auto cow_reader = snapshot_writer.OpenReader();
-  ASSERT_NE(cow_reader, nullptr);
-  ASSERT_TRUE(snapshot_writer.AddRawBlocks(1, buffer.data(), buffer.size()));
-  ASSERT_TRUE(snapshot_writer.AddRawBlocks(2, buffer.data(), buffer.size()));
-  ASSERT_TRUE(snapshot_writer.Finalize());
-  cow_reader = snapshot_writer.OpenReader();
-  ASSERT_NE(cow_reader, nullptr);
+TEST_F(FilesystemVerifierActionTest, VABC_NoVerity_Target_Mismatch) {
+  DoTestVABC(true, false);
+}
+
+TEST_F(FilesystemVerifierActionTest, VABC_Verity_Success) {
+  DoTestVABC(false, true);
+}
+
+TEST_F(FilesystemVerifierActionTest, VABC_Verity_ReadAfterWrite) {
+  ASSERT_NO_FATAL_FAILURE(DoTestVABC(false, true));
+  // Run FS verification again, w/o writing verity. We have seen a bug where
+  // attempting to run fs again will cause previously written verity data to be
+  // dropped, so cover this scenario.
+  ASSERT_GE(install_plan_.partitions.size(), 1UL);
+  auto& part = install_plan_.partitions[0];
+  install_plan_.write_verity = false;
+  part.readonly_target_path = target_part_.path();
+  NiceMock<MockDynamicPartitionControl> dynamic_control;
+  EnableVABC(&dynamic_control, part.name);
+
+  // b/186196758 is only visible if we repeatedely run FS verification w/o
+  // writing verity
+  for (int i = 0; i < 3; i++) {
+    BuildActions(install_plan_, &dynamic_control);
+
+    FilesystemVerifierActionTestDelegate delegate;
+    processor_.set_delegate(&delegate);
+    loop_.PostTask(
+        FROM_HERE,
+        base::Bind(
+            [](ActionProcessor* processor) { processor->StartProcessing(); },
+            base::Unretained(&processor_)));
+    loop_.Run();
+    ASSERT_FALSE(processor_.IsRunning());
+    ASSERT_TRUE(delegate.ran());
+    ASSERT_EQ(ErrorCode::kSuccess, delegate.code());
+  }
+}
+
+TEST_F(FilesystemVerifierActionTest, VABC_Verity_Target_Mismatch) {
+  DoTestVABC(true, true);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc
index 39827a4..06b7dd8 100644
--- a/payload_consumer/install_plan.cc
+++ b/payload_consumer/install_plan.cc
@@ -122,6 +122,7 @@
                              partition.target_hash.size())},
             {"run_postinstall", utils::ToString(partition.run_postinstall)},
             {"postinstall_path", partition.postinstall_path},
+            {"readonly_target_path", partition.readonly_target_path},
             {"filesystem_type", partition.filesystem_type},
         },
         "\n  "));
@@ -165,7 +166,7 @@
           partition.name, target_slot, source_slot);
       TEST_AND_RETURN_FALSE(device.has_value());
       partition.target_path = device->rw_device_path;
-      partition.postinstall_mount_device = device->mountable_device_path;
+      partition.readonly_target_path = device->readonly_device_path;
     } else {
       partition.target_path.clear();
     }
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
index 43b94fc..7c77789 100644
--- a/payload_consumer/install_plan.h
+++ b/payload_consumer/install_plan.h
@@ -109,7 +109,7 @@
     std::string target_path;
     // |mountable_target_device| is intended to be a path to block device which
     // can be used for mounting this block device's underlying filesystem.
-    std::string postinstall_mount_device;
+    std::string readonly_target_path;
     uint64_t target_size{0};
     brillo::Blob target_hash;
 
diff --git a/payload_consumer/install_plan_unittest.cc b/payload_consumer/install_plan_unittest.cc
index 2ca8d81..7779494 100644
--- a/payload_consumer/install_plan_unittest.cc
+++ b/payload_consumer/install_plan_unittest.cc
@@ -38,6 +38,7 @@
           .source_path = "foo-source-path",
           .source_hash = {0xb1, 0xb2},
           .target_path = "foo-target-path",
+          .readonly_target_path = "mountable-device",
           .target_hash = {0xb3, 0xb4},
           .postinstall_path = "foo-path",
           .filesystem_type = "foo-type",
@@ -66,6 +67,7 @@
   target_hash: B3B4
   run_postinstall: false
   postinstall_path: foo-path
+  readonly_target_path: mountable-device
   filesystem_type: foo-type
 Payload: 0
   urls: (url1,url2)
diff --git a/payload_consumer/partition_writer.cc b/payload_consumer/partition_writer.cc
index 6f06dd2..f2022a1 100644
--- a/payload_consumer/partition_writer.cc
+++ b/payload_consumer/partition_writer.cc
@@ -42,6 +42,7 @@
 #include "update_engine/payload_consumer/mount_history.h"
 #include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_consumer/xz_extent_writer.h"
+#include "update_engine/payload_generator/extent_utils.h"
 
 namespace chromeos_update_engine {
 
@@ -83,6 +84,8 @@
 
 }  // namespace
 
+using google::protobuf::RepeatedPtrField;
+
 // Opens path for read/write. On success returns an open FileDescriptor
 // and sets *err to 0. On failure, sets *err to errno and returns nullptr.
 FileDescriptorPtr OpenFile(const char* path,
@@ -326,8 +329,7 @@
     writer.reset(new XzExtentWriter(std::move(writer)));
   }
 
-  TEST_AND_RETURN_FALSE(
-      writer->Init(target_fd_, operation.dst_extents(), block_size_));
+  TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
   TEST_AND_RETURN_FALSE(writer->Write(data, operation.data_length()));
 
   return true;
@@ -366,6 +368,23 @@
   return true;
 }
 
+std::ostream& operator<<(std::ostream& out,
+                         const RepeatedPtrField<Extent>& extents) {
+  if (extents.size() == 0) {
+    out << "[]";
+    return out;
+  }
+  out << "[";
+  auto begin = extents.begin();
+  out << *begin;
+  for (int i = 1; i < extents.size(); i++) {
+    ++begin;
+    out << ", " << *begin;
+  }
+  out << "]";
+  return out;
+}
+
 bool PartitionWriter::PerformSourceCopyOperation(
     const InstallOperation& operation, ErrorCode* error) {
   TEST_AND_RETURN_FALSE(source_fd_ != nullptr);
@@ -374,107 +393,29 @@
   // Being this a device-specific optimization let DynamicPartitionController
   // decide it the operation should be skipped.
   const PartitionUpdate& partition = partition_update_;
-  const auto& partition_control = dynamic_control_;
 
   InstallOperation buf;
-  bool should_optimize = partition_control->OptimizeOperation(
+  const bool should_optimize = dynamic_control_->OptimizeOperation(
       partition.partition_name(), operation, &buf);
   const InstallOperation& optimized = should_optimize ? buf : operation;
 
-  if (operation.has_src_sha256_hash()) {
-    bool read_ok;
-    brillo::Blob source_hash;
-    brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(),
-                                      operation.src_sha256_hash().end());
-
-    // We fall back to use the error corrected device if the hash of the raw
-    // device doesn't match or there was an error reading the source partition.
-    // Note that this code will also fall back if writing the target partition
-    // fails.
-    if (should_optimize) {
-      // Hash operation.src_extents(), then copy optimized.src_extents to
-      // optimized.dst_extents.
-      read_ok =
-          fd_utils::ReadAndHashExtents(
-              source_fd_, operation.src_extents(), block_size_, &source_hash) &&
-          fd_utils::CopyAndHashExtents(source_fd_,
-                                       optimized.src_extents(),
-                                       target_fd_,
-                                       optimized.dst_extents(),
-                                       block_size_,
-                                       nullptr /* skip hashing */);
-    } else {
-      read_ok = fd_utils::CopyAndHashExtents(source_fd_,
-                                             operation.src_extents(),
-                                             target_fd_,
-                                             operation.dst_extents(),
-                                             block_size_,
-                                             &source_hash);
-    }
-    if (read_ok && expected_source_hash == source_hash)
-      return true;
-    LOG(WARNING) << "Source hash from RAW device mismatched, attempting to "
-                    "correct using ECC";
-    if (!OpenCurrentECCPartition()) {
-      // The following function call will return false since the source hash
-      // mismatches, but we still want to call it so it prints the appropriate
-      // log message.
-      return ValidateSourceHash(source_hash, operation, source_fd_, error);
-    }
-
-    LOG(WARNING) << "Source hash from RAW device mismatched: found "
-                 << base::HexEncode(source_hash.data(), source_hash.size())
-                 << ", expected "
-                 << base::HexEncode(expected_source_hash.data(),
-                                    expected_source_hash.size());
-    if (should_optimize) {
-      TEST_AND_RETURN_FALSE(fd_utils::ReadAndHashExtents(
-          source_ecc_fd_, operation.src_extents(), block_size_, &source_hash));
-      TEST_AND_RETURN_FALSE(
-          fd_utils::CopyAndHashExtents(source_ecc_fd_,
-                                       optimized.src_extents(),
-                                       target_fd_,
-                                       optimized.dst_extents(),
-                                       block_size_,
-                                       nullptr /* skip hashing */));
-    } else {
-      TEST_AND_RETURN_FALSE(
-          fd_utils::CopyAndHashExtents(source_ecc_fd_,
-                                       operation.src_extents(),
-                                       target_fd_,
-                                       operation.dst_extents(),
-                                       block_size_,
-                                       &source_hash));
-    }
-    TEST_AND_RETURN_FALSE(
-        ValidateSourceHash(source_hash, operation, source_ecc_fd_, error));
-    // At this point reading from the error corrected device worked, but
-    // reading from the raw device failed, so this is considered a recovered
-    // failure.
-    source_ecc_recovered_failures_++;
-  } else {
-    // When the operation doesn't include a source hash, we attempt the error
-    // corrected device first since we can't verify the block in the raw device
-    // at this point, but we fall back to the raw device since the error
-    // corrected device can be shorter or not available.
-
-    if (OpenCurrentECCPartition() &&
-        fd_utils::CopyAndHashExtents(source_ecc_fd_,
-                                     optimized.src_extents(),
-                                     target_fd_,
-                                     optimized.dst_extents(),
-                                     block_size_,
-                                     nullptr)) {
-      return true;
-    }
-    TEST_AND_RETURN_FALSE(fd_utils::CopyAndHashExtents(source_fd_,
-                                                       optimized.src_extents(),
-                                                       target_fd_,
-                                                       optimized.dst_extents(),
-                                                       block_size_,
-                                                       nullptr));
+  // Invoke ChooseSourceFD with original operation, so that it can properly
+  // verify source hashes. Optimized operation might contain a smaller set of
+  // extents, or completely empty.
+  auto source_fd = ChooseSourceFD(operation, error);
+  if (source_fd == nullptr) {
+    LOG(ERROR) << "Unrecoverable source hash mismatch found on partition "
+               << partition.partition_name()
+               << " extents: " << operation.src_extents();
+    return false;
   }
-  return true;
+
+  return fd_utils::CopyAndHashExtents(source_fd,
+                                      optimized.src_extents(),
+                                      target_fd_,
+                                      optimized.dst_extents(),
+                                      block_size_,
+                                      nullptr);
 }
 
 bool PartitionWriter::PerformSourceBsdiffOperation(
@@ -493,8 +434,7 @@
       utils::BlocksInExtents(operation.src_extents()) * block_size_);
 
   auto writer = CreateBaseExtentWriter();
-  TEST_AND_RETURN_FALSE(
-      writer->Init(target_fd_, operation.dst_extents(), block_size_));
+  TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
   auto dst_file = std::make_unique<BsdiffExtentFile>(
       std::move(writer),
       utils::BlocksInExtents(operation.dst_extents()) * block_size_);
@@ -522,8 +462,7 @@
       utils::BlocksInExtents(operation.src_extents()) * block_size_));
 
   auto writer = CreateBaseExtentWriter();
-  TEST_AND_RETURN_FALSE(
-      writer->Init(target_fd_, operation.dst_extents(), block_size_));
+  TEST_AND_RETURN_FALSE(writer->Init(operation.dst_extents(), block_size_));
   puffin::UniqueStreamPtr dst_stream(new PuffinExtentStream(
       std::move(writer),
       utils::BlocksInExtents(operation.dst_extents()) * block_size_));
@@ -658,7 +597,7 @@
 }
 
 std::unique_ptr<ExtentWriter> PartitionWriter::CreateBaseExtentWriter() {
-  return std::make_unique<DirectExtentWriter>();
+  return std::make_unique<DirectExtentWriter>(target_fd_);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/payload_consumer/partition_writer_unittest.cc b/payload_consumer/partition_writer_unittest.cc
index 91e5e26..564d8d4 100644
--- a/payload_consumer/partition_writer_unittest.cc
+++ b/payload_consumer/partition_writer_unittest.cc
@@ -82,10 +82,10 @@
   brillo::Blob PerformSourceCopyOp(const InstallOperation& op,
                                    const brillo::Blob blob_data) {
     ScopedTempFile source_partition("Blob-XXXXXX");
-    DirectExtentWriter extent_writer;
     FileDescriptorPtr fd(new EintrSafeFileDescriptor());
+    DirectExtentWriter extent_writer{fd};
     EXPECT_TRUE(fd->Open(source_partition.path().c_str(), O_RDWR));
-    EXPECT_TRUE(extent_writer.Init(fd, op.src_extents(), kBlockSize));
+    EXPECT_TRUE(extent_writer.Init(op.src_extents(), kBlockSize));
     EXPECT_TRUE(extent_writer.Write(blob_data.data(), blob_data.size()));
 
     ScopedTempFile target_partition("Blob-XXXXXX");
@@ -167,7 +167,7 @@
   ASSERT_EQ(output_data, expected_data);
 
   // Verify that the fake_fec was actually used.
-  EXPECT_EQ(1U, fake_fec->GetReadOps().size());
+  EXPECT_GE(fake_fec->GetReadOps().size(), 1U);
   EXPECT_EQ(1U, GetSourceEccRecoveredFailures());
 }
 
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
index 8f2d674..051ccbf 100644
--- a/payload_consumer/postinstall_runner_action.cc
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -140,7 +140,7 @@
   const InstallPlan::Partition& partition =
       install_plan_.partitions[current_partition_];
 
-  const string mountable_device = partition.postinstall_mount_device;
+  const string mountable_device = partition.readonly_target_path;
   if (mountable_device.empty()) {
     LOG(ERROR) << "Cannot make mountable device from " << partition.target_path;
     return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
@@ -383,6 +383,11 @@
     }
   }
 
+  auto dynamic_control = boot_control_->GetDynamicPartitionControl();
+  CHECK(dynamic_control);
+  dynamic_control->UnmapAllPartitions();
+  LOG(INFO) << "Unmapped all partitions.";
+
   ScopedActionCompleter completer(processor_, this);
   completer.set_code(error_code);
 
@@ -401,10 +406,6 @@
   if (HasOutputPipe()) {
     SetOutputObject(install_plan_);
   }
-  auto dynamic_control = boot_control_->GetDynamicPartitionControl();
-  CHECK(dynamic_control);
-  dynamic_control->UnmapAllPartitions();
-  LOG(INFO) << "Unmapped all partitions.";
 }
 
 void PostinstallRunnerAction::SuspendAction() {
diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc
index 5ee2989..792ee28 100644
--- a/payload_consumer/postinstall_runner_action_unittest.cc
+++ b/payload_consumer/postinstall_runner_action_unittest.cc
@@ -195,7 +195,7 @@
   InstallPlan::Partition part;
   part.name = "part";
   part.target_path = device_path;
-  part.postinstall_mount_device = device_path;
+  part.readonly_target_path = device_path;
   part.run_postinstall = true;
   part.postinstall_path = postinstall_program;
   InstallPlan install_plan;
@@ -360,7 +360,7 @@
   InstallPlan::Partition part;
   part.name = "part";
   part.target_path = "/dev/null";
-  part.postinstall_mount_device = "/dev/null";
+  part.readonly_target_path = "/dev/null";
   part.run_postinstall = true;
   part.postinstall_path = kPostinstallDefaultScript;
   part.postinstall_optional = true;
diff --git a/payload_consumer/snapshot_extent_writer.cc b/payload_consumer/snapshot_extent_writer.cc
index c9e6f31..242e726 100644
--- a/payload_consumer/snapshot_extent_writer.cc
+++ b/payload_consumer/snapshot_extent_writer.cc
@@ -36,7 +36,6 @@
 }
 
 bool SnapshotExtentWriter::Init(
-    FileDescriptorPtr /*fd*/,
     const google::protobuf::RepeatedPtrField<Extent>& extents,
     uint32_t block_size) {
   extents_ = extents;
diff --git a/payload_consumer/snapshot_extent_writer.h b/payload_consumer/snapshot_extent_writer.h
index 6d9fe7d..c3a948e 100644
--- a/payload_consumer/snapshot_extent_writer.h
+++ b/payload_consumer/snapshot_extent_writer.h
@@ -14,6 +14,9 @@
 // limitations under the License.
 //
 
+#ifndef UPDATE_ENGINE_SNAPSHOT_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_SNAPSHOT_EXTENT_WRITER_H_
+
 #include <cstdint>
 #include <vector>
 
@@ -29,8 +32,7 @@
   explicit SnapshotExtentWriter(android::snapshot::ICowWriter* cow_writer);
   ~SnapshotExtentWriter();
   // Returns true on success.
-  bool Init(FileDescriptorPtr fd,
-            const google::protobuf::RepeatedPtrField<Extent>& extents,
+  bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents,
             uint32_t block_size) override;
   // Returns true on success.
   // This will construct a COW_REPLACE operation and forward it to CowWriter. It
@@ -53,3 +55,5 @@
 };
 
 }  // namespace chromeos_update_engine
+
+#endif
diff --git a/payload_consumer/snapshot_extent_writer_unittest.cc b/payload_consumer/snapshot_extent_writer_unittest.cc
index 0e22482..2201043 100644
--- a/payload_consumer/snapshot_extent_writer_unittest.cc
+++ b/payload_consumer/snapshot_extent_writer_unittest.cc
@@ -107,7 +107,7 @@
 TEST_F(SnapshotExtentWriterTest, BufferWrites) {
   google::protobuf::RepeatedPtrField<Extent> extents;
   AddExtent(&extents, 123, 1);
-  writer_.Init(nullptr, extents, kBlockSize);
+  writer_.Init(extents, kBlockSize);
 
   std::vector<uint8_t> buf(kBlockSize, 0);
   buf[123] = 231;
@@ -130,7 +130,7 @@
   google::protobuf::RepeatedPtrField<Extent> extents;
   AddExtent(&extents, 123, 1);
   AddExtent(&extents, 125, 1);
-  writer_.Init(nullptr, extents, kBlockSize);
+  writer_.Init(extents, kBlockSize);
 
   std::vector<uint8_t> buf(kBlockSize * 2, 0);
   buf[123] = 231;
@@ -153,7 +153,7 @@
   google::protobuf::RepeatedPtrField<Extent> extents;
   AddExtent(&extents, 123, 1);
   AddExtent(&extents, 125, 2);
-  writer_.Init(nullptr, extents, kBlockSize);
+  writer_.Init(extents, kBlockSize);
 
   std::vector<uint8_t> buf(kBlockSize * 3);
   std::memset(buf.data(), 0, buf.size());
diff --git a/payload_consumer/xz_extent_writer.cc b/payload_consumer/xz_extent_writer.cc
index a5b939d..a648351 100644
--- a/payload_consumer/xz_extent_writer.cc
+++ b/payload_consumer/xz_extent_writer.cc
@@ -57,12 +57,11 @@
   TEST_AND_RETURN(input_buffer_.empty());
 }
 
-bool XzExtentWriter::Init(FileDescriptorPtr fd,
-                          const RepeatedPtrField<Extent>& extents,
+bool XzExtentWriter::Init(const RepeatedPtrField<Extent>& extents,
                           uint32_t block_size) {
   stream_ = xz_dec_init(XZ_DYNALLOC, kXzMaxDictSize);
   TEST_AND_RETURN_FALSE(stream_ != nullptr);
-  return underlying_writer_->Init(fd, extents, block_size);
+  return underlying_writer_->Init(extents, block_size);
 }
 
 bool XzExtentWriter::Write(const void* bytes, size_t count) {
diff --git a/payload_consumer/xz_extent_writer.h b/payload_consumer/xz_extent_writer.h
index e022274..70338f2 100644
--- a/payload_consumer/xz_extent_writer.h
+++ b/payload_consumer/xz_extent_writer.h
@@ -39,8 +39,7 @@
       : underlying_writer_(std::move(underlying_writer)) {}
   ~XzExtentWriter() override;
 
-  bool Init(FileDescriptorPtr fd,
-            const google::protobuf::RepeatedPtrField<Extent>& extents,
+  bool Init(const google::protobuf::RepeatedPtrField<Extent>& extents,
             uint32_t block_size) override;
   bool Write(const void* bytes, size_t count) override;
 
diff --git a/payload_consumer/xz_extent_writer_unittest.cc b/payload_consumer/xz_extent_writer_unittest.cc
index 34980a9..5269dbc 100644
--- a/payload_consumer/xz_extent_writer_unittest.cc
+++ b/payload_consumer/xz_extent_writer_unittest.cc
@@ -87,7 +87,7 @@
   }
 
   void WriteAll(const brillo::Blob& compressed) {
-    EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+    EXPECT_TRUE(xz_writer_->Init({}, 1024));
     EXPECT_TRUE(xz_writer_->Write(compressed.data(), compressed.size()));
 
     EXPECT_TRUE(fake_extent_writer_->InitCalled());
@@ -130,7 +130,7 @@
 }
 
 TEST_F(XzExtentWriterTest, GarbageDataRejected) {
-  EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+  EXPECT_TRUE(xz_writer_->Init({}, 1024));
   // The sample_data_ is an uncompressed string.
   EXPECT_FALSE(xz_writer_->Write(sample_data_.data(), sample_data_.size()));
 }
@@ -138,7 +138,7 @@
 TEST_F(XzExtentWriterTest, PartialDataIsKept) {
   brillo::Blob compressed(std::begin(kCompressed30KiBofA),
                           std::end(kCompressed30KiBofA));
-  EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+  EXPECT_TRUE(xz_writer_->Init({}, 1024));
   for (uint8_t byte : compressed) {
     EXPECT_TRUE(xz_writer_->Write(&byte, 1));
   }
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc
index d45de6a..2cd2ebc 100644
--- a/payload_generator/payload_generation_config.cc
+++ b/payload_generator/payload_generation_config.cc
@@ -23,6 +23,7 @@
 #include <base/logging.h>
 #include <base/strings/string_number_conversions.h>
 #include <brillo/strings/string_utils.h>
+#include <libsnapshot/cow_format.h>
 
 #include "update_engine/common/utils.h"
 #include "update_engine/payload_consumer/delta_performer.h"
@@ -185,6 +186,7 @@
   // We use "gz" compression by default for VABC.
   if (metadata->vabc_enabled()) {
     metadata->set_vabc_compression_param("gz");
+    metadata->set_cow_version(android::snapshot::kCowVersionManifest);
   }
   dynamic_partition_metadata = std::move(metadata);
   return true;
diff --git a/payload_generator/zip_unittest.cc b/payload_generator/zip_unittest.cc
index e357b15..10e899b 100644
--- a/payload_generator/zip_unittest.cc
+++ b/payload_generator/zip_unittest.cc
@@ -33,7 +33,6 @@
 using chromeos_update_engine::test_utils::kRandomString;
 using google::protobuf::RepeatedPtrField;
 using std::string;
-using std::vector;
 
 namespace chromeos_update_engine {
 
@@ -50,8 +49,7 @@
   }
   ~MemoryExtentWriter() override = default;
 
-  bool Init(FileDescriptorPtr fd,
-            const RepeatedPtrField<Extent>& extents,
+  bool Init(const RepeatedPtrField<Extent>& extents,
             uint32_t block_size) override {
     return true;
   }
@@ -72,7 +70,7 @@
   std::unique_ptr<ExtentWriter> writer(
       new W(std::make_unique<MemoryExtentWriter>(out)));
   // Init() parameters are ignored by the testing MemoryExtentWriter.
-  bool ok = writer->Init(nullptr, {}, 1);
+  bool ok = writer->Init({}, 1);
   ok = writer->Write(in.data(), in.size()) && ok;
   return ok;
 }
diff --git a/scripts/ota_stress_test.py b/scripts/ota_stress_test.py
new file mode 100644
index 0000000..55aa4b1
--- /dev/null
+++ b/scripts/ota_stress_test.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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.
+#
+
+"""Repeatedly install an A/B update to an Android device over adb."""
+
+import argparse
+import sys
+from pathlib import Path
+import subprocess
+import signal
+
+
+def CleanupLoopDevices():
+  # b/184716804 clean up unused loop devices
+  subprocess.check_call(["adb", "shell", "su", "0", "losetup", '-D'])
+
+
+def CancelOTA():
+  subprocess.call(["adb", "shell", "su", "0",
+                  "update_engine_client", "--cancel"])
+
+
+def PerformOTAThenPause(otafile: Path, update_device_script: Path):
+  python = sys.executable
+  ota_cmd = [python, str(update_device_script), str(otafile),
+             "--no-postinstall", "--no-slot-switch"]
+  p = subprocess.Popen(ota_cmd)
+  pid = p.pid
+  try:
+    ret = p.wait(10)
+    if ret is not None and ret != 0:
+      raise RuntimeError("OTA failed to apply")
+    if ret == 0:
+      print("OTA finished early? Surprise.")
+      return
+  except subprocess.TimeoutExpired:
+    pass
+  print(f"Killing {pid}")
+  subprocess.check_call(["pkill", "-INT", "-P", str(pid)])
+  p.send_signal(signal.SIGINT)
+  p.wait()
+
+
+def PerformTest(otafile: Path, resumes: int, timeout: int):
+  """Install an OTA to device, raising exceptions on failure
+
+  Args:
+    otafile: Path to the ota.zip to install
+
+  Return:
+    None if no error, if there's an error exception will be thrown
+  """
+  assert otafile.exists()
+  print("Applying", otafile)
+  script_dir = Path(__file__).parent.absolute()
+  update_device_script = script_dir / "update_device.py"
+  assert update_device_script.exists()
+  print(update_device_script)
+  python = sys.executable
+
+  for i in range(resumes):
+    print("Pause/Resume for the", i+1, "th time")
+    PerformOTAThenPause(otafile, update_device_script)
+  CancelOTA()
+  CleanupLoopDevices()
+
+  ota_cmd = [python, str(update_device_script),
+             str(otafile), "--no-postinstall"]
+  print("Finishing OTA Update", ota_cmd)
+  output = subprocess.check_output(
+      ota_cmd, stderr=subprocess.STDOUT, timeout=timeout).decode()
+  print(output)
+  if "onPayloadApplicationComplete(ErrorCode::kSuccess" not in output:
+    raise RuntimeError("Failed to finish OTA")
+  subprocess.call(
+      ["adb", "shell", "su", "0", "update_engine_client", "--cancel"])
+  subprocess.check_call(
+      ["adb", "shell", "su", "0", "update_engine_client", "--reset_status"])
+  CleanupLoopDevices()
+
+
+def main():
+  parser = argparse.ArgumentParser(
+      description='Android A/B OTA stress test helper.')
+  parser.add_argument('otafile', metavar='PAYLOAD', type=Path,
+                      help='the OTA package file (a .zip file) or raw payload \
+                      if device uses Omaha.')
+  parser.add_argument('-n', "--iterations", type=int, default=10,
+                      metavar='ITERATIONS',
+                      help='The number of iterations to run the stress test, or\
+                       -1 to keep running until CTRL+C')
+  parser.add_argument('-r', "--resumes", type=int, default=5, metavar='RESUMES',
+                      help='The number of iterations to pause the update when \
+                        installing')
+  parser.add_argument('-t', "--timeout", type=int, default=60*60,
+                      metavar='TIMEOUTS',
+                      help='Timeout, in seconds, when waiting for OTA to \
+                        finish')
+  args = parser.parse_args()
+  print(args)
+  n = args.iterations
+  while n != 0:
+    PerformTest(args.otafile, args.resumes, args.timeout)
+    n -= 1
+
+
+if __name__ == "__main__":
+  main()
diff --git a/scripts/update_device.py b/scripts/update_device.py
index b784b1b..f672cda 100755
--- a/scripts/update_device.py
+++ b/scripts/update_device.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 #
 # Copyright (C) 2017 The Android Open Source Project
 #
@@ -350,7 +350,7 @@
     if self._device_serial:
       self._command_prefix += ['-s', self._device_serial]
 
-  def adb(self, command):
+  def adb(self, command, timeout_seconds: float = None):
     """Run an ADB command like "adb push".
 
     Args:
@@ -365,7 +365,7 @@
     command = self._command_prefix + command
     logging.info('Running: %s', ' '.join(str(x) for x in command))
     p = subprocess.Popen(command, universal_newlines=True)
-    p.wait()
+    p.wait(timeout_seconds)
     return p.returncode
 
   def adb_output(self, command):
@@ -430,12 +430,12 @@
                       help='Do not perform slot switch after the update.')
   parser.add_argument('--no-postinstall', action='store_true',
                       help='Do not execute postinstall scripts after the update.')
-  parser.add_argument('--allocate_only', action='store_true',
+  parser.add_argument('--allocate-only', action='store_true',
                       help='Allocate space for this OTA, instead of actually \
                         applying the OTA.')
-  parser.add_argument('--verify_only', action='store_true',
+  parser.add_argument('--verify-only', action='store_true',
                       help='Verify metadata then exit, instead of applying the OTA.')
-  parser.add_argument('--no_care_map', action='store_true',
+  parser.add_argument('--no-care-map', action='store_true',
                       help='Do not push care_map.pb to device.')
   args = parser.parse_args()
   logging.basicConfig(
@@ -486,7 +486,7 @@
         care_map_fp.write(zfp.read(CARE_MAP_ENTRY_NAME))
         care_map_fp.flush()
         dut.adb(["push", care_map_fp.name,
-                 "/data/ota_package/" + CARE_MAP_ENTRY_NAME])
+                "/data/ota_package/" + CARE_MAP_ENTRY_NAME])
 
   if args.file:
     # Update via pushing a file to /data.
@@ -546,7 +546,7 @@
     if server_thread:
       server_thread.StopServer()
     for cmd in finalize_cmds:
-      dut.adb(cmd)
+      dut.adb(cmd, 5)
 
   return 0
 
diff --git a/update_metadata.proto b/update_metadata.proto
index bc9e34a..93e4e2e 100644
--- a/update_metadata.proto
+++ b/update_metadata.proto
@@ -359,6 +359,10 @@
   // See system/core/fs_mgr/libsnapshot/cow_writer.cpp for available options,
   // as this parameter is ultimated forwarded to libsnapshot's CowWriter
   optional string vabc_compression_param = 4;
+
+  // COW version used by VABC. The represents the major version in the COW
+  // header
+  optional uint32 cow_version = 5;
 }
 
 // Definition has been duplicated from