Merge "Snap for 9979206 from 229d3d27d8cddd0eea9b6eaa1a5d541473631c12 to sdk-release" into sdk-release
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 5da1b40..d20de6b 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -368,6 +368,7 @@
     name: "crash_dump",
     srcs: [
         "crash_dump.cpp",
+        "tombstone_handler.cpp",
         "util.cpp",
     ],
     defaults: ["debuggerd_defaults"],
@@ -509,6 +510,9 @@
         arm64: {
             src: "seccomp_policy/crash_dump.arm.policy",
         },
+        riscv64: {
+            enabled: false,
+        },
         x86: {
             src: "seccomp_policy/crash_dump.x86_64.policy",
         },
diff --git a/debuggerd/TEST_MAPPING b/debuggerd/TEST_MAPPING
index 394447b..8633cb8 100644
--- a/debuggerd/TEST_MAPPING
+++ b/debuggerd/TEST_MAPPING
@@ -5,6 +5,9 @@
     },
     {
       "name": "libtombstoned_client_rust_test"
+    },
+    {
+      "name": "MicrodroidHostTestCases"
     }
   ],
   "hwasan-presubmit": [
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index b302918..c9e097e 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -216,7 +216,7 @@
       log_error(output_fd, 0,
                 "received packet of unexpected length from tombstoned while reading %s response: "
                 "expected %zd, received %zd",
-                kind, sizeof(response), rc);
+                kind, sizeof(*response), rc);
       return false;
     }
     return true;
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 442392d..3563436 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -59,7 +59,7 @@
 #include "libdebuggerd/utility.h"
 
 #include "debuggerd/handler.h"
-#include "tombstoned/tombstoned.h"
+#include "tombstone_handler.h"
 
 #include "protocol.h"
 #include "util.h"
@@ -142,7 +142,8 @@
   return false;
 }
 
-static bool activity_manager_notify(pid_t pid, int signal, const std::string& amfd_data) {
+static bool activity_manager_notify(pid_t pid, int signal, const std::string& amfd_data,
+                                    bool recoverable_gwp_asan_crash) {
   ATRACE_CALL();
   android::base::unique_fd amfd(socket_local_client(
       "/data/system/ndebugsocket", ANDROID_SOCKET_NAMESPACE_FILESYSTEM, SOCK_STREAM));
@@ -165,19 +166,32 @@
     return false;
   }
 
-  // Activity Manager protocol: binary 32-bit network-byte-order ints for the
-  // pid and signal number, followed by the raw text of the dump, culminating
-  // in a zero byte that marks end-of-data.
+  // Activity Manager protocol:
+  //  - 32-bit network-byte-order: pid
+  //  - 32-bit network-byte-order: signal number
+  //  - byte: recoverable_gwp_asan_crash
+  //  - bytes: raw text of the dump
+  //  - null terminator
+
   uint32_t datum = htonl(pid);
-  if (!android::base::WriteFully(amfd, &datum, 4)) {
+  if (!android::base::WriteFully(amfd, &datum, sizeof(datum))) {
     PLOG(ERROR) << "AM pid write failed";
     return false;
   }
+
   datum = htonl(signal);
-  if (!android::base::WriteFully(amfd, &datum, 4)) {
-    PLOG(ERROR) << "AM signal write failed";
+  if (!android::base::WriteFully(amfd, &datum, sizeof(datum))) {
+    PLOG(ERROR) << "AM signo write failed";
     return false;
   }
+
+  uint8_t recoverable_gwp_asan_crash_byte = recoverable_gwp_asan_crash ? 1 : 0;
+  if (!android::base::WriteFully(amfd, &recoverable_gwp_asan_crash_byte,
+                                 sizeof(recoverable_gwp_asan_crash_byte))) {
+    PLOG(ERROR) << "AM recoverable_gwp_asan_crash_byte write failed";
+    return false;
+  }
+
   if (!android::base::WriteFully(amfd, amfd_data.c_str(), amfd_data.size() + 1)) {
     PLOG(ERROR) << "AM data write failed";
     return false;
@@ -215,8 +229,8 @@
     // If we abort before we get an output fd, contact tombstoned to let any
     // potential listeners know that we failed.
     if (!g_tombstoned_connected) {
-      if (!tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd, &g_proto_fd,
-                              kDebuggerdAnyIntercept)) {
+      if (!connect_tombstone_server(g_target_thread, &g_tombstoned_socket, &g_output_fd,
+                                    &g_proto_fd, kDebuggerdAnyIntercept)) {
         // We failed to connect, not much we can do.
         LOG(ERROR) << "failed to connected to tombstoned to report failure";
         _exit(1);
@@ -589,8 +603,8 @@
   {
     ATRACE_NAME("tombstoned_connect");
     LOG(INFO) << "obtaining output fd from tombstoned, type: " << dump_type;
-    g_tombstoned_connected = tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd,
-                                                &g_proto_fd, dump_type);
+    g_tombstoned_connected = connect_tombstone_server(g_target_thread, &g_tombstoned_socket,
+                                                      &g_output_fd, &g_proto_fd, dump_type);
   }
 
   if (g_tombstoned_connected) {
@@ -651,10 +665,10 @@
     }
   }
 
-  if (fatal_signal && !recoverable_gwp_asan_crash) {
+  if (fatal_signal) {
     // Don't try to notify ActivityManager if it just crashed, or we might hang until timeout.
     if (thread_info[target_process].thread_name != "system_server") {
-      activity_manager_notify(target_process, signo, amfd_data);
+      activity_manager_notify(target_process, signo, amfd_data, recoverable_gwp_asan_crash);
     }
   }
 
@@ -673,7 +687,8 @@
 
   // Close stdout before we notify tombstoned of completion.
   close(STDOUT_FILENO);
-  if (g_tombstoned_connected && !tombstoned_notify_completion(g_tombstoned_socket.get())) {
+  if (g_tombstoned_connected &&
+      !notify_completion(g_tombstoned_socket.get(), g_output_fd.get(), g_proto_fd.get())) {
     LOG(ERROR) << "failed to notify tombstoned of completion";
   }
 
diff --git a/debuggerd/crasher/arm/crashglue.S b/debuggerd/crasher/arm/crashglue.S
index 8649056..e4adf40 100644
--- a/debuggerd/crasher/arm/crashglue.S
+++ b/debuggerd/crasher/arm/crashglue.S
@@ -1,6 +1,10 @@
 .globl crash1
 .type crash1, %function
 crash1:
+	.cfi_startproc
+	push {lr}
+	.cfi_def_cfa_offset 4
+	.cfi_rel_offset lr, 0
 	ldr r0, =0xa5a50000
 	ldr r1, =0xa5a50001
 	ldr r2, =0xa5a50002
@@ -15,48 +19,19 @@
 	ldr r11, =0xa5a50011
 	ldr r12, =0xa5a50012
 
-
-	fconstd   d0, #0
-	fconstd   d1, #1
-	fconstd   d2, #2
-	fconstd   d3, #3
-	fconstd   d4, #4
-	fconstd   d5, #5
-	fconstd   d6, #6
-	fconstd   d7, #7
-	fconstd   d8, #8
-	fconstd   d9, #9
-	fconstd   d10, #10
-	fconstd   d11, #11
-	fconstd   d12, #12
-	fconstd   d13, #13
-	fconstd   d14, #14
-	fconstd   d15, #15
-	fconstd   d16, #16
-	fconstd   d17, #17
-	fconstd   d18, #18
-	fconstd   d19, #19
-	fconstd   d20, #20
-	fconstd   d21, #21
-	fconstd   d22, #22
-	fconstd   d23, #23
-	fconstd   d24, #24
-	fconstd   d25, #25
-	fconstd   d26, #26
-	fconstd   d27, #27
-	fconstd   d28, #28
-	fconstd   d29, #29
-	fconstd   d30, #30
-	fconstd   d31, #31
-
 	mov lr, #0
 	ldr lr, [lr]
 	b .
+	.cfi_endproc
 
 .globl crashnostack
 .type crashnostack, %function
 crashnostack:
+	.cfi_startproc
+	mov r1, sp
+	.cfi_def_cfa_register r1
 	mov sp, #0
 	mov r0, #0
 	ldr r0, [r0]
 	b .
+	.cfi_endproc
diff --git a/debuggerd/crasher/arm64/crashglue.S b/debuggerd/crasher/arm64/crashglue.S
index e58b542..97c824e 100644
--- a/debuggerd/crasher/arm64/crashglue.S
+++ b/debuggerd/crasher/arm64/crashglue.S
@@ -1,6 +1,11 @@
 .globl crash1
 .type crash1, %function
 crash1:
+	.cfi_startproc
+	stp x29, x30, [sp, -16]!
+	.cfi_def_cfa_offset 16
+	.cfi_rel_offset x29, 0
+	.cfi_rel_offset x30, 8
 	ldr x0, =0xa5a50000
 	ldr x1, =0xa5a50001
 	ldr x2, =0xa5a50002
@@ -32,48 +37,20 @@
 	ldr x28, =0xa5a50028
 	ldr x29, =0xa5a50029
 
-	fmov   d0, -1.0  // -1 is more convincing than 0.
-	fmov   d1, 1.0
-	fmov   d2, 2.0
-	fmov   d3, 3.0
-	fmov   d4, 4.0
-	fmov   d5, 5.0
-	fmov   d6, 6.0
-	fmov   d7, 7.0
-	fmov   d8, 8.0
-	fmov   d9, 9.0
-	fmov   d10, 10.0
-	fmov   d11, 11.0
-	fmov   d12, 12.0
-	fmov   d13, 13.0
-	fmov   d14, 14.0
-	fmov   d15, 15.0
-	fmov   d16, 16.0
-	fmov   d17, 17.0
-	fmov   d18, 18.0
-	fmov   d19, 19.0
-	fmov   d20, 20.0
-	fmov   d21, 21.0
-	fmov   d22, 22.0
-	fmov   d23, 23.0
-	fmov   d24, 24.0
-	fmov   d25, 25.0
-	fmov   d26, 26.0
-	fmov   d27, 27.0
-	fmov   d28, 28.0
-	fmov   d29, 29.0
-	fmov   d30, 30.0
-	fmov   d31, 31.0
-
 	mov x30, xzr
 	ldr x30, [x30]
 	b .
+	.cfi_endproc
 
 
 .globl crashnostack
 .type crashnostack, %function
 crashnostack:
+	.cfi_startproc
+	mov x1, sp
+	.cfi_def_cfa_register x1
 	mov x0, xzr
 	add sp, x0, xzr
 	ldr x0, [x0]
 	b .
+	.cfi_endproc
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index 4eb7382..6a19878 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -159,11 +159,13 @@
 }
 
 noinline void fprintf_null() {
-    fprintf(nullptr, "oops");
+    FILE* sneaky_null = nullptr;
+    fprintf(sneaky_null, "oops");
 }
 
 noinline void readdir_null() {
-    readdir(nullptr);
+    DIR* sneaky_null = nullptr;
+    readdir(sneaky_null);
 }
 
 noinline int strlen_null() {
diff --git a/debuggerd/crasher/riscv64/crashglue.S b/debuggerd/crasher/riscv64/crashglue.S
index 47dd93b..42f59b3 100644
--- a/debuggerd/crasher/riscv64/crashglue.S
+++ b/debuggerd/crasher/riscv64/crashglue.S
@@ -1,45 +1,56 @@
-
-	.globl crash1
-	.globl crashnostack
-
+.globl crash1
 crash1:
-	li	x0,0xdead0000+0
-	li	x1,0xdead0000+1
-	li	x2,0xdead0000+2
-	li	x3,0xdead0000+3
-	li	x4,0xdead0000+4
-	li	x5,0xdead0000+5
-	li	x6,0xdead0000+6
-	li	x7,0xdead0000+7
-	li	x8,0xdead0000+8
-	li	x9,0xdead0000+9
-	li	x10,0xdead0000+10
-	li	x11,0xdead0000+11
-	li	x12,0xdead0000+12
-	li	x13,0xdead0000+13
-	li	x14,0xdead0000+14
-	li	x15,0xdead0000+15
-	li	x16,0xdead0000+16
-	li	x17,0xdead0000+17
-	li	x18,0xdead0000+18
-	li	x19,0xdead0000+19
-	li	x20,0xdead0000+20
-	li	x21,0xdead0000+21
-	li	x22,0xdead0000+22
-	li	x23,0xdead0000+23
-	li	x24,0xdead0000+24
-	li	x25,0xdead0000+25
-	li	x26,0xdead0000+26
-	li	x27,0xdead0000+27
-	li	x28,0xdead0000+28
-	# don't trash the stack otherwise the signal handler won't run
-	#li	$29,0xdead0000+29
-	li	x30,0xdead0000+30
-	li	x31,0xdead0000+31
+	.cfi_startproc
+	addi sp, sp, -16
+	.cfi_def_cfa_offset 16
+	sd ra, 8(sp)
+	.cfi_offset ra, -8
 
+	li	x0,0xa5a50000
+	li	x1,0xa5a50001
+	li	x2,0xa5a50002
+	li	x3,0xa5a50003
+	li	x4,0xa5a50004
+	li	x5,0xa5a50005
+	li	x6,0xa5a50006
+	li	x7,0xa5a50007
+	li	x8,0xa5a50008
+	li	x9,0xa5a50009
+	li	x10,0xa5a50010
+	li	x11,0xa5a50011
+	li	x12,0xa5a50012
+	li	x13,0xa5a50013
+	li	x14,0xa5a50014
+	li	x15,0xa5a50015
+	li	x16,0xa5a50016
+	li	x17,0xa5a50017
+	li	x18,0xa5a50018
+	li	x19,0xa5a50019
+	li	x20,0xa5a50020
+	li	x21,0xa5a50021
+	li	x22,0xa5a50022
+	li	x23,0xa5a50023
+	li	x24,0xa5a50024
+	li	x25,0xa5a50025
+	li	x26,0xa5a50026
+	li	x27,0xa5a50027
+	li	x28,0xa5a50028
+	li	x29,0xa5a50029
+	li	x30,0xa5a50030
+	li	x31,0xa5a50031
+
+	li sp, 0
+	ld t2, 0(zero)
 	j .
+	.cfi_endproc
 
 
+.globl crashnostack
 crashnostack:
-	li	sp, 0
+	.cfi_startproc
+	mv t1, sp
+	.cfi_def_cfa_register t1
+	li sp, 0
+	ld t2, 0(zero)
 	j .
+	.cfi_endproc
diff --git a/debuggerd/crasher/x86/crashglue.S b/debuggerd/crasher/x86/crashglue.S
index 59df432..e8eb3a7 100644
--- a/debuggerd/crasher/x86/crashglue.S
+++ b/debuggerd/crasher/x86/crashglue.S
@@ -1,6 +1,4 @@
 .globl crash1
-.globl crashnostack
-
 crash1:
 	movl $0xa5a50000, %eax
 	movl $0xa5a50001, %ebx
@@ -10,6 +8,11 @@
 	jmp *%edx
 
 
+.globl crashnostack
 crashnostack:
-	movl $0, %ebp
-	jmp *%ebp
+	.cfi_startproc
+	movl %esp, %eax
+	.cfi_def_cfa_register %eax
+	movl $0, %esp
+	movl (%esp), %ebx
+	.cfi_endproc
diff --git a/debuggerd/crasher/x86_64/crashglue.S b/debuggerd/crasher/x86_64/crashglue.S
index 4d2a5c0..8f67214 100644
--- a/debuggerd/crasher/x86_64/crashglue.S
+++ b/debuggerd/crasher/x86_64/crashglue.S
@@ -1,6 +1,4 @@
 .globl crash1
-.globl crashnostack
-
 crash1:
 	movl $0xa5a50000, %eax
 	movl $0xa5a50001, %ebx
@@ -10,6 +8,11 @@
 	jmp *%rdx
 
 
+.globl crashnostack
 crashnostack:
-	movl $0, %ebp
-	jmp *%rbp
+	.cfi_startproc
+	movq %rsp, %rax
+	.cfi_def_cfa_register %rax
+	movq $0, %rsp
+	movq (%rsp), %rbx
+	.cfi_endproc
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 4d60ddb..a00a202 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -642,7 +642,7 @@
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
 
-  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 8 \(SEGV_MTEAERR\), fault addr --------)");
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code [89] \(SEGV_MTE[AS]ERR\), fault addr)");
 #else
   GTEST_SKIP() << "Requires aarch64";
 #endif
@@ -2437,35 +2437,42 @@
 #if defined(__arm__)
     asm volatile(
         "mov r1, %[base]\n"
-        "mov r2, 0\n"
-        "str r3, [r2]\n"
+        "mov r2, #0\n"
+        "str r2, [r2]\n"
         : [base] "+r"(ptr)
         :
-        : "r1", "r2", "r3", "memory");
+        : "r1", "r2", "memory");
 #elif defined(__aarch64__)
     asm volatile(
         "mov x1, %[base]\n"
-        "mov x2, 0\n"
-        "str x3, [x2]\n"
+        "mov x2, #0\n"
+        "str xzr, [x2]\n"
         : [base] "+r"(ptr)
         :
-        : "x1", "x2", "x3", "memory");
+        : "x1", "x2", "memory");
+#elif defined(__riscv)
+    // TODO: x1 is ra (the link register) on riscv64, so this might have
+    // unintended consequences, but we'll need to change the .cfi_escape if so.
+    asm volatile(
+        "mv x1, %[base]\n"
+        "sw zero, 0(zero)\n"
+        : [base] "+r"(ptr)
+        :
+        : "x1", "memory");
 #elif defined(__i386__)
     asm volatile(
         "mov %[base], %%ecx\n"
-        "movl $0, %%edi\n"
-        "movl 0(%%edi), %%edx\n"
+        "movl $0, 0\n"
         : [base] "+r"(ptr)
         :
-        : "edi", "ecx", "edx", "memory");
+        : "ecx", "memory");
 #elif defined(__x86_64__)
     asm volatile(
         "mov %[base], %%rdx\n"
-        "movq 0, %%rdi\n"
-        "movq 0(%%rdi), %%rcx\n"
+        "movq $0, 0\n"
         : [base] "+r"(ptr)
         :
-        : "rcx", "rdx", "rdi", "memory");
+        : "rdx", "memory");
 #else
 #error "Unsupported architecture"
 #endif
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 25b03af..198de37 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -51,7 +51,6 @@
   HEADER,
   THREAD,
   REGISTERS,
-  FP_REGISTERS,
   BACKTRACE,
   MAPS,
   MEMORY,
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index e4d68f8..8e6abdf 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -190,6 +190,7 @@
 static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone,
                                    const Thread& thread, bool should_log) {
   CBS("");
+  CB(should_log, "%d total frames", thread.current_backtrace().size());
   CB(should_log, "backtrace:");
   if (!thread.backtrace_note().empty()) {
     CB(should_log, "  NOTE: %s",
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 74a1423..d71fc6c 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -47,12 +47,7 @@
 using android::base::unique_fd;
 
 bool is_allowed_in_logcat(enum logtype ltype) {
-  if ((ltype == HEADER)
-   || (ltype == REGISTERS)
-   || (ltype == BACKTRACE)) {
-    return true;
-  }
-  return false;
+  return (ltype == HEADER) || (ltype == REGISTERS) || (ltype == BACKTRACE);
 }
 
 static bool should_write_to_kmsg() {
diff --git a/debuggerd/tombstone_handler.cpp b/debuggerd/tombstone_handler.cpp
new file mode 100644
index 0000000..09df6d9
--- /dev/null
+++ b/debuggerd/tombstone_handler.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2023, 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 "tombstoned/tombstoned.h"
+
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <cutils/sockets.h>
+#include <linux/vm_sockets.h>
+#include "util.h"
+
+using android::base::unique_fd;
+
+/*
+  Port number that VirtualMachineService listens on connections from the guest VMs.
+  Kep in sync with IVirtualMachineService.aidl
+*/
+const unsigned int VM_TOMBSTONES_SERVICE_PORT = 2000;
+
+static bool is_microdroid() {
+  return android::base::GetProperty("ro.hardware", "") == "microdroid";
+}
+
+static bool connect_tombstone_server_microdroid(unique_fd* text_output_fd,
+                                                unique_fd* proto_output_fd,
+                                                DebuggerdDumpType dump_type) {
+  // We do not wait for the property to be set, the default behaviour is not export tombstones.
+  if (!android::base::GetBoolProperty("microdroid_manager.export_tombstones.enabled", false)) {
+    LOG(WARNING) << "exporting tombstones is not enabled";
+    return false;
+  }
+
+  // Microdroid supports handling requests originating from crash_dump which
+  // supports limited dump types. Java traces and incept management are not supported.
+  switch (dump_type) {
+    case kDebuggerdNativeBacktrace:
+    case kDebuggerdTombstone:
+    case kDebuggerdTombstoneProto:
+      break;
+
+    default:
+      LOG(WARNING) << "Requested dump type: " << dump_type << " "
+                   << "not supported";
+  }
+
+  int fd1 = TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM, 0));
+  int fd2 = TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM, 0));
+  if (fd1 < 0 || fd2 < 0) {
+    LOG(WARNING) << "Unable to create virtual socket for writing tombstones";
+    return false;
+  }
+
+  unique_fd vsock_output_fd(fd1), vsock_proto_fd(fd2);
+
+  struct sockaddr_vm sa = (struct sockaddr_vm){
+      .svm_family = AF_VSOCK,
+      .svm_port = VM_TOMBSTONES_SERVICE_PORT,
+      .svm_cid = VMADDR_CID_HOST,
+  };
+
+  if (TEMP_FAILURE_RETRY(connect(vsock_output_fd, (struct sockaddr*)&sa, sizeof(sa))) < 0) {
+    PLOG(WARNING) << "Unable to connect to tombstone service in host";
+    return false;
+  }
+
+  if (dump_type == kDebuggerdTombstoneProto) {
+    if (TEMP_FAILURE_RETRY(connect(vsock_proto_fd, (struct sockaddr*)&sa, sizeof(sa))) < 0) {
+      PLOG(WARNING) << "Unable to connect to tombstone service in host";
+      return false;
+    }
+  }
+
+  *text_output_fd = std::move(vsock_output_fd);
+  if (proto_output_fd) {
+    *proto_output_fd = std::move(vsock_proto_fd);
+  }
+  return true;
+}
+
+static bool notify_completion_microdroid(int vsock_out, int vsock_proto) {
+  if (shutdown(vsock_out, SHUT_WR) || shutdown(vsock_proto, SHUT_WR)) return false;
+  return true;
+}
+bool connect_tombstone_server(pid_t pid, unique_fd* tombstoned_socket, unique_fd* text_output_fd,
+                              unique_fd* proto_output_fd, DebuggerdDumpType dump_type) {
+  if (is_microdroid()) {
+    return connect_tombstone_server_microdroid(text_output_fd, proto_output_fd, dump_type);
+  }
+  return tombstoned_connect(pid, tombstoned_socket, text_output_fd, proto_output_fd, dump_type);
+}
+
+bool notify_completion(int tombstoned_socket, int vsock_out, int vsock_proto) {
+  if (is_microdroid()) {
+    return notify_completion_microdroid(vsock_out, vsock_proto);
+  }
+  return tombstoned_notify_completion(tombstoned_socket);
+}
diff --git a/debuggerd/tombstone_handler.h b/debuggerd/tombstone_handler.h
new file mode 100644
index 0000000..8726bd3
--- /dev/null
+++ b/debuggerd/tombstone_handler.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023, 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/unique_fd.h>
+#include "dump_type.h"
+
+bool connect_tombstone_server(pid_t pid, android::base::unique_fd* tombstoned_socket,
+                              android::base::unique_fd* text_output_fd,
+                              android::base::unique_fd* proto_output_fd,
+                              DebuggerdDumpType dump_type);
+
+bool notify_completion(int tombstoned_socket, int vsock_out, int vsock_proto);
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 3b786e8..7794c4b 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -381,6 +381,7 @@
         "socket_mock.cpp",
         "socket_test.cpp",
         "super_flash_helper_test.cpp",
+        "task_test.cpp",
         "tcp_test.cpp",
         "udp_test.cpp",
     ],
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 5dac3f5..cdcd036 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -115,38 +115,6 @@
 
 fastboot::FastBootDriver* fb = nullptr;
 
-enum fb_buffer_type {
-    FB_BUFFER_FD,
-    FB_BUFFER_SPARSE,
-};
-
-struct fastboot_buffer {
-    enum fb_buffer_type type;
-    std::vector<SparsePtr> files;
-    int64_t sz;
-    unique_fd fd;
-    int64_t image_size;
-};
-
-enum class ImageType {
-    // Must be flashed for device to boot into the kernel.
-    BootCritical,
-    // Normal partition to be flashed during "flashall".
-    Normal,
-    // Partition that is never flashed during "flashall".
-    Extra
-};
-
-struct Image {
-    std::string nickname;
-    std::string img_name;
-    std::string sig_name;
-    std::string part_name;
-    bool optional_if_no_image;
-    ImageType type;
-    bool IsSecondary() const { return nickname.empty(); }
-};
-
 static std::vector<Image> images = {
         // clang-format off
     { "boot",     "boot.img",         "boot.sig",     "boot",     false, ImageType::BootCritical },
@@ -336,28 +304,7 @@
     return -1;
 }
 
-struct NetworkSerial {
-    Socket::Protocol protocol;
-    std::string address;
-    int port;
-};
-
-class ParseNetworkAddressError {
-  public:
-    enum Type { WRONG_PREFIX = 1, WRONG_ADDRESS = 2 };
-
-    ParseNetworkAddressError(Type&& type) : type_(std::forward<Type>(type)) {}
-
-    Type value() const { return type_; }
-    operator Type() const { return value(); }
-    std::string print() const { return ""; }
-
-  private:
-    Type type_;
-};
-
-static Result<NetworkSerial, ParseNetworkAddressError> ParseNetworkSerial(
-        const std::string& serial) {
+Result<NetworkSerial, FastbootError> ParseNetworkSerial(const std::string& serial) {
     Socket::Protocol protocol;
     const char* net_address = nullptr;
     int port = 0;
@@ -371,7 +318,7 @@
         net_address = serial.c_str() + strlen("udp:");
         port = udp::kDefaultPort;
     } else {
-        return Error<ParseNetworkAddressError>(ParseNetworkAddressError::Type::WRONG_PREFIX)
+        return Error<FastbootError>(FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX)
                << "protocol prefix ('tcp:' or 'udp:') is missed: " << serial << ". "
                << "Expected address format:\n"
                << "<protocol>:<address>:<port> (tcp:localhost:5554)";
@@ -380,7 +327,7 @@
     std::string error;
     std::string host;
     if (!android::base::ParseNetAddress(net_address, &host, &port, nullptr, &error)) {
-        return Error<ParseNetworkAddressError>(ParseNetworkAddressError::Type::WRONG_ADDRESS)
+        return Error<FastbootError>(FastbootError::Type::NETWORK_SERIAL_WRONG_ADDRESS)
                << "invalid network address '" << net_address << "': " << error;
     }
 
@@ -399,8 +346,7 @@
 // object, and the caller should not attempt to delete the returned Transport.
 static Transport* open_device(const char* local_serial, bool wait_for_device = true,
                               bool announce = true) {
-    const Result<NetworkSerial, ParseNetworkAddressError> network_serial =
-            ParseNetworkSerial(local_serial);
+    const Result<NetworkSerial, FastbootError> network_serial = ParseNetworkSerial(local_serial);
 
     Transport* transport = nullptr;
     while (true) {
@@ -417,7 +363,8 @@
             if (transport == nullptr && announce) {
                 LOG(ERROR) << "error: " << error;
             }
-        } else if (network_serial.error().code() == ParseNetworkAddressError::Type::WRONG_PREFIX) {
+        } else if (network_serial.error().code() ==
+                   FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX) {
             // WRONG_PREFIX is special because it happens when user wants to communicate with USB
             // device
             transport = usb_open(match_fastboot(local_serial));
@@ -1022,13 +969,13 @@
     fprintf(stderr, "--------------------------------------------\n");
 }
 
-static std::vector<SparsePtr> resparse_file(sparse_file* s, int64_t max_size) {
+std::vector<SparsePtr> resparse_file(sparse_file* s, int64_t max_size) {
     if (max_size <= 0 || max_size > std::numeric_limits<uint32_t>::max()) {
         die("invalid max size %" PRId64, max_size);
     }
 
     const int files = sparse_file_resparse(s, max_size, nullptr, 0);
-    if (files < 0) die("Failed to resparse");
+    if (files < 0) die("Failed to compute resparse boundaries");
 
     auto temp = std::make_unique<sparse_file*[]>(files);
     const int rv = sparse_file_resparse(s, max_size, temp.get(), files);
@@ -1067,7 +1014,7 @@
     return value;
 }
 
-static int64_t get_sparse_limit(int64_t size) {
+int64_t get_sparse_limit(int64_t size) {
     int64_t limit = sparse_limit;
     if (limit == 0) {
         // Unlimited, so see what the target device's limit is.
@@ -1097,6 +1044,10 @@
 
     if (sparse_file* s = sparse_file_import(fd.get(), false, false)) {
         buf->image_size = sparse_file_len(s, false, false);
+        if (buf->image_size < 0) {
+            LOG(ERROR) << "Could not compute length of sparse file";
+            return false;
+        }
         sparse_file_destroy(s);
     } else {
         buf->image_size = sz;
@@ -1199,20 +1150,17 @@
            fb->GetVar("partition-type:vbmeta_b", &partition_type) == fastboot::SUCCESS;
 }
 
-// Note: this only works in userspace fastboot. In the bootloader, use
-// should_flash_in_userspace().
-static bool is_logical(const std::string& partition) {
-    std::string value;
-    return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes";
+static bool is_vbmeta_partition(const std::string& partition) {
+    return android::base::EndsWith(partition, "vbmeta") ||
+           android::base::EndsWith(partition, "vbmeta_a") ||
+           android::base::EndsWith(partition, "vbmeta_b");
 }
 
-static std::string fb_fix_numeric_var(std::string var) {
-    // Some bootloaders (angler, for example), send spurious leading whitespace.
-    var = android::base::Trim(var);
-    // Some bootloaders (hammerhead, for example) use implicit hex.
-    // This code used to use strtol with base 16.
-    if (!android::base::StartsWith(var, "0x")) var = "0x" + var;
-    return var;
+// Note: this only works in userspace fastboot. In the bootloader, use
+// should_flash_in_userspace().
+bool is_logical(const std::string& partition) {
+    std::string value;
+    return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes";
 }
 
 static uint64_t get_partition_size(const std::string& partition) {
@@ -1236,10 +1184,10 @@
 }
 
 static void copy_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
-    if (buf->sz < AVB_FOOTER_SIZE) {
+    if (buf->sz < AVB_FOOTER_SIZE || is_logical(partition) ||
+        should_flash_in_userspace(partition)) {
         return;
     }
-
     // If overflows and negative, it should be < buf->sz.
     int64_t partition_size = static_cast<int64_t>(get_partition_size(partition));
 
@@ -1283,29 +1231,26 @@
     lseek(buf->fd.get(), 0, SEEK_SET);
 }
 
-static void flash_partition_files(const std::string& partition,
-                                  const std::vector<SparsePtr>& files) {
+void flash_partition_files(const std::string& partition, const std::vector<SparsePtr>& files) {
     for (size_t i = 0; i < files.size(); i++) {
         sparse_file* s = files[i].get();
         int64_t sz = sparse_file_len(s, true, false);
+        if (sz < 0) {
+            LOG(FATAL) << "Could not compute length of sparse image for " << partition;
+        }
         fb->FlashPartition(partition, s, sz, i + 1, files.size());
     }
 }
 
-static void flash_buf(const std::string& partition, struct fastboot_buffer* buf) {
-    if (partition == "boot" || partition == "boot_a" || partition == "boot_b" ||
-        partition == "init_boot" || partition == "init_boot_a" || partition == "init_boot_b" ||
-        partition == "recovery" || partition == "recovery_a" || partition == "recovery_b") {
-        copy_avb_footer(partition, buf);
-    }
+static void flash_buf(const std::string& partition, struct fastboot_buffer* buf,
+                      const bool apply_vbmeta) {
+    copy_avb_footer(partition, buf);
 
     // Rewrite vbmeta if that's what we're flashing and modification has been requested.
     if (g_disable_verity || g_disable_verification) {
         // The vbmeta partition might have additional prefix if running in virtual machine
         // e.g., guest_vbmeta_a.
-        if (android::base::EndsWith(partition, "vbmeta") ||
-            android::base::EndsWith(partition, "vbmeta_a") ||
-            android::base::EndsWith(partition, "vbmeta_b")) {
+        if (apply_vbmeta) {
             rewrite_vbmeta_buffer(buf, false /* vbmeta_in_boot */);
         } else if (!has_vbmeta_partition() &&
                    (partition == "boot" || partition == "boot_a" || partition == "boot_b")) {
@@ -1326,7 +1271,7 @@
     }
 }
 
-static std::string get_current_slot() {
+std::string get_current_slot() {
     std::string current_slot;
     if (fb->GetVar("current-slot", &current_slot) != fastboot::SUCCESS) return "";
     if (current_slot[0] == '_') current_slot.erase(0, 1);
@@ -1343,7 +1288,7 @@
     return count;
 }
 
-static bool supports_AB() {
+bool supports_AB() {
     return get_slot_count() >= 2;
 }
 
@@ -1466,7 +1411,7 @@
     }
 }
 
-static bool is_retrofit_device() {
+bool is_retrofit_device() {
     std::string value;
     if (fb->GetVar("super-partition-name", &value) != fastboot::SUCCESS) {
         return false;
@@ -1540,7 +1485,7 @@
     return partition;
 }
 
-void do_flash(const char* pname, const char* fname) {
+void do_flash(const char* pname, const char* fname, const bool apply_vbmeta) {
     verbose("Do flash %s %s", pname, fname);
     struct fastboot_buffer buf;
 
@@ -1551,7 +1496,7 @@
         fb->ResizePartition(pname, std::to_string(buf.image_size));
     }
     std::string flash_pname = repack_ramdisk(pname, &buf);
-    flash_buf(flash_pname, &buf);
+    flash_buf(flash_pname, &buf, apply_vbmeta);
 }
 
 // Sets slot_override as the active slot. If slot_override is blank,
@@ -1602,48 +1547,231 @@
     }
 }
 
-class FlashAllTool {
-  public:
-    FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary,
-                 bool wipe, bool force_flash);
+std::string GetPartitionName(const ImageEntry& entry, const std::string& current_slot) {
+    auto slot = entry.second;
+    if (slot.empty()) {
+        slot = current_slot;
+    }
+    if (slot.empty()) {
+        return entry.first->part_name;
+    }
+    if (slot == "all") {
+        LOG(FATAL) << "Cannot retrieve a singular name when using all slots";
+    }
+    return entry.first->part_name + "_" + slot;
+}
 
-    void Flash();
+std::unique_ptr<FlashTask> ParseFlashCommand(const FlashingPlan* fp,
+                                             const std::vector<std::string>& parts) {
+    bool apply_vbmeta = false;
+    std::string slot = fp->slot_override;
+    std::string partition;
+    std::string img_name;
+    for (auto& part : parts) {
+        if (part == "--apply-vbmeta") {
+            apply_vbmeta = true;
+        } else if (part == "--slot-other") {
+            slot = fp->secondary_slot;
+        } else if (partition.empty()) {
+            partition = part;
+        } else if (img_name.empty()) {
+            img_name = part;
+        } else {
+            LOG(ERROR) << "unknown argument" << part
+                       << " in fastboot-info.txt. parts: " << android::base::Join(parts, " ");
+            return nullptr;
+        }
+    }
+    if (partition.empty()) {
+        LOG(ERROR) << "partition name not found when parsing fastboot-info.txt. parts: "
+                   << android::base::Join(parts, " ");
+        return nullptr;
+    }
+    if (img_name.empty()) {
+        img_name = partition + ".img";
+    }
+    return std::make_unique<FlashTask>(slot, partition, img_name, apply_vbmeta);
+}
 
-  private:
-    void CheckRequirements();
-    void DetermineSlot();
-    void CollectImages();
-    void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
-    void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
-    void UpdateSuperPartition();
-    bool OptimizedFlashSuper();
+std::unique_ptr<RebootTask> ParseRebootCommand(const FlashingPlan* fp,
+                                               const std::vector<std::string>& parts) {
+    if (parts.empty()) return std::make_unique<RebootTask>(fp);
+    if (parts.size() > 1) {
+        LOG(ERROR) << "unknown arguments in reboot {target} in fastboot-info.txt: "
+                   << android::base::Join(parts, " ");
+        return nullptr;
+    }
+    return std::make_unique<RebootTask>(fp, parts[0]);
+}
 
-    // If the image uses the default slot, or the user specified "all", then
-    // the paired string will be empty. If the image requests a specific slot
-    // (for example, system_other) it is specified instead.
-    using ImageEntry = std::pair<const Image*, std::string>;
+std::unique_ptr<WipeTask> ParseWipeCommand(const FlashingPlan* fp,
+                                           const std::vector<std::string>& parts) {
+    if (parts.size() != 1) {
+        LOG(ERROR) << "unknown arguments in erase {partition} in fastboot-info.txt: "
+                   << android::base::Join(parts, " ");
+        return nullptr;
+    }
+    return std::make_unique<WipeTask>(fp, parts[0]);
+}
 
-    std::string GetPartitionName(const ImageEntry& entry);
+std::unique_ptr<Task> ParseFastbootInfoLine(const FlashingPlan* fp,
+                                            const std::vector<std::string>& command) {
+    if (command.size() == 0) {
+        return nullptr;
+    }
+    std::unique_ptr<Task> task;
 
-    const ImageSource& source_;
-    std::string slot_override_;
-    bool skip_secondary_;
-    bool wipe_;
-    bool force_flash_;
-    std::string current_slot_;
-    std::string secondary_slot_;
+    if (command[0] == "flash") {
+        task = ParseFlashCommand(fp, std::vector<std::string>{command.begin() + 1, command.end()});
+    } else if (command[0] == "reboot") {
+        task = ParseRebootCommand(fp, std::vector<std::string>{command.begin() + 1, command.end()});
+    } else if (command[0] == "update-super" && command.size() == 1) {
+        task = std::make_unique<UpdateSuperTask>(fp);
+    } else if (command[0] == "erase" && command.size() == 2) {
+        task = ParseWipeCommand(fp, std::vector<std::string>{command.begin() + 1, command.end()});
+    }
+    if (!task) {
+        LOG(ERROR) << "unknown command parsing fastboot-info.txt line: "
+                   << android::base::Join(command, " ");
+    }
+    return task;
+}
 
-    std::vector<ImageEntry> boot_images_;
-    std::vector<ImageEntry> os_images_;
-};
+void AddResizeTasks(const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>* tasks) {
+    // expands "resize-partitions" into individual commands : resize {os_partition_1}, resize
+    // {os_partition_2}, etc.
+    std::vector<std::unique_ptr<Task>> resize_tasks;
+    std::optional<size_t> loc;
+    for (size_t i = 0; i < tasks->size(); i++) {
+        if (auto flash_task = tasks->at(i)->AsFlashTask()) {
+            if (should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
+                if (!loc) {
+                    loc = i;
+                }
+                resize_tasks.emplace_back(std::make_unique<ResizeTask>(
+                        fp, flash_task->GetPartition(), "0", fp->slot_override));
+            }
+        }
+    }
+    // if no logical partitions (although should never happen since system will always need to be
+    // flashed)
+    if (!loc) {
+        return;
+    }
+    tasks->insert(tasks->begin() + loc.value(), std::make_move_iterator(resize_tasks.begin()),
+                  std::make_move_iterator(resize_tasks.end()));
+    return;
+}
 
-FlashAllTool::FlashAllTool(const ImageSource& source, const std::string& slot_override,
-                           bool skip_secondary, bool wipe, bool force_flash)
-    : source_(source),
-      slot_override_(slot_override),
-      skip_secondary_(skip_secondary),
-      wipe_(wipe),
-      force_flash_(force_flash) {}
+static bool IsNumber(const std::string& s) {
+    bool period = false;
+    for (size_t i = 0; i < s.length(); i++) {
+        if (!isdigit(s[i])) {
+            if (!period && s[i] == '.' && i != 0 && i != s.length() - 1) {
+                period = true;
+            } else {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+static bool IsIgnore(const std::vector<std::string>& command) {
+    if (command[0][0] == '#') {
+        return true;
+    }
+    return false;
+}
+
+bool CheckFastbootInfoRequirements(const std::vector<std::string>& command) {
+    if (command.size() != 2) {
+        LOG(ERROR) << "unknown characters in version info in fastboot-info.txt -> "
+                   << android::base::Join(command, " ");
+        return false;
+    }
+    if (command[0] != "version") {
+        LOG(ERROR) << "unknown characters in version info in fastboot-info.txt -> "
+                   << android::base::Join(command, " ");
+        return false;
+    }
+
+    if (!IsNumber(command[1])) {
+        LOG(ERROR) << "version number contains non-numeric values in fastboot-info.txt -> "
+                   << android::base::Join(command, " ");
+        return false;
+    }
+
+    LOG(VERBOSE) << "Checking 'fastboot-info.txt version'";
+    if (command[1] < PLATFORM_TOOLS_VERSION) {
+        return true;
+    }
+    LOG(ERROR) << "fasboot-info.txt version: " << command[1]
+               << " not compatible with host tool version --> " << PLATFORM_TOOLS_VERSION;
+    return false;
+}
+
+std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp,
+                                                     const std::vector<std::string>& file) {
+    std::vector<std::unique_ptr<Task>> tasks;
+    // Get os_partitions that need to be resized
+    for (auto& text : file) {
+        std::vector<std::string> command = android::base::Tokenize(text, " ");
+        if (IsIgnore(command)) {
+            continue;
+        }
+        if (command.size() > 1 && command[0] == "version") {
+            if (!CheckFastbootInfoRequirements(command)) {
+                return {};
+            }
+            continue;
+        } else if (command.size() >= 2 && command[0] == "if-wipe") {
+            if (!fp->wants_wipe) {
+                continue;
+            }
+            command.erase(command.begin());
+        }
+        auto task = ParseFastbootInfoLine(fp, command);
+        if (!task) {
+            LOG(ERROR) << "Error when parsing fastboot-info.txt, falling back on Hardcoded list: "
+                       << text;
+            return {};
+        }
+        tasks.emplace_back(std::move(task));
+    }
+    if (auto flash_super_task = FlashSuperLayoutTask::InitializeFromTasks(fp, tasks)) {
+        auto it = tasks.begin();
+        for (size_t i = 0; i < tasks.size(); i++) {
+            if (auto flash_task = tasks[i]->AsFlashTask()) {
+                if (should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
+                    break;
+                }
+            }
+            if (auto wipe_task = tasks[i]->AsWipeTask()) {
+                break;
+            }
+            it++;
+        }
+        tasks.insert(it, std::move(flash_super_task));
+    } else {
+        AddResizeTasks(fp, &tasks);
+    }
+    return tasks;
+}
+
+std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp, std::ifstream& fs) {
+    if (!fs || fs.eof()) return {};
+
+    std::string text;
+    std::vector<std::string> file;
+    // Get os_partitions that need to be resized
+    while (std::getline(fs, text)) {
+        file.emplace_back(text);
+    }
+    return ParseFastbootInfo(fp, file);
+}
+
+FlashAllTool::FlashAllTool(FlashingPlan* fp) : fp_(fp) {}
 
 void FlashAllTool::Flash() {
     DumpInfo();
@@ -1651,10 +1779,10 @@
 
     // Change the slot first, so we boot into the correct recovery image when
     // using fastbootd.
-    if (slot_override_ == "all") {
+    if (fp_->slot_override == "all") {
         set_active("a");
     } else {
-        set_active(slot_override_);
+        set_active(fp_->slot_override);
     }
 
     DetermineSlot();
@@ -1662,135 +1790,64 @@
 
     CancelSnapshotIfNeeded();
 
-    // First flash boot partitions. We allow this to happen either in userspace
-    // or in bootloader fastboot.
-    FlashImages(boot_images_);
-
-    if (!OptimizedFlashSuper()) {
-        // Sync the super partition. This will reboot to userspace fastboot if needed.
-        UpdateSuperPartition();
-
-        // Resize any logical partition to 0, so each partition is reset to 0
-        // extents, and will achieve more optimal allocation.
-        for (const auto& [image, slot] : os_images_) {
-            auto resize_partition = [](const std::string& partition) -> void {
-                if (is_logical(partition)) {
-                    fb->ResizePartition(partition, "0");
-                }
-            };
-            do_for_partitions(image->part_name, slot, resize_partition, false);
-        }
+    std::string path = find_item_given_name("fastboot-info.txt");
+    std::ifstream stream(path);
+    std::vector<std::unique_ptr<Task>> tasks = ParseFastbootInfo(fp_, stream);
+    if (tasks.empty()) {
+        LOG(VERBOSE) << "Flashing from hardcoded images. fastboot-info.txt is empty or does not "
+                        "exist";
+        HardcodedFlash();
+        return;
     }
-
-    // Flash OS images, resizing logical partitions as needed.
-    FlashImages(os_images_);
-}
-
-bool FlashAllTool::OptimizedFlashSuper() {
-    if (!supports_AB()) {
-        LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
-        return false;
+    LOG(VERBOSE) << "Flashing from fastboot-info.txt";
+    for (auto& task : tasks) {
+        task->Run();
     }
-    if (slot_override_ == "all") {
-        LOG(VERBOSE) << "Cannot optimize flashing super for all slots";
-        return false;
+    if (fp_->wants_wipe) {
+        // avoid adding duplicate wipe tasks in fastboot main code.
+        fp_->wants_wipe = false;
     }
-
-    // Does this device use dynamic partitions at all?
-    unique_fd fd = source_.OpenFile("super_empty.img");
-    if (fd < 0) {
-        LOG(VERBOSE) << "could not open super_empty.img";
-        return false;
-    }
-
-    // Try to find whether there is a super partition.
-    std::string super_name;
-    if (fb->GetVar("super-partition-name", &super_name) != fastboot::SUCCESS) {
-        super_name = "super";
-    }
-    std::string partition_size_str;
-    if (fb->GetVar("partition-size:" + super_name, &partition_size_str) != fastboot::SUCCESS) {
-        LOG(VERBOSE) << "Cannot optimize super flashing: could not determine super partition";
-        return false;
-    }
-
-    SuperFlashHelper helper(source_);
-    if (!helper.Open(fd)) {
-        return false;
-    }
-
-    for (const auto& entry : os_images_) {
-        auto partition = GetPartitionName(entry);
-        auto image = entry.first;
-
-        if (!helper.AddPartition(partition, image->img_name, image->optional_if_no_image)) {
-            return false;
-        }
-    }
-
-    auto s = helper.GetSparseLayout();
-    if (!s) {
-        return false;
-    }
-
-    std::vector<SparsePtr> files;
-    if (int limit = get_sparse_limit(sparse_file_len(s.get(), false, false))) {
-        files = resparse_file(s.get(), limit);
-    } else {
-        files.emplace_back(std::move(s));
-    }
-
-    // Send the data to the device.
-    flash_partition_files(super_name, files);
-
-    // Remove images that we already flashed, just in case we have non-dynamic OS images.
-    auto remove_if_callback = [&, this](const ImageEntry& entry) -> bool {
-        return helper.WillFlash(GetPartitionName(entry));
-    };
-    os_images_.erase(std::remove_if(os_images_.begin(), os_images_.end(), remove_if_callback),
-                     os_images_.end());
-    return true;
 }
 
 void FlashAllTool::CheckRequirements() {
     std::vector<char> contents;
-    if (!source_.ReadFile("android-info.txt", &contents)) {
+    if (!fp_->source->ReadFile("android-info.txt", &contents)) {
         die("could not read android-info.txt");
     }
-    ::CheckRequirements({contents.data(), contents.size()}, force_flash_);
+    ::CheckRequirements({contents.data(), contents.size()}, fp_->force_flash);
 }
 
 void FlashAllTool::DetermineSlot() {
-    if (slot_override_.empty()) {
-        current_slot_ = get_current_slot();
+    if (fp_->slot_override.empty()) {
+        fp_->current_slot = get_current_slot();
     } else {
-        current_slot_ = slot_override_;
+        fp_->current_slot = fp_->slot_override;
     }
 
-    if (skip_secondary_) {
+    if (fp_->skip_secondary) {
         return;
     }
-    if (slot_override_ != "" && slot_override_ != "all") {
-        secondary_slot_ = get_other_slot(slot_override_);
+    if (fp_->slot_override != "" && fp_->slot_override != "all") {
+        fp_->secondary_slot = get_other_slot(fp_->slot_override);
     } else {
-        secondary_slot_ = get_other_slot();
+        fp_->secondary_slot = get_other_slot();
     }
-    if (secondary_slot_ == "") {
+    if (fp_->secondary_slot == "") {
         if (supports_AB()) {
             fprintf(stderr, "Warning: Could not determine slot for secondary images. Ignoring.\n");
         }
-        skip_secondary_ = true;
+        fp_->skip_secondary = true;
     }
 }
 
 void FlashAllTool::CollectImages() {
     for (size_t i = 0; i < images.size(); ++i) {
-        std::string slot = slot_override_;
+        std::string slot = fp_->slot_override;
         if (images[i].IsSecondary()) {
-            if (skip_secondary_) {
+            if (fp_->skip_secondary) {
                 continue;
             }
-            slot = secondary_slot_;
+            slot = fp_->secondary_slot;
         }
         if (images[i].type == ImageType::BootCritical) {
             boot_images_.emplace_back(&images[i], slot);
@@ -1800,10 +1857,46 @@
     }
 }
 
+void FlashAllTool::HardcodedFlash() {
+    CollectImages();
+    // First flash boot partitions. We allow this to happen either in userspace
+    // or in bootloader fastboot.
+    FlashImages(boot_images_);
+
+    std::vector<std::unique_ptr<Task>> tasks;
+
+    if (auto flash_super_task = FlashSuperLayoutTask::Initialize(fp_, os_images_)) {
+        tasks.emplace_back(std::move(flash_super_task));
+    } else {
+        // Sync the super partition. This will reboot to userspace fastboot if needed.
+        tasks.emplace_back(std::make_unique<UpdateSuperTask>(fp_));
+        // Resize any logical partition to 0, so each partition is reset to 0
+        // extents, and will achieve more optimal allocation.
+        for (const auto& [image, slot] : os_images_) {
+            // Retrofit devices have two super partitions, named super_a and super_b.
+            // On these devices, secondary slots must be flashed as physical
+            // partitions (otherwise they would not mount on first boot). To enforce
+            // this, we delete any logical partitions for the "other" slot.
+            if (is_retrofit_device()) {
+                std::string partition_name = image->part_name + "_"s + slot;
+                if (image->IsSecondary() && should_flash_in_userspace(partition_name)) {
+                    fp_->fb->DeletePartition(partition_name);
+                }
+                tasks.emplace_back(std::make_unique<DeleteTask>(fp_, partition_name));
+            }
+            tasks.emplace_back(std::make_unique<ResizeTask>(fp_, image->part_name, "0", slot));
+        }
+    }
+    for (auto& i : tasks) {
+        i->Run();
+    }
+    FlashImages(os_images_);
+}
+
 void FlashAllTool::FlashImages(const std::vector<std::pair<const Image*, std::string>>& images) {
     for (const auto& [image, slot] : images) {
         fastboot_buffer buf;
-        unique_fd fd = source_.OpenFile(image->img_name);
+        unique_fd fd = fp_->source->OpenFile(image->img_name);
         if (fd < 0 || !load_buf_fd(std::move(fd), &buf)) {
             if (image->optional_if_no_image) {
                 continue;
@@ -1817,7 +1910,7 @@
 void FlashAllTool::FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf) {
     auto flash = [&, this](const std::string& partition_name) {
         std::vector<char> signature_data;
-        if (source_.ReadFile(image.sig_name, &signature_data)) {
+        if (fp_->source->ReadFile(image.sig_name, &signature_data)) {
             fb->Download("signature", signature_data);
             fb->RawCommand("signature", "installing signature");
         }
@@ -1825,60 +1918,12 @@
         if (is_logical(partition_name)) {
             fb->ResizePartition(partition_name, std::to_string(buf->image_size));
         }
-        flash_buf(partition_name.c_str(), buf);
+
+        flash_buf(partition_name.c_str(), buf, is_vbmeta_partition(partition_name));
     };
     do_for_partitions(image.part_name, slot, flash, false);
 }
 
-void FlashAllTool::UpdateSuperPartition() {
-    unique_fd fd = source_.OpenFile("super_empty.img");
-    if (fd < 0) {
-        return;
-    }
-    if (!is_userspace_fastboot()) {
-        reboot_to_userspace_fastboot();
-    }
-
-    std::string super_name;
-    if (fb->GetVar("super-partition-name", &super_name) != fastboot::RetCode::SUCCESS) {
-        super_name = "super";
-    }
-    fb->Download(super_name, fd, get_file_size(fd));
-
-    std::string command = "update-super:" + super_name;
-    if (wipe_) {
-        command += ":wipe";
-    }
-    fb->RawCommand(command, "Updating super partition");
-
-    // Retrofit devices have two super partitions, named super_a and super_b.
-    // On these devices, secondary slots must be flashed as physical
-    // partitions (otherwise they would not mount on first boot). To enforce
-    // this, we delete any logical partitions for the "other" slot.
-    if (is_retrofit_device()) {
-        for (const auto& [image, slot] : os_images_) {
-            std::string partition_name = image->part_name + "_"s + slot;
-            if (image->IsSecondary() && is_logical(partition_name)) {
-                fb->DeletePartition(partition_name);
-            }
-        }
-    }
-}
-
-std::string FlashAllTool::GetPartitionName(const ImageEntry& entry) {
-    auto slot = entry.second;
-    if (slot.empty()) {
-        slot = current_slot_;
-    }
-    if (slot.empty()) {
-        return entry.first->part_name;
-    }
-    if (slot == "all") {
-        LOG(FATAL) << "Cannot retrieve a singular name when using all slots";
-    }
-    return entry.first->part_name + "_" + slot;
-}
-
 class ZipImageSource final : public ImageSource {
   public:
     explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {}
@@ -1897,15 +1942,16 @@
     return unzip_to_file(zip_, name.c_str());
 }
 
-static void do_update(const char* filename, const std::string& slot_override, bool skip_secondary,
-                      bool force_flash) {
+static void do_update(const char* filename, FlashingPlan* fp) {
     ZipArchiveHandle zip;
     int error = OpenArchive(filename, &zip);
     if (error != 0) {
         die("failed to open zip file '%s': %s", filename, ErrorCodeString(error));
     }
-
-    FlashAllTool tool(ZipImageSource(zip), slot_override, skip_secondary, false, force_flash);
+    ZipImageSource zp = ZipImageSource(zip);
+    fp->source = &zp;
+    fp->wants_wipe = false;
+    FlashAllTool tool(fp);
     tool.Flash();
 
     CloseArchive(zip);
@@ -1930,9 +1976,10 @@
     return unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY)));
 }
 
-static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe,
-                        bool force_flash) {
-    FlashAllTool tool(LocalImageSource(), slot_override, skip_secondary, wipe, force_flash);
+static void do_flashall(FlashingPlan* fp) {
+    LocalImageSource s = LocalImageSource();
+    fp->source = &s;
+    FlashAllTool tool(fp);
     tool.Flash();
 }
 
@@ -1973,9 +2020,9 @@
     return size;
 }
 
-static void fb_perform_format(const std::string& partition, int skip_if_not_supported,
-                              const std::string& type_override, const std::string& size_override,
-                              const unsigned fs_options) {
+void fb_perform_format(const std::string& partition, int skip_if_not_supported,
+                       const std::string& type_override, const std::string& size_override,
+                       const unsigned fs_options) {
     std::string partition_type, partition_size;
 
     struct fastboot_buffer buf;
@@ -2047,7 +2094,7 @@
     if (!load_buf_fd(std::move(fd), &buf)) {
         die("Cannot read image: %s", strerror(errno));
     }
-    flash_buf(partition, &buf);
+    flash_buf(partition, &buf, is_vbmeta_partition(partition));
     return;
 
 failed:
@@ -2117,7 +2164,7 @@
 
         auto image_path = temp_dir.path + "/"s + image_name;
         auto flash = [&](const std::string& partition_name) {
-            do_flash(partition_name.c_str(), image_path.c_str());
+            do_flash(partition_name.c_str(), image_path.c_str(), false);
         };
         do_for_partitions(partition, slot, flash, force_slot);
 
@@ -2167,15 +2214,9 @@
 
 int FastBootTool::Main(int argc, char* argv[]) {
     android::base::InitLogging(argv, FastbootLogger, FastbootAborter);
+    std::unique_ptr<FlashingPlan> fp = std::make_unique<FlashingPlan>();
 
-    bool wants_wipe = false;
-    bool skip_reboot = false;
-    bool wants_set_active = false;
-    bool skip_secondary = false;
-    bool force_flash = false;
-    unsigned fs_options = 0;
     int longindex;
-    std::string slot_override;
     std::string next_active;
 
     g_boot_img_hdr.kernel_addr = 0x00008000;
@@ -2225,9 +2266,9 @@
             } else if (name == "disable-verity") {
                 g_disable_verity = true;
             } else if (name == "force") {
-                force_flash = true;
+                fp->force_flash = true;
             } else if (name == "fs-options") {
-                fs_options = ParseFsOption(optarg);
+                fp->fs_options = ParseFsOption(optarg);
             } else if (name == "header-version") {
                 g_boot_img_hdr.header_version = strtoul(optarg, nullptr, 0);
             } else if (name == "dtb") {
@@ -2244,11 +2285,11 @@
             } else if (name == "ramdisk-offset") {
                 g_boot_img_hdr.ramdisk_addr = strtoul(optarg, 0, 16);
             } else if (name == "skip-reboot") {
-                skip_reboot = true;
+                fp->skip_reboot = true;
             } else if (name == "skip-secondary") {
-                skip_secondary = true;
+                fp->skip_secondary = true;
             } else if (name == "slot") {
-                slot_override = optarg;
+                fp->slot_override = optarg;
             } else if (name == "dtb-offset") {
                 g_boot_img_hdr.dtb_addr = strtoul(optarg, 0, 16);
             } else if (name == "tags-offset") {
@@ -2267,7 +2308,7 @@
         } else {
             switch (c) {
                 case 'a':
-                    wants_set_active = true;
+                    fp->wants_set_active = true;
                     if (optarg) next_active = optarg;
                     break;
                 case 'h':
@@ -2287,7 +2328,7 @@
                     set_verbose();
                     break;
                 case 'w':
-                    wants_wipe = true;
+                    fp->wants_wipe = true;
                     break;
                 case '?':
                     return 1;
@@ -2300,7 +2341,7 @@
     argc -= optind;
     argv += optind;
 
-    if (argc == 0 && !wants_wipe && !wants_set_active) syntax_error("no command");
+    if (argc == 0 && !fp->wants_wipe && !fp->wants_set_active) syntax_error("no command");
 
     if (argc > 0 && !strcmp(*argv, "devices")) {
         list_devices();
@@ -2336,28 +2377,29 @@
 
     fastboot::FastBootDriver fastboot_driver(transport, driver_callbacks, false);
     fb = &fastboot_driver;
+    fp->fb = &fastboot_driver;
 
     const double start = now();
 
-    if (slot_override != "") slot_override = verify_slot(slot_override);
+    if (fp->slot_override != "") fp->slot_override = verify_slot(fp->slot_override);
     if (next_active != "") next_active = verify_slot(next_active, false);
 
-    if (wants_set_active) {
+    if (fp->wants_set_active) {
         if (next_active == "") {
-            if (slot_override == "") {
+            if (fp->slot_override == "") {
                 std::string current_slot;
                 if (fb->GetVar("current-slot", &current_slot) == fastboot::SUCCESS) {
                     if (current_slot[0] == '_') current_slot.erase(0, 1);
                     next_active = verify_slot(current_slot, false);
                 } else {
-                    wants_set_active = false;
+                    fp->wants_set_active = false;
                 }
             } else {
-                next_active = verify_slot(slot_override, false);
+                next_active = verify_slot(fp->slot_override, false);
             }
         }
     }
-    std::unique_ptr<Task> reboot_task = nullptr;
+    std::vector<std::unique_ptr<Task>> tasks;
     std::vector<std::string> args(argv, argv + argc);
     while (!args.empty()) {
         std::string command = next_arg(&args);
@@ -2378,7 +2420,7 @@
 
                 fb->Erase(partition);
             };
-            do_for_partitions(partition, slot_override, erase, true);
+            do_for_partitions(partition, fp->slot_override, erase, true);
         } else if (android::base::StartsWith(command, "format")) {
             // Parsing for: "format[:[type][:[size]]]"
             // Some valid things:
@@ -2396,9 +2438,9 @@
             std::string partition = next_arg(&args);
 
             auto format = [&](const std::string& partition) {
-                fb_perform_format(partition, 0, type_override, size_override, fs_options);
+                fb_perform_format(partition, 0, type_override, size_override, fp->fs_options);
             };
-            do_for_partitions(partition, slot_override, format, true);
+            do_for_partitions(partition, fp->slot_override, format, true);
         } else if (command == "signature") {
             std::string filename = next_arg(&args);
             std::vector<char> data;
@@ -2411,17 +2453,17 @@
         } else if (command == FB_CMD_REBOOT) {
             if (args.size() == 1) {
                 std::string reboot_target = next_arg(&args);
-                reboot_task = std::make_unique<RebootTask>(fb, reboot_target);
-            } else {
-                reboot_task = std::make_unique<RebootTask>(fb);
+                tasks.emplace_back(std::make_unique<RebootTask>(fp.get(), reboot_target));
+            } else if (!fp->skip_reboot) {
+                tasks.emplace_back(std::make_unique<RebootTask>(fp.get()));
             }
             if (!args.empty()) syntax_error("junk after reboot command");
         } else if (command == FB_CMD_REBOOT_BOOTLOADER) {
-            reboot_task = std::make_unique<RebootTask>(fb, "bootloader");
+            tasks.emplace_back(std::make_unique<RebootTask>(fp.get(), "bootloader"));
         } else if (command == FB_CMD_REBOOT_RECOVERY) {
-            reboot_task = std::make_unique<RebootTask>(fb, "recovery");
+            tasks.emplace_back(std::make_unique<RebootTask>(fp.get(), "recovery"));
         } else if (command == FB_CMD_REBOOT_FASTBOOT) {
-            reboot_task = std::make_unique<RebootTask>(fb, "fastboot");
+            tasks.emplace_back(std::make_unique<RebootTask>(fp.get(), "fastboot"));
         } else if (command == FB_CMD_CONTINUE) {
             fb->Continue();
         } else if (command == FB_CMD_BOOT) {
@@ -2442,7 +2484,8 @@
                 fname = find_item(pname);
             }
             if (fname.empty()) die("cannot determine image filename for '%s'", pname.c_str());
-            FlashTask task(slot_override, force_flash, pname, fname);
+
+            FlashTask task(fp->slot_override, pname, fname, is_vbmeta_partition(pname));
             task.Run();
         } else if (command == "flash:raw") {
             std::string partition = next_arg(&args);
@@ -2456,18 +2499,20 @@
             auto flashraw = [&data](const std::string& partition) {
                 fb->FlashPartition(partition, data);
             };
-            do_for_partitions(partition, slot_override, flashraw, true);
+            do_for_partitions(partition, fp->slot_override, flashraw, true);
         } else if (command == "flashall") {
-            if (slot_override == "all") {
+            if (fp->slot_override == "all") {
                 fprintf(stderr,
                         "Warning: slot set to 'all'. Secondary slots will not be flashed.\n");
-                do_flashall(slot_override, true, wants_wipe, force_flash);
-            } else {
-                do_flashall(slot_override, skip_secondary, wants_wipe, force_flash);
+                fp->skip_secondary = true;
             }
-            reboot_task = std::make_unique<RebootTask>(fb);
+            do_flashall(fp.get());
+
+            if (!fp->skip_reboot) {
+                tasks.emplace_back(std::make_unique<RebootTask>(fp.get()));
+            }
         } else if (command == "update") {
-            bool slot_all = (slot_override == "all");
+            bool slot_all = (fp->slot_override == "all");
             if (slot_all) {
                 fprintf(stderr,
                         "Warning: slot set to 'all'. Secondary slots will not be flashed.\n");
@@ -2476,8 +2521,10 @@
             if (!args.empty()) {
                 filename = next_arg(&args);
             }
-            do_update(filename.c_str(), slot_override, skip_secondary || slot_all, force_flash);
-            reboot_task = std::make_unique<RebootTask>(fb);
+            do_update(filename.c_str(), fp.get());
+            if (!fp->skip_reboot) {
+                tasks.emplace_back(std::make_unique<RebootTask>(fp.get()));
+            }
         } else if (command == FB_CMD_SET_ACTIVE) {
             std::string slot = verify_slot(next_arg(&args), false);
             fb->SetActive(slot);
@@ -2510,11 +2557,13 @@
             fb->CreatePartition(partition, size);
         } else if (command == FB_CMD_DELETE_PARTITION) {
             std::string partition = next_arg(&args);
-            fb->DeletePartition(partition);
+            tasks.emplace_back(std::make_unique<DeleteTask>(fp.get(), partition));
         } else if (command == FB_CMD_RESIZE_PARTITION) {
             std::string partition = next_arg(&args);
             std::string size = next_arg(&args);
-            fb->ResizePartition(partition, size);
+            std::unique_ptr<ResizeTask> resize_task =
+                    std::make_unique<ResizeTask>(fp.get(), partition, size, fp->slot_override);
+            resize_task->Run();
         } else if (command == "gsi") {
             std::string arg = next_arg(&args);
             if (arg == "wipe") {
@@ -2531,7 +2580,7 @@
             } else {
                 image = next_arg(&args);
             }
-            do_wipe_super(image, slot_override);
+            do_wipe_super(image, fp->slot_override);
         } else if (command == "snapshot-update") {
             std::string arg;
             if (!args.empty()) {
@@ -2544,31 +2593,26 @@
         } else if (command == FB_CMD_FETCH) {
             std::string partition = next_arg(&args);
             std::string outfile = next_arg(&args);
-            do_fetch(partition, slot_override, outfile);
+            do_fetch(partition, fp->slot_override, outfile);
         } else {
             syntax_error("unknown command %s", command.c_str());
         }
     }
-    if (wants_wipe) {
-        if (force_flash) {
+
+    if (fp->wants_wipe) {
+        if (fp->force_flash) {
             CancelSnapshotIfNeeded();
         }
         std::vector<std::string> partitions = {"userdata", "cache", "metadata"};
         for (const auto& partition : partitions) {
-            std::string partition_type;
-            if (fb->GetVar("partition-type:" + partition, &partition_type) != fastboot::SUCCESS) {
-                continue;
-            }
-            if (partition_type.empty()) continue;
-            fb->Erase(partition);
-            fb_perform_format(partition, 1, partition_type, "", fs_options);
+            tasks.emplace_back(std::make_unique<WipeTask>(fp.get(), partition));
         }
     }
-    if (wants_set_active) {
+    if (fp->wants_set_active) {
         fb->SetActive(next_active);
     }
-    if (reboot_task && !skip_reboot) {
-        reboot_task->Run();
+    for (auto& task : tasks) {
+        task->Run();
     }
     fprintf(stderr, "Finished. Total time: %.3fs\n", (now() - start));
 
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index b5fb8c0..b3dc67d 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -28,9 +28,19 @@
 #pragma once
 
 #include <string>
+#include "fastboot_driver.h"
+#include "fastboot_driver_interface.h"
+#include "filesystem.h"
+#include "super_flash_helper.h"
+#include "task.h"
+#include "util.h"
 
 #include <bootimg.h>
 
+#include "result.h"
+#include "socket.h"
+#include "util.h"
+
 class FastBootTool {
   public:
     int Main(int argc, char* argv[]);
@@ -40,11 +50,116 @@
     unsigned ParseFsOption(const char*);
 };
 
+enum fb_buffer_type {
+    FB_BUFFER_FD,
+    FB_BUFFER_SPARSE,
+};
+
+struct fastboot_buffer {
+    enum fb_buffer_type type;
+    std::vector<SparsePtr> files;
+    int64_t sz;
+    unique_fd fd;
+    int64_t image_size;
+};
+
+enum class ImageType {
+    // Must be flashed for device to boot into the kernel.
+    BootCritical,
+    // Normal partition to be flashed during "flashall".
+    Normal,
+    // Partition that is never flashed during "flashall".
+    Extra
+};
+
+struct Image {
+    std::string nickname;
+    std::string img_name;
+    std::string sig_name;
+    std::string part_name;
+    bool optional_if_no_image;
+    ImageType type;
+    bool IsSecondary() const { return nickname.empty(); }
+};
+
+using ImageEntry = std::pair<const Image*, std::string>;
+
+struct FlashingPlan {
+    unsigned fs_options = 0;
+    // If the image uses the default slot, or the user specified "all", then
+    // the paired string will be empty. If the image requests a specific slot
+    // (for example, system_other) it is specified instead.
+    ImageSource* source;
+    bool wants_wipe = false;
+    bool skip_reboot = false;
+    bool wants_set_active = false;
+    bool skip_secondary = false;
+    bool force_flash = false;
+
+    std::string slot_override;
+    std::string current_slot;
+    std::string secondary_slot;
+
+    fastboot::IFastBootDriver* fb;
+};
+
+class FlashAllTool {
+  public:
+    FlashAllTool(FlashingPlan* fp);
+
+    void Flash();
+
+  private:
+    void CheckRequirements();
+    void DetermineSlot();
+    void CollectImages();
+    void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
+    void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
+    void HardcodedFlash();
+
+    std::vector<ImageEntry> boot_images_;
+    std::vector<ImageEntry> os_images_;
+    FlashingPlan* fp_;
+};
+
 bool should_flash_in_userspace(const std::string& partition_name);
 bool is_userspace_fastboot();
-void do_flash(const char* pname, const char* fname);
+void do_flash(const char* pname, const char* fname, const bool apply_vbmeta);
 void do_for_partitions(const std::string& part, const std::string& slot,
                        const std::function<void(const std::string&)>& func, bool force_slot);
 std::string find_item(const std::string& item);
 void reboot_to_userspace_fastboot();
 void syntax_error(const char* fmt, ...);
+std::string get_current_slot();
+
+// Code for Parsing fastboot-info.txt
+std::unique_ptr<FlashTask> ParseFlashCommand(const FlashingPlan* fp,
+                                             const std::vector<std::string>& parts);
+std::unique_ptr<RebootTask> ParseRebootCommand(const FlashingPlan* fp,
+                                               const std::vector<std::string>& parts);
+std::unique_ptr<WipeTask> ParseWipeCommand(const FlashingPlan* fp,
+                                           const std::vector<std::string>& parts);
+std::unique_ptr<Task> ParseFastbootInfoLine(const FlashingPlan* fp,
+                                            const std::vector<std::string>& command);
+void AddResizeTasks(const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks);
+std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp,
+                                                     const std::vector<std::string>& file);
+
+struct NetworkSerial {
+    Socket::Protocol protocol;
+    std::string address;
+    int port;
+};
+
+Result<NetworkSerial, FastbootError> ParseNetworkSerial(const std::string& serial);
+bool supports_AB();
+std::string GetPartitionName(const ImageEntry& entry, const std::string& current_slot_);
+void flash_partition_files(const std::string& partition, const std::vector<SparsePtr>& files);
+int64_t get_sparse_limit(int64_t size);
+std::vector<SparsePtr> resparse_file(sparse_file* s, int64_t max_size);
+
+bool is_retrofit_device();
+bool is_logical(const std::string& partition);
+void fb_perform_format(const std::string& partition, int skip_if_not_supported,
+                       const std::string& type_override, const std::string& size_override,
+                       const unsigned fs_options);
diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h
index f60c9f1..3d6c7b0 100644
--- a/fastboot/fastboot_driver.h
+++ b/fastboot/fastboot_driver.h
@@ -33,7 +33,6 @@
 #include <vector>
 
 #include <android-base/endian.h>
-#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
 #include <bootimg.h>
@@ -41,21 +40,13 @@
 #include <sparse/sparse.h>
 
 #include "constants.h"
+#include "fastboot_driver_interface.h"
 #include "transport.h"
 
 class Transport;
 
 namespace fastboot {
 
-enum RetCode : int {
-    SUCCESS = 0,
-    BAD_ARG,
-    IO_ERROR,
-    BAD_DEV_RESP,
-    DEVICE_FAIL,
-    TIMEOUT,
-};
-
 struct DriverCallbacks {
     std::function<void(const std::string&)> prolog = [](const std::string&) {};
     std::function<void(int)> epilog = [](int) {};
@@ -63,7 +54,7 @@
     std::function<void(const std::string&)> text = [](const std::string&) {};
 };
 
-class FastBootDriver {
+class FastBootDriver : public IFastBootDriver {
     friend class FastBootTest;
 
   public:
@@ -78,9 +69,10 @@
     RetCode Boot(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
     RetCode Continue(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
     RetCode CreatePartition(const std::string& partition, const std::string& size);
-    RetCode DeletePartition(const std::string& partition);
+    RetCode DeletePartition(const std::string& partition) override;
     RetCode Download(const std::string& name, android::base::borrowed_fd fd, size_t size,
-                     std::string* response = nullptr, std::vector<std::string>* info = nullptr);
+                     std::string* response = nullptr,
+                     std::vector<std::string>* info = nullptr) override;
     RetCode Download(android::base::borrowed_fd fd, size_t size, std::string* response = nullptr,
                      std::vector<std::string>* info = nullptr);
     RetCode Download(const std::string& name, const std::vector<char>& buf,
@@ -93,16 +85,17 @@
     RetCode Download(sparse_file* s, bool use_crc = false, std::string* response = nullptr,
                      std::vector<std::string>* info = nullptr);
     RetCode Erase(const std::string& partition, std::string* response = nullptr,
-                  std::vector<std::string>* info = nullptr);
+                  std::vector<std::string>* info = nullptr) override;
     RetCode Flash(const std::string& partition, std::string* response = nullptr,
                   std::vector<std::string>* info = nullptr);
     RetCode GetVar(const std::string& key, std::string* val,
-                   std::vector<std::string>* info = nullptr);
+                   std::vector<std::string>* info = nullptr) override;
     RetCode GetVarAll(std::vector<std::string>* response);
-    RetCode Reboot(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
+    RetCode Reboot(std::string* response = nullptr,
+                   std::vector<std::string>* info = nullptr) override;
     RetCode RebootTo(std::string target, std::string* response = nullptr,
-                     std::vector<std::string>* info = nullptr);
-    RetCode ResizePartition(const std::string& partition, const std::string& size);
+                     std::vector<std::string>* info = nullptr) override;
+    RetCode ResizePartition(const std::string& partition, const std::string& size) override;
     RetCode SetActive(const std::string& slot, std::string* response = nullptr,
                       std::vector<std::string>* info = nullptr);
     RetCode Upload(const std::string& outfile, std::string* response = nullptr,
@@ -116,7 +109,7 @@
     /* HIGHER LEVEL COMMANDS -- Composed of the commands above */
     RetCode FlashPartition(const std::string& partition, const std::vector<char>& data);
     RetCode FlashPartition(const std::string& partition, android::base::borrowed_fd fd,
-                           uint32_t sz);
+                           uint32_t sz) override;
     RetCode FlashPartition(const std::string& partition, sparse_file* s, uint32_t sz,
                            size_t current, size_t total);
 
@@ -128,7 +121,7 @@
     void SetInfoCallback(std::function<void(const std::string&)> info);
     static const std::string RCString(RetCode rc);
     std::string Error();
-    RetCode WaitForDisconnect();
+    RetCode WaitForDisconnect() override;
 
     // Note: set_transport will return the previous transport.
     Transport* set_transport(Transport* transport);
diff --git a/fastboot/fastboot_driver_interface.h b/fastboot/fastboot_driver_interface.h
new file mode 100644
index 0000000..795938f
--- /dev/null
+++ b/fastboot/fastboot_driver_interface.h
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#pragma once
+
+#include <string>
+
+#include "android-base/unique_fd.h"
+
+class Transport;
+
+namespace fastboot {
+
+enum RetCode : int {
+    SUCCESS = 0,
+    BAD_ARG,
+    IO_ERROR,
+    BAD_DEV_RESP,
+    DEVICE_FAIL,
+    TIMEOUT,
+};
+
+class IFastBootDriver {
+  public:
+    RetCode virtual FlashPartition(const std::string& partition, android::base::borrowed_fd fd,
+                                   uint32_t sz) = 0;
+    RetCode virtual DeletePartition(const std::string& partition) = 0;
+    RetCode virtual WaitForDisconnect() = 0;
+    RetCode virtual Reboot(std::string* response = nullptr,
+                           std::vector<std::string>* info = nullptr) = 0;
+
+    RetCode virtual RebootTo(std::string target, std::string* response = nullptr,
+                             std::vector<std::string>* info = nullptr) = 0;
+    RetCode virtual GetVar(const std::string& key, std::string* val,
+                           std::vector<std::string>* info = nullptr) = 0;
+    RetCode virtual Download(const std::string& name, android::base::borrowed_fd fd, size_t size,
+                             std::string* response = nullptr,
+                             std::vector<std::string>* info = nullptr) = 0;
+    RetCode virtual RawCommand(const std::string& cmd, const std::string& message,
+                               std::string* response = nullptr,
+                               std::vector<std::string>* info = nullptr, int* dsize = nullptr) = 0;
+    RetCode virtual ResizePartition(const std::string& partition, const std::string& size) = 0;
+    RetCode virtual Erase(const std::string& partition, std::string* response = nullptr,
+                          std::vector<std::string>* info = nullptr) = 0;
+    virtual ~IFastBootDriver() = default;
+};
+}  // namespace fastboot
\ No newline at end of file
diff --git a/fastboot/fastboot_driver_mock.h b/fastboot/fastboot_driver_mock.h
new file mode 100644
index 0000000..62a5708
--- /dev/null
+++ b/fastboot/fastboot_driver_mock.h
@@ -0,0 +1,51 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#pragma once
+
+#include <gmock/gmock.h>
+#include "fastboot_driver_interface.h"
+
+namespace fastboot {
+
+class MockFastbootDriver : public IFastBootDriver {
+  public:
+    MOCK_METHOD(RetCode, FlashPartition,
+                (const std::string& partition, android::base::borrowed_fd fd, uint32_t sz),
+                (override));
+    MOCK_METHOD(RetCode, DeletePartition, (const std::string&), (override));
+    MOCK_METHOD(RetCode, Reboot, (std::string*, std::vector<std::string>*), (override));
+    MOCK_METHOD(RetCode, RebootTo, (std::string, std::string*, std::vector<std::string>*),
+                (override));
+
+    MOCK_METHOD(RetCode, GetVar, (const std::string&, std::string*, std::vector<std::string>*),
+                (override));
+
+    MOCK_METHOD(RetCode, Download,
+                (const std::string&, android::base::borrowed_fd, size_t, std::string*,
+                 std::vector<std::string>*),
+                (override));
+
+    MOCK_METHOD(RetCode, RawCommand,
+                (const std::string&, const std::string&, std::string*, std::vector<std::string>*,
+                 int*),
+                (override));
+    MOCK_METHOD(RetCode, ResizePartition, (const std::string&, const std::string&), (override));
+    MOCK_METHOD(RetCode, Erase, (const std::string&, std::string*, std::vector<std::string>*),
+                (override));
+    MOCK_METHOD(RetCode, WaitForDisconnect, (), (override));
+};
+
+}  // namespace fastboot
\ No newline at end of file
diff --git a/fastboot/fastboot_test.cpp b/fastboot/fastboot_test.cpp
index 79f37fd..1863e95 100644
--- a/fastboot/fastboot_test.cpp
+++ b/fastboot/fastboot_test.cpp
@@ -203,6 +203,54 @@
     ParseRequirementLineTestMalformed("require-for-product :");
 }
 
+static void ParseNetworkSerialTest(const std::string& description, const std::string& serial,
+                                   const std::string& expected_address,
+                                   const Socket::Protocol expected_protocol,
+                                   const int expected_port) {
+    const Result<NetworkSerial, FastbootError> parsed = ParseNetworkSerial(serial);
+
+    ASSERT_RESULT_OK(parsed) << description;
+
+    const NetworkSerial network_serial = parsed.value();
+    EXPECT_EQ(network_serial.address, expected_address) << description;
+    EXPECT_EQ(network_serial.protocol, expected_protocol) << description;
+    EXPECT_EQ(network_serial.port, expected_port) << description;
+}
+
+static void ParseNetworkSerialNegativeTest(const std::string& description,
+                                           const std::string& serial,
+                                           const FastbootError::Type expected_error) {
+    const Result<NetworkSerial, FastbootError> parsed = ParseNetworkSerial(serial);
+
+    EXPECT_FALSE(parsed.ok()) << description;
+    EXPECT_EQ(parsed.error().code(), expected_error) << description;
+}
+
+TEST(FastBoot, ParseNetworkSerial) {
+    ParseNetworkSerialTest("tcp IPv4 parsed", "tcp:192.168.1.0", "192.168.1.0",
+                           Socket::Protocol::kTcp, 5554);
+
+    ParseNetworkSerialTest("udp IPv4 parsed", "udp:192.168.1.0", "192.168.1.0",
+                           Socket::Protocol::kUdp, 5554);
+
+    ParseNetworkSerialTest("port parsed", "udp:192.168.1.0:9999", "192.168.1.0",
+                           Socket::Protocol::kUdp, 9999);
+
+    ParseNetworkSerialTest("IPv6 parsed", "tcp:2001:db8:3333:4444:5555:6666:7777:8888",
+                           "2001:db8:3333:4444:5555:6666:7777:8888", Socket::Protocol::kTcp, 5554);
+
+    ParseNetworkSerialTest("empty IPv6 parsed", "tcp:::", "::", Socket::Protocol::kTcp, 5554);
+
+    ParseNetworkSerialNegativeTest("wrong prefix", "tcpa:192.168.1.0",
+                                   FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX);
+
+    ParseNetworkSerialNegativeTest("no prefix", "192.168.1.0",
+                                   FastbootError::Type::NETWORK_SERIAL_WRONG_PREFIX);
+
+    ParseNetworkSerialNegativeTest("wrong port", "tcp:192.168.1.0:-1",
+                                   FastbootError::Type::NETWORK_SERIAL_WRONG_ADDRESS);
+}
+
 int main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
     android::base::InitLogging(argv);
diff --git a/fastboot/fuzzer/Android.bp b/fastboot/fuzzer/Android.bp
index 1b59e4a..a898070 100644
--- a/fastboot/fuzzer/Android.bp
+++ b/fastboot/fuzzer/Android.bp
@@ -58,5 +58,13 @@
             "android-media-fuzzing-reports@google.com",
         ],
         componentid: 533764,
+        hotlists: [
+            "4593311",
+        ],
+        description: "The fuzzer targets the APIs of libfastboot library",
+        vector: "local_no_privileges_required",
+        service_privilege: "host_only",
+        users: "single_user",
+        fuzzed_code_usage: "shipped",
     },
 }
diff --git a/fastboot/result.h b/fastboot/result.h
new file mode 100644
index 0000000..dfa5e10
--- /dev/null
+++ b/fastboot/result.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <android-base/result.h>
+
+#include "util.h"
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::ResultError;
+
+class FastbootError {
+  public:
+    enum Type { NETWORK_SERIAL_WRONG_PREFIX = 1, NETWORK_SERIAL_WRONG_ADDRESS = 2 };
+
+    FastbootError(Type&& type) : type_(std::forward<Type>(type)) {}
+
+    Type value() const { return type_; }
+    operator Type() const { return value(); }
+    std::string print() const { return ""; }
+
+  private:
+    Type type_;
+};
+
+template <typename T, typename U>
+inline T Expect(Result<T, U> r) {
+    if (r.ok()) {
+        return r.value();
+    }
+
+    die(r.error().message());
+
+    return r.value();
+}
\ No newline at end of file
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
index 94dd5c3..054c1ed 100644
--- a/fastboot/task.cpp
+++ b/fastboot/task.cpp
@@ -14,26 +14,25 @@
 // limitations under the License.
 //
 #include "task.h"
-#include "fastboot.h"
-#include "util.h"
+
+#include <iostream>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
 
 #include "fastboot.h"
+#include "filesystem.h"
+#include "super_flash_helper.h"
 #include "util.h"
 
-FlashTask::FlashTask(const std::string& _slot) : slot_(_slot){};
-FlashTask::FlashTask(const std::string& _slot, bool _force_flash)
-    : slot_(_slot), force_flash_(_force_flash) {}
-FlashTask::FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname)
-    : pname_(_pname), fname_(find_item(_pname)), slot_(_slot), force_flash_(_force_flash) {
-    if (fname_.empty()) die("cannot determine image filename for '%s'", pname_.c_str());
-}
-FlashTask::FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname,
-                     const std::string& _fname)
-    : pname_(_pname), fname_(_fname), slot_(_slot), force_flash_(_force_flash) {}
+using namespace std::string_literals;
+FlashTask::FlashTask(const std::string& _slot, const std::string& _pname, const std::string& _fname,
+                     const bool apply_vbmeta)
+    : pname_(_pname), fname_(_fname), slot_(_slot), apply_vbmeta_(apply_vbmeta) {}
 
 void FlashTask::Run() {
     auto flash = [&](const std::string& partition) {
-        if (should_flash_in_userspace(partition) && !is_userspace_fastboot() && !force_flash_) {
+        if (should_flash_in_userspace(partition) && !is_userspace_fastboot()) {
             die("The partition you are trying to flash is dynamic, and "
                 "should be flashed via fastbootd. Please run:\n"
                 "\n"
@@ -42,31 +41,262 @@
                 "And try again. If you are intentionally trying to "
                 "overwrite a fixed partition, use --force.");
         }
-        do_flash(partition.c_str(), fname_.c_str());
+        do_flash(partition.c_str(), fname_.c_str(), apply_vbmeta_);
     };
     do_for_partitions(pname_, slot_, flash, true);
 }
 
-RebootTask::RebootTask(fastboot::FastBootDriver* _fb) : fb_(_fb){};
-RebootTask::RebootTask(fastboot::FastBootDriver* _fb, std::string _reboot_target)
-    : reboot_target_(std::move(_reboot_target)), fb_(_fb){};
+std::string FlashTask::GetPartitionAndSlot() {
+    auto slot = slot_;
+    if (slot.empty()) {
+        slot = get_current_slot();
+    }
+    if (slot.empty()) {
+        return pname_;
+    }
+    if (slot == "all") {
+        LOG(FATAL) << "Cannot retrieve a singular name when using all slots";
+    }
+    return pname_ + "_" + slot;
+}
+
+RebootTask::RebootTask(const FlashingPlan* fp) : fp_(fp){};
+RebootTask::RebootTask(const FlashingPlan* fp, const std::string& reboot_target)
+    : reboot_target_(reboot_target), fp_(fp){};
 
 void RebootTask::Run() {
     if ((reboot_target_ == "userspace" || reboot_target_ == "fastboot")) {
         if (!is_userspace_fastboot()) {
             reboot_to_userspace_fastboot();
-            fb_->WaitForDisconnect();
+            fp_->fb->WaitForDisconnect();
         }
     } else if (reboot_target_ == "recovery") {
-        fb_->RebootTo("recovery");
-        fb_->WaitForDisconnect();
+        fp_->fb->RebootTo("recovery");
+        fp_->fb->WaitForDisconnect();
     } else if (reboot_target_ == "bootloader") {
-        fb_->RebootTo("bootloader");
-        fb_->WaitForDisconnect();
+        fp_->fb->RebootTo("bootloader");
+        fp_->fb->WaitForDisconnect();
     } else if (reboot_target_ == "") {
-        fb_->Reboot();
-        fb_->WaitForDisconnect();
+        fp_->fb->Reboot();
+        fp_->fb->WaitForDisconnect();
     } else {
         syntax_error("unknown reboot target %s", reboot_target_.c_str());
     }
 }
+
+FlashSuperLayoutTask::FlashSuperLayoutTask(const std::string& super_name,
+                                           std::unique_ptr<SuperFlashHelper> helper,
+                                           SparsePtr sparse_layout, uint64_t super_size)
+    : super_name_(super_name),
+      helper_(std::move(helper)),
+      sparse_layout_(std::move(sparse_layout)),
+      super_size_(super_size) {}
+
+void FlashSuperLayoutTask::Run() {
+    // Use the reported super partition size as the upper limit, rather than
+    // sparse_file_len, which (1) can fail and (2) is kind of expensive, since
+    // it will map in all of the embedded fds.
+    std::vector<SparsePtr> files;
+    if (int limit = get_sparse_limit(super_size_)) {
+        files = resparse_file(sparse_layout_.get(), limit);
+    } else {
+        files.emplace_back(std::move(sparse_layout_));
+    }
+
+    // Send the data to the device.
+    flash_partition_files(super_name_, files);
+}
+
+std::unique_ptr<FlashSuperLayoutTask> FlashSuperLayoutTask::Initialize(
+        const FlashingPlan* fp, std::vector<ImageEntry>& os_images) {
+    if (!supports_AB()) {
+        LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
+        return nullptr;
+    }
+    if (fp->slot_override == "all") {
+        LOG(VERBOSE) << "Cannot optimize flashing super for all slots";
+        return nullptr;
+    }
+
+    // Does this device use dynamic partitions at all?
+    unique_fd fd = fp->source->OpenFile("super_empty.img");
+
+    if (fd < 0) {
+        LOG(VERBOSE) << "could not open super_empty.img";
+        return nullptr;
+    }
+
+    std::string super_name;
+    // Try to find whether there is a super partition.
+    if (fp->fb->GetVar("super-partition-name", &super_name) != fastboot::SUCCESS) {
+        super_name = "super";
+    }
+
+    uint64_t partition_size;
+    std::string partition_size_str;
+    if (fp->fb->GetVar("partition-size:" + super_name, &partition_size_str) != fastboot::SUCCESS) {
+        LOG(VERBOSE) << "Cannot optimize super flashing: could not determine super partition";
+        return nullptr;
+    }
+    partition_size_str = fb_fix_numeric_var(partition_size_str);
+    if (!android::base::ParseUint(partition_size_str, &partition_size)) {
+        LOG(VERBOSE) << "Could not parse " << super_name << " size: " << partition_size_str;
+        return nullptr;
+    }
+
+    std::unique_ptr<SuperFlashHelper> helper = std::make_unique<SuperFlashHelper>(*fp->source);
+    if (!helper->Open(fd)) {
+        return nullptr;
+    }
+
+    for (const auto& entry : os_images) {
+        auto partition = GetPartitionName(entry, fp->current_slot);
+        auto image = entry.first;
+
+        if (!helper->AddPartition(partition, image->img_name, image->optional_if_no_image)) {
+            return nullptr;
+        }
+    }
+
+    auto s = helper->GetSparseLayout();
+    if (!s) return nullptr;
+
+    // Remove images that we already flashed, just in case we have non-dynamic OS images.
+    auto remove_if_callback = [&](const ImageEntry& entry) -> bool {
+        return helper->WillFlash(GetPartitionName(entry, fp->current_slot));
+    };
+    os_images.erase(std::remove_if(os_images.begin(), os_images.end(), remove_if_callback),
+                    os_images.end());
+    return std::make_unique<FlashSuperLayoutTask>(super_name, std::move(helper), std::move(s),
+                                                  partition_size);
+}
+
+std::unique_ptr<FlashSuperLayoutTask> FlashSuperLayoutTask::InitializeFromTasks(
+        const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks) {
+    if (!supports_AB()) {
+        LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
+        return nullptr;
+    }
+    if (fp->slot_override == "all") {
+        LOG(VERBOSE) << "Cannot optimize flashing super for all slots";
+        return nullptr;
+    }
+
+    // Does this device use dynamic partitions at all?
+    unique_fd fd = fp->source->OpenFile("super_empty.img");
+
+    if (fd < 0) {
+        LOG(VERBOSE) << "could not open super_empty.img";
+        return nullptr;
+    }
+
+    std::string super_name;
+    // Try to find whether there is a super partition.
+    if (fp->fb->GetVar("super-partition-name", &super_name) != fastboot::SUCCESS) {
+        super_name = "super";
+    }
+    uint64_t partition_size;
+    std::string partition_size_str;
+    if (fp->fb->GetVar("partition-size:" + super_name, &partition_size_str) != fastboot::SUCCESS) {
+        LOG(VERBOSE) << "Cannot optimize super flashing: could not determine super partition";
+        return nullptr;
+    }
+    partition_size_str = fb_fix_numeric_var(partition_size_str);
+    if (!android::base::ParseUint(partition_size_str, &partition_size)) {
+        LOG(VERBOSE) << "Could not parse " << super_name << " size: " << partition_size_str;
+        return nullptr;
+    }
+
+    std::unique_ptr<SuperFlashHelper> helper = std::make_unique<SuperFlashHelper>(*fp->source);
+    if (!helper->Open(fd)) {
+        return nullptr;
+    }
+
+    for (const auto& task : tasks) {
+        if (auto flash_task = task->AsFlashTask()) {
+            if (should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
+                auto partition = flash_task->GetPartitionAndSlot();
+                if (!helper->AddPartition(partition, flash_task->GetImageName(), false)) {
+                    return nullptr;
+                }
+            }
+        }
+    }
+
+    auto s = helper->GetSparseLayout();
+    if (!s) return nullptr;
+    // Remove images that we already flashed, just in case we have non-dynamic OS images.
+    auto remove_if_callback = [&](const auto& task) -> bool {
+        if (auto flash_task = task->AsFlashTask()) {
+            return helper->WillFlash(flash_task->GetPartitionAndSlot());
+        } else if (auto update_super_task = task->AsUpdateSuperTask()) {
+            return true;
+        } else if (auto reboot_task = task->AsRebootTask()) {
+            return true;
+        }
+        return false;
+    };
+    tasks.erase(std::remove_if(tasks.begin(), tasks.end(), remove_if_callback), tasks.end());
+
+    return std::make_unique<FlashSuperLayoutTask>(super_name, std::move(helper), std::move(s),
+                                                  partition_size);
+}
+
+UpdateSuperTask::UpdateSuperTask(const FlashingPlan* fp) : fp_(fp) {}
+
+void UpdateSuperTask::Run() {
+    unique_fd fd = fp_->source->OpenFile("super_empty.img");
+    if (fd < 0) {
+        return;
+    }
+    if (!is_userspace_fastboot()) {
+        reboot_to_userspace_fastboot();
+    }
+
+    std::string super_name;
+    if (fp_->fb->GetVar("super-partition-name", &super_name) != fastboot::RetCode::SUCCESS) {
+        super_name = "super";
+    }
+    fp_->fb->Download(super_name, fd, get_file_size(fd));
+
+    std::string command = "update-super:" + super_name;
+    if (fp_->wants_wipe) {
+        command += ":wipe";
+    }
+    fp_->fb->RawCommand(command, "Updating super partition");
+}
+
+ResizeTask::ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size,
+                       const std::string& slot)
+    : fp_(fp), pname_(pname), size_(size), slot_(slot) {}
+
+void ResizeTask::Run() {
+    auto resize_partition = [this](const std::string& partition) -> void {
+        if (is_logical(partition)) {
+            fp_->fb->ResizePartition(partition, size_);
+        }
+    };
+    do_for_partitions(pname_, slot_, resize_partition, false);
+}
+
+DeleteTask::DeleteTask(const FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
+
+void DeleteTask::Run() {
+    fp_->fb->DeletePartition(pname_);
+}
+
+WipeTask::WipeTask(const FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
+
+void WipeTask::Run() {
+    std::string partition_type;
+    if (fp_->fb->GetVar("partition-type:" + pname_, &partition_type) != fastboot::SUCCESS) {
+        LOG(ERROR) << "wipe task partition not found: " << pname_;
+        return;
+    }
+    if (partition_type.empty()) return;
+    if (fp_->fb->Erase(pname_) != fastboot::SUCCESS) {
+        LOG(ERROR) << "wipe task erase failed with partition: " << pname_;
+        return;
+    }
+    fb_perform_format(pname_, 1, partition_type, "", fp_->fs_options);
+}
diff --git a/fastboot/task.h b/fastboot/task.h
index 582fa2f..34e3e92 100644
--- a/fastboot/task.h
+++ b/fastboot/task.h
@@ -19,40 +19,121 @@
 #include <string>
 
 #include "fastboot_driver.h"
+#include "super_flash_helper.h"
+#include "util.h"
+
+struct FlashingPlan;
+struct Image;
+using ImageEntry = std::pair<const Image*, std::string>;
+
+class FlashTask;
+class RebootTask;
+class UpdateSuperTask;
+class WipeTask;
 
 class Task {
   public:
     Task() = default;
     virtual void Run() = 0;
+    virtual FlashTask* AsFlashTask() { return nullptr; }
+    virtual RebootTask* AsRebootTask() { return nullptr; }
+    virtual UpdateSuperTask* AsUpdateSuperTask() { return nullptr; }
+    virtual WipeTask* AsWipeTask() { return nullptr; }
+
     virtual ~Task() = default;
 };
 
 class FlashTask : public Task {
   public:
-    FlashTask(const std::string& _slot);
-    FlashTask(const std::string& _slot, bool _force_flash);
-    FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname);
-    FlashTask(const std::string& _slot, bool _force_flash, const std::string& _pname,
-              const std::string& _fname);
+    FlashTask(const std::string& slot, const std::string& pname, const std::string& fname,
+              const bool apply_vbmeta);
+    virtual FlashTask* AsFlashTask() override { return this; }
 
+    std::string GetPartition() { return pname_; }
+    std::string GetImageName() { return fname_; }
+    std::string GetPartitionAndSlot();
+    std::string GetSlot() { return slot_; }
     void Run() override;
-    ~FlashTask() {}
 
   private:
     const std::string pname_;
     const std::string fname_;
     const std::string slot_;
-    bool force_flash_ = false;
+    const bool apply_vbmeta_;
 };
 
 class RebootTask : public Task {
   public:
-    RebootTask(fastboot::FastBootDriver* _fb);
-    RebootTask(fastboot::FastBootDriver* _fb, const std::string _reboot_target);
+    RebootTask(const FlashingPlan* fp);
+    RebootTask(const FlashingPlan* fp, const std::string& reboot_target);
+    virtual RebootTask* AsRebootTask() override { return this; }
     void Run() override;
-    ~RebootTask() {}
 
   private:
     const std::string reboot_target_ = "";
-    fastboot::FastBootDriver* fb_;
-};
\ No newline at end of file
+    const FlashingPlan* fp_;
+};
+
+class FlashSuperLayoutTask : public Task {
+  public:
+    FlashSuperLayoutTask(const std::string& super_name, std::unique_ptr<SuperFlashHelper> helper,
+                         SparsePtr sparse_layout, uint64_t super_size);
+    static std::unique_ptr<FlashSuperLayoutTask> Initialize(const FlashingPlan* fp,
+                                                            std::vector<ImageEntry>& os_images);
+    static std::unique_ptr<FlashSuperLayoutTask> InitializeFromTasks(
+            const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks);
+    using ImageEntry = std::pair<const Image*, std::string>;
+    void Run() override;
+
+  private:
+    const std::string super_name_;
+    std::unique_ptr<SuperFlashHelper> helper_;
+    SparsePtr sparse_layout_;
+    uint64_t super_size_;
+};
+
+class UpdateSuperTask : public Task {
+  public:
+    UpdateSuperTask(const FlashingPlan* fp);
+    virtual UpdateSuperTask* AsUpdateSuperTask() override { return this; }
+
+    void Run() override;
+
+  private:
+    const FlashingPlan* fp_;
+};
+
+class ResizeTask : public Task {
+  public:
+    ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size,
+               const std::string& slot);
+    void Run() override;
+
+  private:
+    const FlashingPlan* fp_;
+    const std::string pname_;
+    const std::string size_;
+    const std::string slot_;
+};
+
+class DeleteTask : public Task {
+  public:
+    DeleteTask(const FlashingPlan* _fp, const std::string& _pname);
+    void Run() override;
+
+  private:
+    const FlashingPlan* fp_;
+    const std::string pname_;
+};
+
+class WipeTask : public Task {
+  public:
+    WipeTask(const FlashingPlan* fp, const std::string& pname);
+    virtual WipeTask* AsWipeTask() override { return this; }
+
+    void Run() override;
+
+  private:
+    const FlashingPlan* fp_;
+    const std::string pname_;
+};
diff --git a/fastboot/task_test.cpp b/fastboot/task_test.cpp
new file mode 100644
index 0000000..400e27f
--- /dev/null
+++ b/fastboot/task_test.cpp
@@ -0,0 +1,82 @@
+//
+// Copyright (C) 2023 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 "task.h"
+#include "fastboot.h"
+
+#include <gtest/gtest.h>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <unordered_map>
+#include "android-base/strings.h"
+using android::base::Split;
+
+class ParseTest : public ::testing ::Test {
+  protected:
+    void SetUp() override {
+        fp = std::make_unique<FlashingPlan>();
+        fp->slot_override = "b";
+        fp->secondary_slot = "a";
+        fp->wants_wipe = false;
+    }
+    void TearDown() override {}
+
+    std::unique_ptr<FlashingPlan> fp;
+
+  private:
+};
+
+static std::vector<std::unique_ptr<Task>> collectTasks(FlashingPlan* fp,
+                                                       const std::vector<std::string>& commands) {
+    std::vector<std::vector<std::string>> vec_commands;
+    for (auto& command : commands) {
+        vec_commands.emplace_back(android::base::Split(command, " "));
+    }
+    std::vector<std::unique_ptr<Task>> tasks;
+    for (auto& command : vec_commands) {
+        tasks.emplace_back(ParseFastbootInfoLine(fp, command));
+    }
+    return tasks;
+}
+
+TEST_F(ParseTest, CORRECT_FlASH_TASK_FORMED) {
+    std::vector<std::string> commands = {"flash dtbo", "flash --slot-other system system_other.img",
+                                         "flash system", "flash --apply-vbmeta vbmeta"};
+
+    std::vector<std::unique_ptr<Task>> tasks = collectTasks(fp.get(), commands);
+
+    std::vector<std::vector<std::string>> expected_values{
+            {"dtbo", "dtbo_b", "b", "dtbo.img"},
+            {"system", "system_a", "a", "system_other.img"},
+            {"system", "system_b", "b", "system.img"},
+            {"vbmeta", "vbmeta_b", "b", "vbmeta.img"}
+
+    };
+
+    for (auto& task : tasks) {
+        ASSERT_TRUE(task != nullptr);
+    }
+
+    for (size_t i = 0; i < tasks.size(); i++) {
+        auto task = tasks[i]->AsFlashTask();
+        ASSERT_TRUE(task != nullptr);
+        ASSERT_EQ(task->GetPartition(), expected_values[i][0]);
+        ASSERT_EQ(task->GetPartitionAndSlot(), expected_values[i][1]);
+        ASSERT_EQ(task->GetSlot(), expected_values[i][2]);
+        ASSERT_EQ(task->GetImageName(), expected_values[i][3]);
+    }
+}
diff --git a/fastboot/util.cpp b/fastboot/util.cpp
index ded54a5..e03012a 100644
--- a/fastboot/util.cpp
+++ b/fastboot/util.cpp
@@ -33,6 +33,9 @@
 #include <sys/stat.h>
 #include <sys/time.h>
 
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+
 #include "util.h"
 
 using android::base::borrowed_fd;
@@ -106,3 +109,12 @@
     }
     return sb.st_size;
 }
+
+std::string fb_fix_numeric_var(std::string var) {
+    // Some bootloaders (angler, for example), send spurious leading whitespace.
+    var = android::base::Trim(var);
+    // Some bootloaders (hammerhead, for example) use implicit hex.
+    // This code used to use strtol with base 16.
+    if (!android::base::StartsWith(var, "0x")) var = "0x" + var;
+    return var;
+}
diff --git a/fastboot/util.h b/fastboot/util.h
index 8a79e13..fdbc1d6 100644
--- a/fastboot/util.h
+++ b/fastboot/util.h
@@ -6,29 +6,11 @@
 #include <string>
 #include <vector>
 
-#include <android-base/logging.h>
-#include <android-base/result.h>
 #include <android-base/unique_fd.h>
 #include <bootimg.h>
 #include <liblp/liblp.h>
 #include <sparse/sparse.h>
 
-using android::base::ErrnoError;
-using android::base::Error;
-using android::base::Result;
-using android::base::ResultError;
-
-template <typename T, typename U>
-inline T Expect(Result<T, U> r) {
-    if (r.ok()) {
-        return r.value();
-    }
-
-    LOG(FATAL) << r.error().message();
-
-    return r.value();
-}
-
 using SparsePtr = std::unique_ptr<sparse_file, decltype(&sparse_file_destroy)>;
 
 /* util stuff */
@@ -48,6 +30,7 @@
                                const std::string& partition_name);
 bool is_sparse_file(android::base::borrowed_fd fd);
 int64_t get_file_size(android::base::borrowed_fd fd);
+std::string fb_fix_numeric_var(std::string var);
 
 class ImageSource {
   public:
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index b6710d5..db27cf0 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -28,9 +28,6 @@
       "name": "vabc_legacy_tests"
     },
     {
-      "name": "libsnapshot_fuzzer_test"
-    },
-    {
       "name": "cow_api_test"
     }
   ],
diff --git a/fs_mgr/libfs_avb/avb_util.cpp b/fs_mgr/libfs_avb/avb_util.cpp
index 85dbb36..90b65ce 100644
--- a/fs_mgr/libfs_avb/avb_util.cpp
+++ b/fs_mgr/libfs_avb/avb_util.cpp
@@ -101,7 +101,6 @@
     if (wait_for_verity_dev) timeout = 1s;
 
     std::string dev_path;
-    const std::string mount_point(Basename(fstab_entry->mount_point));
     const std::string device_name(GetVerityDeviceName(*fstab_entry));
     android::dm::DeviceMapper& dm = android::dm::DeviceMapper::Instance();
     if (!dm.CreateDevice(device_name, table, &dev_path, timeout)) {
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index c2f435f..3dd1f1a 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -252,13 +252,6 @@
     header_libs: [
         "libstorage_literals_headers",
     ],
-    test_suites: [
-        "vts",
-        "device-tests"
-    ],
-    test_options: {
-        min_shipping_api_level: 29,
-    },
     auto_gen_config: true,
     require_root: true,
     compile_multilib: "first",
@@ -267,6 +260,13 @@
 cc_test {
     name: "vts_libsnapshot_test",
     defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
+    test_suites: [
+        "vts",
+        "device-tests"
+    ],
+    test_options: {
+        min_shipping_api_level: 30,
+    },
 }
 
 cc_test {
@@ -275,6 +275,13 @@
     cppflags: [
         "-DLIBSNAPSHOT_TEST_VAB_LEGACY",
     ],
+    test_suites: [
+        "device-tests"
+    ],
+    test_options: {
+        // Legacy VAB launched in Android R.
+        min_shipping_api_level: 30,
+    },
 }
 
 cc_test {
@@ -283,6 +290,13 @@
     cppflags: [
         "-DLIBSNAPSHOT_TEST_VABC_LEGACY",
     ],
+    test_suites: [
+        "device-tests"
+    ],
+    test_options: {
+        // Legacy VABC launched in Android S.
+        min_shipping_api_level: 31,
+    },
 }
 
 cc_test {
@@ -349,98 +363,6 @@
 }
 
 cc_test {
-    name: "snapshot_power_test",
-    srcs: [
-        "power_test.cpp",
-    ],
-    static_libs: [
-        "libc++fs",
-        "libsnapshot",
-        "update_metadata-protos",
-    ],
-    shared_libs: [
-        "libbase",
-        "libfs_mgr_binder",
-        "liblog",
-    ],
-    gtest: false,
-}
-
-cc_defaults {
-    name: "libsnapshot_fuzzer_defaults",
-    defaults: [
-        "libsnapshot_cow_defaults",
-    ],
-    native_coverage : true,
-    srcs: [
-        // Compile the protobuf definition again with type full.
-        "android/snapshot/snapshot_fuzz.proto",
-        "update_engine/update_metadata.proto",
-        "fuzz_utils.cpp",
-        "snapshot_fuzz.cpp",
-        "snapshot_fuzz_utils.cpp",
-
-        // Compile libsnapshot sources directly to avoid dependency
-        // to update_metadata-protos
-        ":libsnapshot_sources",
-    ],
-    static_libs: [
-        "libbase",
-        "libbrotli",
-        "libc++fs",
-        "libchrome",
-        "libcrypto_static",
-        "libcutils",
-        "libext2_uuid",
-        "libext4_utils",
-        "libfstab",
-        "libfs_mgr",
-        "libgtest", // from libsnapshot_test_helpers
-        "libgmock", // from libsnapshot_test_helpers
-        "liblog",
-        "liblp",
-        "libsnapshot_cow",
-        "libsnapshot_test_helpers",
-        "libprotobuf-mutator",
-        "libz",
-    ],
-    header_libs: [
-        "libfiemap_headers",
-        "libstorage_literals_headers",
-        "libupdate_engine_headers",
-    ],
-    proto: {
-        type: "full",
-        canonical_path_from_root: false,
-        local_include_dirs: ["."],
-    },
-}
-
-cc_fuzz {
-    name: "libsnapshot_fuzzer",
-    defaults: ["libsnapshot_fuzzer_defaults"],
-    corpus: ["corpus/*"],
-    fuzz_config: {
-        cc: ["android-virtual-ab+bugs@google.com"],
-        componentid: 30545,
-        hotlists: ["1646452"],
-        fuzz_on_haiku_host: false,
-        fuzz_on_haiku_device: true,
-    },
-}
-
-cc_test {
-    name: "libsnapshot_fuzzer_test",
-    defaults: ["libsnapshot_fuzzer_defaults"],
-    data: ["corpus/*"],
-    test_suites: [
-        "device-tests",
-    ],
-    auto_gen_config: true,
-    require_root: true,
-}
-
-cc_test {
     name: "cow_api_test",
     defaults: [
         "fs_mgr_defaults",
@@ -477,78 +399,6 @@
 }
 
 cc_binary {
-    name: "make_cow_from_ab_ota",
-    host_supported: true,
-    device_supported: false,
-    cflags: [
-        "-D_FILE_OFFSET_BITS=64",
-        "-Wall",
-        "-Werror",
-    ],
-    static_libs: [
-        "libbase",
-        "libbspatch",
-        "libbrotli",
-        "libbz",
-        "libchrome",
-        "libcrypto",
-        "libgflags",
-        "liblog",
-        "libprotobuf-cpp-lite",
-        "libpuffpatch",
-        "libsnapshot_cow",
-        "libsparse",
-        "libxz",
-        "libz",
-        "liblz4",
-        "libziparchive",
-        "update_metadata-protos",
-    ],
-    srcs: [
-        "make_cow_from_ab_ota.cpp",
-    ],
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-}
-
-cc_binary {
-    name: "estimate_cow_from_nonab_ota",
-    defaults: [
-        "libsnapshot_cow_defaults",
-    ],
-    host_supported: true,
-    device_supported: false,
-    cflags: [
-        "-D_FILE_OFFSET_BITS=64",
-        "-Wall",
-        "-Werror",
-    ],
-    static_libs: [
-        "libbase",
-        "libbrotli",
-        "libbz",
-        "libcrypto",
-        "libgflags",
-        "liblog",
-        "libsnapshot_cow",
-        "libsparse",
-        "libz",
-        "libziparchive",
-    ],
-    srcs: [
-        "estimate_cow_from_nonab_ota.cpp",
-    ],
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-}
-
-cc_binary {
     name: "inspect_cow",
     host_supported: true,
     device_supported: true,
diff --git a/fs_mgr/libsnapshot/PowerTest.md b/fs_mgr/libsnapshot/PowerTest.md
deleted file mode 100644
index 0b0cb5d..0000000
--- a/fs_mgr/libsnapshot/PowerTest.md
+++ /dev/null
@@ -1,40 +0,0 @@
-snapshot\_power\_test
----------------------
-
-snapshot\_power\_test is a standalone test to simulate power failures during a snapshot-merge operation.
-
-### Test Setup
-
-Start by creating two large files that will be used as the pre-merge and post-merge state. You can take two different partition images (for example, a product.img from two separate builds), or just create random data:
-
-	dd if=/dev/urandom of=pre-merge count=1024 bs=1048576
-	dd if=/dev/urandom of=post-merge count=1024 bs=1048576
-
-Next, push these files to an unencrypted directory on the device:
-
-	adb push pre-merge /data/local/unencrypted
-	adb push post-merge /data/local/unencrypted
-
-Next, run the test setup:
-
-	adb sync data
-	adb shell /data/nativetest64/snapshot_power_test/snapshot_power_test \
-		/data/local/unencrypted/pre-merge \
-		/data/local/unencrypted/post-merge
-
-This will create the necessary fiemap-based images.
-
-### Running
-The actual test can be run via `run_power_test.sh`. Its syntax is:
-
-	run_power_test.sh <POST_MERGE_FILE>
-
-`POST_MERGE_FILE` should be the path on the device of the image to validate the merge against. Example:
-
-	run_power_test.sh /data/local/unencrypted/post-merge
-
-The device will begin the merge with a 5% chance of injecting a kernel crash every 10ms. The device should be capable of rebooting normally without user intervention. Once the merge has completed, the test will run a final check command to validate the contents of the snapshot against the post-merge file. It will error if there are any incorrect blocks.
-
-Two environment variables can be passed to `run_power_test.sh`:
-1. `FAIL_RATE` - A fraction between 0 and 100 (inclusive) indicating the probability the device should inject a kernel crash every 10ms.
-2. `DEVICE_SERIAL` - If multiple devices are attached to adb, this argument is passed as the serial to select (to `adb -s`).
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto
deleted file mode 100644
index a55b42a..0000000
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot_fuzz.proto
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-package android.snapshot;
-
-import "update_engine/update_metadata.proto";
-
-// Controls the behavior of IDeviceInfo.
-// Next: 6
-message FuzzDeviceInfoData {
-    bool slot_suffix_is_a = 1;
-    bool is_overlayfs_setup = 2;
-    bool allow_set_boot_control_merge_status = 3;
-    bool allow_set_slot_as_unbootable = 4;
-    bool is_recovery = 5;
-}
-
-// Controls the behavior of the test SnapshotManager.
-// Next: 2
-message FuzzSnapshotManagerData {
-    bool is_local_image_manager = 1;
-}
-
-// A simplified version of CreateLogicalPartitionParams for fuzzing.
-// Next: 9
-message CreateLogicalPartitionParamsProto {
-    bool use_correct_super = 1;
-    string block_device = 2;
-    bool has_metadata_slot = 3;
-    uint32 metadata_slot = 4;
-    string partition_name = 5;
-    bool force_writable = 6;
-    int64 timeout_millis = 7;
-    string device_name = 8;
-}
-
-// Mimics the API of ISnapshotManager. Defines one action on the snapshot
-// manager.
-// Next: 18
-message SnapshotManagerActionProto {
-    message NoArgs {}
-    message ProcessUpdateStateArgs {
-        bool has_before_cancel = 1;
-        bool fail_before_cancel = 2;
-    }
-    message CreateLogicalAndSnapshotPartitionsArgs {
-        bool use_correct_super = 1;
-        string super = 2;
-        int64 timeout_millis = 3;
-    }
-    message RecoveryCreateSnapshotDevicesArgs {
-        bool has_metadata_device_object = 1;
-        bool metadata_mounted = 2;
-    }
-    reserved 18 to 9999;
-    oneof value {
-        NoArgs begin_update = 1;
-        NoArgs cancel_update = 2;
-        bool finished_snapshot_writes = 3;
-        NoArgs initiate_merge = 4;
-        ProcessUpdateStateArgs process_update_state = 5;
-        bool get_update_state = 6;
-        chromeos_update_engine.DeltaArchiveManifest create_update_snapshots = 7;
-        CreateLogicalPartitionParamsProto map_update_snapshot = 8;
-        string unmap_update_snapshot = 9;
-        NoArgs need_snapshots_in_first_stage_mount = 10;
-        CreateLogicalAndSnapshotPartitionsArgs create_logical_and_snapshot_partitions = 11;
-        bool handle_imminent_data_wipe = 12;
-        NoArgs recovery_create_snapshot_devices = 13;
-        RecoveryCreateSnapshotDevicesArgs recovery_create_snapshot_devices_with_metadata = 14;
-        NoArgs dump = 15;
-        NoArgs ensure_metadata_mounted = 16;
-        NoArgs get_snapshot_merge_stats_instance = 17;
-
-        // Test directives that has nothing to do with ISnapshotManager API surface.
-        NoArgs switch_slot = 10000;
-    }
-}
-
-// Includes all data that needs to be fuzzed.
-message SnapshotFuzzData {
-    FuzzDeviceInfoData device_info_data = 1;
-    FuzzSnapshotManagerData manager_data = 2;
-
-    // If true:
-    // - if super_data is empty, create empty super partition metadata.
-    // - otherwise, create super partition metadata accordingly.
-    // If false, no valid super partition metadata (it is zeroed)
-    bool is_super_metadata_valid = 3;
-    chromeos_update_engine.DeltaArchiveManifest super_data = 4;
-
-    // Whether the directory that mocks /metadata/ota/snapshot is created.
-    bool has_metadata_snapshots_dir = 5;
-
-    // More data used to prep the test before running actions.
-    reserved 6 to 9999;
-    repeated SnapshotManagerActionProto actions = 10000;
-}
diff --git a/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt b/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt
deleted file mode 100644
index c474f4c..0000000
--- a/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-device_info_data {
-  allow_set_slot_as_unbootable: true
-  is_recovery: true
-}
-is_super_metadata_valid: true
-super_data {
-  partitions {
-    partition_name: "sys_a"
-    new_partition_info {
-      size: 3145728
-    }
-  }
-  partitions {
-    partition_name: "vnnd_"
-    new_partition_info {
-      size: 3145728
-    }
-  }
-  partitions {
-    partition_name: "prd_a"
-    new_partition_info {
-    }
-  }
-  dynamic_partition_metadata {
-    groups {
-      name: "group_google_dp_a"
-      size: 34375467008
-      partition_names: "sys_a"
-      partition_names: "vnd_a"
-      partition_names: "prd_a"
-    }
-  }
-}
-has_metadata_snapshots_dir: true
-actions {
-  handle_imminent_data_wipe: true
-}
-actions {
-  begin_update {
-  }
-}
diff --git a/fs_mgr/libsnapshot/corpus/launch_device.txt b/fs_mgr/libsnapshot/corpus/launch_device.txt
deleted file mode 100644
index 55a7f2c..0000000
--- a/fs_mgr/libsnapshot/corpus/launch_device.txt
+++ /dev/null
@@ -1,161 +0,0 @@
-device_info_data {
-  slot_suffix_is_a: true
-  is_overlayfs_setup: false
-  allow_set_boot_control_merge_status: true
-  allow_set_slot_as_unbootable: true
-  is_recovery: false
-}
-manager_data {
-  is_local_image_manager: false
-}
-is_super_metadata_valid: true
-super_data {
-  partitions {
-    partition_name: "sys_a"
-    new_partition_info {
-      size: 3145728
-    }
-  }
-  partitions {
-    partition_name: "vnd_a"
-    new_partition_info {
-      size: 3145728
-    }
-  }
-  partitions {
-    partition_name: "prd_a"
-    new_partition_info {
-      size: 3145728
-    }
-  }
-  dynamic_partition_metadata {
-    groups {
-      name: "group_google_dp_a"
-      size: 15728640
-      partition_names: "sys_a"
-      partition_names: "vnd_a"
-      partition_names: "prd_a"
-    }
-  }
-}
-has_metadata_snapshots_dir: true
-actions {
-  begin_update {
-  }
-}
-actions {
-  create_update_snapshots {
-    partitions {
-      partition_name: "sys"
-      new_partition_info {
-        size: 3878912
-      }
-      operations {
-        type: ZERO,
-        dst_extents {
-          start_block: 0
-          num_blocks: 947
-        }
-      }
-    }
-    partitions {
-      partition_name: "vnd"
-      new_partition_info {
-        size: 3878912
-      }
-      operations {
-        type: ZERO,
-        dst_extents {
-          start_block: 0
-          num_blocks: 947
-        }
-      }
-    }
-    partitions {
-      partition_name: "prd"
-      new_partition_info {
-        size: 3878912
-      }
-      operations {
-        type: ZERO,
-        dst_extents {
-          start_block: 0
-          num_blocks: 947
-        }
-      }
-    }
-    dynamic_partition_metadata {
-      groups {
-        name: "group_google_dp"
-        size: 15728640
-        partition_names: "sys"
-        partition_names: "vnd"
-        partition_names: "prd"
-      }
-    }
-  }
-}
-actions {
-  map_update_snapshot {
-    use_correct_super: true
-    has_metadata_slot: true
-    metadata_slot: 1
-    partition_name: "sys_b"
-    force_writable: true
-    timeout_millis: 3000
-  }
-}
-actions {
-  map_update_snapshot {
-    use_correct_super: true
-    has_metadata_slot: true
-    metadata_slot: 1
-    partition_name: "vnd_b"
-    force_writable: true
-    timeout_millis: 3000
-  }
-}
-actions {
-  map_update_snapshot {
-    use_correct_super: true
-    has_metadata_slot: true
-    metadata_slot: 1
-    partition_name: "prd_b"
-    force_writable: true
-    timeout_millis: 3000
-  }
-}
-actions {
-  finished_snapshot_writes: false
-}
-actions {
-  unmap_update_snapshot: "sys_b"
-}
-actions {
-  unmap_update_snapshot: "vnd_b"
-}
-actions {
-  unmap_update_snapshot: "prd_b"
-}
-actions {
-  switch_slot {
-  }
-}
-actions {
-  need_snapshots_in_first_stage_mount {
-  }
-}
-actions {
-  create_logical_and_snapshot_partitions {
-    use_correct_super: true
-    timeout_millis: 5000
-  }
-}
-actions {
-  initiate_merge {
-  }
-}
-actions {
-  process_update_state {
-  }
-}
diff --git a/fs_mgr/libsnapshot/estimate_cow_from_nonab_ota.cpp b/fs_mgr/libsnapshot/estimate_cow_from_nonab_ota.cpp
deleted file mode 100644
index 45833e1..0000000
--- a/fs_mgr/libsnapshot/estimate_cow_from_nonab_ota.cpp
+++ /dev/null
@@ -1,432 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-#include <stdio.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <iostream>
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <gflags/gflags.h>
-#include <libsnapshot/cow_writer.h>
-#include <openssl/sha.h>
-#include <sparse/sparse.h>
-#include <ziparchive/zip_archive.h>
-
-DEFINE_string(source_tf, "", "Source target files (dir or zip file)");
-DEFINE_string(ota_tf, "", "Target files of the build for an OTA");
-DEFINE_string(compression, "gz", "Compression (options: none, gz, brotli)");
-
-namespace android {
-namespace snapshot {
-
-using android::base::borrowed_fd;
-using android::base::unique_fd;
-
-static constexpr size_t kBlockSize = 4096;
-
-void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
-              unsigned int, const char* message) {
-    if (severity == android::base::ERROR) {
-        fprintf(stderr, "%s\n", message);
-    } else {
-        fprintf(stdout, "%s\n", message);
-    }
-}
-
-class TargetFilesPackage final {
-  public:
-    explicit TargetFilesPackage(const std::string& path);
-
-    bool Open();
-    bool HasFile(const std::string& path);
-    std::unordered_set<std::string> GetDynamicPartitionNames();
-    unique_fd OpenFile(const std::string& path);
-    unique_fd OpenImage(const std::string& path);
-
-  private:
-    std::string path_;
-    unique_fd fd_;
-    std::unique_ptr<ZipArchive, decltype(&CloseArchive)> zip_;
-};
-
-TargetFilesPackage::TargetFilesPackage(const std::string& path)
-    : path_(path), zip_(nullptr, &CloseArchive) {}
-
-bool TargetFilesPackage::Open() {
-    fd_.reset(open(path_.c_str(), O_RDONLY));
-    if (fd_ < 0) {
-        PLOG(ERROR) << "open failed: " << path_;
-        return false;
-    }
-
-    struct stat s;
-    if (fstat(fd_.get(), &s) < 0) {
-        PLOG(ERROR) << "fstat failed: " << path_;
-        return false;
-    }
-    if (S_ISDIR(s.st_mode)) {
-        return true;
-    }
-
-    // Otherwise, assume it's a zip file.
-    ZipArchiveHandle handle;
-    if (OpenArchiveFd(fd_.get(), path_.c_str(), &handle, false)) {
-        LOG(ERROR) << "Could not open " << path_ << " as a zip archive.";
-        return false;
-    }
-    zip_.reset(handle);
-    return true;
-}
-
-bool TargetFilesPackage::HasFile(const std::string& path) {
-    if (zip_) {
-        ZipEntry64 entry;
-        return !FindEntry(zip_.get(), path, &entry);
-    }
-
-    auto full_path = path_ + "/" + path;
-    return access(full_path.c_str(), F_OK) == 0;
-}
-
-unique_fd TargetFilesPackage::OpenFile(const std::string& path) {
-    if (!zip_) {
-        auto full_path = path_ + "/" + path;
-        unique_fd fd(open(full_path.c_str(), O_RDONLY));
-        if (fd < 0) {
-            PLOG(ERROR) << "open failed: " << full_path;
-            return {};
-        }
-        return fd;
-    }
-
-    ZipEntry64 entry;
-    if (FindEntry(zip_.get(), path, &entry)) {
-        LOG(ERROR) << path << " not found in archive: " << path_;
-        return {};
-    }
-
-    TemporaryFile temp;
-    if (temp.fd < 0) {
-        PLOG(ERROR) << "mkstemp failed";
-        return {};
-    }
-
-    LOG(INFO) << "Extracting " << path << " from " << path_ << " ...";
-    if (ExtractEntryToFile(zip_.get(), &entry, temp.fd)) {
-        LOG(ERROR) << "could not extract " << path << " from " << path_;
-        return {};
-    }
-    if (lseek(temp.fd, 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek failed";
-        return {};
-    }
-    return unique_fd{temp.release()};
-}
-
-unique_fd TargetFilesPackage::OpenImage(const std::string& path) {
-    auto fd = OpenFile(path);
-    if (fd < 0) {
-        return {};
-    }
-
-    LOG(INFO) << "Unsparsing " << path << " ...";
-    std::unique_ptr<struct sparse_file, decltype(&sparse_file_destroy)> s(
-            sparse_file_import(fd.get(), false, false), &sparse_file_destroy);
-    if (!s) {
-        return fd;
-    }
-
-    TemporaryFile temp;
-    if (temp.fd < 0) {
-        PLOG(ERROR) << "mkstemp failed";
-        return {};
-    }
-    if (sparse_file_write(s.get(), temp.fd, false, false, false) < 0) {
-        LOG(ERROR) << "sparse_file_write failed";
-        return {};
-    }
-    if (lseek(temp.fd, 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek failed";
-        return {};
-    }
-
-    fd.reset(temp.release());
-    return fd;
-}
-
-std::unordered_set<std::string> TargetFilesPackage::GetDynamicPartitionNames() {
-    auto fd = OpenFile("META/misc_info.txt");
-    if (fd < 0) {
-        return {};
-    }
-
-    std::string contents;
-    if (!android::base::ReadFdToString(fd, &contents)) {
-        PLOG(ERROR) << "read failed";
-        return {};
-    }
-
-    std::unordered_set<std::string> set;
-
-    auto lines = android::base::Split(contents, "\n");
-    for (const auto& line : lines) {
-        auto parts = android::base::Split(line, "=");
-        if (parts.size() == 2 && parts[0] == "dynamic_partition_list") {
-            auto partitions = android::base::Split(parts[1], " ");
-            for (const auto& name : partitions) {
-                if (!name.empty()) {
-                    set.emplace(name);
-                }
-            }
-            break;
-        }
-    }
-    return set;
-}
-
-class NonAbEstimator final {
-  public:
-    NonAbEstimator(const std::string& ota_tf_path, const std::string& source_tf_path)
-        : ota_tf_path_(ota_tf_path), source_tf_path_(source_tf_path) {}
-
-    bool Run();
-
-  private:
-    bool OpenPackages();
-    bool AnalyzePartition(const std::string& partition_name);
-    std::unordered_map<std::string, uint64_t> GetBlockMap(borrowed_fd fd);
-
-    std::string ota_tf_path_;
-    std::string source_tf_path_;
-    std::unique_ptr<TargetFilesPackage> ota_tf_;
-    std::unique_ptr<TargetFilesPackage> source_tf_;
-    uint64_t size_ = 0;
-};
-
-bool NonAbEstimator::Run() {
-    if (!OpenPackages()) {
-        return false;
-    }
-
-    auto partitions = ota_tf_->GetDynamicPartitionNames();
-    if (partitions.empty()) {
-        LOG(ERROR) << "No dynamic partitions found in META/misc_info.txt";
-        return false;
-    }
-    for (const auto& partition : partitions) {
-        if (!AnalyzePartition(partition)) {
-            return false;
-        }
-    }
-
-    int64_t size_in_mb = int64_t(double(size_) / 1024.0 / 1024.0);
-
-    std::cout << "Estimated COW size: " << size_ << " (" << size_in_mb << "MiB)\n";
-    return true;
-}
-
-bool NonAbEstimator::OpenPackages() {
-    ota_tf_ = std::make_unique<TargetFilesPackage>(ota_tf_path_);
-    if (!ota_tf_->Open()) {
-        return false;
-    }
-    if (!source_tf_path_.empty()) {
-        source_tf_ = std::make_unique<TargetFilesPackage>(source_tf_path_);
-        if (!source_tf_->Open()) {
-            return false;
-        }
-    }
-    return true;
-}
-
-static std::string SHA256(const std::string& input) {
-    std::string hash(32, '\0');
-    SHA256_CTX c;
-    SHA256_Init(&c);
-    SHA256_Update(&c, input.data(), input.size());
-    SHA256_Final(reinterpret_cast<unsigned char*>(hash.data()), &c);
-    return hash;
-}
-
-bool NonAbEstimator::AnalyzePartition(const std::string& partition_name) {
-    auto path = "IMAGES/" + partition_name + ".img";
-    auto fd = ota_tf_->OpenImage(path);
-    if (fd < 0) {
-        return false;
-    }
-
-    unique_fd source_fd;
-    uint64_t source_size = 0;
-    std::unordered_map<std::string, uint64_t> source_blocks;
-    if (source_tf_) {
-        auto dap = source_tf_->GetDynamicPartitionNames();
-
-        source_fd = source_tf_->OpenImage(path);
-        if (source_fd >= 0) {
-            struct stat s;
-            if (fstat(source_fd.get(), &s)) {
-                PLOG(ERROR) << "fstat failed";
-                return false;
-            }
-            source_size = s.st_size;
-
-            std::cout << "Hashing blocks for " << partition_name << "...\n";
-            source_blocks = GetBlockMap(source_fd);
-            if (source_blocks.empty()) {
-                LOG(ERROR) << "Could not build a block map for source partition: "
-                           << partition_name;
-                return false;
-            }
-        } else {
-            if (dap.count(partition_name)) {
-                return false;
-            }
-            LOG(ERROR) << "Warning: " << partition_name
-                       << " has no incremental diff since it's not in the source image.";
-        }
-    }
-
-    TemporaryFile cow;
-    if (cow.fd < 0) {
-        PLOG(ERROR) << "mkstemp failed";
-        return false;
-    }
-
-    CowOptions options;
-    options.block_size = kBlockSize;
-    options.compression = FLAGS_compression;
-
-    auto writer = std::make_unique<CowWriter>(options);
-    if (!writer->Initialize(borrowed_fd{cow.fd})) {
-        LOG(ERROR) << "Could not initialize COW writer";
-        return false;
-    }
-
-    LOG(INFO) << "Analyzing " << partition_name << " ...";
-
-    std::string zeroes(kBlockSize, '\0');
-    std::string chunk(kBlockSize, '\0');
-    std::string src_chunk(kBlockSize, '\0');
-    uint64_t next_block_number = 0;
-    while (true) {
-        if (!android::base::ReadFully(fd, chunk.data(), chunk.size())) {
-            if (errno) {
-                PLOG(ERROR) << "read failed";
-                return false;
-            }
-            break;
-        }
-
-        uint64_t block_number = next_block_number++;
-        if (chunk == zeroes) {
-            if (!writer->AddZeroBlocks(block_number, 1)) {
-                LOG(ERROR) << "Could not add zero block";
-                return false;
-            }
-            continue;
-        }
-
-        uint64_t source_offset = block_number * kBlockSize;
-        if (source_fd >= 0 && source_offset <= source_size) {
-            off64_t offset = block_number * kBlockSize;
-            if (android::base::ReadFullyAtOffset(source_fd, src_chunk.data(), src_chunk.size(),
-                                                 offset)) {
-                if (chunk == src_chunk) {
-                    continue;
-                }
-            } else if (errno) {
-                PLOG(ERROR) << "pread failed";
-                return false;
-            }
-        }
-
-        auto hash = SHA256(chunk);
-        if (auto iter = source_blocks.find(hash); iter != source_blocks.end()) {
-            if (!writer->AddCopy(block_number, iter->second)) {
-                return false;
-            }
-            continue;
-        }
-
-        if (!writer->AddRawBlocks(block_number, chunk.data(), chunk.size())) {
-            return false;
-        }
-    }
-
-    if (!writer->Finalize()) {
-        return false;
-    }
-
-    struct stat s;
-    if (fstat(cow.fd, &s) < 0) {
-        PLOG(ERROR) << "fstat failed";
-        return false;
-    }
-
-    size_ += s.st_size;
-    return true;
-}
-
-std::unordered_map<std::string, uint64_t> NonAbEstimator::GetBlockMap(borrowed_fd fd) {
-    std::string chunk(kBlockSize, '\0');
-
-    std::unordered_map<std::string, uint64_t> block_map;
-    uint64_t block_number = 0;
-    while (true) {
-        if (!android::base::ReadFully(fd, chunk.data(), chunk.size())) {
-            if (errno) {
-                PLOG(ERROR) << "read failed";
-                return {};
-            }
-            break;
-        }
-        auto hash = SHA256(chunk);
-        block_map[hash] = block_number;
-        block_number++;
-    }
-    return block_map;
-}
-
-}  // namespace snapshot
-}  // namespace android
-
-using namespace android::snapshot;
-
-int main(int argc, char** argv) {
-    android::base::InitLogging(argv, android::snapshot::MyLogger);
-    gflags::SetUsageMessage("Estimate VAB disk usage from Non A/B builds");
-    gflags::ParseCommandLineFlags(&argc, &argv, false);
-
-    if (FLAGS_ota_tf.empty()) {
-        std::cerr << "Must specify -ota_tf on the command-line." << std::endl;
-        return 1;
-    }
-
-    NonAbEstimator estimator(FLAGS_ota_tf, FLAGS_source_tf);
-    if (!estimator.Run()) {
-        return 1;
-    }
-    return 0;
-}
diff --git a/fs_mgr/libsnapshot/fuzz.sh b/fs_mgr/libsnapshot/fuzz.sh
deleted file mode 100755
index 5995cef..0000000
--- a/fs_mgr/libsnapshot/fuzz.sh
+++ /dev/null
@@ -1,90 +0,0 @@
-#!/bin/bash
-PROJECT_PATH=system/core/fs_mgr/libsnapshot
-FUZZ_TARGET=libsnapshot_fuzzer
-TARGET_ARCH=$(get_build_var TARGET_ARCH)
-FUZZ_BINARY=/data/fuzz/${TARGET_ARCH}/${FUZZ_TARGET}/${FUZZ_TARGET}
-DEVICE_INIT_CORPUS_DIR=/data/fuzz/${TARGET_ARCH}/${FUZZ_TARGET}/corpus
-DEVICE_GENERATED_CORPUS_DIR=/data/local/tmp/${FUZZ_TARGET}/corpus
-DEVICE_GCOV_DIR=/data/local/tmp/${FUZZ_TARGET}/gcov
-HOST_SCRATCH_DIR=/tmp/${FUZZ_TARGET}
-GCOV_TOOL=${HOST_SCRATCH_DIR}/llvm-gcov
-
-build_normal() (
-    pushd $(gettop)
-    NATIVE_COVERAGE="" NATIVE_LINE_COVERAGE="" NATIVE_COVERAGE_PATHS="" m ${FUZZ_TARGET}
-    ret=$?
-    popd
-    return ${ret}
-)
-
-build_cov() {
-    pushd $(gettop)
-    NATIVE_COVERAGE="true" NATIVE_LINE_COVERAGE="true" NATIVE_COVERAGE_PATHS="${PROJECT_PATH}" m ${FUZZ_TARGET}
-    ret=$?
-    popd
-    return ${ret}
-}
-
-prepare_device() {
-    adb root && adb remount &&
-    adb shell mkdir -p ${DEVICE_GENERATED_CORPUS_DIR} &&
-    adb shell rm -rf ${DEVICE_GCOV_DIR} &&
-    adb shell mkdir -p ${DEVICE_GCOV_DIR}
-}
-
-push_binary() {
-    adb push ${ANDROID_PRODUCT_OUT}/${FUZZ_BINARY} ${FUZZ_BINARY} &&
-    adb push ${ANDROID_PRODUCT_OUT}/${DEVICE_INIT_CORPUS_DIR} $(dirname ${FUZZ_BINARY})
-}
-
-prepare_host() {
-    which lcov || {
-        echo "please run:";
-        echo "   sudo apt-get install lcov ";
-        return 1;
-    }
-    rm -rf ${HOST_SCRATCH_DIR} &&
-    mkdir -p ${HOST_SCRATCH_DIR}
-}
-
-# run_snapshot_fuzz -runs=10000
-generate_corpus() {
-    [[ "$@" ]] || { echo "run with -runs=X"; return 1; }
-
-    prepare_device &&
-    build_normal &&
-    push_binary &&
-    adb shell ${FUZZ_BINARY} "$@" ${DEVICE_INIT_CORPUS_DIR} ${DEVICE_GENERATED_CORPUS_DIR}
-}
-
-run_snapshot_fuzz() {
-    prepare_device &&
-    build_cov &&
-    push_binary &&
-    adb shell GCOV_PREFIX=${DEVICE_GCOV_DIR} GCOV_PREFIX_STRIP=3 \
-        ${FUZZ_BINARY} \
-        -runs=0 \
-        ${DEVICE_INIT_CORPUS_DIR} ${DEVICE_GENERATED_CORPUS_DIR}
-}
-
-show_fuzz_result() {
-    prepare_host &&
-    unzip -o -j -d ${HOST_SCRATCH_DIR} ${ANDROID_PRODUCT_OUT}/coverage/data/fuzz/${TARGET_ARCH}/${FUZZ_TARGET}/${FUZZ_TARGET}.zip &&
-    adb shell find ${DEVICE_GCOV_DIR} -type f | xargs -I {} adb pull {} ${HOST_SCRATCH_DIR} &&
-    ls ${HOST_SCRATCH_DIR} &&
-    cat > ${GCOV_TOOL} <<< '
-#!/bin/bash
-exec llvm-cov gcov "$@"
-' &&
-    chmod +x ${GCOV_TOOL} &&
-    lcov --directory ${HOST_SCRATCH_DIR} --base-directory $(gettop) --gcov-tool ${GCOV_TOOL} --capture -o ${HOST_SCRATCH_DIR}/report.cov &&
-    genhtml ${HOST_SCRATCH_DIR}/report.cov -o ${HOST_SCRATCH_DIR}/html &&
-    echo file://$(realpath ${HOST_SCRATCH_DIR}/html/index.html)
-}
-
-# run_snapshot_fuzz -runs=10000
-run_snapshot_fuzz_all() {
-    generate_corpus "$@" &&
-    run_snapshot_fuzz &&
-    show_fuzz_result
-}
diff --git a/fs_mgr/libsnapshot/fuzz_utils.cpp b/fs_mgr/libsnapshot/fuzz_utils.cpp
deleted file mode 100644
index 0263f7e..0000000
--- a/fs_mgr/libsnapshot/fuzz_utils.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "fuzz_utils.h"
-
-#include <android-base/logging.h>
-
-namespace android::fuzz {
-
-void CheckInternal(bool value, std::string_view msg) {
-    CHECK(value) << msg;
-}
-
-const google::protobuf::OneofDescriptor* GetProtoValueDescriptor(
-        const google::protobuf::Descriptor* action_desc) {
-    CHECK(action_desc);
-    CHECK(action_desc->oneof_decl_count() == 1)
-            << action_desc->oneof_decl_count() << " oneof fields found in " << action_desc->name()
-            << "; only one is expected.";
-    auto* oneof_value_desc = action_desc->oneof_decl(0);
-    CHECK(oneof_value_desc);
-    CHECK(oneof_value_desc->name() == "value")
-            << "oneof field has name " << oneof_value_desc->name();
-    return oneof_value_desc;
-}
-
-}  // namespace android::fuzz
diff --git a/fs_mgr/libsnapshot/fuzz_utils.h b/fs_mgr/libsnapshot/fuzz_utils.h
deleted file mode 100644
index 20b13b2..0000000
--- a/fs_mgr/libsnapshot/fuzz_utils.h
+++ /dev/null
@@ -1,285 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#include <map>
-#include <string>
-#include <string_view>
-
-#include <google/protobuf/descriptor.h>
-#include <google/protobuf/message.h>
-#include <google/protobuf/repeated_field.h>
-
-// Utilities for using a protobuf definition to fuzz APIs in a class.
-// Terms:
-// The "fuzzed class" is the C++ class definition whose functions are fuzzed.
-// The "fuzzed object" is an instantiated object of the fuzzed class. It is
-//   typically created and destroyed for each test run.
-// An "action" is an operation on the fuzzed object that may mutate its state.
-//   This typically involves one function call into the fuzzed object.
-
-namespace android::fuzz {
-
-// CHECK(value) << msg
-void CheckInternal(bool value, std::string_view msg);
-
-// Get the oneof descriptor inside Action
-const google::protobuf::OneofDescriptor* GetProtoValueDescriptor(
-        const google::protobuf::Descriptor* action_desc);
-
-template <typename Class>
-using FunctionMapImpl =
-        std::map<int, std::function<void(Class*, const google::protobuf::Message& action_proto,
-                                         const google::protobuf::FieldDescriptor* field_desc)>>;
-
-template <typename Class>
-class FunctionMap : public FunctionMapImpl<Class> {
-  public:
-    void CheckEmplace(typename FunctionMapImpl<Class>::key_type key,
-                      typename FunctionMapImpl<Class>::mapped_type&& value) {
-        auto [it, inserted] = this->emplace(key, std::move(value));
-        CheckInternal(inserted,
-                      "Multiple implementation registered for tag number " + std::to_string(key));
-    }
-};
-
-template <typename Action>
-int CheckConsistency() {
-    const auto* function_map = Action::GetFunctionMap();
-    const auto* action_value_desc = GetProtoValueDescriptor(Action::Proto::GetDescriptor());
-
-    for (int field_index = 0; field_index < action_value_desc->field_count(); ++field_index) {
-        const auto* field_desc = action_value_desc->field(field_index);
-        CheckInternal(function_map->find(field_desc->number()) != function_map->end(),
-                      "Missing impl for function " + field_desc->camelcase_name());
-    }
-    return 0;
-}
-
-// Get the field descriptor for the oneof field in the action message. If no oneof field is set,
-// return nullptr.
-template <typename Action>
-const google::protobuf::FieldDescriptor* GetValueFieldDescriptor(
-        const typename Action::Proto& action_proto) {
-    static auto* action_value_desc = GetProtoValueDescriptor(Action::Proto::GetDescriptor());
-
-    auto* action_refl = Action::Proto::GetReflection();
-    if (!action_refl->HasOneof(action_proto, action_value_desc)) {
-        return nullptr;
-    }
-    return action_refl->GetOneofFieldDescriptor(action_proto, action_value_desc);
-}
-
-template <typename Action>
-void ExecuteActionProto(typename Action::ClassType* module,
-                        const typename Action::Proto& action_proto) {
-    const auto* field_desc = GetValueFieldDescriptor<Action>(action_proto);
-    if (field_desc == nullptr) return;
-    auto number = field_desc->number();
-    const auto& map = *Action::GetFunctionMap();
-    auto it = map.find(number);
-    CheckInternal(it != map.end(), "Missing impl for function " + field_desc->camelcase_name());
-    const auto& func = it->second;
-    func(module, action_proto, field_desc);
-}
-
-template <typename Action>
-void ExecuteAllActionProtos(
-        typename Action::ClassType* module,
-        const google::protobuf::RepeatedPtrField<typename Action::Proto>& action_protos) {
-    for (const auto& proto : action_protos) {
-        ExecuteActionProto<Action>(module, proto);
-    }
-}
-
-// Safely cast message to T. Returns a pointer to message if cast successfully, otherwise nullptr.
-template <typename T>
-const T* SafeCast(const google::protobuf::Message& message) {
-    if (message.GetDescriptor() != T::GetDescriptor()) {
-        return nullptr;
-    }
-    return static_cast<const T*>(&message);
-}
-
-// Cast message to const T&. Abort if type mismatch.
-template <typename T>
-const T& CheckedCast(const google::protobuf::Message& message) {
-    const auto* ptr = SafeCast<T>(message);
-    CheckInternal(ptr, "Cannot cast " + message.GetDescriptor()->name() + " to " +
-                               T::GetDescriptor()->name());
-    return *ptr;
-}
-
-// A templated way to a primitive field from a message using reflection.
-template <typename T>
-struct PrimitiveGetter;
-#define FUZZ_DEFINE_PRIMITIVE_GETTER(type, func_name)                              \
-    template <>                                                                    \
-    struct PrimitiveGetter<type> {                                                 \
-        static constexpr const auto fp = &google::protobuf::Reflection::func_name; \
-    }
-
-FUZZ_DEFINE_PRIMITIVE_GETTER(bool, GetBool);
-FUZZ_DEFINE_PRIMITIVE_GETTER(uint32_t, GetUInt32);
-FUZZ_DEFINE_PRIMITIVE_GETTER(int32_t, GetInt32);
-FUZZ_DEFINE_PRIMITIVE_GETTER(uint64_t, GetUInt64);
-FUZZ_DEFINE_PRIMITIVE_GETTER(int64_t, GetInt64);
-FUZZ_DEFINE_PRIMITIVE_GETTER(double, GetDouble);
-FUZZ_DEFINE_PRIMITIVE_GETTER(float, GetFloat);
-
-// ActionPerformer extracts arguments from the protobuf message, and then call FuzzFunction
-// with these arguments.
-template <typename FuzzFunction, typename Signature, typename Enabled = void>
-struct ActionPerformerImpl;  // undefined
-
-template <typename FuzzFunction, typename MessageProto>
-struct ActionPerformerImpl<
-        FuzzFunction, void(const MessageProto&),
-        typename std::enable_if_t<std::is_base_of_v<google::protobuf::Message, MessageProto>>> {
-    static typename FuzzFunction::ReturnType Invoke(
-            typename FuzzFunction::ClassType* module, const google::protobuf::Message& action_proto,
-            const google::protobuf::FieldDescriptor* field_desc) {
-        const MessageProto& arg = CheckedCast<std::remove_reference_t<MessageProto>>(
-                action_proto.GetReflection()->GetMessage(action_proto, field_desc));
-        return FuzzFunction::ImplBody(module, arg);
-    }
-};
-
-template <typename FuzzFunction, typename Primitive>
-struct ActionPerformerImpl<FuzzFunction, void(Primitive),
-                           typename std::enable_if_t<std::is_arithmetic_v<Primitive>>> {
-    static typename FuzzFunction::ReturnType Invoke(
-            typename FuzzFunction::ClassType* module, const google::protobuf::Message& action_proto,
-            const google::protobuf::FieldDescriptor* field_desc) {
-        Primitive arg = std::invoke(PrimitiveGetter<Primitive>::fp, action_proto.GetReflection(),
-                                    action_proto, field_desc);
-        return FuzzFunction::ImplBody(module, arg);
-    }
-};
-
-template <typename FuzzFunction>
-struct ActionPerformerImpl<FuzzFunction, void()> {
-    static typename FuzzFunction::ReturnType Invoke(typename FuzzFunction::ClassType* module,
-                                                    const google::protobuf::Message&,
-                                                    const google::protobuf::FieldDescriptor*) {
-        return FuzzFunction::ImplBody(module);
-    }
-};
-
-template <typename FuzzFunction>
-struct ActionPerformerImpl<FuzzFunction, void(const std::string&)> {
-    static typename FuzzFunction::ReturnType Invoke(
-            typename FuzzFunction::ClassType* module, const google::protobuf::Message& action_proto,
-            const google::protobuf::FieldDescriptor* field_desc) {
-        std::string scratch;
-        const std::string& arg = action_proto.GetReflection()->GetStringReference(
-                action_proto, field_desc, &scratch);
-        return FuzzFunction::ImplBody(module, arg);
-    }
-};
-
-template <typename FuzzFunction>
-struct ActionPerformer : ActionPerformerImpl<FuzzFunction, typename FuzzFunction::Signature> {};
-
-}  // namespace android::fuzz
-
-// Fuzz existing C++ class, ClassType, with a collection of functions under the name Action.
-//
-// Prerequisite: ActionProto must be defined in Protobuf to describe possible actions:
-// message FooActionProto {
-//     message NoArgs {}
-//     oneof value {
-//         bool do_foo = 1;
-//         NoArgs do_bar = 1;
-//     }
-// }
-// Use it to fuzz a C++ class Foo by doing the following:
-//   FUZZ_CLASS(Foo, FooAction)
-// After linking functions of Foo to FooAction, execute all actions by:
-//   FooAction::ExecuteAll(foo_object, action_protos)
-#define FUZZ_CLASS(Class, Action)                                                                \
-    class Action {                                                                               \
-      public:                                                                                    \
-        using Proto = Action##Proto;                                                             \
-        using ClassType = Class;                                                                 \
-        using FunctionMap = android::fuzz::FunctionMap<Class>;                                   \
-        static FunctionMap* GetFunctionMap() {                                                   \
-            static Action::FunctionMap map;                                                      \
-            return &map;                                                                         \
-        }                                                                                        \
-        static void ExecuteAll(Class* module,                                                    \
-                               const google::protobuf::RepeatedPtrField<Proto>& action_protos) { \
-            [[maybe_unused]] static int consistent = android::fuzz::CheckConsistency<Action>();  \
-            android::fuzz::ExecuteAllActionProtos<Action>(module, action_protos);                \
-        }                                                                                        \
-    }
-
-#define FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) Action##_##FunctionName
-#define FUZZ_FUNCTION_TAG_NAME(FunctionName) k##FunctionName
-
-// Implement an action defined in protobuf. Example:
-// message FooActionProto {
-//     oneof value {
-//         bool do_foo = 1;
-//     }
-// }
-// class Foo { public: void DoAwesomeFoo(bool arg); };
-// FUZZ_OBJECT(FooAction, Foo);
-// FUZZ_FUNCTION(FooAction, DoFoo, void, IFoo* module, bool arg) {
-//   module->DoAwesomeFoo(arg);
-// }
-// The name DoFoo is the camel case name of the action in protobuf definition of FooActionProto.
-#define FUZZ_FUNCTION(Action, FunctionName, Return, ModuleArg, ...)             \
-    class FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName) {                      \
-      public:                                                                   \
-        using ActionType = Action;                                              \
-        using ClassType = Action::ClassType;                                    \
-        using ReturnType = Return;                                              \
-        using Signature = void(__VA_ARGS__);                                    \
-        static constexpr const char name[] = #FunctionName;                     \
-        static constexpr const auto tag =                                       \
-                Action::Proto::ValueCase::FUZZ_FUNCTION_TAG_NAME(FunctionName); \
-        static ReturnType ImplBody(ModuleArg, ##__VA_ARGS__);                   \
-                                                                                \
-      private:                                                                  \
-        static bool registered_;                                                \
-    };                                                                          \
-    auto FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::registered_ = ([] {    \
-        auto tag = FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::tag;         \
-        auto func = &::android::fuzz::ActionPerformer<FUZZ_FUNCTION_CLASS_NAME( \
-                Action, FunctionName)>::Invoke;                                 \
-        Action::GetFunctionMap()->CheckEmplace(tag, func);                      \
-        return true;                                                            \
-    })();                                                                       \
-    Return FUZZ_FUNCTION_CLASS_NAME(Action, FunctionName)::ImplBody(ModuleArg, ##__VA_ARGS__)
-
-// Implement a simple action by linking it to the function with the same name. Example:
-// message FooActionProto {
-//     message NoArgs {}
-//     oneof value {
-//         NoArgs do_bar = 1;
-//     }
-// }
-// class Foo { public void DoBar(); };
-// FUZZ_OBJECT(FooAction, Foo);
-// FUZZ_FUNCTION(FooAction, DoBar);
-// The name DoBar is the camel case name of the action in protobuf definition of FooActionProto, and
-// also the name of the function of Foo.
-#define FUZZ_SIMPLE_FUNCTION(Action, FunctionName)                            \
-    FUZZ_FUNCTION(Action, FunctionName,                                       \
-                  decltype(std::declval<Action::ClassType>().FunctionName()), \
-                  Action::ClassType* module) {                                \
-        return module->FunctionName();                                        \
-    }
diff --git a/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp b/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp
deleted file mode 100644
index 6a5754d..0000000
--- a/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp
+++ /dev/null
@@ -1,692 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <iostream>
-#include <limits>
-#include <string>
-#include <unordered_set>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
-#include <bsdiff/bspatch.h>
-#include <bzlib.h>
-#include <gflags/gflags.h>
-#include <libsnapshot/cow_writer.h>
-#include <puffin/puffpatch.h>
-#include <sparse/sparse.h>
-#include <update_engine/update_metadata.pb.h>
-#include <xz.h>
-#include <ziparchive/zip_archive.h>
-
-namespace android {
-namespace snapshot {
-
-using android::base::borrowed_fd;
-using android::base::unique_fd;
-using chromeos_update_engine::DeltaArchiveManifest;
-using chromeos_update_engine::Extent;
-using chromeos_update_engine::InstallOperation;
-using chromeos_update_engine::PartitionUpdate;
-
-static constexpr uint64_t kBlockSize = 4096;
-
-DEFINE_string(source_tf, "", "Source target files (dir or zip file) for incremental payloads");
-DEFINE_string(compression, "gz", "Compression type to use (none or gz)");
-DEFINE_uint32(cluster_ops, 0, "Number of Cow Ops per cluster (0 or >1)");
-
-void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
-              unsigned int, const char* message) {
-    if (severity == android::base::ERROR) {
-        fprintf(stderr, "%s\n", message);
-    } else {
-        fprintf(stdout, "%s\n", message);
-    }
-}
-
-uint64_t ToLittleEndian(uint64_t value) {
-    union {
-        uint64_t u64;
-        char bytes[8];
-    } packed;
-    packed.u64 = value;
-    std::swap(packed.bytes[0], packed.bytes[7]);
-    std::swap(packed.bytes[1], packed.bytes[6]);
-    std::swap(packed.bytes[2], packed.bytes[5]);
-    std::swap(packed.bytes[3], packed.bytes[4]);
-    return packed.u64;
-}
-
-class PayloadConverter final {
-  public:
-    PayloadConverter(const std::string& in_file, const std::string& out_dir)
-        : in_file_(in_file), out_dir_(out_dir), source_tf_zip_(nullptr, &CloseArchive) {}
-
-    bool Run();
-
-  private:
-    bool OpenPayload();
-    bool OpenSourceTargetFiles();
-    bool ProcessPartition(const PartitionUpdate& update);
-    bool ProcessOperation(const InstallOperation& op);
-    bool ProcessZero(const InstallOperation& op);
-    bool ProcessCopy(const InstallOperation& op);
-    bool ProcessReplace(const InstallOperation& op);
-    bool ProcessDiff(const InstallOperation& op);
-    borrowed_fd OpenSourceImage();
-
-    std::string in_file_;
-    std::string out_dir_;
-    unique_fd in_fd_;
-    uint64_t payload_offset_ = 0;
-    DeltaArchiveManifest manifest_;
-    std::unordered_set<std::string> dap_;
-    unique_fd source_tf_fd_;
-    std::unique_ptr<ZipArchive, decltype(&CloseArchive)> source_tf_zip_;
-
-    // Updated during ProcessPartition().
-    std::string partition_name_;
-    std::unique_ptr<CowWriter> writer_;
-    unique_fd source_image_;
-};
-
-bool PayloadConverter::Run() {
-    if (!OpenPayload()) {
-        return false;
-    }
-
-    if (manifest_.has_dynamic_partition_metadata()) {
-        const auto& dpm = manifest_.dynamic_partition_metadata();
-        for (const auto& group : dpm.groups()) {
-            for (const auto& partition : group.partition_names()) {
-                dap_.emplace(partition);
-            }
-        }
-    }
-
-    if (dap_.empty()) {
-        LOG(ERROR) << "No dynamic partitions found.";
-        return false;
-    }
-
-    if (!OpenSourceTargetFiles()) {
-        return false;
-    }
-
-    for (const auto& update : manifest_.partitions()) {
-        if (!ProcessPartition(update)) {
-            return false;
-        }
-        writer_ = nullptr;
-        source_image_.reset();
-    }
-    return true;
-}
-
-bool PayloadConverter::OpenSourceTargetFiles() {
-    if (FLAGS_source_tf.empty()) {
-        return true;
-    }
-
-    source_tf_fd_.reset(open(FLAGS_source_tf.c_str(), O_RDONLY));
-    if (source_tf_fd_ < 0) {
-        LOG(ERROR) << "open failed: " << FLAGS_source_tf;
-        return false;
-    }
-
-    struct stat s;
-    if (fstat(source_tf_fd_.get(), &s) < 0) {
-        LOG(ERROR) << "fstat failed: " << FLAGS_source_tf;
-        return false;
-    }
-    if (S_ISDIR(s.st_mode)) {
-        return true;
-    }
-
-    // Otherwise, assume it's a zip file.
-    ZipArchiveHandle handle;
-    if (OpenArchiveFd(source_tf_fd_.get(), FLAGS_source_tf.c_str(), &handle, false)) {
-        LOG(ERROR) << "Could not open " << FLAGS_source_tf << " as a zip archive.";
-        return false;
-    }
-    source_tf_zip_.reset(handle);
-    return true;
-}
-
-bool PayloadConverter::ProcessPartition(const PartitionUpdate& update) {
-    auto partition_name = update.partition_name();
-    if (dap_.find(partition_name) == dap_.end()) {
-        // Skip non-DAP partitions.
-        return true;
-    }
-
-    auto path = out_dir_ + "/" + partition_name + ".cow";
-    unique_fd fd(open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0644));
-    if (fd < 0) {
-        PLOG(ERROR) << "open failed: " << path;
-        return false;
-    }
-
-    CowOptions options;
-    options.block_size = kBlockSize;
-    options.compression = FLAGS_compression;
-    options.cluster_ops = FLAGS_cluster_ops;
-
-    writer_ = std::make_unique<CowWriter>(options);
-    if (!writer_->Initialize(std::move(fd))) {
-        LOG(ERROR) << "Unable to initialize COW writer";
-        return false;
-    }
-
-    partition_name_ = partition_name;
-
-    for (const auto& op : update.operations()) {
-        if (!ProcessOperation(op)) {
-            return false;
-        }
-    }
-
-    if (!writer_->Finalize()) {
-        LOG(ERROR) << "Unable to finalize COW for " << partition_name;
-        return false;
-    }
-    return true;
-}
-
-bool PayloadConverter::ProcessOperation(const InstallOperation& op) {
-    switch (op.type()) {
-        case InstallOperation::SOURCE_COPY:
-            return ProcessCopy(op);
-        case InstallOperation::BROTLI_BSDIFF:
-        case InstallOperation::PUFFDIFF:
-            return ProcessDiff(op);
-        case InstallOperation::REPLACE:
-        case InstallOperation::REPLACE_XZ:
-        case InstallOperation::REPLACE_BZ:
-            return ProcessReplace(op);
-        case InstallOperation::ZERO:
-            return ProcessZero(op);
-        default:
-            LOG(ERROR) << "Unsupported op: " << (int)op.type();
-            return false;
-    }
-    return true;
-}
-
-bool PayloadConverter::ProcessZero(const InstallOperation& op) {
-    for (const auto& extent : op.dst_extents()) {
-        if (!writer_->AddZeroBlocks(extent.start_block(), extent.num_blocks())) {
-            LOG(ERROR) << "Could not add zero operation";
-            return false;
-        }
-    }
-    return true;
-}
-
-template <typename T>
-static uint64_t SizeOfAllExtents(const T& extents) {
-    uint64_t total = 0;
-    for (const auto& extent : extents) {
-        total += extent.num_blocks() * kBlockSize;
-    }
-    return total;
-}
-
-class PuffInputStream final : public puffin::StreamInterface {
-  public:
-    PuffInputStream(uint8_t* buffer, size_t length) : buffer_(buffer), length_(length), pos_(0) {}
-
-    bool GetSize(uint64_t* size) const override {
-        *size = length_;
-        return true;
-    }
-    bool GetOffset(uint64_t* offset) const override {
-        *offset = pos_;
-        return true;
-    }
-    bool Seek(uint64_t offset) override {
-        if (offset > length_) return false;
-        pos_ = offset;
-        return true;
-    }
-    bool Read(void* buffer, size_t length) override {
-        if (length_ - pos_ < length) return false;
-        memcpy(buffer, buffer_ + pos_, length);
-        pos_ += length;
-        return true;
-    }
-    bool Write(const void*, size_t) override { return false; }
-    bool Close() override { return true; }
-
-  private:
-    uint8_t* buffer_;
-    size_t length_;
-    size_t pos_;
-};
-
-class PuffOutputStream final : public puffin::StreamInterface {
-  public:
-    PuffOutputStream(std::vector<uint8_t>& stream) : stream_(stream), pos_(0) {}
-
-    bool GetSize(uint64_t* size) const override {
-        *size = stream_.size();
-        return true;
-    }
-    bool GetOffset(uint64_t* offset) const override {
-        *offset = pos_;
-        return true;
-    }
-    bool Seek(uint64_t offset) override {
-        if (offset > stream_.size()) {
-            return false;
-        }
-        pos_ = offset;
-        return true;
-    }
-    bool Read(void* buffer, size_t length) override {
-        if (stream_.size() - pos_ < length) {
-            return false;
-        }
-        memcpy(buffer, &stream_[0] + pos_, length);
-        pos_ += length;
-        return true;
-    }
-    bool Write(const void* buffer, size_t length) override {
-        auto remaining = stream_.size() - pos_;
-        if (remaining < length) {
-            stream_.resize(stream_.size() + (length - remaining));
-        }
-        memcpy(&stream_[0] + pos_, buffer, length);
-        pos_ += length;
-        return true;
-    }
-    bool Close() override { return true; }
-
-  private:
-    std::vector<uint8_t>& stream_;
-    size_t pos_;
-};
-
-bool PayloadConverter::ProcessDiff(const InstallOperation& op) {
-    auto source_image = OpenSourceImage();
-    if (source_image < 0) {
-        return false;
-    }
-
-    uint64_t src_length = SizeOfAllExtents(op.src_extents());
-    auto src = std::make_unique<uint8_t[]>(src_length);
-    size_t src_pos = 0;
-
-    // Read source bytes.
-    for (const auto& extent : op.src_extents()) {
-        uint64_t offset = extent.start_block() * kBlockSize;
-        if (lseek(source_image.get(), offset, SEEK_SET) < 0) {
-            PLOG(ERROR) << "lseek source image failed";
-            return false;
-        }
-
-        uint64_t size = extent.num_blocks() * kBlockSize;
-        CHECK(src_length - src_pos >= size);
-        if (!android::base::ReadFully(source_image, src.get() + src_pos, size)) {
-            PLOG(ERROR) << "read source image failed";
-            return false;
-        }
-        src_pos += size;
-    }
-    CHECK(src_pos == src_length);
-
-    // Read patch bytes.
-    auto patch = std::make_unique<uint8_t[]>(op.data_length());
-    if (lseek(in_fd_.get(), payload_offset_ + op.data_offset(), SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek payload failed";
-        return false;
-    }
-    if (!android::base::ReadFully(in_fd_, patch.get(), op.data_length())) {
-        PLOG(ERROR) << "read payload failed";
-        return false;
-    }
-
-    std::vector<uint8_t> dest(SizeOfAllExtents(op.dst_extents()));
-
-    // Apply the diff.
-    if (op.type() == InstallOperation::BROTLI_BSDIFF) {
-        size_t dest_pos = 0;
-        auto sink = [&](const uint8_t* data, size_t length) -> size_t {
-            CHECK(dest.size() - dest_pos >= length);
-            memcpy(&dest[dest_pos], data, length);
-            dest_pos += length;
-            return length;
-        };
-        if (int rv = bsdiff::bspatch(src.get(), src_pos, patch.get(), op.data_length(), sink)) {
-            LOG(ERROR) << "bspatch failed, error code " << rv;
-            return false;
-        }
-    } else if (op.type() == InstallOperation::PUFFDIFF) {
-        auto src_stream = std::make_unique<PuffInputStream>(src.get(), src_length);
-        auto dest_stream = std::make_unique<PuffOutputStream>(dest);
-        bool ok = PuffPatch(std::move(src_stream), std::move(dest_stream), patch.get(),
-                            op.data_length());
-        if (!ok) {
-            LOG(ERROR) << "puffdiff operation failed to apply";
-            return false;
-        }
-    } else {
-        LOG(ERROR) << "unsupported diff operation: " << op.type();
-        return false;
-    }
-
-    // Write the final blocks to the COW.
-    size_t dest_pos = 0;
-    for (const auto& extent : op.dst_extents()) {
-        uint64_t size = extent.num_blocks() * kBlockSize;
-        CHECK(dest.size() - dest_pos >= size);
-
-        if (!writer_->AddRawBlocks(extent.start_block(), &dest[dest_pos], size)) {
-            return false;
-        }
-        dest_pos += size;
-    }
-    return true;
-}
-
-borrowed_fd PayloadConverter::OpenSourceImage() {
-    if (source_image_ >= 0) {
-        return source_image_;
-    }
-
-    unique_fd unzip_fd;
-
-    auto local_path = "IMAGES/" + partition_name_ + ".img";
-    if (source_tf_zip_) {
-        {
-            TemporaryFile tmp;
-            if (tmp.fd < 0) {
-                PLOG(ERROR) << "mkstemp failed";
-                return -1;
-            }
-            unzip_fd.reset(tmp.release());
-        }
-
-        ZipEntry64 entry;
-        if (FindEntry(source_tf_zip_.get(), local_path, &entry)) {
-            LOG(ERROR) << "not found in archive: " << local_path;
-            return -1;
-        }
-        if (ExtractEntryToFile(source_tf_zip_.get(), &entry, unzip_fd.get())) {
-            LOG(ERROR) << "could not extract " << local_path;
-            return -1;
-        }
-        if (lseek(unzip_fd.get(), 0, SEEK_SET) < 0) {
-            PLOG(ERROR) << "lseek failed";
-            return -1;
-        }
-    } else if (source_tf_fd_ >= 0) {
-        unzip_fd.reset(openat(source_tf_fd_.get(), local_path.c_str(), O_RDONLY));
-        if (unzip_fd < 0) {
-            PLOG(ERROR) << "open failed: " << FLAGS_source_tf << "/" << local_path;
-            return -1;
-        }
-    } else {
-        LOG(ERROR) << "No source target files package was specified; need -source_tf";
-        return -1;
-    }
-
-    std::unique_ptr<struct sparse_file, decltype(&sparse_file_destroy)> s(
-            sparse_file_import(unzip_fd.get(), false, false), &sparse_file_destroy);
-    if (s) {
-        TemporaryFile tmp;
-        if (tmp.fd < 0) {
-            PLOG(ERROR) << "mkstemp failed";
-            return -1;
-        }
-        if (sparse_file_write(s.get(), tmp.fd, false, false, false) < 0) {
-            LOG(ERROR) << "sparse_file_write failed";
-            return -1;
-        }
-        source_image_.reset(tmp.release());
-    } else {
-        source_image_ = std::move(unzip_fd);
-    }
-    return source_image_;
-}
-
-template <typename ContainerType>
-class ExtentIter final {
-  public:
-    ExtentIter(const ContainerType& container)
-        : iter_(container.cbegin()), end_(container.cend()), dst_index_(0) {}
-
-    bool GetNext(uint64_t* block) {
-        while (iter_ != end_) {
-            if (dst_index_ < iter_->num_blocks()) {
-                break;
-            }
-            iter_++;
-            dst_index_ = 0;
-        }
-        if (iter_ == end_) {
-            return false;
-        }
-        *block = iter_->start_block() + dst_index_;
-        dst_index_++;
-        return true;
-    }
-
-  private:
-    typename ContainerType::const_iterator iter_;
-    typename ContainerType::const_iterator end_;
-    uint64_t dst_index_;
-};
-
-bool PayloadConverter::ProcessCopy(const InstallOperation& op) {
-    ExtentIter dst_blocks(op.dst_extents());
-
-    for (const auto& extent : op.src_extents()) {
-        for (uint64_t i = 0; i < extent.num_blocks(); i++) {
-            uint64_t src_block = extent.start_block() + i;
-            uint64_t dst_block;
-            if (!dst_blocks.GetNext(&dst_block)) {
-                LOG(ERROR) << "SOURCE_COPY contained mismatching extents";
-                return false;
-            }
-            if (src_block == dst_block) continue;
-            if (!writer_->AddCopy(dst_block, src_block)) {
-                LOG(ERROR) << "Could not add copy operation";
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-bool PayloadConverter::ProcessReplace(const InstallOperation& op) {
-    auto buffer_size = op.data_length();
-    auto buffer = std::make_unique<char[]>(buffer_size);
-    uint64_t offs = payload_offset_ + op.data_offset();
-    if (lseek(in_fd_.get(), offs, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek " << offs << " failed";
-        return false;
-    }
-    if (!android::base::ReadFully(in_fd_, buffer.get(), buffer_size)) {
-        PLOG(ERROR) << "read " << buffer_size << " bytes from offset " << offs << "failed";
-        return false;
-    }
-
-    uint64_t dst_size = 0;
-    for (const auto& extent : op.dst_extents()) {
-        dst_size += extent.num_blocks() * kBlockSize;
-    }
-
-    if (op.type() == InstallOperation::REPLACE_BZ) {
-        auto tmp = std::make_unique<char[]>(dst_size);
-
-        uint32_t actual_size;
-        if (dst_size > std::numeric_limits<typeof(actual_size)>::max()) {
-            LOG(ERROR) << "too many bytes to decompress: " << dst_size;
-            return false;
-        }
-        actual_size = static_cast<uint32_t>(dst_size);
-
-        auto rv = BZ2_bzBuffToBuffDecompress(tmp.get(), &actual_size, buffer.get(), buffer_size, 0,
-                                             0);
-        if (rv) {
-            LOG(ERROR) << "bz2 decompress failed: " << rv;
-            return false;
-        }
-        if (actual_size != dst_size) {
-            LOG(ERROR) << "bz2 returned " << actual_size << " bytes, expected " << dst_size;
-            return false;
-        }
-        buffer = std::move(tmp);
-        buffer_size = dst_size;
-    } else if (op.type() == InstallOperation::REPLACE_XZ) {
-        constexpr uint32_t kXzMaxDictSize = 64 * 1024 * 1024;
-
-        if (dst_size > std::numeric_limits<size_t>::max()) {
-            LOG(ERROR) << "too many bytes to decompress: " << dst_size;
-            return false;
-        }
-
-        std::unique_ptr<struct xz_dec, decltype(&xz_dec_end)> s(
-                xz_dec_init(XZ_DYNALLOC, kXzMaxDictSize), xz_dec_end);
-        if (!s) {
-            LOG(ERROR) << "xz_dec_init failed";
-            return false;
-        }
-
-        auto tmp = std::make_unique<char[]>(dst_size);
-
-        struct xz_buf args;
-        args.in = reinterpret_cast<const uint8_t*>(buffer.get());
-        args.in_pos = 0;
-        args.in_size = buffer_size;
-        args.out = reinterpret_cast<uint8_t*>(tmp.get());
-        args.out_pos = 0;
-        args.out_size = dst_size;
-
-        auto rv = xz_dec_run(s.get(), &args);
-        if (rv != XZ_STREAM_END) {
-            LOG(ERROR) << "xz decompress failed: " << (int)rv;
-            return false;
-        }
-        buffer = std::move(tmp);
-        buffer_size = dst_size;
-    }
-
-    uint64_t buffer_pos = 0;
-    for (const auto& extent : op.dst_extents()) {
-        uint64_t extent_size = extent.num_blocks() * kBlockSize;
-        if (buffer_size - buffer_pos < extent_size) {
-            LOG(ERROR) << "replace op ran out of input buffer";
-            return false;
-        }
-        if (!writer_->AddRawBlocks(extent.start_block(), buffer.get() + buffer_pos, extent_size)) {
-            LOG(ERROR) << "failed to add raw blocks from replace op";
-            return false;
-        }
-        buffer_pos += extent_size;
-    }
-    return true;
-}
-
-bool PayloadConverter::OpenPayload() {
-    in_fd_.reset(open(in_file_.c_str(), O_RDONLY));
-    if (in_fd_ < 0) {
-        PLOG(ERROR) << "open " << in_file_;
-        return false;
-    }
-
-    char magic[4];
-    if (!android::base::ReadFully(in_fd_, magic, sizeof(magic))) {
-        PLOG(ERROR) << "read magic";
-        return false;
-    }
-    if (std::string(magic, sizeof(magic)) != "CrAU") {
-        LOG(ERROR) << "Invalid magic in " << in_file_;
-        return false;
-    }
-
-    uint64_t version;
-    uint64_t manifest_size;
-    uint32_t manifest_signature_size = 0;
-    if (!android::base::ReadFully(in_fd_, &version, sizeof(version))) {
-        PLOG(ERROR) << "read version";
-        return false;
-    }
-    version = ToLittleEndian(version);
-    if (version < 2) {
-        LOG(ERROR) << "Only payload version 2 or higher is supported.";
-        return false;
-    }
-
-    if (!android::base::ReadFully(in_fd_, &manifest_size, sizeof(manifest_size))) {
-        PLOG(ERROR) << "read manifest_size";
-        return false;
-    }
-    manifest_size = ToLittleEndian(manifest_size);
-    if (!android::base::ReadFully(in_fd_, &manifest_signature_size,
-                                  sizeof(manifest_signature_size))) {
-        PLOG(ERROR) << "read manifest_signature_size";
-        return false;
-    }
-    manifest_signature_size = ntohl(manifest_signature_size);
-
-    auto manifest = std::make_unique<uint8_t[]>(manifest_size);
-    if (!android::base::ReadFully(in_fd_, manifest.get(), manifest_size)) {
-        PLOG(ERROR) << "read manifest";
-        return false;
-    }
-
-    // Skip past manifest signature.
-    auto offs = lseek(in_fd_, manifest_signature_size, SEEK_CUR);
-    if (offs < 0) {
-        PLOG(ERROR) << "lseek failed";
-        return false;
-    }
-    payload_offset_ = offs;
-
-    if (!manifest_.ParseFromArray(manifest.get(), manifest_size)) {
-        LOG(ERROR) << "could not parse manifest";
-        return false;
-    }
-    return true;
-}
-
-}  // namespace snapshot
-}  // namespace android
-
-int main(int argc, char** argv) {
-    android::base::InitLogging(argv, android::snapshot::MyLogger);
-    gflags::SetUsageMessage("Convert OTA payload to a Virtual A/B COW");
-    int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, false);
-
-    xz_crc32_init();
-
-    if (argc - arg_start != 2) {
-        std::cerr << "Usage: [options] <payload.bin> <out-dir>\n";
-        return 1;
-    }
-
-    android::snapshot::PayloadConverter pc(argv[arg_start], argv[arg_start + 1]);
-    return pc.Run() ? 0 : 1;
-}
diff --git a/fs_mgr/libsnapshot/power_test.cpp b/fs_mgr/libsnapshot/power_test.cpp
deleted file mode 100644
index 4d2548a..0000000
--- a/fs_mgr/libsnapshot/power_test.cpp
+++ /dev/null
@@ -1,559 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-#include <errno.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <chrono>
-#include <iostream>
-#include <random>
-#include <string>
-#include <thread>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/parsedouble.h>
-#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <ext4_utils/ext4_utils.h>
-#include <fstab/fstab.h>
-#include <libdm/dm.h>
-#include <libfiemap/image_manager.h>
-
-using namespace std::chrono_literals;
-using namespace std::string_literals;
-using android::base::borrowed_fd;
-using android::base::unique_fd;
-using android::dm::DeviceMapper;
-using android::dm::DmDeviceState;
-using android::dm::DmTable;
-using android::dm::DmTargetSnapshot;
-using android::dm::SnapshotStorageMode;
-using android::fiemap::ImageManager;
-using android::fs_mgr::Fstab;
-
-namespace android {
-namespace snapshot {
-
-static void usage() {
-    std::cerr << "Usage:\n";
-    std::cerr << "  create <orig-payload> <new-payload>\n";
-    std::cerr << "\n";
-    std::cerr << "  Create a snapshot device containing the contents of\n";
-    std::cerr << "  orig-payload, and then write the contents of new-payload.\n";
-    std::cerr << "  The original files are not modified.\n";
-    std::cerr << "\n";
-    std::cerr << "  merge <fail-rate>\n";
-    std::cerr << "\n";
-    std::cerr << "  Merge the snapshot previously started by create, and wait\n";
-    std::cerr << "  for it to complete. Once done, it is compared to the\n";
-    std::cerr << "  new-payload for consistency. The original files are not \n";
-    std::cerr << "  modified. If a fail-rate is passed (as a fraction between 0\n";
-    std::cerr << "  and 100), every 10ms the device has that percent change of\n";
-    std::cerr << "  injecting a kernel crash.\n";
-    std::cerr << "\n";
-    std::cerr << "  check <new-payload>\n";
-    std::cerr << "  Verify that all artifacts are correct after a merge\n";
-    std::cerr << "  completes.\n";
-    std::cerr << "\n";
-    std::cerr << "  cleanup\n";
-    std::cerr << "  Remove all ImageManager artifacts from create/merge.\n";
-}
-
-class PowerTest final {
-  public:
-    PowerTest();
-    bool Run(int argc, char** argv);
-
-  private:
-    bool OpenImageManager();
-    bool Create(int argc, char** argv);
-    bool Merge(int argc, char** argv);
-    bool Check(int argc, char** argv);
-    bool Cleanup();
-    bool CleanupImage(const std::string& name);
-    bool SetupImages(const std::string& first_file, borrowed_fd second_fd);
-    bool MapImages();
-    bool MapSnapshot(SnapshotStorageMode mode);
-    bool GetMergeStatus(DmTargetSnapshot::Status* status);
-
-    static constexpr char kSnapshotName[] = "snapshot-power-test";
-    static constexpr char kSnapshotImageName[] = "snapshot-power-test-image";
-    static constexpr char kSnapshotCowName[] = "snapshot-power-test-cow";
-
-    DeviceMapper& dm_;
-    std::unique_ptr<ImageManager> images_;
-    std::string image_path_;
-    std::string cow_path_;
-    std::string snapshot_path_;
-};
-
-PowerTest::PowerTest() : dm_(DeviceMapper::Instance()) {}
-
-bool PowerTest::Run([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
-    if (!OpenImageManager()) {
-        return false;
-    }
-
-    if (argc < 2) {
-        usage();
-        return false;
-    }
-    if (argv[1] == "create"s) {
-        return Create(argc, argv);
-    } else if (argv[1] == "merge"s) {
-        return Merge(argc, argv);
-    } else if (argv[1] == "check"s) {
-        return Check(argc, argv);
-    } else if (argv[1] == "cleanup"s) {
-        return Cleanup();
-    } else {
-        usage();
-        return false;
-    }
-}
-
-bool PowerTest::OpenImageManager() {
-    std::vector<std::string> dirs = {
-            "/data/gsi/test",
-            "/metadata/gsi/test",
-    };
-    for (const auto& dir : dirs) {
-        if (mkdir(dir.c_str(), 0700) && errno != EEXIST) {
-            std::cerr << "mkdir " << dir << ": " << strerror(errno) << "\n";
-            return false;
-        }
-    }
-
-    images_ = ImageManager::Open("/metadata/gsi/test", "/data/gsi/test");
-    if (!images_) {
-        std::cerr << "Could not open ImageManager\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::Create(int argc, char** argv) {
-    if (argc < 4) {
-        usage();
-        return false;
-    }
-
-    std::string first = argv[2];
-    std::string second = argv[3];
-
-    unique_fd second_fd(open(second.c_str(), O_RDONLY));
-    if (second_fd < 0) {
-        std::cerr << "open " << second << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    if (!Cleanup()) {
-        return false;
-    }
-    if (!SetupImages(first, second_fd)) {
-        return false;
-    }
-    if (!MapSnapshot(SnapshotStorageMode::Persistent)) {
-        return false;
-    }
-
-    struct stat s;
-    if (fstat(second_fd, &s)) {
-        std::cerr << "fstat " << second << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    unique_fd snap_fd(open(snapshot_path_.c_str(), O_WRONLY));
-    if (snap_fd < 0) {
-        std::cerr << "open " << snapshot_path_ << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    uint8_t chunk[4096];
-    uint64_t written = 0;
-    while (written < s.st_size) {
-        uint64_t remaining = s.st_size - written;
-        size_t bytes = (size_t)std::min((uint64_t)sizeof(chunk), remaining);
-        if (!android::base::ReadFully(second_fd, chunk, bytes)) {
-            std::cerr << "read " << second << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        if (!android::base::WriteFully(snap_fd, chunk, bytes)) {
-            std::cerr << "write " << snapshot_path_ << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        written += bytes;
-    }
-    if (fsync(snap_fd)) {
-        std::cerr << "fsync: " << strerror(errno) << "\n";
-        return false;
-    }
-
-    sync();
-
-    snap_fd = {};
-    if (!dm_.DeleteDeviceIfExists(kSnapshotName)) {
-        std::cerr << "could not delete dm device " << kSnapshotName << "\n";
-        return false;
-    }
-    if (!images_->UnmapImageIfExists(kSnapshotImageName)) {
-        std::cerr << "failed to unmap " << kSnapshotImageName << "\n";
-        return false;
-    }
-    if (!images_->UnmapImageIfExists(kSnapshotCowName)) {
-        std::cerr << "failed to unmap " << kSnapshotImageName << "\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::Cleanup() {
-    if (!dm_.DeleteDeviceIfExists(kSnapshotName)) {
-        std::cerr << "could not delete dm device " << kSnapshotName << "\n";
-        return false;
-    }
-    if (!CleanupImage(kSnapshotImageName) || !CleanupImage(kSnapshotCowName)) {
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::CleanupImage(const std::string& name) {
-    if (!images_->UnmapImageIfExists(name)) {
-        std::cerr << "failed to unmap " << name << "\n";
-        return false;
-    }
-    if (images_->BackingImageExists(name) && !images_->DeleteBackingImage(name)) {
-        std::cerr << "failed to delete " << name << "\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::SetupImages(const std::string& first, borrowed_fd second_fd) {
-    unique_fd first_fd(open(first.c_str(), O_RDONLY));
-    if (first_fd < 0) {
-        std::cerr << "open " << first << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    struct stat s1, s2;
-    if (fstat(first_fd.get(), &s1)) {
-        std::cerr << "first stat: " << strerror(errno) << "\n";
-        return false;
-    }
-    if (fstat(second_fd.get(), &s2)) {
-        std::cerr << "second stat: " << strerror(errno) << "\n";
-        return false;
-    }
-
-    // Pick the bigger size of both images, rounding up to the nearest block.
-    uint64_t s1_size = (s1.st_size + 4095) & ~uint64_t(4095);
-    uint64_t s2_size = (s2.st_size + 4095) & ~uint64_t(4095);
-    uint64_t image_size = std::max(s1_size, s2_size) + (1024 * 1024 * 128);
-    if (!images_->CreateBackingImage(kSnapshotImageName, image_size, 0, nullptr)) {
-        std::cerr << "failed to create " << kSnapshotImageName << "\n";
-        return false;
-    }
-    // Use the same size for the cow.
-    if (!images_->CreateBackingImage(kSnapshotCowName, image_size, 0, nullptr)) {
-        std::cerr << "failed to create " << kSnapshotCowName << "\n";
-        return false;
-    }
-    if (!MapImages()) {
-        return false;
-    }
-
-    unique_fd image_fd(open(image_path_.c_str(), O_WRONLY));
-    if (image_fd < 0) {
-        std::cerr << "open: " << image_path_ << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    uint8_t chunk[4096];
-    uint64_t written = 0;
-    while (written < s1.st_size) {
-        uint64_t remaining = s1.st_size - written;
-        size_t bytes = (size_t)std::min((uint64_t)sizeof(chunk), remaining);
-        if (!android::base::ReadFully(first_fd, chunk, bytes)) {
-            std::cerr << "read: " << strerror(errno) << "\n";
-            return false;
-        }
-        if (!android::base::WriteFully(image_fd, chunk, bytes)) {
-            std::cerr << "write: " << strerror(errno) << "\n";
-            return false;
-        }
-        written += bytes;
-    }
-    if (fsync(image_fd)) {
-        std::cerr << "fsync: " << strerror(errno) << "\n";
-        return false;
-    }
-
-    // Zero the first block of the COW.
-    unique_fd cow_fd(open(cow_path_.c_str(), O_WRONLY));
-    if (cow_fd < 0) {
-        std::cerr << "open: " << cow_path_ << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    memset(chunk, 0, sizeof(chunk));
-    if (!android::base::WriteFully(cow_fd, chunk, sizeof(chunk))) {
-        std::cerr << "read: " << strerror(errno) << "\n";
-        return false;
-    }
-    if (fsync(cow_fd)) {
-        std::cerr << "fsync: " << strerror(errno) << "\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::MapImages() {
-    if (!images_->MapImageDevice(kSnapshotImageName, 10s, &image_path_)) {
-        std::cerr << "failed to map " << kSnapshotImageName << "\n";
-        return false;
-    }
-    if (!images_->MapImageDevice(kSnapshotCowName, 10s, &cow_path_)) {
-        std::cerr << "failed to map " << kSnapshotCowName << "\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::MapSnapshot(SnapshotStorageMode mode) {
-    uint64_t sectors;
-    {
-        unique_fd fd(open(image_path_.c_str(), O_RDONLY));
-        if (fd < 0) {
-            std::cerr << "open: " << image_path_ << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        sectors = get_block_device_size(fd) / 512;
-    }
-
-    DmTable table;
-    table.Emplace<DmTargetSnapshot>(0, sectors, image_path_, cow_path_, mode, 8);
-    if (!dm_.CreateDevice(kSnapshotName, table, &snapshot_path_, 10s)) {
-        std::cerr << "failed to create snapshot device\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::GetMergeStatus(DmTargetSnapshot::Status* status) {
-    std::vector<DeviceMapper::TargetInfo> targets;
-    if (!dm_.GetTableStatus(kSnapshotName, &targets)) {
-        std::cerr << "failed to get merge status\n";
-        return false;
-    }
-    if (targets.size() != 1) {
-        std::cerr << "merge device has wrong number of targets\n";
-        return false;
-    }
-    if (!DmTargetSnapshot::ParseStatusText(targets[0].data, status)) {
-        std::cerr << "could not parse merge target status text\n";
-        return false;
-    }
-    return true;
-}
-
-static std::string GetUserdataBlockDeviceName() {
-    Fstab fstab;
-    if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
-        return {};
-    }
-
-    auto entry = android::fs_mgr::GetEntryForMountPoint(&fstab, "/data");
-    if (!entry) {
-        return {};
-    }
-
-    auto prefix = "/dev/block/"s;
-    if (!android::base::StartsWith(entry->blk_device, prefix)) {
-        return {};
-    }
-    return entry->blk_device.substr(prefix.size());
-}
-
-bool PowerTest::Merge(int argc, char** argv) {
-    // Start an f2fs GC to really stress things. :TODO: figure out data device
-    auto userdata_dev = GetUserdataBlockDeviceName();
-    if (userdata_dev.empty()) {
-        std::cerr << "could not locate userdata block device\n";
-        return false;
-    }
-
-    auto cmd =
-            android::base::StringPrintf("echo 1 > /sys/fs/f2fs/%s/gc_urgent", userdata_dev.c_str());
-    system(cmd.c_str());
-
-    if (dm_.GetState(kSnapshotName) == DmDeviceState::INVALID) {
-        if (!MapImages()) {
-            return false;
-        }
-        if (!MapSnapshot(SnapshotStorageMode::Merge)) {
-            return false;
-        }
-    }
-
-    std::random_device r;
-    std::default_random_engine re(r());
-    std::uniform_real_distribution<double> dist(0.0, 100.0);
-
-    std::optional<double> failure_rate;
-    if (argc >= 3) {
-        double d;
-        if (!android::base::ParseDouble(argv[2], &d)) {
-            std::cerr << "Could not parse failure rate as double: " << argv[2] << "\n";
-            return false;
-        }
-        failure_rate = d;
-    }
-
-    while (true) {
-        DmTargetSnapshot::Status status;
-        if (!GetMergeStatus(&status)) {
-            return false;
-        }
-        if (!status.error.empty()) {
-            std::cerr << "merge reported error: " << status.error << "\n";
-            return false;
-        }
-        if (status.sectors_allocated == status.metadata_sectors) {
-            break;
-        }
-
-        std::cerr << status.sectors_allocated << " / " << status.metadata_sectors << "\n";
-
-        if (failure_rate && *failure_rate >= dist(re)) {
-            system("echo 1 > /proc/sys/kernel/sysrq");
-            system("echo c > /proc/sysrq-trigger");
-        }
-
-        std::this_thread::sleep_for(10ms);
-    }
-
-    std::cout << "Merge completed.\n";
-    return true;
-}
-
-bool PowerTest::Check([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
-    if (argc < 3) {
-        std::cerr << "Expected argument: <new-image-path>\n";
-        return false;
-    }
-    std::string md_path, image_path;
-    std::string canonical_path = argv[2];
-
-    if (!dm_.GetDmDevicePathByName(kSnapshotName, &md_path)) {
-        std::cerr << "could not get dm-path for merge device\n";
-        return false;
-    }
-    if (!images_->GetMappedImageDevice(kSnapshotImageName, &image_path)) {
-        std::cerr << "could not get image path\n";
-        return false;
-    }
-
-    unique_fd md_fd(open(md_path.c_str(), O_RDONLY));
-    if (md_fd < 0) {
-        std::cerr << "open: " << md_path << ": " << strerror(errno) << "\n";
-        return false;
-    }
-    unique_fd image_fd(open(image_path.c_str(), O_RDONLY));
-    if (image_fd < 0) {
-        std::cerr << "open: " << image_path << ": " << strerror(errno) << "\n";
-        return false;
-    }
-    unique_fd canonical_fd(open(canonical_path.c_str(), O_RDONLY));
-    if (canonical_fd < 0) {
-        std::cerr << "open: " << canonical_path << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    struct stat s;
-    if (fstat(canonical_fd, &s)) {
-        std::cerr << "fstat: " << canonical_path << ": " << strerror(errno) << "\n";
-        return false;
-    }
-    uint64_t canonical_size = s.st_size;
-    uint64_t md_size = get_block_device_size(md_fd);
-    uint64_t image_size = get_block_device_size(image_fd);
-    if (image_size != md_size) {
-        std::cerr << "image size does not match merge device size\n";
-        return false;
-    }
-    if (canonical_size > image_size) {
-        std::cerr << "canonical size " << canonical_size << " is greater than image size "
-                  << image_size << "\n";
-        return false;
-    }
-
-    constexpr size_t kBlockSize = 4096;
-    uint8_t canonical_buffer[kBlockSize];
-    uint8_t image_buffer[kBlockSize];
-    uint8_t md_buffer[kBlockSize];
-
-    uint64_t remaining = canonical_size;
-    uint64_t blockno = 0;
-    while (remaining) {
-        size_t bytes = (size_t)std::min((uint64_t)kBlockSize, remaining);
-        if (!android::base::ReadFully(canonical_fd, canonical_buffer, bytes)) {
-            std::cerr << "read: " << canonical_buffer << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        if (!android::base::ReadFully(image_fd, image_buffer, bytes)) {
-            std::cerr << "read: " << image_buffer << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        if (!android::base::ReadFully(md_fd, md_buffer, bytes)) {
-            std::cerr << "read: " << md_buffer << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        if (memcmp(canonical_buffer, image_buffer, bytes)) {
-            std::cerr << "canonical and image differ at block " << blockno << "\n";
-            return false;
-        }
-        if (memcmp(canonical_buffer, md_buffer, bytes)) {
-            std::cerr << "canonical and image differ at block " << blockno << "\n";
-            return false;
-        }
-
-        remaining -= bytes;
-        blockno++;
-    }
-
-    std::cout << "Images all match.\n";
-    return true;
-}
-
-}  // namespace snapshot
-}  // namespace android
-
-int main(int argc, char** argv) {
-    android::snapshot::PowerTest test;
-
-    if (!test.Run(argc, argv)) {
-        std::cerr << "Unexpected error running test." << std::endl;
-        return 1;
-    }
-    fflush(stdout);
-    return 0;
-}
diff --git a/fs_mgr/libsnapshot/run_power_test.sh b/fs_mgr/libsnapshot/run_power_test.sh
deleted file mode 100755
index dc03dc9..0000000
--- a/fs_mgr/libsnapshot/run_power_test.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-
-set -e
-
-if [ -z "$FAIL_RATE" ]; then
-    FAIL_RATE=5.0
-fi
-if [ ! -z "$ANDROID_SERIAL" ]; then
-    DEVICE_ARGS=-s $ANDROID_SERIAL
-else
-    DEVICE_ARGS=
-fi
-
-TEST_BIN=/data/nativetest64/snapshot_power_test/snapshot_power_test
-
-while :
-do
-    adb $DEVICE_ARGS wait-for-device
-    adb $DEVICE_ARGS root
-    adb $DEVICE_ARGS shell rm $TEST_BIN
-    adb $DEVICE_ARGS sync data
-    set +e
-    output=$(adb $DEVICE_ARGS shell $TEST_BIN merge $FAIL_RATE 2>&1)
-    set -e
-    if [[ "$output" == *"Merge completed"* ]]; then
-        echo "Merge completed."
-        break
-    fi
-    if [[ "$output" == *"Unexpected error"* ]]; then
-        echo "Unexpected error."
-        exit 1
-    fi
-done
-
-adb $DEVICE_ARGS shell $TEST_BIN check $1
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 15f025c..64637c2 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -2901,6 +2901,20 @@
     }
 }
 
+std::ostream& operator<<(std::ostream& os, MergePhase phase) {
+    switch (phase) {
+        case MergePhase::NO_MERGE:
+            return os << "none";
+        case MergePhase::FIRST_PHASE:
+            return os << "first";
+        case MergePhase::SECOND_PHASE:
+            return os << "second";
+        default:
+            LOG(ERROR) << "Unknown merge phase: " << static_cast<uint32_t>(phase);
+            return os << "unknown(" << static_cast<uint32_t>(phase) << ")";
+    }
+}
+
 UpdateState SnapshotManager::ReadUpdateState(LockedFile* lock) {
     SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock);
     return status.state();
@@ -3216,6 +3230,8 @@
         vabc_disable_reason = "recovery";
     } else if (!cow_format_support) {
         vabc_disable_reason = "cow format not supported";
+    } else if (!KernelSupportsCompressedSnapshots()) {
+        vabc_disable_reason = "kernel missing userspace block device support";
     }
 
     if (!vabc_disable_reason.empty()) {
@@ -3759,7 +3775,7 @@
 
     auto update_status = ReadSnapshotUpdateStatus(file.get());
 
-    ss << "Update state: " << ReadUpdateState(file.get()) << std::endl;
+    ss << "Update state: " << update_status.state() << 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;
@@ -3774,6 +3790,17 @@
        << std::endl;
     ss << "Source build fingerprint: " << update_status.source_build_fingerprint() << std::endl;
 
+    if (update_status.state() == UpdateState::Merging) {
+        ss << "Merge completion: ";
+        if (!EnsureSnapuserdConnected()) {
+            ss << "N/A";
+        } else {
+            ss << snapuserd_client_->GetMergePercent() << "%";
+        }
+        ss << std::endl;
+        ss << "Merge phase: " << update_status.merge_phase() << std::endl;
+    }
+
     bool ok = true;
     std::vector<std::string> snapshots;
     if (!ListSnapshots(file.get(), &snapshots)) {
@@ -3796,6 +3823,7 @@
         ss << "    allocated sectors: " << status.sectors_allocated() << std::endl;
         ss << "    metadata sectors: " << status.metadata_sectors() << std::endl;
         ss << "    compression: " << status.compression_algorithm() << std::endl;
+        ss << "    merge phase: " << DecideMergePhase(status) << std::endl;
     }
     os << ss.rdbuf();
     return ok;
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz.cpp b/fs_mgr/libsnapshot/snapshot_fuzz.cpp
deleted file mode 100644
index aced3ed..0000000
--- a/fs_mgr/libsnapshot/snapshot_fuzz.cpp
+++ /dev/null
@@ -1,352 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <stddef.h>
-#include <stdint.h>
-#include <sysexits.h>
-
-#include <functional>
-#include <sstream>
-#include <tuple>
-
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <android-base/result.h>
-#include <gtest/gtest.h>
-#include <src/libfuzzer/libfuzzer_macro.h>
-#include <storage_literals/storage_literals.h>
-
-#include "fuzz_utils.h"
-#include "snapshot_fuzz_utils.h"
-
-using android::base::Error;
-using android::base::GetBoolProperty;
-using android::base::LogId;
-using android::base::LogSeverity;
-using android::base::ReadFileToString;
-using android::base::Result;
-using android::base::SetLogger;
-using android::base::StderrLogger;
-using android::base::StdioLogger;
-using android::fs_mgr::CreateLogicalPartitionParams;
-using android::fuzz::CheckedCast;
-using android::snapshot::SnapshotFuzzData;
-using android::snapshot::SnapshotFuzzEnv;
-using chromeos_update_engine::DeltaArchiveManifest;
-using google::protobuf::FieldDescriptor;
-using google::protobuf::Message;
-using google::protobuf::RepeatedPtrField;
-
-// Avoid linking to libgsi since it needs disk I/O.
-namespace android::gsi {
-bool IsGsiRunning() {
-    LOG(FATAL) << "Called IsGsiRunning";
-    __builtin_unreachable();
-}
-std::string GetDsuSlot(const std::string& install_dir) {
-    LOG(FATAL) << "Called GetDsuSlot(" << install_dir << ")";
-    __builtin_unreachable();
-}
-}  // namespace android::gsi
-
-namespace android::snapshot {
-
-const SnapshotFuzzData* current_data = nullptr;
-const SnapshotTestModule* current_module = nullptr;
-
-SnapshotFuzzEnv* GetSnapshotFuzzEnv();
-
-FUZZ_CLASS(ISnapshotManager, SnapshotManagerAction);
-
-using ProcessUpdateStateArgs = SnapshotManagerAction::Proto::ProcessUpdateStateArgs;
-using CreateLogicalAndSnapshotPartitionsArgs =
-        SnapshotManagerAction::Proto::CreateLogicalAndSnapshotPartitionsArgs;
-using RecoveryCreateSnapshotDevicesArgs =
-        SnapshotManagerAction::Proto::RecoveryCreateSnapshotDevicesArgs;
-
-FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, BeginUpdate);
-FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, CancelUpdate);
-FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, InitiateMerge);
-FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, NeedSnapshotsInFirstStageMount);
-FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, RecoveryCreateSnapshotDevices);
-FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, EnsureMetadataMounted);
-FUZZ_SIMPLE_FUNCTION(SnapshotManagerAction, GetSnapshotMergeStatsInstance);
-
-#define SNAPSHOT_FUZZ_FUNCTION(FunctionName, ReturnType, ...)                                  \
-    FUZZ_FUNCTION(SnapshotManagerAction, FunctionName, ReturnType, ISnapshotManager* snapshot, \
-                  ##__VA_ARGS__)
-
-SNAPSHOT_FUZZ_FUNCTION(FinishedSnapshotWrites, bool, bool wipe) {
-    return snapshot->FinishedSnapshotWrites(wipe);
-}
-
-SNAPSHOT_FUZZ_FUNCTION(ProcessUpdateState, bool, const ProcessUpdateStateArgs& args) {
-    std::function<bool()> before_cancel;
-    if (args.has_before_cancel()) {
-        before_cancel = [&]() { return args.fail_before_cancel(); };
-    }
-    return snapshot->ProcessUpdateState({}, before_cancel);
-}
-
-SNAPSHOT_FUZZ_FUNCTION(GetUpdateState, UpdateState, bool has_progress_arg) {
-    double progress;
-    return snapshot->GetUpdateState(has_progress_arg ? &progress : nullptr);
-}
-
-SNAPSHOT_FUZZ_FUNCTION(HandleImminentDataWipe, bool, bool has_callback) {
-    std::function<void()> callback;
-    if (has_callback) {
-        callback = []() {};
-    }
-    return snapshot->HandleImminentDataWipe(callback);
-}
-
-SNAPSHOT_FUZZ_FUNCTION(Dump, bool) {
-    std::stringstream ss;
-    return snapshot->Dump(ss);
-}
-
-SNAPSHOT_FUZZ_FUNCTION(CreateUpdateSnapshots, bool, const DeltaArchiveManifest& manifest) {
-    return snapshot->CreateUpdateSnapshots(manifest);
-}
-
-SNAPSHOT_FUZZ_FUNCTION(UnmapUpdateSnapshot, bool, const std::string& name) {
-    return snapshot->UnmapUpdateSnapshot(name);
-}
-
-SNAPSHOT_FUZZ_FUNCTION(CreateLogicalAndSnapshotPartitions, bool,
-                       const CreateLogicalAndSnapshotPartitionsArgs& args) {
-    const std::string* super;
-    if (args.use_correct_super()) {
-        super = &GetSnapshotFuzzEnv()->super();
-    } else {
-        super = &args.super();
-    }
-    return snapshot->CreateLogicalAndSnapshotPartitions(
-            *super, std::chrono::milliseconds(args.timeout_millis()));
-}
-
-SNAPSHOT_FUZZ_FUNCTION(RecoveryCreateSnapshotDevicesWithMetadata, CreateResult,
-                       const RecoveryCreateSnapshotDevicesArgs& args) {
-    std::unique_ptr<AutoDevice> device;
-    if (args.has_metadata_device_object()) {
-        device = std::make_unique<NoOpAutoDevice>(args.metadata_mounted());
-    }
-    return snapshot->RecoveryCreateSnapshotDevices(device);
-}
-
-SNAPSHOT_FUZZ_FUNCTION(MapUpdateSnapshot, bool,
-                       const CreateLogicalPartitionParamsProto& params_proto) {
-    auto partition_opener = std::make_unique<TestPartitionOpener>(GetSnapshotFuzzEnv()->super());
-    CreateLogicalPartitionParams params;
-    if (params_proto.use_correct_super()) {
-        params.block_device = GetSnapshotFuzzEnv()->super();
-    } else {
-        params.block_device = params_proto.block_device();
-    }
-    if (params_proto.has_metadata_slot()) {
-        params.metadata_slot = params_proto.metadata_slot();
-    }
-    params.partition_name = params_proto.partition_name();
-    params.force_writable = params_proto.force_writable();
-    params.timeout_ms = std::chrono::milliseconds(params_proto.timeout_millis());
-    params.device_name = params_proto.device_name();
-    params.partition_opener = partition_opener.get();
-    std::string path;
-    return snapshot->MapUpdateSnapshot(params, &path);
-}
-
-SNAPSHOT_FUZZ_FUNCTION(SwitchSlot, void) {
-    (void)snapshot;
-    CHECK(current_module != nullptr);
-    CHECK(current_module->device_info != nullptr);
-    current_module->device_info->SwitchSlot();
-}
-
-// During global init, log all messages to stdio. This is only done once.
-int AllowLoggingDuringGlobalInit() {
-    SetLogger(&StdioLogger);
-    return 0;
-}
-
-// Only log fatal messages during tests.
-void FatalOnlyLogger(LogId logid, LogSeverity severity, const char* tag, const char* file,
-                     unsigned int line, const char* message) {
-    if (severity == LogSeverity::FATAL) {
-        StderrLogger(logid, severity, tag, file, line, message);
-
-        // If test fails by a LOG(FATAL) or CHECK(), log the corpus. If it abort()'s, there's
-        // nothing else we can do.
-        StderrLogger(logid, severity, tag, __FILE__, __LINE__,
-                     "Attempting to dump current corpus:");
-        if (current_data == nullptr) {
-            StderrLogger(logid, severity, tag, __FILE__, __LINE__, "Current corpus is nullptr.");
-        } else {
-            std::string content;
-            if (!google::protobuf::TextFormat::PrintToString(*current_data, &content)) {
-                StderrLogger(logid, severity, tag, __FILE__, __LINE__,
-                             "Failed to print corpus to string.");
-            } else {
-                StderrLogger(logid, severity, tag, __FILE__, __LINE__, content.c_str());
-            }
-        }
-    }
-}
-// Stop logging (except fatal messages) after global initialization. This is only done once.
-int StopLoggingAfterGlobalInit() {
-    (void)GetSnapshotFuzzEnv();
-    [[maybe_unused]] static protobuf_mutator::protobuf::LogSilencer log_silencer;
-    SetLogger(&FatalOnlyLogger);
-    return 0;
-}
-
-SnapshotFuzzEnv* GetSnapshotFuzzEnv() {
-    [[maybe_unused]] static auto allow_logging = AllowLoggingDuringGlobalInit();
-    static SnapshotFuzzEnv env;
-    return &env;
-}
-
-SnapshotTestModule SetUpTest(const SnapshotFuzzData& snapshot_fuzz_data) {
-    current_data = &snapshot_fuzz_data;
-
-    auto env = GetSnapshotFuzzEnv();
-    env->CheckSoftReset();
-
-    auto test_module = env->CheckCreateSnapshotManager(snapshot_fuzz_data);
-    current_module = &test_module;
-    CHECK(test_module.snapshot);
-    return test_module;
-}
-
-void TearDownTest() {
-    current_module = nullptr;
-    current_data = nullptr;
-}
-
-}  // namespace android::snapshot
-
-DEFINE_PROTO_FUZZER(const SnapshotFuzzData& snapshot_fuzz_data) {
-    using namespace android::snapshot;
-
-    [[maybe_unused]] static auto stop_logging = StopLoggingAfterGlobalInit();
-    auto test_module = SetUpTest(snapshot_fuzz_data);
-    SnapshotManagerAction::ExecuteAll(test_module.snapshot.get(), snapshot_fuzz_data.actions());
-    TearDownTest();
-}
-
-namespace android::snapshot {
-
-// Work-around to cast a 'void' value to Result<void>.
-template <typename T>
-struct GoodResult {
-    template <typename F>
-    static Result<T> Cast(F&& f) {
-        return f();
-    }
-};
-
-template <>
-struct GoodResult<void> {
-    template <typename F>
-    static Result<void> Cast(F&& f) {
-        f();
-        return {};
-    }
-};
-
-class LibsnapshotFuzzerTest : public ::testing::Test {
-  protected:
-    static void SetUpTestCase() {
-        // Do initialization once.
-        (void)GetSnapshotFuzzEnv();
-    }
-    void SetUp() override {
-        bool is_virtual_ab = GetBoolProperty("ro.virtual_ab.enabled", false);
-        if (!is_virtual_ab) GTEST_SKIP() << "Test only runs on Virtual A/B devices.";
-    }
-    void SetUpFuzzData(const std::string& fn) {
-        auto path = android::base::GetExecutableDirectory() + "/corpus/"s + fn;
-        std::string proto_text;
-        ASSERT_TRUE(ReadFileToString(path, &proto_text));
-        snapshot_fuzz_data_ = std::make_unique<SnapshotFuzzData>();
-        ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(proto_text,
-                                                                  snapshot_fuzz_data_.get()));
-        test_module_ = android::snapshot::SetUpTest(*snapshot_fuzz_data_);
-    }
-    void TearDown() override { android::snapshot::TearDownTest(); }
-    template <typename FuzzFunction>
-    Result<typename FuzzFunction::ReturnType> Execute(int action_index) {
-        if (action_index >= snapshot_fuzz_data_->actions_size()) {
-            return Error() << "Index " << action_index << " is out of bounds ("
-                           << snapshot_fuzz_data_->actions_size() << " actions in corpus";
-        }
-        const auto& action_proto = snapshot_fuzz_data_->actions(action_index);
-        const auto* field_desc =
-                android::fuzz::GetValueFieldDescriptor<typename FuzzFunction::ActionType>(
-                        action_proto);
-        if (field_desc == nullptr) {
-            return Error() << "Action at index " << action_index << " has no value defined.";
-        }
-        if (FuzzFunction::tag != field_desc->number()) {
-            return Error() << "Action at index " << action_index << " is expected to be "
-                           << FuzzFunction::name << ", but it is " << field_desc->name()
-                           << " in corpus.";
-        }
-        return GoodResult<typename FuzzFunction::ReturnType>::Cast([&]() {
-            return android::fuzz::ActionPerformer<FuzzFunction>::Invoke(test_module_.snapshot.get(),
-                                                                        action_proto, field_desc);
-        });
-    }
-
-    std::unique_ptr<SnapshotFuzzData> snapshot_fuzz_data_;
-    SnapshotTestModule test_module_;
-};
-
-#define SNAPSHOT_FUZZ_FN_NAME(name) FUZZ_FUNCTION_CLASS_NAME(SnapshotManagerAction, name)
-
-MATCHER_P(ResultIs, expected, "") {
-    if (!arg.ok()) {
-        *result_listener << arg.error();
-        return false;
-    }
-    *result_listener << "expected: " << expected;
-    return arg.value() == expected;
-}
-
-#define ASSERT_RESULT_TRUE(actual) ASSERT_THAT(actual, ResultIs(true))
-
-// Check that launch_device.txt is executed correctly.
-TEST_F(LibsnapshotFuzzerTest, LaunchDevice) {
-    SetUpFuzzData("launch_device.txt");
-
-    int i = 0;
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(BeginUpdate)>(i++));
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(CreateUpdateSnapshots)>(i++));
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "sys_b";
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "vnd_b";
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(MapUpdateSnapshot)>(i++)) << "prd_b";
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(FinishedSnapshotWrites)>(i++));
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "sys_b";
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "vnd_b";
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(UnmapUpdateSnapshot)>(i++)) << "prd_b";
-    ASSERT_RESULT_OK(Execute<SNAPSHOT_FUZZ_FN_NAME(SwitchSlot)>(i++));
-    ASSERT_EQ("_b", test_module_.device_info->GetSlotSuffix());
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(NeedSnapshotsInFirstStageMount)>(i++));
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(CreateLogicalAndSnapshotPartitions)>(i++));
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(InitiateMerge)>(i++));
-    ASSERT_RESULT_TRUE(Execute<SNAPSHOT_FUZZ_FN_NAME(ProcessUpdateState)>(i++));
-    ASSERT_EQ(i, snapshot_fuzz_data_->actions_size()) << "Not all actions are executed.";
-}
-
-}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
deleted file mode 100644
index 54c6a00..0000000
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
+++ /dev/null
@@ -1,513 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <ftw.h>
-#include <inttypes.h>
-#include <sys/mman.h>
-#include <sys/mount.h>
-#include <sys/stat.h>
-#include <sysexits.h>
-
-#include <chrono>
-#include <string>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
-#include <fs_mgr.h>
-#include <libsnapshot/auto_device.h>
-#include <libsnapshot/snapshot.h>
-#include <storage_literals/storage_literals.h>
-
-#include "snapshot_fuzz_utils.h"
-#include "utility.h"
-
-// Prepends the errno string, but it is good enough.
-#ifndef PCHECK
-#define PCHECK(x) CHECK(x) << strerror(errno) << ": "
-#endif
-
-using namespace android::storage_literals;
-using namespace std::chrono_literals;
-using namespace std::string_literals;
-
-using android::base::Basename;
-using android::base::ReadFileToString;
-using android::base::SetProperty;
-using android::base::Split;
-using android::base::StartsWith;
-using android::base::StringPrintf;
-using android::base::unique_fd;
-using android::base::WriteStringToFile;
-using android::dm::DeviceMapper;
-using android::dm::DmTarget;
-using android::dm::LoopControl;
-using android::fiemap::IImageManager;
-using android::fiemap::ImageManager;
-using android::fs_mgr::BlockDeviceInfo;
-using android::fs_mgr::FstabEntry;
-using android::fs_mgr::IPartitionOpener;
-using chromeos_update_engine::DynamicPartitionMetadata;
-
-static const char MNT_DIR[] = "/mnt";
-static const char BLOCK_SYSFS[] = "/sys/block";
-
-static const char FAKE_ROOT_NAME[] = "snapshot_fuzz";
-static const auto SUPER_IMAGE_SIZE = 16_MiB;
-static const auto DATA_IMAGE_SIZE = 16_MiB;
-static const auto FAKE_ROOT_SIZE = 64_MiB;
-
-namespace android::snapshot {
-
-bool Mkdir(const std::string& path) {
-    if (mkdir(path.c_str(), 0750) == -1 && errno != EEXIST) {
-        PLOG(ERROR) << "Cannot create " << path;
-        return false;
-    }
-    return true;
-}
-
-bool RmdirRecursive(const std::string& path) {
-    auto callback = [](const char* child, const struct stat*, int file_type, struct FTW*) -> int {
-        switch (file_type) {
-            case FTW_D:
-            case FTW_DP:
-            case FTW_DNR:
-                if (rmdir(child) == -1) {
-                    PLOG(ERROR) << "rmdir " << child;
-                    return -1;
-                }
-                return 0;
-            case FTW_NS:
-            default:
-                if (rmdir(child) != -1) break;
-                [[fallthrough]];
-            case FTW_F:
-            case FTW_SL:
-            case FTW_SLN:
-                if (unlink(child) == -1) {
-                    PLOG(ERROR) << "unlink " << child;
-                    return -1;
-                }
-                return 0;
-        }
-        return 0;
-    };
-
-    return nftw(path.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) == 0;
-}
-
-std::string GetLinearBaseDeviceString(const DeviceMapper::TargetInfo& target) {
-    if (target.spec.target_type != "linear"s) return {};
-    auto tokens = Split(target.data, " ");
-    CHECK_EQ(2, tokens.size());
-    return tokens[0];
-}
-
-std::vector<std::string> GetSnapshotBaseDeviceStrings(const DeviceMapper::TargetInfo& target) {
-    if (target.spec.target_type != "snapshot"s && target.spec.target_type != "snapshot-merge"s)
-        return {};
-    auto tokens = Split(target.data, " ");
-    CHECK_EQ(4, tokens.size());
-    return {tokens[0], tokens[1]};
-}
-
-bool ShouldDeleteLoopDevice(const std::string& node) {
-    std::string backing_file;
-    if (ReadFileToString(StringPrintf("%s/loop/backing_file", node.data()), &backing_file)) {
-        if (StartsWith(backing_file, std::string(MNT_DIR) + "/" + FAKE_ROOT_NAME)) {
-            return true;
-        }
-    }
-    return false;
-}
-
-std::vector<DeviceMapper::TargetInfo> GetTableInfoIfExists(const std::string& dev_name) {
-    auto& dm = DeviceMapper::Instance();
-    std::vector<DeviceMapper::TargetInfo> table;
-    if (!dm.GetTableInfo(dev_name, &table)) {
-        PCHECK(errno == ENODEV || errno == ENXIO);
-        return {};
-    }
-    return table;
-}
-
-std::set<std::string> GetAllBaseDeviceStrings(const std::string& child_dev) {
-    std::set<std::string> ret;
-    for (const auto& child_target : GetTableInfoIfExists(child_dev)) {
-        auto snapshot_bases = GetSnapshotBaseDeviceStrings(child_target);
-        ret.insert(snapshot_bases.begin(), snapshot_bases.end());
-
-        auto linear_base = GetLinearBaseDeviceString(child_target);
-        if (!linear_base.empty()) {
-            ret.insert(linear_base);
-        }
-    }
-    return ret;
-}
-
-using PropertyList = std::set<std::string>;
-void InsertProperty(const char* key, const char* /*name*/, void* cookie) {
-    reinterpret_cast<PropertyList*>(cookie)->insert(key);
-}
-
-// Attempt to delete all devices that is based on dev_name, including itself.
-void CheckDeleteDeviceMapperTree(const std::string& dev_name, bool known_allow_delete = false,
-                                 uint64_t depth = 100) {
-    CHECK(depth > 0) << "Reaching max depth when deleting " << dev_name
-                     << ". There may be devices referencing itself. Check `dmctl list devices -v`.";
-
-    auto& dm = DeviceMapper::Instance();
-    auto table = GetTableInfoIfExists(dev_name);
-    if (table.empty()) {
-        PCHECK(dm.DeleteDeviceIfExists(dev_name)) << dev_name;
-        return;
-    }
-
-    if (!known_allow_delete) {
-        for (const auto& target : table) {
-            auto base_device_string = GetLinearBaseDeviceString(target);
-            if (base_device_string.empty()) continue;
-            if (ShouldDeleteLoopDevice(
-                        StringPrintf("/sys/dev/block/%s", base_device_string.data()))) {
-                known_allow_delete = true;
-                break;
-            }
-        }
-    }
-    if (!known_allow_delete) {
-        return;
-    }
-
-    std::string dev_string;
-    PCHECK(dm.GetDeviceString(dev_name, &dev_string));
-
-    std::vector<DeviceMapper::DmBlockDevice> devices;
-    PCHECK(dm.GetAvailableDevices(&devices));
-    for (const auto& child_dev : devices) {
-        auto child_bases = GetAllBaseDeviceStrings(child_dev.name());
-        if (child_bases.find(dev_string) != child_bases.end()) {
-            CheckDeleteDeviceMapperTree(child_dev.name(), true /* known_allow_delete */, depth - 1);
-        }
-    }
-
-    PCHECK(dm.DeleteDeviceIfExists(dev_name)) << dev_name;
-}
-
-// Attempt to clean up residues from previous runs.
-void CheckCleanupDeviceMapperDevices() {
-    auto& dm = DeviceMapper::Instance();
-    std::vector<DeviceMapper::DmBlockDevice> devices;
-    PCHECK(dm.GetAvailableDevices(&devices));
-
-    for (const auto& dev : devices) {
-        CheckDeleteDeviceMapperTree(dev.name());
-    }
-}
-
-void CheckUmount(const std::string& path) {
-    PCHECK(TEMP_FAILURE_RETRY(umount(path.data()) == 0) || errno == ENOENT || errno == EINVAL)
-            << path;
-}
-
-void CheckDetachLoopDevices(const std::set<std::string>& exclude_names = {}) {
-    // ~SnapshotFuzzEnv automatically does the following.
-    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(BLOCK_SYSFS), closedir);
-    PCHECK(dir != nullptr) << BLOCK_SYSFS;
-    LoopControl loop_control;
-    dirent* dp;
-    while ((dp = readdir(dir.get())) != nullptr) {
-        if (exclude_names.find(dp->d_name) != exclude_names.end()) {
-            continue;
-        }
-        if (!ShouldDeleteLoopDevice(StringPrintf("%s/%s", BLOCK_SYSFS, dp->d_name).data())) {
-            continue;
-        }
-        PCHECK(loop_control.Detach(StringPrintf("/dev/block/%s", dp->d_name).data()));
-    }
-}
-
-void CheckUmountAll() {
-    CheckUmount(std::string(MNT_DIR) + "/snapshot_fuzz_data");
-    CheckUmount(std::string(MNT_DIR) + "/" + FAKE_ROOT_NAME);
-}
-
-class AutoDeleteDir : public AutoDevice {
-  public:
-    static std::unique_ptr<AutoDeleteDir> New(const std::string& path) {
-        if (!Mkdir(path)) {
-            return std::unique_ptr<AutoDeleteDir>(new AutoDeleteDir(""));
-        }
-        return std::unique_ptr<AutoDeleteDir>(new AutoDeleteDir(path));
-    }
-    ~AutoDeleteDir() {
-        if (!HasDevice()) return;
-        PCHECK(rmdir(name_.c_str()) == 0 || errno == ENOENT) << name_;
-    }
-
-  private:
-    AutoDeleteDir(const std::string& path) : AutoDevice(path) {}
-};
-
-class AutoUnmount : public AutoDevice {
-  public:
-    ~AutoUnmount() {
-        if (!HasDevice()) return;
-        CheckUmount(name_);
-    }
-    AutoUnmount(const std::string& path) : AutoDevice(path) {}
-};
-
-class AutoUnmountTmpfs : public AutoUnmount {
-  public:
-    static std::unique_ptr<AutoUnmount> New(const std::string& path, uint64_t size) {
-        if (mount("tmpfs", path.c_str(), "tmpfs", 0,
-                  (void*)StringPrintf("size=%" PRIu64, size).data()) == -1) {
-            PLOG(ERROR) << "Cannot mount " << path;
-            return std::unique_ptr<AutoUnmount>(new AutoUnmount(""));
-        }
-        return std::unique_ptr<AutoUnmount>(new AutoUnmount(path));
-    }
-  private:
-    using AutoUnmount::AutoUnmount;
-};
-
-// A directory on tmpfs. Upon destruct, it is unmounted and deleted.
-class AutoMemBasedDir : public AutoDevice {
-  public:
-    static std::unique_ptr<AutoMemBasedDir> New(const std::string& name, uint64_t size) {
-        auto ret = std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(name));
-        ret->auto_delete_mount_dir_ = AutoDeleteDir::New(ret->mount_path());
-        if (!ret->auto_delete_mount_dir_->HasDevice()) {
-            return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
-        }
-        ret->auto_umount_mount_point_ = AutoUnmountTmpfs::New(ret->mount_path(), size);
-        if (!ret->auto_umount_mount_point_->HasDevice()) {
-            return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
-        }
-        // tmp_path() and persist_path does not need to be deleted upon destruction, hence it is
-        // not wrapped with AutoDeleteDir.
-        if (!Mkdir(ret->tmp_path())) {
-            return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
-        }
-        if (!Mkdir(ret->persist_path())) {
-            return std::unique_ptr<AutoMemBasedDir>(new AutoMemBasedDir(""));
-        }
-        return ret;
-    }
-    // Return the temporary scratch directory.
-    std::string tmp_path() const {
-        CHECK(HasDevice());
-        return mount_path() + "/tmp";
-    }
-    // Return the temporary scratch directory.
-    std::string persist_path() const {
-        CHECK(HasDevice());
-        return mount_path() + "/persist";
-    }
-    // Delete all contents in tmp_path() and start over. tmp_path() itself is re-created.
-    void CheckSoftReset() {
-        PCHECK(RmdirRecursive(tmp_path()));
-        PCHECK(Mkdir(tmp_path()));
-    }
-
-  private:
-    AutoMemBasedDir(const std::string& name) : AutoDevice(name) {}
-    std::string mount_path() const {
-        CHECK(HasDevice());
-        return MNT_DIR + "/"s + name_;
-    }
-    std::unique_ptr<AutoDeleteDir> auto_delete_mount_dir_;
-    std::unique_ptr<AutoUnmount> auto_umount_mount_point_;
-};
-
-SnapshotFuzzEnv::SnapshotFuzzEnv() {
-    CheckCleanupDeviceMapperDevices();
-    CheckDetachLoopDevices();
-    CheckUmountAll();
-
-    fake_root_ = AutoMemBasedDir::New(FAKE_ROOT_NAME, FAKE_ROOT_SIZE);
-    CHECK(fake_root_ != nullptr);
-    CHECK(fake_root_->HasDevice());
-    loop_control_ = std::make_unique<LoopControl>();
-
-    fake_data_mount_point_ = MNT_DIR + "/snapshot_fuzz_data"s;
-    auto_delete_data_mount_point_ = AutoDeleteDir::New(fake_data_mount_point_);
-    CHECK(auto_delete_data_mount_point_ != nullptr);
-    CHECK(auto_delete_data_mount_point_->HasDevice());
-
-    const auto& fake_persist_path = fake_root_->persist_path();
-    mapped_super_ = CheckMapImage(fake_persist_path + "/super.img", SUPER_IMAGE_SIZE,
-                                  loop_control_.get(), &fake_super_);
-    mapped_data_ = CheckMapImage(fake_persist_path + "/data.img", DATA_IMAGE_SIZE,
-                                 loop_control_.get(), &fake_data_block_device_);
-    mounted_data_ = CheckMountFormatData(fake_data_block_device_, fake_data_mount_point_);
-}
-
-SnapshotFuzzEnv::~SnapshotFuzzEnv() {
-    CheckCleanupDeviceMapperDevices();
-    mounted_data_ = nullptr;
-    auto_delete_data_mount_point_ = nullptr;
-    mapped_data_ = nullptr;
-    mapped_super_ = nullptr;
-    CheckDetachLoopDevices();
-    loop_control_ = nullptr;
-    fake_root_ = nullptr;
-    CheckUmountAll();
-}
-
-void CheckZeroFill(const std::string& file, size_t size) {
-    std::string zeros(size, '\0');
-    PCHECK(WriteStringToFile(zeros, file)) << "Cannot write zeros to " << file;
-}
-
-void SnapshotFuzzEnv::CheckSoftReset() {
-    fake_root_->CheckSoftReset();
-    CheckZeroFill(super(), SUPER_IMAGE_SIZE);
-    CheckCleanupDeviceMapperDevices();
-    CheckDetachLoopDevices({Basename(fake_super_), Basename(fake_data_block_device_)});
-}
-
-std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager() {
-    auto metadata_dir = fake_root_->tmp_path() + "/images_manager_metadata";
-    auto data_dir = fake_data_mount_point_ + "/image_manager_data";
-    PCHECK(Mkdir(metadata_dir));
-    PCHECK(Mkdir(data_dir));
-    return SnapshotFuzzImageManager::Open(metadata_dir, data_dir);
-}
-
-// Helper to create a loop device for a file.
-static void CheckCreateLoopDevice(LoopControl* control, const std::string& file,
-                                  const std::chrono::milliseconds& timeout_ms, std::string* path) {
-    static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
-    android::base::unique_fd file_fd(open(file.c_str(), kOpenFlags));
-    PCHECK(file_fd >= 0) << "Could not open file: " << file;
-    CHECK(control->Attach(file_fd, timeout_ms, path))
-            << "Could not create loop device for: " << file;
-}
-
-class AutoDetachLoopDevice : public AutoDevice {
-  public:
-    AutoDetachLoopDevice(LoopControl* control, const std::string& device)
-        : AutoDevice(device), control_(control) {}
-    ~AutoDetachLoopDevice() { PCHECK(control_->Detach(name_)) << name_; }
-
-  private:
-    LoopControl* control_;
-};
-
-std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMapImage(const std::string& img_path,
-                                                           uint64_t size, LoopControl* control,
-                                                           std::string* mapped_path) {
-    CheckZeroFill(img_path, size);
-    CheckCreateLoopDevice(control, img_path, 1s, mapped_path);
-
-    return std::make_unique<AutoDetachLoopDevice>(control, *mapped_path);
-}
-
-SnapshotTestModule SnapshotFuzzEnv::CheckCreateSnapshotManager(const SnapshotFuzzData& data) {
-    SnapshotTestModule ret;
-    auto partition_opener = std::make_unique<TestPartitionOpener>(super());
-    ret.opener = partition_opener.get();
-    CheckWriteSuperMetadata(data, *partition_opener);
-    auto metadata_dir = fake_root_->tmp_path() + "/snapshot_metadata";
-    PCHECK(Mkdir(metadata_dir));
-    if (data.has_metadata_snapshots_dir()) {
-        PCHECK(Mkdir(metadata_dir + "/snapshots"));
-    }
-
-    ret.device_info = new SnapshotFuzzDeviceInfo(this, data.device_info_data(),
-                                                 std::move(partition_opener), metadata_dir);
-    auto snapshot = SnapshotManager::New(ret.device_info /* takes ownership */);
-    ret.snapshot = std::move(snapshot);
-
-    return ret;
-}
-
-const std::string& SnapshotFuzzEnv::super() const {
-    return fake_super_;
-}
-
-void SnapshotFuzzEnv::CheckWriteSuperMetadata(const SnapshotFuzzData& data,
-                                              const IPartitionOpener& opener) {
-    if (!data.is_super_metadata_valid()) {
-        // Leave it zero.
-        return;
-    }
-
-    BlockDeviceInfo super_device("super", SUPER_IMAGE_SIZE, 0, 0, 4096);
-    std::vector<BlockDeviceInfo> devices = {super_device};
-    auto builder = MetadataBuilder::New(devices, "super", 65536, 2);
-    CHECK(builder != nullptr);
-
-    // Attempt to create a super partition metadata using proto. All errors are ignored.
-    for (const auto& group_proto : data.super_data().dynamic_partition_metadata().groups()) {
-        (void)builder->AddGroup(group_proto.name(), group_proto.size());
-        for (const auto& partition_name : group_proto.partition_names()) {
-            (void)builder->AddPartition(partition_name, group_proto.name(),
-                                        LP_PARTITION_ATTR_READONLY);
-        }
-    }
-
-    for (const auto& partition_proto : data.super_data().partitions()) {
-        auto p = builder->FindPartition(partition_proto.partition_name());
-        if (p == nullptr) continue;
-        (void)builder->ResizePartition(p, partition_proto.new_partition_info().size());
-    }
-
-    auto metadata = builder->Export();
-    // metadata may be nullptr if it is not valid (e.g. partition name too long).
-    // In this case, just use empty super partition data.
-    if (metadata == nullptr) {
-        builder = MetadataBuilder::New(devices, "super", 65536, 2);
-        CHECK(builder != nullptr);
-        metadata = builder->Export();
-        CHECK(metadata != nullptr);
-    }
-    CHECK(FlashPartitionTable(opener, super(), *metadata.get()));
-}
-
-std::unique_ptr<AutoDevice> SnapshotFuzzEnv::CheckMountFormatData(const std::string& blk_device,
-                                                                  const std::string& mount_point) {
-    FstabEntry entry{
-            .blk_device = blk_device,
-            .length = static_cast<off64_t>(DATA_IMAGE_SIZE),
-            .fs_type = "ext4",
-            .mount_point = mount_point,
-    };
-    CHECK(0 == fs_mgr_do_format(entry));
-    CHECK(0 == fs_mgr_do_mount_one(entry));
-    return std::make_unique<AutoUnmount>(mount_point);
-}
-
-SnapshotFuzzImageManager::~SnapshotFuzzImageManager() {
-    // Remove relevant gsid.mapped_images.* props.
-    for (const auto& name : mapped_) {
-        CHECK(UnmapImageIfExists(name)) << "Cannot unmap " << name;
-    }
-}
-
-bool SnapshotFuzzImageManager::MapImageDevice(const std::string& name,
-                                              const std::chrono::milliseconds& timeout_ms,
-                                              std::string* path) {
-    if (impl_->MapImageDevice(name, timeout_ms, path)) {
-        mapped_.insert(name);
-        return true;
-    }
-    return false;
-}
-
-}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
deleted file mode 100644
index eb8246a..0000000
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ /dev/null
@@ -1,214 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <memory>
-#include <set>
-#include <string>
-
-#include <android-base/file.h>
-#include <android-base/stringprintf.h>
-#include <android/snapshot/snapshot_fuzz.pb.h>
-#include <libdm/loop_control.h>
-#include <libfiemap/image_manager.h>
-#include <liblp/liblp.h>
-#include <libsnapshot/auto_device.h>
-#include <libsnapshot/test_helpers.h>
-
-// libsnapshot-specific code for fuzzing. Defines fake classes that are depended
-// by SnapshotManager.
-
-#include "android/snapshot/snapshot_fuzz.pb.h"
-#include "libsnapshot/snapshot.h"
-
-namespace android::snapshot {
-
-class AutoMemBasedDir;
-class SnapshotFuzzDeviceInfo;
-
-class NoOpAutoDevice : public AutoDevice {
-  public:
-    NoOpAutoDevice(bool mounted) : AutoDevice(mounted ? "no_op" : "") {}
-};
-
-struct SnapshotTestModule {
-    std::unique_ptr<ISnapshotManager> snapshot;
-    SnapshotFuzzDeviceInfo* device_info = nullptr;
-    TestPartitionOpener* opener = nullptr;
-};
-
-// Prepare test environment. This has a heavy overhead and should be done once.
-class SnapshotFuzzEnv {
-  public:
-    // Check if test should run at all.
-    static bool ShouldSkipTest();
-
-    // Initialize the environment.
-    SnapshotFuzzEnv();
-    ~SnapshotFuzzEnv();
-
-    // Soft reset part of the environment before running the next test.
-    // Abort if fails.
-    void CheckSoftReset();
-
-    // Create a snapshot manager for this test run.
-    // Client is responsible for maintaining the lifetime of |data| over the life time of
-    // ISnapshotManager.
-    SnapshotTestModule CheckCreateSnapshotManager(const SnapshotFuzzData& data);
-
-    std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager();
-
-    // Return path to super partition.
-    const std::string& super() const;
-
-  private:
-    std::unique_ptr<AutoMemBasedDir> fake_root_;
-    std::unique_ptr<android::dm::LoopControl> loop_control_;
-    std::string fake_data_mount_point_;
-    std::unique_ptr<AutoDevice> auto_delete_data_mount_point_;
-    std::unique_ptr<AutoDevice> mapped_super_;
-    std::string fake_super_;
-    std::unique_ptr<AutoDevice> mapped_data_;
-    std::string fake_data_block_device_;
-    std::unique_ptr<AutoDevice> mounted_data_;
-
-    static std::unique_ptr<AutoDevice> CheckMapImage(const std::string& fake_persist_path,
-                                                     uint64_t size,
-                                                     android::dm::LoopControl* control,
-                                                     std::string* mapped_path);
-    static std::unique_ptr<AutoDevice> CheckMountFormatData(const std::string& blk_device,
-                                                            const std::string& mount_point);
-
-    void CheckWriteSuperMetadata(const SnapshotFuzzData& proto,
-                                 const android::fs_mgr::IPartitionOpener& opener);
-};
-
-class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo {
-  public:
-    using MergeStatus = ISnapshotManager::IDeviceInfo::MergeStatus;
-    // Client is responsible for maintaining the lifetime of |data|.
-    SnapshotFuzzDeviceInfo(SnapshotFuzzEnv* env, const FuzzDeviceInfoData& data,
-                           std::unique_ptr<TestPartitionOpener>&& partition_opener,
-                           const std::string& metadata_dir)
-        : env_(env),
-          data_(&data),
-          partition_opener_(std::move(partition_opener)),
-          metadata_dir_(metadata_dir),
-          dm_(android::dm::DeviceMapper::Instance()) {}
-
-    // Following APIs are mocked.
-    std::string GetMetadataDir() const override { return metadata_dir_; }
-    std::string GetSuperDevice(uint32_t) const override {
-        // TestPartitionOpener can recognize this.
-        return "super";
-    }
-    const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const override {
-        return *partition_opener_;
-    }
-
-    // Following APIs are fuzzed.
-    std::string GetSlotSuffix() const override { return CurrentSlotIsA() ? "_a" : "_b"; }
-    std::string GetOtherSlotSuffix() const override { return CurrentSlotIsA() ? "_b" : "_a"; }
-    bool IsOverlayfsSetup() const override { return data_->is_overlayfs_setup(); }
-    bool SetBootControlMergeStatus(MergeStatus) override {
-        return data_->allow_set_boot_control_merge_status();
-    }
-    bool SetSlotAsUnbootable(unsigned int) override {
-        return data_->allow_set_slot_as_unbootable();
-    }
-    bool IsRecovery() const override { return data_->is_recovery(); }
-    bool IsFirstStageInit() const override { return false; }
-    android::dm::IDeviceMapper& GetDeviceMapper() override { return dm_; }
-    std::unique_ptr<IImageManager> OpenImageManager() const {
-        return env_->CheckCreateFakeImageManager();
-    }
-
-    void SwitchSlot() { switched_slot_ = !switched_slot_; }
-
-  private:
-    SnapshotFuzzEnv* env_;
-    const FuzzDeviceInfoData* data_;
-    std::unique_ptr<TestPartitionOpener> partition_opener_;
-    std::string metadata_dir_;
-    bool switched_slot_ = false;
-    android::dm::DeviceMapper& dm_;
-
-    bool CurrentSlotIsA() const { return data_->slot_suffix_is_a() != switched_slot_; }
-};
-
-// A spy class on ImageManager implementation. Upon destruction, unmaps all images
-// map through this object.
-class SnapshotFuzzImageManager : public android::fiemap::IImageManager {
-  public:
-    static std::unique_ptr<SnapshotFuzzImageManager> Open(const std::string& metadata_dir,
-                                                          const std::string& data_dir) {
-        auto impl = android::fiemap::ImageManager::Open(metadata_dir, data_dir);
-        if (impl == nullptr) return nullptr;
-        return std::unique_ptr<SnapshotFuzzImageManager>(
-                new SnapshotFuzzImageManager(std::move(impl)));
-    }
-
-    ~SnapshotFuzzImageManager();
-
-    // Spied APIs.
-    bool MapImageDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
-                        std::string* path) override;
-
-    // Other functions call through.
-    android::fiemap::FiemapStatus CreateBackingImage(
-            const std::string& name, uint64_t size, int flags,
-            std::function<bool(uint64_t, uint64_t)>&& on_progress) override {
-        return impl_->CreateBackingImage(name, size, flags, std::move(on_progress));
-    }
-    bool DeleteBackingImage(const std::string& name) override {
-        return impl_->DeleteBackingImage(name);
-    }
-    bool UnmapImageDevice(const std::string& name) override {
-        return impl_->UnmapImageDevice(name);
-    }
-    bool BackingImageExists(const std::string& name) override {
-        return impl_->BackingImageExists(name);
-    }
-    bool IsImageMapped(const std::string& name) override { return impl_->IsImageMapped(name); }
-    bool MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
-                                  std::string* dev) override {
-        return impl_->MapImageWithDeviceMapper(opener, name, dev);
-    }
-    bool GetMappedImageDevice(const std::string& name, std::string* device) override {
-        return impl_->GetMappedImageDevice(name, device);
-    }
-    bool MapAllImages(const std::function<bool(std::set<std::string>)>& init) override {
-        return impl_->MapAllImages(init);
-    }
-    bool DisableImage(const std::string& name) override { return impl_->DisableImage(name); }
-    bool RemoveDisabledImages() override { return impl_->RemoveDisabledImages(); }
-    std::vector<std::string> GetAllBackingImages() override { return impl_->GetAllBackingImages(); }
-    android::fiemap::FiemapStatus ZeroFillNewImage(const std::string& name,
-                                                   uint64_t bytes) override {
-        return impl_->ZeroFillNewImage(name, bytes);
-    }
-    bool RemoveAllImages() override { return impl_->RemoveAllImages(); }
-    bool UnmapImageIfExists(const std::string& name) override {
-        return impl_->UnmapImageIfExists(name);
-    }
-    bool IsImageDisabled(const std::string& name) override { return impl_->IsImageDisabled(name); }
-
-  private:
-    std::unique_ptr<android::fiemap::IImageManager> impl_;
-    std::set<std::string> mapped_;
-
-    SnapshotFuzzImageManager(std::unique_ptr<android::fiemap::IImageManager>&& impl)
-        : impl_(std::move(impl)) {}
-};
-
-}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 13314da..460d49d 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -124,6 +124,10 @@
         SKIP_IF_NON_VIRTUAL_AB();
 
         SetupProperties();
+        if (!DeviceSupportsMode()) {
+            GTEST_SKIP() << "Mode not supported on this device";
+        }
+
         InitializeState();
         CleanupTestArtifacts();
         FormatFakeSuper();
@@ -159,7 +163,13 @@
         IPropertyFetcher::OverrideForTesting(std::move(fetcher));
 
         if (GetLegacyCompressionEnabledProperty() || CanUseUserspaceSnapshots()) {
-            snapuserd_required_ = true;
+            // If we're asked to test the device's actual configuration, then it
+            // may be misconfigured, so check for kernel support as libsnapshot does.
+            if (FLAGS_force_mode.empty()) {
+                snapuserd_required_ = KernelSupportsCompressedSnapshots();
+            } else {
+                snapuserd_required_ = true;
+            }
         }
     }
 
@@ -176,6 +186,16 @@
         LOG(INFO) << "Teardown complete for test: " << test_name_;
     }
 
+    bool DeviceSupportsMode() {
+        if (FLAGS_force_mode.empty()) {
+            return true;
+        }
+        if (snapuserd_required_ && !KernelSupportsCompressedSnapshots()) {
+            return false;
+        }
+        return true;
+    }
+
     void InitializeState() {
         ASSERT_TRUE(sm->EnsureImageManager());
         image_manager_ = sm->image_manager();
@@ -193,6 +213,11 @@
         // get an accurate list to remove.
         lock_ = nullptr;
 
+        // If there is no image manager, the test was skipped.
+        if (!image_manager_) {
+            return;
+        }
+
         std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a",
                                               "test_partition_b"};
         for (const auto& snapshot : snapshots) {
@@ -946,6 +971,11 @@
         SKIP_IF_NON_VIRTUAL_AB();
 
         SnapshotTest::SetUp();
+        if (!image_manager_) {
+            // Test was skipped.
+            return;
+        }
+
         Cleanup();
 
         // Cleanup() changes slot suffix, so initialize it again.
@@ -2680,6 +2710,9 @@
         CleanUp();
     }
     void CleanUp() {
+        if (!image_manager_) {
+            return;
+        }
         EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) ||
                     image_manager_->DeleteBackingImage(kImageName));
     }
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index a67e37c..1e03683 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -25,8 +25,6 @@
     ],
     cflags: [
         "-D_FILE_OFFSET_BITS=64",
-        "-Wall",
-        "-Werror",
     ],
     export_include_dirs: ["include"],
     srcs: [
@@ -54,6 +52,40 @@
     vendor_ramdisk_available: true,
 }
 
+cc_library_static {
+    name: "libsnapuserd",
+    defaults: [
+        "fs_mgr_defaults",
+    ],
+    srcs: [
+        "dm-snapshot-merge/snapuserd.cpp",
+        "dm-snapshot-merge/snapuserd_worker.cpp",
+        "dm-snapshot-merge/snapuserd_readahead.cpp",
+        "snapuserd_buffer.cpp",
+        "user-space-merge/handler_manager.cpp",
+        "user-space-merge/snapuserd_core.cpp",
+        "user-space-merge/snapuserd_dm_user.cpp",
+        "user-space-merge/snapuserd_merge.cpp",
+        "user-space-merge/snapuserd_readahead.cpp",
+        "user-space-merge/snapuserd_transitions.cpp",
+        "user-space-merge/snapuserd_verify.cpp",
+    ],
+    static_libs: [
+        "libbase",
+        "libdm",
+        "libext4_utils",
+        "libsnapshot_cow",
+        "liburing",
+    ],
+    include_dirs: ["bionic/libc/kernel"],
+    header_libs: [
+        "libstorage_literals_headers",
+    ],
+    ramdisk_available: true,
+    vendor_ramdisk_available: true,
+    recovery_available: true,
+}
+
 cc_defaults {
     name: "snapuserd_defaults",
     defaults: [
@@ -61,23 +93,8 @@
     ],
     srcs: [
         "dm-snapshot-merge/snapuserd_server.cpp",
-        "dm-snapshot-merge/snapuserd.cpp",
-        "dm-snapshot-merge/snapuserd_worker.cpp",
-        "dm-snapshot-merge/snapuserd_readahead.cpp",
         "snapuserd_daemon.cpp",
-        "snapuserd_buffer.cpp",
-        "user-space-merge/snapuserd_core.cpp",
-        "user-space-merge/snapuserd_dm_user.cpp",
-        "user-space-merge/snapuserd_merge.cpp",
-        "user-space-merge/snapuserd_readahead.cpp",
-        "user-space-merge/snapuserd_transitions.cpp",
         "user-space-merge/snapuserd_server.cpp",
-        "user-space-merge/snapuserd_verify.cpp",
-    ],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
     ],
 
     static_libs: [
@@ -90,6 +107,7 @@
         "liblog",
         "libsnapshot_cow",
         "libsnapshot_snapuserd",
+        "libsnapuserd",
         "libz",
         "liblz4",
         "libext4_utils",
@@ -125,7 +143,6 @@
     ],
     ramdisk_available: false,
     vendor_ramdisk_available: true,
-    recovery_available: true,
 }
 
 // This target will install to /system/bin/snapuserd_ramdisk 
@@ -147,7 +164,7 @@
 }
 
 cc_test {
-    name: "cow_snapuserd_test",
+    name: "snapuserd_test_legacy",
     defaults: [
         "fs_mgr_defaults",
         "libsnapshot_cow_defaults",
@@ -158,10 +175,6 @@
         "dm-snapshot-merge/snapuserd_worker.cpp",
         "snapuserd_buffer.cpp",
     ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
     shared_libs: [
         "libbase",
         "liblog",
@@ -197,26 +210,23 @@
     srcs: [
         "user-space-merge/snapuserd_test.cpp",
     ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
     shared_libs: [
         "libbase",
         "liblog",
     ],
     static_libs: [
         "libbrotli",
+        "libcutils_sockets",
+        "libdm",
+        "libext4_utils",
+        "libfs_mgr",
+        "libgflags",
         "libgtest",
         "libsnapshot_cow",
         "libsnapshot_snapuserd",
-        "libcutils_sockets",
-        "libz",
-        "libfs_mgr",
-        "libdm",
-        "libext4_utils",
+        "libsnapuserd",
         "liburing",
-        "libgflags",
+        "libz",
     ],
     include_dirs: ["bionic/libc/kernel"],
     header_libs: [
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
index 484a9c4..3c4ab2e 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
@@ -1152,35 +1152,6 @@
     }
 }
 
-TEST(Snapuserd_Test, xor_buffer) {
-    std::string data = "Test String";
-    std::string jumbled = {0x0C, 0x2A, 0x21, 0x54, 0x73, 0x27, 0x06, 0x1B, 0x07, 0x09, 0x46};
-    std::string result = "XOR String!";
-
-    BufferSink sink;
-    XorSink xor_sink;
-    sink.Initialize(sizeof(struct dm_user_header) + 10);
-    int buffsize = 5;
-    xor_sink.Initialize(&sink, buffsize);
-
-    void* buff = sink.GetPayloadBuffer(data.length());
-    memcpy(buff, data.data(), data.length());
-
-    size_t actual;
-    size_t count = 0;
-    while (count < data.length()) {
-        void* xor_buff = xor_sink.GetBuffer(10, &actual);
-        ASSERT_EQ(actual, buffsize);
-        ASSERT_NE(xor_buff, nullptr);
-        memcpy(xor_buff, jumbled.data() + count, buffsize);
-        xor_sink.ReturnData(xor_buff, actual);
-        count += actual;
-    }
-
-    std::string answer = reinterpret_cast<char*>(sink.GetPayloadBufPtr());
-    ASSERT_EQ(answer, result);
-}
-
 TEST(Snapuserd_Test, Snapshot_Metadata) {
     CowSnapuserdMetadataTest harness;
     harness.Setup();
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index 5f4d706..8926030 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -350,7 +350,7 @@
     CowHeader header;
     CowOptions options;
     bool metadata_found = false;
-    int replace_ops = 0, zero_ops = 0, copy_ops = 0, xor_ops = 0;
+    int replace_ops = 0, zero_ops = 0, copy_ops = 0;
 
     SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file";
 
@@ -515,10 +515,6 @@
             //===========================================================
             uint64_t block_source = cow_op->source;
             uint64_t block_offset = 0;
-            if (cow_op->type == kCowXorOp) {
-                block_source /= BLOCK_SZ;
-                block_offset = cow_op->source % BLOCK_SZ;
-            }
             if (prev_id.has_value()) {
                 if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source) ||
                     (block_offset > 0 && source_blocks.count(block_source + 1))) {
@@ -538,7 +534,7 @@
         } while (!cowop_rm_iter->Done() && pending_ordered_ops);
 
         data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
-        SNAP_LOG(DEBUG) << "Batch Merge copy-ops/xor-ops of size: " << vec.size()
+        SNAP_LOG(DEBUG) << "Batch Merge copy-ops of size: " << vec.size()
                         << " Area: " << vec_.size() << " Area offset: " << offset
                         << " Pending-ordered-ops in this area: " << pending_ordered_ops;
 
@@ -556,8 +552,6 @@
             num_ops += 1;
             if (cow_op->type == kCowCopyOp) {
                 copy_ops++;
-            } else {  // it->second->type == kCowXorOp
-                xor_ops++;
             }
 
             if (read_ahead_feature_) {
@@ -629,8 +623,8 @@
     SNAP_LOG(INFO) << "ReadMetadata completed. Final-chunk-id: " << data_chunk_id
                    << " Num Sector: " << ChunkToSector(data_chunk_id)
                    << " Replace-ops: " << replace_ops << " Zero-ops: " << zero_ops
-                   << " Copy-ops: " << copy_ops << " Xor-ops: " << xor_ops
-                   << " Areas: " << vec_.size() << " Num-ops-merged: " << header.num_merge_ops
+                   << " Copy-ops: " << copy_ops << " Areas: " << vec_.size()
+                   << " Num-ops-merged: " << header.num_merge_ops
                    << " Total-data-ops: " << reader_->get_num_total_data_ops();
 
     // Total number of sectors required for creating dm-user device
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
index 47b9b22..beb6004 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
@@ -170,9 +170,8 @@
     // Processing COW operations
     bool ProcessCowOp(const CowOperation* cow_op);
     bool ProcessReplaceOp(const CowOperation* cow_op);
-    // Handles Copy and Xor
+    // Handles Copy
     bool ProcessCopyOp(const CowOperation* cow_op);
-    bool ProcessXorOp(const CowOperation* cow_op);
     bool ProcessZeroOp();
 
     bool ReadFromBaseDevice(const CowOperation* cow_op);
@@ -191,7 +190,6 @@
 
     std::unique_ptr<CowReader> reader_;
     BufferSink bufsink_;
-    XorSink xorsink_;
 
     std::string cow_device_;
     std::string backing_store_device_;
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index c201b23..01123f8 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -174,10 +174,6 @@
 void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
     uint64_t source_block = cow_op->source;
     uint64_t source_offset = 0;
-    if (cow_op->type == kCowXorOp) {
-        source_block /= BLOCK_SZ;
-        source_offset = cow_op->source % BLOCK_SZ;
-    }
     if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block) ||
         (source_offset > 0 && source_blocks_.count(source_block + 1))) {
         overlap_ = true;
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index 0e9f0f1..965af40 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -116,13 +116,7 @@
         offset *= BLOCK_SZ;
     }
     if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) {
-        std::string op;
-        if (cow_op->type == kCowCopyOp)
-            op = "Copy-op";
-        else {
-            op = "Xor-op";
-        }
-        SNAP_PLOG(ERROR) << op << " failed. Read from backing store: " << backing_store_device_
+        SNAP_PLOG(ERROR) << "Copy op failed. Read from backing store: " << backing_store_device_
                          << "at block :" << offset / BLOCK_SZ << " offset:" << offset % BLOCK_SZ;
         return false;
     }
@@ -158,23 +152,6 @@
     return true;
 }
 
-bool WorkerThread::ProcessXorOp(const CowOperation* cow_op) {
-    if (!GetReadAheadPopulatedBuffer(cow_op)) {
-        SNAP_LOG(DEBUG) << " GetReadAheadPopulatedBuffer failed..."
-                        << " new_block: " << cow_op->new_block;
-        if (!ReadFromBaseDevice(cow_op)) {
-            return false;
-        }
-    }
-    xorsink_.Reset();
-    if (!reader_->ReadData(*cow_op, &xorsink_)) {
-        SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block;
-        return false;
-    }
-
-    return true;
-}
-
 bool WorkerThread::ProcessZeroOp() {
     // Zero out the entire block
     void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
@@ -206,12 +183,8 @@
             return ProcessCopyOp(cow_op);
         }
 
-        case kCowXorOp: {
-            return ProcessXorOp(cow_op);
-        }
-
         default: {
-            SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+            SNAP_LOG(ERROR) << "Unsupported operation-type found: " << cow_op->type;
         }
     }
     return false;
@@ -830,7 +803,6 @@
 
 bool WorkerThread::RunThread() {
     InitializeBufsink();
-    xorsink_.Initialize(&bufsink_, BLOCK_SZ);
 
     if (!InitializeFds()) {
         return false;
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index bfe93eb..36dad33 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -110,11 +110,11 @@
     for (int i = arg_start; i < argc; i++) {
         auto parts = android::base::Split(argv[i], ",");
         if (parts.size() != 4) {
-            LOG(ERROR) << "Malformed message, expected three sub-arguments.";
+            LOG(ERROR) << "Malformed message, expected four sub-arguments.";
             return false;
         }
         auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3]);
-        if (!handler || !user_server_.StartHandler(handler)) {
+        if (!handler || !user_server_.StartHandler(parts[0])) {
             return false;
         }
     }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
new file mode 100644
index 0000000..bdba5c0
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
@@ -0,0 +1,374 @@
+// Copyright (C) 2023 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 "handler_manager.h"
+
+#include <sys/eventfd.h>
+
+#include <android-base/logging.h>
+
+#include "snapuserd_core.h"
+
+namespace android {
+namespace snapshot {
+
+static constexpr uint8_t kMaxMergeThreads = 2;
+
+HandlerThread::HandlerThread(std::shared_ptr<SnapshotHandler> snapuserd)
+    : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
+
+void HandlerThread::FreeResources() {
+    // Each worker thread holds a reference to snapuserd.
+    // Clear them so that all the resources
+    // held by snapuserd is released
+    if (snapuserd_) {
+        snapuserd_->FreeResources();
+        snapuserd_ = nullptr;
+    }
+}
+
+SnapshotHandlerManager::SnapshotHandlerManager() {
+    monitor_merge_event_fd_.reset(eventfd(0, EFD_CLOEXEC));
+    if (monitor_merge_event_fd_ == -1) {
+        PLOG(FATAL) << "monitor_merge_event_fd_: failed to create eventfd";
+    }
+}
+
+std::shared_ptr<HandlerThread> SnapshotHandlerManager::AddHandler(
+        const std::string& misc_name, const std::string& cow_device_path,
+        const std::string& backing_device, const std::string& base_path_merge,
+        int num_worker_threads, bool use_iouring, bool perform_verification) {
+    auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
+                                                       base_path_merge, num_worker_threads,
+                                                       use_iouring, perform_verification);
+    if (!snapuserd->InitCowDevice()) {
+        LOG(ERROR) << "Failed to initialize Snapuserd";
+        return nullptr;
+    }
+
+    if (!snapuserd->InitializeWorkers()) {
+        LOG(ERROR) << "Failed to initialize workers";
+        return nullptr;
+    }
+
+    auto handler = std::make_shared<HandlerThread>(snapuserd);
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        if (FindHandler(&lock, misc_name) != dm_users_.end()) {
+            LOG(ERROR) << "Handler already exists: " << misc_name;
+            return nullptr;
+        }
+        dm_users_.push_back(handler);
+    }
+    return handler;
+}
+
+bool SnapshotHandlerManager::StartHandler(const std::string& misc_name) {
+    std::lock_guard<std::mutex> lock(lock_);
+    auto iter = FindHandler(&lock, misc_name);
+    if (iter == dm_users_.end()) {
+        LOG(ERROR) << "Could not find handler: " << misc_name;
+        return false;
+    }
+    if (!(*iter)->snapuserd() || (*iter)->snapuserd()->IsAttached()) {
+        LOG(ERROR) << "Tried to re-attach control device: " << misc_name;
+        return false;
+    }
+    if (!StartHandler(*iter)) {
+        return false;
+    }
+    return true;
+}
+
+bool SnapshotHandlerManager::StartHandler(const std::shared_ptr<HandlerThread>& handler) {
+    if (handler->snapuserd()->IsAttached()) {
+        LOG(ERROR) << "Handler already attached";
+        return false;
+    }
+
+    handler->snapuserd()->AttachControlDevice();
+
+    handler->thread() = std::thread(std::bind(&SnapshotHandlerManager::RunThread, this, handler));
+    return true;
+}
+
+bool SnapshotHandlerManager::DeleteHandler(const std::string& misc_name) {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        auto iter = FindHandler(&lock, misc_name);
+        if (iter == dm_users_.end()) {
+            // After merge is completed, we swap dm-user table with
+            // the underlying dm-linear base device. Hence, worker
+            // threads would have terminted and was removed from
+            // the list.
+            LOG(DEBUG) << "Could not find handler: " << misc_name;
+            return true;
+        }
+
+        if (!(*iter)->ThreadTerminated()) {
+            (*iter)->snapuserd()->NotifyIOTerminated();
+        }
+    }
+    if (!RemoveAndJoinHandler(misc_name)) {
+        return false;
+    }
+    return true;
+}
+
+void SnapshotHandlerManager::RunThread(std::shared_ptr<HandlerThread> handler) {
+    LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
+
+    if (!handler->snapuserd()->Start()) {
+        LOG(ERROR) << " Failed to launch all worker threads";
+    }
+
+    handler->snapuserd()->CloseFds();
+    bool merge_completed = handler->snapuserd()->CheckMergeCompletionStatus();
+    handler->snapuserd()->UnmapBufferRegion();
+
+    auto misc_name = handler->misc_name();
+    LOG(INFO) << "Handler thread about to exit: " << misc_name;
+
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        if (merge_completed) {
+            num_partitions_merge_complete_ += 1;
+            active_merge_threads_ -= 1;
+            WakeupMonitorMergeThread();
+        }
+        handler->SetThreadTerminated();
+        auto iter = FindHandler(&lock, handler->misc_name());
+        if (iter == dm_users_.end()) {
+            // RemoveAndJoinHandler() already removed us from the list, and is
+            // now waiting on a join(), so just return. Additionally, release
+            // all the resources held by snapuserd object which are shared
+            // by worker threads. This should be done when the last reference
+            // of "handler" is released; but we will explicitly release here
+            // to make sure snapuserd object is freed as it is the biggest
+            // consumer of memory in the daemon.
+            handler->FreeResources();
+            LOG(INFO) << "Exiting handler thread to allow for join: " << misc_name;
+            return;
+        }
+
+        LOG(INFO) << "Exiting handler thread and freeing resources: " << misc_name;
+
+        if (handler->snapuserd()->IsAttached()) {
+            handler->thread().detach();
+        }
+
+        // Important: free resources within the lock. This ensures that if
+        // WaitForDelete() is called, the handler is either in the list, or
+        // it's not and its resources are guaranteed to be freed.
+        handler->FreeResources();
+        dm_users_.erase(iter);
+    }
+}
+
+bool SnapshotHandlerManager::InitiateMerge(const std::string& misc_name) {
+    std::lock_guard<std::mutex> lock(lock_);
+    auto iter = FindHandler(&lock, misc_name);
+    if (iter == dm_users_.end()) {
+        LOG(ERROR) << "Could not find handler: " << misc_name;
+        return false;
+    }
+
+    return StartMerge(&lock, *iter);
+}
+
+bool SnapshotHandlerManager::StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
+                                        const std::shared_ptr<HandlerThread>& handler) {
+    CHECK(proof_of_lock);
+
+    if (!handler->snapuserd()->IsAttached()) {
+        LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
+        return false;
+    }
+
+    handler->snapuserd()->MonitorMerge();
+
+    if (!is_merge_monitor_started_) {
+        std::thread(&SnapshotHandlerManager::MonitorMerge, this).detach();
+        is_merge_monitor_started_ = true;
+    }
+
+    merge_handlers_.push(handler);
+    WakeupMonitorMergeThread();
+    return true;
+}
+
+void SnapshotHandlerManager::WakeupMonitorMergeThread() {
+    uint64_t notify = 1;
+    ssize_t rc = TEMP_FAILURE_RETRY(write(monitor_merge_event_fd_.get(), &notify, sizeof(notify)));
+    if (rc < 0) {
+        PLOG(FATAL) << "failed to notify monitor merge thread";
+    }
+}
+
+void SnapshotHandlerManager::MonitorMerge() {
+    while (!stop_monitor_merge_thread_) {
+        uint64_t testVal;
+        ssize_t ret =
+                TEMP_FAILURE_RETRY(read(monitor_merge_event_fd_.get(), &testVal, sizeof(testVal)));
+        if (ret == -1) {
+            PLOG(FATAL) << "Failed to read from eventfd";
+        } else if (ret == 0) {
+            LOG(FATAL) << "Hit EOF on eventfd";
+        }
+
+        LOG(INFO) << "MonitorMerge: active-merge-threads: " << active_merge_threads_;
+        {
+            std::lock_guard<std::mutex> lock(lock_);
+            while (active_merge_threads_ < kMaxMergeThreads && merge_handlers_.size() > 0) {
+                auto handler = merge_handlers_.front();
+                merge_handlers_.pop();
+
+                if (!handler->snapuserd()) {
+                    LOG(INFO) << "MonitorMerge: skipping deleted handler: " << handler->misc_name();
+                    continue;
+                }
+
+                LOG(INFO) << "Starting merge for partition: "
+                          << handler->snapuserd()->GetMiscName();
+                handler->snapuserd()->InitiateMerge();
+                active_merge_threads_ += 1;
+            }
+        }
+    }
+
+    LOG(INFO) << "Exiting MonitorMerge: size: " << merge_handlers_.size();
+}
+
+std::string SnapshotHandlerManager::GetMergeStatus(const std::string& misc_name) {
+    std::lock_guard<std::mutex> lock(lock_);
+    auto iter = FindHandler(&lock, misc_name);
+    if (iter == dm_users_.end()) {
+        LOG(ERROR) << "Could not find handler: " << misc_name;
+        return {};
+    }
+
+    return (*iter)->snapuserd()->GetMergeStatus();
+}
+
+double SnapshotHandlerManager::GetMergePercentage() {
+    std::lock_guard<std::mutex> lock(lock_);
+
+    double percentage = 0.0;
+    int n = 0;
+
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        auto& th = (*iter)->thread();
+        if (th.joinable()) {
+            // Merge percentage by individual partitions wherein merge is still
+            // in-progress
+            percentage += (*iter)->snapuserd()->GetMergePercentage();
+            n += 1;
+        }
+    }
+
+    // Calculate final merge including those partitions where merge was already
+    // completed - num_partitions_merge_complete_ will track them when each
+    // thread exists in RunThread.
+    int total_partitions = n + num_partitions_merge_complete_;
+
+    if (total_partitions) {
+        percentage = ((num_partitions_merge_complete_ * 100.0) + percentage) / total_partitions;
+    }
+
+    LOG(DEBUG) << "Merge %: " << percentage
+               << " num_partitions_merge_complete_: " << num_partitions_merge_complete_
+               << " total_partitions: " << total_partitions << " n: " << n;
+    return percentage;
+}
+
+bool SnapshotHandlerManager::GetVerificationStatus() {
+    std::lock_guard<std::mutex> lock(lock_);
+
+    bool status = true;
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        auto& th = (*iter)->thread();
+        if (th.joinable() && status) {
+            status = (*iter)->snapuserd()->CheckPartitionVerification() && status;
+        } else {
+            // return immediately if there is a failure
+            return false;
+        }
+    }
+
+    return status;
+}
+
+bool SnapshotHandlerManager::RemoveAndJoinHandler(const std::string& misc_name) {
+    std::shared_ptr<HandlerThread> handler;
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+
+        auto iter = FindHandler(&lock, misc_name);
+        if (iter == dm_users_.end()) {
+            // Client already deleted.
+            return true;
+        }
+        handler = std::move(*iter);
+        dm_users_.erase(iter);
+    }
+
+    auto& th = handler->thread();
+    if (th.joinable()) {
+        th.join();
+    }
+    return true;
+}
+
+void SnapshotHandlerManager::TerminateMergeThreads() {
+    std::lock_guard<std::mutex> guard(lock_);
+
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        if (!(*iter)->ThreadTerminated()) {
+            (*iter)->snapuserd()->NotifyIOTerminated();
+        }
+    }
+}
+
+void SnapshotHandlerManager::JoinAllThreads() {
+    // Acquire the thread list within the lock.
+    std::vector<std::shared_ptr<HandlerThread>> dm_users;
+    {
+        std::lock_guard<std::mutex> guard(lock_);
+        dm_users = std::move(dm_users_);
+    }
+
+    for (auto& client : dm_users) {
+        auto& th = client->thread();
+
+        if (th.joinable()) th.join();
+    }
+
+    stop_monitor_merge_thread_ = true;
+    WakeupMonitorMergeThread();
+}
+
+auto SnapshotHandlerManager::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+                                         const std::string& misc_name) -> HandlerList::iterator {
+    CHECK(proof_of_lock);
+
+    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
+        if ((*iter)->misc_name() == misc_name) {
+            return iter;
+        }
+    }
+    return dm_users_.end();
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
new file mode 100644
index 0000000..b7ddac1
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.h
@@ -0,0 +1,131 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <memory>
+#include <queue>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace snapshot {
+
+class SnapshotHandler;
+
+class HandlerThread {
+  public:
+    explicit HandlerThread(std::shared_ptr<SnapshotHandler> snapuserd);
+
+    void FreeResources();
+    const std::shared_ptr<SnapshotHandler>& snapuserd() const { return snapuserd_; }
+    std::thread& thread() { return thread_; }
+
+    const std::string& misc_name() const { return misc_name_; }
+    bool ThreadTerminated() { return thread_terminated_; }
+    void SetThreadTerminated() { thread_terminated_ = true; }
+
+  private:
+    std::thread thread_;
+    std::shared_ptr<SnapshotHandler> snapuserd_;
+    std::string misc_name_;
+    bool thread_terminated_ = false;
+};
+
+class ISnapshotHandlerManager {
+  public:
+    virtual ~ISnapshotHandlerManager() {}
+
+    // Add a new snapshot handler but do not start serving requests yet.
+    virtual std::shared_ptr<HandlerThread> AddHandler(const std::string& misc_name,
+                                                      const std::string& cow_device_path,
+                                                      const std::string& backing_device,
+                                                      const std::string& base_path_merge,
+                                                      int num_worker_threads, bool use_iouring,
+                                                      bool perform_verification) = 0;
+
+    // Start serving requests on a snapshot handler.
+    virtual bool StartHandler(const std::string& misc_name) = 0;
+
+    // Stop serving requests on a snapshot handler and remove it.
+    virtual bool DeleteHandler(const std::string& misc_name) = 0;
+
+    // Begin merging blocks on the given snapshot handler.
+    virtual bool InitiateMerge(const std::string& misc_name) = 0;
+
+    // Return a string containing a status code indicating the merge status
+    // on the handler. Returns empty on error.
+    virtual std::string GetMergeStatus(const std::string& misc_name) = 0;
+
+    // Wait until all handlers have terminated.
+    virtual void JoinAllThreads() = 0;
+
+    // Stop any in-progress merge threads.
+    virtual void TerminateMergeThreads() = 0;
+
+    // Returns the merge progress across all merging snapshot handlers.
+    virtual double GetMergePercentage() = 0;
+
+    // Returns whether all snapshots have verified.
+    virtual bool GetVerificationStatus() = 0;
+};
+
+class SnapshotHandlerManager final : public ISnapshotHandlerManager {
+  public:
+    SnapshotHandlerManager();
+    std::shared_ptr<HandlerThread> AddHandler(const std::string& misc_name,
+                                              const std::string& cow_device_path,
+                                              const std::string& backing_device,
+                                              const std::string& base_path_merge,
+                                              int num_worker_threads, bool use_iouring,
+                                              bool perform_verification) override;
+    bool StartHandler(const std::string& misc_name) override;
+    bool DeleteHandler(const std::string& misc_name) override;
+    bool InitiateMerge(const std::string& misc_name) override;
+    std::string GetMergeStatus(const std::string& misc_name) override;
+    void JoinAllThreads() override;
+    void TerminateMergeThreads() override;
+    double GetMergePercentage() override;
+    bool GetVerificationStatus() override;
+
+  private:
+    bool StartHandler(const std::shared_ptr<HandlerThread>& handler);
+    void RunThread(std::shared_ptr<HandlerThread> handler);
+    bool StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
+                    const std::shared_ptr<HandlerThread>& handler);
+    void MonitorMerge();
+    void WakeupMonitorMergeThread();
+    bool RemoveAndJoinHandler(const std::string& misc_name);
+
+    // Find a HandlerThread within a lock.
+    using HandlerList = std::vector<std::shared_ptr<HandlerThread>>;
+    HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+                                      const std::string& misc_name);
+
+    std::mutex lock_;
+    HandlerList dm_users_;
+
+    bool is_merge_monitor_started_ = false;
+    bool stop_monitor_merge_thread_ = false;
+    int active_merge_threads_ = 0;
+    int num_partitions_merge_complete_ = 0;
+    std::queue<std::shared_ptr<HandlerThread>> merge_handlers_;
+    android::base::unique_fd monitor_merge_event_fd_;
+};
+
+}  // namespace snapshot
+}  // namespace android
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 492c43f..25ce0ae 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -31,30 +31,21 @@
 using android::base::unique_fd;
 
 SnapshotHandler::SnapshotHandler(std::string misc_name, std::string cow_device,
-                                 std::string backing_device, std::string base_path_merge) {
+                                 std::string backing_device, std::string base_path_merge,
+                                 int num_worker_threads, bool use_iouring,
+                                 bool perform_verification) {
     misc_name_ = std::move(misc_name);
     cow_device_ = std::move(cow_device);
     backing_store_device_ = std::move(backing_device);
     control_device_ = "/dev/dm-user/" + misc_name_;
     base_path_merge_ = std::move(base_path_merge);
+    num_worker_threads_ = num_worker_threads;
+    is_io_uring_enabled_ = use_iouring;
+    perform_verification_ = perform_verification;
 }
 
 bool SnapshotHandler::InitializeWorkers() {
-    int num_worker_threads = kNumWorkerThreads;
-
-    // We will need multiple worker threads only during
-    // device boot after OTA. For all other purposes,
-    // one thread is sufficient. We don't want to consume
-    // unnecessary memory especially during OTA install phase
-    // when daemon will be up during entire post install phase.
-    //
-    // During boot up, we need multiple threads primarily for
-    // update-verification.
-    if (is_socket_present_) {
-        num_worker_threads = 1;
-    }
-
-    for (int i = 0; i < num_worker_threads; i++) {
+    for (int i = 0; i < num_worker_threads_; i++) {
         std::unique_ptr<Worker> wt =
                 std::make_unique<Worker>(cow_device_, backing_store_device_, control_device_,
                                          misc_name_, base_path_merge_, GetSharedPtr());
@@ -331,19 +322,11 @@
                 std::async(std::launch::async, &Worker::RunThread, worker_threads_[i].get()));
     }
 
-    bool partition_verification = true;
-
-    // We don't want to read the blocks during first stage init or
-    // during post-install phase.
-    if (android::base::EndsWith(misc_name_, "-init") || is_socket_present_) {
-        partition_verification = false;
-    }
-
     std::future<bool> merge_thread =
             std::async(std::launch::async, &Worker::RunMergeThread, merge_thread_.get());
 
     // Now that the worker threads are up, scan the partitions.
-    if (partition_verification) {
+    if (perform_verification_) {
         update_verify_->VerifyUpdatePartition();
     }
 
@@ -450,11 +433,6 @@
     struct utsname uts;
     unsigned int major, minor;
 
-    if (android::base::GetBoolProperty("snapuserd.test.io_uring.force_disable", false)) {
-        SNAP_LOG(INFO) << "io_uring disabled for testing";
-        return false;
-    }
-
     if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) {
         SNAP_LOG(ERROR) << "Could not parse the kernel version from uname. "
                         << " io_uring not supported";
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
index 42237ef..777aa07 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -301,7 +301,8 @@
 class SnapshotHandler : public std::enable_shared_from_this<SnapshotHandler> {
   public:
     SnapshotHandler(std::string misc_name, std::string cow_device, std::string backing_device,
-                    std::string base_path_merge);
+                    std::string base_path_merge, int num_workers, bool use_iouring,
+                    bool perform_verification);
     bool InitCowDevice();
     bool Start();
 
@@ -369,8 +370,6 @@
     // Total number of blocks to be merged in a given read-ahead buffer region
     void SetMergedBlockCountForNextCommit(int x) { total_ra_blocks_merged_ = x; }
     int GetTotalBlocksToMerge() { return total_ra_blocks_merged_; }
-    void SetSocketPresent(bool socket) { is_socket_present_ = socket; }
-    void SetIouringEnabled(bool io_uring_enabled) { is_io_uring_enabled_ = io_uring_enabled; }
     bool MergeInitiated() { return merge_initiated_; }
     bool MergeMonitored() { return merge_monitored_; }
     double GetMergePercentage() { return merge_completion_percentage_; }
@@ -441,9 +440,10 @@
     bool merge_initiated_ = false;
     bool merge_monitored_ = false;
     bool attached_ = false;
-    bool is_socket_present_;
     bool is_io_uring_enabled_ = false;
     bool scratch_space_ = false;
+    int num_worker_threads_ = kNumWorkerThreads;
+    bool perform_verification_ = true;
 
     std::unique_ptr<struct io_uring> ring_;
     std::unique_ptr<UpdateVerify> update_verify_;
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index d437d32..c953f1a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -29,6 +29,7 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/scopeguard.h>
+#include <android-base/strings.h>
 #include <fs_mgr/file_wait.h>
 #include <snapuserd/snapuserd_client.h>
 #include "snapuserd_server.h"
@@ -44,28 +45,9 @@
 using android::base::borrowed_fd;
 using android::base::unique_fd;
 
-DaemonOps UserSnapshotServer::Resolveop(std::string& input) {
-    if (input == "init") return DaemonOps::INIT;
-    if (input == "start") return DaemonOps::START;
-    if (input == "stop") return DaemonOps::STOP;
-    if (input == "query") return DaemonOps::QUERY;
-    if (input == "delete") return DaemonOps::DELETE;
-    if (input == "detach") return DaemonOps::DETACH;
-    if (input == "supports") return DaemonOps::SUPPORTS;
-    if (input == "initiate_merge") return DaemonOps::INITIATE;
-    if (input == "merge_percent") return DaemonOps::PERCENTAGE;
-    if (input == "getstatus") return DaemonOps::GETSTATUS;
-    if (input == "update-verify") return DaemonOps::UPDATE_VERIFY;
-
-    return DaemonOps::INVALID;
-}
-
 UserSnapshotServer::UserSnapshotServer() {
-    monitor_merge_event_fd_.reset(eventfd(0, EFD_CLOEXEC));
-    if (monitor_merge_event_fd_ == -1) {
-        PLOG(FATAL) << "monitor_merge_event_fd_: failed to create eventfd";
-    }
     terminating_ = false;
+    handlers_ = std::make_unique<SnapshotHandlerManager>();
 }
 
 UserSnapshotServer::~UserSnapshotServer() {
@@ -98,12 +80,9 @@
 
 void UserSnapshotServer::ShutdownThreads() {
     terminating_ = true;
-    JoinAllThreads();
+    handlers_->JoinAllThreads();
 }
 
-UserSnapshotDmUserHandler::UserSnapshotDmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd)
-    : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
-
 bool UserSnapshotServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
     ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
     if (ret < 0) {
@@ -134,226 +113,118 @@
 
     std::vector<std::string> out;
     Parsemsg(str, delim, out);
-    DaemonOps op = Resolveop(out[0]);
 
-    switch (op) {
-        case DaemonOps::INIT: {
-            // Message format:
-            // init,<misc_name>,<cow_device_path>,<backing_device>,<base_path_merge>
-            //
-            // Reads the metadata and send the number of sectors
-            if (out.size() != 5) {
-                LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-
-            auto handler = AddHandler(out[1], out[2], out[3], out[4]);
-            if (!handler) {
-                return Sendmsg(fd, "fail");
-            }
-
-            auto retval = "success," + std::to_string(handler->snapuserd()->GetNumSectors());
-            return Sendmsg(fd, retval);
-        }
-        case DaemonOps::START: {
-            // Message format:
-            // start,<misc_name>
-            //
-            // Start the new thread which binds to dm-user misc device
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed start message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-
-            std::lock_guard<std::mutex> lock(lock_);
-            auto iter = FindHandler(&lock, out[1]);
-            if (iter == dm_users_.end()) {
-                LOG(ERROR) << "Could not find handler: " << out[1];
-                return Sendmsg(fd, "fail");
-            }
-            if (!(*iter)->snapuserd() || (*iter)->snapuserd()->IsAttached()) {
-                LOG(ERROR) << "Tried to re-attach control device: " << out[1];
-                return Sendmsg(fd, "fail");
-            }
-            if (!StartHandler(*iter)) {
-                return Sendmsg(fd, "fail");
-            }
-            return Sendmsg(fd, "success");
-        }
-        case DaemonOps::STOP: {
-            // Message format: stop
-            //
-            // Stop all the threads gracefully and then shutdown the
-            // main thread
-            SetTerminating();
-            ShutdownThreads();
-            return true;
-        }
-        case DaemonOps::QUERY: {
-            // Message format: query
-            //
-            // As part of transition, Second stage daemon will be
-            // created before terminating the first stage daemon. Hence,
-            // for a brief period client may have to distiguish between
-            // first stage daemon and second stage daemon.
-            //
-            // Second stage daemon is marked as active and hence will
-            // be ready to receive control message.
-            return Sendmsg(fd, GetDaemonStatus());
-        }
-        case DaemonOps::DELETE: {
-            // Message format:
-            // delete,<misc_name>
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-            {
-                std::lock_guard<std::mutex> lock(lock_);
-                auto iter = FindHandler(&lock, out[1]);
-                if (iter == dm_users_.end()) {
-                    // After merge is completed, we swap dm-user table with
-                    // the underlying dm-linear base device. Hence, worker
-                    // threads would have terminted and was removed from
-                    // the list.
-                    LOG(DEBUG) << "Could not find handler: " << out[1];
-                    return Sendmsg(fd, "success");
-                }
-
-                if (!(*iter)->ThreadTerminated()) {
-                    (*iter)->snapuserd()->NotifyIOTerminated();
-                }
-            }
-            if (!RemoveAndJoinHandler(out[1])) {
-                return Sendmsg(fd, "fail");
-            }
-            return Sendmsg(fd, "success");
-        }
-        case DaemonOps::DETACH: {
-            std::lock_guard<std::mutex> lock(lock_);
-            TerminateMergeThreads(&lock);
-            terminating_ = true;
-            return true;
-        }
-        case DaemonOps::SUPPORTS: {
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed supports message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-            if (out[1] == "second_stage_socket_handoff") {
-                return Sendmsg(fd, "success");
-            }
+    const auto& cmd = out[0];
+    if (cmd == "init") {
+        // Message format:
+        // init,<misc_name>,<cow_device_path>,<backing_device>,<base_path_merge>
+        //
+        // Reads the metadata and send the number of sectors
+        if (out.size() != 5) {
+            LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
             return Sendmsg(fd, "fail");
         }
-        case DaemonOps::INITIATE: {
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed initiate-merge message, " << out.size() << " parts";
-                return Sendmsg(fd, "fail");
-            }
-            if (out[0] == "initiate_merge") {
-                std::lock_guard<std::mutex> lock(lock_);
-                auto iter = FindHandler(&lock, out[1]);
-                if (iter == dm_users_.end()) {
-                    LOG(ERROR) << "Could not find handler: " << out[1];
-                    return Sendmsg(fd, "fail");
-                }
 
-                if (!StartMerge(&lock, *iter)) {
-                    return Sendmsg(fd, "fail");
-                }
-
-                return Sendmsg(fd, "success");
-            }
+        auto handler = AddHandler(out[1], out[2], out[3], out[4]);
+        if (!handler) {
             return Sendmsg(fd, "fail");
         }
-        case DaemonOps::PERCENTAGE: {
-            std::lock_guard<std::mutex> lock(lock_);
-            double percentage = GetMergePercentage(&lock);
 
-            return Sendmsg(fd, std::to_string(percentage));
+        auto retval = "success," + std::to_string(handler->snapuserd()->GetNumSectors());
+        return Sendmsg(fd, retval);
+    } else if (cmd == "start") {
+        // Message format:
+        // start,<misc_name>
+        //
+        // Start the new thread which binds to dm-user misc device
+        if (out.size() != 2) {
+            LOG(ERROR) << "Malformed start message, " << out.size() << " parts";
+            return Sendmsg(fd, "fail");
         }
-        case DaemonOps::GETSTATUS: {
-            // Message format:
-            // getstatus,<misc_name>
-            if (out.size() != 2) {
-                LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
-                return Sendmsg(fd, "snapshot-merge-failed");
-            }
-            {
-                std::lock_guard<std::mutex> lock(lock_);
-                auto iter = FindHandler(&lock, out[1]);
-                if (iter == dm_users_.end()) {
-                    LOG(ERROR) << "Could not find handler: " << out[1];
-                    return Sendmsg(fd, "snapshot-merge-failed");
-                }
 
-                std::string merge_status = GetMergeStatus(*iter);
-                return Sendmsg(fd, merge_status);
-            }
+        if (!handlers_->StartHandler(out[1])) {
+            return Sendmsg(fd, "fail");
         }
-        case DaemonOps::UPDATE_VERIFY: {
-            std::lock_guard<std::mutex> lock(lock_);
-            if (!UpdateVerification(&lock)) {
-                return Sendmsg(fd, "fail");
-            }
-
+        return Sendmsg(fd, "success");
+    } else if (cmd == "stop") {
+        // Message format: stop
+        //
+        // Stop all the threads gracefully and then shutdown the
+        // main thread
+        SetTerminating();
+        ShutdownThreads();
+        return true;
+    } else if (cmd == "query") {
+        // Message format: query
+        //
+        // As part of transition, Second stage daemon will be
+        // created before terminating the first stage daemon. Hence,
+        // for a brief period client may have to distiguish between
+        // first stage daemon and second stage daemon.
+        //
+        // Second stage daemon is marked as active and hence will
+        // be ready to receive control message.
+        return Sendmsg(fd, GetDaemonStatus());
+    } else if (cmd == "delete") {
+        // Message format:
+        // delete,<misc_name>
+        if (out.size() != 2) {
+            LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
+            return Sendmsg(fd, "fail");
+        }
+        if (!handlers_->DeleteHandler(out[1])) {
+            return Sendmsg(fd, "fail");
+        }
+        return Sendmsg(fd, "success");
+    } else if (cmd == "detach") {
+        handlers_->TerminateMergeThreads();
+        terminating_ = true;
+        return true;
+    } else if (cmd == "supports") {
+        if (out.size() != 2) {
+            LOG(ERROR) << "Malformed supports message, " << out.size() << " parts";
+            return Sendmsg(fd, "fail");
+        }
+        if (out[1] == "second_stage_socket_handoff") {
             return Sendmsg(fd, "success");
         }
-        default: {
-            LOG(ERROR) << "Received unknown message type from client";
-            Sendmsg(fd, "fail");
-            return false;
+        return Sendmsg(fd, "fail");
+    } else if (cmd == "initiate_merge") {
+        if (out.size() != 2) {
+            LOG(ERROR) << "Malformed initiate-merge message, " << out.size() << " parts";
+            return Sendmsg(fd, "fail");
         }
-    }
-}
-
-void UserSnapshotServer::RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler) {
-    LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
-
-    if (!handler->snapuserd()->Start()) {
-        LOG(ERROR) << " Failed to launch all worker threads";
-    }
-
-    handler->snapuserd()->CloseFds();
-    bool merge_completed = handler->snapuserd()->CheckMergeCompletionStatus();
-    handler->snapuserd()->UnmapBufferRegion();
-
-    auto misc_name = handler->misc_name();
-    LOG(INFO) << "Handler thread about to exit: " << misc_name;
-
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        if (merge_completed) {
-            num_partitions_merge_complete_ += 1;
-            active_merge_threads_ -= 1;
-            WakeupMonitorMergeThread();
+        if (out[0] == "initiate_merge") {
+            if (!handlers_->InitiateMerge(out[1])) {
+                return Sendmsg(fd, "fail");
+            }
+            return Sendmsg(fd, "success");
         }
-        handler->SetThreadTerminated();
-        auto iter = FindHandler(&lock, handler->misc_name());
-        if (iter == dm_users_.end()) {
-            // RemoveAndJoinHandler() already removed us from the list, and is
-            // now waiting on a join(), so just return. Additionally, release
-            // all the resources held by snapuserd object which are shared
-            // by worker threads. This should be done when the last reference
-            // of "handler" is released; but we will explicitly release here
-            // to make sure snapuserd object is freed as it is the biggest
-            // consumer of memory in the daemon.
-            handler->FreeResources();
-            LOG(INFO) << "Exiting handler thread to allow for join: " << misc_name;
-            return;
+        return Sendmsg(fd, "fail");
+    } else if (cmd == "merge_percent") {
+        double percentage = handlers_->GetMergePercentage();
+        return Sendmsg(fd, std::to_string(percentage));
+    } else if (cmd == "getstatus") {
+        // Message format:
+        // getstatus,<misc_name>
+        if (out.size() != 2) {
+            LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
+            return Sendmsg(fd, "snapshot-merge-failed");
         }
-
-        LOG(INFO) << "Exiting handler thread and freeing resources: " << misc_name;
-
-        if (handler->snapuserd()->IsAttached()) {
-            handler->thread().detach();
+        auto status = handlers_->GetMergeStatus(out[1]);
+        if (status.empty()) {
+            return Sendmsg(fd, "snapshot-merge-failed");
         }
-
-        // Important: free resources within the lock. This ensures that if
-        // WaitForDelete() is called, the handler is either in the list, or
-        // it's not and its resources are guaranteed to be freed.
-        handler->FreeResources();
-        dm_users_.erase(iter);
+        return Sendmsg(fd, status);
+    } else if (cmd == "update-verify") {
+        if (!handlers_->GetVerificationStatus()) {
+            return Sendmsg(fd, "fail");
+        }
+        return Sendmsg(fd, "success");
+    } else {
+        LOG(ERROR) << "Received unknown message type from client";
+        Sendmsg(fd, "fail");
+        return false;
     }
 }
 
@@ -422,28 +293,10 @@
         }
     }
 
-    JoinAllThreads();
+    handlers_->JoinAllThreads();
     return true;
 }
 
-void UserSnapshotServer::JoinAllThreads() {
-    // Acquire the thread list within the lock.
-    std::vector<std::shared_ptr<UserSnapshotDmUserHandler>> dm_users;
-    {
-        std::lock_guard<std::mutex> guard(lock_);
-        dm_users = std::move(dm_users_);
-    }
-
-    for (auto& client : dm_users) {
-        auto& th = client->thread();
-
-        if (th.joinable()) th.join();
-    }
-
-    stop_monitor_merge_thread_ = true;
-    WakeupMonitorMergeThread();
-}
-
 void UserSnapshotServer::AddWatchedFd(android::base::borrowed_fd fd, int events) {
     struct pollfd p = {};
     p.fd = fd.get();
@@ -483,191 +336,35 @@
     SetTerminating();
 }
 
-std::shared_ptr<UserSnapshotDmUserHandler> UserSnapshotServer::AddHandler(
-        const std::string& misc_name, const std::string& cow_device_path,
-        const std::string& backing_device, const std::string& base_path_merge) {
-    auto snapuserd = std::make_shared<SnapshotHandler>(misc_name, cow_device_path, backing_device,
-                                                       base_path_merge);
-    if (!snapuserd->InitCowDevice()) {
-        LOG(ERROR) << "Failed to initialize Snapuserd";
-        return nullptr;
+std::shared_ptr<HandlerThread> UserSnapshotServer::AddHandler(const std::string& misc_name,
+                                                              const std::string& cow_device_path,
+                                                              const std::string& backing_device,
+                                                              const std::string& base_path_merge) {
+    // We will need multiple worker threads only during
+    // device boot after OTA. For all other purposes,
+    // one thread is sufficient. We don't want to consume
+    // unnecessary memory especially during OTA install phase
+    // when daemon will be up during entire post install phase.
+    //
+    // During boot up, we need multiple threads primarily for
+    // update-verification.
+    int num_worker_threads = kNumWorkerThreads;
+    if (is_socket_present_) {
+        num_worker_threads = 1;
     }
 
-    snapuserd->SetSocketPresent(is_socket_present_);
-    snapuserd->SetIouringEnabled(io_uring_enabled_);
-
-    if (!snapuserd->InitializeWorkers()) {
-        LOG(ERROR) << "Failed to initialize workers";
-        return nullptr;
+    bool perform_verification = true;
+    if (android::base::EndsWith(misc_name, "-init") || is_socket_present_) {
+        perform_verification = false;
     }
 
-    auto handler = std::make_shared<UserSnapshotDmUserHandler>(snapuserd);
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        if (FindHandler(&lock, misc_name) != dm_users_.end()) {
-            LOG(ERROR) << "Handler already exists: " << misc_name;
-            return nullptr;
-        }
-        dm_users_.push_back(handler);
-    }
-    return handler;
-}
-
-bool UserSnapshotServer::StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
-    if (handler->snapuserd()->IsAttached()) {
-        LOG(ERROR) << "Handler already attached";
-        return false;
-    }
-
-    handler->snapuserd()->AttachControlDevice();
-
-    handler->thread() = std::thread(std::bind(&UserSnapshotServer::RunThread, this, handler));
-    return true;
-}
-
-bool UserSnapshotServer::StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
-                                    const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
-    CHECK(proof_of_lock);
-
-    if (!handler->snapuserd()->IsAttached()) {
-        LOG(ERROR) << "Handler not attached to dm-user - Merge thread cannot be started";
-        return false;
-    }
-
-    handler->snapuserd()->MonitorMerge();
-
-    if (!is_merge_monitor_started_.has_value()) {
-        std::thread(&UserSnapshotServer::MonitorMerge, this).detach();
-        is_merge_monitor_started_ = true;
-    }
-
-    merge_handlers_.push(handler);
-    WakeupMonitorMergeThread();
-    return true;
-}
-
-auto UserSnapshotServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
-                                     const std::string& misc_name) -> HandlerList::iterator {
-    CHECK(proof_of_lock);
-
-    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
-        if ((*iter)->misc_name() == misc_name) {
-            return iter;
-        }
-    }
-    return dm_users_.end();
-}
-
-void UserSnapshotServer::TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock) {
-    CHECK(proof_of_lock);
-
-    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
-        if (!(*iter)->ThreadTerminated()) {
-            (*iter)->snapuserd()->NotifyIOTerminated();
-        }
-    }
-}
-
-std::string UserSnapshotServer::GetMergeStatus(
-        const std::shared_ptr<UserSnapshotDmUserHandler>& handler) {
-    return handler->snapuserd()->GetMergeStatus();
-}
-
-double UserSnapshotServer::GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock) {
-    CHECK(proof_of_lock);
-    double percentage = 0.0;
-    int n = 0;
-
-    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
-        auto& th = (*iter)->thread();
-        if (th.joinable()) {
-            // Merge percentage by individual partitions wherein merge is still
-            // in-progress
-            percentage += (*iter)->snapuserd()->GetMergePercentage();
-            n += 1;
-        }
-    }
-
-    // Calculate final merge including those partitions where merge was already
-    // completed - num_partitions_merge_complete_ will track them when each
-    // thread exists in RunThread.
-    int total_partitions = n + num_partitions_merge_complete_;
-
-    if (total_partitions) {
-        percentage = ((num_partitions_merge_complete_ * 100.0) + percentage) / total_partitions;
-    }
-
-    LOG(DEBUG) << "Merge %: " << percentage
-               << " num_partitions_merge_complete_: " << num_partitions_merge_complete_
-               << " total_partitions: " << total_partitions << " n: " << n;
-    return percentage;
-}
-
-bool UserSnapshotServer::RemoveAndJoinHandler(const std::string& misc_name) {
-    std::shared_ptr<UserSnapshotDmUserHandler> handler;
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-
-        auto iter = FindHandler(&lock, misc_name);
-        if (iter == dm_users_.end()) {
-            // Client already deleted.
-            return true;
-        }
-        handler = std::move(*iter);
-        dm_users_.erase(iter);
-    }
-
-    auto& th = handler->thread();
-    if (th.joinable()) {
-        th.join();
-    }
-    return true;
-}
-
-void UserSnapshotServer::WakeupMonitorMergeThread() {
-    uint64_t notify = 1;
-    ssize_t rc = TEMP_FAILURE_RETRY(write(monitor_merge_event_fd_.get(), &notify, sizeof(notify)));
-    if (rc < 0) {
-        PLOG(FATAL) << "failed to notify monitor merge thread";
-    }
-}
-
-void UserSnapshotServer::MonitorMerge() {
-    while (!stop_monitor_merge_thread_) {
-        uint64_t testVal;
-        ssize_t ret =
-                TEMP_FAILURE_RETRY(read(monitor_merge_event_fd_.get(), &testVal, sizeof(testVal)));
-        if (ret == -1) {
-            PLOG(FATAL) << "Failed to read from eventfd";
-        } else if (ret == 0) {
-            LOG(FATAL) << "Hit EOF on eventfd";
-        }
-
-        LOG(INFO) << "MonitorMerge: active-merge-threads: " << active_merge_threads_;
-        {
-            std::lock_guard<std::mutex> lock(lock_);
-            while (active_merge_threads_ < kMaxMergeThreads && merge_handlers_.size() > 0) {
-                auto handler = merge_handlers_.front();
-                merge_handlers_.pop();
-
-                if (!handler->snapuserd()) {
-                    LOG(INFO) << "MonitorMerge: skipping deleted handler: " << handler->misc_name();
-                    continue;
-                }
-
-                LOG(INFO) << "Starting merge for partition: "
-                          << handler->snapuserd()->GetMiscName();
-                handler->snapuserd()->InitiateMerge();
-                active_merge_threads_ += 1;
-            }
-        }
-    }
-
-    LOG(INFO) << "Exiting MonitorMerge: size: " << merge_handlers_.size();
+    return handlers_->AddHandler(misc_name, cow_device_path, backing_device, base_path_merge,
+                                 num_worker_threads, io_uring_enabled_, perform_verification);
 }
 
 bool UserSnapshotServer::WaitForSocket() {
-    auto scope_guard = android::base::make_scope_guard([this]() -> void { JoinAllThreads(); });
+    auto scope_guard =
+            android::base::make_scope_guard([this]() -> void { handlers_->JoinAllThreads(); });
 
     auto socket_path = ANDROID_SOCKET_DIR "/"s + kSnapuserdSocketProxy;
 
@@ -764,21 +461,8 @@
     return true;
 }
 
-bool UserSnapshotServer::UpdateVerification(std::lock_guard<std::mutex>* proof_of_lock) {
-    CHECK(proof_of_lock);
-
-    bool status = true;
-    for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
-        auto& th = (*iter)->thread();
-        if (th.joinable() && status) {
-            status = (*iter)->snapuserd()->CheckPartitionVerification() && status;
-        } else {
-            // return immediately if there is a failure
-            return false;
-        }
-    }
-
-    return status;
+bool UserSnapshotServer::StartHandler(const std::string& misc_name) {
+    return handlers_->StartHandler(misc_name);
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
index c2af61f..988c01a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.h
@@ -31,6 +31,7 @@
 #include <vector>
 
 #include <android-base/unique_fd.h>
+#include "handler_manager.h"
 #include "snapuserd_core.h"
 
 namespace android {
@@ -39,48 +40,6 @@
 static constexpr uint32_t kMaxPacketSize = 512;
 static constexpr uint8_t kMaxMergeThreads = 2;
 
-enum class DaemonOps {
-    INIT,
-    START,
-    QUERY,
-    STOP,
-    DELETE,
-    DETACH,
-    SUPPORTS,
-    INITIATE,
-    PERCENTAGE,
-    GETSTATUS,
-    UPDATE_VERIFY,
-    INVALID,
-};
-
-class UserSnapshotDmUserHandler {
-  public:
-    explicit UserSnapshotDmUserHandler(std::shared_ptr<SnapshotHandler> snapuserd);
-
-    void FreeResources() {
-        // Each worker thread holds a reference to snapuserd.
-        // Clear them so that all the resources
-        // held by snapuserd is released
-        if (snapuserd_) {
-            snapuserd_->FreeResources();
-            snapuserd_ = nullptr;
-        }
-    }
-    const std::shared_ptr<SnapshotHandler>& snapuserd() const { return snapuserd_; }
-    std::thread& thread() { return thread_; }
-
-    const std::string& misc_name() const { return misc_name_; }
-    bool ThreadTerminated() { return thread_terminated_; }
-    void SetThreadTerminated() { thread_terminated_ = true; }
-
-  private:
-    std::thread thread_;
-    std::shared_ptr<SnapshotHandler> snapuserd_;
-    std::string misc_name_;
-    bool thread_terminated_ = false;
-};
-
 class UserSnapshotServer {
   private:
     android::base::unique_fd sockfd_;
@@ -88,21 +47,12 @@
     volatile bool received_socket_signal_ = false;
     std::vector<struct pollfd> watched_fds_;
     bool is_socket_present_ = false;
-    int num_partitions_merge_complete_ = 0;
-    int active_merge_threads_ = 0;
-    bool stop_monitor_merge_thread_ = false;
     bool is_server_running_ = false;
     bool io_uring_enabled_ = false;
-    std::optional<bool> is_merge_monitor_started_;
-
-    android::base::unique_fd monitor_merge_event_fd_;
+    std::unique_ptr<ISnapshotHandlerManager> handlers_;
 
     std::mutex lock_;
 
-    using HandlerList = std::vector<std::shared_ptr<UserSnapshotDmUserHandler>>;
-    HandlerList dm_users_;
-    std::queue<std::shared_ptr<UserSnapshotDmUserHandler>> merge_handlers_;
-
     void AddWatchedFd(android::base::borrowed_fd fd, int events);
     void AcceptClient();
     bool HandleClient(android::base::borrowed_fd fd, int revents);
@@ -111,28 +61,14 @@
     bool Receivemsg(android::base::borrowed_fd fd, const std::string& str);
 
     void ShutdownThreads();
-    bool RemoveAndJoinHandler(const std::string& control_device);
-    DaemonOps Resolveop(std::string& input);
     std::string GetDaemonStatus();
     void Parsemsg(std::string const& msg, const char delim, std::vector<std::string>& out);
 
     bool IsTerminating() { return terminating_; }
 
-    void RunThread(std::shared_ptr<UserSnapshotDmUserHandler> handler);
-    void MonitorMerge();
-
     void JoinAllThreads();
     bool StartWithSocket(bool start_listening);
 
-    // Find a UserSnapshotDmUserHandler within a lock.
-    HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
-                                      const std::string& misc_name);
-
-    double GetMergePercentage(std::lock_guard<std::mutex>* proof_of_lock);
-    void TerminateMergeThreads(std::lock_guard<std::mutex>* proof_of_lock);
-
-    bool UpdateVerification(std::lock_guard<std::mutex>* proof_of_lock);
-
   public:
     UserSnapshotServer();
     ~UserSnapshotServer();
@@ -143,16 +79,12 @@
     bool RunForSocketHandoff();
     bool WaitForSocket();
 
-    std::shared_ptr<UserSnapshotDmUserHandler> AddHandler(const std::string& misc_name,
-                                                          const std::string& cow_device_path,
-                                                          const std::string& backing_device,
-                                                          const std::string& base_path_merge);
-    bool StartHandler(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
-    bool StartMerge(std::lock_guard<std::mutex>* proof_of_lock,
-                    const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
-    std::string GetMergeStatus(const std::shared_ptr<UserSnapshotDmUserHandler>& handler);
+    std::shared_ptr<HandlerThread> AddHandler(const std::string& misc_name,
+                                              const std::string& cow_device_path,
+                                              const std::string& backing_device,
+                                              const std::string& base_path_merge);
+    bool StartHandler(const std::string& misc_name);
 
-    void WakeupMonitorMergeThread();
     void SetTerminating() { terminating_ = true; }
     void ReceivedSocketSignal() { received_socket_signal_ = true; }
     void SetServerRunning() { is_server_running_ = true; }
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index 1421403..57f9e7a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -37,9 +37,9 @@
 #include <libdm/dm.h>
 #include <libdm/loop_control.h>
 #include <libsnapshot/cow_writer.h>
-#include <snapuserd/snapuserd_client.h>
 #include <storage_literals/storage_literals.h>
 
+#include "handler_manager.h"
 #include "snapuserd_core.h"
 
 DEFINE_string(force_config, "", "Force testing mode with iouring disabled");
@@ -54,8 +54,6 @@
 using namespace android::dm;
 using namespace std;
 
-static constexpr char kSnapuserdSocketTest[] = "snapuserdTest";
-
 class Tempdevice {
   public:
     Tempdevice(const std::string& name, const DmTable& table)
@@ -68,15 +66,15 @@
     }
     ~Tempdevice() {
         if (valid_) {
-            dm_.DeleteDevice(name_);
+            dm_.DeleteDeviceIfExists(name_);
         }
     }
     bool Destroy() {
         if (!valid_) {
-            return false;
+            return true;
         }
         valid_ = false;
-        return dm_.DeleteDevice(name_);
+        return dm_.DeleteDeviceIfExists(name_);
     }
     const std::string& path() const { return path_; }
     const std::string& name() const { return name_; }
@@ -138,7 +136,6 @@
     void SetDeviceControlName();
     void InitDaemon();
     void CreateDmUserDevice();
-    void StartSnapuserdDaemon();
 
     unique_ptr<LoopDevice> base_loop_;
     unique_ptr<Tempdevice> dmuser_dev_;
@@ -148,9 +145,9 @@
 
     unique_fd base_fd_;
     std::unique_ptr<TemporaryFile> cow_system_;
-    std::unique_ptr<SnapuserdClient> client_;
     std::unique_ptr<uint8_t[]> orig_buffer_;
     std::unique_ptr<uint8_t[]> merged_buffer_;
+    SnapshotHandlerManager handlers_;
     bool setup_ok_ = false;
     bool merge_ok_ = false;
     size_t size_ = 100_MiB;
@@ -180,9 +177,9 @@
     ASSERT_TRUE(dmuser_dev_->Destroy());
 
     auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
-    ASSERT_TRUE(client_->WaitForDeviceDelete(system_device_ctrl_name_));
+    ASSERT_TRUE(handlers_.DeleteHandler(system_device_ctrl_name_));
     ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s));
-    ASSERT_TRUE(client_->DetachSnapuserd());
+    handlers_.TerminateMergeThreads();
 }
 
 bool SnapuserdTest::SetupDefault() {
@@ -217,8 +214,6 @@
 bool SnapuserdTest::SetupDaemon() {
     SetDeviceControlName();
 
-    StartSnapuserdDaemon();
-
     CreateDmUserDevice();
     InitCowDevice();
     InitDaemon();
@@ -228,20 +223,6 @@
     return setup_ok_;
 }
 
-void SnapuserdTest::StartSnapuserdDaemon() {
-    pid_t pid = fork();
-    ASSERT_GE(pid, 0);
-    if (pid == 0) {
-        std::string arg0 = "/system/bin/snapuserd";
-        std::string arg1 = "-socket="s + kSnapuserdSocketTest;
-        char* const argv[] = {arg0.data(), arg1.data(), nullptr};
-        ASSERT_GE(execv(arg0.c_str(), argv), 0);
-    } else {
-        client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s);
-        ASSERT_NE(client_, nullptr);
-    }
-}
-
 void SnapuserdTest::CreateBaseDevice() {
     unique_fd rnd_fd;
 
@@ -606,9 +587,17 @@
 }
 
 void SnapuserdTest::InitCowDevice() {
-    uint64_t num_sectors = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
-                                                  base_loop_->device(), base_loop_->device());
-    ASSERT_NE(num_sectors, 0);
+    bool use_iouring = true;
+    if (FLAGS_force_config == "iouring_disabled") {
+        use_iouring = false;
+    }
+
+    auto handler =
+            handlers_.AddHandler(system_device_ctrl_name_, cow_system_->path, base_loop_->device(),
+                                 base_loop_->device(), 1, use_iouring, false);
+    ASSERT_NE(handler, nullptr);
+    ASSERT_NE(handler->snapuserd(), nullptr);
+    ASSERT_NE(handler->snapuserd()->GetNumSectors(), 0);
 }
 
 void SnapuserdTest::SetDeviceControlName() {
@@ -646,13 +635,12 @@
 }
 
 void SnapuserdTest::InitDaemon() {
-    bool ok = client_->AttachDmUser(system_device_ctrl_name_);
-    ASSERT_TRUE(ok);
+    ASSERT_TRUE(handlers_.StartHandler(system_device_ctrl_name_));
 }
 
 void SnapuserdTest::CheckMergeCompletion() {
     while (true) {
-        double percentage = client_->GetMergePercent();
+        double percentage = handlers_.GetMergePercentage();
         if ((int)percentage == 100) {
             break;
         }
@@ -667,8 +655,6 @@
 
     SetDeviceControlName();
 
-    StartSnapuserdDaemon();
-
     CreateDmUserDevice();
     InitCowDevice();
     InitDaemon();
@@ -684,8 +670,7 @@
 }
 
 void SnapuserdTest::StartMerge() {
-    bool ok = client_->InitiateMerge(system_device_ctrl_name_);
-    ASSERT_TRUE(ok);
+    ASSERT_TRUE(handlers_.InitiateMerge(system_device_ctrl_name_));
 }
 
 void SnapuserdTest::ValidateMerge() {
@@ -699,7 +684,6 @@
     Shutdown();
     std::this_thread::sleep_for(500ms);
     SetDeviceControlName();
-    StartSnapuserdDaemon();
     CreateDmUserDevice();
     InitCowDevice();
     InitDaemon();
@@ -859,20 +843,5 @@
 
     gflags::ParseCommandLineFlags(&argc, &argv, false);
 
-    android::base::SetProperty("ctl.stop", "snapuserd");
-
-    if (FLAGS_force_config == "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";
-        }
-    }
-
-    int ret = RUN_ALL_TESTS();
-
-    if (FLAGS_force_config == "iouring_disabled") {
-        android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0");
-    }
-
-    return ret;
+    return RUN_ALL_TESTS();
 }
diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
deleted file mode 100644
index cc12d1d..0000000
--- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto
+++ /dev/null
@@ -1,92 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-// A subset of system/update_engine/update_metadata.proto. A separate file is
-// used here because:
-// - The original file is optimized for LITE_RUNTIME, but fuzzing needs
-// reflection.
-// - The definition here has less fields. libsnapshot only uses fields declared
-// here, and all fields declared here are fuzzed by libsnapshot_fuzzer. If
-// libsnapshot uses more fields in system/update_engine/update_metadata.proto
-// in the future, they must be added here too, otherwise it will fail to
-// compile.
-//
-// It is okay that this file is older than
-// system/update_engine/update_metadata.proto as long as the messages defined
-// here can also be parsed by protobuf defined there. However, it is not
-// okay to add fields here without adding them to
-// system/update_engine/update_metadata.proto. Doing so will cause a compiler
-// error when libsnapshot code starts to use these dangling fields.
-
-syntax = "proto2";
-
-package chromeos_update_engine;
-
-message Extent {
-    optional uint64 start_block = 1;
-    optional uint64 num_blocks = 2;
-}
-
-message PartitionInfo {
-    optional uint64 size = 1;
-}
-
-message InstallOperation {
-    enum Type {
-        SOURCE_COPY = 4;
-        // Not used by libsnapshot. Declared here so that the fuzzer has an
-        // alternative value to use for |type|.
-        ZERO = 6;
-    }
-    required Type type = 1;
-    repeated Extent src_extents = 4;
-    repeated Extent dst_extents = 6;
-}
-
-message PartitionUpdate {
-    required string partition_name = 1;
-    optional PartitionInfo new_partition_info = 7;
-    repeated InstallOperation operations = 8;
-    optional Extent hash_tree_extent = 11;
-    optional Extent fec_extent = 15;
-    optional uint64 estimate_cow_size = 19;
-}
-
-message DynamicPartitionGroup {
-    required string name = 1;
-    optional uint64 size = 2;
-    repeated string partition_names = 3;
-}
-
-message VABCFeatureSet {
-  optional bool threaded = 1;
-  optional bool batch_writes = 2;
-}
-
-message DynamicPartitionMetadata {
-    repeated DynamicPartitionGroup groups = 1;
-    optional bool vabc_enabled = 3;
-    optional string vabc_compression_param = 4;
-    optional uint32 cow_version = 5;
-    // A collection of knobs to tune Virtual AB Compression
-    optional VABCFeatureSet vabc_feature_set = 6;
-}
-
-message DeltaArchiveManifest {
-    repeated PartitionUpdate partitions = 13;
-    optional DynamicPartitionMetadata dynamic_partition_metadata = 15;
-    optional bool partial_update = 16;
-}
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index a98bf0e..1ffa89c 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -29,6 +29,7 @@
 #include <fs_mgr/roots.h>
 #include <liblp/property_fetcher.h>
 
+using android::dm::DeviceMapper;
 using android::dm::kSectorSize;
 using android::fiemap::FiemapStatus;
 using android::fs_mgr::EnsurePathMounted;
@@ -251,7 +252,10 @@
         LOG(INFO) << "Userspace snapshots disabled for testing";
         return false;
     }
-
+    if (!KernelSupportsCompressedSnapshots()) {
+        LOG(ERROR) << "Userspace snapshots requested, but no kernel support is available.";
+        return false;
+    }
     return true;
 }
 
@@ -278,5 +282,10 @@
     return fetcher->GetBoolProperty("snapuserd.test.dm.snapshots", false);
 }
 
+bool KernelSupportsCompressedSnapshots() {
+    auto& dm = DeviceMapper::Instance();
+    return dm.GetTargetByName("user", nullptr);
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index 8c4c7c6..370f3c4 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -127,6 +127,8 @@
 void AppendExtent(google::protobuf::RepeatedPtrField<chromeos_update_engine::Extent>* extents,
                   uint64_t start_block, uint64_t num_blocks);
 
+bool KernelSupportsCompressedSnapshots();
+
 bool GetLegacyCompressionEnabledProperty();
 bool GetUserspaceSnapshotsEnabledProperty();
 bool GetIouringEnabledProperty();
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index 10efd0c..7273087 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -53,6 +53,7 @@
     std::cerr << "  getpath <dm-name>" << std::endl;
     std::cerr << "  getuuid <dm-name>" << std::endl;
     std::cerr << "  info <dm-name>" << std::endl;
+    std::cerr << "  replace <dm-name> <targets...>" << std::endl;
     std::cerr << "  status <dm-name>" << std::endl;
     std::cerr << "  resume <dm-name>" << std::endl;
     std::cerr << "  suspend <dm-name>" << std::endl;
diff --git a/gatekeeperd/OWNERS b/gatekeeperd/OWNERS
index 9c99c6e..04cd19e 100644
--- a/gatekeeperd/OWNERS
+++ b/gatekeeperd/OWNERS
@@ -1,2 +1,5 @@
+# Bug component: 1124862
+drysdale@google.com
+oarbildo@google.com
+subrahmanyaman@google.com
 swillden@google.com
-jdanis@google.com
diff --git a/gatekeeperd/gatekeeperd.cpp b/gatekeeperd/gatekeeperd.cpp
index 76fcd55..eb43a33 100644
--- a/gatekeeperd/gatekeeperd.cpp
+++ b/gatekeeperd/gatekeeperd.cpp
@@ -151,7 +151,7 @@
     void clear_sid(uint32_t userId) {
         char filename[21];
         snprintf(filename, sizeof(filename), "%u", userId);
-        if (remove(filename) < 0) {
+        if (remove(filename) < 0 && errno != ENOENT) {
             ALOGE("%s: could not remove file [%s], attempting 0 write", __func__, strerror(errno));
             store_sid(userId, 0);
         }
diff --git a/healthd/healthd.rc b/healthd/healthd.rc
deleted file mode 100644
index 8e2ebb6..0000000
--- a/healthd/healthd.rc
+++ /dev/null
@@ -1,4 +0,0 @@
-service healthd /system/bin/healthd
-    class hal
-    critical
-    group root system wakelock
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index 1ce174b..26af13b 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -88,7 +88,7 @@
 #define POWER_ON_KEY_TIME (2 * MSEC_PER_SEC)
 #define UNPLUGGED_SHUTDOWN_TIME (10 * MSEC_PER_SEC)
 #define UNPLUGGED_DISPLAY_TIME (3 * MSEC_PER_SEC)
-#define MAX_BATT_LEVEL_WAIT_TIME (3 * MSEC_PER_SEC)
+#define MAX_BATT_LEVEL_WAIT_TIME (5 * MSEC_PER_SEC)
 #define UNPLUGGED_SHUTDOWN_TIME_PROP "ro.product.charger.unplugged_shutdown_time"
 
 #define LAST_KMSG_MAX_SZ (32 * 1024)
@@ -620,6 +620,18 @@
         kick_animation(&batt_anim_);
     }
     health_info_ = health_info;
+
+    if (property_get_bool("ro.charger_mode_autoboot", false)) {
+        if (health_info_.battery_level >= boot_min_cap_) {
+            if (property_get_bool("ro.enable_boot_charger_mode", false)) {
+                LOGW("booting from charger mode\n");
+                property_set("sys.boot_from_charger_mode", "1");
+            } else {
+                LOGW("Battery SOC = %d%%, Automatically rebooting\n", health_info_.battery_level);
+                reboot(RB_AUTOBOOT);
+            }
+        }
+    }
 }
 
 int Charger::OnPrepareToWait(void) {
diff --git a/init/Android.bp b/init/Android.bp
index 1aba4b3..7b52903 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -502,11 +502,13 @@
         "libbase",
         "libcutils",
         "libselinux",
-        "libhidl-gen-utils",
         "liblog",
         "libprocessgroup",
         "libprotobuf-cpp-lite",
     ],
+    static_libs: [
+        "libhidl-gen-utils",
+    ],
 }
 
 cc_library_static {
diff --git a/init/README.md b/init/README.md
index b006365..6bdff4a 100644
--- a/init/README.md
+++ b/init/README.md
@@ -642,17 +642,17 @@
   the current SELinux policy or its parent if not specified in the policy. If
   the directory exists, its security context will not be changed (even if
   different from the policy).
-
-  > _action_ can be one of:
-  * `None`: take no encryption action; directory will be encrypted if parent is.
-  * `Require`: encrypt directory, abort boot process if encryption fails
-  * `Attempt`: try to set an encryption policy, but continue if it fails
-  * `DeleteIfNecessary`: recursively delete directory if necessary to set
-  encryption policy.
-
-  > _key_ can be one of:
-  * `ref`: use the systemwide DE key
-  * `per_boot_ref`: use the key freshly generated on each boot.
+>
+> _action_ can be one of:
+>  * `None`: take no encryption action; directory will be encrypted if parent is.
+>  * `Require`: encrypt directory, abort boot process if encryption fails
+>  * `Attempt`: try to set an encryption policy, but continue if it fails
+>  * `DeleteIfNecessary`: recursively delete directory if necessary to set
+>  encryption policy.
+>
+> _key_ can be one of:
+>  * `ref`: use the systemwide DE key
+>  * `per_boot_ref`: use the key freshly generated on each boot.
 
 `mount_all [ <fstab> ] [--<option>]`
 > Calls fs\_mgr\_mount\_all on the given fs\_mgr-format fstab with optional
diff --git a/init/fuzzer/init_parser_fuzzer.cpp b/init/fuzzer/init_parser_fuzzer.cpp
index e6a78a2..dc76465 100644
--- a/init/fuzzer/init_parser_fuzzer.cpp
+++ b/init/fuzzer/init_parser_fuzzer.cpp
@@ -125,7 +125,7 @@
     std::string path = fdp_.ConsumeBool() ? fdp_.PickValueInArray(kValidPaths)
                                           : fdp_.ConsumeRandomLengthString(kMaxBytes);
     parser.ParseConfig(path);
-    parser.ParseConfigFileInsecure(path);
+    parser.ParseConfigFileInsecure(path, false /* follow_symlinks */);
 }
 
 void InitParserFuzzer::Process() {
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index db127d3..f070776 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -326,7 +326,9 @@
             }
         }
     } else {
-        if (!parser.ParseConfigFileInsecure(*argv)) {
+        if (!parser.ParseConfigFileInsecure(*argv, true /* follow_symlinks */)) {
+          // Follow symlinks as inputs during build execution in Bazel's
+          // execution root are symlinks, unlike Soong or Make.
             LOG(ERROR) << "Failed to open init rc script '" << *argv << "'";
             return EXIT_FAILURE;
         }
diff --git a/init/init.cpp b/init/init.cpp
index c965fe6..be1ebee 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -247,48 +247,21 @@
         WakeMainInitThread();
     }
 
-    std::optional<std::string> CheckShutdown() {
+    std::optional<std::string> CheckShutdown() __attribute__((warn_unused_result)) {
         auto lock = std::lock_guard{shutdown_command_lock_};
         if (do_shutdown_ && !IsShuttingDown()) {
+            do_shutdown_ = false;
             return shutdown_command_;
         }
         return {};
     }
 
-    bool do_shutdown() const { return do_shutdown_; }
-    void set_do_shutdown(bool value) { do_shutdown_ = value; }
-
   private:
     std::mutex shutdown_command_lock_;
     std::string shutdown_command_ GUARDED_BY(shutdown_command_lock_);
     bool do_shutdown_ = false;
 } shutdown_state;
 
-static void UnwindMainThreadStack() {
-    unwindstack::AndroidLocalUnwinder unwinder;
-    unwindstack::AndroidUnwinderData data;
-    if (!unwinder.Unwind(data)) {
-        LOG(ERROR) << __FUNCTION__
-                   << "sys.powerctl: Failed to unwind callstack: " << data.GetErrorString();
-    }
-    for (const auto& frame : data.frames) {
-        LOG(ERROR) << "sys.powerctl: " << unwinder.FormatFrame(frame);
-    }
-}
-
-void DebugRebootLogging() {
-    LOG(INFO) << "sys.powerctl: do_shutdown: " << shutdown_state.do_shutdown()
-              << " IsShuttingDown: " << IsShuttingDown();
-    if (shutdown_state.do_shutdown()) {
-        LOG(ERROR) << "sys.powerctl set while a previous shutdown command has not been handled";
-        UnwindMainThreadStack();
-    }
-    if (IsShuttingDown()) {
-        LOG(ERROR) << "sys.powerctl set while init is already shutting down";
-        UnwindMainThreadStack();
-    }
-}
-
 void DumpState() {
     ServiceList::GetInstance().DumpState();
     ActionManager::GetInstance().DumpState();
@@ -1109,36 +1082,43 @@
     // Restore prio before main loop
     setpriority(PRIO_PROCESS, 0, 0);
     while (true) {
-        // By default, sleep until something happens.
-        std::optional<std::chrono::milliseconds> epoll_timeout;
+        // By default, sleep until something happens. Do not convert far_future into
+        // std::chrono::milliseconds because that would trigger an overflow. The unit of boot_clock
+        // is 1ns.
+        const boot_clock::time_point far_future = boot_clock::time_point::max();
+        boot_clock::time_point next_action_time = far_future;
 
         auto shutdown_command = shutdown_state.CheckShutdown();
         if (shutdown_command) {
             LOG(INFO) << "Got shutdown_command '" << *shutdown_command
                       << "' Calling HandlePowerctlMessage()";
             HandlePowerctlMessage(*shutdown_command);
-            shutdown_state.set_do_shutdown(false);
         }
 
         if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
             am.ExecuteOneCommand();
+            // If there's more work to do, wake up again immediately.
+            if (am.HasMoreCommands()) {
+                next_action_time = boot_clock::now();
+            }
         }
+        // Since the above code examined pending actions, no new actions must be
+        // queued by the code between this line and the Epoll::Wait() call below
+        // without calling WakeMainInitThread().
         if (!IsShuttingDown()) {
             auto next_process_action_time = HandleProcessActions();
 
             // If there's a process that needs restarting, wake up in time for that.
             if (next_process_action_time) {
-                epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
-                        *next_process_action_time - boot_clock::now());
-                if (epoll_timeout < 0ms) epoll_timeout = 0ms;
+                next_action_time = std::min(next_action_time, *next_process_action_time);
             }
         }
 
-        if (!(prop_waiter_state.MightBeWaiting() || Service::is_exec_service_running())) {
-            // If there's more work to do, wake up again immediately.
-            if (am.HasMoreCommands()) epoll_timeout = 0ms;
+        std::optional<std::chrono::milliseconds> epoll_timeout;
+        if (next_action_time != far_future) {
+            epoll_timeout = std::chrono::ceil<std::chrono::milliseconds>(
+                    std::max(next_action_time - boot_clock::now(), 0ns));
         }
-
         auto epoll_result = epoll.Wait(epoll_timeout);
         if (!epoll_result.ok()) {
             LOG(ERROR) << epoll_result.error();
diff --git a/init/init.h b/init/init.h
index 063632a..9c7e918 100644
--- a/init/init.h
+++ b/init/init.h
@@ -42,8 +42,6 @@
 void PropertyChanged(const std::string& name, const std::string& value);
 bool QueueControlMessage(const std::string& message, const std::string& name, pid_t pid, int fd);
 
-void DebugRebootLogging();
-
 int SecondStageMain(int argc, char** argv);
 
 int StopServicesFromApex(const std::string& apex_name);
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 305bf95..0fc3ffc 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -204,6 +204,10 @@
         GTEST_SKIP() << "Must run on userdebug/eng builds. b/262090304";
         return;
     }
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Must be run as root.";
+        return;
+    }
     std::string init_script = R"init(
 service console /system/bin/sh
     class core
diff --git a/init/parser.cpp b/init/parser.cpp
index adb41ad..8c0bb2b 100644
--- a/init/parser.cpp
+++ b/init/parser.cpp
@@ -131,9 +131,9 @@
     }
 }
 
-bool Parser::ParseConfigFileInsecure(const std::string& path) {
+bool Parser::ParseConfigFileInsecure(const std::string& path, bool follow_symlinks = false) {
     std::string config_contents;
-    if (!android::base::ReadFileToString(path, &config_contents)) {
+    if (!android::base::ReadFileToString(path, &config_contents, follow_symlinks)) {
         return false;
     }
 
diff --git a/init/parser.h b/init/parser.h
index 980ae0c..8e5bca7 100644
--- a/init/parser.h
+++ b/init/parser.h
@@ -77,7 +77,7 @@
     void AddSingleLineParser(const std::string& prefix, LineCallback callback);
 
     // Host init verifier check file permissions.
-    bool ParseConfigFileInsecure(const std::string& path);
+    bool ParseConfigFileInsecure(const std::string& path, bool follow_symlinks);
 
     size_t parse_error_count() const { return parse_error_count_; }
 
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 87ffdb9..8da6982 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -550,9 +550,6 @@
         }
         LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                   << process_log_string;
-        if (!value.empty()) {
-            DebugRebootLogging();
-        }
         if (value == "reboot,userspace" && !is_userspace_reboot_supported().value_or(false)) {
             *error = "Userspace reboot is not supported by this device";
             return {PROP_ERROR_INVALID_VALUE};
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 062ed39..907eb80 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -761,15 +761,7 @@
 
 constexpr size_t kKlogMessageSize = 1024;
 
-void SelinuxAvcLog(char* buf, size_t buf_len) {
-    CHECK_GT(buf_len, 0u);
-
-    size_t str_len = strnlen(buf, buf_len);
-    // trim newline at end of string
-    if (buf[str_len - 1] == '\n') {
-        buf[str_len - 1] = '\0';
-    }
-
+void SelinuxAvcLog(char* buf) {
     struct NetlinkMessage {
         nlmsghdr hdr;
         char buf[kKlogMessageSize];
@@ -835,8 +827,17 @@
     if (length_written <= 0) {
         return 0;
     }
+
+    // libselinux log messages usually contain a new line character, while
+    // Android LOG() does not expect it. Remove it to avoid empty lines in
+    // the log buffers.
+    size_t str_len = strlen(buf);
+    if (buf[str_len - 1] == '\n') {
+        buf[str_len - 1] = '\0';
+    }
+
     if (type == SELINUX_AVC) {
-        SelinuxAvcLog(buf, sizeof(buf));
+        SelinuxAvcLog(buf);
     } else {
         android::base::KernelLogger(android::base::MAIN, severity, "selinux", nullptr, 0, buf);
     }
diff --git a/init/service.cpp b/init/service.cpp
index cce24c3..35beaad 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -447,6 +447,16 @@
         return {};
     }
 
+    // On newer kernels, /dev/console will always exist because
+    // "console=ttynull" is hard-coded in CONFIG_CMDLINE. This new boot
+    // property should be set via "androidboot.serialconsole=0" to explicitly
+    // disable services requiring the console. For older kernels and boot
+    // images, not setting this at all will fall back to the old behavior
+    if (GetProperty("ro.boot.serialconsole", "") == "0") {
+        flags_ |= SVC_DISABLED;
+        return {};
+    }
+
     if (proc_attr_.console.empty()) {
         proc_attr_.console = "/dev/" + GetProperty("ro.boot.console", "console");
     }
diff --git a/init/test_kill_services/init_kill_services_test.cpp b/init/test_kill_services/init_kill_services_test.cpp
index 66a3328..5355703 100644
--- a/init/test_kill_services/init_kill_services_test.cpp
+++ b/init/test_kill_services/init_kill_services_test.cpp
@@ -29,8 +29,8 @@
 
     const std::string initial_pid = GetProperty(pid_prop, "");
 
-    EXPECT_EQ("running", GetProperty(status_prop, "")) << status_prop;
-    EXPECT_NE("", initial_pid) << pid_prop;
+    ASSERT_EQ("running", GetProperty(status_prop, "")) << status_prop;
+    ASSERT_NE("", initial_pid) << pid_prop;
 
     EXPECT_EQ(0, system(("kill -9 " + initial_pid).c_str()));
 
diff --git a/init/test_upgrade_mte/mte_upgrade_test.rc b/init/test_upgrade_mte/mte_upgrade_test.rc
index a3e596c..aa6c18f 100644
--- a/init/test_upgrade_mte/mte_upgrade_test.rc
+++ b/init/test_upgrade_mte/mte_upgrade_test.rc
@@ -16,9 +16,11 @@
   class late_start
   disabled
   seclabel u:r:su:s0
+  user root
 
 service mte_upgrade_test_helper_overridden /system/bin/mte_upgrade_test_helper ${sys.mte_crash_test_uuid}
   class late_start
   disabled
   seclabel u:r:su:s0
+  user root
   setenv BIONIC_MEMTAG_UPGRADE_SECS 0
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index a6835fc..79d79dd 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -83,11 +83,15 @@
     { 00751, AID_ROOT,         AID_SHELL,        0, "product/apex/*/bin" },
     { 00777, AID_ROOT,         AID_ROOT,         0, "sdcard" },
     { 00751, AID_ROOT,         AID_SDCARD_R,     0, "storage" },
+    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system/apex/com.android.tethering/bin/for-system" },
+    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system/apex/com.android.tethering.inprocess/bin/for-system" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/bin" },
     { 00755, AID_ROOT,         AID_ROOT,         0, "system/etc/ppp" },
     { 00755, AID_ROOT,         AID_SHELL,        0, "system/vendor" },
     { 00750, AID_ROOT,         AID_SHELL,        0, "system/xbin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/apex/*/bin" },
+    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system_ext/apex/com.android.tethering/bin/for-system" },
+    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/apex/*/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "vendor/bin" },
@@ -194,6 +198,10 @@
 
     // the following files have enhanced capabilities and ARE included
     // in user builds.
+    { 06755, AID_CLAT,      AID_CLAT,      0, "system/apex/com.android.tethering/bin/for-system/clatd" },
+    { 06755, AID_CLAT,      AID_CLAT,      0, "system/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
+    { 06755, AID_CLAT,      AID_CLAT,      0, "system_ext/apex/com.android.tethering/bin/for-system/clatd" },
+    { 06755, AID_CLAT,      AID_CLAT,      0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
     { 00700, AID_SYSTEM,    AID_SHELL,     CAP_MASK_LONG(CAP_BLOCK_SUSPEND),
                                               "system/bin/inputflinger" },
     { 00750, AID_ROOT,      AID_SHELL,     CAP_MASK_LONG(CAP_SETUID) |
diff --git a/libmodprobe/Android.bp b/libmodprobe/Android.bp
index 525a880..1d94a96 100644
--- a/libmodprobe/Android.bp
+++ b/libmodprobe/Android.bp
@@ -10,6 +10,7 @@
     vendor_available: true,
     ramdisk_available: true,
     recovery_available: true,
+    host_supported: true,
     srcs: [
         "libmodprobe.cpp",
         "libmodprobe_ext.cpp",
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index e071c96..1971f01 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -562,7 +562,7 @@
         // Attempt to match both the canonical module name and the module filename.
         if (!fnmatch(pattern.c_str(), module.c_str(), 0)) {
             rv.emplace_back(module);
-        } else if (!fnmatch(pattern.c_str(), basename(deps[0].c_str()), 0)) {
+        } else if (!fnmatch(pattern.c_str(), android::base::Basename(deps[0]).c_str(), 0)) {
             rv.emplace_back(deps[0]);
         }
     }
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 9b2d775..48bc0b7 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -36,6 +36,7 @@
 
 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);
+bool SetUserProfiles(uid_t uid, const std::vector<std::string>& profiles);
 
 __END_DECLS
 
@@ -75,6 +76,11 @@
 // that it only returns 0 in the case that the cgroup exists and it contains no processes.
 int killProcessGroupOnce(uid_t uid, int initialPid, int signal, int* max_processes = nullptr);
 
+// Sends the provided signal to all members of a process group, but does not wait for processes to
+// exit, or for the cgroup to be removed. Callers should also ensure that killProcessGroup is called
+// later to ensure the cgroup is fully removed, otherwise system resources may leak.
+int sendSignalToProcessGroup(uid_t uid, int initialPid, int signal);
+
 int createProcessGroup(uid_t uid, int initialPid, bool memControl = false);
 
 // Set various properties of a process group. For these functions to work, the process group must
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 384a8f0..a021594 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -200,6 +200,11 @@
     return SetProcessProfiles(uid, pid, std::span<const std::string_view>(profiles_));
 }
 
+bool SetUserProfiles(uid_t uid, const std::vector<std::string>& profiles) {
+    return TaskProfiles::GetInstance().SetUserProfiles(uid, std::span<const std::string>(profiles),
+                                                       false);
+}
+
 static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
     return StringPrintf("%s/uid_%d", cgroup, uid);
 }
@@ -372,6 +377,7 @@
     std::set<pid_t> pgids;
     pgids.emplace(initialPid);
     std::set<pid_t> pids;
+    int processes = 0;
 
     std::unique_ptr<FILE, decltype(&fclose)> fd(nullptr, fclose);
 
@@ -390,6 +396,7 @@
         pid_t pid;
         bool file_is_empty = true;
         while (fscanf(fd.get(), "%d\n", &pid) == 1 && pid >= 0) {
+            processes++;
             file_is_empty = false;
             if (pid == 0) {
                 // Should never happen...  but if it does, trying to kill this
@@ -419,15 +426,12 @@
         }
     }
 
-    int processes = 0;
     // Kill all process groups.
     for (const auto pgid : pgids) {
         LOG(VERBOSE) << "Killing process group " << -pgid << " in uid " << uid
                      << " as part of process cgroup " << initialPid;
 
-        if (kill(-pgid, signal) == 0) {
-            processes++;
-        } else if (errno != ESRCH) {
+        if (kill(-pgid, signal) == -1 && errno != ESRCH) {
             PLOG(WARNING) << "kill(" << -pgid << ", " << signal << ") failed";
         }
     }
@@ -437,9 +441,7 @@
         LOG(VERBOSE) << "Killing pid " << pid << " in uid " << uid << " as part of process cgroup "
                      << initialPid;
 
-        if (kill(pid, signal) == 0) {
-            processes++;
-        } else if (errno != ESRCH) {
+        if (kill(pid, signal) == -1 && errno != ESRCH) {
             PLOG(WARNING) << "kill(" << pid << ", " << signal << ") failed";
         }
     }
@@ -449,6 +451,9 @@
 
 static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries,
                             int* max_processes) {
+    CHECK_GE(uid, 0);
+    CHECK_GT(initialPid, 0);
+
     std::string hierarchy_root_path;
     if (CgroupsAvailable()) {
         CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
@@ -537,6 +542,15 @@
     return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/, max_processes);
 }
 
+int sendSignalToProcessGroup(uid_t uid, int initialPid, int signal) {
+    std::string hierarchy_root_path;
+    if (CgroupsAvailable()) {
+        CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
+    }
+    const char* cgroup = hierarchy_root_path.c_str();
+    return DoKillProcessGroupOnce(cgroup, uid, initialPid, signal);
+}
+
 static int createProcessGroupInternal(uid_t uid, int initialPid, std::string cgroup,
                                       bool activate_controllers) {
     auto uid_path = ConvertUidToPath(cgroup.c_str(), uid);
@@ -585,7 +599,8 @@
 }
 
 int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
-    std::string cgroup;
+    CHECK_GE(uid, 0);
+    CHECK_GT(initialPid, 0);
 
     if (memControl && !UsePerAppMemcg()) {
         PLOG(ERROR) << "service memory controls are used without per-process memory cgroup support";
@@ -603,6 +618,7 @@
         }
     }
 
+    std::string cgroup;
     CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cgroup);
     return createProcessGroupInternal(uid, initialPid, cgroup, true);
 }
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index 3e4393d..d013ec8 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -1,13 +1,6 @@
 {
   "Cgroups": [
     {
-      "Controller": "blkio",
-      "Path": "/dev/blkio",
-      "Mode": "0775",
-      "UID": "system",
-      "GID": "system"
-    },
-    {
       "Controller": "cpu",
       "Path": "/dev/cpuctl",
       "Mode": "0755",
@@ -39,6 +32,12 @@
       {
         "Controller": "freezer",
         "Path": "."
+      },
+      {
+        "Controller": "io",
+        "Path": ".",
+        "NeedsActivation": true,
+        "Optional": true
       }
     ]
   }
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index e44d3bf..12f7b44 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -457,14 +457,6 @@
       "Name": "LowIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": "background"
-          }
-        },
-        {
           "Name": "SetAttribute",
           "Params":
           {
@@ -497,14 +489,6 @@
       "Name": "NormalIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        },
-        {
           "Name": "SetAttribute",
           "Params":
           {
@@ -537,14 +521,6 @@
       "Name": "HighIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        },
-        {
           "Name": "SetAttribute",
           "Params":
           {
@@ -577,14 +553,6 @@
       "Name": "MaxIoPriority",
       "Actions": [
         {
-          "Name": "JoinCgroup",
-          "Params":
-          {
-            "Controller": "blkio",
-            "Path": ""
-          }
-        },
-        {
           "Name": "SetAttribute",
           "Params":
           {
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 4db7372..1731828 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -139,6 +139,17 @@
     return true;
 }
 
+bool ProfileAttribute::GetPathForUID(uid_t uid, std::string* path) const {
+    if (path == nullptr) {
+        return true;
+    }
+
+    const std::string& file_name =
+            controller()->version() == 2 && !file_v2_name_.empty() ? file_v2_name_ : file_name_;
+    *path = StringPrintf("%s/uid_%d/%s", controller()->path(), uid, file_name.c_str());
+    return true;
+}
+
 bool SetClampsAction::ExecuteForProcess(uid_t, pid_t) const {
     // TODO: add support when kernel supports util_clamp
     LOG(WARNING) << "SetClampsAction::ExecuteForProcess is not supported";
@@ -225,6 +236,29 @@
     return true;
 }
 
+bool SetAttributeAction::ExecuteForUID(uid_t uid) const {
+    std::string path;
+
+    if (!attribute_->GetPathForUID(uid, &path)) {
+        LOG(ERROR) << "Failed to find cgroup for uid " << uid;
+        return false;
+    }
+
+    if (!WriteStringToFile(value_, path)) {
+        if (access(path.c_str(), F_OK) < 0) {
+            if (optional_) {
+                return true;
+            } else {
+                LOG(ERROR) << "No such cgroup attribute: " << path;
+                return false;
+            }
+        }
+        PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
+        return false;
+    }
+    return true;
+}
+
 SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
     : controller_(c), path_(p) {
     FdCacheHelper::Init(controller_.GetTasksFilePath(path_), fd_[ProfileAction::RCT_TASK]);
@@ -552,6 +586,16 @@
     return true;
 }
 
+bool TaskProfile::ExecuteForUID(uid_t uid) const {
+    for (const auto& element : elements_) {
+        if (!element->ExecuteForUID(uid)) {
+            LOG(VERBOSE) << "Applying profile action " << element->Name() << " failed";
+            return false;
+        }
+    }
+    return true;
+}
+
 void TaskProfile::EnableResourceCaching(ProfileAction::ResourceCacheType cache_type) {
     if (res_cached_) {
         return;
@@ -805,6 +849,24 @@
 }
 
 template <typename T>
+bool TaskProfiles::SetUserProfiles(uid_t uid, std::span<const T> profiles, bool use_fd_cache) {
+    for (const auto& name : profiles) {
+        TaskProfile* profile = GetProfile(name);
+        if (profile != nullptr) {
+            if (use_fd_cache) {
+                profile->EnableResourceCaching(ProfileAction::RCT_PROCESS);
+            }
+            if (!profile->ExecuteForUID(uid)) {
+                PLOG(WARNING) << "Failed to apply " << name << " process profile";
+            }
+        } else {
+            PLOG(WARNING) << "Failed to find " << name << "process profile";
+        }
+    }
+    return true;
+}
+
+template <typename T>
 bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid, std::span<const T> profiles,
                                       bool use_fd_cache) {
     bool success = true;
@@ -857,3 +919,5 @@
                                             bool use_fd_cache);
 template bool TaskProfiles::SetTaskProfiles(int tid, std::span<const std::string_view> profiles,
                                             bool use_fd_cache);
+template bool TaskProfiles::SetUserProfiles(uid_t uid, std::span<const std::string> profiles,
+                                            bool use_fd_cache);
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 85b3f91..a8ecb87 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -36,6 +36,7 @@
     virtual const CgroupController* controller() const = 0;
     virtual const std::string& file_name() const = 0;
     virtual bool GetPathForTask(int tid, std::string* path) const = 0;
+    virtual bool GetPathForUID(uid_t uid, std::string* path) const = 0;
 };
 
 class ProfileAttribute : public IProfileAttribute {
@@ -53,6 +54,7 @@
     void Reset(const CgroupController& controller, const std::string& file_name) override;
 
     bool GetPathForTask(int tid, std::string* path) const override;
+    bool GetPathForUID(uid_t uid, std::string* path) const override;
 
   private:
     CgroupController controller_;
@@ -72,6 +74,7 @@
     // Default implementations will fail
     virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; };
     virtual bool ExecuteForTask(int) const { return false; };
+    virtual bool ExecuteForUID(uid_t) const { return false; };
 
     virtual void EnableResourceCaching(ResourceCacheType) {}
     virtual void DropResourceCaching(ResourceCacheType) {}
@@ -116,6 +119,7 @@
     const char* Name() const override { return "SetAttribute"; }
     bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
     bool ExecuteForTask(int tid) const override;
+    bool ExecuteForUID(uid_t uid) const override;
 
   private:
     const IProfileAttribute* attribute_;
@@ -179,6 +183,7 @@
 
     bool ExecuteForProcess(uid_t uid, pid_t pid) const;
     bool ExecuteForTask(int tid) const;
+    bool ExecuteForUID(uid_t uid) const;
     void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type);
     void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
 
@@ -216,6 +221,8 @@
     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);
+    template <typename T>
+    bool SetUserProfiles(uid_t uid, std::span<const T> profiles, bool use_fd_cache);
 
   private:
     TaskProfiles();
diff --git a/libprocessgroup/task_profiles_test.cpp b/libprocessgroup/task_profiles_test.cpp
index 09ac44c..6a5b48b 100644
--- a/libprocessgroup/task_profiles_test.cpp
+++ b/libprocessgroup/task_profiles_test.cpp
@@ -16,6 +16,7 @@
 
 #include "task_profiles.h"
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <gtest/gtest.h>
 #include <mntent.h>
 #include <processgroup/processgroup.h>
@@ -29,13 +30,14 @@
 using ::android::base::LogId;
 using ::android::base::LogSeverity;
 using ::android::base::SetLogger;
+using ::android::base::Split;
 using ::android::base::VERBOSE;
 using ::testing::TestWithParam;
 using ::testing::Values;
 
 namespace {
 
-bool IsCgroupV2Mounted() {
+bool IsCgroupV2MountedRw() {
     std::unique_ptr<FILE, int (*)(FILE*)> mnts(setmntent("/proc/mounts", "re"), endmntent);
     if (!mnts) {
         LOG(ERROR) << "Failed to open /proc/mounts";
@@ -43,9 +45,11 @@
     }
     struct mntent* mnt;
     while ((mnt = getmntent(mnts.get()))) {
-        if (strcmp(mnt->mnt_fsname, "cgroup2") == 0) {
-            return true;
+        if (strcmp(mnt->mnt_type, "cgroup2") != 0) {
+            continue;
         }
+        const std::vector<std::string> options = Split(mnt->mnt_opts, ",");
+        return std::count(options.begin(), options.end(), "ro") == 0;
     }
     return false;
 }
@@ -121,6 +125,10 @@
         return true;
     };
 
+    bool GetPathForUID(uid_t, std::string*) const override {
+        return false;
+    }
+
   private:
     const std::string file_name_;
 };
@@ -141,8 +149,9 @@
 };
 
 TEST_P(SetAttributeFixture, SetAttribute) {
-    // Treehugger runs host tests inside a container without cgroupv2 support.
-    if (!IsCgroupV2Mounted()) {
+    // Treehugger runs host tests inside a container either without cgroupv2
+    // support or with the cgroup filesystem mounted read-only.
+    if (!IsCgroupV2MountedRw()) {
         GTEST_SKIP();
         return;
     }
diff --git a/libsparse/backed_block.cpp b/libsparse/backed_block.cpp
index 6229e7c..a0d1cde 100644
--- a/libsparse/backed_block.cpp
+++ b/libsparse/backed_block.cpp
@@ -315,6 +315,10 @@
   bb->len = len;
   bb->type = BACKED_BLOCK_FILE;
   bb->file.filename = strdup(filename);
+  if (!bb->file.filename) {
+    free(bb);
+    return -ENOMEM;
+  }
   bb->file.offset = offset;
   bb->next = nullptr;
 
@@ -359,14 +363,17 @@
   new_bb->len = bb->len - max_len;
   new_bb->block = bb->block + max_len / bbl->block_size;
   new_bb->next = bb->next;
-  bb->next = new_bb;
-  bb->len = max_len;
 
   switch (bb->type) {
     case BACKED_BLOCK_DATA:
       new_bb->data.data = (char*)bb->data.data + max_len;
       break;
     case BACKED_BLOCK_FILE:
+      new_bb->file.filename = strdup(bb->file.filename);
+      if (!new_bb->file.filename) {
+        free(new_bb);
+        return -ENOMEM;
+      }
       new_bb->file.offset += max_len;
       break;
     case BACKED_BLOCK_FD:
@@ -376,5 +383,7 @@
       break;
   }
 
+  bb->next = new_bb;
+  bb->len = max_len;
   return 0;
 }
diff --git a/libsparse/output_file.cpp b/libsparse/output_file.cpp
index cb5d730..08312e4 100644
--- a/libsparse/output_file.cpp
+++ b/libsparse/output_file.cpp
@@ -58,6 +58,8 @@
 
 #define container_of(inner, outer_t, elem) ((outer_t*)((char*)(inner)-offsetof(outer_t, elem)))
 
+static constexpr size_t kMaxMmapSize = 256 * 1024 * 1024;
+
 struct output_file_ops {
   int (*open)(struct output_file*, int fd);
   int (*skip)(struct output_file*, int64_t);
@@ -71,6 +73,7 @@
   int (*write_fill_chunk)(struct output_file* out, uint64_t len, uint32_t fill_val);
   int (*write_skip_chunk)(struct output_file* out, uint64_t len);
   int (*write_end_chunk)(struct output_file* out);
+  int (*write_fd_chunk)(struct output_file* out, uint64_t len, int fd, int64_t offset);
 };
 
 struct output_file {
@@ -318,6 +321,26 @@
   return 0;
 }
 
+template <typename T>
+static bool write_fd_chunk_range(int fd, int64_t offset, uint64_t len, T callback) {
+  uint64_t bytes_written = 0;
+  int64_t current_offset = offset;
+  while (bytes_written < len) {
+    size_t mmap_size = std::min(static_cast<uint64_t>(kMaxMmapSize), len - bytes_written);
+    auto m = android::base::MappedFile::FromFd(fd, current_offset, mmap_size, PROT_READ);
+    if (!m) {
+      error("failed to mmap region of length %zu", mmap_size);
+      return false;
+    }
+    if (!callback(m->data(), mmap_size)) {
+      return false;
+    }
+    bytes_written += mmap_size;
+    current_offset += mmap_size;
+  }
+  return true;
+}
+
 static int write_sparse_skip_chunk(struct output_file* out, uint64_t skip_len) {
   chunk_header_t chunk_header;
   int ret;
@@ -424,6 +447,61 @@
   return 0;
 }
 
+static int write_sparse_fd_chunk(struct output_file* out, uint64_t len, int fd, int64_t offset) {
+  chunk_header_t chunk_header;
+  uint64_t rnd_up_len, zero_len;
+  int ret;
+
+  /* Round up the data length to a multiple of the block size */
+  rnd_up_len = ALIGN(len, out->block_size);
+  zero_len = rnd_up_len - len;
+
+  /* Finally we can safely emit a chunk of data */
+  chunk_header.chunk_type = CHUNK_TYPE_RAW;
+  chunk_header.reserved1 = 0;
+  chunk_header.chunk_sz = rnd_up_len / out->block_size;
+  chunk_header.total_sz = CHUNK_HEADER_LEN + rnd_up_len;
+  ret = out->ops->write(out, &chunk_header, sizeof(chunk_header));
+
+  if (ret < 0) return -1;
+  bool ok = write_fd_chunk_range(fd, offset, len, [&ret, out](char* data, size_t size) -> bool {
+    ret = out->ops->write(out, data, size);
+    if (ret < 0) return false;
+    if (out->use_crc) {
+      out->crc32 = sparse_crc32(out->crc32, data, size);
+    }
+    return true;
+  });
+  if (!ok) return -1;
+  if (zero_len) {
+    uint64_t len = zero_len;
+    uint64_t write_len;
+    while (len) {
+      write_len = std::min(len, (uint64_t)FILL_ZERO_BUFSIZE);
+      ret = out->ops->write(out, out->zero_buf, write_len);
+      if (ret < 0) {
+        return ret;
+      }
+      len -= write_len;
+    }
+
+    if (out->use_crc) {
+      uint64_t len = zero_len;
+      uint64_t write_len;
+      while (len) {
+        write_len = std::min(len, (uint64_t)FILL_ZERO_BUFSIZE);
+        out->crc32 = sparse_crc32(out->crc32, out->zero_buf, write_len);
+        len -= write_len;
+      }
+    }
+  }
+
+  out->cur_out_ptr += rnd_up_len;
+  out->chunk_cnt++;
+
+  return 0;
+}
+
 int write_sparse_end_chunk(struct output_file* out) {
   chunk_header_t chunk_header;
   int ret;
@@ -454,6 +532,7 @@
     .write_fill_chunk = write_sparse_fill_chunk,
     .write_skip_chunk = write_sparse_skip_chunk,
     .write_end_chunk = write_sparse_end_chunk,
+    .write_fd_chunk = write_sparse_fd_chunk,
 };
 
 static int write_normal_data_chunk(struct output_file* out, uint64_t len, void* data) {
@@ -495,6 +574,23 @@
   return 0;
 }
 
+static int write_normal_fd_chunk(struct output_file* out, uint64_t len, int fd, int64_t offset) {
+  int ret;
+  uint64_t rnd_up_len = ALIGN(len, out->block_size);
+
+  bool ok = write_fd_chunk_range(fd, offset, len, [&ret, out](char* data, size_t size) -> bool {
+    ret = out->ops->write(out, data, size);
+    return ret >= 0;
+  });
+  if (!ok) return ret;
+
+  if (rnd_up_len > len) {
+    ret = out->ops->skip(out, rnd_up_len - len);
+  }
+
+  return ret;
+}
+
 static int write_normal_skip_chunk(struct output_file* out, uint64_t len) {
   return out->ops->skip(out, len);
 }
@@ -508,6 +604,7 @@
     .write_fill_chunk = write_normal_fill_chunk,
     .write_skip_chunk = write_normal_skip_chunk,
     .write_end_chunk = write_normal_end_chunk,
+    .write_fd_chunk = write_normal_fd_chunk,
 };
 
 void output_file_close(struct output_file* out) {
@@ -670,10 +767,7 @@
 }
 
 int write_fd_chunk(struct output_file* out, uint64_t len, int fd, int64_t offset) {
-  auto m = android::base::MappedFile::FromFd(fd, offset, len, PROT_READ);
-  if (!m) return -errno;
-
-  return out->sparse_ops->write_data_chunk(out, m->size(), m->data());
+  return out->sparse_ops->write_fd_chunk(out, len, fd, offset);
 }
 
 /* Write a contiguous region of data blocks from a file */
diff --git a/libsparse/sparse.cpp b/libsparse/sparse.cpp
index 396e7eb..ca7e5fe 100644
--- a/libsparse/sparse.cpp
+++ b/libsparse/sparse.cpp
@@ -260,8 +260,8 @@
   return s->block_size;
 }
 
-static struct backed_block* move_chunks_up_to_len(struct sparse_file* from, struct sparse_file* to,
-                                                  unsigned int len) {
+static int move_chunks_up_to_len(struct sparse_file* from, struct sparse_file* to, unsigned int len,
+                                 backed_block** out_bb) {
   int64_t count = 0;
   struct output_file* out_counter;
   struct backed_block* last_bb = nullptr;
@@ -282,7 +282,7 @@
   out_counter = output_file_open_callback(out_counter_write, &count, to->block_size, to->len, false,
                                           true, 0, false);
   if (!out_counter) {
-    return nullptr;
+    return -1;
   }
 
   for (bb = start; bb; bb = backed_block_iter_next(bb)) {
@@ -319,7 +319,8 @@
 out:
   output_file_close(out_counter);
 
-  return bb;
+  *out_bb = bb;
+  return 0;
 }
 
 int sparse_file_resparse(struct sparse_file* in_s, unsigned int max_len, struct sparse_file** out_s,
@@ -337,7 +338,15 @@
   do {
     s = sparse_file_new(in_s->block_size, in_s->len);
 
-    bb = move_chunks_up_to_len(in_s, s, max_len);
+    if (move_chunks_up_to_len(in_s, s, max_len, &bb) < 0) {
+      sparse_file_destroy(s);
+      for (int i = 0; i < c && i < out_s_count; i++) {
+        sparse_file_destroy(out_s[i]);
+        out_s[i] = nullptr;
+      }
+      sparse_file_destroy(tmp);
+      return -1;
+    }
 
     if (c < out_s_count) {
       out_s[c] = s;
diff --git a/libstats/expresslog/Counter.cpp b/libstats/expresslog/Counter.cpp
index bee1303..9382041 100644
--- a/libstats/expresslog/Counter.cpp
+++ b/libstats/expresslog/Counter.cpp
@@ -28,5 +28,10 @@
     stats_write(EXPRESS_EVENT_REPORTED, metricIdHash, amount);
 }
 
+void Counter::logIncrementWithUid(const char* metricName, int32_t uid, int64_t amount) {
+    const int64_t metricIdHash = farmhash::Fingerprint64(metricName, strlen(metricName));
+    stats_write(EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid);
+}
+
 }  // namespace expresslog
 }  // namespace android
diff --git a/libstats/expresslog/Histogram.cpp b/libstats/expresslog/Histogram.cpp
index cb29a00..50bb343 100644
--- a/libstats/expresslog/Histogram.cpp
+++ b/libstats/expresslog/Histogram.cpp
@@ -71,5 +71,10 @@
     stats_write(EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash, /*count*/ 1, binIndex);
 }
 
+void Histogram::logSampleWithUid(int32_t uid, float sample) const {
+    const int binIndex = mBinOptions->getBinForSample(sample);
+    stats_write(EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash, /*count*/ 1, binIndex, uid);
+}
+
 }  // namespace expresslog
 }  // namespace android
diff --git a/libstats/expresslog/include/Counter.h b/libstats/expresslog/include/Counter.h
index 57328f5..8d0ab6a 100644
--- a/libstats/expresslog/include/Counter.h
+++ b/libstats/expresslog/include/Counter.h
@@ -24,6 +24,8 @@
 class Counter final {
 public:
     static void logIncrement(const char* metricId, int64_t amount = 1);
+
+    static void logIncrementWithUid(const char* metricId, int32_t uid, int64_t amount = 1);
 };
 
 }  // namespace expresslog
diff --git a/libstats/expresslog/include/Histogram.h b/libstats/expresslog/include/Histogram.h
index 8fdc1b6..49aee3d 100644
--- a/libstats/expresslog/include/Histogram.h
+++ b/libstats/expresslog/include/Histogram.h
@@ -72,6 +72,11 @@
      */
     void logSample(float sample) const;
 
+    /**
+     * Logs increment sample count for automatically calculated bin with uid
+     */
+    void logSampleWithUid(int32_t uid, float sample) const;
+
 private:
     const int64_t mMetricIdHash;
     const std::shared_ptr<BinOptions> mBinOptions;
diff --git a/rootdir/etc/public.libraries.android.txt b/rootdir/etc/public.libraries.android.txt
index 967205f..cacc47c 100644
--- a/rootdir/etc/public.libraries.android.txt
+++ b/rootdir/etc/public.libraries.android.txt
@@ -5,6 +5,7 @@
 libbinder_ndk.so
 libc.so
 libcamera2ndk.so
+libclang_rt.hwasan-aarch64-android.so 64 nopreload
 libdl.so
 libEGL.so
 libGLESv1_CM.so
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 68191bb..d755b50 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -221,26 +221,6 @@
     write /dev/stune/nnapi-hal/schedtune.boost 1
     write /dev/stune/nnapi-hal/schedtune.prefer_idle 1
 
-    # Create blkio group and apply initial settings.
-    # This feature needs kernel to support it, and the
-    # device's init.rc must actually set the correct values.
-    mkdir /dev/blkio/background
-    chown system system /dev/blkio
-    chown system system /dev/blkio/background
-    chown system system /dev/blkio/tasks
-    chown system system /dev/blkio/background/tasks
-    chown system system /dev/blkio/cgroup.procs
-    chown system system /dev/blkio/background/cgroup.procs
-    chmod 0664 /dev/blkio/tasks
-    chmod 0664 /dev/blkio/background/tasks
-    chmod 0664 /dev/blkio/cgroup.procs
-    chmod 0664 /dev/blkio/background/cgroup.procs
-    write /dev/blkio/blkio.weight 1000
-    write /dev/blkio/background/blkio.weight 200
-    write /dev/blkio/background/blkio.bfq.weight 10
-    write /dev/blkio/blkio.group_idle 0
-    write /dev/blkio/background/blkio.group_idle 0
-
     restorecon_recursive /mnt
 
     mount configfs none /config nodev noexec nosuid
@@ -495,25 +475,28 @@
     stdio_to_kmsg
     # Explicitly specify that boringssl_self_test32 doesn't require any capabilities
     capabilities
+    user nobody
 
 service boringssl_self_test64 /system/bin/boringssl_self_test64
     reboot_on_failure reboot,boringssl-self-check-failed
     stdio_to_kmsg
     # Explicitly specify that boringssl_self_test64 doesn't require any capabilities
     capabilities
+    user nobody
 
 service boringssl_self_test_apex32 /apex/com.android.conscrypt/bin/boringssl_self_test32
     reboot_on_failure reboot,boringssl-self-check-failed
     stdio_to_kmsg
     # Explicitly specify that boringssl_self_test_apex32 doesn't require any capabilities
     capabilities
+    user nobody
 
 service boringssl_self_test_apex64 /apex/com.android.conscrypt/bin/boringssl_self_test64
     reboot_on_failure reboot,boringssl-self-check-failed
     stdio_to_kmsg
     # Explicitly specify that boringssl_self_test_apex64 doesn't require any capabilities
     capabilities
-
+    user nobody
 
 # Healthd can trigger a full boot from charger mode by signaling this
 # property when the power button is held.
@@ -1280,6 +1263,7 @@
     class core
     critical
     seclabel u:r:ueventd:s0
+    user root
     shutdown critical
 
 service console /system/bin/sh
diff --git a/trusty/keymaster/TEST_MAPPING b/trusty/keymaster/TEST_MAPPING
index 0dd39fb..0475e04 100644
--- a/trusty/keymaster/TEST_MAPPING
+++ b/trusty/keymaster/TEST_MAPPING
@@ -5,6 +5,18 @@
       },
       {
         "name": "VtsHalRemotelyProvisionedComponentTargetTest"
+      },
+      {
+        "name": "RkpdAppUnitTests"
+      },
+      {
+        "name": "RkpdAppGoogleUnitTests"
+      },
+      {
+        "name": "RkpdAppIntegrationTests"
+      },
+      {
+        "name": "RkpdAppGoogleIntegrationTests"
       }
   ]
 }
diff --git a/trusty/stats/aidl/Android.bp b/trusty/stats/aidl/Android.bp
new file mode 100644
index 0000000..078cc99
--- /dev/null
+++ b/trusty/stats/aidl/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+    name: "android.trusty.stats.nw.setter",
+    unstable: true,
+    vendor_available: true,
+    srcs: [
+        "android/trusty/stats/nw/setter/IStatsSetter.aidl",
+    ],
+    imports: ["android.frameworks.stats-V1"],
+    backend: {
+        cpp: {
+            enabled: true,
+        },
+        java: {
+            enabled: false,
+            platform_apis: false,
+        },
+        ndk: {
+            enabled: false,
+        },
+    },
+}
diff --git a/trusty/stats/aidl/android/trusty/stats/nw/setter/IStatsSetter.aidl b/trusty/stats/aidl/android/trusty/stats/nw/setter/IStatsSetter.aidl
new file mode 100644
index 0000000..f44f4a3
--- /dev/null
+++ b/trusty/stats/aidl/android/trusty/stats/nw/setter/IStatsSetter.aidl
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2023 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.
+//
+package android.trusty.stats.nw.setter;
+
+import android.frameworks.stats.IStats;
+
+interface IStatsSetter {
+    /**
+     * Set the IStats interface facet.
+     *
+     * @param istats The IStats facet provided by the caller for the remote
+     *        service to report IStats' VendorAtom.
+     */
+    void setInterface(in IStats istats);
+}
diff --git a/trusty/stats/test/Android.bp b/trusty/stats/test/Android.bp
new file mode 100644
index 0000000..6b2bce9
--- /dev/null
+++ b/trusty/stats/test/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 The Android Open-Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "trusty_stats_test",
+    vendor: true,
+    srcs: [
+        "stats_test.cpp",
+    ],
+    static_libs: [
+        "libgmock",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libtrusty",
+        "libbinder",
+        "libbinder_trusty",
+        "libutils",
+
+        // AIDL interface deps versions, please refer to below link
+        // https://source.android.com/docs/core/architecture/aidl/stable-aidl#module-naming-rules
+        "android.frameworks.stats-V1-cpp",
+        "android.trusty.stats.nw.setter-cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    require_root: true,
+    proprietary: true,
+}
diff --git a/trusty/stats/test/README.md b/trusty/stats/test/README.md
new file mode 100644
index 0000000..45e6af8
--- /dev/null
+++ b/trusty/stats/test/README.md
@@ -0,0 +1,97 @@
+# Development Notes
+
+*    First get [repo_pull.py and gerrit.py](https://android.googlesource.com/platform/development/+/master/tools/repo_pull/) from aosp.
+
+*    Although this repo is not currently in Trusty’s manifest, it’s sufficient to copy these two python scripts to the root of the Trusty project and run them from there. Make sure to follow the [repo_pull installation](https://android.googlesource.com/platform/development/+/master/tools/repo_pull/#installation) steps if necessary.
+
+## Build
+
+Build Android:
+
+```sh
+source build/envsetup.sh
+lunch qemu_trusty_arm64-userdebug
+m
+```
+
+Build Trusty:
+
+```sh
+./trusty/vendor/google/aosp/scripts/build.py qemu-generic-arm64-test-debug --skip-tests 2>stderr.log
+```
+
+## Trusty PORT_TEST
+
+On QEmu:
+
+```sh
+./build-root/build-qemu-generic-arm64-test-debug/run --headless --boot-test "com.android.trusty.stats.test" --verbose
+```
+
+On device: (Build for your device's debug target on both Adroid and Trusty)
+
+```sh
+/vendor/bin/trusty-ut-ctrl -D /dev/trusty-ipc-dev0 "com.android.trusty.stats.test"
+```
+
+On device, in a loop:
+
+```sh
+cat << 'EOF' > metrics.sh
+#!/system/bin/sh
+TIMES=${1:-0}
+X=0
+while [ "$TIMES" -eq 0 -o "$TIMES" -gt "$X" ]
+do
+  echo "######################## stats.test $X " $(( X++ ));
+  /vendor/bin/trusty-ut-ctrl -D /dev/trusty-ipc-dev0 "com.android.trusty.stats.test"
+done
+EOF
+
+adb wait-for-device
+adb push metrics.sh /data/user/test/metrics.sh
+adb shell sh /data/user/test/metrics.sh
+```
+
+## Android Native Test
+
+On QEmu:
+
+```sh
+./build-root/build-qemu-generic-arm64-test-debug/run --headless --android $ANDROID_PROJECT_ROOT --shell-command "/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test" --verbose
+```
+
+On device: (Build for your device's debug target on both Adroid and Trusty)
+
+```sh
+/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
+```
+
+On device, in a loop:
+
+```sh
+cat << 'EOF' > metrics-nw.sh
+#!/system/bin/sh
+TIMES=${1:-0}
+X=0
+while [ "$TIMES" -eq 0 -o "$TIMES" -gt "$X" ]
+do
+  echo "######################## stats.test $X " $(( X++ ));
+  /data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
+done
+EOF
+
+adb wait-for-device
+adb push metrics.sh /data/user/test/metrics-nw.sh
+adb shell sh /data/user/test/metrics-nw.sh
+```
+
+## Trusty Backtrace analysis
+
+
+```
+$ export A2L=./prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-addr2line
+$ export OD=./prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-objdump
+$ $OD -d -C build-root/build-qemu-generic-arm64-test-debug/user_tasks/trusty/user/base/app/metrics/metrics.syms.elf > objdump.lst
+$ $A2L -e build-root/build-qemu-generic-arm64-test-debug/user_tasks/trusty/user/base/app/metrics/metrics.syms.elf 0xe5104
+```
diff --git a/trusty/stats/test/stats_test.cpp b/trusty/stats/test/stats_test.cpp
new file mode 100644
index 0000000..1edddeb
--- /dev/null
+++ b/trusty/stats/test/stats_test.cpp
@@ -0,0 +1,364 @@
+/*
+ * 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 <errno.h>
+#include <getopt.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <condition_variable>
+#include <cstddef>
+#include <mutex>
+#include <queue>
+
+#include <android-base/expected.h>
+#include <android-base/logging.h>
+#include <android/frameworks/stats/BnStats.h>
+#include <android/frameworks/stats/IStats.h>
+#include <android/trusty/stats/nw/setter/IStatsSetter.h>
+#include <binder/RpcServer.h>
+#include <binder/RpcSession.h>
+#include <binder/RpcTransportRaw.h>
+#include <binder/RpcTransportTipcAndroid.h>
+#include <binder/RpcTrusty.h>
+#include <trusty/tipc.h>
+
+/** DOC:
+ * ./build-root/build-qemu-generic-arm64-test-debug/run \
+ *       --android $ANDROID_PROJECT_ROOT \
+ *       --headless --shell-command \
+ *       "/data/nativetest64/vendor/trusty_stats_test/trusty_stats_test"
+ *
+ * adb -s emulator-5554 shell \
+ *       /data/nativetest64/vendor/trusty_stats_test/trusty_stats_test
+ */
+using ::android::base::unique_fd;
+using ::android::binder::Status;
+using ::android::frameworks::stats::BnStats;
+using ::android::frameworks::stats::IStats;
+using ::android::frameworks::stats::VendorAtom;
+using ::android::frameworks::stats::VendorAtomValue;
+using ::android::trusty::stats::nw::setter::IStatsSetter;
+
+constexpr const char kTrustyDefaultDeviceName[] = "/dev/trusty-ipc-dev0";
+constexpr const char kTrustyStatsSetterTest[] =
+        "com.android.frameworks.stats.trusty.test.relayer.istats_setter";
+constexpr const char kTrustyStatsSetterMetrics[] =
+        "com.android.frameworks.stats.trusty.metrics.istats_setter";
+constexpr const char kTrustyStatsPortTest[] = "com.android.trusty.stats.test";
+constexpr const char kTrustyCrashPortTest[] = "com.android.trusty.crashtest";
+constexpr const char kTrustyCrasherUuid[] = "7ee4dddc-177a-420a-96ea-5d413d88228e:crasher";
+
+enum TrustyAtoms : int32_t {
+    TrustyAppCrashed = 100072,
+    TrustyError = 100145,
+    TrustyStorageError = 100146
+};
+
+enum TestMsgHeader : int32_t {
+    TEST_PASSED = 0,
+    TEST_FAILED = 1,
+    TEST_MESSAGE = 2,
+};
+
+namespace android {
+namespace trusty {
+namespace stats {
+
+class Stats : public BnStats {
+  public:
+    Stats() : BnStats() {}
+
+    Status reportVendorAtom(const VendorAtom& vendorAtom) override {
+        const char* atomIdStr = vendorAtomStr(vendorAtom.atomId);
+        ALOGD("Vendor atom reported of type: %s\n", atomIdStr);
+        std::lock_guard lock(mLock);
+        mQueueVendorAtom.push(vendorAtom);
+        mCondVar.notify_one();
+        return Status::ok();
+    }
+
+    status_t getVendorAtom(VendorAtom* pVendorAtom, int64_t waitForMs) {
+        std::unique_lock lock(mLock);
+        while (mQueueVendorAtom.empty()) {
+            auto rc = mCondVar.wait_for(lock, std::chrono::milliseconds(waitForMs));
+            if (rc == std::cv_status::timeout) {
+                return TIMED_OUT;
+            }
+        }
+        *pVendorAtom = mQueueVendorAtom.front();
+        mQueueVendorAtom.pop();
+        return NO_ERROR;
+    }
+
+  private:
+    const char* vendorAtomStr(int32_t atomId) {
+        switch (atomId) {
+            case TrustyAtoms::TrustyAppCrashed:
+                return "TrustyAtoms::TrustyAppCrashed";
+            case TrustyAtoms::TrustyError:
+                return "TrustyAtoms::TrustyError";
+            case TrustyAtoms::TrustyStorageError:
+                return "TrustyAtoms::TrustyStorageError";
+            default:
+                return "unknown TrustyAtoms type";
+        }
+    }
+    std::mutex mLock;
+    std::condition_variable mCondVar;
+    std::queue<VendorAtom> mQueueVendorAtom;
+};
+
+class TrustyStatsTestBase : public ::testing::Test {
+  protected:
+    TrustyStatsTestBase(std::string&& portNameStatsSetter, std::string&& portNamePortTest)
+        : mPortTestFd(-1),
+          mPortNameStatsSetter(std::move(portNameStatsSetter)),
+          mPortNamePortTest(std::move(portNamePortTest)) {}
+
+    void SetUp() override {
+        // Commenting out the server portion because we do not have any direct
+        // incoming call Calls from TA are currently being handled on the mSession's
+        // extra thread. android::sp<::android::RpcServer> server =
+        // ::android::RpcServer::make(::android::RpcTransportCtxFactoryRaw::make());
+
+        mStats = android::sp<Stats>::make();
+        // Increasing number of incoming threads on mSession to be able to receive
+        // callbacks
+        auto session_initializer = [](sp<RpcSession>& session) {
+            session->setMaxIncomingThreads(1);
+        };
+
+        ASSERT_FALSE(mSession);
+        mSession = RpcTrustyConnectWithSessionInitializer(
+                kTrustyDefaultDeviceName, mPortNameStatsSetter.c_str(), session_initializer);
+        ASSERT_TRUE(mSession);
+
+        auto root = mSession->getRootObject();
+        ASSERT_TRUE(root);
+        auto statsSetter = IStatsSetter::asInterface(root);
+        ASSERT_TRUE(statsSetter);
+        statsSetter->setInterface(mStats);
+    }
+    void TearDown() override {
+        // close connection to unitest app
+        if (mPortTestFd != -1) {
+            tipc_close(mPortTestFd);
+        }
+        mPortTestFd = -1;
+
+        if (mSession) {
+            // shutdownAndWait here races with sending out the DecStrong
+            // messages after reportVendorAtom returns, so we delay it a little
+            // bit to give the messages time to go out over the transport
+            usleep(50000);
+            ASSERT_TRUE(mSession->shutdownAndWait(true));
+        }
+        mSession.clear();
+        mStats.clear();
+    }
+    void StartPortTest() {
+        // connect to unitest app
+        mPortTestFd = tipc_connect(kTrustyDefaultDeviceName, mPortNamePortTest.c_str());
+        if (mPortTestFd < 0) {
+            ALOGE("Failed to connect to '%s' app: %s\n", kTrustyStatsPortTest,
+                  strerror(-mPortTestFd));
+        }
+        ASSERT_GT(mPortTestFd, 0);
+    }
+    void WaitPortTestDone() {
+        // wait for test to complete
+        char rxBuf[1024];
+        const char prolog[] = "Trusty PORT_TEST:";
+        strncpy(rxBuf, prolog, sizeof(prolog) - 1);
+        char* pRxBuf = rxBuf + sizeof(prolog) - 1;
+        size_t remainingBufSize = sizeof(rxBuf) - sizeof(prolog) - 1;
+
+        ASSERT_NE(mPortTestFd, -1);
+        for (;;) {
+            int rc = read(mPortTestFd, pRxBuf, remainingBufSize);
+            ASSERT_GT(rc, 0);
+            ASSERT_LT(rc, (int)remainingBufSize);
+            if (pRxBuf[0] == TEST_PASSED) {
+                break;
+            } else if (pRxBuf[0] == TEST_FAILED) {
+                break;
+            } else if (pRxBuf[0] == TEST_MESSAGE) {
+                pRxBuf[0] = ' ';
+                write(STDOUT_FILENO, rxBuf, rc + sizeof(prolog) - 1);
+            } else {
+                ALOGE("Bad message header: %d\n", rxBuf[0]);
+                break;
+            }
+        }
+        ASSERT_EQ(pRxBuf[0], TEST_PASSED);
+    }
+
+    android::sp<Stats> mStats;
+
+  private:
+    android::sp<RpcSession> mSession;
+    int mPortTestFd;
+    std::string mPortNameStatsSetter;
+    std::string mPortNamePortTest;
+};
+
+class TrustyStatsTest : public TrustyStatsTestBase {
+  protected:
+    TrustyStatsTest() : TrustyStatsTestBase(kTrustyStatsSetterTest, kTrustyStatsPortTest) {}
+};
+
+class TrustyMetricsCrashTest : public TrustyStatsTestBase {
+  protected:
+    TrustyMetricsCrashTest()
+        : TrustyStatsTestBase(kTrustyStatsSetterMetrics, kTrustyCrashPortTest) {}
+};
+
+TEST_F(TrustyStatsTest, CheckAtoms) {
+    int atomAppCrashedCnt = 0;
+    int atomStorageErrorCnt = 0;
+    int atomTrustyErrorCnt = 0;
+    uint64_t blockForMs = 500;
+    StartPortTest();
+    WaitPortTestDone();
+    for (;;) {
+        VendorAtom vendorAtom;
+        auto status = mStats->getVendorAtom(&vendorAtom, blockForMs);
+        ASSERT_THAT(status, ::testing::AnyOf(NO_ERROR, TIMED_OUT));
+        if (status == TIMED_OUT) {
+            // No more atoms
+            break;
+        }
+
+        ASSERT_THAT(vendorAtom.atomId,
+                    ::testing::AnyOf(::testing::Eq(TrustyAtoms::TrustyAppCrashed),
+                                     ::testing::Eq(TrustyAtoms::TrustyError),
+                                     ::testing::Eq(TrustyAtoms::TrustyStorageError)));
+        ASSERT_STREQ(String8(vendorAtom.reverseDomainName), "google.android.trusty");
+        switch (vendorAtom.atomId) {
+            case TrustyAtoms::TrustyAppCrashed:
+                ++atomAppCrashedCnt;
+                ASSERT_STREQ(String8(vendorAtom.values[0].get<VendorAtomValue::stringValue>()),
+                             "5247d19b-cf09-4272-a450-3ef20dbefc14");
+                break;
+            case TrustyAtoms::TrustyStorageError:
+                ++atomStorageErrorCnt;
+                ASSERT_EQ(vendorAtom.values[0].get<VendorAtomValue::intValue>(), 5);
+                ASSERT_STREQ(String8(vendorAtom.values[1].get<VendorAtomValue::stringValue>()),
+                             "5247d19b-cf09-4272-a450-3ef20dbefc14");
+                ASSERT_STREQ(String8(vendorAtom.values[2].get<VendorAtomValue::stringValue>()),
+                             "5247d19b-cf09-4272-a450-3ef20dbefc14");
+                ASSERT_EQ(vendorAtom.values[3].get<VendorAtomValue::intValue>(), 1);
+                ASSERT_EQ(vendorAtom.values[4].get<VendorAtomValue::intValue>(), 3);
+                ASSERT_EQ(vendorAtom.values[5].get<VendorAtomValue::longValue>(),
+                          0x4BCDEFABBAFEDCBALL);
+                ASSERT_EQ(vendorAtom.values[6].get<VendorAtomValue::intValue>(), 4);
+                ASSERT_EQ(vendorAtom.values[7].get<VendorAtomValue::longValue>(), 1023);
+                break;
+            case TrustyAtoms::TrustyError:
+                ++atomTrustyErrorCnt;
+                break;
+            default:
+                FAIL() << "Unknown vendor atom ID: " << vendorAtom.atomId;
+                break;
+        }
+    };
+    ASSERT_EQ(atomAppCrashedCnt, 1);
+    ASSERT_EQ(atomStorageErrorCnt, 1);
+    ASSERT_EQ(atomTrustyErrorCnt, 0);
+}
+
+TEST_F(TrustyMetricsCrashTest, CheckTrustyCrashAtoms) {
+    const std::vector<uint32_t> kExpectedCrashReasonsArm64{
+            0x00000001U,  // exit_failure (twice)
+            0x00000001U,
+            0x92000004U,  // read_null_ptr
+            0xf200002aU,  // brk_instruction
+            0x92000004U,  // read_bad_ptr
+            0x92000044U,  // crash_write_bad_ptr
+            0x9200004fU,  // crash_write_ro_ptr
+            0x8200000fU,  // crash_exec_rodata
+            0x8200000fU,  // crash_exec_data
+    };
+    const std::vector<uint32_t> kExpectedCrashReasonsArm32{
+            0x00000001U,  // exit_failure (twice)
+            0x00000001U,
+            0x20000007U,  // read_null_ptr
+            0x20000007U,  // read_bad_ptr
+            0x20000807U,  // crash_write_bad_ptr
+            0x2000080fU,  // crash_write_ro_ptr
+            0x3000000fU,  // crash_exec_rodata
+            0x3000000fU,  // crash_exec_data
+    };
+
+    int expectedAtomCnt = 7;
+    int atomAppCrashedCnt = 0;
+    int atomStorageErrorCnt = 0;
+    int atomTrustyErrorCnt = 0;
+    std::vector<uint32_t> atomCrashReasons;
+    uint64_t blockForMs = 500;
+    StartPortTest();
+    WaitPortTestDone();
+    for (;;) {
+        VendorAtom vendorAtom;
+        auto status = mStats->getVendorAtom(&vendorAtom, blockForMs);
+        ASSERT_THAT(status, ::testing::AnyOf(NO_ERROR, TIMED_OUT));
+        if (status == TIMED_OUT) {
+            // No more atoms
+            break;
+        }
+
+        ASSERT_THAT(vendorAtom.atomId,
+                    ::testing::AnyOf(::testing::Eq(TrustyAtoms::TrustyAppCrashed),
+                                     ::testing::Eq(TrustyAtoms::TrustyError),
+                                     ::testing::Eq(TrustyAtoms::TrustyStorageError)));
+        ASSERT_STREQ(String8(vendorAtom.reverseDomainName), "google.android.trusty");
+
+        switch (vendorAtom.atomId) {
+            case TrustyAtoms::TrustyAppCrashed:
+                ++atomAppCrashedCnt;
+                ASSERT_STREQ(String8(vendorAtom.values[0].get<VendorAtomValue::stringValue>()),
+                             kTrustyCrasherUuid);
+                atomCrashReasons.push_back(vendorAtom.values[1].get<VendorAtomValue::intValue>());
+                break;
+            case TrustyAtoms::TrustyStorageError:
+                ++atomStorageErrorCnt;
+                break;
+            case TrustyAtoms::TrustyError:
+                ++atomTrustyErrorCnt;
+                ASSERT_STREQ(String8(vendorAtom.values[1].get<VendorAtomValue::stringValue>()), "");
+                break;
+            default:
+                FAIL() << "Unknown vendor atom ID: " << vendorAtom.atomId;
+        }
+    }
+    ASSERT_GE(atomAppCrashedCnt, expectedAtomCnt - 1);
+    ASSERT_EQ(atomStorageErrorCnt, 0);
+    // There is one dropped event left over from Trusty boot,
+    // it may show up here
+    ASSERT_LE(atomTrustyErrorCnt, 1);
+    ASSERT_THAT(atomCrashReasons,
+                ::testing::AnyOf(kExpectedCrashReasonsArm64, kExpectedCrashReasonsArm32));
+};
+
+}  // namespace stats
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/trusty-base.mk b/trusty/trusty-base.mk
index 5a3a320..1986c73 100644
--- a/trusty/trusty-base.mk
+++ b/trusty/trusty-base.mk
@@ -39,7 +39,6 @@
 	$(LOCAL_KEYMINT_PRODUCT_PACKAGE) \
 	android.hardware.gatekeeper-service.trusty \
 	trusty_apploader \
-	RemoteProvisioner
 
 PRODUCT_PROPERTY_OVERRIDES += \
 	ro.hardware.keystore_desede=true \