Merge "libprocessgroup: Support write to file feature"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 89bd66a..52cff94 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -22,12 +22,6 @@
       "name": "CtsInitTestCases"
     },
     {
-      "name": "CtsLiblogTestCases"
-    },
-    {
-      "name": "CtsLogdTestCases"
-    },
-    {
       "name": "debuggerd_test"
     },
     {
diff --git a/base b/base
deleted file mode 120000
index 622c552..0000000
--- a/base
+++ /dev/null
@@ -1 +0,0 @@
-../libbase
\ No newline at end of file
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 99cabdd..6391acc 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -17,6 +17,7 @@
     name: "libdebuggerd_common_headers",
     export_include_dirs: ["common/include"],
     recovery_available: true,
+    vendor_ramdisk_available: true,
 }
 
 cc_library_shared {
@@ -47,6 +48,7 @@
     name: "libtombstoned_client_static",
     defaults: ["debuggerd_defaults"],
     recovery_available: true,
+    vendor_ramdisk_available: true,
     srcs: [
         "tombstoned/tombstoned_client.cpp",
         "util.cpp",
@@ -69,6 +71,7 @@
     name: "libdebuggerd_handler_core",
     defaults: ["debuggerd_defaults"],
     recovery_available: true,
+    vendor_ramdisk_available: true,
     srcs: ["handler/debuggerd_handler.cpp"],
 
     header_libs: [
@@ -113,6 +116,7 @@
     ],
     defaults: ["debuggerd_defaults"],
     recovery_available: true,
+    vendor_ramdisk_available: true,
     srcs: [
         "handler/debuggerd_fallback.cpp",
     ],
@@ -164,6 +168,7 @@
     name: "libdebuggerd",
     defaults: ["debuggerd_defaults"],
     recovery_available: true,
+    vendor_ramdisk_available: true,
 
     srcs: [
         "libdebuggerd/backtrace.cpp",
@@ -209,6 +214,11 @@
                 "libdexfile_support",
             ],
         },
+        vendor_ramdisk: {
+            exclude_static_libs: [
+                "libdexfile_support",
+            ],
+        },
     },
 
     product_variables: {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 5ed9e57..5565e8b 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -312,7 +312,7 @@
 
   if (mte_supported()) {
     // Test that the default TAGGED_ADDR_CTRL value is set.
-    ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff3)");
+    ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff5)");
   }
 }
 
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 1bf4c9c..333ca50 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -67,7 +67,7 @@
 
         if ((partition + device->GetCurrentSlot()) == partition_name) {
             mount_metadata.emplace();
-            fs_mgr_overlayfs_teardown(entry.mount_point.c_str());
+            android::fs_mgr::TeardownAllOverlayForMountPoint(entry.mount_point);
         }
     }
 }
@@ -194,7 +194,7 @@
         if (!FlashPartitionTable(super_name, *new_metadata.get())) {
             return device->WriteFail("Unable to flash new partition table");
         }
-        fs_mgr_overlayfs_teardown();
+        android::fs_mgr::TeardownAllOverlayForMountPoint();
         sync();
         return device->WriteOkay("Successfully flashed partition table");
     }
@@ -234,7 +234,7 @@
     if (!UpdateAllPartitionMetadata(device, super_name, *new_metadata.get())) {
         return device->WriteFail("Unable to write new partition table");
     }
-    fs_mgr_overlayfs_teardown();
+    android::fs_mgr::TeardownAllOverlayForMountPoint();
     sync();
     return device->WriteOkay("Successfully updated partition table");
 }
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index fe72393..6294b3f 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -1534,6 +1534,8 @@
                            attempted_entry.mount_point},
                           nullptr)) {
                 ++error_count;
+            } else if (current_entry.mount_point == "/data") {
+                userdata_mounted = true;
             }
             encryptable = FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED;
             continue;
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 616a06f..793a725 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -591,7 +591,7 @@
     FstabEntry userdata;
     if (FstabEntry* entry = GetEntryForMountPoint(fstab, "/data")) {
         userdata = *entry;
-        userdata.blk_device = "userdata_gsi";
+        userdata.blk_device = android::gsi::kDsuUserdata;
         userdata.fs_mgr_flags.logical = true;
         userdata.fs_mgr_flags.formattable = true;
         if (!userdata.metadata_key_dir.empty()) {
@@ -611,7 +611,11 @@
             continue;
         }
         // userdata has been handled
-        if (StartsWith(partition, "user")) {
+        if (partition == android::gsi::kDsuUserdata) {
+            continue;
+        }
+        // scratch is handled by fs_mgr_overlayfs
+        if (partition == android::gsi::kDsuScratch) {
             continue;
         }
         // dsu_partition_name = corresponding_partition_name + kDsuPostfix
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index a7704de..899978f 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -73,6 +73,25 @@
     return ret;
 }
 
+bool fs_mgr_in_recovery() {
+    // Check the existence of recovery binary instead of using the compile time
+    // macro, because first-stage-init is compiled with __ANDROID_RECOVERY__
+    // defined, albeit not in recovery. More details: system/core/init/README.md
+    return fs_mgr_access("/system/bin/recovery");
+}
+
+bool fs_mgr_is_dsu_running() {
+    // Since android::gsi::CanBootIntoGsi() or android::gsi::MarkSystemAsGsi() is
+    // never called in recovery, the return value of android::gsi::IsGsiRunning()
+    // is not well-defined. In this case, just return false as being in recovery
+    // implies not running a DSU system.
+    if (fs_mgr_in_recovery()) return false;
+    auto saved_errno = errno;
+    auto ret = android::gsi::IsGsiRunning();
+    errno = saved_errno;
+    return ret;
+}
+
 // determine if a filesystem is available
 bool fs_mgr_overlayfs_filesystem_available(const std::string& filesystem) {
     std::string filesystems;
@@ -113,8 +132,11 @@
 namespace android {
 namespace fs_mgr {
 
-void MapScratchPartitionIfNeeded(Fstab*,
-                                 const std::function<bool(const std::set<std::string>&)>&) {}
+void MapScratchPartitionIfNeeded(Fstab*, const std::function<bool(const std::set<std::string>&)>&) {
+}
+
+void TeardownAllOverlayForMountPoint(const std::string&) {}
+
 }  // namespace fs_mgr
 }  // namespace android
 
@@ -171,6 +193,10 @@
 
 // Note: this is meant only for recovery/first-stage init.
 bool ScratchIsOnData() {
+    // The scratch partition of DSU is managed by gsid.
+    if (fs_mgr_is_dsu_running()) {
+        return false;
+    }
     return fs_mgr_access(kScratchImageMetadata);
 }
 
@@ -464,6 +490,12 @@
     // umount and delete kScratchMountPoint storage if we have logical partitions
     if (overlay != kScratchMountPoint) return true;
 
+    // Validation check.
+    if (fs_mgr_is_dsu_running()) {
+        LERROR << "Destroying DSU scratch is not allowed.";
+        return false;
+    }
+
     auto save_errno = errno;
     if (fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
         fs_mgr_overlayfs_umount_scratch();
@@ -512,10 +544,13 @@
 }
 
 bool fs_mgr_overlayfs_teardown_one(const std::string& overlay, const std::string& mount_point,
-                                   bool* change) {
+                                   bool* change, bool* should_destroy_scratch = nullptr) {
     const auto top = overlay + kOverlayTopDir;
 
-    if (!fs_mgr_access(top)) return fs_mgr_overlayfs_teardown_scratch(overlay, change);
+    if (!fs_mgr_access(top)) {
+        if (should_destroy_scratch) *should_destroy_scratch = true;
+        return true;
+    }
 
     auto cleanup_all = mount_point.empty();
     const auto partition_name = android::base::Basename(mount_point);
@@ -571,7 +606,7 @@
             PERROR << "rmdir " << top;
         }
     }
-    if (cleanup_all) ret &= fs_mgr_overlayfs_teardown_scratch(overlay, change);
+    if (should_destroy_scratch) *should_destroy_scratch = cleanup_all;
     return ret;
 }
 
@@ -881,12 +916,29 @@
     return "";
 }
 
+// Note: The scratch partition of DSU is managed by gsid, and should be initialized during
+// first-stage-mount. Just check if the DM device for DSU scratch partition is created or not.
+static std::string GetDsuScratchDevice() {
+    auto& dm = DeviceMapper::Instance();
+    std::string device;
+    if (dm.GetState(android::gsi::kDsuScratch) != DmDeviceState::INVALID &&
+        dm.GetDmDevicePathByName(android::gsi::kDsuScratch, &device)) {
+        return device;
+    }
+    return "";
+}
+
 // This returns the scratch device that was detected during early boot (first-
 // stage init). If the device was created later, for example during setup for
 // the adb remount command, it can return an empty string since it does not
 // query ImageManager. (Note that ImageManager in first-stage init will always
 // use device-mapper, since /data is not available to use loop devices.)
 static std::string GetBootScratchDevice() {
+    // Note: fs_mgr_is_dsu_running() always returns false in recovery or fastbootd.
+    if (fs_mgr_is_dsu_running()) {
+        return GetDsuScratchDevice();
+    }
+
     auto& dm = DeviceMapper::Instance();
 
     // If there is a scratch partition allocated in /data or on super, we
@@ -1108,6 +1160,14 @@
 
 bool fs_mgr_overlayfs_create_scratch(const Fstab& fstab, std::string* scratch_device,
                                      bool* partition_exists, bool* change) {
+    // Use the DSU scratch device managed by gsid if within a DSU system.
+    if (fs_mgr_is_dsu_running()) {
+        *scratch_device = GetDsuScratchDevice();
+        *partition_exists = !scratch_device->empty();
+        *change = false;
+        return *partition_exists;
+    }
+
     // Try a physical partition first.
     *scratch_device = GetPhysicalScratchDevice();
     if (!scratch_device->empty() && fs_mgr_rw_access(*scratch_device)) {
@@ -1166,12 +1226,8 @@
 bool fs_mgr_overlayfs_invalid() {
     if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kNotSupported) return true;
 
-    // in recovery, fastbootd, or gsi mode, not allowed!
-    if (fs_mgr_access("/system/bin/recovery")) return true;
-    auto save_errno = errno;
-    auto ret = android::gsi::IsGsiRunning();
-    errno = save_errno;
-    return ret;
+    // in recovery or fastbootd, not allowed!
+    return fs_mgr_in_recovery();
 }
 
 }  // namespace
@@ -1314,6 +1370,8 @@
     return ret;
 }
 
+// Note: This function never returns the DSU scratch device in recovery or fastbootd,
+// because the DSU scratch is created in the first-stage-mount, which is not run in recovery.
 static bool EnsureScratchMapped(std::string* device, bool* mapped) {
     *mapped = false;
     *device = GetBootScratchDevice();
@@ -1321,6 +1379,11 @@
         return true;
     }
 
+    if (!fs_mgr_in_recovery()) {
+        errno = EINVAL;
+        return false;
+    }
+
     auto partition_name = android::base::Basename(kScratchMountPoint);
 
     // Check for scratch on /data first, before looking for a modified super
@@ -1362,10 +1425,27 @@
     return true;
 }
 
-static void UnmapScratchDevice() {
-    // This should only be reachable in recovery, where scratch is not
-    // automatically mapped and therefore can be unmapped.
-    DestroyLogicalPartition(android::base::Basename(kScratchMountPoint));
+// This should only be reachable in recovery, where DSU scratch is not
+// automatically mapped.
+static bool MapDsuScratchDevice(std::string* device) {
+    std::string dsu_slot;
+    if (!android::gsi::IsGsiInstalled() || !android::gsi::GetActiveDsu(&dsu_slot) ||
+        dsu_slot.empty()) {
+        // Nothing to do if no DSU installation present.
+        return false;
+    }
+
+    auto images = IImageManager::Open("dsu/" + dsu_slot, 10s);
+    if (!images || !images->BackingImageExists(android::gsi::kDsuScratch)) {
+        // Nothing to do if DSU scratch device doesn't exist.
+        return false;
+    }
+
+    images->UnmapImageDevice(android::gsi::kDsuScratch);
+    if (!images->MapImageDevice(android::gsi::kDsuScratch, 10s, device)) {
+        return false;
+    }
+    return true;
 }
 
 // Returns false if teardown not permitted, errno set to last error.
@@ -1377,21 +1457,27 @@
     // If scratch exists, but is not mounted, lets gain access to clean
     // specific override entries.
     auto mount_scratch = false;
-    bool unmap = false;
     if ((mount_point != nullptr) && !fs_mgr_overlayfs_already_mounted(kScratchMountPoint, false)) {
-        std::string scratch_device;
-        if (EnsureScratchMapped(&scratch_device, &unmap)) {
+        std::string scratch_device = GetBootScratchDevice();
+        if (!scratch_device.empty()) {
             mount_scratch = fs_mgr_overlayfs_mount_scratch(scratch_device,
                                                            fs_mgr_overlayfs_scratch_mount_type());
         }
     }
+    bool should_destroy_scratch = false;
     for (const auto& overlay_mount_point : kOverlayMountPoints) {
         ret &= fs_mgr_overlayfs_teardown_one(
-                overlay_mount_point, mount_point ? fs_mgr_mount_point(mount_point) : "", change);
+                overlay_mount_point, mount_point ? fs_mgr_mount_point(mount_point) : "", change,
+                overlay_mount_point == kScratchMountPoint ? &should_destroy_scratch : nullptr);
+    }
+    // Do not attempt to destroy DSU scratch if within a DSU system,
+    // because DSU scratch partition is managed by gsid.
+    if (should_destroy_scratch && !fs_mgr_is_dsu_running()) {
+        ret &= fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, change);
     }
     if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kNotSupported) {
         // After obligatory teardown to make sure everything is clean, but if
-        // we didn't want overlayfs in the the first place, we do not want to
+        // we didn't want overlayfs in the first place, we do not want to
         // waste time on a reboot (or reboot request message).
         if (change) *change = false;
     }
@@ -1405,9 +1491,6 @@
     if (mount_scratch) {
         fs_mgr_overlayfs_umount_scratch();
     }
-    if (unmap) {
-        UnmapScratchDevice();
-    }
     return ret;
 }
 
@@ -1475,6 +1558,54 @@
     }
 }
 
+void TeardownAllOverlayForMountPoint(const std::string& mount_point) {
+    if (!fs_mgr_in_recovery()) {
+        LERROR << __FUNCTION__ << "(): must be called within recovery.";
+        return;
+    }
+
+    // Empty string means teardown everything.
+    const std::string teardown_dir = mount_point.empty() ? "" : fs_mgr_mount_point(mount_point);
+    constexpr bool* ignore_change = nullptr;
+
+    // Teardown legacy overlay mount points that's not backed by a scratch device.
+    for (const auto& overlay_mount_point : kOverlayMountPoints) {
+        if (overlay_mount_point == kScratchMountPoint) {
+            continue;
+        }
+        fs_mgr_overlayfs_teardown_one(overlay_mount_point, teardown_dir, ignore_change);
+    }
+
+    // Map scratch device, mount kScratchMountPoint and teardown kScratchMountPoint.
+    bool mapped = false;
+    std::string scratch_device;
+    if (EnsureScratchMapped(&scratch_device, &mapped)) {
+        fs_mgr_overlayfs_umount_scratch();
+        if (fs_mgr_overlayfs_mount_scratch(scratch_device, fs_mgr_overlayfs_scratch_mount_type())) {
+            bool should_destroy_scratch = false;
+            fs_mgr_overlayfs_teardown_one(kScratchMountPoint, teardown_dir, ignore_change,
+                                          &should_destroy_scratch);
+            if (should_destroy_scratch) {
+                fs_mgr_overlayfs_teardown_scratch(kScratchMountPoint, nullptr);
+            }
+            fs_mgr_overlayfs_umount_scratch();
+        }
+        if (mapped) {
+            DestroyLogicalPartition(android::base::Basename(kScratchMountPoint));
+        }
+    }
+
+    // Teardown DSU overlay if present.
+    if (MapDsuScratchDevice(&scratch_device)) {
+        fs_mgr_overlayfs_umount_scratch();
+        if (fs_mgr_overlayfs_mount_scratch(scratch_device, fs_mgr_overlayfs_scratch_mount_type())) {
+            fs_mgr_overlayfs_teardown_one(kScratchMountPoint, teardown_dir, ignore_change);
+            fs_mgr_overlayfs_umount_scratch();
+        }
+        DestroyLogicalPartition(android::gsi::kDsuScratch);
+    }
+}
+
 }  // namespace fs_mgr
 }  // namespace android
 
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index 34aded9..d45e2de 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -49,5 +49,12 @@
                                  const std::function<bool(const std::set<std::string>&)>& init);
 void CleanupOldScratchFiles();
 
