Merge "Use Apex sepolicy if it's available"
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 5bb1d75..967b942 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -502,15 +502,24 @@
         continue;
       }
 
-      struct iovec iov = {
+      struct iovec tagged_addr_iov = {
           &info.tagged_addr_ctrl,
           sizeof(info.tagged_addr_ctrl),
       };
       if (ptrace(PTRACE_GETREGSET, thread, NT_ARM_TAGGED_ADDR_CTRL,
-                 reinterpret_cast<void*>(&iov)) == -1) {
+                 reinterpret_cast<void*>(&tagged_addr_iov)) == -1) {
         info.tagged_addr_ctrl = -1;
       }
 
+      struct iovec pac_enabled_keys_iov = {
+          &info.pac_enabled_keys,
+          sizeof(info.pac_enabled_keys),
+      };
+      if (ptrace(PTRACE_GETREGSET, thread, NT_ARM_PAC_ENABLED_KEYS,
+                 reinterpret_cast<void*>(&pac_enabled_keys_iov)) == -1) {
+        info.pac_enabled_keys = -1;
+      }
+
       if (thread == g_target_thread) {
         // Read the thread's registers along with the rest of the crash info out of the pipe.
         ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info);
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index b107767..2cf5b18 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -173,6 +173,14 @@
   *status = response.status;
 }
 
+static bool pac_supported() {
+#if defined(__aarch64__)
+  return getauxval(AT_HWCAP) & HWCAP_PACA;
+#else
+  return false;
+#endif
+}
+
 class CrasherTest : public ::testing::Test {
  public:
   pid_t crasher_pid = -1;
@@ -357,6 +365,12 @@
     ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff3)"
                          R"( \(PR_TAGGED_ADDR_ENABLE, PR_MTE_TCF_SYNC, mask 0xfffe\))");
   }
+
+  if (pac_supported()) {
+    // Test that the default PAC_ENABLED_KEYS value is set.
+    ASSERT_MATCH(result, R"(pac_enabled_keys: 000000000000000f)"
+                         R"( \(PR_PAC_APIAKEY, PR_PAC_APIBKEY, PR_PAC_APDAKEY, PR_PAC_APDBKEY\))");
+  }
 }
 
 TEST_F(CrasherTest, tagged_fault_addr) {
diff --git a/debuggerd/libdebuggerd/gwp_asan.cpp b/debuggerd/libdebuggerd/gwp_asan.cpp
index 3ee309f..ed2b974 100644
--- a/debuggerd/libdebuggerd/gwp_asan.cpp
+++ b/debuggerd/libdebuggerd/gwp_asan.cpp
@@ -147,7 +147,7 @@
   for (size_t i = 0; i != num_frames; ++i) {
     unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
     BacktraceFrame* f = heap_object->add_allocation_backtrace();
-    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+    fill_in_backtrace_frame(f, frame_data);
   }
 
   heap_object->set_deallocation_tid(__gwp_asan_get_deallocation_thread_id(responsible_allocation_));
@@ -156,7 +156,7 @@
   for (size_t i = 0; i != num_frames; ++i) {
     unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
     BacktraceFrame* f = heap_object->add_deallocation_backtrace();
-    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+    fill_in_backtrace_frame(f, frame_data);
   }
 
   set_human_readable_cause(cause, crash_address_);
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
index 2331f1e..7bf1688 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
@@ -37,7 +37,6 @@
 
 namespace unwindstack {
 struct FrameData;
-class Maps;
 class Unwinder;
 }
 
@@ -68,8 +67,7 @@
     const Tombstone& tombstone,
     std::function<void(const std::string& line, bool should_log)> callback);
 
-void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame,
-                             unwindstack::Maps* maps);
+void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame);
 void set_human_readable_cause(Cause* cause, uint64_t fault_addr);
 
 #endif  // _DEBUGGERD_TOMBSTONE_H
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
index 086dc97..a51e276 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -25,6 +25,7 @@
 struct ThreadInfo {
   std::unique_ptr<unwindstack::Regs> registers;
   long tagged_addr_ctrl = -1;
+  long pac_enabled_keys = -1;
 
   pid_t uid;
 
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 002321f..63e142f 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -1,22 +1,20 @@
-/* system/debuggerd/utility.h
-**
-** Copyright 2008, 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.
-*/
+/*
+ * Copyright 2008, 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.
+ */
 
-#ifndef _DEBUGGERD_UTILITY_H
-#define _DEBUGGERD_UTILITY_H
+#pragma once
 
 #include <inttypes.h>
 #include <signal.h>
@@ -93,6 +91,7 @@
 const char* get_signame(const siginfo_t*);
 const char* get_sigcode(const siginfo_t*);
 std::string describe_tagged_addr_ctrl(long ctrl);
+std::string describe_pac_enabled_keys(long keys);
 
 // Number of bytes per MTE granule.
 constexpr size_t kTagGranuleSize = 16;
@@ -100,5 +99,3 @@
 // Number of rows and columns to display in an MTE tag dump.
 constexpr size_t kNumTagColumns = 16;
 constexpr size_t kNumTagRows = 16;
-
-#endif // _DEBUGGERD_UTILITY_H
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index a89f385..a2933f2 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -108,7 +108,7 @@
   for (size_t i = 0; i < arraysize(report->allocation_trace) && report->allocation_trace[i]; ++i) {
     unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(report->allocation_trace[i]);
     BacktraceFrame* f = heap_object->add_allocation_backtrace();
-    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+    fill_in_backtrace_frame(f, frame_data);
   }
 
   heap_object->set_deallocation_tid(report->deallocation_tid);
@@ -117,7 +117,7 @@
     unwindstack::FrameData frame_data =
         unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]);
     BacktraceFrame* f = heap_object->add_deallocation_backtrace();
-    fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps());
+    fill_in_backtrace_frame(f, frame_data);
   }
 
   set_human_readable_cause(cause, untagged_fault_addr_);
diff --git a/debuggerd/libdebuggerd/test/utility_test.cpp b/debuggerd/libdebuggerd/test/utility_test.cpp
index 97328b7..dad3380 100644
--- a/debuggerd/libdebuggerd/test/utility_test.cpp
+++ b/debuggerd/libdebuggerd/test/utility_test.cpp
@@ -31,3 +31,12 @@
       describe_tagged_addr_ctrl(0xf0000000 | PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
                                 PR_MTE_TCF_ASYNC | (0xfffe << PR_MTE_TAG_SHIFT)));
 }
+
+TEST(UtilityTest, describe_pac_enabled_keys) {
+  EXPECT_EQ("", describe_pac_enabled_keys(0));
+  EXPECT_EQ(" (PR_PAC_APIAKEY)", describe_pac_enabled_keys(PR_PAC_APIAKEY));
+  EXPECT_EQ(" (PR_PAC_APIAKEY, PR_PAC_APDBKEY)",
+            describe_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY));
+  EXPECT_EQ(" (PR_PAC_APIAKEY, PR_PAC_APDBKEY, unknown 0x1000)",
+            describe_pac_enabled_keys(PR_PAC_APIAKEY | PR_PAC_APDBKEY | 0x1000));
+}
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index b1c4ef3..b7d5bc4 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -312,8 +312,7 @@
   }
 }
 
-void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame,
-                             unwindstack::Maps* maps) {
+void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame) {
   f->set_rel_pc(frame.rel_pc);
   f->set_pc(frame.pc);
   f->set_sp(frame.sp);
@@ -331,21 +330,20 @@
 
   f->set_function_offset(frame.function_offset);
 
-  if (frame.map_start == frame.map_end) {
+  if (frame.map_info == nullptr) {
     // No valid map associated with this frame.
     f->set_file_name("<unknown>");
-  } else if (!frame.map_name.empty()) {
-    f->set_file_name(frame.map_name);
+    return;
+  }
+
+  if (!frame.map_info->name().empty()) {
+    f->set_file_name(frame.map_info->GetFullName());
   } else {
-    f->set_file_name(StringPrintf("<anonymous:%" PRIx64 ">", frame.map_start));
+    f->set_file_name(StringPrintf("<anonymous:%" PRIx64 ">", frame.map_info->start()));
   }
+  f->set_file_map_offset(frame.map_info->elf_start_offset());
 
-  f->set_file_map_offset(frame.map_elf_start_offset);
-
-  auto map_info = maps->Find(frame.map_start);
-  if (map_info.get() != nullptr) {
-    f->set_build_id(map_info->GetPrintableBuildID());
-  }
+  f->set_build_id(frame.map_info->GetPrintableBuildID());
 }
 
 static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
@@ -355,6 +353,7 @@
   thread.set_id(thread_info.tid);
   thread.set_name(thread_info.thread_name);
   thread.set_tagged_addr_ctrl(thread_info.tagged_addr_ctrl);
+  thread.set_pac_enabled_keys(thread_info.pac_enabled_keys);
 
   unwindstack::Maps* maps = unwinder->GetMaps();
   unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
@@ -434,7 +433,7 @@
     unwinder->SetDisplayBuildID(true);
     for (const auto& frame : unwinder->frames()) {
       BacktraceFrame* f = thread.add_current_backtrace();
-      fill_in_backtrace_frame(f, frame, maps);
+      fill_in_backtrace_frame(f, frame);
     }
   }
 
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index de86b0a..0265641 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -85,6 +85,10 @@
     CB(should_log, "tagged_addr_ctrl: %016" PRIx64 "%s", thread.tagged_addr_ctrl(),
        describe_tagged_addr_ctrl(thread.tagged_addr_ctrl()).c_str());
   }
+  if (thread.pac_enabled_keys() != -1) {
+    CB(should_log, "pac_enabled_keys: %016" PRIx64 "%s", thread.pac_enabled_keys(),
+       describe_pac_enabled_keys(thread.pac_enabled_keys()).c_str());
+  }
 }
 
 static void print_register_row(CallbackType callback, int word_size,
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 71f0c09..543a67c 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -446,31 +446,40 @@
   return "?";
 }
 
-std::string describe_tagged_addr_ctrl(long ctrl) {
+#define DESCRIBE_FLAG(flag) \
+  if (value & flag) {       \
+    desc += ", ";           \
+    desc += #flag;          \
+    value &= ~flag;         \
+  }
+
+static std::string describe_end(long value, std::string& desc) {
+  if (value) {
+    desc += StringPrintf(", unknown 0x%lx", value);
+  }
+  return desc.empty() ? "" : " (" + desc.substr(2) + ")";
+}
+
+std::string describe_tagged_addr_ctrl(long value) {
   std::string desc;
-  if (ctrl & PR_TAGGED_ADDR_ENABLE) {
-    desc += ", PR_TAGGED_ADDR_ENABLE";
-    ctrl &= ~PR_TAGGED_ADDR_ENABLE;
+  DESCRIBE_FLAG(PR_TAGGED_ADDR_ENABLE);
+  DESCRIBE_FLAG(PR_MTE_TCF_SYNC);
+  DESCRIBE_FLAG(PR_MTE_TCF_ASYNC);
+  if (value & PR_MTE_TAG_MASK) {
+    desc += StringPrintf(", mask 0x%04lx", (value & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT);
+    value &= ~PR_MTE_TAG_MASK;
   }
-  if (ctrl & PR_MTE_TCF_SYNC) {
-    desc += ", PR_MTE_TCF_SYNC";
-    ctrl &= ~PR_MTE_TCF_SYNC;
-  }
-  if (ctrl & PR_MTE_TCF_ASYNC) {
-    desc += ", PR_MTE_TCF_ASYNC";
-    ctrl &= ~PR_MTE_TCF_ASYNC;
-  }
-  if (ctrl & PR_MTE_TAG_MASK) {
-    desc += StringPrintf(", mask 0x%04lx", (ctrl & PR_MTE_TAG_MASK) >> PR_MTE_TAG_SHIFT);
-    ctrl &= ~PR_MTE_TAG_MASK;
-  }
-  if (ctrl) {
-    desc += StringPrintf(", unknown 0x%lx", ctrl);
-  }
-  if (desc.empty()) {
-    return "";
-  }
-  return " (" + desc.substr(2) + ")";
+  return describe_end(value, desc);
+}
+
+std::string describe_pac_enabled_keys(long value) {
+  std::string desc;
+  DESCRIBE_FLAG(PR_PAC_APIAKEY);
+  DESCRIBE_FLAG(PR_PAC_APIBKEY);
+  DESCRIBE_FLAG(PR_PAC_APDAKEY);
+  DESCRIBE_FLAG(PR_PAC_APDBKEY);
+  DESCRIBE_FLAG(PR_PAC_APGAKEY);
+  return describe_end(value, desc);
 }
 
 void log_backtrace(log_t* log, unwindstack::Unwinder* unwinder, const char* prefix) {
diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto
index a701212..40a942e 100644
--- a/debuggerd/proto/tombstone.proto
+++ b/debuggerd/proto/tombstone.proto
@@ -126,8 +126,9 @@
   repeated BacktraceFrame current_backtrace = 4;
   repeated MemoryDump memory_dump = 5;
   int64 tagged_addr_ctrl = 6;
+  int64 pac_enabled_keys = 8;
 
-  reserved 8 to 999;
+  reserved 9 to 999;
 }
 
 message BacktraceFrame {
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 708a677..9ae2c37 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -15,7 +15,10 @@
 // This is required because no Android.bp can include a library defined in an
 // Android.mk. Eventually should kill libfastboot (defined in Android.mk)
 package {
-    default_applicable_licenses: ["system_core_fastboot_license"],
+    default_applicable_licenses: [
+        "system_core_fastboot_license",
+        "Android-Apache-2.0",
+    ],
 }
 
 // Added automatically by a large-scale-change that took the approach of
@@ -36,10 +39,9 @@
     name: "system_core_fastboot_license",
     visibility: [":__subpackages__"],
     license_kinds: [
-        "SPDX-license-identifier-Apache-2.0",
         "SPDX-license-identifier-BSD",
     ],
-    // large-scale-change unable to identify any license_text files
+    license_text: ["LICENSE"],
 }
 
 cc_library_host_static {
diff --git a/fastboot/Android.mk b/fastboot/Android.mk
index 322fe5c..10bed6d 100644
--- a/fastboot/Android.mk
+++ b/fastboot/Android.mk
@@ -23,5 +23,5 @@
 my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs
 my_dist_files += $(HOST_OUT_EXECUTABLES)/make_f2fs_casefold
 my_dist_files += $(HOST_OUT_EXECUTABLES)/sload_f2fs
-$(call dist-for-goals,dist_files sdk win_sdk,$(my_dist_files))
+$(call dist-for-goals,dist_files sdk,$(my_dist_files))
 my_dist_files :=
diff --git a/fastboot/LICENSE b/fastboot/LICENSE
new file mode 100644
index 0000000..f0a0e52
--- /dev/null
+++ b/fastboot/LICENSE
@@ -0,0 +1,23 @@
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+  * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in
+    the documentation and/or other materials provided with the
+    distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 7bef72a..44dc81f 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -119,9 +119,11 @@
 }
 
 int FlashSparseData(int fd, std::vector<char>& downloaded_data) {
-    struct sparse_file* file = sparse_file_import_buf(downloaded_data.data(), true, false);
+    struct sparse_file* file = sparse_file_import_buf(downloaded_data.data(),
+                                                      downloaded_data.size(), true, false);
     if (!file) {
-        return -ENOENT;
+        // Invalid sparse format
+        return -EINVAL;
     }
     return sparse_file_callback(file, false, false, WriteCallback, reinterpret_cast<void*>(fd));
 }
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index af8c502..fde1dab 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -144,7 +144,7 @@
     { "init_boot",
                   "init_boot.img",    "init_boot.sig",
                                                       "init_boot",
-                                                                  false, ImageType::BootCritical },
+                                                                  true,  ImageType::BootCritical },
     { nullptr,    "boot_other.img",   "boot.sig",     "boot",     true,  ImageType::Normal },
     { "cache",    "cache.img",        "cache.sig",    "cache",    true,  ImageType::Extra },
     { "dtbo",     "dtbo.img",         "dtbo.sig",     "dtbo",     true,  ImageType::BootCritical },
diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp
index 8593adc..074306b 100644
--- a/fastboot/fuzzy_fastboot/main.cpp
+++ b/fastboot/fuzzy_fastboot/main.cpp
@@ -874,6 +874,12 @@
             << "Device did not respond with FAIL for malformed download command '" << cmd << "'";
 }
 
+TEST_F(Fuzz, DownloadInvalid9) {
+    std::string cmd("download:2PPPPPPPPPPPPPPPPPPPPPPPPPPPPPP");
+    EXPECT_EQ(fb->RawCommand(cmd), DEVICE_FAIL)
+            << "Device did not respond with FAIL for malformed download command '" << cmd << "'";
+}
+
 TEST_F(Fuzz, GetVarAllSpam) {
     auto start = std::chrono::high_resolution_clock::now();
     std::chrono::duration<double> elapsed;
@@ -977,6 +983,80 @@
     }
 }
 
+TEST_F(Fuzz, SparseZeroBlkSize) {
+    // handcrafted malform sparse file with zero as block size
+    const std::vector<char> buf = {
+        '\x3a', '\xff', '\x26', '\xed', '\x01', '\x00', '\x00', '\x00', '\x1c', '\x00', '\x0c', '\x00',
+        '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00',
+        '\x00', '\x00', '\x00', '\x00', '\xc2', '\xca', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00',
+        '\x10', '\x00', '\x00', '\x00', '\x11', '\x22', '\x33', '\x44'
+    };
+
+    ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command";
+    ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed";
+
+    // It can either reject this download or reject it during flash
+    if (HandleResponse() != DEVICE_FAIL) {
+        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
+                << "Flashing a zero block size in sparse file should fail";
+    }
+}
+
+TEST_F(Fuzz, SparseVeryLargeBlkSize) {
+    // handcrafted sparse file with block size of ~4GB and divisible 4
+    const std::vector<char> buf = {
+        '\x3a', '\xff', '\x26', '\xed', '\x01', '\x00', '\x00', '\x00',
+        '\x1c', '\x00', '\x0c', '\x00', '\xF0', '\xFF', '\xFF', '\xFF',
+        '\x01', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00',
+        '\x00', '\x00', '\x00', '\x00', '\xc3', '\xca', '\x00', '\x00',
+        '\x01', '\x00', '\x00', '\x00', '\x0c', '\x00', '\x00', '\x00',
+        '\x11', '\x22', '\x33', '\x44'
+    };
+
+    ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command";
+    ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed";
+    ASSERT_EQ(HandleResponse(), SUCCESS) << "Not receive okay";
+    ASSERT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed";
+}
+
+TEST_F(Fuzz, SparseTrimmed) {
+    // handcrafted malform sparse file which is trimmed
+    const std::vector<char> buf = {
+        '\x3a', '\xff', '\x26', '\xed', '\x01', '\x00', '\x00', '\x00', '\x1c', '\x00', '\x0c', '\x00',
+        '\x00', '\x10', '\x00', '\x00', '\x00', '\x00', '\x08', '\x00', '\x01', '\x00', '\x00', '\x00',
+        '\x00', '\x00', '\x00', '\x00', '\xc1', '\xca', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00',
+        '\x00', '\x00', '\x00', '\x80', '\x11', '\x22', '\x33', '\x44'
+    };
+
+    ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command";
+    ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed";
+
+    // It can either reject this download or reject it during flash
+    if (HandleResponse() != DEVICE_FAIL) {
+        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
+                << "Flashing a trimmed sparse file should fail";
+    }
+}
+
+TEST_F(Fuzz, SparseInvalidChurk) {
+    // handcrafted malform sparse file with invalid churk
+    const std::vector<char> buf = {
+        '\x3a', '\xff', '\x26', '\xed', '\x01', '\x00', '\x00', '\x00', '\x1c', '\x00', '\x0c', '\x00',
+        '\x00', '\x10', '\x00', '\x00', '\x00', '\x00', '\x08', '\x00', '\x01', '\x00', '\x00', '\x00',
+        '\x00', '\x00', '\x00', '\x00', '\xc1', '\xca', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00',
+        '\x10', '\x00', '\x00', '\x00', '\x11', '\x22', '\x33', '\x44'
+    };
+
+    ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command";
+    ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed";
+
+    // It can either reject this download or reject it during flash
+    if (HandleResponse() != DEVICE_FAIL) {
+        EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL)
+                << "Flashing a sparse file with invalid churk should fail";
+    }
+}
+
 TEST_F(Fuzz, SparseTooManyChunks) {
     SparseWrapper sparse(4096, 4096);  // 1 block, but we send two chunks that will use 2 blocks
     ASSERT_TRUE(*sparse) << "Sparse image creation failed";
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 33dca58..8ce961b 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -170,6 +170,22 @@
             FS_STAT_SET_RESERVED_BLOCKS_FAILED | FS_STAT_ENABLE_ENCRYPTION_FAILED);
 }
 
+static bool umount_retry(const std::string& mount_point) {
+    int retry_count = 5;
+    bool umounted = false;
+
+    while (retry_count-- > 0) {
+        umounted = umount(mount_point.c_str()) == 0;
+        if (umounted) {
+            LINFO << __FUNCTION__ << "(): unmount(" << mount_point << ") succeeded";
+            break;
+        }
+        PERROR << __FUNCTION__ << "(): umount(" << mount_point << ") failed";
+        if (retry_count) sleep(1);
+    }
+    return umounted;
+}
+
 static void check_fs(const std::string& blk_device, const std::string& fs_type,
                      const std::string& target, int* fs_stat) {
     int status;
@@ -209,25 +225,12 @@
                         tmpmnt_opts.c_str());
             PINFO << __FUNCTION__ << "(): mount(" << blk_device << "," << target << "," << fs_type
                   << ")=" << ret;
-            if (!ret) {
-                bool umounted = false;
-                int retry_count = 5;
-                while (retry_count-- > 0) {
-                    umounted = umount(target.c_str()) == 0;
-                    if (umounted) {
-                        LINFO << __FUNCTION__ << "(): unmount(" << target << ") succeeded";
-                        break;
-                    }
-                    PERROR << __FUNCTION__ << "(): umount(" << target << ") failed";
-                    if (retry_count) sleep(1);
-                }
-                if (!umounted) {
-                    // boot may fail but continue and leave it to later stage for now.
-                    PERROR << __FUNCTION__ << "(): umount(" << target << ") timed out";
-                    *fs_stat |= FS_STAT_RO_UNMOUNT_FAILED;
-                }
-            } else {
+            if (ret) {
                 *fs_stat |= FS_STAT_RO_MOUNT_FAILED;
+            } else if (!umount_retry(target)) {
+                // boot may fail but continue and leave it to later stage for now.
+                PERROR << __FUNCTION__ << "(): umount(" << target << ") timed out";
+                *fs_stat |= FS_STAT_RO_UNMOUNT_FAILED;
             }
         }
 
