update_engine resize dynamic partitions during OTA.

update_engine uses device mapper to resize dynamic partitions
before opening the devices to apply the update.

* DeltaPerformer calls BootControlInterface::InitPartitionMetadata
  when parsing the update manifest. The implementation for
  BootControlAndroid::InitPartitionMetadata does the following
  if sizes for dynamic partitions are incorrect (assuming updating
  from slot A to B):
  * Load metadata from metadata slot A
  * Delete all extents of partitions at slot B (with _b suffix)
  * Add extents for partitions at slot B
  * Write metadata to metadata slot B
  * Re-map all partitions at slot B using metadata slot B with
    force_writable = true
* BootControlAndroid::GetPartitionDevice() checks device-mapper
  before returning static partitions.
* PostinstallRunnerAction::Cleanup calls BootControlInterface::Cleanup
  which unmaps all partitions at slot B.

A partition "foo" is considered dynamic if foo_a exists as a dynamic
partition OR foo_b does NOT exist as a static partition.

Bug: 110717529

Test: manual ota
Test: update_engine_unittests --gtest_filter=*BootControlAndroid*
Change-Id: I50f410b486a874242663624801c3694151bdda18
diff --git a/Android.mk b/Android.mk
index c49d3c2..062babb 100644
--- a/Android.mk
+++ b/Android.mk
@@ -216,8 +216,10 @@
 
 ue_libupdate_engine_boot_control_exported_shared_libraries := \
     libbootloader_message \
+    libfs_mgr \
     libhwbinder \
     libhidlbase \
+    liblp \
     libutils \
     [email protected] \
     $(ue_update_metadata_protos_exported_shared_libraries)
@@ -238,7 +240,8 @@
     $(ue_common_shared_libraries) \
     $(ue_libupdate_engine_boot_control_exported_shared_libraries)
 LOCAL_SRC_FILES := \
-    boot_control_android.cc
+    boot_control_android.cc \
+    dynamic_partition_control_android.cc
 include $(BUILD_STATIC_LIBRARY)
 
 ifeq ($(local_use_omaha),1)
@@ -919,6 +922,7 @@
     $(ue_common_static_libraries) \
     $(ue_libpayload_generator_exported_static_libraries)
 LOCAL_SHARED_LIBRARIES := \
+    libhidltransport \
     $(ue_common_shared_libraries) \
     $(ue_libpayload_generator_exported_shared_libraries)
 LOCAL_SRC_FILES := \
@@ -1029,6 +1033,7 @@
 LOCAL_SHARED_LIBRARIES += \
     $(ue_libupdate_engine_android_exported_shared_libraries)
 LOCAL_SRC_FILES += \
+    boot_control_android_unittest.cc \
     update_attempter_android_unittest.cc
 endif  # local_use_omaha == 1
 include $(BUILD_NATIVE_TEST)
diff --git a/boot_control_android.cc b/boot_control_android.cc
index 12a3a10..abf898e 100644
--- a/boot_control_android.cc
+++ b/boot_control_android.cc
@@ -20,22 +20,32 @@
 #include <utility>
 
 #include <base/bind.h>
-#include <base/files/file_util.h>
 #include <base/logging.h>
 #include <bootloader_message/bootloader_message.h>
 #include <brillo/message_loops/message_loop.h>
 
 #include "update_engine/common/utils.h"
+#include "update_engine/dynamic_partition_control_android.h"
 
 using std::string;
 
+using android::dm::DmDeviceState;
+using android::fs_mgr::MetadataBuilder;
+using android::fs_mgr::Partition;
+using android::fs_mgr::UpdatePartitionTable;
+using android::hardware::hidl_string;
 using android::hardware::Return;
 using android::hardware::boot::V1_0::BoolResult;
 using android::hardware::boot::V1_0::CommandResult;
 using android::hardware::boot::V1_0::IBootControl;
-using android::hardware::hidl_string;
+using Slot = chromeos_update_engine::BootControlInterface::Slot;
+using PartitionSizes =
+    chromeos_update_engine::BootControlInterface::PartitionSizes;
 
 namespace {
+
+constexpr char kZeroGuid[] = "00000000-0000-0000-0000-000000000000";
+
 auto StoreResultCallback(CommandResult* dest) {
   return [dest](const CommandResult& result) { *dest = result; };
 }
@@ -47,7 +57,7 @@
 
 // Factory defined in boot_control.h.
 std::unique_ptr<BootControlInterface> CreateBootControl() {
-  std::unique_ptr<BootControlAndroid> boot_control(new BootControlAndroid());
+  auto boot_control = std::make_unique<BootControlAndroid>();
   if (!boot_control->Init()) {
     return nullptr;
   }
@@ -65,9 +75,15 @@
 
   LOG(INFO) << "Loaded boot control hidl hal.";
 
+  dynamic_control_ = std::make_unique<DynamicPartitionControlAndroid>();
+
   return true;
 }
 
+void BootControlAndroid::Cleanup() {
+  dynamic_control_->Cleanup();
+}
+
 unsigned int BootControlAndroid::GetNumSlots() const {
   return module_->getNumberSlots();
 }
@@ -76,43 +92,9 @@
   return module_->getCurrentSlot();
 }
 
