Merge "ueventd: allow using external firmware handlers"
diff --git a/adb/adb.cpp b/adb/adb.cpp
index ba6df7a..1ec145b 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -1131,7 +1131,9 @@
 
     if (service == "features") {
         std::string error;
-        atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &error);
+        atransport* t =
+                s->transport ? s->transport
+                             : acquire_one_transport(type, serial, transport_id, nullptr, &error);
         if (t != nullptr) {
             SendOkay(reply_fd, FeatureSetToString(t->features()));
         } else {
@@ -1190,7 +1192,9 @@
     // These always report "unknown" rather than the actual error, for scripts.
     if (service == "get-serialno") {
         std::string error;
-        atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &error);
+        atransport* t =
+                s->transport ? s->transport
+                             : acquire_one_transport(type, serial, transport_id, nullptr, &error);
         if (t) {
             SendOkay(reply_fd, !t->serial.empty() ? t->serial : "unknown");
         } else {
@@ -1200,7 +1204,9 @@
     }
     if (service == "get-devpath") {
         std::string error;
-        atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &error);
+        atransport* t =
+                s->transport ? s->transport
+                             : acquire_one_transport(type, serial, transport_id, nullptr, &error);
         if (t) {
             SendOkay(reply_fd, !t->devpath.empty() ? t->devpath : "unknown");
         } else {
@@ -1210,7 +1216,9 @@
     }
     if (service == "get-state") {
         std::string error;
-        atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &error);
+        atransport* t =
+                s->transport ? s->transport
+                             : acquire_one_transport(type, serial, transport_id, nullptr, &error);
         if (t) {
             SendOkay(reply_fd, t->connection_state_name());
         } else {
@@ -1234,7 +1242,9 @@
 
     if (service == "reconnect") {
         std::string response;
-        atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &response, true);
+        atransport* t = s->transport ? s->transport
+                                     : acquire_one_transport(type, serial, transport_id, nullptr,
+                                                             &response, true);
         if (t != nullptr) {
             kick_transport(t, true);
             response =
@@ -1246,8 +1256,15 @@
 
     // TODO: Switch handle_forward_request to string_view.
     std::string service_str(service);
-    if (handle_forward_request(
-                service_str.c_str(), [=](std::string* error) { return s->transport; }, reply_fd)) {
+    auto transport_acquirer = [=](std::string* error) {
+        if (s->transport) {
+            return s->transport;
+        } else {
+            std::string error;
+            return acquire_one_transport(type, serial, transport_id, nullptr, &error);
+        }
+    };
+    if (handle_forward_request(service_str.c_str(), transport_acquirer, reply_fd)) {
         return HostRequestResult::Handled;
     }
 
diff --git a/adb/sockets.cpp b/adb/sockets.cpp
index 75993b3..e78530c 100644
--- a/adb/sockets.cpp
+++ b/adb/sockets.cpp
@@ -757,6 +757,8 @@
 
 #if ADB_HOST
     service = std::string_view(s->smart_socket_data).substr(4);
+
+    // TODO: These should be handled in handle_host_request.
     if (android::base::ConsumePrefix(&service, "host-serial:")) {
         // serial number should follow "host:" and could be a host:port string.
         if (!internal::parse_host_service(&serial, &service, service)) {
diff --git a/adb/test_device.py b/adb/test_device.py
index f95a5b3..dbd80ed 100755
--- a/adb/test_device.py
+++ b/adb/test_device.py
@@ -139,6 +139,25 @@
         msg = self.device.forward_list()
         self.assertEqual('', msg.strip())
 
+    def test_forward_old_protocol(self):
+        serialno = subprocess.check_output(self.device.adb_cmd + ['get-serialno']).strip()
+
+        msg = self.device.forward_list()
+        self.assertEqual('', msg.strip(),
+                         'Forwarding list must be empty to run this test.')
+
+        s = socket.create_connection(("localhost", 5037))
+        service = b"host-serial:%s:forward:tcp:5566;tcp:6655" % serialno
+        cmd = b"%04x%s" % (len(service), service)
+        s.sendall(cmd)
+
+        msg = self.device.forward_list()
+        self.assertTrue(re.search(r'tcp:5566.+tcp:6655', msg))
+
+        self.device.forward_remove_all()
+        msg = self.device.forward_list()
+        self.assertEqual('', msg.strip())
+
     def test_forward_tcp_port_0(self):
         self.assertEqual('', self.device.forward_list().strip(),
                          'Forwarding list must be empty to run this test.')
diff --git a/base/Android.bp b/base/Android.bp
index 357ce01..f5000c1 100644
--- a/base/Android.bp
+++ b/base/Android.bp
@@ -20,8 +20,14 @@
         "-Wall",
         "-Werror",
         "-Wextra",
-        "-D_FILE_OFFSET_BITS=64",
     ],
+    target: {
+        android: {
+            cflags: [
+                "-D_FILE_OFFSET_BITS=64",
+            ],
+        },
+    },
 }
 
 cc_library_headers {
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 978eed0..546bce2 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -143,6 +143,10 @@
     static_libs: [
         "libhealthhalutils",
     ],
+
+    header_libs: [
+        "libsnapshot_headers",
+    ]
 }
 
 cc_defaults {
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 99854c9..102ebdb 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -32,6 +32,7 @@
 #include <fstab/fstab.h>
 #include <liblp/builder.h>
 #include <liblp/liblp.h>
+#include <libsnapshot/snapshot.h>
 #include <sparse/sparse.h>
 
 #include "fastboot_device.h"
@@ -171,6 +172,11 @@
         if (!slot_suffix.empty() && GetPartitionSlotSuffix(partition_name) == slot_suffix) {
             continue;
         }
+        std::string group_name = GetPartitionGroupName(old_metadata->groups[partition.group_index]);
+        // Skip partitions in the COW group
+        if (group_name == android::snapshot::kCowGroupName) {
+            continue;
+        }
         partitions_to_keep.emplace(partition_name);
     }
 
diff --git a/fs_mgr/fs_mgr_dm_linear.cpp b/fs_mgr/fs_mgr_dm_linear.cpp
index ea799ce..0dcb9fe 100644
--- a/fs_mgr/fs_mgr_dm_linear.cpp
+++ b/fs_mgr/fs_mgr_dm_linear.cpp
@@ -79,22 +79,22 @@
     return true;
 }
 
-bool CreateDmTable(const IPartitionOpener& opener, const LpMetadata& metadata,
-                   const LpMetadataPartition& partition, const std::string& super_device,
-                   DmTable* table) {
+bool CreateDmTableInternal(const CreateLogicalPartitionParams& params, DmTable* table) {
+    const auto& super_device = params.block_device;
+
     uint64_t sector = 0;
-    for (size_t i = 0; i < partition.num_extents; i++) {
-        const auto& extent = metadata.extents[partition.first_extent_index + i];
+    for (size_t i = 0; i < params.partition->num_extents; i++) {
+        const auto& extent = params.metadata->extents[params.partition->first_extent_index + i];
         std::unique_ptr<DmTarget> target;
         switch (extent.target_type) {
             case LP_TARGET_TYPE_ZERO:
                 target = std::make_unique<DmTargetZero>(sector, extent.num_sectors);
                 break;
             case LP_TARGET_TYPE_LINEAR: {
-                const auto& block_device = metadata.block_devices[extent.target_source];
+                const auto& block_device = params.metadata->block_devices[extent.target_source];
                 std::string dev_string;
-                if (!GetPhysicalPartitionDevicePath(opener, metadata, block_device, super_device,
-                                                    &dev_string)) {
+                if (!GetPhysicalPartitionDevicePath(*params.partition_opener, *params.metadata,
+                                                    block_device, super_device, &dev_string)) {
                     LOG(ERROR) << "Unable to complete device-mapper table, unknown block device";
                     return false;
                 }
@@ -111,12 +111,21 @@
         }
         sector += extent.num_sectors;
     }
-    if (partition.attributes & LP_PARTITION_ATTR_READONLY) {
+    if (params.partition->attributes & LP_PARTITION_ATTR_READONLY) {
         table->set_readonly(true);
     }
+    if (params.force_writable) {
+        table->set_readonly(false);
+    }
     return true;
 }
 
+bool CreateDmTable(CreateLogicalPartitionParams params, DmTable* table) {
+    CreateLogicalPartitionParams::OwnedData owned_data;
+    if (!params.InitDefaults(&owned_data)) return false;
+    return CreateDmTableInternal(params, table);
+}
+
 bool CreateLogicalPartitions(const std::string& block_device) {
     uint32_t slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix());
     auto metadata = ReadMetadata(block_device.c_str(), slot);
@@ -160,6 +169,11 @@
         return false;
     }
 
+    if (!partition_opener) {
+        owned->partition_opener = std::make_unique<PartitionOpener>();
+        partition_opener = owned->partition_opener.get();
+    }
+
     // Read metadata if needed.
     if (!metadata) {
         if (!metadata_slot) {
@@ -167,7 +181,8 @@
             return false;
         }
         auto slot = *metadata_slot;
-        if (owned->metadata = ReadMetadata(block_device, slot); !owned->metadata) {
+        if (owned->metadata = ReadMetadata(*partition_opener, block_device, slot);
+            !owned->metadata) {
             LOG(ERROR) << "Could not read partition table for: " << block_device;
             return false;
         }
@@ -195,11 +210,6 @@
         return false;
     }
 
-    if (!partition_opener) {
-        owned->partition_opener = std::make_unique<PartitionOpener>();
-        partition_opener = owned->partition_opener.get();
-    }
-
     if (device_name.empty()) {
         device_name = partition_name;
     }
@@ -212,13 +222,9 @@
     if (!params.InitDefaults(&owned_data)) return false;
 
     DmTable table;
-    if (!CreateDmTable(*params.partition_opener, *params.metadata, *params.partition,
-                       params.block_device, &table)) {
+    if (!CreateDmTableInternal(params, &table)) {
         return false;
     }
-    if (params.force_writable) {
-        table.set_readonly(false);
-    }
 
     DeviceMapper& dm = DeviceMapper::Instance();
     if (!dm.CreateDevice(params.device_name, table, path, params.timeout_ms)) {
diff --git a/fs_mgr/include/fs_mgr_dm_linear.h b/fs_mgr/include/fs_mgr_dm_linear.h
index a912208..f3d09fb 100644
--- a/fs_mgr/include/fs_mgr_dm_linear.h
+++ b/fs_mgr/include/fs_mgr_dm_linear.h
@@ -105,9 +105,7 @@
 bool DestroyLogicalPartition(const std::string& name);
 
 // Helper for populating a DmTable for a logical partition.
-bool CreateDmTable(const IPartitionOpener& opener, const LpMetadata& metadata,
-                   const LpMetadataPartition& partition, const std::string& super_device,
-                   android::dm::DmTable* table);
+bool CreateDmTable(CreateLogicalPartitionParams params, android::dm::DmTable* table);
 
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index c5d6a3b..ea3b58e 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -40,6 +40,10 @@
     return true;
 }
 
+Interval LinearExtent::AsInterval() const {
+    return Interval(device_index(), physical_sector(), end_sector());
+}
+
 bool ZeroExtent::AddTo(LpMetadata* out) const {
     out->extents.emplace_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_ZERO, 0, 0});
     return true;
@@ -96,6 +100,20 @@
     DCHECK(size_ == aligned_size);
 }
 
+Partition Partition::GetBeginningExtents(uint64_t aligned_size) const {
+    Partition p(name_, group_name_, attributes_);
+    for (const auto& extent : extents_) {
+        auto le = extent->AsLinearExtent();
+        if (le) {
+            p.AddExtent(std::make_unique<LinearExtent>(*le));
+        } else {
+            p.AddExtent(std::make_unique<ZeroExtent>(extent->num_sectors()));
+        }
+    }
+    p.ShrinkTo(aligned_size);
+    return p;
+}
+
 uint64_t Partition::BytesOnDisk() const {
     uint64_t sectors = 0;
     for (const auto& extent : extents_) {
@@ -602,6 +620,10 @@
     return ret;
 }
 
+std::unique_ptr<Extent> Interval::AsExtent() const {
+    return std::make_unique<LinearExtent>(length(), device_index, start);
+}
+
 bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t aligned_size,
                                     const std::vector<Interval>& free_region_hint) {
     uint64_t space_needed = aligned_size - partition->size();
@@ -1168,5 +1190,9 @@
                    : "";
 }
 
+uint64_t MetadataBuilder::logical_block_size() const {
+    return geometry_.logical_block_size;
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/liblp/include/liblp/builder.h b/fs_mgr/liblp/include/liblp/builder.h
index 6f2ab75..69885fe 100644
--- a/fs_mgr/liblp/include/liblp/builder.h
+++ b/fs_mgr/liblp/include/liblp/builder.h
@@ -33,10 +33,11 @@
 namespace fs_mgr {
 
 class LinearExtent;
+struct Interval;
 
 // By default, partitions are aligned on a 1MiB boundary.
-static const uint32_t kDefaultPartitionAlignment = 1024 * 1024;
-static const uint32_t kDefaultBlockSize = 4096;
+static constexpr uint32_t kDefaultPartitionAlignment = 1024 * 1024;
+static constexpr uint32_t kDefaultBlockSize = 4096;
 
 // Name of the default group in a metadata.
 static constexpr std::string_view kDefaultGroup = "default";
@@ -74,6 +75,8 @@
         return sector >= physical_sector_ && sector < end_sector();
     }
 
+    Interval AsInterval() const;
+
   private:
     uint32_t device_index_;
     uint64_t physical_sector_;
@@ -127,6 +130,12 @@
     const std::vector<std::unique_ptr<Extent>>& extents() const { return extents_; }
     uint64_t size() const { return size_; }
 
+    // Return a copy of *this, but with extents that includes only the first
+    // |aligned_size| bytes. |aligned_size| should be aligned to
+    // logical_block_size() of the MetadataBuilder that this partition belongs
+    // to.
+    Partition GetBeginningExtents(uint64_t aligned_size) const;
+
   private:
     void ShrinkTo(uint64_t aligned_size);
     void set_group_name(std::string_view group_name) { group_name_ = group_name; }
@@ -156,6 +165,8 @@
         return (start == other.start) ? end < other.end : start < other.start;
     }
 
+    std::unique_ptr<Extent> AsExtent() const;
+
     // Intersect |a| with |b|.
     // If no intersection, result has 0 length().
     static Interval Intersect(const Interval& a, const Interval& b);
@@ -325,6 +336,8 @@
     // Return the list of free regions not occupied by extents in the metadata.
     std::vector<Interval> GetFreeRegions() const;
 
+    uint64_t logical_block_size() const;
+
   private:
     MetadataBuilder();
     MetadataBuilder(const MetadataBuilder&) = delete;
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 8df9c52..f2b6141 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -25,10 +25,12 @@
     shared_libs: [
         "libbase",
         "liblog",
+        "liblp",
     ],
     static_libs: [
         "libdm",
         "libfs_mgr",
+        "libfstab",
         "liblp",
     ],
     whole_static_libs: [
@@ -49,10 +51,17 @@
     name: "libsnapshot_sources",
     srcs: [
         "snapshot.cpp",
+        "partition_cow_creator.cpp",
         "utility.cpp",
     ],
 }
 
+cc_library_headers {
+    name: "libsnapshot_headers",
+    recovery_available: true,
+    defaults: ["libsnapshot_defaults"],
+}
+
 cc_library_static {
     name: "libsnapshot",
     defaults: ["libsnapshot_defaults"],
@@ -77,6 +86,7 @@
     defaults: ["libsnapshot_defaults"],
     srcs: [
         "snapshot_test.cpp",
+        "partition_cow_creator_test.cpp",
         "test_helpers.cpp",
     ],
     shared_libs: [
@@ -90,5 +100,7 @@
         "libgmock",
         "liblp",
         "libsnapshot",
+        "libsparse",
+        "libz",
     ],
 }
diff --git a/fs_mgr/libsnapshot/digital_storage.h b/fs_mgr/libsnapshot/digital_storage.h
new file mode 100644
index 0000000..210298e
--- /dev/null
+++ b/fs_mgr/libsnapshot/digital_storage.h
@@ -0,0 +1,77 @@
+// Copyright (C) 2019 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.
+
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+
+namespace android {
+namespace digital_storage {
+
+template <size_t Power>
+struct Size {
+    static constexpr size_t power = Power;
+    constexpr Size(uint64_t count) : value_(count) {}
+
+    constexpr uint64_t bytes() const { return value_ << (Power * 10); }
+    constexpr uint64_t count() const { return value_; }
+    operator uint64_t() const { return bytes(); }
+
+  private:
+    uint64_t value_;
+};
+
+using B = Size<0>;
+using KiB = Size<1>;
+using MiB = Size<2>;
+using GiB = Size<3>;
+
+constexpr B operator""_B(unsigned long long v) {  // NOLINT
+    return B{v};
+}
+
+constexpr KiB operator""_KiB(unsigned long long v) {  // NOLINT
+    return KiB{v};
+}
+
+constexpr MiB operator""_MiB(unsigned long long v) {  // NOLINT
+    return MiB{v};
+}
+
+constexpr GiB operator""_GiB(unsigned long long v) {  // NOLINT
+    return GiB{v};
+}
+
+template <typename Dest, typename Src>
+constexpr Dest size_cast(Src src) {
+    if (Src::power < Dest::power) {
+        return Dest(src.count() >> ((Dest::power - Src::power) * 10));
+    }
+    if (Src::power > Dest::power) {
+        return Dest(src.count() << ((Src::power - Dest::power) * 10));
+    }
+    return Dest(src.count());
+}
+
+static_assert((1_B).bytes() == 1);
+static_assert((1_KiB).bytes() == 1 << 10);
+static_assert((1_MiB).bytes() == 1 << 20);
+static_assert((1_GiB).bytes() == 1 << 30);
+static_assert(size_cast<KiB>(1_B).count() == 0);
+static_assert(size_cast<KiB>(1024_B).count() == 1);
+static_assert(size_cast<KiB>(1_MiB).count() == 1024);
+
+}  // namespace digital_storage
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index c41a951..6bc09a1 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -17,13 +17,17 @@
 #include <stdint.h>
 
 #include <chrono>
+#include <map>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <android-base/unique_fd.h>
+#include <fs_mgr_dm_linear.h>
 #include <libdm/dm.h>
 #include <libfiemap/image_manager.h>
+#include <liblp/builder.h>
 #include <liblp/liblp.h>
 
 #ifndef FRIEND_TEST
@@ -45,6 +49,13 @@
 
 namespace snapshot {
 
+struct AutoDeleteCowImage;
+struct AutoDeleteSnapshot;
+struct PartitionCowCreator;
+struct AutoDeviceList;
+
+static constexpr const std::string_view kCowGroupName = "cow";
+
 enum class UpdateState : unsigned int {
     // No update or merge is in progress.
     None,
@@ -75,8 +86,9 @@
 
 class SnapshotManager final {
     using CreateLogicalPartitionParams = android::fs_mgr::CreateLogicalPartitionParams;
-    using LpMetadata = android::fs_mgr::LpMetadata;
     using IPartitionOpener = android::fs_mgr::IPartitionOpener;
+    using LpMetadata = android::fs_mgr::LpMetadata;
+    using MetadataBuilder = android::fs_mgr::MetadataBuilder;
 
   public:
     // Dependency injection for testing.
@@ -88,6 +100,7 @@
         virtual std::string GetSlotSuffix() const = 0;
         virtual std::string GetSuperDevice(uint32_t slot) const = 0;
         virtual const IPartitionOpener& GetPartitionOpener() const = 0;
+        virtual bool IsOverlayfsSetup() const = 0;
     };
 
     ~SnapshotManager();
@@ -153,6 +166,20 @@
     //   Other: 0
     UpdateState GetUpdateState(double* progress = nullptr);
 
+    // Create necessary COW device / files for OTA clients. New logical partitions will be added to
+    // group "cow" in target_metadata. Regions of partitions of current_metadata will be
+    // "write-protected" and snapshotted.
+    bool CreateUpdateSnapshots(MetadataBuilder* target_metadata, const std::string& target_suffix,
+                               MetadataBuilder* current_metadata, const std::string& current_suffix,
+                               const std::map<std::string, uint64_t>& cow_sizes);
+
+    // Map a snapshotted partition for OTA clients to write to. Write-protected regions are
+    // determined previously in CreateSnapshots.
+    bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params, std::string* snapshot_path);
+
+    // Unmap a snapshot device that's previously mapped with MapUpdateSnapshot.
+    bool UnmapUpdateSnapshot(const std::string& target_partition_name);
+
     // If this returns true, first-stage mount must call
     // CreateLogicalAndSnapshotPartitions rather than CreateLogicalPartitions.
     bool NeedSnapshotsInFirstStageMount();
@@ -173,7 +200,12 @@
     FRIEND_TEST(SnapshotTest, Merge);
     FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow);
     FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
+    FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
     friend class SnapshotTest;
+    friend class SnapshotUpdateTest;
+    friend struct AutoDeleteCowImage;
+    friend struct AutoDeleteSnapshot;
+    friend struct PartitionCowCreator;
 
     using DmTargetSnapshot = android::dm::DmTargetSnapshot;
     using IImageManager = android::fiemap::IImageManager;
@@ -241,9 +273,8 @@
     // be mapped with two table entries: a dm-snapshot range covering
     // snapshot_size, and a dm-linear range covering the remainder.
     //
-    // All sizes are specified in bytes, and the device, snapshot and COW partition sizes
-    // must be a multiple of the sector size (512 bytes). COW file size will be rounded up
-    // to the nearest sector.
+    // All sizes are specified in bytes, and the device, snapshot, COW partition and COW file sizes
+    // must be a multiple of the sector size (512 bytes).
     bool CreateSnapshot(LockedFile* lock, const std::string& name, SnapshotStatus status);
 
     // |name| should be the base partition name (e.g. "system_a"). Create the
@@ -262,8 +293,7 @@
                      std::string* dev_path);
 
     // Map a COW image that was previous created with CreateCowImage.
-    bool MapCowImage(const std::string& name, const std::chrono::milliseconds& timeout_ms,
-                     std::string* cow_image_device);
+    bool MapCowImage(const std::string& name, const std::chrono::milliseconds& timeout_ms);
 
     // Remove the backing copy-on-write image and snapshot states for the named snapshot. The
     // caller is responsible for ensuring that the snapshot is unmapped.
@@ -344,6 +374,24 @@
     bool MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params,
                                   std::string* path);
 
+    // Map the COW devices, including the partition in super and the images.
+    // |params|:
+    //    - |partition_name| should be the name of the top-level partition (e.g. system_b),
+    //            not system_b-cow-img
+    //    - |device_name| and |partition| is ignored
+    //    - |timeout_ms| and the rest is respected
+    // Return the path in |cow_device_path| (e.g. /dev/block/dm-1) and major:minor in
+    // |cow_device_string|
+    bool MapCowDevices(LockedFile* lock, const CreateLogicalPartitionParams& params,
+                       const SnapshotStatus& snapshot_status, AutoDeviceList* created_devices,
+                       std::string* cow_name);
+
+    // The reverse of MapCowDevices.
+    bool UnmapCowDevices(LockedFile* lock, const std::string& name);
+
+    // The reverse of MapPartitionWithSnapshot.
+    bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name);
+
     std::string gsid_dir_;
     std::string metadata_dir_;
     std::unique_ptr<IDeviceInfo> device_;
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
new file mode 100644
index 0000000..44d8860
--- /dev/null
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -0,0 +1,176 @@
+// Copyright (C) 2019 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 "partition_cow_creator.h"
+
+#include <math.h>
+
+#include <android-base/logging.h>
+
+#include "utility.h"
+
+using android::dm::kSectorSize;
+using android::fs_mgr::Extent;
+using android::fs_mgr::Interval;
+using android::fs_mgr::kDefaultBlockSize;
+using android::fs_mgr::Partition;
+
+namespace android {
+namespace snapshot {
+
+// Round |d| up to a multiple of |block_size|.
+static uint64_t RoundUp(double d, uint64_t block_size) {
+    uint64_t ret = ((uint64_t)ceil(d) + block_size - 1) / block_size * block_size;
+    CHECK(ret >= d) << "Can't round " << d << " up to a multiple of " << block_size;
+    return ret;
+}
+
+// Intersect two linear extents. If no intersection, return an extent with length 0.
+static std::unique_ptr<Extent> Intersect(Extent* target_extent, Extent* existing_extent) {
+    // Convert target_extent and existing_extent to linear extents. Zero extents
+    // doesn't matter and doesn't result in any intersection.
+    auto existing_linear_extent = existing_extent->AsLinearExtent();
+    if (!existing_extent) return nullptr;
+
+    auto target_linear_extent = target_extent->AsLinearExtent();
+    if (!target_linear_extent) return nullptr;
+
+    return Interval::Intersect(target_linear_extent->AsInterval(),
+                               existing_linear_extent->AsInterval())
+            .AsExtent();
+}
+
+// Check that partition |p| contains |e| fully. Both of them should
+// be from |target_metadata|.
+// Returns true as long as |e| is a subrange of any extent of |p|.
+bool PartitionCowCreator::HasExtent(Partition* p, Extent* e) {
+    for (auto& partition_extent : p->extents()) {
+        auto intersection = Intersect(partition_extent.get(), e);
+        if (intersection != nullptr && intersection->num_sectors() == e->num_sectors()) {
+            return true;
+        }
+    }
+    return false;
+}
+
+// Return the number of sectors, N, where |target_partition|[0..N] (from
+// |target_metadata|) are the sectors that should be snapshotted. N is computed
+// so that this range of sectors are used by partitions in |current_metadata|.
+//
+// The client code (update_engine) should have computed target_metadata by
+// resizing partitions of current_metadata, so only the first N sectors should
+// be snapshotted, not a range with start index != 0.
+//
+// Note that if partition A has shrunk and partition B has grown, the new
+// extents of partition B may use the empty space that was used by partition A.
+// In this case, that new extent cannot be written directly, as it may be used
+// by the running system. Hence, all extents of the new partition B must be
+// intersected with all old partitions (including old partition A and B) to get
+// the region that needs to be snapshotted.
+std::optional<uint64_t> PartitionCowCreator::GetSnapshotSize() {
+    // Compute the number of sectors that needs to be snapshotted.
+    uint64_t snapshot_sectors = 0;
+    std::vector<std::unique_ptr<Extent>> intersections;
+    for (const auto& extent : target_partition->extents()) {
+        for (auto* existing_partition :
+             ListPartitionsWithSuffix(current_metadata, current_suffix)) {
+            for (const auto& existing_extent : existing_partition->extents()) {
+                auto intersection = Intersect(extent.get(), existing_extent.get());
+                if (intersection != nullptr && intersection->num_sectors() > 0) {
+                    snapshot_sectors += intersection->num_sectors();
+                    intersections.emplace_back(std::move(intersection));
+                }
+            }
+        }
+    }
+    uint64_t snapshot_size = snapshot_sectors * kSectorSize;
+
+    // Sanity check that all recorded intersections are indeed within
+    // target_partition[0..snapshot_sectors].
+    Partition target_partition_snapshot = target_partition->GetBeginningExtents(snapshot_size);
+    for (const auto& intersection : intersections) {
+        if (!HasExtent(&target_partition_snapshot, intersection.get())) {
+            auto linear_intersection = intersection->AsLinearExtent();
+            LOG(ERROR) << "Extent "
+                       << (linear_intersection
+                                   ? (std::to_string(linear_intersection->physical_sector()) + "," +
+                                      std::to_string(linear_intersection->end_sector()))
+                                   : "")
+                       << " is not part of Partition " << target_partition->name() << "[0.."
+                       << snapshot_size
+                       << "]. The metadata wasn't constructed correctly. This should not happen.";
+            return std::nullopt;
+        }
+    }
+
+    return snapshot_size;
+}
+
+std::optional<PartitionCowCreator::Return> PartitionCowCreator::Run() {
+    static constexpr double kCowEstimateFactor = 1.05;
+
+    CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME &&
+          target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME);
+
+    uint64_t logical_block_size = current_metadata->logical_block_size();
+    CHECK(logical_block_size != 0 && !(logical_block_size & (logical_block_size - 1)))
+            << "logical_block_size is not power of 2";
+
+    Return ret;
+    ret.snapshot_status.device_size = target_partition->size();
+
+    auto snapshot_size = GetSnapshotSize();
+    if (!snapshot_size.has_value()) return std::nullopt;
+
+    ret.snapshot_status.snapshot_size = *snapshot_size;
+
+    // TODO: always read from cow_size when the COW size is written in
+    // update package. kCowEstimateFactor is good for prototyping but
+    // we can't use that in production.
+    if (!cow_size.has_value()) {
+        cow_size =
+                RoundUp(ret.snapshot_status.snapshot_size * kCowEstimateFactor, kDefaultBlockSize);
+    }
+
+    // Compute regions that are free in both current and target metadata. These are the regions
+    // we can use for COW partition.
+    auto target_free_regions = target_metadata->GetFreeRegions();
+    auto current_free_regions = current_metadata->GetFreeRegions();
+    auto free_regions = Interval::Intersect(target_free_regions, current_free_regions);
+    uint64_t free_region_length = 0;
+    for (const auto& interval : free_regions) {
+        free_region_length += interval.length() * kSectorSize;
+    }
+
+    LOG(INFO) << "Remaining free space for COW: " << free_region_length << " bytes";
+
+    // Compute the COW partition size.
+    ret.snapshot_status.cow_partition_size = std::min(*cow_size, free_region_length);
+    // Round it down to the nearest logical block. Logical partitions must be a multiple
+    // of logical blocks.
+    ret.snapshot_status.cow_partition_size &= ~(logical_block_size - 1);
+    // Assign cow_partition_usable_regions to indicate what regions should the COW partition uses.
+    ret.cow_partition_usable_regions = std::move(free_regions);
+
+    // The rest of the COW space is allocated on ImageManager.
+    ret.snapshot_status.cow_file_size = (*cow_size) - ret.snapshot_status.cow_partition_size;
+    // Round it up to the nearest sector.
+    ret.snapshot_status.cow_file_size += kSectorSize - 1;
+    ret.snapshot_status.cow_file_size &= ~(kSectorSize - 1);
+
+    return ret;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h
new file mode 100644
index 0000000..a235914
--- /dev/null
+++ b/fs_mgr/libsnapshot/partition_cow_creator.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2019 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.
+
+#pragma once
+
+#include <stdint.h>
+
+#include <optional>
+#include <string>
+
+#include <liblp/builder.h>
+
+#include <libsnapshot/snapshot.h>
+
+namespace android {
+namespace snapshot {
+
+// Helper class that creates COW for a partition.
+struct PartitionCowCreator {
+    using Extent = android::fs_mgr::Extent;
+    using Interval = android::fs_mgr::Interval;
+    using MetadataBuilder = android::fs_mgr::MetadataBuilder;
+    using Partition = android::fs_mgr::Partition;
+
+    // The metadata that will be written to target metadata slot.
+    MetadataBuilder* target_metadata;
+    // The suffix of the target slot.
+    std::string target_suffix;
+    // The partition in target_metadata that needs to be snapshotted.
+    Partition* target_partition;
+    // The metadata at the current slot (that would be used if the device boots
+    // normally). This is used to determine which extents are being used.
+    MetadataBuilder* current_metadata;
+    // The suffix of the current slot.
+    std::string current_suffix;
+    // The COW size given by client code.
+    std::optional<uint64_t> cow_size;
+
+    struct Return {
+        SnapshotManager::SnapshotStatus snapshot_status;
+        std::vector<Interval> cow_partition_usable_regions;
+    };
+
+    std::optional<Return> Run();
+
+  private:
+    bool HasExtent(Partition* p, Extent* e);
+    std::optional<uint64_t> GetSnapshotSize();
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
new file mode 100644
index 0000000..e308943
--- /dev/null
+++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
@@ -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.
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <liblp/builder.h>
+#include <liblp/property_fetcher.h>
+
+#include "partition_cow_creator.h"
+
+using ::android::fs_mgr::MetadataBuilder;
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::Return;
+
+namespace android {
+namespace snapshot {
+
+class MockPropertyFetcher : public fs_mgr::IPropertyFetcher {
+  public:
+    MOCK_METHOD2(GetProperty, std::string(const std::string&, const std::string&));
+    MOCK_METHOD2(GetBoolProperty, bool(const std::string&, bool));
+};
+
+class PartitionCowCreatorTest : ::testing::Test {
+  public:
+    void SetUp() override {
+        fs_mgr::IPropertyFetcher::OverrideForTesting(std::make_unique<MockPropertyFetcher>());
+
+        EXPECT_CALL(fetcher(), GetProperty("ro.boot.slot_suffix", _))
+                .Times(AnyNumber())
+                .WillRepeatedly(Return("_a"));
+        EXPECT_CALL(fetcher(), GetBoolProperty("ro.boot.dynamic_partitions", _))
+                .Times(AnyNumber())
+                .WillRepeatedly(Return(true));
+        EXPECT_CALL(fetcher(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
+                .Times(AnyNumber())
+                .WillRepeatedly(Return(false));
+        EXPECT_CALL(fetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
+                .Times(AnyNumber())
+                .WillRepeatedly(Return(true));
+    }
+    void TearDown() override {
+        fs_mgr::IPropertyFetcher::OverrideForTesting(std::make_unique<MockPropertyFetcher>());
+    }
+    MockPropertyFetcher& fetcher() {
+        return *static_cast<MockPropertyFetcher*>(fs_mgr::IPropertyFetcher::GetInstance());
+    }
+};
+
+TEST(PartitionCowCreator, IntersectSelf) {
+    auto builder_a = MetadataBuilder::New(1024 * 1024, 1024, 2);
+    ASSERT_NE(builder_a, nullptr);
+    auto system_a = builder_a->AddPartition("system_a", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system_a, nullptr);
+    ASSERT_TRUE(builder_a->ResizePartition(system_a, 40 * 1024));
+
+    auto builder_b = MetadataBuilder::New(1024 * 1024, 1024, 2);
+    ASSERT_NE(builder_b, nullptr);
+    auto system_b = builder_b->AddPartition("system_b", LP_PARTITION_ATTR_READONLY);
+    ASSERT_NE(system_b, nullptr);
+    ASSERT_TRUE(builder_b->ResizePartition(system_b, 40 * 1024));
+
+    PartitionCowCreator creator{.target_metadata = builder_b.get(),
+                                .target_suffix = "_b",
+                                .target_partition = system_b,
+                                .current_metadata = builder_a.get(),
+                                .current_suffix = "_a",
+                                .cow_size = 20 * 1024};
+    auto ret = creator.Run();
+    ASSERT_TRUE(ret.has_value());
+    ASSERT_EQ(40 * 1024, ret->snapshot_status.device_size);
+    ASSERT_EQ(40 * 1024, ret->snapshot_status.snapshot_size);
+    ASSERT_EQ(20 * 1024,
+              ret->snapshot_status.cow_file_size + ret->snapshot_status.cow_partition_size);
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 5813a2d..15d52f1 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -15,6 +15,7 @@
 #include <libsnapshot/snapshot.h>
 
 #include <dirent.h>
+#include <math.h>
 #include <sys/file.h>
 #include <sys/types.h>
 #include <sys/unistd.h>
@@ -31,11 +32,13 @@
 #include <ext4_utils/ext4_utils.h>
 #include <fs_mgr.h>
 #include <fs_mgr_dm_linear.h>
+#include <fs_mgr_overlayfs.h>
 #include <fstab/fstab.h>
 #include <libdm/dm.h>
 #include <libfiemap/image_manager.h>
 #include <liblp/liblp.h>
 
+#include "partition_cow_creator.h"
 #include "utility.h"
 
 namespace android {
@@ -53,8 +56,10 @@
 using android::fs_mgr::CreateDmTable;
 using android::fs_mgr::CreateLogicalPartition;
 using android::fs_mgr::CreateLogicalPartitionParams;
+using android::fs_mgr::GetPartitionGroupName;
 using android::fs_mgr::GetPartitionName;
 using android::fs_mgr::LpMetadata;
+using android::fs_mgr::MetadataBuilder;
 using android::fs_mgr::SlotNumberForSlotSuffix;
 using std::chrono::duration_cast;
 using namespace std::chrono_literals;
@@ -73,6 +78,7 @@
     std::string GetSuperDevice(uint32_t slot) const override {
         return fs_mgr_get_super_partition_name(slot);
     }
+    bool IsOverlayfsSetup() const override { return fs_mgr_overlayfs_is_setup(); }
 
   private:
     android::fs_mgr::PartitionOpener opener_;
@@ -102,7 +108,7 @@
     metadata_dir_ = device_->GetMetadataDir();
 }
 
-[[maybe_unused]] static std::string GetCowName(const std::string& snapshot_name) {
+static std::string GetCowName(const std::string& snapshot_name) {
     return snapshot_name + "-cow";
 }
 
@@ -205,7 +211,7 @@
     CHECK(lock->lock_mode() == LOCK_EX);
     // Sanity check these sizes. Like liblp, we guarantee the partition size
     // is respected, which means it has to be sector-aligned. (This guarantee
-    // is useful for locating avb footers correctly). The COW size, however,
+    // is useful for locating avb footers correctly). The COW file size, however,
     // can be arbitrarily larger than specified, so we can safely round it up.
     if (status.device_size % kSectorSize != 0) {
         LOG(ERROR) << "Snapshot " << name
@@ -217,10 +223,17 @@
                    << status.snapshot_size;
         return false;
     }
-
-    // Round the COW size up to the nearest sector.
-    status.cow_file_size += kSectorSize - 1;
-    status.cow_file_size &= ~(kSectorSize - 1);
+    if (status.cow_partition_size % kSectorSize != 0) {
+        LOG(ERROR) << "Snapshot " << name
+                   << " cow partition size is not a multiple of the sector size: "
+                   << status.cow_partition_size;
+        return false;
+    }
+    if (status.cow_file_size % kSectorSize != 0) {
+        LOG(ERROR) << "Snapshot " << name << " cow file size is not a multiple of the sector size: "
+                   << status.cow_partition_size;
+        return false;
+    }
 
     status.state = SnapshotState::Created;
     status.sectors_allocated = 0;
@@ -253,26 +266,7 @@
 
     std::string cow_image_name = GetCowImageDeviceName(name);
     int cow_flags = IImageManager::CREATE_IMAGE_DEFAULT;
-    if (!images_->CreateBackingImage(cow_image_name, status.cow_file_size, cow_flags)) {
-        return false;
-    }
-
-    // when the kernel creates a persistent dm-snapshot, it requires a CoW file
-    // to store the modifications. The kernel interface does not specify how
-    // the CoW is used, and there is no standard associated.
-    // By looking at the current implementation, the CoW file is treated as:
-    // - a _NEW_ snapshot if its first 32 bits are zero, so the newly created
-    // dm-snapshot device will look like a perfect copy of the origin device;
-    // - an _EXISTING_ snapshot if the first 32 bits are equal to a
-    // kernel-specified magic number and the CoW file metadata is set as valid,
-    // so it can be used to resume the last state of a snapshot device;
-    // - an _INVALID_ snapshot otherwise.
-    // To avoid zero-filling the whole CoW file when a new dm-snapshot is
-    // created, here we zero-fill only the first 32 bits. This is a temporary
-    // workaround that will be discussed again when the kernel API gets
-    // consolidated.
-    ssize_t dm_snap_magic_size = 4;  // 32 bit
-    return images_->ZeroFillNewImage(cow_image_name, dm_snap_magic_size);
+    return images_->CreateBackingImage(cow_image_name, status.cow_file_size, cow_flags);
 }
 
 bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
@@ -390,21 +384,24 @@
 }
 
 bool SnapshotManager::MapCowImage(const std::string& name,
-                                  const std::chrono::milliseconds& timeout_ms,
-                                  std::string* cow_dev) {
+                                  const std::chrono::milliseconds& timeout_ms) {
     if (!EnsureImageManager()) return false;
     auto cow_image_name = GetCowImageDeviceName(name);
 
     bool ok;
+    std::string cow_dev;
     if (has_local_image_manager_) {
         // If we forced a local image manager, it means we don't have binder,
         // which means first-stage init. We must use device-mapper.
         const auto& opener = device_->GetPartitionOpener();
-        ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, cow_dev);
+        ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, &cow_dev);
     } else {
-        ok = images_->MapImageDevice(cow_image_name, timeout_ms, cow_dev);
+        ok = images_->MapImageDevice(cow_image_name, timeout_ms, &cow_dev);
     }
-    if (!ok) {
+
+    if (ok) {
+        LOG(INFO) << "Mapped " << cow_image_name << " to " << cow_dev;
+    } else {
         LOG(ERROR) << "Could not map image device: " << cow_image_name;
     }
     return ok;
@@ -413,22 +410,15 @@
 bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
     CHECK(lock);
 
-    SnapshotStatus status;
-    if (!ReadSnapshotStatus(lock, name, &status)) {
-        return false;
-    }
-
     auto& dm = DeviceMapper::Instance();
     if (!dm.DeleteDeviceIfExists(name)) {
         LOG(ERROR) << "Could not delete snapshot device: " << name;
         return false;
     }
 
-    // There may be an extra device, since the kernel doesn't let us have a
-    // snapshot and linear target in the same table.
-    auto dm_name = GetSnapshotDeviceName(name, status);
-    if (name != dm_name && !dm.DeleteDeviceIfExists(dm_name)) {
-        LOG(ERROR) << "Could not delete inner snapshot device: " << dm_name;
+    auto snapshot_extra_device = GetSnapshotExtraDeviceName(name);
+    if (!dm.DeleteDeviceIfExists(snapshot_extra_device)) {
+        LOG(ERROR) << "Could not delete snapshot inner device: " << snapshot_extra_device;
         return false;
     }
 
@@ -445,11 +435,12 @@
     CHECK(lock->lock_mode() == LOCK_EX);
     if (!EnsureImageManager()) return false;
 
+    if (!UnmapCowDevices(lock, name)) {
+        return false;
+    }
+
     auto cow_image_name = GetCowImageDeviceName(name);
     if (images_->BackingImageExists(cow_image_name)) {
-        if (!images_->UnmapImageIfExists(cow_image_name)) {
-            return false;
-        }
         if (!images_->DeleteBackingImage(cow_image_name)) {
             return false;
         }
@@ -992,24 +983,16 @@
         }
     }
 
-    // Grab the partition metadata for the snapshot.
     uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
-    auto super_device = device_->GetSuperDevice(slot);
-    const auto& opener = device_->GetPartitionOpener();
-    auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
-    if (!metadata) {
-        LOG(ERROR) << "Could not read super partition metadata.";
-        return false;
-    }
-    auto partition = android::fs_mgr::FindPartition(*metadata.get(), name);
-    if (!partition) {
-        LOG(ERROR) << "Snapshot does not have a partition in super: " << name;
-        return false;
-    }
-
     // Create a DmTable that is identical to the base device.
+    CreateLogicalPartitionParams base_device_params{
+            .block_device = device_->GetSuperDevice(slot),
+            .metadata_slot = slot,
+            .partition_name = name,
+            .partition_opener = &device_->GetPartitionOpener(),
+    };
     DmTable table;
-    if (!CreateDmTable(opener, *metadata.get(), *partition, super_device, &table)) {
+    if (!CreateDmTable(base_device_params, &table)) {
         LOG(ERROR) << "Could not create a DmTable for partition: " << name;
         return false;
     }
@@ -1086,7 +1069,7 @@
 
     bool ok = true;
     for (const auto& name : snapshots) {
-        ok &= DeleteSnapshot(lock, name);
+        ok &= (UnmapPartitionWithSnapshot(lock, name) && DeleteSnapshot(lock, name));
     }
     return ok;
 }
@@ -1188,6 +1171,12 @@
     }
 
     for (const auto& partition : metadata->partitions) {
+        if (GetPartitionGroupName(metadata->groups[partition.group_index]) == kCowGroupName) {
+            LOG(INFO) << "Skip mapping partition " << GetPartitionName(partition) << " in group "
+                      << kCowGroupName;
+            continue;
+        }
+
         CreateLogicalPartitionParams params = {
                 .block_device = super_device,
                 .metadata = metadata.get(),
@@ -1229,6 +1218,12 @@
     CHECK(lock);
     path->clear();
 
+    if (params.GetPartitionName() != params.GetDeviceName()) {
+        LOG(ERROR) << "Mapping snapshot with a different name is unsupported: partition_name = "
+                   << params.GetPartitionName() << ", device_name = " << params.GetDeviceName();
+        return false;
+    }
+
     // Fill out fields in CreateLogicalPartitionParams so that we have more information (e.g. by
     // reading super partition metadata).
     CreateLogicalPartitionParams::OwnedData params_owned_data;
@@ -1304,22 +1299,20 @@
         return false;
     }
 
-    // If there is a timeout specified, compute the remaining time to call Map* functions.
-    // init calls CreateLogicalAndSnapshotPartitions, which has no timeout specified. Still call
-    // Map* functions in this case.
     auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
     if (remaining_time.count() < 0) return false;
 
-    std::string cow_image_device;
-    if (!MapCowImage(params.GetPartitionName(), remaining_time, &cow_image_device)) {
-        LOG(ERROR) << "Could not map cow image for partition: " << params.GetPartitionName();
+    std::string cow_name;
+    CreateLogicalPartitionParams cow_params = params;
+    cow_params.timeout_ms = remaining_time;
+    if (!MapCowDevices(lock, cow_params, *live_snapshot_status, &created_devices, &cow_name)) {
         return false;
     }
-    created_devices.EmplaceBack<AutoUnmapImage>(images_.get(),
-                                                GetCowImageDeviceName(params.partition_name));
-
-    // TODO: map cow linear device here
-    std::string cow_device = cow_image_device;
+    std::string cow_device;
+    if (!dm.GetDeviceString(cow_name, &cow_device)) {
+        LOG(ERROR) << "Could not determine major/minor for: " << cow_name;
+        return false;
+    }
 
     remaining_time = GetRemainingTime(params.timeout_ms, begin);
     if (remaining_time.count() < 0) return false;
@@ -1338,6 +1331,121 @@
     return true;
 }
 
+bool SnapshotManager::UnmapPartitionWithSnapshot(LockedFile* lock,
+                                                 const std::string& target_partition_name) {
+    CHECK(lock);
+
+    if (!UnmapSnapshot(lock, target_partition_name)) {
+        return false;
+    }
+
+    if (!UnmapCowDevices(lock, target_partition_name)) {
+        return false;
+    }
+
+    auto& dm = DeviceMapper::Instance();
+    std::string base_name = GetBaseDeviceName(target_partition_name);
+    if (!dm.DeleteDeviceIfExists(base_name)) {
+        LOG(ERROR) << "Cannot delete base device: " << base_name;
+        return false;
+    }
+
+    LOG(INFO) << "Successfully unmapped snapshot " << target_partition_name;
+
+    return true;
+}
+
+bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartitionParams& params,
+                                    const SnapshotStatus& snapshot_status,
+                                    AutoDeviceList* created_devices, std::string* cow_name) {
+    CHECK(lock);
+    if (!EnsureImageManager()) return false;
+    CHECK(snapshot_status.cow_partition_size + snapshot_status.cow_file_size > 0);
+    auto begin = std::chrono::steady_clock::now();
+
+    std::string partition_name = params.GetPartitionName();
+    std::string cow_image_name = GetCowImageDeviceName(partition_name);
+    *cow_name = GetCowName(partition_name);
+
+    auto& dm = DeviceMapper::Instance();
+
+    // Map COW image if necessary.
+    if (snapshot_status.cow_file_size > 0) {
+        auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
+        if (remaining_time.count() < 0) return false;
+
+        if (!MapCowImage(partition_name, remaining_time)) {
+            LOG(ERROR) << "Could not map cow image for partition: " << partition_name;
+            return false;
+        }
+        created_devices->EmplaceBack<AutoUnmapImage>(images_.get(), cow_image_name);
+
+        // If no COW partition exists, just return the image alone.
+        if (snapshot_status.cow_partition_size == 0) {
+            *cow_name = std::move(cow_image_name);
+            LOG(INFO) << "Mapped COW image for " << partition_name << " at " << *cow_name;
+            return true;
+        }
+    }
+
+    auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
+    if (remaining_time.count() < 0) return false;
+
+    CHECK(snapshot_status.cow_partition_size > 0);
+
+    // Create the DmTable for the COW device. It is the DmTable of the COW partition plus
+    // COW image device as the last extent.
+    CreateLogicalPartitionParams cow_partition_params = params;
+    cow_partition_params.partition = nullptr;
+    cow_partition_params.partition_name = *cow_name;
+    cow_partition_params.device_name.clear();
+    DmTable table;
+    if (!CreateDmTable(cow_partition_params, &table)) {
+        return false;
+    }
+    // If the COW image exists, append it as the last extent.
+    if (snapshot_status.cow_file_size > 0) {
+        std::string cow_image_device;
+        if (!dm.GetDeviceString(cow_image_name, &cow_image_device)) {
+            LOG(ERROR) << "Cannot determine major/minor for: " << cow_image_name;
+            return false;
+        }
+        auto cow_partition_sectors = snapshot_status.cow_partition_size / kSectorSize;
+        auto cow_image_sectors = snapshot_status.cow_file_size / kSectorSize;
+        table.Emplace<DmTargetLinear>(cow_partition_sectors, cow_image_sectors, cow_image_device,
+                                      0);
+    }
+
+    // We have created the DmTable now. Map it.
+    std::string cow_path;
+    if (!dm.CreateDevice(*cow_name, table, &cow_path, remaining_time)) {
+        LOG(ERROR) << "Could not create COW device: " << *cow_name;
+        return false;
+    }
+    created_devices->EmplaceBack<AutoUnmapDevice>(&dm, *cow_name);
+    LOG(INFO) << "Mapped COW device for " << params.GetPartitionName() << " at " << cow_path;
+    return true;
+}
+
+bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name) {
+    CHECK(lock);
+    if (!EnsureImageManager()) return false;
+
+    auto& dm = DeviceMapper::Instance();
+    auto cow_name = GetCowName(name);
+    if (!dm.DeleteDeviceIfExists(cow_name)) {
+        LOG(ERROR) << "Cannot unmap " << cow_name;
+        return false;
+    }
+
+    std::string cow_image_name = GetCowImageDeviceName(name);
+    if (!images_->UnmapImageIfExists(cow_image_name)) {
+        LOG(ERROR) << "Cannot unmap image " << cow_image_name;
+        return false;
+    }
+    return true;
+}
+
 auto SnapshotManager::OpenFile(const std::string& file, int open_flags, int lock_flags)
         -> std::unique_ptr<LockedFile> {
     unique_fd fd(open(file.c_str(), open_flags | O_CLOEXEC | O_NOFOLLOW | O_SYNC, 0660));
@@ -1605,5 +1713,200 @@
     return true;
 }
 
+bool SnapshotManager::CreateUpdateSnapshots(MetadataBuilder* target_metadata,
+                                            const std::string& target_suffix,
+                                            MetadataBuilder* current_metadata,
+                                            const std::string& current_suffix,
+                                            const std::map<std::string, uint64_t>& cow_sizes) {
+    auto lock = LockExclusive();
+    if (!lock) return false;
+
+    // Add _{target_suffix} to COW size map.
+    std::map<std::string, uint64_t> suffixed_cow_sizes;
+    for (const auto& [name, size] : cow_sizes) {
+        suffixed_cow_sizes[name + target_suffix] = size;
+    }
+
+    target_metadata->RemoveGroupAndPartitions(kCowGroupName);
+    if (!target_metadata->AddGroup(kCowGroupName, 0)) {
+        LOG(ERROR) << "Cannot add group " << kCowGroupName;
+        return false;
+    }
+
+    // TODO(b/134949511): remove this check. Right now, with overlayfs mounted, the scratch
+    // partition takes up a big chunk of space in super, causing COW images to be created on
+    // retrofit Virtual A/B devices.
+    if (device_->IsOverlayfsSetup()) {
+        LOG(ERROR) << "Cannot create update snapshots with overlayfs setup. Run `adb enable-verity`"
+                   << ", reboot, then try again.";
+        return false;
+    }
+
+    // Check that all these metadata is not retrofit dynamic partitions. Snapshots on
+    // devices with retrofit dynamic partitions does not make sense.
+    // This ensures that current_metadata->GetFreeRegions() uses the same device
+    // indices as target_metadata (i.e. 0 -> "super").
+    // This is also assumed in MapCowDevices() call below.
+    CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME &&
+          target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME);
+
+    std::map<std::string, SnapshotStatus> all_snapshot_status;
+
+    // In case of error, automatically delete devices that are created along the way.
+    // Note that "lock" is destroyed after "created_devices", so it is safe to use |lock| for
+    // these devices.
+    AutoDeviceList created_devices;
+
+    for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) {
+        std::optional<uint64_t> cow_size = std::nullopt;
+        auto it = suffixed_cow_sizes.find(target_partition->name());
+        if (it != suffixed_cow_sizes.end()) {
+            cow_size = it->second;
+            LOG(INFO) << "Using provided COW size " << *cow_size << " for partition "
+                      << target_partition->name();
+        }
+
+        // Compute the device sizes for the partition.
+        PartitionCowCreator cow_creator{target_metadata,  target_suffix,  target_partition,
+                                        current_metadata, current_suffix, cow_size};
+        auto cow_creator_ret = cow_creator.Run();
+        if (!cow_creator_ret.has_value()) {
+            return false;
+        }
+
+        LOG(INFO) << "For partition " << target_partition->name()
+                  << ", device size = " << cow_creator_ret->snapshot_status.device_size
+                  << ", snapshot size = " << cow_creator_ret->snapshot_status.snapshot_size
+                  << ", cow partition size = "
+                  << cow_creator_ret->snapshot_status.cow_partition_size
+                  << ", cow file size = " << cow_creator_ret->snapshot_status.cow_file_size;
+
+        // Delete any existing snapshot before re-creating one.
+        if (!DeleteSnapshot(lock.get(), target_partition->name())) {
+            LOG(ERROR) << "Cannot delete existing snapshot before creating a new one for partition "
+                       << target_partition->name();
+            return false;
+        }
+
+        // It is possible that the whole partition uses free space in super, and snapshot / COW
+        // would not be needed. In this case, skip the partition.
+        bool needs_snapshot = cow_creator_ret->snapshot_status.snapshot_size > 0;
+        bool needs_cow = (cow_creator_ret->snapshot_status.cow_partition_size +
+                          cow_creator_ret->snapshot_status.cow_file_size) > 0;
+        CHECK(needs_snapshot == needs_cow);
+
+        if (!needs_snapshot) {
+            LOG(INFO) << "Skip creating snapshot for partition " << target_partition->name()
+                      << "because nothing needs to be snapshotted.";
+            continue;
+        }
+
+        // Store these device sizes to snapshot status file.
+        if (!CreateSnapshot(lock.get(), target_partition->name(),
+                            cow_creator_ret->snapshot_status)) {
+            return false;
+        }
+        created_devices.EmplaceBack<AutoDeleteSnapshot>(this, lock.get(), target_partition->name());
+
+        // Create the COW partition. That is, use any remaining free space in super partition before
+        // creating the COW images.
+        if (cow_creator_ret->snapshot_status.cow_partition_size > 0) {
+            CHECK(cow_creator_ret->snapshot_status.cow_partition_size % kSectorSize == 0)
+                    << "cow_partition_size == "
+                    << cow_creator_ret->snapshot_status.cow_partition_size
+                    << " is not a multiple of sector size " << kSectorSize;
+            auto cow_partition = target_metadata->AddPartition(GetCowName(target_partition->name()),
+                                                               kCowGroupName, 0 /* flags */);
+            if (cow_partition == nullptr) {
+                return false;
+            }
+
+            if (!target_metadata->ResizePartition(
+                        cow_partition, cow_creator_ret->snapshot_status.cow_partition_size,
+                        cow_creator_ret->cow_partition_usable_regions)) {
+                LOG(ERROR) << "Cannot create COW partition on metadata with size "
+                           << cow_creator_ret->snapshot_status.cow_partition_size;
+                return false;
+            }
+            // Only the in-memory target_metadata is modified; nothing to clean up if there is an
+            // error in the future.
+        }
+
+        // Create the backing COW image if necessary.
+        if (cow_creator_ret->snapshot_status.cow_file_size > 0) {
+            if (!CreateCowImage(lock.get(), target_partition->name())) {
+                return false;
+            }
+        }
+
+        all_snapshot_status[target_partition->name()] = std::move(cow_creator_ret->snapshot_status);
+
+        LOG(INFO) << "Successfully created snapshot for " << target_partition->name();
+    }
+
+    auto& dm = DeviceMapper::Instance();
+    auto exported_target_metadata = target_metadata->Export();
+    CreateLogicalPartitionParams cow_params{
+            .block_device = LP_METADATA_DEFAULT_PARTITION_NAME,
+            .metadata = exported_target_metadata.get(),
+            .timeout_ms = std::chrono::milliseconds::max(),
+            .partition_opener = &device_->GetPartitionOpener(),
+    };
+    for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) {
+        AutoDeviceList created_devices_for_cow;
+
+        if (!UnmapPartitionWithSnapshot(lock.get(), target_partition->name())) {
+            LOG(ERROR) << "Cannot unmap existing COW devices before re-mapping them for zero-fill: "
+                       << target_partition->name();
+            return false;
+        }
+
+        auto it = all_snapshot_status.find(target_partition->name());
+        CHECK(it != all_snapshot_status.end()) << target_partition->name();
+        cow_params.partition_name = target_partition->name();
+        std::string cow_name;
+        if (!MapCowDevices(lock.get(), cow_params, it->second, &created_devices_for_cow,
+                           &cow_name)) {
+            return false;
+        }
+
+        std::string cow_path;
+        if (!dm.GetDmDevicePathByName(cow_name, &cow_path)) {
+            LOG(ERROR) << "Cannot determine path for " << cow_name;
+            return false;
+        }
+
+        if (!InitializeCow(cow_path)) {
+            LOG(ERROR) << "Can't zero-fill COW device for " << target_partition->name() << ": "
+                       << cow_path;
+            return false;
+        }
+        // Let destructor of created_devices_for_cow to unmap the COW devices.
+    };
+
+    created_devices.Release();
+    LOG(INFO) << "Successfully created all snapshots for target slot " << target_suffix;
+
+    return true;
+}
+
+bool SnapshotManager::MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
+                                        std::string* snapshot_path) {
+    auto lock = LockShared();
+    if (!lock) return false;
+    if (!UnmapPartitionWithSnapshot(lock.get(), params.GetPartitionName())) {
+        LOG(ERROR) << "Cannot unmap existing snapshot before re-mapping it: "
+                   << params.GetPartitionName();
+        return false;
+    }
+    return MapPartitionWithSnapshot(lock.get(), params, snapshot_path);
+}
+
+bool SnapshotManager::UnmapUpdateSnapshot(const std::string& target_partition_name) {
+    auto lock = LockShared();
+    if (!lock) return false;
+    return UnmapPartitionWithSnapshot(lock.get(), target_partition_name);
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 429fd8e..876b8f8 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -33,7 +33,9 @@
 #include <liblp/builder.h>
 #include <liblp/mock_property_fetcher.h>
 
+#include "digital_storage.h"
 #include "test_helpers.h"
+#include "utility.h"
 
 namespace android {
 namespace snapshot {
@@ -45,10 +47,12 @@
 using android::fs_mgr::BlockDeviceInfo;
 using android::fs_mgr::CreateLogicalPartitionParams;
 using android::fs_mgr::DestroyLogicalPartition;
+using android::fs_mgr::GetPartitionGroupName;
 using android::fs_mgr::GetPartitionName;
 using android::fs_mgr::MetadataBuilder;
 using namespace ::testing;
 using namespace android::fs_mgr::testing;
+using namespace android::digital_storage;
 using namespace std::chrono_literals;
 using namespace std::string_literals;
 
@@ -58,7 +62,7 @@
 TestDeviceInfo* test_device = nullptr;
 std::string fake_super;
 
-static constexpr uint64_t kSuperSize = 16 * 1024 * 1024;
+static constexpr uint64_t kSuperSize = (16_MiB).bytes();
 
 class SnapshotTest : public ::testing::Test {
   public:
@@ -105,7 +109,7 @@
         std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a",
                                               "test_partition_b"};
         for (const auto& snapshot : snapshots) {
-            DeleteSnapshotDevice(snapshot);
+            ASSERT_TRUE(DeleteSnapshotDevice(snapshot));
             DeleteBackingImage(image_manager_, snapshot + "-cow-img");
 
             auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
@@ -211,15 +215,52 @@
         return true;
     }
 
-    void DeleteSnapshotDevice(const std::string& snapshot) {
-        DeleteDevice(snapshot);
-        DeleteDevice(snapshot + "-inner");
-        ASSERT_TRUE(image_manager_->UnmapImageIfExists(snapshot + "-cow-img"));
-    }
-    void DeleteDevice(const std::string& device) {
-        if (dm_.GetState(device) != DmDeviceState::INVALID) {
-            ASSERT_TRUE(dm_.DeleteDevice(device));
+    AssertionResult DeleteSnapshotDevice(const std::string& snapshot) {
+        AssertionResult res = AssertionSuccess();
+        if (!(res = DeleteDevice(snapshot))) return res;
+        if (!(res = DeleteDevice(snapshot + "-inner"))) return res;
+        if (!(res = DeleteDevice(snapshot + "-cow"))) return res;
+        if (!image_manager_->UnmapImageIfExists(snapshot + "-cow-img")) {
+            return AssertionFailure() << "Cannot unmap image " << snapshot << "-cow-img";
         }
+        if (!(res = DeleteDevice(snapshot + "-base"))) return res;
+        return AssertionSuccess();
+    }
+
+    AssertionResult DeleteDevice(const std::string& device) {
+        if (!dm_.DeleteDeviceIfExists(device)) {
+            return AssertionFailure() << "Can't delete " << device;
+        }
+        return AssertionSuccess();
+    }
+
+    AssertionResult CreateCowImage(const std::string& name) {
+        if (!sm->CreateCowImage(lock_.get(), name)) {
+            return AssertionFailure() << "Cannot create COW image " << name;
+        }
+        std::string cow_device;
+        auto map_res = MapCowImage(name, 10s, &cow_device);
+        if (!map_res) {
+            return map_res;
+        }
+        if (!InitializeCow(cow_device)) {
+            return AssertionFailure() << "Cannot zero fill " << cow_device;
+        }
+        if (!sm->UnmapCowImage(name)) {
+            return AssertionFailure() << "Cannot unmap " << name << " after zero filling it";
+        }
+        return AssertionSuccess();
+    }
+
+    AssertionResult MapCowImage(const std::string& name,
+                                const std::chrono::milliseconds& timeout_ms, std::string* path) {
+        if (!sm->MapCowImage(name, timeout_ms)) {
+            return AssertionFailure() << "Cannot map cow image " << name;
+        }
+        if (!dm_.GetDmDevicePathByName(name + "-cow-img"s, path)) {
+            return AssertionFailure() << "No path for " << name << "-cow-img";
+        }
+        return AssertionSuccess();
     }
 
     DeviceMapper& dm_;
@@ -236,7 +277,7 @@
                                    {.device_size = kDeviceSize,
                                     .snapshot_size = kDeviceSize,
                                     .cow_file_size = kDeviceSize}));
-    ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
+    ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::vector<std::string> snapshots;
     ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots));
@@ -265,13 +306,13 @@
                                    {.device_size = kDeviceSize,
                                     .snapshot_size = kDeviceSize,
                                     .cow_file_size = kDeviceSize}));
-    ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
+    ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::string base_device;
     ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
 
     std::string cow_device;
-    ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
+    ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
 
     std::string snap_device;
     ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
@@ -288,13 +329,13 @@
                                    {.device_size = kDeviceSize,
                                     .snapshot_size = kSnapshotSize,
                                     .cow_file_size = kSnapshotSize}));
-    ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
+    ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::string base_device;
     ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
 
     std::string cow_device;
-    ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
+    ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
 
     std::string snap_device;
     ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
@@ -344,8 +385,8 @@
                                    {.device_size = kDeviceSize,
                                     .snapshot_size = kDeviceSize,
                                     .cow_file_size = kDeviceSize}));
-    ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
-    ASSERT_TRUE(sm->MapCowImage("test_partition_b", 10s, &cow_device));
+    ASSERT_TRUE(CreateCowImage("test_partition_b"));
+    ASSERT_TRUE(MapCowImage("test_partition_b", 10s, &cow_device));
     ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, cow_device, 10s,
                                 &snap_device));
 
@@ -403,11 +444,11 @@
                                    {.device_size = kDeviceSize,
                                     .snapshot_size = kDeviceSize,
                                     .cow_file_size = kDeviceSize}));
