Merge "Allow /postinstall files to have custom contexts" into sc-dev
diff --git a/Android.bp b/Android.bp
index bc178bc..a4b7978 100644
--- a/Android.bp
+++ b/Android.bp
@@ -224,6 +224,7 @@
         "payload_consumer/bzip_extent_writer.cc",
         "payload_consumer/cached_file_descriptor.cc",
         "payload_consumer/certificate_parser_android.cc",
+        "payload_consumer/cow_writer_file_descriptor.cc",
         "payload_consumer/delta_performer.cc",
         "payload_consumer/extent_reader.cc",
         "payload_consumer/extent_writer.cc",
diff --git a/aosp/dynamic_partition_control_android.cc b/aosp/dynamic_partition_control_android.cc
index 4220445..444fe42 100644
--- a/aosp/dynamic_partition_control_android.cc
+++ b/aosp/dynamic_partition_control_android.cc
@@ -49,6 +49,7 @@
 #include "update_engine/common/dynamic_partition_control_interface.h"
 #include "update_engine/common/platform_constants.h"
 #include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/cow_writer_file_descriptor.h"
 #include "update_engine/payload_consumer/delta_performer.h"
 
 using android::base::GetBoolProperty;
@@ -283,6 +284,7 @@
 }
 
 bool DynamicPartitionControlAndroid::UnmapAllPartitions() {
+  snapshot_->UnmapAllSnapshots();
   if (mapped_devices_.empty()) {
     return false;
   }
@@ -803,9 +805,7 @@
   }
 
   std::string device_dir_str;
-  if (!GetDeviceDir(&device_dir_str)) {
-    return false;
-  }
+  TEST_AND_RETURN_FALSE(GetDeviceDir(&device_dir_str));
   base::FilePath device_dir(device_dir_str);
   auto source_device =
       device_dir.Append(GetSuperPartitionName(source_slot)).value();
@@ -822,21 +822,68 @@
         DeleteSourcePartitions(builder.get(), source_slot, manifest));
   }
 
-  if (!UpdatePartitionMetadata(builder.get(), target_slot, manifest)) {
-    return false;
-  }
+  TEST_AND_RETURN_FALSE(
+      UpdatePartitionMetadata(builder.get(), target_slot, manifest));
 
   auto target_device =
       device_dir.Append(GetSuperPartitionName(target_slot)).value();
   return StoreMetadata(target_device, builder.get(), target_slot);
 }
 