-bool BootControlAndroid::GetPartitionDevice(const string& partition_name,
-                                            Slot slot,
-                                            string* device) const {
-  // We can't use fs_mgr to look up |partition_name| because fstab
-  // doesn't list every slot partition (it uses the slotselect option
-  // to mask the suffix).
-  //
-  // We can however assume that there's an entry for the /misc mount
-  // point and use that to get the device file for the misc
-  // partition. This helps us locate the disk that |partition_name|
-  // resides on. From there we'll assume that a by-name scheme is used
-  // so we can just replace the trailing "misc" by the given
-  // |partition_name| and suffix corresponding to |slot|, e.g.
-  //
-  //   /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
-  //   /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
-  //
-  // If needed, it's possible to relax the by-name assumption in the
-  // future by trawling /sys/block looking for the appropriate sibling
-  // of misc and then finding an entry in /dev matching the sysfs
-  // entry.
-
-  string err, misc_device = get_bootloader_message_blk_device(&err);
-  if (misc_device.empty()) {
-    LOG(ERROR) << "Unable to get misc block device: " << err;
-    return false;
-  }
-
-  if (!utils::IsSymlink(misc_device.c_str())) {
-    LOG(ERROR) << "Device file " << misc_device << " for /misc "
-               << "is not a symlink.";
-    return false;
-  }
-
-  string suffix;
+bool BootControlAndroid::GetSuffix(Slot slot, string* suffix) const {
   auto store_suffix_cb = [&suffix](hidl_string cb_suffix) {
-    suffix = cb_suffix.c_str();
+    *suffix = cb_suffix.c_str();
   };
   Return<void> ret = module_->getSuffix(slot, store_suffix_cb);
 
@@ -121,10 +103,56 @@
                << SlotName(slot);
     return false;
   }
+  return true;
+}
+
+bool BootControlAndroid::GetPartitionDevice(const string& partition_name,
+                                            Slot slot,
+                                            string* device) const {
+  string suffix;
+  if (!GetSuffix(slot, &suffix)) {
+    return false;
+  }
+
+  const string target_partition_name = partition_name + suffix;
+
+  // DeltaPerformer calls InitPartitionMetadata before calling
+  // InstallPlan::LoadPartitionsFromSlots. After InitPartitionMetadata,
+  // the partition must be re-mapped with force_writable == true. Hence,
+  // we only need to check device mapper.
+  if (dynamic_control_->IsDynamicPartitionsEnabled()) {
+    switch (dynamic_control_->GetState(target_partition_name)) {
+      case DmDeviceState::ACTIVE:
+        if (dynamic_control_->GetDmDevicePathByName(target_partition_name,
+                                                    device)) {
+          LOG(INFO) << target_partition_name
+                    << " is mapped on device mapper: " << *device;
+          return true;
+        }
+        LOG(ERROR) << target_partition_name
+                   << " is mapped but path is unknown.";
+        return false;
+
+      case DmDeviceState::INVALID:
+        // Try static partitions.
+        break;
+
+      case DmDeviceState::SUSPENDED:  // fallthrough
+      default:
+        LOG(ERROR) << target_partition_name
+                   << " is mapped on device mapper but state is unknown";
+        return false;
+    }
+  }
+
+  string device_dir_str;
+  if (!dynamic_control_->GetDeviceDir(&device_dir_str)) {
+    return false;
+  }
 
   base::FilePath path =
-      base::FilePath(misc_device).DirName().Append(partition_name + suffix);
-  if (!base::PathExists(path)) {
+      base::FilePath(device_dir_str).Append(target_partition_name);
+  if (!dynamic_control_->DeviceExists(path.value())) {
     LOG(ERROR) << "Device file " << path.value() << " does not exist.";
     return false;
   }
@@ -196,4 +224,250 @@
          brillo::MessageLoop::kTaskIdNull;
 }
 
+namespace {
+
+// Resize |partition_name|_|slot| to the given |size|.
+bool ResizePartition(MetadataBuilder* builder,
+                     const string& target_partition_name,
+                     uint64_t size) {
+  Partition* partition = builder->FindPartition(target_partition_name);
+  if (partition == nullptr) {
+    LOG(ERROR) << "Cannot find " << target_partition_name << " in metadata.";
+    return false;
+  }
+
+  uint64_t old_size = partition->size();
+  const string action = "resize " + target_partition_name + " in super (" +
+                        std::to_string(old_size) + " -> " +
+                        std::to_string(size) + " bytes)";
+  if (!builder->ResizePartition(partition, size)) {
+    LOG(ERROR) << "Cannot " << action << "; see previous log messages.";
+    return false;
+  }
+
+  if (partition->size() != size) {
+    LOG(ERROR) << "Cannot " << action
+               << "; value is misaligned and partition should have been "
+               << partition->size();
+    return false;
+  }
+
+  LOG(INFO) << "Successfully " << action;
+
+  return true;
+}
+
+bool ResizePartitions(DynamicPartitionControlInterface* dynamic_control,
+                      const string& super_device,
+                      Slot target_slot,
+                      const string& target_suffix,
+                      const PartitionSizes& logical_sizes,
+                      MetadataBuilder* builder) {
+  // Delete all extents to ensure that each partition has enough space to
+  // grow.
+  for (const auto& pair : logical_sizes) {
+    const string target_partition_name = pair.first + target_suffix;
+    if (builder->FindPartition(target_partition_name) == nullptr) {
+      // Use constant GUID because it is unused.
+      LOG(INFO) << "Adding partition " << target_partition_name << " to slot "
+                << BootControlInterface::SlotName(target_slot) << " in "
+                << super_device;
+      if (builder->AddPartition(target_partition_name,
+                                kZeroGuid,
+                                LP_PARTITION_ATTR_READONLY) == nullptr) {
+        LOG(ERROR) << "Cannot add partition " << target_partition_name;
+        return false;
+      }
+    }
+    if (!ResizePartition(builder, pair.first + target_suffix, 0 /* size */)) {
+      return false;
+    }
+  }
+
+  for (const auto& pair : logical_sizes) {
+    if (!ResizePartition(builder, pair.first + target_suffix, pair.second)) {
+      LOG(ERROR) << "Not enough space?";
+      return false;
+    }
+  }
+
+  if (!dynamic_control->StoreMetadata(super_device, builder, target_slot)) {
+    return false;
+  }
+  return true;
+}
+
+// Assume upgrading from slot A to B. A partition foo is considered dynamic
+// iff one of the following:
+// 1. foo_a exists as a dynamic partition (so it should continue to be a
+//    dynamic partition)
+// 2. foo_b does not exist as a static partition (in which case we may be
+//    adding a new partition).
+bool IsDynamicPartition(DynamicPartitionControlInterface* dynamic_control,
+                        const base::FilePath& device_dir,
+                        MetadataBuilder* source_metadata,
+                        const string& partition_name,
+                        const string& source_suffix,
+                        const string& target_suffix) {
+  bool dynamic_source_exist =
+      source_metadata->FindPartition(partition_name + source_suffix) != nullptr;
+  bool static_target_exist = dynamic_control->DeviceExists(
+      device_dir.Append(partition_name + target_suffix).value());
+
+  return dynamic_source_exist || !static_target_exist;
+}
+
+bool FilterPartitionSizes(DynamicPartitionControlInterface* dynamic_control,
+                          const base::FilePath& device_dir,
+                          const PartitionSizes& partition_sizes,
+                          MetadataBuilder* source_metadata,
+                          const string& source_suffix,
+                          const string& target_suffix,
+                          PartitionSizes* logical_sizes) {
+  for (const auto& pair : partition_sizes) {
+    if (!IsDynamicPartition(dynamic_control,
+                            device_dir,
+                            source_metadata,
+                            pair.first,
+                            source_suffix,
+                            target_suffix)) {
+      // In the future we can check static partition sizes, but skip for now.
+      LOG(INFO) << pair.first << " is static; assume its size is "
+                << pair.second << " bytes.";
+      continue;
+    }
+
+    logical_sizes->insert(pair);
+  }
+  return true;
+}
+
+// Return false if partition sizes are all correct in metadata slot
+// |target_slot|. If so, no need to resize. |logical_sizes| have format like
+// {vendor: size, ...}, and fail if a partition is not found.
+bool NeedResizePartitions(DynamicPartitionControlInterface* dynamic_control,
+                          const string& super_device,
+                          Slot target_slot,
+                          const string& suffix,
+                          const PartitionSizes& logical_sizes) {
+  auto target_metadata =
+      dynamic_control->LoadMetadataBuilder(super_device, target_slot);
+  if (target_metadata == nullptr) {
+    LOG(INFO) << "Metadata slot " << BootControlInterface::SlotName(target_slot)
+              << " in " << super_device
+              << " is corrupted; attempt to recover from source slot.";
+    return true;
+  }
+
+  for (const auto& pair : logical_sizes) {
+    Partition* partition = target_metadata->FindPartition(pair.first + suffix);
+    if (partition == nullptr) {
+      LOG(INFO) << "Cannot find " << pair.first << suffix << " at slot "
+                << BootControlInterface::SlotName(target_slot) << " in "
+                << super_device << ". Need to resize.";
+      return true;
+    }
+    if (partition->size() != pair.second) {
+      LOG(INFO) << super_device << ":"
+                << BootControlInterface::SlotName(target_slot) << ":"
+                << pair.first << suffix << ": size == " << partition->size()
+                << " but requested " << pair.second << ". Need to resize.";
+      return true;
+    }
+    LOG(INFO) << super_device << ":"
+              << BootControlInterface::SlotName(target_slot) << ":"
+              << pair.first << suffix << ": size == " << partition->size()
+              << " as requested.";
+  }
+  LOG(INFO) << "No need to resize at metadata slot "
+            << BootControlInterface::SlotName(target_slot) << " in "
+            << super_device;
+  return false;
+}
+}  // namespace
+
+bool BootControlAndroid::InitPartitionMetadata(
+    Slot target_slot, const PartitionSizes& partition_sizes) {
+  if (!dynamic_control_->IsDynamicPartitionsEnabled()) {
+    return true;
+  }
+
+  string device_dir_str;
+  if (!dynamic_control_->GetDeviceDir(&device_dir_str)) {
+    return false;
+  }
+  base::FilePath device_dir(device_dir_str);
+  string super_device = device_dir.Append(LP_METADATA_PARTITION_NAME).value();
+
+  Slot current_slot = GetCurrentSlot();
+  if (target_slot == current_slot) {
+    LOG(ERROR) << "Cannot call InitPartitionMetadata on current slot.";
+    return false;
+  }
+
+  string current_suffix;
+  if (!GetSuffix(current_slot, &current_suffix)) {
+    return false;
+  }
+
+  string target_suffix;
+  if (!GetSuffix(target_slot, &target_suffix)) {
+    return false;
+  }
+
+  auto builder =
+      dynamic_control_->LoadMetadataBuilder(super_device, current_slot);
+  if (builder == nullptr) {
+    return false;
+  }
+
+  // Read metadata from current slot to determine which partitions are logical
+  // and may be resized. Do not read from target slot because metadata at
+  // target slot may be corrupted.
+  PartitionSizes logical_sizes;
+  if (!FilterPartitionSizes(dynamic_control_.get(),
+                            device_dir,
+                            partition_sizes,
+                            builder.get() /* source metadata */,
+                            current_suffix,
+                            target_suffix,
+                            &logical_sizes)) {
+    return false;
+  }
+
+  // Read metadata from target slot to determine if the sizes are correct. Only
+  // test logical partitions.
+  if (NeedResizePartitions(dynamic_control_.get(),
+                           super_device,
+                           target_slot,
+                           target_suffix,
+                           logical_sizes)) {
+    if (!ResizePartitions(dynamic_control_.get(),
+                          super_device,
+                          target_slot,
+                          target_suffix,
+                          logical_sizes,
+                          builder.get())) {
+      return false;
+    }
+  }
+
+  // Unmap all partitions, and remap partitions if size is non-zero.
+  for (const auto& pair : logical_sizes) {
+    if (!dynamic_control_->UnmapPartitionOnDeviceMapper(
+            pair.first + target_suffix, true /* wait */)) {
+      return false;
+    }
+    if (pair.second == 0) {
+      continue;
+    }
+    string map_path;
+    if (!dynamic_control_->MapPartitionOnDeviceMapper(
+            super_device, pair.first + target_suffix, target_slot, &map_path)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/boot_control_android.h b/boot_control_android.h
index 1de0e41..24ab4dc 100644
--- a/boot_control_android.h
+++ b/boot_control_android.h
@@ -17,11 +17,16 @@
 #ifndef UPDATE_ENGINE_BOOT_CONTROL_ANDROID_H_
 #define UPDATE_ENGINE_BOOT_CONTROL_ANDROID_H_
 
+#include <map>
+#include <memory>
 #include <string>
 
 #include <android/hardware/boot/1.0/IBootControl.h>
+#include <base/files/file_util.h>
+#include <liblp/builder.h>
 
 #include "update_engine/common/boot_control.h"
+#include "update_engine/dynamic_partition_control_interface.h"
 
 namespace chromeos_update_engine {
 
@@ -46,9 +51,18 @@
   bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
   bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
   bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+  bool InitPartitionMetadata(Slot slot,
+                             const PartitionSizes& partition_sizes) override;
+  void Cleanup() override;
 
  private:
   ::android::sp<::android::hardware::boot::V1_0::IBootControl> module_;
+  std::unique_ptr<DynamicPartitionControlInterface> dynamic_control_;
+
+  friend class BootControlAndroidTest;
+
+  // Wrapper method of IBootControl::getSuffix().
+  bool GetSuffix(Slot slot, std::string* out) const;
 
   DISALLOW_COPY_AND_ASSIGN(BootControlAndroid);
 };
diff --git a/boot_control_android_unittest.cc b/boot_control_android_unittest.cc
new file mode 100644
index 0000000..9744b42
--- /dev/null
+++ b/boot_control_android_unittest.cc
@@ -0,0 +1,670 @@
+//
+// Copyright (C) 2018 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/boot_control_android.h"
+
+#include <set>
+
+#include <android-base/strings.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/mock_boot_control_hal.h"
+#include "update_engine/mock_dynamic_partition_control.h"
+
+using android::base::Join;
+using android::fs_mgr::MetadataBuilder;
+using android::hardware::Void;
+using testing::_;
+using testing::AnyNumber;
+using testing::Contains;
+using testing::Eq;
+using testing::Invoke;
+using testing::Key;
+using testing::MakeMatcher;
+using testing::Matcher;
+using testing::MatcherInterface;
+using testing::MatchResultListener;
+using testing::NiceMock;
+using testing::Return;
+
+namespace chromeos_update_engine {
+
+constexpr const uint32_t kMaxNumSlots = 2;
+constexpr const char* kSlotSuffixes[kMaxNumSlots] = {"_a", "_b"};
+constexpr const char* kFakeDevicePath = "/fake/dev/path/";
+constexpr const char* kFakeMappedPath = "/fake/mapped/path/";
+constexpr const uint32_t kFakeMetadataSize = 65536;
+constexpr const char* kZeroGuid = "00000000-0000-0000-0000-000000000000";
+
+// A map describing the size of each partition.
+using PartitionSizes = std::map<std::string, uint64_t>;
+
+// C++ standards do not allow uint64_t (aka unsigned long) to be the parameter
+// of user-defined literal operators.
+unsigned long long operator"" _MiB(unsigned long long x) {  // NOLINT
+  return x << 20;
+}
+unsigned long long operator"" _GiB(unsigned long long x) {  // NOLINT
+  return x << 30;
+}
+
+template <typename U, typename V>
+std::ostream& operator<<(std::ostream& os, const std::map<U, V>& param) {
+  os << "{";
+  bool first = true;
+  for (const auto& pair : param) {
+    if (!first)
+      os << ", ";
+    os << pair.first << ":" << pair.second;
+    first = false;
+  }
+  return os << "}";
+}
+
+inline std::string GetDevice(const std::string& name) {
+  return kFakeDevicePath + name;
+}
+inline std::string GetSuperDevice() {
+  return GetDevice(LP_METADATA_PARTITION_NAME);
+}
+
+struct TestParam {
+  uint32_t source;
+  uint32_t target;
+};
+std::ostream& operator<<(std::ostream& os, const TestParam& param) {
+  return os << "{source: " << param.source << ", target:" << param.target
+            << "}";
+}
+
+std::unique_ptr<MetadataBuilder> NewFakeMetadata(const PartitionSizes& sizes) {
+  auto builder = MetadataBuilder::New(10_GiB, kFakeMetadataSize, kMaxNumSlots);
+  EXPECT_NE(nullptr, builder);
+  if (builder == nullptr)
+    return nullptr;
+  for (const auto& pair : sizes) {
+    auto p = builder->AddPartition(pair.first, kZeroGuid, 0 /* attr */);
+    EXPECT_TRUE(p && builder->ResizePartition(p, pair.second));
+  }
+  return builder;
+}
+
+class MetadataMatcher : public MatcherInterface<MetadataBuilder*> {
+ public:
+  explicit MetadataMatcher(const PartitionSizes& partition_sizes)
+      : partition_sizes_(partition_sizes) {}
+  bool MatchAndExplain(MetadataBuilder* metadata,
+                       MatchResultListener* listener) const override {
+    bool success = true;
+    for (const auto& pair : partition_sizes_) {
+      auto p = metadata->FindPartition(pair.first);
+      if (p == nullptr) {
+        if (success)
+          *listener << "; ";
+        *listener << "No partition " << pair.first;
+        success = false;
+        continue;
+      }
+      if (p->size() != pair.second) {
+        if (success)
+          *listener << "; ";
+        *listener << "Partition " << pair.first << " has size " << p->size()
+                  << ", expected " << pair.second;
+        success = false;
+      }
+    }
+    return success;
+  }
+
+  void DescribeTo(std::ostream* os) const override {
+    *os << "expect: " << partition_sizes_;
+  }
+
+  void DescribeNegationTo(std::ostream* os) const override {
+    *os << "expect not: " << partition_sizes_;
+  }
+
+ private:
+  PartitionSizes partition_sizes_;
+};
+
+inline Matcher<MetadataBuilder*> MetadataMatches(
+    const PartitionSizes& partition_sizes) {
+  return MakeMatcher(new MetadataMatcher(partition_sizes));
+}
+
+class BootControlAndroidTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    // Fake init bootctl_
+    bootctl_.module_ = new NiceMock<MockBootControlHal>();
+    bootctl_.dynamic_control_ =
+        std::make_unique<NiceMock<MockDynamicPartitionControl>>();
+
+    ON_CALL(module(), getNumberSlots()).WillByDefault(Invoke([] {
+      return kMaxNumSlots;
+    }));
+    ON_CALL(module(), getSuffix(_, _))
+        .WillByDefault(Invoke([](auto slot, auto cb) {
+          EXPECT_LE(slot, kMaxNumSlots);
+          cb(slot < kMaxNumSlots ? kSlotSuffixes[slot] : "");
+          return Void();
+        }));
+
+    ON_CALL(dynamicControl(), IsDynamicPartitionsEnabled())
+        .WillByDefault(Return(true));
+    ON_CALL(dynamicControl(), GetDeviceDir(_))
+        .WillByDefault(Invoke([](auto path) {
+          *path = kFakeDevicePath;
+          return true;
+        }));
+  }
+
+  // Return the mocked HAL module.
+  NiceMock<MockBootControlHal>& module() {
+    return static_cast<NiceMock<MockBootControlHal>&>(*bootctl_.module_);
+  }
+
+  // Return the mocked DynamicPartitionControlInterface.
+  NiceMock<MockDynamicPartitionControl>& dynamicControl() {
+    return static_cast<NiceMock<MockDynamicPartitionControl>&>(
+        *bootctl_.dynamic_control_);
+  }
+
+  // Set the fake metadata to return when LoadMetadataBuilder is called on
+  // |slot|.
+  void SetMetadata(uint32_t slot, const PartitionSizes& sizes) {
+    EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), slot))
+        .WillOnce(
+            Invoke([sizes](auto, auto) { return NewFakeMetadata(sizes); }));
+  }
+
+  // Expect that MapPartitionOnDeviceMapper is called on target() metadata slot
+  // with each partition in |partitions|.
+  void ExpectMap(const std::set<std::string>& partitions) {
+    // Error when MapPartitionOnDeviceMapper is called on unknown arguments.
+    ON_CALL(dynamicControl(), MapPartitionOnDeviceMapper(_, _, _, _))
+        .WillByDefault(Return(false));
+
+    for (const auto& partition : partitions) {
+      EXPECT_CALL(
+          dynamicControl(),
+          MapPartitionOnDeviceMapper(GetSuperDevice(), partition, target(), _))
+          .WillOnce(Invoke([this](auto, auto partition, auto, auto path) {
+            auto it = mapped_devices_.find(partition);
+            if (it != mapped_devices_.end()) {
+              *path = it->second;
+              return true;
+            }
+            mapped_devices_[partition] = *path = kFakeMappedPath + partition;
+            return true;
+          }));
+    }
+  }
+
+  // Expect that UnmapPartitionOnDeviceMapper is called on target() metadata
+  // slot with each partition in |partitions|.
+  void ExpectUnmap(const std::set<std::string>& partitions) {
+    // Error when UnmapPartitionOnDeviceMapper is called on unknown arguments.
+    ON_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(_, _))
+        .WillByDefault(Return(false));
+
+    for (const auto& partition : partitions) {
+      EXPECT_CALL(dynamicControl(), UnmapPartitionOnDeviceMapper(partition, _))
+          .WillOnce(Invoke([this](auto partition, auto) {
+            mapped_devices_.erase(partition);
+            return true;
+          }));
+    }
+  }
+
+  void ExpectRemap(const std::set<std::string>& partitions) {
+    ExpectUnmap(partitions);
+    ExpectMap(partitions);
+  }
+
+  void ExpectDevicesAreMapped(const std::set<std::string>& partitions) {
+    ASSERT_EQ(partitions.size(), mapped_devices_.size());
+    for (const auto& partition : partitions) {
+      EXPECT_THAT(mapped_devices_, Contains(Key(Eq(partition))))
+          << "Expect that " << partition << " is mapped, but it is not.";
+    }
+  }
+
+  uint32_t source() { return slots_.source; }
+
+  uint32_t target() { return slots_.target; }
+
+  // Return partition names with suffix of source().
+  std::string S(const std::string& name) {
+    return name + std::string(kSlotSuffixes[source()]);
+  }
+
+  // Return partition names with suffix of target().
+  std::string T(const std::string& name) {
+    return name + std::string(kSlotSuffixes[target()]);
+  }
+
+  // Set source and target slots to use before testing.
+  void SetSlots(const TestParam& slots) {
+    slots_ = slots;
+
+    ON_CALL(module(), getCurrentSlot()).WillByDefault(Invoke([this] {
+      return source();
+    }));
+    // Should not store metadata to source slot.
+    EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, source()))
+        .Times(0);
+  }
+
+  BootControlAndroid bootctl_;  // BootControlAndroid under test.
+  TestParam slots_;
+  // mapped devices through MapPartitionOnDeviceMapper.
+  std::map<std::string, std::string> mapped_devices_;
+};
+
+class BootControlAndroidTestP
+    : public BootControlAndroidTest,
+      public ::testing::WithParamInterface<TestParam> {
+ public:
+  void SetUp() override {
+    BootControlAndroidTest::SetUp();
+    SetSlots(GetParam());
+  }
+};
+
+// Test no resize if no dynamic partitions at all.
+TEST_P(BootControlAndroidTestP, NoResizeIfNoDynamicPartitions) {
+  SetMetadata(source(), {});
+  SetMetadata(target(), {});
+  // Should not need to resize and store metadata
+  EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target()))
+      .Times(0);
+  EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_a"))))
+      .Times(AnyNumber())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_b"))))
+      .Times(AnyNumber())
+      .WillRepeatedly(Return(true));
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(), {{"static", 1_GiB}}));
+  ExpectDevicesAreMapped({});
+}
+
+// Test no resize if update manifest does not contain any dynamic partitions
+TEST_P(BootControlAndroidTestP, NoResizeIfEmptyMetadata) {
+  SetMetadata(source(),
+              {{S("system"), 4_GiB},
+               {S("vendor"), 100_MiB},
+               {T("system"), 3_GiB},
+               {T("vendor"), 150_MiB}});
+  SetMetadata(target(),
+              {{S("system"), 2_GiB},
+               {S("vendor"), 1_GiB},
+               {T("system"), 3_GiB},
+               {T("vendor"), 150_MiB}});
+  // Should not need to resize and store metadata
+  EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target()))
+      .Times(0);
+  EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_a"))))
+      .Times(AnyNumber())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(dynamicControl(), DeviceExists(Eq(GetDevice("static_b"))))
+      .Times(AnyNumber())
+      .WillRepeatedly(Return(true));
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(), {{"static", 1_GiB}}));
+  ExpectDevicesAreMapped({});
+}
+
+// Do not resize if manifest size matches size in target metadata. When resuming
+// from an update, do not redo the resize if not needed.
+TEST_P(BootControlAndroidTestP, NoResizeIfSizeMatchWhenResizing) {
+  SetMetadata(source(), {{S("system"), 2_GiB}, {S("vendor"), 1_GiB}});
+  SetMetadata(target(),
+              {{S("system"), 2_GiB},
+               {S("vendor"), 1_GiB},
+               {T("system"), 3_GiB},
+               {T("vendor"), 1_GiB}});
+  // Should not need to resize and store metadata
+  EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target()))
+      .Times(0);
+  ExpectRemap({T("system"), T("vendor")});
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+      target(), {{"system", 3_GiB}, {"vendor", 1_GiB}}));
+  ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Do not resize if manifest size matches size in target metadata. When resuming
+// from an update, do not redo the resize if not needed.
+TEST_P(BootControlAndroidTestP, NoResizeIfSizeMatchWhenAdding) {
+  SetMetadata(source(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}});
+  SetMetadata(
+      target(),
+      {{S("system"), 2_GiB}, {T("system"), 2_GiB}, {T("vendor"), 1_GiB}});
+  // Should not need to resize and store metadata
+  EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target()))
+      .Times(0);
+  ExpectRemap({T("system"), T("vendor")});
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+      target(), {{"system", 2_GiB}, {"vendor", 1_GiB}}));
+  ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Do not resize if manifest size matches size in target metadata. When resuming
+// from an update, do not redo the resize if not needed.
+TEST_P(BootControlAndroidTestP, NoResizeIfSizeMatchWhenDeleting) {
+  SetMetadata(source(),
+              {{S("system"), 2_GiB},
+               {S("vendor"), 1_GiB},
+               {T("system"), 2_GiB},
+               {T("vendor"), 1_GiB}});
+  SetMetadata(target(),
+              {{S("system"), 2_GiB},
+               {S("vendor"), 1_GiB},
+               {T("system"), 2_GiB},
+               {T("vendor"), 0}});
+  // Should not need to resize and store metadata
+  EXPECT_CALL(dynamicControl(), StoreMetadata(GetSuperDevice(), _, target()))
+      .Times(0);
+  ExpectUnmap({T("system"), T("vendor")});
+  ExpectMap({T("system")});
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+      target(), {{"system", 2_GiB}, {"vendor", 0}}));
+  ExpectDevicesAreMapped({T("system")});
+}
+
+// Test resize case. Grow if target metadata contains a partition with a size
+// less than expected.
+TEST_P(BootControlAndroidTestP, NeedGrowIfSizeNotMatchWhenResizing) {
+  PartitionSizes initial{{S("system"), 2_GiB},
+                         {S("vendor"), 1_GiB},
+                         {T("system"), 2_GiB},
+                         {T("vendor"), 1_GiB}};
+  SetMetadata(source(), initial);
+  SetMetadata(target(), initial);
+  EXPECT_CALL(dynamicControl(),
+              StoreMetadata(GetSuperDevice(),
+                            MetadataMatches({{S("system"), 2_GiB},
+                                             {S("vendor"), 1_GiB},
+                                             {T("system"), 3_GiB},
+                                             {T("vendor"), 1_GiB}}),
+                            target()))
+      .WillOnce(Return(true));
+  ExpectRemap({T("system"), T("vendor")});
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+      target(), {{"system", 3_GiB}, {"vendor", 1_GiB}}));
+  ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Test resize case. Shrink if target metadata contains a partition with a size
+// greater than expected.
+TEST_P(BootControlAndroidTestP, NeedShrinkIfSizeNotMatchWhenResizing) {
+  PartitionSizes initial{{S("system"), 2_GiB},
+                         {S("vendor"), 1_GiB},
+                         {T("system"), 2_GiB},
+                         {T("vendor"), 1_GiB}};
+  SetMetadata(source(), initial);
+  SetMetadata(target(), initial);
+  EXPECT_CALL(dynamicControl(),
+              StoreMetadata(GetSuperDevice(),
+                            MetadataMatches({{S("system"), 2_GiB},
+                                             {S("vendor"), 1_GiB},
+                                             {T("system"), 2_GiB},
+                                             {T("vendor"), 150_MiB}}),
+                            target()))
+      .WillOnce(Return(true));
+  ExpectRemap({T("system"), T("vendor")});
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+      target(), {{"system", 2_GiB}, {"vendor", 150_MiB}}));
+  ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Test adding partitions on the first run.
+TEST_P(BootControlAndroidTestP, AddPartitionToEmptyMetadata) {
+  SetMetadata(source(), {});
+  SetMetadata(target(), {});
+  EXPECT_CALL(dynamicControl(),
+              StoreMetadata(
+                  GetSuperDevice(),
+                  MetadataMatches({{T("system"), 2_GiB}, {T("vendor"), 1_GiB}}),
+                  target()))
+      .WillOnce(Return(true));
+  ExpectRemap({T("system"), T("vendor")});
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+      target(), {{"system", 2_GiB}, {"vendor", 1_GiB}}));
+  ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Test subsequent add case.
+TEST_P(BootControlAndroidTestP, AddAdditionalPartition) {
+  SetMetadata(source(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}});
+  SetMetadata(target(), {{S("system"), 2_GiB}, {T("system"), 2_GiB}});
+  EXPECT_CALL(dynamicControl(),
+              StoreMetadata(GetSuperDevice(),
+                            MetadataMatches({{S("system"), 2_GiB},
+                                             {T("system"), 2_GiB},
+                                             {T("vendor"), 1_GiB}}),
+                            target()))
+      .WillOnce(Return(true));
+  ExpectRemap({T("system"), T("vendor")});
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+      target(), {{"system", 2_GiB}, {"vendor", 1_GiB}}));
+  ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Test delete one partition.
+TEST_P(BootControlAndroidTestP, DeletePartition) {
+  PartitionSizes initial{{S("system"), 2_GiB},
+                         {S("vendor"), 1_GiB},
+                         {T("system"), 2_GiB},
+                         {T("vendor"), 1_GiB}};
+  SetMetadata(source(), initial);
+  SetMetadata(target(), initial);
+  EXPECT_CALL(dynamicControl(),
+              StoreMetadata(GetSuperDevice(),
+                            MetadataMatches({{S("system"), 2_GiB},
+                                             {S("vendor"), 1_GiB},
+                                             {T("system"), 2_GiB},
+                                             {T("vendor"), 0}}),
+                            target()))
+      .WillOnce(Return(true));
+  ExpectUnmap({T("system"), T("vendor")});
+  ExpectMap({T("system")});
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+      target(), {{"system", 2_GiB}, {"vendor", 0}}));
+  ExpectDevicesAreMapped({T("system")});
+}
+
+// Test delete all partitions.
+TEST_P(BootControlAndroidTestP, DeleteAll) {
+  PartitionSizes initial{{S("system"), 2_GiB},
+                         {S("vendor"), 1_GiB},
+                         {T("system"), 2_GiB},
+                         {T("vendor"), 1_GiB}};
+  SetMetadata(source(), initial);
+  SetMetadata(target(), initial);
+  EXPECT_CALL(dynamicControl(),
+              StoreMetadata(GetSuperDevice(),
+                            MetadataMatches({{S("system"), 2_GiB},
+                                             {S("vendor"), 1_GiB},
+                                             {T("system"), 0},
+                                             {T("vendor"), 0}}),
+                            target()))
+      .WillOnce(Return(true));
+  ExpectUnmap({T("system"), T("vendor")});
+  ExpectMap({});
+
+  EXPECT_TRUE(
+      bootctl_.InitPartitionMetadata(target(), {{"system", 0}, {"vendor", 0}}));
+  ExpectDevicesAreMapped({});
+}
+
+// Test corrupt source metadata case. This shouldn't happen in practice,
+// because the device is already booted normally.
+TEST_P(BootControlAndroidTestP, CorruptedSourceMetadata) {
+  EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), source()))
+      .WillOnce(Invoke([](auto, auto) { return nullptr; }));
+  EXPECT_FALSE(bootctl_.InitPartitionMetadata(target(), {}))
+      << "Should not be able to continue with corrupt source metadata";
+}
+
+// Test corrupt target metadata case. This may happen in practice.
+// BootControlAndroid should copy from source metadata and make necessary
+// modifications on it.
+TEST_P(BootControlAndroidTestP, CorruptedTargetMetadata) {
+  SetMetadata(source(),
+              {{S("system"), 2_GiB},
+               {S("vendor"), 1_GiB},
+               {T("system"), 0},
+               {T("vendor"), 0}});
+  EXPECT_CALL(dynamicControl(), LoadMetadataBuilder(GetSuperDevice(), target()))
+      .WillOnce(Invoke([](auto, auto) { return nullptr; }));
+  EXPECT_CALL(dynamicControl(),
+              StoreMetadata(GetSuperDevice(),
+                            MetadataMatches({{S("system"), 2_GiB},
+                                             {S("vendor"), 1_GiB},
+                                             {T("system"), 3_GiB},
+                                             {T("vendor"), 150_MiB}}),
+                            target()))
+      .WillOnce(Return(true));
+  ExpectRemap({T("system"), T("vendor")});
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(
+      target(), {{"system", 3_GiB}, {"vendor", 150_MiB}}));
+  ExpectDevicesAreMapped({T("system"), T("vendor")});
+}
+
+// Test that InitPartitionMetadata fail if there is not enough space on the
+// device.
+TEST_P(BootControlAndroidTestP, NotEnoughSpace) {
+  PartitionSizes initial{{S("system"), 3_GiB},
+                         {S("vendor"), 2_GiB},
+                         {T("system"), 0},
+                         {T("vendor"), 0}};
+  SetMetadata(source(), initial);
+  SetMetadata(target(), initial);
+  EXPECT_FALSE(bootctl_.InitPartitionMetadata(
+      target(), {{"system", 3_GiB}, {"vendor", 3_GiB}}))
+      << "Should not be able to fit 11GiB data into 10GiB space";
+}
+
+INSTANTIATE_TEST_CASE_P(ParamTest,
+                        BootControlAndroidTestP,
+                        testing::Values(TestParam{0, 1}, TestParam{1, 0}));
+
+const PartitionSizes update_sizes_0() {
+  return {{"grown_a", 2_GiB},
+          {"shrunk_a", 1_GiB},
+          {"same_a", 100_MiB},
+          {"deleted_a", 150_MiB},
+          {"grown_b", 200_MiB},
+          {"shrunk_b", 0},
+          {"same_b", 0}};
+}
+
+const PartitionSizes update_sizes_1() {
+  return {
+      {"grown_a", 2_GiB},
+      {"shrunk_a", 1_GiB},
+      {"same_a", 100_MiB},
+      {"deleted_a", 150_MiB},
+      {"grown_b", 3_GiB},
+      {"shrunk_b", 150_MiB},
+      {"same_b", 100_MiB},
+      {"added_b", 150_MiB},
+      {"deleted_b", 0},
+  };
+}
+
+const PartitionSizes update_sizes_2() {
+  return {{"grown_a", 4_GiB},
+          {"shrunk_a", 100_MiB},
+          {"same_a", 100_MiB},
+          {"added_a", 0_MiB},
+          {"deleted_a", 64_MiB},
+          {"grown_b", 3_GiB},
+          {"shrunk_b", 150_MiB},
+          {"same_b", 100_MiB},
+          {"added_b", 150_MiB},
+          {"deleted_b", 0}};
+}
+
+// Test case for first update after the device is manufactured, in which
+// case the "other" slot is likely of size "0" (except system, which is
+// non-zero because of system_other partition)
+TEST_F(BootControlAndroidTest, SimulatedFirstUpdate) {
+  SetSlots({0, 1});
+
+  SetMetadata(source(), update_sizes_0());
+  SetMetadata(target(), update_sizes_0());
+  EXPECT_CALL(
+      dynamicControl(),
+      StoreMetadata(
+          GetSuperDevice(), MetadataMatches(update_sizes_1()), target()))
+      .WillOnce(Return(true));
+  ExpectUnmap({"grown_b", "shrunk_b", "same_b", "added_b", "deleted_b"});
+  ExpectMap({"grown_b", "shrunk_b", "same_b", "added_b"});
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(),
+                                             {{"grown", 3_GiB},
+                                              {"shrunk", 150_MiB},
+                                              {"same", 100_MiB},
+                                              {"added", 150_MiB},
+                                              {"deleted", 0_MiB}}));
+  ExpectDevicesAreMapped({"grown_b", "shrunk_b", "same_b", "added_b"});
+}
+
+// After first update, test for the second update. In the second update, the
+// "added" partition is deleted and "deleted" partition is re-added.
+TEST_F(BootControlAndroidTest, SimulatedSecondUpdate) {
+  SetSlots({1, 0});
+
+  SetMetadata(source(), update_sizes_1());
+  SetMetadata(target(), update_sizes_0());
+
+  EXPECT_CALL(
+      dynamicControl(),
+      StoreMetadata(
+          GetSuperDevice(), MetadataMatches(update_sizes_2()), target()))
+      .WillOnce(Return(true));
+  ExpectUnmap({"grown_a", "shrunk_a", "same_a", "added_a", "deleted_a"});
+  ExpectMap({"grown_a", "shrunk_a", "same_a", "deleted_a"});
+
+  EXPECT_TRUE(bootctl_.InitPartitionMetadata(target(),
+                                             {{"grown", 4_GiB},
+                                              {"shrunk", 100_MiB},
+                                              {"same", 100_MiB},
+                                              {"added", 0_MiB},
+                                              {"deleted", 64_MiB}}));
+  ExpectDevicesAreMapped({"grown_a", "shrunk_a", "same_a", "deleted_a"});
+}
+
+TEST_F(BootControlAndroidTest, ApplyingToCurrentSlot) {
+  SetSlots({1, 1});
+  EXPECT_FALSE(bootctl_.InitPartitionMetadata(target(), {}))
+      << "Should not be able to apply to current slot.";
+}
+
+}  // namespace chromeos_update_engine
diff --git a/boot_control_chromeos.cc b/boot_control_chromeos.cc
index 7d8f068..68ac5bd 100644
--- a/boot_control_chromeos.cc
+++ b/boot_control_chromeos.cc
@@ -302,4 +302,11 @@
   return -1;
 }
 