-    ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
+    ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::string base_device, cow_device, snap_device;
     ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
-    ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
+    ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
     ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
                                 &snap_device));
 
@@ -433,11 +474,11 @@
     // Wait 1s, otherwise DeleteSnapshotDevice may fail with EBUSY.
     sleep(1);
     // Forcefully delete the snapshot device, so it looks like we just rebooted.
-    DeleteSnapshotDevice("test-snapshot");
+    ASSERT_TRUE(DeleteSnapshotDevice("test-snapshot"));
 
     // Map snapshot should fail now, because we're in a merge-complete state.
     ASSERT_TRUE(AcquireLock());
-    ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
+    ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
     ASSERT_FALSE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
                                  &snap_device));
 
@@ -462,7 +503,7 @@
                                    {.device_size = kDeviceSize,
                                     .snapshot_size = kDeviceSize,
                                     .cow_file_size = kDeviceSize}));
-    ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
+    ASSERT_TRUE(CreateCowImage("test_partition_b"));
 
     // Simulate a reboot into the new slot.
     lock_ = nullptr;
@@ -504,7 +545,7 @@
                                    {.device_size = kDeviceSize,
                                     .snapshot_size = kDeviceSize,
                                     .cow_file_size = kDeviceSize}));
-    ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
+    ASSERT_TRUE(CreateCowImage("test_partition_b"));
 
     // Simulate a reboot into the new slot.
     lock_ = nullptr;
