Merge "Move flattened APEX activation logic to apexd."
diff --git a/debuggerd/debuggerd.cpp b/debuggerd/debuggerd.cpp
index 360ea95..e20e8d9 100644
--- a/debuggerd/debuggerd.cpp
+++ b/debuggerd/debuggerd.cpp
@@ -93,8 +93,18 @@
     errx(1, "process %d is a zombie", pid);
   }
 
-  if (kill(pid, 0) != 0) {
-    err(1, "cannot send signal to process %d", pid);
+  // Send a signal to the main thread pid, not a side thread. The signal
+  // handler always sets the crashing tid to the main thread pid when sent this
+  // signal. This is to avoid a problem where the signal is sent to a process,
+  // but happens on a side thread and the intercept mismatches since it
+  // is looking for the main thread pid, not the tid of this random thread.
+  // See b/194346289 for extra details.
+  if (kill(proc_info.pid, 0) != 0) {
+    if (pid == proc_info.pid) {
+      err(1, "cannot send signal to process %d", pid);
+    } else {
+      err(1, "cannot send signal to main thread %d (requested thread %d)", proc_info.pid, pid);
+    }
   }
 
   unique_fd piperead, pipewrite;
@@ -103,9 +113,13 @@
   }
 
   std::thread redirect_thread = spawn_redirect_thread(std::move(piperead));
-  if (!debuggerd_trigger_dump(pid, dump_type, 0, std::move(pipewrite))) {
+  if (!debuggerd_trigger_dump(proc_info.pid, dump_type, 0, std::move(pipewrite))) {
     redirect_thread.join();
-    errx(1, "failed to dump process %d", pid);
+    if (pid == proc_info.pid) {
+      errx(1, "failed to dump process %d", pid);
+    } else {
+      errx(1, "failed to dump main thread %d (requested thread %d)", proc_info.pid, pid);
+    }
   }
 
   redirect_thread.join();
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index abda071..f24c4fc 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -1825,3 +1825,29 @@
   ASSERT_TRUE(android::base::ReadFdToString(output_fd, &output));
   ASSERT_EQ("foo", output);
 }