+// Teardown overlays of all sources (cache dir, scratch device, DSU) for |mount_point|.
+// Teardown all overlays if |mount_point| is empty.
+//
+// Note: This should be called if and only if in recovery or fastbootd to teardown
+// overlays if any partition is flashed or updated.
+void TeardownAllOverlayForMountPoint(const std::string& mount_point = {});
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 95606d7..f1b0031 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -31,13 +31,17 @@
         "libbrotli",
         "libdm",
         "libfstab",
-        "libsnapshot_cow",
         "update_metadata-protos",
     ],
     whole_static_libs: [
+        "libbrotli",
+        "libcutils",
         "libext2_uuid",
         "libext4_utils",
         "libfstab",
+        "libsnapshot_cow",
+        "libsnapshot_snapuserd",
+        "libz",
     ],
     header_libs: [
         "libchrome",
@@ -239,6 +243,7 @@
     srcs: [
         "partition_cow_creator_test.cpp",
         "snapshot_metadata_updater_test.cpp",
+        "snapshot_reader_test.cpp",
         "snapshot_test.cpp",
     ],
     shared_libs: [
@@ -429,6 +434,7 @@
     init_rc: [
         "snapuserd.rc",
     ],
+    static_executable: true,
 }
 
 cc_binary {
@@ -571,3 +577,27 @@
     auto_gen_config: true,
     require_root: false,
 }
+
+cc_binary {
+    name: "inspect_cow",
+    host_supported: true,
+    device_supported: true,
+    cflags: [
+        "-D_FILE_OFFSET_BITS=64",
+        "-Wall",
+        "-Werror",
+    ],
+    static_libs: [
+        "libbase",
+        "libbrotli",
+        "libcrypto_static",
+        "liblog",
+        "libsnapshot_cow",
+        "libz",
+    ],
+    shared_libs: [
+    ],
+    srcs: [
+        "inspect_cow.cpp",
+    ],
+}
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 3d0321f..1408244 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -272,7 +272,6 @@
 }
 
 TEST_F(CowTest, Append) {
-    cow_->DoNotRemove();
     CowOptions options;
     auto writer = std::make_unique<CowWriter>(options);
     ASSERT_TRUE(writer->Initialize(cow_->fd));
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index b1667e3..452a5f3 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -18,6 +18,7 @@
 #include <unistd.h>
 
 #include <limits>
+#include <optional>
 #include <vector>
 
 #include <android-base/file.h>
@@ -117,8 +118,7 @@
         PLOG(ERROR) << "lseek ops failed";
         return false;
     }
-    uint64_t next_last_label = 0;
-    bool has_next = false;
+    std::optional<uint64_t> next_last_label;
     auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
     if (has_footer_) ops_buffer->reserve(footer_.op.num_ops);
     uint64_t current_op_num = 0;
@@ -135,7 +135,7 @@
         }
         auto& current_op = ops_buffer->data()[current_op_num];
         pos = lseek(fd_.get(), GetNextOpOffset(current_op), SEEK_CUR);
-        if (pos < 0) {
+        if (pos == uint64_t(-1)) {
             PLOG(ERROR) << "lseek next op failed";
             return false;
         }
@@ -146,11 +146,24 @@
                 has_last_label_ = true;
                 last_label_ = current_op.source;
             } else {
-                last_label_ = next_last_label;
-                if (has_next) has_last_label_ = true;
-                next_last_label = current_op.source;
-                has_next = true;
+                if (next_last_label) {
+                    last_label_ = next_last_label.value();
+                    has_last_label_ = true;
+                }
+                next_last_label = {current_op.source};
             }
+        } else if (current_op.type == kCowFooterOp) {
+            memcpy(&footer_.op, &current_op, sizeof(footer_.op));
+            // we don't consider this an operation for the checksum
+            current_op_num--;
+            if (android::base::ReadFully(fd_, &footer_.data, sizeof(footer_.data))) {
+                has_footer_ = true;
+                if (next_last_label) {
+                    last_label_ = next_last_label.value();
+                    has_last_label_ = true;
+                }
+            }
+            break;
         }
     }
 
@@ -158,6 +171,19 @@
     memset(csum, 0, sizeof(uint8_t) * 32);
 
     if (has_footer_) {
+        if (ops_buffer->size() != footer_.op.num_ops) {
+            LOG(ERROR) << "num ops does not match";
+            return false;
+        }
+        if (ops_buffer->size() * sizeof(CowOperation) != footer_.op.ops_size) {
+            LOG(ERROR) << "ops size does not match ";
+            return false;
+        }
+        SHA256(&footer_.op, sizeof(footer_.op), footer_.data.footer_checksum);
+        if (memcmp(csum, footer_.data.ops_checksum, sizeof(csum)) != 0) {
+            LOG(ERROR) << "ops checksum does not match";
+            return false;
+        }
         SHA256(ops_buffer.get()->data(), footer_.op.ops_size, csum);
         if (memcmp(csum, footer_.data.ops_checksum, sizeof(csum)) != 0) {
             LOG(ERROR) << "ops checksum does not match";
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index ec2dc96..b3e75a0 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -185,6 +185,7 @@
     // Reset this, since we're going to reimport all operations.
     footer_.op.num_ops = 0;
     next_op_pos_ = sizeof(header_);
+    ops_.resize(0);
 
     auto iter = reader->GetOpIter();
     while (!iter->Done()) {
@@ -233,6 +234,7 @@
     // Reset this, since we're going to reimport all operations.
     footer_.op.num_ops = 0;
     next_op_pos_ = sizeof(header_);
+    ops_.resize(0);
 
     auto iter = reader->GetOpIter();
     while (!iter->Done()) {
@@ -247,7 +249,7 @@
     }
 
     if (!found_label) {
-        PLOG(ERROR) << "Failed to find last label";
+        LOG(ERROR) << "Failed to find last label";
         return false;
     }
 
@@ -384,7 +386,7 @@
 }
 
 bool CowWriter::Finalize() {
-    footer_.op.ops_size = ops_.size() + sizeof(footer_.op);
+    footer_.op.ops_size = ops_.size();
     uint64_t pos;
 
     if (!GetDataPos(&pos)) {
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 814e104..b863ff2 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -39,11 +39,17 @@
     // maximum number of bytes that can be written to the returned buffer.
     //
     // The returned buffer is owned by IByteSink, but must remain valid until
-    // the ready operation has completed (or the entire buffer has been
+    // the read operation has completed (or the entire buffer has been
     // covered by calls to ReturnData).
     //
     // After calling GetBuffer(), all previous buffers returned are no longer
     // valid.
+    //
+    // GetBuffer() is intended to be sequential. A returned size of N indicates
+    // that the output stream will advance by N bytes, and the ReturnData call
+    // indicates that those bytes have been fulfilled. Therefore, it is
+    // possible to have ReturnBuffer do nothing, if the implementation doesn't
+    // care about incremental writes.
     virtual void* GetBuffer(size_t requested, size_t* actual) = 0;
 
     // Called when a section returned by |GetBuffer| has been filled with data.
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index c031d63..0f47622 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -114,7 +114,6 @@
     bool OpenForWrite();
     bool OpenForAppend();
     bool OpenForAppend(uint64_t label);
-    bool ImportOps(std::unique_ptr<ICowOpIter> iter);
     bool GetDataPos(uint64_t* pos);
     bool WriteRawData(const void* data, size_t size);
     bool WriteOperation(const CowOperation& op, const void* data = nullptr, size_t size = 0);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index 6dee3d4..92e7910 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -47,6 +47,8 @@
     MOCK_METHOD(bool, CreateLogicalAndSnapshotPartitions,
                 (const std::string& super_device, const std::chrono::milliseconds& timeout_ms),
                 (override));
+    MOCK_METHOD(bool, MapAllSnapshots, (const std::chrono::milliseconds& timeout_ms), (override));
+    MOCK_METHOD(bool, UnmapAllSnapshots, (), (override));
     MOCK_METHOD(bool, HandleImminentDataWipe, (const std::function<void()>& callback), (override));
     MOCK_METHOD(bool, FinishMergeInRecovery, (), (override));
     MOCK_METHOD(CreateResult, RecoveryCreateSnapshotDevices, (), (override));
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 4bbdca3..8bed1b9 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -15,6 +15,7 @@
 #pragma once
 
 #include <stdint.h>
+#include <unistd.h>
 
 #include <chrono>
 #include <map>
@@ -37,6 +38,7 @@
 #include <libsnapshot/auto_device.h>
 #include <libsnapshot/return.h>
 #include <libsnapshot/snapshot_writer.h>
+#include <libsnapshot/snapuserd_client.h>
 
 #ifndef FRIEND_TEST
 #define FRIEND_TEST(test_set_name, individual_test) \
@@ -76,6 +78,7 @@
 class SnapshotStatus;
 
 static constexpr const std::string_view kCowGroupName = "cow";
+static constexpr char kVirtualAbCompressionProp[] = "ro.virtual_ab.compression.enabled";
 
 bool OptimizeSourceCopyOperation(const chromeos_update_engine::InstallOperation& operation,
                                  chromeos_update_engine::InstallOperation* optimized);
@@ -103,6 +106,7 @@
                 android::hardware::boot::V1_1::MergeStatus status) = 0;
         virtual bool SetSlotAsUnbootable(unsigned int slot) = 0;
         virtual bool IsRecovery() const = 0;
+        virtual bool IsTestDevice() const { return false; }
     };
     virtual ~ISnapshotManager() = default;
 
@@ -185,6 +189,9 @@
     // must be suffixed. If a source partition exists, it must be specified as well. The source
     // partition will only be used if raw bytes are needed. The source partition should be an
     // absolute path to the device, not a partition name.
+    //
+    // After calling OpenSnapshotWriter, the caller must invoke Initialize or InitializeForAppend
+    // before invoking write operations.
     virtual std::unique_ptr<ISnapshotWriter> OpenSnapshotWriter(
             const android::fs_mgr::CreateLogicalPartitionParams& params,
             const std::optional<std::string>& source_device) = 0;
@@ -203,6 +210,14 @@
     virtual bool CreateLogicalAndSnapshotPartitions(
             const std::string& super_device, const std::chrono::milliseconds& timeout_ms = {}) = 0;
 
+    // Map all snapshots. This is analogous to CreateLogicalAndSnapshotPartitions, except it maps
+    // the target slot rather than the current slot. It should only be used immediately after
+    // applying an update, before rebooting to the new slot.
+    virtual bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms = {}) = 0;
+
+    // Unmap all snapshots. This should be called to undo MapAllSnapshots().
+    virtual bool UnmapAllSnapshots() = 0;
+
     // This method should be called preceding any wipe or flash of metadata or
     // userdata. It is only valid in recovery or fastbootd, and it ensures that
     // a merge has been completed.
@@ -291,6 +306,14 @@
     // Helper function for second stage init to restorecon on the rollback indicator.
     static std::string GetGlobalRollbackIndicatorPath();
 
+    // Initiate the transition from first-stage to second-stage snapuserd. This
+    // process involves re-creating the dm-user table entries for each device,
+    // so that they connect to the new daemon. Once all new tables have been
+    // activated, we ask the first-stage daemon to cleanly exit.
+    //
+    // The caller must pass a function which starts snapuserd.
+    bool PerformSecondStageTransition();
+
     // ISnapshotManager overrides.
     bool BeginUpdate() override;
     bool CancelUpdate() override;
@@ -318,6 +341,8 @@
     bool Dump(std::ostream& os) override;
     std::unique_ptr<AutoDevice> EnsureMetadataMounted() override;
     ISnapshotMergeStats* GetSnapshotMergeStatsInstance() override;
+    bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms = {}) override;
+    bool UnmapAllSnapshots() override;
 
   private:
     FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
@@ -331,6 +356,7 @@
     FRIEND_TEST(SnapshotTest, Merge);
     FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
     FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
+    FRIEND_TEST(SnapshotUpdateTest, DaemonTransition);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery);
     FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow);
@@ -355,11 +381,16 @@
     // This is created lazily since it can connect via binder.
     bool EnsureImageManager();
 
-    // Helper for first-stage init.
-    bool ForceLocalImageManager();
+    // Ensure we're connected to snapuserd.
+    bool EnsureSnapuserdConnected();
 
-    // Helper function for tests.
+    // Helpers for first-stage init.
+    bool ForceLocalImageManager();
+    const std::unique_ptr<IDeviceInfo>& device() const { return device_; }
+
+    // Helper functions for tests.
     IImageManager* image_manager() const { return images_.get(); }
+    void set_use_first_stage_snapuserd(bool value) { use_first_stage_snapuserd_ = value; }
 
     // Since libsnapshot is included into multiple processes, we flock() our
     // files for simple synchronization. LockedFile is a helper to assist with
@@ -411,6 +442,11 @@
                      const std::string& cow_device, const std::chrono::milliseconds& timeout_ms,
                      std::string* dev_path);
 
+    // Create a dm-user device for a given snapshot.
+    bool MapDmUserCow(LockedFile* lock, const std::string& name, const std::string& cow_file,
+                      const std::string& base_device, const std::chrono::milliseconds& timeout_ms,
+                      std::string* path);
+
     // Map a COW image that was previous created with CreateCowImage.
     std::optional<std::string> MapCowImage(const std::string& name,
                                            const std::chrono::milliseconds& timeout_ms);
@@ -523,6 +559,9 @@
     std::string GetSnapshotDeviceName(const std::string& snapshot_name,
                                       const SnapshotStatus& status);
 
+    bool MapAllPartitions(LockedFile* lock, const std::string& super_device, uint32_t slot,
+                          const std::chrono::milliseconds& timeout_ms);
+
     // Reason for calling MapPartitionWithSnapshot.
     enum class SnapshotContext {
         // For writing or verification (during update_engine).
@@ -596,9 +635,12 @@
             const LpMetadata* exported_target_metadata, const std::string& target_suffix,
             const std::map<std::string, SnapshotStatus>& all_snapshot_status);
 
+    // Implementation of UnmapAllSnapshots(), with the lock provided.
+    bool UnmapAllSnapshots(LockedFile* lock);
+
     // Unmap all partitions that were mapped by CreateLogicalAndSnapshotPartitions.
     // This should only be called in recovery.
-    bool UnmapAllPartitions();
+    bool UnmapAllPartitionsInRecovery();
 
     // Check no snapshot overflows. Note that this returns false negatives if the snapshot
     // overflows, then is remapped and not written afterwards.
@@ -638,7 +680,9 @@
     std::unique_ptr<IDeviceInfo> device_;
     std::unique_ptr<IImageManager> images_;
     bool has_local_image_manager_ = false;
+    bool use_first_stage_snapuserd_ = false;
     bool in_factory_data_reset_ = false;
+    std::unique_ptr<SnapuserdClient> snapuserd_client_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index ed790a0..cba3560 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -52,6 +52,8 @@
     bool Dump(std::ostream& os) override;
     std::unique_ptr<AutoDevice> EnsureMetadataMounted() override;
     ISnapshotMergeStats* GetSnapshotMergeStatsInstance() override;
+    bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) override;
+    bool UnmapAllSnapshots() override;
 };
 
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
index f76f545..4732c2d 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
@@ -14,6 +14,8 @@
 
 #pragma once
 
+#include <optional>
+
 #include <android-base/unique_fd.h>
 
 #include <libsnapshot/cow_writer.h>
@@ -37,14 +39,22 @@
     // device is only opened on the first operation that requires it.
     void SetSourceDevice(const std::string& source_device);
 
+    // Open the writer in write mode (no append).
+    virtual bool Initialize() = 0;
+
+    // Open the writer in append mode, optionally with the last label to resume
+    // from. See CowWriter::InitializeAppend.
+    virtual bool InitializeAppend(std::optional<uint64_t> label = {}) = 0;
+
     virtual std::unique_ptr<FileDescriptor> OpenReader() = 0;
 
   protected:
     android::base::borrowed_fd GetSourceFd();
 
+    std::optional<std::string> source_device_;
+
   private:
     android::base::unique_fd source_fd_;
-    std::optional<std::string> source_device_;
 };
 
 // Send writes to a COW or a raw device directly, based on a threshold.
@@ -52,9 +62,11 @@
   public:
     CompressedSnapshotWriter(const CowOptions& options);
 
-    // Sets the COW device, if needed.
+    // Sets the COW device; this is required.
     bool SetCowDevice(android::base::unique_fd&& cow_device);
 