@@ -268,12 +271,12 @@
             LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache "
                   << realpath(blk_device);
             ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_forced_argv), f2fs_fsck_forced_argv,
-                                      &status, false, LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE);
+                                      &status, false, LOG_KLOG | LOG_FILE, false, nullptr);
         } else {
             LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache "
                   << realpath(blk_device);
             ret = logwrap_fork_execvp(ARRAY_SIZE(f2fs_fsck_argv), f2fs_fsck_argv, &status, false,
-                                      LOG_KLOG | LOG_FILE, false, FSCK_LOG_FILE);
+                                      LOG_KLOG | LOG_FILE, false, nullptr);
         }
         if (ret < 0) {
             /* No need to check for error in fork, we can't really handle it now */
@@ -1009,12 +1012,11 @@
 // Check to see if a mountable volume has encryption requirements
 static int handle_encryptable(const FstabEntry& entry) {
     if (should_use_metadata_encryption(entry)) {
-        if (umount(entry.mount_point.c_str()) == 0) {
+        if (umount_retry(entry.mount_point)) {
             return FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION;
-        } else {
-            PERROR << "Could not umount " << entry.mount_point << " - fail since can't encrypt";
-            return FS_MGR_MNTALL_FAIL;
         }
+        PERROR << "Could not umount " << entry.mount_point << " - fail since can't encrypt";
+        return FS_MGR_MNTALL_FAIL;
     } else if (entry.fs_mgr_flags.file_encryption) {
         LINFO << entry.mount_point << " is file encrypted";
         return FS_MGR_MNTALL_DEV_FILE_ENCRYPTED;
@@ -1807,9 +1809,13 @@
     auto& mount_point = alt_mount_point.empty() ? entry.mount_point : alt_mount_point;
 
     // Run fsck if needed
-    prepare_fs_for_mount(entry.blk_device, entry, mount_point);
+    int ret = prepare_fs_for_mount(entry.blk_device, entry, mount_point);
+    // Wiped case doesn't require to try __mount below.
+    if (ret & FS_STAT_INVALID_MAGIC) {
+      return FS_MGR_DOMNT_FAILED;
+    }
 
-    int ret = __mount(entry.blk_device, mount_point, entry);
+    ret = __mount(entry.blk_device, mount_point, entry);
     if (ret) {
       ret = (errno == EBUSY) ? FS_MGR_DOMNT_BUSY : FS_MGR_DOMNT_FAILED;
     }
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 2b31119..2da5b0f 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -880,9 +880,14 @@
             errno = save_errno;
         }
         entry.flags &= ~MS_RDONLY;
+        entry.flags |= MS_SYNCHRONOUS;
+        entry.fs_options = "nodiscard";
         fs_mgr_set_blk_ro(device_path, false);
     }
-    entry.fs_mgr_flags.check = true;
+    // check_fs requires apex runtime library
+    if (fs_mgr_overlayfs_already_mounted("/data", false)) {
+        entry.fs_mgr_flags.check = true;
+    }
     auto save_errno = errno;
     if (mounted) mounted = fs_mgr_do_mount_one(entry) == 0;
     if (!mounted) {
diff --git a/fs_mgr/libfs_avb/avb_util.cpp b/fs_mgr/libfs_avb/avb_util.cpp
index e913d50..85dbb36 100644
--- a/fs_mgr/libfs_avb/avb_util.cpp
+++ b/fs_mgr/libfs_avb/avb_util.cpp
@@ -35,19 +35,6 @@
 namespace android {
 namespace fs_mgr {
 
-std::string GetAvbPropertyDescriptor(const std::string& key,
-                                     const std::vector<VBMetaData>& vbmeta_images) {
-    size_t value_size;
-    for (const auto& vbmeta : vbmeta_images) {
-        const char* value = avb_property_lookup(vbmeta.data(), vbmeta.size(), key.data(),
-                                                key.size(), &value_size);
-        if (value != nullptr) {
-            return {value, value_size};
-        }
-    }
-    return "";
-}
-
 // Constructs dm-verity arguments for sending DM_TABLE_LOAD ioctl to kernel.
 // See the following link for more details:
 // https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity
@@ -130,64 +117,6 @@
     return true;
 }
 
-std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(
-        const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images) {
-    bool found = false;
-    const uint8_t* desc_partition_name;
-    auto hash_desc = std::make_unique<FsAvbHashDescriptor>();
-
-    for (const auto& vbmeta : vbmeta_images) {
-        size_t num_descriptors;
-        std::unique_ptr<const AvbDescriptor*[], decltype(&avb_free)> descriptors(
-                avb_descriptor_get_all(vbmeta.data(), vbmeta.size(), &num_descriptors), avb_free);
-
-        if (!descriptors || num_descriptors < 1) {
-            continue;
-        }
-
-        for (size_t n = 0; n < num_descriptors && !found; n++) {
-            AvbDescriptor desc;
-            if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) {
-                LWARNING << "Descriptor[" << n << "] is invalid";
-                continue;
-            }
-            if (desc.tag == AVB_DESCRIPTOR_TAG_HASH) {
-                desc_partition_name = (const uint8_t*)descriptors[n] + sizeof(AvbHashDescriptor);
-                if (!avb_hash_descriptor_validate_and_byteswap((AvbHashDescriptor*)descriptors[n],
-                                                               hash_desc.get())) {
-                    continue;
-                }
-                if (hash_desc->partition_name_len != partition_name.length()) {
-                    continue;
-                }
-                // Notes that desc_partition_name is not NUL-terminated.
-                std::string hash_partition_name((const char*)desc_partition_name,
-                                                hash_desc->partition_name_len);
-                if (hash_partition_name == partition_name) {
-                    found = true;
-                }
-            }
-        }
-
-        if (found) break;
-    }
-
-    if (!found) {
-        LERROR << "Hash descriptor not found: " << partition_name;
-        return nullptr;
-    }
-
-    hash_desc->partition_name = partition_name;
-
-    const uint8_t* desc_salt = desc_partition_name + hash_desc->partition_name_len;
-    hash_desc->salt = BytesToHex(desc_salt, hash_desc->salt_len);
-
-    const uint8_t* desc_digest = desc_salt + hash_desc->salt_len;
-    hash_desc->digest = BytesToHex(desc_digest, hash_desc->digest_len);
-
-    return hash_desc;
-}
-
 std::unique_ptr<FsAvbHashtreeDescriptor> GetHashtreeDescriptor(
         const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images) {
     bool found = false;
diff --git a/fs_mgr/libfs_avb/avb_util.h b/fs_mgr/libfs_avb/avb_util.h
index e8f7c39..7941c70 100644
--- a/fs_mgr/libfs_avb/avb_util.h
+++ b/fs_mgr/libfs_avb/avb_util.h
@@ -37,12 +37,6 @@
         : partition_name(chain_partition_name), public_key_blob(chain_public_key_blob) {}
 };
 
-std::string GetAvbPropertyDescriptor(const std::string& key,
-                                     const std::vector<VBMetaData>& vbmeta_images);
-
-std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(
-        const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images);
-
 // AvbHashtreeDescriptor to dm-verity table setup.
 std::unique_ptr<FsAvbHashtreeDescriptor> GetHashtreeDescriptor(
         const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images);
diff --git a/fs_mgr/libfs_avb/fs_avb.cpp b/fs_mgr/libfs_avb/fs_avb.cpp
index 1da7117..a288876 100644
--- a/fs_mgr/libfs_avb/fs_avb.cpp
+++ b/fs_mgr/libfs_avb/fs_avb.cpp
@@ -37,6 +37,7 @@
 
 #include "avb_ops.h"
 #include "avb_util.h"
+#include "fs_avb/fs_avb_util.h"
 #include "sha.h"
 #include "util.h"
 
diff --git a/fs_mgr/libfs_avb/fs_avb_util.cpp b/fs_mgr/libfs_avb/fs_avb_util.cpp
index 1c14cc0..5326226 100644
--- a/fs_mgr/libfs_avb/fs_avb_util.cpp
+++ b/fs_mgr/libfs_avb/fs_avb_util.cpp
@@ -74,6 +74,64 @@
     return GetHashtreeDescriptor(avb_partition_name, vbmeta_images);
 }
 
+std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(
+        const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images) {
+    bool found = false;
+    const uint8_t* desc_partition_name;
+    auto hash_desc = std::make_unique<FsAvbHashDescriptor>();
+
+    for (const auto& vbmeta : vbmeta_images) {
+        size_t num_descriptors;
+        std::unique_ptr<const AvbDescriptor*[], decltype(&avb_free)> descriptors(
+                avb_descriptor_get_all(vbmeta.data(), vbmeta.size(), &num_descriptors), avb_free);
+
+        if (!descriptors || num_descriptors < 1) {
+            continue;
+        }
+
+        for (size_t n = 0; n < num_descriptors && !found; n++) {
+            AvbDescriptor desc;
+            if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) {
+                LWARNING << "Descriptor[" << n << "] is invalid";
+                continue;
+            }
+            if (desc.tag == AVB_DESCRIPTOR_TAG_HASH) {
+                desc_partition_name = (const uint8_t*)descriptors[n] + sizeof(AvbHashDescriptor);
+                if (!avb_hash_descriptor_validate_and_byteswap((AvbHashDescriptor*)descriptors[n],
+                                                               hash_desc.get())) {
+                    continue;
+                }
+                if (hash_desc->partition_name_len != partition_name.length()) {
+                    continue;
+                }
+                // Notes that desc_partition_name is not NUL-terminated.
+                std::string hash_partition_name((const char*)desc_partition_name,
+                                                hash_desc->partition_name_len);
+                if (hash_partition_name == partition_name) {
+                    found = true;
+                }
+            }
+        }
+
+        if (found) break;
+    }
+
+    if (!found) {
+        LERROR << "Hash descriptor not found: " << partition_name;
+        return nullptr;
+    }
+
+    hash_desc->partition_name = partition_name;
+
+    const uint8_t* desc_salt = desc_partition_name + hash_desc->partition_name_len;
+    hash_desc->salt = BytesToHex(desc_salt, hash_desc->salt_len);
+
+    const uint8_t* desc_digest = desc_salt + hash_desc->salt_len;
+    hash_desc->digest = BytesToHex(desc_digest, hash_desc->digest_len);
+
+    return hash_desc;
+}
+
 // Given a path, loads and verifies the vbmeta, to extract the Avb Hash descriptor.
 std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(const std::string& avb_partition_name,
                                                        VBMetaData&& vbmeta) {
@@ -84,5 +142,18 @@
     return GetHashDescriptor(avb_partition_name, vbmeta_images);
 }
 
+std::string GetAvbPropertyDescriptor(const std::string& key,
+                                     const std::vector<VBMetaData>& vbmeta_images) {
+    size_t value_size;
+    for (const auto& vbmeta : vbmeta_images) {
+        const char* value = avb_property_lookup(vbmeta.data(), vbmeta.size(), key.data(),
+                                                key.size(), &value_size);
+        if (value != nullptr) {
+            return {value, value_size};
+        }
+    }
+    return "";
+}
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/libfs_avb/include/fs_avb/fs_avb_util.h b/fs_mgr/libfs_avb/include/fs_avb/fs_avb_util.h
index 3f37bd7..1b15db7 100644
--- a/fs_mgr/libfs_avb/include/fs_avb/fs_avb_util.h
+++ b/fs_mgr/libfs_avb/include/fs_avb/fs_avb_util.h
@@ -43,9 +43,15 @@
 std::unique_ptr<FsAvbHashtreeDescriptor> GetHashtreeDescriptor(
         const std::string& avb_partition_name, VBMetaData&& vbmeta);
 
+std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(
+        const std::string& partition_name, const std::vector<VBMetaData>& vbmeta_images);
+
 // Gets the hash descriptor for avb_partition_name from the vbmeta.
 std::unique_ptr<FsAvbHashDescriptor> GetHashDescriptor(const std::string& avb_partition_name,
                                                        VBMetaData&& vbmeta);
 
+std::string GetAvbPropertyDescriptor(const std::string& key,
+                                     const std::vector<VBMetaData>& vbmeta_images);
+
 }  // namespace fs_mgr
 }  // namespace android
diff --git a/fs_mgr/libfs_avb/run_tests.sh b/fs_mgr/libfs_avb/run_tests.sh
index 5d2ce3d..3e945a4 100755
--- a/fs_mgr/libfs_avb/run_tests.sh
+++ b/fs_mgr/libfs_avb/run_tests.sh
@@ -1,8 +1,13 @@
 #!/bin/sh
 #
 # Run host tests
-atest libfs_avb_test                 # Tests public libfs_avb APIs.
-atest libfs_avb_internal_test        # Tests libfs_avb private APIs.
+atest --host libfs_avb_test          # Tests public libfs_avb APIs.
+
+# Tests libfs_avb private APIs.
+# The tests need more time to finish, so increase the timeout to 5 mins.
+# The default timeout is only 60 seconds.
+atest --host libfs_avb_internal_test -- --test-arg \
+    com.android.tradefed.testtype.HostGTest:native-test-timeout:5m
 
 # Run device tests
 atest libfs_avb_device_test          # Test public libfs_avb APIs on a device.
diff --git a/fs_mgr/libfs_avb/tests/avb_util_test.cpp b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
index 6f874a6..2e34920 100644
--- a/fs_mgr/libfs_avb/tests/avb_util_test.cpp
+++ b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
@@ -23,6 +23,7 @@
 #include <libavb/libavb.h>
 
 #include "avb_util.h"
+#include "fs_avb/fs_avb_util.h"
 #include "fs_avb_test_util.h"
 
 // Target classes or functions to test:
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 18a9d22..f3de2b4 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -1467,6 +1467,14 @@
     }
 
     RemoveAllUpdateState(lock);
+
+    if (UpdateUsesUserSnapshots(lock) && !device()->IsTestDevice()) {
+        if (snapuserd_client_) {
+            snapuserd_client_->DetachSnapuserd();
+            snapuserd_client_->CloseConnection();
+            snapuserd_client_ = nullptr;
+        }
+    }
 }
 
 void SnapshotManager::AcknowledgeMergeFailure(MergeFailureCode failure_code) {
@@ -3200,7 +3208,7 @@
 
             // Terminate stale daemon if any
             std::unique_ptr<SnapuserdClient> snapuserd_client =
-                    SnapuserdClient::Connect(kSnapuserdSocket, 10s);
+                    SnapuserdClient::Connect(kSnapuserdSocket, 5s);
             if (snapuserd_client) {
                 snapuserd_client->DetachSnapuserd();
                 snapuserd_client->CloseConnection();
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 11cebe1..d76558b 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -54,6 +54,8 @@
 #include <libsnapshot/mock_snapshot.h>
 
 DEFINE_string(force_config, "", "Force testing mode (dmsnap, vab, vabc) ignoring device config.");
+DEFINE_string(force_iouring_disable, "",
+              "Force testing mode (iouring_disabled) - disable io_uring");
 
 namespace android {
 namespace snapshot {
@@ -2769,10 +2771,22 @@
         }
     }
 
+    if (FLAGS_force_iouring_disable == "iouring_disabled") {
+        if (!android::base::SetProperty("snapuserd.test.io_uring.force_disable", "1")) {
+            return testing::AssertionFailure()
+                   << "Failed to disable property: snapuserd.test.io_uring.disabled";
+        }
+    }
+
     int ret = RUN_ALL_TESTS();
 
     if (FLAGS_force_config == "dmsnap") {
         android::base::SetProperty("snapuserd.test.dm.snapshots", "0");
     }
+
+    if (FLAGS_force_iouring_disable == "iouring_disabled") {
+        android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0");
+    }
+
     return ret;
 }
diff --git a/fs_mgr/libsnapshot/snapuserd/Android.bp b/fs_mgr/libsnapshot/snapuserd/Android.bp
index 84bcb94..bc2bceb 100644
--- a/fs_mgr/libsnapshot/snapuserd/Android.bp
+++ b/fs_mgr/libsnapshot/snapuserd/Android.bp
@@ -86,7 +86,9 @@
         "libsnapshot_cow",
         "libz",
         "libext4_utils",
+        "liburing",
     ],
+    include_dirs: ["bionic/libc/kernel"],
 }
 
 cc_binary {
@@ -182,7 +184,10 @@
         "libfs_mgr",
         "libdm",
         "libext4_utils",
+        "liburing",
+        "libgflags",
     ],
+    include_dirs: ["bionic/libc/kernel"],
     header_libs: [
         "libstorage_literals_headers",
         "libfiemap_headers",
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index ddb1f79..a082742 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -209,6 +209,8 @@
 int main(int argc, char** argv) {
     android::base::InitLogging(argv, &android::base::KernelLogger);
 
+    LOG(INFO) << "snapuserd daemon about to start";
+
     android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance();
 
     if (!daemon.StartDaemon(argc, argv)) {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
index 95d95cd..5109d82 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.cpp
@@ -16,6 +16,10 @@
 
 #include "snapuserd_core.h"
 
+#include <sys/utsname.h>
+
+#include <android-base/properties.h>
+#include <android-base/scopeguard.h>
 #include <android-base/strings.h>
 
 namespace android {
@@ -288,6 +292,136 @@
     return ReadMetadata();
 }
 
+void SnapshotHandler::FinalizeIouring() {
+    io_uring_queue_exit(ring_.get());
+}
+
+bool SnapshotHandler::InitializeIouring(int io_depth) {
+    ring_ = std::make_unique<struct io_uring>();
+
+    int ret = io_uring_queue_init(io_depth, ring_.get(), 0);
+    if (ret) {
+        LOG(ERROR) << "io_uring_queue_init failed with ret: " << ret;
+        return false;
+    }
+
+    LOG(INFO) << "io_uring_queue_init success with io_depth: " << io_depth;
+    return true;
+}
+
+bool SnapshotHandler::ReadBlocksAsync(const std::string& dm_block_device,
+                                      const std::string& partition_name, size_t size) {
+    // 64k block size with io_depth of 64 is optimal
+    // for a single thread. We just need a single thread
+    // to read all the blocks from all dynamic partitions.
+    size_t io_depth = 64;
+    size_t bs = (64 * 1024);
+
+    if (!InitializeIouring(io_depth)) {
+        return false;
+    }
+
+    LOG(INFO) << "ReadBlockAsync start "
+              << " Block-device: " << dm_block_device << " Partition-name: " << partition_name
+              << " Size: " << size;
+
+    auto scope_guard = android::base::make_scope_guard([this]() -> void { FinalizeIouring(); });
+
+    std::vector<std::unique_ptr<struct iovec>> vecs;
+    using AlignedBuf = std::unique_ptr<void, decltype(free)*>;
+    std::vector<AlignedBuf> alignedBufVector;
+
+    /*
+     * TODO: We need aligned memory for DIRECT-IO. However, if we do
+     * a DIRECT-IO and verify the blocks then we need to inform
+     * update-verifier that block verification has been done and
+     * there is no need to repeat the same. We are not there yet
+     * as we need to see if there are any boot time improvements doing
+     * a DIRECT-IO.
+     *
+     * Also, we could you the same function post merge for block verification;
+     * again, we can do a DIRECT-IO instead of thrashing page-cache and
+     * hurting other applications.
+     *
+     * For now, we will just create aligned buffers but rely on buffered
+     * I/O until we have perf numbers to justify DIRECT-IO.
+     */
+    for (int i = 0; i < io_depth; i++) {
+        auto iovec = std::make_unique<struct iovec>();
+        vecs.push_back(std::move(iovec));
+
+        struct iovec* iovec_ptr = vecs[i].get();
+
+        if (posix_memalign(&iovec_ptr->iov_base, BLOCK_SZ, bs)) {
+            LOG(ERROR) << "posix_memalign failed";
+            return false;
+        }
+
+        iovec_ptr->iov_len = bs;
+        alignedBufVector.push_back(
+                std::unique_ptr<void, decltype(free)*>(iovec_ptr->iov_base, free));
+    }
+
+    android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(dm_block_device.c_str(), O_RDONLY)));
+    if (fd.get() == -1) {
+        SNAP_PLOG(ERROR) << "File open failed - block-device " << dm_block_device
+                         << " partition-name: " << partition_name;
+        return false;
+    }
+
+    loff_t offset = 0;
+    size_t remain = size;
+    size_t read_sz = io_depth * bs;
+
+    while (remain > 0) {
+        size_t to_read = std::min(remain, read_sz);
+        size_t queue_size = to_read / bs;
+
+        for (int i = 0; i < queue_size; i++) {
+            struct io_uring_sqe* sqe = io_uring_get_sqe(ring_.get());
+            if (!sqe) {
+                SNAP_LOG(ERROR) << "io_uring_get_sqe() failed";
+                return false;
+            }
+
+            struct iovec* iovec_ptr = vecs[i].get();
+
+            io_uring_prep_read(sqe, fd.get(), iovec_ptr->iov_base, iovec_ptr->iov_len, offset);
+            sqe->flags |= IOSQE_ASYNC;
+            offset += bs;
+        }
+
+        int ret = io_uring_submit(ring_.get());
+        if (ret != queue_size) {
+            SNAP_LOG(ERROR) << "submit got: " << ret << " wanted: " << queue_size;
+            return false;
+        }
+
+        for (int i = 0; i < queue_size; i++) {
+            struct io_uring_cqe* cqe;
+
+            int ret = io_uring_wait_cqe(ring_.get(), &cqe);
+            if (ret) {
+                SNAP_PLOG(ERROR) << "wait_cqe failed" << ret;
+                return false;
+            }
+
+            if (cqe->res < 0) {
+                SNAP_LOG(ERROR) << "io failed with res: " << cqe->res;
+                return false;
+            }
+            io_uring_cqe_seen(ring_.get(), cqe);
+        }
+
+        remain -= to_read;
+    }
+
+    LOG(INFO) << "ReadBlockAsync complete: "
+              << " Block-device: " << dm_block_device << " Partition-name: " << partition_name
+              << " Size: " << size;
+    return true;
+}
+
 void SnapshotHandler::ReadBlocksToCache(const std::string& dm_block_device,
                                         const std::string& partition_name, off_t offset,
                                         size_t size) {
@@ -344,17 +478,22 @@
         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;
+    if (IsIouringSupported()) {
+        std::async(std::launch::async, &SnapshotHandler::ReadBlocksAsync, this, dm_block_device,
+                   partition_name, dev_sz);
+    } else {
+        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, &SnapshotHandler::ReadBlocksToCache, this, dm_block_device,
-                   partition_name, offset, read_sz_per_thread);
+        for (int i = 0; i < num_threads; i++) {
+            std::async(std::launch::async, &SnapshotHandler::ReadBlocksToCache, this,
+                       dm_block_device, partition_name, offset, read_sz_per_thread);
 
-        offset += read_sz_per_thread;
+            offset += read_sz_per_thread;
+        }
     }
 }
 