+bool DynamicPartitionControlAndroid::CheckSuperPartitionAllocatableSpace(
+    android::fs_mgr::MetadataBuilder* builder,
+    const DeltaArchiveManifest& manifest,
+    bool use_snapshot) {
+  uint64_t total_size = 0;
+  for (const auto& group : manifest.dynamic_partition_metadata().groups()) {
+    total_size += 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;
+  }
+
+  return true;
+}
+
 bool DynamicPartitionControlAndroid::PrepareSnapshotPartitionsForUpdate(
     uint32_t source_slot,
     uint32_t target_slot,
     const DeltaArchiveManifest& manifest,
     uint64_t* required_size) {
   TEST_AND_RETURN_FALSE(ExpectMetadataMounted());
+
+  std::string device_dir_str;
+  TEST_AND_RETURN_FALSE(GetDeviceDir(&device_dir_str));
+  base::FilePath device_dir(device_dir_str);
+  auto super_device =
+      device_dir.Append(GetSuperPartitionName(source_slot)).value();
+  auto builder = LoadMetadataBuilder(super_device, source_slot);
+  if (builder == nullptr) {
+    LOG(ERROR) << "No metadata at "
+               << BootControlInterface::SlotName(source_slot);
+    return false;
+  }
+
+  TEST_AND_RETURN_FALSE(
+      CheckSuperPartitionAllocatableSpace(builder.get(), manifest, true));
+
   if (!snapshot_->BeginUpdate()) {
     LOG(ERROR) << "Cannot begin new update.";
     return false;
@@ -875,29 +922,8 @@
   const std::string target_suffix = SlotSuffixForSlotNumber(target_slot);
   DeleteGroupsWithSuffix(builder, target_suffix);
 
-  uint64_t total_size = 0;
-  for (const auto& group : manifest.dynamic_partition_metadata().groups()) {
-    total_size += 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.
-  if (!GetDynamicPartitionsFeatureFlag().IsRetrofit() &&
-      !GetVirtualAbFeatureFlag().IsEnabled()) {
-    allocatable_space /= 2;
-    expr = "half of ";
-  }
-  if (total_size > allocatable_space) {
-    LOG(ERROR) << "The maximum size of all groups with suffix " << target_suffix
-               << " (" << total_size << ") has exceeded " << expr
-               << "allocatable space for dynamic partitions "
-               << allocatable_space << ".";
-    return false;
-  }
+  TEST_AND_RETURN_FALSE(
+      CheckSuperPartitionAllocatableSpace(builder, manifest, false));
 
   // name of partition(e.g. "system") -> size in bytes
   std::map<std::string, uint64_t> partition_sizes;
@@ -1165,7 +1191,7 @@
 
   LOG(INFO) << "Will overwrite existing partitions. Slot "
             << BootControlInterface::SlotName(source_slot)
-            << "may be unbootable until update finishes!";
+            << " may be unbootable until update finishes!";
   const std::string source_suffix = SlotSuffixForSlotNumber(source_slot);
   DeleteGroupsWithSuffix(builder, source_suffix);
 
@@ -1328,7 +1354,7 @@
   return snapshot_->OpenSnapshotWriter(params, std::move(source_path));
 }  // namespace chromeos_update_engine
 
-FileDescriptorPtr DynamicPartitionControlAndroid::OpenCowReader(
+FileDescriptorPtr DynamicPartitionControlAndroid::OpenCowFd(
     const std::string& unsuffixed_partition_name,
     const std::optional<std::string>& source_path,
     bool is_append) {
@@ -1337,8 +1363,10 @@
   if (cow_writer == nullptr) {
     return nullptr;
   }
-  cow_writer->InitializeAppend(kEndOfInstallLabel);
-  return cow_writer->OpenReader();
+  if (!cow_writer->InitializeAppend(kEndOfInstallLabel)) {
+    return nullptr;
+  }
+  return std::make_shared<CowWriterFileDescriptor>(std::move(cow_writer));
 }
 
 std::optional<base::FilePath> DynamicPartitionControlAndroid::GetSuperDevice() {
diff --git a/aosp/dynamic_partition_control_android.h b/aosp/dynamic_partition_control_android.h
index 4e75a9b..b7aa7ea 100644
--- a/aosp/dynamic_partition_control_android.h
+++ b/aosp/dynamic_partition_control_android.h
@@ -104,9 +104,9 @@
       const std::string& unsuffixed_partition_name,
       const std::optional<std::string>& source_path,
       bool is_append) override;
-  FileDescriptorPtr OpenCowReader(const std::string& unsuffixed_partition_name,
-                                  const std::optional<std::string>&,
-                                  bool is_append = false) override;
+  FileDescriptorPtr OpenCowFd(const std::string& unsuffixed_partition_name,
+                              const std::optional<std::string>&,
+                              bool is_append = false) override;
 
   bool UnmapAllPartitions() override;
 
@@ -258,6 +258,13 @@
                                           const DeltaArchiveManifest& manifest,
                                           uint64_t* required_size);
 
+  // Returns true if the allocatable space in super partition is larger than
+  // the size of dynamic partition groups in the manifest.
+  bool CheckSuperPartitionAllocatableSpace(
+      android::fs_mgr::MetadataBuilder* builder,
+      const DeltaArchiveManifest& manifest,
+      bool use_snapshot);
+
   enum class DynamicPartitionDeviceStatus {
     SUCCESS,
     ERROR,
diff --git a/aosp/dynamic_partition_control_android_unittest.cc b/aosp/dynamic_partition_control_android_unittest.cc
index eb3f60c..0bb8df7 100644
--- a/aosp/dynamic_partition_control_android_unittest.cc
+++ b/aosp/dynamic_partition_control_android_unittest.cc
@@ -1051,6 +1051,7 @@
 // Test happy path of PreparePartitionsForUpdate on a Virtual A/B device.
 TEST_P(SnapshotPartitionTestP, PreparePartitions) {
   ExpectCreateUpdateSnapshots(android::snapshot::Return::Ok());
+  SetMetadata(source(), {});
   uint64_t required_size = 0;
   EXPECT_TRUE(PreparePartitionsForUpdate(&required_size));
   EXPECT_EQ(0u, required_size);
@@ -1061,6 +1062,8 @@
 TEST_P(SnapshotPartitionTestP, PreparePartitionsNoSpace) {
   ExpectCreateUpdateSnapshots(android::snapshot::Return::NoSpace(1_GiB));
   uint64_t required_size = 0;
+
+  SetMetadata(source(), {});
   EXPECT_FALSE(PreparePartitionsForUpdate(&required_size));
   EXPECT_EQ(1_GiB, required_size);
 }
@@ -1070,6 +1073,10 @@
 TEST_P(SnapshotPartitionTestP, RecoveryUseSuperEmpty) {
   ExpectCreateUpdateSnapshots(android::snapshot::Return::Ok());
   EXPECT_CALL(dynamicControl(), IsRecovery()).WillRepeatedly(Return(true));
+
+  // Metadata is needed to perform super partition size check.
+  SetMetadata(source(), {});
+
   // Must not call PrepareDynamicPartitionsForUpdate if
   // PrepareSnapshotPartitionsForUpdate succeeds.
   EXPECT_CALL(dynamicControl(), PrepareDynamicPartitionsForUpdate(_, _, _, _))
diff --git a/aosp/mock_dynamic_partition_control_android.h b/aosp/mock_dynamic_partition_control_android.h
index d80dfb5..428b6c7 100644
--- a/aosp/mock_dynamic_partition_control_android.h
+++ b/aosp/mock_dynamic_partition_control_android.h
@@ -95,7 +95,7 @@
                bool is_append),
               (override));
   MOCK_METHOD(FileDescriptorPtr,
-              OpenCowReader,
+              OpenCowFd,
               (const std::string& unsuffixed_partition_name,
                const std::optional<std::string>& source_path,
                bool is_append),
diff --git a/common/dynamic_partition_control_interface.h b/common/dynamic_partition_control_interface.h
index da27932..d5e1d8d 100644
--- a/common/dynamic_partition_control_interface.h
+++ b/common/dynamic_partition_control_interface.h
@@ -165,7 +165,9 @@
       const std::string& unsuffixed_partition_name,
       const std::optional<std::string>&,
       bool is_append = false) = 0;
-  virtual FileDescriptorPtr OpenCowReader(
+  // Open a general purpose FD capable to reading and writing to COW. Note that
+  // writes must be block aligned.
+  virtual FileDescriptorPtr OpenCowFd(
       const std::string& unsuffixed_partition_name,
       const std::optional<std::string>&,
       bool is_append = false) = 0;
diff --git a/common/dynamic_partition_control_stub.cc b/common/dynamic_partition_control_stub.cc
index 05803fe..dd30a8b 100644
--- a/common/dynamic_partition_control_stub.cc
+++ b/common/dynamic_partition_control_stub.cc
@@ -98,13 +98,6 @@
   return nullptr;
 }
 
-FileDescriptorPtr DynamicPartitionControlStub::OpenCowReader(
-    const std::string& unsuffixed_partition_name,
-    const std::optional<std::string>&,
-    bool /*is_append */) {
-  return nullptr;
-}
-
 bool DynamicPartitionControlStub::MapAllPartitions() {
   return false;
 }
diff --git a/common/dynamic_partition_control_stub.h b/common/dynamic_partition_control_stub.h
index eb7154c..515ec7c 100644
--- a/common/dynamic_partition_control_stub.h
+++ b/common/dynamic_partition_control_stub.h
@@ -64,9 +64,12 @@
       const std::string& unsuffixed_partition_name,
       const std::optional<std::string>&,
       bool is_append) override;
-  FileDescriptorPtr OpenCowReader(const std::string& unsuffixed_partition_name,
-                                  const std::optional<std::string>&,
-                                  bool is_append = false) override;
+
+  FileDescriptorPtr OpenCowFd(const std::string& unsuffixed_partition_name,
+                              const std::optional<std::string>&,
+                              bool is_append = false) override {
+    return nullptr;
+  }
 
   bool MapAllPartitions() override;
   bool UnmapAllPartitions() override;
diff --git a/common/mock_dynamic_partition_control.h b/common/mock_dynamic_partition_control.h
index 391d3eb..bfd1b0c 100644
--- a/common/mock_dynamic_partition_control.h
+++ b/common/mock_dynamic_partition_control.h
@@ -37,7 +37,7 @@
   MOCK_METHOD(FeatureFlag, GetVirtualAbFeatureFlag, (), (override));
   MOCK_METHOD(bool, FinishUpdate, (bool), (override));
   MOCK_METHOD(FileDescriptorPtr,
-              OpenCowReader,
+              OpenCowFd,
               (const std::string& unsuffixed_partition_name,
                const std::optional<std::string>& source_path,
                bool is_append),
diff --git a/download_action_android_unittest.cc b/download_action_android_unittest.cc
index b17550b..fef2d24 100644
--- a/download_action_android_unittest.cc
+++ b/download_action_android_unittest.cc
@@ -36,6 +36,7 @@
 #include "update_engine/common/utils.h"
 #include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/annotated_operation.h"
 #include "update_engine/payload_generator/payload_file.h"
 #include "update_engine/payload_generator/payload_signer.h"
 
@@ -149,13 +150,16 @@
   payload.size = data.size();
   payload.payload_urls.emplace_back("http://fake_url.invalid");
   install_plan.is_resume = true;
+  auto& install_part = install_plan.partitions.emplace_back();
+  install_part.source_path = partition_file.path();
+  install_part.target_path = partition_file.path();
   action_pipe->set_contents(install_plan);
 
+  FakeHardware hardware;
   // takes ownership of passed in HttpFetcher
   auto download_action = std::make_unique<DownloadAction>(
-      &prefs, &boot_control, nullptr, http_fetcher, false /* interactive */);
+      &prefs, &boot_control, &hardware, http_fetcher, false /* interactive */);
 
-  FakeHardware hardware;
   auto delta_performer = std::make_unique<DeltaPerformer>(&prefs,
                                                           &boot_control,
                                                           &hardware,
diff --git a/payload_consumer/cow_writer_file_descriptor.cc b/payload_consumer/cow_writer_file_descriptor.cc
new file mode 100644
index 0000000..d8c7afb
--- /dev/null
+++ b/payload_consumer/cow_writer_file_descriptor.cc
@@ -0,0 +1,138 @@
+//
+// 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 <memory>
+#include <utility>
+
+#include <base/logging.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+
+namespace chromeos_update_engine {
+CowWriterFileDescriptor::CowWriterFileDescriptor(
+    std::unique_ptr<android::snapshot::ISnapshotWriter> cow_writer)
+    : cow_writer_(std::move(cow_writer)),
+      cow_reader_(cow_writer_->OpenReader()) {}
+
+bool CowWriterFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+  LOG(ERROR) << "CowWriterFileDescriptor doesn't support Open()";
+  return false;
+}
+bool CowWriterFileDescriptor::Open(const char* path, int flags) {
+  LOG(ERROR) << "CowWriterFileDescriptor doesn't support Open()";
+  return false;
+}
+
+ssize_t CowWriterFileDescriptor::Read(void* buf, size_t count) {
+  if (dirty_) {
+    // OK, CowReader provides a snapshot view of what the cow contains. Which
+    // means any writes happened after opening a CowReader isn't visible to
+    // that CowReader. Therefore, we re-open CowReader whenever we attempt a
+    // read after write. This does incur an overhead everytime you read after
+    // write.
+    // The usage of |dirty_| flag to coordinate re-open is a very coarse grained
+    // checked. This implementation has suboptimal performance. For better
+    // performance, keep track of blocks which are overwritten, and only re-open
+    // if reading a dirty block.
+    // TODO(b/173432386) Implement finer grained dirty checks
+    const auto offset = cow_reader_->Seek(0, SEEK_CUR);
+    cow_reader_.reset();
+    if (!cow_writer_->Finalize()) {
+      LOG(ERROR) << "Failed to Finalize() cow writer";
+      return -1;
+    }
+    cow_reader_ = cow_writer_->OpenReader();
+    if (cow_reader_ == nullptr) {
+      LOG(ERROR) << "Failed to re-open cow reader after writing to COW";
+      return -1;
+    }
+    const auto pos = cow_reader_->Seek(offset, SEEK_SET);
+    if (pos != offset) {
+      LOG(ERROR) << "Failed to seek to previous position after re-opening cow "
+                    "reader, expected "
+                 << offset << " actual: " << pos;
+      return -1;
+    }
+    dirty_ = false;
+  }
+  return cow_reader_->Read(buf, count);
+}
+
+ssize_t CowWriterFileDescriptor::Write(const void* buf, size_t count) {
+  auto offset = cow_reader_->Seek(0, SEEK_CUR);
+  CHECK_EQ(offset % cow_writer_->options().block_size, 0);
+  auto success = cow_writer_->AddRawBlocks(
+      offset / cow_writer_->options().block_size, buf, count);
+  if (success) {
+    if (cow_reader_->Seek(count, SEEK_CUR) < 0) {
+      return -1;
+    }
+    dirty_ = true;
+    return count;
+  }
+  return -1;
+}
+
+off64_t CowWriterFileDescriptor::Seek(const off64_t offset, int whence) {
+  return cow_reader_->Seek(offset, whence);
+}
+
+uint64_t CowWriterFileDescriptor::BlockDevSize() {
+  LOG(ERROR) << "CowWriterFileDescriptor doesn't support BlockDevSize()";
+  return 0;
+}
+
+bool CowWriterFileDescriptor::BlkIoctl(int request,
+                                       uint64_t start,
+                                       uint64_t length,
+                                       int* result) {
+  LOG(ERROR) << "CowWriterFileDescriptor doesn't support BlkIoctl()";
+  return false;
+}
+
+bool CowWriterFileDescriptor::Flush() {
+  // CowWriter already automatilly flushes, no need to do anything.
+  return true;
+}
+
+bool CowWriterFileDescriptor::Close() {
+  if (cow_writer_) {
+    TEST_AND_RETURN_FALSE(cow_writer_->Finalize());
+    cow_writer_ = nullptr;
+  }
+  if (cow_reader_) {
+    TEST_AND_RETURN_FALSE(cow_reader_->Close());
+    cow_reader_ = nullptr;
+  }
+  return true;
+}
+
+bool CowWriterFileDescriptor::IsSettingErrno() {
+  return false;
+}
+
+bool CowWriterFileDescriptor::IsOpen() {
+  return cow_writer_ != nullptr && cow_reader_ != nullptr;
+}
+
+CowWriterFileDescriptor::~CowWriterFileDescriptor() {
+  Close();
+}
+
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/cow_writer_file_descriptor.h b/payload_consumer/cow_writer_file_descriptor.h
new file mode 100644
index 0000000..5d9ffc6
--- /dev/null
+++ b/payload_consumer/cow_writer_file_descriptor.h
@@ -0,0 +1,66 @@
+//
+// 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 <cstdint>
+#include <memory>
+
+#include <libsnapshot/snapshot_writer.h>
+
+#include "update_engine/payload_consumer/file_descriptor.h"
+
+namespace chromeos_update_engine {
+
+// A Readable/Writable FileDescriptor class. This is a simple wrapper around
+// CowWriter. Only intended to be used by FileSystemVerifierAction for writing
+// FEC. Writes must be block aligned(4096) or write will fail.
+class CowWriterFileDescriptor final : public FileDescriptor {
+ public:
+  explicit CowWriterFileDescriptor(
+      std::unique_ptr<android::snapshot::ISnapshotWriter> cow_writer);
+  ~CowWriterFileDescriptor();
+
+  bool Open(const char* path, int flags, mode_t mode) override;
+  bool Open(const char* path, int flags) override;
+
+  ssize_t Read(void* buf, size_t count) override;
+
+  // |count| must be block aligned, current offset of this fd must also be block
+  // aligned.
+  ssize_t Write(const void* buf, size_t count) override;
+
+  off64_t Seek(off64_t offset, int whence) override;
+
+  uint64_t BlockDevSize() override;
+
+  bool BlkIoctl(int request,
+                uint64_t start,
+                uint64_t length,
+                int* result) override;
+
+  bool Flush() override;
+
+  bool Close() override;
+
+  bool IsSettingErrno() override;
+
+  bool IsOpen() override;
+
+ private:
+  std::unique_ptr<android::snapshot::ISnapshotWriter> cow_writer_;
+  FileDescriptorPtr cow_reader_;
+  bool dirty_ = false;
+};
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index 82e589e..a57169b 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -584,6 +584,11 @@
     CheckpointUpdateProgress(false);
   }
 
+  if (partition_writer_) {
+    TEST_AND_RETURN_FALSE(partition_writer_->FinishedInstallOps());
+  }
+  CloseCurrentPartition();
+
   // In major version 2, we don't add unused operation to the payload.
   // If we already extracted the signature we should skip this step.
   if (manifest_.has_signatures_offset() && manifest_.has_signatures_size() &&
diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc
index aa2fbaa..09dc638 100644
--- a/payload_consumer/filesystem_verifier_action.cc
+++ b/payload_consumer/filesystem_verifier_action.cc
@@ -32,8 +32,10 @@
 #include <base/strings/string_util.h>
 #include <brillo/data_encoding.h>
 #include <brillo/message_loops/message_loop.h>
+#include <brillo/secure_blob.h>
 #include <brillo/streams/file_stream.h>
 
+#include "payload_generator/delta_diff_generator.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/payload_consumer/file_descriptor.h"
 
@@ -106,8 +108,7 @@
 }
 
 void FilesystemVerifierAction::Cleanup(ErrorCode code) {
-  read_fd_.reset();
-  write_fd_.reset();
+  partition_fd_.reset();
   // This memory is not used anymore.
   buffer_.clear();
 
@@ -129,36 +130,30 @@
   const InstallPlan::Partition& partition =
       install_plan_.partitions[partition_index_];
 
-  read_fd_ = dynamic_control_->OpenCowReader(
-      partition.name, partition.source_path, true);
-  if (!read_fd_) {
+  // FilesystemVerifierAction need the read_fd_.
+  partition_fd_ =
+      dynamic_control_->OpenCowFd(partition.name, partition.source_path, true);
+  if (!partition_fd_) {
     LOG(ERROR) << "OpenCowReader(" << partition.name << ", "
                << partition.source_path << ") failed.";
     return false;
   }
   partition_size_ = partition.target_size;
-  // TODO(b/173432386): Support Verity writes for VABC.
-  CHECK_EQ(partition.fec_size, 0U);
-  CHECK_EQ(partition.hash_tree_size, 0U);
   return true;
 }
 
 bool FilesystemVerifierAction::InitializeFd(const std::string& part_path) {
-  read_fd_ = FileDescriptorPtr(new EintrSafeFileDescriptor());
-  if (!read_fd_->Open(part_path.c_str(), O_RDONLY)) {
+  partition_fd_ = FileDescriptorPtr(new EintrSafeFileDescriptor());
+  const bool write_verity = ShouldWriteVerity();
+  int flags = write_verity ? O_RDWR : O_RDONLY;
+  if (!utils::SetBlockDeviceReadOnly(part_path, !write_verity)) {
+    LOG(WARNING) << "Failed to set block device " << part_path << " as "
+                 << (write_verity ? "writable" : "readonly");
+  }
+  if (!partition_fd_->Open(part_path.c_str(), flags)) {
     LOG(ERROR) << "Unable to open " << part_path << " for reading.";
     return false;
   }
-
-  // Can't re-use |read_fd_|, as verity writer may call `seek` to modify state
-  // of a file descriptor.
-  if (ShouldWriteVerity()) {
-    write_fd_ = FileDescriptorPtr(new EintrSafeFileDescriptor());
-    if (!write_fd_->Open(part_path.c_str(), O_RDWR)) {
-      LOG(ERROR) << "Unable to open " << part_path << " for Read/Write.";
-      return false;
-    }
-  }
   return true;
 }
 
@@ -238,9 +233,12 @@
   }
   if (ShouldWriteVerity()) {
     if (!verity_writer_->Init(partition)) {
+      LOG(INFO) << "Verity writes enabled on partition " << partition.name;
       Cleanup(ErrorCode::kVerityCalculationError);
       return;
     }
+  } else {
+    LOG(INFO) << "Verity writes disabled on partition " << partition.name;
   }
 
   // Start the first read.
@@ -257,19 +255,19 @@
 
 void FilesystemVerifierAction::ReadVerityAndFooter() {
   if (ShouldWriteVerity()) {
-    if (!verity_writer_->Finalize(read_fd_, write_fd_)) {
+    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_;
-  // Since we handed our |read_fd_| to verity_writer_ during |Finalize()| call,
-  // fd's position could have been changed. Re-seek.
-  read_fd_->Seek(filesystem_data_end_, SEEK_SET);
   while (bytes_to_read > 0) {
     const auto read_size = std::min<size_t>(buffer_.size(), bytes_to_read);
-    auto bytes_read = read_fd_->Read(buffer_.data(), read_size);
+    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);
@@ -296,10 +294,11 @@
     ReadVerityAndFooter();
     return;
   }
-
-  auto bytes_read = read_fd_->Read(buffer_.data(), bytes_to_read);
+  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.";
+    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|
@@ -424,11 +423,8 @@
   // Start hashing the next partition, if any.
   hasher_.reset();
   buffer_.clear();
-  if (read_fd_) {
-    read_fd_.reset();
-  }
-  if (write_fd_) {
-    write_fd_.reset();
+  if (partition_fd_) {
+    partition_fd_.reset();
   }
   StartPartitionHashing();
 }
diff --git a/payload_consumer/filesystem_verifier_action.h b/payload_consumer/filesystem_verifier_action.h
index 1f527c9..78634cb 100644
--- a/payload_consumer/filesystem_verifier_action.h
+++ b/payload_consumer/filesystem_verifier_action.h
@@ -126,14 +126,8 @@
   size_t partition_index_{0};
 
   // If not null, the FileDescriptor used to read from the device.
-  // |read_fd_| and |write_fd_| will be initialized when we begin hashing a
-  // partition. They will be deallocated once we encounter an error or
-  // successfully finished hashing.
-  FileDescriptorPtr read_fd_;
-  // If not null, the FileDescriptor used to write to the device.
-  // For VABC, this will be different from |read_fd_|. For other cases
-  // this can be the same as |read_fd_|.
-  FileDescriptorPtr write_fd_;
+  // verity writer might attempt to write to this fd, if verity is enabled.
+  FileDescriptorPtr partition_fd_;
 
   // Buffer for storing data we read.
   brillo::Blob buffer_;
diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc
index f618626..2cad523 100644
--- a/payload_consumer/filesystem_verifier_action_unittest.cc
+++ b/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -33,6 +33,7 @@
 #include "update_engine/common/mock_dynamic_partition_control.h"
 #include "update_engine/common/test_utils.h"
 #include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/fake_file_descriptor.h"
 #include "update_engine/payload_consumer/install_plan.h"
 
 using brillo::MessageLoop;
@@ -83,7 +84,8 @@
     if (action->Type() == FilesystemVerifierAction::StaticType()) {
       ran_ = true;
       code_ = code;
-      EXPECT_FALSE(static_cast<FilesystemVerifierAction*>(action)->read_fd_);
+      EXPECT_FALSE(
+          static_cast<FilesystemVerifierAction*>(action)->partition_fd_);
     } else if (action->Type() ==
                ObjectCollectorAction<InstallPlan>::StaticType()) {
       auto collector_action =
@@ -251,8 +253,8 @@
   processor_.set_delegate(&delegate);
 
   processor_.StartProcessing();
-  EXPECT_FALSE(processor_.IsRunning());
-  EXPECT_TRUE(delegate.ran_);
+  ASSERT_FALSE(processor_.IsRunning());
+  ASSERT_TRUE(delegate.ran_);
   EXPECT_EQ(ErrorCode::kError, delegate.code_);
 }
 
@@ -397,12 +399,12 @@
           base::Unretained(&processor_)));
   loop_.Run();
 
-  EXPECT_FALSE(processor_.IsRunning());
-  EXPECT_TRUE(delegate.ran());
-  EXPECT_EQ(ErrorCode::kSuccess, delegate.code());
+  ASSERT_FALSE(processor_.IsRunning());
+  ASSERT_TRUE(delegate.ran());
+  ASSERT_EQ(ErrorCode::kSuccess, delegate.code());
 }
 
-TEST_F(FilesystemVerifierActionTest, RunWithVABC) {
+TEST_F(FilesystemVerifierActionTest, RunWithVABCNoVerity) {
   InstallPlan install_plan;
   InstallPlan::Partition& part = install_plan.partitions.emplace_back();
   part.name = "fake_part";
@@ -410,21 +412,25 @@
   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();
 
   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, OpenCowReader(_, _, _))
-      .WillByDefault(Return(nullptr));
+  ON_CALL(dynamic_control, OpenCowFd(_, _, _)).WillByDefault(Return(fake_fd));
   ON_CALL(dynamic_control, IsDynamicPartition(part.name, _))
       .WillByDefault(Return(true));
 
   EXPECT_CALL(dynamic_control, UpdateUsesSnapshotCompression())
       .Times(AtLeast(1));
-  EXPECT_CALL(dynamic_control, OpenCowReader(part.name, {part.source_path}, _))
+  EXPECT_CALL(dynamic_control, OpenCowFd(part.name, {part.source_path}, _))
       .Times(1);
   EXPECT_CALL(dynamic_control, ListDynamicPartitionsForSlot(_, _, _))
       .WillRepeatedly(
@@ -443,10 +449,18 @@
           base::Unretained(&processor_)));
   loop_.Run();
 
-  EXPECT_FALSE(processor_.IsRunning());
-  EXPECT_TRUE(delegate.ran());
-  // Filesystem verifier will fail, because we returned nullptr as CowReader
-  EXPECT_EQ(ErrorCode::kFilesystemVerifierError, delegate.code());
+  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;
+  }
+  const auto actual_read_size = expected_offset;
+  ASSERT_EQ(actual_read_size, part.target_size);
 }
 
 }  // namespace chromeos_update_engine
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
index 5e42089..8f2d674 100644
--- a/payload_consumer/postinstall_runner_action.cc
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -401,6 +401,10 @@
   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/verity_writer_android.cc b/payload_consumer/verity_writer_android.cc