+
+// Verify that when an intercept is present for the main thread, and the signal
+// is received on a different thread, the intercept still works.
+TEST_F(CrasherTest, intercept_for_main_thread_signal_on_side_thread) {
+  StartProcess([]() {
+    std::thread thread([]() {
+      // Raise the signal on the side thread.
+      raise_debugger_signal(kDebuggerdNativeBacktrace);
+    });
+    thread.join();
+    _exit(0);
+  });
+
+  unique_fd output_fd;
+  StartIntercept(&output_fd, kDebuggerdNativeBacktrace);
+  FinishCrasher();
+  AssertDeath(0);
+
+  int intercept_result;
+  FinishIntercept(&intercept_result);
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_BACKTRACE_FRAME(result, "raise_debugger_signal");
+}
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 375dbed..35be2bf 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -155,18 +155,14 @@
  * could allocate memory or hold a lock.
  */
 static void log_signal_summary(const siginfo_t* info) {
-  char thread_name[MAX_TASK_NAME_LEN + 1];  // one more for termination
-  if (prctl(PR_GET_NAME, reinterpret_cast<unsigned long>(thread_name), 0, 0, 0) != 0) {
-    strcpy(thread_name, "<name unknown>");
-  } else {
-    // short names are null terminated by prctl, but the man page
-    // implies that 16 byte names are not.
-    thread_name[MAX_TASK_NAME_LEN] = 0;
+  char main_thread_name[MAX_TASK_NAME_LEN + 1];
+  if (!get_main_thread_name(main_thread_name, sizeof(main_thread_name))) {
+    strncpy(main_thread_name, "<unknown>", sizeof(main_thread_name));
   }
 
   if (info->si_signo == BIONIC_SIGNAL_DEBUGGER) {
-    async_safe_format_log(ANDROID_LOG_INFO, "libc", "Requested dump for tid %d (%s)", __gettid(),
-                          thread_name);
+    async_safe_format_log(ANDROID_LOG_INFO, "libc", "Requested dump for pid %d (%s)", __getpid(),
+                          main_thread_name);
     return;
   }
 
@@ -181,9 +177,13 @@
     get_signal_sender(sender_desc, sizeof(sender_desc), info);
   }
 
-  char main_thread_name[MAX_TASK_NAME_LEN + 1];
-  if (!get_main_thread_name(main_thread_name, sizeof(main_thread_name))) {
-    strncpy(main_thread_name, "<unknown>", sizeof(main_thread_name));
+  char thread_name[MAX_TASK_NAME_LEN + 1];  // one more for termination
+  if (prctl(PR_GET_NAME, reinterpret_cast<unsigned long>(thread_name), 0, 0, 0) != 0) {
+    strcpy(thread_name, "<name unknown>");
+  } else {
+    // short names are null terminated by prctl, but the man page
+    // implies that 16 byte names are not.
+    thread_name[MAX_TASK_NAME_LEN] = 0;
   }
 
   async_safe_format_log(ANDROID_LOG_FATAL, "libc",
@@ -532,8 +532,13 @@
 
   log_signal_summary(info);
 
+  // If we got here due to the signal BIONIC_SIGNAL_DEBUGGER, it's possible
+  // this is not the main thread, which can cause the intercept logic to fail
+  // since the intercept is only looking for the main thread. In this case,
+  // setting crashing_tid to pid instead of the current thread's tid avoids
+  // the problem.
   debugger_thread_info thread_info = {
-      .crashing_tid = __gettid(),
+      .crashing_tid = (signal_number == BIONIC_SIGNAL_DEBUGGER) ? __getpid() : __gettid(),
       .pseudothread_tid = -1,
       .siginfo = info,
       .ucontext = context,
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 4826ccf..ec66144 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -34,11 +34,13 @@
 #include <time.h>
 #include <unistd.h>
 
+#include <array>
 #include <chrono>
 #include <functional>
 #include <map>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <thread>
 #include <utility>
 #include <vector>
@@ -101,6 +103,7 @@
 using android::base::Realpath;
 using android::base::SetProperty;
 using android::base::StartsWith;
+using android::base::StringPrintf;
 using android::base::Timer;
 using android::base::unique_fd;
 using android::dm::DeviceMapper;
@@ -2044,6 +2047,35 @@
     return 0;
 }
 
+static bool ConfigureIoScheduler(const std::string& device_path) {
+    if (!StartsWith(device_path, "/dev/")) {
+        LERROR << __func__ << ": invalid argument " << device_path;
+        return false;
+    }
+
+    const std::string iosched_path =
+            StringPrintf("/sys/block/%s/queue/scheduler", Basename(device_path).c_str());
+    unique_fd iosched_fd(open(iosched_path.c_str(), O_RDWR | O_CLOEXEC));
+    if (iosched_fd.get() == -1) {
+        PERROR << __func__ << ": failed to open " << iosched_path;
+        return false;
+    }
+
+    // Kernels before v4.1 only support 'noop'. Kernels [v4.1, v5.0) support
+    // 'noop' and 'none'. Kernels v5.0 and later only support 'none'.
+    static constexpr const std::array<std::string_view, 2> kNoScheduler = {"none", "noop"};
+
+    for (const std::string_view& scheduler : kNoScheduler) {
+        int ret = write(iosched_fd.get(), scheduler.data(), scheduler.size());
+        if (ret > 0) {
+            return true;
+        }
+    }
+
+    PERROR << __func__ << ": failed to write to " << iosched_path;
+    return false;
+}
+
 static bool InstallZramDevice(const std::string& device) {
     if (!android::base::WriteStringToFile(device, ZRAM_BACK_DEV)) {
         PERROR << "Cannot write " << device << " in: " << ZRAM_BACK_DEV;
@@ -2071,22 +2103,24 @@
 
     // Allocate loop device and attach it to file_path.
     LoopControl loop_control;
-    std::string device;
-    if (!loop_control.Attach(target_fd.get(), 5s, &device)) {
+    std::string loop_device;
+    if (!loop_control.Attach(target_fd.get(), 5s, &loop_device)) {
         return false;
     }
 
+    ConfigureIoScheduler(loop_device);
+
     // set block size & direct IO
-    unique_fd device_fd(TEMP_FAILURE_RETRY(open(device.c_str(), O_RDWR | O_CLOEXEC)));
-    if (device_fd.get() == -1) {
-        PERROR << "Cannot open " << device;
+    unique_fd loop_fd(TEMP_FAILURE_RETRY(open(loop_device.c_str(), O_RDWR | O_CLOEXEC)));
+    if (loop_fd.get() == -1) {
+        PERROR << "Cannot open " << loop_device;
         return false;
     }
-    if (!LoopControl::EnableDirectIo(device_fd.get())) {
+    if (!LoopControl::EnableDirectIo(loop_fd.get())) {
         return false;
     }
 
-    return InstallZramDevice(device);
+    return InstallZramDevice(loop_device);
 }
 
 bool fs_mgr_swapon_all(const Fstab& fstab) {
@@ -2328,7 +2362,24 @@
         return false;
     }
 
-    auto options = "lowerdir=" + entry.lowerdir;
+    auto lowerdir = entry.lowerdir;
+    if (entry.fs_mgr_flags.overlayfs_remove_missing_lowerdir) {
+        bool removed_any = false;
+        std::vector<std::string> lowerdirs;
+        for (const auto& dir : android::base::Split(entry.lowerdir, ":")) {
+            if (access(dir.c_str(), F_OK)) {
+                PWARNING << __FUNCTION__ << "(): remove missing lowerdir '" << dir << "'";
+                removed_any = true;
+            } else {
+                lowerdirs.push_back(dir);
+            }
+        }
+        if (removed_any) {
+            lowerdir = android::base::Join(lowerdirs, ":");
+        }
+    }
+
+    auto options = "lowerdir=" + lowerdir;
     if (overlayfs_valid_result == OverlayfsValidResult::kOverrideCredsRequired) {
         options += ",override_creds=off";
     }
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index d0c89b9..9b6c3dd 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -181,6 +181,7 @@
         CheckFlag("fsverity", fs_verity);
         CheckFlag("metadata_csum", ext_meta_csum);
         CheckFlag("fscompress", fs_compress);
+        CheckFlag("overlayfs_remove_missing_lowerdir", overlayfs_remove_missing_lowerdir);
 
 #undef CheckFlag
 
@@ -413,17 +414,24 @@
     return fstab_result;
 }
 
-// Identify path to fstab file. Lookup is based on pattern
-// fstab.<fstab_suffix>, fstab.<hardware>, fstab.<hardware.platform> in
-// folders /odm/etc, vendor/etc, or /.
+// Return the path to the fstab file.  There may be multiple fstab files; the
+// one that is returned will be the first that exists of fstab.<fstab_suffix>,
+// fstab.<hardware>, and fstab.<hardware.platform>.  The fstab is searched for
+// in /odm/etc/ and /vendor/etc/, as well as in the locations where it may be in
+// the first stage ramdisk during early boot.  Previously, the first stage
+// ramdisk's copy of the fstab had to be located in the root directory, but now
+// the system/etc directory is supported too and is the preferred location.
 std::string GetFstabPath() {
     for (const char* prop : {"fstab_suffix", "hardware", "hardware.platform"}) {
         std::string suffix;
 
         if (!fs_mgr_get_boot_config(prop, &suffix)) continue;
 
-        for (const char* prefix :
-             {"/odm/etc/fstab.", "/vendor/etc/fstab.", "/fstab.", "/first_stage_ramdisk/fstab."}) {
+        for (const char* prefix : {// late-boot/post-boot locations
+                                   "/odm/etc/fstab.", "/vendor/etc/fstab.",
+                                   // early boot locations
+                                   "/system/etc/fstab.", "/first_stage_ramdisk/system/etc/fstab.",
+                                   "/fstab.", "/first_stage_ramdisk/fstab."}) {
             std::string fstab_path = prefix + suffix;
             if (access(fstab_path.c_str(), F_OK) == 0) {
                 return fstab_path;
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index f33768b..9a4ed46 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -86,6 +86,7 @@
         bool fs_verity : 1;
         bool ext_meta_csum : 1;
         bool fs_compress : 1;
+        bool overlayfs_remove_missing_lowerdir : 1;
     } fs_mgr_flags = {};
 
     bool is_encryptable() const {
diff --git a/fs_mgr/libdm/dm.cpp b/fs_mgr/libdm/dm.cpp
index a5eda29..8e3d5a5 100644
--- a/fs_mgr/libdm/dm.cpp
+++ b/fs_mgr/libdm/dm.cpp
@@ -651,5 +651,61 @@
     return spec.target_type == "snapshot"s && data == "Overflow"s;
 }
 
+// Find directories in format of "/sys/block/dm-X".
+static int DmNameFilter(const dirent* de) {
+    if (android::base::StartsWith(de->d_name, "dm-")) {
+        return 1;
+    }
+    return 0;
+}
+
+std::map<std::string, std::string> DeviceMapper::FindDmPartitions() {
+    static constexpr auto DM_PATH_PREFIX = "/sys/block/";
+    dirent** namelist;
+    int n = scandir(DM_PATH_PREFIX, &namelist, DmNameFilter, alphasort);
+    if (n == -1) {
+        PLOG(ERROR) << "Failed to scan dir " << DM_PATH_PREFIX;
+        return {};
+    }
+    if (n == 0) {
+        LOG(ERROR) << "No dm block device found.";
+        free(namelist);
+        return {};
+    }
+
+    static constexpr auto DM_PATH_SUFFIX = "/dm/name";
+    static constexpr auto DEV_PATH = "/dev/block/";
+    std::map<std::string, std::string> dm_block_devices;
+    while (n--) {
+        std::string path = DM_PATH_PREFIX + std::string(namelist[n]->d_name) + DM_PATH_SUFFIX;
+        std::string content;
+        if (!android::base::ReadFileToString(path, &content)) {
+            PLOG(WARNING) << "Failed to read " << path;
+        } else {
+            std::string dm_block_name = android::base::Trim(content);
+            // AVB is using 'vroot' for the root block device but we're expecting 'system'.
+            if (dm_block_name == "vroot") {
+                dm_block_name = "system";
+            } else if (android::base::EndsWith(dm_block_name, "-verity")) {
+                auto npos = dm_block_name.rfind("-verity");
+                dm_block_name = dm_block_name.substr(0, npos);
+            } else if (!android::base::GetProperty("ro.boot.avb_version", "").empty()) {
+                // Verified Boot 1.0 doesn't add a -verity suffix. On AVB 2 devices,
+                // if DAP is enabled, then a -verity suffix must be used to
+                // differentiate between dm-linear and dm-verity devices. If we get
+                // here, we're AVB 2 and looking at a non-verity partition.
+                free(namelist[n]);
+                continue;
+            }
+
+            dm_block_devices.emplace(dm_block_name, DEV_PATH + std::string(namelist[n]->d_name));
+        }
+        free(namelist[n]);
+    }
+    free(namelist);
+
+    return dm_block_devices;
+}
+
 }  // namespace dm
 }  // namespace android
diff --git a/fs_mgr/libdm/include/libdm/dm.h b/fs_mgr/libdm/include/libdm/dm.h
index 8fcdf74..31c3003 100644
--- a/fs_mgr/libdm/include/libdm/dm.h
+++ b/fs_mgr/libdm/include/libdm/dm.h
@@ -17,6 +17,7 @@
 #ifndef _LIBDM_DM_H_
 #define _LIBDM_DM_H_
 
+#include <dirent.h>
 #include <fcntl.h>
 #include <linux/dm-ioctl.h>
 #include <linux/kdev_t.h>
@@ -26,6 +27,7 @@
 #include <unistd.h>
 
 #include <chrono>
+#include <map>
 #include <memory>
 #include <optional>
 #include <string>
@@ -255,6 +257,12 @@
     //  * A failure occurred.
     std::optional<std::string> GetParentBlockDeviceByPath(const std::string& path);
 
+    // Iterate the content over "/sys/block/dm-x/dm/name" and find
+    // all the dm-wrapped block devices.
+    //
+    // Returns mapping <partition-name, /dev/block/dm-x>
+    std::map<std::string, std::string> FindDmPartitions();
+
   private:
     // Maximum possible device mapper targets registered in the kernel.
     // This is only used to read the list of targets from kernel so we allocate
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 1c44e53..df00f45 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -504,3 +504,13 @@
         "inspect_cow.cpp",
     ],
 }
+
+python_library_host {
+    name: "snapshot_proto_python",
+    srcs: [
+        "android/snapshot/snapshot.proto",
+    ],
+    proto: {
+        canonical_path_from_root: false,
+    },
+}
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index ecfdefe..6066309 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -140,6 +140,85 @@
     ASSERT_TRUE(iter->Done());
 }
 
+TEST_F(CowTest, ReadWriteXor) {
+    CowOptions options;
+    options.cluster_ops = 0;
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_->fd));
+
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+
+    ASSERT_TRUE(writer.AddCopy(10, 20));
+    ASSERT_TRUE(writer.AddXorBlocks(50, data.data(), data.size(), 24, 10));
+    ASSERT_TRUE(writer.AddZeroBlocks(51, 2));
+    ASSERT_TRUE(writer.Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    CowHeader header;
+    CowFooter footer;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    ASSERT_TRUE(reader.GetHeader(&header));
+    ASSERT_TRUE(reader.GetFooter(&footer));
+    ASSERT_EQ(header.magic, kCowMagicNumber);
+    ASSERT_EQ(header.major_version, kCowVersionMajor);
+    ASSERT_EQ(header.minor_version, kCowVersionMinor);
+    ASSERT_EQ(header.block_size, options.block_size);
+    ASSERT_EQ(footer.op.num_ops, 4);
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->Done());
+    auto op = &iter->Get();
+
+    ASSERT_EQ(op->type, kCowCopyOp);
+    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_EQ(op->data_length, 0);
+    ASSERT_EQ(op->new_block, 10);
+    ASSERT_EQ(op->source, 20);
+
+    StringSink sink;
+
+    iter->Next();
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+
+    ASSERT_EQ(op->type, kCowXorOp);
+    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_EQ(op->data_length, 4096);
+    ASSERT_EQ(op->new_block, 50);
+    ASSERT_EQ(op->source, 98314);  // 4096 * 24 + 10
+    ASSERT_TRUE(reader.ReadData(*op, &sink));
+    ASSERT_EQ(sink.stream(), data);
+
+    iter->Next();
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+
+    // Note: the zero operation gets split into two blocks.
+    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_EQ(op->data_length, 0);
+    ASSERT_EQ(op->new_block, 51);
+    ASSERT_EQ(op->source, 0);
+
+    iter->Next();
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+
+    ASSERT_EQ(op->type, kCowZeroOp);
+    ASSERT_EQ(op->compression, kCowCompressNone);
+    ASSERT_EQ(op->data_length, 0);
+    ASSERT_EQ(op->new_block, 52);
+    ASSERT_EQ(op->source, 0);
+
+    iter->Next();
+    ASSERT_TRUE(iter->Done());
+}
+
 TEST_F(CowTest, CompressGz) {
     CowOptions options;
     options.cluster_ops = 0;
@@ -1034,6 +1113,54 @@
     ASSERT_FALSE(reader.Parse(cow_->fd));
 }
 
+TEST_F(CowTest, ResumeSeqOp) {
+    CowOptions options;
+    auto writer = std::make_unique<CowWriter>(options);
+    const int seq_len = 10;
+    uint32_t sequence[seq_len];
+    for (int i = 0; i < seq_len; i++) {
+        sequence[i] = i + 1;
+    }
+
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(writer->AddSequenceData(seq_len, sequence));
+    ASSERT_TRUE(writer->AddZeroBlocks(1, seq_len / 2));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, 1));
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+    auto reader = std::make_unique<CowReader>();
+    ASSERT_TRUE(reader->Parse(cow_->fd, 1));
+    auto itr = reader->GetRevMergeOpIter();
+    ASSERT_TRUE(itr->Done());
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    ASSERT_TRUE(writer->AddZeroBlocks(1 + seq_len / 2, seq_len / 2));
+    ASSERT_TRUE(writer->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    reader = std::make_unique<CowReader>();
+    ASSERT_TRUE(reader->Parse(cow_->fd));
+
+    auto iter = reader->GetRevMergeOpIter();
+
+    uint64_t expected_block = 10;
+    while (!iter->Done() && expected_block > 0) {
+        ASSERT_FALSE(iter->Done());
+        const auto& op = iter->Get();
+
+        ASSERT_EQ(op.new_block, expected_block);
+
+        iter->Next();
+        expected_block--;
+    }
+    ASSERT_EQ(expected_block, 0);
+    ASSERT_TRUE(iter->Done());
+}
+
 TEST_F(CowTest, RevMergeOpItrTest) {
     CowOptions options;
     options.cluster_ops = 5;
diff --git a/fs_mgr/libsnapshot/cow_format.cpp b/fs_mgr/libsnapshot/cow_format.cpp
index 3085f80..8e6bec7 100644
--- a/fs_mgr/libsnapshot/cow_format.cpp
+++ b/fs_mgr/libsnapshot/cow_format.cpp
@@ -37,6 +37,8 @@
         os << "kCowLabelOp,   ";
     else if (op.type == kCowClusterOp)
         os << "kCowClusterOp  ";
+    else if (op.type == kCowXorOp)
+        os << "kCowXorOp      ";
     else if (op.type == kCowSequenceOp)
         os << "kCowSequenceOp ";
     else if (op.type == kCowFooterOp)
@@ -61,7 +63,7 @@
 int64_t GetNextOpOffset(const CowOperation& op, uint32_t cluster_ops) {
     if (op.type == kCowClusterOp) {
         return op.source;
-    } else if (op.type == kCowReplaceOp && cluster_ops == 0) {
+    } else if ((op.type == kCowReplaceOp || op.type == kCowXorOp) && cluster_ops == 0) {
         return op.data_length;
     } else {
         return 0;
@@ -93,6 +95,7 @@
 bool IsOrderedOp(const CowOperation& op) {
     switch (op.type) {
         case kCowCopyOp:
+        case kCowXorOp:
             return true;
         default:
             return false;
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index ace6f59..773d978 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -34,7 +34,11 @@
 namespace android {
 namespace snapshot {
 
-CowReader::CowReader() : fd_(-1), header_(), fd_size_(0) {}
+CowReader::CowReader()
+    : fd_(-1),
+      header_(),
+      fd_size_(0),
+      merge_op_blocks_(std::make_shared<std::vector<uint32_t>>()) {}
 
 static void SHA256(const void*, size_t, uint8_t[]) {
 #if 0
@@ -45,6 +49,23 @@
 #endif
 }
 
+std::unique_ptr<CowReader> CowReader::CloneCowReader() {
+    auto cow = std::make_unique<CowReader>();
+    cow->owned_fd_.reset();
+    cow->header_ = header_;
+    cow->footer_ = footer_;
+    cow->fd_size_ = fd_size_;
+    cow->last_label_ = last_label_;
+    cow->ops_ = ops_;
+    cow->merge_op_blocks_ = merge_op_blocks_;
+    cow->block_map_ = block_map_;
+    cow->num_total_data_ops_ = num_total_data_ops_;
+    cow->num_ordered_ops_to_merge_ = num_ordered_ops_to_merge_;
+    cow->has_seq_ops_ = has_seq_ops_;
+    cow->data_loc_ = data_loc_;
+    return cow;
+}
+
 bool CowReader::InitForMerge(android::base::unique_fd&& fd) {
     owned_fd_ = std::move(fd);
     fd_ = owned_fd_.get();
@@ -133,11 +154,14 @@
     if (!ParseOps(label)) {
         return false;
     }
+    // If we're resuming a write, we're not ready to merge
+    if (label.has_value()) return true;
     return PrepMergeOps();
 }
 
 bool CowReader::ParseOps(std::optional<uint64_t> label) {
     uint64_t pos;
+    auto data_loc = std::make_shared<std::unordered_map<uint64_t, uint64_t>>();
 
     // Skip the scratch space
     if (header_.major_version >= 2 && (header_.buffer_size > 0)) {
@@ -157,6 +181,13 @@
         // Reading a v1 version of COW which doesn't have buffer_size.
         header_.buffer_size = 0;
     }
+    uint64_t data_pos = 0;
+
+    if (header_.cluster_ops) {
+        data_pos = pos + header_.cluster_ops * sizeof(CowOperation);
+    } else {
+        data_pos = pos + sizeof(CowOperation);
+    }
 
     auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
     uint64_t current_op_num = 0;
@@ -177,7 +208,11 @@
         while (current_op_num < ops_buffer->size()) {
             auto& current_op = ops_buffer->data()[current_op_num];
             current_op_num++;
+            if (current_op.type == kCowXorOp) {
+                data_loc->insert({current_op.new_block, data_pos});
+            }
             pos += sizeof(CowOperation) + GetNextOpOffset(current_op, header_.cluster_ops);
+            data_pos += current_op.data_length + GetNextDataOffset(current_op, header_.cluster_ops);
 
             if (current_op.type == kCowClusterOp) {
                 break;
@@ -268,6 +303,7 @@
 
     ops_ = ops_buffer;
     ops_->shrink_to_fit();
+    data_loc_ = data_loc;
 
     return true;
 }
@@ -606,7 +642,13 @@
             return false;
     }
 
-    CowDataStream stream(this, op.source, op.data_length);
+    uint64_t offset;
+    if (op.type == kCowXorOp) {
+        offset = data_loc_->at(op.new_block);
+    } else {
+        offset = op.source;
+    }
+    CowDataStream stream(this, offset, op.data_length);
     decompressor->set_stream(&stream);
     decompressor->set_sink(sink);
     return decompressor->Decompress(header_.block_size);
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index ef30e32..5ce1d3b 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -58,10 +58,24 @@
     return EmitRawBlocks(new_block_start, data, size);
 }
 
-bool ICowWriter::AddXorBlocks(uint32_t /*new_block_start*/, const void* /*data*/, size_t /*size*/,
-                              uint32_t /*old_block*/, uint16_t /*offset*/) {
-    LOG(ERROR) << "AddXorBlocks not yet implemented";
-    return false;
+bool ICowWriter::AddXorBlocks(uint32_t new_block_start, const void* data, size_t size,
+                              uint32_t old_block, uint16_t offset) {
+    if (size % options_.block_size != 0) {
+        LOG(ERROR) << "AddRawBlocks: size " << size << " is not a multiple of "
+                   << options_.block_size;
+        return false;
+    }
+
+    uint64_t num_blocks = size / options_.block_size;
+    uint64_t last_block = new_block_start + num_blocks - 1;
+    if (!ValidateNewBlock(last_block)) {
+        return false;
+    }
+    if (offset >= options_.block_size) {
+        LOG(ERROR) << "AddXorBlocks: offset " << offset << " is not less than "
+                   << options_.block_size;
+    }
+    return EmitXorBlocks(new_block_start, data, size, old_block, offset);
 }
 
 bool ICowWriter::AddZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
@@ -278,13 +292,27 @@
 }
 
 bool CowWriter::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
+    return EmitBlocks(new_block_start, data, size, 0, 0, kCowReplaceOp);
+}
+
+bool CowWriter::EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
+                              uint32_t old_block, uint16_t offset) {
+    return EmitBlocks(new_block_start, data, size, old_block, offset, kCowXorOp);
+}
+
+bool CowWriter::EmitBlocks(uint64_t new_block_start, const void* data, size_t size,
+                           uint64_t old_block, uint16_t offset, uint8_t type) {
     const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
     CHECK(!merge_in_progress_);
     for (size_t i = 0; i < size / header_.block_size; i++) {
         CowOperation op = {};
-        op.type = kCowReplaceOp;
         op.new_block = new_block_start + i;
-        op.source = next_data_pos_;
+        op.type = type;
+        if (type == kCowXorOp) {
+            op.source = (old_block + i) * header_.block_size + offset;
+        } else {
+            op.source = next_data_pos_;
+        }
 
         if (compression_) {
             auto data = Compress(iter, header_.block_size);
diff --git a/fs_mgr/libsnapshot/dm_snapshot_internals.h b/fs_mgr/libsnapshot/dm_snapshot_internals.h
index ed77c15..4a36251 100644
--- a/fs_mgr/libsnapshot/dm_snapshot_internals.h
+++ b/fs_mgr/libsnapshot/dm_snapshot_internals.h
@@ -17,8 +17,9 @@
 #include <android-base/logging.h>
 #include <stdint.h>
 
+#include <limits>
 #include <optional>
-#include <vector>
+#include <unordered_set>
 
 namespace android {
 namespace snapshot {
@@ -37,21 +38,16 @@
             return;
         }
 
-        if (modified_chunks_.size() <= chunk_id) {
-            if (modified_chunks_.max_size() <= chunk_id) {
-                LOG(ERROR) << "Invalid COW size, chunk_id is too large.";
-                valid_ = false;
-                return;
-            }
-            modified_chunks_.resize(chunk_id + 1, false);
-            if (modified_chunks_.size() <= chunk_id) {
-                LOG(ERROR) << "Invalid COW size, chunk_id is too large.";
-                valid_ = false;
-                return;
-            }
+        if (chunk_id > std::numeric_limits<uint32_t>::max()) {
+            LOG(ERROR) << "Chunk exceeds maximum size: " << chunk_id;
+            valid_ = false;
+            return;
+        }
+        if (modified_chunks_.count(chunk_id) > 0) {
+            return;
         }
 
-        modified_chunks_[chunk_id] = true;
+        modified_chunks_.emplace(chunk_id);
     }
 
     std::optional<uint64_t> cow_size_bytes() const {
@@ -91,23 +87,16 @@
             return std::nullopt;
         }
 
-        uint64_t modified_chunks_count = 0;
         uint64_t cow_chunks = 0;
 
-        for (const auto& c : modified_chunks_) {
-            if (c) {
-                ++modified_chunks_count;
-            }
-        }
-
         /* disk header + padding = 1 chunk */
         cow_chunks += 1;
 
         /* snapshot modified chunks */
-        cow_chunks += modified_chunks_count;
+        cow_chunks += modified_chunks_.size();
 
         /* snapshot chunks index metadata */
-        cow_chunks += 1 + modified_chunks_count / exceptions_per_chunk;
+        cow_chunks += 1 + modified_chunks_.size() / exceptions_per_chunk;
 
         return cow_chunks;
     }
@@ -150,30 +139,8 @@
     /*
      * |modified_chunks_| is a container that keeps trace of the modified
      * chunks.
-     * Multiple options were considered when choosing the most appropriate data
-     * structure for this container. Here follows a summary of why vector<bool>
-     * has been chosen, taking as a reference a snapshot partition of 4 GiB and
-     * chunk size of 4 KiB.
-     * - std::set<uint64_t> is very space-efficient for a small number of
-     *   operations, but if the whole snapshot is changed, it would need to
-     *   store
-     *     4 GiB / 4 KiB * (64 bit / 8) = 8 MiB
-     *   just for the data, plus the additional data overhead for the red-black
-     *   tree used for data sorting (if each rb-tree element stores 3 address
-     *   and the word-aligne color, the total size grows to 32 MiB).
-     * - std::bitset<N> is not a good fit because requires a priori knowledge,
-     *   at compile time, of the bitset size.
-     * - std::vector<bool> is a special case of vector, which uses a data
-     *   compression that allows reducing the space utilization of each element
-     *   to 1 bit. In detail, this data structure is composed of a resizable
-     *   array of words, each of them representing a bitmap. On a 64 bit
-     *   device, modifying the whole 4 GiB snapshot grows this container up to
-     *     4 * GiB / 4 KiB / 64 = 64 KiB
-     *   that, even if is the same space requirement to change a single byte at
-     *   the highest address of the snapshot, is a very affordable space
-     *   requirement.
      */
-    std::vector<bool> modified_chunks_;
+    std::unordered_set<uint32_t> modified_chunks_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 464046b..c15682a 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -138,6 +138,8 @@
     // For Label operations, this is the value of the applied label.
     //
     // For Cluster operations, this is the length of the following data region
+    //
+    // For Xor operations, this is the byte location in the source image.
     uint64_t source;
 } __attribute__((packed));
 
@@ -148,6 +150,7 @@
 static constexpr uint8_t kCowZeroOp = 3;
 static constexpr uint8_t kCowLabelOp = 4;
 static constexpr uint8_t kCowClusterOp = 5;
+static constexpr uint8_t kCowXorOp = 6;
 static constexpr uint8_t kCowSequenceOp = 7;
 static constexpr uint8_t kCowFooterOp = -1;
 
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index 6c3059c..0786e82 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -136,6 +136,9 @@
 
     void CloseCowFd() { owned_fd_ = {}; }
 
+    // Creates a clone of the current CowReader without the file handlers
+    std::unique_ptr<CowReader> CloneCowReader();
+
   private:
     bool ParseOps(std::optional<uint64_t> label);
     bool PrepMergeOps();
@@ -153,6 +156,7 @@
     uint64_t num_total_data_ops_;
     uint64_t num_ordered_ops_to_merge_;
     bool has_seq_ops_;
+    std::shared_ptr<std::unordered_map<uint64_t, uint64_t>> data_loc_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index 4a807fb..e17b5c6 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -86,6 +86,8 @@
   protected:
     virtual bool EmitCopy(uint64_t new_block, uint64_t old_block) = 0;
     virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) = 0;
+    virtual bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
+                               uint32_t old_block, uint16_t offset) = 0;
     virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) = 0;
     virtual bool EmitLabel(uint64_t label) = 0;
     virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) = 0;
@@ -122,6 +124,8 @@
   protected:
     virtual bool EmitCopy(uint64_t new_block, uint64_t old_block) override;
     virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
+    virtual bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size,
+                               uint32_t old_block, uint16_t offset) override;
     virtual bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
     virtual bool EmitLabel(uint64_t label) override;
     virtual bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
@@ -129,6 +133,8 @@
   private:
     bool EmitCluster();
     bool EmitClusterIfNeeded();
+    bool EmitBlocks(uint64_t new_block_start, const void* data, size_t size, uint64_t old_block,
+                    uint16_t offset, uint8_t type);
     void SetupHeaders();
     bool ParseOptions();
     bool OpenForWrite();
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
index c00dafa..b09e1ae 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
@@ -74,6 +74,8 @@
   protected:
     bool EmitCopy(uint64_t new_block, uint64_t old_block) override;
     bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
+    bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block,
+                       uint16_t offset) override;
     bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
     bool EmitLabel(uint64_t label) override;
     bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
@@ -102,6 +104,8 @@
   protected:
     bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
     bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override;
+    bool EmitXorBlocks(uint32_t new_block_start, const void* data, size_t size, uint32_t old_block,
+                       uint16_t offset) override;
     bool EmitCopy(uint64_t new_block, uint64_t old_block) override;
     bool EmitLabel(uint64_t label) override;
     bool EmitSequenceData(size_t num_ops, const uint32_t* data) override;
diff --git a/fs_mgr/libsnapshot/scripts/Android.bp b/fs_mgr/libsnapshot/scripts/Android.bp
new file mode 100644
index 0000000..3e09cc3
--- /dev/null
+++ b/fs_mgr/libsnapshot/scripts/Android.bp
@@ -0,0 +1,26 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+python_binary_host {
+    name: "dump_snapshot_proto",
+    main: "dump_snapshot_proto.py",
+    srcs: [
+        "dump_snapshot_proto.py",
+    ],
+    libs: [
+        "snapshot_proto_python",
+    ],
+}
diff --git a/fs_mgr/libsnapshot/scripts/dump_snapshot_proto.py b/fs_mgr/libsnapshot/scripts/dump_snapshot_proto.py
new file mode 100644
index 0000000..566108d
--- /dev/null
+++ b/fs_mgr/libsnapshot/scripts/dump_snapshot_proto.py
@@ -0,0 +1,39 @@
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+
+from android.snapshot import snapshot_pb2
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('type', type = str, help = 'Type (snapshot or update)')
+    parser.add_argument('file', type = str, help = 'Input file')
+    args = parser.parse_args()
+
+    with open(args.file, 'rb') as fp:
+        data = fp.read()
+
+    if args.type == 'snapshot':
+        msg = snapshot_pb2.SnapshotStatus()
+    elif args.type == 'update':
+        msg = snapshot_pb2.SnapshotUpdateStatus()
+    else:
+        raise Exception('Unknown proto type')
+
+    msg.ParseFromString(data)
+    print(msg)
+
+if __name__ == '__main__':
+    main()
diff --git a/fs_mgr/libsnapshot/snapshot_reader.cpp b/fs_mgr/libsnapshot/snapshot_reader.cpp
index 5ee8e25..6546c2a 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader.cpp
@@ -221,7 +221,7 @@
 
   private:
     size_t ignore_start_;
-    char discard_[4096];
+    char discard_[BLOCK_SZ];
 };
 
 ssize_t CompressedSnapshotReader::ReadBlock(uint64_t chunk, IByteSink* sink, size_t start_offset,
@@ -277,6 +277,29 @@
             errno = EIO;
             return -1;
         }
+    } else if (op->type == kCowXorOp) {
+        borrowed_fd fd = GetSourceFd();
+        if (fd < 0) {
+            // GetSourceFd sets errno.
+            return -1;
+        }
+
+        off64_t offset = op->source + start_offset;
+        char data[BLOCK_SZ];
+        if (!android::base::ReadFullyAtOffset(fd, &data, bytes_to_read, offset)) {
+            PLOG(ERROR) << "read " << *source_device_;
+            // ReadFullyAtOffset sets errno.
+            return -1;
+        }
+        PartialSink partial_sink(buffer, bytes_to_read, start_offset);
+        if (!cow_->ReadData(*op, &partial_sink)) {
+            LOG(ERROR) << "CompressedSnapshotReader failed to read xor op";
+            errno = EIO;
+            return -1;
+        }
+        for (size_t i = 0; i < bytes_to_read; i++) {
+            ((char*)buffer)[i] ^= data[i];
+        }
     } else {
         LOG(ERROR) << "CompressedSnapshotReader unknown op type: " << uint32_t(op->type);
         errno = EINVAL;
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
index 9373059..078f16e 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -63,7 +63,9 @@
 
     void WriteCow(ISnapshotWriter* writer) {
         std::string new_block = MakeNewBlockString();
+        std::string xor_block = MakeXorBlockString();
 
+        ASSERT_TRUE(writer->AddXorBlocks(1, xor_block.data(), xor_block.size(), 0, kBlockSize / 2));
         ASSERT_TRUE(writer->AddCopy(3, 0));
         ASSERT_TRUE(writer->AddRawBlocks(5, new_block.data(), new_block.size()));
         ASSERT_TRUE(writer->AddZeroBlocks(7, 2));
@@ -75,7 +77,7 @@
         ASSERT_NE(reader, nullptr);
 
         // Test that unchanged blocks are not modified.
-        std::unordered_set<size_t> changed_blocks = {3, 5, 7, 8};
+        std::unordered_set<size_t> changed_blocks = {1, 3, 5, 7, 8};
         for (size_t i = 0; i < kBlockCount; i++) {
             if (changed_blocks.count(i)) {
                 continue;
@@ -88,6 +90,17 @@
         }
 
         // Test that we can read back our modified blocks.
+        std::string data(kBlockSize, 0);
+        std::string offsetblock = base_blocks_[0].substr(kBlockSize / 2, kBlockSize / 2) +
+                                  base_blocks_[1].substr(0, kBlockSize / 2);
+        ASSERT_EQ(offsetblock.size(), kBlockSize);
+        ASSERT_EQ(reader->Seek(1 * kBlockSize, SEEK_SET), 1 * kBlockSize);
+        ASSERT_EQ(reader->Read(data.data(), data.size()), kBlockSize);
+        for (int i = 0; i < 100; i++) {
+            data[i] = (char)~(data[i]);
+        }
+        ASSERT_EQ(data, offsetblock);
+
         std::string block(kBlockSize, 0);
         ASSERT_EQ(reader->Seek(3 * kBlockSize, SEEK_SET), 3 * kBlockSize);
         ASSERT_EQ(reader->Read(block.data(), block.size()), kBlockSize);
@@ -141,6 +154,12 @@
         return new_block;
     }
 
+    std::string MakeXorBlockString() {
+        std::string data(100, -1);
+        data.resize(kBlockSize, 0);
+        return data;
+    }
+
     std::unique_ptr<TemporaryFile> base_;
     std::unique_ptr<TemporaryFile> cow_;
     std::vector<std::string> base_blocks_;
diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp
index 34b3e87..3eda08e 100644
--- a/fs_mgr/libsnapshot/snapshot_writer.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer.cpp
@@ -106,6 +106,11 @@
     return cow_->AddRawBlocks(new_block_start, data, size);
 }
 
+bool CompressedSnapshotWriter::EmitXorBlocks(uint32_t new_block_start, const void* data,
+                                             size_t size, uint32_t old_block, uint16_t offset) {
+    return cow_->AddXorBlocks(new_block_start, data, size, old_block, offset);
+}
+
 bool CompressedSnapshotWriter::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
     return cow_->AddZeroBlocks(new_block_start, num_blocks);
 }
@@ -157,6 +162,11 @@
     return true;
 }
 