+    bool Initialize() override;
+    bool InitializeAppend(std::optional<uint64_t> label = {}) override;
     bool Finalize() override;
     uint64_t GetCowSize() override;
     std::unique_ptr<FileDescriptor> OpenReader() override;
@@ -79,6 +91,9 @@
     // Set the device used for all writes.
     void SetSnapshotDevice(android::base::unique_fd&& snapshot_fd, uint64_t cow_size);
 
+    bool Initialize() override { return true; }
+    bool InitializeAppend(std::optional<uint64_t>) override { return true; }
+
     bool Finalize() override;
     uint64_t GetCowSize() override { return cow_size_; }
     std::unique_ptr<FileDescriptor> OpenReader() override;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
index 2f727d6..80f87d9 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
@@ -77,7 +77,7 @@
     int ReadDiskExceptions(chunk_t chunk, size_t size);
     int ReadData(chunk_t chunk, size_t size);
 
-    std::string GetControlDevicePath() { return control_device_; }
+    const std::string& GetControlDevicePath() { return control_device_; }
 
   private:
     int ProcessReplaceOp(const CowOperation* cow_op);
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
index dffd481..aaec229 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
@@ -14,6 +14,8 @@
 
 #pragma once
 
+#include <unistd.h>
+
 #include <chrono>
 #include <cstring>
 #include <iostream>
@@ -31,9 +33,15 @@
 static constexpr char kSnapuserdSocketFirstStage[] = "snapuserd_first_stage";
 static constexpr char kSnapuserdSocket[] = "snapuserd";
 
+static constexpr char kSnapuserdFirstStagePidVar[] = "FIRST_STAGE_SNAPUSERD_PID";
+
 // Ensure that the second-stage daemon for snapuserd is running.
 bool EnsureSnapuserdStarted();
 
+// Start the first-stage version of snapuserd, returning its pid. This is used
+// by first-stage init, as well as vts_libsnapshot_test. On failure, -1 is returned.
+pid_t StartFirstStageSnapuserd();
+
 class SnapuserdClient {
   private:
     android::base::unique_fd sockfd_;
@@ -53,6 +61,10 @@
     int RestartSnapuserd(std::vector<std::vector<std::string>>& vec);
     bool InitializeSnapuserd(const std::string& cow_device, const std::string& backing_device,
                              const std::string& control_device);
+
+    // Wait for snapuserd to disassociate with a dm-user control device. This
+    // must ONLY be called if the control device has already been deleted.
+    bool WaitForDeviceDelete(const std::string& control_device);
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h
index 357acac..181ee33 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h
@@ -21,12 +21,14 @@
 #include <functional>
 #include <future>
 #include <iostream>
+#include <mutex>
 #include <sstream>
 #include <string>
 #include <thread>
 #include <vector>
 
 #include <android-base/unique_fd.h>
+#include <libsnapshot/snapuserd.h>
 
 namespace android {
 namespace snapshot {
@@ -37,19 +39,23 @@
     START,
     QUERY,
     STOP,
+    DELETE,
     INVALID,
 };
 
 class DmUserHandler {
   private:
-    std::unique_ptr<std::thread> threadHandler_;
+    std::thread thread_;
+    std::unique_ptr<Snapuserd> snapuserd_;
 
   public:
-    void SetThreadHandler(std::function<void(void)> func) {
-        threadHandler_ = std::make_unique<std::thread>(func);
-    }
+    explicit DmUserHandler(std::unique_ptr<Snapuserd>&& snapuserd)
+        : snapuserd_(std::move(snapuserd)) {}
 
-    std::unique_ptr<std::thread>& GetThreadHandler() { return threadHandler_; }
+    const std::unique_ptr<Snapuserd>& snapuserd() const { return snapuserd_; }
+    std::thread& thread() { return thread_; }
+
+    const std::string& GetControlDevice() const;
 };
 
 class Stoppable {
@@ -61,9 +67,6 @@
 
     virtual ~Stoppable() {}
 
-    virtual void ThreadStart(std::string cow_device, std::string backing_device,
-                             std::string control_device) = 0;
-
     bool StopRequested() {
         // checks if value in future object is available
         if (futureObj_.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout)
@@ -78,27 +81,33 @@
   private:
     android::base::unique_fd sockfd_;
     bool terminating_;
-    std::vector<std::unique_ptr<DmUserHandler>> dm_users_;
     std::vector<struct pollfd> watched_fds_;
 
+    std::mutex lock_;
+    std::vector<std::unique_ptr<DmUserHandler>> dm_users_;
+
     void AddWatchedFd(android::base::borrowed_fd fd);
     void AcceptClient();
     bool HandleClient(android::base::borrowed_fd fd, int revents);
     bool Recv(android::base::borrowed_fd fd, std::string* data);
     bool Sendmsg(android::base::borrowed_fd fd, const std::string& msg);
-    bool Receivemsg(android::base::borrowed_fd fd, const std::string& msg);
+    bool Receivemsg(android::base::borrowed_fd fd, const std::string& str);
 
-    void ThreadStart(std::string cow_device, std::string backing_device,
-                     std::string control_device) override;
     void ShutdownThreads();
+    bool WaitForDelete(const std::string& control_device);
     DaemonOperations Resolveop(std::string& input);
     std::string GetDaemonStatus();
     void Parsemsg(std::string const& msg, const char delim, std::vector<std::string>& out);
 
     void SetTerminating() { terminating_ = true; }
-
     bool IsTerminating() { return terminating_; }
 
+    void RunThread(DmUserHandler* handler);
+
+    // Remove a DmUserHandler from dm_users_, searching by its control device.
+    // If none is found, return nullptr.
+    std::unique_ptr<DmUserHandler> RemoveHandler(const std::string& control_device);
+
   public:
     SnapuserdServer() { terminating_ = false; }
     ~SnapuserdServer();
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 197aeaa..7aef086 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -95,6 +95,7 @@
         unbootable_slots_.insert(slot);
         return true;
     }
+    bool IsTestDevice() const override { return true; }
 
     bool IsSlotUnbootable(uint32_t slot) { return unbootable_slots_.count(slot) != 0; }
 
diff --git a/fs_mgr/libsnapshot/inspect_cow.cpp b/fs_mgr/libsnapshot/inspect_cow.cpp
new file mode 100644
index 0000000..6046bad
--- /dev/null
+++ b/fs_mgr/libsnapshot/inspect_cow.cpp
@@ -0,0 +1,90 @@
+//
+// Copyright (C) 2020 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 <stdio.h>
+
+#include <iostream>
+#include <string>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <libsnapshot/cow_reader.h>
+
+namespace android {
+namespace snapshot {
+
+void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
+              unsigned int, const char* message) {
+    if (severity == android::base::ERROR) {
+        fprintf(stderr, "%s\n", message);
+    } else {
+        fprintf(stdout, "%s\n", message);
+    }
+}
+
+static bool Inspect(const std::string& path) {
+    android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
+    if (fd < 0) {
+        PLOG(ERROR) << "open failed: " << path;
+        return false;
+    }
+
+    CowReader reader;
+    if (!reader.Parse(fd)) {
+        LOG(ERROR) << "parse failed: " << path;
+        return false;
+    }
+
+    CowHeader header;
+    if (!reader.GetHeader(&header)) {
+        LOG(ERROR) << "could not get header: " << path;
+        return false;
+    }
+
+    std::cout << "Major version: " << header.major_version << "\n";
+    std::cout << "Minor version: " << header.minor_version << "\n";
+    std::cout << "Header size: " << header.header_size << "\n";
+    std::cout << "Footer size: " << header.footer_size << "\n";
+    std::cout << "Block size: " << header.block_size << "\n";
+    std::cout << "\n";
+
+    auto iter = reader.GetOpIter();
+    while (!iter->Done()) {
+        const CowOperation& op = iter->Get();
+
+        std::cout << op << "\n";
+
+        iter->Next();
+    }
+
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
+
+int main(int argc, char** argv) {
+    android::base::InitLogging(argv, android::snapshot::MyLogger);
+
+    if (argc < 2) {
+        LOG(ERROR) << "Usage: inspect_cow <COW_FILE>";
+        return 1;
+    }
+
+    if (!android::snapshot::Inspect(argv[1])) {
+        return 1;
+    }
+    return 0;
+}
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 6574457..f9bb0dd 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -31,8 +31,10 @@
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
+#include <cutils/sockets.h>
 #include <ext4_utils/ext4_utils.h>
 #include <fs_mgr.h>
+#include <fs_mgr/file_wait.h>
 #include <fs_mgr_dm_linear.h>
 #include <fstab/fstab.h>
 #include <libdm/dm.h>
@@ -56,6 +58,7 @@
 using android::dm::DmTable;
 using android::dm::DmTargetLinear;
 using android::dm::DmTargetSnapshot;
+using android::dm::DmTargetUser;
 using android::dm::kSectorSize;
 using android::dm::SnapshotStorageMode;
 using android::fiemap::FiemapStatus;
@@ -99,6 +102,12 @@
     if (!sm || !sm->ForceLocalImageManager()) {
         return nullptr;
     }
+
+    // The first-stage version of snapuserd is explicitly started by init. Do
+    // not attempt to using it during tests (which run in normal AOSP).
+    if (!sm->device()->IsTestDevice()) {
+        sm->use_first_stage_snapuserd_ = true;
+    }
     return sm;
 }
 
@@ -115,6 +124,10 @@
     return snapshot_name + "-cow";
 }
 
+static std::string GetDmUserCowName(const std::string& snapshot_name) {
+    return snapshot_name + "-user-cow";
+}
+
 static std::string GetCowImageDeviceName(const std::string& snapshot_name) {
     return snapshot_name + "-cow-img";
 }
@@ -370,6 +383,52 @@
     return Return(images_->CreateBackingImage(cow_image_name, status.cow_file_size(), cow_flags));
 }
 
+bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
+                                   const std::string& cow_file, const std::string& base_device,
+                                   const std::chrono::milliseconds& timeout_ms, std::string* path) {
+    CHECK(lock);
+
+    auto& dm = DeviceMapper::Instance();
+
+    // Use the size of the base device for the COW device. It doesn't really
+    // matter, it just needs to look similar enough so the kernel doesn't complain
+    // about alignment or being too small.
+    uint64_t base_sectors = 0;
+    {
+        unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC));
+        if (fd < 0) {
+            PLOG(ERROR) << "open failed: " << base_device;
+            return false;
+        }
+        auto dev_size = get_block_device_size(fd);
+        if (!dev_size) {
+            PLOG(ERROR) << "Could not determine block device size: " << base_device;
+            return false;
+        }
+        base_sectors = dev_size / kSectorSize;
+    }
+
+    // Use an extra decoration for first-stage init, so we can transition
+    // to a new table entry in second-stage.
+    std::string misc_name = name;
+    if (use_first_stage_snapuserd_) {
+        misc_name += "-init";
+    }
+
+    DmTable table;
+    table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
+    if (!dm.CreateDevice(name, table, path, timeout_ms)) {
+        return false;
+    }
+
+    if (!EnsureSnapuserdConnected()) {
+        return false;
+    }
+
+    auto control_device = "/dev/dm-user/" + misc_name;
+    return snapuserd_client_->InitializeSnapuserd(cow_file, base_device, control_device);
+}
+
 bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
                                   const std::string& base_device, const std::string& cow_device,
                                   const std::chrono::milliseconds& timeout_ms,
@@ -1240,6 +1299,107 @@
     return RemoveAllUpdateState(lock, before_cancel);
 }
 
+bool SnapshotManager::PerformSecondStageTransition() {
+    LOG(INFO) << "Performing second-stage transition for snapuserd.";
+
+    // Don't use EnsuerSnapuserdConnected() because this is called from init,
+    // and attempting to do so will deadlock.
+    if (!snapuserd_client_) {
+        snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
+        if (!snapuserd_client_) {
+            LOG(ERROR) << "Unable to connect to snapuserd";
+            return false;
+        }
+    }
+
+    auto& dm = DeviceMapper::Instance();
+
+    auto lock = LockExclusive();
+    if (!lock) return false;
+
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock.get(), &snapshots)) {
+        LOG(ERROR) << "Failed to list snapshots.";
+        return false;
+    }
+
+    size_t num_cows = 0;
+    size_t ok_cows = 0;
+    for (const auto& snapshot : snapshots) {
+        std::string cow_name = GetDmUserCowName(snapshot);
+        if (dm.GetState(cow_name) == DmDeviceState::INVALID) {
+            continue;
+        }
+
+        DeviceMapper::TargetInfo target;
+        if (!GetSingleTarget(cow_name, TableQuery::Table, &target)) {
+            continue;
+        }
+
+        auto target_type = DeviceMapper::GetTargetType(target.spec);
+        if (target_type != "user") {
+            LOG(ERROR) << "Unexpected target type for " << cow_name << ": " << target_type;
+            continue;
+        }
+
+        num_cows++;
+
+        DmTable table;
+        table.Emplace<DmTargetUser>(0, target.spec.length, cow_name);
+        if (!dm.LoadTableAndActivate(cow_name, table)) {
+            LOG(ERROR) << "Unable to swap tables for " << cow_name;
+            continue;
+        }
+
+        std::string backing_device;
+        if (!dm.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &backing_device)) {
+            LOG(ERROR) << "Could not get device path for " << GetBaseDeviceName(snapshot);
+            continue;
+        }
+
+        std::string cow_device;
+        if (!dm.GetDmDevicePathByName(GetCowName(snapshot), &cow_device)) {
+            LOG(ERROR) << "Could not get device path for " << GetCowName(snapshot);
+            continue;
+        }
+
+        // Wait for ueventd to acknowledge and create the control device node.
+        std::string control_device = "/dev/dm-user/" + cow_name;
+        if (!android::fs_mgr::WaitForFile(control_device, 10s)) {
+            LOG(ERROR) << "Could not find control device: " << control_device;
+            continue;
+        }
+
+        if (!snapuserd_client_->InitializeSnapuserd(cow_device, backing_device, control_device)) {
+            // This error is unrecoverable. We cannot proceed because reads to
+            // the underlying device will fail.
+            LOG(FATAL) << "Could not initialize snapuserd for " << cow_name;
+            return false;
+        }
+
+        ok_cows++;
+    }
+
+    if (ok_cows != num_cows) {
+        LOG(ERROR) << "Could not transition all snapuserd consumers.";
+        return false;
+    }
+
+    int pid;
+    const char* pid_str = getenv(kSnapuserdFirstStagePidVar);
+    if (pid_str && android::base::ParseInt(pid_str, &pid)) {
+        if (kill(pid, SIGTERM) < 0 && errno != ESRCH) {
+            LOG(ERROR) << "kill snapuserd failed";
+            return false;
+        }
+    } else {
+        LOG(ERROR) << "Could not find or parse " << kSnapuserdFirstStagePidVar
+                   << " for snapuserd pid";
+        return false;
+    }
+    return true;
+}
+
 std::unique_ptr<LpMetadata> SnapshotManager::ReadCurrentMetadata() {
     const auto& opener = device_->GetPartitionOpener();
     uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
@@ -1549,8 +1709,13 @@
     auto lock = LockExclusive();
     if (!lock) return false;
 
-    const auto& opener = device_->GetPartitionOpener();
     uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
+    return MapAllPartitions(lock.get(), super_device, slot, timeout_ms);
+}
+
+bool SnapshotManager::MapAllPartitions(LockedFile* lock, const std::string& super_device,
+                                       uint32_t slot, const std::chrono::milliseconds& timeout_ms) {
+    const auto& opener = device_->GetPartitionOpener();
     auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
     if (!metadata) {
         LOG(ERROR) << "Could not read dynamic partition metadata for device: " << super_device;
@@ -1571,12 +1736,20 @@
                 .partition_opener = &opener,
                 .timeout_ms = timeout_ms,
         };
-        if (!MapPartitionWithSnapshot(lock.get(), std::move(params), SnapshotContext::Mount,
-                                      nullptr)) {
+        if (!MapPartitionWithSnapshot(lock, std::move(params), SnapshotContext::Mount, nullptr)) {
             return false;
         }
     }
 
+    if (use_first_stage_snapuserd_) {
+        // Remove the first-stage socket as a precaution, there is no need to
+        // access the daemon anymore and we'll be killing it once second-stage
+        // is running.
+        auto socket = ANDROID_SOCKET_DIR + "/"s + kSnapuserdSocketFirstStage;
+        snapuserd_client_ = nullptr;
+        unlink(socket.c_str());
+    }
+
     LOG(INFO) << "Created logical partitions with snapshot.";
     return true;
 }
