Merge "Revert "Ramdisk: add metadata dir in ramdisk""
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 5280121..b3e81b0 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -299,11 +299,8 @@
       process_info->abort_msg_address = crash_info->data.s.abort_msg_address;
       *siginfo = crash_info->data.s.siginfo;
       if (signal_has_si_addr(siginfo)) {
-        // Make a copy of the ucontext field because otherwise it is not aligned enough (due to
-        // being in a packed struct) and clang complains about that.
-        ucontext_t ucontext = crash_info->data.s.ucontext;
         process_info->has_fault_address = true;
-        process_info->fault_address = get_fault_address(siginfo, &ucontext);
+        process_info->fault_address = reinterpret_cast<uintptr_t>(siginfo->si_addr);
       }
       regs->reset(unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(),
                                                         &crash_info->data.s.ucontext));
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 5565e8b..e5af425 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -34,7 +34,6 @@
 #include <android/set_abort_message.h>
 #include <bionic/malloc.h>
 #include <bionic/mte.h>
-#include <bionic/mte_kernel.h>
 #include <bionic/reserved_signals.h>
 
 #include <android-base/cmsg.h>
@@ -386,16 +385,6 @@
 
 #if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
 static void SetTagCheckingLevelSync() {
-  int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
-  if (tagged_addr_ctrl < 0) {
-    abort();
-  }
-
-  tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | PR_MTE_TCF_SYNC;
-  if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) != 0) {
-    abort();
-  }
-
   HeapTaggingLevel heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC;
   if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level))) {
     abort();
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 121a074..85ffc98 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -167,7 +167,7 @@
  * mutex is being held, so we don't want to use any libc functions that
  * could allocate memory or hold a lock.
  */
-static void log_signal_summary(const siginfo_t* info, const ucontext_t* ucontext) {
+static void log_signal_summary(const siginfo_t* info) {
   char thread_name[MAX_TASK_NAME_LEN + 1];  // one more for termination
   if (prctl(PR_GET_NAME, reinterpret_cast<unsigned long>(thread_name), 0, 0, 0) != 0) {
     strcpy(thread_name, "<name unknown>");
@@ -186,8 +186,7 @@
   // Many signals don't have an address or sender.
   char addr_desc[32] = "";  // ", fault addr 0x1234"
   if (signal_has_si_addr(info)) {
-    async_safe_format_buffer(addr_desc, sizeof(addr_desc), ", fault addr %p",
-                             reinterpret_cast<void*>(get_fault_address(info, ucontext)));
+    async_safe_format_buffer(addr_desc, sizeof(addr_desc), ", fault addr %p", info->si_addr);
   }
   pid_t self_pid = __getpid();
   char sender_desc[32] = {};  // " from pid 1234, uid 666"
@@ -544,7 +543,7 @@
     return;
   }
 
-  log_signal_summary(info, ucontext);
+  log_signal_summary(info);
 
   debugger_thread_info thread_info = {
       .crashing_tid = __gettid(),
@@ -638,5 +637,11 @@
 
   // Use the alternate signal stack if available so we can catch stack overflows.
   action.sa_flags |= SA_ONSTACK;
+
+#define SA_EXPOSE_TAGBITS 0x00000800
+  // Request that the kernel set tag bits in the fault address. This is necessary for diagnosing MTE
+  // faults.
+  action.sa_flags |= SA_EXPOSE_TAGBITS;
+
   debuggerd_register_handlers(&action);
 }
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 76155b1..29fb9a4 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -91,6 +91,4 @@
 const char* get_signame(const siginfo_t*);
 const char* get_sigcode(const siginfo_t*);
 
-uintptr_t get_fault_address(const siginfo_t* siginfo, const ucontext_t* ucontext);
-
 #endif // _DEBUGGERD_UTILITY_H
diff --git a/debuggerd/libdebuggerd/test/UnwinderMock.h b/debuggerd/libdebuggerd/test/UnwinderMock.h
index 023a578..44a9214 100644
--- a/debuggerd/libdebuggerd/test/UnwinderMock.h
+++ b/debuggerd/libdebuggerd/test/UnwinderMock.h
@@ -34,7 +34,7 @@
     unwindstack::MapInfo* map_info = GetMaps()->Find(offset);
     if (map_info != nullptr) {
       std::string* new_build_id = new std::string(build_id);
-      map_info->build_id = reinterpret_cast<uintptr_t>(new_build_id);
+      map_info->build_id = new_build_id;
     }
   }
 };
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 4e6df09..d7067ca 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -451,40 +451,3 @@
     _LOG(log, logtype::BACKTRACE, "%s%s\n", prefix, unwinder->FormatFrame(i).c_str());
   }
 }
-
-#if defined(__aarch64__)
-#define FAR_MAGIC 0x46415201
-
-struct far_context {
-  struct _aarch64_ctx head;
-  __u64 far;
-};
-#endif
-
-uintptr_t get_fault_address(const siginfo_t* siginfo, const ucontext_t* ucontext) {
-  (void)ucontext;
-#if defined(__aarch64__)
-  // This relies on a kernel patch:
-  //   https://patchwork.kernel.org/patch/11435077/
-  // that hasn't been accepted into the kernel yet. TODO(pcc): Update this to
-  // use the official interface once it lands.
-  auto* begin = reinterpret_cast<const char*>(ucontext->uc_mcontext.__reserved);
-  auto* end = begin + sizeof(ucontext->uc_mcontext.__reserved);
-  auto* ptr = begin;
-  while (1) {
-    auto* ctx = reinterpret_cast<const _aarch64_ctx*>(ptr);
-    if (ctx->magic == 0) {
-      break;
-    }
-    if (ctx->magic == FAR_MAGIC) {
-      auto* far_ctx = reinterpret_cast<const far_context*>(ctx);
-      return far_ctx->far;
-    }
-    ptr += ctx->size;
-    if (ctx->size % sizeof(void*) != 0 || ptr < begin || ptr >= end) {
-      break;
-    }
-  }
-#endif
-  return reinterpret_cast<uintptr_t>(siginfo->si_addr);
-}
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 6294b3f..4c9fd9b 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -656,7 +656,17 @@
 // If needed, we'll also enable (or disable) filesystem features as specified by
 // the fstab record.
 //
-static int prepare_fs_for_mount(const std::string& blk_device, const FstabEntry& entry) {
+static int prepare_fs_for_mount(const std::string& blk_device, const FstabEntry& entry,
+                                const std::string& alt_mount_point = "") {
+    auto& mount_point = alt_mount_point.empty() ? entry.mount_point : alt_mount_point;
+    // We need this because sometimes we have legacy symlinks that are
+    // lingering around and need cleaning up.
+    struct stat info;
+    if (lstat(mount_point.c_str(), &info) == 0 && (info.st_mode & S_IFMT) == S_IFLNK) {
+        unlink(mount_point.c_str());
+    }
+    mkdir(mount_point.c_str(), 0755);
+
     int fs_stat = 0;
 
     if (is_extfs(entry.fs_type)) {
@@ -684,7 +694,7 @@
 
     if (entry.fs_mgr_flags.check ||
         (fs_stat & (FS_STAT_UNCLEAN_SHUTDOWN | FS_STAT_QUOTA_ENABLED))) {
-        check_fs(blk_device, entry.fs_type, entry.mount_point, &fs_stat);
+        check_fs(blk_device, entry.fs_type, mount_point, &fs_stat);
     }
 
     if (is_extfs(entry.fs_type) &&
@@ -729,13 +739,6 @@
 // sets the underlying block device to read-only if the mount is read-only.
 // See "man 2 mount" for return values.
 static int __mount(const std::string& source, const std::string& target, const FstabEntry& entry) {
-    // We need this because sometimes we have legacy symlinks that are
-    // lingering around and need cleaning up.
-    struct stat info;
-    if (lstat(target.c_str(), &info) == 0 && (info.st_mode & S_IFMT) == S_IFLNK) {
-        unlink(target.c_str());
-    }
-    mkdir(target.c_str(), 0755);
     errno = 0;
     unsigned long mountflags = entry.flags;
     int ret = 0;
@@ -1799,17 +1802,18 @@
 
 // wrapper to __mount() and expects a fully prepared fstab_rec,
 // unlike fs_mgr_do_mount which does more things with avb / verity etc.
-int fs_mgr_do_mount_one(const FstabEntry& entry, const std::string& mount_point) {
+int fs_mgr_do_mount_one(const FstabEntry& entry, const std::string& alt_mount_point) {
     // First check the filesystem if requested.
     if (entry.fs_mgr_flags.wait && !WaitForFile(entry.blk_device, 20s)) {
         LERROR << "Skipping mounting '" << entry.blk_device << "'";
     }
 
-    // Run fsck if needed
-    prepare_fs_for_mount(entry.blk_device, entry);
+    auto& mount_point = alt_mount_point.empty() ? entry.mount_point : alt_mount_point;
 
-    int ret =
-            __mount(entry.blk_device, mount_point.empty() ? entry.mount_point : mount_point, entry);
+    // Run fsck if needed
+    prepare_fs_for_mount(entry.blk_device, entry, mount_point);
+
+    int ret = __mount(entry.blk_device, mount_point, entry);
     if (ret) {
       ret = (errno == EBUSY) ? FS_MGR_DOMNT_BUSY : FS_MGR_DOMNT_FAILED;
     }
@@ -1868,7 +1872,14 @@
             continue;
         }
 
-        int fs_stat = prepare_fs_for_mount(n_blk_device, fstab_entry);
+        // Now mount it where requested */
+        if (tmp_mount_point) {
+            mount_point = tmp_mount_point;
+        } else {
+            mount_point = fstab_entry.mount_point;
+        }
+
+        int fs_stat = prepare_fs_for_mount(n_blk_device, fstab_entry, mount_point);
 
         if (fstab_entry.fs_mgr_flags.avb) {
             if (!avb_handle) {
@@ -1902,12 +1913,6 @@
             }
         }
 
-        // Now mount it where requested */
-        if (tmp_mount_point) {
-            mount_point = tmp_mount_point;
-        } else {
-            mount_point = fstab_entry.mount_point;
-        }
         int retry_count = 2;
         while (retry_count-- > 0) {
             if (!__mount(n_blk_device, mount_point, fstab_entry)) {
@@ -1919,7 +1924,7 @@
                 mount_errors++;
                 fs_stat |= FS_STAT_FULL_MOUNT_FAILED;
                 // try again after fsck
-                check_fs(n_blk_device, fstab_entry.fs_type, fstab_entry.mount_point, &fs_stat);
+                check_fs(n_blk_device, fstab_entry.fs_type, mount_point, &fs_stat);
             }
         }
         log_fs_stat(fstab_entry.blk_device, fs_stat);
diff --git a/fs_mgr/libdm/Android.bp b/fs_mgr/libdm/Android.bp
index a0bc44d..0efe384 100644
--- a/fs_mgr/libdm/Android.bp
+++ b/fs_mgr/libdm/Android.bp
@@ -43,6 +43,7 @@
         },
     },
     ramdisk_available: true,
+    vendor_ramdisk_available: true,
 }
 
 filegroup {
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 910911e..34049d4 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -174,6 +174,7 @@
         "libz",
     ],
     ramdisk_available: true,
+    vendor_ramdisk_available: true,
 }
 
 cc_defaults {
@@ -408,9 +409,9 @@
         "fs_mgr_defaults",
     ],
     srcs: [
-	"snapuserd_server.cpp",
-    "snapuserd.cpp",
-	"snapuserd_daemon.cpp",
+        "snapuserd_server.cpp",
+        "snapuserd.cpp",
+        "snapuserd_daemon.cpp",
     ],
 
     cflags: [
@@ -421,7 +422,7 @@
     static_libs: [
         "libbase",
         "libbrotli",
-	"libcutils_sockets",
+        "libcutils_sockets",
         "liblog",
         "libdm",
         "libz",
@@ -436,15 +437,9 @@
         "snapuserd.rc",
     ],
     static_executable: true,
-}
-
-cc_binary {
-    name: "snapuserd_ramdisk",
-    stem: "snapuserd",
-    defaults: ["snapuserd_defaults"],
-
-    ramdisk: true,
-    static_executable: true,
+    system_shared_libs: [],
+    ramdisk_available: true,
+    vendor_ramdisk_available: true,
 }
 
 cc_test {
@@ -568,7 +563,7 @@
         "libsnapshot_snapuserd",
         "libcutils_sockets",
         "libz",
-	"libdm",
+        "libdm",
     ],
     header_libs: [
         "libstorage_literals_headers",
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 0328132..38c6bf8 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -34,7 +34,7 @@
     MERGE_COMPLETED = 3;
 }
 
-// Next: 9
+// Next: 10
 message SnapshotStatus {
     // Name of the snapshot. This is usually the name of the snapshotted
     // logical partition; for example, "system_b".
@@ -84,6 +84,9 @@
     // the merge process.
     // This is non-zero when |state| == MERGING or MERGE_COMPLETED.
     uint64 metadata_sectors = 8;
+
+    // True if compression is enabled, false otherwise.
+    bool compression_enabled = 9;
 }
 
 // Next: 8
@@ -115,7 +118,7 @@
     Cancelled = 7;
 };
 
-// Next: 5
+// Next: 6
 message SnapshotUpdateStatus {
     UpdateState state = 1;
 
@@ -130,6 +133,9 @@
 
     // Sectors allocated for metadata in all the snapshot devices.
     uint64 metadata_sectors = 4;
+
+    // Whether compression/dm-user was used for any snapshots.
+    bool compression_enabled = 5;
 }
 
 // Next: 4
diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
index 7eddf8c..5483fd0 100644
--- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
@@ -288,20 +288,20 @@
 }
 
 void SnapuserdTest::InitCowDevices() {
-    system_blksize_ = client_->InitDmUserCow(cow_system_->path);
+    system_blksize_ = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
+                                             system_a_loop_->device());
     ASSERT_NE(system_blksize_, 0);
 
-    product_blksize_ = client_->InitDmUserCow(cow_product_->path);
+    product_blksize_ = client_->InitDmUserCow(product_device_ctrl_name_, cow_product_->path,
+                                              product_a_loop_->device());
     ASSERT_NE(product_blksize_, 0);
 }
 
 void SnapuserdTest::InitDaemon() {
-    bool ok = client_->InitializeSnapuserd(cow_system_->path, system_a_loop_->device(),
-                                           GetSystemControlPath());
+    bool ok = client_->AttachDmUser(system_device_ctrl_name_);
     ASSERT_TRUE(ok);
 
-    ok = client_->InitializeSnapuserd(cow_product_->path, product_a_loop_->device(),
-                                      GetProductControlPath());
+    ok = client_->AttachDmUser(product_device_ctrl_name_);
     ASSERT_TRUE(ok);
 }
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 8bed1b9..f8d3cdc 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -344,6 +344,18 @@
     bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms = {}) override;
     bool UnmapAllSnapshots() override;
 
+    // We can't use WaitForFile during first-stage init, because ueventd is not
+    // running and therefore will not automatically create symlinks. Instead,
+    // we let init provide us with the correct function to use to ensure
+    // uevents have been processed and symlink/mknod calls completed.
+    void SetUeventRegenCallback(std::function<bool(const std::string&)> callback) {
+        uevent_regen_callback_ = callback;
+    }
+
+    // If true, compression is enabled for this update. This is used by
+    // first-stage to decide whether to launch snapuserd.
+    bool IsSnapuserdRequired();
+
   private:
     FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
     FRIEND_TEST(SnapshotTest, CreateSnapshot);
@@ -675,6 +687,12 @@
     // Same as above, but for paths only (no major:minor device strings).
     bool GetMappedImageDevicePath(const std::string& device_name, std::string* device_path);
 
+    // Wait for a device to be created by ueventd (eg, its symlink or node to be populated).
+    // This is needed for any code that uses device-mapper path in first-stage init. If
+    // |timeout_ms| is empty or the given device is not a path, WaitForDevice immediately
+    // returns true.
+    bool WaitForDevice(const std::string& device, std::chrono::milliseconds timeout_ms);
+
     std::string gsid_dir_;
     std::string metadata_dir_;
     std::unique_ptr<IDeviceInfo> device_;
@@ -682,6 +700,7 @@
     bool has_local_image_manager_ = false;
     bool use_first_stage_snapuserd_ = false;
     bool in_factory_data_reset_ = false;
+    std::function<bool(const std::string&)> uevent_regen_callback_;
     std::unique_ptr<SnapuserdClient> snapuserd_client_;
 };
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
index cd8b080..24b44fa 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
@@ -61,28 +61,31 @@
 
 class Snapuserd final {
   public:
-    bool InitBackingAndControlDevice(std::string& backing_device, std::string& control_device);
-    bool InitCowDevice(std::string& cow_device);
-    int Run();
+    Snapuserd(const std::string& misc_name, const std::string& cow_device,
+              const std::string& backing_device);
+    bool InitBackingAndControlDevice();
+    bool InitCowDevice();
+    bool Run();
     const std::string& GetControlDevicePath() { return control_device_; }
-    const std::string& GetCowDevice() { return cow_device_; }
+    const std::string& GetMiscName() { return misc_name_; }
     uint64_t GetNumSectors() { return num_sectors_; }
+    bool IsAttached() const { return ctrl_fd_ >= 0; }
 
   private:
-    int ReadDmUserHeader();
+    bool ReadDmUserHeader();
     bool ReadDmUserPayload(void* buffer, size_t size);
-    int WriteDmUserPayload(size_t size);
-    int ConstructKernelCowHeader();
+    bool WriteDmUserPayload(size_t size);
+    void ConstructKernelCowHeader();
     bool ReadMetadata();
-    int ZerofillDiskExceptions(size_t read_size);
-    int ReadDiskExceptions(chunk_t chunk, size_t size);
-    int ReadData(chunk_t chunk, size_t size);
+    bool ZerofillDiskExceptions(size_t read_size);
+    bool ReadDiskExceptions(chunk_t chunk, size_t size);
+    bool ReadData(chunk_t chunk, size_t size);
     bool IsChunkIdMetadata(chunk_t chunk);
     chunk_t GetNextAllocatableChunkId(chunk_t chunk);
 
-    int ProcessReplaceOp(const CowOperation* cow_op);
-    int ProcessCopyOp(const CowOperation* cow_op);
-    int ProcessZeroOp();
+    bool ProcessReplaceOp(const CowOperation* cow_op);
+    bool ProcessCopyOp(const CowOperation* cow_op);
+    bool ProcessZeroOp();
 
     loff_t GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
                                int* unmerged_exceptions);