+bool BootControlChromeOS::InitPartitionMetadata(
+    Slot slot, const PartitionSizes& partition_sizes) {
+  return true;
+}
+
+void BootControlChromeOS::Cleanup() {}
+
 }  // namespace chromeos_update_engine
diff --git a/boot_control_chromeos.h b/boot_control_chromeos.h
index a1d57fe..d7bab05 100644
--- a/boot_control_chromeos.h
+++ b/boot_control_chromeos.h
@@ -50,6 +50,9 @@
   bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
   bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
   bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+  bool InitPartitionMetadata(Slot slot,
+                             const PartitionSizes& partition_sizes) override;
+  void Cleanup() override;
 
  private:
   friend class BootControlChromeOSTest;
diff --git a/boot_control_recovery.cc b/boot_control_recovery.cc
index 722d32a..c6e3ffd 100644
--- a/boot_control_recovery.cc
+++ b/boot_control_recovery.cc
@@ -183,4 +183,18 @@
          brillo::MessageLoop::kTaskIdNull;
 }
 
+// TODO(b/78598708): Remove BootControlRecovery, so don't bother resizing
+// partitions here.
+bool BootControlRecovery::InitPartitionMetadata(
+    Slot slot, const PartitionSizes& partition_sizes) {
+  LOG(ERROR) << __FUNCTION__
+             << " is not implemented. BootControlRecovery will be removed.";
+  return true;
+}
+
+void BootControlRecovery::Cleanup() {
+  LOG(ERROR) << __FUNCTION__
+             << " is not implemented. BootControlRecovery will be removed.";
+}
+
 }  // namespace chromeos_update_engine