@@ -513,5 +652,33 @@
     return ra_state;
 }
 
+bool SnapshotHandler::IsIouringSupported() {
+    struct utsname uts;
+    unsigned int major, minor;
+
+    if (android::base::GetBoolProperty("snapuserd.test.io_uring.force_disable", false)) {
+        SNAP_LOG(INFO) << "io_uring disabled for testing";
+        return false;
+    }
+
+    if ((uname(&uts) != 0) || (sscanf(uts.release, "%u.%u", &major, &minor) != 2)) {
+        SNAP_LOG(ERROR) << "Could not parse the kernel version from uname. "
+                        << " io_uring not supported";
+        return false;
+    }
+
+    // We will only support kernels from 5.6 onwards as IOSQE_ASYNC flag and
+    // IO_URING_OP_READ/WRITE opcodes were introduced only on 5.6 kernel
+    if (major >= 5) {
+        if (major == 5 && minor < 6) {
+            return false;
+        }
+    } else {
+        return false;
+    }
+
+    return android::base::GetBoolProperty("ro.virtual_ab.io_uring.enabled", false);
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
index 1953316..b0f2d65 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_core.h
@@ -39,6 +39,7 @@
 #include <libdm/dm.h>
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
+#include <liburing.h>
 #include <snapuserd/snapuserd_buffer.h>
 #include <snapuserd/snapuserd_kernel.h>
 
@@ -113,6 +114,19 @@
     bool ReconstructDataFromCow();
     void CheckOverlap(const CowOperation* cow_op);
 
+    bool ReadAheadAsyncIO();
+    bool ReapIoCompletions(int pending_ios_to_complete);
+    bool ReadXorData(size_t block_index, size_t xor_op_index,
+                     std::vector<const CowOperation*>& xor_op_vec);
+    void ProcessXorData(size_t& block_xor_index, size_t& xor_index,
+                        std::vector<const CowOperation*>& xor_op_vec, void* buffer,
+                        loff_t& buffer_offset);
+    void UpdateScratchMetadata();
+
+    bool ReadAheadSyncIO();
+    bool InitializeIouring();
+    void FinalizeIouring();
+
     void* read_ahead_buffer_;
     void* metadata_buffer_;
 
@@ -131,7 +145,19 @@
     std::unordered_set<uint64_t> dest_blocks_;
     std::unordered_set<uint64_t> source_blocks_;
     bool overlap_;
+    std::vector<uint64_t> blocks_;
+    int total_blocks_merged_ = 0;
+    std::unique_ptr<uint8_t[]> ra_temp_buffer_;
+    std::unique_ptr<uint8_t[]> ra_temp_meta_buffer_;
     BufferSink bufsink_;
+
+    bool read_ahead_async_ = false;
+    // Queue depth of 32 seems optimal. We don't want
+    // to have a huge depth as it may put more memory pressure
+    // on the kernel worker threads given that we use
+    // IOSQE_ASYNC flag.
+    int queue_depth_ = 32;
+    std::unique_ptr<struct io_uring> ring_;
 };
 
 class Worker {
@@ -185,6 +211,7 @@
     // Merge related ops
     bool Merge();
     bool MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
+    bool MergeOrderedOpsAsync(const std::unique_ptr<ICowOpIter>& cowop_iter);
     bool MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter);
     int PrepareMerge(uint64_t* source_offset, int* pending_ops,
                      const std::unique_ptr<ICowOpIter>& cowop_iter,
@@ -193,6 +220,9 @@
     sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
     chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
 
+    bool InitializeIouring();
+    void FinalizeIouring();
+
     std::unique_ptr<CowReader> reader_;
     BufferSink bufsink_;
     XorSink xorsink_;
@@ -208,6 +238,14 @@
     unique_fd base_path_merge_fd_;
     unique_fd ctrl_fd_;
 
+    bool merge_async_ = false;
+    // Queue depth of 32 seems optimal. We don't want
+    // to have a huge depth as it may put more memory pressure
+    // on the kernel worker threads given that we use
+    // IOSQE_ASYNC flag.
+    int queue_depth_ = 32;
+    std::unique_ptr<struct io_uring> ring_;
+
     std::shared_ptr<SnapshotHandler> snapuserd_;
 };
 
@@ -292,6 +330,8 @@
     bool GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer);
     MERGE_GROUP_STATE ProcessMergingBlock(uint64_t new_block, void* buffer);
 
+    bool IsIouringSupported();
+
   private:
     bool ReadMetadata();
     sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
@@ -304,6 +344,11 @@
     void ReadBlocksToCache(const std::string& dm_block_device, const std::string& partition_name,
                            off_t offset, size_t size);
 
+    bool InitializeIouring(int io_depth);
+    void FinalizeIouring();
+    bool ReadBlocksAsync(const std::string& dm_block_device, const std::string& partition_name,
+                         size_t size);
+
     // COW device
     std::string cow_device_;
     // Source device
@@ -352,6 +397,8 @@
     bool attached_ = false;
     bool is_socket_present_;
     bool scratch_space_ = false;
+
+    std::unique_ptr<struct io_uring> ring_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
index fa055b7..d4d4efe 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_merge.cpp
@@ -72,16 +72,16 @@
 }
 
 bool Worker::MergeReplaceZeroOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
-    // Flush every 2048 ops. Since all ops are independent and there is no
+    // Flush every 8192 ops. Since all ops are independent and there is no
     // dependency between COW ops, we will flush the data and the number
-    // of ops merged in COW file for every 2048 ops. If there is a crash,
+    // of ops merged in COW file for every 8192 ops. If there is a crash,
     // we will end up replaying some of the COW ops which were already merged.
     // That is ok.
     //