+bool OnlineKernelSnapshotWriter::EmitXorBlocks(uint32_t, const void*, size_t, uint32_t, uint16_t) {
+    LOG(ERROR) << "EmitXorBlocks not implemented.";
+    return false;
+}
+
 bool OnlineKernelSnapshotWriter::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) {
     std::string zeroes(options_.block_size, 0);
     for (uint64_t i = 0; i < num_blocks; i++) {
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index bc97afc..47268d4 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -78,6 +78,7 @@
         "liblog",
         "libsnapshot_cow",
         "libz",
+        "libext4_utils",
     ],
 }
 
@@ -121,6 +122,7 @@
         "libz",
         "libfs_mgr",
         "libdm",
+        "libext4_utils",
     ],
     header_libs: [
         "libstorage_literals_headers",
diff --git a/fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp
index a718328..f4aef44 100644
--- a/fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/cow_snapuserd_test.cpp
@@ -259,7 +259,7 @@
 void CowSnapuserdTest::CreateBaseDevice() {
     unique_fd rnd_fd;
 
-    total_base_size_ = (size_ * 4);
+    total_base_size_ = (size_ * 5);
     base_fd_ = CreateTempFile("base_device", total_base_size_);
     ASSERT_GE(base_fd_, 0);
 
@@ -304,6 +304,11 @@
     offset += size_;
     ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
     ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
+
+    // XOR
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 4), size_), 0);
 }
 
 void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