index 01d8977..e2fab7d 100644
--- a/payload_consumer/verity_writer_android.cc
+++ b/payload_consumer/verity_writer_android.cc
@@ -43,9 +43,6 @@
 bool VerityWriterAndroid::Init(const InstallPlan::Partition& partition) {
   partition_ = &partition;
 
-  if (partition_->hash_tree_size != 0 || partition_->fec_size != 0) {
-    utils::SetBlockDeviceReadOnly(partition_->target_path, false);
-  }
   if (partition_->hash_tree_size != 0) {
     auto hash_function =
         HashTreeBuilder::HashFunction(partition_->hash_tree_algorithm);
@@ -67,12 +64,18 @@
       return false;
     }
   }
+  total_offset_ = 0;
   return true;
 }
 
-bool VerityWriterAndroid::Update(uint64_t offset,
+bool VerityWriterAndroid::Update(const uint64_t offset,
                                  const uint8_t* buffer,
                                  size_t size) {
+  if (offset != total_offset_) {
+    LOG(ERROR) << "Sequential read expected, expected to read at: "
+               << total_offset_ << " actual read occurs at: " << offset;
+    return false;
+  }
   if (partition_->hash_tree_size != 0) {
     const uint64_t hash_tree_data_end =
         partition_->hash_tree_data_offset + partition_->hash_tree_data_size;
@@ -96,12 +99,22 @@
       }
     }
   }