-    // Why 2048 ops ? We can probably increase this to bigger value but just
-    // need to ensure that merge makes forward progress if there are
-    // crashes repeatedly which is highly unlikely.
-    int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 8;
+    // Why 8192 ops ? Increasing this may improve merge time 3-4 seconds but
+    // we need to make sure that we checkpoint; 8k ops seems optimal. In-case
+    // if there is a crash merge should always make forward progress.
+    int total_ops_merged_per_commit = (PAYLOAD_BUFFER_SZ / BLOCK_SZ) * 32;
     int num_ops_merged = 0;
 
     while (!cowop_iter->Done()) {
@@ -128,7 +128,7 @@
 
         num_ops_merged += linear_blocks;
 
-        if (num_ops_merged == total_ops_merged_per_commit) {
+        if (num_ops_merged >= total_ops_merged_per_commit) {
             // Flush the data
             if (fsync(base_path_merge_fd_.get()) < 0) {
                 SNAP_LOG(ERROR) << "Merge: ReplaceZeroOps: Failed to fsync merged data";
@@ -172,6 +172,173 @@
     return true;
 }
 
+bool Worker::MergeOrderedOpsAsync(const std::unique_ptr<ICowOpIter>& cowop_iter) {
+    void* mapped_addr = snapuserd_->GetMappedAddr();
+    void* read_ahead_buffer =
+            static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
+    size_t block_index = 0;
+
+    SNAP_LOG(INFO) << "MergeOrderedOpsAsync started....";
+
+    while (!cowop_iter->Done()) {
+        const CowOperation* cow_op = &cowop_iter->Get();
+        if (!IsOrderedOp(*cow_op)) {
+            break;
+        }
+
+        SNAP_LOG(DEBUG) << "Waiting for merge begin...";
+        // Wait for RA thread to notify that the merge window
+        // is ready for merging.
+        if (!snapuserd_->WaitForMergeBegin()) {
+            snapuserd_->SetMergeFailed(block_index);
+            return false;
+        }
+
+        snapuserd_->SetMergeInProgress(block_index);
+
+        loff_t offset = 0;
+        int num_ops = snapuserd_->GetTotalBlocksToMerge();
+
+        int pending_sqe = queue_depth_;
+        int pending_ios_to_submit = 0;
+        bool flush_required = false;
+
+        SNAP_LOG(DEBUG) << "Merging copy-ops of size: " << num_ops;
+        while (num_ops) {
+            uint64_t source_offset;
+
+            int linear_blocks = PrepareMerge(&source_offset, &num_ops, cowop_iter);
+
+            if (linear_blocks != 0) {
+                size_t io_size = (linear_blocks * BLOCK_SZ);
+
+                // Get an SQE entry from the ring and populate the I/O variables
+                struct io_uring_sqe* sqe = io_uring_get_sqe(ring_.get());
+                if (!sqe) {
+                    SNAP_PLOG(ERROR) << "io_uring_get_sqe failed during merge-ordered ops";
+                    snapuserd_->SetMergeFailed(block_index);
+                    return false;
+                }
+
+                io_uring_prep_write(sqe, base_path_merge_fd_.get(),
+                                    (char*)read_ahead_buffer + offset, io_size, source_offset);
+
+                offset += io_size;
+                num_ops -= linear_blocks;
+
+                pending_sqe -= 1;
+                pending_ios_to_submit += 1;
+                sqe->flags |= IOSQE_ASYNC;
+            }
+
+            // Ring is full or no more COW ops to be merged in this batch
+            if (pending_sqe == 0 || num_ops == 0 || (linear_blocks == 0 && pending_ios_to_submit)) {
+                // If this is a last set of COW ops to be merged in this batch, we need
+                // to sync the merged data. We will try to grab an SQE entry
+                // and set the FSYNC command; additionally, make sure that
+                // the fsync is done after all the I/O operations queued
+                // in the ring is completed by setting IOSQE_IO_DRAIN.
+                //
+                // If there is no space in the ring, we will flush it later
+                // by explicitly calling fsync() system call.
+                if (num_ops == 0 || (linear_blocks == 0 && pending_ios_to_submit)) {
+                    if (pending_sqe != 0) {
+                        struct io_uring_sqe* sqe = io_uring_get_sqe(ring_.get());
+                        if (!sqe) {
+                            // very unlikely but let's continue and not fail the
+                            // merge - we will flush it later
+                            SNAP_PLOG(ERROR) << "io_uring_get_sqe failed during merge-ordered ops";
+                            flush_required = true;
+                        } else {
+                            io_uring_prep_fsync(sqe, base_path_merge_fd_.get(), 0);
+                            // Drain the queue before fsync
+                            io_uring_sqe_set_flags(sqe, IOSQE_IO_DRAIN);
+                            pending_sqe -= 1;
+                            flush_required = false;
+                            pending_ios_to_submit += 1;
+                            sqe->flags |= IOSQE_ASYNC;
+                        }
+                    } else {
+                        flush_required = true;
+                    }
+                }
+
+                // Submit the IO for all the COW ops in a single syscall
+                int ret = io_uring_submit(ring_.get());
+                if (ret != pending_ios_to_submit) {
+                    SNAP_PLOG(ERROR)
+                            << "io_uring_submit failed for read-ahead: "
+                            << " io submit: " << ret << " expected: " << pending_ios_to_submit;
+                    snapuserd_->SetMergeFailed(block_index);
+                    return false;
+                }
+
+                int pending_ios_to_complete = pending_ios_to_submit;
+                pending_ios_to_submit = 0;
+
+                // Reap I/O completions
+                while (pending_ios_to_complete) {
+                    struct io_uring_cqe* cqe;
+
+                    ret = io_uring_wait_cqe(ring_.get(), &cqe);
+                    if (ret) {
+                        SNAP_LOG(ERROR) << "Read-ahead - io_uring_wait_cqe failed: " << ret;
+                        snapuserd_->SetMergeFailed(block_index);
+                        return false;
+                    }
+
+                    if (cqe->res < 0) {
+                        SNAP_LOG(ERROR)
+                                << "Read-ahead - io_uring_Wait_cqe failed with res: " << cqe->res;
+                        snapuserd_->SetMergeFailed(block_index);
+                        return false;
+                    }
+
+                    io_uring_cqe_seen(ring_.get(), cqe);
+                    pending_ios_to_complete -= 1;
+                }
+
+                pending_sqe = queue_depth_;
+            }
+
+            if (linear_blocks == 0) {
+                break;
+            }
+        }
+
+        // Verify all ops are merged
+        CHECK(num_ops == 0);
+
+        // Flush the data
+        if (flush_required && (fsync(base_path_merge_fd_.get()) < 0)) {
+            SNAP_LOG(ERROR) << " Failed to fsync merged data";
+            snapuserd_->SetMergeFailed(block_index);
+            return false;
+        }
+
+        // Merge is done and data is on disk. Update the COW Header about
+        // the merge completion
+        if (!snapuserd_->CommitMerge(snapuserd_->GetTotalBlocksToMerge())) {
+            SNAP_LOG(ERROR) << " Failed to commit the merged block in the header";
+            snapuserd_->SetMergeFailed(block_index);
+            return false;
+        }
+
+        SNAP_LOG(DEBUG) << "Block commit of size: " << snapuserd_->GetTotalBlocksToMerge();
+        // Mark the block as merge complete
+        snapuserd_->SetMergeCompleted(block_index);
+
+        // Notify RA thread that the merge thread is ready to merge the next
+        // window
+        snapuserd_->NotifyRAForMergeReady();
+
+        // Get the next block
+        block_index += 1;
+    }
+
+    return true;
+}
+
 bool Worker::MergeOrderedOps(const std::unique_ptr<ICowOpIter>& cowop_iter) {
     void* mapped_addr = snapuserd_->GetMappedAddr();
     void* read_ahead_buffer =
@@ -260,15 +427,23 @@
 bool Worker::Merge() {
     std::unique_ptr<ICowOpIter> cowop_iter = reader_->GetMergeOpIter();
 
-    // Start with Copy and Xor ops
-    if (!MergeOrderedOps(cowop_iter)) {
-        SNAP_LOG(ERROR) << "Merge failed for ordered ops";
-        snapuserd_->MergeFailed();
-        return false;
+    if (merge_async_) {
+        if (!MergeOrderedOpsAsync(cowop_iter)) {
+            SNAP_LOG(ERROR) << "Merge failed for ordered ops";
+            snapuserd_->MergeFailed();
+            return false;
+        }
+        SNAP_LOG(INFO) << "MergeOrderedOpsAsync completed.....";
+    } else {
+        // Start with Copy and Xor ops
+        if (!MergeOrderedOps(cowop_iter)) {
+            SNAP_LOG(ERROR) << "Merge failed for ordered ops";
+            snapuserd_->MergeFailed();
+            return false;
+        }
+        SNAP_LOG(INFO) << "MergeOrderedOps completed.....";
     }
 
-    SNAP_LOG(INFO) << "MergeOrderedOps completed...";
-
     // Replace and Zero ops
     if (!MergeReplaceZeroOps(cowop_iter)) {
         SNAP_LOG(ERROR) << "Merge failed for replace/zero ops";
@@ -281,6 +456,31 @@
     return true;
 }
 
+bool Worker::InitializeIouring() {
+    if (!snapuserd_->IsIouringSupported()) {
+        return false;
+    }
+
+    ring_ = std::make_unique<struct io_uring>();
+
+    int ret = io_uring_queue_init(queue_depth_, ring_.get(), 0);
+    if (ret) {
+        LOG(ERROR) << "Merge: io_uring_queue_init failed with ret: " << ret;
+        return false;
+    }
+
+    merge_async_ = true;
+
+    LOG(INFO) << "Merge: io_uring initialized with queue depth: " << queue_depth_;
+    return true;
+}
+
+void Worker::FinalizeIouring() {
+    if (merge_async_) {
+        io_uring_queue_exit(ring_.get());
+    }
+}
+
 bool Worker::RunMergeThread() {
     SNAP_LOG(DEBUG) << "Waiting for merge begin...";
     if (!snapuserd_->WaitForMergeBegin()) {
@@ -296,10 +496,13 @@
         return false;
     }
 
+    InitializeIouring();
+
     if (!Merge()) {
         return false;
     }
 
+    FinalizeIouring();
     CloseFds();
     reader_->CloseCowFd();
 
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
index 9e8ccfb..26c5f19 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_readahead.cpp
@@ -183,25 +183,311 @@
     return true;
 }
 
-bool ReadAhead::ReadAheadIOStart() {
-    // Check if the data has to be constructed from the COW file.
-    // This will be true only once during boot up after a crash
-    // during merge.
-    if (snapuserd_->ShouldReconstructDataFromCow()) {
-        return ReconstructDataFromCow();
-    }
+/*
+ * With io_uring, the data flow is slightly different.
+ *
+ * The data flow is as follows:
+ *
+ * 1: Queue the I/O requests to be read from backing source device.
+ * This is done by retrieving the SQE entry from ring and populating
+ * the SQE entry. Note that the I/O is not submitted yet.
+ *
+ * 2: Once the ring is full (aka queue_depth), we will submit all
+ * the queued I/O request with a single system call. This essentially
+ * cuts down "queue_depth" number of system calls to a single system call.
+ *
+ * 3: Once the I/O is submitted, user-space thread will now work
+ * on processing the XOR Operations. This happens in parallel when
+ * I/O requests are submitted to the kernel. This is ok because, for XOR
+ * operations, we first need to retrieve the compressed data form COW block
+ * device. Thus, we have offloaded the backing source I/O to the kernel
+ * and user-space is parallely working on fetching the data for XOR operations.
+ *
+ * 4: After the XOR operations are read from COW device, poll the completion
+ * queue for all the I/O submitted. If the I/O's were already completed,
+ * then user-space thread will just read the CQE requests from the ring
+ * without doing any system call. If none of the I/O were completed yet,
+ * user-space thread will do a system call and wait for I/O completions.
+ *
+ * Flow diagram:
+ *                                                    SQ-RING
+ *  SQE1 <----------- Fetch SQE1 Entry ---------- |SQE1||SQE2|SQE3|
+ *
+ *  SQE1  ------------ Populate SQE1 Entry ------> |SQE1-X||SQE2|SQE3|
+ *
+ *  SQE2 <----------- Fetch SQE2 Entry ---------- |SQE1-X||SQE2|SQE3|
+ *
+ *  SQE2  ------------ Populate SQE2 Entry ------> |SQE1-X||SQE2-X|SQE3|
+ *
+ *  SQE3 <----------- Fetch SQE3 Entry ---------- |SQE1-X||SQE2-X|SQE3|
+ *
+ *  SQE3  ------------ Populate SQE3 Entry ------> |SQE1-X||SQE2-X|SQE3-X|
+ *
+ *  Submit-IO ---------------------------------> |SQE1-X||SQE2-X|SQE3-X|
+ *     |                                                  |
+ *     |                                        Process I/O entries in kernel
+ *     |                                                  |
+ *  Retrieve XOR                                          |
+ *  data from COW                                         |
+ *     |                                                  |
+ *     |                                                  |
+ *  Fetch CQ completions
+ *     |                                              CQ-RING
+ *                                               |CQE1-X||CQE2-X|CQE3-X|
+ *                                                        |
+ *   CQE1 <------------Fetch CQE1 Entry          |CQE1||CQE2-X|CQE3-X|
+ *   CQE2 <------------Fetch CQE2 Entry          |CQE1||CQE2-|CQE3-X|
+ *   CQE3 <------------Fetch CQE3 Entry          |CQE1||CQE2-|CQE3-|
+ *    |
+ *    |
+ *  Continue Next set of operations in the RING
+ */
 
-    std::vector<uint64_t> blocks;
-
+bool ReadAhead::ReadAheadAsyncIO() {
     int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ;
     loff_t buffer_offset = 0;
-    int total_blocks_merged = 0;
+    total_blocks_merged_ = 0;
     overlap_ = false;
     dest_blocks_.clear();
     source_blocks_.clear();
+    blocks_.clear();
     std::vector<const CowOperation*> xor_op_vec;
 
-    auto ra_temp_buffer = std::make_unique<uint8_t[]>(snapuserd_->GetBufferDataSize());
+    int pending_sqe = queue_depth_;
+    int pending_ios_to_submit = 0;
+
+    size_t xor_op_index = 0;
+    size_t block_index = 0;
+
+    loff_t offset = 0;
+
+    bufsink_.ResetBufferOffset();
+
+    // Number of ops to be merged in this window. This is a fixed size
+    // except for the last window wherein the number of ops can be less
+    // than the size of the RA window.
+    while (num_ops) {
+        uint64_t source_offset;
+        struct io_uring_sqe* sqe;
+
+        int linear_blocks = PrepareNextReadAhead(&source_offset, &num_ops, blocks_, xor_op_vec);
+
+        if (linear_blocks != 0) {
+            size_t io_size = (linear_blocks * BLOCK_SZ);
+
+            // Get an SQE entry from the ring and populate the I/O variables
+            sqe = io_uring_get_sqe(ring_.get());
+            if (!sqe) {
+                SNAP_PLOG(ERROR) << "io_uring_get_sqe failed during read-ahead";
+                snapuserd_->ReadAheadIOFailed();
+                return false;
+            }
+
+            io_uring_prep_read(sqe, backing_store_fd_.get(),
+                               (char*)ra_temp_buffer_.get() + buffer_offset, io_size,
+                               source_offset);
+
+            buffer_offset += io_size;
+            num_ops -= linear_blocks;
+            total_blocks_merged_ += linear_blocks;
+
+            pending_sqe -= 1;
+            pending_ios_to_submit += 1;
+            sqe->flags |= IOSQE_ASYNC;
+        }
+
+        // pending_sqe == 0 : Ring is full
+        //
+        // num_ops == 0 : All the COW ops in this batch are processed - Submit
+        // pending I/O requests in the ring
+        //
+        // linear_blocks == 0 : All the COW ops processing is done. Submit
+        // pending I/O requests in the ring
+        if (pending_sqe == 0 || num_ops == 0 || (linear_blocks == 0 && pending_ios_to_submit)) {
+            // Submit the IO for all the COW ops in a single syscall
+            int ret = io_uring_submit(ring_.get());
+            if (ret != pending_ios_to_submit) {
+                SNAP_PLOG(ERROR) << "io_uring_submit failed for read-ahead: "
+                                 << " io submit: " << ret << " expected: " << pending_ios_to_submit;
+                snapuserd_->ReadAheadIOFailed();
+                return false;
+            }
+
+            int pending_ios_to_complete = pending_ios_to_submit;
+            pending_ios_to_submit = 0;
+
+            bool xor_processing_required = (xor_op_vec.size() > 0);
+
+            // Read XOR data from COW file in parallel when I/O's are in-flight
+            if (xor_processing_required && !ReadXorData(block_index, xor_op_index, xor_op_vec)) {
+                SNAP_LOG(ERROR) << "ReadXorData failed";
+                snapuserd_->ReadAheadIOFailed();
+                return false;
+            }
+
+            // Fetch I/O completions
+            if (!ReapIoCompletions(pending_ios_to_complete)) {
+                SNAP_LOG(ERROR) << "ReapIoCompletions failed";
+                snapuserd_->ReadAheadIOFailed();
+                return false;
+            }
+
+            // Retrieve XOR'ed data
+            if (xor_processing_required) {
+                ProcessXorData(block_index, xor_op_index, xor_op_vec, ra_temp_buffer_.get(),
+                               offset);
+            }
+
+            // All the I/O in the ring is processed.
+            pending_sqe = queue_depth_;
+        }
+
+        if (linear_blocks == 0) {
+            break;
+        }
+    }
+
+    // Done with merging ordered ops
+    if (RAIterDone() && total_blocks_merged_ == 0) {
+        return true;
+    }
+
+    CHECK(blocks_.size() == total_blocks_merged_);
+
+    UpdateScratchMetadata();
+
+    return true;
+}
+
+void ReadAhead::UpdateScratchMetadata() {
+    loff_t metadata_offset = 0;
+
+    struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
+            (char*)ra_temp_meta_buffer_.get() + metadata_offset);
+
+    bm->new_block = 0;
+    bm->file_offset = 0;
+
+    loff_t file_offset = snapuserd_->GetBufferDataOffset();
+
+    for (size_t block_index = 0; block_index < blocks_.size(); block_index++) {
+        uint64_t new_block = blocks_[block_index];
+        // Track the metadata blocks which are stored in scratch space
+        bm = reinterpret_cast<struct ScratchMetadata*>((char*)ra_temp_meta_buffer_.get() +
+                                                       metadata_offset);
+
+        bm->new_block = new_block;
+        bm->file_offset = file_offset;
+
+        metadata_offset += sizeof(struct ScratchMetadata);
+        file_offset += BLOCK_SZ;
+    }
+
+    // This is important - explicitly set the contents to zero. This is used
+    // when re-constructing the data after crash. This indicates end of
+    // reading metadata contents when re-constructing the data
+    bm = reinterpret_cast<struct ScratchMetadata*>((char*)ra_temp_meta_buffer_.get() +
+                                                   metadata_offset);
+    bm->new_block = 0;
+    bm->file_offset = 0;
+}
+
+bool ReadAhead::ReapIoCompletions(int pending_ios_to_complete) {
+    // Reap I/O completions
+    while (pending_ios_to_complete) {
+        struct io_uring_cqe* cqe;
+
+        int ret = io_uring_wait_cqe(ring_.get(), &cqe);
+        if (ret) {
+            SNAP_LOG(ERROR) << "Read-ahead - io_uring_wait_cqe failed: " << ret;
+            return false;
+        }
+
+        if (cqe->res < 0) {
+            SNAP_LOG(ERROR) << "Read-ahead - io_uring_Wait_cqe failed with res: " << cqe->res;
+            return false;
+        }
+
+        io_uring_cqe_seen(ring_.get(), cqe);
+        pending_ios_to_complete -= 1;
+    }
+
+    return true;
+}
+
+void ReadAhead::ProcessXorData(size_t& block_xor_index, size_t& xor_index,
+                               std::vector<const CowOperation*>& xor_op_vec, void* buffer,
+                               loff_t& buffer_offset) {
+    loff_t xor_buf_offset = 0;
+
+    while (block_xor_index < blocks_.size()) {
+        void* bufptr = static_cast<void*>((char*)buffer + buffer_offset);
+        uint64_t new_block = blocks_[block_xor_index];
+
+        if (xor_index < xor_op_vec.size()) {
+            const CowOperation* xor_op = xor_op_vec[xor_index];
+
+            // Check if this block is an XOR op
+            if (xor_op->new_block == new_block) {
+                // Pointer to the data read from base device
+                uint8_t* buffer = reinterpret_cast<uint8_t*>(bufptr);
+                // Get the xor'ed data read from COW device
+                uint8_t* xor_data = reinterpret_cast<uint8_t*>((char*)bufsink_.GetPayloadBufPtr() +
+                                                               xor_buf_offset);
+
+                for (size_t byte_offset = 0; byte_offset < BLOCK_SZ; byte_offset++) {
+                    buffer[byte_offset] ^= xor_data[byte_offset];
+                }
+
+                // Move to next XOR op
+                xor_index += 1;
+                xor_buf_offset += BLOCK_SZ;
+            }
+        }
+
+        buffer_offset += BLOCK_SZ;
+        block_xor_index += 1;
+    }
+
+    bufsink_.ResetBufferOffset();
+}
+
+bool ReadAhead::ReadXorData(size_t block_index, size_t xor_op_index,
+                            std::vector<const CowOperation*>& xor_op_vec) {
+    // Process the XOR ops in parallel - We will be reading data
+    // from COW file for XOR ops processing.
+    while (block_index < blocks_.size()) {
+        uint64_t new_block = blocks_[block_index];
+
+        if (xor_op_index < xor_op_vec.size()) {
+            const CowOperation* xor_op = xor_op_vec[xor_op_index];
+            if (xor_op->new_block == new_block) {
+                if (!reader_->ReadData(*xor_op, &bufsink_)) {
+                    SNAP_LOG(ERROR)
+                            << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block;
+                    return false;
+                }
+
+                xor_op_index += 1;
+                bufsink_.UpdateBufferOffset(BLOCK_SZ);
+            }
+        }
+        block_index += 1;
+    }
+    return true;
+}
+
+bool ReadAhead::ReadAheadSyncIO() {
+    int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ;
+    loff_t buffer_offset = 0;
+    total_blocks_merged_ = 0;
+    overlap_ = false;
+    dest_blocks_.clear();
+    source_blocks_.clear();
+    blocks_.clear();
+    std::vector<const CowOperation*> xor_op_vec;
+
+    bufsink_.ResetBufferOffset();
 
     // Number of ops to be merged in this window. This is a fixed size
     // except for the last window wherein the number of ops can be less
@@ -209,7 +495,7 @@
     while (num_ops) {
         uint64_t source_offset;
 
-        int linear_blocks = PrepareNextReadAhead(&source_offset, &num_ops, blocks, xor_op_vec);
+        int linear_blocks = PrepareNextReadAhead(&source_offset, &num_ops, blocks_, xor_op_vec);
         if (linear_blocks == 0) {
             // No more blocks to read
             SNAP_LOG(DEBUG) << " Read-ahead completed....";
@@ -220,7 +506,7 @@
 
         // Read from the base device consecutive set of blocks in one shot
         if (!android::base::ReadFullyAtOffset(backing_store_fd_,
-                                              (char*)ra_temp_buffer.get() + buffer_offset, io_size,
+                                              (char*)ra_temp_buffer_.get() + buffer_offset, io_size,
                                               source_offset)) {
             SNAP_PLOG(ERROR) << "Ordered-op failed. Read from backing store: "
                              << backing_store_device_ << "at block :" << source_offset / BLOCK_SZ
@@ -233,21 +519,19 @@
         }
 
         buffer_offset += io_size;
-        total_blocks_merged += linear_blocks;
+        total_blocks_merged_ += linear_blocks;
         num_ops -= linear_blocks;
     }
 
     // Done with merging ordered ops
-    if (RAIterDone() && total_blocks_merged == 0) {
+    if (RAIterDone() && total_blocks_merged_ == 0) {
         return true;
     }
 
     loff_t metadata_offset = 0;
 
-    auto ra_temp_meta_buffer = std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
-
     struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
-            (char*)ra_temp_meta_buffer.get() + metadata_offset);
+            (char*)ra_temp_meta_buffer_.get() + metadata_offset);
 
     bm->new_block = 0;
     bm->file_offset = 0;
@@ -255,12 +539,15 @@
     loff_t file_offset = snapuserd_->GetBufferDataOffset();
 
     loff_t offset = 0;
-    CHECK(blocks.size() == total_blocks_merged);
+    CHECK(blocks_.size() == total_blocks_merged_);
 
     size_t xor_index = 0;
-    for (size_t block_index = 0; block_index < blocks.size(); block_index++) {
-        void* bufptr = static_cast<void*>((char*)ra_temp_buffer.get() + offset);
-        uint64_t new_block = blocks[block_index];
+    BufferSink bufsink;
+    bufsink.Initialize(BLOCK_SZ * 2);
+
+    for (size_t block_index = 0; block_index < blocks_.size(); block_index++) {
+        void* bufptr = static_cast<void*>((char*)ra_temp_buffer_.get() + offset);
+        uint64_t new_block = blocks_[block_index];
 
         if (xor_index < xor_op_vec.size()) {
             const CowOperation* xor_op = xor_op_vec[xor_index];
@@ -268,17 +555,16 @@
             // Check if this block is an XOR op
             if (xor_op->new_block == new_block) {
                 // Read the xor'ed data from COW
-                if (!reader_->ReadData(*xor_op, &bufsink_)) {
+                if (!reader_->ReadData(*xor_op, &bufsink)) {
                     SNAP_LOG(ERROR)
                             << " ReadAhead - XorOp Read failed for block: " << xor_op->new_block;
                     snapuserd_->ReadAheadIOFailed();
                     return false;
                 }
-
                 // Pointer to the data read from base device
                 uint8_t* buffer = reinterpret_cast<uint8_t*>(bufptr);
                 // Get the xor'ed data read from COW device
-                uint8_t* xor_data = reinterpret_cast<uint8_t*>(bufsink_.GetPayloadBufPtr());
+                uint8_t* xor_data = reinterpret_cast<uint8_t*>(bufsink.GetPayloadBufPtr());
 
                 // Retrieve the original data
                 for (size_t byte_offset = 0; byte_offset < BLOCK_SZ; byte_offset++) {
@@ -292,7 +578,7 @@
 
         offset += BLOCK_SZ;
         // Track the metadata blocks which are stored in scratch space
-        bm = reinterpret_cast<struct ScratchMetadata*>((char*)ra_temp_meta_buffer.get() +
+        bm = reinterpret_cast<struct ScratchMetadata*>((char*)ra_temp_meta_buffer_.get() +
                                                        metadata_offset);
 
         bm->new_block = new_block;
@@ -308,11 +594,34 @@
     // This is important - explicitly set the contents to zero. This is used
     // when re-constructing the data after crash. This indicates end of
     // reading metadata contents when re-constructing the data
-    bm = reinterpret_cast<struct ScratchMetadata*>((char*)ra_temp_meta_buffer.get() +
+    bm = reinterpret_cast<struct ScratchMetadata*>((char*)ra_temp_meta_buffer_.get() +
                                                    metadata_offset);
     bm->new_block = 0;
     bm->file_offset = 0;
 
+    return true;
+}
+
+bool ReadAhead::ReadAheadIOStart() {
+    // Check if the data has to be constructed from the COW file.
+    // This will be true only once during boot up after a crash
+    // during merge.
+    if (snapuserd_->ShouldReconstructDataFromCow()) {
+        return ReconstructDataFromCow();
+    }
+
+    if (read_ahead_async_) {
+        if (!ReadAheadAsyncIO()) {
+            SNAP_LOG(ERROR) << "ReadAheadAsyncIO failed - io_uring processing failure.";
+            return false;
+        }
+    } else {
+        if (!ReadAheadSyncIO()) {
+            SNAP_LOG(ERROR) << "ReadAheadSyncIO failed";
+            return false;
+        }
+    }
+
     // Wait for the merge to finish for the previous RA window. We shouldn't
     // be touching the scratch space until merge is complete of previous RA
     // window. If there is a crash during this time frame, merge should resume
@@ -322,22 +631,22 @@
     }
 
     // Copy the data to scratch space
-    memcpy(metadata_buffer_, ra_temp_meta_buffer.get(), snapuserd_->GetBufferMetadataSize());
-    memcpy(read_ahead_buffer_, ra_temp_buffer.get(), total_blocks_merged * BLOCK_SZ);
+    memcpy(metadata_buffer_, ra_temp_meta_buffer_.get(), snapuserd_->GetBufferMetadataSize());
+    memcpy(read_ahead_buffer_, ra_temp_buffer_.get(), total_blocks_merged_ * BLOCK_SZ);
 
-    offset = 0;
+    loff_t offset = 0;
     std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
     read_ahead_buffer_map.clear();
 
-    for (size_t block_index = 0; block_index < blocks.size(); block_index++) {
+    for (size_t block_index = 0; block_index < blocks_.size(); block_index++) {
         void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + offset);
-        uint64_t new_block = blocks[block_index];
+        uint64_t new_block = blocks_[block_index];
 
         read_ahead_buffer_map[new_block] = bufptr;
         offset += BLOCK_SZ;
     }
 
-    snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged);
+    snapuserd_->SetMergedBlockCountForNextCommit(total_blocks_merged_);
 
     // Flush the data only if we have a overlapping blocks in the region
     // Notify the Merge thread to resume merging this window
@@ -350,6 +659,33 @@
     return true;
 }
 
+bool ReadAhead::InitializeIouring() {
+    if (!snapuserd_->IsIouringSupported()) {
+        return false;
+    }
+
+    ring_ = std::make_unique<struct io_uring>();
+
+    int ret = io_uring_queue_init(queue_depth_, ring_.get(), 0);
+    if (ret) {
+        SNAP_LOG(ERROR) << "io_uring_queue_init failed with ret: " << ret;
+        return false;
+    }
+
+    // For xor ops processing
+    bufsink_.Initialize(PAYLOAD_BUFFER_SZ * 2);
+    read_ahead_async_ = true;
+
+    SNAP_LOG(INFO) << "Read-ahead: io_uring initialized with queue depth: " << queue_depth_;
+    return true;
+}
+
+void ReadAhead::FinalizeIouring() {
+    if (read_ahead_async_) {
+        io_uring_queue_exit(ring_.get());
+    }
+}
+
 bool ReadAhead::RunThread() {
     if (!InitializeFds()) {
         return false;
@@ -363,14 +699,18 @@
 
     InitializeRAIter();
 
+    InitializeIouring();
+
     while (!RAIterDone()) {
         if (!ReadAheadIOStart()) {
             break;
         }
     }
 
+    FinalizeIouring();
     CloseFds();
     reader_->CloseCowFd();
+
     SNAP_LOG(INFO) << " ReadAhead thread terminating....";
     return true;
 }
@@ -434,8 +774,9 @@
     metadata_buffer_ =
             static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
     read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
-    // For xor ops
-    bufsink_.Initialize(PAYLOAD_BUFFER_SZ);
+
+    ra_temp_buffer_ = std::make_unique<uint8_t[]>(snapuserd_->GetBufferDataSize());
+    ra_temp_meta_buffer_ = std::make_unique<uint8_t[]>(snapuserd_->GetBufferMetadataSize());
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
index a79e3e1..eb64704 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_server.cpp
@@ -599,8 +599,13 @@
         return false;
     }
 
-    // We must re-initialize property service access, since we launched before
-    // second-stage init.
+    // This initialization of system property is important. When daemon is
+    // launched post selinux transition (before init second stage),
+    // bionic libc initializes system property as part of __libc_init_common();
+    // however that initialization fails silently given that fact that we don't
+    // have /dev/__properties__ setup which is created at init second stage.
+    //
+    // At this point, we have the handlers setup and is safe to setup property.
     __system_properties_init();
 
     if (!android::base::WaitForProperty("snapuserd.proxy_ready", "true")) {
diff --git a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
index 1c3e04b..d670f1e 100644
--- a/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/user-space-merge/snapuserd_test.cpp
@@ -12,6 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <android-base/strings.h>
+#include <gflags/gflags.h>
+
 #include <fcntl.h>
 #include <linux/fs.h>
 #include <linux/memfd.h>
@@ -27,6 +30,7 @@
 #include <string_view>
 
 #include <android-base/file.h>
+#include <android-base/properties.h>
 #include <android-base/unique_fd.h>
 #include <fs_mgr/file_wait.h>
 #include <gtest/gtest.h>
@@ -38,6 +42,8 @@
 
 #include "snapuserd_core.h"
 
+DEFINE_string(force_config, "", "Force testing mode with iouring disabled");
+
 namespace android {
 namespace snapshot {
 
@@ -857,5 +863,23 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
-    return RUN_ALL_TESTS();
+
+    gflags::ParseCommandLineFlags(&argc, &argv, false);
+
+    android::base::SetProperty("ctl.stop", "snapuserd");
+
+    if (FLAGS_force_config == "iouring_disabled") {
+        if (!android::base::SetProperty("snapuserd.test.io_uring.force_disable", "1")) {
+            return testing::AssertionFailure()
+                   << "Failed to disable property: snapuserd.test.io_uring.disabled";
+        }
+    }
+
+    int ret = RUN_ALL_TESTS();
+
+    if (FLAGS_force_config == "iouring_disabled") {
+        android::base::SetProperty("snapuserd.test.io_uring.force_disable", "0");
+    }
+
+    return ret;
 }
diff --git a/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index 377acb7..5890f9a 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -92,6 +92,7 @@
       mBatteryDevicePresent(false),
       mBatteryFixedCapacity(0),
       mBatteryFixedTemperature(0),
+      mChargerDockOnline(false),
       mHealthInfo(std::make_unique<HealthInfo_2_1>()) {
     initHealthInfo(mHealthInfo.get());
 }
@@ -196,6 +197,7 @@
             {"USB_PD", ANDROID_POWER_SUPPLY_TYPE_AC},
             {"USB_PD_DRP", ANDROID_POWER_SUPPLY_TYPE_USB},
             {"Wireless", ANDROID_POWER_SUPPLY_TYPE_WIRELESS},
+            {"Dock", ANDROID_POWER_SUPPLY_TYPE_DOCK},
             {NULL, 0},
     };
     std::string buf;
@@ -319,9 +321,21 @@
             case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
                 props.chargerWirelessOnline = true;
                 break;
+            case ANDROID_POWER_SUPPLY_TYPE_DOCK:
+                mChargerDockOnline = true;
+                break;
             default:
-                KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
-                             mChargerNames[i].string());
+                path.clear();
+                path.appendFormat("%s/%s/is_dock", POWER_SUPPLY_SYSFS_PATH,
+                                  mChargerNames[i].string());
+                if (access(path.string(), R_OK) == 0) {
+                    mChargerDockOnline = true;
+                    KLOG_INFO(LOG_TAG, "%s: online\n",
+                              mChargerNames[i].string());
+                } else {
+                    KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
+                                 mChargerNames[i].string());
+                }
             }
             path.clear();
             path.appendFormat("%s/%s/current_max", POWER_SUPPLY_SYSFS_PATH,
@@ -391,8 +405,8 @@
 
 bool BatteryMonitor::isChargerOnline() {
     const HealthInfo_1_0& props = mHealthInfo->legacy.legacy;
-    return props.chargerAcOnline | props.chargerUsbOnline |
-            props.chargerWirelessOnline;
+    return props.chargerAcOnline | props.chargerUsbOnline | props.chargerWirelessOnline |
+           mChargerDockOnline;
 }
 
 int BatteryMonitor::getChargeStatus() {
@@ -477,10 +491,10 @@
     char vs[128];
     const HealthInfo_1_0& props = mHealthInfo->legacy.legacy;
 
-    snprintf(vs, sizeof(vs), "ac: %d usb: %d wireless: %d current_max: %d voltage_max: %d\n",
-             props.chargerAcOnline, props.chargerUsbOnline,
-             props.chargerWirelessOnline, props.maxChargingCurrent,
-             props.maxChargingVoltage);
+    snprintf(vs, sizeof(vs),
+             "ac: %d usb: %d wireless: %d dock: %d current_max: %d voltage_max: %d\n",
+             props.chargerAcOnline, props.chargerUsbOnline, props.chargerWirelessOnline,
+             mChargerDockOnline, props.maxChargingCurrent, props.maxChargingVoltage);
     write(fd, vs, strlen(vs));
     snprintf(vs, sizeof(vs), "status: %d health: %d present: %d\n",
              props.batteryStatus, props.batteryHealth, props.batteryPresent);
@@ -554,6 +568,7 @@
             case ANDROID_POWER_SUPPLY_TYPE_AC:
             case ANDROID_POWER_SUPPLY_TYPE_USB:
             case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
+            case ANDROID_POWER_SUPPLY_TYPE_DOCK:
                 path.clear();
                 path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name);
                 if (access(path.string(), R_OK) == 0)
@@ -691,6 +706,17 @@
             case ANDROID_POWER_SUPPLY_TYPE_UNKNOWN:
                 break;
             }
+
+            // Look for "is_dock" file
+            path.clear();
+            path.appendFormat("%s/%s/is_dock", POWER_SUPPLY_SYSFS_PATH, name);
+            if (access(path.string(), R_OK) == 0) {
+                path.clear();
+                path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name);
+                if (access(path.string(), R_OK) == 0)
+                    mChargerNames.add(String8(name));
+
+            }
         }
     }
 
diff --git a/healthd/healthd_draw.cpp b/healthd/healthd_draw.cpp
index 4484fa6..0e6fd27 100644
--- a/healthd/healthd_draw.cpp
+++ b/healthd/healthd_draw.cpp
@@ -94,9 +94,9 @@
     gr_flip();
 }
 
-void HealthdDraw::blank_screen(bool blank) {
+void HealthdDraw::blank_screen(bool blank, int drm) {
     if (!graphics_available) return;
-    gr_fb_blank(blank);
+    gr_fb_blank(blank, drm);
 }
 
 void HealthdDraw::clear_screen(void) {
@@ -139,6 +139,8 @@
 void HealthdDraw::determine_xy(const animation::text_field& field,
                                const int length, int* x, int* y) {
   *x = field.pos_x;
+  screen_width_ = gr_fb_width() / (kSplitScreen ? 2 : 1);
+  screen_height_ = gr_fb_height();
 
   int str_len_px = length * field.font->char_width;
   if (field.pos_x == CENTER_VAL) {
diff --git a/healthd/healthd_draw.h b/healthd/healthd_draw.h
index 0b48ce8..0d7ac7a 100644
--- a/healthd/healthd_draw.h
+++ b/healthd/healthd_draw.h
@@ -31,8 +31,9 @@
   // Redraws screen.
   void redraw_screen(const animation* batt_anim, GRSurface* surf_unknown);
 
+  // According to the index of Direct Rendering Manager,
   // Blanks screen if true, unblanks if false.
-  virtual void blank_screen(bool blank);
+  virtual void blank_screen(bool blank, int drm);
 
   static std::unique_ptr<HealthdDraw> Create(animation *anim);
 
diff --git a/healthd/healthd_mode_charger.cpp b/healthd/healthd_mode_charger.cpp
index 0f9779c..012e33b 100644
--- a/healthd/healthd_mode_charger.cpp
+++ b/healthd/healthd_mode_charger.cpp
@@ -327,7 +327,7 @@
 
 #if !defined(__ANDROID_VNDK__)
         if (android::sysprop::ChargerProperties::disable_init_blank().value_or(false)) {
-            healthd_draw_->blank_screen(true);
+            healthd_draw_->blank_screen(true, static_cast<int>(drm_));
             screen_blanked_ = true;
         }
 #endif
@@ -337,7 +337,7 @@
     if (batt_anim_.num_cycles > 0 && batt_anim_.cur_cycle == batt_anim_.num_cycles) {
         reset_animation(&batt_anim_);
         next_screen_transition_ = -1;
-        healthd_draw_->blank_screen(true);
+        healthd_draw_->blank_screen(true, static_cast<int>(drm_));
         screen_blanked_ = true;
         LOGV("[%" PRId64 "] animation done\n", now);
         if (configuration_->ChargerIsOnline()) {
@@ -348,8 +348,16 @@
 
     disp_time = batt_anim_.frames[batt_anim_.cur_frame].disp_time;
 
+    /* turn off all screen */
+    if (screen_switch_ == SCREEN_SWITCH_ENABLE) {
+        healthd_draw_->blank_screen(true, 0 /* drm */);
+        healthd_draw_->blank_screen(true, 1 /* drm */);
+        screen_blanked_ = true;
+        screen_switch_ = SCREEN_SWITCH_DISABLE;
+    }
+
     if (screen_blanked_) {
-        healthd_draw_->blank_screen(false);
+        healthd_draw_->blank_screen(false, static_cast<int>(drm_));
         screen_blanked_ = false;
     }
 
@@ -452,7 +460,26 @@
     return 0;
 }
 
+int Charger::SetSwCallback(int code, int value) {
+    if (code > SW_MAX) return -1;
+    if (code == SW_LID) {
+        if ((screen_switch_ == SCREEN_SWITCH_DEFAULT) || ((value != 0) && (drm_ == DRM_INNER)) ||
+            ((value == 0) && (drm_ == DRM_OUTER))) {
+            screen_switch_ = SCREEN_SWITCH_ENABLE;
+            drm_ = (value != 0) ? DRM_OUTER : DRM_INNER;
+            keys_[code].pending = true;
+        }
+    }
+
+    return 0;
+}
+
 void Charger::UpdateInputState(input_event* ev) {
+    if (ev->type == EV_SW && ev->code == SW_LID) {
+        SetSwCallback(ev->code, ev->value);
+        return;
+    }
+
     if (ev->type != EV_KEY) return;
     SetKeyCallback(ev->code, ev->value);
 }
@@ -511,10 +538,26 @@
     key->pending = false;
 }
 
+void Charger::ProcessHallSensor(int code) {
+    key_state* key = &keys_[code];
+
+    if (code == SW_LID) {
+        if (key->pending) {
+            reset_animation(&batt_anim_);
+            kick_animation(&batt_anim_);
+            RequestDisableSuspend();
+        }
+    }
+
+    key->pending = false;
+}
+
 void Charger::HandleInputState(int64_t now) {
     ProcessKey(KEY_POWER, now);
 
     if (next_key_check_ != -1 && now > next_key_check_) next_key_check_ = -1;
+
+    ProcessHallSensor(SW_LID);
 }
 
 void Charger::HandlePowerSupplyState(int64_t now) {
@@ -743,9 +786,14 @@
             batt_anim_.frames[i].surface = scale_frames[i];
         }
     }
+    drm_ = DRM_INNER;
+    screen_switch_ = SCREEN_SWITCH_DEFAULT;
     ev_sync_key_state(std::bind(&Charger::SetKeyCallback, this, std::placeholders::_1,
                                 std::placeholders::_2));
 
+    (void)ev_sync_sw_state(
+            std::bind(&Charger::SetSwCallback, this, std::placeholders::_1, std::placeholders::_2));
+
     next_screen_transition_ = -1;
     next_key_check_ = -1;
     next_pwr_check_ = -1;
diff --git a/healthd/include/healthd/BatteryMonitor.h b/healthd/include/healthd/BatteryMonitor.h
index 3cda727..89c2e25 100644
--- a/healthd/include/healthd/BatteryMonitor.h
+++ b/healthd/include/healthd/BatteryMonitor.h
@@ -48,7 +48,8 @@
         ANDROID_POWER_SUPPLY_TYPE_AC,
         ANDROID_POWER_SUPPLY_TYPE_USB,
         ANDROID_POWER_SUPPLY_TYPE_WIRELESS,
-        ANDROID_POWER_SUPPLY_TYPE_BATTERY
+        ANDROID_POWER_SUPPLY_TYPE_BATTERY,
+        ANDROID_POWER_SUPPLY_TYPE_DOCK
     };
 
     BatteryMonitor();
@@ -75,6 +76,8 @@
     bool mBatteryDevicePresent;
     int mBatteryFixedCapacity;
     int mBatteryFixedTemperature;
+    // TODO(b/214126090): to migrate to AIDL HealthInfo
+    bool mChargerDockOnline;
     std::unique_ptr<android::hardware::health::V2_1::HealthInfo> mHealthInfo;
 
     int readFromFile(const String8& path, std::string* buf);
diff --git a/healthd/include_charger/charger/healthd_mode_charger.h b/healthd/include_charger/charger/healthd_mode_charger.h
index 216e5ad..28e1fb5 100644
--- a/healthd/include_charger/charger/healthd_mode_charger.h
+++ b/healthd/include_charger/charger/healthd_mode_charger.h
@@ -44,6 +44,17 @@
     aidl::android::hardware::health::BatteryStatus battery_status;
 };
 
+enum DirectRenderManager {
+    DRM_INNER,
+    DRM_OUTER,
+};
+
+enum SrceenSwitch {
+    SCREEN_SWITCH_DEFAULT,
+    SCREEN_SWITCH_DISABLE,
+    SCREEN_SWITCH_ENABLE,
+};
+
 // Configuration interface for charger. This includes:
 // - HalHealthLoop APIs that interests charger.
 // - configuration values that used to be provided by sysprops
@@ -85,9 +96,11 @@
     void InitDefaultAnimationFrames();
     void UpdateScreenState(int64_t now);
     int SetKeyCallback(int code, int value);
+    int SetSwCallback(int code, int value);
     void UpdateInputState(input_event* ev);
     void SetNextKeyCheck(key_state* key, int64_t timeout);
     void ProcessKey(int code, int64_t now);
+    void ProcessHallSensor(int code);
     void HandleInputState(int64_t now);
     void HandlePowerSupplyState(int64_t now);
     int InputCallback(int fd, unsigned int epevents);
@@ -102,6 +115,9 @@
     int64_t next_pwr_check_ = 0;
     int64_t wait_batt_level_timestamp_ = 0;
 
+    DirectRenderManager drm_;
+    SrceenSwitch screen_switch_;
+
     key_state keys_[KEY_MAX + 1] = {};
 
     animation batt_anim_;
diff --git a/init/builtins.cpp b/init/builtins.cpp
index cc445be..0eb894b 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -117,7 +117,7 @@
                         android::base::GetMinimumLogSeverity() > android::base::DEBUG) {}
 
     template <typename T>
-    operator android::base::expected<T, ResultError<int>>() {
+    operator android::base::expected<T, ResultError<android::base::Errno>>() {
         if (ignore_error_) {
             return {};
         }
diff --git a/init/init.cpp b/init/init.cpp
index e3596cb..1df4c44 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -33,6 +33,7 @@
 #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
 #include <sys/_system_properties.h>
 
+#include <filesystem>
 #include <functional>
 #include <map>
 #include <memory>
@@ -46,6 +47,7 @@
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/properties.h>
+#include <android-base/scopeguard.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <backtrace/Backtrace.h>
@@ -773,6 +775,82 @@
     return {};
 }
 
+static bool SystemReadSmokeTest() {
+    std::string dev = "/dev/block/mapper/system"s + fs_mgr_get_slot_suffix();
+    android::base::unique_fd fd(open(dev.c_str(), O_RDONLY));
+    if (fd < 0) {
+        PLOG(ERROR) << "open " << dev << " failed, will not diangose snapuserd hangs";
+        return false;
+    }
+
+    for (size_t i = 1; i <= 100; i++) {
+        // Skip around the partition a bit.
+        size_t offset = i * 4096 * 512;
+
+        char b;
+        ssize_t n = TEMP_FAILURE_RETRY(pread(fd.get(), &b, 1, offset));
+        if (n < 0) {
+            PLOG(ERROR) << "snapuserd smoke test read failed";
+            return false;
+        }
+    }
+    return true;
+}
+
+static void DiagnoseSnapuserdHang(pid_t pid) {
+    bool succeeded = false;
+
+    std::mutex m;
+    std::condition_variable cv;
+
+    // Enforce an ordering between this and the thread startup, by taking the
+    // lock before we lanuch the thread.
+    std::unique_lock<std::mutex> cv_lock(m);
+
+    std::thread t([&]() -> void {
+        std::lock_guard<std::mutex> lock(m);
+        succeeded = SystemReadSmokeTest();
+        cv.notify_all();
+    });
+
+    auto join = android::base::make_scope_guard([&]() -> void {
+        // If the smoke test is hung, then this will too. We expect the device to
+        // automatically reboot once the watchdog kicks in.
+        t.join();
+    });
+
+    auto now = std::chrono::system_clock::now();
+    auto deadline = now + 10s;
+    auto status = cv.wait_until(cv_lock, deadline);
+    if (status == std::cv_status::timeout) {
+        LOG(ERROR) << "snapuserd smoke test timed out";
+    } else if (!succeeded) {
+        LOG(ERROR) << "snapuserd smoke test failed";
+    }
+
+    if (succeeded) {
+        LOG(INFO) << "snapuserd smoke test succeeded";
+        return;
+    }
+
+    while (true) {
+        LOG(ERROR) << "snapuserd problem detected, printing open fds";
+
+        std::error_code ec;
+        std::string proc_dir = "/proc/" + std::to_string(pid) + "/fd";
+        for (const auto& entry : std::filesystem::directory_iterator(proc_dir)) {
+            std::string target;
+            if (android::base::Readlink(entry.path(), &target)) {
+                LOG(ERROR) << "snapuserd opened: " << target;
+            } else {
+                LOG(ERROR) << "snapuserd opened: " << entry.path();
+            }
+        }
+
+        std::this_thread::sleep_for(10s);
+    }
+}
+
 int SecondStageMain(int argc, char** argv) {
     if (REBOOT_BOOTLOADER_ON_PANIC) {
         InstallRebootSignalHandlers();
@@ -786,6 +864,11 @@
     InitKernelLogging(argv);
     LOG(INFO) << "init second stage started!";
 
+    if (auto pid = GetSnapuserdFirstStagePid()) {
+        std::thread t(DiagnoseSnapuserdHang, *pid);
+        t.detach();
+    }
+
     // Update $PATH in the case the second stage init is newer than first stage init, where it is
     // first set.
     if (setenv("PATH", _PATH_DEFPATH, 1) != 0) {
diff --git a/init/perfboot.py b/init/perfboot.py
index 4b23ad2..968df38 100755
--- a/init/perfboot.py
+++ b/init/perfboot.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # Copyright (C) 2015 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -39,7 +39,7 @@
 
 import argparse
 import atexit
-import cStringIO
+import io
 import glob
 import inspect
 import logging
@@ -102,7 +102,7 @@
             self._wait_cpu_cool_down(self._product, self._temp_paths)
         else:
             if self._waited:
-                print 'Waiting for %d seconds' % self._interval
+                print('Waiting for %d seconds' % self._interval)
                 time.sleep(self._interval)
         self._waited = True
 
@@ -119,9 +119,9 @@
         threshold = IntervalAdjuster._CPU_COOL_DOWN_THRESHOLDS.get(
             self._product)
         if threshold is None:
-            print 'No CPU temperature threshold is set for ' + self._product
-            print ('Just wait %d seconds' %
-                   IntervalAdjuster._CPU_COOL_DOWN_WAIT_TIME_DEFAULT)
+            print('No CPU temperature threshold is set for ' + self._product)
+            print(('Just wait %d seconds' %
+                   IntervalAdjuster._CPU_COOL_DOWN_WAIT_TIME_DEFAULT))
             time.sleep(IntervalAdjuster._CPU_COOL_DOWN_WAIT_TIME_DEFAULT)
             return
         while True:
@@ -129,8 +129,8 @@
             if temp < threshold:
                 logging.info('Current CPU temperature %s' % temp)
                 return
-            print 'Waiting until CPU temperature (%d) falls below %d' % (
-                temp, threshold)
+            print('Waiting until CPU temperature (%d) falls below %d' % (
+                temp, threshold))
             time.sleep(IntervalAdjuster._CPU_COOL_DOWN_WAIT_INTERVAL)
 
 
@@ -260,7 +260,7 @@
 
 def get_values(record, tag):
     """Gets values that matches |tag| from |record|."""
-    keys = [key for key in record.keys() if key[0] == tag]
+    keys = [key for key in list(record.keys()) if key[0] == tag]
     return [record[k] for k in sorted(keys)]
 
 
@@ -304,7 +304,7 @@
     with open(filename, 'w') as f:
         f.write('\t'.join(labels) + '\n')
         for record in record_list:
-            line = cStringIO.StringIO()
+            line = io.StringIO()
             invalid_line = False
             for i, tag in enumerate(tags):
                 if i != 0:
@@ -319,7 +319,7 @@
                 logging.error('Invalid record found: ' + line.getvalue())
             line.write('\n')
             f.write(line.getvalue())
-    print 'Wrote: ' + filename
+    print(('Wrote: ' + filename))
 
 
 def median(data):
@@ -349,9 +349,9 @@
     # Filter out invalid data.
     end_times = [get_last_value(record, end_tag) for record in record_list
                  if get_last_value(record, end_tag) != 0]
-    print 'mean:', int(round(mean(end_times))), 'ms'
-    print 'median:', int(round(median(end_times))), 'ms'
-    print 'standard deviation:', int(round(stddev(end_times))), 'ms'
+    print(('mean:', int(round(mean(end_times))), 'ms'))
+    print(('median:', int(round(median(end_times))), 'ms'))
+    print(('standard deviation:', int(round(stddev(end_times))), 'ms'))
 
 
 def do_iteration(device, interval_adjuster, event_tags_re, end_tag):
@@ -359,7 +359,7 @@
     device.wait()
     interval_adjuster.wait()
     device.reboot()
-    print 'Rebooted the device'
+    print('Rebooted the device, waiting for tag', end_tag)
     record = {}
     booted = False
     while not booted:
@@ -372,7 +372,7 @@
                 stdout=subprocess.PIPE)
         for line in readlines_unbuffered(p):
             if t.is_timedout():
-                print '*** Timed out ***'
+                print('*** Timed out ***')
                 return record
             m = event_tags_re.search(line)
             if not m:
@@ -381,8 +381,8 @@
             event_time = int(m.group('time'))
             pid = m.group('pid')
             record[(tag, pid)] = event_time
-            print 'Event log recorded: %s (%s) - %d ms' % (
-                tag, pid, event_time)
+            print(('Event log recorded: %s (%s) - %d ms' % (
+                tag, pid, event_time)))
             if tag == end_tag:
                 booted = True
                 t.cancel()
@@ -420,7 +420,7 @@
 
 def install_apks(device, apk_dir):
     for apk in glob.glob(os.path.join(apk_dir, '*.apk')):
-        print 'Installing: ' + apk
+        print('Installing: ' + apk)
         device.install(apk, replace=True)
 
 
@@ -452,7 +452,7 @@
     event_tags_re = make_event_tags_re(event_tags)
 
     for i in range(args.iterations):
-        print 'Run #%d ' % i
+        print('Run #%d ' % i)
         record = do_iteration(
             device, interval_adjuster, event_tags_re, end_tag)
         record_list.append(record)
diff --git a/init/snapuserd_transition.cpp b/init/snapuserd_transition.cpp
index e11510e..5deaf31 100644
--- a/init/snapuserd_transition.cpp
+++ b/init/snapuserd_transition.cpp
@@ -32,6 +32,7 @@
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <cutils/sockets.h>
+#include <fs_avb/fs_avb.h>
 #include <libsnapshot/snapshot.h>
 #include <private/android_filesystem_config.h>
 #include <procinfo/process_map.h>
@@ -247,6 +248,56 @@
     }
 }
 
+/*
+ * Before starting init second stage, we will wait
+ * for snapuserd daemon to be up and running; bionic libc
+ * may read /system/etc/selinux/plat_property_contexts file
+ * before invoking main() function. This will happen if
+ * init initializes property during second stage. Any access
+ * to /system without snapuserd daemon will lead to a deadlock.
+ *
+ * Thus, we do a simple probe by reading system partition. This
+ * read will eventually be serviced by daemon confirming that
+ * daemon is up and running. Furthermore, we are still in the kernel
+ * domain and sepolicy has not been enforced yet. Thus, access
+ * to these device mapper block devices are ok even though
+ * we may see audit logs.
+ */
+bool SnapuserdSelinuxHelper::TestSnapuserdIsReady() {
+    std::string dev = "/dev/block/mapper/system"s + fs_mgr_get_slot_suffix();
+    android::base::unique_fd fd(open(dev.c_str(), O_RDONLY | O_DIRECT));
+    if (fd < 0) {
+        PLOG(ERROR) << "open " << dev << " failed";
+        return false;
+    }
+
+    void* addr;
+    ssize_t page_size = getpagesize();
+    if (posix_memalign(&addr, page_size, page_size) < 0) {
+        PLOG(ERROR) << "posix_memalign with page size " << page_size;
+        return false;
+    }
+
+    std::unique_ptr<void, decltype(&::free)> buffer(addr, ::free);
+
+    int iter = 0;
+    while (iter < 10) {
+        ssize_t n = TEMP_FAILURE_RETRY(pread(fd.get(), buffer.get(), page_size, 0));
+        if (n < 0) {
+            // Wait for sometime before retry
+            std::this_thread::sleep_for(100ms);
+        } else if (n == page_size) {
+            return true;
+        } else {
+            LOG(ERROR) << "pread returned: " << n << " from: " << dev << " expected: " << page_size;
+        }
+
+        iter += 1;
+    }
+
+    return false;
+}
+
 void SnapuserdSelinuxHelper::RelaunchFirstStageSnapuserd() {
     auto fd = GetRamdiskSnapuserdFd();
     if (!fd) {
@@ -268,6 +319,13 @@
         setenv(kSnapuserdFirstStagePidVar, std::to_string(pid).c_str(), 1);
 
         LOG(INFO) << "Relaunched snapuserd with pid: " << pid;
+
+        if (!TestSnapuserdIsReady()) {
+            PLOG(FATAL) << "snapuserd daemon failed to launch";
+        } else {
+            LOG(INFO) << "snapuserd daemon is up and running";
+        }
+
         return;
     }
 
diff --git a/init/snapuserd_transition.h b/init/snapuserd_transition.h
index be22afd..557d105 100644
--- a/init/snapuserd_transition.h
+++ b/init/snapuserd_transition.h
@@ -56,6 +56,7 @@
   private:
     void RelaunchFirstStageSnapuserd();
     void ExecSnapuserd();
+    bool TestSnapuserdIsReady();
 
     std::unique_ptr<SnapshotManager> sm_;
     BlockDevInitializer block_dev_init_;
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index e9497a8..a6835fc 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -211,6 +211,7 @@
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/resize2fs" },
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/snapuserd" },
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/tune2fs" },
+    { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/fsck.f2fs" },
     // generic defaults
     { 00755, AID_ROOT,      AID_ROOT,      0, "bin/*" },
     { 00640, AID_ROOT,      AID_SHELL,     0, "fstab.*" },
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index be34f95..c5badc9 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -35,6 +35,8 @@
 
 #ifndef __ANDROID_VNDK__
 
+bool SetProcessProfilesCached(uid_t uid, pid_t pid, const std::vector<std::string>& profiles);
+
 static constexpr const char* CGROUPS_RC_PATH = "/dev/cgroup_info/cgroup.rc";
 
 bool UsePerAppMemcg();
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 0320b02..cb2fe0a 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -126,11 +126,16 @@
 }
 
 void DropTaskProfilesResourceCaching() {
-    TaskProfiles::GetInstance().DropResourceCaching();
+    TaskProfiles::GetInstance().DropResourceCaching(ProfileAction::RCT_TASK);
+    TaskProfiles::GetInstance().DropResourceCaching(ProfileAction::RCT_PROCESS);
 }
 
 bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
-    return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles);
+    return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles, false);
+}
+
+bool SetProcessProfilesCached(uid_t uid, pid_t pid, const std::vector<std::string>& profiles) {
+    return TaskProfiles::GetInstance().SetProcessProfiles(uid, pid, profiles, true);
 }
 
 bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache) {
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 3834f91..74ba7f6 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -51,6 +51,67 @@
 static constexpr const char* TEMPLATE_TASK_PROFILE_API_FILE =
         "/etc/task_profiles/task_profiles_%u.json";
 
+class FdCacheHelper {
+  public:
+    enum FdState {
+        FDS_INACCESSIBLE = -1,
+        FDS_APP_DEPENDENT = -2,
+        FDS_NOT_CACHED = -3,
+    };
+
+    static void Cache(const std::string& path, android::base::unique_fd& fd);
+    static void Drop(android::base::unique_fd& fd);
+    static void Init(const std::string& path, android::base::unique_fd& fd);
+    static bool IsCached(const android::base::unique_fd& fd) { return fd > FDS_INACCESSIBLE; }
+
+  private:
+    static bool IsAppDependentPath(const std::string& path);
+};
+
+void FdCacheHelper::Init(const std::string& path, android::base::unique_fd& fd) {
+    // file descriptors for app-dependent paths can't be cached
+    if (IsAppDependentPath(path)) {
+        // file descriptor is not cached
+        fd.reset(FDS_APP_DEPENDENT);
+        return;
+    }
+    // file descriptor can be cached later on request
+    fd.reset(FDS_NOT_CACHED);
+}
+
+void FdCacheHelper::Cache(const std::string& path, android::base::unique_fd& fd) {
+    if (fd != FDS_NOT_CACHED) {
+        return;
+    }
+
+    if (access(path.c_str(), W_OK) != 0) {
+        // file is not accessible
+        fd.reset(FDS_INACCESSIBLE);
+        return;
+    }
+
+    unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_WRONLY | O_CLOEXEC)));
+    if (tmp_fd < 0) {
+        PLOG(ERROR) << "Failed to cache fd '" << path << "'";
+        fd.reset(FDS_INACCESSIBLE);
+        return;
+    }
+
+    fd = std::move(tmp_fd);
+}
+
+void FdCacheHelper::Drop(android::base::unique_fd& fd) {
+    if (fd == FDS_NOT_CACHED) {
+        return;
+    }
+
+    fd.reset(FDS_NOT_CACHED);
+}
+
+bool FdCacheHelper::IsAppDependentPath(const std::string& path) {
+    return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
+}
+
 void ProfileAttribute::Reset(const CgroupController& controller, const std::string& file_name) {
     controller_ = controller;
     file_name_ = file_name;
@@ -144,57 +205,11 @@
     return true;
 }
 
-void CachedFdProfileAction::EnableResourceCaching() {
-    std::lock_guard<std::mutex> lock(fd_mutex_);
-    if (fd_ != FDS_NOT_CACHED) {
-        return;
-    }
-
-    std::string tasks_path = GetPath();
-
-    if (access(tasks_path.c_str(), W_OK) != 0) {
-        // file is not accessible
-        fd_.reset(FDS_INACCESSIBLE);
-        return;
-    }
-
-    unique_fd fd(TEMP_FAILURE_RETRY(open(tasks_path.c_str(), O_WRONLY | O_CLOEXEC)));
-    if (fd < 0) {
-        PLOG(ERROR) << "Failed to cache fd '" << tasks_path << "'";
-        fd_.reset(FDS_INACCESSIBLE);
-        return;
-    }
-
-    fd_ = std::move(fd);
-}
-
-void CachedFdProfileAction::DropResourceCaching() {
-    std::lock_guard<std::mutex> lock(fd_mutex_);
-    if (fd_ == FDS_NOT_CACHED) {
-        return;
-    }
-
-    fd_.reset(FDS_NOT_CACHED);
-}
-
-bool CachedFdProfileAction::IsAppDependentPath(const std::string& path) {
-    return path.find("<uid>", 0) != std::string::npos || path.find("<pid>", 0) != std::string::npos;
-}
-
-void CachedFdProfileAction::InitFd(const std::string& path) {
-    // file descriptors for app-dependent paths can't be cached
-    if (IsAppDependentPath(path)) {
-        // file descriptor is not cached
-        fd_.reset(FDS_APP_DEPENDENT);
-        return;
-    }
-    // file descriptor can be cached later on request
-    fd_.reset(FDS_NOT_CACHED);
-}
-
 SetCgroupAction::SetCgroupAction(const CgroupController& c, const std::string& p)
     : controller_(c), path_(p) {
-    InitFd(controller_.GetTasksFilePath(path_));
+    FdCacheHelper::Init(controller_.GetTasksFilePath(path_), fd_[ProfileAction::RCT_TASK]);
+    // uid and pid don't matter because IsAppDependentPath ensures the path doesn't use them
+    FdCacheHelper::Init(controller_.GetProcsFilePath(path_, 0, 0), fd_[ProfileAction::RCT_PROCESS]);
 }
 
 bool SetCgroupAction::AddTidToCgroup(int tid, int fd, const char* controller_name) {
@@ -232,7 +247,40 @@
     return false;
 }
 
+ProfileAction::CacheUseResult SetCgroupAction::UseCachedFd(ResourceCacheType cache_type,
+                                                           int id) const {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    if (FdCacheHelper::IsCached(fd_[cache_type])) {
+        // fd is cached, reuse it
+        if (!AddTidToCgroup(id, fd_[cache_type], controller()->name())) {
+            LOG(ERROR) << "Failed to add task into cgroup";
+            return ProfileAction::FAIL;
+        }
+        return ProfileAction::SUCCESS;
+    }
+
+    if (fd_[cache_type] == FdCacheHelper::FDS_INACCESSIBLE) {
+        // no permissions to access the file, ignore
+        return ProfileAction::SUCCESS;
+    }
+
+    if (cache_type == ResourceCacheType::RCT_TASK &&
+        fd_[cache_type] == FdCacheHelper::FDS_APP_DEPENDENT) {
+        // application-dependent path can't be used with tid
+        PLOG(ERROR) << "Application profile can't be applied to a thread";
+        return ProfileAction::FAIL;
+    }
+
+    return ProfileAction::UNUSED;
+}
+
 bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
+    CacheUseResult result = UseCachedFd(ProfileAction::RCT_PROCESS, pid);
+    if (result != ProfileAction::UNUSED) {
+        return result == ProfileAction::SUCCESS;
+    }
+
+    // fd was not cached or cached fd can't be used
     std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid);
     unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(procs_path.c_str(), O_WRONLY | O_CLOEXEC)));
     if (tmp_fd < 0) {
@@ -248,28 +296,12 @@
 }
 
 bool SetCgroupAction::ExecuteForTask(int tid) const {
-    std::lock_guard<std::mutex> lock(fd_mutex_);
-    if (IsFdValid()) {
-        // fd is cached, reuse it
-        if (!AddTidToCgroup(tid, fd_, controller()->name())) {
-            LOG(ERROR) << "Failed to add task into cgroup";
-            return false;
-        }
-        return true;
+    CacheUseResult result = UseCachedFd(ProfileAction::RCT_TASK, tid);
+    if (result != ProfileAction::UNUSED) {
+        return result == ProfileAction::SUCCESS;
     }
 
-    if (fd_ == FDS_INACCESSIBLE) {
-        // no permissions to access the file, ignore
-        return true;
-    }
-
-    if (fd_ == FDS_APP_DEPENDENT) {
-        // application-dependent path can't be used with tid
-        PLOG(ERROR) << "Application profile can't be applied to a thread";
-        return false;
-    }
-
-    // fd was not cached because cached fd can't be used
+    // fd was not cached or cached fd can't be used
     std::string tasks_path = controller()->GetTasksFilePath(path_);
     unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(tasks_path.c_str(), O_WRONLY | O_CLOEXEC)));
     if (tmp_fd < 0) {
@@ -284,10 +316,36 @@
     return true;
 }
 