@@ -428,9 +433,10 @@
     ASSERT_TRUE(writer.Initialize(cow_system_->fd));
 
     size_t num_blocks = size_ / options.block_size;
-    size_t blk_end_copy = num_blocks * 2;
+    size_t blk_end_copy = num_blocks * 3;
     size_t source_blk = num_blocks - 1;
     size_t blk_src_copy = blk_end_copy - 1;
+    uint16_t xor_offset = 5;
 
     size_t x = num_blocks;
     while (1) {
@@ -443,6 +449,11 @@
         blk_src_copy -= 1;
     }
 
+    for (size_t i = num_blocks; i > 0; i--) {
+        ASSERT_TRUE(writer.AddXorBlocks(num_blocks + i - 1,
+                                        &random_buffer_1_.get()[options.block_size * (i - 1)],
+                                        options.block_size, 2 * num_blocks + i - 1, xor_offset));
+    }
     // Flush operations
     ASSERT_TRUE(writer.Finalize());
     // Construct the buffer required for validation
@@ -451,7 +462,11 @@
     ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
               true);
     // Merged Buffer
-    memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + size_, size_);
+    memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_);
+    memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_);
+    for (int i = 0; i < size_; i++) {
+        orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i];
+    }
 }
 
 void CowSnapuserdTest::CreateCowDeviceOrderedOps() {
@@ -473,6 +488,7 @@
 
         offset += 1_MiB;
     }