+  total_offset_ += size;
 
   return true;
 }
 
 bool VerityWriterAndroid::Finalize(FileDescriptorPtr read_fd,
                                    FileDescriptorPtr write_fd) {
+  const auto hash_tree_data_end =
+      partition_->hash_tree_data_offset + partition_->hash_tree_data_size;
+  if (total_offset_ < hash_tree_data_end) {
+    LOG(ERROR) << "Read up to " << total_offset_
+               << " when we are expecting to read everything "
+                  "before "
+               << hash_tree_data_end;
+    return false;
+  }
   // All hash tree data blocks has been hashed, write hash tree to disk.
   LOG(INFO) << "Writing verity hash tree to " << partition_->target_path;
   TEST_AND_RETURN_FALSE(hash_tree_builder_->BuildHashTree());
diff --git a/payload_consumer/verity_writer_android.h b/payload_consumer/verity_writer_android.h
index e95f188..8339528 100644
--- a/payload_consumer/verity_writer_android.h
+++ b/payload_consumer/verity_writer_android.h
@@ -64,6 +64,7 @@
   const InstallPlan::Partition* partition_ = nullptr;
 
   std::unique_ptr<HashTreeBuilder> hash_tree_builder_;
+  uint64_t total_offset_ = 0;
   DISALLOW_COPY_AND_ASSIGN(VerityWriterAndroid);
 };
 