diff --git a/boot_control_recovery.h b/boot_control_recovery.h
index 3a83caa..c8b02bb 100644
--- a/boot_control_recovery.h
+++ b/boot_control_recovery.h
@@ -49,6 +49,9 @@
   bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
   bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
   bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+  bool InitPartitionMetadata(Slot slot,
+                             const PartitionSizes& partition_sizes) override;
+  void Cleanup() override;
 
  private:
   // NOTE: There is no way to release/unload HAL implementations so
diff --git a/common/boot_control_interface.h b/common/boot_control_interface.h
index 659b388..776333c 100644
--- a/common/boot_control_interface.h
+++ b/common/boot_control_interface.h
@@ -18,6 +18,7 @@
 #define UPDATE_ENGINE_COMMON_BOOT_CONTROL_INTERFACE_H_
 
 #include <climits>
+#include <map>
 #include <string>
 
 #include <base/callback.h>
@@ -32,6 +33,7 @@
 class BootControlInterface {
  public:
   using Slot = unsigned int;
+  using PartitionSizes = std::map<std::string, uint64_t>;
 
   static const Slot kInvalidSlot = UINT_MAX;
 
@@ -77,6 +79,17 @@
   // of the operation.
   virtual bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) = 0;
 
+  // Initialize metadata of underlying partitions for a given |slot|.
+  // Ensure that partitions at the specified |slot| has a given size, as
+  // specified by |partition_sizes|. |partition_sizes| has the format:
+  // {"vendor": 524288000, "system": 2097152000, ...}; values must be
+  // aligned to the logical block size of the super partition.
+  virtual bool InitPartitionMetadata(Slot slot,
+                                     const PartitionSizes& partition_sizes) = 0;
+
+  // Do necessary clean-up operations after the whole update.
+  virtual void Cleanup() = 0;
+
   // Return a human-readable slot name used for logging.
   static std::string SlotName(Slot slot) {
     if (slot == kInvalidSlot)
diff --git a/common/boot_control_stub.cc b/common/boot_control_stub.cc
index 2de0c82..2e326e5 100644
--- a/common/boot_control_stub.cc
+++ b/common/boot_control_stub.cc
@@ -59,4 +59,14 @@
   return false;
 }
 