+    memset(random_buffer_1_.get(), 0, size_);
 
     CowOptions options;
     options.compression = "gz";
@@ -483,7 +499,8 @@
     size_t num_blocks = size_ / options.block_size;
     size_t x = num_blocks;
     size_t source_blk = 0;
-    size_t blk_src_copy = num_blocks;
+    size_t blk_src_copy = 2 * num_blocks;
+    uint16_t xor_offset = 5;
 
     while (1) {
         ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
@@ -496,6 +513,8 @@
         blk_src_copy += 1;
     }
 
+    ASSERT_TRUE(writer.AddXorBlocks(num_blocks, random_buffer_1_.get(), size_, 2 * num_blocks,
+                                    xor_offset));
     // Flush operations
     ASSERT_TRUE(writer.Finalize());
     // Construct the buffer required for validation
@@ -504,7 +523,11 @@
     ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
               true);
     // Merged Buffer
-    memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + size_, size_);
+    memmove(orig_buffer_.get(), (char*)orig_buffer_.get() + 2 * size_, size_);
+    memmove(orig_buffer_.get() + size_, (char*)orig_buffer_.get() + 2 * size_ + xor_offset, size_);
+    for (int i = 0; i < size_; i++) {
+        orig_buffer_.get()[size_ + i] ^= random_buffer_1_.get()[i];
+    }
 }
 
 void CowSnapuserdTest::CreateCowDevice() {
@@ -538,6 +561,17 @@
     size_t source_blk = num_blocks - 1;
     size_t blk_src_copy = blk_end_copy - 1;
 
+    uint32_t sequence[num_blocks * 2];
+    // Sequence for Copy ops
+    for (int i = 0; i < num_blocks; i++) {
+        sequence[i] = num_blocks - 1 - i;
+    }
+    // Sequence for Xor ops
+    for (int i = 0; i < num_blocks; i++) {
+        sequence[num_blocks + i] = 5 * num_blocks - 1 - i;
+    }
+    ASSERT_TRUE(writer.AddSequenceData(2 * num_blocks, sequence));
+
     size_t x = num_blocks;
     while (1) {
         ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
@@ -563,6 +597,11 @@
 
     ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
 
+    size_t blk_xor_start = blk_random2_replace_start + num_blocks;
+    size_t xor_offset = BLOCK_SZ / 2;
+    ASSERT_TRUE(writer.AddXorBlocks(blk_xor_start, random_buffer_1_.get(), size_, num_blocks,
+                                    xor_offset));
+
     // Flush operations
     ASSERT_TRUE(writer.Finalize());
     // Construct the buffer required for validation
@@ -572,6 +611,13 @@
     memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_);
     memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_);
     memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_1_.get(), size_);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, &orig_buffer_.get()[size_ * 4], size_,
+                                               size_ + xor_offset),
+              true);
+    for (int i = 0; i < size_; i++) {
+        orig_buffer_.get()[(size_ * 4) + i] =
+                (uint8_t)(orig_buffer_.get()[(size_ * 4) + i] ^ random_buffer_1_.get()[i]);
+    }
 }
 
 void CowSnapuserdTest::InitCowDevice() {
@@ -1039,6 +1085,35 @@
     }
 }
 
+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/include/snapuserd/snapuserd_kernel.h b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
index 6bb7a39..c592257 100644
--- a/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
+++ b/fs_mgr/libsnapshot/snapuserd/include/snapuserd/snapuserd_kernel.h
@@ -47,8 +47,6 @@
 static constexpr uint32_t CHUNK_SIZE = 8;
 static constexpr uint32_t CHUNK_SHIFT = (__builtin_ffs(CHUNK_SIZE) - 1);
 
-#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d))
-
 // This structure represents the kernel COW header.
 // All the below fields should be in Little Endian format.
 struct disk_header {
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd.cpp
index 31d0221..2abf431 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd.cpp
@@ -16,10 +16,22 @@
 
 #include "snapuserd.h"
 
+#include <dirent.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <unistd.h>
+#include <algorithm>
+
 #include <csignal>
 #include <optional>
 #include <set>
 
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
 #include <snapuserd/snapuserd_client.h>
 
 namespace android {
@@ -53,6 +65,10 @@
     return true;
 }
 
+std::unique_ptr<CowReader> Snapuserd::CloneReaderForWorker() {
+    return reader_->CloneCowReader();
+}
+
 bool Snapuserd::CommitMerge(int num_merge_ops) {
     struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
     ch->num_merge_ops += num_merge_ops;
@@ -334,7 +350,7 @@
     CowHeader header;
     CowOptions options;
     bool metadata_found = false;
-    int replace_ops = 0, zero_ops = 0, copy_ops = 0;
+    int replace_ops = 0, zero_ops = 0, copy_ops = 0, xor_ops = 0;
 
     SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file";
 
@@ -439,12 +455,12 @@
     std::vector<const CowOperation*> vec;
     std::set<uint64_t> dest_blocks;
     std::set<uint64_t> source_blocks;
-    size_t pending_copy_ops = exceptions_per_area_ - num_ops;
-    uint64_t total_copy_ops = reader_->get_num_ordered_ops_to_merge();
+    size_t pending_ordered_ops = exceptions_per_area_ - num_ops;
+    uint64_t total_ordered_ops = reader_->get_num_ordered_ops_to_merge();
 
     SNAP_LOG(DEBUG) << " Processing copy-ops at Area: " << vec_.size()
                     << " Number of replace/zero ops completed in this area: " << num_ops
-                    << " Pending copy ops for this area: " << pending_copy_ops;
+                    << " Pending copy ops for this area: " << pending_ordered_ops;
 
     while (!cowop_rm_iter->Done()) {
         do {
@@ -497,24 +513,34 @@
             // the merge of operations are done based on the ops present
             // in the file.
             //===========================================================
+            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(cow_op->source)) {
+                if (dest_blocks.count(cow_op->new_block) || source_blocks.count(block_source) ||
+                    (block_offset > 0 && source_blocks.count(block_source + 1))) {
                     break;
                 }
             }
             metadata_found = true;
-            pending_copy_ops -= 1;
+            pending_ordered_ops -= 1;
             vec.push_back(cow_op);
-            dest_blocks.insert(cow_op->source);
+            dest_blocks.insert(block_source);
+            if (block_offset > 0) {
+                dest_blocks.insert(block_source + 1);
+            }
             source_blocks.insert(cow_op->new_block);
             prev_id = cow_op->new_block;
             cowop_rm_iter->Next();
-        } while (!cowop_rm_iter->Done() && pending_copy_ops);
+        } while (!cowop_rm_iter->Done() && pending_ordered_ops);
 
         data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
-        SNAP_LOG(DEBUG) << "Batch Merge copy-ops of size: " << vec.size()
+        SNAP_LOG(DEBUG) << "Batch Merge copy-ops/xor-ops of size: " << vec.size()
                         << " Area: " << vec_.size() << " Area offset: " << offset
-                        << " Pending-copy-ops in this area: " << pending_copy_ops;
+                        << " Pending-ordered-ops in this area: " << pending_ordered_ops;
 
         for (size_t i = 0; i < vec.size(); i++) {
             struct disk_exception* de =
@@ -528,13 +554,18 @@
             chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), cow_op));
             offset += sizeof(struct disk_exception);
             num_ops += 1;
-            copy_ops++;
+            if (cow_op->type == kCowCopyOp) {
+                copy_ops++;
+            } else {  // it->second->type == kCowXorOp
+                xor_ops++;
+            }
+
             if (read_ahead_feature_) {
                 read_ahead_ops_.push_back(cow_op);
             }
 
             SNAP_LOG(DEBUG) << num_ops << ":"
-                            << " Copy-op: "
+                            << " Ordered-op: "
                             << " Old-chunk: " << de->old_chunk << " New-chunk: " << de->new_chunk;
 
             if (num_ops == exceptions_per_area_) {
@@ -554,22 +585,22 @@
                     SNAP_LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
                 }
 
-                if (!(pending_copy_ops == 0)) {
-                    SNAP_LOG(ERROR)
-                            << "Invalid pending_copy_ops: expected: 0 found: " << pending_copy_ops;
+                if (!(pending_ordered_ops == 0)) {
+                    SNAP_LOG(ERROR) << "Invalid pending_ordered_ops: expected: 0 found: "
+                                    << pending_ordered_ops;
                     return false;
                 }
-                pending_copy_ops = exceptions_per_area_;
+                pending_ordered_ops = exceptions_per_area_;
             }
 
             data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
-            total_copy_ops -= 1;
+            total_ordered_ops -= 1;
             /*
              * Split the number of ops based on the size of read-ahead buffer
              * region. We need to ensure that kernel doesn't issue IO on blocks
              * which are not read by the read-ahead thread.
              */