diff --git a/payload_consumer/verity_writer_android_unittest.cc b/payload_consumer/verity_writer_android_unittest.cc
index 81d8ca2..75da0ae 100644
--- a/payload_consumer/verity_writer_android_unittest.cc
+++ b/payload_consumer/verity_writer_android_unittest.cc
@@ -76,6 +76,15 @@
   ASSERT_TRUE(verity_writer_.Update(8192, part_data.data(), part_data.size()));
 }
 
+TEST_F(VerityWriterAndroidTest, DiscontinuedRead) {
+  partition_.hash_tree_data_size = 8192;
+  partition_.hash_tree_size = 4096;
+  brillo::Blob part_data(4096);
+  ASSERT_TRUE(verity_writer_.Init(partition_));
+  ASSERT_TRUE(verity_writer_.Update(0, part_data.data(), part_data.size()));
+  ASSERT_FALSE(verity_writer_.Update(8192, part_data.data(), part_data.size()));
+}
+
 TEST_F(VerityWriterAndroidTest, InvalidHashAlgorithmTest) {
   partition_.hash_tree_algorithm = "sha123";
   ASSERT_FALSE(verity_writer_.Init(partition_));
@@ -106,6 +115,32 @@
   ASSERT_EQ(part_data, actual_part);
 }
 