+bool BootControlStub::InitPartitionMetadata(
+    Slot slot, const PartitionSizes& partition_sizes) {
+  LOG(ERROR) << __FUNCTION__ << " should never be called.";
+  return false;
+}
+
+void BootControlStub::Cleanup() {
+  LOG(ERROR) << __FUNCTION__ << " should never be called.";
+}
+
 }  // namespace chromeos_update_engine
diff --git a/common/boot_control_stub.h b/common/boot_control_stub.h
index 9e3b05c..65248af 100644
--- a/common/boot_control_stub.h
+++ b/common/boot_control_stub.h
@@ -45,6 +45,9 @@
   bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
   bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
   bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+  bool InitPartitionMetadata(Slot slot,
+                             const PartitionSizes& partition_sizes) override;
+  void Cleanup() override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(BootControlStub);
diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h
index 3eccc80..e71c83a 100644
--- a/common/fake_boot_control.h
+++ b/common/fake_boot_control.h
@@ -74,6 +74,13 @@
     return true;
   }
 
+  bool InitPartitionMetadata(Slot slot,
+                             const PartitionSizes& partition_sizes) override {
+    return true;
+  }
+
+  void Cleanup() override {}
+
   // Setters
   void SetNumSlots(unsigned int num_slots) {
     num_slots_ = num_slots;
diff --git a/dynamic_partition_control_android.cc b/dynamic_partition_control_android.cc
new file mode 100644
index 0000000..27e117c
--- /dev/null
+++ b/dynamic_partition_control_android.cc
@@ -0,0 +1,195 @@
+//
+// Copyright (C) 2018 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/dynamic_partition_control_android.h"
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <bootloader_message/bootloader_message.h>
+#include <fs_mgr_dm_linear.h>
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/utils.h"
+
+using android::base::GetBoolProperty;
+using android::base::Join;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::fs_mgr::CreateLogicalPartition;
+using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::MetadataBuilder;
+
+namespace chromeos_update_engine {
+
+constexpr char kUseDynamicPartitions[] = "ro.boot.logical_partitions";
+constexpr uint64_t kMapTimeoutMillis = 1000;
+
+DynamicPartitionControlAndroid::~DynamicPartitionControlAndroid() {
+  CleanupInternal(false /* wait */);
+}
+
+bool DynamicPartitionControlAndroid::IsDynamicPartitionsEnabled() {
+  return GetBoolProperty(kUseDynamicPartitions, false);
+}
+
+bool DynamicPartitionControlAndroid::MapPartitionOnDeviceMapper(
+    const std::string& super_device,
+    const std::string& target_partition_name,
+    uint32_t slot,
+    std::string* path) {
+  if (!CreateLogicalPartition(super_device.c_str(),
+                              slot,
+                              target_partition_name,
+                              true /* force_writable */,
+                              std::chrono::milliseconds(kMapTimeoutMillis),
+                              path)) {
+    LOG(ERROR) << "Cannot map " << target_partition_name << " in "
+               << super_device << " on device mapper.";
+    return false;
+  }
+  LOG(INFO) << "Succesfully mapped " << target_partition_name
+            << " to device mapper; device path at " << *path;
+  mapped_devices_.insert(target_partition_name);
+  return true;
+}
+
+bool DynamicPartitionControlAndroid::UnmapPartitionOnDeviceMapper(
+    const std::string& target_partition_name, bool wait) {
+  if (DeviceMapper::Instance().GetState(target_partition_name) !=
+      DmDeviceState::INVALID) {
+    if (!DestroyLogicalPartition(
+            target_partition_name,
+            std::chrono::milliseconds(wait ? kMapTimeoutMillis : 0))) {
+      LOG(ERROR) << "Cannot unmap " << target_partition_name
+                 << " from device mapper.";
+      return false;
+    }
+    LOG(INFO) << "Successfully unmapped " << target_partition_name
+              << " from device mapper.";
+  }
+  mapped_devices_.erase(target_partition_name);
+  return true;
+}
+
+void DynamicPartitionControlAndroid::CleanupInternal(bool wait) {
+  // UnmapPartitionOnDeviceMapper removes objects from mapped_devices_, hence
+  // a copy is needed for the loop.
+  std::set<std::string> mapped = mapped_devices_;
+  LOG(INFO) << "Destroying [" << Join(mapped, ", ") << "] from device mapper";
+  for (const auto& partition_name : mapped) {
+    ignore_result(UnmapPartitionOnDeviceMapper(partition_name, wait));
+  }
+}
+
+void DynamicPartitionControlAndroid::Cleanup() {
+  CleanupInternal(true /* wait */);
+}
+
+bool DynamicPartitionControlAndroid::DeviceExists(const std::string& path) {
+  return base::PathExists(base::FilePath(path));
+}
+
+android::dm::DmDeviceState DynamicPartitionControlAndroid::GetState(
+    const std::string& name) {
+  return DeviceMapper::Instance().GetState(name);
+}
+
+bool DynamicPartitionControlAndroid::GetDmDevicePathByName(
+    const std::string& name, std::string* path) {
+  return DeviceMapper::Instance().GetDmDevicePathByName(name, path);
+}
+
+std::unique_ptr<MetadataBuilder>
+DynamicPartitionControlAndroid::LoadMetadataBuilder(
+    const std::string& super_device, uint32_t source_slot) {
+  auto builder = MetadataBuilder::New(super_device, source_slot);
+  if (builder == nullptr) {
+    LOG(WARNING) << "No metadata slot "
+                 << BootControlInterface::SlotName(source_slot) << " in "
+                 << super_device;
+  }
+  LOG(INFO) << "Loaded metadata from slot "
+            << BootControlInterface::SlotName(source_slot) << " in "
+            << super_device;
+  return builder;
+}
+
+bool DynamicPartitionControlAndroid::StoreMetadata(
+    const std::string& super_device,
+    MetadataBuilder* builder,
+    uint32_t target_slot) {
+  auto metadata = builder->Export();
+  if (metadata == nullptr) {
+    LOG(ERROR) << "Cannot export metadata to slot "
+               << BootControlInterface::SlotName(target_slot) << " in "
+               << super_device;
+    return false;
+  }
+
+  if (!UpdatePartitionTable(super_device, *metadata, target_slot)) {
+    LOG(ERROR) << "Cannot write metadata to slot "
+               << BootControlInterface::SlotName(target_slot) << " in "
+               << super_device;
+    return false;
+  }
+
+  LOG(INFO) << "Copied metadata to slot "
+            << BootControlInterface::SlotName(target_slot) << " in "
+            << super_device;
+  return true;
+}
+
+bool DynamicPartitionControlAndroid::GetDeviceDir(std::string* out) {
+  // We can't use fs_mgr to look up |partition_name| because fstab
+  // doesn't list every slot partition (it uses the slotselect option
+  // to mask the suffix).
+  //
+  // We can however assume that there's an entry for the /misc mount
+  // point and use that to get the device file for the misc
+  // partition. This helps us locate the disk that |partition_name|
+  // resides on. From there we'll assume that a by-name scheme is used
+  // so we can just replace the trailing "misc" by the given
+  // |partition_name| and suffix corresponding to |slot|, e.g.
+  //
+  //   /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
+  //   /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
+  //
+  // If needed, it's possible to relax the by-name assumption in the
+  // future by trawling /sys/block looking for the appropriate sibling
+  // of misc and then finding an entry in /dev matching the sysfs
+  // entry.
+
+  std::string err, misc_device = get_bootloader_message_blk_device(&err);
+  if (misc_device.empty()) {
+    LOG(ERROR) << "Unable to get misc block device: " << err;
+    return false;
+  }
+
+  if (!utils::IsSymlink(misc_device.c_str())) {
+    LOG(ERROR) << "Device file " << misc_device << " for /misc "
+               << "is not a symlink.";
+    return false;
+  }
+  *out = base::FilePath(misc_device).DirName().value();
+  return true;
+}
+}  // namespace chromeos_update_engine
diff --git a/dynamic_partition_control_android.h b/dynamic_partition_control_android.h
new file mode 100644
index 0000000..945954d
--- /dev/null
+++ b/dynamic_partition_control_android.h
@@ -0,0 +1,61 @@
+//
+// Copyright (C) 2018 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_DYNAMIC_PARTITION_CONTROL_ANDROID_H_
+#define UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_ANDROID_H_
+
+#include "update_engine/dynamic_partition_control_interface.h"
+
+#include <memory>
+#include <set>
+#include <string>
+
+namespace chromeos_update_engine {
+
+class DynamicPartitionControlAndroid : public DynamicPartitionControlInterface {
+ public:
+  DynamicPartitionControlAndroid() = default;
+  ~DynamicPartitionControlAndroid();
+  bool IsDynamicPartitionsEnabled() override;
+  bool MapPartitionOnDeviceMapper(const std::string& super_device,
+                                  const std::string& target_partition_name,
+                                  uint32_t slot,
+                                  std::string* path) override;
+  bool UnmapPartitionOnDeviceMapper(const std::string& target_partition_name,
+                                    bool wait) override;
+  void Cleanup() override;
+  bool DeviceExists(const std::string& path) override;
+  android::dm::DmDeviceState GetState(const std::string& name) override;
+  bool GetDmDevicePathByName(const std::string& name,
+                             std::string* path) override;
+  std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder(
+      const std::string& super_device, uint32_t source_slot) override;
+  bool StoreMetadata(const std::string& super_device,
+                     android::fs_mgr::MetadataBuilder* builder,
+                     uint32_t target_slot) override;
+  bool GetDeviceDir(std::string* path) override;
+
+ private:
+  std::set<std::string> mapped_devices_;
+
+  void CleanupInternal(bool wait);
+
+  DISALLOW_COPY_AND_ASSIGN(DynamicPartitionControlAndroid);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_ANDROID_H_
diff --git a/dynamic_partition_control_interface.h b/dynamic_partition_control_interface.h
new file mode 100644
index 0000000..00ed026
--- /dev/null
+++ b/dynamic_partition_control_interface.h
@@ -0,0 +1,90 @@
+//
+// Copyright (C) 2018 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_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_
+#define UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include <base/files/file_util.h>
+#include <libdm/dm.h>
+#include <liblp/builder.h>
+
+namespace chromeos_update_engine {
+
+class DynamicPartitionControlInterface {
+ public:
+  virtual ~DynamicPartitionControlInterface() = default;
+
+  // Return true iff dynamic partitions is enabled on this device.
+  virtual bool IsDynamicPartitionsEnabled() = 0;
+
+  // Map logical partition on device-mapper.
+  // |super_device| is the device path of the physical partition ("super").
+  // |target_partition_name| is the identifier used in metadata; for example,
+  // "vendor_a"
+  // |slot| is the selected slot to mount; for example, 0 for "_a".
+  // Returns true if mapped successfully; if so, |path| is set to the device
+  // path of the mapped logical partition.
+  virtual bool MapPartitionOnDeviceMapper(
+      const std::string& super_device,
+      const std::string& target_partition_name,
+      uint32_t slot,
+      std::string* path) = 0;
+
+  // Unmap logical partition on device mapper. This is the reverse operation
+  // of MapPartitionOnDeviceMapper.
+  // If |wait| is set, wait until the device is unmapped.
+  // Returns true if unmapped successfully.
+  virtual bool UnmapPartitionOnDeviceMapper(
+      const std::string& target_partition_name, bool wait) = 0;
+
+  // Do necessary cleanups before destroying the object.
+  virtual void Cleanup() = 0;
+
+  // Return true if a static partition exists at device path |path|.
+  virtual bool DeviceExists(const std::string& path) = 0;
+
+  // Returns the current state of the underlying device mapper device
+  // with given name.
+  // One of INVALID, SUSPENDED or ACTIVE.
+  virtual android::dm::DmDeviceState GetState(const std::string& name) = 0;
+
+  // Returns the path to the device mapper device node in '/dev' corresponding
+  // to 'name'. If the device does not exist, false is returned, and the path
+  // parameter is not set.
+  virtual bool GetDmDevicePathByName(const std::string& name,
+                                     std::string* path) = 0;
+
+  // Retrieve metadata from |super_device| at slot |source_slot|.
+  virtual std::unique_ptr<android::fs_mgr::MetadataBuilder> LoadMetadataBuilder(
+      const std::string& super_device, uint32_t source_slot) = 0;
+
+  // Write metadata |builder| to |super_device| at slot |target_slot|.
+  virtual bool StoreMetadata(const std::string& super_device,
+                             android::fs_mgr::MetadataBuilder* builder,
+                             uint32_t target_slot) = 0;
+
+  // Return a possible location for devices listed by name.
+  virtual bool GetDeviceDir(std::string* path) = 0;
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DYNAMIC_PARTITION_CONTROL_INTERFACE_H_
diff --git a/mock_boot_control_hal.h b/mock_boot_control_hal.h
new file mode 100644
index 0000000..4e9cb50
--- /dev/null
+++ b/mock_boot_control_hal.h
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2018 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 <android/hardware/boot/1.0/IBootControl.h>
+#include <stdint.h>
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+class MockBootControlHal
+    : public ::android::hardware::boot::V1_0::IBootControl {
+ public:
+  MOCK_METHOD0(getNumberSlots, ::android::hardware::Return<uint32_t>());
+  MOCK_METHOD0(getCurrentSlot, ::android::hardware::Return<uint32_t>());
+  MOCK_METHOD1(markBootSuccessful,
+               ::android::hardware::Return<void>(markBootSuccessful_cb));
+  MOCK_METHOD2(setActiveBootSlot,
+               ::android::hardware::Return<void>(uint32_t,
+                                                 setActiveBootSlot_cb));
+  MOCK_METHOD2(setSlotAsUnbootable,
+               ::android::hardware::Return<void>(uint32_t,
+                                                 setSlotAsUnbootable_cb));
+  MOCK_METHOD1(
+      isSlotBootable,
+      ::android::hardware::Return<::android::hardware::boot::V1_0::BoolResult>(
+          uint32_t));
+  MOCK_METHOD1(
+      isSlotMarkedSuccessful,
+      ::android::hardware::Return<::android::hardware::boot::V1_0::BoolResult>(
+          uint32_t));
+  MOCK_METHOD2(getSuffix,
+               ::android::hardware::Return<void>(uint32_t, getSuffix_cb));
+};
+
+}  // namespace chromeos_update_engine
diff --git a/mock_dynamic_partition_control.h b/mock_dynamic_partition_control.h
new file mode 100644
index 0000000..ccbc7cf
--- /dev/null
+++ b/mock_dynamic_partition_control.h
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2018 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 <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/dynamic_partition_control_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockDynamicPartitionControl : public DynamicPartitionControlInterface {
+ public:
+  MOCK_METHOD4(
+      MapPartitionOnDeviceMapper,
+      bool(const std::string&, const std::string&, uint32_t, std::string*));
+  MOCK_METHOD2(UnmapPartitionOnDeviceMapper, bool(const std::string&, bool));
+  MOCK_METHOD0(Cleanup, void());
+  MOCK_METHOD1(DeviceExists, bool(const std::string&));
+  MOCK_METHOD1(GetState, ::android::dm::DmDeviceState(const std::string&));
+  MOCK_METHOD2(GetDmDevicePathByName, bool(const std::string&, std::string*));
+  MOCK_METHOD2(LoadMetadataBuilder,
+               std::unique_ptr<::android::fs_mgr::MetadataBuilder>(
+                   const std::string&, uint32_t));
+  MOCK_METHOD3(StoreMetadata,
+               bool(const std::string&,
+                    android::fs_mgr::MetadataBuilder*,
+                    uint32_t));
+  MOCK_METHOD1(GetDeviceDir, bool(std::string*));
+  MOCK_METHOD0(IsDynamicPartitionsEnabled, bool());
+};
+
+}  // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index e700255..30fe763 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -21,6 +21,7 @@
 
 #include <algorithm>
 #include <cstring>
+#include <map>
 #include <memory>
 #include <string>
 #include <utility>
@@ -639,9 +640,11 @@
       return false;
     }
 
-    if (!OpenCurrentPartition()) {
-      *error = ErrorCode::kInstallDeviceOpenError;
-      return false;
+    if (next_operation_num_ < acc_num_operations_[current_partition_]) {
+      if (!OpenCurrentPartition()) {
+        *error = ErrorCode::kInstallDeviceOpenError;
+        return false;
+      }
     }
 
     if (next_operation_num_ > 0)
@@ -658,9 +661,12 @@
 
     // We know there are more operations to perform because we didn't reach the
     // |num_total_operations_| limit yet.
-    while (next_operation_num_ >= acc_num_operations_[current_partition_]) {
+    if (next_operation_num_ >= acc_num_operations_[current_partition_]) {
       CloseCurrentPartition();
-      current_partition_++;
+      // Skip until there are operations for current_partition_.
+      while (next_operation_num_ >= acc_num_operations_[current_partition_]) {
+        current_partition_++;
+      }
       if (!OpenCurrentPartition()) {
         *error = ErrorCode::kInstallDeviceOpenError;
         return false;
@@ -912,6 +918,20 @@
     install_plan_->partitions.push_back(install_part);
   }
 
+  if (install_plan_->target_slot != BootControlInterface::kInvalidSlot) {
+    BootControlInterface::PartitionSizes partition_sizes;
+    for (const InstallPlan::Partition& partition : install_plan_->partitions) {
+      partition_sizes.emplace(partition.name, partition.target_size);
+    }
+    if (!boot_control_->InitPartitionMetadata(install_plan_->target_slot,
+                                              partition_sizes)) {
+      LOG(ERROR) << "Unable to initialize partition metadata for slot "
+                 << BootControlInterface::SlotName(install_plan_->target_slot);
+      *error = ErrorCode::kInstallDeviceOpenError;
+      return false;
+    }
+  }
+
   if (!install_plan_->LoadPartitionsFromSlots(boot_control_)) {
     LOG(ERROR) << "Unable to determine all the partition devices.";
     *error = ErrorCode::kInstallDeviceOpenError;
diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc
index a5e1e61..d74d81d 100644
--- a/payload_consumer/filesystem_verifier_action.cc
+++ b/payload_consumer/filesystem_verifier_action.cc
@@ -98,13 +98,25 @@
       partition_size_ = partition.target_size;
       break;
   }
-  LOG(INFO) << "Hashing partition " << partition_index_ << " ("
-            << partition.name << ") on device " << part_path;
+
   if (part_path.empty()) {
+    if (partition_size_ == 0) {
+      LOG(INFO) << "Skip hashing partition " << partition_index_ << " ("
+                << partition.name << ") because size is 0.";
+      partition_index_++;
+      StartPartitionHashing();
+      return;
+    }
+    LOG(ERROR) << "Cannot hash partition " << partition_index_ << " ("
+               << partition.name
+               << ") because its device path cannot be determined.";
     Cleanup(ErrorCode::kFilesystemVerifierError);
     return;
   }
 
+  LOG(INFO) << "Hashing partition " << partition_index_ << " ("
+            << partition.name << ") on device " << part_path;
+
   brillo::ErrorPtr error;
   src_stream_ = brillo::FileStream::Open(
       base::FilePath(part_path),
diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc
index 45112d6..561e294 100644
--- a/payload_consumer/install_plan.cc
+++ b/payload_consumer/install_plan.cc
@@ -103,7 +103,8 @@
       partition.source_path.clear();
     }
 
-    if (target_slot != BootControlInterface::kInvalidSlot) {
+    if (target_slot != BootControlInterface::kInvalidSlot &&
+        partition.target_size > 0) {
       result = boot_control->GetPartitionDevice(
           partition.name, target_slot, &partition.target_path) && result;
     } else {
diff --git a/update_attempter_android.cc b/update_attempter_android.cc
index 4d21de6..97ff6d6 100644
--- a/update_attempter_android.cc
+++ b/update_attempter_android.cc
@@ -558,6 +558,8 @@
     return;
   }
 
+  boot_control_->Cleanup();
+
   download_progress_ = 0;
   UpdateStatus new_status =
       (error_code == ErrorCode::kSuccess ? UpdateStatus::UPDATED_NEED_REBOOT