-            if (read_ahead_feature_ && (total_copy_ops % num_ra_ops_per_iter == 0)) {
+            if (read_ahead_feature_ && (total_ordered_ops % num_ra_ops_per_iter == 0)) {
                 data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
             }
         }
@@ -598,8 +629,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 << " Areas: " << vec_.size()
-                   << " Num-ops-merged: " << header.num_merge_ops
+                   << " Copy-ops: " << copy_ops << " Xor-ops: " << xor_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
@@ -659,6 +690,74 @@
     return ReadMetadata();
 }
 
+void Snapuserd::ReadBlocksToCache(const std::string& dm_block_device,
+                                  const std::string partition_name, off_t offset, size_t size) {
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
+    if (fd.get() == -1) {
+        SNAP_PLOG(ERROR) << "Error reading " << dm_block_device
+                         << " partition-name: " << partition_name;
+        return;
+    }
+
+    size_t remain = size;
+    off_t file_offset = offset;
+    // We pick 4M I/O size based on the fact that the current
+    // update_verifier has a similar I/O size.
+    size_t read_sz = 1024 * BLOCK_SZ;
+    std::vector<uint8_t> buf(read_sz);
+
+    while (remain > 0) {
+        size_t to_read = std::min(remain, read_sz);
+
+        if (!android::base::ReadFullyAtOffset(fd.get(), buf.data(), to_read, file_offset)) {
+            SNAP_PLOG(ERROR) << "Failed to read block from block device: " << dm_block_device
+                             << " at offset: " << file_offset
+                             << " partition-name: " << partition_name << " total-size: " << size
+                             << " remain_size: " << remain;
+            return;
+        }
+
+        file_offset += to_read;
+        remain -= to_read;
+    }
+
+    SNAP_LOG(INFO) << "Finished reading block-device: " << dm_block_device
+                   << " partition: " << partition_name << " size: " << size
+                   << " offset: " << offset;
+}
+
+void Snapuserd::ReadBlocks(const std::string partition_name, const std::string& dm_block_device) {
+    SNAP_LOG(DEBUG) << "Reading partition: " << partition_name
+                    << " Block-Device: " << dm_block_device;
+
+    uint64_t dev_sz = 0;
+
+    unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY | O_CLOEXEC)));
+    if (fd < 0) {
+        SNAP_LOG(ERROR) << "Cannot open block device";
+        return;
+    }
+
+    dev_sz = get_block_device_size(fd.get());
+    if (!dev_sz) {
+        SNAP_PLOG(ERROR) << "Could not determine block device size: " << dm_block_device;
+        return;
+    }
+
+    int num_threads = 2;
+    size_t num_blocks = dev_sz >> BLOCK_SHIFT;
+    size_t num_blocks_per_thread = num_blocks / num_threads;
+    size_t read_sz_per_thread = num_blocks_per_thread << BLOCK_SHIFT;
+    off_t offset = 0;
+
+    for (int i = 0; i < num_threads; i++) {
+        std::async(std::launch::async, &Snapuserd::ReadBlocksToCache, this, dm_block_device,
+                   partition_name, offset, read_sz_per_thread);
+
+        offset += read_sz_per_thread;
+    }
+}
+
 /*
  * Entry point to launch threads
  */
@@ -687,6 +786,39 @@
                 std::async(std::launch::async, &WorkerThread::RunThread, worker_threads_[i].get()));
     }
 
+    bool second_stage_init = true;
+
+    // We don't want to read the blocks during first stage init.
+    if (android::base::EndsWith(misc_name_, "-init") || is_socket_present_) {
+        second_stage_init = false;
+    }
+
+    if (second_stage_init) {
+        SNAP_LOG(INFO) << "Reading blocks to cache....";
+        auto& dm = DeviceMapper::Instance();
+        auto dm_block_devices = dm.FindDmPartitions();
+        if (dm_block_devices.empty()) {
+            SNAP_LOG(ERROR) << "No dm-enabled block device is found.";
+        } else {
+            auto parts = android::base::Split(misc_name_, "-");
+            std::string partition_name = parts[0];
+
+            const char* suffix_b = "_b";
+            const char* suffix_a = "_a";
+
+            partition_name.erase(partition_name.find_last_not_of(suffix_b) + 1);
+            partition_name.erase(partition_name.find_last_not_of(suffix_a) + 1);
+
+            if (dm_block_devices.find(partition_name) == dm_block_devices.end()) {
+                SNAP_LOG(ERROR) << "Failed to find dm block device for " << partition_name;
+            } else {
+                ReadBlocks(partition_name, dm_block_devices.at(partition_name));
+            }
+        }
+    } else {
+        SNAP_LOG(INFO) << "Not reading block device into cache";
+    }
+
     bool ret = true;
     for (auto& t : threads) {
         ret = t.get() && ret;
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd.h b/fs_mgr/libsnapshot/snapuserd/snapuserd.h
index 95d2f77..b30041d 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd.h
@@ -38,6 +38,7 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
 #include <libdm/dm.h>
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
@@ -107,6 +108,20 @@
     size_t buffer_size_;
 };
 
+class XorSink : public IByteSink {
+  public:
+    void Initialize(BufferSink* sink, size_t size);
+    void Reset();
+    void* GetBuffer(size_t requested, size_t* actual) override;
+    bool ReturnData(void* buffer, size_t len) override;
+
+  private:
+    BufferSink* bufsink_;
+    std::unique_ptr<uint8_t[]> buffer_;
+    size_t buffer_size_;
+    size_t returned_;
+};
+
 class Snapuserd;
 
 class ReadAheadThread {
@@ -116,10 +131,10 @@
     bool RunThread();
 
   private:
-    void InitializeIter();
-    bool IterDone();
-    void IterNext();
-    const CowOperation* GetIterOp();
+    void InitializeRAIter();
+    bool RAIterDone();
+    void RAIterNext();
+    const CowOperation* GetRAOpIter();
     void InitializeBuffer();
 
     bool InitializeFds();
@@ -129,7 +144,7 @@
     }
 
     bool ReadAheadIOStart();
-    void PrepareReadAhead(uint64_t* source_block, int* pending_ops, std::vector<uint64_t>& blocks);
+    void PrepareReadAhead(uint64_t* source_offset, int* pending_ops, std::vector<uint64_t>& blocks);
     bool ReconstructDataFromCow();
     void CheckOverlap(const CowOperation* cow_op);
 
@@ -187,7 +202,9 @@
     // Processing COW operations
     bool ProcessCowOp(const CowOperation* cow_op);
     bool ProcessReplaceOp(const CowOperation* cow_op);
+    // Handles Copy and Xor
     bool ProcessCopyOp(const CowOperation* cow_op);
+    bool ProcessXorOp(const CowOperation* cow_op);
     bool ProcessZeroOp();
 
     bool ReadFromBaseDevice(const CowOperation* cow_op);
@@ -206,6 +223,7 @@
 
     std::unique_ptr<CowReader> reader_;
     BufferSink bufsink_;
+    XorSink xorsink_;
 
     std::string cow_device_;
     std::string backing_store_device_;
@@ -244,6 +262,7 @@
     void* GetExceptionBuffer(size_t i) { return vec_[i].get(); }
 
     bool InitializeWorkers();
+    std::unique_ptr<CowReader> CloneReaderForWorker();
     std::shared_ptr<Snapuserd> GetSharedPtr() { return shared_from_this(); }
 
     std::vector<std::pair<sector_t, const CowOperation*>>& GetChunkVec() { return chunk_vec_; }
@@ -284,6 +303,7 @@
     // Total number of blocks to be merged in a given read-ahead buffer region
     void SetTotalRaBlocksMerged(int x) { total_ra_blocks_merged_ = x; }
     int GetTotalRaBlocksMerged() { return total_ra_blocks_merged_; }
+    void SetSocketPresent(bool socket) { is_socket_present_ = socket; }
 
   private:
     bool IsChunkIdMetadata(chunk_t chunk);
@@ -296,6 +316,10 @@
     bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
     struct BufferState* GetBufferState();
 
+    void ReadBlocks(const std::string partition_name, const std::string& dm_block_device);
+    void ReadBlocksToCache(const std::string& dm_block_device, const std::string partition_name,
+                           off_t offset, size_t size);
+
     std::string cow_device_;
     std::string backing_store_device_;
     std::string control_device_;
@@ -335,6 +359,7 @@
 
     bool merge_initiated_ = false;
     bool attached_ = false;
+    bool is_socket_present_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd.rc b/fs_mgr/libsnapshot/snapuserd/snapuserd.rc
index 02fda8d..2750096 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd.rc
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd.rc
@@ -14,3 +14,6 @@
     user root
     group root system
     seclabel u:r:snapuserd:s0
+
+on property:init.svc.snapuserd=stopped
+    setprop snapuserd.ready false
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
index 81e9228..1ea05a3 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_client.cpp
@@ -42,13 +42,15 @@
 using android::base::unique_fd;
 
 bool EnsureSnapuserdStarted() {
-    if (android::base::GetProperty("init.svc.snapuserd", "") == "running") {
-        return true;
+    if (android::base::GetProperty("init.svc.snapuserd", "") != "running") {
+        android::base::SetProperty("ctl.start", "snapuserd");
+        if (!android::base::WaitForProperty("init.svc.snapuserd", "running", 10s)) {
+            LOG(ERROR) << "Timed out waiting for snapuserd to start.";
+            return false;
+        }
     }
-
-    android::base::SetProperty("ctl.start", "snapuserd");
-    if (!android::base::WaitForProperty("init.svc.snapuserd", "running", 10s)) {
-        LOG(ERROR) << "Timed out waiting for snapuserd to start.";
+    if (!android::base::WaitForProperty("snapuserd.ready", "true", 10s)) {
+        LOG(ERROR) << "Timed out waiting for snapuserd to be ready.";
         return false;
     }
     return true;
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp
index 6fc26a6..b868eed 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_readahead.cpp
@@ -172,24 +172,37 @@
 }
 
 void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
-    if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(cow_op->source)) {
+    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;
     }
 
-    dest_blocks_.insert(cow_op->source);
+    dest_blocks_.insert(source_block);
+    if (source_offset > 0) {
+        dest_blocks_.insert(source_block + 1);
+    }
     source_blocks_.insert(cow_op->new_block);
 }
 