@@ -552,7 +593,7 @@
                                    {.device_size = kDeviceSize,
                                     .snapshot_size = kDeviceSize,
                                     .cow_file_size = kDeviceSize}));
-    ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
+    ASSERT_TRUE(CreateCowImage("test_partition_b"));
 
     // Simulate a reboot into the new slot.
     lock_ = nullptr;
@@ -570,7 +611,7 @@
 
     // Now, reflash super. Note that we haven't called ProcessUpdateState, so the
     // status is still Merging.
-    DeleteSnapshotDevice("test_partition_b");
+    ASSERT_TRUE(DeleteSnapshotDevice("test_partition_b"));
     ASSERT_TRUE(init->image_manager()->UnmapImageIfExists("test_partition_b-cow-img"));
     FormatFakeSuper();
     ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize));
@@ -583,6 +624,419 @@
     ASSERT_EQ(sm->GetUpdateState(), UpdateState::None);
 }
 
+class SnapshotUpdateTest : public SnapshotTest {
+  public:
+    void SetUp() override {
+        SnapshotTest::SetUp();
+        Cleanup();
+
+        // Cleanup() changes slot suffix, so initialize it again.
+        test_device->set_slot_suffix("_a");
+
+        ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
+                .WillByDefault(Return(true));
+
+        opener = std::make_unique<TestPartitionOpener>(fake_super);
+
+        // Initialize source partition metadata. sys_b is similar to system_other.
+        // Not using full name "system", "vendor", "product" because these names collide with the
+        // mapped partitions on the running device.
+        src = MetadataBuilder::New(*opener, "super", 0);
+        ASSERT_NE(nullptr, src);
+        auto partition = src->AddPartition("sys_a", 0);
+        ASSERT_NE(nullptr, partition);
+        ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
+        partition = src->AddPartition("vnd_a", 0);
+        ASSERT_NE(nullptr, partition);
+        ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
+        partition = src->AddPartition("prd_a", 0);
+        ASSERT_NE(nullptr, partition);
+        ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
+        partition = src->AddPartition("sys_b", 0);
+        ASSERT_NE(nullptr, partition);
+        ASSERT_TRUE(src->ResizePartition(partition, 1_MiB));
+        auto metadata = src->Export();
+        ASSERT_NE(nullptr, metadata);
+        ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 0));
+
+        // Map source partitions. Additionally, map sys_b to simulate system_other after flashing.
+        std::string path;
+        for (const auto& name : {"sys_a", "vnd_a", "prd_a", "sys_b"}) {
+            ASSERT_TRUE(CreateLogicalPartition(
+                    CreateLogicalPartitionParams{
+                            .block_device = fake_super,
+                            .metadata_slot = 0,
+                            .partition_name = name,
+                            .timeout_ms = 1s,
+                            .partition_opener = opener.get(),
+                    },
+                    &path));
+            ASSERT_TRUE(WriteRandomData(path));
+            auto hash = GetHash(path);
+            ASSERT_TRUE(hash.has_value());
+            hashes_[name] = *hash;
+        }
+    }
+    void TearDown() override {
+        Cleanup();
+        SnapshotTest::TearDown();
+    }
+    void Cleanup() {
+        if (!image_manager_) {
+            InitializeState();
+        }
+        for (const auto& suffix : {"_a", "_b"}) {
+            test_device->set_slot_suffix(suffix);
+            EXPECT_TRUE(sm->CancelUpdate()) << suffix;
+        }
+        EXPECT_TRUE(UnmapAll());
+    }
+
+    static AssertionResult ResizePartitions(
+            MetadataBuilder* builder,
+            const std::vector<std::pair<std::string, uint64_t>>& partition_sizes) {
+        for (auto&& [name, size] : partition_sizes) {
+            auto partition = builder->FindPartition(name);
+            if (!partition) {
+                return AssertionFailure() << "Cannot find partition in metadata " << name;
+            }
+            if (!builder->ResizePartition(partition, size)) {
+                return AssertionFailure()
+                       << "Cannot resize partition " << name << " to " << size << " bytes";
+            }
+        }
+        return AssertionSuccess();
+    }
+
+    AssertionResult IsPartitionUnchanged(const std::string& name) {
+        std::string path;
+        if (!dm_.GetDmDevicePathByName(name, &path)) {
+            return AssertionFailure() << "Path of " << name << " cannot be determined";
+        }
+        auto hash = GetHash(path);
+        if (!hash.has_value()) {
+            return AssertionFailure() << "Cannot read partition " << name << ": " << path;
+        }
+        if (hashes_[name] != *hash) {
+            return AssertionFailure() << "Content of " << name << " has changed after the merge";
+        }
+        return AssertionSuccess();
+    }
+
+    std::optional<uint64_t> GetSnapshotSize(const std::string& name) {
+        if (!AcquireLock()) {
+            return std::nullopt;
+        }
+        auto local_lock = std::move(lock_);
+
+        SnapshotManager::SnapshotStatus status;
+        if (!sm->ReadSnapshotStatus(local_lock.get(), name, &status)) {
+            return std::nullopt;
+        }
+        return status.snapshot_size;
+    }
+
+    AssertionResult UnmapAll() {
+        for (const auto& name : {"sys", "vnd", "prd"}) {
+            if (!dm_.DeleteDeviceIfExists(name + "_a"s)) {
+                return AssertionFailure() << "Cannot unmap " << name << "_a";
+            }
+            if (!DeleteSnapshotDevice(name + "_b"s)) {
+                return AssertionFailure() << "Cannot delete snapshot " << name << "_b";
+            }
+        }
+        return AssertionSuccess();
+    }
+
+    std::unique_ptr<TestPartitionOpener> opener;
+    std::unique_ptr<MetadataBuilder> src;
+    std::map<std::string, std::string> hashes_;
+};
+
+// Test full update flow executed by update_engine. Some partitions uses super empty space,
+// some uses images, and some uses both.
+// Also test UnmapUpdateSnapshot unmaps everything.
+// Also test first stage mount and merge after this.
+TEST_F(SnapshotUpdateTest, FullUpdateFlow) {
+    // OTA client calls CancelUpdate then BeginUpdate before doing anything.
+    ASSERT_TRUE(sm->CancelUpdate());
+    ASSERT_TRUE(sm->BeginUpdate());
+
+    // OTA client blindly unmaps all partitions that are possibly mapped.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
+    }
+
+    // OTA client adjusts the partition sizes before giving it to CreateUpdateSnapshots.
+    auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+    ASSERT_NE(nullptr, tgt);
+    // clang-format off
+    ASSERT_TRUE(ResizePartitions(tgt.get(), {
+            {"sys_b", 4_MiB}, // grows
+            {"vnd_b", 4_MiB}, // grows
+            {"prd_b", 4_MiB}, // grows
+    }));
+    // clang-format on
+
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+    // Test that partitions prioritize using space in super.
+    ASSERT_NE(nullptr, tgt->FindPartition("sys_b-cow"));
+    ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow"));
+    ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow"));
+
+    // The metadata slot 1 is now updated.
+    auto metadata = tgt->Export();
+    ASSERT_NE(nullptr, metadata);
+    ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
+
+    // Write some data to target partitions.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        std::string path;
+        ASSERT_TRUE(sm->MapUpdateSnapshot(
+                CreateLogicalPartitionParams{
+                        .block_device = fake_super,
+                        .metadata_slot = 1,
+                        .partition_name = name,
+                        .timeout_ms = 10s,
+                        .partition_opener = opener.get(),
+                },
+                &path))
+                << name;
+        ASSERT_TRUE(WriteRandomData(path));
+        auto hash = GetHash(path);
+        ASSERT_TRUE(hash.has_value());
+        hashes_[name] = *hash;
+    }
+
+    // Assert that source partitions aren't affected.
+    for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // After reboot, init does first stage mount.
+    auto rebooted = new TestDeviceInfo(fake_super);
+    rebooted->set_slot_suffix("_b");
+    auto init = SnapshotManager::NewForFirstStageMount(rebooted);
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+
+    // Check that the target partitions have the same content.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    // Initiate the merge and wait for it to be completed.
+    ASSERT_TRUE(init->InitiateMerge());
+    ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+
+    // Check that the target partitions have the same content after the merge.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name))
+                << "Content of " << name << " changes after the merge";
+    }
+}
+
+// Test that if new system partitions uses empty space in super, that region is not snapshotted.
+TEST_F(SnapshotUpdateTest, DirectWriteEmptySpace) {
+    auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+    ASSERT_NE(nullptr, tgt);
+    // clang-format off
+    ASSERT_TRUE(ResizePartitions(tgt.get(), {
+            {"sys_b", 4_MiB}, // grows
+            // vnd_b and prd_b are unchanged
+    }));
+    // clang-format on
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+    ASSERT_EQ(3_MiB, GetSnapshotSize("sys_b").value_or(0));
+}
+
+// Test that if new system partitions uses space of old vendor partition, that region is
+// snapshotted.
+TEST_F(SnapshotUpdateTest, SnapshotOldPartitions) {
+    auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+    ASSERT_NE(nullptr, tgt);
+    // clang-format off
+    ASSERT_TRUE(ResizePartitions(tgt.get(), {
+            {"vnd_b", 2_MiB}, // shrinks
+            {"sys_b", 4_MiB}, // grows
+            // prd_b is unchanged
+    }));
+    // clang-format on
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+    ASSERT_EQ(4_MiB, GetSnapshotSize("sys_b").value_or(0));
+}
+
+// Test that even if there seem to be empty space in target metadata, COW partition won't take
+// it because they are used by old partitions.
+TEST_F(SnapshotUpdateTest, CowPartitionDoNotTakeOldPartitions) {
+    auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+    ASSERT_NE(nullptr, tgt);
+    // clang-format off
+    ASSERT_TRUE(ResizePartitions(tgt.get(), {
+            {"sys_b", 2_MiB}, // shrinks
+            // vnd_b and prd_b are unchanged
+    }));
+    // clang-format on
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+    auto metadata = tgt->Export();
+    ASSERT_NE(nullptr, metadata);
+    std::vector<std::string> written;
+    // Write random data to all COW partitions in super
+    for (auto p : metadata->partitions) {
+        if (GetPartitionGroupName(metadata->groups[p.group_index]) != kCowGroupName) {
+            continue;
+        }
+        std::string path;
+        ASSERT_TRUE(CreateLogicalPartition(
+                CreateLogicalPartitionParams{
+                        .block_device = fake_super,
+                        .metadata = metadata.get(),
+                        .partition = &p,
+                        .timeout_ms = 1s,
+                        .partition_opener = opener.get(),
+                },
+                &path));
+        ASSERT_TRUE(WriteRandomData(path));
+        written.push_back(GetPartitionName(p));
+    }
+    ASSERT_FALSE(written.empty())
+            << "No COW partitions are created even if there are empty space in super partition";
+
+    // Make sure source partitions aren't affected.
+    for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+}
+
+// Test that it crashes after creating snapshot status file but before creating COW image, then
+// calling CreateUpdateSnapshots again works.
+TEST_F(SnapshotUpdateTest, SnapshotStatusFileWithoutCow) {
+    // Write some trash snapshot files to simulate leftovers from previous runs.
+    {
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+        ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), "sys_b",
+                                            SnapshotManager::SnapshotStatus{}));
+        ASSERT_TRUE(image_manager_->CreateBackingImage("sys_b-cow-img", 1_MiB,
+                                                       IImageManager::CREATE_IMAGE_DEFAULT));
+    }
+
+    // Redo the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
+    auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+    ASSERT_NE(nullptr, tgt);
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+    // The metadata slot 1 is now updated.
+    auto metadata = tgt->Export();
+    ASSERT_NE(nullptr, metadata);
+    ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
+
+    // Check that target partitions can be mapped.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        std::string path;
+        EXPECT_TRUE(sm->MapUpdateSnapshot(
+                CreateLogicalPartitionParams{
+                        .block_device = fake_super,
+                        .metadata_slot = 1,
+                        .partition_name = name,
+                        .timeout_ms = 10s,
+                        .partition_opener = opener.get(),
+                },
+                &path))
+                << name;
+    }
+}
+
+// Test that the old partitions are not modified.
+TEST_F(SnapshotUpdateTest, TestRollback) {
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
+    auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
+    ASSERT_NE(nullptr, tgt);
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
+
+    // The metadata slot 1 is now updated.
+    auto metadata = tgt->Export();
+    ASSERT_NE(nullptr, metadata);
+    ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
+
+    // Write some data to target partitions.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        std::string path;
+        ASSERT_TRUE(sm->MapUpdateSnapshot(
+                CreateLogicalPartitionParams{
+                        .block_device = fake_super,
+                        .metadata_slot = 1,
+                        .partition_name = name,
+                        .timeout_ms = 10s,
+                        .partition_opener = opener.get(),
+                },
+                &path))
+                << name;
+        ASSERT_TRUE(WriteRandomData(path));
+        auto hash = GetHash(path);
+        ASSERT_TRUE(hash.has_value());
+        hashes_[name] = *hash;
+    }
+
+    // Assert that source partitions aren't affected.
+    for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // After reboot, init does first stage mount.
+    auto rebooted = new TestDeviceInfo(fake_super);
+    rebooted->set_slot_suffix("_b");
+    auto init = SnapshotManager::NewForFirstStageMount(rebooted);
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+
+    // Check that the target partitions have the same content.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    // Simulate shutting down the device again.
+    ASSERT_TRUE(UnmapAll());
+    rebooted = new TestDeviceInfo(fake_super);
+    rebooted->set_slot_suffix("_a");
+    init = SnapshotManager::NewForFirstStageMount(rebooted);
+    ASSERT_NE(init, nullptr);
+    ASSERT_FALSE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
+
+    // Assert that the source partitions aren't affected.
+    for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+}
+
+// Test that if an update is applied but not booted into, it can be canceled.
+TEST_F(SnapshotUpdateTest, CancelAfterApply) {
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->CancelUpdate());
+}
+
 }  // namespace snapshot
 }  // namespace android
 