+void SetCgroupAction::EnableResourceCaching(ResourceCacheType cache_type) {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    // Return early to prevent unnecessary calls to controller_.Get{Tasks|Procs}FilePath() which
+    // include regex evaluations
+    if (fd_[cache_type] != FdCacheHelper::FDS_NOT_CACHED) {
+        return;
+    }
+    switch (cache_type) {
+        case (ProfileAction::RCT_TASK):
+            FdCacheHelper::Cache(controller_.GetTasksFilePath(path_), fd_[cache_type]);
+            break;
+        case (ProfileAction::RCT_PROCESS):
+            // uid and pid don't matter because IsAppDependentPath ensures the path doesn't use them
+            FdCacheHelper::Cache(controller_.GetProcsFilePath(path_, 0, 0), fd_[cache_type]);
+            break;
+        default:
+            LOG(ERROR) << "Invalid cache type is specified!";
+            break;
+    }
+}
+
+void SetCgroupAction::DropResourceCaching(ResourceCacheType cache_type) {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    FdCacheHelper::Drop(fd_[cache_type]);
+}
+
 WriteFileAction::WriteFileAction(const std::string& path, const std::string& value,
                                  bool logfailures)
     : path_(path), value_(value), logfailures_(logfailures) {
-    InitFd(path_);
+    FdCacheHelper::Init(path_, fd_);
 }
 
 bool WriteFileAction::WriteValueToFile(const std::string& value, const std::string& path,
@@ -309,13 +367,43 @@
     return true;
 }
 