-void ReadAheadThread::PrepareReadAhead(uint64_t* source_block, int* pending_ops,
+void ReadAheadThread::PrepareReadAhead(uint64_t* source_offset, int* pending_ops,
                                        std::vector<uint64_t>& blocks) {
     int num_ops = *pending_ops;
     int nr_consecutive = 0;
 
-    if (!IterDone() && num_ops) {
-        // Get the first block
-        const CowOperation* cow_op = GetIterOp();
-        *source_block = cow_op->source;
-        IterNext();
+    if (!RAIterDone() && num_ops) {
+        // Get the first block with offset
+        const CowOperation* cow_op = GetRAOpIter();
+        *source_offset = cow_op->source;
+        if (cow_op->type == kCowCopyOp) {
+            *source_offset *= BLOCK_SZ;
+        }
+        RAIterNext();
         num_ops -= 1;
         nr_consecutive = 1;
         blocks.push_back(cow_op->new_block);
@@ -201,15 +214,19 @@
         /*
          * Find number of consecutive blocks working backwards.
          */
-        while (!IterDone() && num_ops) {
-            const CowOperation* op = GetIterOp();
-            if (op->source != (*source_block - nr_consecutive)) {
+        while (!RAIterDone() && num_ops) {
+            const CowOperation* op = GetRAOpIter();
+            uint64_t next_offset = op->source;
+            if (cow_op->type == kCowCopyOp) {
+                next_offset *= BLOCK_SZ;
+            }
+            if (next_offset != (*source_offset - nr_consecutive * BLOCK_SZ)) {
                 break;
             }
             nr_consecutive += 1;
             num_ops -= 1;
             blocks.push_back(op->new_block);
-            IterNext();
+            RAIterNext();
 
             if (!overlap_) {
                 CheckOverlap(op);
@@ -247,12 +264,12 @@
     // We are done re-constructing the mapping; however, we need to make sure
     // all the COW operations to-be merged are present in the re-constructed
     // mapping.
-    while (!IterDone()) {
-        const CowOperation* op = GetIterOp();
+    while (!RAIterDone()) {
+        const CowOperation* op = GetRAOpIter();
         if (read_ahead_buffer_map.find(op->new_block) != read_ahead_buffer_map.end()) {
             num_ops -= 1;
             snapuserd_->SetFinalBlockMerged(op->new_block);
-            IterNext();
+            RAIterNext();
         } else {
             // Verify that we have covered all the ops which were re-constructed
             // from COW device - These are the ops which are being
@@ -312,10 +329,10 @@
     source_blocks_.clear();
 
     while (true) {
-        uint64_t source_block;
+        uint64_t source_offset;
         int linear_blocks;
 
-        PrepareReadAhead(&source_block, &num_ops, blocks);
+        PrepareReadAhead(&source_offset, &num_ops, blocks);
         linear_blocks = blocks.size();
         if (linear_blocks == 0) {
             // No more blocks to read
@@ -324,7 +341,7 @@
         }
 
         // Get the first block in the consecutive set of blocks
-        source_block = source_block + 1 - linear_blocks;
+        source_offset = source_offset - (linear_blocks - 1) * BLOCK_SZ;
         size_t io_size = (linear_blocks * BLOCK_SZ);
         num_ops -= linear_blocks;
         total_blocks_merged += linear_blocks;
@@ -358,10 +375,12 @@
         // Read from the base device consecutive set of blocks in one shot
         if (!android::base::ReadFullyAtOffset(backing_store_fd_,
                                               (char*)read_ahead_buffer_ + buffer_offset, io_size,
-                                              source_block * BLOCK_SZ)) {
-            SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
-                             << "at block :" << source_block << " buffer_offset : " << buffer_offset
-                             << " io_size : " << io_size << " buf-addr : " << read_ahead_buffer_;
+                                              source_offset)) {
+            SNAP_PLOG(ERROR) << "Ordered-op failed. Read from backing store: "
+                             << backing_store_device_ << "at block :" << source_offset / BLOCK_SZ
+                             << " offset :" << source_offset % BLOCK_SZ
+                             << " buffer_offset : " << buffer_offset << " io_size : " << io_size
+                             << " buf-addr : " << read_ahead_buffer_;
 
             snapuserd_->ReadAheadIOFailed();
             return false;
@@ -394,10 +413,10 @@
         return false;
     }
 
-    InitializeIter();
+    InitializeRAIter();
     InitializeBuffer();
 
-    while (!IterDone()) {
+    while (!RAIterDone()) {
         if (!ReadAheadIOStart()) {
             return false;
         }
@@ -433,21 +452,21 @@
     return true;
 }
 
-void ReadAheadThread::InitializeIter() {
+void ReadAheadThread::InitializeRAIter() {
     std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
     read_ahead_iter_ = read_ahead_ops.rbegin();
 }
 
-bool ReadAheadThread::IterDone() {
+bool ReadAheadThread::RAIterDone() {
     std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
     return read_ahead_iter_ == read_ahead_ops.rend();
 }
 
-void ReadAheadThread::IterNext() {
+void ReadAheadThread::RAIterNext() {
     read_ahead_iter_++;
 }
 
-const CowOperation* ReadAheadThread::GetIterOp() {
+const CowOperation* ReadAheadThread::GetRAOpIter() {
     return *read_ahead_iter_;
 }
 
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
index a29b19b..2f87557 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_server.cpp
@@ -227,6 +227,7 @@
 void SnapuserdServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
     LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
 
+    handler->snapuserd()->SetSocketPresent(is_socket_present_);
     if (!handler->snapuserd()->Start()) {
         LOG(ERROR) << " Failed to launch all worker threads";
     }
@@ -290,6 +291,15 @@
     }
 
     AddWatchedFd(sockfd_, POLLIN);
+    is_socket_present_ = true;
+
+    // If started in first-stage init, the property service won't be online.
+    if (access("/dev/socket/property_service", F_OK) == 0) {
+        if (!android::base::SetProperty("snapuserd.ready", "true")) {
+            LOG(ERROR) << "Failed to set snapuserd.ready property";
+            return false;
+        }
+    }
 
     LOG(DEBUG) << "Snapuserd server now accepting connections";
     return true;
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd/snapuserd_server.h
index 846f848..3b6ff15 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_server.h
@@ -96,6 +96,7 @@
     bool terminating_;
     volatile bool received_socket_signal_ = false;
     std::vector<struct pollfd> watched_fds_;
+    bool is_socket_present_ = false;
 
     std::mutex lock_;
 
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp
index 13d45fe..cdf9fe7 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_worker.cpp
@@ -71,6 +71,39 @@
     return msg->payload.buf;
 }
 
+void XorSink::Initialize(BufferSink* sink, size_t size) {
+    bufsink_ = sink;
+    buffer_size_ = size;
+    returned_ = 0;
+    buffer_ = std::make_unique<uint8_t[]>(size);
+}
+
+void XorSink::Reset() {
+    returned_ = 0;
+}
+
+void* XorSink::GetBuffer(size_t requested, size_t* actual) {
+    if (requested > buffer_size_) {
+        *actual = buffer_size_;
+    } else {
+        *actual = requested;
+    }
+    return buffer_.get();
+}
+
+bool XorSink::ReturnData(void* buffer, size_t len) {
+    uint8_t* xor_data = reinterpret_cast<uint8_t*>(buffer);
+    uint8_t* buff = reinterpret_cast<uint8_t*>(bufsink_->GetPayloadBuffer(len + returned_));
+    if (buff == nullptr) {
+        return false;
+    }
+    for (size_t i = 0; i < len; i++) {
+        buff[returned_ + i] ^= xor_data[i];
+    }
+    returned_ += len;
+    return true;
+}
+
 WorkerThread::WorkerThread(const std::string& cow_device, const std::string& backing_device,
                            const std::string& control_device, const std::string& misc_name,
                            std::shared_ptr<Snapuserd> snapuserd) {
@@ -105,11 +138,11 @@
 }
 
 bool WorkerThread::InitReader() {
-    reader_ = std::make_unique<CowReader>();
+    reader_ = snapuserd_->CloneReaderForWorker();
+
     if (!reader_->InitForMerge(std::move(cow_fd_))) {
         return false;
     }
-
     return true;
 }
 
@@ -150,10 +183,19 @@
     }
     SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
                     << " Source: " << cow_op->source;
-    if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ,
-                                          cow_op->source * BLOCK_SZ)) {
-        SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
-                         << "at block :" << cow_op->source;
+    uint64_t offset = cow_op->source;
+    if (cow_op->type == kCowCopyOp) {
+        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_
+                         << "at block :" << offset / BLOCK_SZ << " offset:" << offset % BLOCK_SZ;
         return false;
     }
 
@@ -188,6 +230,23 @@
     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);
@@ -219,6 +278,10 @@
             return ProcessCopyOp(cow_op);
         }
 
+        case kCowXorOp: {
+            return ProcessXorOp(cow_op);
+        }
+
         default: {
             SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
         }
@@ -470,10 +533,10 @@
 }
 
 int WorkerThread::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
-                                       int unmerged_exceptions, bool* copy_op, bool* commit) {
+                                       int unmerged_exceptions, bool* ordered_op, bool* commit) {
     int merged_ops_cur_iter = 0;
     std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
-    *copy_op = false;
+    *ordered_op = false;
     std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
 
     // Find the operations which are merged in this cycle.
@@ -511,9 +574,9 @@
             }
             const CowOperation* cow_op = it->second;
 
-            if (snapuserd_->IsReadAheadFeaturePresent() && cow_op->type == kCowCopyOp) {
-                *copy_op = true;
-                // Every single copy operation has to come from read-ahead
+            if (snapuserd_->IsReadAheadFeaturePresent() && IsOrderedOp(*cow_op)) {
+                *ordered_op = true;
+                // Every single ordered operation has to come from read-ahead
                 // cache.
                 if (read_ahead_buffer_map.find(cow_op->new_block) == read_ahead_buffer_map.end()) {
                     SNAP_LOG(ERROR)
@@ -557,7 +620,7 @@
 bool WorkerThread::ProcessMergeComplete(chunk_t chunk, void* buffer) {
     uint32_t stride = exceptions_per_area_ + 1;
     const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec();
-    bool copy_op = false;
+    bool ordered_op = false;
     bool commit = false;
 
     // ChunkID to vector index
@@ -582,7 +645,7 @@
     }
 
     int merged_ops_cur_iter = GetNumberOfMergedOps(buffer, vec[divresult.quot].get(), offset,
-                                                   unmerged_exceptions, &copy_op, &commit);
+                                                   unmerged_exceptions, &ordered_op, &commit);
 
     // There should be at least one operation merged in this cycle
     if (!(merged_ops_cur_iter > 0)) {
@@ -590,7 +653,7 @@
         return false;
     }
 
-    if (copy_op) {
+    if (ordered_op) {
         if (commit) {
             // Push the flushing logic to read-ahead thread so that merge thread
             // can make forward progress. Sync will happen in the background
@@ -819,6 +882,7 @@
 
 bool WorkerThread::RunThread() {
     InitializeBufsink();
+    xorsink_.Initialize(&bufsink_, BLOCK_SZ);
 
     if (!InitializeFds()) {
         return false;
diff --git a/gatekeeperd/Android.bp b/gatekeeperd/Android.bp
index 95e814b..a7f0c0e 100644
--- a/gatekeeperd/Android.bp
+++ b/gatekeeperd/Android.bp
@@ -43,8 +43,8 @@
         "libhidlbase",
         "android.hardware.gatekeeper@1.0",
         "libgatekeeper_aidl",
-        "android.hardware.security.keymint-V1-ndk_platform",
-        "android.security.authorization-ndk_platform",
+        "android.hardware.security.keymint-V1-ndk",
+        "android.security.authorization-ndk",
     ],
 
     static_libs: ["libscrypt_static"],
diff --git a/init/block_dev_initializer.cpp b/init/block_dev_initializer.cpp
index 9c2a7bb..05e00ed 100644
--- a/init/block_dev_initializer.cpp
+++ b/init/block_dev_initializer.cpp
@@ -87,7 +87,13 @@
 
     auto iter = devices->find(name);
     if (iter == devices->end()) {
-        return ListenerAction::kContinue;
+        auto partition_name = DeviceHandler::GetPartitionNameForDevice(uevent.device_name);
+        if (!partition_name.empty()) {
+            iter = devices->find(partition_name);
+        }
+        if (iter == devices->end()) {
+            return ListenerAction::kContinue;
+        }
     }
 
     LOG(VERBOSE) << __PRETTY_FUNCTION__ << ": found partition: " << name;
diff --git a/init/devices.cpp b/init/devices.cpp
index 56c6623..d4a3cb9 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -46,6 +46,7 @@
 using android::base::ReadFileToString;
 using android::base::Readlink;
 using android::base::Realpath;
+using android::base::Split;
 using android::base::StartsWith;
 using android::base::StringPrintf;
 using android::base::Trim;
@@ -187,6 +188,36 @@
     }
 }
 
+std::string DeviceHandler::GetPartitionNameForDevice(const std::string& query_device) {
+    static const auto partition_map = [] {
+        std::vector<std::pair<std::string, std::string>> partition_map;
+        auto parser = [&partition_map](const std::string& key, const std::string& value) {
+            if (key != "androidboot.partition_map") {
+                return;
+            }
+            for (const auto& map : Split(value, ";")) {
+                auto map_pieces = Split(map, ",");
+                if (map_pieces.size() != 2) {
+                    LOG(ERROR) << "Expected a comma separated device,partition mapping, but found '"
+                               << map << "'";
+                    continue;
+                }
+                partition_map.emplace_back(map_pieces[0], map_pieces[1]);
+            }
+        };
+        ImportKernelCmdline(parser);
+        ImportBootconfig(parser);
+        return partition_map;
+    }();
+
+    for (const auto& [device, partition] : partition_map) {
+        if (query_device == device) {
+            return partition;
+        }
+    }
+    return {};
+}
+
 // Given a path that may start with a platform device, find the parent platform device by finding a
 // parent directory with a 'subsystem' symlink that points to the platform bus.
 // If it doesn't start with a platform device, return false
@@ -389,6 +420,10 @@
         // If we don't have a partition name but we are a partition on a boot device, create a
         // symlink of /dev/block/by-name/<device_name> for symmetry.
         links.emplace_back("/dev/block/by-name/" + uevent.device_name);
+        auto partition_name = GetPartitionNameForDevice(uevent.device_name);
+        if (!partition_name.empty()) {
+            links.emplace_back("/dev/block/by-name/" + partition_name);
+        }
     }
 
     auto last_slash = uevent.path.rfind('/');
diff --git a/init/devices.h b/init/devices.h
index d70d746..f9f4d79 100644
--- a/init/devices.h
+++ b/init/devices.h
@@ -122,6 +122,12 @@
 
     std::vector<std::string> GetBlockDeviceSymlinks(const Uevent& uevent) const;
 
+    // `androidboot.partition_map` allows associating a partition name for a raw block device
+    // through a comma separated and semicolon deliminated list. For example,
+    // `androidboot.partition_map=vdb,metadata;vdc,userdata` maps `vdb` to `metadata` and `vdc` to
+    // `userdata`.
+    static std::string GetPartitionNameForDevice(const std::string& device);
+
   private:
     bool FindPlatformDevice(std::string path, std::string* platform_device_path) const;
     std::tuple<mode_t, uid_t, gid_t> GetDevicePermissions(
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 68b21c6..0d9f2c7 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -354,18 +354,3 @@
     defaults: ["libcutils_test_static_defaults"],
     test_config: "KernelLibcutilsTest.xml",
 }
-
-rust_bindgen {
-    name: "libcutils_bindgen",
-    wrapper_src: "rust/cutils.h",
-    crate_name: "cutils_bindgen",
-    source_stem: "bindings",
-    local_include_dirs: ["include"],
-    bindgen_flags: [
-        "--allowlist-function", "multiuser_get_app_id",
-        "--allowlist-function", "multiuser_get_uid",
-        "--allowlist-function", "multiuser_get_user_id",
-        "--allowlist-var", "AID_KEYSTORE",
-        "--allowlist-var", "AID_USER_OFFSET",
-    ],
-}
diff --git a/libcutils/rust/cutils.h b/libcutils/rust/cutils.h
deleted file mode 100644
index 9b78af6..0000000
--- a/libcutils/rust/cutils.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#pragma once
-
-#include <cutils/multiuser.h>
-#include <private/android_filesystem_config.h>
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index 5ca0967..0734f25 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -43,6 +43,7 @@
 using android::base::GetBoolProperty;
 using android::base::StringPrintf;
 using android::base::unique_fd;
+using android::base::WriteStringToFile;
 
 static constexpr const char* CGROUP_PROCS_FILE = "/cgroup.procs";
 static constexpr const char* CGROUP_TASKS_FILE = "/tasks";
@@ -202,3 +203,21 @@
 
     return CgroupController(nullptr);
 }
+
+int CgroupMap::ActivateControllers(const std::string& path) const {
+    if (__builtin_available(android 30, *)) {
+        auto controller_count = ACgroupFile_getControllerCount();
+        for (uint32_t i = 0; i < controller_count; ++i) {
+            const ACgroupController* controller = ACgroupFile_getController(i);
+            if (ACgroupController_getFlags(controller) &
+                CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION) {
+                std::string str = std::string("+") + ACgroupController_getName(controller);
+                if (!WriteStringToFile(str, path + "/cgroup.subtree_control")) {
+                    return -errno;
+                }
+            }
+        }
+        return 0;
+    }
+    return -ENOSYS;
+}
diff --git a/libprocessgroup/cgroup_map.h b/libprocessgroup/cgroup_map.h
index 427d71b..22d717b 100644
--- a/libprocessgroup/cgroup_map.h
+++ b/libprocessgroup/cgroup_map.h
@@ -62,6 +62,7 @@
 
     static CgroupMap& GetInstance();
     CgroupController FindController(const std::string& name) const;
+    int ActivateControllers(const std::string& path) const;
 
   private:
     bool loaded_ = false;
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index c824376..faf945c 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -416,13 +416,15 @@
     return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/, max_processes);
 }
 
-static int createProcessGroupInternal(uid_t uid, int initialPid, std::string cgroup) {
+static int createProcessGroupInternal(uid_t uid, int initialPid, std::string cgroup,
+                                      bool activate_controllers) {
     auto uid_path = ConvertUidToPath(cgroup.c_str(), uid);
 
     struct stat cgroup_stat;
     mode_t cgroup_mode = 0750;
     gid_t cgroup_uid = AID_SYSTEM;
     uid_t cgroup_gid = AID_SYSTEM;
+    int ret = 0;
 
     if (stat(cgroup.c_str(), &cgroup_stat) == 1) {
         PLOG(ERROR) << "Failed to get stats for " << cgroup;
@@ -436,6 +438,13 @@
         PLOG(ERROR) << "Failed to make and chown " << uid_path;
         return -errno;
     }
+    if (activate_controllers) {
+        ret = CgroupMap::GetInstance().ActivateControllers(uid_path);
+        if (ret) {
+            LOG(ERROR) << "Failed to activate controllers in " << uid_path;
+            return ret;
+        }
+    }
 
     auto uid_pid_path = ConvertUidPidToPath(cgroup.c_str(), uid, initialPid);
 
@@ -446,7 +455,6 @@
 
     auto uid_pid_procs_file = uid_pid_path + PROCESSGROUP_CGROUP_PROCS_FILE;
 
-    int ret = 0;
     if (!WriteStringToFile(std::to_string(initialPid), uid_pid_procs_file)) {
         ret = -errno;
         PLOG(ERROR) << "Failed to write '" << initialPid << "' to " << uid_pid_procs_file;
@@ -466,14 +474,14 @@
     if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
         CgroupGetControllerPath("memory", &cgroup);
         cgroup += "/apps";
-        int ret = createProcessGroupInternal(uid, initialPid, cgroup);
+        int ret = createProcessGroupInternal(uid, initialPid, cgroup, false);
         if (ret != 0) {
             return ret;
         }
     }
 
     CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cgroup);
-    return createProcessGroupInternal(uid, initialPid, cgroup);
+    return createProcessGroupInternal(uid, initialPid, cgroup, true);
 }
 
 static bool SetProcessGroupValue(int tid, const std::string& attr_name, int64_t value) {
diff --git a/trusty/keymaster/Android.bp b/trusty/keymaster/Android.bp
index ff6460d..155363c 100644
--- a/trusty/keymaster/Android.bp
+++ b/trusty/keymaster/Android.bp
@@ -106,9 +106,9 @@
         "keymint/service.cpp",
     ],
     shared_libs: [
-        "android.hardware.security.keymint-V1-ndk_platform",
-        "android.hardware.security.secureclock-V1-ndk_platform",
-        "android.hardware.security.sharedsecret-V1-ndk_platform",
+        "android.hardware.security.keymint-V1-ndk",
+        "android.hardware.security.secureclock-V1-ndk",
+        "android.hardware.security.sharedsecret-V1-ndk",
         "lib_android_keymaster_keymint_utils",
         "libbase",
         "libbinder_ndk",
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index 5b83e21..2fde30f 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -477,7 +477,6 @@
     if (ssdir_fd < 0) {
         ALOGE("failed to open ss root dir \"%s\": %s\n",
                dirname, strerror(errno));
-        return -1;
     }
     ssdir_name = dirname;
     return 0;