+TEST_F(VerityWriterAndroidTest, NonZeroOffsetSHA256Test) {
+  partition_.hash_tree_algorithm = "sha256";
+  partition_.hash_tree_data_offset = 100;
+  partition_.hash_tree_offset =
+      partition_.hash_tree_data_offset + partition_.hash_tree_data_size;
+  brillo::Blob part_data(8192 + partition_.hash_tree_data_offset);
+  test_utils::WriteFileVector(partition_.target_path, part_data);
+  ASSERT_TRUE(verity_writer_.Init(partition_));
+  ASSERT_TRUE(verity_writer_.Update(0, part_data.data(), 4096));
+  ASSERT_TRUE(verity_writer_.Update(4096, part_data.data() + 4096, 4096));
+  ASSERT_TRUE(verity_writer_.Update(
+      8192, part_data.data() + 8192, partition_.hash_tree_data_offset));
+  ASSERT_TRUE(verity_writer_.Finalize(partition_fd_, partition_fd_));
+  brillo::Blob actual_part;
+  utils::ReadFile(partition_.target_path, &actual_part);
+  // dd if=/dev/zero bs=4096 count=1 2>/dev/null | sha256sum | xxd -r -p |
+  //     hexdump -v -e '/1 "0x%02x, "'
+  brillo::Blob hash = {0xad, 0x7f, 0xac, 0xb2, 0x58, 0x6f, 0xc6, 0xe9,
+                       0x66, 0xc0, 0x04, 0xd7, 0xd1, 0xd1, 0x6b, 0x02,
+                       0x4f, 0x58, 0x05, 0xff, 0x7c, 0xb4, 0x7c, 0x7a,
+                       0x85, 0xda, 0xbd, 0x8b, 0x48, 0x89, 0x2c, 0xa7};
+  memcpy(
+      part_data.data() + partition_.hash_tree_offset, hash.data(), hash.size());
+  ASSERT_EQ(part_data, actual_part);
+}
+
 TEST_F(VerityWriterAndroidTest, FECTest) {
   partition_.fec_data_offset = 0;
   partition_.fec_data_size = 4096;
diff --git a/payload_generator/delta_diff_generator.cc b/payload_generator/delta_diff_generator.cc
index a87dabf..ab0f036 100644
--- a/payload_generator/delta_diff_generator.cc
+++ b/payload_generator/delta_diff_generator.cc
@@ -137,6 +137,12 @@
         {cow_merge_sequence_->begin(), cow_merge_sequence_->end()},
         config_.block_size,
         config_.target.dynamic_partition_metadata->vabc_compression_param());
+    if (!new_part_.disable_fec_computation) {
+      *cow_size_ +=
+          new_part_.verity.fec_extent.num_blocks() * config_.block_size;
+    }
+    *cow_size_ +=
+        new_part_.verity.hash_tree_extent.num_blocks() * config_.block_size;
     LOG(INFO) << "Estimated COW size for partition: " << new_part_.name << " "
               << *cow_size_;
   }