-bool WriteFileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
+ProfileAction::CacheUseResult WriteFileAction::UseCachedFd(ResourceCacheType cache_type,
+                                                           const std::string& value) const {
     std::lock_guard<std::mutex> lock(fd_mutex_);
+    if (FdCacheHelper::IsCached(fd_)) {
+        // fd is cached, reuse it
+        if (!WriteStringToFd(value, fd_)) {
+            if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << path_;
+            return ProfileAction::FAIL;
+        }
+        return ProfileAction::SUCCESS;
+    }
+
+    if (fd_ == FdCacheHelper::FDS_INACCESSIBLE) {
+        // no permissions to access the file, ignore
+        return ProfileAction::SUCCESS;
+    }
+
+    if (cache_type == ResourceCacheType::RCT_TASK && fd_ == FdCacheHelper::FDS_APP_DEPENDENT) {
+        // application-dependent path can't be used with tid
+        PLOG(ERROR) << "Application profile can't be applied to a thread";
+        return ProfileAction::FAIL;
+    }
+    return ProfileAction::UNUSED;
+}
+
+bool WriteFileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
     std::string value(value_);
-    std::string path(path_);
 
     value = StringReplace(value, "<uid>", std::to_string(uid), true);
     value = StringReplace(value, "<pid>", std::to_string(pid), true);
+
+    CacheUseResult result = UseCachedFd(ProfileAction::RCT_PROCESS, value);
+    if (result != ProfileAction::UNUSED) {
+        return result == ProfileAction::SUCCESS;
+    }
+
+    std::string path(path_);
     path = StringReplace(path, "<uid>", std::to_string(uid), true);
     path = StringReplace(path, "<pid>", std::to_string(pid), true);
 
@@ -323,41 +411,33 @@
 }
 
 bool WriteFileAction::ExecuteForTask(int tid) const {
-    std::lock_guard<std::mutex> lock(fd_mutex_);
     std::string value(value_);
     int uid = getuid();
 
     value = StringReplace(value, "<uid>", std::to_string(uid), true);
     value = StringReplace(value, "<pid>", std::to_string(tid), true);
 
-    if (IsFdValid()) {
-        // fd is cached, reuse it
-        if (!WriteStringToFd(value, fd_)) {
-            if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << path_;
-            return false;
-        }
-        return true;
-    }
-
-    if (fd_ == FDS_INACCESSIBLE) {
-        // no permissions to access the file, ignore
-        return true;
-    }
-
-    if (fd_ == FDS_APP_DEPENDENT) {
-        // application-dependent path can't be used with tid
-        PLOG(ERROR) << "Application profile can't be applied to a thread";
-        return false;
+    CacheUseResult result = UseCachedFd(ProfileAction::RCT_TASK, value);
+    if (result != ProfileAction::UNUSED) {
+        return result == ProfileAction::SUCCESS;
     }
 
     return WriteValueToFile(value, path_, logfailures_);
 }
 
+void WriteFileAction::EnableResourceCaching(ResourceCacheType) {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    FdCacheHelper::Cache(path_, fd_);
+}
+
+void WriteFileAction::DropResourceCaching(ResourceCacheType) {
+    std::lock_guard<std::mutex> lock(fd_mutex_);
+    FdCacheHelper::Drop(fd_);
+}
+
 bool ApplyProfileAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
     for (const auto& profile : profiles_) {
-        if (!profile->ExecuteForProcess(uid, pid)) {
-            PLOG(WARNING) << "ExecuteForProcess failed for aggregate profile";
-        }
+        profile->ExecuteForProcess(uid, pid);
     }
     return true;
 }
@@ -369,15 +449,15 @@
     return true;
 }
 
