Merge changes from topic "vapex_stop_service"

* changes:
  Remove and add service and action during apex load and unload
  Stop services when unload an apex
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index ad0231d..c15146b 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -204,6 +204,7 @@
     header_libs: [
         "bionic_libc_platform_headers",
         "gwp_asan_headers",
+        "liblog_headers",
     ],
 
     static_libs: [
@@ -212,7 +213,6 @@
         "liblzma",
         "libbase",
         "libcutils",
-        "liblog",
     ],
     runtime_libs: [
         "libdexfile",           // libdexfile_support dependency
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 26e6e00..b478e6e 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -154,6 +154,7 @@
         "device/flashing.cpp",
         "device/main.cpp",
         "device/usb.cpp",
+        "device/usb_iouring.cpp",
         "device/usb_client.cpp",
         "device/tcp_client.cpp",
         "device/utility.cpp",
@@ -195,7 +196,9 @@
         "liblz4",
         "libsnapshot_nobinder",
         "update_metadata-protos",
+        "liburing",
     ],
+    include_dirs: ["bionic/libc/kernel"],
 
     header_libs: [
         "avb_headers",
@@ -346,9 +349,7 @@
     target: {
         not_windows: {
             required: [
-                "e2fsdroid",
                 "mke2fs.conf",
-                "sload_f2fs",
             ],
         },
         windows: {
diff --git a/fastboot/Android.mk b/fastboot/Android.mk
index 10bed6d..cde0cb2 100644
--- a/fastboot/Android.mk
+++ b/fastboot/Android.mk
@@ -19,9 +19,7 @@
 #
 
 my_dist_files := $(HOST_OUT_EXECUTABLES)/mke2fs
-my_dist_files += $(HOST_OUT_EXECUTABLES)/e2fsdroid
 my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs
 my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs_casefold
-my_dist_files += $(HOST_OUT_EXECUTABLES)/sload_f2fs
 $(call dist-for-goals,dist_files sdk,$(my_dist_files))
 my_dist_files :=
diff --git a/fastboot/device/usb.cpp b/fastboot/device/usb.cpp
index 4115a6d..b77d772 100644
--- a/fastboot/device/usb.cpp
+++ b/fastboot/device/usb.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "usb.h"
+#include "usb_iouring.h"
 
 #include <dirent.h>
 #include <errno.h>
@@ -28,6 +29,7 @@
 
 #include <linux/usb/ch9.h>
 #include <linux/usb/functionfs.h>
+#include <sys/utsname.h>
 
 #include <algorithm>
 #include <atomic>
@@ -38,6 +40,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <liburing.h>
 
 using namespace std::chrono_literals;
 
@@ -65,8 +68,8 @@
     }
 }
 
-static int getMaxPacketSize(int ffs_fd) {
-    usb_endpoint_descriptor desc;
+int getMaxPacketSize(int ffs_fd) {
+    usb_endpoint_descriptor desc{};
     if (ioctl(ffs_fd, FUNCTIONFS_ENDPOINT_DESC, reinterpret_cast<unsigned long>(&desc))) {
         D("[ could not get endpoint descriptor! (%d) ]", errno);
         return MAX_PACKET_SIZE_HS;
@@ -128,11 +131,9 @@
 
 static int usb_ffs_do_aio(usb_handle* h, const void* data, int len, bool read) {
     aio_block* aiob = read ? &h->read_aiob : &h->write_aiob;
-    bool zero_packet = false;
 
     int num_bufs = len / h->io_size + (len % h->io_size == 0 ? 0 : 1);
     const char* cur_data = reinterpret_cast<const char*>(data);
-    int packet_size = getMaxPacketSize(aiob->fd);
 
     if (posix_madvise(const_cast<void*>(data), len, POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED) <
         0) {
@@ -145,17 +146,6 @@
 
         len -= buf_len;
         cur_data += buf_len;
-
-        if (len == 0 && buf_len % packet_size == 0 && read) {
-            // adb does not expect the device to send a zero packet after data transfer,
-            // but the host *does* send a zero packet for the device to read.
-            zero_packet = h->reads_zero_packets;
-        }
-    }
-    if (zero_packet) {
-        io_prep(&aiob->iocb[num_bufs], aiob->fd, reinterpret_cast<const void*>(cur_data),
-                packet_size, 0, read);
-        num_bufs += 1;
     }
 
     while (true) {
@@ -204,21 +194,46 @@
     h->open_new_connection = true;
     h->lock.unlock();
     h->notify.notify_one();
+    if (h->aio_type == AIOType::IO_URING) {
+        exit_io_uring_ffs(h);
+    }
 }
 
-usb_handle* create_usb_handle(unsigned num_bufs, unsigned io_size) {
-    usb_handle* h = new usb_handle();
+bool DoesKernelSupportIouring() {
+    struct utsname uts {};
+    unsigned int major = 0, minor = 0;
+    if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) {
+        return false;
+    }
+    if (major > 5) {
+        return true;
+    }
+    // We will only support kernels from 5.6 onwards as IOSQE_ASYNC flag and
+    // IO_URING_OP_READ/WRITE opcodes were introduced only on 5.6 kernel
+    return minor >= 6;
+}
 
-    if (android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false)) {
+std::unique_ptr<usb_handle> create_usb_handle(unsigned num_bufs, unsigned io_size) {
+    auto h = std::make_unique<usb_handle>();
+    if (DoesKernelSupportIouring() &&
+        android::base::GetBoolProperty("sys.usb.ffs.io_uring_enabled", false)) {
+        init_io_uring_ffs(h.get(), num_bufs);
+        h->aio_type = AIOType::IO_URING;
+        LOG(INFO) << "Using io_uring for usb ffs";
+    } else if (android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false)) {
         // Devices on older kernels (< 3.18) will not have aio support for ffs
         // unless backported. Fall back on the non-aio functions instead.
         h->write = usb_ffs_write;
         h->read = usb_ffs_read;
+        h->aio_type = AIOType::SYNC_IO;
+        LOG(INFO) << "Using sync io for usb ffs";
     } else {
         h->write = usb_ffs_aio_write;
         h->read = usb_ffs_aio_read;
         aio_block_init(&h->read_aiob, num_bufs);
         aio_block_init(&h->write_aiob, num_bufs);
+        h->aio_type = AIOType::AIO;
+        LOG(INFO) << "Using aio for usb ffs";
     }
     h->io_size = io_size;
     h->close = usb_ffs_close;
diff --git a/fastboot/device/usb.h b/fastboot/device/usb.h
index 6c3f542..8996c31 100644
--- a/fastboot/device/usb.h
+++ b/fastboot/device/usb.h
@@ -18,8 +18,10 @@
 
 #include <linux/usb/functionfs.h>
 
+#include <liburing.h>
 #include <atomic>
 #include <condition_variable>
+#include <memory>
 #include <mutex>
 #include <vector>
 
@@ -35,9 +37,11 @@
     int fd;
 };
 
-struct usb_handle {
-    usb_handle() {}
+int getMaxPacketSize(int ffs_fd);
 
+enum class AIOType { SYNC_IO, AIO, IO_URING };
+
+struct usb_handle {
     std::condition_variable notify;
     std::mutex lock;
     bool open_new_connection = true;
@@ -56,8 +60,9 @@
     struct aio_block read_aiob;
     struct aio_block write_aiob;
 
-    bool reads_zero_packets;
+    io_uring ring;
     size_t io_size;
+    AIOType aio_type;
 };
 
-usb_handle* create_usb_handle(unsigned num_bufs, unsigned io_size);
+std::unique_ptr<usb_handle> create_usb_handle(unsigned num_bufs, unsigned io_size);
diff --git a/fastboot/device/usb_client.cpp b/fastboot/device/usb_client.cpp
index 3f9b0f0..d1b38d4 100644
--- a/fastboot/device/usb_client.cpp
+++ b/fastboot/device/usb_client.cpp
@@ -232,7 +232,6 @@
 
     h->read_aiob.fd = h->bulk_out.get();
     h->write_aiob.fd = h->bulk_in.get();
-    h->reads_zero_packets = false;
     return true;
 
 err:
diff --git a/fastboot/device/usb_iouring.cpp b/fastboot/device/usb_iouring.cpp
new file mode 100644
index 0000000..d987712
--- /dev/null
+++ b/fastboot/device/usb_iouring.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <liburing.h>
+#include "liburing/io_uring.h"
+#include "usb.h"
+
+static int prep_async_read(struct io_uring* ring, int fd, void* data, size_t len, int64_t offset) {
+    if (io_uring_sq_space_left(ring) <= 0) {
+        LOG(ERROR) << "Submission queue run out of space.";
+        return -1;
+    }
+    auto sqe = io_uring_get_sqe(ring);
+    if (sqe == nullptr) {
+        return -1;
+    }
+    io_uring_sqe_set_flags(sqe, IOSQE_IO_LINK | IOSQE_ASYNC);
+    io_uring_prep_read(sqe, fd, data, len, offset);
+    return 0;
+}
+
+static int prep_async_write(struct io_uring* ring, int fd, const void* data, size_t len,
+                            int64_t offset) {
+    if (io_uring_sq_space_left(ring) <= 0) {
+        LOG(ERROR) << "Submission queue run out of space.";
+        return -1;
+    }
+    auto sqe = io_uring_get_sqe(ring);
+    if (sqe == nullptr) {
+        return -1;
+    }
+    io_uring_sqe_set_flags(sqe, IOSQE_IO_LINK | IOSQE_ASYNC);
+    io_uring_prep_write(sqe, fd, data, len, offset);
+    return 0;
+}
+
+template <bool read, typename T>
+int prep_async_io(struct io_uring* ring, int fd, T* data, size_t len, int64_t offset) {
+    if constexpr (read) {
+        return prep_async_read(ring, fd, data, len, offset);
+    } else {
+        return prep_async_write(ring, fd, data, len, offset);
+    }
+}
+
+template <typename T>
+static constexpr T DivRoundup(T x, T y) {
+    return (x + y - 1) / y;
+}
+
+extern int getMaxPacketSize(int ffs_fd);
+
+template <bool read, typename T>
+static int usb_ffs_do_aio(usb_handle* h, T* const data, const int len) {
+    const aio_block* aiob = read ? &h->read_aiob : &h->write_aiob;
+    const int num_requests = DivRoundup<int>(len, h->io_size);
+    auto cur_data = data;
+    const auto packet_size = getMaxPacketSize(aiob->fd);
+
+    for (int bytes_remain = len; bytes_remain > 0;) {
+        const int buf_len = std::min(bytes_remain, static_cast<int>(h->io_size));
+        const auto ret = prep_async_io<read>(&h->ring, aiob->fd, cur_data, buf_len, 0);
+        if (ret < 0) {
+            PLOG(ERROR) << "Failed to queue io_uring request";
+            return -1;
+        }
+
+        bytes_remain -= buf_len;
+        cur_data = reinterpret_cast<T*>(reinterpret_cast<size_t>(cur_data) + buf_len);
+    }
+    const int ret = io_uring_submit(&h->ring);
+    if (ret <= 0 || ret != num_requests) {
+        PLOG(ERROR) << "io_uring: failed to submit SQE entries to kernel";
+        return -1;
+    }
+    int res = 0;
+    bool success = true;
+    for (int i = 0; i < num_requests; ++i) {
+        struct io_uring_cqe* cqe{};
+        const auto ret = TEMP_FAILURE_RETRY(io_uring_wait_cqe(&h->ring, &cqe));
+        if (ret < 0 || cqe == nullptr) {
+            PLOG(ERROR) << "Failed to get CQE from kernel";
+            success = false;
+            continue;
+        }
+        res += cqe->res;
+        if (cqe->res < 0) {
+            LOG(ERROR) << "io_uring request failed:, i = " << i
+                       << ", num_requests = " << num_requests << ", res = " << cqe->res << ": "
+                       << strerror(cqe->res) << (read ? " read" : " write")
+                       << " request size: " << len << ", io_size: " << h->io_size
+                       << " max packet size: " << packet_size << ", fd: " << aiob->fd;
+            success = false;
+            errno = -cqe->res;
+        }
+        io_uring_cqe_seen(&h->ring, cqe);
+    }
+    if (!success) {
+        return -1;
+    }
+    return res;
+}
+
+static int usb_ffs_io_uring_read(usb_handle* h, void* data, int len, bool /* allow_partial */) {
+    return usb_ffs_do_aio<true>(h, data, len);
+}
+
+static int usb_ffs_io_uring_write(usb_handle* h, const void* data, int len) {
+    return usb_ffs_do_aio<false>(h, data, len);
+}
+
+void exit_io_uring_ffs(usb_handle* h) {
+    io_uring_queue_exit(&h->ring);
+}
+
+bool init_io_uring_ffs(usb_handle* h, size_t queue_depth) {
+    const auto err = io_uring_queue_init(queue_depth, &h->ring, 0);
+    if (err) {
+        LOG(ERROR) << "Failed to initialize io_uring of depth " << queue_depth << ": "
+                   << strerror(err);
+        return false;
+    }
+    h->write = usb_ffs_io_uring_write;
+    h->read = usb_ffs_io_uring_read;
+    return true;
+}
diff --git a/fastboot/device/usb_iouring.h b/fastboot/device/usb_iouring.h
new file mode 100644
index 0000000..7c14b81
--- /dev/null
+++ b/fastboot/device/usb_iouring.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2022 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 "usb.h"
+
+bool init_io_uring_ffs(usb_handle* h, size_t queue_depth);
+
+void exit_io_uring_ffs(usb_handle* h);
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index c0e3161..1f54f5b 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -889,7 +889,7 @@
 // attempted_idx: On return, will indicate which fstab entry
 //     succeeded. In case of failure, it will be the start_idx.
 // Sets errno to match the 1st mount failure on failure.
-static bool mount_with_alternatives(const Fstab& fstab, int start_idx, int* end_idx,
+static bool mount_with_alternatives(Fstab& fstab, int start_idx, int* end_idx,
                                     int* attempted_idx) {
     unsigned long i;
     int mount_errno = 0;
@@ -909,6 +909,14 @@
             continue;
         }
 
+        // fstab[start_idx].blk_device is already updated to /dev/dm-<N> by
+        // AVB related functions. Copy it from start_idx to the current index i.
+        if ((i != start_idx) && fstab[i].fs_mgr_flags.logical &&
+            fstab[start_idx].fs_mgr_flags.logical &&
+            (fstab[i].logical_partition_name == fstab[start_idx].logical_partition_name)) {
+            fstab[i].blk_device = fstab[start_idx].blk_device;
+        }
+
         int fs_stat = prepare_fs_for_mount(fstab[i].blk_device, fstab[i]);
         if (fs_stat & FS_STAT_INVALID_MAGIC) {
             LERROR << __FUNCTION__
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 07eaf58..354d02a 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -81,10 +81,17 @@
     return filesystems.find("\t" + filesystem + "\n") != std::string::npos;
 }
 
+const auto kLowerdirOption = "lowerdir="s;
+const auto kUpperdirOption = "upperdir="s;
+
 }  // namespace
 
 #if ALLOW_ADBD_DISABLE_VERITY == 0  // If we are a user build, provide stubs
 
+bool fs_mgr_wants_overlayfs(FstabEntry*) {
+    return false;
+}
+
 Fstab fs_mgr_overlayfs_candidate_list(const Fstab&) {
     return {};
 }
@@ -93,7 +100,7 @@
     return false;
 }
 
-bool fs_mgr_overlayfs_setup(const char*, const char*, bool* change, bool) {
+bool fs_mgr_overlayfs_setup(const char*, bool* change, bool) {
     if (change) *change = false;
     return false;
 }
@@ -324,9 +331,6 @@
     return "";
 }
 
-const auto kLowerdirOption = "lowerdir="s;
-const auto kUpperdirOption = "upperdir="s;
-
 static inline bool KernelSupportsUserXattrs() {
     struct utsname uts;
     uname(&uts);
@@ -366,48 +370,6 @@
     return ret;
 }
 
-bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only = true) {
-    Fstab fstab;
-    auto save_errno = errno;
-    if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
-        return false;
-    }
-    errno = save_errno;
-    const auto lowerdir = kLowerdirOption + mount_point;
-    for (const auto& entry : fstab) {
-        if (overlay_only && "overlay" != entry.fs_type && "overlayfs" != entry.fs_type) continue;
-        if (mount_point != entry.mount_point) continue;
-        if (!overlay_only) return true;
-        const auto options = android::base::Split(entry.fs_options, ",");
-        for (const auto& opt : options) {
-            if (opt == lowerdir) {
-                return true;
-            }
-        }
-    }
-    return false;
-}
-
-bool fs_mgr_wants_overlayfs(FstabEntry* entry) {
-    // Don't check entries that are managed by vold.
-    if (entry->fs_mgr_flags.vold_managed || entry->fs_mgr_flags.recovery_only) return false;
-
-    // *_other doesn't want overlayfs.
-    if (entry->fs_mgr_flags.slot_select_other) return false;
-
-    // Only concerned with readonly partitions.
-    if (!(entry->flags & MS_RDONLY)) return false;
-
-    // If unbindable, do not allow overlayfs as this could expose us to
-    // security issues.  On Android, this could also be used to turn off
-    // the ability to overlay an otherwise acceptable filesystem since
-    // /system and /vendor are never bound(sic) to.
-    if (entry->flags & MS_UNBINDABLE) return false;
-
-    if (!fs_mgr_overlayfs_enabled(entry)) return false;
-
-    return true;
-}
 constexpr char kOverlayfsFileContext[] = "u:object_r:overlayfs_file:s0";
 
 bool fs_mgr_overlayfs_setup_dir(const std::string& dir, std::string* overlay, bool* change) {
@@ -1284,9 +1246,45 @@
 
 }  // namespace
 
+bool fs_mgr_wants_overlayfs(FstabEntry* entry) {
+    // Don't check entries that are managed by vold.
+    if (entry->fs_mgr_flags.vold_managed || entry->fs_mgr_flags.recovery_only) return false;
+
+    // *_other doesn't want overlayfs.
+    if (entry->fs_mgr_flags.slot_select_other) return false;
+
+    // Only concerned with readonly partitions.
+    if (!(entry->flags & MS_RDONLY)) return false;
+
+    // If unbindable, do not allow overlayfs as this could expose us to
+    // security issues.  On Android, this could also be used to turn off
+    // the ability to overlay an otherwise acceptable filesystem since
+    // /system and /vendor are never bound(sic) to.
+    if (entry->flags & MS_UNBINDABLE) return false;
+
+    if (!fs_mgr_overlayfs_enabled(entry)) return false;
+
+    return true;
+}
+
 Fstab fs_mgr_overlayfs_candidate_list(const Fstab& fstab) {
+    android::fs_mgr::Fstab mounts;
+    if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
+        PLOG(ERROR) << "Failed to read /proc/mounts";
+        return {};
+    }
+
     Fstab candidates;
     for (const auto& entry : fstab) {
+        // Filter out partitions whose type doesn't match what's mounted.
+        // This avoids spammy behavior on devices which can mount different
+        // filesystems for each partition.
+        auto proc_mount_point = (entry.mount_point == "/system") ? "/" : entry.mount_point;
+        auto mounted = GetEntryForMountPoint(&mounts, proc_mount_point);
+        if (!mounted || mounted->fs_type != entry.fs_type) {
+            continue;
+        }
+
         FstabEntry new_entry = entry;
         if (!fs_mgr_overlayfs_already_mounted(entry.mount_point) &&
             !fs_mgr_wants_overlayfs(&new_entry)) {
@@ -1359,8 +1357,7 @@
 
 // Returns false if setup not permitted, errno set to last error.
 // If something is altered, set *change.
-bool fs_mgr_overlayfs_setup(const char* backing, const char* mount_point, bool* change,
-                            bool force) {
+bool fs_mgr_overlayfs_setup(const char* mount_point, bool* change, bool force) {
     if (change) *change = false;
     auto ret = false;
     if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kNotSupported) return ret;
@@ -1397,7 +1394,6 @@
 
     std::string dir;
     for (const auto& overlay_mount_point : OverlayMountPoints()) {
-        if (backing && backing[0] && (overlay_mount_point != backing)) continue;
         if (overlay_mount_point == kScratchMountPoint) {
             if (!fs_mgr_overlayfs_setup_scratch(fstab, change)) continue;
         } else {
@@ -1693,6 +1689,28 @@
 
 #endif  // ALLOW_ADBD_DISABLE_VERITY != 0
 
+bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only) {
+    Fstab fstab;
+    auto save_errno = errno;
+    if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
+        return false;
+    }
+    errno = save_errno;
+    const auto lowerdir = kLowerdirOption + mount_point;
+    for (const auto& entry : fstab) {
+        if (overlay_only && "overlay" != entry.fs_type && "overlayfs" != entry.fs_type) continue;
+        if (mount_point != entry.mount_point) continue;
+        if (!overlay_only) return true;
+        const auto options = android::base::Split(entry.fs_options, ",");
+        for (const auto& opt : options) {
+            if (opt == lowerdir) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 bool fs_mgr_has_shared_blocks(const std::string& mount_point, const std::string& dev) {
     struct statfs fs;
     if ((statfs((mount_point + "/lost+found").c_str(), &fs) == -1) ||
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index deaf5f7..4a927d0 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -43,6 +43,8 @@
 #include <libgsi/libgsid.h>
 
 using namespace std::literals;
+using android::fs_mgr::Fstab;
+using android::fs_mgr::FstabEntry;
 
 namespace {
 
@@ -62,22 +64,12 @@
     ::exit(exit_status);
 }
 
-bool remountable_partition(const android::fs_mgr::FstabEntry& entry) {
-    if (entry.fs_mgr_flags.vold_managed) return false;
-    if (entry.fs_mgr_flags.recovery_only) return false;
-    if (entry.fs_mgr_flags.slot_select_other) return false;
-    if (!(entry.flags & MS_RDONLY)) return false;
-    if (entry.fs_type == "vfat") return false;
-    return true;
-}
-
 const std::string system_mount_point(const android::fs_mgr::FstabEntry& entry) {
     if (entry.mount_point == "/") return "/system";
     return entry.mount_point;
 }
 
-const android::fs_mgr::FstabEntry* is_wrapped(const android::fs_mgr::Fstab& overlayfs_candidates,
-                                              const android::fs_mgr::FstabEntry& entry) {
+const FstabEntry* GetWrappedEntry(const Fstab& overlayfs_candidates, const FstabEntry& entry) {
     auto mount_point = system_mount_point(entry);
     auto it = std::find_if(overlayfs_candidates.begin(), overlayfs_candidates.end(),
                            [&mount_point](const auto& entry) {
@@ -99,12 +91,8 @@
     logd(id, severity, tag, file, line, message);
 }
 
-[[noreturn]] void reboot(bool overlayfs = false) {
-    if (overlayfs) {
-        LOG(INFO) << "Successfully setup overlayfs\nrebooting device";
-    } else {
-        LOG(INFO) << "Successfully disabled verity\nrebooting device";
-    }
+[[noreturn]] void reboot() {
+    LOG(INFO) << "Rebooting device for new settings to take effect";
     ::sync();
     android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot,remount");
     ::sleep(60);
@@ -140,15 +128,368 @@
     BAD_OVERLAY,
     NO_MOUNTS,
     REMOUNT_FAILED,
-    MUST_REBOOT,
     BINDER_ERROR,
     CHECKPOINTING,
     GSID_ERROR,
-    CLEAN_SCRATCH_FILES,
 };
 
-static int do_remount(int argc, char* argv[]) {
-    RemountStatus retval = REMOUNT_SUCCESS;
+static bool ReadFstab(const char* fstab_file, android::fs_mgr::Fstab* fstab) {
+    if (fstab_file) {
+        return android::fs_mgr::ReadFstabFromFile(fstab_file, fstab);
+    }
+    if (!android::fs_mgr::ReadDefaultFstab(fstab)) {
+        return false;
+    }
+
+    // Manufacture a / entry from /proc/mounts if missing.
+    if (!GetEntryForMountPoint(fstab, "/system") && !GetEntryForMountPoint(fstab, "/")) {
+        android::fs_mgr::Fstab mounts;
+        if (android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
+            if (auto entry = GetEntryForMountPoint(&mounts, "/")) {
+                if (entry->fs_type != "rootfs") fstab->emplace_back(*entry);
+            }
+        }
+    }
+    return true;
+}
+
+static RemountStatus VerifyCheckpointing() {
+    if (!android::base::GetBoolProperty("ro.virtual_ab.enabled", false) &&
+        !android::base::GetBoolProperty("ro.virtual_ab.retrofit", false)) {
+        return REMOUNT_SUCCESS;
+    }
+
+    // Virtual A/B devices can use /data as backing storage; make sure we're
+    // not checkpointing.
+    auto vold = GetVold();
+    bool checkpointing = false;
+    if (!vold->isCheckpointing(&checkpointing).isOk()) {
+        LOG(ERROR) << "Could not determine checkpointing status.";
+        return BINDER_ERROR;
+    }
+    if (checkpointing) {
+        LOG(ERROR) << "Cannot use remount when a checkpoint is in progress.";
+        return CHECKPOINTING;
+    }
+    return REMOUNT_SUCCESS;
+}
+
+static bool IsRemountable(Fstab& candidates, const FstabEntry& entry) {
+    if (entry.fs_mgr_flags.vold_managed || entry.fs_mgr_flags.recovery_only ||
+        entry.fs_mgr_flags.slot_select_other) {
+        return false;
+    }
+    if (!(entry.flags & MS_RDONLY)) {
+        return false;
+    }
+    if (entry.fs_type == "vfat") {
+        return false;
+    }
+    if (auto candidate_entry = GetEntryForMountPoint(&candidates, entry.mount_point)) {
+        return candidate_entry->fs_type == entry.fs_type;
+    }
+    if (GetWrappedEntry(candidates, entry)) {
+        return false;
+    }
+    return true;
+}
+
+static Fstab::const_iterator FindPartition(const Fstab& fstab, const std::string& partition) {
+    Fstab mounts;
+    if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
+        LOG(ERROR) << "Failed to read /proc/mounts";
+        return fstab.end();
+    }
+
+    for (auto iter = fstab.begin(); iter != fstab.end(); iter++) {
+        const auto mount_point = system_mount_point(*iter);
+        if (partition == mount_point || partition == android::base::Basename(mount_point)) {
+            // In case fstab has multiple entries, pick the one that matches the
+            // actual mounted filesystem type.
+            auto proc_mount_point = (iter->mount_point == "/system") ? "/" : iter->mount_point;
+            auto mounted = GetEntryForMountPoint(&mounts, proc_mount_point);
+            if (mounted && mounted->fs_type == iter->fs_type) {
+                return iter;
+            }
+        }
+    }
+    return fstab.end();
+}
+
+static Fstab GetAllRemountablePartitions(Fstab& fstab) {
+    auto candidates = fs_mgr_overlayfs_candidate_list(fstab);
+
+    Fstab partitions;
+    for (const auto& entry : fstab) {
+        if (IsRemountable(candidates, entry)) {
+            partitions.emplace_back(entry);
+        }
+    }
+    return partitions;
+}
+
+static RemountStatus GetRemountList(const Fstab& fstab, const std::vector<std::string>& argv,
+                                    Fstab* partitions) {
+    auto candidates = fs_mgr_overlayfs_candidate_list(fstab);
+
+    for (const auto& arg : argv) {
+        std::string partition = arg;
+        if (partition == "/") {
+            partition = "/system";
+        }
+
+        auto it = FindPartition(fstab, partition);
+        if (it == fstab.end()) {
+            LOG(ERROR) << "Unknown partition " << arg;
+            return UNKNOWN_PARTITION;
+        }
+
+        const FstabEntry* entry = &*it;
+        if (auto wrap = GetWrappedEntry(candidates, *entry); wrap != nullptr) {
+            LOG(INFO) << "partition " << arg << " covered by overlayfs for " << wrap->mount_point
+                      << ", switching";
+            entry = wrap;
+        }
+
+        // If it's already remounted, include it so it gets gracefully skipped
+        // later on.
+        if (!fs_mgr_overlayfs_already_mounted(entry->mount_point) &&
+            !IsRemountable(candidates, *entry)) {
+            LOG(ERROR) << "Invalid partition " << arg;
+            return INVALID_PARTITION;
+        }
+        if (GetEntryForMountPoint(partitions, entry->mount_point) != nullptr) {
+            continue;
+        }
+        partitions->emplace_back(*entry);
+    }
+
+    return REMOUNT_SUCCESS;
+}
+
+struct RemountCheckResult {
+    bool reboot_later = false;
+    bool setup_overlayfs = false;
+    bool disabled_verity = false;
+    bool verity_error = false;
+    bool remounted_anything = false;
+};
+
+static RemountStatus CheckVerity(const FstabEntry& entry, RemountCheckResult* result) {
+    if (!fs_mgr_is_verity_enabled(entry)) {
+        return REMOUNT_SUCCESS;
+    }
+    if (android::base::GetProperty("ro.boot.vbmeta.device_state", "") == "locked") {
+        return VERITY_PARTITION;
+    }
+
+    bool ok = false;
+
+    std::unique_ptr<AvbOps, decltype(&::avb_ops_user_free)> ops(avb_ops_user_new(),
+                                                                &::avb_ops_user_free);
+    if (ops) {
+        auto suffix = android::base::GetProperty("ro.boot.slot_suffix", "");
+        ok = avb_user_verity_set(ops.get(), suffix.c_str(), false);
+    }
+    if (!ok && fs_mgr_set_blk_ro(entry.blk_device, false)) {
+        fec::io fh(entry.blk_device.c_str(), O_RDWR);
+        ok = fh && fh.set_verity_status(false);
+    }
+    if (!ok) {
+        return VERITY_PARTITION;
+    }
+    result->disabled_verity = true;
+    result->reboot_later = true;
+    return REMOUNT_SUCCESS;
+}
+
+static RemountStatus CheckVerityAndOverlayfs(Fstab* partitions, RemountCheckResult* result) {
+    RemountStatus status = REMOUNT_SUCCESS;
+    for (auto it = partitions->begin(); it != partitions->end();) {
+        auto& entry = *it;
+        const auto& mount_point = entry.mount_point;
+
+        if (auto rv = CheckVerity(entry, result); rv != REMOUNT_SUCCESS) {
+            LOG(ERROR) << "Skipping verified partition " << mount_point << " for remount";
+            status = rv;
+            it = partitions->erase(it);
+            continue;
+        }
+
+        if (fs_mgr_wants_overlayfs(&entry)) {
+            bool change = false;
+            bool force = result->disabled_verity;
+            if (!fs_mgr_overlayfs_setup(mount_point.c_str(), &change, force)) {
+                LOG(ERROR) << "Overlayfs setup for " << mount_point << " failed, skipping";
+                status = BAD_OVERLAY;
+                it = partitions->erase(it);
+                continue;
+            }
+            if (change) {
+                LOG(INFO) << "Using overlayfs for " << mount_point;
+                result->reboot_later = true;
+                result->setup_overlayfs = true;
+            }
+        }
+        it++;
+    }
+    return status;
+}
+
+static RemountStatus EnableDsuIfNeeded() {
+    auto gsid = android::gsi::GetGsiService();
+    if (!gsid) {
+        return REMOUNT_SUCCESS;
+    }
+
+    auto dsu_running = false;
+    if (auto status = gsid->isGsiRunning(&dsu_running); !status.isOk()) {
+        LOG(ERROR) << "Failed to get DSU running state: " << status;
+        return BINDER_ERROR;
+    }
+    auto dsu_enabled = false;
+    if (auto status = gsid->isGsiEnabled(&dsu_enabled); !status.isOk()) {
+        LOG(ERROR) << "Failed to get DSU enabled state: " << status;
+        return BINDER_ERROR;
+    }
+    if (dsu_running && !dsu_enabled) {
+        std::string dsu_slot;
+        if (auto status = gsid->getActiveDsuSlot(&dsu_slot); !status.isOk()) {
+            LOG(ERROR) << "Failed to get active DSU slot: " << status;
+            return BINDER_ERROR;
+        }
+        LOG(INFO) << "DSU is running but disabled, enable DSU so that we stay within the "
+                     "DSU guest system after reboot";
+        int error = 0;
+        if (auto status = gsid->enableGsi(/* oneShot = */ true, dsu_slot, &error);
+            !status.isOk() || error != android::gsi::IGsiService::INSTALL_OK) {
+            LOG(ERROR) << "Failed to enable DSU: " << status << ", error code: " << error;
+            return !status.isOk() ? BINDER_ERROR : GSID_ERROR;
+        }
+        LOG(INFO) << "Successfully enabled DSU (one-shot mode)";
+    }
+    return REMOUNT_SUCCESS;
+}
+
+static RemountStatus RemountPartition(Fstab& fstab, Fstab& mounts, FstabEntry& entry) {
+    // unlock the r/o key for the mount point device
+    if (entry.fs_mgr_flags.logical) {
+        fs_mgr_update_logical_partition(&entry);
+    }
+    auto blk_device = entry.blk_device;
+    auto mount_point = entry.mount_point;
+
+    auto found = false;
+    for (auto it = mounts.rbegin(); it != mounts.rend(); ++it) {
+        auto& rentry = *it;
+        if (mount_point == rentry.mount_point) {
+            blk_device = rentry.blk_device;
+            found = true;
+            break;
+        }
+        // Find overlayfs mount point?
+        if ((mount_point == "/" && rentry.mount_point == "/system") ||
+            (mount_point == "/system" && rentry.mount_point == "/")) {
+            blk_device = rentry.blk_device;
+            mount_point = "/system";
+            found = true;
+            break;
+        }
+    }
+    if (!found) {
+        PLOG(INFO) << "skip unmounted partition dev:" << blk_device << " mnt:" << mount_point;
+        return REMOUNT_SUCCESS;
+    }
+    if (blk_device == "/dev/root") {
+        auto from_fstab = GetEntryForMountPoint(&fstab, mount_point);
+        if (from_fstab) blk_device = from_fstab->blk_device;
+    }
+    fs_mgr_set_blk_ro(blk_device, false);
+
+    // Find system-as-root mount point?
+    if ((mount_point == "/system") && !GetEntryForMountPoint(&mounts, mount_point) &&
+        GetEntryForMountPoint(&mounts, "/")) {
+        mount_point = "/";
+    }
+
+    // Now remount!
+    if (::mount(blk_device.c_str(), mount_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
+                nullptr) == 0) {
+        return REMOUNT_SUCCESS;
+    }
+    if ((errno == EINVAL) && (mount_point != entry.mount_point)) {
+        mount_point = entry.mount_point;
+        if (::mount(blk_device.c_str(), mount_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
+                    nullptr) == 0) {
+            return REMOUNT_SUCCESS;
+        }
+    }
+
+    PLOG(ERROR) << "failed to remount partition dev:" << blk_device << " mnt:" << mount_point;
+    return REMOUNT_FAILED;
+}
+
+static int do_remount(Fstab& fstab, const std::vector<std::string>& partition_args,
+                      RemountCheckResult* check_result) {
+    Fstab partitions;
+    if (partition_args.empty()) {
+        partitions = GetAllRemountablePartitions(fstab);
+    } else {
+        if (auto rv = GetRemountList(fstab, partition_args, &partitions); rv != REMOUNT_SUCCESS) {
+            return rv;
+        }
+    }
+
+    // Check verity and optionally setup overlayfs backing.
+    auto retval = CheckVerityAndOverlayfs(&partitions, check_result);
+
+    if (partitions.empty() || check_result->disabled_verity) {
+        if (partitions.empty()) {
+            LOG(WARNING) << "No remountable partitions were found.";
+        }
+        return retval;
+    }
+
+    // Mount overlayfs.
+    errno = 0;
+    if (!fs_mgr_overlayfs_mount_all(&partitions) && errno) {
+        PLOG(ERROR) << "Can not mount overlayfs for partitions";
+        return BAD_OVERLAY;
+    }
+
+    // Get actual mounts _after_ overlayfs has been added.
+    android::fs_mgr::Fstab mounts;
+    if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts) || mounts.empty()) {
+        PLOG(ERROR) << "Failed to read /proc/mounts";
+        return NO_MOUNTS;
+    }
+
+    // Remount selected partitions.
+    for (auto& entry : partitions) {
+        if (auto rv = RemountPartition(fstab, mounts, entry); rv != REMOUNT_SUCCESS) {
+            retval = rv;
+        } else {
+            check_result->remounted_anything = true;
+        }
+    }
+    return retval;
+}
+
+int main(int argc, char* argv[]) {
+    // Do not use MyLogger() when running as clean_scratch_files, as stdout/stderr of daemon process
+    // are discarded.
+    if (argc > 0 && android::base::Basename(argv[0]) == "clean_scratch_files"s) {
+        android::fs_mgr::CleanupOldScratchFiles();
+        return 0;
+    }
+
+    android::base::InitLogging(argv, MyLogger);
+
+    // Make sure we are root.
+    if (::getuid() != 0) {
+        LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
+        return NOT_ROOT;
+    }
 
     // If somehow this executable is delivered on a "user" build, it can
     // not function, so providing a clear message to the caller rather than
@@ -159,14 +500,14 @@
     }
 
     const char* fstab_file = nullptr;
-    auto can_reboot = false;
+    auto auto_reboot = false;
+    std::vector<std::string> partition_args;
 
     struct option longopts[] = {
             {"fstab", required_argument, nullptr, 'T'},
             {"help", no_argument, nullptr, 'h'},
             {"reboot", no_argument, nullptr, 'R'},
             {"verbose", no_argument, nullptr, 'v'},
-            {"clean_scratch_files", no_argument, nullptr, 'C'},
             {0, 0, nullptr, 0},
     };
     for (int opt; (opt = ::getopt_long(argc, argv, "hRT:v", longopts, nullptr)) != -1;) {
@@ -175,7 +516,7 @@
                 usage(SUCCESS);
                 break;
             case 'R':
-                can_reboot = true;
+                auto_reboot = true;
                 break;
             case 'T':
                 if (fstab_file) {
@@ -187,8 +528,6 @@
             case 'v':
                 verbose = true;
                 break;
-            case 'C':
-                return CLEAN_SCRATCH_FILES;
             default:
                 LOG(ERROR) << "Bad Argument -" << char(opt);
                 usage(BADARG);
@@ -196,310 +535,51 @@
         }
     }
 
-    // Make sure we are root.
-    if (::getuid() != 0) {
-        LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
-        return NOT_ROOT;
+    for (; argc > optind; ++optind) {
+        partition_args.emplace_back(argv[optind]);
+    }
+
+    // Make sure checkpointing is disabled if necessary.
+    if (auto rv = VerifyCheckpointing(); rv != REMOUNT_SUCCESS) {
+        return rv;
     }
 
     // Read the selected fstab.
-    android::fs_mgr::Fstab fstab;
-    auto fstab_read = false;
-    if (fstab_file) {
-        fstab_read = android::fs_mgr::ReadFstabFromFile(fstab_file, &fstab);
-    } else {
-        fstab_read = android::fs_mgr::ReadDefaultFstab(&fstab);
-        // Manufacture a / entry from /proc/mounts if missing.
-        if (!GetEntryForMountPoint(&fstab, "/system") && !GetEntryForMountPoint(&fstab, "/")) {
-            android::fs_mgr::Fstab mounts;
-            if (android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts)) {
-                if (auto entry = GetEntryForMountPoint(&mounts, "/")) {
-                    if (entry->fs_type != "rootfs") fstab.emplace_back(*entry);
-                }
-            }
-        }
-    }
-    if (!fstab_read || fstab.empty()) {
+    Fstab fstab;
+    if (!ReadFstab(fstab_file, &fstab) || fstab.empty()) {
         PLOG(ERROR) << "Failed to read fstab";
         return NO_FSTAB;
     }
 
-    if (android::base::GetBoolProperty("ro.virtual_ab.enabled", false) &&
-        !android::base::GetBoolProperty("ro.virtual_ab.retrofit", false)) {
-        // Virtual A/B devices can use /data as backing storage; make sure we're
-        // not checkpointing.
-        auto vold = GetVold();
-        bool checkpointing = false;
-        if (!vold->isCheckpointing(&checkpointing).isOk()) {
-            LOG(ERROR) << "Could not determine checkpointing status.";
-            return BINDER_ERROR;
-        }
-        if (checkpointing) {
-            LOG(ERROR) << "Cannot use remount when a checkpoint is in progress.";
-            return CHECKPOINTING;
-        }
+    RemountCheckResult check_result;
+    int result = do_remount(fstab, partition_args, &check_result);
+
+    if (check_result.disabled_verity && check_result.setup_overlayfs) {
+        LOG(INFO) << "Verity disabled; overlayfs enabled.";
+    } else if (check_result.disabled_verity) {
+        LOG(INFO) << "Verity disabled.";
+    } else if (check_result.setup_overlayfs) {
+        LOG(INFO) << "Overlayfs enabled.";
     }
 
-    // Generate the list of supported overlayfs mount points.
-    auto overlayfs_candidates = fs_mgr_overlayfs_candidate_list(fstab);
-
-    // Generate the all remountable partitions sub-list
-    android::fs_mgr::Fstab all;
-    for (auto const& entry : fstab) {
-        if (!remountable_partition(entry)) continue;
-        if (overlayfs_candidates.empty() ||
-            GetEntryForMountPoint(&overlayfs_candidates, entry.mount_point) ||
-            (is_wrapped(overlayfs_candidates, entry) == nullptr)) {
-            all.emplace_back(entry);
-        }
-    }
-
-    // Parse the unique list of valid partition arguments.
-    android::fs_mgr::Fstab partitions;
-    for (; argc > optind; ++optind) {
-        auto partition = std::string(argv[optind]);
-        if (partition.empty()) continue;
-        if (partition == "/") partition = "/system";
-        auto find_part = [&partition](const auto& entry) {
-            const auto mount_point = system_mount_point(entry);
-            if (partition == mount_point) return true;
-            if (partition == android::base::Basename(mount_point)) return true;
-            return false;
-        };
-        // Do we know about the partition?
-        auto it = std::find_if(fstab.begin(), fstab.end(), find_part);
-        if (it == fstab.end()) {
-            LOG(ERROR) << "Unknown partition " << argv[optind] << ", skipping";
-            retval = UNKNOWN_PARTITION;
-            continue;
-        }
-        // Is that one covered by an existing overlayfs?
-        auto wrap = is_wrapped(overlayfs_candidates, *it);
-        if (wrap) {
-            LOG(INFO) << "partition " << argv[optind] << " covered by overlayfs for "
-                      << wrap->mount_point << ", switching";
-            partition = system_mount_point(*wrap);
-        }
-        // Is it a remountable partition?
-        it = std::find_if(all.begin(), all.end(), find_part);
-        if (it == all.end()) {
-            LOG(ERROR) << "Invalid partition " << argv[optind] << ", skipping";
-            retval = INVALID_PARTITION;
-            continue;
-        }
-        if (GetEntryForMountPoint(&partitions, it->mount_point) == nullptr) {
-            partitions.emplace_back(*it);
-        }
-    }
-
-    if (partitions.empty() && !retval) {
-        partitions = all;
-    }
-
-    // Check verity and optionally setup overlayfs backing.
-    auto reboot_later = false;
-    auto user_please_reboot_later = false;
-    auto setup_overlayfs = false;
-    auto just_disabled_verity = false;
-    for (auto it = partitions.begin(); it != partitions.end();) {
-        auto& entry = *it;
-        auto& mount_point = entry.mount_point;
-        if (fs_mgr_is_verity_enabled(entry)) {
-            retval = VERITY_PARTITION;
-            auto ret = false;
-            if (android::base::GetProperty("ro.boot.vbmeta.device_state", "") != "locked") {
-                if (AvbOps* ops = avb_ops_user_new()) {
-                    ret = avb_user_verity_set(
-                            ops, android::base::GetProperty("ro.boot.slot_suffix", "").c_str(),
-                            false);
-                    avb_ops_user_free(ops);
-                }
-                if (!ret && fs_mgr_set_blk_ro(entry.blk_device, false)) {
-                    fec::io fh(entry.blk_device.c_str(), O_RDWR);
-                    ret = fh && fh.set_verity_status(false);
-                }
-                if (ret) {
-                    LOG(WARNING) << "Disabling verity for " << mount_point;
-                    just_disabled_verity = true;
-                    reboot_later = can_reboot;
-                    user_please_reboot_later = true;
-                }
+    if (check_result.reboot_later) {
+        if (auto_reboot) {
+            // If (1) remount requires a reboot to take effect, (2) system is currently
+            // running a DSU guest and (3) DSU is disabled, then enable DSU so that the
+            // next reboot would not take us back to the host system but stay within
+            // the guest system.
+            if (auto rv = EnableDsuIfNeeded(); rv != REMOUNT_SUCCESS) {
+                LOG(ERROR) << "Unable to automatically enable DSU";
+                return rv;
             }
-            if (!ret) {
-                LOG(ERROR) << "Skipping " << mount_point << " for remount";
-                it = partitions.erase(it);
-                continue;
-            }
+            reboot();
+        } else {
+            LOG(INFO) << "Now reboot your device for settings to take effect";
         }
-
-        auto change = false;
-        errno = 0;
-        if (fs_mgr_overlayfs_setup(nullptr, mount_point.c_str(), &change, just_disabled_verity)) {
-            if (change) {
-                LOG(INFO) << "Using overlayfs for " << mount_point;
-                reboot_later = can_reboot;
-                user_please_reboot_later = true;
-                setup_overlayfs = true;
-            }
-        } else if (errno) {
-            PLOG(ERROR) << "Overlayfs setup for " << mount_point << " failed, skipping";
-            retval = BAD_OVERLAY;
-            it = partitions.erase(it);
-            continue;
-        }
-        ++it;
+        return REMOUNT_SUCCESS;
     }
-
-    // If (1) remount requires a reboot to take effect, (2) system is currently
-    // running a DSU guest and (3) DSU is disabled, then enable DSU so that the
-    // next reboot would not take us back to the host system but stay within
-    // the guest system.
-    if (reboot_later) {
-        if (auto gsid = android::gsi::GetGsiService()) {
-            auto dsu_running = false;
-            if (auto status = gsid->isGsiRunning(&dsu_running); !status.isOk()) {
-                LOG(ERROR) << "Failed to get DSU running state: " << status;
-                return BINDER_ERROR;
-            }
-            auto dsu_enabled = false;
-            if (auto status = gsid->isGsiEnabled(&dsu_enabled); !status.isOk()) {
-                LOG(ERROR) << "Failed to get DSU enabled state: " << status;
-                return BINDER_ERROR;
-            }
-            if (dsu_running && !dsu_enabled) {
-                std::string dsu_slot;
-                if (auto status = gsid->getActiveDsuSlot(&dsu_slot); !status.isOk()) {
-                    LOG(ERROR) << "Failed to get active DSU slot: " << status;
-                    return BINDER_ERROR;
-                }
-                LOG(INFO) << "DSU is running but disabled, enable DSU so that we stay within the "
-                             "DSU guest system after reboot";
-                int error = 0;
-                if (auto status = gsid->enableGsi(/* oneShot = */ true, dsu_slot, &error);
-                    !status.isOk() || error != android::gsi::IGsiService::INSTALL_OK) {
-                    LOG(ERROR) << "Failed to enable DSU: " << status << ", error code: " << error;
-                    return !status.isOk() ? BINDER_ERROR : GSID_ERROR;
-                }
-                LOG(INFO) << "Successfully enabled DSU (one-shot mode)";
-            }
-        }
-    }
-
-    if (partitions.empty() || just_disabled_verity) {
-        if (reboot_later) reboot(setup_overlayfs);
-        if (user_please_reboot_later) {
-            return MUST_REBOOT;
-        }
-        LOG(WARNING) << "No partitions to remount";
-        return retval;
-    }
-
-    // Mount overlayfs.
-    errno = 0;
-    if (!fs_mgr_overlayfs_mount_all(&partitions) && errno) {
-        retval = BAD_OVERLAY;
-        PLOG(ERROR) << "Can not mount overlayfs for partitions";
-    }
-
-    // Get actual mounts _after_ overlayfs has been added.
-    android::fs_mgr::Fstab mounts;
-    if (!android::fs_mgr::ReadFstabFromFile("/proc/mounts", &mounts) || mounts.empty()) {
-        PLOG(ERROR) << "Failed to read /proc/mounts";
-        retval = NO_MOUNTS;
-    }
-
-    // Remount selected partitions.
-    for (auto& entry : partitions) {
-        // unlock the r/o key for the mount point device
-        if (entry.fs_mgr_flags.logical) {
-            fs_mgr_update_logical_partition(&entry);
-        }
-        auto blk_device = entry.blk_device;
-        auto mount_point = entry.mount_point;
-
-        auto found = false;
-        for (auto it = mounts.rbegin(); it != mounts.rend(); ++it) {
-            auto& rentry = *it;
-            if (mount_point == rentry.mount_point) {
-                blk_device = rentry.blk_device;
-                found = true;
-                break;
-            }
-            // Find overlayfs mount point?
-            if ((mount_point == "/" && rentry.mount_point == "/system")  ||
-                (mount_point == "/system" && rentry.mount_point == "/")) {
-                blk_device = rentry.blk_device;
-                mount_point = "/system";
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            PLOG(INFO) << "skip unmounted partition dev:" << blk_device << " mnt:" << mount_point;
-            continue;
-        }
-        if (blk_device == "/dev/root") {
-            auto from_fstab = GetEntryForMountPoint(&fstab, mount_point);
-            if (from_fstab) blk_device = from_fstab->blk_device;
-        }
-        fs_mgr_set_blk_ro(blk_device, false);
-
-        // Find system-as-root mount point?
-        if ((mount_point == "/system") && !GetEntryForMountPoint(&mounts, mount_point) &&
-            GetEntryForMountPoint(&mounts, "/")) {
-            mount_point = "/";
-        }
-
-        // Now remount!
-        if (::mount(blk_device.c_str(), mount_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
-                    nullptr) == 0) {
-            continue;
-        }
-        if ((errno == EINVAL) && (mount_point != entry.mount_point)) {
-            mount_point = entry.mount_point;
-            if (::mount(blk_device.c_str(), mount_point.c_str(), entry.fs_type.c_str(), MS_REMOUNT,
-                        nullptr) == 0) {
-                continue;
-            }
-        }
-        PLOG(ERROR) << "failed to remount partition dev:" << blk_device << " mnt:" << mount_point;
-        // If errno is EROFS at this point, we are dealing with r/o
-        // filesystem types like squashfs, erofs or ext4 dedupe. We will
-        // consider such a device that does not have CONFIG_OVERLAY_FS
-        // in the kernel as a misconfigured.
-        if (errno == EROFS) {
-            LOG(ERROR) << "Consider providing all the dependencies to enable overlayfs";
-        }
-        retval = REMOUNT_FAILED;
-    }
-
-    if (reboot_later) reboot(setup_overlayfs);
-    if (user_please_reboot_later) {
-        LOG(INFO) << "Now reboot your device for settings to take effect";
-        return 0;
-    }
-
-    return retval;
-}
-
-static int do_clean_scratch_files() {
-    android::fs_mgr::CleanupOldScratchFiles();
-    return 0;
-}
-
-int main(int argc, char* argv[]) {
-    android::base::InitLogging(argv, MyLogger);
-    if (argc > 0 && android::base::Basename(argv[0]) == "clean_scratch_files"s) {
-        return do_clean_scratch_files();
-    }
-    int result = do_remount(argc, argv);
-    if (result == MUST_REBOOT) {
-        LOG(INFO) << "Now reboot your device for settings to take effect";
-        result = 0;
-    } else if (result == REMOUNT_SUCCESS) {
+    if (result == REMOUNT_SUCCESS) {
         printf("remount succeeded\n");
-    } else if (result == CLEAN_SCRATCH_FILES) {
-        return do_clean_scratch_files();
     } else {
         printf("remount failed\n");
     }
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index 6caab1f..ec1d78f 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -26,12 +26,14 @@
 
 android::fs_mgr::Fstab fs_mgr_overlayfs_candidate_list(const android::fs_mgr::Fstab& fstab);
 
+bool fs_mgr_wants_overlayfs(android::fs_mgr::FstabEntry* entry);
 bool fs_mgr_overlayfs_mount_all(android::fs_mgr::Fstab* fstab);
-bool fs_mgr_overlayfs_setup(const char* backing = nullptr, const char* mount_point = nullptr,
-                            bool* change = nullptr, bool force = true);
+bool fs_mgr_overlayfs_setup(const char* mount_point = nullptr, bool* change = nullptr,
+                            bool force = true);
 bool fs_mgr_overlayfs_teardown(const char* mount_point = nullptr, bool* change = nullptr);
 bool fs_mgr_overlayfs_is_setup();
 bool fs_mgr_has_shared_blocks(const std::string& mount_point, const std::string& dev);
+bool fs_mgr_overlayfs_already_mounted(const std::string& mount_point, bool overlay_only = true);
 std::string fs_mgr_get_context(const std::string& mount_point);
 
 enum class OverlayfsValidResult {
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 6ee8d4a..b3763ae 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -97,8 +97,8 @@
     // This is non-zero when |state| == MERGING or MERGE_COMPLETED.
     uint64 metadata_sectors = 8;
 
-    // True if compression is enabled, false otherwise.
-    bool compression_enabled = 9;
+    // True if using snapuserd, false otherwise.
+    bool using_snapuserd = 9;
 
     // The old partition size (if none existed, this will be zero).
     uint64 old_partition_size = 10;
@@ -184,7 +184,7 @@
     uint64 metadata_sectors = 4;
 
     // Whether compression/dm-user was used for any snapshots.
-    bool compression_enabled = 5;
+    bool using_snapuserd = 5;
 
     // Merge phase (if state == MERGING).
     MergePhase merge_phase = 6;
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 653492c..45be191 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -34,12 +34,13 @@
 namespace android {
 namespace snapshot {
 
-CowReader::CowReader(ReaderFlags reader_flag)
+CowReader::CowReader(ReaderFlags reader_flag, bool is_merge)
     : fd_(-1),
       header_(),
       fd_size_(0),
-      merge_op_blocks_(std::make_shared<std::vector<uint32_t>>()),
-      reader_flag_(reader_flag) {}
+      block_pos_index_(std::make_shared<std::vector<int>>()),
+      reader_flag_(reader_flag),
+      is_merge_(is_merge) {}
 
 static void SHA256(const void*, size_t, uint8_t[]) {
 #if 0
@@ -58,13 +59,13 @@
     cow->fd_size_ = fd_size_;
     cow->last_label_ = last_label_;
     cow->ops_ = ops_;
-    cow->merge_op_blocks_ = merge_op_blocks_;
     cow->merge_op_start_ = merge_op_start_;
-    cow->block_map_ = block_map_;
     cow->num_total_data_ops_ = num_total_data_ops_;
     cow->num_ordered_ops_to_merge_ = num_ordered_ops_to_merge_;
     cow->has_seq_ops_ = has_seq_ops_;
     cow->data_loc_ = data_loc_;
+    cow->block_pos_index_ = block_pos_index_;
+    cow->is_merge_ = is_merge_;
     return cow;
 }
 
@@ -415,10 +416,10 @@
 //                        Replace-op-4, Zero-op-9, Replace-op-5 }
 //==============================================================
 bool CowReader::PrepMergeOps() {
-    auto merge_op_blocks = std::make_shared<std::vector<uint32_t>>();
+    auto merge_op_blocks = std::make_unique<std::vector<uint32_t>>();
     std::vector<int> other_ops;
     auto seq_ops_set = std::unordered_set<uint32_t>();
-    auto block_map = std::make_shared<std::unordered_map<uint32_t, int>>();
+    auto block_map = std::make_unique<std::unordered_map<uint32_t, int>>();
     size_t num_seqs = 0;
     size_t read;
 
@@ -482,8 +483,26 @@
         merge_op_start_ = header_.num_merge_ops;
     }
 
-    block_map_ = block_map;
-    merge_op_blocks_ = merge_op_blocks;
+    if (is_merge_) {
+        // Metadata ops are not required for merge. Thus, just re-arrange
+        // the ops vector as required for merge operations.
+        auto merge_ops_buffer = std::make_shared<std::vector<CowOperation>>();
+        merge_ops_buffer->reserve(num_total_data_ops_);
+        for (auto block : *merge_op_blocks) {
+            merge_ops_buffer->emplace_back(ops_->data()[block_map->at(block)]);
+        }
+        ops_->clear();
+        ops_ = merge_ops_buffer;
+        ops_->shrink_to_fit();
+    } else {
+        for (auto block : *merge_op_blocks) {
+            block_pos_index_->push_back(block_map->at(block));
+        }
+    }
+
+    block_map->clear();
+    merge_op_blocks->clear();
+
     return true;
 }
 
@@ -544,7 +563,7 @@
 
 class CowOpIter final : public ICowOpIter {
   public:
-    CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops);
+    CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops, uint64_t start);
 
     bool Done() override;
     const CowOperation& Get() override;
@@ -558,9 +577,9 @@
     std::vector<CowOperation>::iterator op_iter_;
 };
 
-CowOpIter::CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops) {
+CowOpIter::CowOpIter(std::shared_ptr<std::vector<CowOperation>>& ops, uint64_t start) {
     ops_ = ops;
-    op_iter_ = ops_->begin();
+    op_iter_ = ops_->begin() + start;
 }
 
 bool CowOpIter::RDone() {
@@ -589,9 +608,7 @@
 class CowRevMergeOpIter final : public ICowOpIter {
   public:
     explicit CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
-                               std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
-                               std::shared_ptr<std::unordered_map<uint32_t, int>> map,
-                               uint64_t start);
+                               std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
 
     bool Done() override;
     const CowOperation& Get() override;
@@ -602,17 +619,15 @@
 
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
-    std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
-    std::shared_ptr<std::unordered_map<uint32_t, int>> map_;
-    std::vector<uint32_t>::reverse_iterator block_riter_;
+    std::vector<int>::reverse_iterator block_riter_;
+    std::shared_ptr<std::vector<int>> cow_op_index_vec_;
     uint64_t start_;
 };
 
 class CowMergeOpIter final : public ICowOpIter {
   public:
     explicit CowMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
-                            std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
-                            std::shared_ptr<std::unordered_map<uint32_t, int>> map, uint64_t start);
+                            std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start);
 
     bool Done() override;
     const CowOperation& Get() override;
@@ -623,26 +638,21 @@
 
   private:
     std::shared_ptr<std::vector<CowOperation>> ops_;
-    std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
-    std::shared_ptr<std::unordered_map<uint32_t, int>> map_;
-    std::vector<uint32_t>::iterator block_iter_;
+    std::vector<int>::iterator block_iter_;
+    std::shared_ptr<std::vector<int>> cow_op_index_vec_;
     uint64_t start_;
 };
 
 CowMergeOpIter::CowMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
-                               std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
-                               std::shared_ptr<std::unordered_map<uint32_t, int>> map,
-                               uint64_t start) {
+                               std::shared_ptr<std::vector<int>> block_pos_index, uint64_t start) {
     ops_ = ops;
-    merge_op_blocks_ = merge_op_blocks;
-    map_ = map;
     start_ = start;
-
-    block_iter_ = merge_op_blocks->begin() + start;
+    cow_op_index_vec_ = block_pos_index;
+    block_iter_ = cow_op_index_vec_->begin() + start;
 }
 
 bool CowMergeOpIter::RDone() {
-    return block_iter_ == merge_op_blocks_->begin();
+    return block_iter_ == cow_op_index_vec_->begin();
 }
 
 void CowMergeOpIter::Prev() {
@@ -651,7 +661,7 @@
 }
 
 bool CowMergeOpIter::Done() {
-    return block_iter_ == merge_op_blocks_->end();
+    return block_iter_ == cow_op_index_vec_->end();
 }
 
 void CowMergeOpIter::Next() {
@@ -661,23 +671,20 @@
 
 const CowOperation& CowMergeOpIter::Get() {
     CHECK(!Done());
-    return ops_->data()[map_->at(*block_iter_)];
+    return ops_->data()[*block_iter_];
 }
 
 CowRevMergeOpIter::CowRevMergeOpIter(std::shared_ptr<std::vector<CowOperation>> ops,
-                                     std::shared_ptr<std::vector<uint32_t>> merge_op_blocks,
-                                     std::shared_ptr<std::unordered_map<uint32_t, int>> map,
+                                     std::shared_ptr<std::vector<int>> block_pos_index,
                                      uint64_t start) {
     ops_ = ops;
-    merge_op_blocks_ = merge_op_blocks;
-    map_ = map;
     start_ = start;
-
-    block_riter_ = merge_op_blocks->rbegin();
+    cow_op_index_vec_ = block_pos_index;
+    block_riter_ = cow_op_index_vec_->rbegin();
 }
 
 bool CowRevMergeOpIter::RDone() {
-    return block_riter_ == merge_op_blocks_->rbegin();
+    return block_riter_ == cow_op_index_vec_->rbegin();
 }
 
 void CowRevMergeOpIter::Prev() {
@@ -686,7 +693,7 @@
 }
 
 bool CowRevMergeOpIter::Done() {
-    return block_riter_ == merge_op_blocks_->rend() - start_;
+    return block_riter_ == cow_op_index_vec_->rend() - start_;
 }
 
 void CowRevMergeOpIter::Next() {
@@ -696,20 +703,20 @@
 
 const CowOperation& CowRevMergeOpIter::Get() {
     CHECK(!Done());
-    return ops_->data()[map_->at(*block_riter_)];
+    return ops_->data()[*block_riter_];
 }
 
-std::unique_ptr<ICowOpIter> CowReader::GetOpIter() {
-    return std::make_unique<CowOpIter>(ops_);
+std::unique_ptr<ICowOpIter> CowReader::GetOpIter(bool merge_progress) {
+    return std::make_unique<CowOpIter>(ops_, merge_progress ? merge_op_start_ : 0);
 }
 
 std::unique_ptr<ICowOpIter> CowReader::GetRevMergeOpIter(bool ignore_progress) {
-    return std::make_unique<CowRevMergeOpIter>(ops_, merge_op_blocks_, block_map_,
+    return std::make_unique<CowRevMergeOpIter>(ops_, block_pos_index_,
                                                ignore_progress ? 0 : merge_op_start_);
 }
 
 std::unique_ptr<ICowOpIter> CowReader::GetMergeOpIter(bool ignore_progress) {
-    return std::make_unique<CowMergeOpIter>(ops_, merge_op_blocks_, block_map_,
+    return std::make_unique<CowMergeOpIter>(ops_, block_pos_index_,
                                             ignore_progress ? 0 : merge_op_start_);
 }
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index f4d5c72..e8e4d72 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -74,7 +74,7 @@
     virtual bool GetLastLabel(uint64_t* label) = 0;
 
     // Return an iterator for retrieving CowOperation entries.
-    virtual std::unique_ptr<ICowOpIter> GetOpIter() = 0;
+    virtual std::unique_ptr<ICowOpIter> GetOpIter(bool merge_progress) = 0;
 
     // Return an iterator for retrieving CowOperation entries in reverse merge order
     virtual std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress) = 0;
@@ -115,7 +115,7 @@
         USERSPACE_MERGE = 1,
     };
 
-    CowReader(ReaderFlags reader_flag = ReaderFlags::DEFAULT);
+    CowReader(ReaderFlags reader_flag = ReaderFlags::DEFAULT, bool is_merge = false);
     ~CowReader() { owned_fd_ = {}; }
 
     // Parse the COW, optionally, up to the given label. If no label is
@@ -135,7 +135,7 @@
     // CowOperation objects. Get() returns a unique CowOperation object
     // whose lifetime depends on the CowOpIter object; the return
     // value of these will never be null.
-    std::unique_ptr<ICowOpIter> GetOpIter() override;
+    std::unique_ptr<ICowOpIter> GetOpIter(bool merge_progress = false) override;
     std::unique_ptr<ICowOpIter> GetRevMergeOpIter(bool ignore_progress = false) override;
     std::unique_ptr<ICowOpIter> GetMergeOpIter(bool ignore_progress = false) override;
 
@@ -170,14 +170,14 @@
     uint64_t fd_size_;
     std::optional<uint64_t> last_label_;
     std::shared_ptr<std::vector<CowOperation>> ops_;
-    std::shared_ptr<std::vector<uint32_t>> merge_op_blocks_;
     uint64_t merge_op_start_{};
-    std::shared_ptr<std::unordered_map<uint32_t, int>> block_map_;
+    std::shared_ptr<std::vector<int>> block_pos_index_;
     uint64_t num_total_data_ops_{};
     uint64_t num_ordered_ops_to_merge_{};
     bool has_seq_ops_{};
     std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
     ReaderFlags reader_flag_;
+    bool is_merge_{};
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index be18b42..f850b94 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -17,6 +17,7 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <unordered_map>
 #include <unordered_set>
 
 #include <android-base/file.h>
@@ -165,27 +166,25 @@
     android::dm::IDeviceMapper& impl_;
 };
 
-class SnapshotTestPropertyFetcher : public android::fs_mgr::testing::MockPropertyFetcher {
+class SnapshotTestPropertyFetcher : public android::fs_mgr::IPropertyFetcher {
   public:
-    SnapshotTestPropertyFetcher(const std::string& slot_suffix) {
-        using testing::Return;
-        ON_CALL(*this, GetProperty("ro.boot.slot_suffix", _)).WillByDefault(Return(slot_suffix));
-        ON_CALL(*this, GetBoolProperty("ro.boot.dynamic_partitions", _))
-                .WillByDefault(Return(true));
-        ON_CALL(*this, GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
-                .WillByDefault(Return(false));
-        ON_CALL(*this, GetBoolProperty("ro.virtual_ab.enabled", _)).WillByDefault(Return(true));
-    }
+    explicit SnapshotTestPropertyFetcher(const std::string& slot_suffix,
+                                         std::unordered_map<std::string, std::string>&& props = {});
+
+    std::string GetProperty(const std::string& key, const std::string& defaultValue) override;
+    bool GetBoolProperty(const std::string& key, bool defaultValue) override;
 
     static void SetUp(const std::string& slot_suffix = "_a") { Reset(slot_suffix); }
-
     static void TearDown() { Reset("_a"); }
 
   private:
     static void Reset(const std::string& slot_suffix) {
         IPropertyFetcher::OverrideForTesting(
-                std::make_unique<NiceMock<SnapshotTestPropertyFetcher>>(slot_suffix));
+                std::make_unique<SnapshotTestPropertyFetcher>(slot_suffix));
     }
+
+  private:
+    std::unordered_map<std::string, std::string> properties_;
 };
 
 // Helper for error-spam-free cleanup.
@@ -196,9 +195,10 @@
 // Expect space of |path| is multiple of 4K.
 bool WriteRandomData(const std::string& path, std::optional<size_t> expect_size = std::nullopt,
                      std::string* hash = nullptr);
-bool WriteRandomData(ICowWriter* writer, std::string* hash = nullptr);
 std::string HashSnapshot(ISnapshotWriter* writer);
 
+std::string ToHexString(const uint8_t* buf, size_t len);
+
 std::optional<std::string> GetHash(const std::string& path);
 
 // Add partitions and groups described by |manifest|.
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index 5569da0..7057223 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -143,7 +143,7 @@
 }
 
 std::optional<uint64_t> PartitionCowCreator::GetCowSize() {
-    if (compression_enabled) {
+    if (using_snapuserd) {
         if (update == nullptr || !update->has_estimate_cow_size()) {
             LOG(ERROR) << "Update manifest does not include a COW size";
             return std::nullopt;
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h
index 34b39ca..949e6c5 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.h
+++ b/fs_mgr/libsnapshot/partition_cow_creator.h
@@ -56,8 +56,8 @@
     // Extra extents that are going to be invalidated during the update
     // process.
     std::vector<ChromeOSExtent> extra_extents = {};
-    // True if compression is enabled.
-    bool compression_enabled = false;
+    // True if snapuserd COWs are enabled.
+    bool using_snapuserd = false;
     std::string compression_algorithm;
 
     struct Return {
diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
index de35c13..cf26a16 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
@@ -249,7 +249,7 @@
                                 .target_partition = system_b,
                                 .current_metadata = builder_a.get(),
                                 .current_suffix = "_a",
-                                .compression_enabled = true,
+                                .using_snapuserd = true,
                                 .update = &update};
 
     auto ret = creator.Run();
@@ -275,7 +275,7 @@
                                 .target_partition = system_b,
                                 .current_metadata = builder_a.get(),
                                 .current_suffix = "_a",
-                                .compression_enabled = true,
+                                .using_snapuserd = true,
                                 .update = nullptr};
 
     auto ret = creator.Run();
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index ab3e210..bc3efd9 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -398,7 +398,7 @@
     status->set_state(SnapshotState::CREATED);
     status->set_sectors_allocated(0);
     status->set_metadata_sectors(0);
-    status->set_compression_enabled(cow_creator->compression_enabled);
+    status->set_using_snapuserd(cow_creator->using_snapuserd);
     status->set_compression_algorithm(cow_creator->compression_algorithm);
 
     if (!WriteSnapshotStatus(lock, *status)) {
@@ -788,7 +788,7 @@
         }
     }
 
-    bool compression_enabled = false;
+    bool using_snapuserd = false;
 
     std::vector<std::string> first_merge_group;
 
@@ -809,7 +809,7 @@
             return false;
         }
 
-        compression_enabled |= snapshot_status.compression_enabled();
+        using_snapuserd |= snapshot_status.using_snapuserd();
         if (DecideMergePhase(snapshot_status) == MergePhase::FIRST_PHASE) {
             first_merge_group.emplace_back(snapshot);
         }
@@ -817,7 +817,7 @@
 
     SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get());
     initial_status.set_state(UpdateState::Merging);
-    initial_status.set_compression_enabled(compression_enabled);
+    initial_status.set_using_snapuserd(using_snapuserd);
 
     if (!UpdateUsesUserSnapshots(lock.get())) {
         initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
@@ -1364,7 +1364,7 @@
 }
 
 MergeFailureCode CheckMergeConsistency(const std::string& name, const SnapshotStatus& status) {
-    if (!status.compression_enabled()) {
+    if (!status.using_snapuserd()) {
         // Do not try to verify old-style COWs yet.
         return MergeFailureCode::Ok;
     }
@@ -1625,7 +1625,7 @@
         // as unmap will fail since dm-user itself was a snapshot device prior
         // to switching of tables. Unmap will fail as the device will be mounted
         // by system partitions
-        if (status.compression_enabled()) {
+        if (status.using_snapuserd()) {
             auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
             UnmapDmUserDevice(dm_user_name);
         }
@@ -2115,8 +2115,10 @@
 }
 
 bool SnapshotManager::UpdateUsesCompression(LockedFile* lock) {
+    // This returns true even if compression is "none", since update_engine is
+    // really just trying to see if snapuserd is in use.
     SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
-    return update_status.compression_enabled();
+    return update_status.using_snapuserd();
 }
 
 bool SnapshotManager::UpdateUsesIouring(LockedFile* lock) {
@@ -2436,13 +2438,13 @@
     remaining_time = GetRemainingTime(params.timeout_ms, begin);
     if (remaining_time.count() < 0) return false;
 
-    if (context == SnapshotContext::Update && live_snapshot_status->compression_enabled()) {
+    if (context == SnapshotContext::Update && live_snapshot_status->using_snapuserd()) {
         // Stop here, we can't run dm-user yet, the COW isn't built.
         created_devices.Release();
         return true;
     }
 
-    if (live_snapshot_status->compression_enabled()) {
+    if (live_snapshot_status->using_snapuserd()) {
         // Get the source device (eg the view of the partition from before it was resized).
         std::string source_device_path;
         if (live_snapshot_status->old_partition_size() > 0) {
@@ -2944,7 +2946,7 @@
     // build fingerprint.
     if (!(state == UpdateState::Initiated || state == UpdateState::None)) {
         SnapshotUpdateStatus old_status = ReadSnapshotUpdateStatus(lock);
-        status.set_compression_enabled(old_status.compression_enabled());
+        status.set_using_snapuserd(old_status.using_snapuserd());
         status.set_source_build_fingerprint(old_status.source_build_fingerprint());
         status.set_merge_phase(old_status.merge_phase());
         status.set_userspace_snapshots(old_status.userspace_snapshots());
@@ -3198,18 +3200,42 @@
     LOG(INFO) << " dap_metadata.cow_version(): " << dap_metadata.cow_version()
               << " writer.GetCowVersion(): " << writer.GetCowVersion();
 
-    bool use_compression = IsCompressionEnabled() && dap_metadata.vabc_enabled() &&
-                           !device_->IsRecovery() && cow_format_support;
+    // Deduce supported features.
+    bool userspace_snapshots = CanUseUserspaceSnapshots();
+    bool legacy_compression = GetLegacyCompressionEnabledProperty();
+
+    std::string vabc_disable_reason;
+    if (!dap_metadata.vabc_enabled()) {
+        vabc_disable_reason = "not enabled metadata";
+    } else if (device_->IsRecovery()) {
+        vabc_disable_reason = "recovery";
+    } else if (!cow_format_support) {
+        vabc_disable_reason = "cow format not supported";
+    }
+
+    if (!vabc_disable_reason.empty()) {
+        if (userspace_snapshots) {
+            LOG(INFO) << "Userspace snapshots disabled: " << vabc_disable_reason;
+        }
+        if (legacy_compression) {
+            LOG(INFO) << "Compression disabled: " << vabc_disable_reason;
+        }
+        userspace_snapshots = false;
+        legacy_compression = false;
+    }
+
+    const bool using_snapuserd = userspace_snapshots || legacy_compression;
+    if (!using_snapuserd) {
+        LOG(INFO) << "Using legacy Virtual A/B (dm-snapshot)";
+    }
 
     std::string compression_algorithm;
-    if (use_compression) {
+    if (using_snapuserd) {
         compression_algorithm = dap_metadata.vabc_compression_param();
         if (compression_algorithm.empty()) {
             // Older OTAs don't set an explicit compression type, so default to gz.
             compression_algorithm = "gz";
         }
-    } else {
-        compression_algorithm = "none";
     }
 
     PartitionCowCreator cow_creator{
@@ -3220,7 +3246,7 @@
             .current_suffix = current_suffix,
             .update = nullptr,
             .extra_extents = {},
-            .compression_enabled = use_compression,
+            .using_snapuserd = using_snapuserd,
             .compression_algorithm = compression_algorithm,
     };
 
@@ -3245,11 +3271,11 @@
         return Return::Error();
     }
 
-    // If compression is enabled, we need to retain a copy of the old metadata
+    // If snapuserd is enabled, we need to retain a copy of the old metadata
     // so we can access original blocks in case they are moved around. We do
     // not want to rely on the old super metadata slot because we don't
     // guarantee its validity after the slot switch is successful.
-    if (cow_creator.compression_enabled) {
+    if (using_snapuserd) {
         auto metadata = current_metadata->Export();
         if (!metadata) {
             LOG(ERROR) << "Could not export current metadata";
@@ -3265,70 +3291,36 @@
 
     SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
     status.set_state(update_state);
-    status.set_compression_enabled(cow_creator.compression_enabled);
-    if (cow_creator.compression_enabled) {
-        if (!device()->IsTestDevice()) {
-            bool userSnapshotsEnabled = IsUserspaceSnapshotsEnabled();
-            const std::string UNKNOWN = "unknown";
-            const std::string vendor_release = android::base::GetProperty(
-                    "ro.vendor.build.version.release_or_codename", UNKNOWN);
+    status.set_using_snapuserd(using_snapuserd);
 
-            // No user-space snapshots if vendor partition is on Android 12
-            if (vendor_release.find("12") != std::string::npos) {
-                LOG(INFO) << "Userspace snapshots disabled as vendor partition is on Android: "
-                          << vendor_release;
-                userSnapshotsEnabled = false;
-            }
+    if (userspace_snapshots) {
+        status.set_userspace_snapshots(true);
+        LOG(INFO) << "Virtual A/B using userspace snapshots";
 
-            // Userspace snapshots is enabled only if compression is enabled
-            status.set_userspace_snapshots(userSnapshotsEnabled);
-            if (userSnapshotsEnabled) {
-                is_snapshot_userspace_ = true;
-                status.set_io_uring_enabled(IsIouringEnabled());
-                LOG(INFO) << "Userspace snapshots enabled";
-            } else {
-                is_snapshot_userspace_ = false;
-                LOG(INFO) << "Userspace snapshots disabled";
-            }
+        if (GetIouringEnabledProperty()) {
+            status.set_io_uring_enabled(true);
+            LOG(INFO) << "io_uring for snapshots enabled";
+        }
+    } else if (legacy_compression) {
+        LOG(INFO) << "Virtual A/B using legacy snapuserd";
+    } else {
+        LOG(INFO) << "Virtual A/B using dm-snapshot";
+    }
 
-            // Terminate stale daemon if any
-            std::unique_ptr<SnapuserdClient> snapuserd_client =
-                    SnapuserdClient::Connect(kSnapuserdSocket, 5s);
-            if (snapuserd_client) {
-                snapuserd_client->DetachSnapuserd();
-                snapuserd_client->CloseConnection();
-                snapuserd_client = nullptr;
-            }
+    is_snapshot_userspace_.emplace(userspace_snapshots);
 
-            // Clear the cached client if any
-            if (snapuserd_client_) {
-                snapuserd_client_->CloseConnection();
-                snapuserd_client_ = nullptr;
-            }
-        } else {
-            bool userSnapshotsEnabled = true;
-            const std::string UNKNOWN = "unknown";
-            const std::string vendor_release = android::base::GetProperty(
-                    "ro.vendor.build.version.release_or_codename", UNKNOWN);
-
-            // No user-space snapshots if vendor partition is on Android 12
-            if (vendor_release.find("12") != std::string::npos) {
-                LOG(INFO) << "Userspace snapshots disabled as vendor partition is on Android: "
-                          << vendor_release;
-                userSnapshotsEnabled = false;
-            }
-
-            userSnapshotsEnabled = (userSnapshotsEnabled && !IsDmSnapshotTestingEnabled());
-            status.set_userspace_snapshots(userSnapshotsEnabled);
-            if (!userSnapshotsEnabled) {
-                is_snapshot_userspace_ = false;
-                LOG(INFO) << "User-space snapshots disabled for testing";
-            } else {
-                is_snapshot_userspace_ = true;
-                LOG(INFO) << "User-space snapshots enabled for testing";
-            }
+    if (!device()->IsTestDevice() && using_snapuserd) {
+        // Terminate stale daemon if any
+        std::unique_ptr<SnapuserdClient> snapuserd_client = std::move(snapuserd_client_);
+        if (!snapuserd_client) {
+            snapuserd_client = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
+        }
+        if (snapuserd_client) {
+            snapuserd_client->DetachSnapuserd();
+            snapuserd_client->CloseConnection();
         }
     }
+
     if (!WriteSnapshotUpdateStatus(lock.get(), status)) {
         LOG(ERROR) << "Unable to write new update state";
         return Return::Error();
@@ -3521,7 +3513,7 @@
             return Return::Error();
         }
 
-        if (it->second.compression_enabled()) {
+        if (it->second.using_snapuserd()) {
             unique_fd fd(open(cow_path.c_str(), O_RDWR | O_CLOEXEC));
             if (fd < 0) {
                 PLOG(ERROR) << "open " << cow_path << " failed for snapshot "
@@ -3567,8 +3559,8 @@
     if (!ReadSnapshotStatus(lock.get(), params.GetPartitionName(), &status)) {
         return false;
     }
-    if (status.compression_enabled()) {
-        LOG(ERROR) << "Cannot use MapUpdateSnapshot with compressed snapshots";
+    if (status.using_snapuserd()) {
+        LOG(ERROR) << "Cannot use MapUpdateSnapshot with snapuserd";
         return false;
     }
 
@@ -3625,7 +3617,7 @@
         return nullptr;
     }
 
-    if (status.compression_enabled()) {
+    if (status.using_snapuserd()) {
         return OpenCompressedSnapshotWriter(lock.get(), source_device, params.GetPartitionName(),
                                             status, paths);
     }
@@ -3755,7 +3747,10 @@
     auto update_status = ReadSnapshotUpdateStatus(file.get());
 
     ss << "Update state: " << ReadUpdateState(file.get()) << std::endl;
-    ss << "Compression: " << update_status.compression_enabled() << std::endl;
+    ss << "Using snapuserd: " << update_status.using_snapuserd() << std::endl;
+    ss << "Using userspace snapshots: " << update_status.userspace_snapshots() << std::endl;
+    ss << "Using io_uring: " << update_status.io_uring_enabled() << std::endl;
+    ss << "Using XOR compression: " << GetXorCompressionEnabledProperty() << std::endl;
     ss << "Current slot: " << device_->GetSlotSuffix() << std::endl;
     ss << "Boot indicator: booting from " << GetCurrentSlot() << " slot" << std::endl;
     ss << "Rollback indicator: "
@@ -3976,7 +3971,7 @@
         if (!ReadSnapshotStatus(lock, snapshot, &status)) {
             return false;
         }
-        if (status.compression_enabled()) {
+        if (status.using_snapuserd()) {
             continue;
         }
 
@@ -4140,7 +4135,7 @@
     if (!lock) return false;
 
     auto status = ReadSnapshotUpdateStatus(lock.get());
-    return status.state() != UpdateState::None && status.compression_enabled();
+    return status.state() != UpdateState::None && status.using_snapuserd();
 }
 
 bool SnapshotManager::DetachSnapuserdForSelinux(std::vector<std::string>* snapuserd_argv) {
@@ -4166,7 +4161,7 @@
 }
 
 MergePhase SnapshotManager::DecideMergePhase(const SnapshotStatus& status) {
-    if (status.compression_enabled() && status.device_size() < status.old_partition_size()) {
+    if (status.using_snapuserd() && status.device_size() < status.old_partition_size()) {
         return MergePhase::FIRST_PHASE;
     }
     return MergePhase::SECOND_PHASE;
@@ -4208,8 +4203,7 @@
     SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock.get());
     stats->report()->set_iouring_used(update_status.io_uring_enabled());
     stats->report()->set_userspace_snapshots_used(update_status.userspace_snapshots());
-    stats->report()->set_xor_compression_used(
-            android::base::GetBoolProperty("ro.virtual_ab.compression.xor.enabled", false));
+    stats->report()->set_xor_compression_used(GetXorCompressionEnabledProperty());
 }
 
 bool SnapshotManager::DeleteDeviceIfExists(const std::string& name,
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index b889fd4..2233a38 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -40,6 +40,7 @@
 #include <libdm/dm.h>
 #include <libfiemap/image_manager.h>
 #include <liblp/builder.h>
+#include <openssl/sha.h>
 #include <storage_literals/storage_literals.h>
 
 #include <android/snapshot/snapshot.pb.h>
@@ -52,9 +53,11 @@
 #include <libsnapshot/mock_device_info.h>
 #include <libsnapshot/mock_snapshot.h>
 
-DEFINE_string(force_config, "", "Force testing mode (dmsnap, vab, vabc) ignoring device config.");
+DEFINE_string(force_mode, "",
+              "Force testing older modes (vab-legacy, vabc-legacy) ignoring device config.");
 DEFINE_string(force_iouring_disable, "",
               "Force testing mode (iouring_disabled) - disable io_uring");
+DEFINE_string(compression_method, "gz", "Default compression algorithm.");
 
 namespace android {
 namespace snapshot {
@@ -91,8 +94,6 @@
 std::string fake_super;
 
 void MountMetadata();
-bool ShouldUseCompression();
-bool IsDaemonRequired();
 
 class SnapshotTest : public ::testing::Test {
   public:
@@ -108,7 +109,7 @@
     void SetUp() override {
         SKIP_IF_NON_VIRTUAL_AB();
 
-        SnapshotTestPropertyFetcher::SetUp();
+        SetupProperties();
         InitializeState();
         CleanupTestArtifacts();
         FormatFakeSuper();
@@ -116,6 +117,38 @@
         ASSERT_TRUE(sm->BeginUpdate());
     }
 
+    void SetupProperties() {
+        std::unordered_map<std::string, std::string> properties;
+
+        ASSERT_TRUE(android::base::SetProperty("snapuserd.test.dm.snapshots", "0"))
+                << "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
+        ASSERT_TRUE(android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0"))
+                << "Failed to set property: snapuserd.test.io_uring.disabled";
+
+        if (FLAGS_force_mode == "vabc-legacy") {
+            ASSERT_TRUE(android::base::SetProperty("snapuserd.test.dm.snapshots", "1"))
+                    << "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
+            properties["ro.virtual_ab.compression.enabled"] = "true";
+            properties["ro.virtual_ab.userspace.snapshots.enabled"] = "false";
+        } else if (FLAGS_force_mode == "vab-legacy") {
+            properties["ro.virtual_ab.compression.enabled"] = "false";
+            properties["ro.virtual_ab.userspace.snapshots.enabled"] = "false";
+        }
+
+        if (FLAGS_force_iouring_disable == "iouring_disabled") {
+            ASSERT_TRUE(android::base::SetProperty("snapuserd.test.io_uring.force_disable", "1"))
+                    << "Failed to set property: snapuserd.test.io_uring.disabled";
+            properties["ro.virtual_ab.io_uring.enabled"] = "false";
+        }
+
+        auto fetcher = std::make_unique<SnapshotTestPropertyFetcher>("_a", std::move(properties));
+        IPropertyFetcher::OverrideForTesting(std::move(fetcher));
+
+        if (GetLegacyCompressionEnabledProperty() || CanUseUserspaceSnapshots()) {
+            snapuserd_required_ = true;
+        }
+    }
+
     void TearDown() override {
         RETURN_IF_NON_VIRTUAL_AB();
 
@@ -316,7 +349,7 @@
     }
 
     AssertionResult DeleteDevice(const std::string& device) {
-        if (!dm_.DeleteDeviceIfExists(device)) {
+        if (!sm->DeleteDeviceIfExists(device, 1s)) {
             return AssertionFailure() << "Can't delete " << device;
         }
         return AssertionSuccess();
@@ -358,8 +391,11 @@
         DeltaArchiveManifest manifest;
 
         auto dynamic_partition_metadata = manifest.mutable_dynamic_partition_metadata();
-        dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+        dynamic_partition_metadata->set_vabc_enabled(snapuserd_required_);
         dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
+        if (snapuserd_required_) {
+            dynamic_partition_metadata->set_vabc_compression_param(FLAGS_compression_method);
+        }
 
         auto group = dynamic_partition_metadata->add_groups();
         group->set_name("group");
@@ -397,7 +433,7 @@
             if (!res) {
                 return res;
             }
-        } else if (!IsCompressionEnabled()) {
+        } else if (!snapuserd_required_) {
             std::string ignore;
             if (!MapUpdateSnapshot("test_partition_b", &ignore)) {
                 return AssertionFailure() << "Failed to map test_partition_b";
@@ -450,15 +486,16 @@
     std::unique_ptr<SnapshotManager::LockedFile> lock_;
     android::fiemap::IImageManager* image_manager_ = nullptr;
     std::string fake_super_;
+    bool snapuserd_required_ = false;
 };
 
 TEST_F(SnapshotTest, CreateSnapshot) {
     ASSERT_TRUE(AcquireLock());
 
     PartitionCowCreator cow_creator;
-    cow_creator.compression_enabled = ShouldUseCompression();
-    if (cow_creator.compression_enabled) {
-        cow_creator.compression_algorithm = "gz";
+    cow_creator.using_snapuserd = snapuserd_required_;
+    if (cow_creator.using_snapuserd) {
+        cow_creator.compression_algorithm = FLAGS_compression_method;
     } else {
         cow_creator.compression_algorithm = "none";
     }
@@ -484,7 +521,7 @@
         ASSERT_EQ(status.state(), SnapshotState::CREATED);
         ASSERT_EQ(status.device_size(), kDeviceSize);
         ASSERT_EQ(status.snapshot_size(), kDeviceSize);
-        ASSERT_EQ(status.compression_enabled(), cow_creator.compression_enabled);
+        ASSERT_EQ(status.using_snapuserd(), cow_creator.using_snapuserd);
         ASSERT_EQ(status.compression_algorithm(), cow_creator.compression_algorithm);
     }
 
@@ -497,7 +534,7 @@
     ASSERT_TRUE(AcquireLock());
 
     PartitionCowCreator cow_creator;
-    cow_creator.compression_enabled = ShouldUseCompression();
+    cow_creator.using_snapuserd = snapuserd_required_;
 
     static const uint64_t kDeviceSize = 1024 * 1024;
     SnapshotStatus status;
@@ -624,10 +661,10 @@
     SnapshotStatus status;
     ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
     ASSERT_EQ(status.state(), SnapshotState::CREATED);
-    if (ShouldUseCompression()) {
-        ASSERT_EQ(status.compression_algorithm(), "gz");
+    if (snapuserd_required_) {
+        ASSERT_EQ(status.compression_algorithm(), FLAGS_compression_method);
     } else {
-        ASSERT_EQ(status.compression_algorithm(), "none");
+        ASSERT_EQ(status.compression_algorithm(), "");
     }
 
     DeviceMapper::TargetInfo target;
@@ -898,8 +935,11 @@
         opener_ = std::make_unique<TestPartitionOpener>(fake_super);
 
         auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
-        dynamic_partition_metadata->set_vabc_enabled(ShouldUseCompression());
+        dynamic_partition_metadata->set_vabc_enabled(snapuserd_required_);
         dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
+        if (snapuserd_required_) {
+            dynamic_partition_metadata->set_vabc_compression_param(FLAGS_compression_method);
+        }
 
         // Create a fake update package metadata.
         // Not using full name "system", "vendor", "product" because these names collide with the
@@ -1031,7 +1071,7 @@
     }
 
     AssertionResult MapOneUpdateSnapshot(const std::string& name) {
-        if (ShouldUseCompression()) {
+        if (snapuserd_required_) {
             std::unique_ptr<ISnapshotWriter> writer;
             return MapUpdateSnapshot(name, &writer);
         } else {
@@ -1040,14 +1080,25 @@
         }
     }
 
-    AssertionResult WriteSnapshotAndHash(const std::string& name) {
-        if (ShouldUseCompression()) {
+    AssertionResult WriteSnapshots() {
+        for (const auto& partition : {sys_, vnd_, prd_}) {
+            auto res = WriteSnapshotAndHash(partition);
+            if (!res) {
+                return res;
+            }
+        }
+        return AssertionSuccess();
+    }
+
+    AssertionResult WriteSnapshotAndHash(PartitionUpdate* partition) {
+        std::string name = partition->partition_name() + "_b";
+        if (snapuserd_required_) {
             std::unique_ptr<ISnapshotWriter> writer;
             auto res = MapUpdateSnapshot(name, &writer);
             if (!res) {
                 return res;
             }
-            if (!WriteRandomData(writer.get(), &hashes_[name])) {
+            if (!WriteRandomSnapshotData(writer.get(), &hashes_[name])) {
                 return AssertionFailure() << "Unable to write random data to snapshot " << name;
             }
             if (!writer->Finalize()) {
@@ -1071,6 +1122,42 @@
                                   << ", hash: " << hashes_[name];
     }
 
+    bool WriteRandomSnapshotData(ICowWriter* writer, std::string* hash) {
+        unique_fd rand(open("/dev/urandom", O_RDONLY));
+        if (rand < 0) {
+            PLOG(ERROR) << "open /dev/urandom";
+            return false;
+        }
+
+        SHA256_CTX ctx;
+        SHA256_Init(&ctx);
+
+        if (!writer->options().max_blocks) {
+            LOG(ERROR) << "CowWriter must specify maximum number of blocks";
+            return false;
+        }
+        const auto num_blocks = writer->options().max_blocks.value();
+
+        const auto block_size = writer->options().block_size;
+        std::string block(block_size, '\0');
+        for (uint64_t i = 0; i < num_blocks; i++) {
+            if (!ReadFully(rand, block.data(), block.size())) {
+                PLOG(ERROR) << "read /dev/urandom";
+                return false;
+            }
+            if (!writer->AddRawBlocks(i, block.data(), block.size())) {
+                LOG(ERROR) << "Failed to add raw block " << i;
+                return false;
+            }
+            SHA256_Update(&ctx, block.data(), block.size());
+        }
+
+        uint8_t out[32];
+        SHA256_Final(out, &ctx);
+        *hash = ToHexString(out, sizeof(out));
+        return true;
+    }
+
     // Generate a snapshot that moves all the upper blocks down to the start.
     // It doesn't really matter the order, we just want copies that reference
     // blocks that won't exist if the partition shrinks.
@@ -1179,9 +1266,7 @@
     ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow"));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     // Assert that source partitions aren't affected.
     for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
@@ -1209,7 +1294,7 @@
 
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
-    ASSERT_EQ(init->IsSnapuserdRequired(), IsDaemonRequired());
+    ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_);
     {
         // We should have started in SECOND_PHASE since nothing shrinks.
         ASSERT_TRUE(AcquireLock());
@@ -1236,8 +1321,8 @@
 }
 
 TEST_F(SnapshotUpdateTest, DuplicateOps) {
-    if (!ShouldUseCompression()) {
-        GTEST_SKIP() << "Compression-only test";
+    if (!snapuserd_required_) {
+        GTEST_SKIP() << "snapuserd-only test";
     }
 
     // Execute the update.
@@ -1245,9 +1330,7 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     std::vector<PartitionUpdate*> partitions = {sys_, vnd_, prd_};
     for (auto* partition : partitions) {
@@ -1280,9 +1363,9 @@
 // Test that shrinking and growing partitions at the same time is handled
 // correctly in VABC.
 TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) {
-    if (!ShouldUseCompression()) {
+    if (!snapuserd_required_) {
         // b/179111359
-        GTEST_SKIP() << "Skipping Virtual A/B Compression test";
+        GTEST_SKIP() << "Skipping snapuserd test";
     }
 
     auto old_sys_size = GetSize(sys_);
@@ -1311,8 +1394,8 @@
         ASSERT_EQ(status.old_partition_size(), 3145728);
     }
 
-    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
-    ASSERT_TRUE(WriteSnapshotAndHash("vnd_b"));
+    ASSERT_TRUE(WriteSnapshotAndHash(sys_));
+    ASSERT_TRUE(WriteSnapshotAndHash(vnd_));
     ASSERT_TRUE(ShiftAllSnapshotBlocks("prd_b", old_prd_size));
 
     sync();
@@ -1343,7 +1426,7 @@
 
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
-    ASSERT_EQ(init->IsSnapuserdRequired(), IsDaemonRequired());
+    ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_);
     {
         // Check that the merge phase is FIRST_PHASE until at least one call
         // to ProcessUpdateState() occurs.
@@ -1397,9 +1480,9 @@
 
 // Test that a transient merge consistency check failure can resume properly.
 TEST_F(SnapshotUpdateTest, ConsistencyCheckResume) {
-    if (!ShouldUseCompression()) {
+    if (!snapuserd_required_) {
         // b/179111359
-        GTEST_SKIP() << "Skipping Virtual A/B Compression test";
+        GTEST_SKIP() << "Skipping snapuserd test";
     }
 
     auto old_sys_size = GetSize(sys_);
@@ -1415,8 +1498,8 @@
 
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
-    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
-    ASSERT_TRUE(WriteSnapshotAndHash("vnd_b"));
+    ASSERT_TRUE(WriteSnapshotAndHash(sys_));
+    ASSERT_TRUE(WriteSnapshotAndHash(vnd_));
     ASSERT_TRUE(ShiftAllSnapshotBlocks("prd_b", old_prd_size));
 
     sync();
@@ -1451,7 +1534,7 @@
 
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
-    ASSERT_EQ(init->IsSnapuserdRequired(), IsDaemonRequired());
+    ASSERT_EQ(init->IsSnapuserdRequired(), snapuserd_required_);
     {
         // Check that the merge phase is FIRST_PHASE until at least one call
         // to ProcessUpdateState() occurs.
@@ -1577,9 +1660,7 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     // Assert that source partitions aren't affected.
     for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
@@ -1738,9 +1819,7 @@
     ASSERT_FALSE(image_manager_->BackingImageExists("prd_b-cow-img"));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     // Assert that source partitions aren't affected.
     for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
@@ -2012,9 +2091,7 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name)) << name;
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */));
 
@@ -2054,17 +2131,15 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name)) << name;
-    }
+    ASSERT_TRUE(WriteSnapshots());
 
     // Create a stale snapshot that should not exist.
     {
         ASSERT_TRUE(AcquireLock());
 
         PartitionCowCreator cow_creator = {
-                .compression_enabled = ShouldUseCompression(),
-                .compression_algorithm = ShouldUseCompression() ? "gz" : "none",
+                .using_snapuserd = snapuserd_required_,
+                .compression_algorithm = snapuserd_required_ ? FLAGS_compression_method : "",
         };
         SnapshotStatus status;
         status.set_name("sys_a");
@@ -2139,7 +2214,7 @@
 
     // Map and write some data to target partition.
     ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"}));
-    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
+    ASSERT_TRUE(WriteSnapshotAndHash(sys_));
 
     // Finish update.
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
@@ -2160,8 +2235,8 @@
 
 // Test for overflow bit after update
 TEST_F(SnapshotUpdateTest, Overflow) {
-    if (ShouldUseCompression()) {
-        GTEST_SKIP() << "No overflow bit set for userspace COWs";
+    if (snapuserd_required_) {
+        GTEST_SKIP() << "No overflow bit set for snapuserd COWs";
     }
 
     const auto actual_write_size = GetSize(sys_);
@@ -2175,7 +2250,7 @@
 
     // Map and write some data to target partitions.
     ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"}));
-    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
+    ASSERT_TRUE(WriteSnapshotAndHash(sys_));
 
     std::vector<android::dm::DeviceMapper::TargetInfo> table;
     ASSERT_TRUE(DeviceMapper::Instance().GetTableStatus("sys_b", &table));
@@ -2235,8 +2310,8 @@
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b", "dlkm_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
+    for (const auto& partition : {sys_, vnd_, prd_, dlkm}) {
+        ASSERT_TRUE(WriteSnapshotAndHash(partition));
     }
 
     // Assert that source partitions aren't affected.
@@ -2295,8 +2370,8 @@
 };
 
 TEST_F(SnapshotUpdateTest, DaemonTransition) {
-    if (!ShouldUseCompression()) {
-        GTEST_SKIP() << "Skipping Virtual A/B Compression test";
+    if (!snapuserd_required_) {
+        GTEST_SKIP() << "Skipping snapuserd test";
     }
 
     // Ensure a connection to the second-stage daemon, but use the first-stage
@@ -2360,9 +2435,7 @@
     // 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(WriteSnapshots());
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
     ASSERT_TRUE(sm->MapAllSnapshots(10s));
 
@@ -2412,13 +2485,6 @@
     // fit in super, but not |prd|.
     constexpr uint64_t partition_size = 3788_KiB;
     SetSize(sys_, partition_size);
-    SetSize(vnd_, partition_size);
-    SetSize(prd_, 18_MiB);
-
-    // Make sure |prd| does not fit in super at all. On VABC, this means we
-    // fake an extra large COW for |vnd| to fill up super.
-    vnd_->set_estimate_cow_size(30_MiB);
-    prd_->set_estimate_cow_size(30_MiB);
 
     AddOperationForPartitions();
 
@@ -2430,23 +2496,7 @@
         GTEST_SKIP() << "Test does not apply to userspace snapshots";
     }
 
-    // Test that partitions prioritize using space in super.
-    auto tgt = MetadataBuilder::New(*opener_, "super", 1);
-    ASSERT_NE(tgt, nullptr);
-    ASSERT_NE(nullptr, tgt->FindPartition("sys_b-cow"));
-    ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow"));
-    ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow"));
-
-    // Write some data to target partitions.
-    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
-        ASSERT_TRUE(WriteSnapshotAndHash(name));
-    }
-
-    // Assert that source partitions aren't affected.
-    for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
-        ASSERT_TRUE(IsPartitionUnchanged(name));
-    }
-
+    ASSERT_TRUE(WriteSnapshots());
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     ASSERT_TRUE(UnmapAll());
@@ -2610,7 +2660,6 @@
     }
     void TearDown() override {
         RETURN_IF_NON_VIRTUAL_AB();
-        return;  // BUG(149738928)
 
         EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) ||
                     image_manager_->DeleteBackingImage(kImageName));
@@ -2619,19 +2668,6 @@
     std::unique_ptr<LowSpaceUserdata> userdata_;
 };
 
-TEST_P(ImageManagerTest, CreateImageEnoughAvailSpace) {
-    if (userdata_->available_space() == 0) {
-        GTEST_SKIP() << "/data is full (" << userdata_->available_space()
-                     << " bytes available), skipping";
-    }
-    ASSERT_TRUE(image_manager_->CreateBackingImage(kImageName, userdata_->available_space(),
-                                                   IImageManager::CREATE_IMAGE_DEFAULT))
-            << "Should be able to create image with size = " << userdata_->available_space()
-            << " bytes";
-    ASSERT_TRUE(image_manager_->DeleteBackingImage(kImageName))
-            << "Should be able to delete created image";
-}
-
 TEST_P(ImageManagerTest, CreateImageNoSpace) {
     uint64_t to_allocate = userdata_->free_space() + userdata_->bsize();
     auto res = image_manager_->CreateBackingImage(kImageName, to_allocate,
@@ -2751,41 +2787,17 @@
     }
 }
 
-bool IsDaemonRequired() {
-    if (FLAGS_force_config == "dmsnap") {
-        return false;
+void KillSnapuserd() {
+    auto status = android::base::GetProperty("init.svc.snapuserd", "stopped");
+    if (status == "stopped") {
+        return;
     }
-
-    if (!IsCompressionEnabled()) {
-        return false;
+    auto snapuserd_client = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
+    if (!snapuserd_client) {
+        return;
     }
-
-    const std::string UNKNOWN = "unknown";
-    const std::string vendor_release =
-            android::base::GetProperty("ro.vendor.build.version.release_or_codename", UNKNOWN);
-
-    // No userspace snapshots if vendor partition is on Android 12
-    // However, for GRF devices, snapuserd daemon will be on
-    // vendor ramdisk in Android 12.
-    if (vendor_release.find("12") != std::string::npos) {
-        return true;
-    }
-
-    if (!FLAGS_force_config.empty()) {
-        return true;
-    }
-
-    return IsUserspaceSnapshotsEnabled();
-}
-
-bool ShouldUseCompression() {
-    if (FLAGS_force_config == "vab" || FLAGS_force_config == "dmsnap") {
-        return false;
-    }
-    if (FLAGS_force_config == "vabc") {
-        return true;
-    }
-    return IsCompressionEnabled();
+    snapuserd_client->DetachSnapuserd();
+    snapuserd_client->CloseConnection();
 }
 
 }  // namespace snapshot
@@ -2798,35 +2810,20 @@
 
     android::base::SetProperty("ctl.stop", "snapuserd");
 
-    std::unordered_set<std::string> configs = {"", "dmsnap", "vab", "vabc"};
-    if (configs.count(FLAGS_force_config) == 0) {
+    std::unordered_set<std::string> modes = {"", "vab-legacy", "vabc-legacy"};
+    if (modes.count(FLAGS_force_mode) == 0) {
         std::cerr << "Unexpected force_config argument\n";
         return 1;
     }
 
-    if (FLAGS_force_config == "dmsnap") {
-        if (!android::base::SetProperty("snapuserd.test.dm.snapshots", "1")) {
-            return testing::AssertionFailure()
-                   << "Failed to disable property: virtual_ab.userspace.snapshots.enabled";
-        }
-    }
-
-    if (FLAGS_force_iouring_disable == "iouring_disabled") {
-        if (!android::base::SetProperty("snapuserd.test.io_uring.force_disable", "1")) {
-            return testing::AssertionFailure()
-                   << "Failed to disable property: snapuserd.test.io_uring.disabled";
-        }
-    }
+    // This is necessary if the configuration we're testing doesn't match the device.
+    android::snapshot::KillSnapuserd();
 
     int ret = RUN_ALL_TESTS();
 
-    if (FLAGS_force_config == "dmsnap") {
-        android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
-    }
+    android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
+    android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0");
 
-    if (FLAGS_force_iouring_disable == "iouring_disabled") {
-        android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0");
-    }
-
+    android::snapshot::KillSnapuserd();
     return ret;
 }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 8939b78..492c43f 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -162,7 +162,7 @@
 }
 
 bool SnapshotHandler::ReadMetadata() {
-    reader_ = std::make_unique<CowReader>(CowReader::ReaderFlags::USERSPACE_MERGE);
+    reader_ = std::make_unique<CowReader>(CowReader::ReaderFlags::USERSPACE_MERGE, true);
     CowHeader header;
     CowOptions options;
 
@@ -193,7 +193,7 @@
     UpdateMergeCompletionPercentage();
 
     // Initialize the iterator for reading metadata
-    std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetMergeOpIter();
+    std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetOpIter(true);
 
     int num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
     int ra_index = 0;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
index 17f3c4f..d57f434 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -71,16 +71,16 @@
 }
 
 bool Worker::MergeReplaceZeroOps() {
-    // Flush every 8192 ops. Since all ops are independent and there is no
+    // Flush after merging 2MB. Since all ops are independent and there is no
     // dependency between COW ops, we will flush the data and the number
-    // of ops merged in COW file for every 8192 ops. If there is a crash,
-    // we will end up replaying some of the COW ops which were already merged.
-    // That is ok.
+    // of ops merged in COW block device. If there is a crash, we will
+    // end up replaying some of the COW ops which were already merged. That is
+    // ok.
     //
-    // Why 8192 ops ? Increasing this may improve merge time 3-4 seconds but
-    // we need to make sure that we checkpoint; 8k ops seems optimal. In-case
-    // if there is a crash merge should always make forward progress.
-    int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 32;
+    // Although increasing this greater than 2MB may help in improving merge
+    // times; however, on devices with low memory, this can be problematic
+    // when there are multiple merge threads in parallel.
+    int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 2;
     int num_ops_merged = 0;
 
     SNAP_LOG(INFO) << "MergeReplaceZeroOps started....";
@@ -466,7 +466,7 @@
 }
 
 bool Worker::Merge() {
-    cowop_iter_ = reader_->GetMergeOpIter();
+    cowop_iter_ = reader_->GetOpIter(true);
 
     bool retry = false;
     bool ordered_ops_merge_status;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
index b9e4255..fbe57d2 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -772,7 +772,7 @@
 }
 
 void ReadAhead::InitializeRAIter() {
-    cowop_iter_ = reader_->GetMergeOpIter();
+    cowop_iter_ = reader_->GetOpIter(true);
 }
 
 bool ReadAhead::RAIterDone() {
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index 71fe124..b05123a 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -18,10 +18,12 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/parsebool.h>
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
+#include <liblp/property_fetcher.h>
 #include <openssl/sha.h>
 #include <payload_consumer/file_descriptor.h>
 
@@ -128,48 +130,6 @@
     return true;
 }
 
-bool WriteRandomData(ICowWriter* writer, std::string* hash) {
-    unique_fd rand(open("/dev/urandom", O_RDONLY));
-    if (rand < 0) {
-        PLOG(ERROR) << "open /dev/urandom";
-        return false;
-    }
-
-    SHA256_CTX ctx;
-    if (hash) {
-        SHA256_Init(&ctx);
-    }
-
-    if (!writer->options().max_blocks) {
-        LOG(ERROR) << "CowWriter must specify maximum number of blocks";
-        return false;
-    }
-    uint64_t num_blocks = writer->options().max_blocks.value();
-
-    size_t block_size = writer->options().block_size;
-    std::string block(block_size, '\0');
-    for (uint64_t i = 0; i < num_blocks; i++) {
-        if (!ReadFully(rand, block.data(), block.size())) {
-            PLOG(ERROR) << "read /dev/urandom";
-            return false;
-        }
-        if (!writer->AddRawBlocks(i, block.data(), block.size())) {
-            LOG(ERROR) << "Failed to add raw block " << i;
-            return false;
-        }
-        if (hash) {
-            SHA256_Update(&ctx, block.data(), block.size());
-        }
-    }
-
-    if (hash) {
-        uint8_t out[32];
-        SHA256_Final(out, &ctx);
-        *hash = ToHexString(out, sizeof(out));
-    }
-    return true;
-}
-
 std::string HashSnapshot(ISnapshotWriter* writer) {
     auto reader = writer->OpenReader();
     if (!reader) {
@@ -320,5 +280,38 @@
     return android::base::GetBoolProperty("ro.virtual_ab.enabled", false);
 }
 
+SnapshotTestPropertyFetcher::SnapshotTestPropertyFetcher(
+        const std::string& slot_suffix, std::unordered_map<std::string, std::string>&& props)
+    : properties_(std::move(props)) {
+    properties_["ro.boot.slot_suffix"] = slot_suffix;
+    properties_["ro.boot.dynamic_partitions"] = "true";
+    properties_["ro.boot.dynamic_partitions_retrofit"] = "false";
+    properties_["ro.virtual_ab.enabled"] = "true";
+}
+
+std::string SnapshotTestPropertyFetcher::GetProperty(const std::string& key,
+                                                     const std::string& defaultValue) {
+    auto iter = properties_.find(key);
+    if (iter == properties_.end()) {
+        return android::base::GetProperty(key, defaultValue);
+    }
+    return iter->second;
+}
+
+bool SnapshotTestPropertyFetcher::GetBoolProperty(const std::string& key, bool defaultValue) {
+    auto iter = properties_.find(key);
+    if (iter == properties_.end()) {
+        return android::base::GetBoolProperty(key, defaultValue);
+    }
+    switch (android::base::ParseBool(iter->second)) {
+        case android::base::ParseBoolResult::kTrue:
+            return true;
+        case android::base::ParseBoolResult::kFalse:
+            return false;
+        default:
+            return defaultValue;
+    }
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index f01bec9..0a1be0d 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -26,6 +26,7 @@
 #include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <fs_mgr/roots.h>
+#include <liblp/property_fetcher.h>
 
 using android::dm::kSectorSize;
 using android::fiemap::FiemapStatus;
@@ -33,6 +34,7 @@
 using android::fs_mgr::EnsurePathUnmounted;
 using android::fs_mgr::Fstab;
 using android::fs_mgr::GetEntryForPath;
+using android::fs_mgr::IPropertyFetcher;
 using android::fs_mgr::MetadataBuilder;
 using android::fs_mgr::Partition;
 using android::fs_mgr::ReadDefaultFstab;
@@ -184,16 +186,50 @@
     new_extent->set_num_blocks(num_blocks);
 }
 
-bool IsCompressionEnabled() {
-    return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false);
+bool GetLegacyCompressionEnabledProperty() {
+    auto fetcher = IPropertyFetcher::GetInstance();
+    return fetcher->GetBoolProperty("ro.virtual_ab.compression.enabled", false);
 }
 
-bool IsUserspaceSnapshotsEnabled() {
-    return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
+bool GetUserspaceSnapshotsEnabledProperty() {
+    auto fetcher = IPropertyFetcher::GetInstance();
+    return fetcher->GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
 }
 
-bool IsIouringEnabled() {
-    return android::base::GetBoolProperty("ro.virtual_ab.io_uring.enabled", false);
+bool CanUseUserspaceSnapshots() {
+    if (!GetUserspaceSnapshotsEnabledProperty()) {
+        return false;
+    }
+
+    auto fetcher = IPropertyFetcher::GetInstance();
+
+    const std::string UNKNOWN = "unknown";
+    const std::string vendor_release =
+            fetcher->GetProperty("ro.vendor.build.version.release_or_codename", UNKNOWN);
+
+    // No user-space snapshots if vendor partition is on Android 12
+    if (vendor_release.find("12") != std::string::npos) {
+        LOG(INFO) << "Userspace snapshots disabled as vendor partition is on Android: "
+                  << vendor_release;
+        return false;
+    }
+
+    if (IsDmSnapshotTestingEnabled()) {
+        LOG(INFO) << "Userspace snapshots disabled for testing";
+        return false;
+    }
+
+    return true;
+}
+
+bool GetIouringEnabledProperty() {
+    auto fetcher = IPropertyFetcher::GetInstance();
+    return fetcher->GetBoolProperty("ro.virtual_ab.io_uring.enabled", false);
+}
+
+bool GetXorCompressionEnabledProperty() {
+    auto fetcher = IPropertyFetcher::GetInstance();
+    return fetcher->GetBoolProperty("ro.virtual_ab.compression.xor.enabled", false);
 }
 
 std::string GetOtherPartitionName(const std::string& name) {
@@ -205,7 +241,8 @@
 }
 
 bool IsDmSnapshotTestingEnabled() {
-    return android::base::GetBoolProperty("snapuserd.test.dm.snapshots", false);
+    auto fetcher = IPropertyFetcher::GetInstance();
+    return fetcher->GetBoolProperty("snapuserd.test.dm.snapshots", false);
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index 0ef3234..eff6f10 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -59,7 +59,6 @@
     // On destruct, delete |name| from device mapper.
     AutoUnmapDevice(android::dm::IDeviceMapper* dm, const std::string& name)
         : AutoDevice(name), dm_(dm) {}
-    AutoUnmapDevice(AutoUnmapDevice&& other) = default;
     ~AutoUnmapDevice();
 
   private:
@@ -72,7 +71,6 @@
     // On destruct, delete |name| from image manager.
     AutoUnmapImage(android::fiemap::IImageManager* images, const std::string& name)
         : AutoDevice(name), images_(images) {}
-    AutoUnmapImage(AutoUnmapImage&& other) = default;
     ~AutoUnmapImage();
 
   private:
@@ -86,7 +84,6 @@
     AutoDeleteSnapshot(SnapshotManager* manager, SnapshotManager::LockedFile* lock,
                        const std::string& name)
         : AutoDevice(name), manager_(manager), lock_(lock) {}
-    AutoDeleteSnapshot(AutoDeleteSnapshot&& other);
     ~AutoDeleteSnapshot();
 
   private:
@@ -129,15 +126,16 @@
 void AppendExtent(google::protobuf::RepeatedPtrField<chromeos_update_engine::Extent>* extents,
                   uint64_t start_block, uint64_t num_blocks);
 
-bool IsCompressionEnabled();
+bool GetLegacyCompressionEnabledProperty();
+bool GetUserspaceSnapshotsEnabledProperty();
+bool GetIouringEnabledProperty();
+bool GetXorCompressionEnabledProperty();
 
-bool IsUserspaceSnapshotsEnabled();
-
+bool CanUseUserspaceSnapshots();
 bool IsDmSnapshotTestingEnabled();
 
-bool IsIouringEnabled();
-
 // Swap the suffix of a partition name.
 std::string GetOtherPartitionName(const std::string& name);
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index 9542bc1..0157f46 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -59,6 +59,7 @@
 print_time=false
 start_time=`date +%s`
 ACTIVE_SLOT=
+OVERLAYFS_BACKING="cache mnt/scratch"
 
 ADB_WAIT=4m
 FASTBOOT_WAIT=2m
@@ -68,6 +69,43 @@
 ##  Helper Functions
 ##
 
+[ "USAGE: LOG [RUN|OK|PASSED|WARNING|ERROR|FAILED|INFO] [message]..." ]
+LOG() {
+  case "${1}" in
+    R*)
+      shift
+      echo "${GREEN}[ RUN      ]${NORMAL}" "${@}"
+      ;;
+    OK)
+      shift
+      echo "${GREEN}[       OK ]${NORMAL}" "${@}"
+      ;;
+    P*)
+      shift
+      echo "${GREEN}[  PASSED  ]${NORMAL}" "${@}"
+      ;;
+    W*)
+      shift
+      echo "${YELLOW}[  WARNING ]${NORMAL}" "${@}"
+      ;;
+    E*)
+      shift
+      echo "${RED}[    ERROR ]${NORMAL}" "${@}"
+      ;;
+    F*)
+      shift
+      echo "${RED}[  FAILED  ]${NORMAL}" "${@}"
+      ;;
+    I*)
+      shift
+      echo "${BLUE}[     INFO ]${NORMAL}" "${@}"
+      ;;
+    *)
+      echo "${BLUE}[     INFO ]${NORMAL}" "${@}"
+      ;;
+  esac >&2
+}
+
 [ "USAGE: inFastboot
 
 Returns: true if device is in fastboot mode" ]
@@ -143,7 +181,7 @@
 
 Returns: the logcat output" ]
 adb_logcat() {
-  echo "${RED}[     INFO ]${NORMAL} logcat ${@}" >&2 &&
+  LOG INFO "logcat ${*}"
   adb logcat "${@}" </dev/null |
     tr -d '\r' |
     grep -v 'logd    : logdr: UID=' |
@@ -164,7 +202,7 @@
   if [ -z "${L}" ]; then
     return
   fi
-  echo "${YELLOW}[  WARNING ]${NORMAL} unlabeled sepolicy violations:" >&2
+  LOG WARNING "unlabeled sepolicy violations:"
   echo "${L}" | sed "s/^/${INDENT}/" >&2
 }
 
@@ -241,15 +279,15 @@
   if [ X"${duration}" != X"${duration%s}" ]; then
     duration=${duration%s}
   elif [ X"${duration}" != X"${duration%m}" ]; then
-    duration=`expr ${duration%m} \* 60`
+    duration=$(( ${duration%m} * 60 ))
   elif [ X"${duration}" != X"${duration%h}" ]; then
-    duration=`expr ${duration%h} \* 3600`
+    duration=$(( ${duration%h} * 3600 ))
   elif [ X"${duration}" != X"${duration%d}" ]; then
-    duration=`expr ${duration%d} \* 86400`
+    duration=$(( ${duration%d} * 86400 ))
   fi
-  local seconds=`expr ${duration} % 60`
-  local minutes=`expr \( ${duration} / 60 \) % 60`
-  local hours=`expr ${duration} / 3600`
+  local seconds=$(( ${duration} % 60 ))
+  local minutes=$(( ( ${duration} / 60 ) % 60 ))
+  local hours=$(( ${duration} / 3600 ))
   if [ 0 -eq ${minutes} -a 0 -eq ${hours} ]; then
     if [ 1 -eq ${duration} ]; then
       echo 1 second
@@ -265,10 +303,10 @@
     return
   fi
   if [ 0 -eq ${hours} ]; then
-    echo ${minutes}:`expr ${seconds} / 10``expr ${seconds} % 10`
+    echo ${minutes}:$(( ${seconds} / 10 ))$(( ${seconds} % 10 ))
     return
   fi
-  echo ${hours}:`expr ${minutes} / 10``expr ${minutes} % 10`:`expr ${seconds} / 10``expr ${seconds} % 10`
+  echo ${hours}:$(( ${minutes} / 10 ))$(( ${minutes} % 10 )):$(( ${seconds} / 10 ))$(( ${seconds} % 10))
 }
 
 [ "USAGE: USB_DEVICE=\`usb_devnum [--next]\`
@@ -282,7 +320,7 @@
     if [ -n "${usb_device}" ]; then
       USB_DEVICE=dev${usb_device}
     elif [ -n "${USB_DEVICE}" -a "${1}" ]; then
-      USB_DEVICE=dev`expr ${USB_DEVICE#dev} + 1`
+      USB_DEVICE=dev$(( ${USB_DEVICE#dev} + 1 ))
     fi
     echo "${USB_DEVICE}"
   fi
@@ -298,10 +336,10 @@
   if [ -n "${1}" -a -n "`which timeout`" ]; then
     USB_DEVICE=`usb_devnum --next`
     duration=`format_duration ${1}`
-    echo -n ". . . waiting ${duration}" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}"
+    echo -n ". . . waiting ${duration}" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} "${CR}" >&2
     timeout --preserve-status --signal=KILL ${1} adb wait-for-device 2>/dev/null
     ret=${?}
-    echo -n "                                                                             ${CR}"
+    echo -n "                                                                             ${CR}" >&2
   else
     adb wait-for-device
     ret=${?}
@@ -310,11 +348,11 @@
   if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${YELLOW}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}" >&2
+      LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
     fi
   fi
   local end=`date +%s`
-  local diff_time=`expr ${end} - ${start}`
+  local diff_time=$(( ${end} - ${start} ))
   local _print_time=${print_time}
   if [ ${diff_time} -lt 15 ]; then
     _print_time=false
@@ -339,8 +377,8 @@
       ;;
   esac
   if ${_print_time} || [ -n "${reason}" ]; then
-    echo "${BLUE}[     INFO ]${NORMAL} adb wait duration ${diff_time}${reason}"
-  fi >&2
+    LOG INFO "adb wait duration ${diff_time}${reason}"
+  fi
 
   return ${ret}
 }
@@ -413,8 +451,8 @@
   if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${YELLOW}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
-    fi >&2
+      LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
+    fi
   fi
   return ${ret}
 }
@@ -438,8 +476,8 @@
   if [ 0 = ${ret} -a -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${YELLOW}[  WARNING ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
-    fi >&2
+      LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
+    fi
   fi
   return ${ret}
 }
@@ -494,10 +532,10 @@
         break
       fi
     fi
-    counter=`expr ${counter} + 1`
+    counter=$(( ${counter} + 1 ))
     if [ ${counter} -gt ${timeout} ]; then
       ${exit_function}
-      echo "ERROR: wait_for_screen() timed out (`format_duration ${timeout}`)" >&2
+      LOG ERROR "wait_for_screen() timed out ($(format_duration ${timeout}))"
       return 1
     fi
     sleep 1
@@ -593,12 +631,12 @@
 Returns: reports duration" ]
 test_duration() {
   if ${print_time}; then
-    echo "${BLUE}[     INFO ]${NORMAL} end `date`"
+    LOG INFO "end $(date)"
     [ -n "${start_time}" ] || return
     end_time=`date +%s`
-    local diff_time=`expr ${end_time} - ${start_time}`
-    echo "${BLUE}[     INFO ]${NORMAL} duration `format_duration ${diff_time}`"
-  fi >&2
+    local diff_time=$(( ${end_time} - ${start_time} ))
+    LOG INFO "duration $(format_duration ${diff_time})"
+  fi
 }
 
 [ "USAGE: die [-d|-t <epoch>] [message] >/dev/stderr
@@ -618,121 +656,68 @@
     fi
     shift 2
   fi >&2
-  echo "${RED}[  FAILED  ]${NORMAL} ${@}" >&2
+  LOG FAILED "${@}"
   cleanup
   restore
   test_duration
   exit 1
 }
 
-[ "USAGE: EXPECT_EQ <lval> <rval> [--warning [message]]
-
-Returns true if (regex) lval matches rval" ]
-EXPECT_EQ() {
-  local lval="${1}"
-  local rval="${2}"
-  shift 2
-  local error=1
-  local prefix="${RED}[    ERROR ]${NORMAL}"
-  if [ X"${1}" = X"--warning" ]; then
-      prefix="${RED}[  WARNING ]${NORMAL}"
-      error=0
-      shift 1
-  fi
-  if ! ( echo X"${rval}" | grep '^X'"${lval}"'$' >/dev/null 2>/dev/null ); then
-    if [ `echo ${lval}${rval}${*} | wc -c` -gt 50 -o "${rval}" != "${rval%
-*}" ]; then
-      echo "${prefix} expected \"${lval}\""
-      echo "${prefix} got \"${rval}\"" |
-        sed ": again
-             N
-             s/\(\n\)\([^ ]\)/\1${INDENT}\2/
-             t again"
-      if [ -n "${*}" ] ; then
-        echo "${prefix} ${*}"
-      fi
-    else
-      echo "${prefix} expected \"${lval}\" got \"${rval}\" ${*}"
-    fi >&2
-    return ${error}
-  fi
-  if [ -n "${*}" ] ; then
-    prefix="${GREEN}[     INFO ]${NORMAL}"
-    if [ X"${lval}" != X"${rval}" ]; then  # we were supplied a regex?
-      if [ `echo ${lval}${rval}${*} | wc -c` -gt 60 -o "${rval}" != "${rval% *}" ]; then
-        echo "${prefix} ok \"${lval}\""
-        echo "       = \"${rval}\"" |
-          sed ": again
-               N
-               s/\(\n\)\([^ ]\)/\1${INDENT}\2/
-               t again"
-        if [ -n "${*}" ] ; then
-          echo "${prefix} ${*}"
-        fi
-      else
-        echo "${prefix} ok \"${lval}\" = \"${rval}\" ${*}"
-      fi
-    else
-      echo "${prefix} ok \"${lval}\" ${*}"
-    fi >&2
-  fi
-  return 0
-}
-
-[ "USAGE: EXPECT_NE <lval> <rval> [--warning [message]]
-
-Returns true if lval matches rval" ]
-EXPECT_NE() {
-  local lval="${1}"
-  local rval="${2}"
-  shift 2
-  local error=1
-  local prefix="${RED}[    ERROR ]${NORMAL}"
-  if [ X"${1}" = X"--warning" ]; then
-      prefix="${RED}[  WARNING ]${NORMAL}"
-      error=0
-      shift 1
-  fi
-  if [ X"${rval}" = X"${lval}" ]; then
-    echo "${prefix} did not expect \"${lval}\" ${*}" >&2
-    return ${error}
-  fi
-  if [ -n "${*}" ] ; then
-    echo "${prefix} ok \"${lval}\" not \"${rval}\" ${*}" >&2
-  fi
-  return 0
-}
-
 [ "USAGE: check_eq <lval> <rval> [--warning [message]]
 
-Exits if (regex) lval mismatches rval" ]
+Exits if (regex) lval mismatches rval.
+
+Returns: true if lval matches rval" ]
 check_eq() {
   local lval="${1}"
   local rval="${2}"
   shift 2
+  if [[ "${rval}" =~ ^${lval}$ ]]; then
+    return 0
+  fi
+
+  local error=true
+  local logt=ERROR
   if [ X"${1}" = X"--warning" ]; then
-      EXPECT_EQ "${lval}" "${rval}" ${*}
-      return
+    shift 1
+    error=false
+    logt=WARNING
   fi
-  if ! EXPECT_EQ "${lval}" "${rval}"; then
-    die "${@}"
+  if [ $(( ${#lval} + ${#rval} )) -gt 40 ]; then
+    LOG "${logt}" "expected \"${lval}\"
+${INDENT}got      \"${rval}\""
+  else
+    LOG "${logt}" "expected \"${lval}\" got \"${rval}\""
   fi
+  ${error} && die "${*}"
+  [ -n "${*}" ] && LOG "${logt}" "${*}"
+  return 1
 }
 
 [ "USAGE: check_ne <lval> <rval> [--warning [message]]
 
-Exits if lval matches rval" ]
+Exits if (regex) lval matches rval.
+
+Returns: true if lval mismatches rval" ]
 check_ne() {
   local lval="${1}"
   local rval="${2}"
   shift 2
+  if ! [[ "${rval}" =~ ^${lval}$ ]]; then
+    return 0
+  fi
+
+  local error=true
+  local logt=ERROR
   if [ X"${1}" = X"--warning" ]; then
-      EXPECT_NE "${lval}" "${rval}" ${*}
-      return
+      shift 1
+      error=false
+      logt=WARNING
   fi
-  if ! EXPECT_NE "${lval}" "${rval}"; then
-    die "${@}"
-  fi
+  LOG "${logt}" "unexpected \"${rval}\""
+  ${error} && die "${*}"
+  [ -n "${*}" ] && LOG "${logt}" "${*}"
+  return 1
 }
 
 [ "USAGE: join_with <delimiter> <strings>
@@ -788,6 +773,41 @@
       grep -v "[%] /\(data_mirror\|apex\|bionic\|system\|vendor\)/[^ ][^ ]*$"
 }
 
+[ "USAGE: surgically_wipe_overlayfs
+
+Surgically wipe any mounted overlayfs scratch files.
+
+Returns: true if wiped anything" ]
+surgically_wipe_overlayfs() {
+  local wiped_anything=false
+  for d in ${OVERLAYFS_BACKING}; do
+    if adb_su test -d "/${d}/overlay" </dev/null; then
+      LOG INFO "/${d}/overlay is setup, surgically wiping"
+      adb_su rm -rf "/${d}/overlay" </dev/null
+      wiped_anything=true
+    fi
+  done
+  ${wiped_anything}
+}
+
+[ "USAGE: is_overlayfs_mounted
+
+Returns: true if overlayfs is mounted" ]
+is_overlayfs_mounted() {
+  local df_output=$(adb_su df -k </dev/null)
+  local df_header_line=$(echo "${df_output}" | head -1)
+  local overlay_mounts=$(echo "${df_output}" | tail +2 |
+                         skip_unrelated_mounts |
+                         awk '$1 == "overlay" || $6 == "/mnt/scratch"')
+  if ! echo "${overlay_mounts}" | grep -q '^overlay '; then
+    return 1
+  fi >/dev/null 2>/dev/null
+  ( echo "${df_header_line}"
+    echo "${overlay_mounts}"
+  ) >&2
+  return 0
+}
+
 ##
 ##  MAINLINE
 ##
@@ -874,24 +894,21 @@
   NORMAL=""
 fi
 
-# Set an ERR trap handler to report any unhandled error
-trap 'die "line ${LINENO}: unhandled error"' ERR
-
 if ${print_time}; then
-  echo "${BLUE}[     INFO ]${NORMAL}" start `date` >&2
+  LOG INFO "start $(date)"
 fi
 
 inFastboot && die "device in fastboot mode"
 inRecovery && die "device in recovery mode"
 if ! inAdb; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} device not in adb mode" >&2
+  LOG WARNING "device not in adb mode"
   adb_wait ${ADB_WAIT}
 fi
 inAdb || die "specified device not in adb mode"
 isDebuggable || die "device not a debug build"
 enforcing=true
 if ! adb_su getenforce </dev/null | grep 'Enforcing' >/dev/null; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} device does not have sepolicy in enforcing mode" >&2
+  LOG WARNING "device does not have sepolicy in enforcing mode"
   enforcing=false
 fi
 
@@ -917,16 +934,16 @@
 fi
 [ -z "${ANDROID_SERIAL}${USB_ADDRESS}" ] ||
   USB_DEVICE=`usb_devnum`
-  echo "${BLUE}[     INFO ]${NORMAL}" ${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE} >&2
+  LOG INFO "${ANDROID_SERIAL} ${USB_ADDRESS} ${USB_DEVICE}"
 BUILD_DESCRIPTION=`get_property ro.build.description`
 [ -z "${BUILD_DESCRIPTION}" ] ||
-  echo "${BLUE}[     INFO ]${NORMAL} ${BUILD_DESCRIPTION}" >&2
+  LOG INFO "${BUILD_DESCRIPTION}"
 KERNEL_VERSION="`adb_su cat /proc/version </dev/null 2>/dev/null`"
 [ -z "${KERNEL_VERSION}" ] ||
-  echo "${BLUE}[     INFO ]${NORMAL} ${KERNEL_VERSION}" >&2
+  LOG INFO "${KERNEL_VERSION}"
 ACTIVE_SLOT=`get_active_slot`
 [ -z "${ACTIVE_SLOT}" ] ||
-  echo "${BLUE}[     INFO ]${NORMAL} active slot is ${ACTIVE_SLOT}" >&2
+  LOG INFO "active slot is ${ACTIVE_SLOT}"
 
 # Acquire list of system partitions
 
@@ -943,7 +960,7 @@
            echo /${i}
          done |
          tr '\n' ' '`"
-echo "${BLUE}[     INFO ]${NORMAL} System Partitions list: ${PARTITIONS}" >&2
+LOG INFO "System Partitions list: ${PARTITIONS}"
 
 # Report existing partition sizes
 adb_sh ls -l /dev/block/by-name/ /dev/block/mapper/ </dev/null 2>/dev/null |
@@ -964,66 +981,65 @@
         ;;
     esac
     size=`adb_su cat /sys/block/${device}/size 2>/dev/null </dev/null` &&
-      size=`expr ${size} / 2` &&
-      echo "${BLUE}[     INFO ]${NORMAL} partition ${name} device ${device} size ${size}K" >&2
+      size=$(( ${size} / 2 )) &&
+      LOG INFO "partition ${name} device ${device} size ${size}K"
   done
 
+overlayfs_supported=true
+can_restore_verity=false
+
+restore() {
+  LOG INFO "restoring device"
+  ${overlayfs_supported} || return 0
+  inFastboot &&
+    fastboot reboot &&
+    adb_wait "${ADB_WAIT}" ||
+    true
+  if ! inAdb; then
+    LOG ERROR "expect adb device"
+    return 1
+  fi
+  adb_root || true
+  local reboot=false
+  if surgically_wipe_overlayfs; then
+    reboot=true
+  fi
+  if ${can_restore_verity}; then
+    if ! adb enable-verity; then
+      LOG ERROR "adb enable-verity"
+      return 1
+    fi
+    LOG INFO "restored verity"
+    reboot=true
+  fi >&2
+  if ${reboot}; then
+    adb_reboot &&
+      adb_wait "${ADB_WAIT}"
+  fi
+}
+
 # If reboot too soon after fresh flash, could trip device update failure logic
 if ${screen_wait}; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} waiting for screen to come up. Consider --no-wait-screen option" >&2
+  LOG WARNING "waiting for screen to come up. Consider --no-wait-screen option"
 fi
 if ! wait_for_screen && ${screen_wait}; then
   screen_wait=false
-  echo "${YELLOW}[  WARNING ]${NORMAL} not healthy, no launcher, skipping wait for screen" >&2
+  LOG WARNING "not healthy, no launcher, skipping wait for screen"
 fi
 
 # Can we test remount -R command?
-OVERLAYFS_BACKING="cache mnt/scratch"
-overlayfs_supported=true
-if [ "orange" != "`get_property ro.boot.verifiedbootstate`" -o \
-     "2" != "`get_property partition.system.verified`" ]; then
-  restore() {
-    ${overlayfs_supported} || return 0
-    inFastboot &&
-      fastboot reboot &&
-      adb_wait ${ADB_WAIT} ||
-      true
-    if inAdb; then
-      reboot=false
-      for d in ${OVERLAYFS_BACKING}; do
-        if adb_test -d /${d}/overlay; then
-          adb_su rm -rf /${d}/overlay </dev/null
-          reboot=true
-        fi
-      done
-      if ${reboot}; then
-        adb_reboot &&
-        adb_wait ${ADB_WAIT}
-      fi
-    fi
-  }
-else
-  restore() {
-    ${overlayfs_supported} || return 0
-    inFastboot &&
-      fastboot reboot &&
-      adb_wait ${ADB_WAIT} ||
-      true
-    inAdb &&
-      adb_root &&
-      adb enable-verity >/dev/null 2>/dev/null &&
-      adb_reboot &&
-      adb_wait ${ADB_WAIT}
-  }
+if [ "orange" = "$(get_property ro.boot.verifiedbootstate)" ] &&
+   [ "2" = "$(get_property partition.system.verified)" ]; then
+  can_restore_verity=true
 
-  echo "${GREEN}[ RUN      ]${NORMAL} Testing adb shell su root remount -R command" >&2
+  LOG RUN "Testing adb shell su root remount -R command"
 
   avc_check
   T=`adb_date`
   adb_su remount -R system </dev/null
   err=${?}
   if [ "${err}" != 0 ]; then
-    echo "${YELLOW}[  WARNING ]${NORMAL} adb shell su root remount -R system = ${err}, likely did not reboot!" >&2
+    LOG WARNING "adb shell su root remount -R system = ${err}, likely did not reboot!"
     T="-t ${T}"
   else
     # Rebooted, logcat will be meaningless, and last logcat will likely be clear
@@ -1039,10 +1055,10 @@
 ${INDENT}partition.system.verified=\"`get_property partition.system.verified`\""
   fi
 
-  echo "${GREEN}[       OK ]${NORMAL} adb shell su root remount -R command" >&2
+  LOG OK "adb shell su root remount -R command"
 fi
 
-echo "${GREEN}[ RUN      ]${NORMAL} Testing kernel support for overlayfs" >&2
+LOG RUN "Testing kernel support for overlayfs"
 
 adb_wait || die "wait for device failed"
 adb_root ||
@@ -1050,57 +1066,45 @@
 
 adb_test -d /sys/module/overlay ||
   adb_sh grep "nodev${TAB}overlay" /proc/filesystems </dev/null >/dev/null 2>/dev/null &&
-  echo "${GREEN}[       OK ]${NORMAL} overlay module present" >&2 ||
+  LOG OK "overlay module present" ||
   (
-    echo "${YELLOW}[  WARNING ]${NORMAL} overlay module not present" >&2 &&
+    LOG WARNING "overlay module not present" &&
       false
   ) ||
   overlayfs_supported=false
 if ${overlayfs_supported}; then
   adb_test -f /sys/module/overlay/parameters/override_creds &&
-    echo "${GREEN}[       OK ]${NORMAL} overlay module supports override_creds" >&2 ||
+    LOG OK "overlay module supports override_creds" ||
     case `adb_sh uname -r </dev/null` in
       4.[456789].* | 4.[1-9][0-9]* | [56789].*)
-        echo "${YELLOW}[  WARNING ]${NORMAL} overlay module does not support override_creds" >&2 &&
+        LOG WARNING "overlay module does not support override_creds" &&
         overlayfs_supported=false
         ;;
       *)
-        echo "${GREEN}[       OK ]${NORMAL} overlay module uses caller's creds" >&2
+        LOG OK "overlay module uses caller's creds"
         ;;
     esac
 fi
 
-echo "${GREEN}[ RUN      ]${NORMAL} Checking current overlayfs status" >&2
+LOG RUN "Checking current overlayfs status"
 
 # We can not universally use adb enable-verity to ensure device is
 # in a overlayfs disabled state since it can prevent reboot on
 # devices that remount the physical content rather than overlayfs.
 # So lets do our best to surgically wipe the overlayfs state without
 # having to go through enable-verity transition.
-reboot=false
-for d in ${OVERLAYFS_BACKING}; do
-  if adb_test -d /${d}/overlay; then
-    echo "${YELLOW}[  WARNING ]${NORMAL} /${d}/overlay is setup, surgically wiping" >&2
-    adb_sh rm -rf /${d}/overlay </dev/null ||
-      die "/${d}/overlay wipe"
-    reboot=true
-  fi
-done
-if ${reboot}; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} rebooting before test" >&2
+if surgically_wipe_overlayfs; then
+  LOG WARNING "rebooting before test"
   adb_reboot &&
     adb_wait ${ADB_WAIT} ||
     die "lost device after reboot after wipe `usb_status`"
   adb_root ||
     die "lost device after elevation to root after wipe `usb_status`"
 fi
-D=`adb_sh df -k </dev/null` &&
-  H=`echo "${D}" | head -1` &&
-  D=`echo "${D}" | grep -v " /vendor/..*$" | grep "^overlay "` &&
-  echo "${H}" &&
-  echo "${D}" &&
-  echo "${YELLOW}[  WARNING ]${NORMAL} overlays present before setup" >&2 ||
-  echo "${GREEN}[       OK ]${NORMAL} no overlay present before setup" >&2
+is_overlayfs_mounted &&
+  die "overlay takeover unexpected at this phase"
+LOG OK "no overlay present before setup"
+
 overlayfs_needed=true
 D=`adb_sh cat /proc/mounts </dev/null |
    skip_administrative_mounts data`
@@ -1117,92 +1121,43 @@
 done
 D=`adb_sh df -k ${D} </dev/null |
    sed 's@\([%] /\)\(apex\|bionic\|system\|vendor\)/[^ ][^ ]*$@\1@'`
-echo "${D}"
+echo "${D}" >&2
 if [ X"${D}" = X"${D##* 100[%] }" ] && ${no_dedupe} ; then
   overlayfs_needed=false
   # if device does not need overlays, then adb enable-verity will brick device
-  restore() {
-    ${overlayfs_supported} || return 0
-    inFastboot &&
-      fastboot reboot &&
-      adb_wait ${ADB_WAIT}
-    inAdb &&
-      adb_wait ${ADB_WAIT}
-  }
+  can_restore_verity=false
 elif ! ${overlayfs_supported}; then
   die "need overlayfs, but do not have it"
 fi
 
-echo "${GREEN}[ RUN      ]${NORMAL} disable verity" >&2
+LOG RUN "disable-verity -R"
 
-T=`adb_date`
-H=`adb disable-verity 2>&1`
-err=${?}
 L=
-D="${H%?Now reboot your device for settings to take effect*}"
-if [ X"${D}" != X"${D##*[Uu]sing overlayfs}" ]; then
-  echo "${GREEN}[       OK ]${NORMAL} using overlayfs" >&2
-fi
-if [ ${err} != 0 ]; then
-  echo "${H}"
-  ( [ -n "${L}" ] && echo "${L}" && false ) ||
-  die -t "${T}" "disable-verity"
-fi
-rebooted=false
-if [ X"${D}" != X"${H}" ]; then
-  echo "${H}"
-  if [ X"${D}" != X"${D##*setup failed}" ]; then
-    echo "${YELLOW}[  WARNING ]${NORMAL} overlayfs setup whined" >&2
-  fi
-  D=`adb_sh df -k </dev/null` &&
-    H=`echo "${D}" | head -1` &&
-    D=`echo "${D}" | grep -v " /vendor/..*$" | grep "^overlay " || true` &&
-    [ -z "${D}" ] ||
-    ( echo "${H}" && echo "${D}" && false ) ||
-    die -t ${T} "overlay takeover unexpected at this phase"
-  echo "${GREEN}[     INFO ]${NORMAL} rebooting as requested" >&2
-  L=`adb_logcat -b all -v nsec -t ${T} 2>&1`
-  adb_reboot &&
-    adb_wait ${ADB_WAIT} ||
-    die "lost device after reboot requested `usb_status`"
-  adb_root ||
-    die "lost device after elevation to root `usb_status`"
-  rebooted=true
-  # re-disable verity to see the setup remarks expected
-  T=`adb_date`
-  H=`adb disable-verity 2>&1`
-  err=${?}
-  D="${H%?Now reboot your device for settings to take effect*}"
-  if [ X"${D}" != X"${D##*[Uu]sing overlayfs}" ]; then
-    echo "${GREEN}[       OK ]${NORMAL} using overlayfs" >&2
-  fi
-  if [ ${err} != 0 ]; then
-    T=
-  fi
-fi
-if ${overlayfs_supported} && ${overlayfs_needed} && [ X"${D}" != X"${D##*setup failed}" ]; then
-  echo "${D}"
-  ( [ -n "${L}" ] && echo "${L}" && false ) ||
-  die -t "${T}" "setup for overlay"
-fi
-if [ X"${D}" != X"${D##*Successfully disabled verity}" ]; then
-  echo "${H}"
-  D=`adb_sh df -k </dev/null` &&
-    H=`echo "${D}" | head -1` &&
-    D=`echo "${D}" | grep -v " /vendor/..*$" | grep "^overlay " || true` &&
-    [ -z "${D}" ] ||
-    ( echo "${H}" && echo "${D}" && false ) ||
-    ( [ -n "${L}" ] && echo "${L}" && false ) ||
-    die -t "${T}" "overlay takeover unexpected"
-  [ -n "${L}" ] && echo "${L}"
-  die -t "${T}" "unexpected report of verity being disabled a second time"
-elif ${rebooted}; then
-  echo "${GREEN}[       OK ]${NORMAL} verity already disabled" >&2
-else
-  echo "${YELLOW}[  WARNING ]${NORMAL} verity already disabled" >&2
+T=$(adb_date)
+H=$(adb_su disable-verity -R 2>&1)
+err="${?}"
+echo "${H}" >&2
+
+if [ "${err}" != 0 ]; then
+  die -t "${T}" "disable-verity -R"
 fi
 
-echo "${GREEN}[ RUN      ]${NORMAL} remount" >&2
+# Fuzzy search for a line that contains "overlay" and "fail". Informational only.
+if echo "${H}" | grep -i "overlay" | grep -iq "fail"; then
+  LOG WARNING "overlayfs setup whined"
+fi
+
+adb_wait "${ADB_WAIT}" &&
+  adb_root ||
+  die "lost device after adb shell su root disable-verity -R $(usb_status)"
+
+if ${overlayfs_needed}; then
+  if ! is_overlayfs_mounted; then
+    die "no overlay being setup after disable-verity -R"
+  fi
+fi
+
+LOG RUN "remount"
 
 # Feed log with selinux denials as baseline before overlays
 adb_unroot
@@ -1211,15 +1166,15 @@
 
 D=`adb remount 2>&1`
 ret=${?}
-echo "${D}"
+echo "${D}" >&2
 [ ${ret} != 0 ] ||
   [ X"${D}" = X"${D##*remount failed}" ] ||
-  ( [ -n "${L}" ] && echo "${L}" && false ) ||
+  ( [ -n "${L}" ] && echo "${L}" && false ) >&2 ||
   die -t "${T}" "adb remount failed"
 D=`adb_sh df -k </dev/null` &&
   H=`echo "${D}" | head -1` &&
   D=`echo "${D}" | skip_unrelated_mounts | grep "^overlay "` ||
-  ( [ -n "${L}" ] && echo "${L}" && false )
+  ( [ -n "${L}" ] && echo "${L}" && false ) >&2
 ret=${?}
 uses_dynamic_scratch=false
 scratch_partition=
@@ -1229,17 +1184,17 @@
     die -t ${T} "overlay takeover failed"
   fi
   echo "${D}" | grep "^overlay .* /system\$" >/dev/null ||
-   echo "${YELLOW}[  WARNING ]${NORMAL} overlay takeover not complete" >&2
+   LOG WARNING "overlay takeover not complete"
   if [ -z "${virtual_ab}" ]; then
     scratch_partition=scratch
   fi
   if echo "${D}" | grep " /mnt/scratch" >/dev/null; then
-    echo "${BLUE}[     INFO ]${NORMAL} using ${scratch_partition} dynamic partition for overrides" >&2
+    LOG INFO "using ${scratch_partition} dynamic partition for overrides"
   fi
   M=`adb_sh cat /proc/mounts </dev/null |
      sed -n 's@\([^ ]*\) /mnt/scratch \([^ ]*\) .*@\2 on \1@p'`
   [ -n "${M}" ] &&
-    echo "${BLUE}[     INFO ]${NORMAL} scratch filesystem ${M}"
+    LOG INFO "scratch filesystem ${M}"
   uses_dynamic_scratch=true
   if [ "${M}" != "${M##*/dev/block/by-name/}" ]; then
     uses_dynamic_scratch=false
@@ -1253,15 +1208,16 @@
                 done` &&
     [ -n "${scratch_size}" ] ||
     die "scratch size"
-  echo "${BLUE}[     INFO ]${NORMAL} scratch size ${scratch_size}KB" >&2
+  LOG INFO "scratch size ${scratch_size}KB"
   for d in ${OVERLAYFS_BACKING}; do
     if adb_test -d /${d}/overlay/system/upper; then
-      echo "${BLUE}[     INFO ]${NORMAL} /${d}/overlay is setup" >&2
+      LOG INFO "/${d}/overlay is setup"
     fi
   done
 
-  echo "${H}" &&
-    echo "${D}" &&
+  ( echo "${H}" &&
+    echo "${D}"
+  ) >&2 &&
     echo "${D}" | grep "^overlay .* /system\$" >/dev/null ||
     die  "overlay takeover after remount"
   !(adb_sh grep "^overlay " /proc/mounts </dev/null |
@@ -1296,7 +1252,7 @@
     D=`adb_sh df -k ${D} </dev/null |
        sed -e 's@\([%] /\)\(apex\|bionic\|system\|vendor\)/[^ ][^ ]*$@\1@' \
            -e 's/^Filesystem      /Filesystem (rw) /'`
-  [ -z "${D}" ] || echo "${D}"
+  [ -z "${D}" ] || echo "${D}" >&2
   ${bad_rw} && die "remount overlayfs missed a spot (rw)"
 else
   if [ ${ret} = 0 ]; then
@@ -1306,7 +1262,7 @@
 
 # Check something.
 
-echo "${GREEN}[ RUN      ]${NORMAL} push content to ${MOUNTS}" >&2
+LOG RUN "push content to ${MOUNTS}"
 
 A="Hello World! $(date)"
 for i in ${MOUNTS}; do
@@ -1319,26 +1275,9 @@
 B="`adb_cat /system/priv-app/hello`" ||
   die "system priv-app hello"
 check_eq "${A}" "${B}" /system/priv-app before reboot
-SYSTEM_DEVT=`adb_sh stat --format=%D /system/hello </dev/null`
-VENDOR_DEVT=`adb_sh stat --format=%D /vendor/hello </dev/null`
 SYSTEM_INO=`adb_sh stat --format=%i /system/hello </dev/null`
 VENDOR_INO=`adb_sh stat --format=%i /vendor/hello </dev/null`
-BASE_SYSTEM_DEVT=`adb_sh stat --format=%D /system/bin/stat </dev/null`
-BASE_VENDOR_DEVT=`adb_sh stat --format=%D /vendor/bin/stat </dev/null`
-check_eq "${SYSTEM_DEVT%[0-9a-fA-F][0-9a-fA-F]}" "${VENDOR_DEVT%[0-9a-fA-F][0-9a-fA-F]}" vendor and system devt
 check_ne "${SYSTEM_INO}" "${VENDOR_INO}" vendor and system inode
-if ${overlayfs_needed}; then
-  check_ne "${SYSTEM_DEVT}" "${BASE_SYSTEM_DEVT}" system devt
-  check_ne "${VENDOR_DEVT}" "${BASE_VENDOR_DEVT}" vendor devt
-else
-  check_eq "${SYSTEM_DEVT}" "${BASE_SYSTEM_DEVT}" system devt
-  check_eq "${VENDOR_DEVT}" "${BASE_VENDOR_DEVT}" vendor devt
-fi
-check_ne "${BASE_SYSTEM_DEVT}" "${BASE_VENDOR_DEVT}" --warning system/vendor devt
-[ -n "${SYSTEM_DEVT%[0-9a-fA-F][0-9a-fA-F]}" ] ||
-  echo "${YELLOW}[  WARNING ]${NORMAL} system devt ${SYSTEM_DEVT} major 0" >&2
-[ -n "${VENDOR_DEVT%[0-9a-fA-F][0-9a-fA-F]}" ] ||
-  echo "${YELLOW}[  WARNING ]${NORMAL} vendor devt ${VENDOR_DEVT} major 0" >&2
 
 # Download libc.so, append some garbage, push back, and check if the file
 # is updated.
@@ -1357,11 +1296,11 @@
 diff ${tempdir}/libc.so ${tempdir}/libc.so.fromdevice > /dev/null ||
   die "libc.so differ"
 
-echo "${GREEN}[ RUN      ]${NORMAL} reboot to confirm content persistent" >&2
+LOG RUN "reboot to confirm content persistent"
 
 fixup_from_recovery() {
   inRecovery || return 1
-  echo "${YELLOW}[    ERROR ]${NORMAL} Device in recovery" >&2
+  LOG ERROR "Device in recovery"
   adb reboot </dev/null
   adb_wait ${ADB_WAIT}
 }
@@ -1375,14 +1314,14 @@
   D=`adb_su df -k </dev/null` &&
     H=`echo "${D}" | head -1` &&
     D=`echo "${D}" | grep -v " /vendor/..*$" | grep "^overlay "` ||
-    ( echo "${L}" && false ) ||
+    ( echo "${L}" && false ) >&2 ||
     die -d "overlay takeover failed after reboot"
 
   adb_su sed -n '1,/overlay \/system/p' /proc/mounts </dev/null |
     skip_administrative_mounts |
     grep -v ' \(erofs\|squashfs\|ext4\|f2fs\|vfat\) ' &&
-    echo "${YELLOW}[  WARNING ]${NORMAL} overlay takeover after first stage init" >&2 ||
-    echo "${GREEN}[       OK ]${NORMAL} overlay takeover in first stage init" >&2
+    LOG WARNING "overlay takeover after first stage init" ||
+    LOG OK "overlay takeover in first stage init"
 fi
 
 if ${enforcing}; then
@@ -1390,7 +1329,7 @@
     die "device not in unroot'd state"
   B="`adb_cat /vendor/hello 2>&1`"
   check_eq "cat: /vendor/hello: Permission denied" "${B}" vendor after reboot w/o root
-  echo "${GREEN}[       OK ]${NORMAL} /vendor content correct MAC after reboot" >&2
+  LOG OK "/vendor content correct MAC after reboot"
   # Feed unprivileged log with selinux denials as a result of overlays
   wait_for_screen
   adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null || true
@@ -1408,16 +1347,11 @@
 for i in ${MOUNTS}; do
   B="`adb_cat ${i}/hello`"
   check_eq "${A}" "${B}" ${i#/} after reboot
-  echo "${GREEN}[       OK ]${NORMAL} ${i} content remains after reboot" >&2
+  LOG OK "${i} content remains after reboot"
 done
 
-check_eq "${SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/hello </dev/null`" system devt after reboot
-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`" --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 || true
@@ -1431,9 +1365,9 @@
 cleanup() {
   true
 }
-echo "${GREEN}[       OK ]${NORMAL} /system/lib/bootstrap/libc.so content remains after reboot" >&2
+LOG OK "/system/lib/bootstrap/libc.so content remains after reboot"
 
-echo "${GREEN}[ RUN      ]${NORMAL} flash vendor, confirm its content disappears" >&2
+LOG RUN "flash vendor, confirm its content disappears"
 
 H=`adb_sh echo '${HOSTNAME}' </dev/null 2>/dev/null`
 is_bootloader_fastboot=false
@@ -1442,20 +1376,20 @@
 is_userspace_fastboot=false
 
 if ! ${is_bootloader_fastboot}; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} does not support fastboot, skipping"
+  LOG WARNING "does not support fastboot, skipping"
 elif [ -z "${ANDROID_PRODUCT_OUT}" ]; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} build tree not setup, skipping"
+  LOG WARNING "build tree not setup, skipping"
 elif [ ! -s "${ANDROID_PRODUCT_OUT}/vendor.img" ]; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} vendor image missing, skipping"
+  LOG WARNING "vendor image missing, skipping"
 elif [ "${ANDROID_PRODUCT_OUT}" = "${ANDROID_PRODUCT_OUT%*/${H}}" ]; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} wrong vendor image, skipping"
+  LOG WARNING "wrong vendor image, skipping"
 elif [ -z "${ANDROID_HOST_OUT}" ]; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} please run lunch, skipping"
+  LOG WARNING "please run lunch, skipping"
 elif ! (
           adb_cat /vendor/build.prop |
           cmp -s ${ANDROID_PRODUCT_OUT}/vendor/build.prop
        ) >/dev/null 2>/dev/null; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} vendor image signature mismatch, skipping"
+  LOG WARNING "vendor image signature mismatch, skipping"
 else
   wait_for_screen
   avc_check
@@ -1469,7 +1403,7 @@
     die "fastboot flash vendor"
   fastboot_getvar is-userspace yes &&
     is_userspace_fastboot=true
-  if [ -n "${scratch_paritition}" ]; then
+  if [ -n "${scratch_partition}" ]; then
     fastboot_getvar partition-type:${scratch_partition} raw ||
       ( fastboot reboot && false) ||
       die "fastboot can not see ${scratch_partition} parameters"
@@ -1489,19 +1423,19 @@
         die "Reboot into fastboot"
     fi
     if ${uses_dynamic_scratch}; then
-      echo "${BLUE}[     INFO ]${NORMAL} expect fastboot erase ${scratch_partition} to fail" >&2
+      LOG INFO "expect fastboot erase ${scratch_partition} to fail"
       fastboot erase ${scratch_partition} &&
         ( fastboot reboot || true) &&
         die "fastboot can erase ${scratch_partition}"
     fi
-    echo "${BLUE}[     INFO ]${NORMAL} expect fastboot format ${scratch_partition} to fail" >&2
+    LOG INFO "expect fastboot format ${scratch_partition} to fail"
     fastboot format ${scratch_partition} &&
       ( fastboot reboot || true) &&
       die "fastboot can format ${scratch_partition}"
   fi
   fastboot reboot ||
     die "can not reboot out of fastboot"
-  echo "${YELLOW}[  WARNING ]${NORMAL} adb after fastboot"
+  LOG WARNING "adb after fastboot"
   adb_wait ${ADB_WAIT} ||
     fixup_from_recovery ||
     die "did not reboot after formatting ${scratch_partition} `usb_status`"
@@ -1510,16 +1444,17 @@
       D=`adb_sh df -k </dev/null` &&
       H=`echo "${D}" | head -1` &&
       D=`echo "${D}" | skip_unrelated_mounts | grep "^overlay "` &&
-      echo "${H}" &&
-      echo "${D}" &&
+      ( echo "${H}" &&
+        echo "${D}"
+      ) >&2 &&
       echo "${D}" | grep "^overlay .* /system\$" >/dev/null ||
       die  "overlay /system takeover after flash vendor"
     echo "${D}" | grep "^overlay .* /vendor\$" >/dev/null &&
       if ${is_userspace_fastboot}; then
         die  "overlay supposed to be minus /vendor takeover after flash vendor"
       else
-        echo "${YELLOW}[  WARNING ]${NORMAL} user fastboot missing required to invalidate, ignoring a failure" >&2
-        echo "${YELLOW}[  WARNING ]${NORMAL} overlay supposed to be minus /vendor takeover after flash vendor" >&2
+        LOG WARNING "user fastboot missing required to invalidate, ignoring a failure"
+        LOG WARNING "overlay supposed to be minus /vendor takeover after flash vendor"
       fi
   fi
   B="`adb_cat /system/hello`"
@@ -1537,20 +1472,17 @@
     check_eq "cat: /vendor/hello: No such file or directory" "${B}" \
              vendor content after flash vendor
   else
-    echo "${YELLOW}[  WARNING ]${NORMAL} user fastboot missing required to invalidate, ignoring a failure" >&2
+    LOG WARNING "user fastboot missing required to invalidate, ignoring a failure"
     check_eq "cat: /vendor/hello: No such file or directory" "${B}" \
              --warning vendor content after flash vendor
   fi
 
-  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`" --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
 
 wait_for_screen
-echo "${GREEN}[ RUN      ]${NORMAL} remove test content (cleanup)" >&2
+LOG RUN "remove test content (cleanup)"
 
 T=`adb_date`
 H=`adb remount 2>&1`
@@ -1558,7 +1490,7 @@
 L=
 D="${H%?Now reboot your device for settings to take effect*}"
 if [ X"${H}" != X"${D}" ]; then
-  echo "${YELLOW}[  WARNING ]${NORMAL} adb remount requires a reboot after partial flash (legacy avb)"
+  LOG WARNING "adb remount requires a reboot after partial flash (legacy avb)"
   L=`adb_logcat -b all -v nsec -t ${T} 2>&1`
   adb_reboot &&
     adb_wait ${ADB_WAIT} &&
@@ -1568,11 +1500,11 @@
   H=`adb remount 2>&1`
   err=${?}
 fi
-echo "${H}"
+echo "${H}" >&2
 [ ${err} = 0 ] &&
   ( adb_sh rm /vendor/hello </dev/null 2>/dev/null || true ) &&
   adb_sh rm /system/hello /system/priv-app/hello </dev/null ||
-  ( [ -n "${L}" ] && echo "${L}" && false ) ||
+  ( [ -n "${L}" ] && echo "${L}" && false ) >&2 ||
   die -t ${T} "cleanup hello"
 B="`adb_cat /system/hello`"
 check_eq "cat: /system/hello: No such file or directory" "${B}" after rm
@@ -1586,7 +1518,7 @@
 
 if ${is_bootloader_fastboot} && [ -n "${scratch_partition}" ]; then
 
-  echo "${GREEN}[ RUN      ]${NORMAL} test fastboot flash to ${scratch_partition} recovery" >&2
+  LOG RUN "test fastboot flash to ${scratch_partition} recovery"
 
   avc_check
   adb reboot fastboot </dev/null ||
@@ -1616,7 +1548,7 @@
   err=${?}
   if [ X"${D}" != "${D%?Now reboot your device for settings to take effect*}" ]
   then
-    echo "${YELLOW}[  WARNING ]${NORMAL} adb disable-verity requires a reboot after partial flash"
+    LOG WARNING "adb disable-verity requires a reboot after partial flash"
     adb_reboot &&
       adb_wait ${ADB_WAIT} &&
       adb_root ||
@@ -1627,32 +1559,32 @@
     err=${?}
   fi
 
-  echo "${D}"
+  echo "${D}" >&2
   [ ${err} = 0 ] &&
     [ X"${D}" = X"${D##*setup failed}" ] &&
     [ X"${D}" != X"${D##*[Uu]sing overlayfs}" ] &&
-    echo "${GREEN}[       OK ]${NORMAL} ${scratch_partition} recreated" >&2 ||
+    LOG OK "${scratch_partition} recreated" ||
     die -t ${T} "setup for overlayfs"
   D=`adb remount 2>&1`
   err=${?}
-  echo "${D}"
+  echo "${D}" >&2
   [ ${err} != 0 ] ||
     [ X"${D}" = X"${D##*remount failed}" ] ||
-    ( echo "${D}" && false ) ||
+    ( echo "${D}" && false ) >&2 ||
     die -t ${T} "remount failed"
 fi
 
-echo "${GREEN}[ RUN      ]${NORMAL} test raw remount commands" >&2
+LOG RUN "test raw remount commands"
 
 fixup_from_fastboot() {
   inFastboot || return 1
   if [ -n "${ACTIVE_SLOT}" ]; then
     local active_slot=`get_active_slot`
     if [ X"${ACTIVE_SLOT}" != X"${active_slot}" ]; then
-      echo "${YELLOW}[    ERROR ]${NORMAL} Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
+      LOG WARNING "Active slot changed from ${ACTIVE_SLOT} to ${active_slot}"
     else
-      echo "${YELLOW}[    ERROR ]${NORMAL} Active slot to be set to ${ACTIVE_SLOT}"
-    fi >&2
+      LOG WARNING "Active slot to be set to ${ACTIVE_SLOT}"
+    fi
     fastboot --set-active=${ACTIVE_SLOT}
   fi
   fastboot reboot
@@ -1670,7 +1602,7 @@
   die "remount command"
 adb_sh grep " /vendor .* rw," /proc/mounts >/dev/null </dev/null ||
   die "/vendor is not read-write"
-echo "${GREEN}[       OK ]${NORMAL} mount -o rw,remount command works" >&2
+LOG OK "mount -o rw,remount command works"
 
 # Prerequisite is a prepped device from above.
 adb_reboot &&
@@ -1685,17 +1617,12 @@
   die "/vendor is not read-write"
 adb_sh grep " /system .* rw," /proc/mounts >/dev/null </dev/null &&
   die "/vendor is not read-only"
-echo "${GREEN}[       OK ]${NORMAL} remount command works from setup" >&2
+LOG OK "remount command works from setup"
 
 # Prerequisite is an overlayfs deconstructed device but with verity disabled.
 # This also saves a lot of 'noise' from the command doing a mkfs on backing
 # storage and all the related tuning and adjustment.
-for d in ${OVERLAYFS_BACKING}; do
-  if adb_test -d /${d}/overlay; then
-    adb_su rm -rf /${d}/overlay </dev/null ||
-      die "/${d}/overlay wipe"
-  fi
-done
+surgically_wipe_overlayfs || true
 adb_reboot &&
   adb_wait ${ADB_WAIT} ||
   fixup_from_fastboot ||
@@ -1704,12 +1631,12 @@
   die "/vendor is not read-only"
 adb_su remount vendor </dev/null ||
   die "remount command"
-adb_su df -k </dev/null | skip_unrelated_mounts
+adb_su df -k </dev/null | skip_unrelated_mounts >&2
 adb_sh grep " /vendor .* rw," /proc/mounts >/dev/null </dev/null ||
   die "/vendor is not read-write"
 adb_sh grep " \(/system\|/\) .* rw," /proc/mounts >/dev/null </dev/null &&
   die "/system is not read-only"
-echo "${GREEN}[       OK ]${NORMAL} remount command works from scratch" >&2
+LOG OK "remount command works from scratch"
 
 if ! restore; then
   restore() {
@@ -1721,7 +1648,7 @@
 err=0
 
 if ${overlayfs_supported}; then
-  echo "${GREEN}[ RUN      ]${NORMAL} test 'adb remount -R'" >&2
+  LOG RUN "test 'adb remount -R'"
   avc_check
   adb_root ||
     die "adb root in preparation for adb remount -R"
@@ -1743,7 +1670,7 @@
 ${INDENT}partition.system.verified=\"`get_property partition.system.verified`\""
   fi
 
-  echo "${GREEN}[       OK ]${NORMAL} 'adb remount -R' command" >&2
+  LOG OK "'adb remount -R' command"
 
   restore
   err=${?}
@@ -1756,6 +1683,6 @@
 [ ${err} = 0 ] ||
   die "failed to restore verity"
 
-echo "${GREEN}[  PASSED  ]${NORMAL} adb remount" >&2
+LOG PASSED "adb remount test"
 
 test_duration
diff --git a/healthd/Android.bp b/healthd/Android.bp
index f180006..a090b74 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -342,20 +342,20 @@
     ],
 }
 
-// /vendor/etc/res/images/charger/battery_fail.png
+// /vendor/etc/res/images/default/charger/battery_fail.png
 prebuilt_etc {
     name: "system_core_charger_res_images_battery_fail.png_default_vendor",
     src: "images/battery_fail.png",
-    relative_install_path: "res/images/charger/default",
+    relative_install_path: "res/images/default/charger",
     vendor: true,
     filename: "battery_fail.png",
 }
 
-// /vendor/etc/res/images/charger/battery_scale.png
+// /vendor/etc/res/images/default/charger/battery_scale.png
 prebuilt_etc {
     name: "system_core_charger_res_images_battery_scale.png_default_vendor",
     src: "images/battery_scale.png",
-    relative_install_path: "res/images/charger/default",
+    relative_install_path: "res/images/default/charger",
     vendor: true,
     filename: "battery_scale.png",
 }
diff --git a/init/Android.bp b/init/Android.bp
index 6d36945..dfc90da 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -224,8 +224,8 @@
     ],
     whole_static_libs: [
         "libcap",
-        "com.android.sysprop.apex",
-        "com.android.sysprop.init",
+        "libcom.android.sysprop.apex",
+        "libcom.android.sysprop.init",
     ],
     header_libs: ["bootimg_headers"],
     proto: {
diff --git a/init/TEST_MAPPING b/init/TEST_MAPPING
index 36ca379..402b501 100644
--- a/init/TEST_MAPPING
+++ b/init/TEST_MAPPING
@@ -8,6 +8,14 @@
     },
     {
       "name": "MicrodroidHostTestCases"
+    },
+    {
+      "name": "CtsSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.security.cts.SeamendcHostTest"
+        }
+      ]
     }
   ],
   "hwasan-presubmit": [
@@ -19,6 +27,14 @@
     },
     {
       "name": "MicrodroidHostTestCases"
+    },
+    {
+      "name": "CtsSecurityHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.security.cts.SeamendcHostTest"
+        }
+      ]
     }
   ]
 }
diff --git a/init/epoll.cpp b/init/epoll.cpp
index 74d8aac..0580f86 100644
--- a/init/epoll.cpp
+++ b/init/epoll.cpp
@@ -23,6 +23,8 @@
 #include <functional>
 #include <map>
 
+#include <android-base/logging.h>
+
 namespace android {
 namespace init {
 
@@ -42,8 +44,11 @@
     if (!events) {
         return Error() << "Must specify events";
     }
-    auto sp = std::make_shared<decltype(handler)>(std::move(handler));
-    auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(sp));
+
+    Info info;
+    info.events = events;
+    info.handler = std::make_shared<decltype(handler)>(std::move(handler));
+    auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(info));
     if (!inserted) {
         return Error() << "Cannot specify two epoll handlers for a given FD";
     }
@@ -84,8 +89,14 @@
     }
     std::vector<std::shared_ptr<Handler>> pending_functions;
     for (int i = 0; i < num_events; ++i) {
-        auto sp = *reinterpret_cast<std::shared_ptr<Handler>*>(ev[i].data.ptr);
-        pending_functions.emplace_back(std::move(sp));
+        auto& info = *reinterpret_cast<Info*>(ev[i].data.ptr);
+        if ((info.events & (EPOLLIN | EPOLLPRI)) == (EPOLLIN | EPOLLPRI) &&
+            (ev[i].events & EPOLLIN) != ev[i].events) {
+            // This handler wants to know about exception events, and just got one.
+            // Log something informational.
+            LOG(ERROR) << "Received unexpected epoll event set: " << ev[i].events;
+        }
+        pending_functions.emplace_back(info.handler);
     }
 
     return pending_functions;
diff --git a/init/epoll.h b/init/epoll.h
index 0df5289..f58ae8d 100644
--- a/init/epoll.h
+++ b/init/epoll.h
@@ -46,8 +46,13 @@
             std::optional<std::chrono::milliseconds> timeout);
 
   private:
+    struct Info {
+        std::shared_ptr<Handler> handler;
+        uint32_t events;
+    };
+
     android::base::unique_fd epoll_fd_;
-    std::map<int, std::shared_ptr<Handler>> epoll_handlers_;
+    std::map<int, Info> epoll_handlers_;
 };
 
 }  // namespace init
diff --git a/init/init.cpp b/init/init.cpp
index 129cf48..9411b47 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -33,7 +33,10 @@
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
 #include <sys/_system_properties.h>
 
+#include <filesystem>
+#include <fstream>
 #include <functional>
+#include <iostream>
 #include <map>
 #include <memory>
 #include <mutex>
@@ -736,10 +739,10 @@
 
 static constexpr std::chrono::milliseconds kDiagnosticTimeout = 10s;
 
-static void HandleSignalFd() {
+static void HandleSignalFd(bool one_off) {
     signalfd_siginfo siginfo;
     auto started = std::chrono::steady_clock::now();
-    for (;;) {
+    do {
         ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));
         if (bytes_read < 0 && errno == EAGAIN) {
             auto now = std::chrono::steady_clock::now();
@@ -757,7 +760,7 @@
             return;
         }
         break;
-    }
+    } while (!one_off);
 
     switch (siginfo.ssi_signo) {
         case SIGCHLD:
@@ -817,7 +820,9 @@
         PLOG(FATAL) << "failed to create signalfd";
     }
 
-    if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); !result.ok()) {
+    constexpr int flags = EPOLLIN | EPOLLPRI;
+    auto handler = std::bind(HandleSignalFd, false);
+    if (auto result = epoll->RegisterHandler(signal_fd, handler, flags); !result.ok()) {
         LOG(FATAL) << result.error();
     }
 }
@@ -946,6 +951,32 @@
     return {};
 }
 
+static void DumpPidFds(const std::string& prefix, pid_t pid) {
+    std::error_code ec;
+    std::string proc_dir = "/proc/" + std::to_string(pid) + "/fd";
+    for (const auto& entry : std::filesystem::directory_iterator(proc_dir)) {
+        std::string target;
+        if (android::base::Readlink(entry.path(), &target)) {
+            LOG(ERROR) << prefix << target;
+        } else {
+            LOG(ERROR) << prefix << entry.path();
+        }
+    }
+}
+
+static void DumpFile(const std::string& prefix, const std::string& file) {
+    std::ifstream fp(file);
+    if (!fp) {
+        LOG(ERROR) << "Could not open " << file;
+        return;
+    }
+
+    std::string line;
+    while (std::getline(fp, line)) {
+        LOG(ERROR) << prefix << line;
+    }
+}
+
 int SecondStageMain(int argc, char** argv) {
     if (REBOOT_BOOTLOADER_ON_PANIC) {
         InstallRebootSignalHandlers();
@@ -1155,11 +1186,23 @@
                 (*function)();
             }
         } else if (Service::is_exec_service_running()) {
+            static bool dumped_diagnostics = false;
             std::chrono::duration<double> waited =
                     std::chrono::steady_clock::now() - Service::exec_service_started();
             if (waited >= kDiagnosticTimeout) {
                 LOG(ERROR) << "Exec service is hung? Waited " << waited.count()
                            << " without SIGCHLD";
+                if (!dumped_diagnostics) {
+                    DumpPidFds("exec service opened: ", Service::exec_service_pid());
+
+                    std::string status_file =
+                            "/proc/" + std::to_string(Service::exec_service_pid()) + "/status";
+                    DumpFile("exec service: ", status_file);
+                    dumped_diagnostics = true;
+
+                    LOG(INFO) << "Attempting to handle any stuck SIGCHLDs...";
+                    HandleSignalFd(true);
+                }
             }
         }
         if (!IsShuttingDown()) {
diff --git a/init/persistent_properties.cpp b/init/persistent_properties.cpp
index 716f62e..d33a6b8 100644
--- a/init/persistent_properties.cpp
+++ b/init/persistent_properties.cpp
@@ -155,19 +155,33 @@
     return *file_contents;
 }
 
+Result<PersistentProperties> ParsePersistentPropertyFile(const std::string& file_contents) {
+    PersistentProperties persistent_properties;
+    if (!persistent_properties.ParseFromString(file_contents)) {
+        return Error() << "Unable to parse persistent property file: Could not parse protobuf";
+    }
+    for (auto& prop : persistent_properties.properties()) {
+        if (!StartsWith(prop.name(), "persist.")) {
+            return Error() << "Unable to load persistent property file: property '" << prop.name()
+                           << "' doesn't start with 'persist.'";
+        }
+    }
+    return persistent_properties;
+}
+
 }  // namespace
 
 Result<PersistentProperties> LoadPersistentPropertyFile() {
     auto file_contents = ReadPersistentPropertyFile();
     if (!file_contents.ok()) return file_contents.error();
 
-    PersistentProperties persistent_properties;
-    if (persistent_properties.ParseFromString(*file_contents)) return persistent_properties;
-
-    // If the file cannot be parsed in either format, then we don't have any recovery
-    // mechanisms, so we delete it to allow for future writes to take place successfully.
-    unlink(persistent_property_filename.c_str());
-    return Error() << "Unable to parse persistent property file: Could not parse protobuf";
+    auto persistent_properties = ParsePersistentPropertyFile(*file_contents);
+    if (!persistent_properties.ok()) {
+        // If the file cannot be parsed in either format, then we don't have any recovery
+        // mechanisms, so we delete it to allow for future writes to take place successfully.
+        unlink(persistent_property_filename.c_str());
+    }
+    return persistent_properties;
 }
 
 Result<void> WritePersistentPropertyFile(const PersistentProperties& persistent_properties) {
diff --git a/init/persistent_properties_test.cpp b/init/persistent_properties_test.cpp
index 60cecde..e5d26db 100644
--- a/init/persistent_properties_test.cpp
+++ b/init/persistent_properties_test.cpp
@@ -155,5 +155,28 @@
     EXPECT_FALSE(it == read_back_properties.properties().end());
 }
 
+TEST(persistent_properties, RejectNonPersistProperty) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    persistent_property_filename = tf.path;
+
+    WritePersistentProperty("notpersist.sys.locale", "pt-BR");
+
+    auto read_back_properties = LoadPersistentProperties();
+    EXPECT_EQ(read_back_properties.properties().size(), 0);
+
+    WritePersistentProperty("persist.sys.locale", "pt-BR");
+
+    read_back_properties = LoadPersistentProperties();
+    EXPECT_GT(read_back_properties.properties().size(), 0);
+
+    auto it = std::find_if(read_back_properties.properties().begin(),
+                           read_back_properties.properties().end(), [](const auto& entry) {
+                               return entry.name() == "persist.sys.locale" &&
+                                      entry.value() == "pt-BR";
+                           });
+    EXPECT_FALSE(it == read_back_properties.properties().end());
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/service.cpp b/init/service.cpp
index 730b6b6..99a0367 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -127,6 +127,7 @@
 
 unsigned long Service::next_start_order_ = 1;
 bool Service::is_exec_service_running_ = false;
+pid_t Service::exec_service_pid_ = -1;
 std::chrono::time_point<std::chrono::steady_clock> Service::exec_service_started_;
 
 Service::Service(const std::string& name, Subcontext* subcontext_for_restart_commands,
@@ -395,6 +396,7 @@
 
     flags_ |= SVC_EXEC;
     is_exec_service_running_ = true;
+    exec_service_pid_ = pid_;
     exec_service_started_ = std::chrono::steady_clock::now();
 
     LOG(INFO) << "SVC_EXEC service '" << name_ << "' pid " << pid_ << " (uid " << proc_attr_.uid
@@ -651,6 +653,11 @@
                        << ") failed for service '" << name_ << "'";
     }
 
+    // When the blkio controller is mounted in the v1 hierarchy, NormalIoPriority is
+    // the default (/dev/blkio). When the blkio controller is mounted in the v2 hierarchy, the
+    // NormalIoPriority profile has to be applied explicitly.
+    SetProcessProfiles(proc_attr_.uid, pid_, {"NormalIoPriority"});
+
     if (use_memcg) {
         ConfigureMemcg();
     }
diff --git a/init/service.h b/init/service.h
index 7af3615..6d9a0ca 100644
--- a/init/service.h
+++ b/init/service.h
@@ -103,6 +103,7 @@
     size_t CheckAllCommands() const { return onrestart_.CheckAllCommands(); }
 
     static bool is_exec_service_running() { return is_exec_service_running_; }
+    static pid_t exec_service_pid() { return exec_service_pid_; }
     static std::chrono::time_point<std::chrono::steady_clock> exec_service_started() {
         return exec_service_started_;
     }
diff --git a/init/sigchld_handler.cpp b/init/sigchld_handler.cpp
index 9b2c7d9..6fc64df 100644
--- a/init/sigchld_handler.cpp
+++ b/init/sigchld_handler.cpp
@@ -95,7 +95,10 @@
         LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string;
     }
 
-    if (!service) return pid;
+    if (!service) {
+        LOG(INFO) << name << " did not have an associated service entry and will not be reaped";
+        return pid;
+    }
 
     service->Reap(siginfo);
 
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index 3054d2b..e071c96 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -440,12 +440,12 @@
 }
 
 // Another option to load kernel modules. load in independent modules in parallel
-// and then load modules which only have soft dependency, third update dependency list of other
-// remaining modules, repeat these steps until all modules are loaded.
+// and then update dependency list of other remaining modules, repeat these steps
+// until all modules are loaded.
 bool Modprobe::LoadModulesParallel(int num_threads) {
     bool ret = true;
+    int count = -1;
     std::map<std::string, std::set<std::string>> mod_with_deps;
-    std::map<std::string, std::set<std::string>> mod_with_softdeps;
 
     // Get dependencies
     for (const auto& module : module_load_) {
@@ -458,26 +458,36 @@
 
     // Get soft dependencies
     for (const auto& [it_mod, it_softdep] : module_pre_softdep_) {
-        mod_with_softdeps[MakeCanonical(it_mod)].emplace(it_softdep);
+        if (mod_with_deps.find(MakeCanonical(it_softdep)) != mod_with_deps.end()) {
+            mod_with_deps[MakeCanonical(it_mod)].emplace(
+                GetDependencies(MakeCanonical(it_softdep))[0]);
+        }
     }
 
     // Get soft post dependencies
     for (const auto& [it_mod, it_softdep] : module_post_softdep_) {
-        mod_with_softdeps[MakeCanonical(it_mod)].emplace(it_softdep);
+        if (mod_with_deps.find(MakeCanonical(it_softdep)) != mod_with_deps.end()) {
+            mod_with_deps[MakeCanonical(it_softdep)].emplace(
+                GetDependencies(MakeCanonical(it_mod))[0]);
+        }
     }
 
-    while (!mod_with_deps.empty()) {
+    while (!mod_with_deps.empty() &&  count != module_loaded_.size()) {
         std::vector<std::thread> threads;
         std::vector<std::string> mods_path_to_load;
-        std::vector<std::string> mods_with_softdep_to_load;
         std::mutex vector_lock;
+        count = module_loaded_.size();
 
-        // Find independent modules and modules only having soft dependencies
+        // Find independent modules
         for (const auto& [it_mod, it_dep] : mod_with_deps) {
-            if (it_dep.size() == 1 && mod_with_softdeps[it_mod].empty()) {
-                mods_path_to_load.emplace_back(*(it_dep.begin()));
-            } else if (it_dep.size() == 1) {
-                mods_with_softdep_to_load.emplace_back(it_mod);
+            if (it_dep.size() == 1) {
+                if (module_options_[it_mod].find("load_sequential=1") != std::string::npos) {
+                    if (!LoadWithAliases(it_mod, true) && !IsBlocklisted(it_mod)) {
+                      return false;
+                    }
+                } else {
+                    mods_path_to_load.emplace_back(it_mod);
+                }
             }
         }
 
@@ -485,12 +495,16 @@
         auto thread_function = [&] {
             std::unique_lock lk(vector_lock);
             while (!mods_path_to_load.empty()) {
-                auto mod_path_to_load = std::move(mods_path_to_load.back());
+                auto ret_load = true;
+                auto mod_to_load = std::move(mods_path_to_load.back());
                 mods_path_to_load.pop_back();
 
                 lk.unlock();
-                ret &= Insmod(mod_path_to_load, "");
+                ret_load &= LoadWithAliases(mod_to_load, true);
                 lk.lock();
+                if (!ret_load && !IsBlocklisted(mod_to_load)) {
+                    ret &= ret_load;
+                }
             }
         };
 
@@ -502,21 +516,12 @@
             thread.join();
         }
 
-        // Since we cannot assure if these soft dependencies tree are overlap,
-        // we loaded these modules one by one.
-        for (auto dep = mods_with_softdep_to_load.rbegin(); dep != mods_with_softdep_to_load.rend();
-             dep++) {
-            ret &= LoadWithAliases(*dep, true);
-        }
+        if (!ret) return ret;
 
         std::lock_guard guard(module_loaded_lock_);
         // Remove loaded module form mod_with_deps and soft dependencies of other modules
         for (const auto& module_loaded : module_loaded_) {
             mod_with_deps.erase(module_loaded);
-
-            for (auto& [mod, softdeps] : mod_with_softdeps) {
-                softdeps.erase(module_loaded);
-            }
         }
 
         // Remove loaded module form dependencies of other modules which are not loaded yet
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index 7b0e0d3..c6a0737 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -2,6 +2,17 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+cc_defaults {
+    name: "libprocessgroup_defaults",
+    cpp_std: "gnu++20",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wexit-time-destructors",
+        "-Wno-unused-parameter",
+    ],
+}
+
 cc_library_headers {
     name: "libprocessgroup_headers",
     vendor_available: true,
@@ -62,11 +73,7 @@
     export_header_lib_headers: [
         "libprocessgroup_headers",
     ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wexit-time-destructors",
-    ],
+    defaults: ["libprocessgroup_defaults"],
     apex_available: [
         "//apex_available:platform",
         "//apex_available:anyapex",
@@ -77,12 +84,7 @@
 cc_test {
     name: "task_profiles_test",
     host_supported: true,
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wexit-time-destructors",
-        "-Wno-unused-parameter",
-    ],
+    defaults: ["libprocessgroup_defaults"],
     srcs: [
         "task_profiles_test.cpp",
     ],
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 39b9f3f..45a723f 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -18,7 +18,10 @@
 
 #include <sys/cdefs.h>
 #include <sys/types.h>
+#include <initializer_list>
+#include <span>
 #include <string>
+#include <string_view>
 #include <vector>
 
 __BEGIN_DECLS
@@ -33,6 +36,19 @@
 bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache = false);
 bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles);
 
+__END_DECLS
+
+bool SetTaskProfiles(int tid, std::initializer_list<std::string_view> profiles,
+                     bool use_fd_cache = false);
+bool SetProcessProfiles(uid_t uid, pid_t pid, std::initializer_list<std::string_view> profiles);
+#if _LIBCPP_STD_VER > 17
+bool SetTaskProfiles(int tid, std::span<const std::string_view> profiles,
+                     bool use_fd_cache = false);
+bool SetProcessProfiles(uid_t uid, pid_t pid, std::span<const std::string_view> profiles);
+#endif
+
+__BEGIN_DECLS
+
 #ifndef __ANDROID_VNDK__
 
 bool SetProcessProfilesCached(uid_t uid, pid_t pid, const std::vector<std::string>& profiles);
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 51c810e..bdda102 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -148,14 +148,35 @@
 }
 
 bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
+    return TaskProfiles::GetInstance().SetProcessProfiles(
+            uid, pid, std::span<const std::string>(profiles), false);
+}
+
+bool SetProcessProfiles(uid_t uid, pid_t pid, std::initializer_list<std::string_view> profiles) {
+    return TaskProfiles::GetInstance().SetProcessProfiles(
+            uid, pid, std::span<const std::string_view>(profiles), false);
+}
+
+bool SetProcessProfiles(uid_t uid, pid_t pid, std::span<const std::string_view> profiles) {
     return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles, false);
 }
 
 bool SetProcessProfilesCached(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
-    return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles, true);
+    return TaskProfiles::GetInstance().SetProcessProfiles(
+            uid, pid, std::span<const std::string>(profiles), true);
 }
 
 bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache) {
+    return TaskProfiles::GetInstance().SetTaskProfiles(tid, std::span<const std::string>(profiles),
+                                                       use_fd_cache);
+}
+
+bool SetTaskProfiles(int tid, std::initializer_list<std::string_view> profiles, bool use_fd_cache) {
+    return TaskProfiles::GetInstance().SetTaskProfiles(
+            tid, std::span<const std::string_view>(profiles), use_fd_cache);
+}
+
+bool SetTaskProfiles(int tid, std::span<const std::string_view> profiles, bool use_fd_cache) {
     return TaskProfiles::GetInstance().SetTaskProfiles(tid, profiles, use_fd_cache);
 }
 
@@ -166,12 +187,12 @@
 // https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3574427/5/src/linux/android.rs#12
 extern "C" bool android_set_process_profiles(uid_t uid, pid_t pid, size_t num_profiles,
                                              const char* profiles[]) {
-    std::vector<std::string> profiles_;
+    std::vector<std::string_view> profiles_;
     profiles_.reserve(num_profiles);
     for (size_t i = 0; i < num_profiles; i++) {
         profiles_.emplace_back(profiles[i]);
     }
-    return SetProcessProfiles(uid, pid, profiles_);
+    return SetProcessProfiles(uid, pid, std::span<const std::string_view>(profiles_));
 }
 
 static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 8589a8d..15f95fc 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -76,6 +76,21 @@
       "Name": "FreezerState",
       "Controller": "freezer",
       "File": "cgroup.freeze"
+    },
+    {
+      "Name": "BfqWeight",
+      "Controller": "io",
+      "File": "io.bfq.weight"
+    },
+    {
+      "Name": "CfqGroupIdle",
+      "Controller": "io",
+      "File": "io.group_idle"
+    },
+    {
+      "Name": "CfqWeight",
+      "Controller": "io",
+      "File": "io.weight"
     }
   ],
 
@@ -444,6 +459,33 @@
           {
             "Controller": "blkio",
             "Path": "background"
+	  }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "BfqWeight",
+            "Value": "10",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "200",
+            "Optional": "true"
           }
         }
       ]
@@ -457,6 +499,33 @@
           {
             "Controller": "blkio",
             "Path": ""
+	  }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "BfqWeight",
+            "Value": "100",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "1000",
+            "Optional": "true"
           }
         }
       ]
@@ -470,6 +539,33 @@
           {
             "Controller": "blkio",
             "Path": ""
+	  }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "BfqWeight",
+            "Value": "100",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "1000",
+            "Optional": "true"
           }
         }
       ]
@@ -483,6 +579,33 @@
           {
             "Controller": "blkio",
             "Path": ""
+	  }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "BfqWeight",
+            "Value": "100",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqGroupIdle",
+            "Value": "0",
+            "Optional": "true"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "CfqWeight",
+            "Value": "1000",
+            "Optional": "true"
           }
         }
       ]
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index 3831ef2..304248a 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -410,7 +410,7 @@
     // Make sure we do this only one time. No need for std::call_once because
     // init is a single-threaded process
     if (access(CGROUPS_RC_PATH, F_OK) == 0) {
-        LOG(WARNING) << "Attempt to call SetupCgroups more than once";
+        LOG(WARNING) << "Attempt to call CgroupSetup() more than once";
         return true;
     }
 
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index e1c5934..744710f 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -786,7 +786,7 @@
     return true;
 }
 
-TaskProfile* TaskProfiles::GetProfile(const std::string& name) const {
+TaskProfile* TaskProfiles::GetProfile(std::string_view name) const {
     auto iter = profiles_.find(name);
 
     if (iter != profiles_.end()) {
@@ -795,7 +795,7 @@
     return nullptr;
 }
 
-const IProfileAttribute* TaskProfiles::GetAttribute(const std::string& name) const {
+const IProfileAttribute* TaskProfiles::GetAttribute(std::string_view name) const {
     auto iter = attributes_.find(name);
 
     if (iter != attributes_.end()) {
@@ -804,8 +804,9 @@
     return nullptr;
 }
 
-bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid,
-                                      const std::vector<std::string>& profiles, bool use_fd_cache) {
+template <typename T>
+bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid, std::span<const T> profiles,
+                                      bool use_fd_cache) {
     bool success = true;
     for (const auto& name : profiles) {
         TaskProfile* profile = GetProfile(name);
@@ -825,8 +826,8 @@
     return success;
 }
 
-bool TaskProfiles::SetTaskProfiles(int tid, const std::vector<std::string>& profiles,
-                                   bool use_fd_cache) {
+template <typename T>
+bool TaskProfiles::SetTaskProfiles(int tid, std::span<const T> profiles, bool use_fd_cache) {
     bool success = true;
     for (const auto& name : profiles) {
         TaskProfile* profile = GetProfile(name);
@@ -845,3 +846,14 @@
     }
     return success;
 }
+
+template bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid,
+                                               std::span<const std::string> profiles,
+                                               bool use_fd_cache);
+template bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid,
+                                               std::span<const std::string_view> profiles,
+                                               bool use_fd_cache);
+template bool TaskProfiles::SetTaskProfiles(int tid, std::span<const std::string> profiles,
+                                            bool use_fd_cache);
+template bool TaskProfiles::SetTaskProfiles(int tid, std::span<const std::string_view> profiles,
+                                            bool use_fd_cache);
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index df08f65..85b3f91 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -18,9 +18,12 @@
 
 #include <sys/cdefs.h>
 #include <sys/types.h>
+#include <functional>
 #include <map>
 #include <mutex>
+#include <span>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <android-base/unique_fd.h>
@@ -206,18 +209,19 @@
     // Should be used by all users
     static TaskProfiles& GetInstance();
 
-    TaskProfile* GetProfile(const std::string& name) const;
-    const IProfileAttribute* GetAttribute(const std::string& name) const;
+    TaskProfile* GetProfile(std::string_view name) const;
+    const IProfileAttribute* GetAttribute(std::string_view name) const;
     void DropResourceCaching(ProfileAction::ResourceCacheType cache_type) const;
-    bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles,
-                            bool use_fd_cache);
-    bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache);
+    template <typename T>
+    bool SetProcessProfiles(uid_t uid, pid_t pid, std::span<const T> profiles, bool use_fd_cache);
+    template <typename T>
+    bool SetTaskProfiles(int tid, std::span<const T> profiles, bool use_fd_cache);
 
   private:
-    std::map<std::string, std::shared_ptr<TaskProfile>> profiles_;
-    std::map<std::string, std::unique_ptr<IProfileAttribute>> attributes_;
-
     TaskProfiles();
 
     bool Load(const CgroupMap& cg_map, const std::string& file_name);
+
+    std::map<std::string, std::shared_ptr<TaskProfile>, std::less<>> profiles_;
+    std::map<std::string, std::unique_ptr<IProfileAttribute>, std::less<>> attributes_;
 };
diff --git a/libstats/pull_lazy/TEST_MAPPING b/libstats/pull_lazy/TEST_MAPPING
index 92f1e6a..0282a03 100644
--- a/libstats/pull_lazy/TEST_MAPPING
+++ b/libstats/pull_lazy/TEST_MAPPING
@@ -4,7 +4,7 @@
       "name" : "libstatspull_lazy_test"
     }
   ],
-  "hwasan-postsubmit" : [
+  "hwasan-presubmit" : [
     {
       "name" : "libstatspull_lazy_test"
     }
diff --git a/libstats/pull_rust/Android.bp b/libstats/pull_rust/Android.bp
index 4ffa98d..a3ef131 100644
--- a/libstats/pull_rust/Android.bp
+++ b/libstats/pull_rust/Android.bp
@@ -22,6 +22,10 @@
     name: "libstatspull_bindgen",
     wrapper_src: "statslog.h",
     crate_name: "statspull_bindgen",
+    visibility: [
+        "//frameworks/proto_logging/stats/stats_log_api_gen",
+        "//packages/modules/Virtualization/libs/statslog_virtualization",
+    ],
     source_stem: "bindings",
     bindgen_flags: [
         "--size_t-is-usize",
@@ -48,7 +52,7 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.virt",
-    ]
+    ],
 }
 
 rust_library {
diff --git a/libutils/Android.bp b/libutils/Android.bp
index f663671..7939e82 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -68,6 +68,7 @@
         "-Wall",
         "-Werror",
         "-Wno-exit-time-destructors",
+        "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
     ],
     header_libs: [
         "libbase_headers",
@@ -173,6 +174,10 @@
     min_sdk_version: "apex_inherit",
 
     afdo: true,
+
+    header_abi_checker: {
+        diff_flags: ["-allow-adding-removing-weak-symbols"],
+    },
 }
 
 cc_library {
diff --git a/libutils/Looper.cpp b/libutils/Looper.cpp
index 292425a..1a3f34b 100644
--- a/libutils/Looper.cpp
+++ b/libutils/Looper.cpp
@@ -117,14 +117,15 @@
     int result = pthread_once(& gTLSOnce, initTLSKey);
     LOG_ALWAYS_FATAL_IF(result != 0, "pthread_once failed");
 
-    return (Looper*)pthread_getspecific(gTLSKey);
+    Looper* looper = (Looper*)pthread_getspecific(gTLSKey);
+    return sp<Looper>::fromExisting(looper);
 }
 
 sp<Looper> Looper::prepare(int opts) {
     bool allowNonCallbacks = opts & PREPARE_ALLOW_NON_CALLBACKS;
     sp<Looper> looper = Looper::getForThread();
     if (looper == nullptr) {
-        looper = new Looper(allowNonCallbacks);
+        looper = sp<Looper>::make(allowNonCallbacks);
         Looper::setForThread(looper);
     }
     if (looper->getAllowNonCallbacks() != allowNonCallbacks) {
@@ -425,7 +426,11 @@
 }
 
 int Looper::addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data) {
-    return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : nullptr, data);
+    sp<SimpleLooperCallback> looperCallback;
+    if (callback) {
+        looperCallback = sp<SimpleLooperCallback>::make(callback);
+    }
+    return addFd(fd, ident, events, looperCallback, data);
 }
 
 int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
diff --git a/libutils/NativeHandle.cpp b/libutils/NativeHandle.cpp
index d437a9f..819a603 100644
--- a/libutils/NativeHandle.cpp
+++ b/libutils/NativeHandle.cpp
@@ -20,7 +20,7 @@
 namespace android {
 
 sp<NativeHandle> NativeHandle::create(native_handle_t* handle, bool ownsHandle) {
-    return handle ? new NativeHandle(handle, ownsHandle) : nullptr;
+    return handle ? sp<NativeHandle>::make(handle, ownsHandle) : nullptr;
 }
 
 NativeHandle::NativeHandle(native_handle_t* handle, bool ownsHandle)
diff --git a/libutils/RefBase.cpp b/libutils/RefBase.cpp
index 4ddac3d..ed5b2a9 100644
--- a/libutils/RefBase.cpp
+++ b/libutils/RefBase.cpp
@@ -149,6 +149,29 @@
 // Same for weak counts.
 #define BAD_WEAK(c) ((c) == 0 || ((c) & (~MAX_COUNT)) != 0)
 
+// name kept because prebuilts used to use it from inlining sp<> code
+void sp_report_stack_pointer() { LOG_ALWAYS_FATAL("RefBase used with stack pointer argument"); }
+
+// Check whether address is definitely on the calling stack.  We actually check whether it is on
+// the same 4K page as the frame pointer.
+//
+// Assumptions:
+// - Pages are never smaller than 4K (MIN_PAGE_SIZE)
+// - Malloced memory never shares a page with a stack.
+//
+// It does not appear safe to broaden this check to include adjacent pages; apparently this code
+// is used in environments where there may not be a guard page below (at higher addresses than)
+// the bottom of the stack.
+static void check_not_on_stack(const void* ptr) {
+    static constexpr int MIN_PAGE_SIZE = 0x1000;  // 4K. Safer than including sys/user.h.
+    static constexpr uintptr_t MIN_PAGE_MASK = ~static_cast<uintptr_t>(MIN_PAGE_SIZE - 1);
+    uintptr_t my_frame_address =
+            reinterpret_cast<uintptr_t>(__builtin_frame_address(0 /* this frame */));
+    if (((reinterpret_cast<uintptr_t>(ptr) ^ my_frame_address) & MIN_PAGE_MASK) == 0) {
+        sp_report_stack_pointer();
+    }
+}
+
 // ---------------------------------------------------------------------------
 
 class RefBase::weakref_impl : public RefBase::weakref_type
@@ -432,6 +455,8 @@
         return;
     }
 
+    check_not_on_stack(this);
+
     int32_t old __unused = refs->mStrong.fetch_sub(INITIAL_STRONG_VALUE, std::memory_order_relaxed);
     // A decStrong() must still happen after us.
     ALOG_ASSERT(old > INITIAL_STRONG_VALUE, "0x%x too small", old);
@@ -744,21 +769,27 @@
         if (mRefs->mWeak.load(std::memory_order_relaxed) == 0) {
             delete mRefs;
         }
-    } else if (mRefs->mStrong.load(std::memory_order_relaxed) == INITIAL_STRONG_VALUE) {
-        // We never acquired a strong reference on this object.
+    } else {
+        int32_t strongs = mRefs->mStrong.load(std::memory_order_relaxed);
 
-        // TODO: make this fatal, but too much code in Android manages RefBase with
-        // new/delete manually (or using other mechanisms such as std::make_unique).
-        // However, this is dangerous because it's also common for code to use the
-        // sp<T>(T*) constructor, assuming that if the object is around, it is already
-        // owned by an sp<>.
-        ALOGW("RefBase: Explicit destruction, weak count = %d (in %p). Use sp<> to manage this "
-              "object.",
-              mRefs->mWeak.load(), this);
+        if (strongs == INITIAL_STRONG_VALUE) {
+            // We never acquired a strong reference on this object.
+
+            // It would be nice to make this fatal, but many places use RefBase on the stack.
+            // However, this is dangerous because it's also common for code to use the
+            // sp<T>(T*) constructor, assuming that if the object is around, it is already
+            // owned by an sp<>.
+            ALOGW("RefBase: Explicit destruction, weak count = %d (in %p). Use sp<> to manage this "
+                  "object.",
+                  mRefs->mWeak.load(), this);
 
 #if CALLSTACK_ENABLED
-        CallStack::logStack(LOG_TAG);
+            CallStack::logStack(LOG_TAG);
 #endif
+        } else if (strongs != 0) {
+            LOG_ALWAYS_FATAL("RefBase: object %p with strong count %d deleted. Double owned?", this,
+                             strongs);
+        }
     }
     // For debugging purposes, clear mRefs.  Ineffective against outstanding wp's.
     const_cast<weakref_impl*&>(mRefs) = nullptr;
@@ -766,6 +797,8 @@
 
 void RefBase::extendObjectLifetime(int32_t mode)
 {
+    check_not_on_stack(this);
+
     // Must be happens-before ordered with respect to construction or any
     // operation that could destroy the object.
     mRefs->mFlags.fetch_or(mode, std::memory_order_relaxed);
diff --git a/libutils/RefBase_test.cpp b/libutils/RefBase_test.cpp
index 93f9654..aed3b9b 100644
--- a/libutils/RefBase_test.cpp
+++ b/libutils/RefBase_test.cpp
@@ -265,6 +265,21 @@
     delete foo;
 }
 
+TEST(RefBase, DoubleOwnershipDeath) {
+    bool isDeleted;
+    auto foo = sp<Foo>::make(&isDeleted);
+
+    // if something else thinks it owns foo, should die
+    EXPECT_DEATH(delete foo.get(), "");
+
+    EXPECT_FALSE(isDeleted);
+}
+
+TEST(RefBase, StackOwnershipDeath) {
+    bool isDeleted;
+    EXPECT_DEATH({ Foo foo(&isDeleted); foo.incStrong(nullptr); }, "");
+}
+
 // Set up a situation in which we race with visit2AndRremove() to delete
 // 2 strong references.  Bar destructor checks that there are no early
 // deletions and prior updates are visible to destructor.
diff --git a/libutils/StrongPointer.cpp b/libutils/StrongPointer.cpp
index ef46723..ba52502 100644
--- a/libutils/StrongPointer.cpp
+++ b/libutils/StrongPointer.cpp
@@ -21,7 +21,4 @@
 namespace android {
 
 void sp_report_race() { LOG_ALWAYS_FATAL("sp<> assignment detected data race"); }
-
-void sp_report_stack_pointer() { LOG_ALWAYS_FATAL("sp<> constructed with stack pointer argument"); }
-
 }
diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp
index 4dacdc6..e756fec 100644
--- a/libutils/Threads.cpp
+++ b/libutils/Threads.cpp
@@ -673,7 +673,7 @@
     mThread = thread_id_t(-1);
 
     // hold a strong reference on ourself
-    mHoldSelf = this;
+    mHoldSelf = sp<Thread>::fromExisting(this);
 
     mRunning = true;
 
diff --git a/libutils/include/utils/LruCache.h b/libutils/include/utils/LruCache.h
index 36775d0..b4243a3 100644
--- a/libutils/include/utils/LruCache.h
+++ b/libutils/include/utils/LruCache.h
@@ -84,13 +84,13 @@
         const TKey& getKey() const final { return key; }
     };
 
-    struct HashForEntry : public std::unary_function<KeyedEntry*, hash_t> {
+    struct HashForEntry {
         size_t operator() (const KeyedEntry* entry) const {
             return hash_type(entry->getKey());
         };
     };
 
-    struct EqualityForHashedEntries : public std::unary_function<KeyedEntry*, hash_t> {
+    struct EqualityForHashedEntries {
         bool operator() (const KeyedEntry* lhs, const KeyedEntry* rhs) const {
             return lhs->getKey() == rhs->getKey();
         };
diff --git a/libutils/include/utils/NativeHandle.h b/libutils/include/utils/NativeHandle.h
index 73fe804..f26a1a4 100644
--- a/libutils/include/utils/NativeHandle.h
+++ b/libutils/include/utils/NativeHandle.h
@@ -39,6 +39,8 @@
 private:
     // for access to the destructor
     friend class LightRefBase<NativeHandle>;
+    // for access to the constructor
+    friend class sp<NativeHandle>;
 
     NativeHandle(native_handle_t* handle, bool ownsHandle);
     ~NativeHandle();
diff --git a/libutils/include/utils/StrongPointer.h b/libutils/include/utils/StrongPointer.h
index bb1941b..54aa691 100644
--- a/libutils/include/utils/StrongPointer.h
+++ b/libutils/include/utils/StrongPointer.h
@@ -120,7 +120,6 @@
     template<typename Y> friend class sp;
     template<typename Y> friend class wp;
     void set_pointer(T* ptr);
-    static inline void check_not_on_stack(const void* ptr);
     T* m_ptr;
 };
 
@@ -185,32 +184,10 @@
 
 // For code size reasons, we do not want these inlined or templated.
 void sp_report_race();
-void sp_report_stack_pointer();
 
 // ---------------------------------------------------------------------------
 // No user serviceable parts below here.
 
-// Check whether address is definitely on the calling stack.  We actually check whether it is on
-// the same 4K page as the frame pointer.
-//
-// Assumptions:
-// - Pages are never smaller than 4K (MIN_PAGE_SIZE)
-// - Malloced memory never shares a page with a stack.
-//
-// It does not appear safe to broaden this check to include adjacent pages; apparently this code
-// is used in environments where there may not be a guard page below (at higher addresses than)
-// the bottom of the stack.
-template <typename T>
-void sp<T>::check_not_on_stack(const void* ptr) {
-    static constexpr int MIN_PAGE_SIZE = 0x1000;  // 4K. Safer than including sys/user.h.
-    static constexpr uintptr_t MIN_PAGE_MASK = ~static_cast<uintptr_t>(MIN_PAGE_SIZE - 1);
-    uintptr_t my_frame_address =
-            reinterpret_cast<uintptr_t>(__builtin_frame_address(0 /* this frame */));
-    if (((reinterpret_cast<uintptr_t>(ptr) ^ my_frame_address) & MIN_PAGE_MASK) == 0) {
-        sp_report_stack_pointer();
-    }
-}
-
 // TODO: Ideally we should find a way to increment the reference count before running the
 // constructor, so that generating an sp<> to this in the constructor is no longer dangerous.
 template <typename T>
@@ -219,14 +196,13 @@
     T* t = new T(std::forward<Args>(args)...);
     sp<T> result;
     result.m_ptr = t;
-    t->incStrong(t);  // bypass check_not_on_stack for heap allocation
+    t->incStrong(t);
     return result;
 }
 
 template <typename T>
 sp<T> sp<T>::fromExisting(T* other) {
     if (other) {
-        check_not_on_stack(other);
         other->incStrongRequireStrong(other);
         sp<T> result;
         result.m_ptr = other;
@@ -240,7 +216,6 @@
 sp<T>::sp(T* other)
         : m_ptr(other) {
     if (other) {
-        check_not_on_stack(other);
         other->incStrong(this);
     }
 }
@@ -249,7 +224,6 @@
 template <typename U>
 sp<T>::sp(U* other) : m_ptr(other) {
     if (other) {
-        check_not_on_stack(other);
         (static_cast<T*>(other))->incStrong(this);
     }
 }
@@ -258,7 +232,6 @@
 sp<T>& sp<T>::operator=(T* other) {
     T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
     if (other) {
-        check_not_on_stack(other);
         other->incStrong(this);
     }
     if (oldPtr) oldPtr->decStrong(this);
diff --git a/rootdir/etc/linker.config.json b/rootdir/etc/linker.config.json
index 780ace5..c88c7ff 100644
--- a/rootdir/etc/linker.config.json
+++ b/rootdir/etc/linker.config.json
@@ -31,5 +31,9 @@
     "libadb_pairing_auth.so",
     "libadb_pairing_connection.so",
     "libadb_pairing_server.so"
+  ],
+  "provideLibs": [
+    "libaptX_encoder.so",
+    "libaptXHD_encoder.so"
   ]
 }
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 660f18c..60bf57b 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -723,9 +723,13 @@
     # Multi-installed APEXes are selected using persist props.
     # Load persist properties and override properties (if enabled) from /data,
     # before starting apexd.
+    # /data/property should be created before `load_persist_props`
+    mkdir /data/property 0700 root root encryption=Require
     load_persist_props
+
     start logd
     start logd-reinit
+
     # Some existing vendor rc files use 'on load_persist_props_action' to know
     # when persist props are ready. These are difficult to change due to GRF,
     # so continue triggering this action here even though props are already loaded
@@ -852,7 +856,6 @@
     mkdir /data/app-asec 0700 root root encryption=Require
     mkdir /data/app-lib 0771 system system encryption=Require
     mkdir /data/app 0771 system system encryption=Require
-    mkdir /data/property 0700 root root encryption=Require
 
     # create directory for updated font files.
     mkdir /data/fonts/ 0771 root root encryption=Require
diff --git a/set-verity-state/Android.bp b/set-verity-state/Android.bp
index f0df350..f40118b 100644
--- a/set-verity-state/Android.bp
+++ b/set-verity-state/Android.bp
@@ -9,17 +9,18 @@
     srcs: ["set-verity-state.cpp"],
     shared_libs: [
         "libbase",
+        "libbinder",
         "libcrypto",
         "libcrypto_utils",
-        "libcutils",
-        "libfec",
         "libfs_mgr_binder",
-        "liblog",
         "libutils",
     ],
     static_libs: [
         "libavb_user",
     ],
+    header_libs: [
+        "libcutils_headers",
+    ],
 
     cflags: ["-Werror"],
     cppflags: [
diff --git a/set-verity-state/OWNERS b/set-verity-state/OWNERS
new file mode 100644
index 0000000..e849450
--- /dev/null
+++ b/set-verity-state/OWNERS
@@ -0,0 +1,3 @@
+dvander@google.com
+yochiang@google.com
+bowgotsai@google.com
diff --git a/set-verity-state/set-verity-state.cpp b/set-verity-state/set-verity-state.cpp
index 52a7f74..de9a452 100644
--- a/set-verity-state/set-verity-state.cpp
+++ b/set-verity-state/set-verity-state.cpp
@@ -14,242 +14,230 @@
  * limitations under the License.
  */
 
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <libavb_user/libavb_user.h>
-#include <stdarg.h>
+#include <getopt.h>
 #include <stdio.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <unistd.h>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <android-base/unique_fd.h>
-#include <fs_mgr.h>
+#include <binder/ProcessState.h>
+#include <cutils/android_reboot.h>
 #include <fs_mgr_overlayfs.h>
-#include <fstab/fstab.h>
-#include <log/log_properties.h>
+#include <libavb_user/libavb_user.h>
 
-#include "fec/io.h"
+using namespace std::string_literals;
+
+namespace {
+
+void print_usage() {
+  printf(
+      "Usage:\n"
+      "\tdisable-verity\n"
+      "\tenable-verity\n"
+      "\tset-verity-state [0|1]\n"
+      "Options:\n"
+      "\t-h --help\tthis help\n"
+      "\t-R --reboot\tautomatic reboot if needed for new settings to take effect\n"
+      "\t-v --verbose\tbe noisy\n");
+}
 
 #ifdef ALLOW_DISABLE_VERITY
-static const bool kAllowDisableVerity = true;
+const bool kAllowDisableVerity = true;
 #else
-static const bool kAllowDisableVerity = false;
+const bool kAllowDisableVerity = false;
 #endif
 
-using android::base::unique_fd;
-
-static void suggest_run_adb_root() {
-  if (getuid() != 0) printf("Maybe run adb root?\n");
-}
-
-static bool make_block_device_writable(const std::string& dev) {
-  unique_fd fd(open(dev.c_str(), O_RDONLY | O_CLOEXEC));
-  if (fd == -1) {
-    return false;
-  }
-
-  int OFF = 0;
-  bool result = (ioctl(fd.get(), BLKROSET, &OFF) != -1);
-  return result;
-}
-
-/* Turn verity on/off */
-static bool set_verity_enabled_state(const char* block_device, const char* mount_point,
-                                     bool enable) {
-  if (!make_block_device_writable(block_device)) {
-    printf("Could not make block device %s writable (%s).\n", block_device, strerror(errno));
-    return false;
-  }
-
-  fec::io fh(block_device, O_RDWR);
-
-  if (!fh) {
-    printf("Could not open block device %s (%s).\n", block_device, strerror(errno));
-    suggest_run_adb_root();
-    return false;
-  }
-
-  fec_verity_metadata metadata;
-
-  if (!fh.get_verity_metadata(metadata)) {
-    printf("Couldn't find verity metadata!\n");
-    return false;
-  }
-
-  if (!enable && metadata.disabled) {
-    printf("Verity already disabled on %s\n", mount_point);
-    return false;
-  }
-
-  if (enable && !metadata.disabled) {
-    printf("Verity already enabled on %s\n", mount_point);
-    return false;
-  }
-
-  if (!fh.set_verity_status(enable)) {
-    printf("Could not set verity %s flag on device %s with error %s\n",
-           enable ? "enabled" : "disabled", block_device, strerror(errno));
-    return false;
-  }
-
-  auto change = false;
-  errno = 0;
-  if (enable ? fs_mgr_overlayfs_teardown(mount_point, &change)
-             : fs_mgr_overlayfs_setup(nullptr, mount_point, &change)) {
-    if (change) {
-      printf("%s overlayfs for %s\n", enable ? "disabling" : "using", mount_point);
-    }
-  } else if (errno) {
-    int expected_errno = enable ? EBUSY : ENOENT;
-    if (errno != expected_errno) {
-      printf("Overlayfs %s for %s failed with error %s\n", enable ? "teardown" : "setup",
-             mount_point, strerror(errno));
-    }
-  }
-  printf("Verity %s on %s\n", enable ? "enabled" : "disabled", mount_point);
-  return true;
-}
-
 /* Helper function to get A/B suffix, if any. If the device isn't
  * using A/B the empty string is returned. Otherwise either "_a",
  * "_b", ... is returned.
  */
-static std::string get_ab_suffix() {
+std::string get_ab_suffix() {
   return android::base::GetProperty("ro.boot.slot_suffix", "");
 }
 
-static bool is_avb_device_locked() {
+bool is_avb_device_locked() {
   return android::base::GetProperty("ro.boot.vbmeta.device_state", "") == "locked";
 }
 
-static bool overlayfs_setup(bool enable) {
+bool is_debuggable() {
+  return android::base::GetBoolProperty("ro.debuggable", false);
+}
+
+bool is_using_avb() {
+  // Figure out if we're using VB1.0 or VB2.0 (aka AVB) - by
+  // contract, androidboot.vbmeta.digest is set by the bootloader
+  // when using AVB).
+  return !android::base::GetProperty("ro.boot.vbmeta.digest", "").empty();
+}
+
+[[noreturn]] void reboot(const std::string& name) {
+  LOG(INFO) << "Rebooting device for new settings to take effect";
+  ::sync();
+  android::base::SetProperty(ANDROID_RB_PROPERTY, "reboot," + name);
+  ::sleep(60);
+  LOG(ERROR) << "Failed to reboot";
+  ::exit(1);
+}
+
+bool overlayfs_setup(bool enable) {
   auto change = false;
   errno = 0;
-  if (enable ? fs_mgr_overlayfs_teardown(nullptr, &change)
-             : fs_mgr_overlayfs_setup(nullptr, nullptr, &change)) {
+  if (enable ? fs_mgr_overlayfs_setup(nullptr, &change)
+             : fs_mgr_overlayfs_teardown(nullptr, &change)) {
     if (change) {
-      printf("%s overlayfs\n", enable ? "disabling" : "using");
+      LOG(INFO) << (enable ? "Enabled" : "Disabled") << " overlayfs";
     }
-  } else if (errno) {
-    printf("Overlayfs %s failed with error %s\n", enable ? "teardown" : "setup", strerror(errno));
-    suggest_run_adb_root();
+  } else {
+    LOG(ERROR) << "Failed to " << (enable ? "enable" : "disable") << " overlayfs";
   }
   return change;
 }
 
+struct SetVerityStateResult {
+  bool success = false;
+  bool want_reboot = false;
+};
+
 /* Use AVB to turn verity on/off */
-static bool set_avb_verity_enabled_state(AvbOps* ops, bool enable_verity) {
+SetVerityStateResult SetVerityState(bool enable_verity) {
   std::string ab_suffix = get_ab_suffix();
-  bool verity_enabled;
+  bool verity_enabled = false;
 
   if (is_avb_device_locked()) {
-    printf("Device is locked. Please unlock the device first\n");
-    return false;
+    LOG(ERROR) << "Device must be bootloader unlocked to change verity state";
+    return {};
   }
 
-  if (!avb_user_verity_get(ops, ab_suffix.c_str(), &verity_enabled)) {
-    printf("Error getting verity state. Try adb root first?\n");
-    return false;
+  std::unique_ptr<AvbOps, decltype(&avb_ops_user_free)> ops(avb_ops_user_new(), &avb_ops_user_free);
+  if (!ops) {
+    LOG(ERROR) << "Error getting AVB ops";
+    return {};
+  }
+
+  if (!avb_user_verity_get(ops.get(), ab_suffix.c_str(), &verity_enabled)) {
+    LOG(ERROR) << "Error getting verity state";
+    return {};
   }
 
   if ((verity_enabled && enable_verity) || (!verity_enabled && !enable_verity)) {
-    printf("verity is already %s\n", verity_enabled ? "enabled" : "disabled");
-    return false;
+    LOG(INFO) << "Verity is already " << (verity_enabled ? "enabled" : "disabled");
+    return {.success = true, .want_reboot = false};
   }
 
-  if (!avb_user_verity_set(ops, ab_suffix.c_str(), enable_verity)) {
-    printf("Error setting verity\n");
-    return false;
+  if (!avb_user_verity_set(ops.get(), ab_suffix.c_str(), enable_verity)) {
+    LOG(ERROR) << "Error setting verity state";
+    return {};
   }
 
-  overlayfs_setup(enable_verity);
-  printf("Successfully %s verity\n", enable_verity ? "enabled" : "disabled");
-  return true;
+  LOG(INFO) << "Successfully " << (enable_verity ? "enabled" : "disabled") << " verity";
+  return {.success = true, .want_reboot = true};
 }
 
+class MyLogger {
+ public:
+  explicit MyLogger(bool verbose) : verbose_(verbose) {}
+
+  void operator()(android::base::LogId id, android::base::LogSeverity severity, const char* tag,
+                  const char* file, unsigned int line, const char* message) {
+    // Hide log starting with '[fs_mgr]' unless it's an error.
+    if (verbose_ || severity >= android::base::ERROR || message[0] != '[') {
+      fprintf(stderr, "%s\n", message);
+    }
+    logd_(id, severity, tag, file, line, message);
+  }
+
+ private:
+  android::base::LogdLogger logd_;
+  bool verbose_;
+};
+
+}  // namespace
+
 int main(int argc, char* argv[]) {
-  if (argc == 0) {
-    LOG(FATAL) << "set-verity-state called with empty argv";
-  }
+  bool auto_reboot = false;
+  bool verbose = false;
 
-  std::optional<bool> enable_opt;
-  std::string procname = android::base::Basename(argv[0]);
-  if (procname == "enable-verity") {
-    enable_opt = true;
-  } else if (procname == "disable-verity") {
-    enable_opt = false;
-  }
-
-  if (!enable_opt.has_value()) {
-    if (argc != 2) {
-      printf("usage: %s [1|0]\n", argv[0]);
-      return 1;
-    }
-
-    if (strcmp(argv[1], "1") == 0) {
-      enable_opt = true;
-    } else if (strcmp(argv[1], "0") == 0) {
-      enable_opt = false;
-    } else {
-      printf("usage: %s [1|0]\n", argv[0]);
-      return 1;
+  struct option longopts[] = {
+      {"help", no_argument, nullptr, 'h'},
+      {"reboot", no_argument, nullptr, 'R'},
+      {"verbose", no_argument, nullptr, 'v'},
+      {0, 0, nullptr, 0},
+  };
+  for (int opt; (opt = ::getopt_long(argc, argv, "hRv", longopts, nullptr)) != -1;) {
+    switch (opt) {
+      case 'h':
+        print_usage();
+        return 0;
+      case 'R':
+        auto_reboot = true;
+        break;
+      case 'v':
+        verbose = true;
+        break;
+      default:
+        print_usage();
+        return 1;
     }
   }
 
-  bool enable = enable_opt.value();
+  android::base::InitLogging(argv, MyLogger(verbose));
 
-  bool any_changed = false;
-
-  // Figure out if we're using VB1.0 or VB2.0 (aka AVB) - by
-  // contract, androidboot.vbmeta.digest is set by the bootloader
-  // when using AVB).
-  bool using_avb = !android::base::GetProperty("ro.boot.vbmeta.digest", "").empty();
-
-  // If using AVB, dm-verity is used on any build so we want it to
-  // be possible to disable/enable on any build (except USER). For
-  // VB1.0 dm-verity is only enabled on certain builds.
-  if (!using_avb) {
-    if (!kAllowDisableVerity) {
-      printf("%s only works for userdebug builds\n", argv[0]);
-    }
-
-    if (!android::base::GetBoolProperty("ro.secure", false)) {
-      overlayfs_setup(enable);
-      printf("verity not enabled - ENG build\n");
-      return 0;
-    }
+  bool enable_verity = false;
+  const std::string progname = getprogname();
+  if (progname == "enable-verity") {
+    enable_verity = true;
+  } else if (progname == "disable-verity") {
+    enable_verity = false;
+  } else if (optind < argc && (argv[optind] == "1"s || argv[optind] == "0"s)) {
+    // progname "set-verity-state"
+    enable_verity = (argv[optind] == "1"s);
+  } else {
+    print_usage();
+    return 1;
   }
 
-  // Should never be possible to disable dm-verity on a USER build
-  // regardless of using AVB or VB1.0.
-  if (!__android_log_is_debuggable()) {
-    printf("verity cannot be disabled/enabled - USER build\n");
-    return 0;
+  if (!kAllowDisableVerity || !is_debuggable()) {
+    errno = EPERM;
+    PLOG(ERROR) << "Cannot disable/enable verity on user build";
+    return 1;
   }
 
-  if (using_avb) {
-    // Yep, the system is using AVB.
-    AvbOps* ops = avb_ops_user_new();
-    if (ops == nullptr) {
-      printf("Error getting AVB ops\n");
-      return 1;
+  if (getuid() != 0) {
+    errno = EACCES;
+    PLOG(ERROR) << "Must be running as root (adb root)";
+    return 1;
+  }
+
+  if (!is_using_avb()) {
+    LOG(ERROR) << "Expected AVB device, VB1.0 is no longer supported";
+    return 1;
+  }
+
+  int exit_code = 0;
+  bool want_reboot = false;
+
+  auto ret = SetVerityState(enable_verity);
+  if (ret.success) {
+    want_reboot |= ret.want_reboot;
+  } else {
+    exit_code = 1;
+  }
+
+  // Disable any overlayfs unconditionally if we want verity enabled.
+  // Enable overlayfs only if verity is successfully disabled or is already disabled.
+  if (enable_verity || ret.success) {
+    // Start a threadpool to service waitForService() callbacks as
+    // fs_mgr_overlayfs_* might call waitForService() to get the image service.
+    android::ProcessState::self()->startThreadPool();
+    want_reboot |= overlayfs_setup(!enable_verity);
+  }
+
+  if (want_reboot) {
+    if (auto_reboot) {
+      reboot(progname);
     }
-    if (set_avb_verity_enabled_state(ops, enable)) {
-      any_changed = true;
-    }
-    avb_ops_user_free(ops);
-  }
-  if (!any_changed) any_changed = overlayfs_setup(enable);
-
-  if (any_changed) {
-    printf("Now reboot your device for settings to take effect\n");
+    printf("Reboot the device for new settings to take effect\n");
   }
 
-  return 0;
+  return exit_code;
 }
diff --git a/trusty/OWNERS b/trusty/OWNERS
index 5c4e03a..61b97c6 100644
--- a/trusty/OWNERS
+++ b/trusty/OWNERS
@@ -1,9 +1,11 @@
 armellel@google.com
 arve@android.com
+danielangell@google.com
 gmar@google.com
 marcone@google.com
 mmaurer@google.com
 ncbray@google.com
 swillden@google.com
+thurston@google.com
 trong@google.com
 wenhaowang@google.com
diff --git a/trusty/apploader/apploader.cpp b/trusty/apploader/apploader.cpp
index 278499f..17d083c 100644
--- a/trusty/apploader/apploader.cpp
+++ b/trusty/apploader/apploader.cpp
@@ -226,6 +226,9 @@
         case APPLOADER_ERR_POLICY_VIOLATION:
             LOG(ERROR) << "Error: loading denied by policy engine";
             break;
+        case APPLOADER_ERR_NOT_ENCRYPTED:
+            LOG(ERROR) << "Error: unmet application encryption requirement";
+            break;
         default:
             LOG(ERROR) << "Unrecognized error: " << resp.error;
             break;
diff --git a/trusty/apploader/apploader_ipc.h b/trusty/apploader/apploader_ipc.h
index 306596e..f037692 100644
--- a/trusty/apploader/apploader_ipc.h
+++ b/trusty/apploader/apploader_ipc.h
@@ -45,6 +45,10 @@
  * @APPLOADER_ERR_INTERNAL:             miscellaneous or internal apploader
  *                                      error not covered by the above
  * @APPLOADER_ERR_INVALID_VERSION:      invalid application version
+ * @APPLOADER_ERR_POLICY_VIOLATION:     signature verification succeeded but
+ *                                      key+manifest combination not allowed
+ *                                      by app loader policy engine
+ * @APPLOADER_ERR_NOT_ENCRYPTED:        unmet application encryption requirement
  */
 enum apploader_error : uint32_t {
     APPLOADER_NO_ERROR = 0,
@@ -57,6 +61,7 @@
     APPLOADER_ERR_INTERNAL,
     APPLOADER_ERR_INVALID_VERSION,
     APPLOADER_ERR_POLICY_VIOLATION,
+    APPLOADER_ERR_NOT_ENCRYPTED,
 };
 
 /**
diff --git a/trusty/metrics/metrics_test.cpp b/trusty/metrics/metrics_test.cpp
index 9897950..0c6db7f 100644
--- a/trusty/metrics/metrics_test.cpp
+++ b/trusty/metrics/metrics_test.cpp
@@ -61,6 +61,18 @@
     virtual void SetUp() override {
         auto ret = Open();
         ASSERT_TRUE(ret.ok()) << ret.error();
+
+        /* Drain events (if any) and reset state */
+        DrainEvents();
+        crashed_app_.clear();
+        event_drop_count_ = 0;
+    }
+
+    void DrainEvents() {
+        while (WaitForEvent(1000 /* 1 second timeout */).ok()) {
+            auto ret = HandleEvent();
+            ASSERT_TRUE(ret.ok()) << ret.error();
+        }
     }
 
     void WaitForAndHandleEvent() {
@@ -79,6 +91,9 @@
     TriggerCrash();
     WaitForAndHandleEvent();
 
+    /* Check that no event was dropped. */
+    ASSERT_EQ(event_drop_count_, 0);
+
     /* Check that correct TA crashed. */
     ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
 }
@@ -110,6 +125,9 @@
     auto ret = HandleEvent();
     ASSERT_TRUE(ret.ok()) << ret.error();
 
+    /* Check that no event was dropped. */
+    ASSERT_EQ(event_drop_count_, 0);
+
     /* Check that correct TA crashed. */
     ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
 }
diff --git a/trusty/storage/interface/include/trusty/interface/storage.h b/trusty/storage/interface/include/trusty/interface/storage.h
index 3f1dcb8..255ade1 100644
--- a/trusty/storage/interface/include/trusty/interface/storage.h
+++ b/trusty/storage/interface/include/trusty/interface/storage.h
@@ -70,6 +70,9 @@
  * @STORAGE_ERR_TRANSACT        returned by various operations to indicate that current transaction
  *                              is in error state. Such state could be only cleared by sending
  *                              STORAGE_END_TRANSACTION message.
+ * @STORAGE_ERR_SYNC_FAILURE    indicates that the current operation failed to sync
+ *                              to disk. Only returned if STORAGE_MSG_FLAG_PRE_COMMIT or
+ *                              STORAGE_MSG_FLAG_POST_COMMIT was set for the request.
  */
 enum storage_err {
 	STORAGE_NO_ERROR          = 0,
@@ -80,6 +83,7 @@
 	STORAGE_ERR_NOT_FOUND     = 5,
 	STORAGE_ERR_EXIST         = 6,
 	STORAGE_ERR_TRANSACT      = 7,
+	STORAGE_ERR_SYNC_FAILURE  = 8,
 };
 
 /**
diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c
index 7cbc24f..b970406 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -73,10 +73,11 @@
 static int handle_req(struct storage_msg* msg, const void* req, size_t req_len) {
     int rc;
 
-    if ((msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) && (msg->cmd != STORAGE_RPMB_SEND)) {
+    if ((msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) && msg->cmd != STORAGE_RPMB_SEND &&
+        msg->cmd != STORAGE_FILE_WRITE) {
         /*
-         * handling post commit messages on non rpmb commands are not
-         * implemented as there is no use case for this yet.
+         * handling post commit messages on commands other than rpmb and write
+         * operations are not implemented as there is no use case for this yet.
          */
         ALOGE("cmd 0x%x: post commit option is not implemented\n", msg->cmd);
         msg->result = STORAGE_ERR_UNIMPLEMENTED;
@@ -86,7 +87,7 @@
     if (msg->flags & STORAGE_MSG_FLAG_PRE_COMMIT) {
         rc = storage_sync_checkpoint();
         if (rc < 0) {
-            msg->result = STORAGE_ERR_GENERIC;
+            msg->result = STORAGE_ERR_SYNC_FAILURE;
             return ipc_respond(msg, NULL, 0);
         }
     }
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index c00c399..c531cfd 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -407,6 +407,14 @@
         goto err_response;
     }
 
+    if (msg->flags & STORAGE_MSG_FLAG_POST_COMMIT) {
+        rc = storage_sync_checkpoint();
+        if (rc < 0) {
+            msg->result = STORAGE_ERR_SYNC_FAILURE;
+            goto err_response;
+        }
+    }
+
     msg->result = STORAGE_NO_ERROR;
 
 err_response:
diff --git a/trusty/test/binder/aidl/ByteEnum.aidl b/trusty/test/binder/aidl/ByteEnum.aidl
new file mode 100644
index 0000000..d3a13ac
--- /dev/null
+++ b/trusty/test/binder/aidl/ByteEnum.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+/*
+ * Hello, world!
+ */
+@Backing(type="byte")
+enum ByteEnum {
+    // Comment about FOO.
+    FOO = 1,
+    BAR = 2,
+    BAZ,
+}
diff --git a/trusty/test/binder/aidl/ITestService.aidl b/trusty/test/binder/aidl/ITestService.aidl
new file mode 100644
index 0000000..c6a99c8
--- /dev/null
+++ b/trusty/test/binder/aidl/ITestService.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+
+import ByteEnum;
+import IntEnum;
+import LongEnum;
+
+interface ITestService {
+    const @utf8InCpp String PORT = "com.android.trusty.binder.test.service";
+
+    const int TEST_CONSTANT = 42;
+    const int TEST_CONSTANT2 = -42;
+    const int TEST_CONSTANT3 = +42;
+    const int TEST_CONSTANT4 = +4;
+    const int TEST_CONSTANT5 = -4;
+    const int TEST_CONSTANT6 = -0;
+    const int TEST_CONSTANT7 = +0;
+    const int TEST_CONSTANT8 = 0;
+    const int TEST_CONSTANT9 = 0x56;
+    const int TEST_CONSTANT10 = 0xa5;
+    const int TEST_CONSTANT11 = 0xFA;
+    const int TEST_CONSTANT12 = 0xffffffff;
+
+    const byte BYTE_TEST_CONSTANT = 17;
+    const long LONG_TEST_CONSTANT = 1L << 40;
+
+    const String STRING_TEST_CONSTANT = "foo";
+    const String STRING_TEST_CONSTANT2 = "bar";
+
+    // Test that primitives work as parameters and return types.
+    boolean RepeatBoolean(boolean token);
+    byte RepeatByte(byte token);
+    char RepeatChar(char token);
+    int RepeatInt(int token);
+    long RepeatLong(long token);
+    float RepeatFloat(float token);
+    double RepeatDouble(double token);
+    String RepeatString(String token);
+    ByteEnum RepeatByteEnum(ByteEnum token);
+    IntEnum RepeatIntEnum(IntEnum token);
+    LongEnum RepeatLongEnum(LongEnum token);
+
+    // Test that arrays work as parameters and return types.
+    boolean[] ReverseBoolean(in boolean[] input, out boolean[] repeated);
+    byte[] ReverseByte(in byte[] input, out byte[] repeated);
+    char[] ReverseChar(in char[] input, out char[] repeated);
+    int[] ReverseInt(in int[] input, out int[] repeated);
+    long[] ReverseLong(in long[] input, out long[] repeated);
+    float[] ReverseFloat(in float[] input, out float[] repeated);
+    double[] ReverseDouble(in double[] input, out double[] repeated);
+    String[] ReverseString(in String[] input, out String[] repeated);
+    ByteEnum[] ReverseByteEnum(in ByteEnum[] input, out ByteEnum[] repeated);
+    IntEnum[] ReverseIntEnum(in IntEnum[] input, out IntEnum[] repeated);
+    LongEnum[] ReverseLongEnum(in LongEnum[] input, out LongEnum[] repeated);
+}
diff --git a/trusty/test/binder/aidl/IntEnum.aidl b/trusty/test/binder/aidl/IntEnum.aidl
new file mode 100644
index 0000000..120e44f
--- /dev/null
+++ b/trusty/test/binder/aidl/IntEnum.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+@JavaDerive(toString=true)
+@Backing(type="int")
+enum IntEnum {
+    FOO = 1000,
+    BAR = 2000,
+    BAZ,
+    /** @deprecated do not use this */
+    QUX,
+}
diff --git a/trusty/test/binder/aidl/LongEnum.aidl b/trusty/test/binder/aidl/LongEnum.aidl
new file mode 100644
index 0000000..0e9e933
--- /dev/null
+++ b/trusty/test/binder/aidl/LongEnum.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+@Backing(type="long")
+enum LongEnum {
+    FOO = 100000000000,
+    BAR = 200000000000,
+    BAZ,
+}
diff --git a/trusty/test/binder/aidl/rules.mk b/trusty/test/binder/aidl/rules.mk
new file mode 100644
index 0000000..6154abb
--- /dev/null
+++ b/trusty/test/binder/aidl/rules.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2022 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_AIDLS := \
+	$(LOCAL_DIR)/ByteEnum.aidl \
+	$(LOCAL_DIR)/IntEnum.aidl \
+	$(LOCAL_DIR)/ITestService.aidl \
+	$(LOCAL_DIR)/LongEnum.aidl \
+
+include make/aidl.mk
diff --git a/trusty/utils/acvp/acvp_ipc.h b/trusty/utils/acvp/acvp_ipc.h
index 300e05a..fc1c9d7 100644
--- a/trusty/utils/acvp/acvp_ipc.h
+++ b/trusty/utils/acvp/acvp_ipc.h
@@ -45,7 +45,7 @@
  * This must be at least as long as the longest reply from the ACVP service
  * (currently the reply from getConfig()).
  */
-#define ACVP_MIN_SHARED_MEMORY 16384
+#define ACVP_MIN_SHARED_MEMORY 32768
 
 /**
  * acvp_req - Request for the Trusty ACVP app