@@ -96,6 +99,7 @@
     std::string cow_device_;
     std::string backing_store_device_;
     std::string control_device_;
+    std::string misc_name_;
 
     unique_fd cow_fd_;
     unique_fd backing_store_fd_;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
index b5e5a96..aa9ba6e 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
@@ -30,7 +30,6 @@
 
 static constexpr uint32_t PACKET_SIZE = 512;
 
-static constexpr char kSnapuserdSocketFirstStage[] = "snapuserd_first_stage";
 static constexpr char kSnapuserdSocket[] = "snapuserd";
 
 static constexpr char kSnapuserdFirstStagePidVar[] = "FIRST_STAGE_SNAPUSERD_PID";
@@ -38,10 +37,6 @@
 // Ensure that the second-stage daemon for snapuserd is running.
 bool EnsureSnapuserdStarted();
 
-// Start the first-stage version of snapuserd, returning its pid. This is used
-// by first-stage init, as well as vts_libsnapshot_test. On failure, -1 is returned.
-pid_t StartFirstStageSnapuserd();
-
 class SnapuserdClient {
   private:
     android::base::unique_fd sockfd_;
@@ -58,13 +53,28 @@
                                                     std::chrono::milliseconds timeout_ms);
 
     bool StopSnapuserd();
-    uint64_t InitDmUserCow(const std::string& cow_device);
-    bool InitializeSnapuserd(const std::string& cow_device, const std::string& backing_device,
-                             const std::string& control_device);
+
+    // Initializing a snapuserd handler is a three-step process:
+    //
+    //  1. Client invokes InitDmUserCow. This creates the snapuserd handler and validates the
+    //     COW. The number of sectors required for the dm-user target is returned.
+    //  2. Client creates the device-mapper device with the dm-user target.
+    //  3. Client calls AttachControlDevice.
+    //
+    // The misc_name must be the "misc_name" given to dm-user in step 2.
+    //
+    uint64_t InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
+                           const std::string& backing_device);
+    bool AttachDmUser(const std::string& misc_name);
 
     // Wait for snapuserd to disassociate with a dm-user control device. This
     // must ONLY be called if the control device has already been deleted.
     bool WaitForDeviceDelete(const std::string& control_device);
+
+    // Detach snapuserd. This shuts down the listener socket, and will cause
+    // snapuserd to gracefully exit once all handler threads have terminated.
+    // This should only be used on first-stage instances of snapuserd.
+    bool DetachSnapuserd();
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
index 1037c12..e8dbe6e 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
@@ -17,6 +17,13 @@
 namespace android {
 namespace snapshot {
 
+#define DM_USER_REQ_MAP_READ 0
+#define DM_USER_REQ_MAP_WRITE 1
+
+#define DM_USER_RESP_SUCCESS 0
+#define DM_USER_RESP_ERROR 1
+#define DM_USER_RESP_UNSUPPORTED 2
+
 // Kernel COW header fields
 static constexpr uint32_t SNAP_MAGIC = 0x70416e53;
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h
index be48400..1491aac 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h
@@ -41,6 +41,7 @@
     QUERY,
     STOP,
     DELETE,
+    DETACH,
     INVALID,
 };
 
@@ -56,7 +57,7 @@
     const std::unique_ptr<Snapuserd>& snapuserd() const { return snapuserd_; }
     std::thread& thread() { return thread_; }
 
-    const std::string& GetControlDevice() const;
+    const std::string& GetMiscName() const;
 };
 
 class Stoppable {
@@ -85,7 +86,9 @@
     std::vector<struct pollfd> watched_fds_;
 
     std::mutex lock_;
-    std::vector<std::unique_ptr<DmUserHandler>> dm_users_;
+
+    using HandlerList = std::vector<std::shared_ptr<DmUserHandler>>;
+    HandlerList dm_users_;
 
     void AddWatchedFd(android::base::borrowed_fd fd);
     void AcceptClient();
@@ -95,7 +98,7 @@
     bool Receivemsg(android::base::borrowed_fd fd, const std::string& str);
 
     void ShutdownThreads();
-    bool WaitForDelete(const std::string& control_device);
+    bool RemoveHandler(const std::string& control_device, bool wait);
     DaemonOperations Resolveop(std::string& input);
     std::string GetDaemonStatus();
     void Parsemsg(std::string const& msg, const char delim, std::vector<std::string>& out);
@@ -103,11 +106,12 @@
     void SetTerminating() { terminating_ = true; }
     bool IsTerminating() { return terminating_; }
 
-    void RunThread(DmUserHandler* handler);
+    void RunThread(std::shared_ptr<DmUserHandler> handler);
+    void JoinAllThreads();
 
-    // Remove a DmUserHandler from dm_users_, searching by its control device.
-    // If none is found, return nullptr.
-    std::unique_ptr<DmUserHandler> RemoveHandler(const std::string& control_device);
+    // Find a DmUserHandler within a lock.
+    HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
+                                      const std::string& misc_name);
 
   public:
     SnapuserdServer() { terminating_ = false; }
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index c4c557e..04c3ccc 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -280,7 +280,7 @@
         return false;
     }
 