@@ -1728,6 +1901,30 @@
         return true;
     }
 
+    if (IsCompressionEnabled()) {
+        auto name = GetDmUserCowName(params.GetPartitionName());
+
+        // :TODO: need to force init to process uevents for these in first-stage.
+        std::string cow_path;
+        if (!GetMappedImageDevicePath(cow_name, &cow_path)) {
+            LOG(ERROR) << "Could not determine path for: " << cow_name;
+            return false;
+        }
+
+        std::string new_cow_device;
+        if (!MapDmUserCow(lock, name, cow_path, base_path, remaining_time, &new_cow_device)) {
+            LOG(ERROR) << "Could not map dm-user device for partition "
+                       << params.GetPartitionName();
+            return false;
+        }
+        created_devices.EmplaceBack<AutoUnmapDevice>(&dm, name);
+
+        remaining_time = GetRemainingTime(params.timeout_ms, begin);
+        if (remaining_time.count() < 0) return false;
+
+        cow_device = new_cow_device;
+    }
+
     std::string path;
     if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
                      &path)) {
@@ -1847,6 +2044,30 @@
     if (!EnsureImageManager()) return false;
 
     auto& dm = DeviceMapper::Instance();
+
+    auto dm_user_name = GetDmUserCowName(name);
+    if (IsCompressionEnabled() && dm.GetState(dm_user_name) != DmDeviceState::INVALID) {
+        if (!EnsureSnapuserdConnected()) {
+            return false;
+        }
+        if (!dm.DeleteDevice(dm_user_name)) {
+            LOG(ERROR) << "Cannot unmap " << dm_user_name;
+            return false;
+        }
+
+        auto control_device = "/dev/dm-user/" + dm_user_name;
+        if (!snapuserd_client_->WaitForDeviceDelete(control_device)) {
+            LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
+            return false;
+        }
+
+        // Ensure the control device is gone so we don't run into ABA problems.
+        if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) {
+            LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink";
+            return false;
+        }
+    }
+
     auto cow_name = GetCowName(name);
     if (!dm.DeleteDeviceIfExists(cow_name)) {
         LOG(ERROR) << "Cannot unmap " << cow_name;
@@ -1861,6 +2082,51 @@
     return true;
 }
 