@@ -624,6 +1078,7 @@
     }
 
     // Clean up previous run.
+    SnapshotUpdateTest().Cleanup();
     SnapshotTest().Cleanup();
 
     // Use a separate image manager for our fake super partition.
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index f67dd21..e12ec77 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -14,11 +14,18 @@
 
 #include "test_helpers.h"
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
+#include <openssl/sha.h>
 
 namespace android {
 namespace snapshot {
 
+using android::base::ReadFully;
+using android::base::unique_fd;
+using android::base::WriteFully;
 using android::fiemap::IImageManager;
 
 void DeleteBackingImage(IImageManager* manager, const std::string& name) {
@@ -53,5 +60,55 @@
     return PartitionOpener::GetDeviceString(partition_name);
 }
 
+bool WriteRandomData(const std::string& path) {
+    unique_fd rand(open("/dev/urandom", O_RDONLY));
+    unique_fd fd(open(path.c_str(), O_WRONLY));
+
+    char buf[4096];
+    while (true) {
+        ssize_t n = TEMP_FAILURE_RETRY(read(rand.get(), buf, sizeof(buf)));
+        if (n <= 0) return false;
+        if (!WriteFully(fd.get(), buf, n)) {
+            if (errno == ENOSPC) {
+                return true;
+            }
+            PLOG(ERROR) << "Cannot write " << path;
+            return false;
+        }
+    }
+}
+
+std::string ToHexString(const uint8_t* buf, size_t len) {
+    char lookup[] = "0123456789abcdef";
+    std::string out(len * 2 + 1, '\0');
+    char* outp = out.data();
+    for (; len > 0; len--, buf++) {
+        *outp++ = (char)lookup[*buf >> 4];
+        *outp++ = (char)lookup[*buf & 0xf];
+    }
+    return out;
+}
+
+std::optional<std::string> GetHash(const std::string& path) {
+    unique_fd fd(open(path.c_str(), O_RDONLY));
+    char buf[4096];
+    SHA256_CTX ctx;
+    SHA256_Init(&ctx);
+    while (true) {
+        ssize_t n = TEMP_FAILURE_RETRY(read(fd.get(), buf, sizeof(buf)));
+        if (n < 0) {
+            PLOG(ERROR) << "Cannot read " << path;
+            return std::nullopt;
+        }
+        if (n == 0) {
+            break;
+        }
+        SHA256_Update(&ctx, buf, n);
+    }
+    uint8_t out[32];
+    SHA256_Final(out, &ctx);
+    return ToHexString(out, sizeof(out));
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/test_helpers.h
index 9f582d9..d917e35 100644
--- a/fs_mgr/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/test_helpers.h
@@ -14,6 +14,7 @@
 
 #pragma once
 
+#include <optional>
 #include <string>
 
 #include <libfiemap/image_manager.h>
@@ -51,6 +52,7 @@
     const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const override {
         return *opener_.get();
     }
+    bool IsOverlayfsSetup() const override { return false; }
 
     void set_slot_suffix(const std::string& suffix) { slot_suffix_ = suffix; }
     void set_fake_super(const std::string& path) {
@@ -65,5 +67,10 @@
 // Helper for error-spam-free cleanup.
 void DeleteBackingImage(android::fiemap::IImageManager* manager, const std::string& name);
 
+// Write some random data to the given device. Will write until reaching end of the device.
+bool WriteRandomData(const std::string& device);
+
+std::optional<std::string> GetHash(const std::string& path);
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index 164b472..66629e8 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -14,9 +14,13 @@
 
 #include "utility.h"
 
+#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/strings.h>
 
+using android::fs_mgr::MetadataBuilder;
+using android::fs_mgr::Partition;
+
 namespace android {
 namespace snapshot {
 
@@ -52,5 +56,58 @@
     }
 }
 
+std::vector<Partition*> ListPartitionsWithSuffix(MetadataBuilder* builder,
+                                                 const std::string& suffix) {
+    std::vector<Partition*> ret;
+    for (const auto& group : builder->ListGroups()) {
+        for (auto* partition : builder->ListPartitionsInGroup(group)) {
+            if (!base::EndsWith(partition->name(), suffix)) {
+                continue;
+            }
+            ret.push_back(partition);
+        }
+    }
+    return ret;
+}
+
+AutoDeleteSnapshot::~AutoDeleteSnapshot() {
+    if (!name_.empty() && !manager_->DeleteSnapshot(lock_, name_)) {
+        LOG(ERROR) << "Failed to auto delete snapshot " << name_;
+    }
+}
+
+bool InitializeCow(const std::string& device) {
+    // When the kernel creates a persistent dm-snapshot, it requires a CoW file
+    // to store the modifications. The kernel interface does not specify how
+    // the CoW is used, and there is no standard associated.
+    // By looking at the current implementation, the CoW file is treated as:
+    // - a _NEW_ snapshot if its first 32 bits are zero, so the newly created
+    // dm-snapshot device will look like a perfect copy of the origin device;
+    // - an _EXISTING_ snapshot if the first 32 bits are equal to a
+    // kernel-specified magic number and the CoW file metadata is set as valid,
+    // so it can be used to resume the last state of a snapshot device;
+    // - an _INVALID_ snapshot otherwise.
+    // To avoid zero-filling the whole CoW file when a new dm-snapshot is
+    // created, here we zero-fill only the first 32 bits. This is a temporary
+    // workaround that will be discussed again when the kernel API gets
+    // consolidated.
+    // TODO(b/139202197): Remove this hack once the kernel API is consolidated.
+    constexpr ssize_t kDmSnapZeroFillSize = 4;  // 32-bit
+
+    char zeros[kDmSnapZeroFillSize] = {0};
+    android::base::unique_fd fd(open(device.c_str(), O_WRONLY | O_BINARY));
+    if (fd < 0) {
+        PLOG(ERROR) << "Can't open COW device: " << device;
+        return false;
+    }
+
+    LOG(INFO) << "Zero-filling COW device: " << device;
+    if (!android::base::WriteFully(fd, zeros, kDmSnapZeroFillSize)) {
+        PLOG(ERROR) << "Can't zero-fill COW device for " << device;
+        return false;
+    }
+    return true;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index cbab472..a700c46 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -14,11 +14,14 @@
 
 #pragma once
 
+#include <functional>
 #include <string>
 
 #include <android-base/macros.h>
 #include <libdm/dm.h>
 #include <libfiemap/image_manager.h>
+#include <liblp/builder.h>
+#include <libsnapshot/snapshot.h>
 
 namespace android {
 namespace snapshot {
@@ -81,5 +84,27 @@
     android::fiemap::IImageManager* images_ = nullptr;
 };
 
+// Automatically deletes a snapshot. |name| should be the name of the partition, e.g. "system_a".
+// Client is responsible for maintaining the lifetime of |manager| and |lock|.
+struct AutoDeleteSnapshot : AutoDevice {
+    AutoDeleteSnapshot(SnapshotManager* manager, SnapshotManager::LockedFile* lock,
+                       const std::string& name)
+        : AutoDevice(name), manager_(manager), lock_(lock) {}
+    AutoDeleteSnapshot(AutoDeleteSnapshot&& other);
+    ~AutoDeleteSnapshot();
+
+  private:
+    DISALLOW_COPY_AND_ASSIGN(AutoDeleteSnapshot);
+    SnapshotManager* manager_ = nullptr;
+    SnapshotManager::LockedFile* lock_ = nullptr;
+};
+
+// Return a list of partitions in |builder| with the name ending in |suffix|.
+std::vector<android::fs_mgr::Partition*> ListPartitionsWithSuffix(
+        android::fs_mgr::MetadataBuilder* builder, const std::string& suffix);
+
+// Initialize a device before using it as the COW device for a dm-snapshot device.
+bool InitializeCow(const std::string& device);
+
 }  // namespace snapshot
 }  // namespace android