-void ApplyProfileAction::EnableResourceCaching() {
+void ApplyProfileAction::EnableResourceCaching(ResourceCacheType cache_type) {
     for (const auto& profile : profiles_) {
-        profile->EnableResourceCaching();
+        profile->EnableResourceCaching(cache_type);
     }
 }
 
-void ApplyProfileAction::DropResourceCaching() {
+void ApplyProfileAction::DropResourceCaching(ResourceCacheType cache_type) {
     for (const auto& profile : profiles_) {
-        profile->DropResourceCaching();
+        profile->DropResourceCaching(cache_type);
     }
 }
 
@@ -407,33 +487,33 @@
     return true;
 }
 
-void TaskProfile::EnableResourceCaching() {
+void TaskProfile::EnableResourceCaching(ProfileAction::ResourceCacheType cache_type) {
     if (res_cached_) {
         return;
     }
 
     for (auto& element : elements_) {
-        element->EnableResourceCaching();
+        element->EnableResourceCaching(cache_type);
     }
 
     res_cached_ = true;
 }
 
-void TaskProfile::DropResourceCaching() {
+void TaskProfile::DropResourceCaching(ProfileAction::ResourceCacheType cache_type) {
     if (!res_cached_) {
         return;
     }
 
     for (auto& element : elements_) {
-        element->DropResourceCaching();
+        element->DropResourceCaching(cache_type);
     }
 
     res_cached_ = false;
 }
 
-void TaskProfiles::DropResourceCaching() const {
+void TaskProfiles::DropResourceCaching(ProfileAction::ResourceCacheType cache_type) const {
     for (auto& iter : profiles_) {
-        iter.second->DropResourceCaching();
+        iter.second->DropResourceCaching(cache_type);
     }
 }
 
@@ -457,8 +537,7 @@
                 android::base::StringPrintf(TEMPLATE_TASK_PROFILE_API_FILE, api_level);
         if (!access(api_profiles_path.c_str(), F_OK) || errno != ENOENT) {
             if (!Load(CgroupMap::GetInstance(), api_profiles_path)) {
-                LOG(ERROR) << "Loading " << api_profiles_path << " for [" << getpid()
-                           << "] failed";
+                LOG(ERROR) << "Loading " << api_profiles_path << " for [" << getpid() << "] failed";
             }
         }
     }
@@ -651,10 +730,13 @@
 }
 
 bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid,
-                                      const std::vector<std::string>& profiles) {
+                                      const std::vector<std::string>& profiles, bool use_fd_cache) {
     for (const auto& name : profiles) {
         TaskProfile* profile = GetProfile(name);
         if (profile != nullptr) {
+            if (use_fd_cache) {
+                profile->EnableResourceCaching(ProfileAction::RCT_PROCESS);
+            }
             if (!profile->ExecuteForProcess(uid, pid)) {
                 PLOG(WARNING) << "Failed to apply " << name << " process profile";
             }
@@ -671,7 +753,7 @@
         TaskProfile* profile = GetProfile(name);
         if (profile != nullptr) {
             if (use_fd_cache) {
-                profile->EnableResourceCaching();
+                profile->EnableResourceCaching(ProfileAction::RCT_TASK);
             }
             if (!profile->ExecuteForTask(tid)) {
                 PLOG(WARNING) << "Failed to apply " << name << " task profile";
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 278892d..1aaa196 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -45,14 +45,19 @@
 // Abstract profile element
 class ProfileAction {
   public:
+    enum ResourceCacheType { RCT_TASK = 0, RCT_PROCESS, RCT_COUNT };
+
     virtual ~ProfileAction() {}
 
     // Default implementations will fail
     virtual bool ExecuteForProcess(uid_t, pid_t) const { return false; };
     virtual bool ExecuteForTask(int) const { return false; };
 
-    virtual void EnableResourceCaching() {}
-    virtual void DropResourceCaching() {}
+    virtual void EnableResourceCaching(ResourceCacheType) {}
+    virtual void DropResourceCaching(ResourceCacheType) {}
+
+  protected:
+    enum CacheUseResult { SUCCESS, FAIL, UNUSED };
 };
 
 // Profile actions
@@ -108,67 +113,47 @@
     std::string value_;
 };
 
-// Abstract profile element for cached fd
-class CachedFdProfileAction : public ProfileAction {
-  public:
-    virtual void EnableResourceCaching();
-    virtual void DropResourceCaching();
-
-  protected:
-    enum FdState {
-        FDS_INACCESSIBLE = -1,
-        FDS_APP_DEPENDENT = -2,
-        FDS_NOT_CACHED = -3,
-    };
-
-    android::base::unique_fd fd_;
-    mutable std::mutex fd_mutex_;
-
-    static bool IsAppDependentPath(const std::string& path);
-
-    void InitFd(const std::string& path);
-    bool IsFdValid() const { return fd_ > FDS_INACCESSIBLE; }
-
-    virtual const std::string GetPath() const = 0;
-};
-
 // Set cgroup profile element
-class SetCgroupAction : public CachedFdProfileAction {
+class SetCgroupAction : public ProfileAction {
   public:
     SetCgroupAction(const CgroupController& c, const std::string& p);
 
     virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
     virtual bool ExecuteForTask(int tid) const;
+    virtual void EnableResourceCaching(ResourceCacheType cache_type);
+    virtual void DropResourceCaching(ResourceCacheType cache_type);
 
     const CgroupController* controller() const { return &controller_; }
 
-  protected:
-    const std::string GetPath() const override { return controller_.GetTasksFilePath(path_); }
-
   private:
     CgroupController controller_;
     std::string path_;
+    android::base::unique_fd fd_[ProfileAction::RCT_COUNT];
+    mutable std::mutex fd_mutex_;
 
     static bool AddTidToCgroup(int tid, int fd, const char* controller_name);
+    CacheUseResult UseCachedFd(ResourceCacheType cache_type, int id) const;
 };
 
 // Write to file action
-class WriteFileAction : public CachedFdProfileAction {
+class WriteFileAction : public ProfileAction {
   public:
     WriteFileAction(const std::string& path, const std::string& value, bool logfailures);
 
     virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
     virtual bool ExecuteForTask(int tid) const;
-
-  protected:
-    const std::string GetPath() const override { return path_; }
+    virtual void EnableResourceCaching(ResourceCacheType cache_type);
+    virtual void DropResourceCaching(ResourceCacheType cache_type);
 
   private:
     std::string path_, value_;
     bool logfailures_;
+    android::base::unique_fd fd_;
+    mutable std::mutex fd_mutex_;
 
     static bool WriteValueToFile(const std::string& value, const std::string& path,
                                  bool logfailures);
+    CacheUseResult UseCachedFd(ResourceCacheType cache_type, const std::string& value) const;
 };
 
 class TaskProfile {
@@ -180,8 +165,8 @@
 
     bool ExecuteForProcess(uid_t uid, pid_t pid) const;
     bool ExecuteForTask(int tid) const;
-    void EnableResourceCaching();
-    void DropResourceCaching();
+    void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type);
+    void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
 
   private:
     bool res_cached_;
@@ -196,8 +181,8 @@
 
     virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
     virtual bool ExecuteForTask(int tid) const;
-    virtual void EnableResourceCaching();
-    virtual void DropResourceCaching();
+    virtual void EnableResourceCaching(ProfileAction::ResourceCacheType cache_type);
+    virtual void DropResourceCaching(ProfileAction::ResourceCacheType cache_type);
 
   private:
     std::vector<std::shared_ptr<TaskProfile>> profiles_;
@@ -210,8 +195,9 @@
 
     TaskProfile* GetProfile(const std::string& name) const;
     const ProfileAttribute* GetAttribute(const std::string& name) const;
-    void DropResourceCaching() const;
-    bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles);
+    void DropResourceCaching(ProfileAction::ResourceCacheType cache_type) const;
+    bool SetProcessProfiles(uid_t uid, pid_t pid, const std::vector<std::string>& profiles,
+                            bool use_fd_cache);
     bool SetTaskProfiles(int tid, const std::vector<std::string>& profiles, bool use_fd_cache);
 
   private:
diff --git a/libqtaguid/Android.bp b/libqtaguid/Android.bp
deleted file mode 100644
index 64db095..0000000
--- a/libqtaguid/Android.bp
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_library_headers {
-    name: "libqtaguid_headers",
-    vendor_available: false,
-    host_supported: false,
-    export_include_dirs: ["include"],
-    target: {
-        linux_bionic: {
-            enabled: true,
-        },
-    },
-}
-
-cc_library {
-    name: "libqtaguid",
-    vendor_available: false,
-    host_supported: false,
-    target: {
-        android: {
-            srcs: [
-                "qtaguid.c",
-            ],
-            sanitize: {
-                misc_undefined: ["integer"],
-            },
-        },
-    },
-
-    shared_libs: ["liblog"],
-    header_libs: [
-        "libqtaguid_headers",
-    ],
-    export_header_lib_headers: ["libqtaguid_headers"],
-    local_include_dirs: ["include"],
-
-    cflags: [
-        "-Werror",
-        "-Wall",
-        "-Wextra",
-    ],
-}
diff --git a/libqtaguid/include/qtaguid/qtaguid.h b/libqtaguid/include/qtaguid/qtaguid.h
deleted file mode 100644
index 72285e5..0000000
--- a/libqtaguid/include/qtaguid/qtaguid.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2011 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.
- */
-
-#ifndef __LEGACY_QTAGUID_H
-#define __LEGACY_QTAGUID_H
-
-#include <stdint.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/*
- * Set tags (and owning UIDs) for network sockets. The socket must be untagged
- * by calling qtaguid_untagSocket() before closing it, otherwise the qtaguid
- * module will keep a reference to it even after close.
- */
-extern int legacy_tagSocket(int sockfd, int tag, uid_t uid);
-
-/*
- * Untag a network socket before closing.
- */
-extern int legacy_untagSocket(int sockfd);
-
-/*
- * For the given uid, switch counter sets.
- * The kernel only keeps a limited number of sets.
- * 2 for now.
- */
-extern int legacy_setCounterSet(int counterSetNum, uid_t uid);
-
-/*
- * Delete all tag info that relates to the given tag an uid.
- * If the tag is 0, then ALL info about the uid is freeded.
- * The delete data also affects active tagged socketd, which are
- * then untagged.
- * The calling process can only operate on its own tags.
- * Unless it is part of the happy AID_NET_BW_ACCT group.
- * In which case it can clobber everything.
- */
-extern int legacy_deleteTagData(int tag, uid_t uid);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* __LEGACY_QTAGUID_H */
diff --git a/libqtaguid/qtaguid.c b/libqtaguid/qtaguid.c
deleted file mode 100644
index cd38bad..0000000
--- a/libqtaguid/qtaguid.c
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
-** Copyright 2011, 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.
-*/
-
-// #define LOG_NDEBUG 0
-
-#define LOG_TAG "qtaguid"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <pthread.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <log/log.h>
-#include <qtaguid/qtaguid.h>
-
-static const char* CTRL_PROCPATH = "/proc/net/xt_qtaguid/ctrl";
-static const int CTRL_MAX_INPUT_LEN = 128;
-
-/*
- * One per proccess.
- * Once the device is open, this process will have its socket tags tracked.
- * And on exit or untimely death, all socket tags will be removed.
- * A process can only open /dev/xt_qtaguid once.
- * It should not close it unless it is really done with all the socket tags.
- * Failure to open it will be visible when socket tagging will be attempted.
- */
-static int resTrackFd = -1;
-pthread_once_t resTrackInitDone = PTHREAD_ONCE_INIT;
-
-/* Only call once per process. */
-void legacy_resTrack(void) {
-    resTrackFd = TEMP_FAILURE_RETRY(open("/dev/xt_qtaguid", O_RDONLY | O_CLOEXEC));
-}
-
-/*
- * Returns:
- *   0 on success.
- *   -errno on failure.
- */
-static int write_ctrl(const char* cmd) {
-    int fd, res, savedErrno;
-
-    ALOGV("write_ctrl(%s)", cmd);
-
-    fd = TEMP_FAILURE_RETRY(open(CTRL_PROCPATH, O_WRONLY | O_CLOEXEC));
-    if (fd < 0) {
-        return -errno;
-    }
-
-    res = TEMP_FAILURE_RETRY(write(fd, cmd, strlen(cmd)));
-    if (res < 0) {
-        savedErrno = errno;
-    } else {
-        savedErrno = 0;
-    }
-    if (res < 0) {
-        // ALOGV is enough because all the callers also log failures
-        ALOGV("Failed write_ctrl(%s) res=%d errno=%d", cmd, res, savedErrno);
-    }
-    close(fd);
-    return -savedErrno;
-}
-
-int legacy_tagSocket(int sockfd, int tag, uid_t uid) {
-    char lineBuf[CTRL_MAX_INPUT_LEN];
-    int res;
-    uint64_t kTag = ((uint64_t)tag << 32);
-
-    pthread_once(&resTrackInitDone, legacy_resTrack);
-
-    snprintf(lineBuf, sizeof(lineBuf), "t %d %" PRIu64 " %d", sockfd, kTag, uid);
-
-    ALOGV("Tagging socket %d with tag %" PRIx64 "{%u,0} for uid %d", sockfd, kTag, tag, uid);
-
-    res = write_ctrl(lineBuf);
-    if (res < 0) {
-        ALOGI("Tagging socket %d with tag %" PRIx64 "(%d) for uid %d failed errno=%d", sockfd, kTag,
-              tag, uid, res);
-    }
-
-    return res;
-}
-
-int legacy_untagSocket(int sockfd) {
-    char lineBuf[CTRL_MAX_INPUT_LEN];
-    int res;
-
-    ALOGV("Untagging socket %d", sockfd);
-
-    snprintf(lineBuf, sizeof(lineBuf), "u %d", sockfd);
-    res = write_ctrl(lineBuf);
-    if (res < 0) {
-        ALOGI("Untagging socket %d failed errno=%d", sockfd, res);
-    }
-
-    return res;
-}
-
-int legacy_setCounterSet(int counterSetNum, uid_t uid) {
-    char lineBuf[CTRL_MAX_INPUT_LEN];
-    int res;
-
-    ALOGV("Setting counters to set %d for uid %d", counterSetNum, uid);
-
-    snprintf(lineBuf, sizeof(lineBuf), "s %d %d", counterSetNum, uid);
-    res = write_ctrl(lineBuf);
-    return res;
-}
-
-int legacy_deleteTagData(int tag, uid_t uid) {
-    char lineBuf[CTRL_MAX_INPUT_LEN];
-    int cnt = 0, res = 0;
-    uint64_t kTag = (uint64_t)tag << 32;
-
-    ALOGV("Deleting tag data with tag %" PRIx64 "{%d,0} for uid %d", kTag, tag, uid);
-
-    pthread_once(&resTrackInitDone, legacy_resTrack);
-
-    snprintf(lineBuf, sizeof(lineBuf), "d %" PRIu64 " %d", kTag, uid);
-    res = write_ctrl(lineBuf);
-    if (res < 0) {
-        ALOGI("Deleting tag data with tag %" PRIx64 "/%d for uid %d failed with cnt=%d errno=%d",
-              kTag, tag, uid, cnt, errno);
-    }
-
-    return res;
-}
diff --git a/libsparse/Android.bp b/libsparse/Android.bp
index 3f9aeb2..02bfee6 100644
--- a/libsparse/Android.bp
+++ b/libsparse/Android.bp
@@ -96,12 +96,14 @@
 
 cc_fuzz {
     name: "sparse_fuzzer",
-    host_supported: false,
+    host_supported: true,
     srcs: [
         "sparse_fuzzer.cpp",
     ],
     static_libs: [
         "libsparse",
+        "libbase",
+        "libz",
         "liblog",
     ],
 }
diff --git a/libsparse/include/sparse/sparse.h b/libsparse/include/sparse/sparse.h
index 2f75349..9f91269 100644
--- a/libsparse/include/sparse/sparse.h
+++ b/libsparse/include/sparse/sparse.h
@@ -244,21 +244,6 @@
 int sparse_file_read(struct sparse_file *s, int fd, bool sparse, bool crc);
 
 /**
- * sparse_file_read_buf - read a buffer into a sparse file cookie
- *
- * @s - sparse file cookie
- * @buf - buffer to read from
- * @crc - verify the crc of a file in the Android sparse file format
- *
- * Reads a buffer into a sparse file cookie. The buffer must remain
- * valid until the sparse file cookie is freed. If crc is true, the
- * crc of the sparse file will be verified.
- *
- * Returns 0 on success, negative errno on error.
- */
-int sparse_file_read_buf(struct sparse_file *s, char *buf, bool crc);
-
-/**
  * sparse_file_import - import an existing sparse file
  *
  * @fd - file descriptor to read from
@@ -277,6 +262,7 @@
  * sparse_file_import_buf - import an existing sparse file from a buffer
  *
  * @buf - buffer to read from
+ * @len - length of buffer
  * @verbose - print verbose errors while reading the sparse file
  * @crc - verify the crc of a file in the Android sparse file format
  *
@@ -286,7 +272,7 @@
  *
  * Returns a new sparse file cookie on success, NULL on error.
  */
-struct sparse_file *sparse_file_import_buf(char* buf, bool verbose, bool crc);
+struct sparse_file* sparse_file_import_buf(char* buf, size_t len, bool verbose, bool crc);
 
 /**
  * sparse_file_import_auto - import an existing sparse or normal file
diff --git a/libsparse/output_file.cpp b/libsparse/output_file.cpp
index b2c5407..cb5d730 100644
--- a/libsparse/output_file.cpp
+++ b/libsparse/output_file.cpp
@@ -54,6 +54,8 @@
 #define SPARSE_HEADER_LEN (sizeof(sparse_header_t))
 #define CHUNK_HEADER_LEN (sizeof(chunk_header_t))
 
+#define FILL_ZERO_BUFSIZE (2 * 1024 * 1024)
+
 #define container_of(inner, outer_t, elem) ((outer_t*)((char*)(inner)-offsetof(outer_t, elem)))
 
 struct output_file_ops {
@@ -391,13 +393,29 @@
   ret = out->ops->write(out, data, len);
   if (ret < 0) return -1;
   if (zero_len) {
-    ret = out->ops->write(out, out->zero_buf, zero_len);
-    if (ret < 0) return -1;
+    uint64_t len = zero_len;
+    uint64_t write_len;
+    while (len) {
+      write_len = std::min(len, (uint64_t)FILL_ZERO_BUFSIZE);
+      ret = out->ops->write(out, out->zero_buf, write_len);
+      if (ret < 0) {
+        return ret;
+      }
+      len -= write_len;
+    }
   }
 
   if (out->use_crc) {
     out->crc32 = sparse_crc32(out->crc32, data, len);
-    if (zero_len) out->crc32 = sparse_crc32(out->crc32, out->zero_buf, zero_len);
+    if (zero_len) {
+      uint64_t len = zero_len;
+      uint64_t write_len;
+      while (len) {
+        write_len = std::min(len, (uint64_t)FILL_ZERO_BUFSIZE);
+        out->crc32 = sparse_crc32(out->crc32, out->zero_buf, write_len);
+        len -= write_len;
+      }
+    }
   }
 
   out->cur_out_ptr += rnd_up_len;
@@ -460,12 +478,12 @@
   uint64_t write_len;
 
   /* Initialize fill_buf with the fill_val */
-  for (i = 0; i < out->block_size / sizeof(uint32_t); i++) {
+  for (i = 0; i < FILL_ZERO_BUFSIZE / sizeof(uint32_t); i++) {
     out->fill_buf[i] = fill_val;
   }
 
   while (len) {
-    write_len = std::min(len, (uint64_t)out->block_size);
+    write_len = std::min(len, (uint64_t)FILL_ZERO_BUFSIZE);
     ret = out->ops->write(out, out->fill_buf, write_len);
     if (ret < 0) {
       return ret;
@@ -512,13 +530,15 @@
   out->crc32 = 0;
   out->use_crc = crc;
 
-  out->zero_buf = reinterpret_cast<char*>(calloc(block_size, 1));
+  // don't use sparse format block size as it can takes up to 32GB
+  out->zero_buf = reinterpret_cast<char*>(calloc(FILL_ZERO_BUFSIZE, 1));
   if (!out->zero_buf) {
     error_errno("malloc zero_buf");
     return -ENOMEM;
   }
 
-  out->fill_buf = reinterpret_cast<uint32_t*>(calloc(block_size, 1));
+  // don't use sparse format block size as it can takes up to 32GB
+  out->fill_buf = reinterpret_cast<uint32_t*>(calloc(FILL_ZERO_BUFSIZE, 1));
   if (!out->fill_buf) {
     error_errno("malloc fill_buf");
     ret = -ENOMEM;
diff --git a/libsparse/simg_dump.py b/libsparse/simg_dump.py
index b0b3b22..8811a52 100755
--- a/libsparse/simg_dump.py
+++ b/libsparse/simg_dump.py
@@ -120,7 +120,7 @@
                           "output offset", "output blocks", "type", "hash"])
 
     offset = 0
-    for i in xrange(1, total_chunks + 1):
+    for i in range(1, total_chunks + 1):
       header_bin = FH.read(12)
       header = struct.unpack("<2H2I", header_bin)
       chunk_type = header[0]
@@ -159,7 +159,7 @@
           if showhash:
             h = hashlib.sha1()
             data = fill_bin * (blk_sz / 4);
-            for block in xrange(chunk_sz):
+            for block in range(chunk_sz):
               h.update(data)
             curhash = h.hexdigest()
       elif chunk_type == 0xCAC3:
diff --git a/libsparse/sparse_fuzzer.cpp b/libsparse/sparse_fuzzer.cpp
index 42f331f..235d15d 100644
--- a/libsparse/sparse_fuzzer.cpp
+++ b/libsparse/sparse_fuzzer.cpp
@@ -1,16 +1,27 @@
 #include "include/sparse/sparse.h"
 
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  if (size < 2 * sizeof(wchar_t)) return 0;
+static volatile int count;
 
-  int64_t blocksize = 4096;
-  struct sparse_file* file = sparse_file_new(size, blocksize);
-  if (!file) {
+int WriteCallback(void* priv  __attribute__((__unused__)), const void* data, size_t len) {
+  if (!data) {
+    return 0;
+  }
+  if (len == 0) {
     return 0;
   }
 
-  unsigned int block = 1;
-  sparse_file_add_data(file, &data, size, block);
-  sparse_file_destroy(file);
+  const char* p = (const char*)data;
+  // Just to make sure the data is accessible
+  // We only check the head and tail to save time
+  count += *p;
+  count += *(p+len-1);
   return 0;
 }
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  struct sparse_file* file = sparse_file_import_buf((char*)data, size, true, false);
+  if (!file) {
+      return 0;
+  }
+  return sparse_file_callback(file, false, false, WriteCallback, nullptr);
+}
diff --git a/libsparse/sparse_read.cpp b/libsparse/sparse_read.cpp
index c4c1823..0f39172 100644
--- a/libsparse/sparse_read.cpp
+++ b/libsparse/sparse_read.cpp
@@ -58,14 +58,15 @@
 
 class SparseFileSource {
  public:
-  /* Seeks the source ahead by the given offset. */
-  virtual void Seek(int64_t offset) = 0;
+  /* Seeks the source ahead by the given offset.
+   * Return 0 if successful. */
+  virtual int Seek(int64_t offset) = 0;
 
   /* Return the current offset. */
   virtual int64_t GetOffset() = 0;
 
-  /* Set the current offset. Return 0 if successful. */
-  virtual int SetOffset(int64_t offset) = 0;
+  /* Rewind to beginning. Return 0 if successful. */
+  virtual int Rewind() = 0;
 
   /* Adds the given length from the current offset of the source to the file at the given block.
    * Return 0 if successful. */
@@ -88,12 +89,14 @@
   SparseFileFdSource(int fd) : fd(fd) {}
   ~SparseFileFdSource() override {}
 
-  void Seek(int64_t off) override { lseek64(fd, off, SEEK_CUR); }
+  int Seek(int64_t off) override {
+    return lseek64(fd, off, SEEK_CUR) != -1 ? 0 : -errno;
+  }
 
   int64_t GetOffset() override { return lseek64(fd, 0, SEEK_CUR); }
 
-  int SetOffset(int64_t offset) override {
-    return lseek64(fd, offset, SEEK_SET) == offset ? 0 : -errno;
+  int Rewind() override {
+    return lseek64(fd, 0, SEEK_SET) == 0 ? 0 : -errno;
   }
 
   int AddToSparseFile(struct sparse_file* s, int64_t len, unsigned int block) override {
@@ -120,39 +123,74 @@
 
 class SparseFileBufSource : public SparseFileSource {
  private:
+  char* buf_start;
+  char* buf_end;
   char* buf;
   int64_t offset;
 
+  int AccessOkay(int64_t len) {
+    if (len <= 0) return -EINVAL;
+    if (buf < buf_start) return -EOVERFLOW;
+    if (buf >= buf_end) return -EOVERFLOW;
+    if (len > buf_end - buf) return -EOVERFLOW;
+
+    return 0;
+  }
+
  public:
-  SparseFileBufSource(char* buf) : buf(buf), offset(0) {}
+  SparseFileBufSource(char* buf, uint64_t len) {
+    this->buf = buf;
+    this->offset = 0;
+    this->buf_start = buf;
+    this->buf_end = buf + len;
+  }
   ~SparseFileBufSource() override {}
 
-  void Seek(int64_t off) override {
+  int Seek(int64_t off) override {
+    int ret = AccessOkay(off);
+    if (ret < 0) {
+      return ret;
+    }
     buf += off;
     offset += off;
+    return 0;
   }
 
   int64_t GetOffset() override { return offset; }
 
-  int SetOffset(int64_t off) override {
-    buf += off - offset;
-    offset = off;
+  int Rewind() override {
+    buf = buf_start;
+    offset = 0;
     return 0;
   }
 
   int AddToSparseFile(struct sparse_file* s, int64_t len, unsigned int block) override {
+    int ret = AccessOkay(len);
+    if (ret < 0) {
+      return ret;
+    }
     return sparse_file_add_data(s, buf, len, block);
   }
 
   int ReadValue(void* ptr, int len) override {
+    int ret = AccessOkay(len);
+    if (ret < 0) {
+      return ret;
+    }
     memcpy(ptr, buf, len);
-    Seek(len);
+    buf += len;
+    offset += len;
     return 0;
   }
 
   int GetCrc32(uint32_t* crc32, int64_t len) override {
+    int ret = AccessOkay(len);
+    if (ret < 0) {
+      return ret;
+    }
     *crc32 = sparse_crc32(*crc32, buf, len);
-    Seek(len);
+    buf += len;
+    offset += len;
     return 0;
   }
 };
@@ -175,7 +213,7 @@
                              SparseFileSource* source, unsigned int blocks, unsigned int block,
                              uint32_t* crc32) {
   int ret;
-  int64_t len = blocks * s->block_size;
+  int64_t len = (int64_t)blocks * s->block_size;
 
   if (chunk_size % s->block_size != 0) {
     return -EINVAL;
@@ -196,7 +234,10 @@
       return ret;
     }
   } else {
-    source->Seek(len);
+    ret = source->Seek(len);
+    if (ret < 0) {
+      return ret;
+    }
   }
 
   return 0;
@@ -379,7 +420,10 @@
     /* Skip the remaining bytes in a header that is longer than
      * we expected.
      */
-    source->Seek(sparse_header.file_hdr_sz - SPARSE_HEADER_LEN);
+    ret = source->Seek(sparse_header.file_hdr_sz - SPARSE_HEADER_LEN);
+    if (ret < 0) {
+      return ret;
+    }
   }
 
   for (i = 0; i < sparse_header.total_chunks; i++) {
@@ -392,7 +436,10 @@
       /* Skip the remaining bytes in a header that is longer than
        * we expected.
        */
-      source->Seek(sparse_header.chunk_hdr_sz - CHUNK_HEADER_LEN);
+      ret = source->Seek(sparse_header.chunk_hdr_sz - CHUNK_HEADER_LEN);
+      if (ret < 0) {
+        return ret;
+      }
     }
 
     ret = process_chunk(s, source, sparse_header.chunk_hdr_sz, &chunk_header, cur_block, crc_ptr);
@@ -474,11 +521,6 @@
   }
 }
 
-int sparse_file_read_buf(struct sparse_file* s, char* buf, bool crc) {
-  SparseFileBufSource source(buf);
-  return sparse_file_read_sparse(s, &source, crc);
-}
-
 static struct sparse_file* sparse_file_import_source(SparseFileSource* source, bool verbose,
                                                      bool crc) {
   int ret;
@@ -510,6 +552,14 @@
     return nullptr;
   }
 
+  if (!sparse_header.blk_sz || (sparse_header.blk_sz % 4)) {
+    return nullptr;
+  }
+
+  if (!sparse_header.total_blks) {
+    return nullptr;
+  }
+
   len = (int64_t)sparse_header.total_blks * sparse_header.blk_sz;
   s = sparse_file_new(sparse_header.blk_sz, len);
   if (!s) {
@@ -517,7 +567,7 @@
     return nullptr;
   }
 
-  ret = source->SetOffset(0);
+  ret = source->Rewind();
   if (ret < 0) {
     verbose_error(verbose, ret, "seeking");
     sparse_file_destroy(s);
@@ -540,8 +590,8 @@
   return sparse_file_import_source(&source, verbose, crc);
 }
 
-struct sparse_file* sparse_file_import_buf(char* buf, bool verbose, bool crc) {
-  SparseFileBufSource source(buf);
+struct sparse_file* sparse_file_import_buf(char* buf, size_t len, bool verbose, bool crc) {
+  SparseFileBufSource source(buf, len);
   return sparse_file_import_source(&source, verbose, crc);
 }
 
diff --git a/libstats/pull_rust/Android.bp b/libstats/pull_rust/Android.bp
index f07e32b..4ffa98d 100644
--- a/libstats/pull_rust/Android.bp
+++ b/libstats/pull_rust/Android.bp
@@ -44,6 +44,11 @@
             ],
         },
     },
+    min_sdk_version: "apex_inherit",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ]
 }
 
 rust_library {
diff --git a/libutils/Android.bp b/libutils/Android.bp
index bda9d6b..234638b 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -28,16 +28,18 @@
     min_sdk_version: "apex_inherit",
 
     header_libs: [
-        "liblog_headers",
-        "libsystem_headers",
+        "libbase_headers",
         "libcutils_headers",
+        "liblog_headers",
         "libprocessgroup_headers",
+        "libsystem_headers",
     ],
     export_header_lib_headers: [
-        "liblog_headers",
-        "libsystem_headers",
+        "libbase_headers",
         "libcutils_headers",
+        "liblog_headers",
         "libprocessgroup_headers",
+        "libsystem_headers",
     ],
     export_include_dirs: ["include"],
 
@@ -299,13 +301,14 @@
 
     srcs: [
         "BitSet_test.cpp",
+        "Errors_test.cpp",
         "FileMap_test.cpp",
         "LruCache_test.cpp",
         "Mutex_test.cpp",
         "SharedBuffer_test.cpp",
         "Singleton_test.cpp",
-        "String8_test.cpp",
         "String16_test.cpp",
+        "String8_test.cpp",
         "StrongPointer_test.cpp",
         "Timers_test.cpp",
         "Unicode_test.cpp",
@@ -364,6 +367,7 @@
         "-Wall",
         "-Werror",
     ],
+    header_libs: ["libutils_headers"],
 }
 
 cc_test_library {
@@ -376,6 +380,7 @@
         "-Werror",
     ],
     shared_libs: ["libutils_test_singleton1"],
+    header_libs: ["libutils_headers"],
 }
 
 cc_benchmark {
diff --git a/libutils/Errors_test.cpp b/libutils/Errors_test.cpp
new file mode 100644
index 0000000..873c994
--- /dev/null
+++ b/libutils/Errors_test.cpp
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+#include "utils/ErrorsMacros.h"
+
+#include <android-base/result.h>
+
+#include <gtest/gtest.h>
+
+using namespace android;
+
+using android::base::Error;
+using android::base::Result;
+
+status_t success_or_fail(bool success) {
+    if (success)
+        return OK;
+    else
+        return PERMISSION_DENIED;
+}
+
+TEST(errors, unwrap_or_return) {
+    auto f = [](bool success, int* val) -> status_t {
+        OR_RETURN(success_or_fail(success));
+        *val = 10;
+        return OK;
+    };
+
+    int val;
+    status_t s = f(true, &val);
+    EXPECT_EQ(OK, s);
+    EXPECT_EQ(10, val);
+
+    val = 0;  // reset
+    status_t q = f(false, &val);
+    EXPECT_EQ(PERMISSION_DENIED, q);
+    EXPECT_EQ(0, val);
+}
+
+TEST(errors, unwrap_or_return_result) {
+    auto f = [](bool success) -> Result<std::string, StatusT> {
+        OR_RETURN(success_or_fail(success));
+        return "hello";
+    };
+
+    auto r = f(true);
+    EXPECT_TRUE(r.ok());
+    EXPECT_EQ("hello", *r);
+
+    auto s = f(false);
+    EXPECT_FALSE(s.ok());
+    EXPECT_EQ(PERMISSION_DENIED, s.error().code());
+    EXPECT_EQ("PERMISSION_DENIED", s.error().message());
+}
+
+TEST(errors, unwrap_or_return_result_int) {
+    auto f = [](bool success) -> Result<int, StatusT> {
+        OR_RETURN(success_or_fail(success));
+        return 10;
+    };
+
+    auto r = f(true);
+    EXPECT_TRUE(r.ok());
+    EXPECT_EQ(10, *r);
+
+    auto s = f(false);
+    EXPECT_FALSE(s.ok());
+    EXPECT_EQ(PERMISSION_DENIED, s.error().code());
+    EXPECT_EQ("PERMISSION_DENIED", s.error().message());
+}
+
+TEST(errors, unwrap_or_fatal) {
+    OR_FATAL(success_or_fail(true));
+
+    EXPECT_DEATH(OR_FATAL(success_or_fail(false)), "PERMISSION_DENIED");
+}
+
+TEST(errors, result_in_status) {
+    auto f = [](bool success) -> Result<std::string, StatusT> {
+        if (success)
+            return "OK";
+        else
+            return Error<StatusT>(PERMISSION_DENIED) << "custom error message";
+    };
+
+    auto g = [&](bool success) -> status_t {
+        std::string val = OR_RETURN(f(success));
+        EXPECT_EQ("OK", val);
+        return OK;
+    };
+
+    status_t a = g(true);
+    EXPECT_EQ(OK, a);
+
+    status_t b = g(false);
+    EXPECT_EQ(PERMISSION_DENIED, b);
+}
diff --git a/libutils/Unicode_test.cpp b/libutils/Unicode_test.cpp
index b92eef8..8b994d9 100644
--- a/libutils/Unicode_test.cpp
+++ b/libutils/Unicode_test.cpp
@@ -100,7 +100,7 @@
         0xF0, 0x90, 0x80, 0x80, // U+10000, 2 UTF-16 character
     };
 
-    char16_t output[1 + 1 + 1 + 2 + 1]; // Room for NULL
+    char16_t output[1 + 1 + 1 + 2 + 1];  // Room for null
 
     utf8_to_utf16(str, sizeof(str), output, sizeof(output) / sizeof(output[0]));
 
@@ -114,8 +114,7 @@
             << "should be first half of surrogate U+10000";
     EXPECT_EQ(0xDC00, output[4])
             << "should be second half of surrogate U+10000";
-    EXPECT_EQ(NULL, output[5])
-            << "should be NULL terminated";
+    EXPECT_EQ(0, output[5]) << "should be null terminated";
 }
 
 TEST_F(UnicodeTest, strstr16EmptyTarget) {
diff --git a/libutils/include/utils/ErrorsMacros.h b/libutils/include/utils/ErrorsMacros.h
new file mode 100644
index 0000000..048c538
--- /dev/null
+++ b/libutils/include/utils/ErrorsMacros.h
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "Errors.h"
+
+// It would have been better if this file (ErrorsMacros.h) is entirely in utils/Errors.h. However
+// that is infeasible as some (actually many) are using utils/Errors.h via the implicit include path
+// `system/core/include` [1].  Since such users are not guaranteed to specify the dependency to
+// libbase_headers, the following headers from libbase_headers can't be found.
+// [1] build/soong/cc/config/global.go#commonGlobalIncludes
+#include <android-base/errors.h>
+#include <android-base/result.h>
+
+#include <assert.h>
+
+namespace android {
+
+// StatusT is a wrapper class for status_t. Use this type instead of status_t when instantiating
+// Result<T, E> and Error<E> template classes. This is required to distinguish status_t from
+// other integer-based error code types like errno, and also to provide utility functions like
+// print().
+struct StatusT {
+    StatusT() : val_(OK) {}
+    StatusT(status_t s) : val_(s) {}
+    const status_t& value() const { return val_; }
+    operator status_t() const { return val_; }
+    std::string print() const { return statusToString(val_); }
+
+    status_t val_;
+};
+
+namespace base {
+
+// Specialization of android::base::OkOrFail<V> for V = status_t. This is used to use the OR_RETURN
+// and OR_FATAL macros with statements that yields a value of status_t. See android-base/errors.h
+// for the detailed contract.
+template <>
+struct OkOrFail<status_t> {
+    // Tests if status_t is a success value of not.
+    static bool IsOk(const status_t& s) { return s == OK; }
+
+    // Unwrapping status_t in the success case is just asserting that it is actually a success.
+    // We don't return OK because it would be redundant.
+    static void Unwrap([[maybe_unused]] status_t&& s) { assert(IsOk(s)); }
+
+    // Consumes status_t when it's a fail value
+    static OkOrFail<status_t> Fail(status_t&& s) {
+        assert(!IsOk(s));
+        return OkOrFail<status_t>{s};
+    }
+    status_t val_;
+
+    // And converts back into status_t. This is used when OR_RETURN is used in a function whose
+    // return type is status_t.
+    operator status_t() && { return val_; }
+
+    // Or converts into Result<T, StatusT>. This is used when OR_RETURN is used in a function whose
+    // return type is Result<T, StatusT>.
+    template <typename T, typename = std::enable_if_t<!std::is_same_v<T, status_t>>>
+    operator Result<T, StatusT>() && {
+        return Error<StatusT>(std::move(val_));
+    }
+
+    operator Result<int, StatusT>() && { return Error<StatusT>(std::move(val_)); }
+
+    // String representation of the error value.
+    static std::string ErrorMessage(const status_t& s) { return statusToString(s); }
+};
+
+}  // namespace base
+}  // namespace android
diff --git a/rootdir/avb/Android.mk b/rootdir/avb/Android.mk
index 647cfa2..8cf3172 100644
--- a/rootdir/avb/Android.mk
+++ b/rootdir/avb/Android.mk
@@ -15,19 +15,6 @@
 endif
 
 #######################################
-# q-gsi.avbpubkey
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := q-gsi.avbpubkey
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := ETC
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_PATH := $(my_gsi_avb_keys_path)
-
-include $(BUILD_PREBUILT)
-
-#######################################
 # q-developer-gsi.avbpubkey
 include $(CLEAR_VARS)
 
@@ -41,19 +28,6 @@
 include $(BUILD_PREBUILT)
 
 #######################################
-# r-gsi.avbpubkey
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := r-gsi.avbpubkey
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := ETC
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_PATH := $(my_gsi_avb_keys_path)
-
-include $(BUILD_PREBUILT)
-
-#######################################
 # r-developer-gsi.avbpubkey
 include $(CLEAR_VARS)
 
@@ -67,19 +41,6 @@
 include $(BUILD_PREBUILT)
 
 #######################################
-# s-gsi.avbpubkey
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := s-gsi.avbpubkey
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := ETC
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_PATH := $(my_gsi_avb_keys_path)
-
-include $(BUILD_PREBUILT)
-
-#######################################
 # s-developer-gsi.avbpubkey
 include $(CLEAR_VARS)
 
@@ -92,17 +53,4 @@
 
 include $(BUILD_PREBUILT)
 
-#######################################
-# qcar-gsi.avbpubkey
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := qcar-gsi.avbpubkey
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := ETC
-LOCAL_SRC_FILES := $(LOCAL_MODULE)
-LOCAL_MODULE_PATH := $(my_gsi_avb_keys_path)
-
-include $(BUILD_PREBUILT)
-
 my_gsi_avb_keys_path :=
diff --git a/rootdir/avb/q-gsi.avbpubkey b/rootdir/avb/q-gsi.avbpubkey
deleted file mode 100644
index 5ed7543..0000000
--- a/rootdir/avb/q-gsi.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/rootdir/avb/qcar-gsi.avbpubkey b/rootdir/avb/qcar-gsi.avbpubkey
deleted file mode 100644
index ce56646..0000000
--- a/rootdir/avb/qcar-gsi.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/rootdir/avb/r-gsi.avbpubkey b/rootdir/avb/r-gsi.avbpubkey
deleted file mode 100644
index 2609b30..0000000
--- a/rootdir/avb/r-gsi.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/rootdir/avb/s-gsi.avbpubkey b/rootdir/avb/s-gsi.avbpubkey
deleted file mode 100644
index 9065fb8..0000000
--- a/rootdir/avb/s-gsi.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/rootdir/init.rc b/rootdir/init.rc
index cd73498..c4c9eca 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -142,11 +142,21 @@
     chown system system /dev/stune/background/tasks
     chown system system /dev/stune/top-app/tasks
     chown system system /dev/stune/rt/tasks
+    chown system system /dev/stune/cgroup.procs
+    chown system system /dev/stune/foreground/cgroup.procs
+    chown system system /dev/stune/background/cgroup.procs
+    chown system system /dev/stune/top-app/cgroup.procs
+    chown system system /dev/stune/rt/cgroup.procs
     chmod 0664 /dev/stune/tasks
     chmod 0664 /dev/stune/foreground/tasks
     chmod 0664 /dev/stune/background/tasks
     chmod 0664 /dev/stune/top-app/tasks
     chmod 0664 /dev/stune/rt/tasks
+    chmod 0664 /dev/stune/cgroup.procs
+    chmod 0664 /dev/stune/foreground/cgroup.procs
+    chmod 0664 /dev/stune/background/cgroup.procs
+    chmod 0664 /dev/stune/top-app/cgroup.procs
+    chmod 0664 /dev/stune/rt/cgroup.procs
 
     # cpuctl hierarchy for devices using utilclamp
     mkdir /dev/cpuctl/foreground
@@ -172,6 +182,14 @@
     chown system system /dev/cpuctl/system/tasks
     chown system system /dev/cpuctl/system-background/tasks
     chown system system /dev/cpuctl/dex2oat/tasks
+    chown system system /dev/cpuctl/cgroup.procs
+    chown system system /dev/cpuctl/foreground/cgroup.procs
+    chown system system /dev/cpuctl/background/cgroup.procs
+    chown system system /dev/cpuctl/top-app/cgroup.procs
+    chown system system /dev/cpuctl/rt/cgroup.procs
+    chown system system /dev/cpuctl/system/cgroup.procs
+    chown system system /dev/cpuctl/system-background/cgroup.procs
+    chown system system /dev/cpuctl/dex2oat/cgroup.procs
     chmod 0664 /dev/cpuctl/tasks
     chmod 0664 /dev/cpuctl/foreground/tasks
     chmod 0664 /dev/cpuctl/background/tasks
@@ -180,12 +198,22 @@
     chmod 0664 /dev/cpuctl/system/tasks
     chmod 0664 /dev/cpuctl/system-background/tasks
     chmod 0664 /dev/cpuctl/dex2oat/tasks
+    chmod 0664 /dev/cpuctl/cgroup.procs
+    chmod 0664 /dev/cpuctl/foreground/cgroup.procs
+    chmod 0664 /dev/cpuctl/background/cgroup.procs
+    chmod 0664 /dev/cpuctl/top-app/cgroup.procs
+    chmod 0664 /dev/cpuctl/rt/cgroup.procs
+    chmod 0664 /dev/cpuctl/system/cgroup.procs
+    chmod 0664 /dev/cpuctl/system-background/cgroup.procs
+    chmod 0664 /dev/cpuctl/dex2oat/cgroup.procs
 
     # Create a cpu group for NNAPI HAL processes
     mkdir /dev/cpuctl/nnapi-hal
     chown system system /dev/cpuctl/nnapi-hal
     chown system system /dev/cpuctl/nnapi-hal/tasks
+    chown system system /dev/cpuctl/nnapi-hal/cgroup.procs
     chmod 0664 /dev/cpuctl/nnapi-hal/tasks
+    chmod 0664 /dev/cpuctl/nnapi-hal/cgroup.procs
     write /dev/cpuctl/nnapi-hal/cpu.uclamp.min 1
     write /dev/cpuctl/nnapi-hal/cpu.uclamp.latency_sensitive 1
 
@@ -193,19 +221,25 @@
     mkdir /dev/cpuctl/camera-daemon
     chown system system /dev/cpuctl/camera-daemon
     chown system system /dev/cpuctl/camera-daemon/tasks
+    chown system system /dev/cpuctl/camera-daemon/cgroup.procs
     chmod 0664 /dev/cpuctl/camera-daemon/tasks
+    chmod 0664 /dev/cpuctl/camera-daemon/cgroup.procs
 
     # Create an stune group for camera-specific processes
     mkdir /dev/stune/camera-daemon
     chown system system /dev/stune/camera-daemon
     chown system system /dev/stune/camera-daemon/tasks
+    chown system system /dev/stune/camera-daemon/cgroup.procs
     chmod 0664 /dev/stune/camera-daemon/tasks
+    chmod 0664 /dev/stune/camera-daemon/cgroup.procs
 
     # Create an stune group for NNAPI HAL processes
     mkdir /dev/stune/nnapi-hal
     chown system system /dev/stune/nnapi-hal
     chown system system /dev/stune/nnapi-hal/tasks
+    chown system system /dev/stune/nnapi-hal/cgroup.procs
     chmod 0664 /dev/stune/nnapi-hal/tasks
+    chmod 0664 /dev/stune/nnapi-hal/cgroup.procs
     write /dev/stune/nnapi-hal/schedtune.boost 1
     write /dev/stune/nnapi-hal/schedtune.prefer_idle 1
 
@@ -217,8 +251,12 @@
     chown system system /dev/blkio/background
     chown system system /dev/blkio/tasks
     chown system system /dev/blkio/background/tasks
+    chown system system /dev/blkio/cgroup.procs
+    chown system system /dev/blkio/background/cgroup.procs
     chmod 0664 /dev/blkio/tasks
     chmod 0664 /dev/blkio/background/tasks
+    chmod 0664 /dev/blkio/cgroup.procs
+    chmod 0664 /dev/blkio/background/cgroup.procs
     write /dev/blkio/blkio.weight 1000
     write /dev/blkio/background/blkio.weight 200
     write /dev/blkio/background/blkio.bfq.weight 10
@@ -367,6 +405,13 @@
     chown system system /dev/cpuset/top-app/tasks
     chown system system /dev/cpuset/restricted/tasks
     chown system system /dev/cpuset/camera-daemon/tasks
+    chown system system /dev/cpuset/cgroup.procs
+    chown system system /dev/cpuset/foreground/cgroup.procs
+    chown system system /dev/cpuset/background/cgroup.procs
+    chown system system /dev/cpuset/system-background/cgroup.procs
+    chown system system /dev/cpuset/top-app/cgroup.procs
+    chown system system /dev/cpuset/restricted/cgroup.procs
+    chown system system /dev/cpuset/camera-daemon/cgroup.procs
 
     # set system-background to 0775 so SurfaceFlinger can touch it
     chmod 0775 /dev/cpuset/system-background
@@ -378,6 +423,13 @@
     chmod 0664 /dev/cpuset/restricted/tasks
     chmod 0664 /dev/cpuset/tasks
     chmod 0664 /dev/cpuset/camera-daemon/tasks
+    chmod 0664 /dev/cpuset/foreground/cgroup.procs
+    chmod 0664 /dev/cpuset/background/cgroup.procs
+    chmod 0664 /dev/cpuset/system-background/cgroup.procs
+    chmod 0664 /dev/cpuset/top-app/cgroup.procs
+    chmod 0664 /dev/cpuset/restricted/cgroup.procs
+    chmod 0664 /dev/cpuset/cgroup.procs
+    chmod 0664 /dev/cpuset/camera-daemon/cgroup.procs
 
     # make the PSI monitor accessible to others
     chown system system /proc/pressure/memory
diff --git a/trusty/storage/proxy/Android.bp b/trusty/storage/proxy/Android.bp
index 38d8685..94f26d8 100644
--- a/trusty/storage/proxy/Android.bp
+++ b/trusty/storage/proxy/Android.bp
@@ -35,7 +35,10 @@
         "liblog",
         "libhardware_legacy",
     ],
-    header_libs: ["libcutils_headers"],
+    header_libs: [
+        "libcutils_headers",
+        "libgsi_headers",
+    ],
 
     static_libs: [
         "libfstab",
diff --git a/trusty/storage/proxy/checkpoint_handling.cpp b/trusty/storage/proxy/checkpoint_handling.cpp
index 6c2fd36..3305d8d 100644
--- a/trusty/storage/proxy/checkpoint_handling.cpp
+++ b/trusty/storage/proxy/checkpoint_handling.cpp
@@ -18,9 +18,12 @@
 #include "log.h"
 
 #include <fstab/fstab.h>
+#include <unistd.h>
 #include <cstring>
 #include <string>
 
+#include <libgsi/libgsi.h>
+
 namespace {
 
 bool checkpointingDoneForever = false;
@@ -75,3 +78,15 @@
 
     return 0;
 }
+
+/**
+ * is_gsi_running() - Check if a GSI image is running via DSU.
+ *
+ * This function is equivalent to android::gsi::IsGsiRunning(), but this API is
+ * not yet vendor-accessible although the underlying metadata file is.
+ *
+ */
+bool is_gsi_running() {
+    /* TODO(b/210501710): Expose GSI image running state to vendor storageproxyd */
+    return !access(android::gsi::kGsiBootedIndicatorFile, F_OK);
+}
diff --git a/trusty/storage/proxy/checkpoint_handling.h b/trusty/storage/proxy/checkpoint_handling.h
index f1bf27c..dfe2947 100644
--- a/trusty/storage/proxy/checkpoint_handling.h
+++ b/trusty/storage/proxy/checkpoint_handling.h
@@ -32,6 +32,8 @@
  */
 int is_data_checkpoint_active(bool* active);
 
+bool is_gsi_running();
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/trusty/storage/proxy/proxy.c b/trusty/storage/proxy/proxy.c
index c690a28..2620034 100644
--- a/trusty/storage/proxy/proxy.c
+++ b/trusty/storage/proxy/proxy.c
@@ -104,8 +104,11 @@
         return -1;
     }
 
-    /* no-execute for user, no access for group and other */
-    umask(S_IXUSR | S_IRWXG | S_IRWXO);
+    /*
+     * No access for group and other. We need execute access for user to create
+     * an accessible directory.
+     */
+    umask(S_IRWXG | S_IRWXO);
 
     return 0;
 }
diff --git a/trusty/storage/proxy/storage.c b/trusty/storage/proxy/storage.c
index 2fde30f..d74a708 100644
--- a/trusty/storage/proxy/storage.c
+++ b/trusty/storage/proxy/storage.c
@@ -16,6 +16,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <libgen.h>
 #include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
@@ -24,13 +25,16 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#include "log.h"
+#include "checkpoint_handling.h"
 #include "ipc.h"
+#include "log.h"
 #include "storage.h"
 
 #define FD_TBL_SIZE 64
 #define MAX_READ_SIZE 4096
 
+#define ALTERNATE_DATA_DIR "alternate/"
+
 enum sync_state {
     SS_UNUSED = -1,
     SS_CLEAN =  0,
@@ -44,6 +48,8 @@
 static enum sync_state dir_state;
 static enum sync_state fd_state[FD_TBL_SIZE];
 
+static bool alternate_mode;
+
 static struct {
    struct storage_file_read_resp hdr;
    uint8_t data[MAX_READ_SIZE];
@@ -216,6 +222,7 @@
                       const void *r, size_t req_len)
 {
     char *path = NULL;
+    char* parent_path;
     const struct storage_file_open_req *req = r;
     struct storage_file_open_resp resp = {0};
 
@@ -234,6 +241,24 @@
         goto err_response;
     }
 
+    /*
+     * TODO(b/210501710): Expose GSI image running state to vendor
+     * storageproxyd. We want to control data file paths in vendor_init, but we
+     * don't have access to the necessary property there yet. When we have
+     * access to that property we can set the root data path read-only and only
+     * allow creation of files in alternate/. Checking paths here temporarily
+     * until that is fixed.
+     *
+     * We are just checking for "/" instead of "alternate/" because we still
+     * want to still allow access to "persist/" in alternate mode (for now, this
+     * may change in the future).
+     */
+    if (alternate_mode && !strchr(req->name, '/')) {
+        ALOGE("%s: Cannot open root data file \"%s\" in alternate mode\n", __func__, req->name);
+        msg->result = STORAGE_ERR_ACCESS;
+        goto err_response;
+    }
+
     int rc = asprintf(&path, "%s/%s", ssdir_name, req->name);
     if (rc < 0) {
         ALOGE("%s: asprintf failed\n", __func__);
@@ -246,7 +271,23 @@
     if (req->flags & STORAGE_FILE_OPEN_TRUNCATE)
         open_flags |= O_TRUNC;
 
+    parent_path = dirname(path);
     if (req->flags & STORAGE_FILE_OPEN_CREATE) {
+        /*
+         * Create the alternate parent dir if needed & allowed.
+         *
+         * TODO(b/210501710): Expose GSI image running state to vendor
+         * storageproxyd. This directory should be created by vendor_init, once
+         * it has access to the necessary bit of information.
+         */
+        if (strstr(req->name, ALTERNATE_DATA_DIR) == req->name) {
+            rc = mkdir(parent_path, S_IRWXU);
+            if (rc && errno != EEXIST) {
+                ALOGE("%s: Could not create parent directory \"%s\": %s\n", __func__, parent_path,
+                      strerror(errno));
+            }
+        }
+
         /* open or create */
         if (req->flags & STORAGE_FILE_OPEN_CREATE_EXCLUSIVE) {
             /* create exclusive */
@@ -467,6 +508,9 @@
 
 int storage_init(const char *dirname)
 {
+    /* If there is an active DSU image, use the alternate fs mode. */
+    alternate_mode = is_gsi_running();
+
     fs_state = SS_CLEAN;
     dir_state = SS_CLEAN;
     for (uint i = 0; i < FD_TBL_SIZE; i++) {