+bool SnapshotManager::MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) {
+    auto lock = LockExclusive();
+    if (!lock) return false;
+
+    auto state = ReadUpdateState(lock.get());
+    if (state == UpdateState::Unverified) {
+        if (GetCurrentSlot() == Slot::Target) {
+            LOG(ERROR) << "Cannot call MapAllSnapshots when booting from the target slot.";
+            return false;
+        }
+    } else if (state != UpdateState::Initiated) {
+        LOG(ERROR) << "Cannot call MapAllSnapshots from update state: " << state;
+        return false;
+    }
+
+    if (!UnmapAllSnapshots(lock.get())) {
+        return false;
+    }
+
+    uint32_t slot = SlotNumberForSlotSuffix(device_->GetOtherSlotSuffix());
+    return MapAllPartitions(lock.get(), device_->GetSuperDevice(slot), slot, timeout_ms);
+}
+
+bool SnapshotManager::UnmapAllSnapshots() {
+    auto lock = LockExclusive();
+    if (!lock) return false;
+
+    return UnmapAllSnapshots(lock.get());
+}
+
+bool SnapshotManager::UnmapAllSnapshots(LockedFile* lock) {
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock, &snapshots)) {
+        return false;
+    }
+
+    for (const auto& snapshot : snapshots) {
+        if (!UnmapPartitionWithSnapshot(lock, snapshot)) {
+            LOG(ERROR) << "Failed to unmap snapshot: " << snapshot;
+            return false;
+        }
+    }
+    return true;
+}
+
 auto SnapshotManager::OpenFile(const std::string& file, int lock_flags)
         -> std::unique_ptr<LockedFile> {
     unique_fd fd(open(file.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
@@ -2117,6 +2383,40 @@
     return true;
 }
 
+bool SnapshotManager::EnsureSnapuserdConnected() {
+    if (snapuserd_client_) {
+        return true;
+    }
+
+    std::string socket;
+    if (use_first_stage_snapuserd_) {
+        auto pid = StartFirstStageSnapuserd();
+        if (pid < 0) {
+            LOG(ERROR) << "Failed to start snapuserd";
+            return false;
+        }
+
+        auto pid_str = std::to_string(static_cast<int>(pid));
+        if (setenv(kSnapuserdFirstStagePidVar, pid_str.c_str(), 1) < 0) {
+            PLOG(ERROR) << "setenv failed storing the snapuserd pid";
+        }
+
+        socket = kSnapuserdSocketFirstStage;
+    } else {
+        if (!EnsureSnapuserdStarted()) {
+            return false;
+        }
+        socket = kSnapuserdSocket;
+    }
+
+    snapuserd_client_ = SnapuserdClient::Connect(socket, 10s);
+    if (!snapuserd_client_) {
+        LOG(ERROR) << "Unable to connect to snapuserd";
+        return false;
+    }
+    return true;
+}
+
 bool SnapshotManager::ForceLocalImageManager() {
     images_ = android::fiemap::ImageManager::Open(gsid_dir_);
     if (!images_) {
@@ -2430,11 +2730,26 @@
             return Return::Error();
         }
 
-        auto ret = InitializeCow(cow_path);
-        if (!ret.is_ok()) {
-            LOG(ERROR) << "Can't zero-fill COW device for " << target_partition->name() << ": "
-                       << cow_path;
-            return AddRequiredSpace(ret, all_snapshot_status);
+        if (IsCompressionEnabled()) {
+            unique_fd fd(open(cow_path.c_str(), O_RDWR | O_CLOEXEC));
+            if (fd < 0) {
+                PLOG(ERROR) << "open " << cow_path << " failed for snapshot "
+                            << cow_params.partition_name;
+                return Return::Error();
+            }
+
+            CowWriter writer(CowOptions{});
+            if (!writer.Initialize(fd) || !writer.Finalize()) {
+                LOG(ERROR) << "Could not initialize COW device for " << target_partition->name();
+                return Return::Error();
+            }
+        } else {
+            auto ret = InitializeKernelCow(cow_path);
+            if (!ret.is_ok()) {
+                LOG(ERROR) << "Can't zero-fill COW device for " << target_partition->name() << ": "
+                           << cow_path;
+                return AddRequiredSpace(ret, all_snapshot_status);
+            }
         }
         // Let destructor of created_devices_for_cow to unmap the COW devices.
     };
@@ -2592,7 +2907,7 @@
     return UnmapPartitionWithSnapshot(lock.get(), target_partition_name);
 }
 
-bool SnapshotManager::UnmapAllPartitions() {
+bool SnapshotManager::UnmapAllPartitionsInRecovery() {
     auto lock = LockExclusive();
     if (!lock) return false;
 
@@ -2736,7 +3051,7 @@
     }
 
     // Nothing should be depending on partitions now, so unmap them all.
-    if (!UnmapAllPartitions()) {
+    if (!UnmapAllPartitionsInRecovery()) {
         LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash.";
     }
     return true;
@@ -2767,7 +3082,7 @@
     }
 
     // Nothing should be depending on partitions now, so unmap them all.
-    if (!UnmapAllPartitions()) {
+    if (!UnmapAllPartitionsInRecovery()) {
         LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash.";
     }
     return true;
diff --git a/fs_mgr/libsnapshot/snapshot_reader.cpp b/fs_mgr/libsnapshot/snapshot_reader.cpp
index 0d47468..a4a652a 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader.cpp
@@ -14,13 +14,17 @@
 // limitations under the License.
 //
 
-#include <ext4_utils/ext4_utils.h>
-
 #include "snapshot_reader.h"
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <ext4_utils/ext4_utils.h>
+
 namespace android {
 namespace snapshot {
 
+using android::base::borrowed_fd;
+
 // Not supported.
 bool ReadOnlyFileDescriptor::Open(const char*, int, mode_t) {
     errno = EINVAL;
@@ -73,5 +77,252 @@
     return true;
 }
 
+bool CompressedSnapshotReader::SetCow(std::unique_ptr<CowReader>&& cow) {
+    cow_ = std::move(cow);
+
+    CowHeader header;
+    if (!cow_->GetHeader(&header)) {
+        return false;
+    }
+    block_size_ = header.block_size;
+
+    // Populate the operation map.
+    op_iter_ = cow_->GetOpIter();
+    while (!op_iter_->Done()) {
+        const CowOperation* op = &op_iter_->Get();
+        if (op->new_block >= ops_.size()) {
+            ops_.resize(op->new_block + 1, nullptr);
+        }
+        ops_[op->new_block] = op;
+        op_iter_->Next();
+    }
+
+    return true;
+}
+
+void CompressedSnapshotReader::SetSourceDevice(const std::string& source_device) {
+    source_device_ = {source_device};
+}
+
+void CompressedSnapshotReader::SetBlockDeviceSize(uint64_t block_device_size) {
+    block_device_size_ = block_device_size;
+}
+
+borrowed_fd CompressedSnapshotReader::GetSourceFd() {
+    if (source_fd_ < 0) {
+        if (!source_device_) {
+            LOG(ERROR) << "CompressedSnapshotReader needs source device, but none was set";
+            errno = EINVAL;
+            return {-1};
+        }
+        source_fd_.reset(open(source_device_->c_str(), O_RDONLY | O_CLOEXEC));
+        if (source_fd_ < 0) {
+            PLOG(ERROR) << "open " << *source_device_;
+            return {-1};
+        }
+    }
+    return source_fd_;
+}
+
+class MemoryByteSink : public IByteSink {
+  public:
+    MemoryByteSink(void* buf, size_t count) {
+        buf_ = reinterpret_cast<uint8_t*>(buf);
+        pos_ = buf_;
+        end_ = buf_ + count;
+    }
+
+    void* GetBuffer(size_t requested, size_t* actual) override {
+        *actual = std::min(remaining(), requested);
+        if (!*actual) {
+            return nullptr;
+        }
+
+        uint8_t* start = pos_;
+        pos_ += *actual;
+        return start;
+    }
+
+    bool ReturnData(void*, size_t) override { return true; }
+
+    uint8_t* buf() const { return buf_; }
+    uint8_t* pos() const { return pos_; }
+    size_t remaining() const { return end_ - pos_; }
+
+  private:
+    uint8_t* buf_;
+    uint8_t* pos_;
+    uint8_t* end_;
+};
+
+ssize_t CompressedSnapshotReader::Read(void* buf, size_t count) {
+    // Find the start and end chunks, inclusive.
+    uint64_t start_chunk = offset_ / block_size_;
+    uint64_t end_chunk = (offset_ + count - 1) / block_size_;
+
+    // Chop off the first N bytes if the position is not block-aligned.
+    size_t start_offset = offset_ % block_size_;
+
+    MemoryByteSink sink(buf, count);
+
+    size_t initial_bytes = std::min(block_size_ - start_offset, sink.remaining());
+    ssize_t rv = ReadBlock(start_chunk, &sink, start_offset, initial_bytes);
+    if (rv < 0) {
+        return -1;
+    }
+    offset_ += rv;
+
+    for (uint64_t chunk = start_chunk + 1; chunk < end_chunk; chunk++) {
+        ssize_t rv = ReadBlock(chunk, &sink, 0);
+        if (rv < 0) {
+            return -1;
+        }
+        offset_ += rv;
+    }
+
+    if (sink.remaining()) {
+        ssize_t rv = ReadBlock(end_chunk, &sink, 0, {sink.remaining()});
+        if (rv < 0) {
+            return -1;
+        }
+        offset_ += rv;
+    }
+
+    errno = 0;
+
+    DCHECK(sink.pos() - sink.buf() == count);
+    return count;
+}
+
+// Discard the first N bytes of a sink request, or any excess bytes.
+class PartialSink : public MemoryByteSink {
+  public:
+    PartialSink(void* buffer, size_t size, size_t ignore_start)
+        : MemoryByteSink(buffer, size), ignore_start_(ignore_start) {}
+
+    void* GetBuffer(size_t requested, size_t* actual) override {
+        // Throw away the first N bytes if needed.
+        if (ignore_start_) {
+            *actual = std::min({requested, ignore_start_, sizeof(discard_)});
+            ignore_start_ -= *actual;
+            return discard_;
+        }
+        // Throw away any excess bytes if needed.
+        if (remaining() == 0) {
+            *actual = std::min(requested, sizeof(discard_));
+            return discard_;
+        }
+        return MemoryByteSink::GetBuffer(requested, actual);
+    }
+
+  private:
+    size_t ignore_start_;
+    char discard_[4096];
+};
+
+ssize_t CompressedSnapshotReader::ReadBlock(uint64_t chunk, IByteSink* sink, size_t start_offset,
+                                            const std::optional<uint64_t>& max_bytes) {
+    size_t bytes_to_read = block_size_;
+    if (max_bytes) {
+        bytes_to_read = *max_bytes;
+    }
+
+    // The offset is relative to the chunk; we should be reading no more than
+    // one chunk.
+    CHECK(start_offset + bytes_to_read <= block_size_);
+
+    const CowOperation* op = nullptr;
+    if (chunk < ops_.size()) {
+        op = ops_[chunk];
+    }
+
+    size_t actual;
+    void* buffer = sink->GetBuffer(bytes_to_read, &actual);
+    if (!buffer || actual < bytes_to_read) {
+        // This should never happen unless we calculated the read size wrong
+        // somewhere. MemoryByteSink always fulfills the entire requested
+        // region unless there's not enough buffer remaining.
+        LOG(ERROR) << "Asked for buffer of size " << bytes_to_read << ", got " << actual;
+        errno = EINVAL;
+        return -1;
+    }
+
+    if (!op || op->type == kCowCopyOp) {
+        borrowed_fd fd = GetSourceFd();
+        if (fd < 0) {
+            // GetSourceFd sets errno.
+            return -1;
+        }
+
+        if (op) {
+            chunk = op->source;
+        }
+
+        off64_t offset = (chunk * block_size_) + start_offset;
+        if (!android::base::ReadFullyAtOffset(fd, buffer, bytes_to_read, offset)) {
+            PLOG(ERROR) << "read " << *source_device_;
+            // ReadFullyAtOffset sets errno.
+            return -1;
+        }
+    } else if (op->type == kCowZeroOp) {
+        memset(buffer, 0, bytes_to_read);
+    } else if (op->type == kCowReplaceOp) {
+        PartialSink partial_sink(buffer, bytes_to_read, start_offset);
+        if (!cow_->ReadData(*op, &partial_sink)) {
+            LOG(ERROR) << "CompressedSnapshotReader failed to read replace op";
+            errno = EIO;
+            return -1;
+        }
+    } else {
+        LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << op->type;
+        errno = EINVAL;
+        return -1;
+    }
+
+    // MemoryByteSink doesn't do anything in ReturnBuffer, so don't bother calling it.
+    return bytes_to_read;
+}
+
+off64_t CompressedSnapshotReader::Seek(off64_t offset, int whence) {
+    switch (whence) {
+        case SEEK_SET:
+            offset_ = offset;
+            break;
+        case SEEK_END:
+            offset_ = static_cast<off64_t>(block_device_size_) + offset;
+            break;
+        case SEEK_CUR:
+            offset_ += offset;
+            break;
+        default:
+            LOG(ERROR) << "Unrecognized seek whence: " << whence;
+            errno = EINVAL;
+            return -1;
+    }
+    return offset_;
+}
+
+uint64_t CompressedSnapshotReader::BlockDevSize() {
+    return block_device_size_;
+}
+
+bool CompressedSnapshotReader::Close() {
+    cow_ = nullptr;
+    source_fd_ = {};
+    return true;
+}
+
+bool CompressedSnapshotReader::IsSettingErrno() {
+    return true;
+}
+
+bool CompressedSnapshotReader::IsOpen() {
+    return cow_ != nullptr;
+}
+
+bool CompressedSnapshotReader::Flush() {
+    return true;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_reader.h b/fs_mgr/libsnapshot/snapshot_reader.h
index 1f2ffe2..a3e7e22 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.h
+++ b/fs_mgr/libsnapshot/snapshot_reader.h
@@ -16,7 +16,11 @@
 
 #pragma once
 
+#include <optional>
+#include <vector>
+
 #include <android-base/file.h>
+#include <libsnapshot/cow_reader.h>
 #include <payload_consumer/file_descriptor.h>
 
 namespace android {
@@ -46,5 +50,36 @@
     android::base::unique_fd fd_;
 };
 
+class CompressedSnapshotReader : public ReadOnlyFileDescriptor {
+  public:
+    bool SetCow(std::unique_ptr<CowReader>&& cow);
+    void SetSourceDevice(const std::string& source_device);
+    void SetBlockDeviceSize(uint64_t block_device_size);
+
+    ssize_t Read(void* buf, size_t count) override;
+    off64_t Seek(off64_t offset, int whence) override;
+    uint64_t BlockDevSize() override;
+    bool Close() override;
+    bool IsSettingErrno() override;
+    bool IsOpen() override;
+    bool Flush() override;
+
+  private:
+    ssize_t ReadBlock(uint64_t chunk, IByteSink* sink, size_t start_offset,
+                      const std::optional<uint64_t>& max_bytes = {});
+    android::base::borrowed_fd GetSourceFd();
+
+    std::unique_ptr<CowReader> cow_;
+    std::unique_ptr<ICowOpIter> op_iter_;
+    uint32_t block_size_ = 0;
+
+    std::optional<std::string> source_device_;
+    android::base::unique_fd source_fd_;
+    uint64_t block_device_size_ = 0;
+    off64_t offset_ = 0;
+
+    std::vector<const CowOperation*> ops_;
+};
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
new file mode 100644
index 0000000..4202d22
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -0,0 +1,166 @@
+// 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 <libsnapshot/snapshot.h>
+
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <libsnapshot/cow_writer.h>
+#include <payload_consumer/file_descriptor.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::unique_fd;
+using chromeos_update_engine::FileDescriptor;
+
+static constexpr uint32_t kBlockSize = 4096;
+static constexpr size_t kBlockCount = 10;
+
+class OfflineSnapshotTest : public ::testing::Test {
+  protected:
+    virtual void SetUp() override {
+        base_ = std::make_unique<TemporaryFile>();
+        ASSERT_GE(base_->fd, 0) << strerror(errno);
+
+        cow_ = std::make_unique<TemporaryFile>();
+        ASSERT_GE(cow_->fd, 0) << strerror(errno);
+
+        WriteBaseDevice();
+    }
+
+    virtual void TearDown() override {
+        base_ = nullptr;
+        cow_ = nullptr;
+        base_blocks_ = {};
+    }
+
+    void WriteBaseDevice() {
+        unique_fd random(open("/dev/urandom", O_RDONLY));
+        ASSERT_GE(random, 0);
+
+        for (size_t i = 0; i < kBlockCount; i++) {
+            std::string block(kBlockSize, 0);
+            ASSERT_TRUE(android::base::ReadFully(random, block.data(), block.size()));
+            ASSERT_TRUE(android::base::WriteFully(base_->fd, block.data(), block.size()));
+            base_blocks_.emplace_back(std::move(block));
+        }
+        ASSERT_EQ(fsync(base_->fd), 0);
+    }
+
+    void WriteCow(ISnapshotWriter* writer) {
+        std::string new_block = MakeNewBlockString();
+
+        ASSERT_TRUE(writer->AddCopy(3, 0));
+        ASSERT_TRUE(writer->AddRawBlocks(5, new_block.data(), new_block.size()));
+        ASSERT_TRUE(writer->AddZeroBlocks(7, 2));
+        ASSERT_TRUE(writer->Finalize());
+    }
+
+    void TestBlockReads(ISnapshotWriter* writer) {
+        auto reader = writer->OpenReader();
+        ASSERT_NE(reader, nullptr);
+
+        // Test that unchanged blocks are not modified.
+        std::unordered_set<size_t> changed_blocks = {3, 5, 7, 8};
+        for (size_t i = 0; i < kBlockCount; i++) {
+            if (changed_blocks.count(i)) {
+                continue;
+            }
+
+            std::string block(kBlockSize, 0);
+            ASSERT_EQ(reader->Seek(i * kBlockSize, SEEK_SET), i * kBlockSize);
+            ASSERT_EQ(reader->Read(block.data(), block.size()), kBlockSize);
+            ASSERT_EQ(block, base_blocks_[i]);
+        }
+
+        // Test that we can read back our modified blocks.
+        std::string block(kBlockSize, 0);
+        ASSERT_EQ(reader->Seek(3 * kBlockSize, SEEK_SET), 3 * kBlockSize);
+        ASSERT_EQ(reader->Read(block.data(), block.size()), kBlockSize);
+        ASSERT_EQ(block, base_blocks_[0]);
+
+        ASSERT_EQ(reader->Seek(5 * kBlockSize, SEEK_SET), 5 * kBlockSize);
+        ASSERT_EQ(reader->Read(block.data(), block.size()), kBlockSize);
+        ASSERT_EQ(block, MakeNewBlockString());
+
+        std::string two_blocks(kBlockSize * 2, 0x7f);
+        std::string zeroes(kBlockSize * 2, 0);
+        ASSERT_EQ(reader->Seek(7 * kBlockSize, SEEK_SET), 7 * kBlockSize);
+        ASSERT_EQ(reader->Read(two_blocks.data(), two_blocks.size()), two_blocks.size());
+        ASSERT_EQ(two_blocks, zeroes);
+    }
+
+    void TestByteReads(ISnapshotWriter* writer) {
+        auto reader = writer->OpenReader();
+        ASSERT_NE(reader, nullptr);
+
+        std::string blob(kBlockSize * 3, 'x');
+
+        // Test that we can read in the middle of a block.
+        static constexpr size_t kOffset = 970;
+        off64_t offset = 3 * kBlockSize + kOffset;
+        ASSERT_EQ(reader->Seek(0, SEEK_SET), 0);
+        ASSERT_EQ(reader->Seek(offset, SEEK_CUR), offset);
+        ASSERT_EQ(reader->Read(blob.data(), blob.size()), blob.size());
+        ASSERT_EQ(blob.substr(0, 100), base_blocks_[0].substr(kOffset, 100));
+        ASSERT_EQ(blob.substr(kBlockSize - kOffset, kBlockSize), base_blocks_[4]);
+        ASSERT_EQ(blob.substr(kBlockSize * 2 - kOffset, 100), MakeNewBlockString().substr(0, 100));
+        ASSERT_EQ(blob.substr(blob.size() - kOffset), base_blocks_[6].substr(0, kOffset));
+
+        // Pull a random byte from the compressed block.
+        char value;
+        offset = 5 * kBlockSize + 1000;
+        ASSERT_EQ(reader->Seek(offset, SEEK_SET), offset);
+        ASSERT_EQ(reader->Read(&value, sizeof(value)), sizeof(value));
+        ASSERT_EQ(value, MakeNewBlockString()[1000]);
+    }
+
+    void TestReads(ISnapshotWriter* writer) {
+        ASSERT_NO_FATAL_FAILURE(TestBlockReads(writer));
+        ASSERT_NO_FATAL_FAILURE(TestByteReads(writer));
+    }
+
+    std::string MakeNewBlockString() {
+        std::string new_block = "This is a new block";
+        new_block.resize(kBlockSize / 2, '*');
+        new_block.resize(kBlockSize, '!');
+        return new_block;
+    }
+
+    std::unique_ptr<TemporaryFile> base_;
+    std::unique_ptr<TemporaryFile> cow_;
+    std::vector<std::string> base_blocks_;
+};
+
+TEST_F(OfflineSnapshotTest, CompressedSnapshot) {
+    CowOptions options;
+    options.compression = "gz";
+    options.max_blocks = {kBlockCount};
+
+    unique_fd cow_fd(dup(cow_->fd));
+    ASSERT_GE(cow_fd, 0);
+
+    auto writer = std::make_unique<CompressedSnapshotWriter>(options);
+    writer->SetSourceDevice(base_->path);
+    ASSERT_TRUE(writer->SetCowDevice(std::move(cow_fd)));
+    ASSERT_TRUE(writer->Initialize());
+    ASSERT_NO_FATAL_FAILURE(WriteCow(writer.get()));
+    ASSERT_NO_FATAL_FAILURE(TestReads(writer.get()));
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index 5be3c10..26b9129 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -136,4 +136,14 @@
     return nullptr;
 }
 
+bool SnapshotManagerStub::MapAllSnapshots(const std::chrono::milliseconds&) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
+bool SnapshotManagerStub::UnmapAllSnapshots() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 7327629..7fc64e5 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -15,6 +15,7 @@
 #include <libsnapshot/snapshot.h>
 
 #include <fcntl.h>
+#include <signal.h>
 #include <sys/file.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -29,6 +30,7 @@
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
+#include <fs_mgr/file_wait.h>
 #include <fs_mgr/roots.h>
 #include <fs_mgr_dm_linear.h>
 #include <gtest/gtest.h>
@@ -118,6 +120,8 @@
         image_manager_ = sm->image_manager();
 
         test_device->set_slot_suffix("_a");
+
+        sm->set_use_first_stage_snapuserd(false);
     }
 
     void CleanupTestArtifacts() {
@@ -265,7 +269,7 @@
         if (!map_res) {
             return map_res;
         }
-        if (!InitializeCow(cow_device)) {
+        if (!InitializeKernelCow(cow_device)) {
             return AssertionFailure() << "Cannot zero fill " << cow_device;
         }
         if (!sm->UnmapCowImage(name)) {
@@ -907,6 +911,9 @@
         if (!result) {
             return AssertionFailure() << "Cannot open snapshot for writing: " << name;
         }
+        if (!result->Initialize()) {
+            return AssertionFailure() << "Cannot initialize snapshot for writing: " << name;
+        }
 
         if (writer) {
             *writer = std::move(result);
@@ -950,6 +957,9 @@
             if (!WriteRandomData(writer.get(), &hashes_[name])) {
                 return AssertionFailure() << "Unable to write random data to snapshot " << name;
             }
+            if (!writer->Finalize()) {
+                return AssertionFailure() << "Unable to finalize COW for " << name;
+            }
         } else {
             std::string path;
             auto res = MapUpdateSnapshot(name, &path);
@@ -1730,6 +1740,74 @@
     ASSERT_LT(res.required_size(), 15_MiB);
 }
 
+class AutoKill final {
+  public:
+    explicit AutoKill(pid_t pid) : pid_(pid) {}
+    ~AutoKill() {
+        if (pid_ > 0) kill(pid_, SIGKILL);
+    }
+
+    bool valid() const { return pid_ > 0; }
+
+  private:
+    pid_t pid_;
+};
+
+TEST_F(SnapshotUpdateTest, DaemonTransition) {
+    if (!IsCompressionEnabled()) {
+        GTEST_SKIP() << "Skipping Virtual A/B Compression test";
+    }
+
+    AutoKill auto_kill(StartFirstStageSnapuserd());
+    ASSERT_TRUE(auto_kill.valid());
+
+    // Ensure a connection to the second-stage daemon, but use the first-stage
+    // code paths thereafter.
+    ASSERT_TRUE(sm->EnsureSnapuserdConnected());
+    sm->set_use_first_stage_snapuserd(true);
+
+    AddOperationForPartitions();
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    ASSERT_TRUE(MapUpdateSnapshots());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+    ASSERT_TRUE(UnmapAll());
+
+    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    ASSERT_NE(init, nullptr);
+
+    ASSERT_TRUE(init->EnsureSnapuserdConnected());
+    init->set_use_first_stage_snapuserd(true);
+
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+    ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow-init", F_OK), 0);
+    ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), -1);
+
+    ASSERT_TRUE(init->PerformSecondStageTransition());
+
+    // The control device should have been renamed.
+    ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-user-cow-init", 10s));
+    ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), 0);
+}
+
+TEST_F(SnapshotUpdateTest, MapAllSnapshots) {
+    AddOperationForPartitions();
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(WriteSnapshotAndHash(name));
+    }
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+    ASSERT_TRUE(sm->MapAllSnapshots(10s));
+
+    // Read bytes back and verify they match the cache.
+    ASSERT_TRUE(IsPartitionUnchanged("sys_b"));
+}
+
 class FlashAfterUpdateTest : public SnapshotUpdateTest,
                              public WithParamInterface<std::tuple<uint32_t, bool>> {
   public:
diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp
index 9b1ab97..85ed156 100644
--- a/fs_mgr/libsnapshot/snapshot_writer.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer.cpp
@@ -56,9 +56,9 @@
 bool CompressedSnapshotWriter::SetCowDevice(android::base::unique_fd&& cow_device) {
     cow_device_ = std::move(cow_device);
     cow_ = std::make_unique<CowWriter>(options_);
-
-    return cow_->Initialize(cow_device_);
+    return true;
 }
+
 bool CompressedSnapshotWriter::Finalize() {
     return cow_->Finalize();
 }
@@ -68,7 +68,31 @@
 }
 
 std::unique_ptr<FileDescriptor> CompressedSnapshotWriter::OpenReader() {
-    return nullptr;
+    unique_fd cow_fd(dup(cow_device_.get()));
+    if (cow_fd < 0) {
+        PLOG(ERROR) << "dup COW device";
+        return nullptr;
+    }
+
+    auto cow = std::make_unique<CowReader>();
+    if (!cow->Parse(std::move(cow_fd))) {
+        LOG(ERROR) << "Unable to read COW";
+        return nullptr;
+    }
+
+    auto reader = std::make_unique<CompressedSnapshotReader>();
+    if (!reader->SetCow(std::move(cow))) {
+        LOG(ERROR) << "Unable to initialize COW reader";
+        return nullptr;
+    }
+    if (source_device_) {
+        reader->SetSourceDevice(*source_device_);
+    }
+
+    const auto& cow_options = options();
+    reader->SetBlockDeviceSize(*cow_options.max_blocks * cow_options.block_size);
+
+    return reader;
 }
 
 bool CompressedSnapshotWriter::EmitCopy(uint64_t new_block, uint64_t old_block) {
@@ -88,6 +112,17 @@
     return cow_->AddLabel(label);
 }
 
+bool CompressedSnapshotWriter::Initialize() {
+    return cow_->Initialize(cow_device_, CowWriter::OpenMode::WRITE);
+}
+
+bool CompressedSnapshotWriter::InitializeAppend(std::optional<uint64_t> label) {
+    if (label) {
+        return cow_->InitializeAppend(cow_device_, *label);
+    }
+    return cow_->Initialize(cow_device_, CowWriter::OpenMode::APPEND);
+}
+
 OnlineKernelSnapshotWriter::OnlineKernelSnapshotWriter(const CowOptions& options)
     : ISnapshotWriter(options) {}
 
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 6e772ad..40f26d6 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -379,7 +379,11 @@
         struct disk_exception* de =
                 reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
 
-        if (cow_op->type == kCowFooterOp || cow_op->type == kCowLabelOp) continue;
+        if (cow_op->type == kCowFooterOp || cow_op->type == kCowLabelOp) {
+            cowop_iter_->Next();
+            continue;
+        }
+
         if (!(cow_op->type == kCowReplaceOp || cow_op->type == kCowZeroOp ||
               cow_op->type == kCowCopyOp)) {
             LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp
index 532e585..5650139 100644
--- a/fs_mgr/libsnapshot/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_client.cpp
@@ -51,6 +51,25 @@
     return true;
 }
 
+pid_t StartFirstStageSnapuserd() {
+    pid_t pid = fork();
+    if (pid < 0) {
+        PLOG(ERROR) << "fork failed";
+        return pid;
+    }
+    if (pid != 0) {
+        return pid;
+    }
+
+    std::string arg0 = "/system/bin/snapuserd";
+    std::string arg1 = kSnapuserdSocketFirstStage;
+    char* const argv[] = {arg0.data(), arg1.data(), nullptr};
+    if (execv(arg0.c_str(), argv) < 0) {
+        PLOG(FATAL) << "execv failed";
+    }
+    return pid;
+}
+
 SnapuserdClient::SnapuserdClient(android::base::unique_fd&& sockfd) : sockfd_(std::move(sockfd)) {}
 
 static inline bool IsRetryErrno() {
@@ -123,34 +142,32 @@
     return true;
 }
 
-std::string SnapuserdClient::Receivemsg() {
-    int ret;
-    struct timeval tv;
-    fd_set set;
-    char msg[PACKET_SIZE];
-    std::string msgStr("fail");
-
-    tv.tv_sec = 2;
-    tv.tv_usec = 0;
-    FD_ZERO(&set);
-    FD_SET(sockfd_, &set);
-    ret = select(sockfd_ + 1, &set, NULL, NULL, &tv);
-    if (ret == -1) {  // select failed
-        LOG(ERROR) << "Snapuserd:client: Select call failed";
-    } else if (ret == 0) {  // timeout
-        LOG(ERROR) << "Snapuserd:client: Select call timeout";
-    } else {
-        ret = TEMP_FAILURE_RETRY(recv(sockfd_, msg, PACKET_SIZE, 0));
-        if (ret < 0) {
-            PLOG(ERROR) << "Snapuserd:client: recv failed";
-        } else if (ret == 0) {
-            LOG(DEBUG) << "Snapuserd:client disconnected";
-        } else {
-            msgStr.clear();
-            msgStr = msg;
-        }
+bool SnapuserdClient::WaitForDeviceDelete(const std::string& control_device) {
+    std::string msg = "delete," + control_device;
+    if (!Sendmsg(msg)) {
+        LOG(ERROR) << "Failed to send message " << msg << " to snapuserd";
+        return false;
     }
-    return msgStr;
+    std::string response = Receivemsg();
+    if (response != "success") {
+        LOG(ERROR) << "Failed waiting to delete device " << control_device;
+        return false;
+    }
+    return true;
+}
+
+std::string SnapuserdClient::Receivemsg() {
+    char msg[PACKET_SIZE];
+    ssize_t ret = TEMP_FAILURE_RETRY(recv(sockfd_, msg, sizeof(msg), 0));
+    if (ret < 0) {
+        PLOG(ERROR) << "Snapuserd:client: recv failed";
+        return {};
+    }
+    if (ret == 0) {
+        LOG(DEBUG) << "Snapuserd:client disconnected";
+        return {};
+    }
+    return std::string(msg, ret);
 }
 
 bool SnapuserdClient::StopSnapuserd() {
diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp
index 48a3b2a..6a89218 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_server.cpp
@@ -36,6 +36,7 @@
     if (input == "start") return DaemonOperations::START;
     if (input == "stop") return DaemonOperations::STOP;
     if (input == "query") return DaemonOperations::QUERY;
+    if (input == "delete") return DaemonOperations::DELETE;
 
     return DaemonOperations::INVALID;
 }
@@ -68,33 +69,25 @@
     }
 }
 
-// new thread
-void SnapuserdServer::ThreadStart(std::string cow_device, std::string backing_device,
-                                  std::string control_device) {
-    Snapuserd snapd(cow_device, backing_device, control_device);
-    if (!snapd.Init()) {
-        LOG(ERROR) << "Snapuserd: Init failed";
-        return;
-    }
-
-    while (StopRequested() == false) {
-        int ret = snapd.Run();
-
-        if (ret < 0) {
-            LOG(ERROR) << "Snapuserd: Thread terminating as control device is de-registered";
-            break;
-        }
-    }
-}
-
 void SnapuserdServer::ShutdownThreads() {
     StopThreads();
 
-    for (auto& client : dm_users_) {
-        auto& th = client->GetThreadHandler();
-
-        if (th->joinable()) th->join();
+    // Acquire the thread list within the lock.
+    std::vector<std::unique_ptr<DmUserHandler>> dm_users;
+    {
+        std::lock_guard<std::mutex> guard(lock_);
+        dm_users = std::move(dm_users_);
     }
+
+    for (auto& client : dm_users) {
+        auto& th = client->thread();
+
+        if (th.joinable()) th.join();
+    }
+}
+
+const std::string& DmUserHandler::GetControlDevice() const {
+    return snapuserd_->GetControlDevicePath();
 }
 
 bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
@@ -135,10 +128,25 @@
             // start,<cow_device_path>,<source_device_path>,<control_device>
             //
             // Start the new thread which binds to dm-user misc device
-            auto handler = std::make_unique<DmUserHandler>();
-            handler->SetThreadHandler(
-                    std::bind(&SnapuserdServer::ThreadStart, this, out[1], out[2], out[3]));
-            dm_users_.push_back(std::move(handler));
+            if (out.size() != 4) {
+                LOG(ERROR) << "Malformed start message, " << out.size() << " parts";
+                return Sendmsg(fd, "fail");
+            }
+
+            auto snapuserd = std::make_unique<Snapuserd>(out[1], out[2], out[3]);
+            if (!snapuserd->Init()) {
+                LOG(ERROR) << "Failed to initialize Snapuserd";
+                return Sendmsg(fd, "fail");
+            }
+
+            auto handler = std::make_unique<DmUserHandler>(std::move(snapuserd));
+            {
+                std::lock_guard<std::mutex> lock(lock_);
+
+                handler->thread() =
+                        std::thread(std::bind(&SnapuserdServer::RunThread, this, handler.get()));
+                dm_users_.push_back(std::move(handler));
+            }
             return Sendmsg(fd, "success");
         }
         case DaemonOperations::STOP: {
@@ -162,6 +170,18 @@
             // be ready to receive control message.
             return Sendmsg(fd, GetDaemonStatus());
         }
+        case DaemonOperations::DELETE: {
+            // Message format:
+            // delete,<cow_device_path>
+            if (out.size() != 2) {
+                LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
+                return Sendmsg(fd, "fail");
+            }
+            if (!WaitForDelete(out[1])) {
+                return Sendmsg(fd, "fail");
+            }
+            return Sendmsg(fd, "success");
+        }
         default: {
             LOG(ERROR) << "Received unknown message type from client";
             Sendmsg(fd, "fail");
@@ -170,6 +190,27 @@
     }
 }
 
+void SnapuserdServer::RunThread(DmUserHandler* handler) {
+    LOG(INFO) << "Entering thread for handler: " << handler->GetControlDevice();
+
+    while (!StopRequested()) {
+        if (handler->snapuserd()->Run() < 0) {
+            LOG(INFO) << "Snapuserd: Thread terminating as control device is de-registered";
+            break;
+        }
+    }
+
+    LOG(INFO) << "Exiting thread for handler: " << handler->GetControlDevice();
+
+    if (auto client = RemoveHandler(handler->GetControlDevice())) {
+        // The main thread did not receive a WaitForDelete request for this
+        // control device. Since we transferred ownership within the lock,
+        // we know join() was never called, and will never be called. We can
+        // safely detach here.
+        client->thread().detach();
+    }
+}
+
 bool SnapuserdServer::Start(const std::string& socketname) {
     sockfd_.reset(android_get_control_socket(socketname.c_str()));
     if (sockfd_ >= 0) {
@@ -260,5 +301,37 @@
     SetTerminating();
 }
 
+std::unique_ptr<DmUserHandler> SnapuserdServer::RemoveHandler(const std::string& control_device) {
+    std::unique_ptr<DmUserHandler> client;
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        auto iter = dm_users_.begin();
+        while (iter != dm_users_.end()) {
+            if ((*iter)->GetControlDevice() == control_device) {
+                client = std::move(*iter);
+                iter = dm_users_.erase(iter);
+                break;
+            }
+            iter++;
+        }
+    }
+    return client;
+}
+
+bool SnapuserdServer::WaitForDelete(const std::string& control_device) {
+    auto client = RemoveHandler(control_device);
+
+    // Client already deleted.
+    if (!client) {
+        return true;
+    }
+
+    auto& th = client->thread();
+    if (th.joinable()) {
+        th.join();
+    }
+    return true;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index d32b61e..4cae83a 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -91,7 +91,7 @@
     }
 }
 
-Return InitializeCow(const std::string& device) {
+Return InitializeKernelCow(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.
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index e69bdad..482888a 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -112,7 +112,7 @@
         android::fs_mgr::MetadataBuilder* builder, const std::string& suffix);
 
 // Initialize a device before using it as the COW device for a dm-snapshot device.
-Return InitializeCow(const std::string& device);
+Return InitializeKernelCow(const std::string& device);
 
 // "Atomically" write string to file. This is done by a series of actions:
 // 1. Write to path + ".tmp"
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index e995888..f5bbe35 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -1380,9 +1380,9 @@
 check_eq "${VENDOR_DEVT}" "`adb_sh stat --format=%D /vendor/hello </dev/null`" vendor devt after reboot
 check_eq "${SYSTEM_INO}" "`adb_sh stat --format=%i /system/hello </dev/null`" system inode after reboot
 check_eq "${VENDOR_INO}" "`adb_sh stat --format=%i /vendor/hello </dev/null`" vendor inode after reboot
-check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/bin/stat </dev/null`" base system devt after reboot
-check_eq "${BASE_VENDOR_DEVT}" "`adb_sh stat --format=%D /vendor/bin/stat </dev/null`" base system devt after reboot
-check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" devt for su after reboot
+check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/bin/stat </dev/null`" --warning base system devt after reboot
+check_eq "${BASE_VENDOR_DEVT}" "`adb_sh stat --format=%D /vendor/bin/stat </dev/null`" --warning base vendor devt after reboot
+check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" --warning devt for su after reboot
 
 # Feed log with selinux denials as a result of overlays
 adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null
@@ -1509,8 +1509,8 @@
 
   check_eq "${SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/hello </dev/null`" system devt after reboot
   check_eq "${SYSTEM_INO}" "`adb_sh stat --format=%i /system/hello </dev/null`" system inode after reboot
-  check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/bin/stat </dev/null`" base system devt after reboot
-  check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" devt for su after reboot
+  check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/bin/stat </dev/null`" --warning base system devt after reboot
+  check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" --warning devt for su after reboot
 
 fi
 
diff --git a/init/Android.mk b/init/Android.mk
index 895f50f..ac31ef1 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -78,7 +78,6 @@
 LOCAL_POST_INSTALL_CMD := mkdir -p \
     $(TARGET_RAMDISK_OUT)/debug_ramdisk \
     $(TARGET_RAMDISK_OUT)/dev \
-    $(TARGET_RAMDISK_OUT)/first_stage_ramdisk \
     $(TARGET_RAMDISK_OUT)/mnt \
     $(TARGET_RAMDISK_OUT)/proc \
     $(TARGET_RAMDISK_OUT)/second_stage_resources \
diff --git a/init/README.md b/init/README.md
index 6439393..ab6a885 100644
--- a/init/README.md
+++ b/init/README.md
@@ -172,9 +172,12 @@
   This option connects stdin, stdout, and stderr to the console. It is mutually exclusive with the
   stdio_to_kmsg option, which only connects stdout and stderr to kmsg.
 
-`critical`
+`critical [window=<fatal crash window mins>] [target=<fatal reboot target>]`
 > This is a device-critical service. If it exits more than four times in
-  four minutes or before boot completes, the device will reboot into bootloader.
+  _fatal crash window mins_ minutes or before boot completes, the device
+  will reboot into _fatal reboot target_.
+  The default value of _fatal crash window mins_ is 4, and default value
+  of _fatal reboot target_ is 'bootloader'.
 
 `disabled`
 > This service will not automatically start with its class.
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 554f301..d2a952f 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -99,6 +99,34 @@
     return cmdline.find("androidboot.force_normal_boot=1") != std::string::npos;
 }
 
+// Move e2fsck before switching root, so that it is available at the same path
+// after switching root.
+void PrepareSwitchRoot() {
+    constexpr const char* src = "/system/bin/e2fsck";
+    constexpr const char* dst = "/first_stage_ramdisk/system/bin/e2fsck";
+
+    if (access(dst, X_OK) == 0) {
+        LOG(INFO) << dst << " already exists and it can be executed";
+        return;
+    }
+
+    if (access(src, F_OK) != 0) {
+        PLOG(INFO) << "Not moving " << src << " because it cannot be accessed";
+        return;
+    }
+
+    auto dst_dir = android::base::Dirname(dst);
+    std::error_code ec;
+    if (!fs::create_directories(dst_dir, ec)) {
+        LOG(FATAL) << "Cannot create " << dst_dir << ": " << ec.message();
+    }
+    if (rename(src, dst) != 0) {
+        PLOG(FATAL) << "Cannot move " << src << " to " << dst
+                    << ". Either install e2fsck.ramdisk so that it is at the correct place (" << dst
+                    << "), or make ramdisk writable";
+    }
+}
+
 }  // namespace
 
 std::string GetModuleLoadList(bool recovery, const std::string& dir_path) {
@@ -298,6 +326,7 @@
 
     if (ForceNormalBoot(cmdline)) {
         mkdir("/first_stage_ramdisk", 0755);
+        PrepareSwitchRoot();
         // SwitchRoot() must be called with a mount point as the target, so we bind mount the
         // target directory to itself here.
         if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
diff --git a/init/host_init_stubs.h b/init/host_init_stubs.h
index caa8f8d..2a8bf6c 100644
--- a/init/host_init_stubs.h
+++ b/init/host_init_stubs.h
@@ -20,6 +20,7 @@
 #include <sys/socket.h>
 #include <sys/types.h>
 
+#include <optional>
 #include <string>
 
 #include <android-base/properties.h>
@@ -41,7 +42,7 @@
 }
 
 // reboot_utils.h
-inline void SetFatalRebootTarget() {}
+inline void SetFatalRebootTarget(const std::optional<std::string>& = std::nullopt) {}
 inline void __attribute__((noreturn)) InitFatalReboot(int signal_number) {
     abort();
 }
diff --git a/init/init.cpp b/init/init.cpp
index ea04494..c6f2066 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -53,6 +53,7 @@
 #include <keyutils.h>
 #include <libavb/libavb.h>
 #include <libgsi/libgsi.h>
+#include <libsnapshot/snapshot.h>
 #include <processgroup/processgroup.h>
 #include <processgroup/setup.h>
 #include <selinux/android.h>
@@ -94,6 +95,7 @@
 using android::base::Timer;
 using android::base::Trim;
 using android::fs_mgr::AvbHandle;
+using android::snapshot::SnapshotManager;
 
 namespace android {
 namespace init {
@@ -722,6 +724,32 @@
     }
 }
 
+static Result<void> TransitionSnapuserdAction(const BuiltinArguments&) {
+    if (!SnapshotManager::IsSnapshotManagerNeeded() ||
+        !android::base::GetBoolProperty(android::snapshot::kVirtualAbCompressionProp, false)) {
+        return {};
+    }
+
+    auto sm = SnapshotManager::New();
+    if (!sm) {
+        LOG(FATAL) << "Failed to create SnapshotManager, will not transition snapuserd";
+        return {};
+    }
+
+    ServiceList& service_list = ServiceList::GetInstance();
+    auto svc = service_list.FindService("snapuserd");
+    if (!svc) {
+        LOG(FATAL) << "Failed to find snapuserd service, aborting transition";
+        return {};
+    }
+    svc->Start();
+
+    if (!sm->PerformSecondStageTransition()) {
+        LOG(FATAL) << "Failed to transition snapuserd to second-stage";
+    }
+    return {};
+}
+
 int SecondStageMain(int argc, char** argv) {
     if (REBOOT_BOOTLOADER_ON_PANIC) {
         InstallRebootSignalHandlers();
@@ -847,6 +875,7 @@
     SetProperty(gsi::kGsiInstalledProp, is_installed);
 
     am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
+    am.QueueBuiltinAction(TransitionSnapuserdAction, "TransitionSnapuserd");
     am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
     am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
     am.QueueEventTrigger("early-init");
diff --git a/init/reboot_utils.cpp b/init/reboot_utils.cpp
index 76460a5..98f6857 100644
--- a/init/reboot_utils.cpp
+++ b/init/reboot_utils.cpp
@@ -19,6 +19,7 @@
 #include <sys/syscall.h>
 #include <unistd.h>
 
+#include <optional>
 #include <string>
 
 #include <android-base/file.h>
@@ -37,7 +38,7 @@
 static std::string init_fatal_reboot_target = "bootloader";
 static bool init_fatal_panic = false;
 
-void SetFatalRebootTarget() {
+void SetFatalRebootTarget(const std::optional<std::string>& reboot_target) {
     std::string cmdline;
     android::base::ReadFileToString("/proc/cmdline", &cmdline);
     cmdline = android::base::Trim(cmdline);
@@ -45,6 +46,11 @@
     const char kInitFatalPanicString[] = "androidboot.init_fatal_panic=true";
     init_fatal_panic = cmdline.find(kInitFatalPanicString) != std::string::npos;
 
+    if (reboot_target) {
+        init_fatal_reboot_target = *reboot_target;
+        return;
+    }
+
     const char kRebootTargetString[] = "androidboot.init_fatal_reboot_target=";
     auto start_pos = cmdline.find(kRebootTargetString);
     if (start_pos == std::string::npos) {
diff --git a/init/reboot_utils.h b/init/reboot_utils.h
index 05bb9ae..a0023b9 100644
--- a/init/reboot_utils.h
+++ b/init/reboot_utils.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <optional>
 #include <string>
 
 #define PROC_SYSRQ "/proc/sysrq-trigger"
@@ -23,7 +24,7 @@
 namespace android {
 namespace init {
 
-void SetFatalRebootTarget();
+void SetFatalRebootTarget(const std::optional<std::string>& reboot_target = std::nullopt);
 // Determines whether the system is capable of rebooting. This is conservative,
 // so if any of the attempts to determine this fail, it will still return true.
 bool IsRebootCapable();
diff --git a/init/security.cpp b/init/security.cpp
index 2450d65..ac784a3 100644
--- a/init/security.cpp
+++ b/init/security.cpp
@@ -19,6 +19,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/perf_event.h>
+#include <selinux/selinux.h>
 #include <sys/ioctl.h>
 #include <sys/syscall.h>
 #include <unistd.h>
@@ -222,6 +223,19 @@
 // supporting kernels that precede the perf_event_open hooks (Android common
 // kernels 4.4 and 4.9).
 Result<void> TestPerfEventSelinuxAction(const BuiltinArguments&) {
+    // Special case: for *development devices* that boot with permissive
+    // SELinux, treat the LSM hooks as present for the effect of lowering the
+    // perf_event_paranoid sysctl. The sysprop is reused for pragmatic reasons,
+    // as there no existing way for init rules to check for permissive boot at
+    // the time of writing.
+    if (ALLOW_PERMISSIVE_SELINUX) {
+        if (!security_getenforce()) {
+            LOG(INFO) << "Permissive SELinux boot, forcing sys.init.perf_lsm_hooks to 1.";
+            SetProperty("sys.init.perf_lsm_hooks", "1");
+            return {};
+        }
+    }
+
     // Use a trivial event that will be configured, but not started.
     struct perf_event_attr pe = {
             .type = PERF_TYPE_SOFTWARE,
diff --git a/init/service.cpp b/init/service.cpp
index 68365b3..ecc86d9 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -45,6 +45,7 @@
 #include <android/api-level.h>
 
 #include "mount_namespace.h"
+#include "reboot_utils.h"
 #include "selinux.h"
 #else
 #include "host_init_stubs.h"
@@ -312,20 +313,24 @@
 #endif
     const bool is_process_updatable = !pre_apexd_ && is_apex_updatable;
 
-    // If we crash > 4 times in 4 minutes or before boot_completed,
+    // If we crash > 4 times in 'fatal_crash_window_' minutes or before boot_completed,
     // reboot into bootloader or set crashing property
     boot_clock::time_point now = boot_clock::now();
     if (((flags_ & SVC_CRITICAL) || is_process_updatable) && !(flags_ & SVC_RESTART)) {
         bool boot_completed = android::base::GetBoolProperty("sys.boot_completed", false);
-        if (now < time_crashed_ + 4min || !boot_completed) {
+        if (now < time_crashed_ + fatal_crash_window_ || !boot_completed) {
             if (++crash_count_ > 4) {
+                auto exit_reason = boot_completed ?
+                    "in " + std::to_string(fatal_crash_window_.count()) + " minutes" :
+                    "before boot completed";
                 if (flags_ & SVC_CRITICAL) {
-                    // Aborts into bootloader
+                    // Aborts into `fatal_reboot_target_'.
+                    SetFatalRebootTarget(fatal_reboot_target_);
                     LOG(FATAL) << "critical process '" << name_ << "' exited 4 times "
-                               << (boot_completed ? "in 4 minutes" : "before boot completed");
+                               << exit_reason;
                 } else {
                     LOG(ERROR) << "updatable process '" << name_ << "' exited 4 times "
-                               << (boot_completed ? "in 4 minutes" : "before boot completed");
+                               << exit_reason;
                     // Notifies update_verifier and apexd
                     SetProperty("sys.init.updatable_crashing_process_name", name_);
                     SetProperty("sys.init.updatable_crashing", "1");
diff --git a/init/service.h b/init/service.h
index 34ed5ef..bc5c90f 100644
--- a/init/service.h
+++ b/init/service.h
@@ -155,6 +155,8 @@
     android::base::boot_clock::time_point time_started_;  // time of last start
     android::base::boot_clock::time_point time_crashed_;  // first crash within inspection window
     int crash_count_;                     // number of times crashed within window
+    std::chrono::minutes fatal_crash_window_ = 4min;  // fatal() when more than 4 crashes in it
+    std::optional<std::string> fatal_reboot_target_;  // reboot target of fatal handler
 
     std::optional<CapSet> capabilities_;
     ProcessAttributes proc_attr_;
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index bdac077..97621da 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -93,6 +93,39 @@
 }
 
 Result<void> ServiceParser::ParseCritical(std::vector<std::string>&& args) {
+    std::optional<std::string> fatal_reboot_target;
+    std::optional<std::chrono::minutes> fatal_crash_window;
+
+    for (auto it = args.begin() + 1; it != args.end(); ++it) {
+        auto arg = android::base::Split(*it, "=");
+        if (arg.size() != 2) {
+            return Error() << "critical: Argument '" << *it << "' is not supported";
+        } else if (arg[0] == "target") {
+            fatal_reboot_target = arg[1];
+        } else if (arg[0] == "window") {
+            int minutes;
+            auto window = ExpandProps(arg[1]);
+            if (!window.ok()) {
+                return Error() << "critical: Could not expand argument ': " << arg[1];
+            }
+            if (*window == "off") {
+                return {};
+            }
+            if (!ParseInt(*window, &minutes, 0)) {
+                return Error() << "critical: 'fatal_crash_window' must be an integer > 0";
+            }
+            fatal_crash_window = std::chrono::minutes(minutes);
+        } else {
+            return Error() << "critical: Argument '" << *it << "' is not supported";
+        }
+    }
+
+    if (fatal_reboot_target) {
+        service_->fatal_reboot_target_ = *fatal_reboot_target;
+    }
+    if (fatal_crash_window) {
+        service_->fatal_crash_window_ = *fatal_crash_window;
+    }
     service_->flags_ |= SVC_CRITICAL;
     return {};
 }
@@ -506,7 +539,7 @@
         {"capabilities",            {0,     kMax, &ServiceParser::ParseCapabilities}},
         {"class",                   {1,     kMax, &ServiceParser::ParseClass}},
         {"console",                 {0,     1,    &ServiceParser::ParseConsole}},
-        {"critical",                {0,     0,    &ServiceParser::ParseCritical}},
+        {"critical",                {0,     2,    &ServiceParser::ParseCritical}},
         {"disabled",                {0,     0,    &ServiceParser::ParseDisabled}},
         {"enter_namespace",         {2,     2,    &ServiceParser::ParseEnterNamespace}},
         {"file",                    {2,     2,    &ServiceParser::ParseFile}},
diff --git a/libbacktrace b/libbacktrace
deleted file mode 120000
index 571194c..0000000
--- a/libbacktrace
+++ /dev/null
@@ -1 +0,0 @@
-../unwinding/libbacktrace
\ No newline at end of file
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 0c75dc7..284c0b9 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -34,6 +34,7 @@
     vendor_available: true,
     recovery_available: true,
     ramdisk_available: true,
+    vendor_ramdisk_available: true,
     host_supported: true,
     apex_available: [
         "//apex_available:platform",
@@ -61,6 +62,7 @@
     vendor_available: true,
     recovery_available: true,
     ramdisk_available: true,
+    vendor_ramdisk_available: true,
     host_supported: true,
     native_bridge_supported: true,
     apex_available: [
@@ -146,6 +148,7 @@
         support_system_process: true,
     },
     recovery_available: true,
+    vendor_ramdisk_available: true,
     host_supported: true,
     apex_available: [
         "//apex_available:platform",
diff --git a/liblog b/liblog
deleted file mode 120000
index 71443ae..0000000
--- a/liblog
+++ /dev/null
@@ -1 +0,0 @@
-../logging/liblog
\ No newline at end of file
diff --git a/libpackagelistparser/Android.bp b/libpackagelistparser/Android.bp
index b56dcdb..c3f8692 100644
--- a/libpackagelistparser/Android.bp
+++ b/libpackagelistparser/Android.bp
@@ -1,5 +1,7 @@
 cc_library {
     name: "libpackagelistparser",
+    ramdisk_available: true,
+    vendor_ramdisk_available: true,
     recovery_available: true,
     srcs: ["packagelistparser.cpp"],
     shared_libs: ["liblog"],
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index bda11e9..71e2b91 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -1,6 +1,8 @@
 cc_library_headers {
     name: "libprocessgroup_headers",
     vendor_available: true,
+    ramdisk_available: true,
+    vendor_ramdisk_available: true,
     recovery_available: true,
     host_supported: true,
     native_bridge_supported: true,
@@ -30,6 +32,8 @@
     name: "libprocessgroup",
     host_supported: true,
     native_bridge_supported: true,
+    ramdisk_available: true,
+    vendor_ramdisk_available: true,
     recovery_available: true,
     vendor_available: true,
     vndk: {
diff --git a/libprocessgroup/cgrouprc/Android.bp b/libprocessgroup/cgrouprc/Android.bp
index a107baa..bb59942 100644
--- a/libprocessgroup/cgrouprc/Android.bp
+++ b/libprocessgroup/cgrouprc/Android.bp
@@ -15,6 +15,8 @@
 cc_library {
     name: "libcgrouprc",
     host_supported: true,
+    ramdisk_available: true,
+    vendor_ramdisk_available: true,
     recovery_available: true,
     // Do not ever mark this as vendor_available; otherwise, vendor modules
     // that links to the static library will behave unexpectedly. All on-device
diff --git a/libprocessgroup/cgrouprc_format/Android.bp b/libprocessgroup/cgrouprc_format/Android.bp
index 559a869..6428930 100644
--- a/libprocessgroup/cgrouprc_format/Android.bp
+++ b/libprocessgroup/cgrouprc_format/Android.bp
@@ -15,6 +15,8 @@
 cc_library_static {
     name: "libcgrouprc_format",
     host_supported: true,
+    ramdisk_available: true,
+    vendor_ramdisk_available: true,
     recovery_available: true,
     native_bridge_supported: true,
     srcs: [
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index 4518487..5b7a28a 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -32,13 +32,6 @@
       "Mode": "0700",
       "UID": "root",
       "GID": "system"
-    },
-    {
-      "Controller": "schedtune",
-      "Path": "/dev/stune",
-      "Mode": "0755",
-      "UID": "system",
-      "GID": "system"
     }
   ],
   "Cgroups2": {
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index c4dbf8e..ea0064f 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -31,16 +31,6 @@
       "File": "memory.swappiness"
     },
     {
-      "Name": "STuneBoost",
-      "Controller": "schedtune",
-      "File": "schedtune.boost"
-    },
-    {
-      "Name": "STunePreferIdle",
-      "Controller": "schedtune",
-      "File": "schedtune.prefer_idle"
-    },
-    {
       "Name": "UClampMin",
       "Controller": "cpu",
       "File": "cpu.uclamp.min"
@@ -51,6 +41,11 @@
       "File": "cpu.uclamp.max"
     },
     {
+      "Name": "UClampLatencySensitive",
+      "Controller": "cpu",
+      "File": "cpu.uclamp.latency_sensitive"
+    },
+    {
       "Name": "FreezerState",
       "Controller": "freezer",
       "File": "cgroup.freeze"
@@ -65,7 +60,7 @@
           "Name": "JoinCgroup",
           "Params":
           {
-            "Controller": "schedtune",
+            "Controller": "cpu",
             "Path": "background"
           }
         }
@@ -104,7 +99,7 @@
           "Name": "JoinCgroup",
           "Params":
           {
-            "Controller": "schedtune",
+            "Controller": "cpu",
             "Path": ""
           }
         }
@@ -117,7 +112,7 @@
           "Name": "JoinCgroup",
           "Params":
           {
-            "Controller": "schedtune",
+            "Controller": "cpu",
             "Path": "foreground"
           }
         }
@@ -130,7 +125,7 @@
           "Name": "JoinCgroup",
           "Params":
           {
-            "Controller": "schedtune",
+            "Controller": "cpu",
             "Path": "top-app"
           }
         }
@@ -143,7 +138,7 @@
           "Name": "JoinCgroup",
           "Params":
           {
-            "Controller": "schedtune",
+            "Controller": "cpu",
             "Path": "rt"
           }
         }
@@ -156,12 +151,25 @@
           "Name": "JoinCgroup",
           "Params":
           {
-            "Controller": "schedtune",
+            "Controller": "cpu",
             "Path": "camera-daemon"
           }
         }
       ]
     },
+    {
+      "Name": "NNApiHALPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpu",
+            "Path": "nnapi-hal"
+          }
+        }
+      ]
+    },
 
     {
       "Name": "CpuPolicySpread",
@@ -170,7 +178,7 @@
           "Name": "SetAttribute",
           "Params":
           {
-            "Name": "STunePreferIdle",
+            "Name": "UClampLatencySensitive",
             "Value": "1"
           }
         }
@@ -183,7 +191,7 @@
           "Name": "SetAttribute",
           "Params":
           {
-            "Name": "STunePreferIdle",
+            "Name": "UClampLatencySensitive",
             "Value": "0"
           }
         }
diff --git a/libprocinfo b/libprocinfo
deleted file mode 120000
index dec8cf8..0000000
--- a/libprocinfo
+++ /dev/null
@@ -1 +0,0 @@
-../libprocinfo
\ No newline at end of file
diff --git a/libsystem/Android.bp b/libsystem/Android.bp
index db61669..12c946c 100644
--- a/libsystem/Android.bp
+++ b/libsystem/Android.bp
@@ -2,6 +2,7 @@
     name: "libsystem_headers",
     vendor_available: true,
     recovery_available: true,
+    vendor_ramdisk_available: true,
     host_supported: true,
     native_bridge_supported: true,
     apex_available: [
diff --git a/libunwindstack b/libunwindstack
deleted file mode 120000
index 9a12403..0000000
--- a/libunwindstack
+++ /dev/null
@@ -1 +0,0 @@
-../unwinding/libunwindstack
\ No newline at end of file
diff --git a/libutils/Android.bp b/libutils/Android.bp
index e53e89b..8ee16f3 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -16,6 +16,7 @@
     name: "libutils_headers",
     vendor_available: true,
     recovery_available: true,
+    vendor_ramdisk_available: true,
     host_supported: true,
     native_bridge_supported: true,
     apex_available: [
diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp
index b6e457b..843a81a 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -359,49 +359,6 @@
 // UTF-8
 // --------------------------------------------------------------------------
 
-ssize_t utf8_length(const char *src)
-{
-    const char *cur = src;
-    size_t ret = 0;
-    while (*cur != '\0') {
-        const char first_char = *cur++;
-        if ((first_char & 0x80) == 0) { // ASCII
-            ret += 1;
-            continue;
-        }
-        // (UTF-8's character must not be like 10xxxxxx,
-        //  but 110xxxxx, 1110xxxx, ... or 1111110x)
-        if ((first_char & 0x40) == 0) {
-            return -1;
-        }
-
-        int32_t mask, to_ignore_mask;
-        size_t num_to_read = 0;
-        char32_t utf32 = 0;
-        for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0x80;
-             num_to_read < 5 && (first_char & mask);
-             num_to_read++, to_ignore_mask |= mask, mask >>= 1) {
-            if ((*cur & 0xC0) != 0x80) { // must be 10xxxxxx
-                return -1;
-            }
-            // 0x3F == 00111111
-            utf32 = (utf32 << 6) + (*cur++ & 0x3F);
-        }
-        // "first_char" must be (110xxxxx - 11110xxx)
-        if (num_to_read == 5) {
-            return -1;
-        }
-        to_ignore_mask |= mask;
-        utf32 |= ((~to_ignore_mask) & first_char) << (6 * (num_to_read - 1));
-        if (utf32 > kUnicodeMaxCodepoint) {
-            return -1;
-        }
-
-        ret += num_to_read;
-    }
-    return ret;
-}
-
 ssize_t utf16_to_utf8_length(const char16_t *src, size_t src_len)
 {
     if (src == nullptr || src_len == 0) {
diff --git a/libutils/include/utils/Unicode.h b/libutils/include/utils/Unicode.h
index fc6712d..0087383 100644
--- a/libutils/include/utils/Unicode.h
+++ b/libutils/include/utils/Unicode.h
@@ -111,24 +111,6 @@
 void utf16_to_utf8(const char16_t* src, size_t src_len, char* dst, size_t dst_len);
 
 /**
- * Returns the length of "src" when "src" is valid UTF-8 string.
- * Returns 0 if src is NULL or 0-length string. Returns -1 when the source
- * is an invalid string.
- *
- * This function should be used to determine whether "src" is valid UTF-8
- * characters with valid unicode codepoints. "src" must be nul-terminated.
- *
- * If you are going to use other utf8_to_... functions defined in this header
- * with string which may not be valid UTF-8 with valid codepoint (form 0 to
- * 0x10FFFF), you should use this function before calling others, since the
- * other functions do not check whether the string is valid UTF-8 or not.
- *
- * If you do not care whether "src" is valid UTF-8 or not, you should use
- * strlen() as usual, which should be much faster.
- */
-ssize_t utf8_length(const char *src);
-
-/**
  * Returns the UTF-16 length of UTF-8 string "src". Returns -1 in case
  * it's invalid utf8. No buffer over-read occurs because of bound checks. Using overreadIsFatal you
  * can ask to log a message and fail in case the invalid utf8 could have caused an override if no
diff --git a/property_service/libpropertyinfoparser/Android.bp b/property_service/libpropertyinfoparser/Android.bp
index 108d15a..2d7e9cb 100644
--- a/property_service/libpropertyinfoparser/Android.bp
+++ b/property_service/libpropertyinfoparser/Android.bp
@@ -3,6 +3,7 @@
     host_supported: true,
     vendor_available: true,
     ramdisk_available: true,
+    vendor_ramdisk_available: true,
     recovery_available: true,
     native_bridge_supported: true,
     srcs: ["property_info_parser.cpp"],
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 900edb4..746fc61 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -148,6 +148,27 @@
     chmod 0664 /dev/stune/top-app/tasks
     chmod 0664 /dev/stune/rt/tasks
 
+    # cpuctl hierarchy for devices using utilclamp
+    mkdir /dev/cpuctl/foreground
+    mkdir /dev/cpuctl/background
+    mkdir /dev/cpuctl/top-app
+    mkdir /dev/cpuctl/rt
+    chown system system /dev/cpuctl
+    chown system system /dev/cpuctl/foreground
+    chown system system /dev/cpuctl/background
+    chown system system /dev/cpuctl/top-app
+    chown system system /dev/cpuctl/rt
+    chown system system /dev/cpuctl/tasks
+    chown system system /dev/cpuctl/foreground/tasks
+    chown system system /dev/cpuctl/background/tasks
+    chown system system /dev/cpuctl/top-app/tasks
+    chown system system /dev/cpuctl/rt/tasks
+    chmod 0664 /dev/cpuctl/tasks
+    chmod 0664 /dev/cpuctl/foreground/tasks
+    chmod 0664 /dev/cpuctl/background/tasks
+    chmod 0664 /dev/cpuctl/top-app/tasks
+    chmod 0664 /dev/cpuctl/rt/tasks
+
     # Create an stune group for NNAPI HAL processes
     mkdir /dev/stune/nnapi-hal
     chown system system /dev/stune/nnapi-hal
@@ -156,6 +177,14 @@
     write /dev/stune/nnapi-hal/schedtune.boost 1
     write /dev/stune/nnapi-hal/schedtune.prefer_idle 1
 
+    # cpuctl hierarchy for devices using utilclamp
+    mkdir /dev/cpuctl/nnapi-hal
+    chown system system /dev/cpuctl/nnapi-hal
+    chown system system /dev/cpuctl/nnapi-hal/tasks
+    chmod 0664 /dev/cpuctl/nnapi-hal/tasks
+    write /dev/cpuctl/nnapi-hal/cpu.uclamp.min 1
+    write /dev/cpuctl/nnapi-hal/cpu.uclamp.latency_sensitive 1
+
     # Create blkio group and apply initial settings.
     # This feature needs kernel to support it, and the
     # device's init.rc must actually set the correct values.
diff --git a/rootdir/init.zygote32.rc b/rootdir/init.zygote32.rc
index e827cf5..9469a48 100644
--- a/rootdir/init.zygote32.rc
+++ b/rootdir/init.zygote32.rc
@@ -13,3 +13,4 @@
     onrestart restart netd
     onrestart restart wificond
     writepid /dev/cpuset/foreground/tasks
+    critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
diff --git a/rootdir/init.zygote64.rc b/rootdir/init.zygote64.rc
index adc7031..98dc088 100644
--- a/rootdir/init.zygote64.rc
+++ b/rootdir/init.zygote64.rc
@@ -13,3 +13,4 @@
     onrestart restart netd
     onrestart restart wificond
     writepid /dev/cpuset/foreground/tasks
+    critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
diff --git a/rootdir/init.zygote64_32.rc b/rootdir/init.zygote64_32.rc
index fb9e99b..3eee180 100644
--- a/rootdir/init.zygote64_32.rc
+++ b/rootdir/init.zygote64_32.rc
@@ -13,6 +13,7 @@
     onrestart restart netd
     onrestart restart wificond
     task_profiles ProcessCapacityHigh MaxPerformance
+    critical window=${zygote.critical_window.minute:-off} target=zygote-fatal
 
 service zygote_secondary /system/bin/app_process32 -Xzygote /system/bin --zygote --socket-name=zygote_secondary --enable-lazy-preload
     class main
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index f83c43e..5e013fe 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -51,3 +51,13 @@
         "toybox_vendor",
     ],
 }
+
+// shell and utilities for first stage console. The list of binaries are
+// enough for debugging purposes.
+phony {
+    name: "shell_and_utilities_vendor_ramdisk",
+    required: [
+        "sh.vendor_ramdisk",
+        "toybox.vendor_ramdisk",
+    ],
+}
diff --git a/trusty/fuzz/Android.bp b/trusty/fuzz/Android.bp
new file mode 100644
index 0000000..969431c
--- /dev/null
+++ b/trusty/fuzz/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2020 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.
+
+cc_defaults {
+    name: "trusty_fuzzer_defaults",
+    static_libs: [
+        "libtrusty_fuzz_utils",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    fuzz_config: {
+        fuzz_on_haiku_device: false,
+        fuzz_on_haiku_host: false,
+    },
+}
+
+cc_library {
+    name: "libtrusty_fuzz_utils",
+    srcs: ["utils.cpp"],
+    export_include_dirs: ["include"],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+}
diff --git a/trusty/fuzz/include/trusty/fuzz/utils.h b/trusty/fuzz/include/trusty/fuzz/utils.h
new file mode 100644
index 0000000..bca84e9
--- /dev/null
+++ b/trusty/fuzz/include/trusty/fuzz/utils.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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 <string>
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+
+#define TIPC_MAX_MSG_SIZE PAGE_SIZE
+
+namespace android {
+namespace trusty {
+namespace fuzz {
+
+class TrustyApp {
+  public:
+    TrustyApp(std::string tipc_dev, std::string ta_port);
+
+    android::base::Result<void> Connect();
+    android::base::Result<void> Read(void* buf, size_t len);
+    android::base::Result<void> Write(const void* buf, size_t len);
+
+    android::base::Result<int> GetRawFd();
+
+  private:
+    std::string tipc_dev_;
+    std::string ta_port_;
+    android::base::unique_fd ta_fd_;
+};
+
+void Abort();
+
+}  // namespace fuzz
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/fuzz/utils.cpp b/trusty/fuzz/utils.cpp
new file mode 100644
index 0000000..240afe7
--- /dev/null
+++ b/trusty/fuzz/utils.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2020 The Android Open Sourete 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.
+ */
+
+#define LOG_TAG "trusty-fuzz-utils"
+
+#include <trusty/fuzz/utils.h>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <linux/ioctl.h>
+#include <linux/types.h>
+#include <linux/uio.h>
+#include <log/log_read.h>
+#include <time.h>
+#include <iostream>
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::unique_fd;
+
+#define TIPC_IOC_MAGIC 'r'
+#define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char*)
+
+namespace {
+
+const size_t kTimeoutSeconds = 5;
+const std::string kTrustyLogTag = "trusty-log";
+
+const time_t kInitialTime = time(nullptr);
+
+void PrintTrustyLog() {
+    auto logger_list = android_logger_list_open(LOG_ID_KERNEL, ANDROID_LOG_NONBLOCK, 1000, 0);
+    if (logger_list == nullptr) {
+        std::cerr << "Could not open android kernel log\n";
+        return;
+    }
+
+    while (true) {
+        log_msg log_msg;
+        int rc = android_logger_list_read(logger_list, &log_msg);
+        if (rc < 0) {
+            break;
+        }
+        if (log_msg.entry.sec < kInitialTime) {
+            continue;
+        }
+        char* msg = log_msg.msg();
+        if (msg) {
+            std::string line(msg, log_msg.entry.len);
+            if (line.find(kTrustyLogTag) != std::string::npos) {
+                std::cerr << line.substr(kTrustyLogTag.length() + 2) << std::endl;
+            }
+        }
+    }
+
+    android_logger_list_free(logger_list);
+}
+
+}  // namespace
+
+namespace android {
+namespace trusty {
+namespace fuzz {
+
+TrustyApp::TrustyApp(std::string tipc_dev, std::string ta_port)
+    : tipc_dev_(tipc_dev), ta_port_(ta_port), ta_fd_(-1) {}
+
+Result<void> TrustyApp::Connect() {
+    /*
+     * TODO: We can't use libtrusty because (yet)
+     * (1) cc_fuzz can't deal with vendor components (b/170753563)
+     * (2) We need non-blocking behavior to detect Trusty going down.
+     * (we could implement the timeout in the fuzzing code though, as
+     * it needs to be around the call to read())
+     */
+    alarm(kTimeoutSeconds);
+    int fd = open(tipc_dev_.c_str(), O_RDWR);
+    alarm(0);
+    if (fd < 0) {
+        return ErrnoError() << "failed to open TIPC device: ";
+    }
+    ta_fd_.reset(fd);
+
+    // This ioctl will time out in the kernel if it can't connect.
+    int rc = TEMP_FAILURE_RETRY(ioctl(ta_fd_, TIPC_IOC_CONNECT, ta_port_.c_str()));
+    if (rc < 0) {
+        return ErrnoError() << "failed to connect to TIPC service: ";
+    }
+
+    return {};
+}
+
+Result<void> TrustyApp::Read(void* buf, size_t len) {
+    if (ta_fd_ == -1) {
+        return Error() << "TA is not connected to yet: ";
+    }
+
+    alarm(kTimeoutSeconds);
+    int rc = read(ta_fd_, buf, len);
+    alarm(0);
+    if (rc < 0) {
+        return Error() << "failed to read TIPC message from TA: ";
+    }
+
+    return {};
+}
+
+Result<void> TrustyApp::Write(const void* buf, size_t len) {
+    if (ta_fd_ == -1) {
+        return Error() << "TA is not connected to yet: ";
+    }
+
+    alarm(kTimeoutSeconds);
+    int rc = write(ta_fd_, buf, len);
+    alarm(0);
+    if (rc < 0) {
+        return Error() << "failed to read TIPC message from TA: ";
+    }
+
+    return {};
+}
+
+Result<int> TrustyApp::GetRawFd() {
+    if (ta_fd_ == -1) {
+        return Error() << "TA is not connected to yet: ";
+    }
+
+    return ta_fd_;
+}
+
+void Abort() {
+    PrintTrustyLog();
+    exit(-1);
+}
+
+}  // namespace fuzz
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/gatekeeper/fuzz/Android.bp b/trusty/gatekeeper/fuzz/Android.bp
new file mode 100644
index 0000000..7ffa776
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2020 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.
+
+cc_fuzz {
+    name: "trusty_gatekeeper_fuzzer",
+    defaults: ["trusty_fuzzer_defaults"],
+    srcs: ["fuzz.cpp"],
+
+    // The initial corpus for this fuzzer was derived by dumping messages from
+    // the `secure_env` emulator interface for cuttlefish while enrolling a new
+    // password in the emulator.
+    corpus: ["corpus/*"],
+}
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-2MMzSr b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-2MMzSr
new file mode 100644
index 0000000..f3c1f79
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-2MMzSr
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Et63W0 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Et63W0
new file mode 100644
index 0000000..b3e6585
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Et63W0
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-G41Iz8 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-G41Iz8
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-G41Iz8
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-ItEoqJ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-ItEoqJ
new file mode 100644
index 0000000..85d38c7
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-ItEoqJ
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-MGXdfu b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-MGXdfu
new file mode 100644
index 0000000..f8e1467
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-MGXdfu
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Yq4f10 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Yq4f10
new file mode 100644
index 0000000..c221077
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-Yq4f10
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-agxKZa b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-agxKZa
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-agxKZa
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-alhn2v b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-alhn2v
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-alhn2v
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-eVJFHV b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-eVJFHV
new file mode 100644
index 0000000..f3c1f79
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-eVJFHV
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-et5K21 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-et5K21
new file mode 100644
index 0000000..f3c1f79
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-et5K21
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-gun5YX b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-gun5YX
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-gun5YX
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-kXw1R9 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-kXw1R9
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-kXw1R9
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-moapss b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-moapss
new file mode 100644
index 0000000..85d38c7
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-moapss
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-u5QySb b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-u5QySb
new file mode 100644
index 0000000..09f9d74
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-u5QySb
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-uZtvkq b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-uZtvkq
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-uZtvkq
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-w5G2SF b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-w5G2SF
new file mode 100644
index 0000000..d42956d
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-w5G2SF
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-y3H74x b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-y3H74x
new file mode 100644
index 0000000..1cec413
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-y3H74x
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-yALfeS b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-yALfeS
new file mode 100644
index 0000000..f3c1f79
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-recv-yALfeS
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-2S1GLi b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-2S1GLi
new file mode 100644
index 0000000..08b3449
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-2S1GLi
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-4j7hUc b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-4j7hUc
new file mode 100644
index 0000000..5507400
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-4j7hUc
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-6hsSQG b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-6hsSQG
new file mode 100644
index 0000000..ffa74cb
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-6hsSQG
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-E8CE7b b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-E8CE7b
new file mode 100644
index 0000000..21cdd9c
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-E8CE7b
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-GEDmHj b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-GEDmHj
new file mode 100644
index 0000000..23a8c08
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-GEDmHj
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-MpwDEN b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-MpwDEN
new file mode 100644
index 0000000..1795d09
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-MpwDEN
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Qutf8O b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Qutf8O
new file mode 100644
index 0000000..4f69edf
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Qutf8O
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Sg1WMt b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Sg1WMt
new file mode 100644
index 0000000..ba6d1cb
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Sg1WMt
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-U6Y1My b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-U6Y1My
new file mode 100644
index 0000000..631ef79
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-U6Y1My
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-WdSRky b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-WdSRky
new file mode 100644
index 0000000..02d4820
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-WdSRky
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Ypw6WP b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Ypw6WP
new file mode 100644
index 0000000..6d7574f
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Ypw6WP
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Yyj4Af b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Yyj4Af
new file mode 100644
index 0000000..47f518d
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-Yyj4Af
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-amyF62 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-amyF62
new file mode 100644
index 0000000..3a5fdf5
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-amyF62
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-gu8ziA b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-gu8ziA
new file mode 100644
index 0000000..bab5da1
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-gu8ziA
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-iCATsM b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-iCATsM
new file mode 100644
index 0000000..fae9173
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-iCATsM
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-kawT3I b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-kawT3I
new file mode 100644
index 0000000..51e3630
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-kawT3I
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-sYFzM5 b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-sYFzM5
new file mode 100644
index 0000000..173d77e
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-sYFzM5
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-yNFMdn b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-yNFMdn
new file mode 100644
index 0000000..96f9e42
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/corpus/gatekeeper-send-yNFMdn
Binary files differ
diff --git a/trusty/gatekeeper/fuzz/fuzz.cpp b/trusty/gatekeeper/fuzz/fuzz.cpp
new file mode 100644
index 0000000..f8ec931
--- /dev/null
+++ b/trusty/gatekeeper/fuzz/fuzz.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <log/log.h>
+#include <stdlib.h>
+#include <trusty/fuzz/utils.h>
+#include <unistd.h>
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define GATEKEEPER_PORT "com.android.trusty.gatekeeper"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    static uint8_t buf[TIPC_MAX_MSG_SIZE];
+
+    android::trusty::fuzz::TrustyApp ta(TIPC_DEV, GATEKEEPER_PORT);
+
+    auto ret = ta.Connect();
+    /*
+     * If we can't connect, then assume TA crashed.
+     * TODO: Get some more info, e.g. stacks, to help Haiku dedup crashes.
+     */
+    if (!ret.ok()) {
+        android::trusty::fuzz::Abort();
+    }
+
+    /* Send message to test server */
+    ret = ta.Write(data, size);
+    if (!ret.ok()) {
+        return -1;
+    }
+
+    /* Read message from test server */
+    ret = ta.Read(&buf, sizeof(buf));
+    if (!ret.ok()) {
+        return -1;
+    }
+
+    return 0;
+}