-    if (!IsCompressionEnabled() && !EnsureNoOverflowSnapshot(lock.get())) {
+    if (!EnsureNoOverflowSnapshot(lock.get())) {
         LOG(ERROR) << "Cannot ensure there are no overflow snapshots.";
         return false;
     }
@@ -349,6 +349,7 @@
     status->set_state(SnapshotState::CREATED);
     status->set_sectors_allocated(0);
     status->set_metadata_sectors(0);
+    status->set_compression_enabled(IsCompressionEnabled());
 
     if (!WriteSnapshotStatus(lock, *status)) {
         PLOG(ERROR) << "Could not write snapshot status: " << status->name();
@@ -397,7 +398,7 @@
         return false;
     }
 
-    uint64_t base_sectors = snapuserd_client_->InitDmUserCow(cow_file);
+    uint64_t base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
     if (base_sectors == 0) {
         LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
         return false;
@@ -408,9 +409,16 @@
     if (!dm.CreateDevice(name, table, path, timeout_ms)) {
         return false;
     }
+    if (!WaitForDevice(*path, timeout_ms)) {
+        return false;
+    }
 
     auto control_device = "/dev/dm-user/" + misc_name;
-    return snapuserd_client_->InitializeSnapuserd(cow_file, base_device, control_device);
+    if (!WaitForDevice(control_device, timeout_ms)) {
+        return false;
+    }
+
+    return snapuserd_client_->AttachDmUser(misc_name);
 }
 
 bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
@@ -1310,28 +1318,36 @@
     size_t num_cows = 0;
     size_t ok_cows = 0;
     for (const auto& snapshot : snapshots) {
-        std::string cow_name = GetDmUserCowName(snapshot);
-        if (dm.GetState(cow_name) == DmDeviceState::INVALID) {
+        std::string user_cow_name = GetDmUserCowName(snapshot);
+        if (dm.GetState(user_cow_name) == DmDeviceState::INVALID) {
             continue;
         }
 
         DeviceMapper::TargetInfo target;
-        if (!GetSingleTarget(cow_name, TableQuery::Table, &target)) {
+        if (!GetSingleTarget(user_cow_name, TableQuery::Table, &target)) {
             continue;
         }
 
         auto target_type = DeviceMapper::GetTargetType(target.spec);
         if (target_type != "user") {
-            LOG(ERROR) << "Unexpected target type for " << cow_name << ": " << target_type;
+            LOG(ERROR) << "Unexpected target type for " << user_cow_name << ": " << target_type;
             continue;
         }
 
         num_cows++;
 
+        SnapshotStatus snapshot_status;
+        if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) {
+            LOG(ERROR) << "Unable to read snapshot status: " << snapshot;
+            continue;
+        }
+
+        auto misc_name = user_cow_name;
+
         DmTable table;
-        table.Emplace<DmTargetUser>(0, target.spec.length, cow_name);
-        if (!dm.LoadTableAndActivate(cow_name, table)) {
-            LOG(ERROR) << "Unable to swap tables for " << cow_name;
+        table.Emplace<DmTargetUser>(0, target.spec.length, misc_name);
+        if (!dm.LoadTableAndActivate(user_cow_name, table)) {
+            LOG(ERROR) << "Unable to swap tables for " << misc_name;
             continue;
         }
 
@@ -1341,20 +1357,29 @@
             continue;
         }
 
-        std::string cow_device;
-        if (!dm.GetDmDevicePathByName(GetCowName(snapshot), &cow_device)) {
-            LOG(ERROR) << "Could not get device path for " << GetCowName(snapshot);
+        // If no partition was created (the COW exists entirely on /data), the
+        // device-mapper layering is different than if we had a partition.
+        std::string cow_image_name;
+        if (snapshot_status.cow_partition_size() == 0) {
+            cow_image_name = GetCowImageDeviceName(snapshot);
+        } else {
+            cow_image_name = GetCowName(snapshot);
+        }
+
+        std::string cow_image_device;
+        if (!dm.GetDmDevicePathByName(cow_image_name, &cow_image_device)) {
+            LOG(ERROR) << "Could not get device path for " << cow_image_name;
             continue;
         }
 
         // Wait for ueventd to acknowledge and create the control device node.
-        std::string control_device = "/dev/dm-user/" + cow_name;
-        if (!android::fs_mgr::WaitForFile(control_device, 10s)) {
-            LOG(ERROR) << "Could not find control device: " << control_device;
+        std::string control_device = "/dev/dm-user/" + misc_name;
+        if (!WaitForDevice(control_device, 10s)) {
             continue;
         }
 
-        uint64_t base_sectors = snapuserd_client_->InitDmUserCow(cow_device);
+        uint64_t base_sectors =
+                snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, backing_device);
         if (base_sectors == 0) {
             // Unrecoverable as metadata reads from cow device failed
             LOG(FATAL) << "Failed to retrieve base_sectors from Snapuserd";
@@ -1363,10 +1388,10 @@
 
         CHECK(base_sectors == target.spec.length);
 
-        if (!snapuserd_client_->InitializeSnapuserd(cow_device, backing_device, control_device)) {
+        if (!snapuserd_client_->AttachDmUser(misc_name)) {
             // This error is unrecoverable. We cannot proceed because reads to
             // the underlying device will fail.
-            LOG(FATAL) << "Could not initialize snapuserd for " << cow_name;
+            LOG(FATAL) << "Could not initialize snapuserd for " << user_cow_name;
             return false;
         }
 
@@ -1377,19 +1402,6 @@
         LOG(ERROR) << "Could not transition all snapuserd consumers.";
         return false;
     }
-
-    int pid;
-    const char* pid_str = getenv(kSnapuserdFirstStagePidVar);
-    if (pid_str && android::base::ParseInt(pid_str, &pid)) {
-        if (kill(pid, SIGTERM) < 0 && errno != ESRCH) {
-            LOG(ERROR) << "kill snapuserd failed";
-            return false;
-        }
-    } else {
-        LOG(ERROR) << "Could not find or parse " << kSnapuserdFirstStagePidVar
-                   << " for snapuserd pid";
-        return false;
-    }
     return true;
 }
 
@@ -1734,15 +1746,6 @@
         }
     }
 
-    if (use_first_stage_snapuserd_) {
-        // Remove the first-stage socket as a precaution, there is no need to
-        // access the daemon anymore and we'll be killing it once second-stage
-        // is running.
-        auto socket = ANDROID_SOCKET_DIR + "/"s + kSnapuserdSocketFirstStage;
-        snapuserd_client_ = nullptr;
-        unlink(socket.c_str());
-    }
-
     LOG(INFO) << "Created logical partitions with snapshot.";
     return true;
 }
@@ -1888,22 +1891,29 @@
     remaining_time = GetRemainingTime(params.timeout_ms, begin);
     if (remaining_time.count() < 0) return false;
 
-    if (context == SnapshotContext::Update && IsCompressionEnabled()) {
+    if (context == SnapshotContext::Update && live_snapshot_status->compression_enabled()) {
         // Stop here, we can't run dm-user yet, the COW isn't built.
         created_devices.Release();
         return true;
     }
 
-    if (IsCompressionEnabled()) {
+    if (live_snapshot_status->compression_enabled()) {
         auto name = GetDmUserCowName(params.GetPartitionName());
 
-        // :TODO: need to force init to process uevents for these in first-stage.
         std::string cow_path;
         if (!GetMappedImageDevicePath(cow_name, &cow_path)) {
             LOG(ERROR) << "Could not determine path for: " << cow_name;
             return false;
         }
 
+        // Ensure both |base_path| and |cow_path| are created, for snapuserd.
+        if (!WaitForDevice(base_path, remaining_time)) {
+            return false;
+        }
+        if (!WaitForDevice(cow_path, remaining_time)) {
+            return false;
+        }
+
         std::string new_cow_device;
         if (!MapDmUserCow(lock, name, cow_path, base_path, remaining_time, &new_cow_device)) {
             LOG(ERROR) << "Could not map dm-user device for partition "
@@ -2043,18 +2053,18 @@
         if (!EnsureSnapuserdConnected()) {
             return false;
         }
-        if (!dm.DeleteDevice(dm_user_name)) {
+        if (!dm.DeleteDeviceIfExists(dm_user_name)) {
             LOG(ERROR) << "Cannot unmap " << dm_user_name;
             return false;
         }
 
-        auto control_device = "/dev/dm-user/" + dm_user_name;
-        if (!snapuserd_client_->WaitForDeviceDelete(control_device)) {
+        if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) {
             LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
             return false;
         }
 
         // Ensure the control device is gone so we don't run into ABA problems.
+        auto control_device = "/dev/dm-user/" + dm_user_name;
         if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) {
             LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink";
             return false;
@@ -2246,6 +2256,7 @@
 bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state) {
     SnapshotUpdateStatus status = {};
     status.set_state(state);
+    status.set_compression_enabled(IsCompressionEnabled());
     return WriteSnapshotUpdateStatus(lock, status);
 }
 
@@ -2381,28 +2392,11 @@
         return true;
     }
 
-    std::string socket;
-    if (use_first_stage_snapuserd_) {
-        auto pid = StartFirstStageSnapuserd();
-        if (pid < 0) {
-            LOG(ERROR) << "Failed to start snapuserd";
-            return false;
-        }
-
-        auto pid_str = std::to_string(static_cast<int>(pid));
-        if (setenv(kSnapuserdFirstStagePidVar, pid_str.c_str(), 1) < 0) {
-            PLOG(ERROR) << "setenv failed storing the snapuserd pid";
-        }
-
-        socket = kSnapuserdSocketFirstStage;
-    } else {
-        if (!EnsureSnapuserdStarted()) {
-            return false;
-        }
-        socket = kSnapuserdSocket;
+    if (!use_first_stage_snapuserd_ && !EnsureSnapuserdStarted()) {
+        return false;
     }
 
-    snapuserd_client_ = SnapuserdClient::Connect(socket, 10s);
+    snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
     if (!snapuserd_client_) {
         LOG(ERROR) << "Unable to connect to snapuserd";
         return false;
@@ -2724,7 +2718,7 @@
             return Return::Error();
         }
 
-        if (IsCompressionEnabled()) {
+        if (it->second.compression_enabled()) {
             unique_fd fd(open(cow_path.c_str(), O_RDWR | O_CLOEXEC));
             if (fd < 0) {
                 PLOG(ERROR) << "open " << cow_path << " failed for snapshot "
@@ -2818,7 +2812,7 @@
         return nullptr;
     }
 
-    if (IsCompressionEnabled()) {
+    if (status.compression_enabled()) {
         return OpenCompressedSnapshotWriter(lock.get(), source_device, params.GetPartitionName(),
                                             status, paths);
     }
@@ -3143,6 +3137,14 @@
 
     auto& dm = DeviceMapper::Instance();
     for (const auto& snapshot : snapshots) {
+        SnapshotStatus status;
+        if (!ReadSnapshotStatus(lock, snapshot, &status)) {
+            return false;
+        }
+        if (status.compression_enabled()) {
+            continue;
+        }
+
         std::vector<DeviceMapper::TargetInfo> targets;
         if (!dm.GetTableStatus(snapshot, &targets)) {
             LOG(ERROR) << "Could not read snapshot device table: " << snapshot;
@@ -3267,5 +3269,47 @@
     return true;
 }
 
+bool SnapshotManager::WaitForDevice(const std::string& device,
+                                    std::chrono::milliseconds timeout_ms) {
+    if (!android::base::StartsWith(device, "/")) {
+        return true;
+    }
+
+    // In first-stage init, we rely on init setting a callback which can
+    // regenerate uevents and populate /dev for us.
+    if (uevent_regen_callback_) {
+        if (!uevent_regen_callback_(device)) {
+            LOG(ERROR) << "Failed to find device after regenerating uevents: " << device;
+            return false;
+        }
+        return true;
+    }
+
+    // Otherwise, the only kind of device we need to wait for is a dm-user
+    // misc device. Normal calls to DeviceMapper::CreateDevice() guarantee
+    // the path has been created.
+    if (!android::base::StartsWith(device, "/dev/dm-user/")) {
+        return true;
+    }
+
+    if (timeout_ms.count() == 0) {
+        LOG(ERROR) << "No timeout was specified to wait for device: " << device;
+        return false;
+    }
+    if (!android::fs_mgr::WaitForFile(device, timeout_ms)) {
+        LOG(ERROR) << "Timed out waiting for device to appear: " << device;
+        return false;
+    }
+    return true;
+}
+
+bool SnapshotManager::IsSnapuserdRequired() {
+    auto lock = LockExclusive();
+    if (!lock) return false;
+
+    auto status = ReadSnapshotUpdateStatus(lock.get());
+    return status.state() != UpdateState::None && status.compression_enabled();
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 74558ab..c6a1786 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -1760,9 +1760,6 @@
         GTEST_SKIP() << "Skipping Virtual A/B Compression test";
     }
 
-    AutoKill auto_kill(StartFirstStageSnapuserd());
-    ASSERT_TRUE(auto_kill.valid());
-
     // Ensure a connection to the second-stage daemon, but use the first-stage
     // code paths thereafter.
     ASSERT_TRUE(sm->EnsureSnapuserdConnected());
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 7c393fc..49e6c3d 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -28,8 +28,8 @@
 using namespace android::dm;
 using android::base::unique_fd;
 
-#define DM_USER_MAP_READ 0
-#define DM_USER_MAP_WRITE 1
+#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
+#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
 
 static constexpr size_t PAYLOAD_SIZE = (1UL << 16);
 
@@ -66,11 +66,19 @@
     return header;
 }
 
+Snapuserd::Snapuserd(const std::string& misc_name, const std::string& cow_device,
+                     const std::string& backing_device) {
+    misc_name_ = misc_name;
+    cow_device_ = cow_device;
+    backing_store_device_ = backing_device;
+    control_device_ = "/dev/dm-user/" + misc_name;
+}
+
 // Construct kernel COW header in memory
 // This header will be in sector 0. The IO
 // request will always be 4k. After constructing
 // the header, zero out the remaining block.
-int Snapuserd::ConstructKernelCowHeader() {
+void Snapuserd::ConstructKernelCowHeader() {
     void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SIZE);
     CHECK(buffer != nullptr);
 
@@ -82,25 +90,23 @@
     dh->valid = SNAPSHOT_VALID;
     dh->version = SNAPSHOT_DISK_VERSION;
     dh->chunk_size = CHUNK_SIZE;
-
-    return BLOCK_SIZE;
 }
 
 // Start the replace operation. This will read the
 // internal COW format and if the block is compressed,
 // it will be de-compressed.
-int Snapuserd::ProcessReplaceOp(const CowOperation* cow_op) {
+bool Snapuserd::ProcessReplaceOp(const CowOperation* cow_op) {
     if (!reader_->ReadData(*cow_op, &bufsink_)) {
-        LOG(ERROR) << "ReadData failed for chunk: " << cow_op->new_block;
-        return -EIO;
+        SNAP_LOG(ERROR) << "ReadData failed for chunk: " << cow_op->new_block;
+        return false;
     }
 
-    return BLOCK_SIZE;
+    return true;
 }
 
 // Start the copy operation. This will read the backing
 // block device which is represented by cow_op->source.
-int Snapuserd::ProcessCopyOp(const CowOperation* cow_op) {
+bool Snapuserd::ProcessCopyOp(const CowOperation* cow_op) {
     void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SIZE);
     CHECK(buffer != nullptr);
 
@@ -108,20 +114,20 @@
     // if the successive blocks are contiguous.
     if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SIZE,
                                           cow_op->source * BLOCK_SIZE)) {
-        LOG(ERROR) << "Copy-op failed. Read from backing store at: " << cow_op->source;
-        return -1;
+        SNAP_LOG(ERROR) << "Copy-op failed. Read from backing store at: " << cow_op->source;
+        return false;
     }
 
-    return BLOCK_SIZE;
+    return true;
 }
 
-int Snapuserd::ProcessZeroOp() {
+bool Snapuserd::ProcessZeroOp() {
     // Zero out the entire block
     void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SIZE);
     CHECK(buffer != nullptr);
 
     memset(buffer, 0, BLOCK_SIZE);
-    return BLOCK_SIZE;
+    return true;
 }
 
 /*
@@ -146,11 +152,9 @@
  * 3: Zero operation
  *
  */
-int Snapuserd::ReadData(chunk_t chunk, size_t size) {
-    int ret = 0;
-
+bool Snapuserd::ReadData(chunk_t chunk, size_t size) {
     size_t read_size = size;
-
+    bool ret = true;
     chunk_t chunk_key = chunk;
     uint32_t stride;
     lldiv_t divresult;
@@ -161,41 +165,39 @@
     while (read_size > 0) {
         const CowOperation* cow_op = chunk_map_[chunk_key];
         CHECK(cow_op != nullptr);
-        int result;
 
         switch (cow_op->type) {
             case kCowReplaceOp: {
-                result = ProcessReplaceOp(cow_op);
+                ret = ProcessReplaceOp(cow_op);
                 break;
             }
 
             case kCowZeroOp: {
-                result = ProcessZeroOp();
+                ret = ProcessZeroOp();
                 break;
             }
 
             case kCowCopyOp: {
-                result = ProcessCopyOp(cow_op);
+                ret = ProcessCopyOp(cow_op);
                 break;
             }
 
             default: {
-                LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
-                ret = -EIO;
-                goto done;
+                SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+                ret = false;
+                break;
             }
         }
 
-        if (result < 0) {
-            ret = result;
-            goto done;
+        if (!ret) {
+            SNAP_LOG(ERROR) << "ReadData failed for operation: " << cow_op->type;
+            return false;
         }
 
         // Update the buffer offset
         bufsink_.UpdateBufferOffset(BLOCK_SIZE);
 
         read_size -= BLOCK_SIZE;
-        ret += BLOCK_SIZE;
 
         // Start iterating the chunk incrementally; Since while
         // constructing the metadata, we know that the chunk IDs
@@ -223,8 +225,6 @@
         }
     }
 
-done:
-
     // Reset the buffer offset
     bufsink_.ResetBufferOffset();
     return ret;
@@ -241,16 +241,18 @@
  * When dm-snap starts parsing the buffer, it will stop
  * reading metadata page once the buffer content is zero.
  */
-int Snapuserd::ZerofillDiskExceptions(size_t read_size) {
+bool Snapuserd::ZerofillDiskExceptions(size_t read_size) {
     size_t size = exceptions_per_area_ * sizeof(struct disk_exception);
 
-    if (read_size > size) return -EINVAL;
+    if (read_size > size) {
+        return false;
+    }
 
     void* buffer = bufsink_.GetPayloadBuffer(size);
     CHECK(buffer != nullptr);
 
     memset(buffer, 0, size);
-    return size;
+    return true;
 }
 
 /*
@@ -266,7 +268,7 @@
  * Convert the chunk ID to index into the vector which gives us
  * the metadata page.
  */
-int Snapuserd::ReadDiskExceptions(chunk_t chunk, size_t read_size) {
+bool Snapuserd::ReadDiskExceptions(chunk_t chunk, size_t read_size) {
     uint32_t stride = exceptions_per_area_ + 1;
     size_t size;
 
@@ -276,17 +278,19 @@
     if (divresult.quot < vec_.size()) {
         size = exceptions_per_area_ * sizeof(struct disk_exception);
 
-        if (read_size > size) return -EINVAL;
+        if (read_size > size) {
+            return false;
+        }
 
         void* buffer = bufsink_.GetPayloadBuffer(size);
         CHECK(buffer != nullptr);
 
         memcpy(buffer, vec_[divresult.quot].get(), size);
     } else {
-        size = ZerofillDiskExceptions(read_size);
+        return ZerofillDiskExceptions(read_size);
     }
 
-    return size;
+    return true;
 }
 
 loff_t Snapuserd::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
@@ -321,7 +325,7 @@
 
     CHECK(!(*unmerged_exceptions == exceptions_per_area_));
 
-    LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
+    SNAP_LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
     return offset;
 }
 
@@ -330,7 +334,7 @@
     int merged_ops_cur_iter = 0;
 
     // Find the operations which are merged in this cycle.
-    while ((unmerged_exceptions + merged_ops_cur_iter) <= exceptions_per_area_) {
+    while ((unmerged_exceptions + merged_ops_cur_iter) < exceptions_per_area_) {
         struct disk_exception* merged_de =
                 reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
         struct disk_exception* cow_de =
@@ -354,11 +358,11 @@
             CHECK(cow_de->new_chunk == 0);
             break;
         } else {
-            LOG(ERROR) << "Error in merge operation. Found invalid metadata";
-            LOG(ERROR) << "merged_de-old-chunk: " << merged_de->old_chunk;
-            LOG(ERROR) << "merged_de-new-chunk: " << merged_de->new_chunk;
-            LOG(ERROR) << "cow_de-old-chunk: " << cow_de->old_chunk;
-            LOG(ERROR) << "cow_de-new-chunk: " << cow_de->new_chunk;
+            SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata";
+            SNAP_LOG(ERROR) << "merged_de-old-chunk: " << merged_de->old_chunk;
+            SNAP_LOG(ERROR) << "merged_de-new-chunk: " << merged_de->new_chunk;
+            SNAP_LOG(ERROR) << "cow_de-old-chunk: " << cow_de->old_chunk;
+            SNAP_LOG(ERROR) << "cow_de-new-chunk: " << cow_de->new_chunk;
             return -1;
         }
     }
@@ -383,19 +387,19 @@
 
         if (!(cow_op->type == kCowReplaceOp || cow_op->type == kCowZeroOp ||
               cow_op->type == kCowCopyOp)) {
-            LOG(ERROR) << "Unknown operation-type found during merge: " << cow_op->type;
+            SNAP_LOG(ERROR) << "Unknown operation-type found during merge: " << cow_op->type;
             return false;
         }
 
         merged_ops_cur_iter -= 1;
-        LOG(DEBUG) << "Merge op found of type " << cow_op->type
-                   << "Pending-merge-ops: " << merged_ops_cur_iter;
+        SNAP_LOG(DEBUG) << "Merge op found of type " << cow_op->type
+                        << "Pending-merge-ops: " << merged_ops_cur_iter;
         cowop_iter_->Next();
     }
 
     if (cowop_iter_->Done()) {
         CHECK(merged_ops_cur_iter == 0);
-        LOG(DEBUG) << "All cow operations merged successfully in this cycle";
+        SNAP_LOG(DEBUG) << "All cow operations merged successfully in this cycle";
     }
 
     return true;
@@ -406,14 +410,15 @@
     CowHeader header;
 
     if (!reader_->GetHeader(&header)) {
-        LOG(ERROR) << "Failed to get header";
+        SNAP_LOG(ERROR) << "Failed to get header";
         return false;
     }
 
     // ChunkID to vector index
     lldiv_t divresult = lldiv(chunk, stride);
     CHECK(divresult.quot < vec_.size());
-    LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk << " Metadata-Index: " << divresult.quot;
+    SNAP_LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk
+                    << " Metadata-Index: " << divresult.quot;
 
     int unmerged_exceptions = 0;
     loff_t offset = GetMergeStartOffset(buffer, vec_[divresult.quot].get(), &unmerged_exceptions);
@@ -428,11 +433,11 @@
     header.num_merge_ops += merged_ops_cur_iter;
     reader_->UpdateMergeProgress(merged_ops_cur_iter);
     if (!writer_->CommitMerge(merged_ops_cur_iter)) {
-        LOG(ERROR) << "CommitMerge failed...";
+        SNAP_LOG(ERROR) << "CommitMerge failed...";
         return false;
     }
 
-    LOG(DEBUG) << "Merge success";
+    SNAP_LOG(DEBUG) << "Merge success";
     return true;
 }
 
@@ -512,21 +517,21 @@
     bool prev_copy_op = false;
     bool metadata_found = false;
 
-    LOG(DEBUG) << "ReadMetadata Start...";
+    SNAP_LOG(DEBUG) << "ReadMetadata Start...";
 
     if (!reader_->Parse(cow_fd_)) {
-        LOG(ERROR) << "Failed to parse";
+        SNAP_LOG(ERROR) << "Failed to parse";
         return false;
     }
 
     if (!reader_->GetHeader(&header)) {
-        LOG(ERROR) << "Failed to get header";
+        SNAP_LOG(ERROR) << "Failed to get header";
         return false;
     }
 
     CHECK(header.block_size == BLOCK_SIZE);
 
-    LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops;
+    SNAP_LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops;
 
     writer_ = std::make_unique<CowWriter>(options);
     writer_->InitializeMerge(cow_fd_.get(), &header);
@@ -562,7 +567,7 @@
 
         if (!(cow_op->type == kCowReplaceOp || cow_op->type == kCowZeroOp ||
               cow_op->type == kCowCopyOp)) {
-            LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+            SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
             return false;
         }
 
@@ -577,7 +582,7 @@
         de->old_chunk = cow_op->new_block;
         de->new_chunk = next_free;
 
-        LOG(DEBUG) << "Old-chunk: " << de->old_chunk << "New-chunk: " << de->new_chunk;
+        SNAP_LOG(DEBUG) << "Old-chunk: " << de->old_chunk << "New-chunk: " << de->new_chunk;
 
         // Store operation pointer.
         chunk_map_[next_free] = cow_op;
@@ -601,7 +606,7 @@
 
             if (cowop_riter_->Done()) {
                 vec_.push_back(std::move(de_ptr));
-                LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
+                SNAP_LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
             }
         }
 
@@ -613,12 +618,12 @@
     // is aware that merge is completed.
     if (num_ops || !metadata_found) {
         vec_.push_back(std::move(de_ptr));
-        LOG(DEBUG) << "ReadMetadata() completed. Partially filled area num_ops: " << num_ops
-                   << "Areas : " << vec_.size();
+        SNAP_LOG(DEBUG) << "ReadMetadata() completed. Partially filled area num_ops: " << num_ops
+                        << "Areas : " << vec_.size();
     }
 
-    LOG(DEBUG) << "ReadMetadata() completed. chunk_id: " << next_free
-               << "Num Sector: " << ChunkToSector(next_free);
+    SNAP_LOG(DEBUG) << "ReadMetadata() completed. chunk_id: " << next_free
+                    << "Num Sector: " << ChunkToSector(next_free);
 
     // Initialize the iterator for merging
     cowop_iter_ = reader_->GetOpIter();
@@ -640,44 +645,39 @@
 
 // Read Header from dm-user misc device. This gives
 // us the sector number for which IO is issued by dm-snapshot device
-int Snapuserd::ReadDmUserHeader() {
-    int ret;
-
-    ret = read(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header));
-    if (ret < 0) {
-        PLOG(ERROR) << "Control-read failed with: " << ret;
-        return ret;
-    }
-
-    return sizeof(struct dm_user_header);
-}
-
-// Send the payload/data back to dm-user misc device.
-int Snapuserd::WriteDmUserPayload(size_t size) {
-    if (!android::base::WriteFully(ctrl_fd_, bufsink_.GetBufPtr(),
-                                   sizeof(struct dm_user_header) + size)) {
-        PLOG(ERROR) << "Write to dm-user failed";
-        return -1;
-    }
-
-    return sizeof(struct dm_user_header) + size;
-}
-
-bool Snapuserd::ReadDmUserPayload(void* buffer, size_t size) {
-    if (!android::base::ReadFully(ctrl_fd_, buffer, size)) {
-        PLOG(ERROR) << "ReadDmUserPayload failed";
+bool Snapuserd::ReadDmUserHeader() {
+    if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
+        SNAP_PLOG(ERROR) << "Control-read failed";
         return false;
     }
 
     return true;
 }
 
-bool Snapuserd::InitCowDevice(std::string& cow_device) {
-    cow_device_ = cow_device;
+// Send the payload/data back to dm-user misc device.
+bool Snapuserd::WriteDmUserPayload(size_t size) {
+    if (!android::base::WriteFully(ctrl_fd_, bufsink_.GetBufPtr(),
+                                   sizeof(struct dm_user_header) + size)) {
+        SNAP_PLOG(ERROR) << "Write to dm-user failed";
+        return false;
+    }
 
+    return true;
+}
+
+bool Snapuserd::ReadDmUserPayload(void* buffer, size_t size) {
+    if (!android::base::ReadFully(ctrl_fd_, buffer, size)) {
+        SNAP_PLOG(ERROR) << "ReadDmUserPayload failed";
+        return false;
+    }
+
+    return true;
+}
+
+bool Snapuserd::InitCowDevice() {
     cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
     if (cow_fd_ < 0) {
-        PLOG(ERROR) << "Open Failed: " << cow_device_;
+        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
         return false;
     }
 
@@ -691,51 +691,45 @@
     return ReadMetadata();
 }
 
-bool Snapuserd::InitBackingAndControlDevice(std::string& backing_device,
-                                            std::string& control_device) {
-    backing_store_device_ = backing_device;
-    control_device_ = control_device;
-
+bool Snapuserd::InitBackingAndControlDevice() {
     backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
     if (backing_store_fd_ < 0) {
-        PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+        SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
         return false;
     }
 
     ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
     if (ctrl_fd_ < 0) {
-        PLOG(ERROR) << "Unable to open " << control_device_;
+        SNAP_PLOG(ERROR) << "Unable to open " << control_device_;
         return false;
     }
 
     return true;
 }
 
-int Snapuserd::Run() {
-    int ret = 0;
-
+bool Snapuserd::Run() {
     struct dm_user_header* header = bufsink_.GetHeaderPtr();
 
     bufsink_.Clear();
 
-    ret = ReadDmUserHeader();
-    if (ret < 0) return ret;
+    if (!ReadDmUserHeader()) {
+        SNAP_LOG(ERROR) << "ReadDmUserHeader failed";
+        return false;
+    }
 
-    LOG(DEBUG) << "dm-user returned " << ret << " bytes";
-
-    LOG(DEBUG) << "msg->seq: " << std::hex << header->seq;
-    LOG(DEBUG) << "msg->type: " << std::hex << header->type;
-    LOG(DEBUG) << "msg->flags: " << std::hex << header->flags;
-    LOG(DEBUG) << "msg->sector: " << std::hex << header->sector;
-    LOG(DEBUG) << "msg->len: " << std::hex << header->len;
+    SNAP_LOG(DEBUG) << "msg->seq: " << std::hex << header->seq;
+    SNAP_LOG(DEBUG) << "msg->type: " << std::hex << header->type;
+    SNAP_LOG(DEBUG) << "msg->flags: " << std::hex << header->flags;
+    SNAP_LOG(DEBUG) << "msg->sector: " << std::hex << header->sector;
+    SNAP_LOG(DEBUG) << "msg->len: " << std::hex << header->len;
 
     switch (header->type) {
-        case DM_USER_MAP_READ: {
+        case DM_USER_REQ_MAP_READ: {
             size_t remaining_size = header->len;
             loff_t offset = 0;
-            ret = 0;
             do {
                 size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+                header->type = DM_USER_RESP_SUCCESS;
 
                 // Request to sector 0 is always for kernel
                 // representation of COW header. This IO should be only
@@ -745,8 +739,8 @@
                 if (header->sector == 0) {
                     CHECK(metadata_read_done_ == true);
                     CHECK(read_size == BLOCK_SIZE);
-                    ret = ConstructKernelCowHeader();
-                    if (ret < 0) return ret;
+                    ConstructKernelCowHeader();
+                    SNAP_LOG(DEBUG) << "Kernel header constructed";
                 } else {
                     // Convert the sector number to a chunk ID.
                     //
@@ -756,71 +750,100 @@
                     chunk_t chunk = SectorToChunk(header->sector);
 
                     if (chunk_map_.find(chunk) == chunk_map_.end()) {
-                        ret = ReadDiskExceptions(chunk, read_size);
-                        if (ret < 0) {
-                            LOG(ERROR) << "ReadDiskExceptions failed";
-                            return ret;
+                        if (!ReadDiskExceptions(chunk, read_size)) {
+                            SNAP_LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk
+                                            << "Sector: " << header->sector;
+                            header->type = DM_USER_RESP_ERROR;
+                        } else {
+                            SNAP_LOG(DEBUG) << "ReadDiskExceptions success for chunk id: " << chunk
+                                            << "Sector: " << header->sector;
                         }
                     } else {
                         chunk_t num_chunks_read = (offset >> BLOCK_SHIFT);
-                        ret = ReadData(chunk + num_chunks_read, read_size);
-                        if (ret < 0) {
-                            LOG(ERROR) << "ReadData failed";
-                            // TODO: Bug 168259959: All the error paths from this function
-                            // should send error code to dm-user thereby IO
-                            // terminates with an error from dm-user. Returning
-                            // here without sending error code will block the
-                            // IO.
-                            return ret;
+                        if (!ReadData(chunk + num_chunks_read, read_size)) {
+                            SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk
+                                            << "Sector: " << header->sector;
+                            header->type = DM_USER_RESP_ERROR;
+                        } else {
+                            SNAP_LOG(DEBUG) << "ReadData success for chunk id: " << chunk
+                                            << "Sector: " << header->sector;
                         }
                     }
                 }
 
-                ssize_t written = WriteDmUserPayload(ret);
-                if (written < 0) return written;
-
-                remaining_size -= ret;
-                offset += ret;
-                if (remaining_size) {
-                    LOG(DEBUG) << "Write done ret: " << ret
-                               << " remaining size: " << remaining_size;
+                // Daemon will not be terminated if there is any error. We will
+                // just send the error back to dm-user.
+                if (!WriteDmUserPayload(read_size)) {
+                    return false;
                 }
+
+                remaining_size -= read_size;
+                offset += read_size;
             } while (remaining_size);
 
             break;
         }
 
-        case DM_USER_MAP_WRITE: {
+        case DM_USER_REQ_MAP_WRITE: {
+            // device mapper has the capability to allow
+            // targets to flush the cache when writes are completed. This
+            // is controlled by each target by a flag "flush_supported".
+            // This flag is set by dm-user. When flush is supported,
+            // a number of zero-length bio's will be submitted to
+            // the target for the purpose of flushing cache. It is the
+            // responsibility of the target driver - which is dm-user in this
+            // case, to remap these bio's to the underlying device. Since,
+            // there is no underlying device for dm-user, this zero length
+            // bio's gets routed to daemon.
+            //
+            // Flush operations are generated post merge by dm-snap by having
+            // REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything
+            // to flush per se; hence, just respond back with a success message.
+            if (header->sector == 0) {
+                CHECK(header->len == 0);
+                header->type = DM_USER_RESP_SUCCESS;
+                if (!WriteDmUserPayload(0)) {
+                    return false;
+                }
+                break;
+            }
+
             size_t remaining_size = header->len;
             size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
             CHECK(read_size == BLOCK_SIZE);
+
             CHECK(header->sector > 0);
             chunk_t chunk = SectorToChunk(header->sector);
             CHECK(chunk_map_.find(chunk) == chunk_map_.end());
 
             void* buffer = bufsink_.GetPayloadBuffer(read_size);
             CHECK(buffer != nullptr);
+            header->type = DM_USER_RESP_SUCCESS;
 
             if (!ReadDmUserPayload(buffer, read_size)) {
-                return 1;
+                SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
+                                << "Sector: " << header->sector;
+                header->type = DM_USER_RESP_ERROR;
             }
 
-            if (!ProcessMergeComplete(chunk, buffer)) {
-                LOG(ERROR) << "ProcessMergeComplete failed...";
-                return 1;
+            if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
+                SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
+                                << "Sector: " << header->sector;
+                header->type = DM_USER_RESP_ERROR;
+            } else {
+                SNAP_LOG(DEBUG) << "ProcessMergeComplete success for chunk id: " << chunk
+                                << "Sector: " << header->sector;
             }
 
-            // Write the header only.
-            ssize_t written = WriteDmUserPayload(0);
-            if (written < 0) return written;
+            if (!WriteDmUserPayload(0)) {
+                return false;
+            }
 
             break;
         }
     }
 
-    LOG(DEBUG) << "read() finished, next message";
-
-    return 0;
+    return true;
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp
index d7fdb43..7282bff 100644
--- a/fs_mgr/libsnapshot/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_client.cpp
@@ -54,25 +54,6 @@
     return true;
 }
 
-pid_t StartFirstStageSnapuserd() {
-    pid_t pid = fork();
-    if (pid < 0) {
-        PLOG(ERROR) << "fork failed";
-        return pid;
-    }
-    if (pid != 0) {
-        return pid;
-    }
-
-    std::string arg0 = "/system/bin/snapuserd";
-    std::string arg1 = kSnapuserdSocketFirstStage;
-    char* const argv[] = {arg0.data(), arg1.data(), nullptr};
-    if (execv(arg0.c_str(), argv) < 0) {
-        PLOG(FATAL) << "execv failed";
-    }
-    return pid;
-}
-
 SnapuserdClient::SnapuserdClient(android::base::unique_fd&& sockfd) : sockfd_(std::move(sockfd)) {}
 
 static inline bool IsRetryErrno() {
@@ -183,10 +164,8 @@
     return true;
 }
 
-bool SnapuserdClient::InitializeSnapuserd(const std::string& cow_device,
-                                          const std::string& backing_device,
-                                          const std::string& control_device) {
-    std::string msg = "start," + cow_device + "," + backing_device + "," + control_device;
+bool SnapuserdClient::AttachDmUser(const std::string& misc_name) {
+    std::string msg = "start," + misc_name;
     if (!Sendmsg(msg)) {
         LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon";
         return false;
@@ -202,8 +181,10 @@
     return true;
 }
 
-uint64_t SnapuserdClient::InitDmUserCow(const std::string& cow_device) {
-    std::string msg = "init," + cow_device;
+uint64_t SnapuserdClient::InitDmUserCow(const std::string& misc_name, const std::string& cow_device,
+                                        const std::string& backing_device) {
+    std::vector<std::string> parts = {"init", misc_name, cow_device, backing_device};
+    std::string msg = android::base::Join(parts, ",");
     if (!Sendmsg(msg)) {
         LOG(ERROR) << "Failed to send message " << msg << " to snapuserd daemon";
         return 0;
@@ -213,7 +194,7 @@
 
     std::vector<std::string> input = android::base::Split(str, ",");
 
-    if (input[0] != "success") {
+    if (input.empty() || input[0] != "success") {
         LOG(ERROR) << "Failed to receive number of sectors for " << msg << " from snapuserd daemon";
         return 0;
     }
@@ -229,5 +210,13 @@
     return num_sectors;
 }
 
+bool SnapuserdClient::DetachSnapuserd() {
+    if (!Sendmsg("detach")) {
+        LOG(ERROR) << "Failed to detach snapuserd.";
+        return false;
+    }
+    return true;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp
index 3aa6136..7a5cead 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_server.cpp
@@ -38,6 +38,7 @@
     if (input == "stop") return DaemonOperations::STOP;
     if (input == "query") return DaemonOperations::QUERY;
     if (input == "delete") return DaemonOperations::DELETE;
+    if (input == "detach") return DaemonOperations::DETACH;
 
     return DaemonOperations::INVALID;
 }
@@ -72,23 +73,11 @@
 
 void SnapuserdServer::ShutdownThreads() {
     StopThreads();
-
-    // Acquire the thread list within the lock.
-    std::vector<std::unique_ptr<DmUserHandler>> dm_users;
-    {
-        std::lock_guard<std::mutex> guard(lock_);
-        dm_users = std::move(dm_users_);
-    }
-
-    for (auto& client : dm_users) {
-        auto& th = client->thread();
-
-        if (th.joinable()) th.join();
-    }
+    JoinAllThreads();
 }
 
-const std::string& DmUserHandler::GetControlDevice() const {
-    return snapuserd_->GetControlDevicePath();
+const std::string& DmUserHandler::GetMiscName() const {
+    return snapuserd_->GetMiscName();
 }
 
 bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
@@ -126,16 +115,16 @@
     switch (op) {
         case DaemonOperations::INIT: {
             // Message format:
-            // init,<cow_device_path>
+            // init,<misc_name>,<cow_device_path>,<control_device>
             //
             // Reads the metadata and send the number of sectors
-            if (out.size() != 2) {
+            if (out.size() != 4) {
                 LOG(ERROR) << "Malformed init message, " << out.size() << " parts";
                 return Sendmsg(fd, "fail");
             }
 
-            auto snapuserd = std::make_unique<Snapuserd>();
-            if (!snapuserd->InitCowDevice(out[1])) {
+            auto snapuserd = std::make_unique<Snapuserd>(out[1], out[2], out[3]);
+            if (!snapuserd->InitCowDevice()) {
                 LOG(ERROR) << "Failed to initialize Snapuserd";
                 return Sendmsg(fd, "fail");
             }
@@ -145,6 +134,10 @@
             auto handler = std::make_unique<DmUserHandler>(std::move(snapuserd));
             {
                 std::lock_guard<std::mutex> lock(lock_);
+                if (FindHandler(&lock, out[1]) != dm_users_.end()) {
+                    LOG(ERROR) << "Handler already exists: " << out[1];
+                    return Sendmsg(fd, "fail");
+                }
                 dm_users_.push_back(std::move(handler));
             }
 
@@ -152,37 +145,30 @@
         }
         case DaemonOperations::START: {
             // Message format:
-            // start,<cow_device_path>,<source_device_path>,<control_device>
+            // start,<misc_name>
             //
             // Start the new thread which binds to dm-user misc device
-            if (out.size() != 4) {
+            if (out.size() != 2) {
                 LOG(ERROR) << "Malformed start message, " << out.size() << " parts";
                 return Sendmsg(fd, "fail");
             }
 
-            bool found = false;
-            {
-                std::lock_guard<std::mutex> lock(lock_);
-                auto iter = dm_users_.begin();
-                while (iter != dm_users_.end()) {
-                    if ((*iter)->snapuserd()->GetCowDevice() == out[1]) {
-                        if (!((*iter)->snapuserd()->InitBackingAndControlDevice(out[2], out[3]))) {
-                            LOG(ERROR) << "Failed to initialize control device: " << out[3];
-                            break;
-                        }
-                        (*iter)->thread() = std::thread(
-                                std::bind(&SnapuserdServer::RunThread, this, (*iter).get()));
-                        found = true;
-                        break;
-                    }
-                    iter++;
-                }
-            }
-            if (found) {
-                return Sendmsg(fd, "success");
-            } else {
+            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()->IsAttached()) {
+                LOG(ERROR) << "Tried to re-attach control device: " << out[1];
+                return Sendmsg(fd, "fail");
+            }
+            if (!((*iter)->snapuserd()->InitBackingAndControlDevice())) {
+                LOG(ERROR) << "Failed to initialize control device: " << out[1];
+                return Sendmsg(fd, "fail");
+            }
+            (*iter)->thread() = std::thread(std::bind(&SnapuserdServer::RunThread, this, *iter));
+            return Sendmsg(fd, "success");
         }
         case DaemonOperations::STOP: {
             // Message format: stop
@@ -207,16 +193,20 @@
         }
         case DaemonOperations::DELETE: {
             // Message format:
-            // delete,<control_device_path>
+            // delete,<misc_name>
             if (out.size() != 2) {
                 LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
                 return Sendmsg(fd, "fail");
             }
-            if (!WaitForDelete(out[1])) {
+            if (!RemoveHandler(out[1], true)) {
                 return Sendmsg(fd, "fail");
             }
             return Sendmsg(fd, "success");
         }
+        case DaemonOperations::DETACH: {
+            terminating_ = true;
+            return Sendmsg(fd, "success");
+        }
         default: {
             LOG(ERROR) << "Received unknown message type from client";
             Sendmsg(fd, "fail");
@@ -225,25 +215,21 @@
     }
 }
 
-void SnapuserdServer::RunThread(DmUserHandler* handler) {
-    LOG(INFO) << "Entering thread for handler: " << handler->GetControlDevice();
+void SnapuserdServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
+    LOG(INFO) << "Entering thread for handler: " << handler->GetMiscName();
 
     while (!StopRequested()) {
-        if (handler->snapuserd()->Run() < 0) {
-            LOG(INFO) << "Snapuserd: Thread terminating as control device is de-registered";
+        if (!handler->snapuserd()->Run()) {
+            LOG(INFO) << "Snapuserd: Thread terminating";
             break;
         }
     }
 
-    LOG(INFO) << "Exiting thread for handler: " << handler->GetControlDevice();
+    LOG(INFO) << "Exiting thread for handler: " << handler->GetMiscName();
 
-    if (auto client = RemoveHandler(handler->GetControlDevice())) {
-        // The main thread did not receive a WaitForDelete request for this
-        // control device. Since we transferred ownership within the lock,
-        // we know join() was never called, and will never be called. We can
-        // safely detach here.
-        client->thread().detach();
-    }
+    // If the main thread called RemoveHandler, the handler was already removed
+    // from within the lock, and calling RemoveHandler again has no effect.
+    RemoveHandler(handler->GetMiscName(), false);
 }
 
 bool SnapuserdServer::Start(const std::string& socketname) {
@@ -293,9 +279,26 @@
             }
         }
     }
+
+    JoinAllThreads();
     return true;
 }
 
+void SnapuserdServer::JoinAllThreads() {
+    // Acquire the thread list within the lock.
+    std::vector<std::shared_ptr<DmUserHandler>> dm_users;
+    {
+        std::lock_guard<std::mutex> guard(lock_);
+        dm_users = std::move(dm_users_);
+    }
+
+    for (auto& client : dm_users) {
+        auto& th = client->thread();
+
+        if (th.joinable()) th.join();
+    }
+}
+
 void SnapuserdServer::AddWatchedFd(android::base::borrowed_fd fd) {
     struct pollfd p = {};
     p.fd = fd.get();
@@ -336,34 +339,37 @@
     SetTerminating();
 }
 
-std::unique_ptr<DmUserHandler> SnapuserdServer::RemoveHandler(const std::string& control_device) {
-    std::unique_ptr<DmUserHandler> client;
-    {
-        std::lock_guard<std::mutex> lock(lock_);
-        auto iter = dm_users_.begin();
-        while (iter != dm_users_.end()) {
-            if ((*iter)->GetControlDevice() == control_device) {
-                client = std::move(*iter);
-                iter = dm_users_.erase(iter);
-                break;
-            }
-            iter++;
+auto SnapuserdServer::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)->GetMiscName() == misc_name) {
+            return iter;
         }
     }
-    return client;
+    return dm_users_.end();
 }
 
-bool SnapuserdServer::WaitForDelete(const std::string& control_device) {
-    auto client = RemoveHandler(control_device);
+bool SnapuserdServer::RemoveHandler(const std::string& misc_name, bool wait) {
+    std::shared_ptr<DmUserHandler> handler;
+    {
+        std::lock_guard<std::mutex> lock(lock_);
 
-    // Client already deleted.
-    if (!client) {
-        return true;
+        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 = client->thread();
-    if (th.joinable()) {
+    auto& th = handler->thread();
+    if (th.joinable() && wait) {
         th.join();
+    } else if (handler->snapuserd()->IsAttached()) {
+        th.detach();
     }
     return true;
 }
diff --git a/fs_mgr/tools/Android.bp b/fs_mgr/tools/Android.bp
index 4d4aae4..d6ccc4b 100644
--- a/fs_mgr/tools/Android.bp
+++ b/fs_mgr/tools/Android.bp
@@ -29,3 +29,15 @@
 
     cflags: ["-Werror"],
 }
+
+cc_binary {
+    name: "dmuserd",
+    srcs: ["dmuserd.cpp"],
+
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+
+    cflags: ["-Werror"],
+}
diff --git a/fs_mgr/tools/dmuserd.cpp b/fs_mgr/tools/dmuserd.cpp
new file mode 100644
index 0000000..e50a4a2
--- /dev/null
+++ b/fs_mgr/tools/dmuserd.cpp
@@ -0,0 +1,319 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#define _LARGEFILE64_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <unistd.h>
+#include <iostream>
+
+#define SECTOR_SIZE ((__u64)512)
+#define BUFFER_BYTES 4096
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+/* This should be replaced with linux/dm-user.h. */
+#ifndef _LINUX_DM_USER_H
+#define _LINUX_DM_USER_H
+
+#include <linux/types.h>
+
+#define DM_USER_REQ_MAP_READ 0
+#define DM_USER_REQ_MAP_WRITE 1
+#define DM_USER_REQ_MAP_FLUSH 2
+#define DM_USER_REQ_MAP_DISCARD 3
+#define DM_USER_REQ_MAP_SECURE_ERASE 4
+#define DM_USER_REQ_MAP_WRITE_SAME 5
+#define DM_USER_REQ_MAP_WRITE_ZEROES 6
+#define DM_USER_REQ_MAP_ZONE_OPEN 7
+#define DM_USER_REQ_MAP_ZONE_CLOSE 8
+#define DM_USER_REQ_MAP_ZONE_FINISH 9
+#define DM_USER_REQ_MAP_ZONE_APPEND 10
+#define DM_USER_REQ_MAP_ZONE_RESET 11
+#define DM_USER_REQ_MAP_ZONE_RESET_ALL 12
+
+#define DM_USER_REQ_MAP_FLAG_FAILFAST_DEV 0x00001
+#define DM_USER_REQ_MAP_FLAG_FAILFAST_TRANSPORT 0x00002
+#define DM_USER_REQ_MAP_FLAG_FAILFAST_DRIVER 0x00004
+#define DM_USER_REQ_MAP_FLAG_SYNC 0x00008
+#define DM_USER_REQ_MAP_FLAG_META 0x00010
+#define DM_USER_REQ_MAP_FLAG_PRIO 0x00020
+#define DM_USER_REQ_MAP_FLAG_NOMERGE 0x00040
+#define DM_USER_REQ_MAP_FLAG_IDLE 0x00080
+#define DM_USER_REQ_MAP_FLAG_INTEGRITY 0x00100
+#define DM_USER_REQ_MAP_FLAG_FUA 0x00200
+#define DM_USER_REQ_MAP_FLAG_PREFLUSH 0x00400
+#define DM_USER_REQ_MAP_FLAG_RAHEAD 0x00800
+#define DM_USER_REQ_MAP_FLAG_BACKGROUND 0x01000
+#define DM_USER_REQ_MAP_FLAG_NOWAIT 0x02000
+#define DM_USER_REQ_MAP_FLAG_CGROUP_PUNT 0x04000
+#define DM_USER_REQ_MAP_FLAG_NOUNMAP 0x08000
+#define DM_USER_REQ_MAP_FLAG_HIPRI 0x10000
+#define DM_USER_REQ_MAP_FLAG_DRV 0x20000
+#define DM_USER_REQ_MAP_FLAG_SWAP 0x40000
+
+#define DM_USER_RESP_SUCCESS 0
+#define DM_USER_RESP_ERROR 1
+#define DM_USER_RESP_UNSUPPORTED 2
+
+struct dm_user_message {
+    __u64 seq;
+    __u64 type;
+    __u64 flags;
+    __u64 sector;
+    __u64 len;
+    __u8 buf[];
+};
+
+#endif
+
+static bool verbose = false;
+
+ssize_t write_all(int fd, void* buf, size_t len) {
+    char* buf_c = (char*)buf;
+    ssize_t total = 0;
+    ssize_t once;
+
+    while (total < len) {
+        once = write(fd, buf_c + total, len - total);
+        if (once < 0) return once;
+        if (once == 0) {
+            errno = ENOSPC;
+            return 0;
+        }
+        total += once;
+    }
+
+    return total;
+}
+
+ssize_t read_all(int fd, void* buf, size_t len) {
+    char* buf_c = (char*)buf;
+    ssize_t total = 0;
+    ssize_t once;
+
+    while (total < len) {
+        once = read(fd, buf_c + total, len - total);
+        if (once < 0) return once;
+        if (once == 0) {
+            errno = ENOSPC;
+            return 0;
+        }
+        total += once;
+    }
+
+    return total;
+}
+
+int not_splice(int from, int to, __u64 count) {
+    while (count > 0) {
+        char buf[BUFFER_BYTES];
+        __u64 max = count > BUFFER_BYTES ? BUFFER_BYTES : count;
+
+        if (read_all(from, buf, max) <= 0) {
+            perror("Unable to read");
+            return -EIO;
+        }
+
+        if (write_all(to, buf, max) <= 0) {
+            perror("Unable to write");
+            return -EIO;
+        }
+
+        count -= max;
+    }
+
+    return 0;
+}
+
+int simple_daemon(char* control_path, char* backing_path) {
+    int control_fd = open(control_path, O_RDWR);
+    if (control_fd < 0) {
+        fprintf(stderr, "Unable to open control device %s\n", control_path);
+        return -1;
+    }
+
+    int backing_fd = open(backing_path, O_RDWR);
+    if (backing_fd < 0) {
+        fprintf(stderr, "Unable to open backing device %s\n", backing_path);
+        return -1;
+    }
+
+    while (1) {
+        struct dm_user_message msg;
+        char* base;
+        __u64 type;
+
+        if (verbose) std::cerr << "dmuserd: Waiting for message...\n";
+
+        if (read_all(control_fd, &msg, sizeof(msg)) < 0) {
+            if (errno == ENOTBLK) return 0;
+
+            perror("unable to read msg");
+            return -1;
+        }
+
+        if (verbose) {
+            std::string type;
+            switch (msg.type) {
+                case DM_USER_REQ_MAP_WRITE:
+                    type = "write";
+                    break;
+                case DM_USER_REQ_MAP_READ:
+                    type = "read";
+                    break;
+                case DM_USER_REQ_MAP_FLUSH:
+                    type = "flush";
+                    break;
+                default:
+                    /*
+                     * FIXME: Can't I do "whatever"s here rather that
+                     * std::string("whatever")?
+                     */
+                    type = std::string("(unknown, id=") + std::to_string(msg.type) + ")";
+                    break;
+            }
+
+            std::string flags;
+            if (msg.flags & DM_USER_REQ_MAP_FLAG_SYNC) {
+                if (!flags.empty()) flags += "|";
+                flags += "S";
+            }
+            if (msg.flags & DM_USER_REQ_MAP_FLAG_META) {
+                if (!flags.empty()) flags += "|";
+                flags += "M";
+            }
+            if (msg.flags & DM_USER_REQ_MAP_FLAG_FUA) {
+                if (!flags.empty()) flags += "|";
+                flags += "FUA";
+            }
+            if (msg.flags & DM_USER_REQ_MAP_FLAG_PREFLUSH) {
+                if (!flags.empty()) flags += "|";
+                flags += "F";
+            }
+
+            std::cerr << "dmuserd: Got " << type << " request " << flags << " for sector "
+                      << std::to_string(msg.sector) << " with length " << std::to_string(msg.len)
+                      << "\n";
+        }
+
+        type = msg.type;
+        switch (type) {
+            case DM_USER_REQ_MAP_READ:
+                msg.type = DM_USER_RESP_SUCCESS;
+                break;
+            case DM_USER_REQ_MAP_WRITE:
+                if (msg.flags & DM_USER_REQ_MAP_FLAG_PREFLUSH ||
+                    msg.flags & DM_USER_REQ_MAP_FLAG_FUA) {
+                    if (fsync(backing_fd) < 0) {
+                        perror("Unable to fsync(), just sync()ing instead");
+                        sync();
+                    }
+                }
+                msg.type = DM_USER_RESP_SUCCESS;
+                if (lseek64(backing_fd, msg.sector * SECTOR_SIZE, SEEK_SET) < 0) {
+                    perror("Unable to seek");
+                    return -1;
+                }
+                if (not_splice(control_fd, backing_fd, msg.len) < 0) {
+                    if (errno == ENOTBLK) return 0;
+                    std::cerr << "unable to handle write data\n";
+                    return -1;
+                }
+                if (msg.flags & DM_USER_REQ_MAP_FLAG_FUA) {
+                    if (fsync(backing_fd) < 0) {
+                        perror("Unable to fsync(), just sync()ing instead");
+                        sync();
+                    }
+                }
+                break;
+            case DM_USER_REQ_MAP_FLUSH:
+                msg.type = DM_USER_RESP_SUCCESS;
+                if (fsync(backing_fd) < 0) {
+                    perror("Unable to fsync(), just sync()ing instead");
+                    sync();
+                }
+                break;
+            default:
+                std::cerr << "dmuserd: unsupported op " << std::to_string(msg.type) << "\n";
+                msg.type = DM_USER_RESP_UNSUPPORTED;
+                break;
+        }
+
+        if (verbose) std::cerr << "dmuserd: Responding to message\n";
+
+        if (write_all(control_fd, &msg, sizeof(msg)) < 0) {
+            if (errno == ENOTBLK) return 0;
+            perror("unable to write msg");
+            return -1;
+        }
+
+        switch (type) {
+            case DM_USER_REQ_MAP_READ:
+                if (verbose) std::cerr << "dmuserd: Sending read data\n";
+                if (lseek64(backing_fd, msg.sector * SECTOR_SIZE, SEEK_SET) < 0) {
+                    perror("Unable to seek");
+                    return -1;
+                }
+                if (not_splice(backing_fd, control_fd, msg.len) < 0) {
+                    if (errno == ENOTBLK) return 0;
+                    std::cerr << "unable to handle read data\n";
+                    return -1;
+                }
+                break;
+        }
+    }
+
+    /* The daemon doesn't actully terminate for this test. */
+    perror("Unable to read from control device");
+    return -1;
+}
+
+void usage(char* prog) {
+    printf("Usage: %s\n", prog);
+    printf("	Handles block requests in userspace, backed by memory\n");
+    printf("  -h			Display this help message\n");
+    printf("  -c <control dev>		Control device to use for the test\n");
+    printf("  -b <store path>		The file to use as a backing store, otherwise memory\n");
+    printf("  -v                        Enable verbose mode\n");
+}
+
+int main(int argc, char* argv[]) {
+    char* control_path = NULL;
+    char* backing_path = NULL;
+    char* store;
+    int c;
+
+    prctl(PR_SET_IO_FLUSHER, 0, 0, 0, 0);
+
+    while ((c = getopt(argc, argv, "h:c:s:b:v")) != -1) {
+        switch (c) {
+            case 'h':
+                usage(basename(argv[0]));
+                exit(0);
+            case 'c':
+                control_path = strdup(optarg);
+                break;
+            case 'b':
+                backing_path = strdup(optarg);
+                break;
+            case 'v':
+                verbose = true;
+                break;
+            default:
+                usage(basename(argv[0]));
+                exit(1);
+        }
+    }
+
+    int r = simple_daemon(control_path, backing_path);
+    if (r) fprintf(stderr, "simple_daemon() errored out\n");
+    return r;
+}
diff --git a/init/README.md b/init/README.md
index ab6a885..bcbbfbb 100644
--- a/init/README.md
+++ b/init/README.md
@@ -451,6 +451,10 @@
   exist. And it will be truncated if dst file is a normal regular file and
   already exists.
 
+`copy_per_line <src> <dst>`
+> Copies a file line by line. Similar to copy, but useful for dst is a sysfs node
+  that doesn't handle multiple lines of data.
+
 `domainname <name>`
 > Set the domain name.
 
diff --git a/init/block_dev_initializer.cpp b/init/block_dev_initializer.cpp
index 8db9793..9c2a7bb 100644
--- a/init/block_dev_initializer.cpp
+++ b/init/block_dev_initializer.cpp
@@ -40,8 +40,8 @@
     return InitMiscDevice("device-mapper");
 }
 
-bool BlockDevInitializer::InitDmUser() {
-    return InitMiscDevice("dm-user");
+bool BlockDevInitializer::InitDmUser(const std::string& name) {
+    return InitMiscDevice("dm-user!" + name);
 }
 
 bool BlockDevInitializer::InitMiscDevice(const std::string& name) {
diff --git a/init/block_dev_initializer.h b/init/block_dev_initializer.h
index b8dd3f1..79fe4ec 100644
--- a/init/block_dev_initializer.h
+++ b/init/block_dev_initializer.h
@@ -27,7 +27,7 @@
     BlockDevInitializer();
 
     bool InitDeviceMapper();
-    bool InitDmUser();
+    bool InitDmUser(const std::string& name);
     bool InitDevices(std::set<std::string> devices);
     bool InitDmDevice(const std::string& device);
 
diff --git a/init/builtins.cpp b/init/builtins.cpp
index d00d1b1..b235d2f 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -88,6 +88,7 @@
 
 using android::base::Basename;
 using android::base::SetProperty;
+using android::base::Split;
 using android::base::StartsWith;
 using android::base::StringPrintf;
 using android::base::unique_fd;
@@ -968,6 +969,23 @@
     return {};
 }
 
+static Result<void> do_copy_per_line(const BuiltinArguments& args) {
+    std::string file_contents;
+    if (!android::base::ReadFileToString(args[1], &file_contents, true)) {
+        return Error() << "Could not read input file '" << args[1] << "'";
+    }
+    auto lines = Split(file_contents, "\n");
+    for (const auto& line : lines) {
+        auto result = WriteFile(args[2], line);
+        if (!result.ok()) {
+            LOG(VERBOSE) << "Could not write to output file '" << args[2] << "' with '" << line
+                         << "' : " << result.error();
+        }
+    }
+
+    return {};
+}
+
 static Result<void> do_chown(const BuiltinArguments& args) {
     auto uid = DecodeUid(args[1]);
     if (!uid.ok()) {
@@ -1366,6 +1384,7 @@
         {"class_start_post_data",   {1,     1,    {false,  do_class_start_post_data}}},
         {"class_stop",              {1,     1,    {false,  do_class_stop}}},
         {"copy",                    {2,     2,    {true,   do_copy}}},
+        {"copy_per_line",           {2,     2,    {true,   do_copy_per_line}}},
         {"domainname",              {1,     1,    {true,   do_domainname}}},
         {"enable",                  {1,     1,    {false,  do_enable}}},
         {"exec",                    {1,     kMax, {false,  do_exec}}},
diff --git a/init/devices.cpp b/init/devices.cpp
index f8eb16a..5888c06 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -440,13 +440,6 @@
             }
         }
         unlink(devpath.c_str());
-
-        if (android::base::StartsWith(devpath, "/dev/dm-user/")) {
-            std::error_code ec;
-            if (std::filesystem::is_empty("/dev/dm-user/", ec)) {
-                rmdir("/dev/dm-user");
-            }
-        }
     }
 }
 
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 843ac5c..83a32e7 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -117,7 +117,7 @@
 
     auto dst_dir = android::base::Dirname(dst);
     std::error_code ec;
-    if (!fs::create_directories(dst_dir, ec)) {
+    if (!fs::create_directories(dst_dir, ec) && !!ec) {
         LOG(FATAL) << "Cannot create " << dst_dir << ": " << ec.message();
     }
     if (rename(src, dst) != 0) {
@@ -221,6 +221,7 @@
     CHECKCALL(mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"));
     CHECKCALL(mkdir("/dev/pts", 0755));
     CHECKCALL(mkdir("/dev/socket", 0755));
+    CHECKCALL(mkdir("/dev/dm-user", 0755));
     CHECKCALL(mount("devpts", "/dev/pts", "devpts", 0, NULL));
 #define MAKE_STR(x) __STRING(x)
     CHECKCALL(mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC)));
@@ -314,7 +315,7 @@
         std::string dest = GetRamdiskPropForSecondStage();
         std::string dir = android::base::Dirname(dest);
         std::error_code ec;
-        if (!fs::create_directories(dir, ec)) {
+        if (!fs::create_directories(dir, ec) && !!ec) {
             LOG(FATAL) << "Can't mkdir " << dir << ": " << ec.message();
         }
         if (!fs::copy_file(kBootImageRamdiskProp, dest, ec)) {
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index b7d50cf..a0511cc 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -343,6 +343,15 @@
             if (!InitRequiredDevices({"userdata"})) {
                 return false;
             }
+            sm->SetUeventRegenCallback([this](const std::string& device) -> bool {
+                if (android::base::StartsWith(device, "/dev/block/dm-")) {
+                    return block_dev_init_.InitDmDevice(device);
+                }
+                if (android::base::StartsWith(device, "/dev/dm-user/")) {
+                    return block_dev_init_.InitDmUser(android::base::Basename(device));
+                }
+                return block_dev_init_.InitDevices({device});
+            });
             return sm->CreateLogicalAndSnapshotPartitions(super_path_);
         }
     }
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index ef9a451..db127d3 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -25,6 +25,8 @@
 #include <fstream>
 #include <iostream>
 #include <iterator>
+#include <map>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -51,6 +53,7 @@
 
 using namespace std::literals;
 
+using android::base::EndsWith;
 using android::base::ParseInt;
 using android::base::ReadFileToString;
 using android::base::Split;
@@ -61,6 +64,10 @@
 
 static std::vector<std::string> passwd_files;
 
+// NOTE: Keep this in sync with the order used by init.cpp LoadBootScripts()
+static const std::vector<std::string> partition_search_order =
+        std::vector<std::string>({"system", "system_ext", "odm", "vendor", "product"});
+
 static std::vector<std::pair<std::string, int>> GetVendorPasswd(const std::string& passwd_file) {
     std::string passwd;
     if (!ReadFileToString(passwd_file, &passwd)) {
@@ -148,13 +155,24 @@
 #include "generated_stub_builtin_function_map.h"
 
 void PrintUsage() {
-    std::cout << "usage: host_init_verifier [options] <init rc file>\n"
-                 "\n"
-                 "Tests an init script for correctness\n"
-                 "\n"
-                 "-p FILE\tSearch this passwd file for users and groups\n"
-                 "--property_contexts=FILE\t Use this file for property_contexts\n"
-              << std::endl;
+    fprintf(stdout, R"(usage: host_init_verifier [options]
+
+Tests init script(s) for correctness.
+
+Generic options:
+  -p FILE                     Search this passwd file for users and groups.
+  --property_contexts=FILE    Use this file for property_contexts.
+
+Single script mode options:
+  [init rc file]              Positional argument; test this init script.
+
+Multiple script mode options:
+  --out_system=DIR            Path to the output product directory for the system partition.
+  --out_system_ext=DIR        Path to the output product directory for the system_ext partition.
+  --out_odm=DIR               Path to the output product directory for the odm partition.
+  --out_vendor=DIR            Path to the output product directory for the vendor partition.
+  --out_product=DIR           Path to the output product directory for the product partition.
+)");
 }
 
 Result<InterfaceInheritanceHierarchyMap> ReadInterfaceInheritanceHierarchy() {
@@ -203,12 +221,18 @@
     android::base::SetMinimumLogSeverity(android::base::ERROR);
 
     auto property_infos = std::vector<PropertyInfoEntry>();
+    std::map<std::string, std::string> partition_map;
 
     while (true) {
         static const char kPropertyContexts[] = "property-contexts=";
         static const struct option long_options[] = {
                 {"help", no_argument, nullptr, 'h'},
                 {kPropertyContexts, required_argument, nullptr, 0},
+                {"out_system", required_argument, nullptr, 0},
+                {"out_system_ext", required_argument, nullptr, 0},
+                {"out_odm", required_argument, nullptr, 0},
+                {"out_vendor", required_argument, nullptr, 0},
+                {"out_product", required_argument, nullptr, 0},
                 {nullptr, 0, nullptr, 0},
         };
 
@@ -224,6 +248,16 @@
                 if (long_options[option_index].name == kPropertyContexts) {
                     HandlePropertyContexts(optarg, &property_infos);
                 }
+                for (const auto& p : partition_search_order) {
+                    if (long_options[option_index].name == "out_" + p) {
+                        if (partition_map.find(p) != partition_map.end()) {
+                            PrintUsage();
+                            return EXIT_FAILURE;
+                        }
+                        partition_map[p] =
+                                EndsWith(optarg, "/") ? optarg : std::string(optarg) + "/";
+                    }
+                }
                 break;
             case 'h':
                 PrintUsage();
@@ -240,7 +274,9 @@
     argc -= optind;
     argv += optind;
 
-    if (argc != 1) {
+    // If provided, use the partition map to check multiple init rc files.
+    // Otherwise, check a single init rc file.
+    if ((!partition_map.empty() && argc != 0) || (partition_map.empty() && argc != 1)) {
         PrintUsage();
         return EXIT_FAILURE;
     }
@@ -262,24 +298,42 @@
 
     property_info_area = reinterpret_cast<const PropertyInfoArea*>(serialized_contexts.c_str());
 
+    if (!partition_map.empty()) {
+        std::vector<std::string> vendor_prefixes;
+        for (const auto& partition : {"vendor", "odm"}) {
+            if (partition_map.find(partition) != partition_map.end()) {
+                vendor_prefixes.push_back(partition_map.at(partition));
+            }
+        }
+        InitializeHostSubcontext(vendor_prefixes);
+    }
+
     const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
     Action::set_function_map(&function_map);
     ActionManager& am = ActionManager::GetInstance();
     ServiceList& sl = ServiceList::GetInstance();
     Parser parser;
-    parser.AddSectionParser("service", std::make_unique<ServiceParser>(
-                                               &sl, nullptr, *interface_inheritance_hierarchy_map));
-    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, nullptr));
+    parser.AddSectionParser("service",
+                            std::make_unique<ServiceParser>(&sl, GetSubcontext(),
+                                                            *interface_inheritance_hierarchy_map));
+    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, GetSubcontext()));
     parser.AddSectionParser("import", std::make_unique<HostImportParser>());
 
-    if (!parser.ParseConfigFileInsecure(*argv)) {
-        LOG(ERROR) << "Failed to open init rc script '" << *argv << "'";
-        return EXIT_FAILURE;
+    if (!partition_map.empty()) {
+        for (const auto& p : partition_search_order) {
+            if (partition_map.find(p) != partition_map.end()) {
+                parser.ParseConfig(partition_map.at(p) + "etc/init");
+            }
+        }
+    } else {
+        if (!parser.ParseConfigFileInsecure(*argv)) {
+            LOG(ERROR) << "Failed to open init rc script '" << *argv << "'";
+            return EXIT_FAILURE;
+        }
     }
     size_t failures = parser.parse_error_count() + am.CheckAllCommands() + sl.CheckAllCommands();
     if (failures > 0) {
-        LOG(ERROR) << "Failed to parse init script '" << *argv << "' with " << failures
-                   << " errors";
+        LOG(ERROR) << "Failed to parse init scripts with " << failures << " error(s).";
         return EXIT_FAILURE;
     }
     return EXIT_SUCCESS;
diff --git a/init/init.cpp b/init/init.cpp
index c6f2066..1d0a9dc 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -875,13 +875,13 @@
     SetProperty(gsi::kGsiInstalledProp, is_installed);
 
     am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
-    am.QueueBuiltinAction(TransitionSnapuserdAction, "TransitionSnapuserd");
     am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
     am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
     am.QueueEventTrigger("early-init");
 
     // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
     am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
+    am.QueueBuiltinAction(TransitionSnapuserdAction, "TransitionSnapuserd");
     // ... so that we can start queuing up actions that require stuff from /dev.
     am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
     am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 5a0255a..f03ca6b 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -534,6 +534,7 @@
     selinux_android_restorecon("/dev/__properties__", 0);
 
     selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
+    selinux_android_restorecon("/dev/dm-user", SELINUX_ANDROID_RESTORECON_RECURSE);
     selinux_android_restorecon("/dev/device-mapper", 0);
 
     selinux_android_restorecon("/apex", 0);
diff --git a/init/service.cpp b/init/service.cpp
index 7b98392..766eb5d 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -154,6 +154,7 @@
                  .priority = 0},
       namespaces_{.flags = namespace_flags},
       seclabel_(seclabel),
+      subcontext_(subcontext_for_restart_commands),
       onrestart_(false, subcontext_for_restart_commands, "<Service '" + name + "' onrestart>", 0,
                  "onrestart", {}),
       oom_score_adjust_(DEFAULT_OOM_SCORE_ADJUST),
diff --git a/init/service.h b/init/service.h
index bc5c90f..aee1e5d 100644
--- a/init/service.h
+++ b/init/service.h
@@ -137,6 +137,7 @@
             flags_ &= ~SVC_ONESHOT;
         }
     }
+    Subcontext* subcontext() const { return subcontext_; }
 
   private:
     void NotifyStateChange(const std::string& new_state) const;
@@ -168,6 +169,7 @@
     std::vector<FileDescriptor> files_;
     std::vector<std::pair<std::string, std::string>> environment_vars_;
 
+    Subcontext* subcontext_;
     Action onrestart_;  // Commands to execute on restart.
 
     std::vector<std::string> writepid_files_;
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 97621da..57c311a 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -657,6 +657,14 @@
                            << "' with a config in APEX";
         }
 
+        std::string context = service_->subcontext() ? service_->subcontext()->context() : "";
+        std::string old_context =
+                old_service->subcontext() ? old_service->subcontext()->context() : "";
+        if (context != old_context) {
+            return Error() << "service '" << service_->name() << "' overrides another service "
+                           << "across the treble boundary.";
+        }
+
         service_list_->RemoveService(*old_service);
         old_service = nullptr;
     }
diff --git a/init/service_utils.h b/init/service_utils.h
index e74f8c1..1e0b4bd 100644
--- a/init/service_utils.h
+++ b/init/service_utils.h
@@ -37,6 +37,8 @@
     Descriptor(const std::string& name, android::base::unique_fd fd)
         : name_(name), fd_(std::move(fd)){};
 
+    // Publish() unsets FD_CLOEXEC from the FD and publishes its name via setenv().  It should be
+    // called when starting a service after fork() and before exec().
     void Publish() const;
 
   private:
@@ -53,6 +55,9 @@
     std::string context;
     bool passcred = false;
 
+    // Create() creates the named unix domain socket in /dev/socket and returns a Descriptor object.
+    // It should be called when starting a service, before calling fork(), such that the socket is
+    // synchronously created before starting any other services, which may depend on it.
     Result<Descriptor> Create(const std::string& global_context) const;
 };
 
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index dc2455e..f1fbffe 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -342,6 +342,9 @@
                 new Subcontext(std::vector<std::string>{"/vendor", "/odm"}, kVendorContext));
     }
 }
+void InitializeHostSubcontext(std::vector<std::string> vendor_prefixes) {
+    subcontext.reset(new Subcontext(vendor_prefixes, kVendorContext, /*host=*/true));
+}
 
 Subcontext* GetSubcontext() {
     return subcontext.get();
diff --git a/init/subcontext.h b/init/subcontext.h
index 788d3be..cb4138e 100644
--- a/init/subcontext.h
+++ b/init/subcontext.h
@@ -36,9 +36,11 @@
 
 class Subcontext {
   public:
-    Subcontext(std::vector<std::string> path_prefixes, std::string context)
+    Subcontext(std::vector<std::string> path_prefixes, std::string context, bool host = false)
         : path_prefixes_(std::move(path_prefixes)), context_(std::move(context)), pid_(0) {
-        Fork();
+        if (!host) {
+            Fork();
+        }
     }
 
     Result<void> Execute(const std::vector<std::string>& args);
@@ -61,6 +63,7 @@
 
 int SubcontextMain(int argc, char** argv, const BuiltinFunctionMap* function_map);
 void InitializeSubcontext();
+void InitializeHostSubcontext(std::vector<std::string> vendor_prefixes);
 Subcontext* GetSubcontext();
 bool SubcontextChildReap(pid_t pid);
 void SubcontextTerminate();
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 31e1679..79c3abc 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -203,9 +203,14 @@
                                            CAP_MASK_LONG(CAP_SETGID),
                                               "system/bin/simpleperf_app_runner" },
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/e2fsck" },
-    { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/tune2fs" },
+#ifdef __LP64__
+    { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/linker64" },
+#else
+    { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/linker" },
+#endif
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/resize2fs" },
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/snapuserd" },
+    { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/tune2fs" },
     // generic defaults
     { 00755, AID_ROOT,      AID_ROOT,      0, "bin/*" },
     { 00640, AID_ROOT,      AID_SHELL,     0, "fstab.*" },
diff --git a/libprocessgroup/profiles/Android.bp b/libprocessgroup/profiles/Android.bp
index c371ef7..a496237 100644
--- a/libprocessgroup/profiles/Android.bp
+++ b/libprocessgroup/profiles/Android.bp
@@ -15,6 +15,11 @@
 prebuilt_etc {
     name: "cgroups.json",
     src: "cgroups.json",
+    required: [
+        "cgroups_28.json",
+        "cgroups_29.json",
+        "cgroups_30.json",
+    ],
 }
 
 prebuilt_etc {
@@ -25,8 +30,49 @@
 }
 
 prebuilt_etc {
+    name: "cgroups_28.json",
+    src: "cgroups_28.json",
+    sub_dir: "task_profiles",
+}
+
+prebuilt_etc {
+    name: "cgroups_29.json",
+    src: "cgroups_29.json",
+    sub_dir: "task_profiles",
+}
+
+prebuilt_etc {
+    name: "cgroups_30.json",
+    src: "cgroups_30.json",
+    sub_dir: "task_profiles",
+}
+
+prebuilt_etc {
     name: "task_profiles.json",
     src: "task_profiles.json",
+    required: [
+        "task_profiles_28.json",
+        "task_profiles_29.json",
+        "task_profiles_30.json",
+    ],
+}
+
+prebuilt_etc {
+    name: "task_profiles_28.json",
+    src: "task_profiles_28.json",
+    sub_dir: "task_profiles",
+}
+
+prebuilt_etc {
+    name: "task_profiles_29.json",
+    src: "task_profiles_29.json",
+    sub_dir: "task_profiles",
+}
+
+prebuilt_etc {
+    name: "task_profiles_30.json",
+    src: "task_profiles_30.json",
+    sub_dir: "task_profiles",
 }
 
 cc_defaults {
diff --git a/libprocessgroup/profiles/cgroups_28.json b/libprocessgroup/profiles/cgroups_28.json
new file mode 100644
index 0000000..4518487
--- /dev/null
+++ b/libprocessgroup/profiles/cgroups_28.json
@@ -0,0 +1,59 @@
+{
+  "Cgroups": [
+    {
+      "Controller": "blkio",
+      "Path": "/dev/blkio",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    },
+    {
+      "Controller": "cpu",
+      "Path": "/dev/cpuctl",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    },
+    {
+      "Controller": "cpuacct",
+      "Path": "/acct",
+      "Mode": "0555"
+    },
+    {
+      "Controller": "cpuset",
+      "Path": "/dev/cpuset",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    },
+    {
+      "Controller": "memory",
+      "Path": "/dev/memcg",
+      "Mode": "0700",
+      "UID": "root",
+      "GID": "system"
+    },
+    {
+      "Controller": "schedtune",
+      "Path": "/dev/stune",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    }
+  ],
+  "Cgroups2": {
+    "Path": "/sys/fs/cgroup",
+    "Mode": "0755",
+    "UID": "system",
+    "GID": "system",
+    "Controllers": [
+      {
+        "Controller": "freezer",
+        "Path": "freezer",
+        "Mode": "0755",
+        "UID": "system",
+        "GID": "system"
+      }
+    ]
+  }
+}
diff --git a/libprocessgroup/profiles/cgroups_29.json b/libprocessgroup/profiles/cgroups_29.json
new file mode 100644
index 0000000..4518487
--- /dev/null
+++ b/libprocessgroup/profiles/cgroups_29.json
@@ -0,0 +1,59 @@
+{
+  "Cgroups": [
+    {
+      "Controller": "blkio",
+      "Path": "/dev/blkio",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    },
+    {
+      "Controller": "cpu",
+      "Path": "/dev/cpuctl",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    },
+    {
+      "Controller": "cpuacct",
+      "Path": "/acct",
+      "Mode": "0555"
+    },
+    {
+      "Controller": "cpuset",
+      "Path": "/dev/cpuset",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    },
+    {
+      "Controller": "memory",
+      "Path": "/dev/memcg",
+      "Mode": "0700",
+      "UID": "root",
+      "GID": "system"
+    },
+    {
+      "Controller": "schedtune",
+      "Path": "/dev/stune",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    }
+  ],
+  "Cgroups2": {
+    "Path": "/sys/fs/cgroup",
+    "Mode": "0755",
+    "UID": "system",
+    "GID": "system",
+    "Controllers": [
+      {
+        "Controller": "freezer",
+        "Path": "freezer",
+        "Mode": "0755",
+        "UID": "system",
+        "GID": "system"
+      }
+    ]
+  }
+}
diff --git a/libprocessgroup/profiles/cgroups_30.json b/libprocessgroup/profiles/cgroups_30.json
new file mode 100644
index 0000000..4518487
--- /dev/null
+++ b/libprocessgroup/profiles/cgroups_30.json
@@ -0,0 +1,59 @@
+{
+  "Cgroups": [
+    {
+      "Controller": "blkio",
+      "Path": "/dev/blkio",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    },
+    {
+      "Controller": "cpu",
+      "Path": "/dev/cpuctl",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    },
+    {
+      "Controller": "cpuacct",
+      "Path": "/acct",
+      "Mode": "0555"
+    },
+    {
+      "Controller": "cpuset",
+      "Path": "/dev/cpuset",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    },
+    {
+      "Controller": "memory",
+      "Path": "/dev/memcg",
+      "Mode": "0700",
+      "UID": "root",
+      "GID": "system"
+    },
+    {
+      "Controller": "schedtune",
+      "Path": "/dev/stune",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    }
+  ],
+  "Cgroups2": {
+    "Path": "/sys/fs/cgroup",
+    "Mode": "0755",
+    "UID": "system",
+    "GID": "system",
+    "Controllers": [
+      {
+        "Controller": "freezer",
+        "Path": "freezer",
+        "Mode": "0755",
+        "UID": "system",
+        "GID": "system"
+      }
+    ]
+  }
+}
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index ea0064f..b528fa5 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -100,7 +100,7 @@
           "Params":
           {
             "Controller": "cpu",
-            "Path": ""
+            "Path": "system"
           }
         }
       ]
diff --git a/libprocessgroup/profiles/task_profiles_28.json b/libprocessgroup/profiles/task_profiles_28.json
new file mode 100644
index 0000000..142b0ba
--- /dev/null
+++ b/libprocessgroup/profiles/task_profiles_28.json
@@ -0,0 +1,627 @@
+{
+  "Attributes": [
+    {
+      "Name": "LowCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "background/cpus"
+    },
+    {
+      "Name": "HighCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "foreground/cpus"
+    },
+    {
+      "Name": "MaxCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "top-app/cpus"
+    },
+    {
+      "Name": "MemLimit",
+      "Controller": "memory",
+      "File": "memory.limit_in_bytes"
+    },
+    {
+      "Name": "MemSoftLimit",
+      "Controller": "memory",
+      "File": "memory.soft_limit_in_bytes"
+    },
+    {
+      "Name": "MemSwappiness",
+      "Controller": "memory",
+      "File": "memory.swappiness"
+    },
+    {
+      "Name": "STuneBoost",
+      "Controller": "schedtune",
+      "File": "schedtune.boost"
+    },
+    {
+      "Name": "STunePreferIdle",
+      "Controller": "schedtune",
+      "File": "schedtune.prefer_idle"
+    },
+    {
+      "Name": "UClampMin",
+      "Controller": "cpu",
+      "File": "cpu.uclamp.min"
+    },
+    {
+      "Name": "UClampMax",
+      "Controller": "cpu",
+      "File": "cpu.uclamp.max"
+    },
+    {
+      "Name": "FreezerState",
+      "Controller": "freezer",
+      "File": "cgroup.freeze"
+    }
+  ],
+
+  "Profiles": [
+    {
+      "Name": "HighEnergySaving",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "Frozen",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "freezer",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "Unfrozen",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "freezer",
+            "Path": "../"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NormalPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "foreground"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "MaxPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "top-app"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "RealtimePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "rt"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CameraServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "camera-daemon"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NNApiHALPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "nnapi-hal"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "CpuPolicySpread",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "1"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CpuPolicyPack",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "0"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "VrKernelCapacity",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system/background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityNormal",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityHigh",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system/performance"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "application/background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityNormal",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "application"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityHigh",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "application/performance"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "ProcessCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityNormal",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityHigh",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "foreground"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityMax",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "top-app"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "ServiceCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system-background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ServiceCapacityRestricted",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "restricted"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "CameraServiceCapacity",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "camera-daemon"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "LowIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NormalIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "MaxIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": ""
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "TimerSlackHigh",
+      "Actions": [
+        {
+          "Name": "SetTimerSlack",
+          "Params":
+          {
+            "Slack": "40000000"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "TimerSlackNormal",
+      "Actions": [
+        {
+          "Name": "SetTimerSlack",
+          "Params":
+          {
+            "Slack": "50000"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "PerfBoost",
+      "Actions": [
+        {
+          "Name": "SetClamps",
+          "Params":
+          {
+            "Boost": "50%",
+            "Clamp": "0"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "PerfClamp",
+      "Actions": [
+        {
+          "Name": "SetClamps",
+          "Params":
+          {
+            "Boost": "0",
+            "Clamp": "30%"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "LowMemoryUsage",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSoftLimit",
+            "Value": "16MB"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSwappiness",
+            "Value": "150"
+
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighMemoryUsage",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSoftLimit",
+            "Value": "512MB"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSwappiness",
+            "Value": "100"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "SystemMemoryProcess",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "memory",
+            "Path": "system"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "FreezerDisabled",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "FreezerState",
+            "Value": "0"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "FreezerEnabled",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "FreezerState",
+            "Value": "1"
+          }
+        }
+      ]
+    }
+  ],
+
+  "AggregateProfiles": [
+    {
+      "Name": "SCHED_SP_DEFAULT",
+      "Profiles": [ "TimerSlackNormal" ]
+    },
+    {
+      "Name": "SCHED_SP_BACKGROUND",
+      "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
+    },
+    {
+      "Name": "SCHED_SP_FOREGROUND",
+      "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "SCHED_SP_TOP_APP",
+      "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "SCHED_SP_RT_APP",
+      "Profiles": [ "RealtimePerformance", "MaxIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_DEFAULT",
+      "Profiles": [ "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_BACKGROUND",
+      "Profiles": [ "HighEnergySaving", "ProcessCapacityLow", "LowIoPriority", "TimerSlackHigh" ]
+    },
+    {
+      "Name": "CPUSET_SP_FOREGROUND",
+      "Profiles": [ "HighPerformance", "ProcessCapacityHigh", "HighIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_TOP_APP",
+      "Profiles": [ "MaxPerformance", "ProcessCapacityMax", "MaxIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_SYSTEM",
+      "Profiles": [ "ServiceCapacityLow", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_RESTRICTED",
+      "Profiles": [ "ServiceCapacityRestricted", "TimerSlackNormal" ]
+    }
+  ]
+}
diff --git a/libprocessgroup/profiles/task_profiles_29.json b/libprocessgroup/profiles/task_profiles_29.json
new file mode 100644
index 0000000..142b0ba
--- /dev/null
+++ b/libprocessgroup/profiles/task_profiles_29.json
@@ -0,0 +1,627 @@
+{
+  "Attributes": [
+    {
+      "Name": "LowCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "background/cpus"
+    },
+    {
+      "Name": "HighCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "foreground/cpus"
+    },
+    {
+      "Name": "MaxCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "top-app/cpus"
+    },
+    {
+      "Name": "MemLimit",
+      "Controller": "memory",
+      "File": "memory.limit_in_bytes"
+    },
+    {
+      "Name": "MemSoftLimit",
+      "Controller": "memory",
+      "File": "memory.soft_limit_in_bytes"
+    },
+    {
+      "Name": "MemSwappiness",
+      "Controller": "memory",
+      "File": "memory.swappiness"
+    },
+    {
+      "Name": "STuneBoost",
+      "Controller": "schedtune",
+      "File": "schedtune.boost"
+    },
+    {
+      "Name": "STunePreferIdle",
+      "Controller": "schedtune",
+      "File": "schedtune.prefer_idle"
+    },
+    {
+      "Name": "UClampMin",
+      "Controller": "cpu",
+      "File": "cpu.uclamp.min"
+    },
+    {
+      "Name": "UClampMax",
+      "Controller": "cpu",
+      "File": "cpu.uclamp.max"
+    },
+    {
+      "Name": "FreezerState",
+      "Controller": "freezer",
+      "File": "cgroup.freeze"
+    }
+  ],
+
+  "Profiles": [
+    {
+      "Name": "HighEnergySaving",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "Frozen",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "freezer",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "Unfrozen",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "freezer",
+            "Path": "../"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NormalPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "foreground"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "MaxPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "top-app"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "RealtimePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "rt"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CameraServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "camera-daemon"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NNApiHALPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "nnapi-hal"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "CpuPolicySpread",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "1"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CpuPolicyPack",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "0"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "VrKernelCapacity",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system/background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityNormal",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityHigh",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system/performance"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "application/background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityNormal",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "application"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityHigh",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "application/performance"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "ProcessCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityNormal",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityHigh",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "foreground"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityMax",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "top-app"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "ServiceCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system-background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ServiceCapacityRestricted",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "restricted"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "CameraServiceCapacity",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "camera-daemon"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "LowIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NormalIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "MaxIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": ""
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "TimerSlackHigh",
+      "Actions": [
+        {
+          "Name": "SetTimerSlack",
+          "Params":
+          {
+            "Slack": "40000000"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "TimerSlackNormal",
+      "Actions": [
+        {
+          "Name": "SetTimerSlack",
+          "Params":
+          {
+            "Slack": "50000"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "PerfBoost",
+      "Actions": [
+        {
+          "Name": "SetClamps",
+          "Params":
+          {
+            "Boost": "50%",
+            "Clamp": "0"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "PerfClamp",
+      "Actions": [
+        {
+          "Name": "SetClamps",
+          "Params":
+          {
+            "Boost": "0",
+            "Clamp": "30%"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "LowMemoryUsage",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSoftLimit",
+            "Value": "16MB"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSwappiness",
+            "Value": "150"
+
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighMemoryUsage",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSoftLimit",
+            "Value": "512MB"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSwappiness",
+            "Value": "100"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "SystemMemoryProcess",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "memory",
+            "Path": "system"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "FreezerDisabled",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "FreezerState",
+            "Value": "0"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "FreezerEnabled",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "FreezerState",
+            "Value": "1"
+          }
+        }
+      ]
+    }
+  ],
+
+  "AggregateProfiles": [
+    {
+      "Name": "SCHED_SP_DEFAULT",
+      "Profiles": [ "TimerSlackNormal" ]
+    },
+    {
+      "Name": "SCHED_SP_BACKGROUND",
+      "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
+    },
+    {
+      "Name": "SCHED_SP_FOREGROUND",
+      "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "SCHED_SP_TOP_APP",
+      "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "SCHED_SP_RT_APP",
+      "Profiles": [ "RealtimePerformance", "MaxIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_DEFAULT",
+      "Profiles": [ "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_BACKGROUND",
+      "Profiles": [ "HighEnergySaving", "ProcessCapacityLow", "LowIoPriority", "TimerSlackHigh" ]
+    },
+    {
+      "Name": "CPUSET_SP_FOREGROUND",
+      "Profiles": [ "HighPerformance", "ProcessCapacityHigh", "HighIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_TOP_APP",
+      "Profiles": [ "MaxPerformance", "ProcessCapacityMax", "MaxIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_SYSTEM",
+      "Profiles": [ "ServiceCapacityLow", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_RESTRICTED",
+      "Profiles": [ "ServiceCapacityRestricted", "TimerSlackNormal" ]
+    }
+  ]
+}
diff --git a/libprocessgroup/profiles/task_profiles_30.json b/libprocessgroup/profiles/task_profiles_30.json
new file mode 100644
index 0000000..142b0ba
--- /dev/null
+++ b/libprocessgroup/profiles/task_profiles_30.json
@@ -0,0 +1,627 @@
+{
+  "Attributes": [
+    {
+      "Name": "LowCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "background/cpus"
+    },
+    {
+      "Name": "HighCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "foreground/cpus"
+    },
+    {
+      "Name": "MaxCapacityCPUs",
+      "Controller": "cpuset",
+      "File": "top-app/cpus"
+    },
+    {
+      "Name": "MemLimit",
+      "Controller": "memory",
+      "File": "memory.limit_in_bytes"
+    },
+    {
+      "Name": "MemSoftLimit",
+      "Controller": "memory",
+      "File": "memory.soft_limit_in_bytes"
+    },
+    {
+      "Name": "MemSwappiness",
+      "Controller": "memory",
+      "File": "memory.swappiness"
+    },
+    {
+      "Name": "STuneBoost",
+      "Controller": "schedtune",
+      "File": "schedtune.boost"
+    },
+    {
+      "Name": "STunePreferIdle",
+      "Controller": "schedtune",
+      "File": "schedtune.prefer_idle"
+    },
+    {
+      "Name": "UClampMin",
+      "Controller": "cpu",
+      "File": "cpu.uclamp.min"
+    },
+    {
+      "Name": "UClampMax",
+      "Controller": "cpu",
+      "File": "cpu.uclamp.max"
+    },
+    {
+      "Name": "FreezerState",
+      "Controller": "freezer",
+      "File": "cgroup.freeze"
+    }
+  ],
+
+  "Profiles": [
+    {
+      "Name": "HighEnergySaving",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "Frozen",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "freezer",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "Unfrozen",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "freezer",
+            "Path": "../"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NormalPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "foreground"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "MaxPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "top-app"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "RealtimePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "rt"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CameraServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "camera-daemon"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NNApiHALPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "nnapi-hal"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "CpuPolicySpread",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "1"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CpuPolicyPack",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "0"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "VrKernelCapacity",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system/background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityNormal",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrServiceCapacityHigh",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system/performance"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "application/background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityNormal",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "application"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "VrProcessCapacityHigh",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "application/performance"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "ProcessCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityNormal",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityHigh",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "foreground"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ProcessCapacityMax",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "top-app"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "ServiceCapacityLow",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "system-background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ServiceCapacityRestricted",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "restricted"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "CameraServiceCapacity",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpuset",
+            "Path": "camera-daemon"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "LowIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NormalIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "MaxIoPriority",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "blkio",
+            "Path": ""
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "TimerSlackHigh",
+      "Actions": [
+        {
+          "Name": "SetTimerSlack",
+          "Params":
+          {
+            "Slack": "40000000"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "TimerSlackNormal",
+      "Actions": [
+        {
+          "Name": "SetTimerSlack",
+          "Params":
+          {
+            "Slack": "50000"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "PerfBoost",
+      "Actions": [
+        {
+          "Name": "SetClamps",
+          "Params":
+          {
+            "Boost": "50%",
+            "Clamp": "0"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "PerfClamp",
+      "Actions": [
+        {
+          "Name": "SetClamps",
+          "Params":
+          {
+            "Boost": "0",
+            "Clamp": "30%"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "LowMemoryUsage",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSoftLimit",
+            "Value": "16MB"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSwappiness",
+            "Value": "150"
+
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighMemoryUsage",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSoftLimit",
+            "Value": "512MB"
+          }
+        },
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "MemSwappiness",
+            "Value": "100"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "SystemMemoryProcess",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "memory",
+            "Path": "system"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "FreezerDisabled",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "FreezerState",
+            "Value": "0"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "FreezerEnabled",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "FreezerState",
+            "Value": "1"
+          }
+        }
+      ]
+    }
+  ],
+
+  "AggregateProfiles": [
+    {
+      "Name": "SCHED_SP_DEFAULT",
+      "Profiles": [ "TimerSlackNormal" ]
+    },
+    {
+      "Name": "SCHED_SP_BACKGROUND",
+      "Profiles": [ "HighEnergySaving", "LowIoPriority", "TimerSlackHigh" ]
+    },
+    {
+      "Name": "SCHED_SP_FOREGROUND",
+      "Profiles": [ "HighPerformance", "HighIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "SCHED_SP_TOP_APP",
+      "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "SCHED_SP_RT_APP",
+      "Profiles": [ "RealtimePerformance", "MaxIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_DEFAULT",
+      "Profiles": [ "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_BACKGROUND",
+      "Profiles": [ "HighEnergySaving", "ProcessCapacityLow", "LowIoPriority", "TimerSlackHigh" ]
+    },
+    {
+      "Name": "CPUSET_SP_FOREGROUND",
+      "Profiles": [ "HighPerformance", "ProcessCapacityHigh", "HighIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_TOP_APP",
+      "Profiles": [ "MaxPerformance", "ProcessCapacityMax", "MaxIoPriority", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_SYSTEM",
+      "Profiles": [ "ServiceCapacityLow", "TimerSlackNormal" ]
+    },
+    {
+      "Name": "CPUSET_SP_RESTRICTED",
+      "Profiles": [ "ServiceCapacityRestricted", "TimerSlackNormal" ]
+    }
+  ]
+}
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index 25f16a6..a53132e 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -45,7 +45,7 @@
 
 #include "cgroup_descriptor.h"
 
-using android::base::GetBoolProperty;
+using android::base::GetUintProperty;
 using android::base::StringPrintf;
 using android::base::unique_fd;
 
@@ -55,6 +55,8 @@
 static constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json";
 static constexpr const char* CGROUPS_DESC_VENDOR_FILE = "/vendor/etc/cgroups.json";
 
+static constexpr const char* TEMPLATE_CGROUPS_DESC_API_FILE = "/etc/task_profiles/cgroups_%u.json";
+
 static bool ChangeDirModeAndOwner(const std::string& path, mode_t mode, const std::string& uid,
                                   const std::string& gid, bool permissive_mode = false) {
     uid_t pw_uid = -1;
@@ -212,8 +214,20 @@
 }
 
 static bool ReadDescriptors(std::map<std::string, CgroupDescriptor>* descriptors) {
+    unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
+    std::string sys_cgroups_path = CGROUPS_DESC_FILE;
+
+    // load API-level specific system cgroups descriptors if available
+    if (api_level > 0) {
+        std::string api_cgroups_path =
+                android::base::StringPrintf(TEMPLATE_CGROUPS_DESC_API_FILE, api_level);
+        if (!access(api_cgroups_path.c_str(), F_OK) || errno != ENOENT) {
+            sys_cgroups_path = api_cgroups_path;
+        }
+    }
+
     // load system cgroup descriptors
-    if (!ReadDescriptorsFromFile(CGROUPS_DESC_FILE, descriptors)) {
+    if (!ReadDescriptorsFromFile(sys_cgroups_path, descriptors)) {
         return false;
     }
 
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 4e767db..db44228 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -23,6 +23,7 @@
 
 #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 <android-base/threads.h>
@@ -38,13 +39,17 @@
 #endif
 
 using android::base::GetThreadId;
+using android::base::GetUintProperty;
 using android::base::StringPrintf;
 using android::base::StringReplace;
 using android::base::unique_fd;
 using android::base::WriteStringToFile;
 
-#define TASK_PROFILE_DB_FILE "/etc/task_profiles.json"
-#define TASK_PROFILE_DB_VENDOR_FILE "/vendor/etc/task_profiles.json"
+static constexpr const char* TASK_PROFILE_DB_FILE = "/etc/task_profiles.json";
+static constexpr const char* TASK_PROFILE_DB_VENDOR_FILE = "/vendor/etc/task_profiles.json";
+
+static constexpr const char* TEMPLATE_TASK_PROFILE_API_FILE =
+        "/etc/task_profiles/task_profiles_%u.json";
 
 void ProfileAttribute::Reset(const CgroupController& controller, const std::string& file_name) {
     controller_ = controller;
@@ -386,9 +391,21 @@
 }
 
 TaskProfiles::TaskProfiles() {
+    unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
+    std::string sys_profiles_path = TASK_PROFILE_DB_FILE;
+
+    // load API-level specific system task profiles if available
+    if (api_level > 0) {
+        std::string api_profiles_path =
+                android::base::StringPrintf(TEMPLATE_TASK_PROFILE_API_FILE, api_level);
+        if (!access(api_profiles_path.c_str(), F_OK) || errno != ENOENT) {
+            sys_profiles_path = api_profiles_path;
+        }
+    }
+
     // load system task profiles
-    if (!Load(CgroupMap::GetInstance(), TASK_PROFILE_DB_FILE)) {
-        LOG(ERROR) << "Loading " << TASK_PROFILE_DB_FILE << " for [" << getpid() << "] failed";
+    if (!Load(CgroupMap::GetInstance(), sys_profiles_path)) {
+        LOG(ERROR) << "Loading " << sys_profiles_path << " for [" << getpid() << "] failed";
     }
 
     // load vendor task profiles if the file exists
diff --git a/libsparse/Android.bp b/libsparse/Android.bp
index bf06bbc..860b9ae 100644
--- a/libsparse/Android.bp
+++ b/libsparse/Android.bp
@@ -4,6 +4,7 @@
     name: "libsparse",
     host_supported: true,
     ramdisk_available: true,
+    vendor_ramdisk_available: true,
     recovery_available: true,
     unique_host_soname: true,
     vendor_available: true,
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index c4c8768..2bceb75 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -69,7 +69,7 @@
 
 EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS :=
 ifeq ($(CLANG_COVERAGE),true)
-  EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS := export LLVM_PROFILE_FILE /data/misc/trace/clang-%20m.profraw
+  EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS := export LLVM_PROFILE_FILE /data/misc/trace/clang-%p-%m.profraw
 endif
 
 # Put it here instead of in init.rc module definition,
diff --git a/rootdir/avb/Android.mk b/rootdir/avb/Android.mk
index 9892ae7..c8fc1d6 100644
--- a/rootdir/avb/Android.mk
+++ b/rootdir/avb/Android.mk
@@ -1,11 +1,17 @@
 LOCAL_PATH:= $(call my-dir)
 
-ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true)
-  my_gsi_avb_keys_path := $(TARGET_RECOVERY_ROOT_OUT)/first_stage_ramdisk/avb
-else ifeq ($(BOARD_MOVE_GSI_AVB_KEYS_TO_VENDOR_BOOT),true)
-  my_gsi_avb_keys_path := $(TARGET_VENDOR_RAMDISK_OUT)/avb
+ifeq ($(BOARD_MOVE_GSI_AVB_KEYS_TO_VENDOR_BOOT),true) # AVB keys are installed to vendor ramdisk
+  ifeq ($(BOARD_MOVE_RECOVERY_RESOURCES_TO_VENDOR_BOOT),true) # no dedicated recovery partition
+    my_gsi_avb_keys_path := $(TARGET_VENDOR_RAMDISK_OUT)/first_stage_ramdisk/avb
+  else # device has a dedicated recovery partition
+    my_gsi_avb_keys_path := $(TARGET_VENDOR_RAMDISK_OUT)/avb
+  endif
 else
-  my_gsi_avb_keys_path := $(TARGET_RAMDISK_OUT)/avb
+  ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true) # no dedicated recovery partition
+    my_gsi_avb_keys_path := $(TARGET_RECOVERY_ROOT_OUT)/first_stage_ramdisk/avb
+  else # device has a dedicated recovery partition
+    my_gsi_avb_keys_path := $(TARGET_RAMDISK_OUT)/avb
+  endif
 endif
 
 #######################################
diff --git a/rootdir/init.rc b/rootdir/init.rc
index fbb48e8..52ba921 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -153,21 +153,56 @@
     mkdir /dev/cpuctl/background
     mkdir /dev/cpuctl/top-app
     mkdir /dev/cpuctl/rt
+    mkdir /dev/cpuctl/system
     chown system system /dev/cpuctl
     chown system system /dev/cpuctl/foreground
     chown system system /dev/cpuctl/background
     chown system system /dev/cpuctl/top-app
     chown system system /dev/cpuctl/rt
+    chown system system /dev/cpuctl/system
     chown system system /dev/cpuctl/tasks
     chown system system /dev/cpuctl/foreground/tasks
     chown system system /dev/cpuctl/background/tasks
     chown system system /dev/cpuctl/top-app/tasks
     chown system system /dev/cpuctl/rt/tasks
+    chown system system /dev/cpuctl/system/tasks
     chmod 0664 /dev/cpuctl/tasks
     chmod 0664 /dev/cpuctl/foreground/tasks
     chmod 0664 /dev/cpuctl/background/tasks
     chmod 0664 /dev/cpuctl/top-app/tasks
     chmod 0664 /dev/cpuctl/rt/tasks
+    chmod 0664 /dev/cpuctl/system/tasks
+
+    # Create a cpu group for NNAPI HAL processes
+    mkdir /dev/cpuctl/nnapi-hal
+    chown system system /dev/cpuctl/nnapi-hal
+    chown system system /dev/cpuctl/nnapi-hal/tasks
+    chmod 0664 /dev/cpuctl/nnapi-hal/tasks
+    write /dev/cpuctl/nnapi-hal/cpu.uclamp.min 1
+    write /dev/cpuctl/nnapi-hal/cpu.uclamp.latency_sensitive 1
+
+    # Android only use global RT throttling and doesn't use CONFIG_RT_GROUP_SCHED
+    # for RT group throttling. These values here are just to make sure RT threads
+    # can be migrated to those groups. These settings can be removed once we migrate
+    # to GKI kernel.
+    write /dev/cpuctl/cpu.rt_period_us 1000000
+    write /dev/cpuctl/cpu.rt_runtime_us 950000
+    # Surfaceflinger is in FG group so giving it a bit more
+    write /dev/cpuctl/foreground/cpu.rt_runtime_us 450000
+    write /dev/cpuctl/foreground/cpu.rt_period_us 1000000
+    write /dev/cpuctl/background/cpu.rt_runtime_us 100000
+    write /dev/cpuctl/background/cpu.rt_period_us 1000000
+    write /dev/cpuctl/top-app/cpu.rt_runtime_us 100000
+    write /dev/cpuctl/top-app/cpu.rt_period_us 1000000
+    write /dev/cpuctl/rt/cpu.rt_runtime_us 100000
+    write /dev/cpuctl/rt/cpu.rt_period_us 1000000
+    write /dev/cpuctl/system/cpu.rt_runtime_us 100000
+    write /dev/cpuctl/system/cpu.rt_period_us 1000000
+    write /dev/cpuctl/nnapi-hal/cpu.rt_runtime_us 100000
+    write /dev/cpuctl/nnapi-hal/cpu.rt_period_us 1000000
+
+    # Migrate root group to system subgroup
+    copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks
 
     # Create an stune group for NNAPI HAL processes
     mkdir /dev/stune/nnapi-hal
@@ -177,14 +212,6 @@
     write /dev/stune/nnapi-hal/schedtune.boost 1
     write /dev/stune/nnapi-hal/schedtune.prefer_idle 1
 
-    # cpuctl hierarchy for devices using utilclamp
-    mkdir /dev/cpuctl/nnapi-hal
-    chown system system /dev/cpuctl/nnapi-hal
-    chown system system /dev/cpuctl/nnapi-hal/tasks
-    chmod 0664 /dev/cpuctl/nnapi-hal/tasks
-    write /dev/cpuctl/nnapi-hal/cpu.uclamp.min 1
-    write /dev/cpuctl/nnapi-hal/cpu.uclamp.latency_sensitive 1
-
     # Create blkio group and apply initial settings.
     # This feature needs kernel to support it, and the
     # device's init.rc must actually set the correct values.
@@ -275,8 +302,6 @@
     write /proc/sys/vm/mmap_min_addr 32768
     write /proc/sys/net/ipv4/ping_group_range "0 2147483647"
     write /proc/sys/net/unix/max_dgram_qlen 600
-    write /proc/sys/kernel/sched_rt_runtime_us 950000
-    write /proc/sys/kernel/sched_rt_period_us 1000000
 
     # Assign reasonable ceiling values for socket rcv/snd buffers.
     # These should almost always be overridden by the target per the
@@ -298,13 +323,6 @@
     # /proc/net/fib_trie leaks interface IP addresses
     chmod 0400 /proc/net/fib_trie
 
-    # Create cgroup mount points for process groups
-    chown system system /dev/cpuctl
-    chown system system /dev/cpuctl/tasks
-    chmod 0666 /dev/cpuctl/tasks
-    write /dev/cpuctl/cpu.rt_period_us 1000000
-    write /dev/cpuctl/cpu.rt_runtime_us 950000
-
     # sets up initial cpusets for ActivityManager
     # this ensures that the cpusets are present and usable, but the device's
     # init.rc must actually set the correct cpus
@@ -808,6 +826,10 @@
     wait_for_prop apexd.status activated
     perform_apex_config
 
+    # After apexes are mounted, tell keymaster early boot has ended, so it will
+    # stop allowing use of early-boot keys
+    exec - system system -- /system/bin/vdc keymaster early-boot-ended
+
     # Special-case /data/media/obb per b/64566063
     mkdir /data/media 0770 media_rw media_rw encryption=None
     exec - media_rw media_rw -- /system/bin/chattr +F /data/media
@@ -1147,3 +1169,7 @@
 
 on property:sys.boot_completed=1 && property:sys.init.userspace_reboot.in_progress=1
   setprop sys.init.userspace_reboot.in_progress ""
+
+# Migrate tasks again in case kernel threads are created during boot
+on property:sys.boot_completed=1
+  copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks
diff --git a/toolbox/start.cpp b/toolbox/start.cpp
index cffb89c..46314cf 100644
--- a/toolbox/start.cpp
+++ b/toolbox/start.cpp
@@ -37,7 +37,6 @@
 
 static void ControlDefaultServices(bool start) {
     std::vector<std::string> services = {
-        "iorapd",
         "netd",
         "surfaceflinger",
         "audioserver",
@@ -92,4 +91,4 @@
 
 extern "C" int stop_main(int argc, char** argv) {
     return StartStop(argc, argv, false);
-}
\ No newline at end of file
+}
diff --git a/trusty/confirmationui/fuzz/Android.bp b/trusty/confirmationui/fuzz/Android.bp
new file mode 100644
index 0000000..0819c21
--- /dev/null
+++ b/trusty/confirmationui/fuzz/Android.bp
@@ -0,0 +1,19 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_fuzz {
+    name: "trusty_confirmationui_fuzzer",
+    defaults: ["trusty_fuzzer_defaults"],
+    srcs: ["fuzz.cpp"],
+}
diff --git a/trusty/confirmationui/fuzz/fuzz.cpp b/trusty/confirmationui/fuzz/fuzz.cpp
new file mode 100644
index 0000000..d285116
--- /dev/null
+++ b/trusty/confirmationui/fuzz/fuzz.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <log/log.h>
+#include <stdlib.h>
+#include <trusty/fuzz/utils.h>
+#include <unistd.h>
+
+using android::trusty::fuzz::TrustyApp;
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define CONFIRMATIONUI_PORT "com.android.trusty.confirmationui"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    static uint8_t buf[TIPC_MAX_MSG_SIZE];
+
+    TrustyApp ta(TIPC_DEV, CONFIRMATIONUI_PORT);
+    auto ret = ta.Connect();
+    if (!ret.ok()) {
+        android::trusty::fuzz::Abort();
+    }
+
+    /* Send message to confirmationui server */
+    ret = ta.Write(data, size);
+    if (!ret.ok()) {
+        return -1;
+    }
+
+    /* Read message from confirmationui server */
+    ret = ta.Read(&buf, sizeof(buf));
+    if (!ret.ok()) {
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/trusty/fuzz/Android.bp b/trusty/fuzz/Android.bp
index 969431c..ac49751 100644
--- a/trusty/fuzz/Android.bp
+++ b/trusty/fuzz/Android.bp
@@ -14,10 +14,8 @@
 
 cc_defaults {
     name: "trusty_fuzzer_defaults",
-    static_libs: [
-        "libtrusty_fuzz_utils",
-    ],
     shared_libs: [
+        "libtrusty_fuzz_utils",
         "libbase",
         "liblog",
     ],
@@ -36,6 +34,7 @@
     srcs: ["utils.cpp"],
     export_include_dirs: ["include"],
     shared_libs: [
+        "libtrusty_test",
         "libbase",
         "liblog",
     ],
diff --git a/trusty/fuzz/utils.cpp b/trusty/fuzz/utils.cpp
index 240afe7..f4cf0b6 100644
--- a/trusty/fuzz/utils.cpp
+++ b/trusty/fuzz/utils.cpp
@@ -25,6 +25,7 @@
 #include <linux/uio.h>
 #include <log/log_read.h>
 #include <time.h>
+#include <trusty/tipc.h>
 #include <iostream>
 
 using android::base::ErrnoError;
@@ -32,9 +33,6 @@
 using android::base::Result;
 using android::base::unique_fd;
 
-#define TIPC_IOC_MAGIC 'r'
-#define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char*)
-
 namespace {
 
 const size_t kTimeoutSeconds = 5;
@@ -80,27 +78,14 @@
     : tipc_dev_(tipc_dev), ta_port_(ta_port), ta_fd_(-1) {}
 
 Result<void> TrustyApp::Connect() {
-    /*
-     * TODO: We can't use libtrusty because (yet)
-     * (1) cc_fuzz can't deal with vendor components (b/170753563)
-     * (2) We need non-blocking behavior to detect Trusty going down.
-     * (we could implement the timeout in the fuzzing code though, as
-     * it needs to be around the call to read())
-     */
     alarm(kTimeoutSeconds);
-    int fd = open(tipc_dev_.c_str(), O_RDWR);
+    int fd = tipc_connect(tipc_dev_.c_str(), ta_port_.c_str());
     alarm(0);
     if (fd < 0) {
         return ErrnoError() << "failed to open TIPC device: ";
     }
     ta_fd_.reset(fd);
 
-    // This ioctl will time out in the kernel if it can't connect.
-    int rc = TEMP_FAILURE_RETRY(ioctl(ta_fd_, TIPC_IOC_CONNECT, ta_port_.c_str()));
-    if (rc < 0) {
-        return ErrnoError() << "failed to connect to TIPC service: ";
-    }
-
     return {};
 }
 
diff --git a/trusty/libtrusty/Android.bp b/trusty/libtrusty/Android.bp
index 8dba78d..708fdbd 100644
--- a/trusty/libtrusty/Android.bp
+++ b/trusty/libtrusty/Android.bp
@@ -12,10 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-cc_library {
-    name: "libtrusty",
-    vendor: true,
-
+cc_defaults {
+    name: "libtrusty_defaults",
     srcs: ["trusty.c"],
     export_include_dirs: ["include"],
     cflags: [
@@ -25,3 +23,16 @@
 
     shared_libs: ["liblog"],
 }
+
+cc_library {
+    name: "libtrusty",
+    vendor: true,
+    defaults: ["libtrusty_defaults"],
+}
+
+// TODO(b/170753563): cc_fuzz can't deal with vendor components. Build libtrusty
+// for system.
+cc_test_library {
+    name: "libtrusty_test",
+    defaults: ["libtrusty_defaults"],
+}
diff --git a/trusty/libtrusty/trusty.c b/trusty/libtrusty/trusty.c
index ad4d8cd..f44f8b4 100644
--- a/trusty/libtrusty/trusty.c
+++ b/trusty/libtrusty/trusty.c
@@ -29,30 +29,27 @@
 
 #include <trusty/ipc.h>
 
-int tipc_connect(const char *dev_name, const char *srv_name)
-{
-	int fd;
-	int rc;
+int tipc_connect(const char* dev_name, const char* srv_name) {
+    int fd;
+    int rc;
 
-	fd = open(dev_name, O_RDWR);
-	if (fd < 0) {
-		rc = -errno;
-		ALOGE("%s: cannot open tipc device \"%s\": %s\n",
-		      __func__, dev_name, strerror(errno));
-		return rc < 0 ? rc : -1;
-	}
+    fd = TEMP_FAILURE_RETRY(open(dev_name, O_RDWR));
+    if (fd < 0) {
+        rc = -errno;
+        ALOGE("%s: cannot open tipc device \"%s\": %s\n", __func__, dev_name, strerror(errno));
+        return rc < 0 ? rc : -1;
+    }
 
-	rc = ioctl(fd, TIPC_IOC_CONNECT, srv_name);
-	if (rc < 0) {
-		rc = -errno;
-		ALOGE("%s: can't connect to tipc service \"%s\" (err=%d)\n",
-		      __func__, srv_name, errno);
-		close(fd);
-		return rc < 0 ? rc : -1;
-	}
+    rc = TEMP_FAILURE_RETRY(ioctl(fd, TIPC_IOC_CONNECT, srv_name));
+    if (rc < 0) {
+        rc = -errno;
+        ALOGE("%s: can't connect to tipc service \"%s\" (err=%d)\n", __func__, srv_name, errno);
+        close(fd);
+        return rc < 0 ? rc : -1;
+    }
 
-	ALOGV("%s: connected to \"%s\" fd %d\n", __func__, srv_name, fd);
-	return fd;
+    ALOGV("%s: connected to \"%s\" fd %d\n", __func__, srv_name, fd);
+    return fd;
 }
 
 ssize_t tipc_send(int fd, const struct iovec* iov, int iovcnt, struct trusty_shm* shms,
@@ -63,7 +60,7 @@
     req.shm = (__u64)shms;
     req.shm_cnt = (__u64)shmcnt;
 
-    int rc = ioctl(fd, TIPC_IOC_SEND_MSG, &req);
+    int rc = TEMP_FAILURE_RETRY(ioctl(fd, TIPC_IOC_SEND_MSG, &req));
     if (rc < 0) {
         ALOGE("%s: failed to send message (err=%d)\n", __func__, rc);
     }
@@ -71,7 +68,6 @@
     return rc;
 }
 
-void tipc_close(int fd)
-{
-	close(fd);
+void tipc_close(int fd) {
+    close(fd);
 }
diff --git a/trusty/trusty-test.mk b/trusty/trusty-test.mk
index dc4c962..74106ec 100644
--- a/trusty/trusty-test.mk
+++ b/trusty/trusty-test.mk
@@ -15,4 +15,5 @@
 PRODUCT_PACKAGES += \
 	spiproxyd \
 	trusty_keymaster_set_attestation_key \
-	keymaster_soft_attestation_keys.xml \
\ No newline at end of file
+	keymaster_soft_attestation_keys.xml \
+