Merge "init: log when 'user' is unspecified"
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index b302918..c9e097e 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -216,7 +216,7 @@
       log_error(output_fd, 0,
                 "received packet of unexpected length from tombstoned while reading %s response: "
                 "expected %zd, received %zd",
-                kind, sizeof(response), rc);
+                kind, sizeof(*response), rc);
       return false;
     }
     return true;
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index e4d68f8..8e6abdf 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -190,6 +190,7 @@
 static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone,
                                    const Thread& thread, bool should_log) {
   CBS("");
+  CB(should_log, "%d total frames", thread.current_backtrace().size());
   CB(should_log, "backtrace:");
   if (!thread.backtrace_note().empty()) {
     CB(should_log, "  NOTE: %s",
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 3b786e8..7794c4b 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -381,6 +381,7 @@
         "socket_mock.cpp",
         "socket_test.cpp",
         "super_flash_helper_test.cpp",
+        "task_test.cpp",
         "tcp_test.cpp",
         "udp_test.cpp",
     ],
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 42269fe..cdcd036 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -115,19 +115,6 @@
 
 fastboot::FastBootDriver* fb = nullptr;
 
-enum fb_buffer_type {
-    FB_BUFFER_FD,
-    FB_BUFFER_SPARSE,
-};
-
-struct fastboot_buffer {
-    enum fb_buffer_type type;
-    std::vector<SparsePtr> files;
-    int64_t sz;
-    unique_fd fd;
-    int64_t image_size;
-};
-
 static std::vector<Image> images = {
         // clang-format off
     { "boot",     "boot.img",         "boot.sig",     "boot",     false, ImageType::BootCritical },
@@ -1284,7 +1271,7 @@
     }
 }
 
-static std::string get_current_slot() {
+std::string get_current_slot() {
     std::string current_slot;
     if (fb->GetVar("current-slot", &current_slot) != fastboot::SUCCESS) return "";
     if (current_slot[0] == '_') current_slot.erase(0, 1);
@@ -1560,7 +1547,7 @@
     }
 }
 
-std::string GetPartitionName(const ImageEntry& entry, std::string& current_slot) {
+std::string GetPartitionName(const ImageEntry& entry, const std::string& current_slot) {
     auto slot = entry.second;
     if (slot.empty()) {
         slot = current_slot;
@@ -1574,23 +1561,215 @@
     return entry.first->part_name + "_" + slot;
 }
 
-class FlashAllTool {
-  public:
-    FlashAllTool(FlashingPlan* fp);
+std::unique_ptr<FlashTask> ParseFlashCommand(const FlashingPlan* fp,
+                                             const std::vector<std::string>& parts) {
+    bool apply_vbmeta = false;
+    std::string slot = fp->slot_override;
+    std::string partition;
+    std::string img_name;
+    for (auto& part : parts) {
+        if (part == "--apply-vbmeta") {
+            apply_vbmeta = true;
+        } else if (part == "--slot-other") {
+            slot = fp->secondary_slot;
+        } else if (partition.empty()) {
+            partition = part;
+        } else if (img_name.empty()) {
+            img_name = part;
+        } else {
+            LOG(ERROR) << "unknown argument" << part
+                       << " in fastboot-info.txt. parts: " << android::base::Join(parts, " ");
+            return nullptr;
+        }
+    }
+    if (partition.empty()) {
+        LOG(ERROR) << "partition name not found when parsing fastboot-info.txt. parts: "
+                   << android::base::Join(parts, " ");
+        return nullptr;
+    }
+    if (img_name.empty()) {
+        img_name = partition + ".img";
+    }
+    return std::make_unique<FlashTask>(slot, partition, img_name, apply_vbmeta);
+}
 
-    void Flash();
+std::unique_ptr<RebootTask> ParseRebootCommand(const FlashingPlan* fp,
+                                               const std::vector<std::string>& parts) {
+    if (parts.empty()) return std::make_unique<RebootTask>(fp);
+    if (parts.size() > 1) {
+        LOG(ERROR) << "unknown arguments in reboot {target} in fastboot-info.txt: "
+                   << android::base::Join(parts, " ");
+        return nullptr;
+    }
+    return std::make_unique<RebootTask>(fp, parts[0]);
+}
 
-  private:
-    void CheckRequirements();
-    void DetermineSlot();
-    void CollectImages();
-    void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
-    void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
+std::unique_ptr<WipeTask> ParseWipeCommand(const FlashingPlan* fp,
+                                           const std::vector<std::string>& parts) {
+    if (parts.size() != 1) {
+        LOG(ERROR) << "unknown arguments in erase {partition} in fastboot-info.txt: "
+                   << android::base::Join(parts, " ");
+        return nullptr;
+    }
+    return std::make_unique<WipeTask>(fp, parts[0]);
+}
 
-    std::vector<ImageEntry> boot_images_;
-    std::vector<ImageEntry> os_images_;
-    FlashingPlan* fp_;
-};
+std::unique_ptr<Task> ParseFastbootInfoLine(const FlashingPlan* fp,
+                                            const std::vector<std::string>& command) {
+    if (command.size() == 0) {
+        return nullptr;
+    }
+    std::unique_ptr<Task> task;
+
+    if (command[0] == "flash") {
+        task = ParseFlashCommand(fp, std::vector<std::string>{command.begin() + 1, command.end()});
+    } else if (command[0] == "reboot") {
+        task = ParseRebootCommand(fp, std::vector<std::string>{command.begin() + 1, command.end()});
+    } else if (command[0] == "update-super" && command.size() == 1) {
+        task = std::make_unique<UpdateSuperTask>(fp);
+    } else if (command[0] == "erase" && command.size() == 2) {
+        task = ParseWipeCommand(fp, std::vector<std::string>{command.begin() + 1, command.end()});
+    }
+    if (!task) {
+        LOG(ERROR) << "unknown command parsing fastboot-info.txt line: "
+                   << android::base::Join(command, " ");
+    }
+    return task;
+}
+
+void AddResizeTasks(const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>* tasks) {
+    // expands "resize-partitions" into individual commands : resize {os_partition_1}, resize
+    // {os_partition_2}, etc.
+    std::vector<std::unique_ptr<Task>> resize_tasks;
+    std::optional<size_t> loc;
+    for (size_t i = 0; i < tasks->size(); i++) {
+        if (auto flash_task = tasks->at(i)->AsFlashTask()) {
+            if (should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
+                if (!loc) {
+                    loc = i;
+                }
+                resize_tasks.emplace_back(std::make_unique<ResizeTask>(
+                        fp, flash_task->GetPartition(), "0", fp->slot_override));
+            }
+        }
+    }
+    // if no logical partitions (although should never happen since system will always need to be
+    // flashed)
+    if (!loc) {
+        return;
+    }
+    tasks->insert(tasks->begin() + loc.value(), std::make_move_iterator(resize_tasks.begin()),
+                  std::make_move_iterator(resize_tasks.end()));
+    return;
+}
+
+static bool IsNumber(const std::string& s) {
+    bool period = false;
+    for (size_t i = 0; i < s.length(); i++) {
+        if (!isdigit(s[i])) {
+            if (!period && s[i] == '.' && i != 0 && i != s.length() - 1) {
+                period = true;
+            } else {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+static bool IsIgnore(const std::vector<std::string>& command) {
+    if (command[0][0] == '#') {
+        return true;
+    }
+    return false;
+}
+
+bool CheckFastbootInfoRequirements(const std::vector<std::string>& command) {
+    if (command.size() != 2) {
+        LOG(ERROR) << "unknown characters in version info in fastboot-info.txt -> "
+                   << android::base::Join(command, " ");
+        return false;
+    }
+    if (command[0] != "version") {
+        LOG(ERROR) << "unknown characters in version info in fastboot-info.txt -> "
+                   << android::base::Join(command, " ");
+        return false;
+    }
+
+    if (!IsNumber(command[1])) {
+        LOG(ERROR) << "version number contains non-numeric values in fastboot-info.txt -> "
+                   << android::base::Join(command, " ");
+        return false;
+    }
+
+    LOG(VERBOSE) << "Checking 'fastboot-info.txt version'";
+    if (command[1] < PLATFORM_TOOLS_VERSION) {
+        return true;
+    }
+    LOG(ERROR) << "fasboot-info.txt version: " << command[1]
+               << " not compatible with host tool version --> " << PLATFORM_TOOLS_VERSION;
+    return false;
+}
+
+std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp,
+                                                     const std::vector<std::string>& file) {
+    std::vector<std::unique_ptr<Task>> tasks;
+    // Get os_partitions that need to be resized
+    for (auto& text : file) {
+        std::vector<std::string> command = android::base::Tokenize(text, " ");
+        if (IsIgnore(command)) {
+            continue;
+        }
+        if (command.size() > 1 && command[0] == "version") {
+            if (!CheckFastbootInfoRequirements(command)) {
+                return {};
+            }
+            continue;
+        } else if (command.size() >= 2 && command[0] == "if-wipe") {
+            if (!fp->wants_wipe) {
+                continue;
+            }
+            command.erase(command.begin());
+        }
+        auto task = ParseFastbootInfoLine(fp, command);
+        if (!task) {
+            LOG(ERROR) << "Error when parsing fastboot-info.txt, falling back on Hardcoded list: "
+                       << text;
+            return {};
+        }
+        tasks.emplace_back(std::move(task));
+    }
+    if (auto flash_super_task = FlashSuperLayoutTask::InitializeFromTasks(fp, tasks)) {
+        auto it = tasks.begin();
+        for (size_t i = 0; i < tasks.size(); i++) {
+            if (auto flash_task = tasks[i]->AsFlashTask()) {
+                if (should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
+                    break;
+                }
+            }
+            if (auto wipe_task = tasks[i]->AsWipeTask()) {
+                break;
+            }
+            it++;
+        }
+        tasks.insert(it, std::move(flash_super_task));
+    } else {
+        AddResizeTasks(fp, &tasks);
+    }
+    return tasks;
+}
+
+std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp, std::ifstream& fs) {
+    if (!fs || fs.eof()) return {};
+
+    std::string text;
+    std::vector<std::string> file;
+    // Get os_partitions that need to be resized
+    while (std::getline(fs, text)) {
+        file.emplace_back(text);
+    }
+    return ParseFastbootInfo(fp, file);
+}
 
 FlashAllTool::FlashAllTool(FlashingPlan* fp) : fp_(fp) {}
 
@@ -1600,10 +1779,10 @@
 
     // Change the slot first, so we boot into the correct recovery image when
     // using fastbootd.
-    if (fp_->slot == "all") {
+    if (fp_->slot_override == "all") {
         set_active("a");
     } else {
-        set_active(fp_->slot);
+        set_active(fp_->slot_override);
     }
 
     DetermineSlot();
@@ -1611,6 +1790,75 @@
 
     CancelSnapshotIfNeeded();
 
+    std::string path = find_item_given_name("fastboot-info.txt");
+    std::ifstream stream(path);
+    std::vector<std::unique_ptr<Task>> tasks = ParseFastbootInfo(fp_, stream);
+    if (tasks.empty()) {
+        LOG(VERBOSE) << "Flashing from hardcoded images. fastboot-info.txt is empty or does not "
+                        "exist";
+        HardcodedFlash();
+        return;
+    }
+    LOG(VERBOSE) << "Flashing from fastboot-info.txt";
+    for (auto& task : tasks) {
+        task->Run();
+    }
+    if (fp_->wants_wipe) {
+        // avoid adding duplicate wipe tasks in fastboot main code.
+        fp_->wants_wipe = false;
+    }
+}
+
+void FlashAllTool::CheckRequirements() {
+    std::vector<char> contents;
+    if (!fp_->source->ReadFile("android-info.txt", &contents)) {
+        die("could not read android-info.txt");
+    }
+    ::CheckRequirements({contents.data(), contents.size()}, fp_->force_flash);
+}
+
+void FlashAllTool::DetermineSlot() {
+    if (fp_->slot_override.empty()) {
+        fp_->current_slot = get_current_slot();
+    } else {
+        fp_->current_slot = fp_->slot_override;
+    }
+
+    if (fp_->skip_secondary) {
+        return;
+    }
+    if (fp_->slot_override != "" && fp_->slot_override != "all") {
+        fp_->secondary_slot = get_other_slot(fp_->slot_override);
+    } else {
+        fp_->secondary_slot = get_other_slot();
+    }
+    if (fp_->secondary_slot == "") {
+        if (supports_AB()) {
+            fprintf(stderr, "Warning: Could not determine slot for secondary images. Ignoring.\n");
+        }
+        fp_->skip_secondary = true;
+    }
+}
+
+void FlashAllTool::CollectImages() {
+    for (size_t i = 0; i < images.size(); ++i) {
+        std::string slot = fp_->slot_override;
+        if (images[i].IsSecondary()) {
+            if (fp_->skip_secondary) {
+                continue;
+            }
+            slot = fp_->secondary_slot;
+        }
+        if (images[i].type == ImageType::BootCritical) {
+            boot_images_.emplace_back(&images[i], slot);
+        } else if (images[i].type == ImageType::Normal) {
+            os_images_.emplace_back(&images[i], slot);
+        }
+    }
+}
+
+void FlashAllTool::HardcodedFlash() {
+    CollectImages();
     // First flash boot partitions. We allow this to happen either in userspace
     // or in bootloader fastboot.
     FlashImages(boot_images_);
@@ -1631,7 +1879,7 @@
             // this, we delete any logical partitions for the "other" slot.
             if (is_retrofit_device()) {
                 std::string partition_name = image->part_name + "_"s + slot;
-                if (image->IsSecondary() && is_logical(partition_name)) {
+                if (image->IsSecondary() && should_flash_in_userspace(partition_name)) {
                     fp_->fb->DeletePartition(partition_name);
                 }
                 tasks.emplace_back(std::make_unique<DeleteTask>(fp_, partition_name));
@@ -1639,60 +1887,12 @@
             tasks.emplace_back(std::make_unique<ResizeTask>(fp_, image->part_name, "0", slot));
         }
     }
-    for (auto& task : tasks) {
-        task->Run();
+    for (auto& i : tasks) {
+        i->Run();
     }
     FlashImages(os_images_);
 }
 
-void FlashAllTool::CheckRequirements() {
-    std::vector<char> contents;
-    if (!fp_->source->ReadFile("android-info.txt", &contents)) {
-        die("could not read android-info.txt");
-    }
-    ::CheckRequirements({contents.data(), contents.size()}, fp_->force_flash);
-}
-
-void FlashAllTool::DetermineSlot() {
-    if (fp_->slot.empty()) {
-        fp_->current_slot = get_current_slot();
-    } else {
-        fp_->current_slot = fp_->slot;
-    }
-
-    if (fp_->skip_secondary) {
-        return;
-    }
-    if (fp_->slot != "" && fp_->slot != "all") {
-        fp_->secondary_slot = get_other_slot(fp_->slot);
-    } else {
-        fp_->secondary_slot = get_other_slot();
-    }
-    if (fp_->secondary_slot == "") {
-        if (supports_AB()) {
-            fprintf(stderr, "Warning: Could not determine slot for secondary images. Ignoring.\n");
-        }
-        fp_->skip_secondary = true;
-    }
-}
-
-void FlashAllTool::CollectImages() {
-    for (size_t i = 0; i < images.size(); ++i) {
-        std::string slot = fp_->slot;
-        if (images[i].IsSecondary()) {
-            if (fp_->skip_secondary) {
-                continue;
-            }
-            slot = fp_->secondary_slot;
-        }
-        if (images[i].type == ImageType::BootCritical) {
-            boot_images_.emplace_back(&images[i], slot);
-        } else if (images[i].type == ImageType::Normal) {
-            os_images_.emplace_back(&images[i], slot);
-        }
-    }
-}
-
 void FlashAllTool::FlashImages(const std::vector<std::pair<const Image*, std::string>>& images) {
     for (const auto& [image, slot] : images) {
         fastboot_buffer buf;
@@ -2017,7 +2217,6 @@
     std::unique_ptr<FlashingPlan> fp = std::make_unique<FlashingPlan>();
 
     int longindex;
-    std::string slot_override;
     std::string next_active;
 
     g_boot_img_hdr.kernel_addr = 0x00008000;
@@ -2090,7 +2289,7 @@
             } else if (name == "skip-secondary") {
                 fp->skip_secondary = true;
             } else if (name == "slot") {
-                slot_override = optarg;
+                fp->slot_override = optarg;
             } else if (name == "dtb-offset") {
                 g_boot_img_hdr.dtb_addr = strtoul(optarg, 0, 16);
             } else if (name == "tags-offset") {
@@ -2182,12 +2381,12 @@
 
     const double start = now();
 
-    if (slot_override != "") slot_override = verify_slot(slot_override);
+    if (fp->slot_override != "") fp->slot_override = verify_slot(fp->slot_override);
     if (next_active != "") next_active = verify_slot(next_active, false);
 
     if (fp->wants_set_active) {
         if (next_active == "") {
-            if (slot_override == "") {
+            if (fp->slot_override == "") {
                 std::string current_slot;
                 if (fb->GetVar("current-slot", &current_slot) == fastboot::SUCCESS) {
                     if (current_slot[0] == '_') current_slot.erase(0, 1);
@@ -2196,11 +2395,11 @@
                     fp->wants_set_active = false;
                 }
             } else {
-                next_active = verify_slot(slot_override, false);
+                next_active = verify_slot(fp->slot_override, false);
             }
         }
     }
-    std::unique_ptr<Task> reboot_task = nullptr;
+    std::vector<std::unique_ptr<Task>> tasks;
     std::vector<std::string> args(argv, argv + argc);
     while (!args.empty()) {
         std::string command = next_arg(&args);
@@ -2221,7 +2420,7 @@
 
                 fb->Erase(partition);
             };
-            do_for_partitions(partition, slot_override, erase, true);
+            do_for_partitions(partition, fp->slot_override, erase, true);
         } else if (android::base::StartsWith(command, "format")) {
             // Parsing for: "format[:[type][:[size]]]"
             // Some valid things:
@@ -2241,7 +2440,7 @@
             auto format = [&](const std::string& partition) {
                 fb_perform_format(partition, 0, type_override, size_override, fp->fs_options);
             };
-            do_for_partitions(partition, slot_override, format, true);
+            do_for_partitions(partition, fp->slot_override, format, true);
         } else if (command == "signature") {
             std::string filename = next_arg(&args);
             std::vector<char> data;
@@ -2254,17 +2453,17 @@
         } else if (command == FB_CMD_REBOOT) {
             if (args.size() == 1) {
                 std::string reboot_target = next_arg(&args);
-                reboot_task = std::make_unique<RebootTask>(fp.get(), reboot_target);
-            } else {
-                reboot_task = std::make_unique<RebootTask>(fp.get());
+                tasks.emplace_back(std::make_unique<RebootTask>(fp.get(), reboot_target));
+            } else if (!fp->skip_reboot) {
+                tasks.emplace_back(std::make_unique<RebootTask>(fp.get()));
             }
             if (!args.empty()) syntax_error("junk after reboot command");
         } else if (command == FB_CMD_REBOOT_BOOTLOADER) {
-            reboot_task = std::make_unique<RebootTask>(fp.get(), "bootloader");
+            tasks.emplace_back(std::make_unique<RebootTask>(fp.get(), "bootloader"));
         } else if (command == FB_CMD_REBOOT_RECOVERY) {
-            reboot_task = std::make_unique<RebootTask>(fp.get(), "recovery");
+            tasks.emplace_back(std::make_unique<RebootTask>(fp.get(), "recovery"));
         } else if (command == FB_CMD_REBOOT_FASTBOOT) {
-            reboot_task = std::make_unique<RebootTask>(fp.get(), "fastboot");
+            tasks.emplace_back(std::make_unique<RebootTask>(fp.get(), "fastboot"));
         } else if (command == FB_CMD_CONTINUE) {
             fb->Continue();
         } else if (command == FB_CMD_BOOT) {
@@ -2286,7 +2485,7 @@
             }
             if (fname.empty()) die("cannot determine image filename for '%s'", pname.c_str());
 
-            FlashTask task(slot_override, pname, fname, is_vbmeta_partition(pname));
+            FlashTask task(fp->slot_override, pname, fname, is_vbmeta_partition(pname));
             task.Run();
         } else if (command == "flash:raw") {
             std::string partition = next_arg(&args);
@@ -2300,19 +2499,20 @@
             auto flashraw = [&data](const std::string& partition) {
                 fb->FlashPartition(partition, data);
             };
-            do_for_partitions(partition, slot_override, flashraw, true);
+            do_for_partitions(partition, fp->slot_override, flashraw, true);
         } else if (command == "flashall") {
-            if (slot_override == "all") {
+            if (fp->slot_override == "all") {
                 fprintf(stderr,
                         "Warning: slot set to 'all'. Secondary slots will not be flashed.\n");
                 fp->skip_secondary = true;
-                do_flashall(fp.get());
-            } else {
-                do_flashall(fp.get());
             }
-            reboot_task = std::make_unique<RebootTask>(fp.get());
+            do_flashall(fp.get());
+
+            if (!fp->skip_reboot) {
+                tasks.emplace_back(std::make_unique<RebootTask>(fp.get()));
+            }
         } else if (command == "update") {
-            bool slot_all = (slot_override == "all");
+            bool slot_all = (fp->slot_override == "all");
             if (slot_all) {
                 fprintf(stderr,
                         "Warning: slot set to 'all'. Secondary slots will not be flashed.\n");
@@ -2322,7 +2522,9 @@
                 filename = next_arg(&args);
             }
             do_update(filename.c_str(), fp.get());
-            reboot_task = std::make_unique<RebootTask>(fp.get());
+            if (!fp->skip_reboot) {
+                tasks.emplace_back(std::make_unique<RebootTask>(fp.get()));
+            }
         } else if (command == FB_CMD_SET_ACTIVE) {
             std::string slot = verify_slot(next_arg(&args), false);
             fb->SetActive(slot);
@@ -2355,13 +2557,12 @@
             fb->CreatePartition(partition, size);
         } else if (command == FB_CMD_DELETE_PARTITION) {
             std::string partition = next_arg(&args);
-            auto delete_task = std::make_unique<DeleteTask>(fp.get(), partition);
-            fb->DeletePartition(partition);
+            tasks.emplace_back(std::make_unique<DeleteTask>(fp.get(), partition));
         } else if (command == FB_CMD_RESIZE_PARTITION) {
             std::string partition = next_arg(&args);
             std::string size = next_arg(&args);
             std::unique_ptr<ResizeTask> resize_task =
-                    std::make_unique<ResizeTask>(fp.get(), partition, size, slot_override);
+                    std::make_unique<ResizeTask>(fp.get(), partition, size, fp->slot_override);
             resize_task->Run();
         } else if (command == "gsi") {
             std::string arg = next_arg(&args);
@@ -2379,7 +2580,7 @@
             } else {
                 image = next_arg(&args);
             }
-            do_wipe_super(image, slot_override);
+            do_wipe_super(image, fp->slot_override);
         } else if (command == "snapshot-update") {
             std::string arg;
             if (!args.empty()) {
@@ -2392,7 +2593,7 @@
         } else if (command == FB_CMD_FETCH) {
             std::string partition = next_arg(&args);
             std::string outfile = next_arg(&args);
-            do_fetch(partition, slot_override, outfile);
+            do_fetch(partition, fp->slot_override, outfile);
         } else {
             syntax_error("unknown command %s", command.c_str());
         }
@@ -2404,15 +2605,14 @@
         }
         std::vector<std::string> partitions = {"userdata", "cache", "metadata"};
         for (const auto& partition : partitions) {
-            std::unique_ptr<WipeTask> wipe_task = std::make_unique<WipeTask>(fp.get(), partition);
-            wipe_task->Run();
+            tasks.emplace_back(std::make_unique<WipeTask>(fp.get(), partition));
         }
     }
     if (fp->wants_set_active) {
         fb->SetActive(next_active);
     }
-    if (reboot_task && !fp->skip_reboot) {
-        reboot_task->Run();
+    for (auto& task : tasks) {
+        task->Run();
     }
     fprintf(stderr, "Finished. Total time: %.3fs\n", (now() - start));
 
diff --git a/fastboot/fastboot.h b/fastboot/fastboot.h
index c954487..d6afb9e 100644
--- a/fastboot/fastboot.h
+++ b/fastboot/fastboot.h
@@ -29,7 +29,10 @@
 
 #include <string>
 #include "fastboot_driver.h"
+#include "fastboot_driver_interface.h"
+#include "filesystem.h"
 #include "super_flash_helper.h"
+#include "task.h"
 #include "util.h"
 
 #include <bootimg.h>
@@ -47,6 +50,19 @@
     unsigned ParseFsOption(const char*);
 };
 
+enum fb_buffer_type {
+    FB_BUFFER_FD,
+    FB_BUFFER_SPARSE,
+};
+
+struct fastboot_buffer {
+    enum fb_buffer_type type;
+    std::vector<SparsePtr> files;
+    int64_t sz;
+    unique_fd fd;
+    int64_t image_size;
+};
+
 enum class ImageType {
     // Must be flashed for device to boot into the kernel.
     BootCritical,
@@ -80,10 +96,30 @@
     bool skip_secondary = false;
     bool force_flash = false;
 
-    std::string slot;
+    std::string slot_override;
     std::string current_slot;
     std::string secondary_slot;
-    fastboot::FastBootDriver* fb;
+
+    fastboot::IFastBootDriver* fb;
+};
+
+class FlashAllTool {
+  public:
+    FlashAllTool(FlashingPlan* fp);
+
+    void Flash();
+
+  private:
+    void CheckRequirements();
+    void DetermineSlot();
+    void CollectImages();
+    void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
+    void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
+    void HardcodedFlash();
+
+    std::vector<ImageEntry> boot_images_;
+    std::vector<ImageEntry> os_images_;
+    FlashingPlan* fp_;
 };
 
 bool should_flash_in_userspace(const std::string& partition_name);
@@ -94,6 +130,21 @@
 std::string find_item(const std::string& item);
 void reboot_to_userspace_fastboot();
 void syntax_error(const char* fmt, ...);
+std::string get_current_slot();
+
+// Code for Parsing fastboot-info.txt
+bool CheckFastbootInfoRequirements(const std::vector<std::string>& command);
+std::unique_ptr<FlashTask> ParseFlashCommand(const FlashingPlan* fp,
+                                             const std::vector<std::string>& parts);
+std::unique_ptr<RebootTask> ParseRebootCommand(const FlashingPlan* fp,
+                                               const std::vector<std::string>& parts);
+std::unique_ptr<WipeTask> ParseWipeCommand(const FlashingPlan* fp,
+                                           const std::vector<std::string>& parts);
+std::unique_ptr<Task> ParseFastbootInfoLine(const FlashingPlan* fp,
+                                            const std::vector<std::string>& command);
+void AddResizeTasks(const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks);
+std::vector<std::unique_ptr<Task>> ParseFastbootInfo(const FlashingPlan* fp,
+                                                     const std::vector<std::string>& file);
 
 struct NetworkSerial {
     Socket::Protocol protocol;
@@ -103,7 +154,7 @@
 
 Result<NetworkSerial, FastbootError> ParseNetworkSerial(const std::string& serial);
 bool supports_AB();
-std::string GetPartitionName(const ImageEntry& entry, std::string& current_slot_);
+std::string GetPartitionName(const ImageEntry& entry, const std::string& current_slot_);
 void flash_partition_files(const std::string& partition, const std::vector<SparsePtr>& files);
 int64_t get_sparse_limit(int64_t size);
 std::vector<SparsePtr> resparse_file(sparse_file* s, int64_t max_size);
diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h
index f60c9f1..3d6c7b0 100644
--- a/fastboot/fastboot_driver.h
+++ b/fastboot/fastboot_driver.h
@@ -33,7 +33,6 @@
 #include <vector>
 
 #include <android-base/endian.h>
-#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
 #include <bootimg.h>
@@ -41,21 +40,13 @@
 #include <sparse/sparse.h>
 
 #include "constants.h"
+#include "fastboot_driver_interface.h"
 #include "transport.h"
 
 class Transport;
 
 namespace fastboot {
 
-enum RetCode : int {
-    SUCCESS = 0,
-    BAD_ARG,
-    IO_ERROR,
-    BAD_DEV_RESP,
-    DEVICE_FAIL,
-    TIMEOUT,
-};
-
 struct DriverCallbacks {
     std::function<void(const std::string&)> prolog = [](const std::string&) {};
     std::function<void(int)> epilog = [](int) {};
@@ -63,7 +54,7 @@
     std::function<void(const std::string&)> text = [](const std::string&) {};
 };
 
-class FastBootDriver {
+class FastBootDriver : public IFastBootDriver {
     friend class FastBootTest;
 
   public:
@@ -78,9 +69,10 @@
     RetCode Boot(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
     RetCode Continue(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
     RetCode CreatePartition(const std::string& partition, const std::string& size);
-    RetCode DeletePartition(const std::string& partition);
+    RetCode DeletePartition(const std::string& partition) override;
     RetCode Download(const std::string& name, android::base::borrowed_fd fd, size_t size,
-                     std::string* response = nullptr, std::vector<std::string>* info = nullptr);
+                     std::string* response = nullptr,
+                     std::vector<std::string>* info = nullptr) override;
     RetCode Download(android::base::borrowed_fd fd, size_t size, std::string* response = nullptr,
                      std::vector<std::string>* info = nullptr);
     RetCode Download(const std::string& name, const std::vector<char>& buf,
@@ -93,16 +85,17 @@
     RetCode Download(sparse_file* s, bool use_crc = false, std::string* response = nullptr,
                      std::vector<std::string>* info = nullptr);
     RetCode Erase(const std::string& partition, std::string* response = nullptr,
-                  std::vector<std::string>* info = nullptr);
+                  std::vector<std::string>* info = nullptr) override;
     RetCode Flash(const std::string& partition, std::string* response = nullptr,
                   std::vector<std::string>* info = nullptr);
     RetCode GetVar(const std::string& key, std::string* val,
-                   std::vector<std::string>* info = nullptr);
+                   std::vector<std::string>* info = nullptr) override;
     RetCode GetVarAll(std::vector<std::string>* response);
-    RetCode Reboot(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
+    RetCode Reboot(std::string* response = nullptr,
+                   std::vector<std::string>* info = nullptr) override;
     RetCode RebootTo(std::string target, std::string* response = nullptr,
-                     std::vector<std::string>* info = nullptr);
-    RetCode ResizePartition(const std::string& partition, const std::string& size);
+                     std::vector<std::string>* info = nullptr) override;
+    RetCode ResizePartition(const std::string& partition, const std::string& size) override;
     RetCode SetActive(const std::string& slot, std::string* response = nullptr,
                       std::vector<std::string>* info = nullptr);
     RetCode Upload(const std::string& outfile, std::string* response = nullptr,
@@ -116,7 +109,7 @@
     /* HIGHER LEVEL COMMANDS -- Composed of the commands above */
     RetCode FlashPartition(const std::string& partition, const std::vector<char>& data);
     RetCode FlashPartition(const std::string& partition, android::base::borrowed_fd fd,
-                           uint32_t sz);
+                           uint32_t sz) override;
     RetCode FlashPartition(const std::string& partition, sparse_file* s, uint32_t sz,
                            size_t current, size_t total);
 
@@ -128,7 +121,7 @@
     void SetInfoCallback(std::function<void(const std::string&)> info);
     static const std::string RCString(RetCode rc);
     std::string Error();
-    RetCode WaitForDisconnect();
+    RetCode WaitForDisconnect() override;
 
     // Note: set_transport will return the previous transport.
     Transport* set_transport(Transport* transport);
diff --git a/fastboot/fastboot_driver_interface.h b/fastboot/fastboot_driver_interface.h
new file mode 100644
index 0000000..795938f
--- /dev/null
+++ b/fastboot/fastboot_driver_interface.h
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#pragma once
+
+#include <string>
+
+#include "android-base/unique_fd.h"
+
+class Transport;
+
+namespace fastboot {
+
+enum RetCode : int {
+    SUCCESS = 0,
+    BAD_ARG,
+    IO_ERROR,
+    BAD_DEV_RESP,
+    DEVICE_FAIL,
+    TIMEOUT,
+};
+
+class IFastBootDriver {
+  public:
+    RetCode virtual FlashPartition(const std::string& partition, android::base::borrowed_fd fd,
+                                   uint32_t sz) = 0;
+    RetCode virtual DeletePartition(const std::string& partition) = 0;
+    RetCode virtual WaitForDisconnect() = 0;
+    RetCode virtual Reboot(std::string* response = nullptr,
+                           std::vector<std::string>* info = nullptr) = 0;
+
+    RetCode virtual RebootTo(std::string target, std::string* response = nullptr,
+                             std::vector<std::string>* info = nullptr) = 0;
+    RetCode virtual GetVar(const std::string& key, std::string* val,
+                           std::vector<std::string>* info = nullptr) = 0;
+    RetCode virtual Download(const std::string& name, android::base::borrowed_fd fd, size_t size,
+                             std::string* response = nullptr,
+                             std::vector<std::string>* info = nullptr) = 0;
+    RetCode virtual RawCommand(const std::string& cmd, const std::string& message,
+                               std::string* response = nullptr,
+                               std::vector<std::string>* info = nullptr, int* dsize = nullptr) = 0;
+    RetCode virtual ResizePartition(const std::string& partition, const std::string& size) = 0;
+    RetCode virtual Erase(const std::string& partition, std::string* response = nullptr,
+                          std::vector<std::string>* info = nullptr) = 0;
+    virtual ~IFastBootDriver() = default;
+};
+}  // namespace fastboot
\ No newline at end of file
diff --git a/fastboot/fastboot_driver_mock.h b/fastboot/fastboot_driver_mock.h
new file mode 100644
index 0000000..62a5708
--- /dev/null
+++ b/fastboot/fastboot_driver_mock.h
@@ -0,0 +1,51 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#pragma once
+
+#include <gmock/gmock.h>
+#include "fastboot_driver_interface.h"
+
+namespace fastboot {
+
+class MockFastbootDriver : public IFastBootDriver {
+  public:
+    MOCK_METHOD(RetCode, FlashPartition,
+                (const std::string& partition, android::base::borrowed_fd fd, uint32_t sz),
+                (override));
+    MOCK_METHOD(RetCode, DeletePartition, (const std::string&), (override));
+    MOCK_METHOD(RetCode, Reboot, (std::string*, std::vector<std::string>*), (override));
+    MOCK_METHOD(RetCode, RebootTo, (std::string, std::string*, std::vector<std::string>*),
+                (override));
+
+    MOCK_METHOD(RetCode, GetVar, (const std::string&, std::string*, std::vector<std::string>*),
+                (override));
+
+    MOCK_METHOD(RetCode, Download,
+                (const std::string&, android::base::borrowed_fd, size_t, std::string*,
+                 std::vector<std::string>*),
+                (override));
+
+    MOCK_METHOD(RetCode, RawCommand,
+                (const std::string&, const std::string&, std::string*, std::vector<std::string>*,
+                 int*),
+                (override));
+    MOCK_METHOD(RetCode, ResizePartition, (const std::string&, const std::string&), (override));
+    MOCK_METHOD(RetCode, Erase, (const std::string&, std::string*, std::vector<std::string>*),
+                (override));
+    MOCK_METHOD(RetCode, WaitForDisconnect, (), (override));
+};
+
+}  // namespace fastboot
\ No newline at end of file
diff --git a/fastboot/task.cpp b/fastboot/task.cpp
index 9d4cb75..054c1ed 100644
--- a/fastboot/task.cpp
+++ b/fastboot/task.cpp
@@ -14,12 +14,16 @@
 // limitations under the License.
 //
 #include "task.h"
+
 #include <iostream>
+
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+
 #include "fastboot.h"
 #include "filesystem.h"
 #include "super_flash_helper.h"
-
-#include <android-base/parseint.h>
+#include "util.h"
 
 using namespace std::string_literals;
 FlashTask::FlashTask(const std::string& _slot, const std::string& _pname, const std::string& _fname,
@@ -42,8 +46,22 @@
     do_for_partitions(pname_, slot_, flash, true);
 }
 
-RebootTask::RebootTask(FlashingPlan* fp) : fp_(fp){};
-RebootTask::RebootTask(FlashingPlan* fp, const std::string& reboot_target)
+std::string FlashTask::GetPartitionAndSlot() {
+    auto slot = slot_;
+    if (slot.empty()) {
+        slot = get_current_slot();
+    }
+    if (slot.empty()) {
+        return pname_;
+    }
+    if (slot == "all") {
+        LOG(FATAL) << "Cannot retrieve a singular name when using all slots";
+    }
+    return pname_ + "_" + slot;
+}
+
+RebootTask::RebootTask(const FlashingPlan* fp) : fp_(fp){};
+RebootTask::RebootTask(const FlashingPlan* fp, const std::string& reboot_target)
     : reboot_target_(reboot_target), fp_(fp){};
 
 void RebootTask::Run() {
@@ -90,12 +108,12 @@
 }
 
 std::unique_ptr<FlashSuperLayoutTask> FlashSuperLayoutTask::Initialize(
-        FlashingPlan* fp, std::vector<ImageEntry>& os_images) {
+        const FlashingPlan* fp, std::vector<ImageEntry>& os_images) {
     if (!supports_AB()) {
         LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
         return nullptr;
     }
-    if (fp->slot == "all") {
+    if (fp->slot_override == "all") {
         LOG(VERBOSE) << "Cannot optimize flashing super for all slots";
         return nullptr;
     }
@@ -153,7 +171,78 @@
                                                   partition_size);
 }
 
-UpdateSuperTask::UpdateSuperTask(FlashingPlan* fp) : fp_(fp) {}
+std::unique_ptr<FlashSuperLayoutTask> FlashSuperLayoutTask::InitializeFromTasks(
+        const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks) {
+    if (!supports_AB()) {
+        LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
+        return nullptr;
+    }
+    if (fp->slot_override == "all") {
+        LOG(VERBOSE) << "Cannot optimize flashing super for all slots";
+        return nullptr;
+    }
+
+    // Does this device use dynamic partitions at all?
+    unique_fd fd = fp->source->OpenFile("super_empty.img");
+
+    if (fd < 0) {
+        LOG(VERBOSE) << "could not open super_empty.img";
+        return nullptr;
+    }
+
+    std::string super_name;
+    // Try to find whether there is a super partition.
+    if (fp->fb->GetVar("super-partition-name", &super_name) != fastboot::SUCCESS) {
+        super_name = "super";
+    }
+    uint64_t partition_size;
+    std::string partition_size_str;
+    if (fp->fb->GetVar("partition-size:" + super_name, &partition_size_str) != fastboot::SUCCESS) {
+        LOG(VERBOSE) << "Cannot optimize super flashing: could not determine super partition";
+        return nullptr;
+    }
+    partition_size_str = fb_fix_numeric_var(partition_size_str);
+    if (!android::base::ParseUint(partition_size_str, &partition_size)) {
+        LOG(VERBOSE) << "Could not parse " << super_name << " size: " << partition_size_str;
+        return nullptr;
+    }
+
+    std::unique_ptr<SuperFlashHelper> helper = std::make_unique<SuperFlashHelper>(*fp->source);
+    if (!helper->Open(fd)) {
+        return nullptr;
+    }
+
+    for (const auto& task : tasks) {
+        if (auto flash_task = task->AsFlashTask()) {
+            if (should_flash_in_userspace(flash_task->GetPartitionAndSlot())) {
+                auto partition = flash_task->GetPartitionAndSlot();
+                if (!helper->AddPartition(partition, flash_task->GetImageName(), false)) {
+                    return nullptr;
+                }
+            }
+        }
+    }
+
+    auto s = helper->GetSparseLayout();
+    if (!s) return nullptr;
+    // Remove images that we already flashed, just in case we have non-dynamic OS images.
+    auto remove_if_callback = [&](const auto& task) -> bool {
+        if (auto flash_task = task->AsFlashTask()) {
+            return helper->WillFlash(flash_task->GetPartitionAndSlot());
+        } else if (auto update_super_task = task->AsUpdateSuperTask()) {
+            return true;
+        } else if (auto reboot_task = task->AsRebootTask()) {
+            return true;
+        }
+        return false;
+    };
+    tasks.erase(std::remove_if(tasks.begin(), tasks.end(), remove_if_callback), tasks.end());
+
+    return std::make_unique<FlashSuperLayoutTask>(super_name, std::move(helper), std::move(s),
+                                                  partition_size);
+}
+
+UpdateSuperTask::UpdateSuperTask(const FlashingPlan* fp) : fp_(fp) {}
 
 void UpdateSuperTask::Run() {
     unique_fd fd = fp_->source->OpenFile("super_empty.img");
@@ -177,7 +266,7 @@
     fp_->fb->RawCommand(command, "Updating super partition");
 }
 
-ResizeTask::ResizeTask(FlashingPlan* fp, const std::string& pname, const std::string& size,
+ResizeTask::ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size,
                        const std::string& slot)
     : fp_(fp), pname_(pname), size_(size), slot_(slot) {}
 
@@ -190,13 +279,13 @@
     do_for_partitions(pname_, slot_, resize_partition, false);
 }
 
-DeleteTask::DeleteTask(FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
+DeleteTask::DeleteTask(const FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
 
 void DeleteTask::Run() {
     fp_->fb->DeletePartition(pname_);
 }
 
-WipeTask::WipeTask(FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
+WipeTask::WipeTask(const FlashingPlan* fp, const std::string& pname) : fp_(fp), pname_(pname){};
 
 void WipeTask::Run() {
     std::string partition_type;
diff --git a/fastboot/task.h b/fastboot/task.h
index e89f85b..34e3e92 100644
--- a/fastboot/task.h
+++ b/fastboot/task.h
@@ -18,15 +18,28 @@
 #include <sstream>
 #include <string>
 
-#include "fastboot.h"
 #include "fastboot_driver.h"
 #include "super_flash_helper.h"
 #include "util.h"
 
+struct FlashingPlan;
+struct Image;
+using ImageEntry = std::pair<const Image*, std::string>;
+
+class FlashTask;
+class RebootTask;
+class UpdateSuperTask;
+class WipeTask;
+
 class Task {
   public:
     Task() = default;
     virtual void Run() = 0;
+    virtual FlashTask* AsFlashTask() { return nullptr; }
+    virtual RebootTask* AsRebootTask() { return nullptr; }
+    virtual UpdateSuperTask* AsUpdateSuperTask() { return nullptr; }
+    virtual WipeTask* AsWipeTask() { return nullptr; }
+
     virtual ~Task() = default;
 };
 
@@ -34,7 +47,12 @@
   public:
     FlashTask(const std::string& slot, const std::string& pname, const std::string& fname,
               const bool apply_vbmeta);
+    virtual FlashTask* AsFlashTask() override { return this; }
 
+    std::string GetPartition() { return pname_; }
+    std::string GetImageName() { return fname_; }
+    std::string GetPartitionAndSlot();
+    std::string GetSlot() { return slot_; }
     void Run() override;
 
   private:
@@ -46,8 +64,9 @@
 
 class RebootTask : public Task {
   public:
-    RebootTask(FlashingPlan* fp);
-    RebootTask(FlashingPlan* fp, const std::string& reboot_target);
+    RebootTask(const FlashingPlan* fp);
+    RebootTask(const FlashingPlan* fp, const std::string& reboot_target);
+    virtual RebootTask* AsRebootTask() override { return this; }
     void Run() override;
 
   private:
@@ -59,8 +78,10 @@
   public:
     FlashSuperLayoutTask(const std::string& super_name, std::unique_ptr<SuperFlashHelper> helper,
                          SparsePtr sparse_layout, uint64_t super_size);
-    static std::unique_ptr<FlashSuperLayoutTask> Initialize(FlashingPlan* fp,
+    static std::unique_ptr<FlashSuperLayoutTask> Initialize(const FlashingPlan* fp,
                                                             std::vector<ImageEntry>& os_images);
+    static std::unique_ptr<FlashSuperLayoutTask> InitializeFromTasks(
+            const FlashingPlan* fp, std::vector<std::unique_ptr<Task>>& tasks);
     using ImageEntry = std::pair<const Image*, std::string>;
     void Run() override;
 
@@ -73,7 +94,9 @@
 
 class UpdateSuperTask : public Task {
   public:
-    UpdateSuperTask(FlashingPlan* fp);
+    UpdateSuperTask(const FlashingPlan* fp);
+    virtual UpdateSuperTask* AsUpdateSuperTask() override { return this; }
+
     void Run() override;
 
   private:
@@ -82,7 +105,7 @@
 
 class ResizeTask : public Task {
   public:
-    ResizeTask(FlashingPlan* fp, const std::string& pname, const std::string& size,
+    ResizeTask(const FlashingPlan* fp, const std::string& pname, const std::string& size,
                const std::string& slot);
     void Run() override;
 
@@ -95,7 +118,7 @@
 
 class DeleteTask : public Task {
   public:
-    DeleteTask(FlashingPlan* _fp, const std::string& _pname);
+    DeleteTask(const FlashingPlan* _fp, const std::string& _pname);
     void Run() override;
 
   private:
@@ -105,7 +128,9 @@
 
 class WipeTask : public Task {
   public:
-    WipeTask(FlashingPlan* fp, const std::string& pname);
+    WipeTask(const FlashingPlan* fp, const std::string& pname);
+    virtual WipeTask* AsWipeTask() override { return this; }
+
     void Run() override;
 
   private:
diff --git a/fastboot/task_test.cpp b/fastboot/task_test.cpp
new file mode 100644
index 0000000..145b4d7
--- /dev/null
+++ b/fastboot/task_test.cpp
@@ -0,0 +1,123 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "task.h"
+#include "fastboot.h"
+
+#include <gtest/gtest.h>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <unordered_map>
+#include "android-base/strings.h"
+using android::base::Split;
+
+class ParseTest : public ::testing ::Test {
+  protected:
+    void SetUp() override {
+        fp = std::make_unique<FlashingPlan>();
+        fp->slot_override = "b";
+        fp->secondary_slot = "a";
+        fp->wants_wipe = false;
+    }
+    void TearDown() override {}
+
+    std::unique_ptr<FlashingPlan> fp;
+
+  private:
+};
+
+static std::vector<std::unique_ptr<Task>> collectTasks(FlashingPlan* fp,
+                                                       const std::vector<std::string>& commands) {
+    std::vector<std::vector<std::string>> vec_commands;
+    for (auto& command : commands) {
+        vec_commands.emplace_back(android::base::Split(command, " "));
+    }
+    std::vector<std::unique_ptr<Task>> tasks;
+    for (auto& command : vec_commands) {
+        tasks.emplace_back(ParseFastbootInfoLine(fp, command));
+    }
+    return tasks;
+}
+
+std::unique_ptr<Task> ParseCommand(FlashingPlan* fp, std::string command) {
+    std::vector<std::string> vec_command = android::base::Split(command, " ");
+    return ParseFastbootInfoLine(fp, vec_command);
+}
+
+TEST_F(ParseTest, CORRECT_FlASH_TASK_FORMED) {
+    std::vector<std::string> commands = {"flash dtbo", "flash --slot-other system system_other.img",
+                                         "flash system", "flash --apply-vbmeta vbmeta"};
+
+    std::vector<std::unique_ptr<Task>> tasks = collectTasks(fp.get(), commands);
+
+    std::vector<std::vector<std::string>> expected_values{
+            {"dtbo", "dtbo_b", "b", "dtbo.img"},
+            {"system", "system_a", "a", "system_other.img"},
+            {"system", "system_b", "b", "system.img"},
+            {"vbmeta", "vbmeta_b", "b", "vbmeta.img"}
+
+    };
+
+    for (auto& task : tasks) {
+        ASSERT_TRUE(task != nullptr);
+    }
+
+    for (size_t i = 0; i < tasks.size(); i++) {
+        auto task = tasks[i]->AsFlashTask();
+        ASSERT_TRUE(task != nullptr);
+        ASSERT_EQ(task->GetPartition(), expected_values[i][0]);
+        ASSERT_EQ(task->GetPartitionAndSlot(), expected_values[i][1]);
+        ASSERT_EQ(task->GetSlot(), expected_values[i][2]);
+        ASSERT_EQ(task->GetImageName(), expected_values[i][3]);
+    }
+}
+
+TEST_F(ParseTest, VERSION_CHECK_CORRRECT) {
+    std::vector<std::string> correct_versions = {
+            "version 1.0",
+            "version 22.00",
+    };
+
+    std::vector<std::string> bad_versions = {"version",        "version .01", "version x1",
+                                             "version 1.0.1",  "version 1.",  "s 1.0",
+                                             "version 1.0 2.0"};
+
+    for (auto& version : correct_versions) {
+        ASSERT_TRUE(CheckFastbootInfoRequirements(android::base::Split(version, " "))) << version;
+    }
+    for (auto& version : bad_versions) {
+        ASSERT_FALSE(CheckFastbootInfoRequirements(android::base::Split(version, " "))) << version;
+    }
+}
+
+TEST_F(ParseTest, BAD_FASTBOOT_INFO_INPUT) {
+    ASSERT_EQ(ParseCommand(fp.get(), "flash"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "flash --slot-other --apply-vbmeta"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "flash --apply-vbmeta"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "if-wipe"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "if-wipe flash"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "wipe dtbo"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "update-super dtbo"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "flash system system.img system"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "reboot bootloader fastboot"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(),
+                           "flash --slot-other --apply-vbmeta system system_other.img system"),
+              nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "erase"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "erase dtbo dtbo"), nullptr);
+    ASSERT_EQ(ParseCommand(fp.get(), "wipe this"), nullptr);
+}
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 3bfa4d5..3dd1f1a 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -252,13 +252,6 @@
     header_libs: [
         "libstorage_literals_headers",
     ],
-    test_suites: [
-        "vts",
-        "device-tests"
-    ],
-    test_options: {
-        min_shipping_api_level: 29,
-    },
     auto_gen_config: true,
     require_root: true,
     compile_multilib: "first",
@@ -267,6 +260,13 @@
 cc_test {
     name: "vts_libsnapshot_test",
     defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
+    test_suites: [
+        "vts",
+        "device-tests"
+    ],
+    test_options: {
+        min_shipping_api_level: 30,
+    },
 }
 
 cc_test {
@@ -275,6 +275,13 @@
     cppflags: [
         "-DLIBSNAPSHOT_TEST_VAB_LEGACY",
     ],
+    test_suites: [
+        "device-tests"
+    ],
+    test_options: {
+        // Legacy VAB launched in Android R.
+        min_shipping_api_level: 30,
+    },
 }
 
 cc_test {
@@ -283,6 +290,13 @@
     cppflags: [
         "-DLIBSNAPSHOT_TEST_VABC_LEGACY",
     ],
+    test_suites: [
+        "device-tests"
+    ],
+    test_options: {
+        // Legacy VABC launched in Android S.
+        min_shipping_api_level: 31,
+    },
 }
 
 cc_test {
@@ -349,24 +363,6 @@
 }
 
 cc_test {
-    name: "snapshot_power_test",
-    srcs: [
-        "power_test.cpp",
-    ],
-    static_libs: [
-        "libc++fs",
-        "libsnapshot",
-        "update_metadata-protos",
-    ],
-    shared_libs: [
-        "libbase",
-        "libfs_mgr_binder",
-        "liblog",
-    ],
-    gtest: false,
-}
-
-cc_test {
     name: "cow_api_test",
     defaults: [
         "fs_mgr_defaults",
@@ -403,78 +399,6 @@
 }
 
 cc_binary {
-    name: "make_cow_from_ab_ota",
-    host_supported: true,
-    device_supported: false,
-    cflags: [
-        "-D_FILE_OFFSET_BITS=64",
-        "-Wall",
-        "-Werror",
-    ],
-    static_libs: [
-        "libbase",
-        "libbspatch",
-        "libbrotli",
-        "libbz",
-        "libchrome",
-        "libcrypto",
-        "libgflags",
-        "liblog",
-        "libprotobuf-cpp-lite",
-        "libpuffpatch",
-        "libsnapshot_cow",
-        "libsparse",
-        "libxz",
-        "libz",
-        "liblz4",
-        "libziparchive",
-        "update_metadata-protos",
-    ],
-    srcs: [
-        "make_cow_from_ab_ota.cpp",
-    ],
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-}
-
-cc_binary {
-    name: "estimate_cow_from_nonab_ota",
-    defaults: [
-        "libsnapshot_cow_defaults",
-    ],
-    host_supported: true,
-    device_supported: false,
-    cflags: [
-        "-D_FILE_OFFSET_BITS=64",
-        "-Wall",
-        "-Werror",
-    ],
-    static_libs: [
-        "libbase",
-        "libbrotli",
-        "libbz",
-        "libcrypto",
-        "libgflags",
-        "liblog",
-        "libsnapshot_cow",
-        "libsparse",
-        "libz",
-        "libziparchive",
-    ],
-    srcs: [
-        "estimate_cow_from_nonab_ota.cpp",
-    ],
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-}
-
-cc_binary {
     name: "inspect_cow",
     host_supported: true,
     device_supported: true,
diff --git a/fs_mgr/libsnapshot/PowerTest.md b/fs_mgr/libsnapshot/PowerTest.md
deleted file mode 100644
index 0b0cb5d..0000000
--- a/fs_mgr/libsnapshot/PowerTest.md
+++ /dev/null
@@ -1,40 +0,0 @@
-snapshot\_power\_test
----------------------
-
-snapshot\_power\_test is a standalone test to simulate power failures during a snapshot-merge operation.
-
-### Test Setup
-
-Start by creating two large files that will be used as the pre-merge and post-merge state. You can take two different partition images (for example, a product.img from two separate builds), or just create random data:
-
-	dd if=/dev/urandom of=pre-merge count=1024 bs=1048576
-	dd if=/dev/urandom of=post-merge count=1024 bs=1048576
-
-Next, push these files to an unencrypted directory on the device:
-
-	adb push pre-merge /data/local/unencrypted
-	adb push post-merge /data/local/unencrypted
-
-Next, run the test setup:
-
-	adb sync data
-	adb shell /data/nativetest64/snapshot_power_test/snapshot_power_test \
-		/data/local/unencrypted/pre-merge \
-		/data/local/unencrypted/post-merge
-
-This will create the necessary fiemap-based images.
-
-### Running
-The actual test can be run via `run_power_test.sh`. Its syntax is:
-
-	run_power_test.sh <POST_MERGE_FILE>
-
-`POST_MERGE_FILE` should be the path on the device of the image to validate the merge against. Example:
-
-	run_power_test.sh /data/local/unencrypted/post-merge
-
-The device will begin the merge with a 5% chance of injecting a kernel crash every 10ms. The device should be capable of rebooting normally without user intervention. Once the merge has completed, the test will run a final check command to validate the contents of the snapshot against the post-merge file. It will error if there are any incorrect blocks.
-
-Two environment variables can be passed to `run_power_test.sh`:
-1. `FAIL_RATE` - A fraction between 0 and 100 (inclusive) indicating the probability the device should inject a kernel crash every 10ms.
-2. `DEVICE_SERIAL` - If multiple devices are attached to adb, this argument is passed as the serial to select (to `adb -s`).
diff --git a/fs_mgr/libsnapshot/estimate_cow_from_nonab_ota.cpp b/fs_mgr/libsnapshot/estimate_cow_from_nonab_ota.cpp
deleted file mode 100644
index 45833e1..0000000
--- a/fs_mgr/libsnapshot/estimate_cow_from_nonab_ota.cpp
+++ /dev/null
@@ -1,432 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-#include <stdio.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <iostream>
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <gflags/gflags.h>
-#include <libsnapshot/cow_writer.h>
-#include <openssl/sha.h>
-#include <sparse/sparse.h>
-#include <ziparchive/zip_archive.h>
-
-DEFINE_string(source_tf, "", "Source target files (dir or zip file)");
-DEFINE_string(ota_tf, "", "Target files of the build for an OTA");
-DEFINE_string(compression, "gz", "Compression (options: none, gz, brotli)");
-
-namespace android {
-namespace snapshot {
-
-using android::base::borrowed_fd;
-using android::base::unique_fd;
-
-static constexpr size_t kBlockSize = 4096;
-
-void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
-              unsigned int, const char* message) {
-    if (severity == android::base::ERROR) {
-        fprintf(stderr, "%s\n", message);
-    } else {
-        fprintf(stdout, "%s\n", message);
-    }
-}
-
-class TargetFilesPackage final {
-  public:
-    explicit TargetFilesPackage(const std::string& path);
-
-    bool Open();
-    bool HasFile(const std::string& path);
-    std::unordered_set<std::string> GetDynamicPartitionNames();
-    unique_fd OpenFile(const std::string& path);
-    unique_fd OpenImage(const std::string& path);
-
-  private:
-    std::string path_;
-    unique_fd fd_;
-    std::unique_ptr<ZipArchive, decltype(&CloseArchive)> zip_;
-};
-
-TargetFilesPackage::TargetFilesPackage(const std::string& path)
-    : path_(path), zip_(nullptr, &CloseArchive) {}
-
-bool TargetFilesPackage::Open() {
-    fd_.reset(open(path_.c_str(), O_RDONLY));
-    if (fd_ < 0) {
-        PLOG(ERROR) << "open failed: " << path_;
-        return false;
-    }
-
-    struct stat s;
-    if (fstat(fd_.get(), &s) < 0) {
-        PLOG(ERROR) << "fstat failed: " << path_;
-        return false;
-    }
-    if (S_ISDIR(s.st_mode)) {
-        return true;
-    }
-
-    // Otherwise, assume it's a zip file.
-    ZipArchiveHandle handle;
-    if (OpenArchiveFd(fd_.get(), path_.c_str(), &handle, false)) {
-        LOG(ERROR) << "Could not open " << path_ << " as a zip archive.";
-        return false;
-    }
-    zip_.reset(handle);
-    return true;
-}
-
-bool TargetFilesPackage::HasFile(const std::string& path) {
-    if (zip_) {
-        ZipEntry64 entry;
-        return !FindEntry(zip_.get(), path, &entry);
-    }
-
-    auto full_path = path_ + "/" + path;
-    return access(full_path.c_str(), F_OK) == 0;
-}
-
-unique_fd TargetFilesPackage::OpenFile(const std::string& path) {
-    if (!zip_) {
-        auto full_path = path_ + "/" + path;
-        unique_fd fd(open(full_path.c_str(), O_RDONLY));
-        if (fd < 0) {
-            PLOG(ERROR) << "open failed: " << full_path;
-            return {};
-        }
-        return fd;
-    }
-
-    ZipEntry64 entry;
-    if (FindEntry(zip_.get(), path, &entry)) {
-        LOG(ERROR) << path << " not found in archive: " << path_;
-        return {};
-    }
-
-    TemporaryFile temp;
-    if (temp.fd < 0) {
-        PLOG(ERROR) << "mkstemp failed";
-        return {};
-    }
-
-    LOG(INFO) << "Extracting " << path << " from " << path_ << " ...";
-    if (ExtractEntryToFile(zip_.get(), &entry, temp.fd)) {
-        LOG(ERROR) << "could not extract " << path << " from " << path_;
-        return {};
-    }
-    if (lseek(temp.fd, 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek failed";
-        return {};
-    }
-    return unique_fd{temp.release()};
-}
-
-unique_fd TargetFilesPackage::OpenImage(const std::string& path) {
-    auto fd = OpenFile(path);
-    if (fd < 0) {
-        return {};
-    }
-
-    LOG(INFO) << "Unsparsing " << path << " ...";
-    std::unique_ptr<struct sparse_file, decltype(&sparse_file_destroy)> s(
-            sparse_file_import(fd.get(), false, false), &sparse_file_destroy);
-    if (!s) {
-        return fd;
-    }
-
-    TemporaryFile temp;
-    if (temp.fd < 0) {
-        PLOG(ERROR) << "mkstemp failed";
-        return {};
-    }
-    if (sparse_file_write(s.get(), temp.fd, false, false, false) < 0) {
-        LOG(ERROR) << "sparse_file_write failed";
-        return {};
-    }
-    if (lseek(temp.fd, 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek failed";
-        return {};
-    }
-
-    fd.reset(temp.release());
-    return fd;
-}
-
-std::unordered_set<std::string> TargetFilesPackage::GetDynamicPartitionNames() {
-    auto fd = OpenFile("META/misc_info.txt");
-    if (fd < 0) {
-        return {};
-    }
-
-    std::string contents;
-    if (!android::base::ReadFdToString(fd, &contents)) {
-        PLOG(ERROR) << "read failed";
-        return {};
-    }
-
-    std::unordered_set<std::string> set;
-
-    auto lines = android::base::Split(contents, "\n");
-    for (const auto& line : lines) {
-        auto parts = android::base::Split(line, "=");
-        if (parts.size() == 2 && parts[0] == "dynamic_partition_list") {
-            auto partitions = android::base::Split(parts[1], " ");
-            for (const auto& name : partitions) {
-                if (!name.empty()) {
-                    set.emplace(name);
-                }
-            }
-            break;
-        }
-    }
-    return set;
-}
-
-class NonAbEstimator final {
-  public:
-    NonAbEstimator(const std::string& ota_tf_path, const std::string& source_tf_path)
-        : ota_tf_path_(ota_tf_path), source_tf_path_(source_tf_path) {}
-
-    bool Run();
-
-  private:
-    bool OpenPackages();
-    bool AnalyzePartition(const std::string& partition_name);
-    std::unordered_map<std::string, uint64_t> GetBlockMap(borrowed_fd fd);
-
-    std::string ota_tf_path_;
-    std::string source_tf_path_;
-    std::unique_ptr<TargetFilesPackage> ota_tf_;
-    std::unique_ptr<TargetFilesPackage> source_tf_;
-    uint64_t size_ = 0;
-};
-
-bool NonAbEstimator::Run() {
-    if (!OpenPackages()) {
-        return false;
-    }
-
-    auto partitions = ota_tf_->GetDynamicPartitionNames();
-    if (partitions.empty()) {
-        LOG(ERROR) << "No dynamic partitions found in META/misc_info.txt";
-        return false;
-    }
-    for (const auto& partition : partitions) {
-        if (!AnalyzePartition(partition)) {
-            return false;
-        }
-    }
-
-    int64_t size_in_mb = int64_t(double(size_) / 1024.0 / 1024.0);
-
-    std::cout << "Estimated COW size: " << size_ << " (" << size_in_mb << "MiB)\n";
-    return true;
-}
-
-bool NonAbEstimator::OpenPackages() {
-    ota_tf_ = std::make_unique<TargetFilesPackage>(ota_tf_path_);
-    if (!ota_tf_->Open()) {
-        return false;
-    }
-    if (!source_tf_path_.empty()) {
-        source_tf_ = std::make_unique<TargetFilesPackage>(source_tf_path_);
-        if (!source_tf_->Open()) {
-            return false;
-        }
-    }
-    return true;
-}
-
-static std::string SHA256(const std::string& input) {
-    std::string hash(32, '\0');
-    SHA256_CTX c;
-    SHA256_Init(&c);
-    SHA256_Update(&c, input.data(), input.size());
-    SHA256_Final(reinterpret_cast<unsigned char*>(hash.data()), &c);
-    return hash;
-}
-
-bool NonAbEstimator::AnalyzePartition(const std::string& partition_name) {
-    auto path = "IMAGES/" + partition_name + ".img";
-    auto fd = ota_tf_->OpenImage(path);
-    if (fd < 0) {
-        return false;
-    }
-
-    unique_fd source_fd;
-    uint64_t source_size = 0;
-    std::unordered_map<std::string, uint64_t> source_blocks;
-    if (source_tf_) {
-        auto dap = source_tf_->GetDynamicPartitionNames();
-
-        source_fd = source_tf_->OpenImage(path);
-        if (source_fd >= 0) {
-            struct stat s;
-            if (fstat(source_fd.get(), &s)) {
-                PLOG(ERROR) << "fstat failed";
-                return false;
-            }
-            source_size = s.st_size;
-
-            std::cout << "Hashing blocks for " << partition_name << "...\n";
-            source_blocks = GetBlockMap(source_fd);
-            if (source_blocks.empty()) {
-                LOG(ERROR) << "Could not build a block map for source partition: "
-                           << partition_name;
-                return false;
-            }
-        } else {
-            if (dap.count(partition_name)) {
-                return false;
-            }
-            LOG(ERROR) << "Warning: " << partition_name
-                       << " has no incremental diff since it's not in the source image.";
-        }
-    }
-
-    TemporaryFile cow;
-    if (cow.fd < 0) {
-        PLOG(ERROR) << "mkstemp failed";
-        return false;
-    }
-
-    CowOptions options;
-    options.block_size = kBlockSize;
-    options.compression = FLAGS_compression;
-
-    auto writer = std::make_unique<CowWriter>(options);
-    if (!writer->Initialize(borrowed_fd{cow.fd})) {
-        LOG(ERROR) << "Could not initialize COW writer";
-        return false;
-    }
-
-    LOG(INFO) << "Analyzing " << partition_name << " ...";
-
-    std::string zeroes(kBlockSize, '\0');
-    std::string chunk(kBlockSize, '\0');
-    std::string src_chunk(kBlockSize, '\0');
-    uint64_t next_block_number = 0;
-    while (true) {
-        if (!android::base::ReadFully(fd, chunk.data(), chunk.size())) {
-            if (errno) {
-                PLOG(ERROR) << "read failed";
-                return false;
-            }
-            break;
-        }
-
-        uint64_t block_number = next_block_number++;
-        if (chunk == zeroes) {
-            if (!writer->AddZeroBlocks(block_number, 1)) {
-                LOG(ERROR) << "Could not add zero block";
-                return false;
-            }
-            continue;
-        }
-
-        uint64_t source_offset = block_number * kBlockSize;
-        if (source_fd >= 0 && source_offset <= source_size) {
-            off64_t offset = block_number * kBlockSize;
-            if (android::base::ReadFullyAtOffset(source_fd, src_chunk.data(), src_chunk.size(),
-                                                 offset)) {
-                if (chunk == src_chunk) {
-                    continue;
-                }
-            } else if (errno) {
-                PLOG(ERROR) << "pread failed";
-                return false;
-            }
-        }
-
-        auto hash = SHA256(chunk);
-        if (auto iter = source_blocks.find(hash); iter != source_blocks.end()) {
-            if (!writer->AddCopy(block_number, iter->second)) {
-                return false;
-            }
-            continue;
-        }
-
-        if (!writer->AddRawBlocks(block_number, chunk.data(), chunk.size())) {
-            return false;
-        }
-    }
-
-    if (!writer->Finalize()) {
-        return false;
-    }
-
-    struct stat s;
-    if (fstat(cow.fd, &s) < 0) {
-        PLOG(ERROR) << "fstat failed";
-        return false;
-    }
-
-    size_ += s.st_size;
-    return true;
-}
-
-std::unordered_map<std::string, uint64_t> NonAbEstimator::GetBlockMap(borrowed_fd fd) {
-    std::string chunk(kBlockSize, '\0');
-
-    std::unordered_map<std::string, uint64_t> block_map;
-    uint64_t block_number = 0;
-    while (true) {
-        if (!android::base::ReadFully(fd, chunk.data(), chunk.size())) {
-            if (errno) {
-                PLOG(ERROR) << "read failed";
-                return {};
-            }
-            break;
-        }
-        auto hash = SHA256(chunk);
-        block_map[hash] = block_number;
-        block_number++;
-    }
-    return block_map;
-}
-
-}  // namespace snapshot
-}  // namespace android
-
-using namespace android::snapshot;
-
-int main(int argc, char** argv) {
-    android::base::InitLogging(argv, android::snapshot::MyLogger);
-    gflags::SetUsageMessage("Estimate VAB disk usage from Non A/B builds");
-    gflags::ParseCommandLineFlags(&argc, &argv, false);
-
-    if (FLAGS_ota_tf.empty()) {
-        std::cerr << "Must specify -ota_tf on the command-line." << std::endl;
-        return 1;
-    }
-
-    NonAbEstimator estimator(FLAGS_ota_tf, FLAGS_source_tf);
-    if (!estimator.Run()) {
-        return 1;
-    }
-    return 0;
-}
diff --git a/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp b/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp
deleted file mode 100644
index 6a5754d..0000000
--- a/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp
+++ /dev/null
@@ -1,692 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <iostream>
-#include <limits>
-#include <string>
-#include <unordered_set>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
-#include <bsdiff/bspatch.h>
-#include <bzlib.h>
-#include <gflags/gflags.h>
-#include <libsnapshot/cow_writer.h>
-#include <puffin/puffpatch.h>
-#include <sparse/sparse.h>
-#include <update_engine/update_metadata.pb.h>
-#include <xz.h>
-#include <ziparchive/zip_archive.h>
-
-namespace android {
-namespace snapshot {
-
-using android::base::borrowed_fd;
-using android::base::unique_fd;
-using chromeos_update_engine::DeltaArchiveManifest;
-using chromeos_update_engine::Extent;
-using chromeos_update_engine::InstallOperation;
-using chromeos_update_engine::PartitionUpdate;
-
-static constexpr uint64_t kBlockSize = 4096;
-
-DEFINE_string(source_tf, "", "Source target files (dir or zip file) for incremental payloads");
-DEFINE_string(compression, "gz", "Compression type to use (none or gz)");
-DEFINE_uint32(cluster_ops, 0, "Number of Cow Ops per cluster (0 or >1)");
-
-void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
-              unsigned int, const char* message) {
-    if (severity == android::base::ERROR) {
-        fprintf(stderr, "%s\n", message);
-    } else {
-        fprintf(stdout, "%s\n", message);
-    }
-}
-
-uint64_t ToLittleEndian(uint64_t value) {
-    union {
-        uint64_t u64;
-        char bytes[8];
-    } packed;
-    packed.u64 = value;
-    std::swap(packed.bytes[0], packed.bytes[7]);
-    std::swap(packed.bytes[1], packed.bytes[6]);
-    std::swap(packed.bytes[2], packed.bytes[5]);
-    std::swap(packed.bytes[3], packed.bytes[4]);
-    return packed.u64;
-}
-
-class PayloadConverter final {
-  public:
-    PayloadConverter(const std::string& in_file, const std::string& out_dir)
-        : in_file_(in_file), out_dir_(out_dir), source_tf_zip_(nullptr, &CloseArchive) {}
-
-    bool Run();
-
-  private:
-    bool OpenPayload();
-    bool OpenSourceTargetFiles();
-    bool ProcessPartition(const PartitionUpdate& update);
-    bool ProcessOperation(const InstallOperation& op);
-    bool ProcessZero(const InstallOperation& op);
-    bool ProcessCopy(const InstallOperation& op);
-    bool ProcessReplace(const InstallOperation& op);
-    bool ProcessDiff(const InstallOperation& op);
-    borrowed_fd OpenSourceImage();
-
-    std::string in_file_;
-    std::string out_dir_;
-    unique_fd in_fd_;
-    uint64_t payload_offset_ = 0;
-    DeltaArchiveManifest manifest_;
-    std::unordered_set<std::string> dap_;
-    unique_fd source_tf_fd_;
-    std::unique_ptr<ZipArchive, decltype(&CloseArchive)> source_tf_zip_;
-
-    // Updated during ProcessPartition().
-    std::string partition_name_;
-    std::unique_ptr<CowWriter> writer_;
-    unique_fd source_image_;
-};
-
-bool PayloadConverter::Run() {
-    if (!OpenPayload()) {
-        return false;
-    }
-
-    if (manifest_.has_dynamic_partition_metadata()) {
-        const auto& dpm = manifest_.dynamic_partition_metadata();
-        for (const auto& group : dpm.groups()) {
-            for (const auto& partition : group.partition_names()) {
-                dap_.emplace(partition);
-            }
-        }
-    }
-
-    if (dap_.empty()) {
-        LOG(ERROR) << "No dynamic partitions found.";
-        return false;
-    }
-
-    if (!OpenSourceTargetFiles()) {
-        return false;
-    }
-
-    for (const auto& update : manifest_.partitions()) {
-        if (!ProcessPartition(update)) {
-            return false;
-        }
-        writer_ = nullptr;
-        source_image_.reset();
-    }
-    return true;
-}
-
-bool PayloadConverter::OpenSourceTargetFiles() {
-    if (FLAGS_source_tf.empty()) {
-        return true;
-    }
-
-    source_tf_fd_.reset(open(FLAGS_source_tf.c_str(), O_RDONLY));
-    if (source_tf_fd_ < 0) {
-        LOG(ERROR) << "open failed: " << FLAGS_source_tf;
-        return false;
-    }
-
-    struct stat s;
-    if (fstat(source_tf_fd_.get(), &s) < 0) {
-        LOG(ERROR) << "fstat failed: " << FLAGS_source_tf;
-        return false;
-    }
-    if (S_ISDIR(s.st_mode)) {
-        return true;
-    }
-
-    // Otherwise, assume it's a zip file.
-    ZipArchiveHandle handle;
-    if (OpenArchiveFd(source_tf_fd_.get(), FLAGS_source_tf.c_str(), &handle, false)) {
-        LOG(ERROR) << "Could not open " << FLAGS_source_tf << " as a zip archive.";
-        return false;
-    }
-    source_tf_zip_.reset(handle);
-    return true;
-}
-
-bool PayloadConverter::ProcessPartition(const PartitionUpdate& update) {
-    auto partition_name = update.partition_name();
-    if (dap_.find(partition_name) == dap_.end()) {
-        // Skip non-DAP partitions.
-        return true;
-    }
-
-    auto path = out_dir_ + "/" + partition_name + ".cow";
-    unique_fd fd(open(path.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0644));
-    if (fd < 0) {
-        PLOG(ERROR) << "open failed: " << path;
-        return false;
-    }
-
-    CowOptions options;
-    options.block_size = kBlockSize;
-    options.compression = FLAGS_compression;
-    options.cluster_ops = FLAGS_cluster_ops;
-
-    writer_ = std::make_unique<CowWriter>(options);
-    if (!writer_->Initialize(std::move(fd))) {
-        LOG(ERROR) << "Unable to initialize COW writer";
-        return false;
-    }
-
-    partition_name_ = partition_name;
-
-    for (const auto& op : update.operations()) {
-        if (!ProcessOperation(op)) {
-            return false;
-        }
-    }
-
-    if (!writer_->Finalize()) {
-        LOG(ERROR) << "Unable to finalize COW for " << partition_name;
-        return false;
-    }
-    return true;
-}
-
-bool PayloadConverter::ProcessOperation(const InstallOperation& op) {
-    switch (op.type()) {
-        case InstallOperation::SOURCE_COPY:
-            return ProcessCopy(op);
-        case InstallOperation::BROTLI_BSDIFF:
-        case InstallOperation::PUFFDIFF:
-            return ProcessDiff(op);
-        case InstallOperation::REPLACE:
-        case InstallOperation::REPLACE_XZ:
-        case InstallOperation::REPLACE_BZ:
-            return ProcessReplace(op);
-        case InstallOperation::ZERO:
-            return ProcessZero(op);
-        default:
-            LOG(ERROR) << "Unsupported op: " << (int)op.type();
-            return false;
-    }
-    return true;
-}
-
-bool PayloadConverter::ProcessZero(const InstallOperation& op) {
-    for (const auto& extent : op.dst_extents()) {
-        if (!writer_->AddZeroBlocks(extent.start_block(), extent.num_blocks())) {
-            LOG(ERROR) << "Could not add zero operation";
-            return false;
-        }
-    }
-    return true;
-}
-
-template <typename T>
-static uint64_t SizeOfAllExtents(const T& extents) {
-    uint64_t total = 0;
-    for (const auto& extent : extents) {
-        total += extent.num_blocks() * kBlockSize;
-    }
-    return total;
-}
-
-class PuffInputStream final : public puffin::StreamInterface {
-  public:
-    PuffInputStream(uint8_t* buffer, size_t length) : buffer_(buffer), length_(length), pos_(0) {}
-
-    bool GetSize(uint64_t* size) const override {
-        *size = length_;
-        return true;
-    }
-    bool GetOffset(uint64_t* offset) const override {
-        *offset = pos_;
-        return true;
-    }
-    bool Seek(uint64_t offset) override {
-        if (offset > length_) return false;
-        pos_ = offset;
-        return true;
-    }
-    bool Read(void* buffer, size_t length) override {
-        if (length_ - pos_ < length) return false;
-        memcpy(buffer, buffer_ + pos_, length);
-        pos_ += length;
-        return true;
-    }
-    bool Write(const void*, size_t) override { return false; }
-    bool Close() override { return true; }
-
-  private:
-    uint8_t* buffer_;
-    size_t length_;
-    size_t pos_;
-};
-
-class PuffOutputStream final : public puffin::StreamInterface {
-  public:
-    PuffOutputStream(std::vector<uint8_t>& stream) : stream_(stream), pos_(0) {}
-
-    bool GetSize(uint64_t* size) const override {
-        *size = stream_.size();
-        return true;
-    }
-    bool GetOffset(uint64_t* offset) const override {
-        *offset = pos_;
-        return true;
-    }
-    bool Seek(uint64_t offset) override {
-        if (offset > stream_.size()) {
-            return false;
-        }
-        pos_ = offset;
-        return true;
-    }
-    bool Read(void* buffer, size_t length) override {
-        if (stream_.size() - pos_ < length) {
-            return false;
-        }
-        memcpy(buffer, &stream_[0] + pos_, length);
-        pos_ += length;
-        return true;
-    }
-    bool Write(const void* buffer, size_t length) override {
-        auto remaining = stream_.size() - pos_;
-        if (remaining < length) {
-            stream_.resize(stream_.size() + (length - remaining));
-        }
-        memcpy(&stream_[0] + pos_, buffer, length);
-        pos_ += length;
-        return true;
-    }
-    bool Close() override { return true; }
-
-  private:
-    std::vector<uint8_t>& stream_;
-    size_t pos_;
-};
-
-bool PayloadConverter::ProcessDiff(const InstallOperation& op) {
-    auto source_image = OpenSourceImage();
-    if (source_image < 0) {
-        return false;
-    }
-
-    uint64_t src_length = SizeOfAllExtents(op.src_extents());
-    auto src = std::make_unique<uint8_t[]>(src_length);
-    size_t src_pos = 0;
-
-    // Read source bytes.
-    for (const auto& extent : op.src_extents()) {
-        uint64_t offset = extent.start_block() * kBlockSize;
-        if (lseek(source_image.get(), offset, SEEK_SET) < 0) {
-            PLOG(ERROR) << "lseek source image failed";
-            return false;
-        }
-
-        uint64_t size = extent.num_blocks() * kBlockSize;
-        CHECK(src_length - src_pos >= size);
-        if (!android::base::ReadFully(source_image, src.get() + src_pos, size)) {
-            PLOG(ERROR) << "read source image failed";
-            return false;
-        }
-        src_pos += size;
-    }
-    CHECK(src_pos == src_length);
-
-    // Read patch bytes.
-    auto patch = std::make_unique<uint8_t[]>(op.data_length());
-    if (lseek(in_fd_.get(), payload_offset_ + op.data_offset(), SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek payload failed";
-        return false;
-    }
-    if (!android::base::ReadFully(in_fd_, patch.get(), op.data_length())) {
-        PLOG(ERROR) << "read payload failed";
-        return false;
-    }
-
-    std::vector<uint8_t> dest(SizeOfAllExtents(op.dst_extents()));
-
-    // Apply the diff.
-    if (op.type() == InstallOperation::BROTLI_BSDIFF) {
-        size_t dest_pos = 0;
-        auto sink = [&](const uint8_t* data, size_t length) -> size_t {
-            CHECK(dest.size() - dest_pos >= length);
-            memcpy(&dest[dest_pos], data, length);
-            dest_pos += length;
-            return length;
-        };
-        if (int rv = bsdiff::bspatch(src.get(), src_pos, patch.get(), op.data_length(), sink)) {
-            LOG(ERROR) << "bspatch failed, error code " << rv;
-            return false;
-        }
-    } else if (op.type() == InstallOperation::PUFFDIFF) {
-        auto src_stream = std::make_unique<PuffInputStream>(src.get(), src_length);
-        auto dest_stream = std::make_unique<PuffOutputStream>(dest);
-        bool ok = PuffPatch(std::move(src_stream), std::move(dest_stream), patch.get(),
-                            op.data_length());
-        if (!ok) {
-            LOG(ERROR) << "puffdiff operation failed to apply";
-            return false;
-        }
-    } else {
-        LOG(ERROR) << "unsupported diff operation: " << op.type();
-        return false;
-    }
-
-    // Write the final blocks to the COW.
-    size_t dest_pos = 0;
-    for (const auto& extent : op.dst_extents()) {
-        uint64_t size = extent.num_blocks() * kBlockSize;
-        CHECK(dest.size() - dest_pos >= size);
-
-        if (!writer_->AddRawBlocks(extent.start_block(), &dest[dest_pos], size)) {
-            return false;
-        }
-        dest_pos += size;
-    }
-    return true;
-}
-
-borrowed_fd PayloadConverter::OpenSourceImage() {
-    if (source_image_ >= 0) {
-        return source_image_;
-    }
-
-    unique_fd unzip_fd;
-
-    auto local_path = "IMAGES/" + partition_name_ + ".img";
-    if (source_tf_zip_) {
-        {
-            TemporaryFile tmp;
-            if (tmp.fd < 0) {
-                PLOG(ERROR) << "mkstemp failed";
-                return -1;
-            }
-            unzip_fd.reset(tmp.release());
-        }
-
-        ZipEntry64 entry;
-        if (FindEntry(source_tf_zip_.get(), local_path, &entry)) {
-            LOG(ERROR) << "not found in archive: " << local_path;
-            return -1;
-        }
-        if (ExtractEntryToFile(source_tf_zip_.get(), &entry, unzip_fd.get())) {
-            LOG(ERROR) << "could not extract " << local_path;
-            return -1;
-        }
-        if (lseek(unzip_fd.get(), 0, SEEK_SET) < 0) {
-            PLOG(ERROR) << "lseek failed";
-            return -1;
-        }
-    } else if (source_tf_fd_ >= 0) {
-        unzip_fd.reset(openat(source_tf_fd_.get(), local_path.c_str(), O_RDONLY));
-        if (unzip_fd < 0) {
-            PLOG(ERROR) << "open failed: " << FLAGS_source_tf << "/" << local_path;
-            return -1;
-        }
-    } else {
-        LOG(ERROR) << "No source target files package was specified; need -source_tf";
-        return -1;
-    }
-
-    std::unique_ptr<struct sparse_file, decltype(&sparse_file_destroy)> s(
-            sparse_file_import(unzip_fd.get(), false, false), &sparse_file_destroy);
-    if (s) {
-        TemporaryFile tmp;
-        if (tmp.fd < 0) {
-            PLOG(ERROR) << "mkstemp failed";
-            return -1;
-        }
-        if (sparse_file_write(s.get(), tmp.fd, false, false, false) < 0) {
-            LOG(ERROR) << "sparse_file_write failed";
-            return -1;
-        }
-        source_image_.reset(tmp.release());
-    } else {
-        source_image_ = std::move(unzip_fd);
-    }
-    return source_image_;
-}
-
-template <typename ContainerType>
-class ExtentIter final {
-  public:
-    ExtentIter(const ContainerType& container)
-        : iter_(container.cbegin()), end_(container.cend()), dst_index_(0) {}
-
-    bool GetNext(uint64_t* block) {
-        while (iter_ != end_) {
-            if (dst_index_ < iter_->num_blocks()) {
-                break;
-            }
-            iter_++;
-            dst_index_ = 0;
-        }
-        if (iter_ == end_) {
-            return false;
-        }
-        *block = iter_->start_block() + dst_index_;
-        dst_index_++;
-        return true;
-    }
-
-  private:
-    typename ContainerType::const_iterator iter_;
-    typename ContainerType::const_iterator end_;
-    uint64_t dst_index_;
-};
-
-bool PayloadConverter::ProcessCopy(const InstallOperation& op) {
-    ExtentIter dst_blocks(op.dst_extents());
-
-    for (const auto& extent : op.src_extents()) {
-        for (uint64_t i = 0; i < extent.num_blocks(); i++) {
-            uint64_t src_block = extent.start_block() + i;
-            uint64_t dst_block;
-            if (!dst_blocks.GetNext(&dst_block)) {
-                LOG(ERROR) << "SOURCE_COPY contained mismatching extents";
-                return false;
-            }
-            if (src_block == dst_block) continue;
-            if (!writer_->AddCopy(dst_block, src_block)) {
-                LOG(ERROR) << "Could not add copy operation";
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-bool PayloadConverter::ProcessReplace(const InstallOperation& op) {
-    auto buffer_size = op.data_length();
-    auto buffer = std::make_unique<char[]>(buffer_size);
-    uint64_t offs = payload_offset_ + op.data_offset();
-    if (lseek(in_fd_.get(), offs, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek " << offs << " failed";
-        return false;
-    }
-    if (!android::base::ReadFully(in_fd_, buffer.get(), buffer_size)) {
-        PLOG(ERROR) << "read " << buffer_size << " bytes from offset " << offs << "failed";
-        return false;
-    }
-
-    uint64_t dst_size = 0;
-    for (const auto& extent : op.dst_extents()) {
-        dst_size += extent.num_blocks() * kBlockSize;
-    }
-
-    if (op.type() == InstallOperation::REPLACE_BZ) {
-        auto tmp = std::make_unique<char[]>(dst_size);
-
-        uint32_t actual_size;
-        if (dst_size > std::numeric_limits<typeof(actual_size)>::max()) {
-            LOG(ERROR) << "too many bytes to decompress: " << dst_size;
-            return false;
-        }
-        actual_size = static_cast<uint32_t>(dst_size);
-
-        auto rv = BZ2_bzBuffToBuffDecompress(tmp.get(), &actual_size, buffer.get(), buffer_size, 0,
-                                             0);
-        if (rv) {
-            LOG(ERROR) << "bz2 decompress failed: " << rv;
-            return false;
-        }
-        if (actual_size != dst_size) {
-            LOG(ERROR) << "bz2 returned " << actual_size << " bytes, expected " << dst_size;
-            return false;
-        }
-        buffer = std::move(tmp);
-        buffer_size = dst_size;
-    } else if (op.type() == InstallOperation::REPLACE_XZ) {
-        constexpr uint32_t kXzMaxDictSize = 64 * 1024 * 1024;
-
-        if (dst_size > std::numeric_limits<size_t>::max()) {
-            LOG(ERROR) << "too many bytes to decompress: " << dst_size;
-            return false;
-        }
-
-        std::unique_ptr<struct xz_dec, decltype(&xz_dec_end)> s(
-                xz_dec_init(XZ_DYNALLOC, kXzMaxDictSize), xz_dec_end);
-        if (!s) {
-            LOG(ERROR) << "xz_dec_init failed";
-            return false;
-        }
-
-        auto tmp = std::make_unique<char[]>(dst_size);
-
-        struct xz_buf args;
-        args.in = reinterpret_cast<const uint8_t*>(buffer.get());
-        args.in_pos = 0;
-        args.in_size = buffer_size;
-        args.out = reinterpret_cast<uint8_t*>(tmp.get());
-        args.out_pos = 0;
-        args.out_size = dst_size;
-
-        auto rv = xz_dec_run(s.get(), &args);
-        if (rv != XZ_STREAM_END) {
-            LOG(ERROR) << "xz decompress failed: " << (int)rv;
-            return false;
-        }
-        buffer = std::move(tmp);
-        buffer_size = dst_size;
-    }
-
-    uint64_t buffer_pos = 0;
-    for (const auto& extent : op.dst_extents()) {
-        uint64_t extent_size = extent.num_blocks() * kBlockSize;
-        if (buffer_size - buffer_pos < extent_size) {
-            LOG(ERROR) << "replace op ran out of input buffer";
-            return false;
-        }
-        if (!writer_->AddRawBlocks(extent.start_block(), buffer.get() + buffer_pos, extent_size)) {
-            LOG(ERROR) << "failed to add raw blocks from replace op";
-            return false;
-        }
-        buffer_pos += extent_size;
-    }
-    return true;
-}
-
-bool PayloadConverter::OpenPayload() {
-    in_fd_.reset(open(in_file_.c_str(), O_RDONLY));
-    if (in_fd_ < 0) {
-        PLOG(ERROR) << "open " << in_file_;
-        return false;
-    }
-
-    char magic[4];
-    if (!android::base::ReadFully(in_fd_, magic, sizeof(magic))) {
-        PLOG(ERROR) << "read magic";
-        return false;
-    }
-    if (std::string(magic, sizeof(magic)) != "CrAU") {
-        LOG(ERROR) << "Invalid magic in " << in_file_;
-        return false;
-    }
-
-    uint64_t version;
-    uint64_t manifest_size;
-    uint32_t manifest_signature_size = 0;
-    if (!android::base::ReadFully(in_fd_, &version, sizeof(version))) {
-        PLOG(ERROR) << "read version";
-        return false;
-    }
-    version = ToLittleEndian(version);
-    if (version < 2) {
-        LOG(ERROR) << "Only payload version 2 or higher is supported.";
-        return false;
-    }
-
-    if (!android::base::ReadFully(in_fd_, &manifest_size, sizeof(manifest_size))) {
-        PLOG(ERROR) << "read manifest_size";
-        return false;
-    }
-    manifest_size = ToLittleEndian(manifest_size);
-    if (!android::base::ReadFully(in_fd_, &manifest_signature_size,
-                                  sizeof(manifest_signature_size))) {
-        PLOG(ERROR) << "read manifest_signature_size";
-        return false;
-    }
-    manifest_signature_size = ntohl(manifest_signature_size);
-
-    auto manifest = std::make_unique<uint8_t[]>(manifest_size);
-    if (!android::base::ReadFully(in_fd_, manifest.get(), manifest_size)) {
-        PLOG(ERROR) << "read manifest";
-        return false;
-    }
-
-    // Skip past manifest signature.
-    auto offs = lseek(in_fd_, manifest_signature_size, SEEK_CUR);
-    if (offs < 0) {
-        PLOG(ERROR) << "lseek failed";
-        return false;
-    }
-    payload_offset_ = offs;
-
-    if (!manifest_.ParseFromArray(manifest.get(), manifest_size)) {
-        LOG(ERROR) << "could not parse manifest";
-        return false;
-    }
-    return true;
-}
-
-}  // namespace snapshot
-}  // namespace android
-
-int main(int argc, char** argv) {
-    android::base::InitLogging(argv, android::snapshot::MyLogger);
-    gflags::SetUsageMessage("Convert OTA payload to a Virtual A/B COW");
-    int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, false);
-
-    xz_crc32_init();
-
-    if (argc - arg_start != 2) {
-        std::cerr << "Usage: [options] <payload.bin> <out-dir>\n";
-        return 1;
-    }
-
-    android::snapshot::PayloadConverter pc(argv[arg_start], argv[arg_start + 1]);
-    return pc.Run() ? 0 : 1;
-}
diff --git a/fs_mgr/libsnapshot/power_test.cpp b/fs_mgr/libsnapshot/power_test.cpp
deleted file mode 100644
index 4d2548a..0000000
--- a/fs_mgr/libsnapshot/power_test.cpp
+++ /dev/null
@@ -1,559 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-#include <errno.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <chrono>
-#include <iostream>
-#include <random>
-#include <string>
-#include <thread>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/parsedouble.h>
-#include <android-base/stringprintf.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <ext4_utils/ext4_utils.h>
-#include <fstab/fstab.h>
-#include <libdm/dm.h>
-#include <libfiemap/image_manager.h>
-
-using namespace std::chrono_literals;
-using namespace std::string_literals;
-using android::base::borrowed_fd;
-using android::base::unique_fd;
-using android::dm::DeviceMapper;
-using android::dm::DmDeviceState;
-using android::dm::DmTable;
-using android::dm::DmTargetSnapshot;
-using android::dm::SnapshotStorageMode;
-using android::fiemap::ImageManager;
-using android::fs_mgr::Fstab;
-
-namespace android {
-namespace snapshot {
-
-static void usage() {
-    std::cerr << "Usage:\n";
-    std::cerr << "  create <orig-payload> <new-payload>\n";
-    std::cerr << "\n";
-    std::cerr << "  Create a snapshot device containing the contents of\n";
-    std::cerr << "  orig-payload, and then write the contents of new-payload.\n";
-    std::cerr << "  The original files are not modified.\n";
-    std::cerr << "\n";
-    std::cerr << "  merge <fail-rate>\n";
-    std::cerr << "\n";
-    std::cerr << "  Merge the snapshot previously started by create, and wait\n";
-    std::cerr << "  for it to complete. Once done, it is compared to the\n";
-    std::cerr << "  new-payload for consistency. The original files are not \n";
-    std::cerr << "  modified. If a fail-rate is passed (as a fraction between 0\n";
-    std::cerr << "  and 100), every 10ms the device has that percent change of\n";
-    std::cerr << "  injecting a kernel crash.\n";
-    std::cerr << "\n";
-    std::cerr << "  check <new-payload>\n";
-    std::cerr << "  Verify that all artifacts are correct after a merge\n";
-    std::cerr << "  completes.\n";
-    std::cerr << "\n";
-    std::cerr << "  cleanup\n";
-    std::cerr << "  Remove all ImageManager artifacts from create/merge.\n";
-}
-
-class PowerTest final {
-  public:
-    PowerTest();
-    bool Run(int argc, char** argv);
-
-  private:
-    bool OpenImageManager();
-    bool Create(int argc, char** argv);
-    bool Merge(int argc, char** argv);
-    bool Check(int argc, char** argv);
-    bool Cleanup();
-    bool CleanupImage(const std::string& name);
-    bool SetupImages(const std::string& first_file, borrowed_fd second_fd);
-    bool MapImages();
-    bool MapSnapshot(SnapshotStorageMode mode);
-    bool GetMergeStatus(DmTargetSnapshot::Status* status);
-
-    static constexpr char kSnapshotName[] = "snapshot-power-test";
-    static constexpr char kSnapshotImageName[] = "snapshot-power-test-image";
-    static constexpr char kSnapshotCowName[] = "snapshot-power-test-cow";
-
-    DeviceMapper& dm_;
-    std::unique_ptr<ImageManager> images_;
-    std::string image_path_;
-    std::string cow_path_;
-    std::string snapshot_path_;
-};
-
-PowerTest::PowerTest() : dm_(DeviceMapper::Instance()) {}
-
-bool PowerTest::Run([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
-    if (!OpenImageManager()) {
-        return false;
-    }
-
-    if (argc < 2) {
-        usage();
-        return false;
-    }
-    if (argv[1] == "create"s) {
-        return Create(argc, argv);
-    } else if (argv[1] == "merge"s) {
-        return Merge(argc, argv);
-    } else if (argv[1] == "check"s) {
-        return Check(argc, argv);
-    } else if (argv[1] == "cleanup"s) {
-        return Cleanup();
-    } else {
-        usage();
-        return false;
-    }
-}
-
-bool PowerTest::OpenImageManager() {
-    std::vector<std::string> dirs = {
-            "/data/gsi/test",
-            "/metadata/gsi/test",
-    };
-    for (const auto& dir : dirs) {
-        if (mkdir(dir.c_str(), 0700) && errno != EEXIST) {
-            std::cerr << "mkdir " << dir << ": " << strerror(errno) << "\n";
-            return false;
-        }
-    }
-
-    images_ = ImageManager::Open("/metadata/gsi/test", "/data/gsi/test");
-    if (!images_) {
-        std::cerr << "Could not open ImageManager\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::Create(int argc, char** argv) {
-    if (argc < 4) {
-        usage();
-        return false;
-    }
-
-    std::string first = argv[2];
-    std::string second = argv[3];
-
-    unique_fd second_fd(open(second.c_str(), O_RDONLY));
-    if (second_fd < 0) {
-        std::cerr << "open " << second << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    if (!Cleanup()) {
-        return false;
-    }
-    if (!SetupImages(first, second_fd)) {
-        return false;
-    }
-    if (!MapSnapshot(SnapshotStorageMode::Persistent)) {
-        return false;
-    }
-
-    struct stat s;
-    if (fstat(second_fd, &s)) {
-        std::cerr << "fstat " << second << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    unique_fd snap_fd(open(snapshot_path_.c_str(), O_WRONLY));
-    if (snap_fd < 0) {
-        std::cerr << "open " << snapshot_path_ << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    uint8_t chunk[4096];
-    uint64_t written = 0;
-    while (written < s.st_size) {
-        uint64_t remaining = s.st_size - written;
-        size_t bytes = (size_t)std::min((uint64_t)sizeof(chunk), remaining);
-        if (!android::base::ReadFully(second_fd, chunk, bytes)) {
-            std::cerr << "read " << second << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        if (!android::base::WriteFully(snap_fd, chunk, bytes)) {
-            std::cerr << "write " << snapshot_path_ << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        written += bytes;
-    }
-    if (fsync(snap_fd)) {
-        std::cerr << "fsync: " << strerror(errno) << "\n";
-        return false;
-    }
-
-    sync();
-
-    snap_fd = {};
-    if (!dm_.DeleteDeviceIfExists(kSnapshotName)) {
-        std::cerr << "could not delete dm device " << kSnapshotName << "\n";
-        return false;
-    }
-    if (!images_->UnmapImageIfExists(kSnapshotImageName)) {
-        std::cerr << "failed to unmap " << kSnapshotImageName << "\n";
-        return false;
-    }
-    if (!images_->UnmapImageIfExists(kSnapshotCowName)) {
-        std::cerr << "failed to unmap " << kSnapshotImageName << "\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::Cleanup() {
-    if (!dm_.DeleteDeviceIfExists(kSnapshotName)) {
-        std::cerr << "could not delete dm device " << kSnapshotName << "\n";
-        return false;
-    }
-    if (!CleanupImage(kSnapshotImageName) || !CleanupImage(kSnapshotCowName)) {
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::CleanupImage(const std::string& name) {
-    if (!images_->UnmapImageIfExists(name)) {
-        std::cerr << "failed to unmap " << name << "\n";
-        return false;
-    }
-    if (images_->BackingImageExists(name) && !images_->DeleteBackingImage(name)) {
-        std::cerr << "failed to delete " << name << "\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::SetupImages(const std::string& first, borrowed_fd second_fd) {
-    unique_fd first_fd(open(first.c_str(), O_RDONLY));
-    if (first_fd < 0) {
-        std::cerr << "open " << first << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    struct stat s1, s2;
-    if (fstat(first_fd.get(), &s1)) {
-        std::cerr << "first stat: " << strerror(errno) << "\n";
-        return false;
-    }
-    if (fstat(second_fd.get(), &s2)) {
-        std::cerr << "second stat: " << strerror(errno) << "\n";
-        return false;
-    }
-
-    // Pick the bigger size of both images, rounding up to the nearest block.
-    uint64_t s1_size = (s1.st_size + 4095) & ~uint64_t(4095);
-    uint64_t s2_size = (s2.st_size + 4095) & ~uint64_t(4095);
-    uint64_t image_size = std::max(s1_size, s2_size) + (1024 * 1024 * 128);
-    if (!images_->CreateBackingImage(kSnapshotImageName, image_size, 0, nullptr)) {
-        std::cerr << "failed to create " << kSnapshotImageName << "\n";
-        return false;
-    }
-    // Use the same size for the cow.
-    if (!images_->CreateBackingImage(kSnapshotCowName, image_size, 0, nullptr)) {
-        std::cerr << "failed to create " << kSnapshotCowName << "\n";
-        return false;
-    }
-    if (!MapImages()) {
-        return false;
-    }
-
-    unique_fd image_fd(open(image_path_.c_str(), O_WRONLY));
-    if (image_fd < 0) {
-        std::cerr << "open: " << image_path_ << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    uint8_t chunk[4096];
-    uint64_t written = 0;
-    while (written < s1.st_size) {
-        uint64_t remaining = s1.st_size - written;
-        size_t bytes = (size_t)std::min((uint64_t)sizeof(chunk), remaining);
-        if (!android::base::ReadFully(first_fd, chunk, bytes)) {
-            std::cerr << "read: " << strerror(errno) << "\n";
-            return false;
-        }
-        if (!android::base::WriteFully(image_fd, chunk, bytes)) {
-            std::cerr << "write: " << strerror(errno) << "\n";
-            return false;
-        }
-        written += bytes;
-    }
-    if (fsync(image_fd)) {
-        std::cerr << "fsync: " << strerror(errno) << "\n";
-        return false;
-    }
-
-    // Zero the first block of the COW.
-    unique_fd cow_fd(open(cow_path_.c_str(), O_WRONLY));
-    if (cow_fd < 0) {
-        std::cerr << "open: " << cow_path_ << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    memset(chunk, 0, sizeof(chunk));
-    if (!android::base::WriteFully(cow_fd, chunk, sizeof(chunk))) {
-        std::cerr << "read: " << strerror(errno) << "\n";
-        return false;
-    }
-    if (fsync(cow_fd)) {
-        std::cerr << "fsync: " << strerror(errno) << "\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::MapImages() {
-    if (!images_->MapImageDevice(kSnapshotImageName, 10s, &image_path_)) {
-        std::cerr << "failed to map " << kSnapshotImageName << "\n";
-        return false;
-    }
-    if (!images_->MapImageDevice(kSnapshotCowName, 10s, &cow_path_)) {
-        std::cerr << "failed to map " << kSnapshotCowName << "\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::MapSnapshot(SnapshotStorageMode mode) {
-    uint64_t sectors;
-    {
-        unique_fd fd(open(image_path_.c_str(), O_RDONLY));
-        if (fd < 0) {
-            std::cerr << "open: " << image_path_ << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        sectors = get_block_device_size(fd) / 512;
-    }
-
-    DmTable table;
-    table.Emplace<DmTargetSnapshot>(0, sectors, image_path_, cow_path_, mode, 8);
-    if (!dm_.CreateDevice(kSnapshotName, table, &snapshot_path_, 10s)) {
-        std::cerr << "failed to create snapshot device\n";
-        return false;
-    }
-    return true;
-}
-
-bool PowerTest::GetMergeStatus(DmTargetSnapshot::Status* status) {
-    std::vector<DeviceMapper::TargetInfo> targets;
-    if (!dm_.GetTableStatus(kSnapshotName, &targets)) {
-        std::cerr << "failed to get merge status\n";
-        return false;
-    }
-    if (targets.size() != 1) {
-        std::cerr << "merge device has wrong number of targets\n";
-        return false;
-    }
-    if (!DmTargetSnapshot::ParseStatusText(targets[0].data, status)) {
-        std::cerr << "could not parse merge target status text\n";
-        return false;
-    }
-    return true;
-}
-
-static std::string GetUserdataBlockDeviceName() {
-    Fstab fstab;
-    if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
-        return {};
-    }
-
-    auto entry = android::fs_mgr::GetEntryForMountPoint(&fstab, "/data");
-    if (!entry) {
-        return {};
-    }
-
-    auto prefix = "/dev/block/"s;
-    if (!android::base::StartsWith(entry->blk_device, prefix)) {
-        return {};
-    }
-    return entry->blk_device.substr(prefix.size());
-}
-
-bool PowerTest::Merge(int argc, char** argv) {
-    // Start an f2fs GC to really stress things. :TODO: figure out data device
-    auto userdata_dev = GetUserdataBlockDeviceName();
-    if (userdata_dev.empty()) {
-        std::cerr << "could not locate userdata block device\n";
-        return false;
-    }
-
-    auto cmd =
-            android::base::StringPrintf("echo 1 > /sys/fs/f2fs/%s/gc_urgent", userdata_dev.c_str());
-    system(cmd.c_str());
-
-    if (dm_.GetState(kSnapshotName) == DmDeviceState::INVALID) {
-        if (!MapImages()) {
-            return false;
-        }
-        if (!MapSnapshot(SnapshotStorageMode::Merge)) {
-            return false;
-        }
-    }
-
-    std::random_device r;
-    std::default_random_engine re(r());
-    std::uniform_real_distribution<double> dist(0.0, 100.0);
-
-    std::optional<double> failure_rate;
-    if (argc >= 3) {
-        double d;
-        if (!android::base::ParseDouble(argv[2], &d)) {
-            std::cerr << "Could not parse failure rate as double: " << argv[2] << "\n";
-            return false;
-        }
-        failure_rate = d;
-    }
-
-    while (true) {
-        DmTargetSnapshot::Status status;
-        if (!GetMergeStatus(&status)) {
-            return false;
-        }
-        if (!status.error.empty()) {
-            std::cerr << "merge reported error: " << status.error << "\n";
-            return false;
-        }
-        if (status.sectors_allocated == status.metadata_sectors) {
-            break;
-        }
-
-        std::cerr << status.sectors_allocated << " / " << status.metadata_sectors << "\n";
-
-        if (failure_rate && *failure_rate >= dist(re)) {
-            system("echo 1 > /proc/sys/kernel/sysrq");
-            system("echo c > /proc/sysrq-trigger");
-        }
-
-        std::this_thread::sleep_for(10ms);
-    }
-
-    std::cout << "Merge completed.\n";
-    return true;
-}
-
-bool PowerTest::Check([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
-    if (argc < 3) {
-        std::cerr << "Expected argument: <new-image-path>\n";
-        return false;
-    }
-    std::string md_path, image_path;
-    std::string canonical_path = argv[2];
-
-    if (!dm_.GetDmDevicePathByName(kSnapshotName, &md_path)) {
-        std::cerr << "could not get dm-path for merge device\n";
-        return false;
-    }
-    if (!images_->GetMappedImageDevice(kSnapshotImageName, &image_path)) {
-        std::cerr << "could not get image path\n";
-        return false;
-    }
-
-    unique_fd md_fd(open(md_path.c_str(), O_RDONLY));
-    if (md_fd < 0) {
-        std::cerr << "open: " << md_path << ": " << strerror(errno) << "\n";
-        return false;
-    }
-    unique_fd image_fd(open(image_path.c_str(), O_RDONLY));
-    if (image_fd < 0) {
-        std::cerr << "open: " << image_path << ": " << strerror(errno) << "\n";
-        return false;
-    }
-    unique_fd canonical_fd(open(canonical_path.c_str(), O_RDONLY));
-    if (canonical_fd < 0) {
-        std::cerr << "open: " << canonical_path << ": " << strerror(errno) << "\n";
-        return false;
-    }
-
-    struct stat s;
-    if (fstat(canonical_fd, &s)) {
-        std::cerr << "fstat: " << canonical_path << ": " << strerror(errno) << "\n";
-        return false;
-    }
-    uint64_t canonical_size = s.st_size;
-    uint64_t md_size = get_block_device_size(md_fd);
-    uint64_t image_size = get_block_device_size(image_fd);
-    if (image_size != md_size) {
-        std::cerr << "image size does not match merge device size\n";
-        return false;
-    }
-    if (canonical_size > image_size) {
-        std::cerr << "canonical size " << canonical_size << " is greater than image size "
-                  << image_size << "\n";
-        return false;
-    }
-
-    constexpr size_t kBlockSize = 4096;
-    uint8_t canonical_buffer[kBlockSize];
-    uint8_t image_buffer[kBlockSize];
-    uint8_t md_buffer[kBlockSize];
-
-    uint64_t remaining = canonical_size;
-    uint64_t blockno = 0;
-    while (remaining) {
-        size_t bytes = (size_t)std::min((uint64_t)kBlockSize, remaining);
-        if (!android::base::ReadFully(canonical_fd, canonical_buffer, bytes)) {
-            std::cerr << "read: " << canonical_buffer << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        if (!android::base::ReadFully(image_fd, image_buffer, bytes)) {
-            std::cerr << "read: " << image_buffer << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        if (!android::base::ReadFully(md_fd, md_buffer, bytes)) {
-            std::cerr << "read: " << md_buffer << ": " << strerror(errno) << "\n";
-            return false;
-        }
-        if (memcmp(canonical_buffer, image_buffer, bytes)) {
-            std::cerr << "canonical and image differ at block " << blockno << "\n";
-            return false;
-        }
-        if (memcmp(canonical_buffer, md_buffer, bytes)) {
-            std::cerr << "canonical and image differ at block " << blockno << "\n";
-            return false;
-        }
-
-        remaining -= bytes;
-        blockno++;
-    }
-
-    std::cout << "Images all match.\n";
-    return true;
-}
-
-}  // namespace snapshot
-}  // namespace android
-
-int main(int argc, char** argv) {
-    android::snapshot::PowerTest test;
-
-    if (!test.Run(argc, argv)) {
-        std::cerr << "Unexpected error running test." << std::endl;
-        return 1;
-    }
-    fflush(stdout);
-    return 0;
-}
diff --git a/fs_mgr/libsnapshot/run_power_test.sh b/fs_mgr/libsnapshot/run_power_test.sh
deleted file mode 100755
index dc03dc9..0000000
--- a/fs_mgr/libsnapshot/run_power_test.sh
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-
-set -e
-
-if [ -z "$FAIL_RATE" ]; then
-    FAIL_RATE=5.0
-fi
-if [ ! -z "$ANDROID_SERIAL" ]; then
-    DEVICE_ARGS=-s $ANDROID_SERIAL
-else
-    DEVICE_ARGS=
-fi
-
-TEST_BIN=/data/nativetest64/snapshot_power_test/snapshot_power_test
-
-while :
-do
-    adb $DEVICE_ARGS wait-for-device
-    adb $DEVICE_ARGS root
-    adb $DEVICE_ARGS shell rm $TEST_BIN
-    adb $DEVICE_ARGS sync data
-    set +e
-    output=$(adb $DEVICE_ARGS shell $TEST_BIN merge $FAIL_RATE 2>&1)
-    set -e
-    if [[ "$output" == *"Merge completed"* ]]; then
-        echo "Merge completed."
-        break
-    fi
-    if [[ "$output" == *"Unexpected error"* ]]; then
-        echo "Unexpected error."
-        exit 1
-    fi
-done
-
-adb $DEVICE_ARGS shell $TEST_BIN check $1
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 7fcaac5..1e03683 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -164,7 +164,7 @@
 }
 
 cc_test {
-    name: "cow_snapuserd_test",
+    name: "snapuserd_test_legacy",
     defaults: [
         "fs_mgr_defaults",
         "libsnapshot_cow_defaults",
@@ -216,16 +216,17 @@
     ],
     static_libs: [
         "libbrotli",
+        "libcutils_sockets",
+        "libdm",
+        "libext4_utils",
+        "libfs_mgr",
+        "libgflags",
         "libgtest",
         "libsnapshot_cow",
         "libsnapshot_snapuserd",
-        "libcutils_sockets",
-        "libz",
-        "libfs_mgr",
-        "libdm",
-        "libext4_utils",
+        "libsnapuserd",
         "liburing",
-        "libgflags",
+        "libz",
     ],
     include_dirs: ["bionic/libc/kernel"],
     header_libs: [
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
index 484a9c4..3c4ab2e 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
@@ -1152,35 +1152,6 @@
     }
 }
 
-TEST(Snapuserd_Test, xor_buffer) {
-    std::string data = "Test String";
-    std::string jumbled = {0x0C, 0x2A, 0x21, 0x54, 0x73, 0x27, 0x06, 0x1B, 0x07, 0x09, 0x46};
-    std::string result = "XOR String!";
-
-    BufferSink sink;
-    XorSink xor_sink;
-    sink.Initialize(sizeof(struct dm_user_header) + 10);
-    int buffsize = 5;
-    xor_sink.Initialize(&sink, buffsize);
-
-    void* buff = sink.GetPayloadBuffer(data.length());
-    memcpy(buff, data.data(), data.length());
-
-    size_t actual;
-    size_t count = 0;
-    while (count < data.length()) {
-        void* xor_buff = xor_sink.GetBuffer(10, &actual);
-        ASSERT_EQ(actual, buffsize);
-        ASSERT_NE(xor_buff, nullptr);
-        memcpy(xor_buff, jumbled.data() + count, buffsize);
-        xor_sink.ReturnData(xor_buff, actual);
-        count += actual;
-    }
-
-    std::string answer = reinterpret_cast<char*>(sink.GetPayloadBufPtr());
-    ASSERT_EQ(answer, result);
-}
-
 TEST(Snapuserd_Test, Snapshot_Metadata) {
     CowSnapuserdMetadataTest harness;
     harness.Setup();
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
index 5f4d706..8926030 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.cpp
@@ -350,7 +350,7 @@
     CowHeader header;
     CowOptions options;
     bool metadata_found = false;
-    int replace_ops = 0, zero_ops = 0, copy_ops = 0, xor_ops = 0;
+    int replace_ops = 0, zero_ops = 0, copy_ops = 0;
 
     SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file";
 
@@ -515,10 +515,6 @@
             //===========================================================
             uint64_t block_source = cow_op->source;
             uint64_t block_offset = 0;
-            if (cow_op->type == kCowXorOp) {
-                block_source /= BLOCK_SZ;
-                block_offset = cow_op->source % BLOCK_SZ;
-            }
             if (prev_id.has_value()) {
                 if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source) ||
                     (block_offset > 0 && source_blocks.count(block_source + 1))) {
@@ -538,7 +534,7 @@
         } while (!cowop_rm_iter->Done() && pending_ordered_ops);
 
         data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
-        SNAP_LOG(DEBUG) << "Batch Merge copy-ops/xor-ops of size: " << vec.size()
+        SNAP_LOG(DEBUG) << "Batch Merge copy-ops of size: " << vec.size()
                         << " Area: " << vec_.size() << " Area offset: " << offset
                         << " Pending-ordered-ops in this area: " << pending_ordered_ops;
 
@@ -556,8 +552,6 @@
             num_ops += 1;
             if (cow_op->type == kCowCopyOp) {
                 copy_ops++;
-            } else {  // it->second->type == kCowXorOp
-                xor_ops++;
             }
 
             if (read_ahead_feature_) {
@@ -629,8 +623,8 @@
     SNAP_LOG(INFO) << "ReadMetadata completed. Final-chunk-id: " << data_chunk_id
                    << " Num Sector: " << ChunkToSector(data_chunk_id)
                    << " Replace-ops: " << replace_ops << " Zero-ops: " << zero_ops
-                   << " Copy-ops: " << copy_ops << " Xor-ops: " << xor_ops
-                   << " Areas: " << vec_.size() << " Num-ops-merged: " << header.num_merge_ops
+                   << " Copy-ops: " << copy_ops << " Areas: " << vec_.size()
+                   << " Num-ops-merged: " << header.num_merge_ops
                    << " Total-data-ops: " << reader_->get_num_total_data_ops();
 
     // Total number of sectors required for creating dm-user device
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
index 47b9b22..beb6004 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd.h
@@ -170,9 +170,8 @@
     // Processing COW operations
     bool ProcessCowOp(const CowOperation* cow_op);
     bool ProcessReplaceOp(const CowOperation* cow_op);
-    // Handles Copy and Xor
+    // Handles Copy
     bool ProcessCopyOp(const CowOperation* cow_op);
-    bool ProcessXorOp(const CowOperation* cow_op);
     bool ProcessZeroOp();
 
     bool ReadFromBaseDevice(const CowOperation* cow_op);
@@ -191,7 +190,6 @@
 
     std::unique_ptr<CowReader> reader_;
     BufferSink bufsink_;
-    XorSink xorsink_;
 
     std::string cow_device_;
     std::string backing_store_device_;
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
index c201b23..01123f8 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_readahead.cpp
@@ -174,10 +174,6 @@
 void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
     uint64_t source_block = cow_op->source;
     uint64_t source_offset = 0;
-    if (cow_op->type == kCowXorOp) {
-        source_block /= BLOCK_SZ;
-        source_offset = cow_op->source % BLOCK_SZ;
-    }
     if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(source_block) ||
         (source_offset > 0 && source_blocks_.count(source_block + 1))) {
         overlap_ = true;
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
index 0e9f0f1..965af40 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/snapuserd_worker.cpp
@@ -116,13 +116,7 @@
         offset *= BLOCK_SZ;
     }
     if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, offset)) {
-        std::string op;
-        if (cow_op->type == kCowCopyOp)
-            op = "Copy-op";
-        else {
-            op = "Xor-op";
-        }
-        SNAP_PLOG(ERROR) << op << " failed. Read from backing store: " << backing_store_device_
+        SNAP_PLOG(ERROR) << "Copy op failed. Read from backing store: " << backing_store_device_
                          << "at block :" << offset / BLOCK_SZ << " offset:" << offset % BLOCK_SZ;
         return false;
     }
@@ -158,23 +152,6 @@
     return true;
 }
 
-bool WorkerThread::ProcessXorOp(const CowOperation* cow_op) {
-    if (!GetReadAheadPopulatedBuffer(cow_op)) {
-        SNAP_LOG(DEBUG) << " GetReadAheadPopulatedBuffer failed..."
-                        << " new_block: " << cow_op->new_block;
-        if (!ReadFromBaseDevice(cow_op)) {
-            return false;
-        }
-    }
-    xorsink_.Reset();
-    if (!reader_->ReadData(*cow_op, &xorsink_)) {
-        SNAP_LOG(ERROR) << "ProcessXorOp failed for block " << cow_op->new_block;
-        return false;
-    }
-
-    return true;
-}
-
 bool WorkerThread::ProcessZeroOp() {
     // Zero out the entire block
     void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
@@ -206,12 +183,8 @@
             return ProcessCopyOp(cow_op);
         }
 
-        case kCowXorOp: {
-            return ProcessXorOp(cow_op);
-        }
-
         default: {
-            SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+            SNAP_LOG(ERROR) << "Unsupported operation-type found: " << cow_op->type;
         }
     }
     return false;
@@ -830,7 +803,6 @@
 
 bool WorkerThread::RunThread() {
     InitializeBufsink();
-    xorsink_.Initialize(&bufsink_, BLOCK_SZ);
 
     if (!InitializeFds()) {
         return false;
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index 0557214..36dad33 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -110,7 +110,7 @@
     for (int i = arg_start; i < argc; i++) {
         auto parts = android::base::Split(argv[i], ",");
         if (parts.size() != 4) {
-            LOG(ERROR) << "Malformed message, expected three sub-arguments.";
+            LOG(ERROR) << "Malformed message, expected four sub-arguments.";
             return false;
         }
         auto handler = user_server_.AddHandler(parts[0], parts[1], parts[2], parts[3]);
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
index c5150c4..bdba5c0 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/handler_manager.cpp
@@ -25,6 +25,9 @@
 
 static constexpr uint8_t kMaxMergeThreads = 2;
 
+HandlerThread::HandlerThread(std::shared_ptr<SnapshotHandler> snapuserd)
+    : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
+
 void HandlerThread::FreeResources() {
     // Each worker thread holds a reference to snapuserd.
     // Clear them so that all the resources
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 2c201ff..25ce0ae 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -433,11 +433,6 @@
     struct utsname uts;
     unsigned int major, minor;
 
-    if (android::base::GetBoolProperty("snapuserd.test.io_uring.force_disable", false)) {
-        SNAP_LOG(INFO) << "io_uring disabled for testing";
-        return false;
-    }
-
     if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) {
         SNAP_LOG(ERROR) << "Could not parse the kernel version from uname. "
                         << " io_uring not supported";
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index d87990a..c953f1a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -83,9 +83,6 @@
     handlers_->JoinAllThreads();
 }
 
-HandlerThread::HandlerThread(std::shared_ptr<SnapshotHandler> snapuserd)
-    : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
-
 bool UserSnapshotServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
     ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
     if (ret < 0) {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index 1421403..57f9e7a 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -37,9 +37,9 @@
 #include <libdm/dm.h>
 #include <libdm/loop_control.h>
 #include <libsnapshot/cow_writer.h>
-#include <snapuserd/snapuserd_client.h>
 #include <storage_literals/storage_literals.h>
 
+#include "handler_manager.h"
 #include "snapuserd_core.h"
 
 DEFINE_string(force_config, "", "Force testing mode with iouring disabled");
@@ -54,8 +54,6 @@
 using namespace android::dm;
 using namespace std;
 
-static constexpr char kSnapuserdSocketTest[] = "snapuserdTest";
-
 class Tempdevice {
   public:
     Tempdevice(const std::string& name, const DmTable& table)
@@ -68,15 +66,15 @@
     }
     ~Tempdevice() {
         if (valid_) {
-            dm_.DeleteDevice(name_);
+            dm_.DeleteDeviceIfExists(name_);
         }
     }
     bool Destroy() {
         if (!valid_) {
-            return false;
+            return true;
         }
         valid_ = false;
-        return dm_.DeleteDevice(name_);
+        return dm_.DeleteDeviceIfExists(name_);
     }
     const std::string& path() const { return path_; }
     const std::string& name() const { return name_; }
@@ -138,7 +136,6 @@
     void SetDeviceControlName();
     void InitDaemon();
     void CreateDmUserDevice();
-    void StartSnapuserdDaemon();
 
     unique_ptr<LoopDevice> base_loop_;
     unique_ptr<Tempdevice> dmuser_dev_;
@@ -148,9 +145,9 @@
 
     unique_fd base_fd_;
     std::unique_ptr<TemporaryFile> cow_system_;
-    std::unique_ptr<SnapuserdClient> client_;
     std::unique_ptr<uint8_t[]> orig_buffer_;
     std::unique_ptr<uint8_t[]> merged_buffer_;
+    SnapshotHandlerManager handlers_;
     bool setup_ok_ = false;
     bool merge_ok_ = false;
     size_t size_ = 100_MiB;
@@ -180,9 +177,9 @@
     ASSERT_TRUE(dmuser_dev_->Destroy());
 
     auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
-    ASSERT_TRUE(client_->WaitForDeviceDelete(system_device_ctrl_name_));
+    ASSERT_TRUE(handlers_.DeleteHandler(system_device_ctrl_name_));
     ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s));
-    ASSERT_TRUE(client_->DetachSnapuserd());
+    handlers_.TerminateMergeThreads();
 }
 
 bool SnapuserdTest::SetupDefault() {
@@ -217,8 +214,6 @@
 bool SnapuserdTest::SetupDaemon() {
     SetDeviceControlName();
 
-    StartSnapuserdDaemon();
-
     CreateDmUserDevice();
     InitCowDevice();
     InitDaemon();
@@ -228,20 +223,6 @@
     return setup_ok_;
 }
 
-void SnapuserdTest::StartSnapuserdDaemon() {
-    pid_t pid = fork();
-    ASSERT_GE(pid, 0);
-    if (pid == 0) {
-        std::string arg0 = "/system/bin/snapuserd";
-        std::string arg1 = "-socket="s + kSnapuserdSocketTest;
-        char* const argv[] = {arg0.data(), arg1.data(), nullptr};
-        ASSERT_GE(execv(arg0.c_str(), argv), 0);
-    } else {
-        client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s);
-        ASSERT_NE(client_, nullptr);
-    }
-}
-
 void SnapuserdTest::CreateBaseDevice() {
     unique_fd rnd_fd;
 
@@ -606,9 +587,17 @@
 }
 
 void SnapuserdTest::InitCowDevice() {
-    uint64_t num_sectors = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
-                                                  base_loop_->device(), base_loop_->device());
-    ASSERT_NE(num_sectors, 0);
+    bool use_iouring = true;
+    if (FLAGS_force_config == "iouring_disabled") {
+        use_iouring = false;
+    }
+
+    auto handler =
+            handlers_.AddHandler(system_device_ctrl_name_, cow_system_->path, base_loop_->device(),
+                                 base_loop_->device(), 1, use_iouring, false);
+    ASSERT_NE(handler, nullptr);
+    ASSERT_NE(handler->snapuserd(), nullptr);
+    ASSERT_NE(handler->snapuserd()->GetNumSectors(), 0);
 }
 
 void SnapuserdTest::SetDeviceControlName() {
@@ -646,13 +635,12 @@
 }
 
 void SnapuserdTest::InitDaemon() {
-    bool ok = client_->AttachDmUser(system_device_ctrl_name_);
-    ASSERT_TRUE(ok);
+    ASSERT_TRUE(handlers_.StartHandler(system_device_ctrl_name_));
 }
 
 void SnapuserdTest::CheckMergeCompletion() {
     while (true) {
-        double percentage = client_->GetMergePercent();
+        double percentage = handlers_.GetMergePercentage();
         if ((int)percentage == 100) {
             break;
         }
@@ -667,8 +655,6 @@
 
     SetDeviceControlName();
 
-    StartSnapuserdDaemon();
-
     CreateDmUserDevice();
     InitCowDevice();
     InitDaemon();
@@ -684,8 +670,7 @@
 }
 
 void SnapuserdTest::StartMerge() {
-    bool ok = client_->InitiateMerge(system_device_ctrl_name_);
-    ASSERT_TRUE(ok);
+    ASSERT_TRUE(handlers_.InitiateMerge(system_device_ctrl_name_));
 }
 
 void SnapuserdTest::ValidateMerge() {
@@ -699,7 +684,6 @@
     Shutdown();
     std::this_thread::sleep_for(500ms);
     SetDeviceControlName();
-    StartSnapuserdDaemon();
     CreateDmUserDevice();
     InitCowDevice();
     InitDaemon();
@@ -859,20 +843,5 @@
 
     gflags::ParseCommandLineFlags(&argc, &argv, false);
 
-    android::base::SetProperty("ctl.stop", "snapuserd");
-
-    if (FLAGS_force_config == "iouring_disabled") {
-        if (!android::base::SetProperty("snapuserd.test.io_uring.force_disable", "1")) {
-            return testing::AssertionFailure()
-                   << "Failed to disable property: snapuserd.test.io_uring.disabled";
-        }
-    }
-
-    int ret = RUN_ALL_TESTS();
-
-    if (FLAGS_force_config == "iouring_disabled") {
-        android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0");
-    }
-
-    return ret;
+    return RUN_ALL_TESTS();
 }
diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
deleted file mode 100644
index cc12d1d..0000000
--- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto
+++ /dev/null
@@ -1,92 +0,0 @@
-//
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-// A subset of system/update_engine/update_metadata.proto. A separate file is
-// used here because:
-// - The original file is optimized for LITE_RUNTIME, but fuzzing needs
-// reflection.
-// - The definition here has less fields. libsnapshot only uses fields declared
-// here, and all fields declared here are fuzzed by libsnapshot_fuzzer. If
-// libsnapshot uses more fields in system/update_engine/update_metadata.proto
-// in the future, they must be added here too, otherwise it will fail to
-// compile.
-//
-// It is okay that this file is older than
-// system/update_engine/update_metadata.proto as long as the messages defined
-// here can also be parsed by protobuf defined there. However, it is not
-// okay to add fields here without adding them to
-// system/update_engine/update_metadata.proto. Doing so will cause a compiler
-// error when libsnapshot code starts to use these dangling fields.
-
-syntax = "proto2";
-
-package chromeos_update_engine;
-
-message Extent {
-    optional uint64 start_block = 1;
-    optional uint64 num_blocks = 2;
-}
-
-message PartitionInfo {
-    optional uint64 size = 1;
-}
-
-message InstallOperation {
-    enum Type {
-        SOURCE_COPY = 4;
-        // Not used by libsnapshot. Declared here so that the fuzzer has an
-        // alternative value to use for |type|.
-        ZERO = 6;
-    }
-    required Type type = 1;
-    repeated Extent src_extents = 4;
-    repeated Extent dst_extents = 6;
-}
-
-message PartitionUpdate {
-    required string partition_name = 1;
-    optional PartitionInfo new_partition_info = 7;
-    repeated InstallOperation operations = 8;
-    optional Extent hash_tree_extent = 11;
-    optional Extent fec_extent = 15;
-    optional uint64 estimate_cow_size = 19;
-}
-
-message DynamicPartitionGroup {
-    required string name = 1;
-    optional uint64 size = 2;
-    repeated string partition_names = 3;
-}
-
-message VABCFeatureSet {
-  optional bool threaded = 1;
-  optional bool batch_writes = 2;
-}
-
-message DynamicPartitionMetadata {
-    repeated DynamicPartitionGroup groups = 1;
-    optional bool vabc_enabled = 3;
-    optional string vabc_compression_param = 4;
-    optional uint32 cow_version = 5;
-    // A collection of knobs to tune Virtual AB Compression
-    optional VABCFeatureSet vabc_feature_set = 6;
-}
-
-message DeltaArchiveManifest {
-    repeated PartitionUpdate partitions = 13;
-    optional DynamicPartitionMetadata dynamic_partition_metadata = 15;
-    optional bool partial_update = 16;
-}
diff --git a/fs_mgr/tools/dmctl.cpp b/fs_mgr/tools/dmctl.cpp
index 10efd0c..7273087 100644
--- a/fs_mgr/tools/dmctl.cpp
+++ b/fs_mgr/tools/dmctl.cpp
@@ -53,6 +53,7 @@
     std::cerr << "  getpath <dm-name>" << std::endl;
     std::cerr << "  getuuid <dm-name>" << std::endl;
     std::cerr << "  info <dm-name>" << std::endl;
+    std::cerr << "  replace <dm-name> <targets...>" << std::endl;
     std::cerr << "  status <dm-name>" << std::endl;
     std::cerr << "  resume <dm-name>" << std::endl;
     std::cerr << "  suspend <dm-name>" << std::endl;
diff --git a/healthd/healthd.rc b/healthd/healthd.rc
deleted file mode 100644
index 8e2ebb6..0000000
--- a/healthd/healthd.rc
+++ /dev/null
@@ -1,4 +0,0 @@
-service healthd /system/bin/healthd
-    class hal
-    critical
-    group root system wakelock
diff --git a/init/README.md b/init/README.md
index b006365..6bdff4a 100644
--- a/init/README.md
+++ b/init/README.md
@@ -642,17 +642,17 @@
   the current SELinux policy or its parent if not specified in the policy. If
   the directory exists, its security context will not be changed (even if
   different from the policy).
-
-  > _action_ can be one of:
-  * `None`: take no encryption action; directory will be encrypted if parent is.
-  * `Require`: encrypt directory, abort boot process if encryption fails
-  * `Attempt`: try to set an encryption policy, but continue if it fails
-  * `DeleteIfNecessary`: recursively delete directory if necessary to set
-  encryption policy.
-
-  > _key_ can be one of:
-  * `ref`: use the systemwide DE key
-  * `per_boot_ref`: use the key freshly generated on each boot.
+>
+> _action_ can be one of:
+>  * `None`: take no encryption action; directory will be encrypted if parent is.
+>  * `Require`: encrypt directory, abort boot process if encryption fails
+>  * `Attempt`: try to set an encryption policy, but continue if it fails
+>  * `DeleteIfNecessary`: recursively delete directory if necessary to set
+>  encryption policy.
+>
+> _key_ can be one of:
+>  * `ref`: use the systemwide DE key
+>  * `per_boot_ref`: use the key freshly generated on each boot.
 
 `mount_all [ <fstab> ] [--<option>]`
 > Calls fs\_mgr\_mount\_all on the given fs\_mgr-format fstab with optional
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 305bf95..0fc3ffc 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -204,6 +204,10 @@
         GTEST_SKIP() << "Must run on userdebug/eng builds. b/262090304";
         return;
     }
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Must be run as root.";
+        return;
+    }
     std::string init_script = R"init(
 service console /system/bin/sh
     class core
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 062ed39..907eb80 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -761,15 +761,7 @@
 
 constexpr size_t kKlogMessageSize = 1024;
 
-void SelinuxAvcLog(char* buf, size_t buf_len) {
-    CHECK_GT(buf_len, 0u);
-
-    size_t str_len = strnlen(buf, buf_len);
-    // trim newline at end of string
-    if (buf[str_len - 1] == '\n') {
-        buf[str_len - 1] = '\0';
-    }
-
+void SelinuxAvcLog(char* buf) {
     struct NetlinkMessage {
         nlmsghdr hdr;
         char buf[kKlogMessageSize];
@@ -835,8 +827,17 @@
     if (length_written <= 0) {
         return 0;
     }
+
+    // libselinux log messages usually contain a new line character, while
+    // Android LOG() does not expect it. Remove it to avoid empty lines in
+    // the log buffers.
+    size_t str_len = strlen(buf);
+    if (buf[str_len - 1] == '\n') {
+        buf[str_len - 1] = '\0';
+    }
+
     if (type == SELINUX_AVC) {
-        SelinuxAvcLog(buf, sizeof(buf));
+        SelinuxAvcLog(buf);
     } else {
         android::base::KernelLogger(android::base::MAIN, severity, "selinux", nullptr, 0, buf);
     }
diff --git a/init/test_upgrade_mte/mte_upgrade_test.rc b/init/test_upgrade_mte/mte_upgrade_test.rc
index a3e596c..aa6c18f 100644
--- a/init/test_upgrade_mte/mte_upgrade_test.rc
+++ b/init/test_upgrade_mte/mte_upgrade_test.rc
@@ -16,9 +16,11 @@
   class late_start
   disabled
   seclabel u:r:su:s0
+  user root
 
 service mte_upgrade_test_helper_overridden /system/bin/mte_upgrade_test_helper ${sys.mte_crash_test_uuid}
   class late_start
   disabled
   seclabel u:r:su:s0
+  user root
   setenv BIONIC_MEMTAG_UPGRADE_SECS 0
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index a6835fc..79d79dd 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -83,11 +83,15 @@
     { 00751, AID_ROOT,         AID_SHELL,        0, "product/apex/*/bin" },
     { 00777, AID_ROOT,         AID_ROOT,         0, "sdcard" },
     { 00751, AID_ROOT,         AID_SDCARD_R,     0, "storage" },
+    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system/apex/com.android.tethering/bin/for-system" },
+    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system/apex/com.android.tethering.inprocess/bin/for-system" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/bin" },
     { 00755, AID_ROOT,         AID_ROOT,         0, "system/etc/ppp" },
     { 00755, AID_ROOT,         AID_SHELL,        0, "system/vendor" },
     { 00750, AID_ROOT,         AID_SHELL,        0, "system/xbin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/apex/*/bin" },
+    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system_ext/apex/com.android.tethering/bin/for-system" },
+    { 00750, AID_ROOT,         AID_SYSTEM,       0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/apex/*/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "vendor/bin" },
@@ -194,6 +198,10 @@
 
     // the following files have enhanced capabilities and ARE included
     // in user builds.
+    { 06755, AID_CLAT,      AID_CLAT,      0, "system/apex/com.android.tethering/bin/for-system/clatd" },
+    { 06755, AID_CLAT,      AID_CLAT,      0, "system/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
+    { 06755, AID_CLAT,      AID_CLAT,      0, "system_ext/apex/com.android.tethering/bin/for-system/clatd" },
+    { 06755, AID_CLAT,      AID_CLAT,      0, "system_ext/apex/com.android.tethering.inprocess/bin/for-system/clatd" },
     { 00700, AID_SYSTEM,    AID_SHELL,     CAP_MASK_LONG(CAP_BLOCK_SUSPEND),
                                               "system/bin/inputflinger" },
     { 00750, AID_ROOT,      AID_SHELL,     CAP_MASK_LONG(CAP_SETUID) |
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index e071c96..1971f01 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -562,7 +562,7 @@
         // Attempt to match both the canonical module name and the module filename.
         if (!fnmatch(pattern.c_str(), module.c_str(), 0)) {
             rv.emplace_back(module);
-        } else if (!fnmatch(pattern.c_str(), basename(deps[0].c_str()), 0)) {
+        } else if (!fnmatch(pattern.c_str(), android::base::Basename(deps[0]).c_str(), 0)) {
             rv.emplace_back(deps[0]);
         }
     }
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 9b2d775..48bc0b7 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -36,6 +36,7 @@
 
 bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache = false);
 bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles);
+bool SetUserProfiles(uid_t uid, const std::vector<std::string>& profiles);
 
 __END_DECLS
 
@@ -75,6 +76,11 @@
 // that it only returns 0 in the case that the cgroup exists and it contains no processes.
 int killProcessGroupOnce(uid_t uid, int initialPid, int signal, int* max_processes = nullptr);
 
+// Sends the provided signal to all members of a process group, but does not wait for processes to
+// exit, or for the cgroup to be removed. Callers should also ensure that killProcessGroup is called
+// later to ensure the cgroup is fully removed, otherwise system resources may leak.
+int sendSignalToProcessGroup(uid_t uid, int initialPid, int signal);
+
 int createProcessGroup(uid_t uid, int initialPid, bool memControl = false);
 
 // Set various properties of a process group. For these functions to work, the process group must
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 38eb92f..a021594 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -200,6 +200,11 @@
     return SetProcessProfiles(uid, pid, std::span<const std::string_view>(profiles_));
 }
 
+bool SetUserProfiles(uid_t uid, const std::vector<std::string>& profiles) {
+    return TaskProfiles::GetInstance().SetUserProfiles(uid, std::span<const std::string>(profiles),
+                                                       false);
+}
+
 static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
     return StringPrintf("%s/uid_%d", cgroup, uid);
 }
@@ -537,6 +542,15 @@
     return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/, max_processes);
 }
 
+int sendSignalToProcessGroup(uid_t uid, int initialPid, int signal) {
+    std::string hierarchy_root_path;
+    if (CgroupsAvailable()) {
+        CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
+    }
+    const char* cgroup = hierarchy_root_path.c_str();
+    return DoKillProcessGroupOnce(cgroup, uid, initialPid, signal);
+}
+
 static int createProcessGroupInternal(uid_t uid, int initialPid, std::string cgroup,
                                       bool activate_controllers) {
     auto uid_path = ConvertUidToPath(cgroup.c_str(), uid);
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 4db7372..1731828 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -139,6 +139,17 @@
     return true;
 }
 
+bool ProfileAttribute::GetPathForUID(uid_t uid, std::string* path) const {
+    if (path == nullptr) {
+        return true;
+    }
+
+    const std::string& file_name =
+            controller()->version() == 2 && !file_v2_name_.empty() ? file_v2_name_ : file_name_;
+    *path = StringPrintf("%s/uid_%d/%s", controller()->path(), uid, file_name.c_str());
+    return true;
+}
+
 bool SetClampsAction::ExecuteForProcess(uid_t, pid_t) const {
     // TODO: add support when kernel supports util_clamp
     LOG(WARNING) << "SetClampsAction::ExecuteForProcess is not supported";
@@ -225,6 +236,29 @@
     return true;
 }
 
+bool SetAttributeAction::ExecuteForUID(uid_t uid) const {
+    std::string path;
+
+    if (!attribute_->GetPathForUID(uid, &path)) {
+        LOG(ERROR) << "Failed to find cgroup for uid " << uid;
+        return false;
+    }
+
+    if (!WriteStringToFile(value_, path)) {
+        if (access(path.c_str(), F_OK) < 0) {
+            if (optional_) {
+                return true;
+            } else {
+                LOG(ERROR) << "No such cgroup attribute: " << path;
+                return false;
+            }
+        }
+        PLOG(ERROR) << "Failed to write '" << value_ << "' to " << path;
+        return false;
+    }
+    return true;
+}
+
 SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
     : controller_(c), path_(p) {
     FdCacheHelper::Init(controller_.GetTasksFilePath(path_), fd_[ProfileAction::RCT_TASK]);
@@ -552,6 +586,16 @@
     return true;
 }
 
+bool TaskProfile::ExecuteForUID(uid_t uid) const {
+    for (const auto& element : elements_) {
+        if (!element->ExecuteForUID(uid)) {
+            LOG(VERBOSE) << "Applying profile action " << element->Name() << " failed";
+            return false;
+        }
+    }
+    return true;
+}
+
 void TaskProfile::EnableResourceCaching(ProfileAction::ResourceCacheType cache_type) {
     if (res_cached_) {
         return;
@@ -805,6 +849,24 @@
 }
 
 template <typename T>
+bool TaskProfiles::SetUserProfiles(uid_t uid, std::span<const T> profiles, bool use_fd_cache) {
+    for (const auto& name : profiles) {
+        TaskProfile* profile = GetProfile(name);
+        if (profile != nullptr) {
+            if (use_fd_cache) {
+                profile->EnableResourceCaching(ProfileAction::RCT_PROCESS);
+            }
+            if (!profile->ExecuteForUID(uid)) {
+                PLOG(WARNING) << "Failed to apply " << name << " process profile";
+            }
+        } else {
+            PLOG(WARNING) << "Failed to find " << name << "process profile";
+        }
+    }
+    return true;
+}
+
+template <typename T>
 bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid, std::span<const T> profiles,
                                       bool use_fd_cache) {
     bool success = true;
@@ -857,3 +919,5 @@
                                             bool use_fd_cache);
 template bool TaskProfiles::SetTaskProfiles(int tid, std::span<const std::string_view> profiles,
                                             bool use_fd_cache);
+template bool TaskProfiles::SetUserProfiles(uid_t uid, std::span<const std::string> profiles,
+                                            bool use_fd_cache);
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 85b3f91..a8ecb87 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -36,6 +36,7 @@
     virtual const CgroupController* controller() const = 0;
     virtual const std::string& file_name() const = 0;
     virtual bool GetPathForTask(int tid, std::string* path) const = 0;
+    virtual bool GetPathForUID(uid_t uid, std::string* path) const = 0;
 };
 
 class ProfileAttribute : public IProfileAttribute {
@@ -53,6 +54,7 @@
     void Reset(const CgroupController& controller, const std::string& file_name) override;
 
     bool GetPathForTask(int tid, std::string* path) const override;
+    bool GetPathForUID(uid_t uid, std::string* path) const override;
 
   private:
     CgroupController controller_;
@@ -72,6 +74,7 @@
     // Default implementations will fail
     virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; };
     virtual bool ExecuteForTask(int) const { return false; };
+    virtual bool ExecuteForUID(uid_t) const { return false; };
 
     virtual void EnableResourceCaching(ResourceCacheType) {}
     virtual void DropResourceCaching(ResourceCacheType) {}
@@ -116,6 +119,7 @@
     const char* Name() const override { return "SetAttribute"; }
     bool ExecuteForProcess(uid_t uid, pid_t pid) const override;
     bool ExecuteForTask(int tid) const override;
+    bool ExecuteForUID(uid_t uid) const override;
 
   private:
     const IProfileAttribute* attribute_;
@@ -179,6 +183,7 @@
 
     bool ExecuteForProcess(uid_t uid, pid_t pid) const;
     bool ExecuteForTask(int tid) const;
+    bool ExecuteForUID(uid_t uid) const;
     void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type);
     void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
 
@@ -216,6 +221,8 @@
     bool SetProcessProfiles(uid_t uid, pid_t pid, std::span<const T> profiles, bool use_fd_cache);
     template <typename T>
     bool SetTaskProfiles(int tid, std::span<const T> profiles, bool use_fd_cache);
+    template <typename T>
+    bool SetUserProfiles(uid_t uid, std::span<const T> profiles, bool use_fd_cache);
 
   private:
     TaskProfiles();
diff --git a/libprocessgroup/task_profiles_test.cpp b/libprocessgroup/task_profiles_test.cpp
index 09ac44c..6a5b48b 100644
--- a/libprocessgroup/task_profiles_test.cpp
+++ b/libprocessgroup/task_profiles_test.cpp
@@ -16,6 +16,7 @@
 
 #include "task_profiles.h"
 #include <android-base/logging.h>
+#include <android-base/strings.h>
 #include <gtest/gtest.h>
 #include <mntent.h>
 #include <processgroup/processgroup.h>
@@ -29,13 +30,14 @@
 using ::android::base::LogId;
 using ::android::base::LogSeverity;
 using ::android::base::SetLogger;
+using ::android::base::Split;
 using ::android::base::VERBOSE;
 using ::testing::TestWithParam;
 using ::testing::Values;
 
 namespace {
 
-bool IsCgroupV2Mounted() {
+bool IsCgroupV2MountedRw() {
     std::unique_ptr<FILE, int (*)(FILE*)> mnts(setmntent("/proc/mounts", "re"), endmntent);
     if (!mnts) {
         LOG(ERROR) << "Failed to open /proc/mounts";
@@ -43,9 +45,11 @@
     }
     struct mntent* mnt;
     while ((mnt = getmntent(mnts.get()))) {
-        if (strcmp(mnt->mnt_fsname, "cgroup2") == 0) {
-            return true;
+        if (strcmp(mnt->mnt_type, "cgroup2") != 0) {
+            continue;
         }
+        const std::vector<std::string> options = Split(mnt->mnt_opts, ",");
+        return std::count(options.begin(), options.end(), "ro") == 0;
     }
     return false;
 }
@@ -121,6 +125,10 @@
         return true;
     };
 
+    bool GetPathForUID(uid_t, std::string*) const override {
+        return false;
+    }
+
   private:
     const std::string file_name_;
 };
@@ -141,8 +149,9 @@
 };
 
 TEST_P(SetAttributeFixture, SetAttribute) {
-    // Treehugger runs host tests inside a container without cgroupv2 support.
-    if (!IsCgroupV2Mounted()) {
+    // Treehugger runs host tests inside a container either without cgroupv2
+    // support or with the cgroup filesystem mounted read-only.
+    if (!IsCgroupV2MountedRw()) {
         GTEST_SKIP();
         return;
     }
diff --git a/rootdir/etc/public.libraries.android.txt b/rootdir/etc/public.libraries.android.txt
index 967205f..cacc47c 100644
--- a/rootdir/etc/public.libraries.android.txt
+++ b/rootdir/etc/public.libraries.android.txt
@@ -5,6 +5,7 @@
 libbinder_ndk.so
 libc.so
 libcamera2ndk.so
+libclang_rt.hwasan-aarch64-android.so 64 nopreload
 libdl.so
 libEGL.so
 libGLESv1_CM.so
diff --git a/rootdir/init.rc b/rootdir/init.rc
index b165778..d755b50 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -475,25 +475,28 @@
     stdio_to_kmsg
     # Explicitly specify that boringssl_self_test32 doesn't require any capabilities
     capabilities
+    user nobody
 
 service boringssl_self_test64 /system/bin/boringssl_self_test64
     reboot_on_failure reboot,boringssl-self-check-failed
     stdio_to_kmsg
     # Explicitly specify that boringssl_self_test64 doesn't require any capabilities
     capabilities
+    user nobody
 
 service boringssl_self_test_apex32 /apex/com.android.conscrypt/bin/boringssl_self_test32
     reboot_on_failure reboot,boringssl-self-check-failed
     stdio_to_kmsg
     # Explicitly specify that boringssl_self_test_apex32 doesn't require any capabilities
     capabilities
+    user nobody
 
 service boringssl_self_test_apex64 /apex/com.android.conscrypt/bin/boringssl_self_test64
     reboot_on_failure reboot,boringssl-self-check-failed
     stdio_to_kmsg
     # Explicitly specify that boringssl_self_test_apex64 doesn't require any capabilities
     capabilities
-
+    user nobody
 
 # Healthd can trigger a full boot from charger mode by signaling this
 # property when the power button is held.
@@ -1260,6 +1263,7 @@
     class core
     critical
     seclabel u:r:ueventd:s0
+    user root
     shutdown critical
 
 service console /system/bin/sh