Merge "Fix implicit cast from ssize_t to size_t in KeyedVector.h"
diff --git a/NOTICE b/NOTICE
deleted file mode 100644
index 152be20..0000000
--- a/NOTICE
+++ /dev/null
@@ -1,324 +0,0 @@
-   =========================================================================
-   ==  NOTICE file corresponding to the section 4 d of                    ==
-   ==  the Apache License, Version 2.0,                                   ==
-   ==  in this case for the Android-specific code.                        ==
-   =========================================================================
-
-Android Code
-Copyright 2005-2008 The Android Open Source Project
-
-This product includes software developed as part of
-The Android Open Source Project (http://source.android.com).
-
-   =========================================================================
-   ==  NOTICE file corresponding to the section 4 d of                    ==
-   ==  the Apache License, Version 2.0,                                   ==
-   ==  in this case for Apache Commons code.                              ==
-   =========================================================================
-
-Apache Commons
-Copyright 1999-2006 The Apache Software Foundation
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
-
-   =========================================================================
-   ==  NOTICE file corresponding to the section 4 d of                    ==
-   ==  the Apache License, Version 2.0,                                   ==
-   ==  in this case for Jakarta Commons Logging.                          ==
-   =========================================================================
-
-Jakarta Commons Logging (JCL)
-Copyright 2005,2006 The Apache Software Foundation.
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
-
-   =========================================================================
-   ==  NOTICE file corresponding to the section 4 d of                    ==
-   ==  the Apache License, Version 2.0,                                   ==
-   ==  in this case for the Nuance code.                                  ==
-   =========================================================================
-
-These files are Copyright 2007 Nuance Communications, but released under
-the Apache2 License.
-
-   =========================================================================
-   ==  NOTICE file corresponding to the section 4 d of                    ==
-   ==  the Apache License, Version 2.0,                                   ==
-   ==  in this case for the Media Codecs code.                            ==
-   =========================================================================
-
-Media Codecs
-These files are Copyright 1998 - 2009 PacketVideo, but released under
-the Apache2 License.
-
-   =========================================================================
-   ==  NOTICE file corresponding to the section 4 d of                    ==
-   ==  the Apache License, Version 2.0,                                   ==
-   ==  in this case for the TagSoup code.                                 ==
-   =========================================================================
-
-This file is part of TagSoup and is Copyright 2002-2008 by John Cowan.
-
-TagSoup is licensed under the Apache License,
-Version 2.0.  You may obtain a copy of this license at
-http://www.apache.org/licenses/LICENSE-2.0 .  You may also have
-additional legal rights not granted by this license.
-
-TagSoup is distributed in the hope that it will be useful, but
-unless required by applicable law or agreed to in writing, TagSoup
-is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
-OF ANY KIND, either express or implied; not even the implied warranty
-of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
-   =========================================================================
-   ==  NOTICE file corresponding to the section 4 d of                    ==
-   ==  the Apache License, Version 2.0,                                   ==
-   ==  in this case for Additional Codecs code.                           ==
-   =========================================================================
-
-Additional Codecs
-These files are Copyright 2003-2010 VisualOn, but released under
-the Apache2 License.
-
-  =========================================================================
-  ==  NOTICE file corresponding to the section 4 d of                    ==
-  ==  the Apache License, Version 2.0,                                   ==
-  ==  in this case for the Audio Effects code.                           ==
-  =========================================================================
-
-Audio Effects
-These files are Copyright (C) 2004-2010 NXP Software and
-Copyright (C) 2010 The Android Open Source Project, but released under
-the Apache2 License.
-
-
-                               Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-
-
-UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE
-
-Unicode Data Files include all data files under the directories
-http://www.unicode.org/Public/, http://www.unicode.org/reports/,
-and http://www.unicode.org/cldr/data/ . Unicode Software includes any
-source code published in the Unicode Standard or under the directories
-http://www.unicode.org/Public/, http://www.unicode.org/reports/, and
-http://www.unicode.org/cldr/data/.
-
-NOTICE TO USER: Carefully read the following legal agreement. BY
-DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S DATA
-FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), YOU UNEQUIVOCALLY
-ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE TERMS AND CONDITIONS OF
-THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY,
-DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
-
-COPYRIGHT AND PERMISSION NOTICE
-
-Copyright © 1991-2008 Unicode, Inc. All rights reserved. Distributed
-under the Terms of Use in http://www.unicode.org/copyright.html.
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Unicode data files and any associated documentation (the
-"Data Files") or Unicode software and any associated documentation (the
-"Software") to deal in the Data Files or Software without restriction,
-including without limitation the rights to use, copy, modify, merge,
-publish, distribute, and/or sell copies of the Data Files or Software,
-and to permit persons to whom the Data Files or Software are furnished to
-do so, provided that (a) the above copyright notice(s) and this permission
-notice appear with all copies of the Data Files or Software, (b) both the
-above copyright notice(s) and this permission notice appear in associated
-documentation, and (c) there is clear notice in each modified Data File
-or in the Software as well as in the documentation associated with the
-Data File(s) or Software that the data or software has been modified.
-
-THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
-ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
-INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
-OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
-OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
-OR PERFORMANCE OF THE DATA FILES OR SOFTWARE.
-
-Except as contained in this notice, the name of a copyright holder
-shall not be used in advertising or otherwise to promote the sale, use
-or other dealings in these Data Files or Software without prior written
-authorization of the copyright holder.
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 52cff94..da7fca1 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -52,10 +52,6 @@
       "name": "memunreachable_unit_test"
     },
     {
-      "name": "memunreachable_unit_test",
-      "host": true
-    },
-    {
       "name": "memunreachable_binder_test"
     },
     {
diff --git a/bootstat/Android.bp b/bootstat/Android.bp
index edff26d..ca59ef3 100644
--- a/bootstat/Android.bp
+++ b/bootstat/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 bootstat_lib_src_files = [
     "boot_event_record_store.cpp",
 ]
@@ -31,7 +35,7 @@
         "libcutils",
         "liblog",
     ],
-    static_libs: ["libgtest_prod"],
+    header_libs: ["libgtest_prod_headers"],
 }
 
 // bootstat static library
@@ -93,4 +97,7 @@
         "boot_event_record_store_test.cpp",
         "testrunner.cpp",
     ],
+    test_options: {
+        unit_test: true,
+    },
 }
diff --git a/bootstat/AndroidTest.xml b/bootstat/AndroidTest.xml
deleted file mode 100644
index f3783fa..0000000
--- a/bootstat/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<configuration description="Config for bootstat_tests">
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="bootstat_tests->/data/local/tmp/bootstat_tests" />
-    </target_preparer>
-    <option name="test-suite-tag" value="apct" />
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="bootstat_tests" />
-    </test>
-</configuration>
\ No newline at end of file
diff --git a/bootstat/OWNERS b/bootstat/OWNERS
index 50b2097..f66b309 100644
--- a/bootstat/OWNERS
+++ b/bootstat/OWNERS
@@ -1,2 +1,2 @@
 jhawkins@google.com
-salyzyn@google.com
+dvander@google.com
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index 8979e9a..d6ebb0d 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -438,6 +438,7 @@
     {"reboot,mount_userdata_failed", 190},
     {"reboot,forcedsilent", 191},
     {"reboot,forcednonsilent", 192},
+    {"reboot,thermal,tj", 193},
 };
 
 // Converts a string value representing the reason the system booted to an
@@ -1322,6 +1323,8 @@
 
   // Record the total time from device startup to boot complete, regardless of
   // encryption state.
+  // Note: we are recording seconds here even though the field in statsd atom specifies
+  // milliseconds.
   boot_event_store.AddBootEventWithValue(boot_complete_prefix, uptime_s.count());
 
   RecordInitBootTimeProp(&boot_event_store, "ro.boottime.init");
diff --git a/cli-test/Android.bp b/cli-test/Android.bp
index 37a1d1b..59618b4 100644
--- a/cli-test/Android.bp
+++ b/cli-test/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "cli-test",
     host_supported: true,
diff --git a/code_coverage/Android.bp b/code_coverage/Android.bp
index b51c802..2cb1617 100644
--- a/code_coverage/Android.bp
+++ b/code_coverage/Android.bp
@@ -1,4 +1,8 @@
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 prebuilt_etc {
     name: "code_coverage.policy",
     sub_dir: "seccomp_policy",
diff --git a/code_coverage/seccomp_policy/code_coverage.arm.policy b/code_coverage/seccomp_policy/code_coverage.arm.policy
index b80910f..3589379 100644
--- a/code_coverage/seccomp_policy/code_coverage.arm.policy
+++ b/code_coverage/seccomp_policy/code_coverage.arm.policy
@@ -1,4 +1,5 @@
 close: 1
+fchmod: 1
 mkdirat: 1
 msync: 1
 munmap: 1
diff --git a/code_coverage/seccomp_policy/code_coverage.arm64.policy b/code_coverage/seccomp_policy/code_coverage.arm64.policy
index 7040ea2..fdb4d1e 100644
--- a/code_coverage/seccomp_policy/code_coverage.arm64.policy
+++ b/code_coverage/seccomp_policy/code_coverage.arm64.policy
@@ -1,4 +1,5 @@
 close: 1
+fchmod: 1
 mkdirat: 1
 msync: 1
 munmap: 1
diff --git a/code_coverage/seccomp_policy/code_coverage.policy.def b/code_coverage/seccomp_policy/code_coverage.policy.def
index 599c4a4..b6a4c6d 100644
--- a/code_coverage/seccomp_policy/code_coverage.policy.def
+++ b/code_coverage/seccomp_policy/code_coverage.policy.def
@@ -13,6 +13,9 @@
 // 2nd-Nth: uses mmap() to update in place
 
 close: 1
+// fchmod allowed to set libprofile-clang-extras, which wraps `open` calls, to
+// set correct permission for coverage files.
+fchmod: 1
 mkdirat: 1
 msync: 1
 munmap: 1
diff --git a/code_coverage/seccomp_policy/code_coverage.x86.policy b/code_coverage/seccomp_policy/code_coverage.x86.policy
index f8e0cc0..145d3a3 100644
--- a/code_coverage/seccomp_policy/code_coverage.x86.policy
+++ b/code_coverage/seccomp_policy/code_coverage.x86.policy
@@ -1,4 +1,5 @@
 close: 1
+fchmod: 1
 mkdirat: 1
 msync: 1
 munmap: 1
diff --git a/code_coverage/seccomp_policy/code_coverage.x86_64.policy b/code_coverage/seccomp_policy/code_coverage.x86_64.policy
index dcf2f9a..11c8075 100644
--- a/code_coverage/seccomp_policy/code_coverage.x86_64.policy
+++ b/code_coverage/seccomp_policy/code_coverage.x86_64.policy
@@ -1,4 +1,5 @@
 close: 1
+fchmod: 1
 mkdirat: 1
 msync: 1
 munmap: 1
diff --git a/cpio/Android.bp b/cpio/Android.bp
deleted file mode 100644
index baa0319..0000000
--- a/cpio/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright 2005 The Android Open Source Project
-
-cc_binary_host {
-    name: "mkbootfs",
-    srcs: ["mkbootfs.c"],
-    cflags: ["-Werror"],
-    shared_libs: ["libcutils"],
-    dist: {
-        targets: ["dist_files"],
-    },
-}
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 6391acc..198e4de 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -1,3 +1,31 @@
+package {
+    default_applicable_licenses: ["system_core_debuggerd_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+    name: "system_core_debuggerd_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
+}
+
 cc_defaults {
     name: "debuggerd_defaults",
     cflags: [
@@ -176,6 +204,8 @@
         "libdebuggerd/open_files_list.cpp",
         "libdebuggerd/scudo.cpp",
         "libdebuggerd/tombstone.cpp",
+        "libdebuggerd/tombstone_proto.cpp",
+        "libdebuggerd/tombstone_proto_to_text.cpp",
         "libdebuggerd/utility.cpp",
     ],
 
@@ -204,8 +234,12 @@
     ],
 
     whole_static_libs: [
+        "libasync_safe",
         "gwp_asan_crash_handler",
         "libscudo",
+        "libtombstone_proto",
+        "libprocinfo",
+        "libprotobuf-cpp-lite",
     ],
 
     target: {
@@ -225,12 +259,29 @@
         debuggable: {
             cflags: ["-DROOT_POSSIBLE"],
         },
-        experimental_mte: {
-            cflags: ["-DANDROID_EXPERIMENTAL_MTE"],
-        },
     },
 }
 
+cc_binary {
+    name: "pbtombstone",
+    defaults: ["debuggerd_defaults"],
+    srcs: ["pbtombstone.cpp"],
+    static_libs: [
+        "libbase",
+        "libdebuggerd",
+        "liblog",
+        "libprotobuf-cpp-lite",
+        "libtombstone_proto",
+        "libunwindstack",
+    ],
+}
+
+cc_test_library {
+    name: "libcrash_test",
+    defaults: ["debuggerd_defaults"],
+    srcs: ["crash_test.cpp"],
+}
+
 cc_test {
     name: "debuggerd_test",
     defaults: ["debuggerd_defaults"],
@@ -296,13 +347,11 @@
         },
     },
 
-    test_suites: ["device-tests"],
+    data: [
+        ":libcrash_test",
+    ],
 
-    product_variables: {
-        experimental_mte: {
-            cflags: ["-DANDROID_EXPERIMENTAL_MTE"],
-        },
-    },
+    test_suites: ["device-tests"],
 }
 
 cc_benchmark {
@@ -341,6 +390,9 @@
         "libtombstoned_client_static",
         "libdebuggerd",
         "libcutils",
+
+        "libtombstone_proto",
+        "libprotobuf-cpp-lite",
     ],
 
     shared_libs: [
@@ -353,12 +405,6 @@
     apex_available: [
         "com.android.runtime",
     ],
-
-    product_variables: {
-        experimental_mte: {
-            cflags: ["-DANDROID_EXPERIMENTAL_MTE"],
-        },
-    },
 }
 
 cc_binary {
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index 6bfb5f2..530e0e8 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -96,7 +96,7 @@
 
   if (std::string str = data.str(); !str.empty()) {
     buffer << "\n----- Waiting Channels: pid " << pid << " at " << get_timestamp() << " -----\n"
-           << "Cmd line: " << get_process_name(pid) << "\n";
+           << "Cmd line: " << android::base::Join(get_command_line(pid), " ") << "\n";
     buffer << "\n" << str << "\n";
     buffer << "----- end " << std::to_string(pid) << " -----\n";
     buffer << "\n";
diff --git a/debuggerd/common/include/dump_type.h b/debuggerd/common/include/dump_type.h
index 203269e..a3e171b 100644
--- a/debuggerd/common/include/dump_type.h
+++ b/debuggerd/common/include/dump_type.h
@@ -24,7 +24,8 @@
   kDebuggerdNativeBacktrace,
   kDebuggerdTombstone,
   kDebuggerdJavaBacktrace,
-  kDebuggerdAnyIntercept
+  kDebuggerdAnyIntercept,
+  kDebuggerdTombstoneProto,
 };
 
 inline std::ostream& operator<<(std::ostream& stream, const DebuggerdDumpType& rhs) {
@@ -41,6 +42,9 @@
     case kDebuggerdAnyIntercept:
       stream << "kDebuggerdAnyIntercept";
       break;
+    case kDebuggerdTombstoneProto:
+      stream << "kDebuggerdTombstoneProto";
+      break;
     default:
       stream << "[unknown]";
   }
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index b3e81b0..a152740 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -40,7 +40,7 @@
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <bionic/mte_kernel.h>
+#include <bionic/macros.h>
 #include <bionic/reserved_signals.h>
 #include <cutils/sockets.h>
 #include <log/log.h>
@@ -153,14 +153,14 @@
   }
 
   struct timeval tv = {
-    .tv_sec = 1,
-    .tv_usec = 0,
+      .tv_sec = 1 * android::base::HwTimeoutMultiplier(),
+      .tv_usec = 0,
   };
   if (setsockopt(amfd.get(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) {
     PLOG(ERROR) << "failed to set send timeout on activity manager socket";
     return false;
   }
-  tv.tv_sec = 3;  // 3 seconds on handshake read
+  tv.tv_sec = 3 * android::base::HwTimeoutMultiplier();  // 3 seconds on handshake read
   if (setsockopt(amfd.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
     PLOG(ERROR) << "failed to set receive timeout on activity manager socket";
     return false;
@@ -195,6 +195,7 @@
 static bool g_tombstoned_connected = false;
 static unique_fd g_tombstoned_socket;
 static unique_fd g_output_fd;
+static unique_fd g_proto_fd;
 
 static void DefuseSignalHandlers() {
   // Don't try to dump ourselves.
@@ -215,7 +216,7 @@
     // If we abort before we get an output fd, contact tombstoned to let any
     // potential listeners know that we failed.
     if (!g_tombstoned_connected) {
-      if (!tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd,
+      if (!tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd, &g_proto_fd,
                               kDebuggerdAnyIntercept)) {
         // We failed to connect, not much we can do.
         LOG(ERROR) << "failed to connected to tombstoned to report failure";
@@ -248,10 +249,20 @@
   }
 
   int dump_type_int;
-  if (!android::base::ParseInt(argv[3], &dump_type_int, 0, 1)) {
+  if (!android::base::ParseInt(argv[3], &dump_type_int, 0)) {
     LOG(FATAL) << "invalid requested dump type: " << argv[3];
   }
+
   *dump_type = static_cast<DebuggerdDumpType>(dump_type_int);
+  switch (*dump_type) {
+    case kDebuggerdNativeBacktrace:
+    case kDebuggerdTombstone:
+    case kDebuggerdTombstoneProto:
+      break;
+
+    default:
+      LOG(FATAL) << "invalid requested dump type: " << dump_type_int;
+  }
 }
 
 static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo,
@@ -292,6 +303,7 @@
       process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata;
       process_info->scudo_stack_depot = crash_info->data.d.scudo_stack_depot;
       process_info->scudo_region_info = crash_info->data.d.scudo_region_info;
+      process_info->scudo_ring_buffer = crash_info->data.d.scudo_ring_buffer;
       FALLTHROUGH_INTENDED;
     case 1:
     case 2:
@@ -300,7 +312,9 @@
       *siginfo = crash_info->data.s.siginfo;
       if (signal_has_si_addr(siginfo)) {
         process_info->has_fault_address = true;
-        process_info->fault_address = reinterpret_cast<uintptr_t>(siginfo->si_addr);
+        process_info->maybe_tagged_fault_address = reinterpret_cast<uintptr_t>(siginfo->si_addr);
+        process_info->untagged_fault_address =
+            untag_address(reinterpret_cast<uintptr_t>(siginfo->si_addr));
       }
       regs->reset(unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(),
                                                         &crash_info->data.s.ucontext));
@@ -377,7 +391,7 @@
 
   // There appears to be a bug in the kernel where our death causes SIGHUP to
   // be sent to our process group if we exit while it has stopped jobs (e.g.
-  // because of wait_for_gdb). Use setsid to create a new process group to
+  // because of wait_for_debugger). Use setsid to create a new process group to
   // avoid hitting this.
   setsid();
 
@@ -434,10 +448,7 @@
   //
   // Note: processes with many threads and minidebug-info can take a bit to
   //       unwind, do not make this too small. b/62828735
-  alarm(30);
-
-  // Get the process name (aka cmdline).
-  std::string process_name = get_process_name(g_target_thread);
+  alarm(30 * android::base::HwTimeoutMultiplier());
 
   // Collect the list of open files.
   OpenFilesList open_files;
@@ -475,16 +486,19 @@
       info.pid = target_process;
       info.tid = thread;
       info.uid = getuid();
-      info.process_name = process_name;
       info.thread_name = get_thread_name(thread);
 
+      unique_fd attr_fd(openat(target_proc_fd, "attr/current", O_RDONLY | O_CLOEXEC));
+      if (!android::base::ReadFdToString(attr_fd, &info.selinux_label)) {
+        PLOG(WARNING) << "failed to read selinux label";
+      }
+
       if (!ptrace_interrupt(thread, &info.signo)) {
         PLOG(WARNING) << "failed to ptrace interrupt thread " << thread;
         ptrace(PTRACE_DETACH, thread, 0, 0);
         continue;
       }
 
-#ifdef ANDROID_EXPERIMENTAL_MTE
       struct iovec iov = {
           &info.tagged_addr_ctrl,
           sizeof(info.tagged_addr_ctrl),
@@ -493,13 +507,14 @@
                  reinterpret_cast<void*>(&iov)) == -1) {
         info.tagged_addr_ctrl = -1;
       }
-#endif
 
       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);
         info.siginfo = &siginfo;
         info.signo = info.siginfo->si_signo;
+
+        info.command_line = get_command_line(g_target_thread);
       } else {
         info.registers.reset(unwindstack::Regs::RemoteGet(thread));
         if (!info.registers) {
@@ -531,15 +546,17 @@
   fork_exit_write.reset();
 
   // Defer the message until later, for readability.
-  bool wait_for_gdb = android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false);
+  bool wait_for_debugger = android::base::GetBoolProperty(
+      "debug.debuggerd.wait_for_debugger",
+      android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false));
   if (siginfo.si_signo == BIONIC_SIGNAL_DEBUGGER) {
-    wait_for_gdb = false;
+    wait_for_debugger = false;
   }
 
   // Detach from all of our attached threads before resuming.
   for (const auto& [tid, thread] : thread_info) {
     int resume_signal = thread.signo == BIONIC_SIGNAL_DEBUGGER ? 0 : thread.signo;
-    if (wait_for_gdb) {
+    if (wait_for_debugger) {
       resume_signal = 0;
       if (tgkill(target_process, tid, SIGSTOP) != 0) {
         PLOG(WARNING) << "failed to send SIGSTOP to " << tid;
@@ -558,8 +575,8 @@
   {
     ATRACE_NAME("tombstoned_connect");
     LOG(INFO) << "obtaining output fd from tombstoned, type: " << dump_type;
-    g_tombstoned_connected =
-        tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd, dump_type);
+    g_tombstoned_connected = tombstoned_connect(g_target_thread, &g_tombstoned_socket, &g_output_fd,
+                                                &g_proto_fd, dump_type);
   }
 
   if (g_tombstoned_connected) {
@@ -612,8 +629,8 @@
 
     {
       ATRACE_NAME("engrave_tombstone");
-      engrave_tombstone(std::move(g_output_fd), &unwinder, thread_info, g_target_thread, process_info,
-                        &open_files, &amfd_data);
+      engrave_tombstone(std::move(g_output_fd), std::move(g_proto_fd), &unwinder, thread_info,
+                        g_target_thread, process_info, &open_files, &amfd_data);
     }
   }
 
@@ -624,12 +641,12 @@
     }
   }
 
-  if (wait_for_gdb) {
+  if (wait_for_debugger) {
     // Use ALOGI to line up with output from engrave_tombstone.
     ALOGI(
         "***********************************************************\n"
         "* Process %d has been suspended while crashing.\n"
-        "* To attach gdbserver and start gdb, run this on the host:\n"
+        "* To attach the debugger, run this on the host:\n"
         "*\n"
         "*     gdbclient.py -p %d\n"
         "*\n"
diff --git a/libstats/socket/stats_socket.c b/debuggerd/crash_test.cpp
similarity index 69%
copy from libstats/socket/stats_socket.c
copy to debuggerd/crash_test.cpp
index 09f8967..c15145f 100644
--- a/libstats/socket/stats_socket.c
+++ b/debuggerd/crash_test.cpp
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 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
+ *     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,
@@ -14,9 +14,8 @@
  * limitations under the License.
  */
 
-#include "include/stats_socket.h"
-#include "stats_buffer_writer.h"
+#include <stdint.h>
 
-void AStatsSocket_close() {
-    stats_log_close();
+extern "C" void crash() {
+  *reinterpret_cast<volatile char*>(0xdead) = '1';
 }
diff --git a/debuggerd/crasher/Android.bp b/debuggerd/crasher/Android.bp
index 61c5395..23b106e 100644
--- a/debuggerd/crasher/Android.bp
+++ b/debuggerd/crasher/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_defaults {
     name: "crasher-defaults",
 
@@ -9,7 +13,6 @@
         "-Werror",
         "-O0",
         "-fstack-protector-all",
-        "-Wno-free-nonheap-object",
         "-Wno-date-time",
     ],
     srcs: ["crasher.cpp"],
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index a2b13a3..db30b8f0 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -134,10 +134,14 @@
     return a*2;
 }
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wfree-nonheap-object"
+
 noinline void abuse_heap() {
     char buf[16];
     free(buf); // GCC is smart enough to warn about this, but we're doing it deliberately.
 }
+#pragma clang diagnostic pop
 
 noinline void leak() {
     while (true) {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index e5af425..4634283 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
+#include <dirent.h>
+#include <dlfcn.h>
 #include <err.h>
 #include <fcntl.h>
+#include <malloc.h>
 #include <stdlib.h>
 #include <sys/capability.h>
 #include <sys/mman.h>
@@ -28,6 +31,7 @@
 
 #include <chrono>
 #include <regex>
+#include <string>
 #include <thread>
 
 #include <android/fdsan.h>
@@ -68,7 +72,7 @@
 #define ARCH_SUFFIX ""
 #endif
 
-constexpr char kWaitForGdbKey[] = "debug.debuggerd.wait_for_gdb";
+constexpr char kWaitForDebuggerKey[] = "debug.debuggerd.wait_for_debugger";
 
 #define TIMEOUT(seconds, expr)                                     \
   [&]() {                                                          \
@@ -97,6 +101,13 @@
   ASSERT_MATCH(result,                             \
                R"(#\d\d pc [0-9a-f]+\s+ \S+ (\(offset 0x[0-9a-f]+\) )?\()" frame_name R"(\+)");
 
+// Enable GWP-ASan at the start of this process. GWP-ASan is enabled using
+// process sampling, so we need to ensure we force GWP-ASan on.
+__attribute__((constructor)) static void enable_gwp_asan() {
+  bool force = true;
+  android_mallopt(M_INITIALIZE_GWP_ASAN, &force, sizeof(force));
+}
+
 static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd,
                                  InterceptStatus* status, DebuggerdDumpType intercept_type) {
   intercept_fd->reset(socket_local_client(kTombstonedInterceptSocketName,
@@ -156,7 +167,7 @@
 class CrasherTest : public ::testing::Test {
  public:
   pid_t crasher_pid = -1;
-  bool previous_wait_for_gdb;
+  bool previous_wait_for_debugger;
   unique_fd crasher_pipe;
   unique_fd intercept_fd;
 
@@ -177,8 +188,13 @@
 };
 
 CrasherTest::CrasherTest() {
-  previous_wait_for_gdb = android::base::GetBoolProperty(kWaitForGdbKey, false);
-  android::base::SetProperty(kWaitForGdbKey, "0");
+  previous_wait_for_debugger = android::base::GetBoolProperty(kWaitForDebuggerKey, false);
+  android::base::SetProperty(kWaitForDebuggerKey, "0");
+
+  // Clear the old property too, just in case someone's been using it
+  // on this device. (We only document the new name, but we still support
+  // the old name so we don't break anyone's existing setups.)
+  android::base::SetProperty("debug.debuggerd.wait_for_gdb", "0");
 }
 
 CrasherTest::~CrasherTest() {
@@ -188,7 +204,7 @@
     TEMP_FAILURE_RETRY(waitpid(crasher_pid, &status, WUNTRACED));
   }
 
-  android::base::SetProperty(kWaitForGdbKey, previous_wait_for_gdb ? "1" : "0");
+  android::base::SetProperty(kWaitForDebuggerKey, previous_wait_for_debugger ? "1" : "0");
 }
 
 void CrasherTest::StartIntercept(unique_fd* output_fd, DebuggerdDumpType intercept_type) {
@@ -260,7 +276,7 @@
   }
 
   if (signo == 0) {
-    ASSERT_TRUE(WIFEXITED(status));
+    ASSERT_TRUE(WIFEXITED(status)) << "Terminated due to unexpected signal " << WTERMSIG(status);
     ASSERT_EQ(0, WEXITSTATUS(signo));
   } else {
     ASSERT_FALSE(WIFEXITED(status));
@@ -311,7 +327,7 @@
 
   if (mte_supported()) {
     // Test that the default TAGGED_ADDR_CTRL value is set.
-    ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff5)");
+    ASSERT_MATCH(result, R"(tagged_addr_ctrl: 000000000007fff3)");
   }
 }
 
@@ -373,36 +389,110 @@
   ConsumeFd(std::move(output_fd), &result);
 
 #if defined(__aarch64__)
-  ASSERT_MATCH(result, "memory near x0");
+  ASSERT_MATCH(result, "memory near x0 \\(\\[anon:");
 #elif defined(__arm__)
-  ASSERT_MATCH(result, "memory near r0");
+  ASSERT_MATCH(result, "memory near r0 \\(\\[anon:");
 #elif defined(__x86_64__)
-  ASSERT_MATCH(result, "memory near rdi");
+  ASSERT_MATCH(result, "memory near rdi \\(\\[anon:");
 #else
   ASSERT_TRUE(false) << "unsupported architecture";
 #endif
 }
 
-#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+#if defined(__aarch64__)
 static void SetTagCheckingLevelSync() {
-  HeapTaggingLevel heap_tagging_level = M_HEAP_TAGGING_LEVEL_SYNC;
-  if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level))) {
+  if (mallopt(M_BIONIC_SET_HEAP_TAGGING_LEVEL, M_HEAP_TAGGING_LEVEL_SYNC) == 0) {
     abort();
   }
 }
 #endif
 
-TEST_F(CrasherTest, mte_uaf) {
-#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+// Number of iterations required to reliably guarantee a GWP-ASan crash.
+// GWP-ASan's sample rate is not truly nondeterministic, it initialises a
+// thread-local counter at 2*SampleRate, and decrements on each malloc(). Once
+// the counter reaches zero, we provide a sampled allocation. Then, double that
+// figure to allow for left/right allocation alignment, as this is done randomly
+// without bias.
+#define GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH (0x20000)
+
+struct GwpAsanTestParameters {
+  size_t alloc_size;
+  bool free_before_access;
+  int access_offset;
+  std::string cause_needle; // Needle to be found in the "Cause: [GWP-ASan]" line.
+};
+
+struct GwpAsanCrasherTest : CrasherTest, testing::WithParamInterface<GwpAsanTestParameters> {};
+
+GwpAsanTestParameters gwp_asan_tests[] = {
+  {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 0, "Use After Free, 0 bytes into a 7-byte allocation"},
+  {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 1, "Use After Free, 1 byte into a 7-byte allocation"},
+  {/* alloc_size */ 7, /* free_before_access */ false, /* access_offset */ 16, "Buffer Overflow, 9 bytes right of a 7-byte allocation"},
+  {/* alloc_size */ 16, /* free_before_access */ false, /* access_offset */ -1, "Buffer Underflow, 1 byte left of a 16-byte allocation"},
+};
+
+INSTANTIATE_TEST_SUITE_P(GwpAsanTests, GwpAsanCrasherTest, testing::ValuesIn(gwp_asan_tests));
+
+TEST_P(GwpAsanCrasherTest, gwp_asan_uaf) {
+  if (mte_supported()) {
+    // Skip this test on MTE hardware, as MTE will reliably catch these errors
+    // instead of GWP-ASan.
+    GTEST_SKIP() << "Skipped on MTE.";
+  }
+
+  GwpAsanTestParameters params = GetParam();
+
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([&params]() {
+    for (unsigned i = 0; i < GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH; ++i) {
+      volatile char* p = reinterpret_cast<volatile char*>(malloc(params.alloc_size));
+      if (params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
+      p[params.access_offset] = 42;
+      if (!params.free_before_access) free(static_cast<void*>(const_cast<char*>(p)));
+    }
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))");
+  ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle);
+  if (params.free_before_access) {
+    ASSERT_MATCH(result, R"(deallocated by thread .*
+      #00 pc)");
+  }
+  ASSERT_MATCH(result, R"(allocated by thread .*
+      #00 pc)");
+}
+
+struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface<size_t> {};
+
+INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(0, 16, 131072));
+
+TEST_P(SizeParamCrasherTest, mte_uaf) {
+#if defined(__aarch64__)
   if (!mte_supported()) {
     GTEST_SKIP() << "Requires MTE";
   }
 
+  // Any UAF on a zero-sized allocation will be out-of-bounds so it won't be reported.
+  if (GetParam() == 0) {
+    return;
+  }
+
   int intercept_result;
   unique_fd output_fd;
-  StartProcess([]() {
+  StartProcess([&]() {
     SetTagCheckingLevelSync();
-    volatile int* p = (volatile int*)malloc(16);
+    volatile int* p = (volatile int*)malloc(GetParam());
     free((void *)p);
     p[0] = 42;
   });
@@ -417,30 +507,31 @@
   std::string result;
   ConsumeFd(std::move(output_fd), &result);
 
-  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
-  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a 16-byte allocation.*
-
-allocated by thread .*
-      #00 pc)");
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
+  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" +
+                           std::to_string(GetParam()) + R"(-byte allocation)");
   ASSERT_MATCH(result, R"(deallocated by thread .*
       #00 pc)");
+  ASSERT_MATCH(result, R"(allocated by thread .*
+      #00 pc)");
 #else
-  GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
+  GTEST_SKIP() << "Requires aarch64";
 #endif
 }
 
-TEST_F(CrasherTest, mte_overflow) {
-#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+TEST_P(SizeParamCrasherTest, mte_oob_uaf) {
+#if defined(__aarch64__)
   if (!mte_supported()) {
     GTEST_SKIP() << "Requires MTE";
   }
 
   int intercept_result;
   unique_fd output_fd;
-  StartProcess([]() {
+  StartProcess([&]() {
     SetTagCheckingLevelSync();
-    volatile int* p = (volatile int*)malloc(16);
-    p[4] = 42;
+    volatile int* p = (volatile int*)malloc(GetParam());
+    free((void *)p);
+    p[-1] = 42;
   });
 
   StartIntercept(&output_fd);
@@ -454,26 +545,57 @@
   ConsumeFd(std::move(output_fd), &result);
 
   ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
-  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation.*
-
-allocated by thread .*
-      #00 pc)");
+  ASSERT_NOT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 4 bytes left)");
 #else
-  GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
+  GTEST_SKIP() << "Requires aarch64";
 #endif
 }
 
-TEST_F(CrasherTest, mte_underflow) {
-#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+TEST_P(SizeParamCrasherTest, mte_overflow) {
+#if defined(__aarch64__)
   if (!mte_supported()) {
     GTEST_SKIP() << "Requires MTE";
   }
 
   int intercept_result;
   unique_fd output_fd;
-  StartProcess([]() {
+  StartProcess([&]() {
     SetTagCheckingLevelSync();
-    volatile int* p = (volatile int*)malloc(16);
+    volatile char* p = (volatile char*)malloc(GetParam());
+    p[GetParam()] = 42;
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+
+  ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
+  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
+                           std::to_string(GetParam()) + R"(-byte allocation)");
+  ASSERT_MATCH(result, R"(allocated by thread .*
+      #00 pc)");
+#else
+  GTEST_SKIP() << "Requires aarch64";
+#endif
+}
+
+TEST_P(SizeParamCrasherTest, mte_underflow) {
+#if defined(__aarch64__)
+  if (!mte_supported()) {
+    GTEST_SKIP() << "Requires MTE";
+  }
+
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([&]() {
+    SetTagCheckingLevelSync();
+    volatile int* p = (volatile int*)malloc(GetParam());
     p[-1] = 42;
   });
 
@@ -488,17 +610,17 @@
   ConsumeFd(std::move(output_fd), &result);
 
   ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
-  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a 16-byte allocation.*
-
-allocated by thread .*
+  ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" +
+                           std::to_string(GetParam()) + R"(-byte allocation)");
+  ASSERT_MATCH(result, R"(allocated by thread .*
       #00 pc)");
 #else
-  GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
+  GTEST_SKIP() << "Requires aarch64";
 #endif
 }
 
 TEST_F(CrasherTest, mte_multiple_causes) {
-#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+#if defined(__aarch64__)
   if (!mte_supported()) {
     GTEST_SKIP() << "Requires MTE";
   }
@@ -547,11 +669,11 @@
   // overflows), so we can't match explicitly for an underflow message.
   ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation)");
 #else
-  GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
+  GTEST_SKIP() << "Requires aarch64";
 #endif
 }
 
-#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+#if defined(__aarch64__)
 static uintptr_t CreateTagMapping() {
   uintptr_t mapping =
       reinterpret_cast<uintptr_t>(mmap(nullptr, getpagesize(), PROT_READ | PROT_WRITE | PROT_MTE,
@@ -568,7 +690,7 @@
 #endif
 
 TEST_F(CrasherTest, mte_tag_dump) {
-#if defined(__aarch64__) && defined(ANDROID_EXPERIMENTAL_MTE)
+#if defined(__aarch64__)
   if (!mte_supported()) {
     GTEST_SKIP() << "Requires MTE";
   }
@@ -596,7 +718,7 @@
     01.............0 0000000000000000 0000000000000000  ................
     00.............0)");
 #else
-  GTEST_SKIP() << "Requires aarch64 + ANDROID_EXPERIMENTAL_MTE";
+  GTEST_SKIP() << "Requires aarch64";
 #endif
 }
 
@@ -727,9 +849,9 @@
   AssertDeath(SIGABRT);
 }
 
-TEST_F(CrasherTest, wait_for_gdb) {
-  if (!android::base::SetProperty(kWaitForGdbKey, "1")) {
-    FAIL() << "failed to enable wait_for_gdb";
+TEST_F(CrasherTest, wait_for_debugger) {
+  if (!android::base::SetProperty(kWaitForDebuggerKey, "1")) {
+    FAIL() << "failed to enable wait_for_debugger";
   }
   sleep(1);
 
@@ -1310,11 +1432,11 @@
   tombstoned_intercept(self, &intercept_fd, &output_fd, &status, kDebuggerdJavaBacktrace);
   ASSERT_EQ(InterceptStatus::kRegistered, status);
 
-  // First connect to tombstoned requesting a native backtrace. This
+  // First connect to tombstoned requesting a native tombstone. This
   // should result in a "regular" FD and not the installed intercept.
   const char native[] = "native";
   unique_fd tombstoned_socket, input_fd;
-  ASSERT_TRUE(tombstoned_connect(self, &tombstoned_socket, &input_fd, kDebuggerdNativeBacktrace));
+  ASSERT_TRUE(tombstoned_connect(self, &tombstoned_socket, &input_fd, kDebuggerdTombstone));
   ASSERT_TRUE(android::base::WriteFully(input_fd.get(), native, sizeof(native)));
   tombstoned_notify_completion(tombstoned_socket.get());
 
@@ -1426,3 +1548,130 @@
   ConsumeFd(std::move(output_fd), &result);
   ASSERT_MATCH(result, R"(Cause: stack pointer[^\n]*stack overflow.\n)");
 }
+
+static bool CopySharedLibrary(const char* tmp_dir, std::string* tmp_so_name) {
+  std::string test_lib(testing::internal::GetArgvs()[0]);
+  auto const value = test_lib.find_last_of('/');
+  if (value == std::string::npos) {
+    test_lib = "./";
+  } else {
+    test_lib = test_lib.substr(0, value + 1) + "./";
+  }
+  test_lib += "libcrash_test.so";
+
+  *tmp_so_name = std::string(tmp_dir) + "/libcrash_test.so";
+  std::string cp_cmd = android::base::StringPrintf("cp %s %s", test_lib.c_str(), tmp_dir);
+
+  // Copy the shared so to a tempory directory.
+  return system(cp_cmd.c_str()) == 0;
+}
+
+TEST_F(CrasherTest, unreadable_elf) {
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    TemporaryDir td;
+    std::string tmp_so_name;
+    if (!CopySharedLibrary(td.path, &tmp_so_name)) {
+      _exit(1);
+    }
+    void* handle = dlopen(tmp_so_name.c_str(), RTLD_NOW);
+    if (handle == nullptr) {
+      _exit(1);
+    }
+    // Delete the original shared library so that we get the warning
+    // about unreadable elf files.
+    if (unlink(tmp_so_name.c_str()) == -1) {
+      _exit(1);
+    }
+    void (*crash_func)() = reinterpret_cast<void (*)()>(dlsym(handle, "crash"));
+    if (crash_func == nullptr) {
+      _exit(1);
+    }
+    crash_func();
+  });
+
+  StartIntercept(&output_fd);
+  FinishCrasher();
+  AssertDeath(SIGSEGV);
+  FinishIntercept(&intercept_result);
+
+  ASSERT_EQ(1, intercept_result) << "tombstoned reported failure";
+
+  std::string result;
+  ConsumeFd(std::move(output_fd), &result);
+  ASSERT_MATCH(result, R"(NOTE: Function names and BuildId information is missing )");
+}
+
+TEST(tombstoned, proto) {
+  const pid_t self = getpid();
+  unique_fd tombstoned_socket, text_fd, proto_fd;
+  ASSERT_TRUE(
+      tombstoned_connect(self, &tombstoned_socket, &text_fd, &proto_fd, kDebuggerdTombstoneProto));
+
+  tombstoned_notify_completion(tombstoned_socket.get());
+
+  ASSERT_NE(-1, text_fd.get());
+  ASSERT_NE(-1, proto_fd.get());
+
+  struct stat text_st;
+  ASSERT_EQ(0, fstat(text_fd.get(), &text_st));
+
+  // Give tombstoned some time to link the files into place.
+  std::this_thread::sleep_for(100ms);
+
+  // Find the tombstone.
+  std::optional<std::string> tombstone_file;
+  std::unique_ptr<DIR, decltype(&closedir)> dir_h(opendir("/data/tombstones"), closedir);
+  ASSERT_TRUE(dir_h != nullptr);
+  std::regex tombstone_re("tombstone_\\d+");
+  dirent* entry;
+  while ((entry = readdir(dir_h.get())) != nullptr) {
+    if (!std::regex_match(entry->d_name, tombstone_re)) {
+      continue;
+    }
+    std::string path = android::base::StringPrintf("/data/tombstones/%s", entry->d_name);
+
+    struct stat st;
+    if (TEMP_FAILURE_RETRY(stat(path.c_str(), &st)) != 0) {
+      continue;
+    }
+
+    if (st.st_dev == text_st.st_dev && st.st_ino == text_st.st_ino) {
+      tombstone_file = path;
+      break;
+    }
+  }
+
+  ASSERT_TRUE(tombstone_file);
+  std::string proto_path = tombstone_file.value() + ".pb";
+
+  struct stat proto_fd_st;
+  struct stat proto_file_st;
+  ASSERT_EQ(0, fstat(proto_fd.get(), &proto_fd_st));
+  ASSERT_EQ(0, stat(proto_path.c_str(), &proto_file_st));
+
+  ASSERT_EQ(proto_fd_st.st_dev, proto_file_st.st_dev);
+  ASSERT_EQ(proto_fd_st.st_ino, proto_file_st.st_ino);
+}
+
+TEST(tombstoned, proto_intercept) {
+  const pid_t self = getpid();
+  unique_fd intercept_fd, output_fd;
+  InterceptStatus status;
+
+  tombstoned_intercept(self, &intercept_fd, &output_fd, &status, kDebuggerdTombstone);
+  ASSERT_EQ(InterceptStatus::kRegistered, status);
+
+  unique_fd tombstoned_socket, text_fd, proto_fd;
+  ASSERT_TRUE(
+      tombstoned_connect(self, &tombstoned_socket, &text_fd, &proto_fd, kDebuggerdTombstoneProto));
+  ASSERT_TRUE(android::base::WriteStringToFd("foo", text_fd.get()));
+  tombstoned_notify_completion(tombstoned_socket.get());
+
+  text_fd.reset();
+
+  std::string output;
+  ASSERT_TRUE(android::base::ReadFdToString(output_fd, &output));
+  ASSERT_EQ("foo", output);
+}
diff --git a/debuggerd/handler/debuggerd_fallback.cpp b/debuggerd/handler/debuggerd_fallback.cpp
index e103c82..feafa73 100644
--- a/debuggerd/handler/debuggerd_fallback.cpp
+++ b/debuggerd/handler/debuggerd_fallback.cpp
@@ -92,15 +92,15 @@
   __linker_disable_fallback_allocator();
 }
 
-static void debuggerd_fallback_tombstone(int output_fd, ucontext_t* ucontext, siginfo_t* siginfo,
-                                         void* abort_message) {
+static void debuggerd_fallback_tombstone(int output_fd, int proto_fd, ucontext_t* ucontext,
+                                         siginfo_t* siginfo, void* abort_message) {
   if (!__linker_enable_fallback_allocator()) {
     async_safe_format_log(ANDROID_LOG_ERROR, "libc", "fallback allocator already in use");
     return;
   }
 
-  engrave_tombstone_ucontext(output_fd, reinterpret_cast<uintptr_t>(abort_message), siginfo,
-                             ucontext);
+  engrave_tombstone_ucontext(output_fd, proto_fd, reinterpret_cast<uintptr_t>(abort_message),
+                             siginfo, ucontext);
   __linker_disable_fallback_allocator();
 }
 
@@ -232,7 +232,8 @@
 
   // Fetch output fd from tombstoned.
   unique_fd tombstone_socket, output_fd;
-  if (!tombstoned_connect(getpid(), &tombstone_socket, &output_fd, kDebuggerdNativeBacktrace)) {
+  if (!tombstoned_connect(getpid(), &tombstone_socket, &output_fd, nullptr,
+                          kDebuggerdNativeBacktrace)) {
     async_safe_format_log(ANDROID_LOG_ERROR, "libc",
                           "missing crash_dump_fallback() in selinux policy?");
     goto exit;
@@ -325,10 +326,10 @@
     _exit(1);
   }
 
-  unique_fd tombstone_socket, output_fd;
-  bool tombstoned_connected =
-      tombstoned_connect(getpid(), &tombstone_socket, &output_fd, kDebuggerdTombstone);
-  debuggerd_fallback_tombstone(output_fd.get(), ucontext, info, abort_message);
+  unique_fd tombstone_socket, output_fd, proto_fd;
+  bool tombstoned_connected = tombstoned_connect(getpid(), &tombstone_socket, &output_fd, &proto_fd,
+                                                 kDebuggerdTombstoneProto);
+  debuggerd_fallback_tombstone(output_fd.get(), proto_fd.get(), ucontext, info, abort_message);
   if (tombstoned_connected) {
     tombstoned_notify_completion(tombstone_socket.get());
   }
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 85ffc98..b607397 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -53,7 +53,6 @@
 #include <android-base/unique_fd.h>
 #include <async_safe/log.h>
 #include <bionic/reserved_signals.h>
-#include <cutils/properties.h>
 
 #include <libdebuggerd/utility.h>
 
@@ -275,7 +274,7 @@
 
     // There appears to be a bug in the kernel where our death causes SIGHUP to
     // be sent to our process group if we exit while it has stopped jobs (e.g.
-    // because of wait_for_gdb). Use setsid to create a new process group to
+    // because of wait_for_debugger). Use setsid to create a new process group to
     // avoid hitting this.
     setsid();
 
@@ -313,7 +312,7 @@
     return kDebuggerdNativeBacktrace;
   }
 
-  return kDebuggerdTombstone;
+  return kDebuggerdTombstoneProto;
 }
 
 static int debuggerd_dispatch_pseudothread(void* arg) {
@@ -601,7 +600,7 @@
     // starting to dump right before our death.
     pthread_mutex_unlock(&crash_mutex);
   } else {
-    // Resend the signal, so that either gdb or the parent's waitpid sees it.
+    // Resend the signal, so that either the debugger or the parent's waitpid sees it.
     resend_signal(info);
   }
 }
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index 254ed4f..bc08327 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -42,6 +42,7 @@
   const gwp_asan::AllocationMetadata* gwp_asan_metadata;
   const char* scudo_stack_depot;
   const char* scudo_region_info;
+  const char* scudo_ring_buffer;
 };
 
 // These callbacks are called in a signal handler, and thus must be async signal safe.
diff --git a/debuggerd/libdebuggerd/backtrace.cpp b/debuggerd/libdebuggerd/backtrace.cpp
index c543a83..fd91038 100644
--- a/debuggerd/libdebuggerd/backtrace.cpp
+++ b/debuggerd/libdebuggerd/backtrace.cpp
@@ -34,6 +34,7 @@
 #include <memory>
 #include <string>
 
+#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <log/log.h>
 #include <unwindstack/Unwinder.h>
@@ -42,11 +43,12 @@
 #include "libdebuggerd/utility.h"
 #include "util.h"
 
-static void dump_process_header(log_t* log, pid_t pid, const char* process_name) {
+static void dump_process_header(log_t* log, pid_t pid,
+                                const std::vector<std::string>& command_line) {
   _LOG(log, logtype::BACKTRACE, "\n\n----- pid %d at %s -----\n", pid, get_timestamp().c_str());
 
-  if (process_name) {
-    _LOG(log, logtype::BACKTRACE, "Cmd line: %s\n", process_name);
+  if (!command_line.empty()) {
+    _LOG(log, logtype::BACKTRACE, "Cmd line: %s\n", android::base::Join(command_line, " ").c_str());
   }
   _LOG(log, logtype::BACKTRACE, "ABI: '%s'\n", ABI_STRING);
 }
@@ -89,7 +91,7 @@
     return;
   }
 
-  dump_process_header(&log, target->second.pid, target->second.process_name.c_str());
+  dump_process_header(&log, target->second.pid, target->second.command_line);
 
   dump_backtrace_thread(output_fd.get(), unwinder, target->second);
   for (const auto& [tid, info] : thread_info) {
@@ -107,7 +109,7 @@
   log.amfd_data = nullptr;
 
   pid_t pid = getpid();
-  dump_process_header(&log, pid, get_process_name(pid).c_str());
+  dump_process_header(&log, pid, get_command_line(pid));
 }
 
 void dump_backtrace_footer(int output_fd) {
diff --git a/debuggerd/libdebuggerd/gwp_asan.cpp b/debuggerd/libdebuggerd/gwp_asan.cpp
index f271365..3ee309f 100644
--- a/debuggerd/libdebuggerd/gwp_asan.cpp
+++ b/debuggerd/libdebuggerd/gwp_asan.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "libdebuggerd/gwp_asan.h"
+#include "libdebuggerd/tombstone.h"
 #include "libdebuggerd/utility.h"
 
 #include "gwp_asan/common.h"
@@ -25,6 +26,8 @@
 #include <unwindstack/Regs.h>
 #include <unwindstack/Unwinder.h>
 
+#include "tombstone.pb.h"
+
 // Retrieve GWP-ASan state from `state_addr` inside the process at
 // `process_memory`. Place the state into `*state`.
 static bool retrieve_gwp_asan_state(unwindstack::Memory* process_memory, uintptr_t state_addr,
@@ -72,8 +75,8 @@
 
   // Get the external crash address from the thread info.
   crash_address_ = 0u;
-  if (signal_has_si_addr(thread_info.siginfo)) {
-    crash_address_ = reinterpret_cast<uintptr_t>(thread_info.siginfo->si_addr);
+  if (process_info.has_fault_address) {
+    crash_address_ = process_info.untagged_fault_address;
   }
 
   // Ensure the error belongs to GWP-ASan.
@@ -98,6 +101,67 @@
   return is_gwp_asan_responsible_;
 }
 
+constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
+
+void GwpAsanCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const {
+  if (!CrashIsMine()) {
+    ALOGE("Internal Error: AddCauseProtos() on a non-GWP-ASan crash.");
+    return;
+  }
+
+  Cause* cause = tombstone->add_causes();
+  MemoryError* memory_error = cause->mutable_memory_error();
+  HeapObject* heap_object = memory_error->mutable_heap();
+
+  memory_error->set_tool(MemoryError_Tool_GWP_ASAN);
+  switch (error_) {
+    case gwp_asan::Error::USE_AFTER_FREE:
+      memory_error->set_type(MemoryError_Type_USE_AFTER_FREE);
+      break;
+    case gwp_asan::Error::DOUBLE_FREE:
+      memory_error->set_type(MemoryError_Type_DOUBLE_FREE);
+      break;
+    case gwp_asan::Error::INVALID_FREE:
+      memory_error->set_type(MemoryError_Type_INVALID_FREE);
+      break;
+    case gwp_asan::Error::BUFFER_OVERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW);
+      break;
+    case gwp_asan::Error::BUFFER_UNDERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW);
+      break;
+    default:
+      memory_error->set_type(MemoryError_Type_UNKNOWN);
+      break;
+  }
+
+  heap_object->set_address(__gwp_asan_get_allocation_address(responsible_allocation_));
+  heap_object->set_size(__gwp_asan_get_allocation_size(responsible_allocation_));
+  unwinder->SetDisplayBuildID(true);
+
+  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
+
+  heap_object->set_allocation_tid(__gwp_asan_get_allocation_thread_id(responsible_allocation_));
+  size_t num_frames =
+      __gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
+  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());
+  }
+
+  heap_object->set_deallocation_tid(__gwp_asan_get_deallocation_thread_id(responsible_allocation_));
+  num_frames =
+      __gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
+  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());
+  }
+
+  set_human_readable_cause(cause, crash_address_);
+}
+
 void GwpAsanCrashData::DumpCause(log_t* log) const {
   if (!CrashIsMine()) {
     ALOGE("Internal Error: DumpCause() on a non-GWP-ASan crash.");
@@ -119,13 +183,6 @@
   uintptr_t alloc_address = __gwp_asan_get_allocation_address(responsible_allocation_);
   size_t alloc_size = __gwp_asan_get_allocation_size(responsible_allocation_);
 
-  if (crash_address_ == alloc_address) {
-    // Use After Free on a 41-byte allocation at 0xdeadbeef.
-    _LOG(log, logtype::HEADER, "Cause: [GWP-ASan]: %s on a %zu-byte allocation at 0x%" PRIxPTR "\n",
-         error_string_, alloc_size, alloc_address);
-    return;
-  }
-
   uintptr_t diff;
   const char* location_str;
 
@@ -157,8 +214,6 @@
        error_string_, diff, byte_suffix, location_str, alloc_size, alloc_address);
 }
 
-constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
-
 bool GwpAsanCrashData::HasDeallocationTrace() const {
   assert(CrashIsMine() && "HasDeallocationTrace(): Crash is not mine!");
   if (!responsible_allocation_ || !__gwp_asan_is_deallocated(responsible_allocation_)) {
@@ -171,7 +226,7 @@
   assert(HasDeallocationTrace() && "DumpDeallocationTrace(): No dealloc trace!");
   uint64_t thread_id = __gwp_asan_get_deallocation_thread_id(responsible_allocation_);
 
-  std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
+  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
   size_t num_frames =
       __gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
 
@@ -183,7 +238,7 @@
 
   unwinder->SetDisplayBuildID(true);
   for (size_t i = 0; i < num_frames; ++i) {
-    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
     frame_data.num = i;
     _LOG(log, logtype::BACKTRACE, "    %s\n", unwinder->FormatFrame(frame_data).c_str());
   }
@@ -198,7 +253,7 @@
   assert(HasAllocationTrace() && "DumpAllocationTrace(): No dealloc trace!");
   uint64_t thread_id = __gwp_asan_get_allocation_thread_id(responsible_allocation_);
 
-  std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]);
+  std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]);
   size_t num_frames =
       __gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength);
 
@@ -210,7 +265,7 @@
 
   unwinder->SetDisplayBuildID(true);
   for (size_t i = 0; i < num_frames; ++i) {
-    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]);
     frame_data.num = i;
     _LOG(log, logtype::BACKTRACE, "    %s\n", unwinder->FormatFrame(frame_data).c_str());
   }
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
index 6c88733..f9c2481 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h
@@ -26,6 +26,9 @@
 #include "types.h"
 #include "utility.h"
 
+class Cause;
+class Tombstone;
+
 class GwpAsanCrashData {
  public:
   GwpAsanCrashData() = delete;
@@ -69,6 +72,8 @@
   // HasAllocationTrace() returns true.
   void DumpAllocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const;
 
+  void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;
+
  protected:
   // Is GWP-ASan responsible for this crash.
   bool is_gwp_asan_responsible_ = false;
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
index 4d00ece..c3b95d6 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h
@@ -23,6 +23,9 @@
 
 #include "scudo/interface.h"
 
+class Cause;
+class Tombstone;
+
 class ScudoCrashData {
  public:
   ScudoCrashData() = delete;
@@ -32,6 +35,7 @@
   bool CrashIsMine() const;
 
   void DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const;
+  void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const;
 
  private:
   scudo_error_info error_info_ = {};
@@ -39,4 +43,7 @@
 
   void DumpReport(const scudo_error_report* report, log_t* log,
                   unwindstack::Unwinder* unwinder) const;
+
+  void FillInCause(Cause* cause, const scudo_error_report* report,
+                   unwindstack::Unwinder* unwinder) const;
 };
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
index 3ff7d62..2331f1e 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h
@@ -21,6 +21,7 @@
 #include <stddef.h>
 #include <sys/types.h>
 
+#include <functional>
 #include <map>
 #include <string>
 
@@ -30,7 +31,13 @@
 #include "types.h"
 
 // Forward declarations
+class BacktraceFrame;
+class Cause;
+class Tombstone;
+
 namespace unwindstack {
+struct FrameData;
+class Maps;
 class Unwinder;
 }
 
@@ -44,13 +51,25 @@
 int open_tombstone(std::string* path);
 
 /* Creates a tombstone file and writes the crash dump to it. */
-void engrave_tombstone(android::base::unique_fd output_fd, unwindstack::Unwinder* unwinder,
+void engrave_tombstone(android::base::unique_fd output_fd, android::base::unique_fd proto_fd,
+                       unwindstack::Unwinder* unwinder,
                        const std::map<pid_t, ThreadInfo>& thread_info, pid_t target_thread,
                        const ProcessInfo& process_info, OpenFilesList* open_files,
                        std::string* amfd_data);
 
-void engrave_tombstone_ucontext(int tombstone_fd, uint64_t abort_msg_address, siginfo_t* siginfo,
-                                ucontext_t* ucontext);
+void engrave_tombstone_ucontext(int tombstone_fd, int proto_fd, uint64_t abort_msg_address,
+                                siginfo_t* siginfo, ucontext_t* ucontext);
 
+void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+                             const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
+                             const ProcessInfo& process_info, const OpenFilesList* open_files);
+
+bool tombstone_proto_to_text(
+    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 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 30e75e1..086dc97 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -18,6 +18,7 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
 #include <unwindstack/Regs.h>
 
@@ -31,12 +32,15 @@
   std::string thread_name;
 
   pid_t pid;
-  std::string process_name;
+
+  std::vector<std::string> command_line;
+  std::string selinux_label;
 
   int signo = 0;
   siginfo_t* siginfo = nullptr;
 };
 
+// This struct is written into a pipe from inside the crashing process.
 struct ProcessInfo {
   uintptr_t abort_msg_address = 0;
   uintptr_t fdsan_table_address = 0;
@@ -44,7 +48,9 @@
   uintptr_t gwp_asan_metadata = 0;
   uintptr_t scudo_stack_depot = 0;
   uintptr_t scudo_region_info = 0;
+  uintptr_t scudo_ring_buffer = 0;
 
   bool has_fault_address = false;
-  uintptr_t fault_address = 0;
+  uintptr_t untagged_fault_address = 0;
+  uintptr_t maybe_tagged_fault_address = 0;
 };
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 29fb9a4..c490fb1 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -81,6 +81,8 @@
 
 void log_backtrace(log_t* log, unwindstack::Unwinder* unwinder, const char* prefix);
 
+ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr,
+                    unwindstack::Memory* memory);
 void dump_memory(log_t* log, unwindstack::Memory* backtrace, uint64_t addr, const std::string&);
 
 void drop_capabilities();
@@ -91,4 +93,7 @@
 const char* get_signame(const siginfo_t*);
 const char* get_sigcode(const siginfo_t*);
 
+// Number of bytes per MTE granule.
+constexpr size_t kTagGranuleSize = 16;
+
 #endif // _DEBUGGERD_UTILITY_H
diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp
index f8bfe07..f4690ba 100644
--- a/debuggerd/libdebuggerd/scudo.cpp
+++ b/debuggerd/libdebuggerd/scudo.cpp
@@ -15,13 +15,16 @@
  */
 
 #include "libdebuggerd/scudo.h"
-#include "libdebuggerd/gwp_asan.h"
+#include "libdebuggerd/tombstone.h"
 
 #include "unwindstack/Memory.h"
 #include "unwindstack/Unwinder.h"
 
+#include <android-base/macros.h>
 #include <bionic/macros.h>
 
+#include "tombstone.pb.h"
+
 std::unique_ptr<char[]> AllocAndReadFully(unwindstack::Memory* process_memory, uint64_t addr,
                                           size_t size) {
   auto buf = std::make_unique<char[]>(size);
@@ -31,8 +34,6 @@
   return buf;
 }
 
-static const uintptr_t kTagGranuleSize = 16;
-
 ScudoCrashData::ScudoCrashData(unwindstack::Memory* process_memory,
                                const ProcessInfo& process_info) {
   if (!process_info.has_fault_address) {
@@ -43,8 +44,10 @@
                                        __scudo_get_stack_depot_size());
   auto region_info = AllocAndReadFully(process_memory, process_info.scudo_region_info,
                                        __scudo_get_region_info_size());
+  auto ring_buffer = AllocAndReadFully(process_memory, process_info.scudo_ring_buffer,
+                                       __scudo_get_ring_buffer_size());
 
-  untagged_fault_addr_ = untag_address(process_info.fault_address);
+  untagged_fault_addr_ = process_info.untagged_fault_address;
   uintptr_t fault_page = untagged_fault_addr_ & ~(PAGE_SIZE - 1);
 
   uintptr_t memory_begin = fault_page - PAGE_SIZE * 16;
@@ -67,15 +70,67 @@
     memory_tags[(i - memory_begin) / kTagGranuleSize] = process_memory->ReadTag(i);
   }
 
-  __scudo_get_error_info(&error_info_, process_info.fault_address, stack_depot.get(),
-                         region_info.get(), memory.get(), memory_tags.get(), memory_begin,
-                         memory_end - memory_begin);
+  __scudo_get_error_info(&error_info_, process_info.maybe_tagged_fault_address, stack_depot.get(),
+                         region_info.get(), ring_buffer.get(), memory.get(), memory_tags.get(),
+                         memory_begin, memory_end - memory_begin);
 }
 
 bool ScudoCrashData::CrashIsMine() const {
   return error_info_.reports[0].error_type != UNKNOWN;
 }
 
+void ScudoCrashData::FillInCause(Cause* cause, const scudo_error_report* report,
+                                 unwindstack::Unwinder* unwinder) const {
+  MemoryError* memory_error = cause->mutable_memory_error();
+  HeapObject* heap_object = memory_error->mutable_heap();
+
+  memory_error->set_tool(MemoryError_Tool_SCUDO);
+  switch (report->error_type) {
+    case USE_AFTER_FREE:
+      memory_error->set_type(MemoryError_Type_USE_AFTER_FREE);
+      break;
+    case BUFFER_OVERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW);
+      break;
+    case BUFFER_UNDERFLOW:
+      memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW);
+      break;
+    default:
+      memory_error->set_type(MemoryError_Type_UNKNOWN);
+      break;
+  }
+
+  heap_object->set_address(report->allocation_address);
+  heap_object->set_size(report->allocation_size);
+  unwinder->SetDisplayBuildID(true);
+
+  heap_object->set_allocation_tid(report->allocation_tid);
+  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());
+  }
+
+  heap_object->set_deallocation_tid(report->deallocation_tid);
+  for (size_t i = 0; i < arraysize(report->deallocation_trace) && report->deallocation_trace[i];
+       ++i) {
+    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());
+  }
+
+  set_human_readable_cause(cause, untagged_fault_addr_);
+}
+
+void ScudoCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const {
+  size_t report_num = 0;
+  while (report_num < sizeof(error_info_.reports) / sizeof(error_info_.reports[0]) &&
+         error_info_.reports[report_num].error_type != UNKNOWN) {
+    FillInCause(tombstone->add_causes(), &error_info_.reports[report_num++], unwinder);
+  }
+}
+
 void ScudoCrashData::DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const {
   if (error_info_.reports[1].error_type != UNKNOWN) {
     _LOG(log, logtype::HEADER,
@@ -138,7 +193,8 @@
   if (report->allocation_trace[0]) {
     _LOG(log, logtype::BACKTRACE, "\nallocated by thread %u:\n", report->allocation_tid);
     unwinder->SetDisplayBuildID(true);
-    for (size_t i = 0; i < 64 && report->allocation_trace[i]; ++i) {
+    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]);
       frame_data.num = i;
@@ -149,7 +205,8 @@
   if (report->deallocation_trace[0]) {
     _LOG(log, logtype::BACKTRACE, "\ndeallocated by thread %u:\n", report->deallocation_tid);
     unwinder->SetDisplayBuildID(true);
-    for (size_t i = 0; i < 64 && report->deallocation_trace[i]; ++i) {
+    for (size_t i = 0; i < arraysize(report->deallocation_trace) && report->deallocation_trace[i];
+         ++i) {
       unwindstack::FrameData frame_data =
           unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]);
       frame_data.num = i;
diff --git a/debuggerd/libdebuggerd/test/UnwinderMock.h b/debuggerd/libdebuggerd/test/UnwinderMock.h
index 023a578..8f84346 100644
--- a/debuggerd/libdebuggerd/test/UnwinderMock.h
+++ b/debuggerd/libdebuggerd/test/UnwinderMock.h
@@ -33,8 +33,7 @@
   void MockSetBuildID(uint64_t offset, const std::string& build_id) {
     unwindstack::MapInfo* map_info = GetMaps()->Find(offset);
     if (map_info != nullptr) {
-      std::string* new_build_id = new std::string(build_id);
-      map_info->build_id = reinterpret_cast<uintptr_t>(new_build_id);
+      map_info->SetBuildID(std::string(build_id));
     }
   }
 };
diff --git a/debuggerd/libdebuggerd/test/dump_memory_test.cpp b/debuggerd/libdebuggerd/test/dump_memory_test.cpp
index f16f578..5be145a 100644
--- a/debuggerd/libdebuggerd/test/dump_memory_test.cpp
+++ b/debuggerd/libdebuggerd/test/dump_memory_test.cpp
@@ -73,34 +73,14 @@
 "    0000000012345600 2726252423222120 2f2e2d2c2b2a2928   !\"#$%&'()*+,-./\n"
 "    0000000012345610 3736353433323130 3f3e3d3c3b3a3938  0123456789:;<=>?\n"
 "    0000000012345620 4746454443424140 4f4e4d4c4b4a4948  @ABCDEFGHIJKLMNO\n"
-"    0000000012345630 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n"
-"    0000000012345640 6766656463626160 ----------------  `abcdefg........\n"
-"    0000000012345650 ---------------- ----------------  ................\n"
-"    0000000012345660 ---------------- ----------------  ................\n"
-"    0000000012345670 ---------------- ----------------  ................\n"
-"    0000000012345680 ---------------- ----------------  ................\n"
-"    0000000012345690 ---------------- ----------------  ................\n"
-"    00000000123456a0 ---------------- ----------------  ................\n"
-"    00000000123456b0 ---------------- ----------------  ................\n"
-"    00000000123456c0 ---------------- ----------------  ................\n"
-"    00000000123456d0 ---------------- ----------------  ................\n";
+"    0000000012345630 5756555453525150 5f5e5d5c5b5a5958  PQRSTUVWXYZ[\\]^_\n";
 #else
 "    123455e0 03020100 07060504 0b0a0908 0f0e0d0c  ................\n"
 "    123455f0 13121110 17161514 1b1a1918 1f1e1d1c  ................\n"
 "    12345600 23222120 27262524 2b2a2928 2f2e2d2c   !\"#$%&'()*+,-./\n"
 "    12345610 33323130 37363534 3b3a3938 3f3e3d3c  0123456789:;<=>?\n"
 "    12345620 43424140 47464544 4b4a4948 4f4e4d4c  @ABCDEFGHIJKLMNO\n"
-"    12345630 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n"
-"    12345640 63626160 67666564 -------- --------  `abcdefg........\n"
-"    12345650 -------- -------- -------- --------  ................\n"
-"    12345660 -------- -------- -------- --------  ................\n"
-"    12345670 -------- -------- -------- --------  ................\n"
-"    12345680 -------- -------- -------- --------  ................\n"
-"    12345690 -------- -------- -------- --------  ................\n"
-"    123456a0 -------- -------- -------- --------  ................\n"
-"    123456b0 -------- -------- -------- --------  ................\n"
-"    123456c0 -------- -------- -------- --------  ................\n"
-"    123456d0 -------- -------- -------- --------  ................\n";
+"    12345630 53525150 57565554 5b5a5958 5f5e5d5c  PQRSTUVWXYZ[\\]^_\n";
 #endif
 
 class MemoryMock : public unwindstack::Memory {
@@ -513,15 +493,7 @@
   const char* expected_dump = \
 "\nmemory near r4:\n"
 #if defined(__LP64__)
-R"(    0000000010000f80 ---------------- ----------------  ................
-    0000000010000f90 ---------------- ----------------  ................
-    0000000010000fa0 ---------------- ----------------  ................
-    0000000010000fb0 ---------------- ----------------  ................
-    0000000010000fc0 ---------------- ----------------  ................
-    0000000010000fd0 ---------------- ----------------  ................
-    0000000010000fe0 ---------------- ----------------  ................
-    0000000010000ff0 ---------------- ----------------  ................
-    0000000010001000 8786858483828180 8f8e8d8c8b8a8988  ................
+R"(    0000000010001000 8786858483828180 8f8e8d8c8b8a8988  ................
     0000000010001010 9796959493929190 9f9e9d9c9b9a9998  ................
     0000000010001020 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8  ................
     0000000010001030 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8  ................
@@ -531,15 +503,7 @@
     0000000010001070 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8  ................
 )";
 #else
-R"(    10000f80 -------- -------- -------- --------  ................
-    10000f90 -------- -------- -------- --------  ................
-    10000fa0 -------- -------- -------- --------  ................
-    10000fb0 -------- -------- -------- --------  ................
-    10000fc0 -------- -------- -------- --------  ................
-    10000fd0 -------- -------- -------- --------  ................
-    10000fe0 -------- -------- -------- --------  ................
-    10000ff0 -------- -------- -------- --------  ................
-    10001000 83828180 87868584 8b8a8988 8f8e8d8c  ................
+R"(    10001000 83828180 87868584 8b8a8988 8f8e8d8c  ................
     10001010 93929190 97969594 9b9a9998 9f9e9d9c  ................
     10001020 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac  ................
     10001030 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc  ................
@@ -574,39 +538,11 @@
   const char* expected_dump = \
 "\nmemory near r4:\n"
 #if defined(__LP64__)
-"    0000000010000f40 ---------------- ----------------  ................\n"
-"    0000000010000f50 ---------------- ----------------  ................\n"
-"    0000000010000f60 ---------------- ----------------  ................\n"
-"    0000000010000f70 ---------------- ----------------  ................\n"
-"    0000000010000f80 ---------------- ----------------  ................\n"
-"    0000000010000f90 ---------------- ----------------  ................\n"
-"    0000000010000fa0 ---------------- ----------------  ................\n"
-"    0000000010000fb0 ---------------- ----------------  ................\n"
-"    0000000010000fc0 ---------------- ----------------  ................\n"
-"    0000000010000fd0 ---------------- ----------------  ................\n"
-"    0000000010000fe0 ---------------- ----------------  ................\n"
-"    0000000010000ff0 ---------------- ----------------  ................\n"
 "    0000000010001000 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8  ................\n"
-"    0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n"
-"    0000000010001020 ---------------- ----------------  ................\n"
-"    0000000010001030 ---------------- ----------------  ................\n";
+"    0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8  ................\n";
 #else
-"    10000f40 -------- -------- -------- --------  ................\n"
-"    10000f50 -------- -------- -------- --------  ................\n"
-"    10000f60 -------- -------- -------- --------  ................\n"
-"    10000f70 -------- -------- -------- --------  ................\n"
-"    10000f80 -------- -------- -------- --------  ................\n"
-"    10000f90 -------- -------- -------- --------  ................\n"
-"    10000fa0 -------- -------- -------- --------  ................\n"
-"    10000fb0 -------- -------- -------- --------  ................\n"
-"    10000fc0 -------- -------- -------- --------  ................\n"
-"    10000fd0 -------- -------- -------- --------  ................\n"
-"    10000fe0 -------- -------- -------- --------  ................\n"
-"    10000ff0 -------- -------- -------- --------  ................\n"
 "    10001000 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc  ................\n"
-"    10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n"
-"    10001020 -------- -------- -------- --------  ................\n"
-"    10001030 -------- -------- -------- --------  ................\n";
+"    10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc  ................\n";
 #endif
   ASSERT_STREQ(expected_dump, tombstone_contents.c_str());
 
diff --git a/debuggerd/libdebuggerd/test/sys/system_properties.h b/debuggerd/libdebuggerd/test/sys/system_properties.h
deleted file mode 100644
index 1f4f58a..0000000
--- a/debuggerd/libdebuggerd/test/sys/system_properties.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- * All rights reserved.
- *
- * 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.
- */
-
-#ifndef _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
-#define _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
-
-// This is just enough to get the property code to compile on
-// the host.
-
-#define PROP_VALUE_MAX  92
-
-#endif // _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H
diff --git a/debuggerd/libdebuggerd/test/tombstone_test.cpp b/debuggerd/libdebuggerd/test/tombstone_test.cpp
index b42d70c..a14dcb0 100644
--- a/debuggerd/libdebuggerd/test/tombstone_test.cpp
+++ b/debuggerd/libdebuggerd/test/tombstone_test.cpp
@@ -350,11 +350,11 @@
 }
 
 TEST_F(TombstoneTest, dump_thread_info_uid) {
-  dump_thread_info(&log_, ThreadInfo{.uid = 1,
-                                     .tid = 3,
-                                     .thread_name = "some_thread",
-                                     .pid = 2,
-                                     .process_name = "some_process"});
+  std::vector<std::string> cmdline = {"some_process"};
+  dump_thread_info(
+      &log_,
+      ThreadInfo{
+          .uid = 1, .tid = 3, .thread_name = "some_thread", .pid = 2, .command_line = cmdline});
   std::string expected = "pid: 2, tid: 3, name: some_thread  >>> some_process <<<\nuid: 1\n";
   ASSERT_STREQ(expected.c_str(), amfd_data_.c_str());
 }
@@ -379,7 +379,7 @@
 TEST_F(TombstoneTest, gwp_asan_cause_uaf_exact) {
   gwp_asan::AllocationMetadata meta;
   meta.Addr = 0x1000;
-  meta.Size = 32;
+  meta.RequestedSize = 32;
 
   GwpAsanCrashDataTest crash_data(gwp_asan::Error::USE_AFTER_FREE, &meta);
   crash_data.SetCrashAddress(0x1000);
@@ -388,15 +388,14 @@
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   std::string tombstone_contents;
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_THAT(tombstone_contents,
-              MatchesRegex("Cause: \\[GWP-ASan\\]: Use After Free on a 32-byte "
-                           "allocation at 0x[a-fA-F0-9]+\n"));
+  ASSERT_THAT(tombstone_contents, MatchesRegex("Cause: \\[GWP-ASan\\]: Use After Free, 0 bytes "
+                                               "into a 32-byte allocation at 0x[a-fA-F0-9]+\n"));
 }
 
 TEST_F(TombstoneTest, gwp_asan_cause_double_free) {
   gwp_asan::AllocationMetadata meta;
   meta.Addr = 0x1000;
-  meta.Size = 32;
+  meta.RequestedSize = 32;
 
   GwpAsanCrashDataTest crash_data(gwp_asan::Error::DOUBLE_FREE, &meta);
   crash_data.SetCrashAddress(0x1000);
@@ -405,15 +404,14 @@
   ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0);
   std::string tombstone_contents;
   ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents));
-  ASSERT_THAT(tombstone_contents,
-              MatchesRegex("Cause: \\[GWP-ASan\\]: Double Free on a 32-byte "
-                           "allocation at 0x[a-fA-F0-9]+\n"));
+  ASSERT_THAT(tombstone_contents, MatchesRegex("Cause: \\[GWP-ASan\\]: Double Free, 0 bytes into a "
+                                               "32-byte allocation at 0x[a-fA-F0-9]+\n"));
 }
 
 TEST_F(TombstoneTest, gwp_asan_cause_overflow) {
   gwp_asan::AllocationMetadata meta;
   meta.Addr = 0x1000;
-  meta.Size = 32;
+  meta.RequestedSize = 32;
 
   GwpAsanCrashDataTest crash_data(gwp_asan::Error::BUFFER_OVERFLOW, &meta);
   crash_data.SetCrashAddress(0x1025);
@@ -432,7 +430,7 @@
 TEST_F(TombstoneTest, gwp_asan_cause_underflow) {
   gwp_asan::AllocationMetadata meta;
   meta.Addr = 0x1000;
-  meta.Size = 32;
+  meta.RequestedSize = 32;
 
   GwpAsanCrashDataTest crash_data(gwp_asan::Error::BUFFER_UNDERFLOW, &meta);
   crash_data.SetCrashAddress(0xffe);
@@ -451,7 +449,7 @@
 TEST_F(TombstoneTest, gwp_asan_cause_invalid_free_inside) {
   gwp_asan::AllocationMetadata meta;
   meta.Addr = 0x1000;
-  meta.Size = 32;
+  meta.RequestedSize = 32;
 
   GwpAsanCrashDataTest crash_data(gwp_asan::Error::INVALID_FREE, &meta);
   crash_data.SetCrashAddress(0x1001);
@@ -470,7 +468,7 @@
 TEST_F(TombstoneTest, gwp_asan_cause_invalid_free_outside) {
   gwp_asan::AllocationMetadata meta;
   meta.Addr = 0x1000;
-  meta.Size = 32;
+  meta.RequestedSize = 32;
 
   GwpAsanCrashDataTest crash_data(gwp_asan::Error::INVALID_FREE, &meta);
   crash_data.SetCrashAddress(0x1021);
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index d88c5a9..ad903ce 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -36,12 +36,13 @@
 #include <string>
 
 #include <android-base/file.h>
-#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <android/log.h>
+#include <async_safe/log.h>
+#include <bionic/macros.h>
 #include <log/log.h>
 #include <log/log_read.h>
 #include <log/logprint.h>
@@ -63,6 +64,8 @@
 #include "gwp_asan/common.h"
 #include "gwp_asan/crash_handler.h"
 
+#include "tombstone.pb.h"
+
 using android::base::GetBoolProperty;
 using android::base::GetProperty;
 using android::base::StringPrintf;
@@ -103,9 +106,9 @@
     unwindstack::MapInfo* map_info = maps->Find(sp);
     if (map_info == nullptr) {
       return "stack pointer is in a non-existent map; likely due to stack overflow.";
-    } else if ((map_info->flags & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
+    } else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
       return "stack pointer is not in a rw map; likely due to stack overflow.";
-    } else if ((sp - map_info->start) <= kMaxDifferenceBytes) {
+    } else if ((sp - map_info->start()) <= kMaxDifferenceBytes) {
       return "stack pointer is close to top of stack; likely stack overflow.";
     }
   }
@@ -134,7 +137,7 @@
   } else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
     uint64_t fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
     unwindstack::MapInfo* map_info = maps->Find(fault_addr);
-    if (map_info != nullptr && map_info->flags == PROT_EXEC) {
+    if (map_info != nullptr && map_info->flags() == PROT_EXEC) {
       cause = "execute-only (no-read) memory access error; likely due to data in .text.";
     } else {
       cause = get_stack_overflow_cause(fault_addr, regs->sp(), maps);
@@ -151,7 +154,9 @@
                              const ProcessInfo& process_info, unwindstack::Memory* process_memory) {
   char addr_desc[64];  // ", fault addr 0x1234"
   if (process_info.has_fault_address) {
-    size_t addr = process_info.fault_address;
+    // SIGILL faults will never have tagged addresses, so okay to
+    // indiscriminately use the tagged address here.
+    size_t addr = process_info.maybe_tagged_fault_address;
     if (thread_info.siginfo->si_signo == SIGILL) {
       uint32_t instruction = {};
       process_memory->Read(addr, &instruction, sizeof(instruction));
@@ -177,8 +182,13 @@
   // Don't try to collect logs from the threads that implement the logging system itself.
   if (thread_info.uid == AID_LOGD) log->should_retrieve_logcat = false;
 
+  const char* process_name = "<unknown>";
+  if (!thread_info.command_line.empty()) {
+    process_name = thread_info.command_line[0].c_str();
+  }
+
   _LOG(log, logtype::HEADER, "pid: %d, tid: %d, name: %s  >>> %s <<<\n", thread_info.pid,
-       thread_info.tid, thread_info.thread_name.c_str(), thread_info.process_name.c_str());
+       thread_info.tid, thread_info.thread_name.c_str(), process_name);
   _LOG(log, logtype::HEADER, "uid: %d\n", thread_info.uid);
   if (thread_info.tagged_addr_ctrl != -1) {
     _LOG(log, logtype::HEADER, "tagged_addr_ctrl: %016lx\n", thread_info.tagged_addr_ctrl);
@@ -188,8 +198,7 @@
 static std::string get_addr_string(uint64_t addr) {
   std::string addr_str;
 #if defined(__LP64__)
-  addr_str = StringPrintf("%08x'%08x",
-                          static_cast<uint32_t>(addr >> 32),
+  addr_str = StringPrintf("%08x'%08x", static_cast<uint32_t>(addr >> 32),
                           static_cast<uint32_t>(addr & 0xffffffff));
 #else
   addr_str = StringPrintf("%08x", static_cast<uint32_t>(addr));
@@ -235,7 +244,7 @@
        "memory map (%zu entr%s):",
        maps->Total(), maps->Total() == 1 ? "y" : "ies");
   if (print_fault_address_marker) {
-    if (maps->Total() != 0 && addr < maps->Get(0)->start) {
+    if (maps->Total() != 0 && addr < maps->Get(0)->start()) {
       _LOG(log, logtype::MAPS, "\n--->Fault address falls at %s before any mapped regions\n",
            get_addr_string(addr).c_str());
       print_fault_address_marker = false;
@@ -252,37 +261,37 @@
   for (auto const& map_info : *maps) {
     line = "    ";
     if (print_fault_address_marker) {
-      if (addr < map_info->start) {
+      if (addr < map_info->start()) {
         _LOG(log, logtype::MAPS, "--->Fault address falls at %s between mapped regions\n",
              get_addr_string(addr).c_str());
         print_fault_address_marker = false;
-      } else if (addr >= map_info->start && addr < map_info->end) {
+      } else if (addr >= map_info->start() && addr < map_info->end()) {
         line = "--->";
         print_fault_address_marker = false;
       }
     }
-    line += get_addr_string(map_info->start) + '-' + get_addr_string(map_info->end - 1) + ' ';
-    if (map_info->flags & PROT_READ) {
+    line += get_addr_string(map_info->start()) + '-' + get_addr_string(map_info->end() - 1) + ' ';
+    if (map_info->flags() & PROT_READ) {
       line += 'r';
     } else {
       line += '-';
     }
-    if (map_info->flags & PROT_WRITE) {
+    if (map_info->flags() & PROT_WRITE) {
       line += 'w';
     } else {
       line += '-';
     }
-    if (map_info->flags & PROT_EXEC) {
+    if (map_info->flags() & PROT_EXEC) {
       line += 'x';
     } else {
       line += '-';
     }
-    line += StringPrintf("  %8" PRIx64 "  %8" PRIx64, map_info->offset,
-                         map_info->end - map_info->start);
+    line += StringPrintf("  %8" PRIx64 "  %8" PRIx64, map_info->offset(),
+                         map_info->end() - map_info->start());
     bool space_needed = true;
-    if (!map_info->name.empty()) {
+    if (!map_info->name().empty()) {
       space_needed = false;
-      line += "  " + map_info->name;
+      line += "  " + map_info->name();
       std::string build_id = map_info->GetPrintableBuildID();
       if (!build_id.empty()) {
         line += " (BuildId: " + build_id + ")";
@@ -359,9 +368,9 @@
   regs->IterateRegisters([log, maps, memory](const char* reg_name, uint64_t reg_value) {
     std::string label{"memory near "s + reg_name};
     if (maps) {
-      unwindstack::MapInfo* map_info = maps->Find(reg_value);
-      if (map_info != nullptr && !map_info->name.empty()) {
-        label += " (" + map_info->name + ")";
+      unwindstack::MapInfo* map_info = maps->Find(untag_address(reg_value));
+      if (map_info != nullptr && !map_info->name().empty()) {
+        label += " (" + map_info->name() + ")";
       }
     }
     dump_memory(log, memory, reg_value, label);
@@ -392,8 +401,7 @@
   if (primary_thread && gwp_asan_crash_data->CrashIsMine()) {
     gwp_asan_crash_data->DumpCause(log);
   } else if (thread_info.siginfo && !(primary_thread && scudo_crash_data->CrashIsMine())) {
-    dump_probable_cause(log, thread_info.siginfo, unwinder->GetMaps(),
-                        thread_info.registers.get());
+    dump_probable_cause(log, thread_info.siginfo, unwinder->GetMaps(), thread_info.registers.get());
   }
 
   if (primary_thread) {
@@ -433,9 +441,8 @@
                          thread_info.registers.get());
     if (maps != nullptr) {
       uint64_t addr = 0;
-      siginfo_t* si = thread_info.siginfo;
-      if (signal_has_si_addr(si)) {
-        addr = reinterpret_cast<uint64_t>(si->si_addr);
+      if (process_info.has_fault_address) {
+        addr = process_info.untagged_fault_address;
       }
       dump_all_maps(log, unwinder, addr);
     }
@@ -491,8 +498,7 @@
     // the tombstone file.
 
     if (first) {
-      _LOG(log, logtype::LOGS, "--------- %slog %s\n",
-        tail ? "tail end of " : "", filename);
+      _LOG(log, logtype::LOGS, "--------- %slog %s\n", tail ? "tail end of " : "", filename);
       first = false;
     }
 
@@ -553,8 +559,8 @@
   dump_log_file(log, pid, "main", tail);
 }
 
-void engrave_tombstone_ucontext(int tombstone_fd, uint64_t abort_msg_address, siginfo_t* siginfo,
-                                ucontext_t* ucontext) {
+void engrave_tombstone_ucontext(int tombstone_fd, int proto_fd, uint64_t abort_msg_address,
+                                siginfo_t* siginfo, ucontext_t* ucontext) {
   pid_t uid = getuid();
   pid_t pid = getpid();
   pid_t tid = gettid();
@@ -566,39 +572,51 @@
   log.amfd_data = nullptr;
 
   std::string thread_name = get_thread_name(tid);
-  std::string process_name = get_process_name(pid);
+  std::vector<std::string> command_line = get_command_line(pid);
 
   std::unique_ptr<unwindstack::Regs> regs(
       unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), ucontext));
 
+  std::string selinux_label;
+  android::base::ReadFileToString("/proc/self/attr/current", &selinux_label);
+
   std::map<pid_t, ThreadInfo> threads;
   threads[tid] = ThreadInfo{
       .registers = std::move(regs),
       .uid = uid,
       .tid = tid,
-      .thread_name = thread_name.c_str(),
+      .thread_name = std::move(thread_name),
       .pid = pid,
-      .process_name = process_name.c_str(),
+      .command_line = std::move(command_line),
+      .selinux_label = std::move(selinux_label),
       .siginfo = siginfo,
   };
 
   unwindstack::UnwinderFromPid unwinder(kMaxFrames, pid, unwindstack::Regs::CurrentArch());
   if (!unwinder.Init()) {
-    LOG(FATAL) << "Failed to init unwinder object.";
+    async_safe_fatal("failed to init unwinder object");
   }
 
   ProcessInfo process_info;
   process_info.abort_msg_address = abort_msg_address;
-  engrave_tombstone(unique_fd(dup(tombstone_fd)), &unwinder, threads, tid, process_info, nullptr,
-                    nullptr);
+  engrave_tombstone(unique_fd(dup(tombstone_fd)), unique_fd(dup(proto_fd)), &unwinder, threads, tid,
+                    process_info, nullptr, nullptr);
 }
 
-void engrave_tombstone(unique_fd output_fd, unwindstack::Unwinder* unwinder,
+void engrave_tombstone(unique_fd output_fd, unique_fd proto_fd, unwindstack::Unwinder* unwinder,
                        const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
                        const ProcessInfo& process_info, OpenFilesList* open_files,
                        std::string* amfd_data) {
   // Don't copy log messages to tombstone unless this is a development device.
-  bool want_logs = GetBoolProperty("ro.debuggable", false);
+  Tombstone tombstone;
+  engrave_tombstone_proto(&tombstone, unwinder, threads, target_thread, process_info, open_files);
+
+  if (proto_fd != -1) {
+    if (!tombstone.SerializeToFileDescriptor(proto_fd.get())) {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to write proto tombstone: %s",
+                            strerror(errno));
+    }
+  }
 
   log_t log;
   log.current_tid = target_thread;
@@ -606,35 +624,45 @@
   log.tfd = output_fd.get();
   log.amfd_data = amfd_data;
 
-  _LOG(&log, logtype::HEADER, "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
-  dump_header_info(&log);
-  _LOG(&log, logtype::HEADER, "Timestamp: %s\n", get_timestamp().c_str());
+  bool translate_proto = GetBoolProperty("debug.debuggerd.translate_proto_to_text", true);
+  if (translate_proto) {
+    tombstone_proto_to_text(tombstone, [&log](const std::string& line, bool should_log) {
+      _LOG(&log, should_log ? logtype::HEADER : logtype::LOGS, "%s\n", line.c_str());
+    });
+  } else {
+    bool want_logs = GetBoolProperty("ro.debuggable", false);
 
-  auto it = threads.find(target_thread);
-  if (it == threads.end()) {
-    LOG(FATAL) << "failed to find target thread";
-  }
+    _LOG(&log, logtype::HEADER,
+         "*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***\n");
+    dump_header_info(&log);
+    _LOG(&log, logtype::HEADER, "Timestamp: %s\n", get_timestamp().c_str());
 
-  dump_thread(&log, unwinder, it->second, process_info, true);
-
-  if (want_logs) {
-    dump_logs(&log, it->second.pid, 50);
-  }
-
-  for (auto& [tid, thread_info] : threads) {
-    if (tid == target_thread) {
-      continue;
+    auto it = threads.find(target_thread);
+    if (it == threads.end()) {
+      async_safe_fatal("failed to find target thread");
     }
 
-    dump_thread(&log, unwinder, thread_info, process_info, false);
-  }
+    dump_thread(&log, unwinder, it->second, process_info, true);
 
-  if (open_files) {
-    _LOG(&log, logtype::OPEN_FILES, "\nopen files:\n");
-    dump_open_files_list(&log, *open_files, "    ");
-  }
+    if (want_logs) {
+      dump_logs(&log, it->second.pid, 50);
+    }
 
-  if (want_logs) {
-    dump_logs(&log, it->second.pid, 0);
+    for (auto& [tid, thread_info] : threads) {
+      if (tid == target_thread) {
+        continue;
+      }
+
+      dump_thread(&log, unwinder, thread_info, process_info, false);
+    }
+
+    if (open_files) {
+      _LOG(&log, logtype::OPEN_FILES, "\nopen files:\n");
+      dump_open_files_list(&log, *open_files, "    ");
+    }
+
+    if (want_logs) {
+      dump_logs(&log, it->second.pid, 0);
+    }
   }
 }
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
new file mode 100644
index 0000000..abd1f12
--- /dev/null
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -0,0 +1,624 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "DEBUG"
+
+#include "libdebuggerd/tombstone.h"
+#include "libdebuggerd/gwp_asan.h"
+#include "libdebuggerd/scudo.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+
+#include <memory>
+#include <optional>
+#include <string>
+
+#include <async_safe/log.h>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+
+#include <android/log.h>
+#include <bionic/macros.h>
+#include <log/log.h>
+#include <log/log_read.h>
+#include <log/logprint.h>
+#include <private/android_filesystem_config.h>
+
+#include <procinfo/process.h>
+#include <unwindstack/Maps.h>
+#include <unwindstack/Memory.h>
+#include <unwindstack/Regs.h>
+#include <unwindstack/Unwinder.h>
+
+#include "libdebuggerd/open_files_list.h"
+#include "libdebuggerd/utility.h"
+#include "util.h"
+
+#include "tombstone.pb.h"
+
+using android::base::StringPrintf;
+
+// Use the demangler from libc++.
+extern "C" char* __cxa_demangle(const char*, char*, size_t*, int* status);
+
+static Architecture get_arch() {
+#if defined(__arm__)
+  return Architecture::ARM32;
+#elif defined(__aarch64__)
+  return Architecture::ARM64;
+#elif defined(__i386__)
+  return Architecture::X86;
+#elif defined(__x86_64__)
+  return Architecture::X86_64;
+#else
+#error Unknown architecture!
+#endif
+}
+
+static std::optional<std::string> get_stack_overflow_cause(uint64_t fault_addr, uint64_t sp,
+                                                           unwindstack::Maps* maps) {
+  static constexpr uint64_t kMaxDifferenceBytes = 256;
+  uint64_t difference;
+  if (sp >= fault_addr) {
+    difference = sp - fault_addr;
+  } else {
+    difference = fault_addr - sp;
+  }
+  if (difference <= kMaxDifferenceBytes) {
+    // The faulting address is close to the current sp, check if the sp
+    // indicates a stack overflow.
+    // On arm, the sp does not get updated when the instruction faults.
+    // In this case, the sp will still be in a valid map, which is the
+    // last case below.
+    // On aarch64, the sp does get updated when the instruction faults.
+    // In this case, the sp will be in either an invalid map if triggered
+    // on the main thread, or in a guard map if in another thread, which
+    // will be the first case or second case from below.
+    unwindstack::MapInfo* map_info = maps->Find(sp);
+    if (map_info == nullptr) {
+      return "stack pointer is in a non-existent map; likely due to stack overflow.";
+    } else if ((map_info->flags() & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE)) {
+      return "stack pointer is not in a rw map; likely due to stack overflow.";
+    } else if ((sp - map_info->start()) <= kMaxDifferenceBytes) {
+      return "stack pointer is close to top of stack; likely stack overflow.";
+    }
+  }
+  return {};
+}
+
+void set_human_readable_cause(Cause* cause, uint64_t fault_addr) {
+  if (!cause->has_memory_error() || !cause->memory_error().has_heap()) {
+    return;
+  }
+
+  const MemoryError& memory_error = cause->memory_error();
+  const HeapObject& heap_object = memory_error.heap();
+
+  const char *tool_str;
+  switch (memory_error.tool()) {
+    case MemoryError_Tool_GWP_ASAN:
+      tool_str = "GWP-ASan";
+      break;
+    case MemoryError_Tool_SCUDO:
+      tool_str = "MTE";
+      break;
+    default:
+      tool_str = "Unknown";
+      break;
+  }
+
+  const char *error_type_str;
+  switch (memory_error.type()) {
+    case MemoryError_Type_USE_AFTER_FREE:
+      error_type_str = "Use After Free";
+      break;
+    case MemoryError_Type_DOUBLE_FREE:
+      error_type_str = "Double Free";
+      break;
+    case MemoryError_Type_INVALID_FREE:
+      error_type_str = "Invalid (Wild) Free";
+      break;
+    case MemoryError_Type_BUFFER_OVERFLOW:
+      error_type_str = "Buffer Overflow";
+      break;
+    case MemoryError_Type_BUFFER_UNDERFLOW:
+      error_type_str = "Buffer Underflow";
+      break;
+    default:
+      cause->set_human_readable(
+          StringPrintf("[%s]: Unknown error occurred at 0x%" PRIx64 ".", tool_str, fault_addr));
+      return;
+  }
+
+  uint64_t diff;
+  const char* location_str;
+
+  if (fault_addr < heap_object.address()) {
+    // Buffer Underflow, 6 bytes left of a 41-byte allocation at 0xdeadbeef.
+    location_str = "left of";
+    diff = heap_object.address() - fault_addr;
+  } else if (fault_addr - heap_object.address() < heap_object.size()) {
+    // Use After Free, 40 bytes into a 41-byte allocation at 0xdeadbeef.
+    location_str = "into";
+    diff = fault_addr - heap_object.address();
+  } else {
+    // Buffer Overflow, 6 bytes right of a 41-byte allocation at 0xdeadbeef.
+    location_str = "right of";
+    diff = fault_addr - heap_object.address() - heap_object.size();
+  }
+
+  // Suffix of 'bytes', i.e. 4 bytes' vs. '1 byte'.
+  const char* byte_suffix = "s";
+  if (diff == 1) {
+    byte_suffix = "";
+  }
+
+  cause->set_human_readable(StringPrintf(
+      "[%s]: %s, %" PRIu64 " byte%s %s a %" PRIu64 "-byte allocation at 0x%" PRIx64, tool_str,
+      error_type_str, diff, byte_suffix, location_str, heap_object.size(), heap_object.address()));
+}
+
+static void dump_probable_cause(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+                                const ProcessInfo& process_info, const ThreadInfo& main_thread) {
+  ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info);
+  if (scudo_crash_data.CrashIsMine()) {
+    scudo_crash_data.AddCauseProtos(tombstone, unwinder);
+    return;
+  }
+
+  GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info,
+                                       main_thread);
+  if (gwp_asan_crash_data.CrashIsMine()) {
+    gwp_asan_crash_data.AddCauseProtos(tombstone, unwinder);
+    return;
+  }
+
+  const siginfo *si = main_thread.siginfo;
+  auto fault_addr = reinterpret_cast<uint64_t>(si->si_addr);
+  unwindstack::Maps* maps = unwinder->GetMaps();
+
+  std::optional<std::string> cause;
+  if (si->si_signo == SIGSEGV && si->si_code == SEGV_MAPERR) {
+    if (fault_addr < 4096) {
+      cause = "null pointer dereference";
+    } else if (fault_addr == 0xffff0ffc) {
+      cause = "call to kuser_helper_version";
+    } else if (fault_addr == 0xffff0fe0) {
+      cause = "call to kuser_get_tls";
+    } else if (fault_addr == 0xffff0fc0) {
+      cause = "call to kuser_cmpxchg";
+    } else if (fault_addr == 0xffff0fa0) {
+      cause = "call to kuser_memory_barrier";
+    } else if (fault_addr == 0xffff0f60) {
+      cause = "call to kuser_cmpxchg64";
+    } else {
+      cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
+    }
+  } else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) {
+    unwindstack::MapInfo* map_info = maps->Find(fault_addr);
+    if (map_info != nullptr && map_info->flags() == PROT_EXEC) {
+      cause = "execute-only (no-read) memory access error; likely due to data in .text.";
+    } else {
+      cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps);
+    }
+  } else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) {
+    cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING,
+                         si->si_syscall);
+  }
+
+  if (cause) {
+    Cause *cause_proto = tombstone->add_causes();
+    cause_proto->set_human_readable(*cause);
+  }
+}
+
+static void dump_abort_message(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+                               const ProcessInfo& process_info) {
+  std::shared_ptr<unwindstack::Memory> process_memory = unwinder->GetProcessMemory();
+  uintptr_t address = process_info.abort_msg_address;
+  if (address == 0) {
+    return;
+  }
+
+  size_t length;
+  if (!process_memory->ReadFully(address, &length, sizeof(length))) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read abort message header: %s",
+                          strerror(errno));
+    return;
+  }
+
+  // The length field includes the length of the length field itself.
+  if (length < sizeof(size_t)) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                          "abort message header malformed: claimed length = %zu", length);
+    return;
+  }
+
+  length -= sizeof(size_t);
+
+  // The abort message should be null terminated already, but reserve a spot for NUL just in case.
+  std::string msg;
+  msg.resize(length);
+
+  if (!process_memory->ReadFully(address + sizeof(length), &msg[0], length)) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read abort message header: %s",
+                          strerror(errno));
+    return;
+  }
+
+  tombstone->set_abort_message(msg);
+}
+
+static void dump_open_fds(Tombstone* tombstone, const OpenFilesList* open_files) {
+  if (open_files) {
+    for (auto& [fd, entry] : *open_files) {
+      FD f;
+
+      f.set_fd(fd);
+
+      const std::optional<std::string>& path = entry.path;
+      if (path) {
+        f.set_path(*path);
+      }
+
+      const std::optional<uint64_t>& fdsan_owner = entry.fdsan_owner;
+      if (fdsan_owner) {
+        const char* type = android_fdsan_get_tag_type(*fdsan_owner);
+        uint64_t value = android_fdsan_get_tag_value(*fdsan_owner);
+        f.set_owner(type);
+        f.set_tag(value);
+      }
+
+      *tombstone->add_open_fds() = f;
+    }
+  }
+}
+
+void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame,
+                             unwindstack::Maps* maps) {
+  f->set_rel_pc(frame.rel_pc);
+  f->set_pc(frame.pc);
+  f->set_sp(frame.sp);
+
+  if (!frame.function_name.empty()) {
+    // TODO: Should this happen here, or on the display side?
+    char* demangled_name = __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr, nullptr);
+    if (demangled_name) {
+      f->set_function_name(demangled_name);
+      free(demangled_name);
+    } else {
+      f->set_function_name(frame.function_name);
+    }
+  }
+
+  f->set_function_offset(frame.function_offset);
+
+  if (frame.map_start == frame.map_end) {
+    // 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);
+  } else {
+    f->set_file_name(StringPrintf("<anonymous:%" PRIx64 ">", frame.map_start));
+  }
+
+  f->set_file_map_offset(frame.map_elf_start_offset);
+
+  unwindstack::MapInfo* map_info = maps->Find(frame.map_start);
+  if (map_info) {
+    f->set_build_id(map_info->GetPrintableBuildID());
+  }
+}
+
+static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+                        const ThreadInfo& thread_info, bool memory_dump = false) {
+  Thread thread;
+
+  thread.set_id(thread_info.tid);
+  thread.set_name(thread_info.thread_name);
+  thread.set_tagged_addr_ctrl(thread_info.tagged_addr_ctrl);
+
+  unwindstack::Maps* maps = unwinder->GetMaps();
+  unwindstack::Memory* memory = unwinder->GetProcessMemory().get();
+
+  thread_info.registers->IterateRegisters(
+      [&thread, memory_dump, maps, memory](const char* name, uint64_t value) {
+        Register r;
+        r.set_name(name);
+        r.set_u64(value);
+        *thread.add_registers() = r;
+
+        if (memory_dump) {
+          MemoryDump dump;
+
+          dump.set_register_name(name);
+          unwindstack::MapInfo* map_info = maps->Find(untag_address(value));
+          if (map_info) {
+            dump.set_mapping_name(map_info->name());
+          }
+
+          char buf[256];
+          uint8_t tags[256 / kTagGranuleSize];
+          size_t start_offset = 0;
+          ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory);
+          if (bytes == -1) {
+            return;
+          }
+          dump.set_begin_address(value);
+
+          if (start_offset + bytes > sizeof(buf)) {
+            async_safe_fatal("dump_memory overflowed? start offset = %zu, bytes read = %zd",
+                             start_offset, bytes);
+          }
+
+          dump.set_memory(buf, bytes);
+          dump.set_tags(tags, bytes / kTagGranuleSize);
+
+          *thread.add_memory_dump() = std::move(dump);
+        }
+      });
+
+  std::unique_ptr<unwindstack::Regs> regs_copy(thread_info.registers->Clone());
+  unwinder->SetRegs(regs_copy.get());
+  unwinder->Unwind();
+  if (unwinder->NumFrames() == 0) {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to unwind");
+    if (unwinder->LastErrorCode() != unwindstack::ERROR_NONE) {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error code: %s",
+                            unwinder->LastErrorCodeString());
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error address: 0x%" PRIx64,
+                            unwinder->LastErrorAddress());
+    }
+  } else {
+    if (unwinder->elf_from_memory_not_file()) {
+      auto backtrace_note = thread.mutable_backtrace_note();
+      *backtrace_note->Add() =
+          "Function names and BuildId information is missing for some frames due";
+      *backtrace_note->Add() =
+          "to unreadable libraries. For unwinds of apps, only shared libraries";
+      *backtrace_note->Add() = "found under the lib/ directory are readable.";
+      *backtrace_note->Add() = "On this device, run setenforce 0 to make the libraries readable.";
+    }
+    unwinder->SetDisplayBuildID(true);
+    for (const auto& frame : unwinder->frames()) {
+      BacktraceFrame* f = thread.add_current_backtrace();
+      fill_in_backtrace_frame(f, frame, maps);
+    }
+  }
+
+  auto& threads = *tombstone->mutable_threads();
+  threads[thread_info.tid] = thread;
+}
+
+static void dump_main_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+                             const ThreadInfo& thread_info) {
+  dump_thread(tombstone, unwinder, thread_info, true);
+}
+
+static void dump_mappings(Tombstone* tombstone, unwindstack::Unwinder* unwinder) {
+  unwindstack::Maps* maps = unwinder->GetMaps();
+  std::shared_ptr<unwindstack::Memory> process_memory = unwinder->GetProcessMemory();
+
+  for (const auto& map_info : *maps) {
+    auto* map = tombstone->add_memory_mappings();
+    map->set_begin_address(map_info->start());
+    map->set_end_address(map_info->end());
+    map->set_offset(map_info->offset());
+
+    if (map_info->flags() & PROT_READ) {
+      map->set_read(true);
+    }
+    if (map_info->flags() & PROT_WRITE) {
+      map->set_write(true);
+    }
+    if (map_info->flags() & PROT_EXEC) {
+      map->set_execute(true);
+    }
+
+    map->set_mapping_name(map_info->name());
+
+    std::string build_id = map_info->GetPrintableBuildID();
+    if (!build_id.empty()) {
+      map->set_build_id(build_id);
+    }
+
+    map->set_load_bias(map_info->GetLoadBias(process_memory));
+  }
+}
+
+static void dump_log_file(Tombstone* tombstone, const char* logger, pid_t pid) {
+  logger_list* logger_list =
+      android_logger_list_open(android_name_to_log_id(logger), ANDROID_LOG_NONBLOCK, 0, pid);
+
+  LogBuffer buffer;
+
+  while (true) {
+    log_msg log_entry;
+    ssize_t actual = android_logger_list_read(logger_list, &log_entry);
+
+    if (actual < 0) {
+      if (actual == -EINTR) {
+        // interrupted by signal, retry
+        continue;
+      }
+      if (actual == -EAGAIN) {
+        // non-blocking EOF; we're done
+        break;
+      } else {
+        break;
+      }
+    } else if (actual == 0) {
+      break;
+    }
+
+    char timestamp_secs[32];
+    time_t sec = static_cast<time_t>(log_entry.entry.sec);
+    tm tm;
+    localtime_r(&sec, &tm);
+    strftime(timestamp_secs, sizeof(timestamp_secs), "%m-%d %H:%M:%S", &tm);
+    std::string timestamp =
+        StringPrintf("%s.%03d", timestamp_secs, log_entry.entry.nsec / 1'000'000);
+
+    // Msg format is: <priority:1><tag:N>\0<message:N>\0
+    char* msg = log_entry.msg();
+    if (msg == nullptr) {
+      continue;
+    }
+
+    unsigned char prio = msg[0];
+    char* tag = msg + 1;
+    msg = tag + strlen(tag) + 1;
+
+    // consume any trailing newlines
+    char* nl = msg + strlen(msg) - 1;
+    while (nl >= msg && *nl == '\n') {
+      *nl-- = '\0';
+    }
+
+    // Look for line breaks ('\n') and display each text line
+    // on a separate line, prefixed with the header, like logcat does.
+    do {
+      nl = strchr(msg, '\n');
+      if (nl != nullptr) {
+        *nl = '\0';
+        ++nl;
+      }
+
+      LogMessage* log_msg = buffer.add_logs();
+      log_msg->set_timestamp(timestamp);
+      log_msg->set_pid(log_entry.entry.pid);
+      log_msg->set_tid(log_entry.entry.tid);
+      log_msg->set_priority(prio);
+      log_msg->set_tag(tag);
+      log_msg->set_message(msg);
+    } while ((msg = nl));
+  }
+  android_logger_list_free(logger_list);
+
+  if (!buffer.logs().empty()) {
+    buffer.set_name(logger);
+    *tombstone->add_log_buffers() = std::move(buffer);
+  }
+}
+
+static void dump_logcat(Tombstone* tombstone, pid_t pid) {
+  dump_log_file(tombstone, "system", pid);
+  dump_log_file(tombstone, "main", pid);
+}
+
+static std::optional<uint64_t> read_uptime_secs() {
+  std::string uptime;
+  if (!android::base::ReadFileToString("/proc/uptime", &uptime)) {
+    return {};
+  }
+  return strtoll(uptime.c_str(), nullptr, 10);
+}
+
+void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwinder,
+                             const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread,
+                             const ProcessInfo& process_info, const OpenFilesList* open_files) {
+  Tombstone result;
+
+  result.set_arch(get_arch());
+  result.set_build_fingerprint(android::base::GetProperty("ro.build.fingerprint", "unknown"));
+  result.set_revision(android::base::GetProperty("ro.revision", "unknown"));
+  result.set_timestamp(get_timestamp());
+
+  std::optional<uint64_t> system_uptime = read_uptime_secs();
+  if (system_uptime) {
+    android::procinfo::ProcessInfo proc_info;
+    std::string error;
+    if (android::procinfo::GetProcessInfo(target_thread, &proc_info, &error)) {
+      uint64_t starttime = proc_info.starttime / sysconf(_SC_CLK_TCK);
+      result.set_process_uptime(*system_uptime - starttime);
+    } else {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read process info: %s",
+                            error.c_str());
+    }
+  } else {
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read /proc/uptime: %s",
+                          strerror(errno));
+  }
+
+  const ThreadInfo& main_thread = threads.at(target_thread);
+  result.set_pid(main_thread.pid);
+  result.set_tid(main_thread.tid);
+  result.set_uid(main_thread.uid);
+  result.set_selinux_label(main_thread.selinux_label);
+
+  auto cmd_line = result.mutable_command_line();
+  for (const auto& arg : main_thread.command_line) {
+    *cmd_line->Add() = arg;
+  }
+
+  if (!main_thread.siginfo) {
+    async_safe_fatal("siginfo missing");
+  }
+
+  Signal sig;
+  sig.set_number(main_thread.signo);
+  sig.set_name(get_signame(main_thread.siginfo));
+  sig.set_code(main_thread.siginfo->si_code);
+  sig.set_code_name(get_sigcode(main_thread.siginfo));
+
+  if (signal_has_sender(main_thread.siginfo, main_thread.pid)) {
+    sig.set_has_sender(true);
+    sig.set_sender_uid(main_thread.siginfo->si_uid);
+    sig.set_sender_pid(main_thread.siginfo->si_pid);
+  }
+
+  if (process_info.has_fault_address) {
+    sig.set_has_fault_address(true);
+    sig.set_fault_address(process_info.maybe_tagged_fault_address);
+  }
+
+  *result.mutable_signal_info() = sig;
+
+  dump_abort_message(&result, unwinder, process_info);
+
+  dump_main_thread(&result, unwinder, main_thread);
+
+  for (const auto& [tid, thread_info] : threads) {
+    if (tid != target_thread) {
+      dump_thread(&result, unwinder, thread_info);
+    }
+  }
+
+  dump_probable_cause(&result, unwinder, process_info, main_thread);
+
+  dump_mappings(&result, unwinder);
+
+  // Only dump logs on debuggable devices.
+  if (android::base::GetBoolProperty("ro.debuggable", false)) {
+    dump_logcat(&result, main_thread.pid);
+  }
+
+  dump_open_fds(&result, open_files);
+
+  *tombstone = std::move(result);
+}
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
new file mode 100644
index 0000000..b780b22
--- /dev/null
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <libdebuggerd/tombstone.h>
+
+#include <inttypes.h>
+
+#include <functional>
+#include <set>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <async_safe/log.h>
+
+#include "tombstone.pb.h"
+
+using android::base::StringAppendF;
+using android::base::StringPrintf;
+
+#define CB(log, ...) callback(StringPrintf(__VA_ARGS__), log)
+#define CBL(...) CB(true, __VA_ARGS__)
+#define CBS(...) CB(false, __VA_ARGS__)
+using CallbackType = std::function<void(const std::string& line, bool should_log)>;
+
+static const char* abi_string(const Tombstone& tombstone) {
+  switch (tombstone.arch()) {
+    case Architecture::ARM32:
+      return "arm";
+    case Architecture::ARM64:
+      return "arm64";
+    case Architecture::X86:
+      return "x86";
+    case Architecture::X86_64:
+      return "x86_64";
+    default:
+      return "<unknown>";
+  }
+}
+
+static int pointer_width(const Tombstone& tombstone) {
+  switch (tombstone.arch()) {
+    case Architecture::ARM32:
+      return 4;
+    case Architecture::ARM64:
+      return 8;
+    case Architecture::X86:
+      return 4;
+    case Architecture::X86_64:
+      return 8;
+    default:
+      return 8;
+  }
+}
+
+static void print_thread_header(CallbackType callback, const Tombstone& tombstone,
+                                const Thread& thread, bool should_log) {
+  const char* process_name = "<unknown>";
+  if (!tombstone.command_line().empty()) {
+    process_name = tombstone.command_line()[0].c_str();
+    CB(should_log, "Cmdline: %s", android::base::Join(tombstone.command_line(), " ").c_str());
+  }
+  CB(should_log, "pid: %d, tid: %d, name: %s  >>> %s <<<", tombstone.pid(), thread.id(),
+     thread.name().c_str(), process_name);
+  CB(should_log, "uid: %d", tombstone.uid());
+  if (thread.tagged_addr_ctrl() != -1) {
+    CB(should_log, "tagged_addr_ctrl: %016" PRIx64, thread.tagged_addr_ctrl());
+  }
+}
+
+static void print_register_row(CallbackType callback, int word_size,
+                               std::vector<std::pair<std::string, uint64_t>> row, bool should_log) {
+  std::string output = "  ";
+  for (const auto& [name, value] : row) {
+    output += android::base::StringPrintf("  %-3s %0*" PRIx64, name.c_str(), 2 * word_size,
+                                          static_cast<uint64_t>(value));
+  }
+  callback(output, should_log);
+}
+
+static void print_thread_registers(CallbackType callback, const Tombstone& tombstone,
+                                   const Thread& thread, bool should_log) {
+  static constexpr size_t column_count = 4;
+  std::vector<std::pair<std::string, uint64_t>> current_row;
+  std::vector<std::pair<std::string, uint64_t>> special_row;
+  std::unordered_set<std::string> special_registers;
+
+  int word_size = pointer_width(tombstone);
+
+  switch (tombstone.arch()) {
+    case Architecture::ARM32:
+      special_registers = {"ip", "lr", "sp", "pc", "pst"};
+      break;
+
+    case Architecture::ARM64:
+      special_registers = {"ip", "lr", "sp", "pc", "pst"};
+      break;
+
+    case Architecture::X86:
+      special_registers = {"ebp", "esp", "eip"};
+      break;
+
+    case Architecture::X86_64:
+      special_registers = {"rbp", "rsp", "rip"};
+      break;
+
+    default:
+      async_safe_fatal("unknown architecture");
+  }
+
+  for (const auto& reg : thread.registers()) {
+    auto row = &current_row;
+    if (special_registers.count(reg.name()) == 1) {
+      row = &special_row;
+    }
+
+    row->emplace_back(reg.name(), reg.u64());
+    if (current_row.size() == column_count) {
+      print_register_row(callback, word_size, current_row, should_log);
+      current_row.clear();
+    }
+  }
+
+  if (!current_row.empty()) {
+    print_register_row(callback, word_size, current_row, should_log);
+  }
+
+  print_register_row(callback, word_size, special_row, should_log);
+}
+
+static void print_backtrace(CallbackType callback, const Tombstone& tombstone,
+                            const google::protobuf::RepeatedPtrField<BacktraceFrame>& backtrace,
+                            bool should_log) {
+  int index = 0;
+  for (const auto& frame : backtrace) {
+    std::string function;
+
+    if (!frame.function_name().empty()) {
+      function =
+          StringPrintf(" (%s+%" PRId64 ")", frame.function_name().c_str(), frame.function_offset());
+    }
+
+    std::string build_id;
+    if (!frame.build_id().empty()) {
+      build_id = StringPrintf(" (BuildId: %s)", frame.build_id().c_str());
+    }
+
+    CB(should_log, "      #%02d pc %0*" PRIx64 "  %s%s%s", index++, pointer_width(tombstone) * 2,
+       frame.rel_pc(), frame.file_name().c_str(), function.c_str(), build_id.c_str());
+  }
+}
+
+static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone,
+                                   const Thread& thread, bool should_log) {
+  CBS("");
+  CB(should_log, "backtrace:");
+  if (!thread.backtrace_note().empty()) {
+    CB(should_log, "  NOTE: %s",
+       android::base::Join(thread.backtrace_note(), "\n  NOTE: ").c_str());
+  }
+  print_backtrace(callback, tombstone, thread.current_backtrace(), should_log);
+}
+
+static void print_thread_memory_dump(CallbackType callback, const Tombstone& tombstone,
+                                     const Thread& thread) {
+  static constexpr size_t bytes_per_line = 16;
+  static_assert(bytes_per_line == kTagGranuleSize);
+  int word_size = pointer_width(tombstone);
+  for (const auto& mem : thread.memory_dump()) {
+    CBS("");
+    if (mem.mapping_name().empty()) {
+      CBS("memory near %s:", mem.register_name().c_str());
+    } else {
+      CBS("memory near %s (%s):", mem.register_name().c_str(), mem.mapping_name().c_str());
+    }
+    uint64_t addr = mem.begin_address();
+    for (size_t offset = 0; offset < mem.memory().size(); offset += bytes_per_line) {
+      uint64_t tagged_addr = addr;
+      if (mem.tags().size() > offset / kTagGranuleSize) {
+        tagged_addr |= static_cast<uint64_t>(mem.tags()[offset / kTagGranuleSize]) << 56;
+      }
+      std::string line = StringPrintf("    %0*" PRIx64, word_size * 2, tagged_addr + offset);
+
+      size_t bytes = std::min(bytes_per_line, mem.memory().size() - offset);
+      for (size_t i = 0; i < bytes; i += word_size) {
+        uint64_t word = 0;
+
+        // Assumes little-endian, but what doesn't?
+        memcpy(&word, mem.memory().data() + offset + i, word_size);
+
+        StringAppendF(&line, " %0*" PRIx64, word_size * 2, word);
+      }
+
+      char ascii[bytes_per_line + 1];
+
+      memset(ascii, '.', sizeof(ascii));
+      ascii[bytes_per_line] = '\0';
+
+      for (size_t i = 0; i < bytes; ++i) {
+        uint8_t byte = mem.memory()[offset + i];
+        if (byte >= 0x20 && byte < 0x7f) {
+          ascii[i] = byte;
+        }
+      }
+
+      CBS("%s  %s", line.c_str(), ascii);
+    }
+  }
+}
+
+static void print_thread(CallbackType callback, const Tombstone& tombstone, const Thread& thread) {
+  print_thread_header(callback, tombstone, thread, false);
+  print_thread_registers(callback, tombstone, thread, false);
+  print_thread_backtrace(callback, tombstone, thread, false);
+  print_thread_memory_dump(callback, tombstone, thread);
+}
+
+static void print_main_thread(CallbackType callback, const Tombstone& tombstone,
+                              const Thread& thread) {
+  print_thread_header(callback, tombstone, thread, true);
+
+  const Signal& signal_info = tombstone.signal_info();
+  std::string sender_desc;
+
+  if (signal_info.has_sender()) {
+    sender_desc =
+        StringPrintf(" from pid %d, uid %d", signal_info.sender_pid(), signal_info.sender_uid());
+  }
+
+  if (!tombstone.has_signal_info()) {
+    CBL("signal information missing");
+  } else {
+    std::string fault_addr_desc;
+    if (signal_info.has_fault_address()) {
+      fault_addr_desc = StringPrintf("0x%" PRIx64, signal_info.fault_address());
+    } else {
+      fault_addr_desc = "--------";
+    }
+
+    CBL("signal %d (%s), code %d (%s%s), fault addr %s", signal_info.number(),
+        signal_info.name().c_str(), signal_info.code(), signal_info.code_name().c_str(),
+        sender_desc.c_str(), fault_addr_desc.c_str());
+  }
+
+  if (tombstone.causes_size() == 1) {
+    CBL("Cause: %s", tombstone.causes(0).human_readable().c_str());
+  }
+
+  if (!tombstone.abort_message().empty()) {
+    CBL("Abort message: '%s'", tombstone.abort_message().c_str());
+  }
+
+  print_thread_registers(callback, tombstone, thread, true);
+  print_thread_backtrace(callback, tombstone, thread, true);
+
+  if (tombstone.causes_size() > 1) {
+    CBS("");
+    CBS("Note: multiple potential causes for this crash were detected, listing them in decreasing "
+        "order of probability.");
+  }
+
+  for (const Cause& cause : tombstone.causes()) {
+    if (tombstone.causes_size() > 1) {
+      CBS("");
+      CBS("Cause: %s", cause.human_readable().c_str());
+    }
+
+    if (cause.has_memory_error() && cause.memory_error().has_heap()) {
+      const HeapObject& heap_object = cause.memory_error().heap();
+
+      if (heap_object.deallocation_backtrace_size() != 0) {
+        CBS("");
+        CBS("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid());
+        print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), false);
+      }
+
+      if (heap_object.allocation_backtrace_size() != 0) {
+        CBS("");
+        CBS("allocated by thread %" PRIu64 ":", heap_object.allocation_tid());
+        print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), false);
+      }
+    }
+  }
+
+  print_thread_memory_dump(callback, tombstone, thread);
+
+  CBS("");
+  CBS("memory map (%d %s):", tombstone.memory_mappings().size(),
+      tombstone.memory_mappings().size() == 1 ? "entry" : "entries");
+  int word_size = pointer_width(tombstone);
+  const auto format_pointer = [word_size](uint64_t ptr) -> std::string {
+    if (word_size == 8) {
+      uint64_t top = ptr >> 32;
+      uint64_t bottom = ptr & 0xFFFFFFFF;
+      return StringPrintf("%08" PRIx64 "'%08" PRIx64, top, bottom);
+    }
+
+    return StringPrintf("%0*" PRIx64, word_size * 2, ptr);
+  };
+
+  for (const auto& map : tombstone.memory_mappings()) {
+    std::string line = "    ";
+    StringAppendF(&line, "%s-%s", format_pointer(map.begin_address()).c_str(),
+                  format_pointer(map.end_address() - 1).c_str());
+    StringAppendF(&line, " %s%s%s", map.read() ? "r" : "-", map.write() ? "w" : "-",
+                  map.execute() ? "x" : "-");
+    StringAppendF(&line, "  %8" PRIx64 "  %8" PRIx64, map.offset(),
+                  map.end_address() - map.begin_address());
+
+    if (!map.mapping_name().empty()) {
+      StringAppendF(&line, "  %s", map.mapping_name().c_str());
+
+      if (!map.build_id().empty()) {
+        StringAppendF(&line, " (BuildId: %s)", map.build_id().c_str());
+      }
+
+      if (map.load_bias() != 0) {
+        StringAppendF(&line, " (load bias 0x%" PRIx64 ")", map.load_bias());
+      }
+    }
+
+    CBS("%s", line.c_str());
+  }
+}
+
+void print_logs(CallbackType callback, const Tombstone& tombstone, int tail) {
+  for (const auto& buffer : tombstone.log_buffers()) {
+    if (tail) {
+      CBS("--------- tail end of log %s", buffer.name().c_str());
+    } else {
+      CBS("--------- log %s", buffer.name().c_str());
+    }
+
+    int begin = 0;
+    if (tail != 0) {
+      begin = std::max(0, buffer.logs().size() - tail);
+    }
+
+    for (int i = begin; i < buffer.logs().size(); ++i) {
+      const LogMessage& msg = buffer.logs(i);
+
+      static const char* kPrioChars = "!.VDIWEFS";
+      char priority = (msg.priority() < strlen(kPrioChars) ? kPrioChars[msg.priority()] : '?');
+      CBS("%s %5u %5u %c %-8s: %s", msg.timestamp().c_str(), msg.pid(), msg.tid(), priority,
+          msg.tag().c_str(), msg.message().c_str());
+    }
+  }
+}
+
+bool tombstone_proto_to_text(const Tombstone& tombstone, CallbackType callback) {
+  CBL("*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***");
+  CBL("Build fingerprint: '%s'", tombstone.build_fingerprint().c_str());
+  CBL("Revision: '%s'", tombstone.revision().c_str());
+  CBL("ABI: '%s'", abi_string(tombstone));
+  CBL("Timestamp: %s", tombstone.timestamp().c_str());
+  CBL("Process uptime: %ds", tombstone.process_uptime());
+
+  // Process header
+  const auto& threads = tombstone.threads();
+  auto main_thread_it = threads.find(tombstone.tid());
+  if (main_thread_it == threads.end()) {
+    CBL("failed to find entry for main thread in tombstone");
+    return false;
+  }
+
+  const auto& main_thread = main_thread_it->second;
+
+  print_main_thread(callback, tombstone, main_thread);
+
+  print_logs(callback, tombstone, 50);
+
+  // protobuf's map is unordered, so sort the keys first.
+  std::set<int> thread_ids;
+  for (const auto& [tid, _] : threads) {
+    if (tid != tombstone.tid()) {
+      thread_ids.insert(tid);
+    }
+  }
+
+  for (const auto& tid : thread_ids) {
+    CBS("--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---");
+    print_thread(callback, tombstone, threads.find(tid)->second);
+  }
+
+  if (tombstone.open_fds().size() > 0) {
+    CBS("");
+    CBS("open files:");
+    for (const auto& fd : tombstone.open_fds()) {
+      std::optional<std::string> owner;
+      if (!fd.owner().empty()) {
+        owner = StringPrintf("owned by %s 0x%" PRIx64, fd.owner().c_str(), fd.tag());
+      }
+
+      CBS("    fd %d: %s (%s)", fd.fd(), fd.path().c_str(), owner ? owner->c_str() : "unowned");
+    }
+  }
+
+  print_logs(callback, tombstone, 0);
+
+  return true;
+}
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index d7067ca..2c645b5 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -30,12 +30,11 @@
 
 #include <string>
 
-#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
-#include <bionic/mte_kernel.h>
+#include <async_safe/log.h>
 #include <bionic/reserved_signals.h>
 #include <debuggerd/handler.h>
 #include <log/log.h>
@@ -126,57 +125,57 @@
 
 #define MEMORY_BYTES_TO_DUMP 256
 #define MEMORY_BYTES_PER_LINE 16
+static_assert(MEMORY_BYTES_PER_LINE == kTagGranuleSize);
 
-void dump_memory(log_t* log, unwindstack::Memory* memory, uint64_t addr, const std::string& label) {
+ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr,
+                    unwindstack::Memory* memory) {
   // Align the address to the number of bytes per line to avoid confusing memory tag output if
   // memory is tagged and we start from a misaligned address. Start 32 bytes before the address.
-  addr &= ~(MEMORY_BYTES_PER_LINE - 1);
-  if (addr >= 4128) {
-    addr -= 32;
+  *addr &= ~(MEMORY_BYTES_PER_LINE - 1);
+  if (*addr >= 4128) {
+    *addr -= 32;
   }
 
   // We don't want the address tag to appear in the addresses in the memory dump.
-  addr = untag_address(addr);
+  *addr = untag_address(*addr);
 
   // Don't bother if the address would overflow, taking tag bits into account. Note that
   // untag_address truncates to 32 bits on 32-bit platforms as a side effect of returning a
   // uintptr_t, so this also checks for 32-bit overflow.
-  if (untag_address(addr + MEMORY_BYTES_TO_DUMP - 1) < addr) {
-    return;
+  if (untag_address(*addr + MEMORY_BYTES_TO_DUMP - 1) < *addr) {
+    return -1;
   }
 
-  // Dump 256 bytes
-  uintptr_t data[MEMORY_BYTES_TO_DUMP/sizeof(uintptr_t)];
-  memset(data, 0, MEMORY_BYTES_TO_DUMP);
-  size_t bytes = memory->Read(addr, reinterpret_cast<uint8_t*>(data), sizeof(data));
+  memset(out, 0, len);
+
+  size_t bytes = memory->Read(*addr, reinterpret_cast<uint8_t*>(out), len);
   if (bytes % sizeof(uintptr_t) != 0) {
     // This should never happen, but just in case.
     ALOGE("Bytes read %zu, is not a multiple of %zu", bytes, sizeof(uintptr_t));
     bytes &= ~(sizeof(uintptr_t) - 1);
   }
 
-  uint64_t start = 0;
   bool skip_2nd_read = false;
   if (bytes == 0) {
     // In this case, we might want to try another read at the beginning of
     // the next page only if it's within the amount of memory we would have
     // read.
     size_t page_size = sysconf(_SC_PAGE_SIZE);
-    start = ((addr + (page_size - 1)) & ~(page_size - 1)) - addr;
-    if (start == 0 || start >= MEMORY_BYTES_TO_DUMP) {
+    uint64_t next_page = (*addr + (page_size - 1)) & ~(page_size - 1);
+    if (next_page == *addr || next_page >= *addr + len) {
       skip_2nd_read = true;
     }
+    *addr = next_page;
   }
 
-  if (bytes < MEMORY_BYTES_TO_DUMP && !skip_2nd_read) {
+  if (bytes < len && !skip_2nd_read) {
     // Try to do one more read. This could happen if a read crosses a map,
     // but the maps do not have any break between them. Or it could happen
     // if reading from an unreadable map, but the read would cross back
     // into a readable map. Only requires one extra read because a map has
     // to contain at least one page, and the total number of bytes to dump
     // is smaller than a page.
-    size_t bytes2 = memory->Read(addr + start + bytes, reinterpret_cast<uint8_t*>(data) + bytes,
-                                 sizeof(data) - bytes - start);
+    size_t bytes2 = memory->Read(*addr + bytes, static_cast<uint8_t*>(out) + bytes, len - bytes);
     bytes += bytes2;
     if (bytes2 > 0 && bytes % sizeof(uintptr_t) != 0) {
       // This should never happen, but we'll try and continue any way.
@@ -186,9 +185,30 @@
   }
 
   // If we were unable to read anything, it probably means that the register doesn't contain a
-  // valid pointer. In that case, skip the output for this register entirely rather than emitting 16
-  // lines of dashes.
+  // valid pointer.
   if (bytes == 0) {
+    return -1;
+  }
+
+  for (uint64_t tag_granule = 0; tag_granule < bytes / kTagGranuleSize; ++tag_granule) {
+    long tag = memory->ReadTag(*addr + kTagGranuleSize * tag_granule);
+    if (tag_granule < tags_len) {
+      tags[tag_granule] = tag >= 0 ? tag : 0;
+    } else {
+      ALOGE("Insufficient space for tags");
+    }
+  }
+
+  return bytes;
+}
+
+void dump_memory(log_t* log, unwindstack::Memory* memory, uint64_t addr, const std::string& label) {
+  // Dump 256 bytes
+  uintptr_t data[MEMORY_BYTES_TO_DUMP / sizeof(uintptr_t)];
+  uint8_t tags[MEMORY_BYTES_TO_DUMP / kTagGranuleSize];
+
+  ssize_t bytes = dump_memory(data, sizeof(data), tags, sizeof(tags), &addr, memory);
+  if (bytes == -1) {
     return;
   }
 
@@ -201,38 +221,27 @@
   // On 32-bit machines, there are still 16 bytes per line but addresses and
   // words are of course presented differently.
   uintptr_t* data_ptr = data;
-  size_t current = 0;
-  size_t total_bytes = start + bytes;
-  for (size_t line = 0; line < MEMORY_BYTES_TO_DUMP / MEMORY_BYTES_PER_LINE; line++) {
-    uint64_t tagged_addr = addr;
-    long tag = memory->ReadTag(addr);
-    if (tag >= 0) {
-      tagged_addr |= static_cast<uint64_t>(tag) << 56;
-    }
+  uint8_t* tags_ptr = tags;
+  for (size_t line = 0; line < static_cast<size_t>(bytes) / MEMORY_BYTES_PER_LINE; line++) {
+    uint64_t tagged_addr = addr | static_cast<uint64_t>(*tags_ptr++) << 56;
     std::string logline;
     android::base::StringAppendF(&logline, "    %" PRIPTR, tagged_addr);
 
     addr += MEMORY_BYTES_PER_LINE;
     std::string ascii;
     for (size_t i = 0; i < MEMORY_BYTES_PER_LINE / sizeof(uintptr_t); i++) {
-      if (current >= start && current + sizeof(uintptr_t) <= total_bytes) {
-        android::base::StringAppendF(&logline, " %" PRIPTR, static_cast<uint64_t>(*data_ptr));
+      android::base::StringAppendF(&logline, " %" PRIPTR, static_cast<uint64_t>(*data_ptr));
 
-        // Fill out the ascii string from the data.
-        uint8_t* ptr = reinterpret_cast<uint8_t*>(data_ptr);
-        for (size_t val = 0; val < sizeof(uintptr_t); val++, ptr++) {
-          if (*ptr >= 0x20 && *ptr < 0x7f) {
-            ascii += *ptr;
-          } else {
-            ascii += '.';
-          }
+      // Fill out the ascii string from the data.
+      uint8_t* ptr = reinterpret_cast<uint8_t*>(data_ptr);
+      for (size_t val = 0; val < sizeof(uintptr_t); val++, ptr++) {
+        if (*ptr >= 0x20 && *ptr < 0x7f) {
+          ascii += *ptr;
+        } else {
+          ascii += '.';
         }
-        data_ptr++;
-      } else {
-        logline += ' ' + std::string(sizeof(uintptr_t) * 2, '-');
-        ascii += std::string(sizeof(uintptr_t), '.');
       }
-      current += sizeof(uintptr_t);
+      data_ptr++;
     }
     _LOG(log, logtype::MEMORY, "%s  %s\n", logline.c_str(), ascii.c_str());
   }
@@ -248,11 +257,11 @@
   memset(&capdata, 0, sizeof(capdata));
 
   if (capset(&capheader, &capdata[0]) == -1) {
-    PLOG(FATAL) << "failed to drop capabilities";
+    async_safe_fatal("failed to drop capabilities: %s", strerror(errno));
   }
 
   if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
-    PLOG(FATAL) << "failed to set PR_SET_NO_NEW_PRIVS";
+    async_safe_fatal("failed to set PR_SET_NO_NEW_PRIVS: %s", strerror(errno));
   }
 }
 
@@ -370,20 +379,20 @@
           return "SEGV_ADIDERR";
         case SEGV_ADIPERR:
           return "SEGV_ADIPERR";
-#if defined(ANDROID_EXPERIMENTAL_MTE)
         case SEGV_MTEAERR:
           return "SEGV_MTEAERR";
         case SEGV_MTESERR:
           return "SEGV_MTESERR";
-#endif
       }
-      static_assert(NSIGSEGV == SEGV_ADIPERR, "missing SEGV_* si_code");
+      static_assert(NSIGSEGV == SEGV_MTESERR, "missing SEGV_* si_code");
       break;
     case SIGSYS:
       switch (si->si_code) {
         case SYS_SECCOMP: return "SYS_SECCOMP";
+        case SYS_USER_DISPATCH:
+          return "SYS_USER_DISPATCH";
       }
-      static_assert(NSIGSYS == SYS_SECCOMP, "missing SYS_* si_code");
+      static_assert(NSIGSYS == SYS_USER_DISPATCH, "missing SYS_* si_code");
       break;
     case SIGTRAP:
       switch (si->si_code) {
diff --git a/debuggerd/pbtombstone.cpp b/debuggerd/pbtombstone.cpp
new file mode 100644
index 0000000..7527e31
--- /dev/null
+++ b/debuggerd/pbtombstone.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <android-base/unique_fd.h>
+#include <libdebuggerd/tombstone.h>
+
+#include "tombstone.pb.h"
+
+using android::base::unique_fd;
+
+[[noreturn]] void usage(bool error) {
+  fprintf(stderr, "usage: pbtombstone TOMBSTONE.PB\n");
+  fprintf(stderr, "Convert a protobuf tombstone to text.\n");
+  exit(error);
+}
+
+int main(int argc, const char* argv[]) {
+  if (argc != 2) {
+    usage(true);
+  }
+
+  if (strcmp("-h", argv[1]) == 0 || strcmp("--help", argv[1]) == 0) {
+    usage(false);
+  }
+
+  unique_fd fd(open(argv[1], O_RDONLY | O_CLOEXEC));
+  if (fd == -1) {
+    err(1, "failed to open tombstone '%s'", argv[1]);
+  }
+
+  Tombstone tombstone;
+  if (!tombstone.ParseFromFileDescriptor(fd.get())) {
+    err(1, "failed to parse tombstone");
+  }
+
+  bool result = tombstone_proto_to_text(
+      tombstone, [](const std::string& line, bool) { printf("%s\n", line.c_str()); });
+
+  if (!result) {
+    errx(1, "tombstone was malformed");
+  }
+
+  return 0;
+}
diff --git a/debuggerd/proto/Android.bp b/debuggerd/proto/Android.bp
new file mode 100644
index 0000000..73cf573
--- /dev/null
+++ b/debuggerd/proto/Android.bp
@@ -0,0 +1,40 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+    name: "libtombstone_proto-src",
+    srcs: ["tombstone.proto"],
+}
+
+cc_library_static {
+    name: "libtombstone_proto",
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Wthread-safety",
+        "-Werror",
+    ],
+
+    compile_multilib: "both",
+
+    proto: {
+        export_proto_headers: true,
+        type: "lite",
+    },
+
+    srcs: [":libtombstone_proto-src"],
+
+    // b/155341058: Soong doesn't automatically add libprotobuf if there aren't any explicitly
+    // listed protos in srcs.
+    static_libs: ["libprotobuf-cpp-lite"],
+
+    stl: "libc++_static",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.runtime",
+    ],
+
+    recovery_available: true,
+    vendor_ramdisk_available: true,
+}
diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto
new file mode 100644
index 0000000..22fc30e
--- /dev/null
+++ b/debuggerd/proto/tombstone.proto
@@ -0,0 +1,196 @@
+syntax = "proto3";
+
+option java_package = "com.android.server.os";
+option java_outer_classname = "TombstoneProtos";
+
+// NOTE TO OEMS:
+// If you add custom fields to this proto, do not use numbers in the reserved range.
+
+message Tombstone {
+  Architecture arch = 1;
+  string build_fingerprint = 2;
+  string revision = 3;
+  string timestamp = 4;
+
+  uint32 pid = 5;
+  uint32 tid = 6;
+  uint32 uid = 7;
+  string selinux_label = 8;
+
+  repeated string command_line = 9;
+
+  // Process uptime in seconds.
+  uint32 process_uptime = 20;
+
+  Signal signal_info = 10;
+  string abort_message = 14;
+  repeated Cause causes = 15;
+
+  map<uint32, Thread> threads = 16;
+  repeated MemoryMapping memory_mappings = 17;
+  repeated LogBuffer log_buffers = 18;
+  repeated FD open_fds = 19;
+
+  reserved 21 to 999;
+}
+
+enum Architecture {
+  ARM32 = 0;
+  ARM64 = 1;
+  X86 = 2;
+  X86_64 = 3;
+
+  reserved 4 to 999;
+}
+
+message Signal {
+  int32 number = 1;
+  string name = 2;
+
+  int32 code = 3;
+  string code_name = 4;
+
+  bool has_sender = 5;
+  int32 sender_uid = 6;
+  int32 sender_pid = 7;
+
+  bool has_fault_address = 8;
+  uint64 fault_address = 9;
+
+  reserved 10 to 999;
+}
+
+message HeapObject {
+  uint64 address = 1;
+  uint64 size = 2;
+
+  uint64 allocation_tid = 3;
+  repeated BacktraceFrame allocation_backtrace = 4;
+
+  uint64 deallocation_tid = 5;
+  repeated BacktraceFrame deallocation_backtrace = 6;
+}
+
+message MemoryError {
+  enum Tool {
+    GWP_ASAN = 0;
+    SCUDO = 1;
+
+    reserved 2 to 999;
+  }
+  Tool tool = 1;
+
+  enum Type {
+    UNKNOWN = 0;
+    USE_AFTER_FREE = 1;
+    DOUBLE_FREE = 2;
+    INVALID_FREE = 3;
+    BUFFER_OVERFLOW = 4;
+    BUFFER_UNDERFLOW = 5;
+
+    reserved 6 to 999;
+  }
+  Type type = 2;
+
+  oneof location {
+    HeapObject heap = 3;
+  }
+
+  reserved 4 to 999;
+}
+
+message Cause {
+  string human_readable = 1;
+  oneof details {
+    MemoryError memory_error = 2;
+  }
+
+  reserved 3 to 999;
+}
+
+message Register {
+  string name = 1;
+  uint64 u64 = 2;
+
+  reserved 3 to 999;
+}
+
+message Thread {
+  int32 id = 1;
+  string name = 2;
+  repeated Register registers = 3;
+  repeated string backtrace_note = 7;
+  repeated BacktraceFrame current_backtrace = 4;
+  repeated MemoryDump memory_dump = 5;
+  int64 tagged_addr_ctrl = 6;
+
+  reserved 8 to 999;
+}
+
+message BacktraceFrame {
+  uint64 rel_pc = 1;
+  uint64 pc = 2;
+  uint64 sp = 3;
+
+  string function_name = 4;
+  uint64 function_offset = 5;
+
+  string file_name = 6;
+  uint64 file_map_offset = 7;
+  string build_id = 8;
+
+  reserved 9 to 999;
+}
+
+message MemoryDump {
+  string register_name = 1;
+  string mapping_name = 2;
+  uint64 begin_address = 3;
+  bytes memory = 4;
+  bytes tags = 5;
+
+  reserved 6 to 999;
+}
+
+message MemoryMapping {
+  uint64 begin_address = 1;
+  uint64 end_address = 2;
+  uint64 offset = 3;
+
+  bool read = 4;
+  bool write = 5;
+  bool execute = 6;
+
+  string mapping_name = 7;
+  string build_id = 8;
+  uint64 load_bias = 9;
+
+  reserved 10 to 999;
+}
+
+message FD {
+  int32 fd = 1;
+  string path = 2;
+  string owner = 3;
+  uint64 tag = 4;
+
+  reserved 5 to 999;
+}
+
+message LogBuffer {
+  string name = 1;
+  repeated LogMessage logs = 2;
+
+  reserved 3 to 999;
+}
+
+message LogMessage {
+  string timestamp = 1;
+  uint32 pid = 2;
+  uint32 tid = 3;
+  uint32 priority = 4;
+  string tag = 5;
+  string message = 6;
+
+  reserved 7 to 999;
+}
diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h
index 53a76ea..f33b2f0 100644
--- a/debuggerd/protocol.h
+++ b/debuggerd/protocol.h
@@ -97,6 +97,7 @@
   uintptr_t gwp_asan_metadata;
   uintptr_t scudo_stack_depot;
   uintptr_t scudo_region_info;
+  uintptr_t scudo_ring_buffer;
 };
 
 struct __attribute__((__packed__)) CrashInfo {
diff --git a/debuggerd/seccomp_policy/crash_dump.arm.policy b/debuggerd/seccomp_policy/crash_dump.arm.policy
index 254330d..4eac0e9 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm.policy
@@ -19,6 +19,7 @@
 getdents64: 1
 faccessat: 1
 recvmsg: 1
+recvfrom: 1
 process_vm_readv: 1
 tgkill: 1
 rt_sigprocmask: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.arm64.policy b/debuggerd/seccomp_policy/crash_dump.arm64.policy
index 9b3ef09..21887ab 100644
--- a/debuggerd/seccomp_policy/crash_dump.arm64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.arm64.policy
@@ -18,12 +18,13 @@
 getdents64: 1
 faccessat: 1
 recvmsg: 1
+recvfrom: 1
 process_vm_readv: 1
 tgkill: 1
 rt_sigprocmask: 1
 rt_sigaction: 1
 rt_tgsigqueueinfo: 1
-prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == 0x53564d41 || arg0 == PR_PAC_RESET_KEYS
 madvise: 1
 mprotect: arg2 in 0x1|0x2
 munmap: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.policy.def b/debuggerd/seccomp_policy/crash_dump.policy.def
index 2ef31b0..90843fc 100644
--- a/debuggerd/seccomp_policy/crash_dump.policy.def
+++ b/debuggerd/seccomp_policy/crash_dump.policy.def
@@ -24,6 +24,7 @@
 getdents64: 1
 faccessat: 1
 recvmsg: 1
+recvfrom: 1
 
 process_vm_readv: 1
 
@@ -33,7 +34,12 @@
 rt_tgsigqueueinfo: 1
 
 #define PR_SET_VMA 0x53564d41
+#if defined(__aarch64__)
+// PR_PAC_RESET_KEYS happens on aarch64 in pthread_create path.
+prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA || arg0 == PR_PAC_RESET_KEYS
+#else
 prctl: arg0 == PR_GET_NO_NEW_PRIVS || arg0 == PR_SET_VMA
+#endif
 
 #if 0
 libminijail on vendor partitions older than P does not have constants from <sys/mman.h>.
diff --git a/debuggerd/seccomp_policy/crash_dump.x86.policy b/debuggerd/seccomp_policy/crash_dump.x86.policy
index 254330d..4eac0e9 100644
--- a/debuggerd/seccomp_policy/crash_dump.x86.policy
+++ b/debuggerd/seccomp_policy/crash_dump.x86.policy
@@ -19,6 +19,7 @@
 getdents64: 1
 faccessat: 1
 recvmsg: 1
+recvfrom: 1
 process_vm_readv: 1
 tgkill: 1
 rt_sigprocmask: 1
diff --git a/debuggerd/seccomp_policy/crash_dump.x86_64.policy b/debuggerd/seccomp_policy/crash_dump.x86_64.policy
index 9b3ef09..1585cc6 100644
--- a/debuggerd/seccomp_policy/crash_dump.x86_64.policy
+++ b/debuggerd/seccomp_policy/crash_dump.x86_64.policy
@@ -18,6 +18,7 @@
 getdents64: 1
 faccessat: 1
 recvmsg: 1
+recvfrom: 1
 process_vm_readv: 1
 tgkill: 1
 rt_sigprocmask: 1
diff --git a/debuggerd/tombstoned/include/tombstoned/tombstoned.h b/debuggerd/tombstoned/include/tombstoned/tombstoned.h
index 6403dbe..bff216c 100644
--- a/debuggerd/tombstoned/include/tombstoned/tombstoned.h
+++ b/debuggerd/tombstoned/include/tombstoned/tombstoned.h
@@ -23,6 +23,10 @@
 #include "dump_type.h"
 
 bool tombstoned_connect(pid_t pid, android::base::unique_fd* tombstoned_socket,
-                        android::base::unique_fd* output_fd, DebuggerdDumpType dump_type);
+                        android::base::unique_fd* text_output_fd,
+                        android::base::unique_fd* proto_output_fd, DebuggerdDumpType dump_type);
+
+bool tombstoned_connect(pid_t pid, android::base::unique_fd* tombstoned_socket,
+                        android::base::unique_fd* text_output_fd, DebuggerdDumpType dump_type);
 
 bool tombstoned_notify_completion(int tombstoned_socket);
diff --git a/debuggerd/tombstoned/intercept_manager.cpp b/debuggerd/tombstoned/intercept_manager.cpp
index 7d25c50..613e6f5 100644
--- a/debuggerd/tombstoned/intercept_manager.cpp
+++ b/debuggerd/tombstoned/intercept_manager.cpp
@@ -26,8 +26,8 @@
 
 #include <android-base/cmsg.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/unique_fd.h>
-#include <cutils/sockets.h>
 
 #include "protocol.h"
 #include "util.h"
@@ -163,7 +163,7 @@
     event_assign(intercept->intercept_event, intercept_manager->base, sockfd, EV_READ | EV_TIMEOUT,
                  intercept_close_cb, arg);
 
-    struct timeval timeout = { .tv_sec = 10, .tv_usec = 0 };
+    struct timeval timeout = {.tv_sec = 10 * android::base::HwTimeoutMultiplier(), .tv_usec = 0};
     event_add(intercept->intercept_event, &timeout);
   }
 
@@ -179,7 +179,7 @@
   intercept->intercept_manager = static_cast<InterceptManager*>(arg);
   intercept->sockfd.reset(sockfd);
 
-  struct timeval timeout = { 1, 0 };
+  struct timeval timeout = {1 * android::base::HwTimeoutMultiplier(), 0};
   event_base* base = evconnlistener_get_base(listener);
   event* intercept_event =
     event_new(base, sockfd, EV_TIMEOUT | EV_READ, intercept_request_cb, intercept);
@@ -192,6 +192,18 @@
                                       /* backlog */ -1, intercept_socket);
 }
 
+bool dump_types_compatible(DebuggerdDumpType interceptor, DebuggerdDumpType dump) {
+  if (interceptor == dump) {
+    return true;
+  }
+
+  if (interceptor == kDebuggerdTombstone && dump == kDebuggerdTombstoneProto) {
+    return true;
+  }
+
+  return false;
+}
+
 bool InterceptManager::GetIntercept(pid_t pid, DebuggerdDumpType dump_type,
                                     android::base::unique_fd* out_fd) {
   auto it = this->intercepts.find(pid);
@@ -202,7 +214,7 @@
   if (dump_type == kDebuggerdAnyIntercept) {
     LOG(INFO) << "found registered intercept of type " << it->second->dump_type
               << " for requested type kDebuggerdAnyIntercept";
-  } else if (it->second->dump_type != dump_type) {
+  } else if (!dump_types_compatible(it->second->dump_type, dump_type)) {
     LOG(WARNING) << "found non-matching intercept of type " << it->second->dump_type
                  << " for requested type: " << dump_type;
     return false;
diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp
index d09b8e8..05d8050 100644
--- a/debuggerd/tombstoned/tombstoned.cpp
+++ b/debuggerd/tombstoned/tombstoned.cpp
@@ -48,6 +48,8 @@
 using android::base::GetIntProperty;
 using android::base::SendFileDescriptors;
 using android::base::StringPrintf;
+
+using android::base::borrowed_fd;
 using android::base::unique_fd;
 
 static InterceptManager* intercept_manager;
@@ -57,14 +59,34 @@
   kCrashStatusQueued,
 };
 
+struct CrashArtifact {
+  unique_fd fd;
+  std::optional<std::string> temporary_path;
+
+  static CrashArtifact devnull() {
+    CrashArtifact result;
+    result.fd.reset(open("/dev/null", O_WRONLY | O_CLOEXEC));
+    return result;
+  }
+};
+
+struct CrashArtifactPaths {
+  std::string text;
+  std::optional<std::string> proto;
+};
+
+struct CrashOutput {
+  CrashArtifact text;
+  std::optional<CrashArtifact> proto;
+};
+
 // Ownership of Crash is a bit messy.
 // It's either owned by an active event that must have a timeout, or owned by
 // queued_requests, in the case that multiple crashes come in at the same time.
 struct Crash {
   ~Crash() { event_free(crash_event); }
 
-  std::string crash_tombstone_path;
-  unique_fd crash_tombstone_fd;
+  CrashOutput output;
   unique_fd crash_socket_fd;
   pid_t crash_pid;
   event* crash_event = nullptr;
@@ -75,14 +97,15 @@
 class CrashQueue {
  public:
   CrashQueue(const std::string& dir_path, const std::string& file_name_prefix, size_t max_artifacts,
-             size_t max_concurrent_dumps)
+             size_t max_concurrent_dumps, bool supports_proto)
       : file_name_prefix_(file_name_prefix),
         dir_path_(dir_path),
         dir_fd_(open(dir_path.c_str(), O_DIRECTORY | O_RDONLY | O_CLOEXEC)),
         max_artifacts_(max_artifacts),
         next_artifact_(0),
         max_concurrent_dumps_(max_concurrent_dumps),
-        num_concurrent_dumps_(0) {
+        num_concurrent_dumps_(0),
+        supports_proto_(supports_proto) {
     if (dir_fd_ == -1) {
       PLOG(FATAL) << "failed to open directory: " << dir_path;
     }
@@ -98,59 +121,104 @@
     return (crash->crash_type == kDebuggerdJavaBacktrace) ? for_anrs() : for_tombstones();
   }
 
+  static CrashQueue* for_crash(const std::unique_ptr<Crash>& crash) {
+    return for_crash(crash.get());
+  }
+
   static CrashQueue* for_tombstones() {
     static CrashQueue queue("/data/tombstones", "tombstone_" /* file_name_prefix */,
                             GetIntProperty("tombstoned.max_tombstone_count", 32),
-                            1 /* max_concurrent_dumps */);
+                            1 /* max_concurrent_dumps */, true /* supports_proto */);
     return &queue;
   }
 
   static CrashQueue* for_anrs() {
     static CrashQueue queue("/data/anr", "trace_" /* file_name_prefix */,
                             GetIntProperty("tombstoned.max_anr_count", 64),
-                            4 /* max_concurrent_dumps */);
+                            4 /* max_concurrent_dumps */, false /* supports_proto */);
     return &queue;
   }
 
-  std::pair<std::string, unique_fd> get_output() {
-    std::string path;
-    unique_fd result(openat(dir_fd_, ".", O_WRONLY | O_APPEND | O_TMPFILE | O_CLOEXEC, 0640));
-    if (result == -1) {
+  CrashArtifact create_temporary_file() const {
+    CrashArtifact result;
+
+    std::optional<std::string> path;
+    result.fd.reset(openat(dir_fd_, ".", O_WRONLY | O_APPEND | O_TMPFILE | O_CLOEXEC, 0660));
+    if (result.fd == -1) {
       // We might not have O_TMPFILE. Try creating with an arbitrary filename instead.
       static size_t counter = 0;
       std::string tmp_filename = StringPrintf(".temporary%zu", counter++);
-      result.reset(openat(dir_fd_, tmp_filename.c_str(),
-                          O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_CLOEXEC, 0640));
-      if (result == -1) {
+      result.fd.reset(openat(dir_fd_, tmp_filename.c_str(),
+                             O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_CLOEXEC, 0660));
+      if (result.fd == -1) {
         PLOG(FATAL) << "failed to create temporary tombstone in " << dir_path_;
       }
 
-      path = StringPrintf("%s/%s", dir_path_.c_str(), tmp_filename.c_str());
+      result.temporary_path = std::move(tmp_filename);
     }
-    return std::make_pair(std::move(path), std::move(result));
+
+    return std::move(result);
   }
 
-  std::string get_next_artifact_path() {
-    std::string file_name =
-        StringPrintf("%s/%s%02d", dir_path_.c_str(), file_name_prefix_.c_str(), next_artifact_);
+  std::optional<CrashOutput> get_output(DebuggerdDumpType dump_type) {
+    CrashOutput result;
+
+    switch (dump_type) {
+      case kDebuggerdNativeBacktrace:
+        // Don't generate tombstones for native backtrace requests.
+        return {};
+
+      case kDebuggerdTombstoneProto:
+        if (!supports_proto_) {
+          LOG(ERROR) << "received kDebuggerdTombstoneProto on a queue that doesn't support proto";
+          return {};
+        }
+        result.proto = create_temporary_file();
+        result.text = create_temporary_file();
+        break;
+
+      case kDebuggerdJavaBacktrace:
+      case kDebuggerdTombstone:
+        result.text = create_temporary_file();
+        break;
+
+      default:
+        LOG(ERROR) << "unexpected dump type: " << dump_type;
+        return {};
+    }
+
+    return result;
+  }
+
+  borrowed_fd dir_fd() { return dir_fd_; }
+
+  CrashArtifactPaths get_next_artifact_paths() {
+    CrashArtifactPaths result;
+    result.text = StringPrintf("%s%02d", file_name_prefix_.c_str(), next_artifact_);
+
+    if (supports_proto_) {
+      result.proto = StringPrintf("%s%02d.pb", file_name_prefix_.c_str(), next_artifact_);
+    }
+
     next_artifact_ = (next_artifact_ + 1) % max_artifacts_;
-    return file_name;
+    return result;
   }
 
-  bool maybe_enqueue_crash(Crash* crash) {
+  // Consumes crash if it returns true, otherwise leaves it untouched.
+  bool maybe_enqueue_crash(std::unique_ptr<Crash>&& crash) {
     if (num_concurrent_dumps_ == max_concurrent_dumps_) {
-      queued_requests_.push_back(crash);
+      queued_requests_.emplace_back(std::move(crash));
       return true;
     }
 
     return false;
   }
 
-  void maybe_dequeue_crashes(void (*handler)(Crash* crash)) {
+  void maybe_dequeue_crashes(void (*handler)(std::unique_ptr<Crash> crash)) {
     while (!queued_requests_.empty() && num_concurrent_dumps_ < max_concurrent_dumps_) {
-      Crash* next_crash = queued_requests_.front();
+      std::unique_ptr<Crash> next_crash = std::move(queued_requests_.front());
       queued_requests_.pop_front();
-      handler(next_crash);
+      handler(std::move(next_crash));
     }
   }
 
@@ -164,7 +232,8 @@
     time_t oldest_time = std::numeric_limits<time_t>::max();
 
     for (size_t i = 0; i < max_artifacts_; ++i) {
-      std::string path = StringPrintf("%s/%s%02zu", dir_path_.c_str(), file_name_prefix_.c_str(), i);
+      std::string path =
+          StringPrintf("%s/%s%02zu", dir_path_.c_str(), file_name_prefix_.c_str(), i);
       struct stat st;
       if (stat(path.c_str(), &st) != 0) {
         if (errno == ENOENT) {
@@ -196,7 +265,9 @@
   const size_t max_concurrent_dumps_;
   size_t num_concurrent_dumps_;
 
-  std::deque<Crash*> queued_requests_;
+  bool supports_proto_;
+
+  std::deque<std::unique_ptr<Crash>> queued_requests_;
 
   DISALLOW_COPY_AND_ASSIGN(CrashQueue);
 };
@@ -205,52 +276,61 @@
 static constexpr bool kJavaTraceDumpsEnabled = true;
 
 // Forward declare the callbacks so they can be placed in a sensible order.
-static void crash_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int, void*);
+static void crash_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int,
+                            void*);
 static void crash_request_cb(evutil_socket_t sockfd, short ev, void* arg);
 static void crash_completed_cb(evutil_socket_t sockfd, short ev, void* arg);
 
-static void perform_request(Crash* crash) {
+static void perform_request(std::unique_ptr<Crash> crash) {
   unique_fd output_fd;
   bool intercepted =
       intercept_manager->GetIntercept(crash->crash_pid, crash->crash_type, &output_fd);
-  if (!intercepted) {
-    if (crash->crash_type == kDebuggerdNativeBacktrace) {
-      // Don't generate tombstones for native backtrace requests.
-      output_fd.reset(open("/dev/null", O_WRONLY | O_CLOEXEC));
+  if (intercepted) {
+    if (crash->crash_type == kDebuggerdTombstoneProto) {
+      crash->output.proto = CrashArtifact::devnull();
+    }
+  } else {
+    if (auto o = CrashQueue::for_crash(crash.get())->get_output(crash->crash_type); o) {
+      crash->output = std::move(*o);
+      output_fd.reset(dup(crash->output.text.fd));
     } else {
-      std::tie(crash->crash_tombstone_path, output_fd) = CrashQueue::for_crash(crash)->get_output();
-      crash->crash_tombstone_fd.reset(dup(output_fd.get()));
+      LOG(ERROR) << "failed to get crash output for type " << crash->crash_type;
+      return;
     }
   }
 
-  TombstonedCrashPacket response = {
-    .packet_type = CrashPacketType::kPerformDump
-  };
-  ssize_t rc =
-      SendFileDescriptors(crash->crash_socket_fd, &response, sizeof(response), output_fd.get());
+  TombstonedCrashPacket response = {.packet_type = CrashPacketType::kPerformDump};
+
+  ssize_t rc = -1;
+  if (crash->output.proto) {
+    rc = SendFileDescriptors(crash->crash_socket_fd, &response, sizeof(response), output_fd.get(),
+                             crash->output.proto->fd.get());
+  } else {
+    rc = SendFileDescriptors(crash->crash_socket_fd, &response, sizeof(response), output_fd.get());
+  }
+
   output_fd.reset();
 
   if (rc == -1) {
     PLOG(WARNING) << "failed to send response to CrashRequest";
-    goto fail;
+    return;
   } else if (rc != sizeof(response)) {
     PLOG(WARNING) << "crash socket write returned short";
-    goto fail;
-  } else {
-    // TODO: Make this configurable by the interceptor?
-    struct timeval timeout = { 10, 0 };
-
-    event_base* base = event_get_base(crash->crash_event);
-    event_assign(crash->crash_event, base, crash->crash_socket_fd, EV_TIMEOUT | EV_READ,
-                 crash_completed_cb, crash);
-    event_add(crash->crash_event, &timeout);
+    return;
   }
 
-  CrashQueue::for_crash(crash)->on_crash_started();
-  return;
+  // TODO: Make this configurable by the interceptor?
+  struct timeval timeout = {10 * android::base::HwTimeoutMultiplier(), 0};
 
-fail:
-  delete crash;
+  event_base* base = event_get_base(crash->crash_event);
+
+  event_assign(crash->crash_event, base, crash->crash_socket_fd, EV_TIMEOUT | EV_READ,
+               crash_completed_cb, crash.get());
+  event_add(crash->crash_event, &timeout);
+  CrashQueue::for_crash(crash)->on_crash_started();
+
+  // The crash is now owned by the event loop.
+  crash.release();
 }
 
 static void crash_accept_cb(evconnlistener* listener, evutil_socket_t sockfd, sockaddr*, int,
@@ -260,7 +340,7 @@
 
   // TODO: Make sure that only java crashes come in on the java socket
   // and only native crashes on the native socket.
-  struct timeval timeout = { 1, 0 };
+  struct timeval timeout = {1 * android::base::HwTimeoutMultiplier(), 0};
   event* crash_event = event_new(base, sockfd, EV_TIMEOUT | EV_READ, crash_request_cb, crash);
   crash->crash_socket_fd.reset(sockfd);
   crash->crash_event = crash_event;
@@ -268,39 +348,37 @@
 }
 
 static void crash_request_cb(evutil_socket_t sockfd, short ev, void* arg) {
-  ssize_t rc;
-  Crash* crash = static_cast<Crash*>(arg);
-
+  std::unique_ptr<Crash> crash(static_cast<Crash*>(arg));
   TombstonedCrashPacket request = {};
 
   if ((ev & EV_TIMEOUT) != 0) {
     LOG(WARNING) << "crash request timed out";
-    goto fail;
+    return;
   } else if ((ev & EV_READ) == 0) {
     LOG(WARNING) << "tombstoned received unexpected event from crash socket";
-    goto fail;
+    return;
   }
 
-  rc = TEMP_FAILURE_RETRY(read(sockfd, &request, sizeof(request)));
+  ssize_t rc = TEMP_FAILURE_RETRY(read(sockfd, &request, sizeof(request)));
   if (rc == -1) {
     PLOG(WARNING) << "failed to read from crash socket";
-    goto fail;
+    return;
   } else if (rc != sizeof(request)) {
     LOG(WARNING) << "crash socket received short read of length " << rc << " (expected "
                  << sizeof(request) << ")";
-    goto fail;
+    return;
   }
 
   if (request.packet_type != CrashPacketType::kDumpRequest) {
     LOG(WARNING) << "unexpected crash packet type, expected kDumpRequest, received  "
                  << StringPrintf("%#2hhX", request.packet_type);
-    goto fail;
+    return;
   }
 
   crash->crash_type = request.packet.dump_request.dump_type;
-  if (crash->crash_type < 0 || crash->crash_type > kDebuggerdAnyIntercept) {
+  if (crash->crash_type < 0 || crash->crash_type > kDebuggerdTombstoneProto) {
     LOG(WARNING) << "unexpected crash dump type: " << crash->crash_type;
-    goto fail;
+    return;
   }
 
   if (crash->crash_type != kDebuggerdJavaBacktrace) {
@@ -314,97 +392,119 @@
     int ret = getsockopt(sockfd, SOL_SOCKET, SO_PEERCRED, &cr, &len);
     if (ret != 0) {
       PLOG(ERROR) << "Failed to getsockopt(..SO_PEERCRED)";
-      goto fail;
+      return;
     }
 
     crash->crash_pid = cr.pid;
   }
 
-  LOG(INFO) << "received crash request for pid " << crash->crash_pid;
+  pid_t crash_pid = crash->crash_pid;
+  LOG(INFO) << "received crash request for pid " << crash_pid;
 
-  if (CrashQueue::for_crash(crash)->maybe_enqueue_crash(crash)) {
-    LOG(INFO) << "enqueueing crash request for pid " << crash->crash_pid;
+  if (CrashQueue::for_crash(crash)->maybe_enqueue_crash(std::move(crash))) {
+    LOG(INFO) << "enqueueing crash request for pid " << crash_pid;
   } else {
-    perform_request(crash);
+    perform_request(std::move(crash));
   }
-
-  return;
-
-fail:
-  delete crash;
 }
 
-static void crash_completed_cb(evutil_socket_t sockfd, short ev, void* arg) {
-  ssize_t rc;
-  Crash* crash = static_cast<Crash*>(arg);
-  TombstonedCrashPacket request = {};
-
-  CrashQueue::for_crash(crash)->on_crash_completed();
-
-  if ((ev & EV_READ) == 0) {
-    goto fail;
+static bool rename_tombstone_fd(borrowed_fd fd, borrowed_fd dirfd, const std::string& path) {
+  // Always try to unlink the tombstone file.
+  // linkat doesn't let us replace a file, so we need to unlink before linking
+  // our results onto disk, and if we fail for some reason, we should delete
+  // stale tombstones to avoid confusing inconsistency.
+  int rc = unlinkat(dirfd.get(), path.c_str(), 0);
+  if (rc != 0 && errno != ENOENT) {
+    PLOG(ERROR) << "failed to unlink tombstone at " << path;
+    return false;
   }
 
-  rc = TEMP_FAILURE_RETRY(read(sockfd, &request, sizeof(request)));
+  std::string fd_path = StringPrintf("/proc/self/fd/%d", fd.get());
+  rc = linkat(AT_FDCWD, fd_path.c_str(), dirfd.get(), path.c_str(), AT_SYMLINK_FOLLOW);
+  if (rc != 0) {
+    PLOG(ERROR) << "failed to link tombstone at " << path;
+    return false;
+  }
+  return true;
+}
+
+static void crash_completed(borrowed_fd sockfd, std::unique_ptr<Crash> crash) {
+  TombstonedCrashPacket request = {};
+  CrashQueue* queue = CrashQueue::for_crash(crash);
+
+  ssize_t rc = TEMP_FAILURE_RETRY(read(sockfd.get(), &request, sizeof(request)));
   if (rc == -1) {
     PLOG(WARNING) << "failed to read from crash socket";
-    goto fail;
+    return;
   } else if (rc != sizeof(request)) {
     LOG(WARNING) << "crash socket received short read of length " << rc << " (expected "
                  << sizeof(request) << ")";
-    goto fail;
+    return;
   }
 
   if (request.packet_type != CrashPacketType::kCompletedDump) {
     LOG(WARNING) << "unexpected crash packet type, expected kCompletedDump, received "
                  << uint32_t(request.packet_type);
-    goto fail;
+    return;
   }
 
-  if (crash->crash_tombstone_fd != -1) {
-    std::string fd_path = StringPrintf("/proc/self/fd/%d", crash->crash_tombstone_fd.get());
-    std::string tombstone_path = CrashQueue::for_crash(crash)->get_next_artifact_path();
+  if (crash->output.text.fd == -1) {
+    LOG(WARNING) << "missing output fd";
+    return;
+  }
 
-    // linkat doesn't let us replace a file, so we need to unlink first.
-    int rc = unlink(tombstone_path.c_str());
-    if (rc != 0 && errno != ENOENT) {
-      PLOG(ERROR) << "failed to unlink tombstone at " << tombstone_path;
-      goto fail;
-    }
+  CrashArtifactPaths paths = queue->get_next_artifact_paths();
 
-    rc = linkat(AT_FDCWD, fd_path.c_str(), AT_FDCWD, tombstone_path.c_str(), AT_SYMLINK_FOLLOW);
-    if (rc != 0) {
-      PLOG(ERROR) << "failed to link tombstone";
+  if (rename_tombstone_fd(crash->output.text.fd, queue->dir_fd(), paths.text)) {
+    if (crash->crash_type == kDebuggerdJavaBacktrace) {
+      LOG(ERROR) << "Traces for pid " << crash->crash_pid << " written to: " << paths.text;
     } else {
-      if (crash->crash_type == kDebuggerdJavaBacktrace) {
-        LOG(ERROR) << "Traces for pid " << crash->crash_pid << " written to: " << tombstone_path;
-      } else {
-        // NOTE: Several tools parse this log message to figure out where the
-        // tombstone associated with a given native crash was written. Any changes
-        // to this message must be carefully considered.
-        LOG(ERROR) << "Tombstone written to: " << tombstone_path;
-      }
-    }
-
-    // If we don't have O_TMPFILE, we need to clean up after ourselves.
-    if (!crash->crash_tombstone_path.empty()) {
-      rc = unlink(crash->crash_tombstone_path.c_str());
-      if (rc != 0) {
-        PLOG(ERROR) << "failed to unlink temporary tombstone at " << crash->crash_tombstone_path;
-      }
+      // NOTE: Several tools parse this log message to figure out where the
+      // tombstone associated with a given native crash was written. Any changes
+      // to this message must be carefully considered.
+      LOG(ERROR) << "Tombstone written to: " << paths.text;
     }
   }
 
-fail:
+  if (crash->output.proto && crash->output.proto->fd != -1) {
+    if (!paths.proto) {
+      LOG(ERROR) << "missing path for proto tombstone";
+    } else {
+      rename_tombstone_fd(crash->output.proto->fd, queue->dir_fd(), *paths.proto);
+    }
+  }
+
+  // If we don't have O_TMPFILE, we need to clean up after ourselves.
+  if (crash->output.text.temporary_path) {
+    rc = unlinkat(queue->dir_fd().get(), crash->output.text.temporary_path->c_str(), 0);
+    if (rc != 0) {
+      PLOG(ERROR) << "failed to unlink temporary tombstone at " << paths.text;
+    }
+  }
+  if (crash->output.proto && crash->output.proto->temporary_path) {
+    rc = unlinkat(queue->dir_fd().get(), crash->output.proto->temporary_path->c_str(), 0);
+    if (rc != 0) {
+      PLOG(ERROR) << "failed to unlink temporary proto tombstone";
+    }
+  }
+}
+
+static void crash_completed_cb(evutil_socket_t sockfd, short ev, void* arg) {
+  std::unique_ptr<Crash> crash(static_cast<Crash*>(arg));
   CrashQueue* queue = CrashQueue::for_crash(crash);
-  delete crash;
+
+  queue->on_crash_completed();
+
+  if ((ev & EV_READ) == EV_READ) {
+    crash_completed(sockfd, std::move(crash));
+  }
 
   // If there's something queued up, let them proceed.
   queue->maybe_dequeue_crashes(perform_request);
 }
 
 int main(int, char* []) {
-  umask(0137);
+  umask(0117);
 
   // Don't try to connect to ourselves if we crash.
   struct sigaction action = {};
diff --git a/debuggerd/tombstoned/tombstoned_client.cpp b/debuggerd/tombstoned/tombstoned_client.cpp
index 2c23c98..abfafb1 100644
--- a/debuggerd/tombstoned/tombstoned_client.cpp
+++ b/debuggerd/tombstoned/tombstoned_client.cpp
@@ -32,8 +32,13 @@
 using android::base::ReceiveFileDescriptors;
 using android::base::unique_fd;
 
-bool tombstoned_connect(pid_t pid, unique_fd* tombstoned_socket, unique_fd* output_fd,
+bool tombstoned_connect(pid_t pid, unique_fd* tombstoned_socket, unique_fd* text_output_fd,
                         DebuggerdDumpType dump_type) {
+  return tombstoned_connect(pid, tombstoned_socket, text_output_fd, nullptr, dump_type);
+}
+
+bool tombstoned_connect(pid_t pid, unique_fd* tombstoned_socket, unique_fd* text_output_fd,
+                        unique_fd* proto_output_fd, DebuggerdDumpType dump_type) {
   unique_fd sockfd(
       socket_local_client((dump_type != kDebuggerdJavaBacktrace ? kTombstonedCrashSocketName
                                                                 : kTombstonedJavaTraceSocketName),
@@ -54,8 +59,15 @@
     return false;
   }
 
-  unique_fd tmp_output_fd;
-  ssize_t rc = ReceiveFileDescriptors(sockfd, &packet, sizeof(packet), &tmp_output_fd);
+  unique_fd tmp_output_fd, tmp_proto_fd;
+  ssize_t rc = -1;
+
+  if (dump_type == kDebuggerdTombstoneProto) {
+    rc = ReceiveFileDescriptors(sockfd, &packet, sizeof(packet), &tmp_output_fd, &tmp_proto_fd);
+  } else {
+    rc = ReceiveFileDescriptors(sockfd, &packet, sizeof(packet), &tmp_output_fd);
+  }
+
   if (rc == -1) {
     async_safe_format_log(ANDROID_LOG_ERROR, "libc",
                           "failed to read response to DumpRequest packet: %s", strerror(errno));
@@ -78,7 +90,10 @@
   }
 
   *tombstoned_socket = std::move(sockfd);
-  *output_fd = std::move(tmp_output_fd);
+  *text_output_fd = std::move(tmp_output_fd);
+  if (proto_output_fd) {
+    *proto_output_fd = std::move(tmp_proto_fd);
+  }
   return true;
 }
 
diff --git a/debuggerd/util.cpp b/debuggerd/util.cpp
index 9d09210..ce0fd30 100644
--- a/debuggerd/util.cpp
+++ b/debuggerd/util.cpp
@@ -16,7 +16,6 @@
 
 #include "util.h"
 
-#include <sys/socket.h>
 #include <time.h>
 
 #include <string>
@@ -25,13 +24,33 @@
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
-#include <cutils/sockets.h>
 #include "protocol.h"
 
+std::vector<std::string> get_command_line(pid_t pid) {
+  std::vector<std::string> result;
+
+  std::string cmdline;
+  android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &cmdline);
+
+  auto it = cmdline.cbegin();
+  while (it != cmdline.cend()) {
+    // string::iterator is a wrapped type, not a raw char*.
+    auto terminator = std::find(it, cmdline.cend(), '\0');
+    result.emplace_back(it, terminator);
+    it = std::find_if(terminator, cmdline.cend(), [](char c) { return c != '\0'; });
+  }
+  if (result.empty()) {
+    result.emplace_back("<unknown>");
+  }
+
+  return result;
+}
+
 std::string get_process_name(pid_t pid) {
   std::string result = "<unknown>";
   android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &result);
-  return result;
+  // We only want the name, not the whole command line, so truncate at the first NUL.
+  return result.c_str();
 }
 
 std::string get_thread_name(pid_t tid) {
diff --git a/debuggerd/util.h b/debuggerd/util.h
index 07e7e99..ec2862a 100644
--- a/debuggerd/util.h
+++ b/debuggerd/util.h
@@ -17,10 +17,12 @@
 #pragma once
 
 #include <string>
+#include <vector>
 
 #include <sys/cdefs.h>
 #include <sys/types.h>
 
+std::vector<std::string> get_command_line(pid_t pid);
 std::string get_process_name(pid_t pid);
 std::string get_thread_name(pid_t tid);
 
diff --git a/deprecated-adf/OWNERS b/deprecated-adf/OWNERS
deleted file mode 100644
index 72b8b5a..0000000
--- a/deprecated-adf/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-ghackmann@google.com
-marissaw@google.com
diff --git a/deprecated-adf/libadf/Android.bp b/deprecated-adf/libadf/Android.bp
deleted file mode 100644
index 70f0a3b..0000000
--- a/deprecated-adf/libadf/Android.bp
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-cc_library {
-    name: "libadf",
-    recovery_available: true,
-    vendor_available: true,
-    vndk: {
-        enabled: true,
-    },
-    srcs: ["adf.cpp"],
-    cflags: ["-Werror"],
-    local_include_dirs: ["include"],
-    export_include_dirs: ["include"],
-}
diff --git a/deprecated-adf/libadf/adf.cpp b/deprecated-adf/libadf/adf.cpp
deleted file mode 100644
index fd9c208..0000000
--- a/deprecated-adf/libadf/adf.cpp
+++ /dev/null
@@ -1,746 +0,0 @@
-/*
- * Copyright (C) 2013 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 <dirent.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <malloc.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <memory>
-#include <vector>
-
-#include <linux/limits.h>
-
-#include <sys/ioctl.h>
-
-#include <adf/adf.h>
-
-#define ADF_BASE_PATH "/dev/"
-
-static ssize_t adf_id_vector_to_array(const std::vector<adf_id_t> &in,
-        adf_id_t **out)
-{
-    auto size = sizeof(in[0]) * in.size();
-    // We can't use new[] since the existing API says the caller should free()
-    // the returned array
-    auto ret = static_cast<adf_id_t *>(malloc(size));
-    if (!ret)
-        return -ENOMEM;
-
-    std::copy(in.begin(), in.end(), ret);
-    *out = ret;
-    return in.size();
-}
-
-static ssize_t adf_find_nodes(const char *pattern, adf_id_t **ids_out)
-{
-    struct dirent *dirent;
-    std::unique_ptr<DIR, decltype(&closedir)>
-        dir{opendir(ADF_BASE_PATH), closedir};
-    if (!dir)
-        return -errno;
-
-    std::vector<adf_id_t> ids;
-    errno = 0;
-    while ((dirent = readdir(dir.get()))) {
-        adf_id_t id;
-        int matched = sscanf(dirent->d_name, pattern, &id);
-
-        if (matched < 0)
-            return -errno;
-        else if (matched == 1)
-            ids.push_back(id);
-    }
-    if (errno)
-        return -errno;
-
-    return adf_id_vector_to_array(ids, ids_out);
-}
-
-ssize_t adf_devices(adf_id_t **ids)
-{
-    return adf_find_nodes("adf%u", ids);
-}
-
-int adf_device_open(adf_id_t id, int flags, struct adf_device *dev)
-{
-    char filename[64];
-
-    dev->id = id;
-
-    snprintf(filename, sizeof(filename), ADF_BASE_PATH "adf%u", id);
-    dev->fd = open(filename, flags);
-    if (dev->fd < 0)
-        return -errno;
-
-    return 0;
-}
-
-void adf_device_close(struct adf_device *dev)
-{
-    if (dev->fd >= 0)
-        close(dev->fd);
-}
-
-int adf_get_device_data(struct adf_device *dev, struct adf_device_data *data)
-{
-    int err;
-    int ret = 0;
-
-    memset(data, 0, sizeof(*data));
-
-    err = ioctl(dev->fd, ADF_GET_DEVICE_DATA, data);
-    if (err < 0)
-        return -ENOMEM;
-
-    if (data->n_attachments)
-        data->attachments = new adf_attachment_config[data->n_attachments];
-
-    if (data->n_allowed_attachments)
-        data->allowed_attachments =
-                new adf_attachment_config[data->n_allowed_attachments];
-
-    if (data->custom_data_size)
-        data->custom_data = new char[data->custom_data_size];
-
-    err = ioctl(dev->fd, ADF_GET_DEVICE_DATA, data);
-    if (err < 0) {
-        ret = -errno;
-        adf_free_device_data(data);
-    }
-    return ret;
-}
-
-void adf_free_device_data(struct adf_device_data *data)
-{
-    delete [] data->attachments;
-    data->attachments = nullptr;
-    delete [] data->allowed_attachments;
-    data->allowed_attachments = nullptr;
-    delete [] static_cast<char *>(data->custom_data);
-    data->custom_data = nullptr;
-}
-
-int adf_device_post(struct adf_device *dev,
-        adf_id_t *interfaces, size_t n_interfaces,
-        struct adf_buffer_config *bufs, size_t n_bufs,
-        void *custom_data, size_t custom_data_size)
-{
-    int err;
-    struct adf_post_config data;
-
-    memset(&data, 0, sizeof(data));
-    data.interfaces = interfaces;
-    data.n_interfaces = n_interfaces;
-    data.bufs = bufs;
-    data.n_bufs = n_bufs;
-    data.custom_data = custom_data;
-    data.custom_data_size = custom_data_size;
-
-    err = ioctl(dev->fd, ADF_POST_CONFIG, &data);
-    if (err < 0)
-        return -errno;
-
-    return (int)data.complete_fence;
-}
-
-int adf_device_post_v2(struct adf_device *dev,
-        adf_id_t *interfaces, __u32 n_interfaces,
-        struct adf_buffer_config *bufs, __u32 n_bufs,
-        void *custom_data, __u64 custom_data_size,
-        enum adf_complete_fence_type complete_fence_type,
-        int *complete_fence)
-{
-    int err;
-    struct adf_post_config_v2 data;
-
-    memset(&data, 0, sizeof(data));
-    data.interfaces = (uintptr_t)interfaces;
-    data.n_interfaces = n_interfaces;
-    data.bufs = (uintptr_t)bufs;
-    data.n_bufs = n_bufs;
-    data.custom_data = (uintptr_t)custom_data;
-    data.custom_data_size = custom_data_size;
-    data.complete_fence_type = complete_fence_type;
-
-    err = ioctl(dev->fd, ADF_POST_CONFIG_V2, &data);
-    if (err < 0)
-        return -errno;
-
-    if (complete_fence)
-        *complete_fence = data.complete_fence;
-    else if (data.complete_fence >= 0)
-        close(data.complete_fence);
-
-    return 0;
-}
-
-static int adf_device_attachment(struct adf_device *dev,
-        adf_id_t overlay_engine, adf_id_t interface, bool attach)
-{
-    int err;
-    struct adf_attachment_config data;
-
-    memset(&data, 0, sizeof(data));
-    data.overlay_engine = overlay_engine;
-    data.interface = interface;
-
-    err = ioctl(dev->fd, attach ? ADF_ATTACH : ADF_DETACH, &data);
-    if (err < 0)
-        return -errno;
-
-    return 0;
-}
-
-int adf_device_attach(struct adf_device *dev, adf_id_t overlay_engine,
-                      adf_id_t interface)
-{
-   return adf_device_attachment(dev, overlay_engine, interface, true);
-}
-
-int adf_device_detach(struct adf_device *dev, adf_id_t overlay_engine,
-                      adf_id_t interface)
-{
-   return adf_device_attachment(dev, overlay_engine, interface, false);
-}
-
-ssize_t adf_interfaces(struct adf_device *dev, adf_id_t **interfaces)
-{
-    char pattern[64];
-
-    snprintf(pattern, sizeof(pattern), "adf-interface%u.%%u", dev->id);
-    return adf_find_nodes(pattern, interfaces);
-}
-
-ssize_t adf_interfaces_for_overlay_engine(struct adf_device *dev,
-        adf_id_t overlay_engine, adf_id_t **interfaces)
-{
-    struct adf_device_data data;
-    auto err = adf_get_device_data(dev, &data);
-    if (err < 0)
-        return err;
-
-    std::vector<adf_id_t> ids;
-    if (data.allowed_attachments != nullptr)
-        for (size_t i = 0; i < data.n_allowed_attachments; i++)
-            if (data.allowed_attachments[i].overlay_engine == overlay_engine)
-              ids.push_back(data.allowed_attachments[i].interface);
-
-    adf_free_device_data(&data);
-    return adf_id_vector_to_array(ids, interfaces);
-}
-
-static ssize_t adf_interfaces_filter(struct adf_device *dev,
-        adf_id_t *in, size_t n_in, adf_id_t **out,
-        bool (*filter)(struct adf_interface_data *data, __u32 match),
-        __u32 match)
-{
-    std::vector<adf_id_t> ids;
-    for (size_t i = 0; i < n_in; i++) {
-        int fd = adf_interface_open(dev, in[i], O_RDONLY);
-        if (fd < 0)
-            return fd;
-
-        struct adf_interface_data data;
-        auto ret = adf_get_interface_data(fd, &data);
-        close(fd);
-        if (ret < 0)
-            return ret;
-
-        if (filter(&data, match))
-            ids.push_back(in[i]);
-    }
-
-    return adf_id_vector_to_array(ids, out);
-}
-
-static bool adf_interface_type_filter(struct adf_interface_data *data,
-        __u32 type)
-{
-    return data->type == (enum adf_interface_type)type;
-}
-
-ssize_t adf_interfaces_filter_by_type(struct adf_device *dev,
-        enum adf_interface_type type,
-        adf_id_t *in, size_t n_in, adf_id_t **out)
-{
-    return adf_interfaces_filter(dev, in, n_in, out, adf_interface_type_filter,
-            type);
-}
-
-static bool adf_interface_flags_filter(struct adf_interface_data *data,
-        __u32 flag)
-{
-    return !!(data->flags & flag);
-}
-
-ssize_t adf_interfaces_filter_by_flag(struct adf_device *dev, __u32 flag,
-        adf_id_t *in, size_t n_in, adf_id_t **out)
-{
-    return adf_interfaces_filter(dev, in, n_in, out, adf_interface_flags_filter,
-            flag);
-}
-
-int adf_interface_open(struct adf_device *dev, adf_id_t id, int flags)
-{
-    char filename[64];
-
-    snprintf(filename, sizeof(filename), ADF_BASE_PATH "adf-interface%u.%u",
-            dev->id, id);
-
-    int fd = open(filename, flags);
-    if (fd < 0)
-        return -errno;
-    return fd;
-}
-
-int adf_get_interface_data(int fd, struct adf_interface_data *data)
-{
-    int err;
-    int ret = 0;
-
-    memset(data, 0, sizeof(*data));
-
-    err = ioctl(fd, ADF_GET_INTERFACE_DATA, data);
-    if (err < 0)
-        return -errno;
-
-    if (data->n_available_modes)
-        data->available_modes = new drm_mode_modeinfo[data->n_available_modes];
-
-    if (data->custom_data_size)
-        data->custom_data = new char[data->custom_data_size];
-
-    err = ioctl(fd, ADF_GET_INTERFACE_DATA, data);
-    if (err < 0) {
-        ret = -errno;
-        adf_free_interface_data(data);
-    }
-    return ret;
-}
-
-void adf_free_interface_data(struct adf_interface_data *data)
-{
-    delete [] data->available_modes;
-    delete [] static_cast<char *>(data->custom_data);
-}
-
-int adf_interface_blank(int fd, __u8 mode)
-{
-    int err = ioctl(fd, ADF_BLANK, mode);
-    if (err < 0)
-        return -errno;
-    return 0;
-}
-
-int adf_interface_set_mode(int fd, struct drm_mode_modeinfo *mode)
-{
-    int err = ioctl(fd, ADF_SET_MODE, mode);
-    if (err < 0)
-        return -errno;
-    return 0;
-}
-
-int adf_interface_simple_buffer_alloc(int fd, __u32 w, __u32 h,
-        __u32 format, __u32 *offset, __u32 *pitch)
-{
-    int err;
-    struct adf_simple_buffer_alloc data;
-
-    memset(&data, 0, sizeof(data));
-    data.w = w;
-    data.h = h;
-    data.format = format;
-
-    err = ioctl(fd, ADF_SIMPLE_BUFFER_ALLOC, &data);
-    if (err < 0)
-        return -errno;
-
-    *offset = data.offset;
-    *pitch = data.pitch;
-    return (int)data.fd;
-}
-
-static void adf_interface_simple_post_config_buf(struct adf_buffer_config *buf,
-        __u32 overlay_engine, __u32 w, __u32 h, __u32 format, int buf_fd,
-        __u32 offset, __u32 pitch, int acquire_fence)
-{
-    buf->overlay_engine = overlay_engine;
-    buf->w = w;
-    buf->h = h;
-    buf->format = format;
-    buf->fd[0] = buf_fd;
-    buf->offset[0] = offset;
-    buf->pitch[0] = pitch;
-    buf->n_planes = 1;
-    buf->acquire_fence = acquire_fence;
-}
-
-int adf_interface_simple_post(int fd, __u32 overlay_engine,
-        __u32 w, __u32 h, __u32 format, int buf_fd, __u32 offset,
-        __u32 pitch, int acquire_fence)
-{
-    int ret;
-    struct adf_simple_post_config data;
-
-    memset(&data, 0, sizeof(data));
-    adf_interface_simple_post_config_buf(&data.buf, overlay_engine, w, h, format,
-            buf_fd, offset, pitch, acquire_fence);
-    ret = ioctl(fd, ADF_SIMPLE_POST_CONFIG, &data);
-    if (ret < 0)
-        return -errno;
-
-    return (int)data.complete_fence;
-}
-
-int adf_interface_simple_post_v2(int fd, adf_id_t overlay_engine,
-        __u32 w, __u32 h, __u32 format, int buf_fd, __u32 offset,
-        __u32 pitch, int acquire_fence,
-        enum adf_complete_fence_type complete_fence_type,
-        int *complete_fence)
-{
-    int ret;
-    struct adf_simple_post_config_v2 data;
-
-    memset(&data, 0, sizeof(data));
-    adf_interface_simple_post_config_buf(&data.buf, overlay_engine, w, h, format,
-            buf_fd, offset, pitch, acquire_fence);
-    data.complete_fence_type = complete_fence_type;
-
-    ret = ioctl(fd, ADF_SIMPLE_POST_CONFIG_V2, &data);
-    if (ret < 0)
-        return -errno;
-
-    if (complete_fence)
-        *complete_fence = data.complete_fence;
-    else if (data.complete_fence >= 0)
-        close(data.complete_fence);
-
-    return 0;
-}
-
-ssize_t adf_overlay_engines(struct adf_device *dev, adf_id_t **overlay_engines)
-{
-    char pattern[64];
-
-    snprintf(pattern, sizeof(pattern), "adf-overlay-engine%u.%%u", dev->id);
-    return adf_find_nodes(pattern, overlay_engines);
-}
-
-ssize_t adf_overlay_engines_for_interface(struct adf_device *dev,
-        adf_id_t interface, adf_id_t **overlay_engines)
-{
-    struct adf_device_data data;
-    auto err = adf_get_device_data(dev, &data);
-    if (err < 0)
-        return err;
-
-    std::vector<adf_id_t> ids;
-    if (data.allowed_attachments != nullptr)
-        for (size_t i = 0; i < data.n_allowed_attachments; i++)
-            if (data.allowed_attachments[i].interface == interface)
-                ids.push_back(data.allowed_attachments[i].overlay_engine);
-
-    return adf_id_vector_to_array(ids, overlay_engines);
-}
-
-static ssize_t adf_overlay_engines_filter(struct adf_device *dev,
-        adf_id_t *in, size_t n_in, adf_id_t **out,
-        bool (*filter)(struct adf_overlay_engine_data *data, void *cookie),
-        void *cookie)
-{
-    std::vector<adf_id_t> ids;
-    size_t i;
-    for (i = 0; i < n_in; i++) {
-        int fd = adf_overlay_engine_open(dev, in[i], O_RDONLY);
-        if (fd < 0)
-            return fd;
-
-        struct adf_overlay_engine_data data;
-        auto ret = adf_get_overlay_engine_data(fd, &data);
-        close(fd);
-        if (ret < 0)
-            return ret;
-
-        if (filter(&data, cookie))
-            ids.push_back(in[i]);
-    }
-
-    return adf_id_vector_to_array(ids, out);
-}
-
-struct format_filter_cookie {
-    const __u32 *formats;
-    size_t n_formats;
-};
-
-static bool adf_overlay_engine_format_filter(
-        struct adf_overlay_engine_data *data, void *cookie)
-{
-    auto c = static_cast<format_filter_cookie *>(cookie);
-    size_t i;
-    for (i = 0; i < data->n_supported_formats; i++) {
-        size_t j;
-        for (j = 0; j < c->n_formats; j++)
-            if (data->supported_formats[i] == c->formats[j])
-                return true;
-    }
-    return false;
-}
-
-ssize_t adf_overlay_engines_filter_by_format(struct adf_device *dev,
-        const __u32 *formats, size_t n_formats, adf_id_t *in, size_t n_in,
-        adf_id_t **out)
-{
-    struct format_filter_cookie cookie = { formats, n_formats };
-    return adf_overlay_engines_filter(dev, in, n_in, out,
-            adf_overlay_engine_format_filter, &cookie);
-}
-
-int adf_overlay_engine_open(struct adf_device *dev, adf_id_t id, int flags)
-{
-    char filename[64];
-
-    snprintf(filename, sizeof(filename),
-            ADF_BASE_PATH "adf-overlay-engine%u.%u", dev->id, id);
-
-    int fd = open(filename, flags);
-    if (fd < 0)
-        return -errno;
-    return fd;
-}
-
-int adf_get_overlay_engine_data(int fd, struct adf_overlay_engine_data *data)
-{
-    int err;
-    int ret = 0;
-
-    memset(data, 0, sizeof(*data));
-
-    err = ioctl(fd, ADF_GET_OVERLAY_ENGINE_DATA, data);
-    if (err < 0)
-        return -errno;
-
-    if (data->n_supported_formats)
-        data->supported_formats = new __u32[data->n_supported_formats];
-
-    if (data->custom_data_size)
-      data->custom_data = new char[data->custom_data_size];
-
-    err = ioctl(fd, ADF_GET_OVERLAY_ENGINE_DATA, data);
-    if (err < 0) {
-        ret = -errno;
-        adf_free_overlay_engine_data(data);
-    }
-    return ret;
-}
-
-void adf_free_overlay_engine_data(struct adf_overlay_engine_data *data)
-{
-    delete [] data->supported_formats;
-    data->supported_formats = nullptr;
-    delete [] static_cast<char *>(data->custom_data);
-    data->custom_data = nullptr;
-}
-
-bool adf_overlay_engine_supports_format(int fd, __u32 format)
-{
-    struct adf_overlay_engine_data data;
-    bool ret = false;
-    size_t i;
-
-    int err = adf_get_overlay_engine_data(fd, &data);
-    if (err < 0)
-        return false;
-
-    if (data.supported_formats != nullptr) {
-        for (i = 0; i < data.n_supported_formats; i++) {
-            if (data.supported_formats[i] == format) {
-                ret = true;
-                break;
-            }
-        }
-    }
-
-    adf_free_overlay_engine_data(&data);
-    return ret;
-}
-
-int adf_set_event(int fd, enum adf_event_type type, bool enabled)
-{
-    struct adf_set_event data;
-
-    data.type = type;
-    data.enabled = enabled;
-
-    int err = ioctl(fd, ADF_SET_EVENT, &data);
-    if (err < 0)
-        return -errno;
-    return 0;
-}
-
-int adf_read_event(int fd, struct adf_event **event)
-{
-    struct adf_event header;
-    struct event_with_data {
-        struct adf_event base;
-        uint8_t data[0];
-    };
-    using unique_event = std::unique_ptr<event_with_data, decltype(&free)>;
-    size_t data_size;
-
-    int err = read(fd, &header, sizeof(header));
-    if (err < 0)
-        return -errno;
-    if ((size_t)err < sizeof(header))
-        return -EIO;
-    if (header.length < sizeof(header))
-        return -EIO;
-
-    // Again, we can't use new[] since the existing API says the caller should
-    // free() the returned event
-    auto event_ptr = static_cast<event_with_data *>(malloc(header.length));
-    unique_event event_ret{event_ptr, free};
-    if (!event_ret)
-        return -ENOMEM;
-    data_size = header.length - sizeof(header);
-
-    memcpy(event_ret.get(), &header, sizeof(header));
-    ssize_t read_size = read(fd, &event_ret->data, data_size);
-    if (read_size < 0)
-        return -errno;
-    if ((size_t)read_size < data_size)
-        return -EIO;
-
-    *event = &event_ret.release()->base;
-    return 0;
-}
-
-void adf_format_str(__u32 format, char buf[ADF_FORMAT_STR_SIZE])
-{
-    buf[0] = format & 0xFF;
-    buf[1] = (format >> 8) & 0xFF;
-    buf[2] = (format >> 16) & 0xFF;
-    buf[3] = (format >> 24) & 0xFF;
-    buf[4] = '\0';
-}
-
-static bool adf_find_simple_post_overlay_engine(struct adf_device *dev,
-        const __u32 *formats, size_t n_formats,
-        adf_id_t interface, adf_id_t *overlay_engine)
-{
-    adf_id_t *engs = nullptr;
-    ssize_t n_engs = adf_overlay_engines_for_interface(dev, interface, &engs);
-
-    if (engs == nullptr)
-        return false;
-
-    adf_id_t *filtered_engs = nullptr;
-    ssize_t n_filtered_engs = adf_overlay_engines_filter_by_format(dev,
-            formats, n_formats, engs, n_engs, &filtered_engs);
-    free(engs);
-
-    if (filtered_engs == nullptr)
-        return false;
-
-    *overlay_engine = filtered_engs[0];
-    free(filtered_engs);
-    return true;
-}
-
-static const __u32 any_rgb_format[] = {
-    DRM_FORMAT_C8,
-    DRM_FORMAT_RGB332,
-    DRM_FORMAT_BGR233,
-    DRM_FORMAT_XRGB1555,
-    DRM_FORMAT_XBGR1555,
-    DRM_FORMAT_RGBX5551,
-    DRM_FORMAT_BGRX5551,
-    DRM_FORMAT_ARGB1555,
-    DRM_FORMAT_ABGR1555,
-    DRM_FORMAT_RGBA5551,
-    DRM_FORMAT_BGRA5551,
-    DRM_FORMAT_RGB565,
-    DRM_FORMAT_BGR565,
-    DRM_FORMAT_RGB888,
-    DRM_FORMAT_BGR888,
-    DRM_FORMAT_XRGB8888,
-    DRM_FORMAT_XBGR8888,
-    DRM_FORMAT_RGBX8888,
-    DRM_FORMAT_BGRX8888,
-    DRM_FORMAT_XRGB2101010,
-    DRM_FORMAT_XBGR2101010,
-    DRM_FORMAT_RGBX1010102,
-    DRM_FORMAT_BGRX1010102,
-    DRM_FORMAT_ARGB2101010,
-    DRM_FORMAT_ABGR2101010,
-    DRM_FORMAT_RGBA1010102,
-    DRM_FORMAT_BGRA1010102,
-    DRM_FORMAT_ARGB8888,
-    DRM_FORMAT_ABGR8888,
-    DRM_FORMAT_RGBA8888,
-    DRM_FORMAT_BGRA8888,
-};
-
-int adf_find_simple_post_configuration(struct adf_device *dev,
-        const __u32 *formats, size_t n_formats,
-        adf_id_t *interface, adf_id_t *overlay_engine)
-{
-    adf_id_t *intfs = NULL;
-    ssize_t n_intfs = adf_interfaces(dev, &intfs);
-
-    if (n_intfs < 0)
-        return n_intfs;
-    else if (!intfs)
-        return -ENODEV;
-
-    adf_id_t *primary_intfs = nullptr;
-    ssize_t n_primary_intfs = adf_interfaces_filter_by_flag(dev,
-            ADF_INTF_FLAG_PRIMARY, intfs, n_intfs, &primary_intfs);
-    free(intfs);
-
-    if (n_primary_intfs < 0)
-        return n_primary_intfs;
-    else if (!primary_intfs)
-        return -ENODEV;
-
-    if (!formats) {
-        formats = any_rgb_format;
-        n_formats = sizeof(any_rgb_format) / sizeof(any_rgb_format[0]);
-    }
-
-    bool found = false;
-    ssize_t i = 0;
-    for (i = 0; i < n_primary_intfs; i++) {
-        found = adf_find_simple_post_overlay_engine(dev, formats, n_formats,
-                primary_intfs[i], overlay_engine);
-        if (found) {
-            *interface = primary_intfs[i];
-            break;
-        }
-    }
-    free(primary_intfs);
-
-    if (!found)
-        return -ENODEV;
-
-    return 0;
-}
diff --git a/deprecated-adf/libadf/include/adf/adf.h b/deprecated-adf/libadf/include/adf/adf.h
deleted file mode 100644
index e4c7b28..0000000
--- a/deprecated-adf/libadf/include/adf/adf.h
+++ /dev/null
@@ -1,295 +0,0 @@
-/*
- * Copyright (C) 2013 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 _LIBADF_ADF_H_
-#define _LIBADF_ADF_H_
-
-#include <stdint.h>
-#include <stdbool.h>
-#include <sys/cdefs.h>
-#include <sys/types.h>
-#include <video/adf.h>
-
-typedef __u32 adf_id_t;
-
-struct adf_device {
-    adf_id_t id;
-    int fd;
-};
-
-__BEGIN_DECLS
-
-/**
- * Enumerates all ADF devices.
- *
- * Returns the number of ADF devices, and sets ids to a list of device IDs.
- * The caller must free() the returned list of device IDs.
- *
- * On error, returns -errno.
- */
-ssize_t adf_devices(adf_id_t **ids);
-
-/**
- * Opens an ADF device.
- *
- * On error, returns -errno.
- */
-int adf_device_open(adf_id_t id, int flags, struct adf_device *dev);
-/**
- * Closes an ADF device.
- */
-void adf_device_close(struct adf_device *dev);
-/**
- * Reads the ADF device data.
- *
- * adf_get_device_data() allocates buffers inside data, which the caller
- * must free by calling adf_free_device_data().  On error, returns -errno.
- */
-int adf_get_device_data(struct adf_device *dev, struct adf_device_data *data);
-/**
- * Frees the device data returned by adf_get_device_data().
- */
-void adf_free_device_data(struct adf_device_data *data);
-
-/**
- * Atomically posts a new display configuration to the specified interfaces.
- *
- * Returns a sync fence fd that will fire when the configuration is removed
- * from the screen.  On error, returns -errno.
- */
-int adf_device_post(struct adf_device *dev,
-        adf_id_t *interfaces, size_t n_interfaces,
-        struct adf_buffer_config *bufs, size_t n_bufs,
-        void *custom_data, size_t custom_data_size);
-/**
- * Atomically posts a new display configuration to the specified interfaces.
- *
- * Compared to adf_device_post(), adf_device_post_v2():
- *
- *  (*) allows the client to choose the kind of sync fence returned
- *      (through complete_fence_type)
- *
- *  (*) stores the returned sync fence fd in a provided buffer, so the client
- *      can distinguish between a permission error (ret = -1) and a successful
- *      call that returns no fence (*complete_fence = -1)
- *
- * On error, returns -errno.
- *
- * On devices without the corresponding kernel support, returns -ENOTTY.
- */
-int adf_device_post_v2(struct adf_device *dev,
-        adf_id_t *interfaces, __u32 n_interfaces,
-        struct adf_buffer_config *bufs, __u32 n_bufs,
-        void *custom_data, __u64 custom_data_size,
-        enum adf_complete_fence_type complete_fence_type,
-        int *complete_fence);
-
-/**
- * Attaches the specified interface and overlay engine.
- */
-int adf_device_attach(struct adf_device *dev, adf_id_t overlay_engine,
-                      adf_id_t interface);
-/**
- * Detaches the specified interface and overlay engine.
- */
-int adf_device_detach(struct adf_device *dev, adf_id_t overlay_engine,
-                      adf_id_t interface);
-
-/**
- * Enumerates all interfaces belonging to an ADF device.
- *
- * The caller must free() the returned list of interface IDs.
- */
-ssize_t adf_interfaces(struct adf_device *dev, adf_id_t **interfaces);
-
-/**
- * Enumerates all interfaces which can be attached to the specified overlay
- * engine.
- *
- * The caller must free() the returned list of interface IDs.
- */
-ssize_t adf_interfaces_for_overlay_engine(struct adf_device *dev,
-        adf_id_t overlay_engine, adf_id_t **interfaces);
-/**
- * Filters a list of interfaces by type.
- *
- * Returns the number of matching interfaces, and sets out to a list of matching
- * interface IDs.  The caller must free() the returned list of interface IDs.
- *
- * On error, returns -errno.
- */
-ssize_t adf_interfaces_filter_by_type(struct adf_device *dev,
-        enum adf_interface_type type,
-        adf_id_t *in, size_t n_in, adf_id_t **out);
-/**
- * Filters a list of interfaces by flag.
- *
- * The caller must free() the returned list of interface IDs.
- */
-ssize_t adf_interfaces_filter_by_flag(struct adf_device *dev, __u32 flag,
-        adf_id_t *in, size_t n_in, adf_id_t **out);
-
-/**
- * Opens an ADF interface.
- *
- * Returns a file descriptor.  The caller must close() the fd when done.
- * On error, returns -errno.
- */
-int adf_interface_open(struct adf_device *dev, adf_id_t id, int flags);
-/**
- * Reads the interface data.
- *
- * adf_get_interface_data() allocates buffers inside data, which the caller
- * must free by calling adf_free_interface_data().  On error, returns -errno.
- */
-int adf_get_interface_data(int fd, struct adf_interface_data *data);
-/**
- * Frees the interface data returned by adf_get_interface_data().
- */
-void adf_free_interface_data(struct adf_interface_data *data);
-
-/**
- * Sets the interface's DPMS mode.
- */
-int adf_interface_blank(int fd, __u8 mode);
-/**
- * Sets the interface's display mode.
- */
-int adf_interface_set_mode(int fd, struct drm_mode_modeinfo *mode);
-/**
- * Allocates a single-plane RGB buffer of the specified size and format.
- *
- * Returns a dma-buf fd.  On error, returns -errno.
- */
-int adf_interface_simple_buffer_alloc(int fd, __u32 w, __u32 h,
-        __u32 format, __u32 *offset, __u32 *pitch);
-/**
- * Posts a single-plane RGB buffer to the display using the specified
- * overlay engine.
- *
- * Returns a sync fence fd that will fire when the buffer is removed
- * from the screen.  On error, returns -errno.
- */
-int adf_interface_simple_post(int fd, adf_id_t overlay_engine,
-        __u32 w, __u32 h, __u32 format, int buf_fd, __u32 offset,
-        __u32 pitch, int acquire_fence);
-/**
- * Posts a single-plane RGB buffer to the display using the specified
- * overlay engine.
- *
- * Compared to adf_interface_simple_post(), adf_interface_simple_post_v2():
- *
- *  (*) allows the client to choose the kind of sync fence returned
- *      (through complete_fence_type)
- *
- *  (*) stores the returned sync fence fd in a provided buffer, so the client
- *      can distinguish between a permission error (ret = -1) and a successful
- *      call that returns no fence (*complete_fence = -1)
- *
- * On error, returns -errno.
- *
- * On devices without the corresponding kernel support, returns -ENOTTY.
- */
-int adf_interface_simple_post_v2(int fd, adf_id_t overlay_engine,
-        __u32 w, __u32 h, __u32 format, int buf_fd, __u32 offset,
-        __u32 pitch, int acquire_fence,
-        enum adf_complete_fence_type complete_fence_type,
-        int *complete_fence);
-
-/**
- * Enumerates all overlay engines belonging to an ADF device.
- *
- * The caller must free() the returned list of overlay engine IDs.
- */
-ssize_t adf_overlay_engines(struct adf_device *dev, adf_id_t **overlay_engines);
-
-/**
- * Enumerates all overlay engines which can be attached to the specified
- * interface.
- *
- * The caller must free() the returned list of overlay engine IDs.
- */
-ssize_t adf_overlay_engines_for_interface(struct adf_device *dev,
-        adf_id_t interface, adf_id_t **overlay_engines);
-/**
- * Filters a list of overlay engines by supported buffer format.
- *
- * Returns the overlay engines which support at least one of the specified
- * formats.  The caller must free() the returned list of overlay engine IDs.
- */
-ssize_t adf_overlay_engines_filter_by_format(struct adf_device *dev,
-        const __u32 *formats, size_t n_formats, adf_id_t *in, size_t n_in,
-        adf_id_t **out);
-
-/**
- * Opens an ADF overlay engine.
- *
- * Returns a file descriptor.  The caller must close() the fd when done.
- * On error, returns -errno.
- */
-int adf_overlay_engine_open(struct adf_device *dev, adf_id_t id, int flags);
-/**
- * Reads the overlay engine data.
- *
- * adf_get_overlay_engine_data() allocates buffers inside data, which the caller
- * must free by calling adf_free_overlay_engine_data().  On error, returns
- * -errno.
- */
-int adf_get_overlay_engine_data(int fd, struct adf_overlay_engine_data *data);
-/**
- * Frees the overlay engine data returned by adf_get_overlay_engine_data().
- */
-void adf_free_overlay_engine_data(struct adf_overlay_engine_data *data);
-
-/**
- * Returns whether the overlay engine supports the specified format.
- */
-bool adf_overlay_engine_supports_format(int fd, __u32 format);
-
-/**
- * Subscribes or unsubscribes from the specified hardware event.
- */
-int adf_set_event(int fd, enum adf_event_type type, bool enabled);
-/**
- * Reads one event from the fd, blocking if needed.
- *
- * The caller must free() the returned buffer.  On error, returns -errno.
- */
-int adf_read_event(int fd, struct adf_event **event);
-
-#define ADF_FORMAT_STR_SIZE 5
-/**
- * Converts an ADF/DRM fourcc format to its string representation.
- */
-void adf_format_str(__u32 format, char buf[ADF_FORMAT_STR_SIZE]);
-
-/**
- * Finds an appropriate interface and overlay engine for a simple post.
- *
- * Specifically, finds the primary interface, and an overlay engine
- * that can be attached to the primary interface and supports one of the
- * specified formats.  The caller may pass a NULL formats list, to indicate that
- * any RGB format is acceptable.
- *
- * On error, returns -errno.
- */
-int adf_find_simple_post_configuration(struct adf_device *dev,
-        const __u32 *formats, size_t n_formats,
-        adf_id_t *interface, adf_id_t *overlay_engine);
-
-__END_DECLS
-
-#endif /* _LIBADF_ADF_H_ */
diff --git a/deprecated-adf/libadf/include/video/adf.h b/deprecated-adf/libadf/include/video/adf.h
deleted file mode 100644
index 692a425..0000000
--- a/deprecated-adf/libadf/include/video/adf.h
+++ /dev/null
@@ -1,209 +0,0 @@
-/****************************************************************************
- ****************************************************************************
- ***
- ***   This header was automatically generated from a Linux kernel header
- ***   of the same name, to make information necessary for userspace to
- ***   call into the kernel available to libc.  It contains only constants,
- ***   structures, and macros generated from the original header, and thus,
- ***   contains no copyrightable information.
- ***
- ***   To edit the content of this header, modify the corresponding
- ***   source file (e.g. under external/kernel-headers/original/) then
- ***   run bionic/libc/kernel/tools/update_all.py
- ***
- ***   Any manual change here will be lost the next time this script will
- ***   be run. You've been warned!
- ***
- ****************************************************************************
- ****************************************************************************/
-#ifndef _UAPI_VIDEO_ADF_H_
-#define _UAPI_VIDEO_ADF_H_
-#include <linux/ioctl.h>
-#include <linux/types.h>
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#include <drm/drm_fourcc.h>
-#include <drm/drm_mode.h>
-#define ADF_NAME_LEN 32
-#define ADF_MAX_CUSTOM_DATA_SIZE 4096
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-enum adf_interface_type {
-  ADF_INTF_DSI = 0,
-  ADF_INTF_eDP = 1,
-  ADF_INTF_DPI = 2,
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  ADF_INTF_VGA = 3,
-  ADF_INTF_DVI = 4,
-  ADF_INTF_HDMI = 5,
-  ADF_INTF_MEMORY = 6,
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  ADF_INTF_TYPE_DEVICE_CUSTOM = 128,
-  ADF_INTF_TYPE_MAX = (~(__u32) 0),
-};
-#define ADF_INTF_FLAG_PRIMARY (1 << 0)
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define ADF_INTF_FLAG_EXTERNAL (1 << 1)
-enum adf_event_type {
-  ADF_EVENT_VSYNC = 0,
-  ADF_EVENT_HOTPLUG = 1,
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  ADF_EVENT_DEVICE_CUSTOM = 128,
-  ADF_EVENT_TYPE_MAX = 255,
-};
-enum adf_complete_fence_type {
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  ADF_COMPLETE_FENCE_NONE = 0,
-  ADF_COMPLETE_FENCE_PRESENT = 1,
-  ADF_COMPLETE_FENCE_RELEASE = 2,
-};
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-struct adf_set_event {
-  __u8 type;
-  __u8 enabled;
-};
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-struct adf_event {
-  __u8 type;
-  __u32 length;
-};
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-struct adf_vsync_event {
-  struct adf_event base;
-  __aligned_u64 timestamp;
-};
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-struct adf_hotplug_event {
-  struct adf_event base;
-  __u8 connected;
-};
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define ADF_MAX_PLANES 4
-struct adf_buffer_config {
-  __u32 overlay_engine;
-  __u32 w;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  __u32 h;
-  __u32 format;
-  __s32 fd[ADF_MAX_PLANES];
-  __u32 offset[ADF_MAX_PLANES];
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  __u32 pitch[ADF_MAX_PLANES];
-  __u8 n_planes;
-  __s32 acquire_fence;
-};
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define ADF_MAX_BUFFERS (4096 / sizeof(struct adf_buffer_config))
-struct adf_post_config {
-  size_t n_interfaces;
-  __u32 __user * interfaces;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  size_t n_bufs;
-  struct adf_buffer_config __user * bufs;
-  size_t custom_data_size;
-  void __user * custom_data;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  __s32 complete_fence;
-};
-struct adf_post_config_v2 {
-  __u32 n_interfaces;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  __u64 interfaces;
-  __u32 n_bufs;
-  __u64 bufs;
-  __u64 custom_data_size;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  __u64 custom_data;
-  __s32 complete_fence;
-  __u8 complete_fence_type;
-};
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define ADF_MAX_INTERFACES (4096 / sizeof(__u32))
-struct adf_simple_buffer_alloc {
-  __u16 w;
-  __u16 h;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  __u32 format;
-  __s32 fd;
-  __u32 offset;
-  __u32 pitch;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-};
-struct adf_simple_post_config {
-  struct adf_buffer_config buf;
-  __s32 complete_fence;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-};
-struct adf_simple_post_config_v2 {
-  struct adf_buffer_config buf;
-  __s32 complete_fence;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  __u8 complete_fence_type;
-};
-struct adf_attachment_config {
-  __u32 overlay_engine;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  __u32 interface;
-};
-struct adf_device_data {
-  char name[ADF_NAME_LEN];
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  size_t n_attachments;
-  struct adf_attachment_config __user * attachments;
-  size_t n_allowed_attachments;
-  struct adf_attachment_config __user * allowed_attachments;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  size_t custom_data_size;
-  void __user * custom_data;
-};
-#define ADF_MAX_ATTACHMENTS (4096 / sizeof(struct adf_attachment_config))
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-struct adf_interface_data {
-  char name[ADF_NAME_LEN];
-  __u32 type;
-  __u32 id;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  __u32 flags;
-  __u8 dpms_state;
-  __u8 hotplug_detect;
-  __u16 width_mm;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  __u16 height_mm;
-  struct drm_mode_modeinfo current_mode;
-  size_t n_available_modes;
-  struct drm_mode_modeinfo __user * available_modes;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  size_t custom_data_size;
-  void __user * custom_data;
-};
-#define ADF_MAX_MODES (4096 / sizeof(struct drm_mode_modeinfo))
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-struct adf_overlay_engine_data {
-  char name[ADF_NAME_LEN];
-  size_t n_supported_formats;
-  __u32 __user * supported_formats;
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-  size_t custom_data_size;
-  void __user * custom_data;
-};
-#define ADF_MAX_SUPPORTED_FORMATS (4096 / sizeof(__u32))
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define ADF_IOCTL_TYPE 'D'
-#define ADF_IOCTL_NR_CUSTOM 128
-#define ADF_SET_EVENT _IOW(ADF_IOCTL_TYPE, 0, struct adf_set_event)
-#define ADF_BLANK _IOW(ADF_IOCTL_TYPE, 1, __u8)
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define ADF_POST_CONFIG _IOW(ADF_IOCTL_TYPE, 2, struct adf_post_config)
-#define ADF_SET_MODE _IOW(ADF_IOCTL_TYPE, 3, struct drm_mode_modeinfo)
-#define ADF_GET_DEVICE_DATA _IOR(ADF_IOCTL_TYPE, 4, struct adf_device_data)
-#define ADF_GET_INTERFACE_DATA _IOR(ADF_IOCTL_TYPE, 5, struct adf_interface_data)
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define ADF_GET_OVERLAY_ENGINE_DATA _IOR(ADF_IOCTL_TYPE, 6, struct adf_overlay_engine_data)
-#define ADF_SIMPLE_POST_CONFIG _IOW(ADF_IOCTL_TYPE, 7, struct adf_simple_post_config)
-#define ADF_SIMPLE_BUFFER_ALLOC _IOW(ADF_IOCTL_TYPE, 8, struct adf_simple_buffer_alloc)
-#define ADF_ATTACH _IOW(ADF_IOCTL_TYPE, 9, struct adf_attachment_config)
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-#define ADF_DETACH _IOW(ADF_IOCTL_TYPE, 10, struct adf_attachment_config)
-#define ADF_POST_CONFIG_V2 _IOW(ADF_IOCTL_TYPE, 11, struct adf_post_config_v2)
-#define ADF_SIMPLE_POST_CONFIG_V2 _IOW(ADF_IOCTL_TYPE, 12, struct adf_simple_post_config_v2)
-#endif
-/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
-
diff --git a/deprecated-adf/libadf/original-kernel-headers/video/adf.h b/deprecated-adf/libadf/original-kernel-headers/video/adf.h
deleted file mode 100644
index 8293c1d..0000000
--- a/deprecated-adf/libadf/original-kernel-headers/video/adf.h
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Copyright (C) 2013 Google, Inc.
- *
- * This software is licensed under the terms of the GNU General Public
- * License version 2, as published by the Free Software Foundation, and
- * may be copied, distributed, and modified under those terms.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- */
-
-#ifndef _UAPI_VIDEO_ADF_H_
-#define _UAPI_VIDEO_ADF_H_
-
-#include <linux/ioctl.h>
-#include <linux/types.h>
-
-#include <drm/drm_fourcc.h>
-#include <drm/drm_mode.h>
-
-#define ADF_NAME_LEN 32
-#define ADF_MAX_CUSTOM_DATA_SIZE 4096
-
-enum adf_interface_type {
-	ADF_INTF_DSI = 0,
-	ADF_INTF_eDP = 1,
-	ADF_INTF_DPI = 2,
-	ADF_INTF_VGA = 3,
-	ADF_INTF_DVI = 4,
-	ADF_INTF_HDMI = 5,
-	ADF_INTF_MEMORY = 6,
-	ADF_INTF_TYPE_DEVICE_CUSTOM = 128,
-	ADF_INTF_TYPE_MAX = (~(__u32)0),
-};
-
-#define ADF_INTF_FLAG_PRIMARY (1 << 0)
-#define ADF_INTF_FLAG_EXTERNAL (1 << 1)
-
-enum adf_event_type {
-	ADF_EVENT_VSYNC = 0,
-	ADF_EVENT_HOTPLUG = 1,
-	ADF_EVENT_DEVICE_CUSTOM = 128,
-	ADF_EVENT_TYPE_MAX = 255,
-};
-
-enum adf_complete_fence_type {
-	/* no fence */
-	ADF_COMPLETE_FENCE_NONE = 0,
-	/* fence fires when the configuration appears on the screen */
-	ADF_COMPLETE_FENCE_PRESENT = 1,
-	/* fence fires when the configuration leaves the screen */
-	ADF_COMPLETE_FENCE_RELEASE = 2,
-};
-
-/**
- * struct adf_set_event - start or stop subscribing to ADF events
- *
- * @type: the type of event to (un)subscribe
- * @enabled: subscribe or unsubscribe
- *
- * After subscribing to an event, userspace may poll() the ADF object's fd
- * to wait for events or read() to consume the event's data.
- *
- * ADF reserves event types 0 to %ADF_EVENT_DEVICE_CUSTOM-1 for its own events.
- * Devices may use event types %ADF_EVENT_DEVICE_CUSTOM to %ADF_EVENT_TYPE_MAX-1
- * for driver-private events.
- */
-struct adf_set_event {
-	__u8 type;
-	__u8 enabled;
-};
-
-/**
- * struct adf_event - common header for ADF event data
- *
- * @type: event type
- * @length: total size of event data, header inclusive
- */
-struct adf_event {
-	__u8 type;
-	__u32 length;
-};
-
-/**
- * struct adf_vsync_event - ADF vsync event
- *
- * @base: event header (see &struct adf_event)
- * @timestamp: time of vsync event, in nanoseconds
- */
-struct adf_vsync_event {
-	struct adf_event base;
-	__aligned_u64 timestamp;
-};
-
-/**
- * struct adf_vsync_event - ADF display hotplug event
- *
- * @base: event header (see &struct adf_event)
- * @connected: whether a display is now connected to the interface
- */
-struct adf_hotplug_event {
-	struct adf_event base;
-	__u8 connected;
-};
-
-#define ADF_MAX_PLANES 4
-/**
- * struct adf_buffer_config - description of buffer displayed by adf_post_config
- *
- * @overlay_engine: id of the target overlay engine
- * @w: width of display region in pixels
- * @h: height of display region in pixels
- * @format: DRM-style fourcc, see drm_fourcc.h for standard formats
- * @fd: dma_buf fd for each plane
- * @offset: location of first pixel to scan out, in bytes
- * @pitch: stride (i.e. length of a scanline including padding) in bytes
- * @n_planes: number of planes in buffer
- * @acquire_fence: sync_fence fd which will clear when the buffer is
- *	ready for display, or <0 if the buffer is already ready
- */
-struct adf_buffer_config {
-	__u32 overlay_engine;
-
-	__u32 w;
-	__u32 h;
-	__u32 format;
-
-	__s32 fd[ADF_MAX_PLANES];
-	__u32 offset[ADF_MAX_PLANES];
-	__u32 pitch[ADF_MAX_PLANES];
-	__u8 n_planes;
-
-	__s32 acquire_fence;
-};
-#define ADF_MAX_BUFFERS (4096 / sizeof(struct adf_buffer_config))
-
-/**
- * struct adf_post_config - request to flip to a new set of buffers
- *
- * This request is equivalent to &struct adf_post_config_v2 with
- * @complete_fence_type = %ADF_COMPLETE_FENCE_RELEASE.
- *
- * @n_interfaces: number of interfaces targeted by the flip (input)
- * @interfaces: ids of interfaces targeted by the flip (input)
- * @n_bufs: number of buffers displayed (input)
- * @bufs: description of buffers displayed (input)
- * @custom_data_size: size of driver-private data (input)
- * @custom_data: driver-private data (input)
- * @complete_fence: sync_fence fd which will clear when this
- *	configuration has left the screen (output)
- */
-struct adf_post_config {
-	size_t n_interfaces;
-	__u32 __user *interfaces;
-
-	size_t n_bufs;
-	struct adf_buffer_config __user *bufs;
-
-	size_t custom_data_size;
-	void __user *custom_data;
-
-	__s32 complete_fence;
-};
-
-/**
- * struct adf_post_config_v2 - request to flip to a new set of buffers
- *
- * @n_interfaces: number of interfaces targeted by the flip (input)
- * @interfaces: ids of interfaces targeted by the flip (input)
- * @n_bufs: number of buffers displayed (input)
- * @bufs: description of buffers displayed (input)
- * @custom_data_size: size of driver-private data (input)
- * @custom_data: driver-private data (input)
- * @complete_fence_type: one of &enum adf_complete_fence_type describing what
- * 	fence to return (input)
- * @complete_fence: sync_fence fd which will fire at the time
- * 	requested by @complete_fence_type (output)
- */
-struct adf_post_config_v2 {
-	__u32 n_interfaces;
-	__u64 interfaces; /* __u32 * packed into __u64 */
-
-	__u32 n_bufs;
-	__u64 bufs; /* struct adf_buffer_config * packed into __u64 */
-
-	__u64 custom_data_size;
-	__u64 custom_data; /* void * packed into __u64 */
-
-	__s32 complete_fence;
-	__u8 complete_fence_type;
-};
-#define ADF_MAX_INTERFACES (4096 / sizeof(__u32))
-
-/**
- * struct adf_simple_buffer_allocate - request to allocate a "simple" buffer
- *
- * @w: width of buffer in pixels (input)
- * @h: height of buffer in pixels (input)
- * @format: DRM-style fourcc (input)
- *
- * @fd: dma_buf fd (output)
- * @offset: location of first pixel, in bytes (output)
- * @pitch: length of a scanline including padding, in bytes (output)
- *
- * Simple buffers are analogous to DRM's "dumb" buffers.  They have a single
- * plane of linear RGB data which can be allocated and scanned out without
- * any driver-private ioctls or data.
- *
- * @format must be a standard RGB format defined in drm_fourcc.h.
- *
- * ADF clients must NOT assume that an interface can scan out a simple buffer
- * allocated by a different ADF interface, even if the two interfaces belong to
- * the same ADF device.
- */
-struct adf_simple_buffer_alloc {
-	__u16 w;
-	__u16 h;
-	__u32 format;
-
-	__s32 fd;
-	__u32 offset;
-	__u32 pitch;
-};
-
-/**
- * struct adf_simple_post_config - request to flip to a single buffer without
- * driver-private data
- *
- * This request is equivalent to &struct adf_simple_post_config_v2 with
- * @complete_fence_type = %ADF_COMPLETE_FENCE_RELEASE.
- *
- * @buf: description of buffer displayed (input)
- * @complete_fence: sync_fence fd which will clear when this buffer has left the
- * screen (output)
- */
-struct adf_simple_post_config {
-	struct adf_buffer_config buf;
-	__s32 complete_fence;
-};
-
-/**
- * struct adf_simple_post_config_v2 - request to flip to a single buffer without
- * driver-private data
- *
- * @buf: description of buffer displayed (input)
- * @complete_fence_type: one of &enum adf_complete_fence_type describing what
- * 	fence to return (input)
- * @complete_fence: sync_fence fd which will fire at the time
- * 	requested by @complete_fence_type (output)
- */
-struct adf_simple_post_config_v2 {
-	struct adf_buffer_config buf;
-	__s32 complete_fence;
-	__u8 complete_fence_type;
-};
-
-/**
- * struct adf_attachment_config - description of attachment between an overlay
- * engine and an interface
- *
- * @overlay_engine: id of the overlay engine
- * @interface: id of the interface
- */
-struct adf_attachment_config {
-	__u32 overlay_engine;
-	__u32 interface;
-};
-
-/**
- * struct adf_device_data - describes a display device
- *
- * @name: display device's name
- * @n_attachments: the number of current attachments
- * @attachments: list of current attachments
- * @n_allowed_attachments: the number of allowed attachments
- * @allowed_attachments: list of allowed attachments
- * @custom_data_size: size of driver-private data
- * @custom_data: driver-private data
- */
-struct adf_device_data {
-	char name[ADF_NAME_LEN];
-
-	size_t n_attachments;
-	struct adf_attachment_config __user *attachments;
-
-	size_t n_allowed_attachments;
-	struct adf_attachment_config __user *allowed_attachments;
-
-	size_t custom_data_size;
-	void __user *custom_data;
-};
-#define ADF_MAX_ATTACHMENTS (4096 / sizeof(struct adf_attachment_config))
-
-/**
- * struct adf_device_data - describes a display interface
- *
- * @name: display interface's name
- * @type: interface type (see enum @adf_interface_type)
- * @id: which interface of type @type;
- *	e.g. interface DSI.1 -> @type=@ADF_INTF_TYPE_DSI, @id=1
- * @flags: informational flags (bitmask of %ADF_INTF_FLAG_* values)
- * @dpms_state: DPMS state (one of @DRM_MODE_DPMS_* defined in drm_mode.h)
- * @hotplug_detect: whether a display is plugged in
- * @width_mm: screen width in millimeters, or 0 if unknown
- * @height_mm: screen height in millimeters, or 0 if unknown
- * @current_mode: current display mode
- * @n_available_modes: the number of hardware display modes
- * @available_modes: list of hardware display modes
- * @custom_data_size: size of driver-private data
- * @custom_data: driver-private data
- */
-struct adf_interface_data {
-	char name[ADF_NAME_LEN];
-
-	__u32 type;
-	__u32 id;
-	/* e.g. type=ADF_INTF_TYPE_DSI, id=1 => DSI.1 */
-	__u32 flags;
-
-	__u8 dpms_state;
-	__u8 hotplug_detect;
-	__u16 width_mm;
-	__u16 height_mm;
-
-	struct drm_mode_modeinfo current_mode;
-	size_t n_available_modes;
-	struct drm_mode_modeinfo __user *available_modes;
-
-	size_t custom_data_size;
-	void __user *custom_data;
-};
-#define ADF_MAX_MODES (4096 / sizeof(struct drm_mode_modeinfo))
-
-/**
- * struct adf_overlay_engine_data - describes an overlay engine
- *
- * @name: overlay engine's name
- * @n_supported_formats: number of supported formats
- * @supported_formats: list of supported formats
- * @custom_data_size: size of driver-private data
- * @custom_data: driver-private data
- */
-struct adf_overlay_engine_data {
-	char name[ADF_NAME_LEN];
-
-	size_t n_supported_formats;
-	__u32 __user *supported_formats;
-
-	size_t custom_data_size;
-	void __user *custom_data;
-};
-#define ADF_MAX_SUPPORTED_FORMATS (4096 / sizeof(__u32))
-
-#define ADF_IOCTL_TYPE		'D'
-#define ADF_IOCTL_NR_CUSTOM	128
-
-#define ADF_SET_EVENT		_IOW(ADF_IOCTL_TYPE, 0, struct adf_set_event)
-#define ADF_BLANK		_IOW(ADF_IOCTL_TYPE, 1, __u8)
-#define ADF_POST_CONFIG		_IOW(ADF_IOCTL_TYPE, 2, struct adf_post_config)
-#define ADF_SET_MODE		_IOW(ADF_IOCTL_TYPE, 3, \
-					struct drm_mode_modeinfo)
-#define ADF_GET_DEVICE_DATA	_IOR(ADF_IOCTL_TYPE, 4, struct adf_device_data)
-#define ADF_GET_INTERFACE_DATA	_IOR(ADF_IOCTL_TYPE, 5, \
-					struct adf_interface_data)
-#define ADF_GET_OVERLAY_ENGINE_DATA \
-				_IOR(ADF_IOCTL_TYPE, 6, \
-					struct adf_overlay_engine_data)
-#define ADF_SIMPLE_POST_CONFIG	_IOW(ADF_IOCTL_TYPE, 7, \
-					struct adf_simple_post_config)
-#define ADF_SIMPLE_BUFFER_ALLOC	_IOW(ADF_IOCTL_TYPE, 8, \
-					struct adf_simple_buffer_alloc)
-#define ADF_ATTACH		_IOW(ADF_IOCTL_TYPE, 9, \
-					struct adf_attachment_config)
-#define ADF_DETACH		_IOW(ADF_IOCTL_TYPE, 10, \
-					struct adf_attachment_config)
-
-#define ADF_POST_CONFIG_V2	_IOW(ADF_IOCTL_TYPE, 11, \
-					struct adf_post_config_v2)
-#define ADF_SIMPLE_POST_CONFIG_V2 \
-				_IOW(ADF_IOCTL_TYPE, 12, \
-					struct adf_simple_post_config_v2)
-
-#endif /* _UAPI_VIDEO_ADF_H_ */
diff --git a/deprecated-adf/libadf/tests/adf_test.cpp b/deprecated-adf/libadf/tests/adf_test.cpp
deleted file mode 100644
index 82a91f4..0000000
--- a/deprecated-adf/libadf/tests/adf_test.cpp
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <errno.h>
-#include <fcntl.h>
-
-#include <adf/adf.h>
-#include <gtest/gtest.h>
-#include <sys/mman.h>
-#include <sync/sync.h>
-
-class AdfTest : public testing::Test {
-public:
-    AdfTest() : intf_id(0), intf(-1), eng_id(0), eng(-1) { }
-
-    virtual void SetUp() {
-        int err = adf_device_open(dev_id, O_RDWR, &dev);
-        ASSERT_GE(err, 0) << "opening ADF device " << dev_id <<
-                " failed: " << strerror(-err);
-
-        err = adf_find_simple_post_configuration(&dev, fmt8888, n_fmt8888,
-                &intf_id, &eng_id);
-        ASSERT_GE(err, 0) << "finding ADF configuration failed: " <<
-                strerror(-err);
-
-        intf = adf_interface_open(&dev, intf_id, O_RDWR);
-        ASSERT_GE(intf, 0) << "opening ADF interface " << dev_id << "." <<
-                intf_id << " failed: " << strerror(-intf);
-
-        eng = adf_overlay_engine_open(&dev, eng_id, O_RDWR);
-        ASSERT_GE(eng, 0) << "opening ADF overlay engine " << dev_id << "." <<
-                eng_id << " failed: " << strerror(-eng);
-    }
-
-    virtual void TearDown() {
-        if (eng >= 0)
-            close(eng);
-        if (intf >= 0)
-            close(intf);
-        adf_device_close(&dev);
-    }
-
-    void get8888Format(uint32_t &fmt, char fmt_str[ADF_FORMAT_STR_SIZE]) {
-        adf_overlay_engine_data data;
-        int err = adf_get_overlay_engine_data(eng, &data);
-        ASSERT_GE(err, 0) << "getting ADF overlay engine data failed: " <<
-                strerror(-err);
-
-        for (size_t i = 0; i < data.n_supported_formats; i++) {
-            for (size_t j = 0; j < n_fmt8888; j++) {
-                if (data.supported_formats[i] == fmt8888[j]) {
-                    fmt = data.supported_formats[i];
-                    adf_format_str(fmt, fmt_str);
-                    adf_free_overlay_engine_data(&data);
-                    return;
-                }
-            }
-        }
-
-        adf_free_overlay_engine_data(&data);
-        FAIL(); /* this should never happen */
-    }
-
-    /* various helpers to call ADF and die on failure */
-
-    void getInterfaceData(adf_interface_data &data) {
-         int err = adf_get_interface_data(intf, &data);
-         ASSERT_GE(err, 0) << "getting ADF interface data failed: " <<
-                 strerror(-err);
-    }
-
-    void getCurrentMode(uint32_t &w, uint32_t &h) {
-        adf_interface_data data;
-        ASSERT_NO_FATAL_FAILURE(getInterfaceData(data));
-        w = data.current_mode.hdisplay;
-        h = data.current_mode.vdisplay;
-        adf_free_interface_data(&data);
-    }
-
-    void blank(uint8_t mode) {
-        int err = adf_interface_blank(intf, mode);
-        ASSERT_FALSE(err < 0 && err != -EBUSY) <<
-                "unblanking interface failed: " << strerror(-err);
-    }
-
-    void attach() {
-        int err = adf_device_attach(&dev, eng_id, intf_id);
-        ASSERT_FALSE(err < 0 && err != -EALREADY) <<
-                "attaching overlay engine " << eng_id << " to interface " <<
-                intf_id << " failed: " << strerror(-err);
-    }
-
-    void detach() {
-        int err = adf_device_detach(&dev, eng_id, intf_id);
-        ASSERT_FALSE(err < 0 && err != -EINVAL) <<
-                "detaching overlay engine " << eng_id << " from interface " <<
-                intf_id << " failed: " << strerror(-err);
-    }
-
-    void readVsyncTimestamp(uint64_t &timestamp) {
-        adf_event *event;
-        int err = adf_read_event(intf, &event);
-        ASSERT_GE(err, 0) << "reading ADF event failed: " << strerror(-err);
-
-        ASSERT_EQ(ADF_EVENT_VSYNC, event->type);
-        ASSERT_EQ(sizeof(adf_vsync_event), event->length);
-
-        adf_vsync_event *vsync_event =
-                reinterpret_cast<adf_vsync_event *>(event);
-        timestamp = vsync_event->timestamp;
-        free(event);
-    }
-
-    void drawCheckerboard(uint32_t &w, uint32_t &h, uint32_t &format,
-            char format_str[ADF_FORMAT_STR_SIZE], int &buf_fd, uint32_t &offset,
-            uint32_t &pitch) {
-        ASSERT_NO_FATAL_FAILURE(getCurrentMode(w, h));
-        ASSERT_NO_FATAL_FAILURE(get8888Format(format, format_str));
-
-        buf_fd = adf_interface_simple_buffer_alloc(intf, w, h, format, &offset,
-                &pitch);
-        ASSERT_GE(buf_fd, 0) << "allocating " << w << "x" << h << " " <<
-                format_str << " buffer failed: " << strerror(-buf_fd);
-        EXPECT_GE(pitch, w * 4);
-
-        void *mapped = mmap(NULL, pitch * h, PROT_WRITE, MAP_SHARED, buf_fd,
-                offset);
-        ASSERT_NE(mapped, MAP_FAILED) << "mapping " << w << "x" << h << " " <<
-                format_str << " buffer failed: " << strerror(-errno);
-
-        uint8_t *buf8 = static_cast<uint8_t *>(mapped);
-        for (uint32_t y = 0; y < h / 2; y++) {
-            uint32_t *scanline = reinterpret_cast<uint32_t *>(buf8 + y * pitch);
-            for (uint32_t x = 0; x < w / 2; x++)
-                scanline[x] = 0xFF0000FF;
-            for (uint32_t x = w / 2; x < w; x++)
-                scanline[x] = 0xFF00FFFF;
-        }
-        for (uint32_t y = h / 2; y < h; y++) {
-            uint32_t *scanline = reinterpret_cast<uint32_t *>(buf8 + y * pitch);
-            for (uint32_t x = 0; x < w / 2; x++)
-                scanline[x] = 0xFFFF00FF;
-            for (uint32_t x = w / 2; x < w; x++)
-                scanline[x] = 0xFFFFFFFF;
-        }
-
-        munmap(mapped, pitch * h);
-    }
-
-protected:
-    adf_device dev;
-    adf_id_t intf_id;
-    int intf;
-    adf_id_t eng_id;
-    int eng;
-
-private:
-    const static adf_id_t dev_id;
-    const static __u32 fmt8888[];
-    const static size_t n_fmt8888;
-};
-
-const adf_id_t AdfTest::dev_id = 0;
-
-const __u32 AdfTest::fmt8888[] = {
-   DRM_FORMAT_XRGB8888,
-   DRM_FORMAT_XBGR8888,
-   DRM_FORMAT_RGBX8888,
-   DRM_FORMAT_BGRX8888,
-   DRM_FORMAT_ARGB8888,
-   DRM_FORMAT_ABGR8888,
-   DRM_FORMAT_RGBA8888,
-   DRM_FORMAT_BGRA8888
-};
-const size_t AdfTest::n_fmt8888 = sizeof(fmt8888) / sizeof(fmt8888[0]);
-
-TEST(adf, devices) {
-    adf_id_t *devs = nullptr;
-    ssize_t n_devs = adf_devices(&devs);
-    free(devs);
-
-    ASSERT_GE(n_devs, 0) << "enumerating ADF devices failed: " <<
-            strerror(-n_devs);
-    ASSERT_TRUE(devs != NULL);
-}
-
-TEST_F(AdfTest, device_data) {
-    adf_device_data data;
-    int err = adf_get_device_data(&dev, &data);
-    ASSERT_GE(err, 0) << "getting ADF device data failed: " << strerror(-err);
-
-    EXPECT_LT(data.n_attachments, ADF_MAX_ATTACHMENTS);
-    EXPECT_GT(data.n_allowed_attachments, 0U);
-    EXPECT_LT(data.n_allowed_attachments, ADF_MAX_ATTACHMENTS);
-    EXPECT_LT(data.custom_data_size, (size_t)ADF_MAX_CUSTOM_DATA_SIZE);
-    adf_free_device_data(&data);
-}
-
-TEST_F(AdfTest, interface_data) {
-    adf_interface_data data;
-    ASSERT_NO_FATAL_FAILURE(getInterfaceData(data));
-
-    EXPECT_LT(data.type, ADF_INTF_TYPE_MAX);
-    EXPECT_LE(data.dpms_state, DRM_MODE_DPMS_OFF);
-    EXPECT_EQ(1, data.hotplug_detect);
-    EXPECT_GT(data.n_available_modes, 0U);
-    EXPECT_LT(data.custom_data_size, (size_t)ADF_MAX_CUSTOM_DATA_SIZE);
-    adf_free_interface_data(&data);
-}
-
-TEST_F(AdfTest, overlay_engine_data) {
-    adf_overlay_engine_data data;
-    int err = adf_get_overlay_engine_data(eng, &data);
-    ASSERT_GE(err, 0) << "getting ADF overlay engine failed: " <<
-            strerror(-err);
-
-    EXPECT_GT(data.n_supported_formats, 0U);
-    EXPECT_LT(data.n_supported_formats, ADF_MAX_SUPPORTED_FORMATS);
-    EXPECT_LT(data.custom_data_size, (size_t)ADF_MAX_CUSTOM_DATA_SIZE);
-    adf_free_overlay_engine_data(&data);
-}
-
-TEST_F(AdfTest, blank) {
-    int err = adf_interface_blank(intf, (uint8_t)-1);
-    EXPECT_EQ(-EINVAL, err) << "setting bogus DPMS mode should have failed";
-
-    err = adf_interface_blank(eng, DRM_MODE_DPMS_OFF);
-    EXPECT_EQ(-EINVAL, err) << "blanking overlay engine should have failed";
-
-    ASSERT_NO_FATAL_FAILURE(blank(DRM_MODE_DPMS_OFF));
-    err = adf_interface_blank(intf, DRM_MODE_DPMS_OFF);
-    EXPECT_EQ(-EBUSY, err) << "blanking interface twice should have failed";
-
-    ASSERT_NO_FATAL_FAILURE(blank(DRM_MODE_DPMS_ON));
-    err = adf_interface_blank(intf, DRM_MODE_DPMS_ON);
-    EXPECT_EQ(-EBUSY, err) << "unblanking interface twice should have failed";
-
-    adf_interface_data data;
-    ASSERT_NO_FATAL_FAILURE(getInterfaceData(data));
-    EXPECT_EQ(DRM_MODE_DPMS_ON, data.dpms_state);
-    adf_free_interface_data(&data);
-}
-
-TEST_F(AdfTest, event) {
-    int err = adf_set_event(intf, ADF_EVENT_TYPE_MAX, true);
-    EXPECT_EQ(-EINVAL, err) << "enabling bogus ADF event should have failed";
-
-    err = adf_set_event(intf, ADF_EVENT_TYPE_MAX, false);
-    EXPECT_EQ(-EINVAL, err) << "disabling bogus ADF event should have failed";
-
-    err = adf_set_event(intf, ADF_EVENT_VSYNC, true);
-    ASSERT_GE(err, 0) << "enabling vsync event failed: " << strerror(-err);
-
-    err = adf_set_event(intf, ADF_EVENT_VSYNC, true);
-    EXPECT_EQ(-EALREADY, err) <<
-            "enabling vsync event twice should have failed";
-
-    ASSERT_NO_FATAL_FAILURE(blank(DRM_MODE_DPMS_ON));
-
-    uint64_t timestamp1, timestamp2;
-    ASSERT_NO_FATAL_FAILURE(readVsyncTimestamp(timestamp1));
-    ASSERT_NO_FATAL_FAILURE(readVsyncTimestamp(timestamp2));
-    EXPECT_GT(timestamp2, timestamp1);
-
-    err = adf_set_event(intf, ADF_EVENT_VSYNC, false);
-    EXPECT_GE(err, 0) << "disabling vsync event failed: " << strerror(-err);
-
-    err = adf_set_event(intf, ADF_EVENT_VSYNC, false);
-    EXPECT_EQ(-EALREADY, err) <<
-            "disabling vsync event twice should have failed";
-}
-
-TEST_F(AdfTest, attach) {
-    ASSERT_NO_FATAL_FAILURE(attach());
-    int err = adf_device_attach(&dev, eng_id, intf_id);
-    EXPECT_EQ(-EALREADY, err) << "attaching overlay engine " << eng_id <<
-            " to interface " << intf_id << " twice should have failed";
-
-    ASSERT_NO_FATAL_FAILURE(detach());
-    err = adf_device_detach(&dev, eng_id, intf_id);
-    EXPECT_EQ(-EINVAL, err) << "detaching overlay engine " << eng_id <<
-            " from interface " << intf_id << " twice should have failed";
-
-    err = adf_device_attach(&dev, eng_id, ADF_MAX_INTERFACES);
-    EXPECT_EQ(-EINVAL, err) << "attaching overlay engine " << eng_id <<
-            " to bogus interface should have failed";
-
-    err = adf_device_detach(&dev, eng_id, ADF_MAX_INTERFACES);
-    EXPECT_EQ(-EINVAL, err) << "detaching overlay engine " << eng_id <<
-            " from bogus interface should have failed";
-}
-
-TEST_F(AdfTest, simple_buffer_alloc) {
-    uint32_t w = 0, h = 0;
-    ASSERT_NO_FATAL_FAILURE(getCurrentMode(w, h));
-
-    uint32_t format;
-    char format_str[ADF_FORMAT_STR_SIZE];
-    ASSERT_NO_FATAL_FAILURE(get8888Format(format, format_str));
-
-    uint32_t offset;
-    uint32_t pitch;
-    int buf_fd = adf_interface_simple_buffer_alloc(intf, w, h, format, &offset,
-            &pitch);
-    EXPECT_GE(buf_fd, 0) << "allocating " << w << "x" << h << " " <<
-            format_str << " buffer failed: " << strerror(-buf_fd);
-    EXPECT_GE(pitch, w * 4);
-    close(buf_fd);
-
-    buf_fd = adf_interface_simple_buffer_alloc(intf, w, h, 0xDEADBEEF, &offset,
-            &pitch);
-    /* n.b.: ADF only allows simple buffers with built-in RGB formats,
-       so this should fail even if a driver supports custom format 0xDEADBEEF */
-    EXPECT_EQ(-EINVAL, buf_fd) <<
-            "allocating buffer with bogus format should have failed";
-}
-
-TEST_F(AdfTest, simple_buffer) {
-    int buf_fd;
-    uint32_t w, h, format, offset, pitch;
-    char format_str[ADF_FORMAT_STR_SIZE];
-    ASSERT_NO_FATAL_FAILURE(drawCheckerboard(w, h, format, format_str,
-            buf_fd, offset, pitch));
-
-    ASSERT_NO_FATAL_FAILURE(attach());
-    ASSERT_NO_FATAL_FAILURE(blank(DRM_MODE_DPMS_ON));
-
-    int release_fence = adf_interface_simple_post(intf, eng_id, w, h, format,
-            buf_fd, offset, pitch, -1);
-    close(buf_fd);
-    ASSERT_GE(release_fence, 0) << "posting " << w << "x" << h << " " <<
-            format_str << " buffer failed: " << strerror(-release_fence);
-    close(release_fence);
-}
-
-TEST_F(AdfTest, simple_buffer_v2) {
-    int buf_fd;
-    uint32_t w, h, format, offset, pitch;
-    char format_str[ADF_FORMAT_STR_SIZE];
-    ASSERT_NO_FATAL_FAILURE(drawCheckerboard(w, h, format, format_str,
-            buf_fd, offset, pitch));
-
-    ASSERT_NO_FATAL_FAILURE(attach());
-    ASSERT_NO_FATAL_FAILURE(blank(DRM_MODE_DPMS_ON));
-
-    int config_1_release;
-    int err = adf_interface_simple_post_v2(intf, eng_id, w, h,
-            format, buf_fd, offset, pitch, -1, ADF_COMPLETE_FENCE_RELEASE,
-            &config_1_release);
-    if (err == -ENOTTY) {
-        GTEST_LOG_(INFO) << "ADF_SIMPLE_POST_CONFIG_V2 not supported on this kernel";
-        return;
-    }
-    ASSERT_GE(err, 0) << "posting " << w << "x" << h << " " <<
-            format_str << " buffer failed: " << strerror(-err);
-
-    err = sync_wait(config_1_release, 1000);
-    ASSERT_EQ(-1, err) <<
-            "waiting for config 1's release fence should not have suceeded";
-    ASSERT_EQ(ETIME, errno) <<
-            "config 1's release fence should have timed out, but failed instead: " <<
-            strerror(errno);
-
-    int config_2_present;
-    err = adf_interface_simple_post_v2(intf, eng_id, w, h,
-            format, buf_fd, offset, pitch, -1, ADF_COMPLETE_FENCE_PRESENT,
-            &config_2_present);
-    ASSERT_GE(err, 0) << "posting " << w << "x" << h << " " <<
-            format_str << " buffer failed: " << strerror(-err);
-
-    err = sync_wait(config_2_present, 1000);
-    ASSERT_EQ(0, err) <<
-            "waiting for config 2's present fence failed: " << strerror(errno);
-    err = sync_wait(config_1_release, 0);
-    ASSERT_EQ(0, err) <<
-            "waiting for config 1's release fence failed: " << strerror(errno);
-    close(config_1_release);
-    close(config_2_present);
-
-    int config_3_no_fence;
-    err = adf_interface_simple_post_v2(intf, eng_id, w, h,
-            format, buf_fd, offset, pitch, -1, ADF_COMPLETE_FENCE_NONE,
-            &config_3_no_fence);
-    ASSERT_GE(err, 0) << "posting " << w << "x" << h << " " <<
-            format_str << " buffer failed: " << strerror(-err);
-    ASSERT_EQ(-1, config_3_no_fence) <<
-            "fence returned even though the fence type was ADF_COMPLETE_FENCE_NONE";
-
-    close(buf_fd);
-}
diff --git a/deprecated-adf/libadfhwc/adfhwc.cpp b/deprecated-adf/libadfhwc/adfhwc.cpp
deleted file mode 100644
index 63c0f75..0000000
--- a/deprecated-adf/libadfhwc/adfhwc.cpp
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * Copyright (C) 2013 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 <fcntl.h>
-#include <malloc.h>
-#include <poll.h>
-#include <pthread.h>
-#include <sys/resource.h>
-
-#include <log/log.h>
-#include <utils/Vector.h>
-
-#include <adf/adf.h>
-#include <adfhwc/adfhwc.h>
-
-struct adf_hwc_helper {
-    adf_hwc_event_callbacks const *event_cb;
-    void *event_cb_data;
-
-    pthread_t event_thread;
-
-    android::Vector<int> intf_fds;
-    android::Vector<drm_mode_modeinfo> display_configs;
-};
-
-template<typename T> inline T min(T a, T b) { return (a < b) ? a : b; }
-
-int adf_eventControl(struct adf_hwc_helper *dev, int disp, int event,
-        int enabled)
-{
-    if (enabled != !!enabled)
-        return -EINVAL;
-
-    if ((size_t)disp >= dev->intf_fds.size())
-        return -EINVAL;
-
-    switch (event) {
-    case HWC_EVENT_VSYNC:
-        return adf_set_event(dev->intf_fds[disp], ADF_EVENT_VSYNC, enabled);
-    }
-
-    return -EINVAL;
-}
-
-static inline int32_t dpi(uint16_t res, uint16_t size_mm)
-{
-    if (size_mm)
-        return 1000 * (res * 25.4f) / size_mm;
-    return 0;
-}
-
-int adf_blank(struct adf_hwc_helper *dev, int disp, int blank)
-{
-    if ((size_t)disp >= dev->intf_fds.size())
-        return -EINVAL;
-
-    uint8_t dpms_mode = blank ? DRM_MODE_DPMS_OFF : DRM_MODE_DPMS_ON;
-    return adf_interface_blank(dev->intf_fds[disp], dpms_mode);
-}
-
-int adf_query_display_types_supported(struct adf_hwc_helper *dev, int *value)
-{
-    *value = 0;
-    if (dev->intf_fds.size() > 0)
-        *value |= HWC_DISPLAY_PRIMARY_BIT;
-    if (dev->intf_fds.size() > 1)
-        *value |= HWC_DISPLAY_EXTERNAL_BIT;
-
-    return 0;
-}
-
-int adf_getDisplayConfigs(struct adf_hwc_helper *dev, int disp,
-        uint32_t *configs, size_t *numConfigs)
-{
-    if ((size_t)disp >= dev->intf_fds.size())
-        return -EINVAL;
-
-    adf_interface_data data;
-    int err = adf_get_interface_data(dev->intf_fds[disp], &data);
-    if (err < 0) {
-        ALOGE("failed to get ADF interface data: %s", strerror(err));
-        return err;
-    }
-
-    if (!data.hotplug_detect)
-        return -ENODEV;
-
-    android::Vector<drm_mode_modeinfo *> unique_configs;
-    unique_configs.push_back(&data.current_mode);
-    for (size_t i = 0; i < data.n_available_modes; i++)
-        if (memcmp(&data.available_modes[i], &data.current_mode,
-                sizeof(data.current_mode)))
-            unique_configs.push_back(&data.available_modes[i]);
-
-    for (size_t i = 0; i < min(*numConfigs, unique_configs.size()); i++) {
-        configs[i] = dev->display_configs.size();
-        dev->display_configs.push_back(*unique_configs[i]);
-    }
-    *numConfigs = unique_configs.size();
-
-    adf_free_interface_data(&data);
-    return 0;
-}
-
-static int32_t adf_display_attribute(const adf_interface_data &data,
-        const drm_mode_modeinfo &mode, const uint32_t attribute)
-{
-    switch (attribute) {
-    case HWC_DISPLAY_VSYNC_PERIOD:
-        if (mode.vrefresh)
-            return 1000000000 / mode.vrefresh;
-        return 0;
-
-    case HWC_DISPLAY_WIDTH:
-        return mode.hdisplay;
-
-    case HWC_DISPLAY_HEIGHT:
-        return mode.vdisplay;
-
-    case HWC_DISPLAY_DPI_X:
-        return dpi(mode.hdisplay, data.width_mm);
-
-    case HWC_DISPLAY_DPI_Y:
-        return dpi(mode.vdisplay, data.height_mm);
-
-    default:
-        ALOGE("unknown display attribute %u", attribute);
-        return -EINVAL;
-    }
-}
-
-int adf_getDisplayAttributes(struct adf_hwc_helper *dev, int disp,
-        uint32_t config, const uint32_t *attributes, int32_t *values)
-{
-    if ((size_t)disp >= dev->intf_fds.size())
-        return -EINVAL;
-
-    if (config >= dev->display_configs.size())
-        return -EINVAL;
-
-    adf_interface_data data;
-    int err = adf_get_interface_data(dev->intf_fds[disp], &data);
-    if (err < 0) {
-        ALOGE("failed to get ADF interface data: %s", strerror(err));
-        return err;
-    }
-
-    for (int i = 0; attributes[i] != HWC_DISPLAY_NO_ATTRIBUTE; i++)
-        values[i] = adf_display_attribute(data, dev->display_configs[config],
-                attributes[i]);
-
-    adf_free_interface_data(&data);
-    return 0;
-}
-
-static int32_t adf_display_attribute_hwc2(const adf_interface_data &data,
-        const drm_mode_modeinfo &mode, const uint32_t attribute)
-{
-    switch (attribute) {
-    case HWC2_ATTRIBUTE_VSYNC_PERIOD:
-        if (mode.vrefresh)
-            return 1000000000 / mode.vrefresh;
-        return 0;
-
-    case HWC2_ATTRIBUTE_WIDTH:
-        return mode.hdisplay;
-
-    case HWC2_ATTRIBUTE_HEIGHT:
-        return mode.vdisplay;
-
-    case HWC2_ATTRIBUTE_DPI_X:
-        return dpi(mode.hdisplay, data.width_mm);
-
-    case HWC2_ATTRIBUTE_DPI_Y:
-        return dpi(mode.vdisplay, data.height_mm);
-
-    default:
-        ALOGE("unknown display attribute %u", attribute);
-        return -EINVAL;
-    }
-}
-
-int adf_getDisplayAttributes_hwc2(struct adf_hwc_helper *dev, int disp,
-        uint32_t config, const uint32_t *attributes, int32_t *values)
-{
-    if ((size_t)disp >= dev->intf_fds.size())
-        return -EINVAL;
-
-    if (config >= dev->display_configs.size())
-        return -EINVAL;
-
-    adf_interface_data data;
-    int err = adf_get_interface_data(dev->intf_fds[disp], &data);
-    if (err < 0) {
-        ALOGE("failed to get ADF interface data: %s", strerror(err));
-        return err;
-    }
-
-    for (int i = 0; attributes[i] != HWC2_ATTRIBUTE_INVALID; i++)
-        values[i] = adf_display_attribute_hwc2(data,
-                dev->display_configs[config], attributes[i]);
-
-    adf_free_interface_data(&data);
-    return 0;
-}
-
-int adf_set_active_config_hwc2(struct adf_hwc_helper *dev, int disp,
-        uint32_t config)
-{
-    if ((size_t)disp >= dev->intf_fds.size())
-        return -EINVAL;
-
-    if (config >= dev->display_configs.size())
-        return -EINVAL;
-
-    struct drm_mode_modeinfo mode = dev->display_configs[config];
-
-    return adf_interface_set_mode(dev->intf_fds[disp], &mode);
-}
-
-static void handle_adf_event(struct adf_hwc_helper *dev, int disp)
-{
-    adf_event *event;
-    int err = adf_read_event(dev->intf_fds[disp], &event);
-    if (err < 0) {
-        ALOGE("error reading event from display %d: %s", disp, strerror(err));
-        return;
-    }
-
-    void *vsync_temp;
-    adf_vsync_event *vsync;
-    adf_hotplug_event *hotplug;
-
-    switch (event->type) {
-    case ADF_EVENT_VSYNC:
-        vsync_temp = event;
-        vsync = static_cast<adf_vsync_event *>(vsync_temp);
-        // casting directly to adf_vsync_event * makes g++ warn about
-        // potential alignment issues that don't apply here
-        dev->event_cb->vsync(dev->event_cb_data, disp, vsync->timestamp);
-        break;
-    case ADF_EVENT_HOTPLUG:
-        hotplug = reinterpret_cast<adf_hotplug_event *>(event);
-        dev->event_cb->hotplug(dev->event_cb_data, disp, hotplug->connected);
-        break;
-    default:
-        if (event->type < ADF_EVENT_DEVICE_CUSTOM)
-            ALOGW("unrecognized event type %u", event->type);
-        else if (!dev->event_cb || !dev->event_cb->custom_event)
-            ALOGW("unhandled event type %u", event->type);
-        else
-            dev->event_cb->custom_event(dev->event_cb_data, disp, event);
-    }
-    free(event);
-}
-
-static void *adf_event_thread(void *data)
-{
-    adf_hwc_helper *dev = static_cast<adf_hwc_helper *>(data);
-
-    setpriority(PRIO_PROCESS, 0, HAL_PRIORITY_URGENT_DISPLAY);
-
-    struct sigaction action = { };
-    sigemptyset(&action.sa_mask);
-    action.sa_flags = 0;
-    action.sa_handler = [](int) { pthread_exit(0); };
-
-    if (sigaction(SIGUSR2, &action, NULL) < 0) {
-        ALOGE("failed to set thread exit action %s", strerror(errno));
-        return NULL;
-    }
-
-    sigset_t signal_set;
-    sigemptyset(&signal_set);
-    sigaddset(&signal_set, SIGUSR2);
-
-    pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL);
-
-    pollfd fds[dev->intf_fds.size()];
-    for (size_t i = 0; i < dev->intf_fds.size(); i++) {
-        fds[i].fd = dev->intf_fds[i];
-        fds[i].events = POLLIN | POLLPRI;
-    }
-
-    while (true) {
-        if (TEMP_FAILURE_RETRY(poll(fds, dev->intf_fds.size(), -1)) < 0) {
-            ALOGE("error in event thread: %s", strerror(errno));
-            break;
-        }
-
-        for (size_t i = 0; i < dev->intf_fds.size(); i++)
-            if (fds[i].revents & (POLLIN | POLLPRI))
-                handle_adf_event(dev, i);
-    }
-
-    return NULL;
-}
-
-int adf_hwc_open(int *intf_fds, size_t n_intfs,
-        const struct adf_hwc_event_callbacks *event_cb, void *event_cb_data,
-        struct adf_hwc_helper **dev)
-{
-    if (!n_intfs)
-        return -EINVAL;
-
-    adf_hwc_helper *dev_ret = new adf_hwc_helper;
-    dev_ret->event_cb = event_cb;
-    dev_ret->event_cb_data = event_cb_data;
-
-    int ret;
-
-    for (size_t i = 0; i < n_intfs; i++) {
-        int dup_intf_fd = dup(intf_fds[i]);
-        if (dup_intf_fd < 0) {
-            ALOGE("failed to dup interface fd: %s", strerror(errno));
-            ret = -errno;
-            goto err;
-        }
-
-        dev_ret->intf_fds.push_back(dup_intf_fd);
-
-        ret = adf_set_event(dup_intf_fd, ADF_EVENT_HOTPLUG, 1);
-        if (ret < 0 && ret != -EINVAL) {
-            ALOGE("failed to enable hotplug event on display %zu: %s",
-                    i, strerror(errno));
-            goto err;
-        }
-    }
-
-    sigset_t signal_set;
-    sigemptyset(&signal_set);
-    sigaddset(&signal_set, SIGUSR2);
-
-    pthread_sigmask(SIG_BLOCK, &signal_set, NULL);
-
-    ret = pthread_create(&dev_ret->event_thread, NULL, adf_event_thread,
-            dev_ret);
-    if (ret) {
-        ALOGE("failed to create event thread: %s", strerror(ret));
-        goto err;
-    }
-
-    *dev = dev_ret;
-    return 0;
-
-err:
-    for (size_t i = 0; i < dev_ret->intf_fds.size(); i++)
-        close(dev_ret->intf_fds[i]);
-
-    delete dev_ret;
-    return ret;
-}
-
-void adf_hwc_close(struct adf_hwc_helper *dev)
-{
-    pthread_kill(dev->event_thread, SIGUSR2);
-    pthread_join(dev->event_thread, NULL);
-
-    for (size_t i = 0; i < dev->intf_fds.size(); i++)
-        close(dev->intf_fds[i]);
-
-    delete dev;
-}
diff --git a/deprecated-adf/libadfhwc/include/adfhwc/adfhwc.h b/deprecated-adf/libadfhwc/include/adfhwc/adfhwc.h
deleted file mode 100644
index 4f70925..0000000
--- a/deprecated-adf/libadfhwc/include/adfhwc/adfhwc.h
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2013 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 _LIBADFHWC_ADFHWC_H_
-#define _LIBADFHWC_ADFHWC_H_
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <sys/cdefs.h>
-#include <video/adf.h>
-
-#include <hardware/hwcomposer.h>
-#include <hardware/hwcomposer2.h>
-
-struct adf_hwc_helper;
-
-struct adf_hwc_event_callbacks {
-    /**
-     * Called on vsync (required)
-     */
-    void (*vsync)(void *data, int disp, uint64_t timestamp);
-    /**
-     * Called on hotplug (required)
-     */
-    void (*hotplug)(void *data, int disp, bool connected);
-    /**
-     * Called on hardware-custom ADF events (optional)
-     */
-    void (*custom_event)(void *data, int disp, struct adf_event *event);
-};
-
-/**
- * Converts HAL pixel formats to equivalent ADF/DRM format FourCCs.
- */
-static inline uint32_t adf_fourcc_for_hal_pixel_format(int format)
-{
-    switch (format) {
-    case HAL_PIXEL_FORMAT_RGBA_8888:
-        return DRM_FORMAT_RGBA8888;
-    case HAL_PIXEL_FORMAT_RGBX_8888:
-        return DRM_FORMAT_RGBX8888;
-    case HAL_PIXEL_FORMAT_RGB_888:
-        return DRM_FORMAT_RGB888;
-    case HAL_PIXEL_FORMAT_RGB_565:
-        return DRM_FORMAT_RGB565;
-    case HAL_PIXEL_FORMAT_BGRA_8888:
-        return DRM_FORMAT_BGRA8888;
-    case HAL_PIXEL_FORMAT_YV12:
-        return DRM_FORMAT_YVU420;
-    case HAL_PIXEL_FORMAT_YCbCr_422_SP:
-        return DRM_FORMAT_NV16;
-    case HAL_PIXEL_FORMAT_YCrCb_420_SP:
-        return DRM_FORMAT_NV21;
-    case HAL_PIXEL_FORMAT_YCbCr_422_I:
-        return DRM_FORMAT_YUYV;
-    default:
-        return 0;
-    }
-}
-
-/**
- * Converts HAL display types to equivalent ADF interface flags.
- */
-static inline uint32_t adf_hwc_interface_flag_for_disp(int disp)
-{
-    switch (disp) {
-    case HWC_DISPLAY_PRIMARY:
-        return ADF_INTF_FLAG_PRIMARY;
-    case HWC_DISPLAY_EXTERNAL:
-        return ADF_INTF_FLAG_EXTERNAL;
-    default:
-        return 0;
-    }
-}
-
-__BEGIN_DECLS
-
-/**
- * Create a HWC helper for the specified ADF interfaces.
- *
- * intf_fds must be indexed by HWC display type: e.g.,
- * intf_fds[HWC_DISPLAY_PRIMARY] is the fd for the primary display
- * interface.  n_intfs must be >= 1.
- *
- * The caller retains ownership of the fds in intf_fds and must close()
- * them when they are no longer needed.
- *
- * On error, returns -errno.
- */
-int adf_hwc_open(int *intf_fds, size_t n_intfs,
-        const struct adf_hwc_event_callbacks *event_cb, void *event_cb_data,
-        struct adf_hwc_helper **dev);
-
-/**
- * Destroys a HWC helper.
- */
-void adf_hwc_close(struct adf_hwc_helper *dev);
-
-/**
- * Generic implementations of common HWC ops.
- *
- * The HWC should not point its ops directly at these helpers.  Instead, the HWC
- * should provide stub ops which call these helpers after converting the
- * hwc_composer_device_1* to a struct adf_hwc_helper*.
- */
-int adf_eventControl(struct adf_hwc_helper *dev, int disp, int event,
-        int enabled);
-int adf_blank(struct adf_hwc_helper *dev, int disp, int blank);
-int adf_query_display_types_supported(struct adf_hwc_helper *dev, int *value);
-int adf_getDisplayConfigs(struct adf_hwc_helper *dev, int disp,
-        uint32_t *configs, size_t *numConfigs);
-int adf_getDisplayAttributes(struct adf_hwc_helper *dev, int disp,
-        uint32_t config, const uint32_t *attributes, int32_t *values);
-/**
- * Generic implementation of common HWC2 functions.
- *
- * The HWC2 should not return these functions directly through getFunction.
- * Instead, the HWC2 should return stub functions which call these helpers.
- */
-int adf_getDisplayAttributes_hwc2(struct adf_hwc_helper *dev, int disp,
-        uint32_t config, const uint32_t *attributes, int32_t *values);
-int adf_set_active_config_hwc2(struct adf_hwc_helper *dev, int disp,
-        uint32_t config);
-
-__END_DECLS
-
-#endif /* _LIBADFHWC_ADFHWC_H_ */
diff --git a/diagnose_usb/Android.bp b/diagnose_usb/Android.bp
index 93d13bd..cb79ffe 100644
--- a/diagnose_usb/Android.bp
+++ b/diagnose_usb/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_static {
     name: "libdiagnose_usb",
     cflags: ["-Wall", "-Wextra", "-Werror"],
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 85a19e5..2c70778 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -14,6 +14,34 @@
 
 // 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"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+    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
+}
+
 cc_library_host_static {
     name: "libfastboot2",
 
@@ -27,6 +55,7 @@
         "tcp.cpp",
         "udp.cpp",
         "util.cpp",
+        "vendor_boot_img_utils.cpp",
         "fastboot_driver.cpp",
     ],
 
@@ -47,7 +76,9 @@
     ],
 
     header_libs: [
+        "avb_headers",
         "bootimg_headers",
+        "libstorage_literals_headers",
     ],
 
     export_header_lib_headers: [
@@ -96,6 +127,7 @@
         "-Wextra",
         "-Werror",
         "-Wvla",
+        "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
     ],
     rtti: true,
 
@@ -110,6 +142,12 @@
 
     recovery: true,
 
+    product_variables: {
+        debuggable: {
+            cppflags: ["-DFB_ENABLE_FETCH"],
+        },
+    },
+
     srcs: [
         "device/commands.cpp",
         "device/fastboot_device.cpp",
@@ -145,7 +183,7 @@
     ],
 
     static_libs: [
-        "libgtest_prod",
+        "libc++fs",
         "libhealthhalutils",
         "libsnapshot_cow",
         "libsnapshot_nobinder",
@@ -154,8 +192,10 @@
 
     header_libs: [
         "avb_headers",
+        "libgtest_prod_headers",
         "libsnapshot_headers",
-    ]
+        "libstorage_literals_headers",
+    ],
 }
 
 cc_defaults {
@@ -168,6 +208,8 @@
         "-Wextra",
         "-Werror",
         "-Wunreachable-code",
+        "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
+        "-D_FILE_OFFSET_BITS=64"
     ],
 
     target: {
@@ -234,15 +276,26 @@
         "tcp.cpp",
         "udp.cpp",
         "util.cpp",
+        "vendor_boot_img_utils.cpp",
         "fastboot_driver.cpp",
     ],
 
     // Only version the final binaries
     use_version_lib: false,
     static_libs: ["libbuildversion"],
+    header_libs: [
+        "avb_headers",
+        "libstorage_literals_headers",
+    ],
 
     generated_headers: ["platform_tools_version"],
 
+    tidy_flags: [
+        // DO NOT add quotes around header-filter flag regex argument,
+        // because build/soong will add quotes around the whole flag.
+        "-header-filter=(system/core/fastboot/|development/host/windows/usb/api/)",
+    ],
+
     target: {
         windows: {
             srcs: ["usb_windows.cpp"],
@@ -325,3 +378,33 @@
         },
     },
 }
+
+cc_test_host {
+    name: "fastboot_vendor_boot_img_utils_test",
+    srcs: ["vendor_boot_img_utils_test.cpp"],
+    static_libs: [
+        "libbase",
+        "libc++fs",
+        "libfastboot",
+        "libgmock",
+        "liblog",
+    ],
+    header_libs: [
+        "avb_headers",
+        "bootimg_headers",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    data: [
+        ":fastboot_test_dtb",
+        ":fastboot_test_bootconfig",
+        ":fastboot_test_vendor_ramdisk_none",
+        ":fastboot_test_vendor_ramdisk_platform",
+        ":fastboot_test_vendor_ramdisk_replace",
+        ":fastboot_test_vendor_boot_v3",
+        ":fastboot_test_vendor_boot_v4_without_frag",
+        ":fastboot_test_vendor_boot_v4_with_frag"
+    ],
+}
diff --git a/fastboot/OWNERS b/fastboot/OWNERS
index 2088ae3..a72ee07 100644
--- a/fastboot/OWNERS
+++ b/fastboot/OWNERS
@@ -1,4 +1,4 @@
-dpursell@google.com
+dvander@google.com
+hridya@google.com
 enh@google.com
 jmgao@google.com
-tomcherry@google.com
diff --git a/fastboot/bootimg_utils.cpp b/fastboot/bootimg_utils.cpp
index 2c0989e..d2056aa 100644
--- a/fastboot/bootimg_utils.cpp
+++ b/fastboot/bootimg_utils.cpp
@@ -34,22 +34,22 @@
 #include <stdlib.h>
 #include <string.h>
 
-static void bootimg_set_cmdline_v3(boot_img_hdr_v3* h, const std::string& cmdline) {
+static void bootimg_set_cmdline_v3_and_above(boot_img_hdr_v3* h, const std::string& cmdline) {
     if (cmdline.size() >= sizeof(h->cmdline)) die("command line too large: %zu", cmdline.size());
     strcpy(reinterpret_cast<char*>(h->cmdline), cmdline.c_str());
 }
 
 void bootimg_set_cmdline(boot_img_hdr_v2* h, const std::string& cmdline) {
-    if (h->header_version == 3) {
-        return bootimg_set_cmdline_v3(reinterpret_cast<boot_img_hdr_v3*>(h), cmdline);
+    if (h->header_version >= 3) {
+        return bootimg_set_cmdline_v3_and_above(reinterpret_cast<boot_img_hdr_v3*>(h), cmdline);
     }
     if (cmdline.size() >= sizeof(h->cmdline)) die("command line too large: %zu", cmdline.size());
     strcpy(reinterpret_cast<char*>(h->cmdline), cmdline.c_str());
 }
 
-static boot_img_hdr_v3* mkbootimg_v3(const std::vector<char>& kernel,
-                                     const std::vector<char>& ramdisk, const boot_img_hdr_v2& src,
-                                     std::vector<char>* out) {
+static void mkbootimg_v3_and_above(const std::vector<char>& kernel,
+                                   const std::vector<char>& ramdisk, const boot_img_hdr_v2& src,
+                                   std::vector<char>* out) {
 #define V3_PAGE_SIZE 4096
     const size_t page_mask = V3_PAGE_SIZE - 1;
     int64_t kernel_actual = (kernel.size() + page_mask) & (~page_mask);
@@ -65,22 +65,27 @@
     hdr->ramdisk_size = ramdisk.size();
     hdr->os_version = src.os_version;
     hdr->header_size = sizeof(boot_img_hdr_v3);
-    hdr->header_version = 3;
+    hdr->header_version = src.header_version;
+
+    if (src.header_version >= 4) {
+        auto hdr_v4 = reinterpret_cast<boot_img_hdr_v4*>(hdr);
+        hdr_v4->signature_size = 0;
+    }
 
     memcpy(hdr->magic + V3_PAGE_SIZE, kernel.data(), kernel.size());
     memcpy(hdr->magic + V3_PAGE_SIZE + kernel_actual, ramdisk.data(), ramdisk.size());
-
-    return hdr;
 }
 
-boot_img_hdr_v2* mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk,
-                           const std::vector<char>& second, const std::vector<char>& dtb,
-                           size_t base, const boot_img_hdr_v2& src, std::vector<char>* out) {
-    if (src.header_version == 3) {
+void mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk,
+               const std::vector<char>& second, const std::vector<char>& dtb, size_t base,
+               const boot_img_hdr_v2& src, std::vector<char>* out) {
+    if (src.header_version >= 3) {
         if (!second.empty() || !dtb.empty()) {
-            die("Second stage bootloader and dtb not supported in v3 boot image\n");
+            die("Second stage bootloader and dtb not supported in v%d boot image\n",
+                src.header_version);
         }
-        return reinterpret_cast<boot_img_hdr_v2*>(mkbootimg_v3(kernel, ramdisk, src, out));
+        mkbootimg_v3_and_above(kernel, ramdisk, src, out);
+        return;
     }
     const size_t page_mask = src.page_size - 1;
 
@@ -122,5 +127,4 @@
            second.size());
     memcpy(hdr->magic + hdr->page_size + kernel_actual + ramdisk_actual + second_actual, dtb.data(),
            dtb.size());
-    return hdr;
 }
diff --git a/fastboot/bootimg_utils.h b/fastboot/bootimg_utils.h
index b7cf9bd..0eb003d 100644
--- a/fastboot/bootimg_utils.h
+++ b/fastboot/bootimg_utils.h
@@ -35,7 +35,8 @@
 #include <string>
 #include <vector>
 
-boot_img_hdr_v2* mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk,
-                           const std::vector<char>& second, const std::vector<char>& dtb,
-                           size_t base, const boot_img_hdr_v2& src, std::vector<char>* out);
+void mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk,
+               const std::vector<char>& second, const std::vector<char>& dtb, size_t base,
+               const boot_img_hdr_v2& src, std::vector<char>* out);
+
 void bootimg_set_cmdline(boot_img_hdr_v2* h, const std::string& cmdline);
diff --git a/fastboot/constants.h b/fastboot/constants.h
index ba43ca5..4ea68da 100644
--- a/fastboot/constants.h
+++ b/fastboot/constants.h
@@ -35,6 +35,7 @@
 #define FB_CMD_OEM "oem"
 #define FB_CMD_GSI "gsi"
 #define FB_CMD_SNAPSHOT_UPDATE "snapshot-update"
+#define FB_CMD_FETCH "fetch"
 
 #define RESPONSE_OKAY "OKAY"
 #define RESPONSE_FAIL "FAIL"
@@ -77,3 +78,4 @@
 #define FB_VAR_FIRST_API_LEVEL "first-api-level"
 #define FB_VAR_SECURITY_PATCH_LEVEL "security-patch-level"
 #define FB_VAR_TREBLE_ENABLED "treble-enabled"
+#define FB_VAR_MAX_FETCH_SIZE "max-fetch-size"
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index 4601960..0a72812 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -16,6 +16,7 @@
 
 #include "commands.h"
 
+#include <inttypes.h>
 #include <sys/socket.h>
 #include <sys/un.h>
 
@@ -36,6 +37,7 @@
 #include <liblp/builder.h>
 #include <liblp/liblp.h>
 #include <libsnapshot/snapshot.h>
+#include <storage_literals/storage_literals.h>
 #include <uuid/uuid.h>
 
 #include "constants.h"
@@ -43,6 +45,12 @@
 #include "flashing.h"
 #include "utility.h"
 
+#ifdef FB_ENABLE_FETCH
+static constexpr bool kEnableFetch = true;
+#else
+static constexpr bool kEnableFetch = false;
+#endif
+
 using android::fs_mgr::MetadataBuilder;
 using ::android::hardware::hidl_string;
 using ::android::hardware::boot::V1_0::BoolResult;
@@ -54,6 +62,8 @@
 using android::snapshot::SnapshotManager;
 using IBootControl1_1 = ::android::hardware::boot::V1_1::IBootControl;
 
+using namespace android::storage_literals;
+
 struct VariableHandlers {
     // Callback to retrieve the value of a single variable.
     std::function<bool(FastbootDevice*, const std::vector<std::string>&, std::string*)> get;
@@ -136,7 +146,9 @@
             {FB_VAR_DYNAMIC_PARTITION, {GetDynamicPartition, nullptr}},
             {FB_VAR_FIRST_API_LEVEL, {GetFirstApiLevel, nullptr}},
             {FB_VAR_SECURITY_PATCH_LEVEL, {GetSecurityPatchLevel, nullptr}},
-            {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}}};
+            {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}},
+            {FB_VAR_MAX_FETCH_SIZE, {GetMaxFetchSize, nullptr}},
+    };
 
     if (args.size() < 2) {
         return device->WriteFail("Missing argument");
@@ -228,6 +240,11 @@
         return device->WriteStatus(FastbootResult::FAIL, "Unable to open fastboot HAL");
     }
 
+    //Disable "oem postwipedata userdata" to prevent user wipe oem userdata only.
+    if (args[0] == "oem postwipedata userdata") {
+        return device->WriteStatus(FastbootResult::FAIL, "Unable to do oem postwipedata userdata");
+    }
+
     Result ret;
     auto ret_val = fastboot_hal->doOemCommand(args[0], [&](Result result) { ret = result; });
     if (!ret_val.isOk()) {
@@ -375,13 +392,13 @@
 
     struct sockaddr_un addr = {.sun_family = AF_UNIX};
     strncpy(addr.sun_path, "/dev/socket/recovery", sizeof(addr.sun_path) - 1);
-    if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+    if (connect(sock.get(), (struct sockaddr*)&addr, sizeof(addr)) < 0) {
         PLOG(ERROR) << "Couldn't connect to recovery";
         return false;
     }
     // Switch to recovery will not update the boot reason since it does not
     // require a reboot.
-    auto ret = write(sock, &msg_switch_to_recovery, sizeof(msg_switch_to_recovery));
+    auto ret = write(sock.get(), &msg_switch_to_recovery, sizeof(msg_switch_to_recovery));
     if (ret != sizeof(msg_switch_to_recovery)) {
         PLOG(ERROR) << "Couldn't write message to switch to recovery";
         return false;
@@ -654,7 +671,7 @@
             return device->WriteFail("No snapshot merge is in progress");
         }
 
-        auto sm = SnapshotManager::NewForFirstStageMount();
+        auto sm = SnapshotManager::New();
         if (!sm) {
             return device->WriteFail("Unable to create SnapshotManager");
         }
@@ -666,3 +683,175 @@
     }
     return device->WriteStatus(FastbootResult::OKAY, "Success");
 }
+
+namespace {
+// Helper of FetchHandler.
+class PartitionFetcher {
+  public:
+    static bool Fetch(FastbootDevice* device, const std::vector<std::string>& args) {
+        if constexpr (!kEnableFetch) {
+            return device->WriteFail("Fetch is not allowed on user build");
+        }
+
+        if (GetDeviceLockStatus()) {
+            return device->WriteFail("Fetch is not allowed on locked devices");
+        }
+
+        PartitionFetcher fetcher(device, args);
+        if (fetcher.Open()) {
+            fetcher.Fetch();
+        }
+        CHECK(fetcher.ret_.has_value());
+        return *fetcher.ret_;
+    }
+
+  private:
+    PartitionFetcher(FastbootDevice* device, const std::vector<std::string>& args)
+        : device_(device), args_(&args) {}
+    // Return whether the partition is successfully opened.
+    // If successfully opened, ret_ is left untouched. Otherwise, ret_ is set to the value
+    // that FetchHandler should return.
+    bool Open() {
+        if (args_->size() < 2) {
+            ret_ = device_->WriteFail("Missing partition arg");
+            return false;
+        }
+
+        partition_name_ = args_->at(1);
+        if (std::find(kAllowedPartitions.begin(), kAllowedPartitions.end(), partition_name_) ==
+            kAllowedPartitions.end()) {
+            ret_ = device_->WriteFail("Fetch is only allowed on [" +
+                                      android::base::Join(kAllowedPartitions, ", ") + "]");
+            return false;
+        }
+
+        if (!OpenPartition(device_, partition_name_, &handle_, true /* read */)) {
+            ret_ = device_->WriteFail(
+                    android::base::StringPrintf("Cannot open %s", partition_name_.c_str()));
+            return false;
+        }
+
+        partition_size_ = get_block_device_size(handle_.fd());
+        if (partition_size_ == 0) {
+            ret_ = device_->WriteOkay(android::base::StringPrintf("Partition %s has size 0",
+                                                                  partition_name_.c_str()));
+            return false;
+        }
+
+        start_offset_ = 0;
+        if (args_->size() >= 3) {
+            if (!android::base::ParseUint(args_->at(2), &start_offset_)) {
+                ret_ = device_->WriteFail("Invalid offset, must be integer");
+                return false;
+            }
+            if (start_offset_ > std::numeric_limits<off64_t>::max()) {
+                ret_ = device_->WriteFail(
+                        android::base::StringPrintf("Offset overflows: %" PRIx64, start_offset_));
+                return false;
+            }
+        }
+        if (start_offset_ > partition_size_) {
+            ret_ = device_->WriteFail(android::base::StringPrintf(
+                    "Invalid offset 0x%" PRIx64 ", partition %s has size 0x%" PRIx64, start_offset_,
+                    partition_name_.c_str(), partition_size_));
+            return false;
+        }
+        uint64_t maximum_total_size_to_read = partition_size_ - start_offset_;
+        total_size_to_read_ = maximum_total_size_to_read;
+        if (args_->size() >= 4) {
+            if (!android::base::ParseUint(args_->at(3), &total_size_to_read_)) {
+                ret_ = device_->WriteStatus(FastbootResult::FAIL, "Invalid size, must be integer");
+                return false;
+            }
+        }
+        if (total_size_to_read_ == 0) {
+            ret_ = device_->WriteOkay("Read 0 bytes");
+            return false;
+        }
+        if (total_size_to_read_ > maximum_total_size_to_read) {
+            ret_ = device_->WriteFail(android::base::StringPrintf(
+                    "Invalid size to read 0x%" PRIx64 ", partition %s has size 0x%" PRIx64
+                    " and fetching from offset 0x%" PRIx64,
+                    total_size_to_read_, partition_name_.c_str(), partition_size_, start_offset_));
+            return false;
+        }
+
+        if (total_size_to_read_ > kMaxFetchSizeDefault) {
+            ret_ = device_->WriteFail(android::base::StringPrintf(
+                    "Cannot fetch 0x%" PRIx64
+                    " bytes because it exceeds maximum transport size 0x%x",
+                    partition_size_, kMaxDownloadSizeDefault));
+            return false;
+        }
+
+        return true;
+    }
+
+    // Assume Open() returns true.
+    // After execution, ret_ is set to the value that FetchHandler should return.
+    void Fetch() {
+        CHECK(start_offset_ <= std::numeric_limits<off64_t>::max());
+        if (lseek64(handle_.fd(), start_offset_, SEEK_SET) != static_cast<off64_t>(start_offset_)) {
+            ret_ = device_->WriteFail(android::base::StringPrintf(
+                    "On partition %s, unable to lseek(0x%" PRIx64 ": %s", partition_name_.c_str(),
+                    start_offset_, strerror(errno)));
+            return;
+        }
+
+        if (!device_->WriteStatus(FastbootResult::DATA,
+                                  android::base::StringPrintf(
+                                          "%08x", static_cast<uint32_t>(total_size_to_read_)))) {
+            ret_ = false;
+            return;
+        }
+        uint64_t end_offset = start_offset_ + total_size_to_read_;
+        std::vector<char> buf(1_MiB);
+        uint64_t current_offset = start_offset_;
+        while (current_offset < end_offset) {
+            // On any error, exit. We can't return a status message to the driver because
+            // we are in the middle of writing data, so just let the driver guess what's wrong
+            // by ending the data stream prematurely.
+            uint64_t remaining = end_offset - current_offset;
+            uint64_t chunk_size = std::min<uint64_t>(buf.size(), remaining);
+            if (!android::base::ReadFully(handle_.fd(), buf.data(), chunk_size)) {
+                PLOG(ERROR) << std::hex << "Unable to read 0x" << chunk_size << " bytes from "
+                            << partition_name_ << " @ offset 0x" << current_offset;
+                ret_ = false;
+                return;
+            }
+            if (!device_->HandleData(false /* is read */, buf.data(), chunk_size)) {
+                PLOG(ERROR) << std::hex << "Unable to send 0x" << chunk_size << " bytes of "
+                            << partition_name_ << " @ offset 0x" << current_offset;
+                ret_ = false;
+                return;
+            }
+            current_offset += chunk_size;
+        }
+
+        ret_ = device_->WriteOkay(android::base::StringPrintf(
+                "Fetched %s (offset=0x%" PRIx64 ", size=0x%" PRIx64, partition_name_.c_str(),
+                start_offset_, total_size_to_read_));
+    }
+
+    static constexpr std::array<const char*, 3> kAllowedPartitions{
+            "vendor_boot",
+            "vendor_boot_a",
+            "vendor_boot_b",
+    };
+
+    FastbootDevice* device_;
+    const std::vector<std::string>* args_ = nullptr;
+    std::string partition_name_;
+    PartitionHandle handle_;
+    uint64_t partition_size_ = 0;
+    uint64_t start_offset_ = 0;
+    uint64_t total_size_to_read_ = 0;
+
+    // What FetchHandler should return.
+    std::optional<bool> ret_ = std::nullopt;
+};
+}  // namespace
+
+bool FetchHandler(FastbootDevice* device, const std::vector<std::string>& args) {
+    return PartitionFetcher::Fetch(device, args);
+}
diff --git a/fastboot/device/commands.h b/fastboot/device/commands.h
index c1324bc..345ae1a 100644
--- a/fastboot/device/commands.h
+++ b/fastboot/device/commands.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 constexpr unsigned int kMaxDownloadSizeDefault = 0x10000000;
+constexpr unsigned int kMaxFetchSizeDefault = 0x10000000;
 
 class FastbootDevice;
 
@@ -50,3 +51,4 @@
 bool OemCmdHandler(FastbootDevice* device, const std::vector<std::string>& args);
 bool GsiHandler(FastbootDevice* device, const std::vector<std::string>& args);
 bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector<std::string>& args);
+bool FetchHandler(FastbootDevice* device, const std::vector<std::string>& args);
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 35f3de0..64a934d 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -61,6 +61,7 @@
               {FB_CMD_OEM, OemCmdHandler},
               {FB_CMD_GSI, GsiHandler},
               {FB_CMD_SNAPSHOT_UPDATE, SnapshotUpdateHandler},
+              {FB_CMD_FETCH, FetchHandler},
       }),
       boot_control_hal_(IBootControl::getService()),
       health_hal_(get_health_service()),
@@ -137,14 +138,18 @@
 }
 
 bool FastbootDevice::HandleData(bool read, std::vector<char>* data) {
-    auto read_write_data_size = read ? this->get_transport()->Read(data->data(), data->size())
-                                     : this->get_transport()->Write(data->data(), data->size());
+    return HandleData(read, data->data(), data->size());
+}
+
+bool FastbootDevice::HandleData(bool read, char* data, uint64_t size) {
+    auto read_write_data_size = read ? this->get_transport()->Read(data, size)
+                                     : this->get_transport()->Write(data, size);
     if (read_write_data_size == -1) {
         LOG(ERROR) << (read ? "read from" : "write to") << " transport failed";
         return false;
     }
-    if (static_cast<size_t>(read_write_data_size) != data->size()) {
-        LOG(ERROR) << (read ? "read" : "write") << " expected " << data->size() << " bytes, got "
+    if (static_cast<size_t>(read_write_data_size) != size) {
+        LOG(ERROR) << (read ? "read" : "write") << " expected " << size << " bytes, got "
                    << read_write_data_size;
         return false;
     }
diff --git a/fastboot/device/fastboot_device.h b/fastboot/device/fastboot_device.h
index 23be721..3536136 100644
--- a/fastboot/device/fastboot_device.h
+++ b/fastboot/device/fastboot_device.h
@@ -40,6 +40,7 @@
     void ExecuteCommands();
     bool WriteStatus(FastbootResult result, const std::string& message);
     bool HandleData(bool read, std::vector<char>* data);
+    bool HandleData(bool read, char* data, uint64_t size);
     std::string GetCurrentSlot();
 
     // Shortcuts for writing status results.
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 333ca50..ee0aa58 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -27,6 +27,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/strings.h>
 #include <ext4_utils/ext4_utils.h>
 #include <fs_mgr_overlayfs.h>
@@ -162,7 +163,9 @@
                 partition_name == "boot_b")) {
         CopyAVBFooter(&data, block_device_size);
     }
-    WipeOverlayfsForPartition(device, partition_name);
+    if (android::base::GetProperty("ro.system.build.type", "") != "user") {
+        WipeOverlayfsForPartition(device, partition_name);
+    }
     int result = FlashBlockDevice(handle.fd(), data);
     sync();
     return result;
diff --git a/fastboot/device/usb.cpp b/fastboot/device/usb.cpp
index 4bee7b2..4115a6d 100644
--- a/fastboot/device/usb.cpp
+++ b/fastboot/device/usb.cpp
@@ -82,7 +82,7 @@
     int orig_len = len;
     while (len > 0) {
         int write_len = std::min(USB_FFS_BULK_SIZE, len);
-        int n = write(h->bulk_in, buf, write_len);
+        int n = write(h->bulk_in.get(), buf, write_len);
         if (n < 0) {
             D("ERROR: fd = %d, n = %d: %s", h->bulk_in.get(), n, strerror(errno));
             return -1;
@@ -103,7 +103,7 @@
     unsigned count = 0;
     while (len > 0) {
         int read_len = std::min(USB_FFS_BULK_SIZE, len);
-        int n = read(h->bulk_out, buf, read_len);
+        int n = read(h->bulk_out.get(), buf, read_len);
         if (n < 0) {
             D("ERROR: fd = %d, n = %d: %s", h->bulk_out.get(), n, strerror(errno));
             return -1;
diff --git a/fastboot/device/usb_client.cpp b/fastboot/device/usb_client.cpp
index 2caced4..3f9b0f0 100644
--- a/fastboot/device/usb_client.cpp
+++ b/fastboot/device/usb_client.cpp
@@ -283,7 +283,7 @@
     size_t bytes_written_total = 0;
     while (bytes_written_total < len) {
         auto bytes_to_write = std::min(len - bytes_written_total, kFbFfsNumBufs * kFbFfsBufSize);
-        auto bytes_written_now = handle_->write(handle_.get(), data, bytes_to_write);
+        auto bytes_written_now = handle_->write(handle_.get(), char_data, bytes_to_write);
         if (bytes_written_now < 0) {
             return bytes_written_total == 0 ? -1 : bytes_written_total;
         }
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index 7c6ac89..07ad902 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -77,7 +77,8 @@
 
 }  // namespace
 
-bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle) {
+bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
+                   bool read) {
     // We prioritize logical partitions over physical ones, and do this
     // consistently for other partition operations (like getvar:partition-size).
     if (LogicalPartitionExists(device, name)) {
@@ -89,7 +90,9 @@
         return false;
     }
 
-    unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), O_WRONLY | O_EXCL)));
+    int flags = (read ? O_RDONLY : O_WRONLY);
+    flags |= (O_EXCL | O_CLOEXEC | O_BINARY);
+    unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags)));
     if (fd < 0) {
         PLOG(ERROR) << "Failed to open block device: " << handle->path();
         return false;
@@ -201,12 +204,7 @@
 }
 
 bool GetDeviceLockStatus() {
-    std::string cmdline;
-    // Return lock status true if unable to read kernel command line.
-    if (!android::base::ReadFileToString("/proc/cmdline", &cmdline)) {
-        return true;
-    }
-    return cmdline.find("androidboot.verifiedbootstate=orange") == std::string::npos;
+    return android::base::GetProperty("ro.boot.verifiedbootstate", "") != "orange";
 }
 
 bool UpdateAllPartitionMetadata(FastbootDevice* device, const std::string& super_name,
diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h
index 3b71ef0..c2646d7 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -75,7 +75,11 @@
 std::optional<std::string> FindPhysicalPartition(const std::string& name);
 bool LogicalPartitionExists(FastbootDevice* device, const std::string& name,
                             bool* is_zero_length = nullptr);
-bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle);
+
+// If read, partition is readonly. Else it is write only.
+bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle,
+                   bool read = false);
+
 bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number);
 std::vector<std::string> ListPartitions(FastbootDevice* device);
 bool GetDeviceLockStatus();
diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp
index e7d8bc3..ee1eed8 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -33,6 +33,12 @@
 #include "flashing.h"
 #include "utility.h"
 
+#ifdef FB_ENABLE_FETCH
+static constexpr bool kEnableFetch = true;
+#else
+static constexpr bool kEnableFetch = false;
+#endif
+
 using ::android::hardware::boot::V1_0::BoolResult;
 using ::android::hardware::boot::V1_0::Slot;
 using ::android::hardware::boot::V1_1::MergeStatus;
@@ -509,3 +515,13 @@
     *message = android::base::GetProperty("ro.treble.enabled", "");
     return true;
 }
+
+bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+                     std::string* message) {
+    if (!kEnableFetch) {
+        *message = "fetch not supported on user builds";
+        return false;
+    }
+    *message = android::base::StringPrintf("0x%X", kMaxFetchSizeDefault);
+    return true;
+}
diff --git a/fastboot/device/variables.h b/fastboot/device/variables.h
index c11e472..f40a025 100644
--- a/fastboot/device/variables.h
+++ b/fastboot/device/variables.h
@@ -80,6 +80,8 @@
                            std::string* message);
 bool GetTrebleEnabled(FastbootDevice* device, const std::vector<std::string>& args,
                       std::string* message);
+bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */,
+                     std::string* message);
 
 // Helpers for getvar all.
 std::vector<std::vector<std::string>> GetAllPartitionArgsWithSlot(FastbootDevice* device);
diff --git a/fastboot/fastboot.bash b/fastboot/fastboot.bash
index 406e8b8..f5a3384 100644
--- a/fastboot/fastboot.bash
+++ b/fastboot/fastboot.bash
@@ -109,7 +109,7 @@
 
     cur="${COMP_WORDS[COMP_CWORD]}"
     if [[ $i -eq $COMP_CWORD ]]; then
-        partitions="boot bootloader dtbo modem odm odm_dlkm oem product radio recovery system vbmeta vendor vendor_dlkm"
+        partitions="boot bootloader dtbo modem odm odm_dlkm oem product pvmfw radio recovery system vbmeta vendor vendor_dlkm"
         COMPREPLY=( $(compgen -W "$partitions" -- $cur) )
     else
         _fastboot_util_complete_local_file "${cur}" '!*.img'
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 62f6ac7..e5319a5 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -76,12 +76,15 @@
 #include "udp.h"
 #include "usb.h"
 #include "util.h"
+#include "vendor_boot_img_utils.h"
 
+using android::base::borrowed_fd;
 using android::base::ReadFully;
 using android::base::Split;
 using android::base::Trim;
 using android::base::unique_fd;
 using namespace std::string_literals;
+using namespace std::placeholders;
 
 static const char* serial = nullptr;
 
@@ -114,7 +117,7 @@
     enum fb_buffer_type type;
     void* data;
     int64_t sz;
-    int fd;
+    unique_fd fd;
     int64_t image_size;
 };
 
@@ -147,6 +150,7 @@
     { "odm",      "odm.img",          "odm.sig",      "odm",      true,  ImageType::Normal },
     { "odm_dlkm", "odm_dlkm.img",     "odm_dlkm.sig", "odm_dlkm", true,  ImageType::Normal },
     { "product",  "product.img",      "product.sig",  "product",  true,  ImageType::Normal },
+    { "pvmfw",    "pvmfw.img",        "pvmfw.sig",    "pvmfw",    true,  ImageType::BootCritical },
     { "recovery", "recovery.img",     "recovery.sig", "recovery", true,  ImageType::BootCritical },
     { "super",    "super.img",        "super.sig",    "super",    true,  ImageType::Extra },
     { "system",   "system.img",       "system.sig",   "system",   false, ImageType::Normal },
@@ -226,9 +230,9 @@
     fprintf(stderr, "(bootloader) %s\n", info.c_str());
 }
 
-static int64_t get_file_size(int fd) {
+static int64_t get_file_size(borrowed_fd fd) {
     struct stat sb;
-    if (fstat(fd, &sb) == -1) {
+    if (fstat(fd.get(), &sb) == -1) {
         die("could not get file size");
     }
     return sb.st_size;
@@ -407,12 +411,20 @@
             " gsi wipe|disable           Wipe or disable a GSI installation (fastbootd only).\n"
             " wipe-super [SUPER_EMPTY]   Wipe the super partition. This will reset it to\n"
             "                            contain an empty set of default dynamic partitions.\n"
+            " create-logical-partition NAME SIZE\n"
+            "                            Create a logical partition with the given name and\n"
+            "                            size, in the super partition.\n"
+            " delete-logical-partition NAME\n"
+            "                            Delete a logical partition with the given name.\n"
+            " resize-logical-partition NAME SIZE\n"
+            "                            Change the size of the named logical partition.\n"
             " snapshot-update cancel     On devices that support snapshot-based updates, cancel\n"
             "                            an in-progress update. This may make the device\n"
             "                            unbootable until it is reflashed.\n"
             " snapshot-update merge      On devices that support snapshot-based updates, finish\n"
             "                            an in-progress update if it is in the \"merging\"\n"
             "                            phase.\n"
+            " fetch PARTITION            Fetch a partition image from the device."
             "\n"
             "boot image:\n"
             " boot KERNEL [RAMDISK [SECOND]]\n"
@@ -465,7 +477,7 @@
             " --version                  Display version.\n"
             " --help, -h                 Show this message.\n"
         );
-    // clang-format off
+    // clang-format on
     return 0;
 }
 
@@ -518,10 +530,12 @@
     fprintf(stderr,"creating boot image...\n");
 
     std::vector<char> out;
-    boot_img_hdr_v2* boot_image_data = mkbootimg(kernel_data, ramdisk_data, second_stage_data,
-                                                 dtb_data, g_base_addr, g_boot_img_hdr, &out);
+    mkbootimg(kernel_data, ramdisk_data, second_stage_data, dtb_data, g_base_addr, g_boot_img_hdr,
+              &out);
 
-    if (!g_cmdline.empty()) bootimg_set_cmdline(boot_image_data, g_cmdline);
+    if (!g_cmdline.empty()) {
+        bootimg_set_cmdline(reinterpret_cast<boot_img_hdr_v2*>(out.data()), g_cmdline);
+    }
     fprintf(stderr, "creating boot image - %zu bytes\n", out.size());
     return out;
 }
@@ -639,34 +653,34 @@
     }
 }
 
-static int unzip_to_file(ZipArchiveHandle zip, const char* entry_name) {
+static unique_fd unzip_to_file(ZipArchiveHandle zip, const char* entry_name) {
     unique_fd fd(make_temporary_fd(entry_name));
 
     ZipEntry64 zip_entry;
     if (FindEntry(zip, entry_name, &zip_entry) != 0) {
         fprintf(stderr, "archive does not contain '%s'\n", entry_name);
         errno = ENOENT;
-        return -1;
+        return unique_fd();
     }
 
     fprintf(stderr, "extracting %s (%" PRIu64 " MB) to disk...", entry_name,
             zip_entry.uncompressed_length / 1024 / 1024);
     double start = now();
-    int error = ExtractEntryToFile(zip, &zip_entry, fd);
+    int error = ExtractEntryToFile(zip, &zip_entry, fd.get());
     if (error != 0) {
         die("\nfailed to extract '%s': %s", entry_name, ErrorCodeString(error));
     }
 
-    if (lseek(fd, 0, SEEK_SET) != 0) {
+    if (lseek(fd.get(), 0, SEEK_SET) != 0) {
         die("\nlseek on extracted file '%s' failed: %s", entry_name, strerror(errno));
     }
 
     fprintf(stderr, " took %.3fs\n", now() - start);
 
-    return fd.release();
+    return fd;
 }
 
-static void CheckRequirement(const std::string& cur_product, const std::string& var,
+static bool CheckRequirement(const std::string& cur_product, const std::string& var,
                              const std::string& product, bool invert,
                              const std::vector<std::string>& options) {
     Status("Checking '" + var + "'");
@@ -678,7 +692,7 @@
             double split = now();
             fprintf(stderr, "IGNORE, product is %s required only for %s [%7.3fs]\n",
                     cur_product.c_str(), product.c_str(), (split - start));
-            return;
+            return true;
         }
     }
 
@@ -687,7 +701,7 @@
         fprintf(stderr, "FAILED\n\n");
         fprintf(stderr, "Could not getvar for '%s' (%s)\n\n", var.c_str(),
                 fb->Error().c_str());
-        die("requirements not met!");
+        return false;
     }
 
     bool match = false;
@@ -707,7 +721,7 @@
     if (match) {
         double split = now();
         fprintf(stderr, "OKAY [%7.3fs]\n", (split - start));
-        return;
+        return true;
     }
 
     fprintf(stderr, "FAILED\n\n");
@@ -717,7 +731,7 @@
         fprintf(stderr, " or '%s'", it->c_str());
     }
     fprintf(stderr, ".\n\n");
-    die("requirements not met!");
+    return false;
 }
 
 bool ParseRequirementLine(const std::string& line, std::string* name, std::string* product,
@@ -781,7 +795,7 @@
     }
 }
 
-static void CheckRequirements(const std::string& data) {
+static void CheckRequirements(const std::string& data, bool force_flash) {
     std::string cur_product;
     if (fb->GetVar("product", &cur_product) != fastboot::SUCCESS) {
         fprintf(stderr, "getvar:product FAILED (%s)\n", fb->Error().c_str());
@@ -805,7 +819,14 @@
         if (name == "partition-exists") {
             HandlePartitionExists(options);
         } else {
-            CheckRequirement(cur_product, name, product, invert, options);
+            bool met = CheckRequirement(cur_product, name, product, invert, options);
+            if (!met) {
+                if (!force_flash) {
+                  die("requirements not met!");
+                } else {
+                  fprintf(stderr, "requirements not met! but proceeding due to --force\n");
+                }
+            }
         }
     }
 }
@@ -850,24 +871,23 @@
     return out_s;
 }
 
-static int64_t get_target_sparse_limit() {
-    std::string max_download_size;
-    if (fb->GetVar("max-download-size", &max_download_size) != fastboot::SUCCESS ||
-        max_download_size.empty()) {
-        verbose("target didn't report max-download-size");
+static uint64_t get_uint_var(const char* var_name) {
+    std::string value_str;
+    if (fb->GetVar(var_name, &value_str) != fastboot::SUCCESS || value_str.empty()) {
+        verbose("target didn't report %s", var_name);
         return 0;
     }
 
     // Some bootloaders (angler, for example) send spurious whitespace too.
-    max_download_size = android::base::Trim(max_download_size);
+    value_str = android::base::Trim(value_str);
 
-    uint64_t limit;
-    if (!android::base::ParseUint(max_download_size, &limit)) {
-        fprintf(stderr, "couldn't parse max-download-size '%s'\n", max_download_size.c_str());
+    uint64_t value;
+    if (!android::base::ParseUint(value_str, &value)) {
+        fprintf(stderr, "couldn't parse %s '%s'\n", var_name, value_str.c_str());
         return 0;
     }
-    if (limit > 0) verbose("target reported max download size of %" PRId64 " bytes", limit);
-    return limit;
+    if (value > 0) verbose("target reported %s of %" PRId64 " bytes", var_name, value);
+    return value;
 }
 
 static int64_t get_sparse_limit(int64_t size) {
@@ -876,7 +896,7 @@
         // Unlimited, so see what the target device's limit is.
         // TODO: shouldn't we apply this limit even if you've used -S?
         if (target_sparse_limit == -1) {
-            target_sparse_limit = get_target_sparse_limit();
+            target_sparse_limit = static_cast<int64_t>(get_uint_var("max-download-size"));
         }
         if (target_sparse_limit > 0) {
             limit = target_sparse_limit;
@@ -892,23 +912,24 @@
     return 0;
 }
 
-static bool load_buf_fd(int fd, struct fastboot_buffer* buf) {
+static bool load_buf_fd(unique_fd fd, struct fastboot_buffer* buf) {
     int64_t sz = get_file_size(fd);
     if (sz == -1) {
         return false;
     }
 
-    if (sparse_file* s = sparse_file_import(fd, false, false)) {
+    if (sparse_file* s = sparse_file_import(fd.get(), false, false)) {
         buf->image_size = sparse_file_len(s, false, false);
         sparse_file_destroy(s);
     } else {
         buf->image_size = sz;
     }
 
-    lseek(fd, 0, SEEK_SET);
+    lseek(fd.get(), 0, SEEK_SET);
     int64_t limit = get_sparse_limit(sz);
+    buf->fd = std::move(fd);
     if (limit) {
-        sparse_file** s = load_sparse_files(fd, limit);
+        sparse_file** s = load_sparse_files(buf->fd.get(), limit);
         if (s == nullptr) {
             return false;
         }
@@ -917,7 +938,6 @@
     } else {
         buf->type = FB_BUFFER_FD;
         buf->data = nullptr;
-        buf->fd = fd;
         buf->sz = sz;
     }
 
@@ -932,7 +952,7 @@
     }
 
     struct stat s;
-    if (fstat(fd, &s)) {
+    if (fstat(fd.get(), &s)) {
         return false;
     }
     if (!S_ISREG(s.st_mode)) {
@@ -940,7 +960,7 @@
         return false;
     }
 
-    return load_buf_fd(fd.release(), buf);
+    return load_buf_fd(std::move(fd), buf);
 }
 
 static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_boot) {
@@ -986,13 +1006,12 @@
         data[flags_offset] |= 0x02;
     }
 
-    int fd = make_temporary_fd("vbmeta rewriting");
+    unique_fd fd(make_temporary_fd("vbmeta rewriting"));
     if (!android::base::WriteStringToFd(data, fd)) {
         die("Failed writing to modified vbmeta");
     }
-    close(buf->fd);
-    buf->fd = fd;
-    lseek(fd, 0, SEEK_SET);
+    buf->fd = std::move(fd);
+    lseek(buf->fd.get(), 0, SEEK_SET);
 }
 
 static bool has_vbmeta_partition() {
@@ -1002,6 +1021,11 @@
            fb->GetVar("partition-type:vbmeta_b", &partition_type) == fastboot::SUCCESS;
 }
 
+static bool is_logical(const std::string& partition) {
+    std::string value;
+    return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes";
+}
+
 static std::string fb_fix_numeric_var(std::string var) {
     // Some bootloaders (angler, for example), send spurious leading whitespace.
     var = android::base::Trim(var);
@@ -1011,27 +1035,30 @@
     return var;
 }
 
-static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
-    if (buf->sz < AVB_FOOTER_SIZE) {
-        return;
-    }
-
+static uint64_t get_partition_size(const std::string& partition) {
     std::string partition_size_str;
     if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) {
-        die("cannot get boot partition size");
+        if (!is_logical(partition)) {
+            return 0;
+        }
+        die("cannot get partition size for %s", partition.c_str());
     }
 
     partition_size_str = fb_fix_numeric_var(partition_size_str);
-    int64_t partition_size;
-    if (!android::base::ParseInt(partition_size_str, &partition_size)) {
+    uint64_t partition_size;
+    if (!android::base::ParseUint(partition_size_str, &partition_size)) {
+        if (!is_logical(partition)) {
+            return 0;
+        }
         die("Couldn't parse partition size '%s'.", partition_size_str.c_str());
     }
-    if (partition_size == buf->sz) {
+    return partition_size;
+}
+
+static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) {
+    if (buf->sz < AVB_FOOTER_SIZE) {
         return;
     }
-    if (partition_size < buf->sz) {
-        die("boot partition is smaller than boot image");
-    }
 
     std::string data;
     if (!android::base::ReadFdToString(buf->fd, &data)) {
@@ -1042,19 +1069,27 @@
     if (0 != data.compare(footer_offset, AVB_FOOTER_MAGIC_LEN, AVB_FOOTER_MAGIC)) {
         return;
     }
+    // If overflows and negative, it should be < buf->sz.
+    int64_t partition_size = static_cast<int64_t>(get_partition_size(partition));
 
-    int fd = make_temporary_fd("boot rewriting");
+    if (partition_size == buf->sz) {
+        return;
+    }
+    if (partition_size < buf->sz) {
+        die("boot partition is smaller than boot image");
+    }
+
+    unique_fd fd(make_temporary_fd("boot rewriting"));
     if (!android::base::WriteStringToFd(data, fd)) {
         die("Failed writing to modified boot");
     }
-    lseek(fd, partition_size - AVB_FOOTER_SIZE, SEEK_SET);
+    lseek(fd.get(), partition_size - AVB_FOOTER_SIZE, SEEK_SET);
     if (!android::base::WriteStringToFd(data.substr(footer_offset), fd)) {
         die("Failed copying AVB footer in boot");
     }
-    close(buf->fd);
-    buf->fd = fd;
+    buf->fd = std::move(fd);
     buf->sz = partition_size;
-    lseek(fd, 0, SEEK_SET);
+    lseek(buf->fd.get(), 0, SEEK_SET);
 }
 
 static void flash_buf(const std::string& partition, struct fastboot_buffer *buf)
@@ -1102,6 +1137,7 @@
 static std::string get_current_slot() {
     std::string current_slot;
     if (fb->GetVar("current-slot", &current_slot) != fastboot::SUCCESS) return "";
+    if (current_slot[0] == '_') current_slot.erase(0, 1);
     return current_slot;
 }
 
@@ -1183,8 +1219,10 @@
                              const std::function<void(const std::string&)>& func, bool force_slot) {
     std::string has_slot;
     std::string current_slot;
+    // |part| can be vendor_boot:default. Append slot to the first token.
+    auto part_tokens = android::base::Split(part, ":");
 
-    if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) {
+    if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) {
         /* If has-slot is not supported, the answer is no. */
         has_slot = "no";
     }
@@ -1194,14 +1232,15 @@
             if (current_slot == "") {
                 die("Failed to identify current slot");
             }
-            func(part + "_" + current_slot);
+            part_tokens[0] += "_" + current_slot;
         } else {
-            func(part + '_' + slot);
+            part_tokens[0] += "_" + slot;
         }
+        func(android::base::Join(part_tokens, ":"));
     } else {
         if (force_slot && slot != "") {
-             fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n",
-                     part.c_str(), slot.c_str());
+            fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n",
+                    part_tokens[0].c_str(), slot.c_str());
         }
         func(part);
     }
@@ -1215,10 +1254,13 @@
 static void do_for_partitions(const std::string& part, const std::string& slot,
                               const std::function<void(const std::string&)>& func, bool force_slot) {
     std::string has_slot;
+    // |part| can be vendor_boot:default. Query has-slot on the first token only.
+    auto part_tokens = android::base::Split(part, ":");
 
     if (slot == "all") {
-        if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) {
-            die("Could not check if partition %s has slot %s", part.c_str(), slot.c_str());
+        if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) {
+            die("Could not check if partition %s has slot %s", part_tokens[0].c_str(),
+                slot.c_str());
         }
         if (has_slot == "yes") {
             for (int i=0; i < get_slot_count(); i++) {
@@ -1232,11 +1274,6 @@
     }
 }
 
-static bool is_logical(const std::string& partition) {
-    std::string value;
-    return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes";
-}
-
 static bool is_retrofit_device() {
     std::string value;
     if (fb->GetVar("super-partition-name", &value) != fastboot::SUCCESS) {
@@ -1245,7 +1282,74 @@
     return android::base::StartsWith(value, "system_");
 }
 
+// Fetch a partition from the device to a given fd. This is a wrapper over FetchToFd to fetch
+// the full image.
+static uint64_t fetch_partition(const std::string& partition, borrowed_fd fd) {
+    uint64_t fetch_size = get_uint_var(FB_VAR_MAX_FETCH_SIZE);
+    if (fetch_size == 0) {
+        die("Unable to get %s. Device does not support fetch command.", FB_VAR_MAX_FETCH_SIZE);
+    }
+    uint64_t partition_size = get_partition_size(partition);
+    if (partition_size <= 0) {
+        die("Invalid partition size for partition %s: %" PRId64, partition.c_str(), partition_size);
+    }
+
+    uint64_t offset = 0;
+    while (offset < partition_size) {
+        uint64_t chunk_size = std::min(fetch_size, partition_size - offset);
+        if (fb->FetchToFd(partition, fd, offset, chunk_size) != fastboot::RetCode::SUCCESS) {
+            die("Unable to fetch %s (offset=%" PRIx64 ", size=%" PRIx64 ")", partition.c_str(),
+                offset, chunk_size);
+        }
+        offset += chunk_size;
+    }
+    return partition_size;
+}
+
+static void do_fetch(const std::string& partition, const std::string& slot_override,
+                     const std::string& outfile) {
+    unique_fd fd(TEMP_FAILURE_RETRY(
+            open(outfile.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY, 0644)));
+    auto fetch = std::bind(fetch_partition, _1, borrowed_fd(fd));
+    do_for_partitions(partition, slot_override, fetch, false /* force slot */);
+}
+
+// Return immediately if not flashing a vendor boot image. If flashing a vendor boot image,
+// repack vendor_boot image with an updated ramdisk. After execution, buf is set
+// to the new image to flash, and return value is the real partition name to flash.
+static std::string repack_ramdisk(const char* pname, struct fastboot_buffer* buf) {
+    std::string_view pname_sv{pname};
+
+    if (!android::base::StartsWith(pname_sv, "vendor_boot:") &&
+        !android::base::StartsWith(pname_sv, "vendor_boot_a:") &&
+        !android::base::StartsWith(pname_sv, "vendor_boot_b:")) {
+        return std::string(pname_sv);
+    }
+    if (buf->type != FB_BUFFER_FD) {
+        die("Flashing sparse vendor ramdisk image is not supported.");
+    }
+    if (buf->sz <= 0) {
+        die("repack_ramdisk() sees negative size: %" PRId64, buf->sz);
+    }
+    std::string partition(pname_sv.substr(0, pname_sv.find(':')));
+    std::string ramdisk(pname_sv.substr(pname_sv.find(':') + 1));
+
+    unique_fd vendor_boot(make_temporary_fd("vendor boot repack"));
+    uint64_t vendor_boot_size = fetch_partition(partition, vendor_boot);
+    auto repack_res = replace_vendor_ramdisk(vendor_boot, vendor_boot_size, ramdisk, buf->fd,
+                                             static_cast<uint64_t>(buf->sz));
+    if (!repack_res.ok()) {
+        die("%s", repack_res.error().message().c_str());
+    }
+
+    buf->fd = std::move(vendor_boot);
+    buf->sz = vendor_boot_size;
+    buf->image_size = vendor_boot_size;
+    return partition;
+}
+
 static void do_flash(const char* pname, const char* fname) {
+    verbose("Do flash %s %s", pname, fname);
     struct fastboot_buffer buf;
 
     if (!load_buf(fname, &buf)) {
@@ -1254,7 +1358,8 @@
     if (is_logical(pname)) {
         fb->ResizePartition(pname, std::to_string(buf.image_size));
     }
-    flash_buf(pname, &buf);
+    std::string flash_pname = repack_ramdisk(pname, &buf);
+    flash_buf(flash_pname, &buf);
 }
 
 // Sets slot_override as the active slot. If slot_override is blank,
@@ -1307,13 +1412,15 @@
 
 class ImageSource {
   public:
+    virtual ~ImageSource() {};
     virtual bool ReadFile(const std::string& name, std::vector<char>* out) const = 0;
-    virtual int OpenFile(const std::string& name) const = 0;
+    virtual unique_fd OpenFile(const std::string& name) const = 0;
 };
 
 class FlashAllTool {
   public:
-    FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary, bool wipe);
+    FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary,
+                 bool wipe, bool force_flash);
 
     void Flash();
 
@@ -1329,16 +1436,19 @@
     std::string slot_override_;
     bool skip_secondary_;
     bool wipe_;
+    bool force_flash_;
     std::string secondary_slot_;
     std::vector<std::pair<const Image*, std::string>> boot_images_;
     std::vector<std::pair<const Image*, std::string>> os_images_;
 };
 
-FlashAllTool::FlashAllTool(const ImageSource& source, const std::string& slot_override, bool skip_secondary, bool wipe)
+FlashAllTool::FlashAllTool(const ImageSource& source, const std::string& slot_override,
+                           bool skip_secondary, bool wipe, bool force_flash)
    : source_(source),
      slot_override_(slot_override),
      skip_secondary_(skip_secondary),
-     wipe_(wipe)
+     wipe_(wipe),
+     force_flash_(force_flash)
 {
 }
 
@@ -1386,7 +1496,7 @@
     if (!source_.ReadFile("android-info.txt", &contents)) {
         die("could not read android-info.txt");
     }
-    ::CheckRequirements({contents.data(), contents.size()});
+    ::CheckRequirements({contents.data(), contents.size()}, force_flash_);
 }
 
 void FlashAllTool::DetermineSecondarySlot() {
@@ -1426,8 +1536,8 @@
 void FlashAllTool::FlashImages(const std::vector<std::pair<const Image*, std::string>>& images) {
     for (const auto& [image, slot] : images) {
         fastboot_buffer buf;
-        int fd = source_.OpenFile(image->img_name);
-        if (fd < 0 || !load_buf_fd(fd, &buf)) {
+        unique_fd fd = source_.OpenFile(image->img_name);
+        if (fd < 0 || !load_buf_fd(std::move(fd), &buf)) {
             if (image->optional_if_no_image) {
                 continue;
             }
@@ -1454,7 +1564,7 @@
 }
 
 void FlashAllTool::UpdateSuperPartition() {
-    int fd = source_.OpenFile("super_empty.img");
+    unique_fd fd = source_.OpenFile("super_empty.img");
     if (fd < 0) {
         return;
     }
@@ -1492,7 +1602,7 @@
   public:
     explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {}
     bool ReadFile(const std::string& name, std::vector<char>* out) const override;
-    int OpenFile(const std::string& name) const override;
+    unique_fd OpenFile(const std::string& name) const override;
 
   private:
     ZipArchiveHandle zip_;
@@ -1502,18 +1612,19 @@
     return UnzipToMemory(zip_, name, out);
 }
 
-int ZipImageSource::OpenFile(const std::string& name) const {
+unique_fd ZipImageSource::OpenFile(const std::string& name) const {
     return unzip_to_file(zip_, name.c_str());
 }
 
-static void do_update(const char* filename, const std::string& slot_override, bool skip_secondary) {
+static void do_update(const char* filename, const std::string& slot_override, bool skip_secondary,
+                      bool force_flash) {
     ZipArchiveHandle zip;
     int error = OpenArchive(filename, &zip);
     if (error != 0) {
         die("failed to open zip file '%s': %s", filename, ErrorCodeString(error));
     }
 
-    FlashAllTool tool(ZipImageSource(zip), slot_override, skip_secondary, false);
+    FlashAllTool tool(ZipImageSource(zip), slot_override, skip_secondary, false, force_flash);
     tool.Flash();
 
     CloseArchive(zip);
@@ -1522,7 +1633,7 @@
 class LocalImageSource final : public ImageSource {
   public:
     bool ReadFile(const std::string& name, std::vector<char>* out) const override;
-    int OpenFile(const std::string& name) const override;
+    unique_fd OpenFile(const std::string& name) const override;
 };
 
 bool LocalImageSource::ReadFile(const std::string& name, std::vector<char>* out) const {
@@ -1533,13 +1644,14 @@
     return ReadFileToVector(path, out);
 }
 
-int LocalImageSource::OpenFile(const std::string& name) const {
+unique_fd LocalImageSource::OpenFile(const std::string& name) const {
     auto path = find_item_given_name(name);
-    return open(path.c_str(), O_RDONLY | O_BINARY);
+    return unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY)));
 }
 
-static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe) {
-    FlashAllTool tool(LocalImageSource(), slot_override, skip_secondary, wipe);
+static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe,
+                        bool force_flash) {
+    FlashAllTool tool(LocalImageSource(), slot_override, skip_secondary, wipe, force_flash);
     tool.Flash();
 }
 
@@ -1654,7 +1766,7 @@
     if (fd == -1) {
         die("Cannot open generated image: %s", strerror(errno));
     }
-    if (!load_buf_fd(fd.release(), &buf)) {
+    if (!load_buf_fd(std::move(fd), &buf)) {
         die("Cannot read image: %s", strerror(errno));
     }
     flash_buf(partition, &buf);
@@ -1949,6 +2061,7 @@
             if (slot_override == "") {
                 std::string current_slot;
                 if (fb->GetVar("current-slot", &current_slot) == fastboot::SUCCESS) {
+                    if (current_slot[0] == '_') current_slot.erase(0, 1);
                     next_active = verify_slot(current_slot, false);
                 } else {
                     wants_set_active = false;
@@ -2086,9 +2199,9 @@
         } else if (command == "flashall") {
             if (slot_override == "all") {
                 fprintf(stderr, "Warning: slot set to 'all'. Secondary slots will not be flashed.\n");
-                do_flashall(slot_override, true, wants_wipe);
+                do_flashall(slot_override, true, wants_wipe, force_flash);
             } else {
-                do_flashall(slot_override, skip_secondary, wants_wipe);
+                do_flashall(slot_override, skip_secondary, wants_wipe, force_flash);
             }
             wants_reboot = true;
         } else if (command == "update") {
@@ -2100,7 +2213,7 @@
             if (!args.empty()) {
                 filename = next_arg(&args);
             }
-            do_update(filename.c_str(), slot_override, skip_secondary || slot_all);
+            do_update(filename.c_str(), slot_override, skip_secondary || slot_all, force_flash);
             wants_reboot = true;
         } else if (command == FB_CMD_SET_ACTIVE) {
             std::string slot = verify_slot(next_arg(&args), false);
@@ -2112,7 +2225,7 @@
             if (!load_buf(filename.c_str(), &buf) || buf.type != FB_BUFFER_FD) {
                 die("cannot load '%s'", filename.c_str());
             }
-            fb->Download(filename, buf.fd, buf.sz);
+            fb->Download(filename, buf.fd.get(), buf.sz);
         } else if (command == "get_staged") {
             std::string filename = next_arg(&args);
             fb->Upload(filename);
@@ -2166,6 +2279,10 @@
                 syntax_error("expected: snapshot-update [cancel|merge]");
             }
             fb->SnapshotUpdateCommand(arg);
+        } else if (command == FB_CMD_FETCH) {
+            std::string partition = next_arg(&args);
+            std::string outfile = next_arg(&args);
+            do_fetch(partition, slot_override, outfile);
         } else {
             syntax_error("unknown command %s", command.c_str());
         }
diff --git a/fastboot/fastboot_driver.cpp b/fastboot/fastboot_driver.cpp
index 8d534ea..99a4873 100644
--- a/fastboot/fastboot_driver.cpp
+++ b/fastboot/fastboot_driver.cpp
@@ -30,6 +30,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -42,14 +43,17 @@
 
 #include <android-base/file.h>
 #include <android-base/mapped_file.h>
+#include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
+#include <storage_literals/storage_literals.h>
 
 #include "constants.h"
 #include "transport.h"
 
 using android::base::StringPrintf;
+using namespace android::storage_literals;
 
 namespace fastboot {
 
@@ -140,7 +144,8 @@
     return Flash(partition);
 }
 
-RetCode FastBootDriver::FlashPartition(const std::string& partition, int fd, uint32_t size) {
+RetCode FastBootDriver::FlashPartition(const std::string& partition, android::base::borrowed_fd fd,
+                                       uint32_t size) {
     RetCode ret;
     if ((ret = Download(partition, fd, size))) {
         return ret;
@@ -178,15 +183,16 @@
     return SUCCESS;
 }
 
-RetCode FastBootDriver::Download(const std::string& name, int fd, size_t size,
-                                 std::string* response, std::vector<std::string>* info) {
+RetCode FastBootDriver::Download(const std::string& name, android::base::borrowed_fd fd,
+                                 size_t size, std::string* response,
+                                 std::vector<std::string>* info) {
     prolog_(StringPrintf("Sending '%s' (%zu KB)", name.c_str(), size / 1024));
     auto result = Download(fd, size, response, info);
     epilog_(result);
     return result;
 }
 
-RetCode FastBootDriver::Download(int fd, size_t size, std::string* response,
+RetCode FastBootDriver::Download(android::base::borrowed_fd fd, size_t size, std::string* response,
                                  std::vector<std::string>* info) {
     RetCode ret;
 
@@ -297,41 +303,85 @@
     return result;
 }
 
-RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response,
-                                    std::vector<std::string>* info) {
+// This function executes cmd, then expect a "DATA" response with a number N, followed
+// by N bytes, and another response.
+// This is the common way for the device to send data to the driver used by upload and fetch.
+RetCode FastBootDriver::RunAndReadBuffer(
+        const std::string& cmd, std::string* response, std::vector<std::string>* info,
+        const std::function<RetCode(const char* data, uint64_t size)>& write_fn) {
     RetCode ret;
     int dsize = 0;
-    if ((ret = RawCommand(FB_CMD_UPLOAD, response, info, &dsize))) {
-        error_ = "Upload request failed: " + error_;
+    if ((ret = RawCommand(cmd, response, info, &dsize))) {
+        error_ = android::base::StringPrintf("%s request failed: %s", cmd.c_str(), error_.c_str());
         return ret;
     }
 
-    if (!dsize) {
-        error_ = "Upload request failed, device reports 0 bytes available";
+    if (dsize <= 0) {
+        error_ = android::base::StringPrintf("%s request failed, device reports %d bytes available",
+                                             cmd.c_str(), dsize);
         return BAD_DEV_RESP;
     }
 
-    std::vector<char> data;
-    data.resize(dsize);
-
-    if ((ret = ReadBuffer(data))) {
-        return ret;
+    const uint64_t total_size = dsize;
+    const uint64_t buf_size = std::min<uint64_t>(total_size, 1_MiB);
+    std::vector<char> data(buf_size);
+    uint64_t current_offset = 0;
+    while (current_offset < total_size) {
+        uint64_t remaining = total_size - current_offset;
+        uint64_t chunk_size = std::min(buf_size, remaining);
+        if ((ret = ReadBuffer(data.data(), chunk_size)) != SUCCESS) {
+            return ret;
+        }
+        if ((ret = write_fn(data.data(), chunk_size)) != SUCCESS) {
+            return ret;
+        }
+        current_offset += chunk_size;
     }
+    return HandleResponse(response, info);
+}
 
+RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response,
+                                    std::vector<std::string>* info) {
     std::ofstream ofs;
     ofs.open(outfile, std::ofstream::out | std::ofstream::binary);
     if (ofs.fail()) {
         error_ = android::base::StringPrintf("Failed to open '%s'", outfile.c_str());
         return IO_ERROR;
     }
-    ofs.write(data.data(), data.size());
-    if (ofs.fail() || ofs.bad()) {
-        error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str());
-        return IO_ERROR;
-    }
+    auto write_fn = [&](const char* data, uint64_t size) {
+        ofs.write(data, size);
+        if (ofs.fail() || ofs.bad()) {
+            error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str());
+            return IO_ERROR;
+        }
+        return SUCCESS;
+    };
+    RetCode ret = RunAndReadBuffer(FB_CMD_UPLOAD, response, info, write_fn);
     ofs.close();
+    return ret;
+}
 
-    return HandleResponse(response, info);
+RetCode FastBootDriver::FetchToFd(const std::string& partition, android::base::borrowed_fd fd,
+                                  int64_t offset, int64_t size, std::string* response,
+                                  std::vector<std::string>* info) {
+    prolog_(android::base::StringPrintf("Fetching %s (offset=%" PRIx64 ", size=%" PRIx64 ")",
+                                        partition.c_str(), offset, size));
+    std::string cmd = FB_CMD_FETCH ":" + partition;
+    if (offset >= 0) {
+        cmd += android::base::StringPrintf(":0x%08" PRIx64, offset);
+        if (size >= 0) {
+            cmd += android::base::StringPrintf(":0x%08" PRIx64, size);
+        }
+    }
+    RetCode ret = RunAndReadBuffer(cmd, response, info, [&](const char* data, uint64_t size) {
+        if (!android::base::WriteFully(fd, data, size)) {
+            error_ = android::base::StringPrintf("Cannot write: %s", strerror(errno));
+            return IO_ERROR;
+        }
+        return SUCCESS;
+    });
+    epilog_(ret);
+    return ret;
 }
 
 // Helpers
@@ -473,7 +523,7 @@
 }
 
 /******************************* PRIVATE **************************************/
-RetCode FastBootDriver::SendBuffer(int fd, size_t size) {
+RetCode FastBootDriver::SendBuffer(android::base::borrowed_fd fd, size_t size) {
     static constexpr uint32_t MAX_MAP_SIZE = 512 * 1024 * 1024;
     off64_t offset = 0;
     uint32_t remaining = size;
@@ -524,11 +574,6 @@
     return SUCCESS;
 }
 
-RetCode FastBootDriver::ReadBuffer(std::vector<char>& buf) {
-    // Read the buffer
-    return ReadBuffer(buf.data(), buf.size());
-}
-
 RetCode FastBootDriver::ReadBuffer(void* buf, size_t size) {
     // Read the buffer
     ssize_t tmp = transport_->Read(buf, size);
diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h
index 7265632..bccd668 100644
--- a/fastboot/fastboot_driver.h
+++ b/fastboot/fastboot_driver.h
@@ -34,6 +34,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
 #include <bootimg.h>
 #include <inttypes.h>
 #include <sparse/sparse.h>
@@ -76,9 +77,9 @@
     RetCode Continue(std::string* response = nullptr, std::vector<std::string>* info = nullptr);
     RetCode CreatePartition(const std::string& partition, const std::string& size);
     RetCode DeletePartition(const std::string& partition);
-    RetCode Download(const std::string& name, int fd, size_t size, std::string* response = nullptr,
-                     std::vector<std::string>* info = nullptr);
-    RetCode Download(int fd, size_t size, std::string* response = nullptr,
+    RetCode Download(const std::string& name, android::base::borrowed_fd fd, size_t size,
+                     std::string* response = nullptr, std::vector<std::string>* info = nullptr);
+    RetCode Download(android::base::borrowed_fd fd, size_t size, std::string* response = nullptr,
                      std::vector<std::string>* info = nullptr);
     RetCode Download(const std::string& name, const std::vector<char>& buf,
                      std::string* response = nullptr, std::vector<std::string>* info = nullptr);
@@ -106,10 +107,14 @@
                    std::vector<std::string>* info = nullptr);
     RetCode SnapshotUpdateCommand(const std::string& command, std::string* response = nullptr,
                                   std::vector<std::string>* info = nullptr);
+    RetCode FetchToFd(const std::string& partition, android::base::borrowed_fd fd,
+                      int64_t offset = -1, int64_t size = -1, std::string* response = nullptr,
+                      std::vector<std::string>* info = nullptr);
 
     /* HIGHER LEVEL COMMANDS -- Composed of the commands above */
     RetCode FlashPartition(const std::string& partition, const std::vector<char>& data);
-    RetCode FlashPartition(const std::string& partition, int fd, uint32_t sz);
+    RetCode FlashPartition(const std::string& partition, android::base::borrowed_fd fd,
+                           uint32_t sz);
     RetCode FlashPartition(const std::string& partition, sparse_file* s, uint32_t sz,
                            size_t current, size_t total);
 
@@ -145,15 +150,17 @@
     Transport* transport_;
 
   private:
-    RetCode SendBuffer(int fd, size_t size);
+    RetCode SendBuffer(android::base::borrowed_fd fd, size_t size);
     RetCode SendBuffer(const std::vector<char>& buf);
     RetCode SendBuffer(const void* buf, size_t size);
 
-    RetCode ReadBuffer(std::vector<char>& buf);
     RetCode ReadBuffer(void* buf, size_t size);
 
     RetCode UploadInner(const std::string& outfile, std::string* response = nullptr,
                         std::vector<std::string>* info = nullptr);
+    RetCode RunAndReadBuffer(const std::string& cmd, std::string* response,
+                             std::vector<std::string>* info,
+                             const std::function<RetCode(const char*, uint64_t)>& write_fn);
 
     int SparseWriteCallback(std::vector<char>& tpbuf, const char* data, size_t len);
 
diff --git a/fastboot/fs.cpp b/fastboot/fs.cpp
index 8addcb6..458a7a1 100644
--- a/fastboot/fs.cpp
+++ b/fastboot/fs.cpp
@@ -168,6 +168,19 @@
     return exec_cmd(e2fsdroid_args[0], e2fsdroid_args.data(), nullptr);
 }
 
+enum {
+    // clang-format off
+    FSCK_SUCCESS                 = 0,
+    FSCK_ERROR_CORRECTED         = 1 << 0,
+    FSCK_SYSTEM_SHOULD_REBOOT    = 1 << 1,
+    FSCK_ERRORS_LEFT_UNCORRECTED = 1 << 2,
+    FSCK_OPERATIONAL_ERROR       = 1 << 3,
+    FSCK_USAGE_OR_SYNTAX_ERROR   = 1 << 4,
+    FSCK_USER_CANCELLED          = 1 << 5,
+    FSCK_SHARED_LIB_ERROR        = 1 << 7,
+    // clang-format on
+};
+
 static int generate_f2fs_image(const char* fileName, long long partSize,
                                const std::string& initial_dir, unsigned /* unused */,
                                unsigned /* unused */, const unsigned fsOptions) {
@@ -216,7 +229,11 @@
     std::vector<const char*> sload_args = {sload_path.c_str(), "-S",
                                        "-f", initial_dir.c_str(), fileName, nullptr};
 
-    return exec_cmd(sload_args[0], sload_args.data(), nullptr);
+    ret = exec_cmd(sload_args[0], sload_args.data(), nullptr);
+    if (ret != 0 && ret != FSCK_ERROR_CORRECTED) {
+        return -1;
+    }
+    return 0;
 }
 
 static const struct fs_generator {
diff --git a/fastboot/fuzzy_fastboot/Android.bp b/fastboot/fuzzy_fastboot/Android.bp
index aa449b2..159c314 100644
--- a/fastboot/fuzzy_fastboot/Android.bp
+++ b/fastboot/fuzzy_fastboot/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_core_fastboot_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-BSD
+    default_applicable_licenses: ["system_core_fastboot_license"],
+}
+
 cc_test_host {
   name: "fuzzy_fastboot",
   compile_multilib: "first",
diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp
index 34ab32c..b6beaf9 100644
--- a/fastboot/fuzzy_fastboot/main.cpp
+++ b/fastboot/fuzzy_fastboot/main.cpp
@@ -43,8 +43,10 @@
 #include <thread>
 #include <vector>
 
+#include <android-base/file.h>
 #include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
+#include <android-base/strings.h>
 #include <gtest/gtest.h>
 #include <sparse/sparse.h>
 
@@ -349,22 +351,35 @@
     EXPECT_TRUE(var == "yes" || var == "no") << "getvar:battery-soc-ok must be 'yes' or 'no'";
 }
 
+void AssertHexUint32(const std::string& name, const std::string& var) {
+    ASSERT_NE(var, "") << "getvar:" << name << " responded with empty string";
+    // This must start with 0x
+    ASSERT_FALSE(isspace(var.front()))
+            << "getvar:" << name << " responded with a string with leading whitespace";
+    ASSERT_FALSE(var.compare(0, 2, "0x"))
+            << "getvar:" << name << " responded with a string that does not start with 0x...";
+    int64_t size = strtoll(var.c_str(), nullptr, 16);
+    ASSERT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:" << name;
+    // At most 32-bits
+    ASSERT_LE(size, std::numeric_limits<uint32_t>::max())
+            << "getvar:" << name << " must fit in a uint32_t";
+    ASSERT_LE(var.size(), FB_RESPONSE_SZ - 4)
+            << "getvar:" << name << " responded with too large of string: " + var;
+}
+
 TEST_F(Conformance, GetVarDownloadSize) {
     std::string var;
     EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
-    EXPECT_NE(var, "") << "getvar:max-download-size responded with empty string";
-    // This must start with 0x
-    EXPECT_FALSE(isspace(var.front()))
-            << "getvar:max-download-size responded with a string with leading whitespace";
-    EXPECT_FALSE(var.compare(0, 2, "0x"))
-            << "getvar:max-download-size responded with a string that does not start with 0x...";
-    int64_t size = strtoll(var.c_str(), nullptr, 16);
-    EXPECT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:max-download-size";
-    // At most 32-bits
-    EXPECT_LE(size, std::numeric_limits<uint32_t>::max())
-            << "getvar:max-download-size must fit in a uint32_t";
-    EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4)
-            << "getvar:max-download-size responded with too large of string: " + var;
+    AssertHexUint32("max-download-size", var);
+}
+
+// If fetch is supported, getvar:max-fetch-size must return a hex string.
+TEST_F(Conformance, GetVarFetchSize) {
+    std::string var;
+    if (SUCCESS != fb->GetVar("max-fetch-size", &var)) {
+        GTEST_SKIP() << "getvar:max-fetch-size failed";
+    }
+    AssertHexUint32("max-fetch-size", var);
 }
 
 TEST_F(Conformance, GetVarAll) {
@@ -656,6 +671,33 @@
     EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed in unlocked mode";
 }
 
+// If the implementation supports getvar:max-fetch-size, it must also support fetch:vendor_boot*.
+TEST_F(UnlockPermissions, FetchVendorBoot) {
+    std::string var;
+    uint64_t fetch_size;
+    if (fb->GetVar("max-fetch-size", &var) != SUCCESS) {
+        GTEST_SKIP() << "This test is skipped because fetch is not supported.";
+    }
+    ASSERT_FALSE(var.empty());
+    ASSERT_TRUE(android::base::ParseUint(var, &fetch_size)) << var << " is not an integer";
+    std::vector<std::tuple<std::string, uint64_t>> parts;
+    EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
+    for (const auto& [partition, partition_size] : parts) {
+        if (!android::base::StartsWith(partition, "vendor_boot")) continue;
+        TemporaryFile fetched;
+
+        uint64_t offset = 0;
+        while (offset < partition_size) {
+            uint64_t chunk_size = std::min(fetch_size, partition_size - offset);
+            auto ret = fb->FetchToFd(partition, fetched.fd, offset, chunk_size);
+            ASSERT_EQ(fastboot::RetCode::SUCCESS, ret)
+                    << "Unable to fetch " << partition << " (offset=" << offset
+                    << ", size=" << chunk_size << ")";
+            offset += chunk_size;
+        }
+    }
+}
+
 TEST_F(LockPermissions, DownloadFlash) {
     std::vector<char> buf{'a', 'o', 's', 'p'};
     EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download failed in locked mode";
@@ -717,6 +759,16 @@
     EXPECT_GT(resp.size(), 0) << "No error message was returned by device after FAIL";
 }
 
+TEST_F(LockPermissions, FetchVendorBoot) {
+    std::vector<std::tuple<std::string, uint64_t>> parts;
+    EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed";
+    for (const auto& [partition, _] : parts) {
+        TemporaryFile fetched;
+        ASSERT_EQ(fb->FetchToFd(partition, fetched.fd, 0, 0), DEVICE_FAIL)
+                << "fetch:" << partition << ":0:0 did not fail in locked mode";
+    }
+}
+
 TEST_F(Fuzz, DownloadSize) {
     std::string var;
     EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed";
diff --git a/fastboot/testdata/Android.bp b/fastboot/testdata/Android.bp
new file mode 100644
index 0000000..a490fe2
--- /dev/null
+++ b/fastboot/testdata/Android.bp
@@ -0,0 +1,140 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_binary_host {
+    name: "fastboot_gen_rand",
+    visibility: [":__subpackages__"],
+    srcs: ["fastboot_gen_rand.py"],
+}
+
+genrule_defaults {
+    name: "fastboot_test_data_gen_defaults",
+    visibility: ["//system/core/fastboot"],
+    tools: [
+        "fastboot_gen_rand",
+    ],
+}
+
+// Genrules for components of test vendor boot image.
+
+// Fake dtb image.
+genrule {
+    name: "fastboot_test_dtb",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    out: ["test_dtb.img"],
+    cmd: "$(location fastboot_gen_rand) --seed dtb --length 1024 > $(out)",
+}
+
+// Fake bootconfig image.
+genrule {
+    name: "fastboot_test_bootconfig",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    out: ["test_bootconfig.img"],
+    cmd: "$(location fastboot_gen_rand) --seed bootconfig --length 1024 > $(out)",
+}
+
+// Fake vendor ramdisk with type "none".
+genrule {
+    name: "fastboot_test_vendor_ramdisk_none",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    out: ["test_vendor_ramdisk_none.img"],
+    cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_none --length 1024 > $(out)",
+}
+
+// Fake vendor ramdisk with type "platform".
+genrule {
+    name: "fastboot_test_vendor_ramdisk_platform",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    out: ["test_vendor_ramdisk_platform.img"],
+    cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_platform --length 1024 > $(out)",
+}
+
+// Fake replacement ramdisk.
+genrule {
+    name: "fastboot_test_vendor_ramdisk_replace",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    out: ["test_vendor_ramdisk_replace.img"],
+    cmd: "$(location fastboot_gen_rand) --seed replace --length 3072 > $(out)",
+}
+
+// Genrules for test vendor boot images.
+
+fastboot_sign_test_image = "$(location avbtool) add_hash_footer --salt 00 --image $(out) " +
+    "--partition_name vendor_boot --partition_size $$(( 1 * 1024 * 1024 ))"
+
+genrule_defaults {
+    name: "fastboot_test_vendor_boot_gen_defaults",
+    defaults: ["fastboot_test_data_gen_defaults"],
+    tools: [
+        "avbtool",
+        "mkbootimg",
+    ],
+}
+
+genrule {
+    name: "fastboot_test_vendor_boot_v3",
+    defaults: ["fastboot_test_vendor_boot_gen_defaults"],
+    out: ["vendor_boot_v3.img"],
+    srcs: [
+        ":fastboot_test_dtb",
+        ":fastboot_test_vendor_ramdisk_none",
+    ],
+    cmd: "$(location mkbootimg) --header_version 3 " +
+        "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " +
+        "--dtb $(location :fastboot_test_dtb) " +
+        "--vendor_boot $(out) && " +
+        fastboot_sign_test_image,
+}
+
+genrule {
+    name: "fastboot_test_vendor_boot_v4_without_frag",
+    defaults: ["fastboot_test_vendor_boot_gen_defaults"],
+    out: ["vendor_boot_v4_without_frag.img"],
+    srcs: [
+        ":fastboot_test_dtb",
+        ":fastboot_test_vendor_ramdisk_none",
+        ":fastboot_test_bootconfig",
+    ],
+    cmd: "$(location mkbootimg) --header_version 4 " +
+        "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " +
+        "--dtb $(location :fastboot_test_dtb) " +
+        "--vendor_bootconfig $(location :fastboot_test_bootconfig) " +
+        "--vendor_boot $(out) && " +
+        fastboot_sign_test_image,
+}
+
+genrule {
+    name: "fastboot_test_vendor_boot_v4_with_frag",
+    defaults: ["fastboot_test_vendor_boot_gen_defaults"],
+    out: ["vendor_boot_v4_with_frag.img"],
+    srcs: [
+        ":fastboot_test_dtb",
+        ":fastboot_test_vendor_ramdisk_none",
+        ":fastboot_test_vendor_ramdisk_platform",
+        ":fastboot_test_bootconfig",
+    ],
+    cmd: "$(location mkbootimg) --header_version 4 " +
+        "--dtb $(location :fastboot_test_dtb) " +
+        "--vendor_bootconfig $(location :fastboot_test_bootconfig) " +
+        "--ramdisk_type none --ramdisk_name none_ramdisk " +
+        "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_none) " +
+        "--ramdisk_type platform --ramdisk_name platform_ramdisk " +
+        "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_platform) " +
+        "--vendor_boot $(out) && " +
+        fastboot_sign_test_image,
+}
diff --git a/fastboot/testdata/fastboot_gen_rand.py b/fastboot/testdata/fastboot_gen_rand.py
new file mode 100644
index 0000000..a87467b
--- /dev/null
+++ b/fastboot/testdata/fastboot_gen_rand.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+
+# 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.
+
+"""
+Write given number of random bytes, generated with optional seed.
+"""
+
+import random, argparse
+
+if __name__ == '__main__':
+  parser = argparse.ArgumentParser(description=__doc__)
+  parser.add_argument('--seed', help='Seed to random generator')
+  parser.add_argument('--length', type=int, required=True, help='Length of output')
+  args = parser.parse_args()
+
+  if args.seed:
+    random.seed(args.seed)
+
+  print(''.join(chr(random.randrange(0,0xff)) for _ in range(args.length)))
diff --git a/fastboot/vendor_boot_img_utils.cpp b/fastboot/vendor_boot_img_utils.cpp
new file mode 100644
index 0000000..9e09abb
--- /dev/null
+++ b/fastboot/vendor_boot_img_utils.cpp
@@ -0,0 +1,422 @@
+/*
+ * 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 "vendor_boot_img_utils.h"
+
+#include <string.h>
+
+#include <android-base/file.h>
+#include <android-base/result.h>
+#include <bootimg.h>
+#include <libavb/libavb.h>
+
+namespace {
+
+using android::base::Result;
+
+// Updates a given buffer by creating a new one.
+class DataUpdater {
+  public:
+    DataUpdater(const std::string& old_data) : old_data_(&old_data) {
+        old_data_ptr_ = old_data_->data();
+        new_data_.resize(old_data_->size(), '\0');
+        new_data_ptr_ = new_data_.data();
+    }
+    // Copy |num_bytes| from src to dst.
+    [[nodiscard]] Result<void> Copy(uint32_t num_bytes) {
+        if (num_bytes == 0) return {};
+        if (auto res = CheckAdvance(old_data_ptr_, old_end(), num_bytes, __FUNCTION__); !res.ok())
+            return res;
+        if (auto res = CheckAdvance(new_data_ptr_, new_end(), num_bytes, __FUNCTION__); !res.ok())
+            return res;
+        memcpy(new_data_ptr_, old_data_ptr_, num_bytes);
+        old_data_ptr_ += num_bytes;
+        new_data_ptr_ += num_bytes;
+        return {};
+    }
+    // Replace |old_num_bytes| from src with new data.
+    [[nodiscard]] Result<void> Replace(uint32_t old_num_bytes, const std::string& new_data) {
+        return Replace(old_num_bytes, new_data.data(), new_data.size());
+    }
+    [[nodiscard]] Result<void> Replace(uint32_t old_num_bytes, const void* new_data,
+                                       uint32_t new_data_size) {
+        if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_num_bytes, __FUNCTION__);
+            !res.ok())
+            return res;
+        old_data_ptr_ += old_num_bytes;
+
+        if (new_data_size == 0) return {};
+        if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_data_size, __FUNCTION__);
+            !res.ok())
+            return res;
+        memcpy(new_data_ptr_, new_data, new_data_size);
+        new_data_ptr_ += new_data_size;
+        return {};
+    }
+    // Skip |old_skip| from src and |new_skip| from dst, respectively.
+    [[nodiscard]] Result<void> Skip(uint32_t old_skip, uint32_t new_skip) {
+        if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_skip, __FUNCTION__); !res.ok())
+            return res;
+        old_data_ptr_ += old_skip;
+        if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_skip, __FUNCTION__); !res.ok())
+            return res;
+        new_data_ptr_ += new_skip;
+        return {};
+    }
+
+    [[nodiscard]] Result<void> Seek(uint32_t offset) {
+        if (offset > size()) return Errorf("Cannot seek 0x{:x}, size is 0x{:x}", offset, size());
+        old_data_ptr_ = old_begin() + offset;
+        new_data_ptr_ = new_begin() + offset;
+        return {};
+    }
+
+    std::string Finish() {
+        new_data_ptr_ = nullptr;
+        return std::move(new_data_);
+    }
+
+    [[nodiscard]] Result<void> CheckOffset(uint32_t old_offset, uint32_t new_offset) {
+        if (old_begin() + old_offset != old_cur())
+            return Errorf("Old offset mismatch: expected: 0x{:x}, actual: 0x{:x}", old_offset,
+                          old_cur() - old_begin());
+        if (new_begin() + new_offset != new_cur())
+            return Errorf("New offset mismatch: expected: 0x{:x}, actual: 0x{:x}", new_offset,
+                          new_cur() - new_begin());
+        return {};
+    }
+
+    uint64_t size() const { return old_data_->size(); }
+    const char* old_begin() const { return old_data_->data(); }
+    const char* old_cur() { return old_data_ptr_; }
+    const char* old_end() const { return old_data_->data() + old_data_->size(); }
+    char* new_begin() { return new_data_.data(); }
+    char* new_cur() { return new_data_ptr_; }
+    char* new_end() { return new_data_.data() + new_data_.size(); }
+
+  private:
+    // Check if it is okay to advance |num_bytes| from |current|.
+    [[nodiscard]] Result<void> CheckAdvance(const char* current, const char* end,
+                                            uint32_t num_bytes, const char* op) {
+        auto new_end = current + num_bytes;
+        if (new_end < current /* add overflow */)
+            return Errorf("{}: Addition overflow: 0x{} + 0x{:x} < 0x{}", op, fmt::ptr(current),
+                          num_bytes, fmt::ptr(current));
+        if (new_end > end)
+            return Errorf("{}: Boundary overflow: 0x{} + 0x{:x} > 0x{}", op, fmt::ptr(current),
+                          num_bytes, fmt::ptr(end));
+        return {};
+    }
+    const std::string* old_data_;
+    std::string new_data_;
+    const char* old_data_ptr_;
+    char* new_data_ptr_;
+};
+
+// Get the size of vendor boot header.
+[[nodiscard]] Result<uint32_t> get_vendor_boot_header_size(const vendor_boot_img_hdr_v3* hdr) {
+    if (hdr->header_version == 3) return sizeof(vendor_boot_img_hdr_v3);
+    if (hdr->header_version == 4) return sizeof(vendor_boot_img_hdr_v4);
+    return Errorf("Unrecognized vendor boot header version {}", hdr->header_version);
+}
+
+// Check that content contains a valid vendor boot image header with a version at least |version|.
+[[nodiscard]] Result<void> check_vendor_boot_hdr(const std::string& content, uint32_t version) {
+    // get_vendor_boot_header_size reads header_version, so make sure reading it does not
+    // go out of bounds by ensuring that the content has at least the size of V3 header.
+    if (content.size() < sizeof(vendor_boot_img_hdr_v3)) {
+        return Errorf("Size of vendor boot is 0x{:x}, less than size of V3 header: 0x{:x}",
+                      content.size(), sizeof(vendor_boot_img_hdr_v3));
+    }
+    // Now read hdr->header_version and assert the size.
+    auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(content.data());
+    auto expect_header_size = get_vendor_boot_header_size(hdr);
+    if (!expect_header_size.ok()) return expect_header_size.error();
+    if (content.size() < *expect_header_size) {
+        return Errorf("Size of vendor boot is 0x{:x}, less than size of V{} header: 0x{:x}",
+                      content.size(), version, *expect_header_size);
+    }
+    if (memcmp(hdr->magic, VENDOR_BOOT_MAGIC, VENDOR_BOOT_MAGIC_SIZE) != 0) {
+        return Errorf("Vendor boot image magic mismatch");
+    }
+    if (hdr->header_version < version) {
+        return Errorf("Require vendor boot header V{} but is V{}", version, hdr->header_version);
+    }
+    return {};
+}
+
+// Wrapper of ReadFdToString. Seek to the beginning and read the whole file to string.
+[[nodiscard]] Result<std::string> load_file(android::base::borrowed_fd fd, uint64_t expected_size,
+                                            const char* what) {
+    if (lseek(fd.get(), 0, SEEK_SET) != 0) {
+        return ErrnoErrorf("Can't seek to the beginning of {} image", what);
+    }
+    std::string content;
+    if (!android::base::ReadFdToString(fd, &content)) {
+        return ErrnoErrorf("Cannot read {} to string", what);
+    }
+    if (content.size() != expected_size) {
+        return Errorf("Size of {} does not match, expected 0x{:x}, read 0x{:x}", what,
+                      expected_size, content.size());
+    }
+    return content;
+}
+
+// Wrapper of WriteStringToFd. Seek to the beginning and write the whole file to string.
+[[nodiscard]] Result<void> store_file(android::base::borrowed_fd fd, const std::string& data,
+                                      const char* what) {
+    if (lseek(fd.get(), 0, SEEK_SET) != 0) {
+        return ErrnoErrorf("Cannot seek to beginning of {} before writing", what);
+    }
+    if (!android::base::WriteStringToFd(data, fd)) {
+        return ErrnoErrorf("Cannot write new content to {}", what);
+    }
+    if (TEMP_FAILURE_RETRY(ftruncate(fd.get(), data.size())) == -1) {
+        return ErrnoErrorf("Truncating new vendor boot image to 0x{:x} fails", data.size());
+    }
+    return {};
+}
+
+// Copy AVB footer if it exists in the old buffer.
+[[nodiscard]] Result<void> copy_avb_footer(DataUpdater* updater) {
+    if (updater->size() < AVB_FOOTER_SIZE) return {};
+    if (auto res = updater->Seek(updater->size() - AVB_FOOTER_SIZE); !res.ok()) return res;
+    if (memcmp(updater->old_cur(), AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) != 0) return {};
+    return updater->Copy(AVB_FOOTER_SIZE);
+}
+
+// round |value| up to a multiple of |page_size|.
+inline uint32_t round_up(uint32_t value, uint32_t page_size) {
+    return (value + page_size - 1) / page_size * page_size;
+}
+
+// Replace the vendor ramdisk as a whole.
+[[nodiscard]] Result<std::string> replace_default_vendor_ramdisk(const std::string& vendor_boot,
+                                                                 const std::string& new_ramdisk) {
+    if (auto res = check_vendor_boot_hdr(vendor_boot, 3); !res.ok()) return res.error();
+    auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(vendor_boot.data());
+    auto hdr_size = get_vendor_boot_header_size(hdr);
+    if (!hdr_size.ok()) return hdr_size.error();
+    // Refer to bootimg.h for details. Numbers are in bytes.
+    const uint32_t o = round_up(*hdr_size, hdr->page_size);
+    const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+    const uint32_t q = round_up(hdr->dtb_size, hdr->page_size);
+
+    DataUpdater updater(vendor_boot);
+
+    // Copy header (O bytes), then update fields in header.
+    if (auto res = updater.Copy(o); !res.ok()) return res.error();
+    auto new_hdr = reinterpret_cast<vendor_boot_img_hdr_v3*>(updater.new_begin());
+    new_hdr->vendor_ramdisk_size = new_ramdisk.size();
+    // Because it is unknown how the new ramdisk is fragmented, the whole table is replaced
+    // with a single entry representing the full ramdisk.
+    if (new_hdr->header_version >= 4) {
+        auto new_hdr_v4 = static_cast<vendor_boot_img_hdr_v4*>(new_hdr);
+        new_hdr_v4->vendor_ramdisk_table_entry_size = sizeof(vendor_ramdisk_table_entry_v4);
+        new_hdr_v4->vendor_ramdisk_table_entry_num = 1;
+        new_hdr_v4->vendor_ramdisk_table_size = new_hdr_v4->vendor_ramdisk_table_entry_num *
+                                                new_hdr_v4->vendor_ramdisk_table_entry_size;
+    }
+
+    // Copy the new ramdisk.
+    if (auto res = updater.Replace(hdr->vendor_ramdisk_size, new_ramdisk); !res.ok())
+        return res.error();
+    const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size);
+    if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size);
+        !res.ok())
+        return res.error();
+    if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error();
+
+    // Copy DTB (Q bytes).
+    if (auto res = updater.Copy(q); !res.ok()) return res.error();
+
+    if (new_hdr->header_version >= 4) {
+        auto hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(hdr);
+        const uint32_t r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size);
+        const uint32_t s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size);
+
+        auto new_entry = reinterpret_cast<vendor_ramdisk_table_entry_v4*>(updater.new_cur());
+        auto new_hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(new_hdr);
+        auto new_r = round_up(new_hdr_v4->vendor_ramdisk_table_size, new_hdr->page_size);
+        if (auto res = updater.Skip(r, new_r); !res.ok()) return res.error();
+        if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + new_r); !res.ok())
+            return res.error();
+
+        // Replace table with single entry representing the full ramdisk.
+        new_entry->ramdisk_size = new_hdr->vendor_ramdisk_size;
+        new_entry->ramdisk_offset = 0;
+        new_entry->ramdisk_type = VENDOR_RAMDISK_TYPE_NONE;
+        memset(new_entry->ramdisk_name, '\0', VENDOR_RAMDISK_NAME_SIZE);
+        memset(new_entry->board_id, '\0', VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE);
+
+        // Copy bootconfig (S bytes).
+        if (auto res = updater.Copy(s); !res.ok()) return res.error();
+    }
+
+    if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error();
+    return updater.Finish();
+}
+
+// Find a ramdisk fragment with a unique name. Abort if none or multiple fragments are found.
+[[nodiscard]] Result<const vendor_ramdisk_table_entry_v4*> find_unique_ramdisk(
+        const std::string& ramdisk_name, const vendor_ramdisk_table_entry_v4* table,
+        uint32_t size) {
+    const vendor_ramdisk_table_entry_v4* ret = nullptr;
+    uint32_t idx = 0;
+    const vendor_ramdisk_table_entry_v4* entry = table;
+    for (; idx < size; idx++, entry++) {
+        auto entry_name_c_str = reinterpret_cast<const char*>(entry->ramdisk_name);
+        auto entry_name_len = strnlen(entry_name_c_str, VENDOR_RAMDISK_NAME_SIZE);
+        std::string_view entry_name(entry_name_c_str, entry_name_len);
+        if (entry_name == ramdisk_name) {
+            if (ret != nullptr) {
+                return Errorf("Multiple vendor ramdisk '{}' found, name should be unique",
+                              ramdisk_name.c_str());
+            }
+            ret = entry;
+        }
+    }
+    if (ret == nullptr) {
+        return Errorf("Vendor ramdisk '{}' not found", ramdisk_name.c_str());
+    }
+    return ret;
+}
+
+// Find the vendor ramdisk fragment with |ramdisk_name| within the content of |vendor_boot|, and
+// replace it with the content of |new_ramdisk|.
+[[nodiscard]] Result<std::string> replace_vendor_ramdisk_fragment(const std::string& ramdisk_name,
+                                                                  const std::string& vendor_boot,
+                                                                  const std::string& new_ramdisk) {
+    if (auto res = check_vendor_boot_hdr(vendor_boot, 4); !res.ok()) return res.error();
+    auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v4*>(vendor_boot.data());
+    auto hdr_size = get_vendor_boot_header_size(hdr);
+    if (!hdr_size.ok()) return hdr_size.error();
+    // Refer to bootimg.h for details. Numbers are in bytes.
+    const uint32_t o = round_up(*hdr_size, hdr->page_size);
+    const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+    const uint32_t q = round_up(hdr->dtb_size, hdr->page_size);
+    const uint32_t r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size);
+    const uint32_t s = round_up(hdr->bootconfig_size, hdr->page_size);
+
+    if (hdr->vendor_ramdisk_table_entry_num == std::numeric_limits<uint32_t>::max()) {
+        return Errorf("Too many vendor ramdisk entries in table, overflow");
+    }
+
+    // Find entry with name |ramdisk_name|.
+    auto old_table_start =
+            reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(vendor_boot.data() + o + p + q);
+    auto find_res =
+            find_unique_ramdisk(ramdisk_name, old_table_start, hdr->vendor_ramdisk_table_entry_num);
+    if (!find_res.ok()) return find_res.error();
+    const vendor_ramdisk_table_entry_v4* replace_entry = *find_res;
+    uint32_t replace_idx = replace_entry - old_table_start;
+
+    // Now reconstruct.
+    DataUpdater updater(vendor_boot);
+
+    // Copy header (O bytes), then update fields in header.
+    if (auto res = updater.Copy(o); !res.ok()) return res.error();
+    auto new_hdr = reinterpret_cast<vendor_boot_img_hdr_v4*>(updater.new_begin());
+
+    // Copy ramdisk fragments, replace for the matching index.
+    {
+        auto old_ramdisk_entry = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(
+                vendor_boot.data() + o + p + q);
+        uint32_t new_total_ramdisk_size = 0;
+        for (uint32_t new_ramdisk_idx = 0; new_ramdisk_idx < hdr->vendor_ramdisk_table_entry_num;
+             new_ramdisk_idx++, old_ramdisk_entry++) {
+            if (new_ramdisk_idx == replace_idx) {
+                if (auto res = updater.Replace(replace_entry->ramdisk_size, new_ramdisk); !res.ok())
+                    return res.error();
+                new_total_ramdisk_size += new_ramdisk.size();
+            } else {
+                if (auto res = updater.Copy(old_ramdisk_entry->ramdisk_size); !res.ok())
+                    return res.error();
+                new_total_ramdisk_size += old_ramdisk_entry->ramdisk_size;
+            }
+        }
+        new_hdr->vendor_ramdisk_size = new_total_ramdisk_size;
+    }
+
+    // Pad ramdisk to page boundary.
+    const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size);
+    if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size);
+        !res.ok())
+        return res.error();
+    if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error();
+
+    // Copy DTB (Q bytes).
+    if (auto res = updater.Copy(q); !res.ok()) return res.error();
+
+    // Copy table, but with corresponding entries modified, including:
+    // - ramdisk_size of the entry replaced
+    // - ramdisk_offset of subsequent entries.
+    for (uint32_t new_total_ramdisk_size = 0, new_entry_idx = 0;
+         new_entry_idx < hdr->vendor_ramdisk_table_entry_num; new_entry_idx++) {
+        auto new_entry = reinterpret_cast<vendor_ramdisk_table_entry_v4*>(updater.new_cur());
+        if (auto res = updater.Copy(hdr->vendor_ramdisk_table_entry_size); !res.ok())
+            return res.error();
+        new_entry->ramdisk_offset = new_total_ramdisk_size;
+
+        if (new_entry_idx == replace_idx) {
+            new_entry->ramdisk_size = new_ramdisk.size();
+        }
+        new_total_ramdisk_size += new_entry->ramdisk_size;
+    }
+
+    // Copy padding of R pages; this is okay because table size is not changed.
+    if (auto res = updater.Copy(r - hdr->vendor_ramdisk_table_entry_num *
+                                            hdr->vendor_ramdisk_table_entry_size);
+        !res.ok())
+        return res.error();
+    if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + r); !res.ok())
+        return res.error();
+
+    // Copy bootconfig (S bytes).
+    if (auto res = updater.Copy(s); !res.ok()) return res.error();
+
+    if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error();
+    return updater.Finish();
+}
+
+}  // namespace
+
+[[nodiscard]] Result<void> replace_vendor_ramdisk(android::base::borrowed_fd vendor_boot_fd,
+                                                  uint64_t vendor_boot_size,
+                                                  const std::string& ramdisk_name,
+                                                  android::base::borrowed_fd new_ramdisk_fd,
+                                                  uint64_t new_ramdisk_size) {
+    if (new_ramdisk_size > std::numeric_limits<uint32_t>::max()) {
+        return Errorf("New vendor ramdisk is too big");
+    }
+
+    auto vendor_boot = load_file(vendor_boot_fd, vendor_boot_size, "vendor boot");
+    if (!vendor_boot.ok()) return vendor_boot.error();
+    auto new_ramdisk = load_file(new_ramdisk_fd, new_ramdisk_size, "new vendor ramdisk");
+    if (!new_ramdisk.ok()) return new_ramdisk.error();
+
+    Result<std::string> new_vendor_boot;
+    if (ramdisk_name == "default") {
+        new_vendor_boot = replace_default_vendor_ramdisk(*vendor_boot, *new_ramdisk);
+    } else {
+        new_vendor_boot = replace_vendor_ramdisk_fragment(ramdisk_name, *vendor_boot, *new_ramdisk);
+    }
+    if (!new_vendor_boot.ok()) return new_vendor_boot.error();
+    if (auto res = store_file(vendor_boot_fd, *new_vendor_boot, "new vendor boot image"); !res.ok())
+        return res.error();
+
+    return {};
+}
diff --git a/fastboot/vendor_boot_img_utils.h b/fastboot/vendor_boot_img_utils.h
new file mode 100644
index 0000000..0b702bc
--- /dev/null
+++ b/fastboot/vendor_boot_img_utils.h
@@ -0,0 +1,34 @@
+/*
+ * 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 <inttypes.h>
+
+#include <string>
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+
+// Replace the vendor ramdisk named |ramdisk_name| within the vendor boot image,
+// specified by |vendor_boot_fd|, with the ramdisk specified by |new_ramdisk_fd|. Checks
+// that the size of the files are |vendor_boot_size| and |new_ramdisk_size|, respectively.
+// If |ramdisk_name| is "default", replace the vendor ramdisk as a whole. Otherwise, replace
+// a vendor ramdisk fragment with the given unique name.
+[[nodiscard]] android::base::Result<void> replace_vendor_ramdisk(
+        android::base::borrowed_fd vendor_boot_fd, uint64_t vendor_boot_size,
+        const std::string& ramdisk_name, android::base::borrowed_fd new_ramdisk_fd,
+        uint64_t new_ramdisk_size);
diff --git a/fastboot/vendor_boot_img_utils_test.cpp b/fastboot/vendor_boot_img_utils_test.cpp
new file mode 100644
index 0000000..1563b89
--- /dev/null
+++ b/fastboot/vendor_boot_img_utils_test.cpp
@@ -0,0 +1,429 @@
+/*
+ * 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 <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <optional>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <android-base/strings.h>
+#include <bootimg.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <libavb/libavb.h>
+
+#include "vendor_boot_img_utils.h"
+
+using android::base::borrowed_fd;
+using android::base::ErrnoError;
+using android::base::GetExecutableDirectory;
+using android::base::ReadFdToString;
+using android::base::Result;
+using testing::AllOf;
+using testing::Each;
+using testing::Eq;
+using testing::HasSubstr;
+using testing::Not;
+using testing::Property;
+using std::string_literals::operator""s;
+
+// Expect that the Result<T> returned by |expr| is successful, and value matches |result_matcher|.
+#define EXPECT_RESULT(expr, result_matcher)                          \
+    EXPECT_THAT(expr, AllOf(Property(&decltype(expr)::ok, Eq(true)), \
+                            Property(&decltype(expr)::value, result_matcher)))
+
+// Expect that the Result<T> returned by |expr| fails, and error message matches |error_matcher|.
+#define EXPECT_ERROR(expr, error_matcher)                                                        \
+    do {                                                                                         \
+        EXPECT_THAT(                                                                             \
+                expr,                                                                            \
+                AllOf(Property(&decltype(expr)::ok, Eq(false)),                                  \
+                      Property(&decltype(expr)::error,                                           \
+                               Property(&decltype(expr)::error_type::message, error_matcher)))); \
+    } while (0)
+
+namespace {
+
+// Wrapper of fstat.
+Result<uint64_t> FileSize(borrowed_fd fd, std::filesystem::path path) {
+    struct stat sb;
+    if (fstat(fd.get(), &sb) == -1) return ErrnoError() << "fstat(" << path << ")";
+    return sb.st_size;
+}
+
+// Seek to beginning then read the whole file.
+Result<std::string> ReadStartOfFdToString(borrowed_fd fd, std::filesystem::path path) {
+    if (lseek64(fd.get(), 0, SEEK_SET) != 0)
+        return ErrnoError() << "lseek64(" << path << ", 0, SEEK_SET)";
+    std::string content;
+    if (!android::base::ReadFdToString(fd, &content)) return ErrnoError() << "read(" << path << ")";
+    return content;
+}
+
+// Round |value| up to page boundary.
+inline uint32_t round_up(uint32_t value, uint32_t page_size) {
+    return (value + page_size - 1) / page_size * page_size;
+}
+
+// Match is successful if |arg| is a zero-padded version of |expected|.
+MATCHER_P(IsPadded, expected, (negation ? "is" : "isn't") + " zero-padded of expected value"s) {
+    if (arg.size() < expected.size()) return false;
+    if (0 != memcmp(arg.data(), expected.data(), expected.size())) return false;
+    auto remainder = std::string_view(arg).substr(expected.size());
+    for (char e : remainder)
+        if (e != '\0') return false;
+    return true;
+}
+
+// Same as Eq, but don't print the content to avoid spam.
+MATCHER_P(MemEq, expected, (negation ? "is" : "isn't") + " expected value"s) {
+    if (arg.size() != expected.size()) return false;
+    return 0 == memcmp(arg.data(), expected.data(), expected.size());
+}
+
+// Expect that |arg| and |expected| has the same AVB footer.
+MATCHER_P(HasSameAvbFooter, expected,
+          (negation ? "has" : "does not have") + "expected AVB footer"s) {
+    if (expected.size() < AVB_FOOTER_SIZE || arg.size() < AVB_FOOTER_SIZE) return false;
+    return std::string_view(expected).substr(expected.size() - AVB_FOOTER_SIZE) ==
+           std::string_view(arg).substr(arg.size() - AVB_FOOTER_SIZE);
+}
+
+// A lazy handle of a file.
+struct TestFileHandle {
+    virtual ~TestFileHandle() = default;
+    // Lazily call OpenImpl(), cache result in open_result_.
+    android::base::Result<void> Open() {
+        if (!open_result_.has_value()) open_result_ = OpenImpl();
+        return open_result_.value();
+    }
+    // The original size at the time when the file is opened. If the file has been modified,
+    // this field is NOT updated.
+    uint64_t size() {
+        CHECK(open_result_.has_value());
+        return size_;
+    }
+    // The current size of the file. If the file has been modified since opened,
+    // this is updated.
+    Result<uint64_t> fsize() {
+        CHECK(open_result_.has_value());
+        return FileSize(fd_, abs_path_);
+    }
+    borrowed_fd fd() {
+        CHECK(open_result_.has_value());
+        return fd_;
+    }
+    Result<std::string> Read() {
+        CHECK(open_result_.has_value());
+        return ReadStartOfFdToString(fd_, abs_path_);
+    }
+
+  private:
+    std::filesystem::path abs_path_;
+    uint64_t size_;
+    std::optional<android::base::Result<void>> open_result_;
+    borrowed_fd fd_{-1};
+    // Opens |rel_path_| as a readonly fd, pass it to Transform, and store result to
+    // |borrowed_fd_|.
+    android::base::Result<void> OpenImpl() {
+        android::base::unique_fd read_fd(TEMP_FAILURE_RETRY(
+                open(abs_path_.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_BINARY)));
+        if (!read_fd.ok()) return ErrnoError() << "open(" << abs_path_ << ")";
+        auto size = FileSize(read_fd, abs_path_);
+        if (!size.ok()) return size.error();
+        size_ = *size;
+
+        auto borrowed_fd = Transform(abs_path_, std::move(read_fd));
+        if (!borrowed_fd.ok()) return borrowed_fd.error();
+        fd_ = borrowed_fd.value();
+
+        return {};
+    }
+
+  protected:
+    // |rel_path| is the relative path under test data directory.
+    TestFileHandle(const std::filesystem::path& rel_path)
+        : abs_path_(std::move(std::filesystem::path(GetExecutableDirectory()) / rel_path)) {}
+    // Given |read_fd|, the readonly fd on the test file, return an fd that's suitable for client
+    // to use. Implementation is responsible for managing the lifetime of the returned fd.
+    virtual android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path,
+                                                         android::base::unique_fd read_fd) = 0;
+};
+
+// A TestFileHandle where the file is readonly.
+struct ReadOnlyTestFileHandle : TestFileHandle {
+    ReadOnlyTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {}
+
+  private:
+    android::base::unique_fd owned_fd_;
+    android::base::Result<borrowed_fd> Transform(const std::filesystem::path&,
+                                                 android::base::unique_fd read_fd) override {
+        owned_fd_ = std::move(read_fd);
+        return owned_fd_;
+    }
+};
+
+// A TestFileHandle where the test file is copies, hence read-writable.
+struct ReadWriteTestFileHandle : TestFileHandle {
+    ReadWriteTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {}
+
+  private:
+    std::unique_ptr<TemporaryFile> temp_file_;
+
+    android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path,
+                                                 android::base::unique_fd read_fd) override {
+        // Make a copy to avoid writing to test data. Test files are small, so it is okay
+        // to read the whole file.
+        auto content = ReadStartOfFdToString(read_fd, abs_path);
+        if (!content.ok()) return content.error();
+        temp_file_ = std::make_unique<TemporaryFile>();
+        if (temp_file_->fd == -1)
+            return ErrnoError() << "copy " << abs_path << ": open temp file failed";
+        if (!android::base::WriteStringToFd(*content, temp_file_->fd))
+            return ErrnoError() << "copy " << abs_path << ": write temp file failed";
+
+        return temp_file_->fd;
+    }
+};
+
+class RepackVendorBootImgTestEnv : public ::testing::Environment {
+  public:
+    virtual void SetUp() {
+        OpenTestFile("test_dtb.img", &dtb, &dtb_content);
+        OpenTestFile("test_bootconfig.img", &bootconfig, &bootconfig_content);
+        OpenTestFile("test_vendor_ramdisk_none.img", &none, &none_content);
+        OpenTestFile("test_vendor_ramdisk_platform.img", &platform, &platform_content);
+        OpenTestFile("test_vendor_ramdisk_replace.img", &replace, &replace_content);
+    }
+
+    std::unique_ptr<TestFileHandle> dtb;
+    std::string dtb_content;
+    std::unique_ptr<TestFileHandle> bootconfig;
+    std::string bootconfig_content;
+    std::unique_ptr<TestFileHandle> none;
+    std::string none_content;
+    std::unique_ptr<TestFileHandle> platform;
+    std::string platform_content;
+    std::unique_ptr<TestFileHandle> replace;
+    std::string replace_content;
+
+  private:
+    void OpenTestFile(const char* rel_path, std::unique_ptr<TestFileHandle>* handle,
+                      std::string* content) {
+        *handle = std::make_unique<ReadOnlyTestFileHandle>(rel_path);
+        ASSERT_RESULT_OK((*handle)->Open());
+        auto content_res = (*handle)->Read();
+        ASSERT_RESULT_OK(content_res);
+        *content = *content_res;
+    }
+};
+RepackVendorBootImgTestEnv* env = nullptr;
+
+struct RepackVendorBootImgTestParam {
+    std::string vendor_boot_file_name;
+    uint32_t expected_header_version;
+    friend std::ostream& operator<<(std::ostream& os, const RepackVendorBootImgTestParam& param) {
+        return os << param.vendor_boot_file_name;
+    }
+};
+
+class RepackVendorBootImgTest : public ::testing::TestWithParam<RepackVendorBootImgTestParam> {
+  public:
+    virtual void SetUp() {
+        vboot = std::make_unique<ReadWriteTestFileHandle>(GetParam().vendor_boot_file_name);
+        ASSERT_RESULT_OK(vboot->Open());
+    }
+    std::unique_ptr<TestFileHandle> vboot;
+};
+
+TEST_P(RepackVendorBootImgTest, InvalidSize) {
+    EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size() + 1, "default",
+                                        env->replace->fd(), env->replace->size()),
+                 HasSubstr("Size of vendor boot does not match"));
+    EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default", env->replace->fd(),
+                                        env->replace->size() + 1),
+                 HasSubstr("Size of new vendor ramdisk does not match"));
+}
+
+TEST_P(RepackVendorBootImgTest, ReplaceUnknown) {
+    auto res = replace_vendor_ramdisk(vboot->fd(), vboot->size(), "unknown", env->replace->fd(),
+                                      env->replace->size());
+    if (GetParam().expected_header_version == 3) {
+        EXPECT_ERROR(res, Eq("Require vendor boot header V4 but is V3"));
+    } else if (GetParam().expected_header_version == 4) {
+        EXPECT_ERROR(res, Eq("Vendor ramdisk 'unknown' not found"));
+    }
+}
+
+TEST_P(RepackVendorBootImgTest, ReplaceDefault) {
+    auto old_content = vboot->Read();
+    ASSERT_RESULT_OK(old_content);
+
+    ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default",
+                                            env->replace->fd(), env->replace->size()));
+    EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack";
+
+    auto new_content_res = vboot->Read();
+    ASSERT_RESULT_OK(new_content_res);
+    std::string_view new_content(*new_content_res);
+
+    auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(new_content.data());
+    ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE));
+    ASSERT_EQ(GetParam().expected_header_version, hdr->header_version);
+    EXPECT_EQ(hdr->vendor_ramdisk_size, env->replace->size());
+    EXPECT_EQ(hdr->dtb_size, env->dtb->size());
+
+    auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size);
+    auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+    auto q = round_up(hdr->dtb_size, hdr->page_size);
+
+    EXPECT_THAT(new_content.substr(o, p), IsPadded(env->replace_content));
+    EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content));
+
+    if (hdr->header_version < 4) return;
+
+    auto hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(hdr);
+    EXPECT_EQ(hdr_v4->vendor_ramdisk_table_entry_num, 1);
+    EXPECT_EQ(hdr_v4->vendor_ramdisk_table_size, 1 * hdr_v4->vendor_ramdisk_table_entry_size);
+    EXPECT_GE(hdr_v4->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4));
+    auto entry = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]);
+    EXPECT_EQ(entry->ramdisk_offset, 0);
+    EXPECT_EQ(entry->ramdisk_size, hdr_v4->vendor_ramdisk_size);
+    EXPECT_EQ(entry->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE);
+
+    EXPECT_EQ(hdr_v4->bootconfig_size, env->bootconfig->size());
+    auto r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size);
+    auto s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size);
+    EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content));
+
+    EXPECT_THAT(new_content, HasSameAvbFooter(*old_content));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        RepackVendorBootImgTest, RepackVendorBootImgTest,
+        ::testing::Values(RepackVendorBootImgTestParam{"vendor_boot_v3.img", 3},
+                          RepackVendorBootImgTestParam{"vendor_boot_v4_with_frag.img", 4},
+                          RepackVendorBootImgTestParam{"vendor_boot_v4_without_frag.img", 4}),
+        [](const auto& info) {
+            return android::base::StringReplace(info.param.vendor_boot_file_name, ".", "_", false);
+        });
+
+std::string_view GetRamdiskName(const vendor_ramdisk_table_entry_v4* entry) {
+    auto ramdisk_name = reinterpret_cast<const char*>(entry->ramdisk_name);
+    return std::string_view(ramdisk_name, strnlen(ramdisk_name, VENDOR_RAMDISK_NAME_SIZE));
+}
+
+class RepackVendorBootImgTestV4 : public ::testing::TestWithParam<uint32_t /* ramdisk type */> {
+  public:
+    virtual void SetUp() {
+        vboot = std::make_unique<ReadWriteTestFileHandle>("vendor_boot_v4_with_frag.img");
+        ASSERT_RESULT_OK(vboot->Open());
+    }
+    std::unique_ptr<TestFileHandle> vboot;
+};
+
+TEST_P(RepackVendorBootImgTestV4, Replace) {
+    uint32_t replace_ramdisk_type = GetParam();
+    std::string replace_ramdisk_name;
+    std::string expect_new_ramdisk_content;
+    uint32_t expect_none_size = env->none->size();
+    uint32_t expect_platform_size = env->platform->size();
+    switch (replace_ramdisk_type) {
+        case VENDOR_RAMDISK_TYPE_NONE:
+            replace_ramdisk_name = "none_ramdisk";
+            expect_new_ramdisk_content = env->replace_content + env->platform_content;
+            expect_none_size = env->replace->size();
+            break;
+        case VENDOR_RAMDISK_TYPE_PLATFORM:
+            replace_ramdisk_name = "platform_ramdisk";
+            expect_new_ramdisk_content = env->none_content + env->replace_content;
+            expect_platform_size = env->replace->size();
+            break;
+        default:
+            LOG(FATAL) << "Ramdisk type " << replace_ramdisk_type
+                       << " is not supported by this test.";
+    }
+
+    auto old_content = vboot->Read();
+    ASSERT_RESULT_OK(old_content);
+
+    ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), replace_ramdisk_name,
+                                            env->replace->fd(), env->replace->size()));
+    EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack";
+
+    auto new_content_res = vboot->Read();
+    ASSERT_RESULT_OK(new_content_res);
+    std::string_view new_content(*new_content_res);
+
+    auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v4*>(new_content.data());
+    ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE));
+    ASSERT_EQ(4, hdr->header_version);
+    EXPECT_EQ(hdr->vendor_ramdisk_size, expect_none_size + expect_platform_size);
+    EXPECT_EQ(hdr->dtb_size, env->dtb->size());
+    EXPECT_EQ(hdr->bootconfig_size, env->bootconfig->size());
+
+    auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size);
+    auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
+    auto q = round_up(hdr->dtb_size, hdr->page_size);
+    auto r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size);
+    auto s = round_up(hdr->bootconfig_size, hdr->page_size);
+
+    EXPECT_THAT(new_content.substr(o, p), IsPadded(expect_new_ramdisk_content));
+    EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content));
+
+    // Check changes in table.
+    EXPECT_EQ(hdr->vendor_ramdisk_table_entry_num, 2);
+    EXPECT_EQ(hdr->vendor_ramdisk_table_size, 2 * hdr->vendor_ramdisk_table_entry_size);
+    EXPECT_GE(hdr->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4));
+    auto entry_none =
+            reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]);
+    EXPECT_EQ(entry_none->ramdisk_offset, 0);
+    EXPECT_EQ(entry_none->ramdisk_size, expect_none_size);
+    EXPECT_EQ(entry_none->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE);
+    EXPECT_EQ(GetRamdiskName(entry_none), "none_ramdisk");
+
+    auto entry_platform = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(
+            &new_content[o + p + q + hdr->vendor_ramdisk_table_entry_size]);
+    EXPECT_EQ(entry_platform->ramdisk_offset, expect_none_size);
+    EXPECT_EQ(entry_platform->ramdisk_size, expect_platform_size);
+    EXPECT_EQ(entry_platform->ramdisk_type, VENDOR_RAMDISK_TYPE_PLATFORM);
+    EXPECT_EQ(GetRamdiskName(entry_platform), "platform_ramdisk");
+
+    EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content));
+
+    EXPECT_THAT(new_content, HasSameAvbFooter(*old_content));
+}
+INSTANTIATE_TEST_SUITE_P(RepackVendorBootImgTest, RepackVendorBootImgTestV4,
+                         ::testing::Values(VENDOR_RAMDISK_TYPE_NONE, VENDOR_RAMDISK_TYPE_PLATFORM),
+                         [](const auto& info) {
+                             return info.param == VENDOR_RAMDISK_TYPE_NONE ? "none" : "platform";
+                         });
+
+}  // namespace
+
+int main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    env = static_cast<RepackVendorBootImgTestEnv*>(
+            testing::AddGlobalTestEnvironment(new RepackVendorBootImgTestEnv));
+    return RUN_ALL_TESTS();
+}
diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp
index ac784b2..5356b00 100644
--- a/fs_mgr/Android.bp
+++ b/fs_mgr/Android.bp
@@ -14,6 +14,34 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["system_core_fs_mgr_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+    name: "system_core_fs_mgr_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+        "SPDX-license-identifier-MIT",
+    ],
+    // large-scale-change unable to identify any license_text files
+}
+
 cc_defaults {
     name: "fs_mgr_defaults",
     sanitize: {
@@ -170,6 +198,7 @@
     defaults: ["fs_mgr_defaults"],
     static_libs: [
         "libavb_user",
+        "libgsid",
         "libutils",
         "libvold_binder",
     ],
diff --git a/fs_mgr/OWNERS b/fs_mgr/OWNERS
index cbbd3bc..cf353a1 100644
--- a/fs_mgr/OWNERS
+++ b/fs_mgr/OWNERS
@@ -1,3 +1,2 @@
 bowgotsai@google.com
 dvander@google.com
-tomcherry@google.com
diff --git a/fs_mgr/README.overlayfs.md b/fs_mgr/README.overlayfs.md
index ca782b9..94b2f8c 100644
--- a/fs_mgr/README.overlayfs.md
+++ b/fs_mgr/README.overlayfs.md
@@ -8,8 +8,8 @@
 system partition as read-write and then add or modify any number of files
 without reflashing the system image, which is efficient for a development cycle.
 
-Limited memory systems use read-only types of file systems or logical resizable
-Android partitions (LRAPs). These file systems land system partition images
+Limited memory systems use read-only types of file systems or dynamic
+Android partitions (DAPs). These file systems land system partition images
 right-sized, and have been deduped at the block level to compress the content.
 This means that a remount either isn’t possible, or isn't useful because of
 space limitations or support logistics.
diff --git a/fs_mgr/TEST_MAPPING b/fs_mgr/TEST_MAPPING
index a349408..84709b6 100644
--- a/fs_mgr/TEST_MAPPING
+++ b/fs_mgr/TEST_MAPPING
@@ -17,6 +17,9 @@
     },
     {
       "name": "libsnapshot_fuzzer_test"
+    },
+    {
+      "name": "cow_api_test"
     }
   ]
 }
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 6294b3f..ea9d333 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -264,12 +264,12 @@
                 F2FS_FSCK_BIN, "-f", "-c", "10000", "--debug-cache", blk_device.c_str()};
 
         if (should_force_check(*fs_stat)) {
-            LINFO << "Running " << F2FS_FSCK_BIN << " -f -c 10000 --debug-cache"
+            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);
         } else {
-            LINFO << "Running " << F2FS_FSCK_BIN << " -a -c 10000 --debug-cache"
+            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);
@@ -647,6 +647,46 @@
     return sb == cpu_to_le32(F2FS_SUPER_MAGIC);
 }
 
+static void SetReadAheadSize(const std::string& entry_block_device, off64_t size_kb) {
+    std::string block_device;
+    if (!Realpath(entry_block_device, &block_device)) {
+        PERROR << "Failed to realpath " << entry_block_device;
+        return;
+    }
+
+    static constexpr std::string_view kDevBlockPrefix("/dev/block/");
+    if (!android::base::StartsWith(block_device, kDevBlockPrefix)) {
+        LWARNING << block_device << " is not a block device";
+        return;
+    }
+
+    DeviceMapper& dm = DeviceMapper::Instance();
+    while (true) {
+        std::string block_name = block_device;
+        if (android::base::StartsWith(block_device, kDevBlockPrefix)) {
+            block_name = block_device.substr(kDevBlockPrefix.length());
+        }
+        std::string sys_partition =
+                android::base::StringPrintf("/sys/class/block/%s/partition", block_name.c_str());
+        struct stat info;
+        if (lstat(sys_partition.c_str(), &info) == 0) {
+            // it has a partition like "sda12".
+            block_name += "/..";
+        }
+        std::string sys_ra = android::base::StringPrintf("/sys/class/block/%s/queue/read_ahead_kb",
+                                                         block_name.c_str());
+        std::string size = android::base::StringPrintf("%llu", (long long)size_kb);
+        android::base::WriteStringToFile(size, sys_ra.c_str());
+        LINFO << "Set readahead_kb: " << size << " on " << sys_ra;
+
+        auto parent = dm.GetParentBlockDeviceByPath(block_device);
+        if (!parent) {
+            return;
+        }
+        block_device = *parent;
+    }
+}
+
 //
 // Prepare the filesystem on the given block device to be mounted.
 //
@@ -656,7 +696,22 @@
 // If needed, we'll also enable (or disable) filesystem features as specified by
 // the fstab record.
 //
-static int prepare_fs_for_mount(const std::string& blk_device, const FstabEntry& entry) {
+static int prepare_fs_for_mount(const std::string& blk_device, const FstabEntry& entry,
+                                const std::string& alt_mount_point = "") {
+    auto& mount_point = alt_mount_point.empty() ? entry.mount_point : alt_mount_point;
+    // We need this because sometimes we have legacy symlinks that are
+    // lingering around and need cleaning up.
+    struct stat info;
+    if (lstat(mount_point.c_str(), &info) == 0 && (info.st_mode & S_IFMT) == S_IFLNK) {
+        unlink(mount_point.c_str());
+    }
+    mkdir(mount_point.c_str(), 0755);
+
+    // Don't need to return error, since it's a salt
+    if (entry.readahead_size_kb != -1) {
+        SetReadAheadSize(blk_device, entry.readahead_size_kb);
+    }
+
     int fs_stat = 0;
 
     if (is_extfs(entry.fs_type)) {
@@ -684,7 +739,7 @@
 
     if (entry.fs_mgr_flags.check ||
         (fs_stat & (FS_STAT_UNCLEAN_SHUTDOWN | FS_STAT_QUOTA_ENABLED))) {
-        check_fs(blk_device, entry.fs_type, entry.mount_point, &fs_stat);
+        check_fs(blk_device, entry.fs_type, mount_point, &fs_stat);
     }
 
     if (is_extfs(entry.fs_type) &&
@@ -729,13 +784,6 @@
 // sets the underlying block device to read-only if the mount is read-only.
 // See "man 2 mount" for return values.
 static int __mount(const std::string& source, const std::string& target, const FstabEntry& entry) {
-    // We need this because sometimes we have legacy symlinks that are
-    // lingering around and need cleaning up.
-    struct stat info;
-    if (lstat(target.c_str(), &info) == 0 && (info.st_mode & S_IFMT) == S_IFLNK) {
-        unlink(target.c_str());
-    }
-    mkdir(target.c_str(), 0755);
     errno = 0;
     unsigned long mountflags = entry.flags;
     int ret = 0;
@@ -1321,6 +1369,7 @@
     int error_count = 0;
     CheckpointManager checkpoint_manager;
     AvbUniquePtr avb_handle(nullptr);
+    bool wiped = false;
 
     bool userdata_mounted = false;
     if (fstab->empty()) {
@@ -1454,7 +1503,8 @@
                 encryptable = status;
                 if (status == FS_MGR_MNTALL_DEV_NEEDS_METADATA_ENCRYPTION) {
                     if (!call_vdc({"cryptfs", "encryptFstab", attempted_entry.blk_device,
-                                   attempted_entry.mount_point},
+                                   attempted_entry.mount_point, wiped ? "true" : "false",
+                                   attempted_entry.fs_type},
                                   nullptr)) {
                         LERROR << "Encryption failed";
                         set_type_property(encryptable);
@@ -1471,7 +1521,7 @@
         }
 
         // Mounting failed, understand why and retry.
-        bool wiped = partition_wiped(current_entry.blk_device.c_str());
+        wiped = partition_wiped(current_entry.blk_device.c_str());
         bool crypt_footer = false;
         if (mount_errno != EBUSY && mount_errno != EACCES &&
             current_entry.fs_mgr_flags.formattable && wiped) {
@@ -1496,6 +1546,27 @@
             } else if (current_entry.is_encryptable() && current_entry.key_loc == KEY_IN_FOOTER) {
                 crypt_footer = true;
             }
+
+            // EncryptInplace will be used when vdc gives an error or needs to format partitions
+            // other than /data
+            if (should_use_metadata_encryption(current_entry) &&
+                current_entry.mount_point == "/data") {
+
+                // vdc->Format requires "ro.crypto.type" to set an encryption flag
+                encryptable = FS_MGR_MNTALL_DEV_IS_METADATA_ENCRYPTED;
+                set_type_property(encryptable);
+
+                if (!call_vdc({"cryptfs", "encryptFstab", current_entry.blk_device,
+                               current_entry.mount_point, "true" /* shouldFormat */,
+                               current_entry.fs_type},
+                              nullptr)) {
+                    LERROR << "Encryption failed";
+                } else {
+                    userdata_mounted = true;
+                    continue;
+                }
+            }
+
             if (fs_mgr_do_format(current_entry, crypt_footer) == 0) {
                 // Let's replay the mount actions.
                 i = top_idx - 1;
@@ -1799,17 +1870,18 @@
 
 // wrapper to __mount() and expects a fully prepared fstab_rec,
 // unlike fs_mgr_do_mount which does more things with avb / verity etc.
-int fs_mgr_do_mount_one(const FstabEntry& entry, const std::string& mount_point) {
+int fs_mgr_do_mount_one(const FstabEntry& entry, const std::string& alt_mount_point) {
     // First check the filesystem if requested.
     if (entry.fs_mgr_flags.wait && !WaitForFile(entry.blk_device, 20s)) {
         LERROR << "Skipping mounting '" << entry.blk_device << "'";
     }
 
-    // Run fsck if needed
-    prepare_fs_for_mount(entry.blk_device, entry);
+    auto& mount_point = alt_mount_point.empty() ? entry.mount_point : alt_mount_point;
 
-    int ret =
-            __mount(entry.blk_device, mount_point.empty() ? entry.mount_point : mount_point, entry);
+    // Run fsck if needed
+    prepare_fs_for_mount(entry.blk_device, entry, mount_point);
+
+    int ret = __mount(entry.blk_device, mount_point, entry);
     if (ret) {
       ret = (errno == EBUSY) ? FS_MGR_DOMNT_BUSY : FS_MGR_DOMNT_FAILED;
     }
@@ -1868,7 +1940,14 @@
             continue;
         }
 
-        int fs_stat = prepare_fs_for_mount(n_blk_device, fstab_entry);
+        // Now mount it where requested */
+        if (tmp_mount_point) {
+            mount_point = tmp_mount_point;
+        } else {
+            mount_point = fstab_entry.mount_point;
+        }
+
+        int fs_stat = prepare_fs_for_mount(n_blk_device, fstab_entry, mount_point);
 
         if (fstab_entry.fs_mgr_flags.avb) {
             if (!avb_handle) {
@@ -1902,12 +1981,6 @@
             }
         }
 
-        // Now mount it where requested */
-        if (tmp_mount_point) {
-            mount_point = tmp_mount_point;
-        } else {
-            mount_point = fstab_entry.mount_point;
-        }
         int retry_count = 2;
         while (retry_count-- > 0) {
             if (!__mount(n_blk_device, mount_point, fstab_entry)) {
@@ -1919,7 +1992,7 @@
                 mount_errors++;
                 fs_stat |= FS_STAT_FULL_MOUNT_FAILED;
                 // try again after fsck
-                check_fs(n_blk_device, fstab_entry.fs_type, fstab_entry.mount_point, &fs_stat);
+                check_fs(n_blk_device, fstab_entry.fs_type, mount_point, &fs_stat);
             }
         }
         log_fs_stat(fstab_entry.blk_device, fs_stat);
@@ -2115,6 +2188,41 @@
     return false;
 }
 
+std::string fs_mgr_get_hashtree_algorithm(const android::fs_mgr::FstabEntry& entry) {
+    if (!entry.fs_mgr_flags.verify && !entry.fs_mgr_flags.avb) {
+        return "";
+    }
+    DeviceMapper& dm = DeviceMapper::Instance();
+    std::string device = GetVerityDeviceName(entry);
+
+    std::vector<DeviceMapper::TargetInfo> table;
+    if (dm.GetState(device) == DmDeviceState::INVALID || !dm.GetTableInfo(device, &table)) {
+        return "";
+    }
+    for (const auto& target : table) {
+        if (strcmp(target.spec.target_type, "verity") != 0) {
+            continue;
+        }
+
+        // The format is stable for dm-verity version 0 & 1. And the data is expected to have
+        // the fixed format:
+        // <version> <dev> <hash_dev> <data_block_size> <hash_block_size> <num_data_blocks>
+        // <hash_start_block> <algorithm> <digest> <salt>
+        // Details in https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html
+
+        std::vector<std::string> tokens = android::base::Split(target.data, " \t\r\n");
+        if (tokens[0] != "0" && tokens[0] != "1") {
+            LOG(WARNING) << "Unrecognized device mapper version in " << target.data;
+            return "";
+        }
+
+        // Hashtree algorithm is the 8th token in the output
+        return android::base::Trim(tokens[7]);
+    }
+
+    return "";
+}
+
 bool fs_mgr_verity_is_check_at_most_once(const android::fs_mgr::FstabEntry& entry) {
     if (!entry.fs_mgr_flags.verify && !entry.fs_mgr_flags.avb) {
         return false;
@@ -2140,7 +2248,8 @@
     // Devices upgrading to dynamic partitions are allowed to specify a super
     // partition name. This includes cuttlefish, which is a non-A/B device.
     std::string super_partition;
-    if (fs_mgr_get_boot_config_from_kernel_cmdline("super_partition", &super_partition)) {
+    if (fs_mgr_get_boot_config_from_bootconfig_source("super_partition", &super_partition) ||
+        fs_mgr_get_boot_config_from_kernel_cmdline("super_partition", &super_partition)) {
         if (fs_mgr_get_slot_suffix().empty()) {
             return super_partition;
         }
diff --git a/fs_mgr/fs_mgr_boot_config.cpp b/fs_mgr/fs_mgr_boot_config.cpp
index abece4d..e3ef232 100644
--- a/fs_mgr/fs_mgr_boot_config.cpp
+++ b/fs_mgr/fs_mgr_boot_config.cpp
@@ -26,7 +26,7 @@
 
 #include "fs_mgr_priv.h"
 
-std::vector<std::pair<std::string, std::string>> fs_mgr_parse_boot_config(const std::string& cmdline) {
+std::vector<std::pair<std::string, std::string>> fs_mgr_parse_cmdline(const std::string& cmdline) {
     static constexpr char quote = '"';
 
     std::vector<std::pair<std::string, std::string>> result;
@@ -60,12 +60,56 @@
     return result;
 }
 
+std::vector<std::pair<std::string, std::string>> fs_mgr_parse_proc_bootconfig(
+        const std::string& cmdline) {
+    static constexpr char quote = '"';
+
+    std::vector<std::pair<std::string, std::string>> result;
+    for (auto& line : android::base::Split(cmdline, "\n")) {
+        line.erase(std::remove(line.begin(), line.end(), quote), line.end());
+        auto equal_sign = line.find('=');
+        if (equal_sign == line.npos) {
+            if (!line.empty()) {
+                // no difference between <key> and <key>=
+                result.emplace_back(std::move(line), "");
+            }
+        } else {
+            result.emplace_back(android::base::Trim(line.substr(0, equal_sign)),
+                                android::base::Trim(line.substr(equal_sign + 1)));
+        }
+    }
+
+    return result;
+}
+
+bool fs_mgr_get_boot_config_from_bootconfig(const std::string& bootconfig,
+                                            const std::string& android_key, std::string* out_val) {
+    FS_MGR_CHECK(out_val != nullptr);
+
+    const std::string bootconfig_key("androidboot." + android_key);
+    for (const auto& [key, value] : fs_mgr_parse_proc_bootconfig(bootconfig)) {
+        if (key == bootconfig_key) {
+            *out_val = value;
+            return true;
+        } else if (android_key == "hardware" && android_key == key) {
+            // bootconfig doesn't allow subkeys and values to coexist, so
+            // "androidboot.hardware" cannot be used. It is replaced in
+            // bootconfig with "hardware"
+            *out_val = value;
+            return true;
+        }
+    }
+
+    *out_val = "";
+    return false;
+}
+
 bool fs_mgr_get_boot_config_from_kernel(const std::string& cmdline, const std::string& android_key,
                                         std::string* out_val) {
     FS_MGR_CHECK(out_val != nullptr);
 
     const std::string cmdline_key("androidboot." + android_key);
-    for (const auto& [key, value] : fs_mgr_parse_boot_config(cmdline)) {
+    for (const auto& [key, value] : fs_mgr_parse_cmdline(cmdline)) {
         if (key == cmdline_key) {
             *out_val = value;
             return true;
@@ -76,6 +120,17 @@
     return false;
 }
 
+// Tries to get the given boot config value from bootconfig.
+// Returns true if successfully found, false otherwise.
+bool fs_mgr_get_boot_config_from_bootconfig_source(const std::string& key, std::string* out_val) {
+    std::string bootconfig;
+    if (!android::base::ReadFileToString("/proc/bootconfig", &bootconfig)) return false;
+    if (!bootconfig.empty() && bootconfig.back() == '\n') {
+        bootconfig.pop_back();
+    }
+    return fs_mgr_get_boot_config_from_bootconfig(bootconfig, key, out_val);
+}
+
 // Tries to get the given boot config value from kernel cmdline.
 // Returns true if successfully found, false otherwise.
 bool fs_mgr_get_boot_config_from_kernel_cmdline(const std::string& key, std::string* out_val) {
@@ -110,6 +165,11 @@
         return true;
     }
 
+    // next, check if we have the property in bootconfig
+    if (fs_mgr_get_boot_config_from_bootconfig_source(key, out_val)) {
+        return true;
+    }
+
     // finally, fallback to kernel cmdline, properties may not be ready yet
     if (fs_mgr_get_boot_config_from_kernel_cmdline(key, out_val)) {
         return true;
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index 42459ec..853b24d 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -17,6 +17,7 @@
 #include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
+#include <fnmatch.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -254,6 +255,13 @@
             } else {
                 entry->reserved_size = static_cast<off64_t>(size);
             }
+        } else if (StartsWith(flag, "readahead_size_kb=")) {
+            int val;
+            if (ParseInt(arg, &val, 0, 16 * 1024)) {
+                entry->readahead_size_kb = val;
+            } else {
+                LWARNING << "Warning: readahead_size_kb= flag malformed (0 ~ 16MB): " << arg;
+            }
         } else if (StartsWith(flag, "eraseblk=")) {
             // The erase block size flag is followed by an = and the flash erase block size. Get it,
             // check that it is a power of 2 and at least 4096, and return it.
@@ -290,6 +298,8 @@
             if (!ParseByteCount(arg, &entry->zram_backingdev_size)) {
                 LWARNING << "Warning: zram_backingdev_size= flag malformed: " << arg;
             }
+        } else if (StartsWith(flag, "lowerdir=")) {
+            entry->lowerdir = arg;
         } else {
             LWARNING << "Warning: unknown flag: " << flag;
         }
@@ -299,7 +309,8 @@
 std::string InitAndroidDtDir() {
     std::string android_dt_dir;
     // The platform may specify a custom Android DT path in kernel cmdline
-    if (!fs_mgr_get_boot_config_from_kernel_cmdline("android_dt_dir", &android_dt_dir)) {
+    if (!fs_mgr_get_boot_config_from_bootconfig_source("android_dt_dir", &android_dt_dir) &&
+        !fs_mgr_get_boot_config_from_kernel_cmdline("android_dt_dir", &android_dt_dir)) {
         // Fall back to the standard procfs-based path
         android_dt_dir = kDefaultAndroidDtDir;
     }
@@ -412,7 +423,8 @@
 
         if (!fs_mgr_get_boot_config(prop, &suffix)) continue;
 
-        for (const char* prefix : {"/odm/etc/fstab.", "/vendor/etc/fstab.", "/fstab."}) {
+        for (const char* prefix :
+             {"/odm/etc/fstab.", "/vendor/etc/fstab.", "/fstab.", "/first_stage_ramdisk/fstab."}) {
             std::string fstab_path = prefix + suffix;
             if (access(fstab_path.c_str(), F_OK) == 0) {
                 return fstab_path;
@@ -678,7 +690,7 @@
     }
 }
 
-bool ReadFstabFromFile(const std::string& path, Fstab* fstab) {
+bool ReadFstabFromFile(const std::string& path, Fstab* fstab_out) {
     auto fstab_file = std::unique_ptr<FILE, decltype(&fclose)>{fopen(path.c_str(), "re"), fclose};
     if (!fstab_file) {
         PERROR << __FUNCTION__ << "(): cannot open file: '" << path << "'";
@@ -687,41 +699,51 @@
 
     bool is_proc_mounts = path == "/proc/mounts";
 
-    if (!ReadFstabFile(fstab_file.get(), is_proc_mounts, fstab)) {
+    Fstab fstab;
+    if (!ReadFstabFile(fstab_file.get(), is_proc_mounts, &fstab)) {
         LERROR << __FUNCTION__ << "(): failed to load fstab from : '" << path << "'";
         return false;
     }
-    if (!is_proc_mounts && !access(android::gsi::kGsiBootedIndicatorFile, F_OK)) {
-        // This is expected to fail if host is android Q, since Q doesn't
-        // support DSU slotting. The DSU "active" indicator file would be
-        // non-existent or empty if DSU is enabled within the guest system.
-        // In that case, just use the default slot name "dsu".
-        std::string dsu_slot;
-        if (!android::gsi::GetActiveDsu(&dsu_slot)) {
-            PWARNING << __FUNCTION__ << "(): failed to get active dsu slot";
+    if (!is_proc_mounts) {
+        if (!access(android::gsi::kGsiBootedIndicatorFile, F_OK)) {
+            // This is expected to fail if host is android Q, since Q doesn't
+            // support DSU slotting. The DSU "active" indicator file would be
+            // non-existent or empty if DSU is enabled within the guest system.
+            // In that case, just use the default slot name "dsu".
+            std::string dsu_slot;
+            if (!android::gsi::GetActiveDsu(&dsu_slot) && errno != ENOENT) {
+                PERROR << __FUNCTION__ << "(): failed to get active DSU slot";
+                return false;
+            }
+            if (dsu_slot.empty()) {
+                dsu_slot = "dsu";
+                LWARNING << __FUNCTION__ << "(): assuming default DSU slot: " << dsu_slot;
+            }
+            // This file is non-existent on Q vendor.
+            std::string lp_names;
+            if (!ReadFileToString(gsi::kGsiLpNamesFile, &lp_names) && errno != ENOENT) {
+                PERROR << __FUNCTION__ << "(): failed to read DSU LP names";
+                return false;
+            }
+            TransformFstabForDsu(&fstab, dsu_slot, Split(lp_names, ","));
+        } else if (errno != ENOENT) {
+            PERROR << __FUNCTION__ << "(): failed to access() DSU booted indicator";
+            return false;
         }
-        if (dsu_slot.empty()) {
-            dsu_slot = "dsu";
-        }
-
-        std::string lp_names;
-        ReadFileToString(gsi::kGsiLpNamesFile, &lp_names);
-        TransformFstabForDsu(fstab, dsu_slot, Split(lp_names, ","));
     }
 
-#ifndef NO_SKIP_MOUNT
-    SkipMountingPartitions(fstab);
-#endif
-    EnableMandatoryFlags(fstab);
+    SkipMountingPartitions(&fstab, false /* verbose */);
+    EnableMandatoryFlags(&fstab);
 
+    *fstab_out = std::move(fstab);
     return true;
 }
 
 // Returns fstab entries parsed from the device tree if they exist
-bool ReadFstabFromDt(Fstab* fstab, bool log) {
+bool ReadFstabFromDt(Fstab* fstab, bool verbose) {
     std::string fstab_buf = ReadFstabFromDt();
     if (fstab_buf.empty()) {
-        if (log) LINFO << __FUNCTION__ << "(): failed to read fstab from dt";
+        if (verbose) LINFO << __FUNCTION__ << "(): failed to read fstab from dt";
         return false;
     }
 
@@ -729,34 +751,36 @@
         fmemopen(static_cast<void*>(const_cast<char*>(fstab_buf.c_str())),
                  fstab_buf.length(), "r"), fclose);
     if (!fstab_file) {
-        if (log) PERROR << __FUNCTION__ << "(): failed to create a file stream for fstab dt";
+        if (verbose) PERROR << __FUNCTION__ << "(): failed to create a file stream for fstab dt";
         return false;
     }
 
     if (!ReadFstabFile(fstab_file.get(), false, fstab)) {
-        if (log) {
+        if (verbose) {
             LERROR << __FUNCTION__ << "(): failed to load fstab from kernel:" << std::endl
                    << fstab_buf;
         }
         return false;
     }
 
-#ifndef NO_SKIP_MOUNT
-    SkipMountingPartitions(fstab);
-#endif
+    SkipMountingPartitions(fstab, verbose);
 
     return true;
 }
 
-#ifndef NO_SKIP_MOUNT
+#ifdef NO_SKIP_MOUNT
+bool SkipMountingPartitions(Fstab*, bool) {
+    return true;
+}
+#else
 // For GSI to skip mounting /product and /system_ext, until there are well-defined interfaces
 // between them and /system. Otherwise, the GSI flashed on /system might not be able to work with
 // device-specific /product and /system_ext. skip_mount.cfg belongs to system_ext partition because
 // only common files for all targets can be put into system partition. It is under
 // /system/system_ext because GSI is a single system.img that includes the contents of system_ext
 // partition and product partition under /system/system_ext and /system/product, respectively.
-bool SkipMountingPartitions(Fstab* fstab) {
-    constexpr const char kSkipMountConfig[] = "/system/system_ext/etc/init/config/skip_mount.cfg";
+bool SkipMountingPartitions(Fstab* fstab, bool verbose) {
+    static constexpr char kSkipMountConfig[] = "/system/system_ext/etc/init/config/skip_mount.cfg";
 
     std::string skip_config;
     auto save_errno = errno;
@@ -765,29 +789,39 @@
         return true;
     }
 
-    for (const auto& skip_mount_point : Split(skip_config, "\n")) {
-        if (skip_mount_point.empty()) {
+    std::vector<std::string> skip_mount_patterns;
+    for (const auto& line : Split(skip_config, "\n")) {
+        if (line.empty() || StartsWith(line, "#")) {
             continue;
         }
-        auto it = std::remove_if(fstab->begin(), fstab->end(),
-                                 [&skip_mount_point](const auto& entry) {
-                                     return entry.mount_point == skip_mount_point;
-                                 });
-        if (it == fstab->end()) continue;
-        fstab->erase(it, fstab->end());
-        LOG(INFO) << "Skip mounting partition: " << skip_mount_point;
+        skip_mount_patterns.push_back(line);
     }
 
+    // Returns false if mount_point matches any of the skip mount patterns, so that the FstabEntry
+    // would be partitioned to the second group.
+    auto glob_pattern_mismatch = [&skip_mount_patterns](const FstabEntry& entry) -> bool {
+        for (const auto& pattern : skip_mount_patterns) {
+            if (!fnmatch(pattern.c_str(), entry.mount_point.c_str(), 0 /* flags */)) {
+                return false;
+            }
+        }
+        return true;
+    };
+    auto remove_from = std::stable_partition(fstab->begin(), fstab->end(), glob_pattern_mismatch);
+    if (verbose) {
+        for (auto it = remove_from; it != fstab->end(); ++it) {
+            LINFO << "Skip mounting mountpoint: " << it->mount_point;
+        }
+    }
+    fstab->erase(remove_from, fstab->end());
     return true;
 }
 #endif
 
 // Loads the fstab file and combines with fstab entries passed in from device tree.
 bool ReadDefaultFstab(Fstab* fstab) {
-    Fstab dt_fstab;
-    ReadFstabFromDt(&dt_fstab, false);
-
-    *fstab = std::move(dt_fstab);
+    fstab->clear();
+    ReadFstabFromDt(fstab, false /* verbose */);
 
     std::string default_fstab_path;
     // Use different fstab paths for normal boot and recovery boot, respectively
@@ -798,16 +832,14 @@
     }
 
     Fstab default_fstab;
-    if (!default_fstab_path.empty()) {
-        ReadFstabFromFile(default_fstab_path, &default_fstab);
+    if (!default_fstab_path.empty() && ReadFstabFromFile(default_fstab_path, &default_fstab)) {
+        for (auto&& entry : default_fstab) {
+            fstab->emplace_back(std::move(entry));
+        }
     } else {
         LINFO << __FUNCTION__ << "(): failed to find device default fstab";
     }
 
-    for (auto&& entry : default_fstab) {
-        fstab->emplace_back(std::move(entry));
-    }
-
     return !fstab->empty();
 }
 
@@ -841,9 +873,22 @@
 }
 
 std::set<std::string> GetBootDevices() {
-    // First check the kernel commandline, then try the device tree otherwise
+    // First check bootconfig, then kernel commandline, then the device tree
     std::string dt_file_name = get_android_dt_dir() + "/boot_devices";
     std::string value;
+    if (fs_mgr_get_boot_config_from_bootconfig_source("boot_devices", &value) ||
+        fs_mgr_get_boot_config_from_bootconfig_source("boot_device", &value)) {
+        std::set<std::string> boot_devices;
+        // remove quotes and split by spaces
+        auto boot_device_strings = base::Split(base::StringReplace(value, "\"", "", true), " ");
+        for (std::string_view device : boot_device_strings) {
+            // trim the trailing comma, keep the rest.
+            base::ConsumeSuffix(&device, ",");
+            boot_devices.emplace(device);
+        }
+        return boot_devices;
+    }
+
     if (fs_mgr_get_boot_config_from_kernel_cmdline("boot_devices", &value) ||
         ReadDtFile(dt_file_name, &value)) {
         auto boot_devices = Split(value, ",");
@@ -854,7 +899,7 @@
     if (android::base::ReadFileToString("/proc/cmdline", &cmdline)) {
         std::set<std::string> boot_devices;
         const std::string cmdline_key = "androidboot.boot_device";
-        for (const auto& [key, value] : fs_mgr_parse_boot_config(cmdline)) {
+        for (const auto& [key, value] : fs_mgr_parse_cmdline(cmdline)) {
             if (key == cmdline_key) {
                 boot_devices.emplace(value);
             }
diff --git a/fs_mgr/fs_mgr_overlayfs.cpp b/fs_mgr/fs_mgr_overlayfs.cpp
index 388c296..925d03f 100644
--- a/fs_mgr/fs_mgr_overlayfs.cpp
+++ b/fs_mgr/fs_mgr_overlayfs.cpp
@@ -92,6 +92,10 @@
     return false;
 }
 
+bool fs_mgr_overlayfs_mount_fstab_entry(const android::fs_mgr::FstabEntry&) {
+    return false;
+}
+
 std::vector<std::string> fs_mgr_overlayfs_required_devices(Fstab*) {
     return {};
 }
@@ -116,6 +120,8 @@
 void MapScratchPartitionIfNeeded(Fstab*, const std::function<bool(const std::set<std::string>&)>&) {
 }
 
+void CleanupOldScratchFiles() {}
+
 void TeardownAllOverlayForMountPoint(const std::string&) {}
 
 }  // namespace fs_mgr
@@ -125,10 +131,38 @@
 
 namespace {
 
+bool fs_mgr_in_recovery() {
+    // Check the existence of recovery binary instead of using the compile time
+    // macro, because first-stage-init is compiled with __ANDROID_RECOVERY__
+    // defined, albeit not in recovery. More details: system/core/init/README.md
+    return fs_mgr_access("/system/bin/recovery");
+}
+
+bool fs_mgr_is_dsu_running() {
+    // Since android::gsi::CanBootIntoGsi() or android::gsi::MarkSystemAsGsi() is
+    // never called in recovery, the return value of android::gsi::IsGsiRunning()
+    // is not well-defined. In this case, just return false as being in recovery
+    // implies not running a DSU system.
+    if (fs_mgr_in_recovery()) return false;
+    auto saved_errno = errno;
+    auto ret = android::gsi::IsGsiRunning();
+    errno = saved_errno;
+    return ret;
+}
+
 // list of acceptable overlayfs backing storage
 const auto kScratchMountPoint = "/mnt/scratch"s;
 const auto kCacheMountPoint = "/cache"s;
-const std::vector<const std::string> kOverlayMountPoints = {kScratchMountPoint, kCacheMountPoint};
+
+std::vector<const std::string> OverlayMountPoints() {
+    // Never fallback to legacy cache mount point if within a DSU system,
+    // because running a DSU system implies the device supports dynamic
+    // partitions, which means legacy cache mustn't be used.
+    if (fs_mgr_is_dsu_running()) {
+        return {kScratchMountPoint};
+    }
+    return {kScratchMountPoint, kCacheMountPoint};
+}
 
 // Return true if everything is mounted, but before adb is started.  Right
 // after 'trigger load_persist_props_action' is done.
@@ -166,26 +200,7 @@
     static constexpr unsigned long kSizeThreshold = 8 * 1024 * 1024;  // 8MB
 
     return (vst.f_bfree >= (vst.f_blocks * kPercentThreshold / 100)) &&
-           (vst.f_bfree * vst.f_bsize) >= kSizeThreshold;
-}
-
-bool fs_mgr_in_recovery() {
-    // Check the existence of recovery binary instead of using the compile time
-    // macro, because first-stage-init is compiled with __ANDROID_RECOVERY__
-    // defined, albeit not in recovery. More details: system/core/init/README.md
-    return fs_mgr_access("/system/bin/recovery");
-}
-
-bool fs_mgr_is_dsu_running() {
-    // Since android::gsi::CanBootIntoGsi() or android::gsi::MarkSystemAsGsi() is
-    // never called in recovery, the return value of android::gsi::IsGsiRunning()
-    // is not well-defined. In this case, just return false as being in recovery
-    // implies not running a DSU system.
-    if (fs_mgr_in_recovery()) return false;
-    auto saved_errno = errno;
-    auto ret = android::gsi::IsGsiRunning();
-    errno = saved_errno;
-    return ret;
+           (static_cast<uint64_t>(vst.f_bfree) * vst.f_frsize) >= kSizeThreshold;
 }
 
 const auto kPhysicalDevice = "/dev/block/by-name/"s;
@@ -300,7 +315,7 @@
 std::string fs_mgr_get_overlayfs_candidate(const std::string& mount_point) {
     if (!fs_mgr_is_dir(mount_point)) return "";
     const auto base = android::base::Basename(mount_point) + "/";
-    for (const auto& overlay_mount_point : kOverlayMountPoints) {
+    for (const auto& overlay_mount_point : OverlayMountPoints()) {
         auto dir = overlay_mount_point + kOverlayTopDir + "/" + base;
         auto upper = dir + kUpperName;
         if (!fs_mgr_is_dir(upper)) continue;
@@ -1284,6 +1299,32 @@
     }
 }
 
+bool fs_mgr_overlayfs_mount_fstab_entry(const android::fs_mgr::FstabEntry& entry) {
+    if (fs_mgr_overlayfs_invalid()) return false;
+
+    // Create the mount point in case it doesn't exist.
+    mkdir(entry.mount_point.c_str(), 0755);
+
+    auto options = kLowerdirOption + entry.lowerdir;
+    if (fs_mgr_overlayfs_valid() == OverlayfsValidResult::kOverrideCredsRequired) {
+        options += ",override_creds=off";
+    }
+
+    // Use .blk_device as the mount() source for debugging purposes.
+    // Overlayfs is pseudo filesystem, so the source device is a symbolic value and isn't used to
+    // back the filesystem. /proc/mounts would show the source as the device name of the mount.
+    auto report = "__mount(source=" + entry.blk_device + ",target=" + entry.mount_point +
+                  ",type=overlay," + options + ")=";
+    auto ret = mount(entry.blk_device.c_str(), entry.mount_point.c_str(), "overlay",
+                     MS_RDONLY | MS_NOATIME, options.c_str());
+    if (ret) {
+        PERROR << report << ret;
+        return false;
+    }
+    LINFO << report << ret;
+    return true;
+}
+
 bool fs_mgr_overlayfs_mount_all(Fstab* fstab) {
     auto ret = false;
     if (fs_mgr_overlayfs_invalid()) return ret;
@@ -1344,7 +1385,7 @@
     if (candidates.empty()) return ret;
 
     std::string dir;
-    for (const auto& overlay_mount_point : kOverlayMountPoints) {
+    for (const auto& overlay_mount_point : OverlayMountPoints()) {
         if (backing && backing[0] && (overlay_mount_point != backing)) continue;
         if (overlay_mount_point == kScratchMountPoint) {
             if (!fs_mgr_overlayfs_setup_scratch(fstab, change)) continue;
@@ -1465,7 +1506,7 @@
         }
     }
     bool should_destroy_scratch = false;
-    for (const auto& overlay_mount_point : kOverlayMountPoints) {
+    for (const auto& overlay_mount_point : OverlayMountPoints()) {
         ret &= fs_mgr_overlayfs_teardown_one(
                 overlay_mount_point, mount_point ? fs_mgr_mount_point(mount_point) : "", change,
                 overlay_mount_point == kScratchMountPoint ? &should_destroy_scratch : nullptr);
@@ -1569,7 +1610,7 @@
     constexpr bool* ignore_change = nullptr;
 
     // Teardown legacy overlay mount points that's not backed by a scratch device.
-    for (const auto& overlay_mount_point : kOverlayMountPoints) {
+    for (const auto& overlay_mount_point : OverlayMountPoints()) {
         if (overlay_mount_point == kScratchMountPoint) {
             continue;
         }
diff --git a/fs_mgr/fs_mgr_priv_boot_config.h b/fs_mgr/fs_mgr_priv_boot_config.h
index 417fb38..6a38401 100644
--- a/fs_mgr/fs_mgr_priv_boot_config.h
+++ b/fs_mgr/fs_mgr_priv_boot_config.h
@@ -22,11 +22,16 @@
 #include <utility>
 #include <vector>
 
-std::vector<std::pair<std::string, std::string>> fs_mgr_parse_boot_config(const std::string& cmdline);
+std::vector<std::pair<std::string, std::string>> fs_mgr_parse_cmdline(const std::string& cmdline);
 
 bool fs_mgr_get_boot_config_from_kernel(const std::string& cmdline, const std::string& key,
                                         std::string* out_val);
 bool fs_mgr_get_boot_config_from_kernel_cmdline(const std::string& key, std::string* out_val);
 bool fs_mgr_get_boot_config(const std::string& key, std::string* out_val);
+std::vector<std::pair<std::string, std::string>> fs_mgr_parse_proc_bootconfig(
+        const std::string& bootconfig);
+bool fs_mgr_get_boot_config_from_bootconfig(const std::string& bootconfig, const std::string& key,
+                                            std::string* out_val);
+bool fs_mgr_get_boot_config_from_bootconfig_source(const std::string& key, std::string* out_val);
 
 #endif /* __CORE_FS_MGR_PRIV_BOOTCONFIG_H */
diff --git a/fs_mgr/fs_mgr_remount.cpp b/fs_mgr/fs_mgr_remount.cpp
index b8b074e..e685070 100644
--- a/fs_mgr/fs_mgr_remount.cpp
+++ b/fs_mgr/fs_mgr_remount.cpp
@@ -16,7 +16,6 @@
 
 #include <errno.h>
 #include <getopt.h>
-#include <libavb_user/libavb_user.h>
 #include <stdio.h>
 #include <sys/mount.h>
 #include <sys/types.h>
@@ -40,6 +39,8 @@
 #include <fs_mgr_overlayfs.h>
 #include <fs_mgr_priv.h>
 #include <fstab/fstab.h>
+#include <libavb_user/libavb_user.h>
+#include <libgsi/libgsid.h>
 
 namespace {
 
@@ -52,7 +53,9 @@
                  "\tpartition\tspecific partition(s) (empty does all)\n"
                  "\n"
                  "Remount specified partition(s) read-write, by name or mount point.\n"
-                 "-R notwithstanding, verity must be disabled on partition(s).";
+                 "-R notwithstanding, verity must be disabled on partition(s).\n"
+                 "-R within a DSU guest system reboots into the DSU instead of the host system,\n"
+                 "this command would enable DSU (one-shot) if not already enabled.";
 
     ::exit(exit_status);
 }
@@ -137,7 +140,8 @@
     REMOUNT_FAILED,
     MUST_REBOOT,
     BINDER_ERROR,
-    CHECKPOINTING
+    CHECKPOINTING,
+    GSID_ERROR,
 };
 
 static int do_remount(int argc, char* argv[]) {
@@ -340,6 +344,41 @@
         ++it;
     }
 
+    // If (1) remount requires a reboot to take effect, (2) system is currently
+    // running a DSU guest and (3) DSU is disabled, then enable DSU so that the
+    // next reboot would not take us back to the host system but stay within
+    // the guest system.
+    if (reboot_later) {
+        if (auto gsid = android::gsi::GetGsiService()) {
+            auto dsu_running = false;
+            if (auto status = gsid->isGsiRunning(&dsu_running); !status.isOk()) {
+                LOG(ERROR) << "Failed to get DSU running state: " << status;
+                return BINDER_ERROR;
+            }
+            auto dsu_enabled = false;
+            if (auto status = gsid->isGsiEnabled(&dsu_enabled); !status.isOk()) {
+                LOG(ERROR) << "Failed to get DSU enabled state: " << status;
+                return BINDER_ERROR;
+            }
+            if (dsu_running && !dsu_enabled) {
+                std::string dsu_slot;
+                if (auto status = gsid->getActiveDsuSlot(&dsu_slot); !status.isOk()) {
+                    LOG(ERROR) << "Failed to get active DSU slot: " << status;
+                    return BINDER_ERROR;
+                }
+                LOG(INFO) << "DSU is running but disabled, enable DSU so that we stay within the "
+                             "DSU guest system after reboot";
+                int error = 0;
+                if (auto status = gsid->enableGsi(/* oneShot = */ true, dsu_slot, &error);
+                    !status.isOk() || error != android::gsi::IGsiService::INSTALL_OK) {
+                    LOG(ERROR) << "Failed to enable DSU: " << status << ", error code: " << error;
+                    return !status.isOk() ? BINDER_ERROR : GSID_ERROR;
+                }
+                LOG(INFO) << "Successfully enabled DSU (one-shot mode)";
+            }
+        }
+    }
+
     if (partitions.empty() || just_disabled_verity) {
         if (reboot_later) reboot(setup_overlayfs);
         if (user_please_reboot_later) {
@@ -372,19 +411,26 @@
         auto blk_device = entry.blk_device;
         auto mount_point = entry.mount_point;
 
+        auto found = false;
         for (auto it = mounts.rbegin(); it != mounts.rend(); ++it) {
             auto& rentry = *it;
             if (mount_point == rentry.mount_point) {
                 blk_device = rentry.blk_device;
+                found = true;
                 break;
             }
             // Find overlayfs mount point?
             if ((mount_point == "/") && (rentry.mount_point == "/system")) {
                 blk_device = rentry.blk_device;
                 mount_point = "/system";
+                found = true;
                 break;
             }
         }
+        if (!found) {
+            PLOG(INFO) << "skip unmounted partition dev:" << blk_device << " mnt:" << mount_point;
+            continue;
+        }
         if (blk_device == "/dev/root") {
             auto from_fstab = GetEntryForMountPoint(&fstab, mount_point);
             if (from_fstab) blk_device = from_fstab->blk_device;
diff --git a/fs_mgr/fs_mgr_roots.cpp b/fs_mgr/fs_mgr_roots.cpp
index 1e65587..fdaffbe 100644
--- a/fs_mgr/fs_mgr_roots.cpp
+++ b/fs_mgr/fs_mgr_roots.cpp
@@ -111,7 +111,8 @@
         return true;
     }
 
-    static const std::vector<std::string> supported_fs{"ext4", "squashfs", "vfat", "f2fs", "none"};
+    static const std::vector<std::string> supported_fs{"ext4", "squashfs", "vfat", "f2fs", "erofs",
+                                                       "none"};
     if (std::find(supported_fs.begin(), supported_fs.end(), rec->fs_type) == supported_fs.end()) {
         LERROR << "unknown fs_type \"" << rec->fs_type << "\" for " << mount_point;
         return false;
diff --git a/fs_mgr/fs_mgr_vendor_overlay.cpp b/fs_mgr/fs_mgr_vendor_overlay.cpp
index 830f0dd..1372511 100644
--- a/fs_mgr/fs_mgr_vendor_overlay.cpp
+++ b/fs_mgr/fs_mgr_vendor_overlay.cpp
@@ -92,7 +92,7 @@
     }
     auto report = "__mount(source=overlay,target="s + vendor_mount_point + ",type=overlay," +
                   options + ")=";
-    auto ret = mount("overlay", vendor_mount_point.c_str(), "overlay", MS_RDONLY | MS_RELATIME,
+    auto ret = mount("overlay", vendor_mount_point.c_str(), "overlay", MS_RDONLY | MS_NOATIME,
                      options.c_str());
     if (ret) {
         PERROR << report << ret;
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 11e3664..22c02cc 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -88,6 +88,10 @@
 bool fs_mgr_load_verity_state(int* mode);
 // Returns true if verity is enabled on this particular FstabEntry.
 bool fs_mgr_is_verity_enabled(const android::fs_mgr::FstabEntry& entry);
+// Returns the hash algorithm used to build the hashtree of this particular FstabEntry. Returns an
+// empty string if the input isn't a dm-verity entry, or if there is an error.
+std::string fs_mgr_get_hashtree_algorithm(const android::fs_mgr::FstabEntry& entry);
+
 bool fs_mgr_swapon_all(const android::fs_mgr::Fstab& fstab);
 bool fs_mgr_update_logical_partition(android::fs_mgr::FstabEntry* entry);
 
diff --git a/fs_mgr/include/fs_mgr_overlayfs.h b/fs_mgr/include/fs_mgr_overlayfs.h
index d45e2de..22d12e7 100644
--- a/fs_mgr/include/fs_mgr_overlayfs.h
+++ b/fs_mgr/include/fs_mgr_overlayfs.h
@@ -27,6 +27,7 @@
 android::fs_mgr::Fstab fs_mgr_overlayfs_candidate_list(const android::fs_mgr::Fstab& fstab);
 
 bool fs_mgr_overlayfs_mount_all(android::fs_mgr::Fstab* fstab);
+bool fs_mgr_overlayfs_mount_fstab_entry(const android::fs_mgr::FstabEntry& entry);
 std::vector<std::string> fs_mgr_overlayfs_required_devices(android::fs_mgr::Fstab* fstab);
 bool fs_mgr_overlayfs_setup(const char* backing = nullptr, const char* mount_point = nullptr,
                             bool* change = nullptr, bool force = true);
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index 2d4de09..f33768b9 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -47,6 +47,7 @@
     int max_comp_streams = 0;
     off64_t zram_size = 0;
     off64_t reserved_size = 0;
+    off64_t readahead_size_kb = -1;
     std::string encryption_options;
     off64_t erase_blk_size = 0;
     off64_t logical_blk_size = 0;
@@ -54,6 +55,7 @@
     std::string vbmeta_partition;
     uint64_t zram_backingdev_size = 0;
     std::string avb_keys;
+    std::string lowerdir;
 
     struct FsMgrFlags {
         bool wait : 1;
@@ -97,9 +99,9 @@
 using Fstab = std::vector<FstabEntry>;
 
 bool ReadFstabFromFile(const std::string& path, Fstab* fstab);
-bool ReadFstabFromDt(Fstab* fstab, bool log = true);
+bool ReadFstabFromDt(Fstab* fstab, bool verbose = true);
 bool ReadDefaultFstab(Fstab* fstab);
-bool SkipMountingPartitions(Fstab* fstab);
+bool SkipMountingPartitions(Fstab* fstab, bool verbose = false);
 
 FstabEntry* GetEntryForMountPoint(Fstab* fstab, const std::string& path);
 // The Fstab can contain multiple entries for the same mount point with different configurations.
diff --git a/fs_mgr/libdm/Android.bp b/fs_mgr/libdm/Android.bp
index a0bc44d..428a7f4 100644
--- a/fs_mgr/libdm/Android.bp
+++ b/fs_mgr/libdm/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_static {
     name: "libdm",
     defaults: ["fs_mgr_defaults"],
@@ -43,6 +47,7 @@
         },
     },
     ramdisk_available: true,
+    vendor_ramdisk_available: true,
 }
 
 filegroup {
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index ef46eb9..b0639e6 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -95,7 +95,9 @@
 }
 
 void DmTargetVerity::SetVerityMode(const std::string& mode) {
-    if (mode != "restart_on_corruption" && mode != "ignore_corruption") {
+    if (mode != "panic_on_corruption" &&
+        mode != "restart_on_corruption" &&
+        mode != "ignore_corruption") {
         LOG(ERROR) << "Unknown verity mode: " << mode;
         valid_ = false;
         return;
diff --git a/fs_mgr/libfiemap/Android.bp b/fs_mgr/libfiemap/Android.bp
index a622110..1c5872e 100644
--- a/fs_mgr/libfiemap/Android.bp
+++ b/fs_mgr/libfiemap/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_headers {
     name: "libfiemap_headers",
     recovery_available: true,
diff --git a/fs_mgr/libfiemap/binder.cpp b/fs_mgr/libfiemap/binder.cpp
index c8516ab..31a57a8 100644
--- a/fs_mgr/libfiemap/binder.cpp
+++ b/fs_mgr/libfiemap/binder.cpp
@@ -224,8 +224,9 @@
     return false;
 }
 
-std::unique_ptr<IImageManager> IImageManager::Open(
-        const std::string& dir, const std::chrono::milliseconds& /*timeout_ms*/) {
+std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir,
+                                                   const std::chrono::milliseconds& /*timeout_ms*/,
+                                                   const DeviceInfo&) {
     android::sp<IGsiService> service = android::gsi::GetGsiService();
     android::sp<IImageService> manager;
 
diff --git a/fs_mgr/libfiemap/fiemap_writer.cpp b/fs_mgr/libfiemap/fiemap_writer.cpp
index 621031a..8acb885 100644
--- a/fs_mgr/libfiemap/fiemap_writer.cpp
+++ b/fs_mgr/libfiemap/fiemap_writer.cpp
@@ -52,7 +52,7 @@
 static constexpr const uint32_t kUnsupportedExtentFlags =
         FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_DELALLOC |
         FIEMAP_EXTENT_NOT_ALIGNED | FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_DATA_TAIL |
-        FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_SHARED | FIEMAP_EXTENT_MERGED;
+        FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_SHARED;
 
 // Large file support must be enabled.
 static_assert(sizeof(off_t) == sizeof(uint64_t));
diff --git a/fs_mgr/libfiemap/image_manager.cpp b/fs_mgr/libfiemap/image_manager.cpp
index 93fc131..dcbbc54 100644
--- a/fs_mgr/libfiemap/image_manager.cpp
+++ b/fs_mgr/libfiemap/image_manager.cpp
@@ -16,6 +16,8 @@
 
 #include <libfiemap/image_manager.h>
 
+#include <optional>
+
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
@@ -53,7 +55,8 @@
 static constexpr char kTestImageMetadataDir[] = "/metadata/gsi/test";
 static constexpr char kOtaTestImageMetadataDir[] = "/metadata/gsi/ota/test";
 
-std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix) {
+std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix,
+                                                 const DeviceInfo& device_info) {
     auto metadata_dir = "/metadata/gsi/" + dir_prefix;
     auto data_dir = "/data/gsi/" + dir_prefix;
     auto install_dir_file = gsi::DsuInstallDirFile(gsi::GetDsuSlot(dir_prefix));
@@ -61,17 +64,28 @@
     if (ReadFileToString(install_dir_file, &path)) {
         data_dir = path;
     }
-    return Open(metadata_dir, data_dir);
+    return Open(metadata_dir, data_dir, device_info);
 }
 
 std::unique_ptr<ImageManager> ImageManager::Open(const std::string& metadata_dir,
-                                                 const std::string& data_dir) {
-    return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir));
+                                                 const std::string& data_dir,
+                                                 const DeviceInfo& device_info) {
+    return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir, device_info));
 }
 
-ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir)
-    : metadata_dir_(metadata_dir), data_dir_(data_dir) {
+ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir,
+                           const DeviceInfo& device_info)
+    : metadata_dir_(metadata_dir), data_dir_(data_dir), device_info_(device_info) {
     partition_opener_ = std::make_unique<android::fs_mgr::PartitionOpener>();
+
+    // Allow overriding whether ImageManager thinks it's in recovery, for testing.
+#ifdef __ANDROID_RECOVERY__
+    device_info_.is_recovery = {true};
+#else
+    if (!device_info_.is_recovery.has_value()) {
+        device_info_.is_recovery = {false};
+    }
+#endif
 }
 
 std::string ImageManager::GetImageHeaderPath(const std::string& name) {
@@ -259,10 +273,11 @@
         return false;
     }
 
-#if defined __ANDROID_RECOVERY__
-    LOG(ERROR) << "Cannot remove images backed by /data in recovery";
-    return false;
-#else
+    if (device_info_.is_recovery.value()) {
+        LOG(ERROR) << "Cannot remove images backed by /data in recovery";
+        return false;
+    }
+
     std::string message;
     auto header_file = GetImageHeaderPath(name);
     if (!SplitFiemap::RemoveSplitFiles(header_file, &message)) {
@@ -276,7 +291,6 @@
         LOG(ERROR) << "Error removing " << status_file << ": " << message;
     }
     return RemoveImageMetadata(metadata_dir_, name);
-#endif
 }
 
 // Create a block device for an image file, using its extents in its
@@ -486,15 +500,14 @@
         if (!MapWithLoopDeviceList(loop_devices, name, timeout_ms, path)) {
             return false;
         }
+    } else {
+        auto status_message = "loop:" + loop_devices.back();
+        auto status_file = GetStatusFilePath(name);
+        if (!android::base::WriteStringToFile(status_message, status_file)) {
+            PLOG(ERROR) << "Write failed: " << status_file;
+            return false;
+        }
     }
-
-    auto status_message = "loop:" + loop_devices.back();
-    auto status_file = GetStatusFilePath(name);
-    if (!android::base::WriteStringToFile(status_message, status_file)) {
-        PLOG(ERROR) << "Write failed: " << status_file;
-        return false;
-    }
-
     auto_detach.Commit();
 
     *path = loop_devices.back();
@@ -520,6 +533,9 @@
     // filesystem. This should only happen on devices with no encryption, or
     // devices with FBE and no metadata encryption. For these cases it suffices
     // to perform normal file writes to /data/gsi (which is unencrypted).
+    //
+    // Note: this is not gated on DeviceInfo, because the recovery-specific path
+    // must only be used in actual recovery.
     std::string block_device;
     bool can_use_devicemapper;
     if (!FiemapWriter::GetBlockDeviceForFile(image_header, &block_device, &can_use_devicemapper)) {
@@ -575,7 +591,7 @@
         return false;
     }
     auto& dm = DeviceMapper::Instance();
-    LoopControl loop;
+    std::optional<LoopControl> loop;
 
     std::string status;
     auto status_file = GetStatusFilePath(name);
@@ -599,9 +615,14 @@
                 return false;
             }
         } else if (pieces[0] == "loop") {
+            // Lazily connect to loop-control to avoid spurious errors in recovery.
+            if (!loop.has_value()) {
+                loop.emplace();
+            }
+
             // Failure to remove a loop device is not fatal, since we can still
             // remove the backing file if we want.
-            loop.Detach(pieces[1]);
+            loop->Detach(pieces[1]);
         } else {
             LOG(ERROR) << "Unknown status: " << pieces[0];
         }
diff --git a/fs_mgr/libfiemap/include/libfiemap/image_manager.h b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
index 50f4f33..3c87000 100644
--- a/fs_mgr/libfiemap/include/libfiemap/image_manager.h
+++ b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
@@ -21,6 +21,7 @@
 #include <chrono>
 #include <functional>
 #include <memory>
+#include <optional>
 #include <set>
 #include <string>
 
@@ -37,11 +38,17 @@
 
     virtual ~IImageManager() {}
 
+    // Helper for dependency injection.
+    struct DeviceInfo {
+        std::optional<bool> is_recovery;
+    };
+
     // When linking to libfiemap_binder, the Open() call will use binder.
     // Otherwise, the Open() call will use the ImageManager implementation
-    // below.
+    // below. In binder mode, device_info is ignored.
     static std::unique_ptr<IImageManager> Open(const std::string& dir_prefix,
-                                               const std::chrono::milliseconds& timeout_ms);
+                                               const std::chrono::milliseconds& timeout_ms,
+                                               const DeviceInfo& device_info = {});
 
     // Flags for CreateBackingImage().
     static constexpr int CREATE_IMAGE_DEFAULT = 0x0;
@@ -131,11 +138,13 @@
     // Return an ImageManager for the given metadata and data directories. Both
     // directories must already exist.
     static std::unique_ptr<ImageManager> Open(const std::string& metadata_dir,
-                                              const std::string& data_dir);
+                                              const std::string& data_dir,
+                                              const DeviceInfo& device_info = {});
 
     // Helper function that derives the metadata and data dirs given a single
     // prefix.
-    static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix);
+    static std::unique_ptr<ImageManager> Open(const std::string& dir_prefix,
+                                              const DeviceInfo& device_info = {});
 
     // Methods that must be implemented from IImageManager.
     FiemapStatus CreateBackingImage(const std::string& name, uint64_t size, int flags,
@@ -166,7 +175,8 @@
     FiemapStatus ZeroFillNewImage(const std::string& name, uint64_t bytes);
 
   private:
-    ImageManager(const std::string& metadata_dir, const std::string& data_dir);
+    ImageManager(const std::string& metadata_dir, const std::string& data_dir,
+                 const DeviceInfo& device_info);
     std::string GetImageHeaderPath(const std::string& name);
     std::string GetStatusFilePath(const std::string& image_name);
     bool MapWithLoopDevice(const std::string& name, const std::chrono::milliseconds& timeout_ms,
@@ -187,6 +197,7 @@
     std::string metadata_dir_;
     std::string data_dir_;
     std::unique_ptr<IPartitionOpener> partition_opener_;
+    DeviceInfo device_info_;
 };
 
 // RAII helper class for mapping and opening devices with an ImageManager.
@@ -198,7 +209,7 @@
 
     ~MappedDevice();
 
-    int fd() const { return fd_; }
+    int fd() const { return fd_.get(); }
     const std::string& path() const { return path_; }
 
   protected:
diff --git a/fs_mgr/libfiemap/passthrough.cpp b/fs_mgr/libfiemap/passthrough.cpp
index 1ccd9a0..d521804 100644
--- a/fs_mgr/libfiemap/passthrough.cpp
+++ b/fs_mgr/libfiemap/passthrough.cpp
@@ -20,9 +20,10 @@
 namespace fiemap {
 
 std::unique_ptr<IImageManager> IImageManager::Open(const std::string& dir_prefix,
-                                                   const std::chrono::milliseconds& timeout_ms) {
+                                                   const std::chrono::milliseconds& timeout_ms,
+                                                   const DeviceInfo& device_info) {
     (void)timeout_ms;
-    return ImageManager::Open(dir_prefix);
+    return ImageManager::Open(dir_prefix, device_info);
 }
 
 }  // namespace fiemap
diff --git a/fs_mgr/libfs_avb/Android.bp b/fs_mgr/libfs_avb/Android.bp
index 8fb9697..6892025 100644
--- a/fs_mgr/libfs_avb/Android.bp
+++ b/fs_mgr/libfs_avb/Android.bp
@@ -14,6 +14,16 @@
 // limitations under the License.
 //
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_core_fs_mgr_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    //   SPDX-license-identifier-MIT
+    default_applicable_licenses: ["system_core_fs_mgr_license"],
+}
+
 cc_library_static {
     name: "libfs_avb",
     defaults: ["fs_mgr_defaults"],
@@ -68,6 +78,7 @@
     shared_libs: [
         "libbase",
         "libchrome",
+        "libcrypto",
     ],
     target: {
         darwin: {
@@ -91,11 +102,16 @@
     name: "libfs_avb_test",
     defaults: ["libfs_avb_host_test_defaults"],
     test_suites: ["general-tests"],
+    test_options: {
+        unit_test: true,
+    },
     static_libs: [
         "libfs_avb_test_util",
     ],
-    shared_libs: [
-        "libcrypto",
+    compile_multilib: "first",
+    data: [
+        ":avbtool",
+        ":fec",
     ],
     srcs: [
         "tests/basic_test.cpp",
@@ -108,9 +124,17 @@
     name: "libfs_avb_internal_test",
     defaults: ["libfs_avb_host_test_defaults"],
     test_suites: ["general-tests"],
+    test_options: {
+        unit_test: true,
+    },
     static_libs: [
         "libfs_avb_test_util",
     ],
+    compile_multilib: "first",
+    data: [
+        ":avbtool",
+        ":fec",
+    ],
     srcs: [
         "avb_util.cpp",
         "util.cpp",
diff --git a/fs_mgr/libfs_avb/TEST_MAPPING b/fs_mgr/libfs_avb/TEST_MAPPING
deleted file mode 100644
index b0f36d4..0000000
--- a/fs_mgr/libfs_avb/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "postsubmit": [
-    {
-      "name": "libfs_avb_test",
-      "host": true
-    },
-    {
-      "name": "libfs_avb_internal_test",
-      "host": true
-    }
-  ]
-}
diff --git a/fs_mgr/libfs_avb/avb_util.cpp b/fs_mgr/libfs_avb/avb_util.cpp
index 2288674..31494c1 100644
--- a/fs_mgr/libfs_avb/avb_util.cpp
+++ b/fs_mgr/libfs_avb/avb_util.cpp
@@ -61,7 +61,9 @@
 
     // Converts veritymode to the format used in kernel.
     std::string dm_verity_mode;
-    if (verity_mode == "enforcing") {
+    if (verity_mode == "panicking") {
+        dm_verity_mode = "panic_on_corruption";
+    } else if (verity_mode == "enforcing") {
         dm_verity_mode = "restart_on_corruption";
     } else if (verity_mode == "logging") {
         dm_verity_mode = "ignore_corruption";
diff --git a/fs_mgr/libfs_avb/fs_avb.cpp b/fs_mgr/libfs_avb/fs_avb.cpp
index 49333a1..1da7117 100644
--- a/fs_mgr/libfs_avb/fs_avb.cpp
+++ b/fs_mgr/libfs_avb/fs_avb.cpp
@@ -433,6 +433,16 @@
     // Sets the MAJOR.MINOR for init to set it into "ro.boot.avb_version".
     avb_handle->avb_version_ = StringPrintf("%d.%d", AVB_VERSION_MAJOR, AVB_VERSION_MINOR);
 
+    // Verifies vbmeta structs against the digest passed from bootloader in kernel cmdline.
+    std::unique_ptr<AvbVerifier> avb_verifier = AvbVerifier::Create();
+    if (!avb_verifier || !avb_verifier->VerifyVbmetaImages(avb_handle->vbmeta_images_)) {
+        LERROR << "Failed to verify vbmeta digest";
+        if (!allow_verification_error) {
+            LERROR << "vbmeta digest error isn't allowed ";
+            return nullptr;
+        }
+    }
+
     // Checks whether FLAGS_VERIFICATION_DISABLED is set:
     //   - Only the top-level vbmeta struct is read.
     //   - vbmeta struct in other partitions are NOT processed, including AVB HASH descriptor(s)
@@ -443,26 +453,16 @@
     bool verification_disabled = ((AvbVBMetaImageFlags)vbmeta_header.flags &
                                   AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED);
 
+    // Checks whether FLAGS_HASHTREE_DISABLED is set.
+    //   - vbmeta struct in all partitions are still processed, just disable
+    //     dm-verity in the user space.
+    bool hashtree_disabled =
+            ((AvbVBMetaImageFlags)vbmeta_header.flags & AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED);
+
     if (verification_disabled) {
         avb_handle->status_ = AvbHandleStatus::kVerificationDisabled;
-    } else {
-        // Verifies vbmeta structs against the digest passed from bootloader in kernel cmdline.
-        std::unique_ptr<AvbVerifier> avb_verifier = AvbVerifier::Create();
-        if (!avb_verifier) {
-            LERROR << "Failed to create AvbVerifier";
-            return nullptr;
-        }
-        if (!avb_verifier->VerifyVbmetaImages(avb_handle->vbmeta_images_)) {
-            LERROR << "VerifyVbmetaImages failed";
-            return nullptr;
-        }
-
-        // Checks whether FLAGS_HASHTREE_DISABLED is set.
-        bool hashtree_disabled = ((AvbVBMetaImageFlags)vbmeta_header.flags &
-                                  AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED);
-        if (hashtree_disabled) {
-            avb_handle->status_ = AvbHandleStatus::kHashtreeDisabled;
-        }
+    } else if (hashtree_disabled) {
+        avb_handle->status_ = AvbHandleStatus::kHashtreeDisabled;
     }
 
     LINFO << "Returning avb_handle with status: " << avb_handle->status_;
diff --git a/fs_mgr/libfs_avb/tests/avb_util_test.cpp b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
index 784eb9c..1827566 100644
--- a/fs_mgr/libfs_avb/tests/avb_util_test.cpp
+++ b/fs_mgr/libfs_avb/tests/avb_util_test.cpp
@@ -216,9 +216,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     1088 bytes\n"
             "Auxiliary Block:          2304 bytes\n"
+            "Public key (sha1):        5227b569de003adc7f8ec3fc03e05dfbd969abad\n"
             "Algorithm:                SHA512_RSA8192\n"
             "Rollback Index:           20\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    Hashtree descriptor:\n"
@@ -346,9 +348,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     576 bytes\n"
             "Auxiliary Block:          1216 bytes\n"
+            "Public key (sha1):        2597c218aae470a130f61162feaae70afd97f011\n"
             "Algorithm:                SHA256_RSA4096\n"
             "Rollback Index:           10\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    Hash descriptor:\n"
@@ -639,9 +643,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     1088 bytes\n"
             "Auxiliary Block:          3840 bytes\n"
+            "Public key (sha1):        5227b569de003adc7f8ec3fc03e05dfbd969abad\n"
             "Algorithm:                SHA256_RSA8192\n"
             "Rollback Index:           0\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    Chain Partition descriptor:\n"
@@ -854,9 +860,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     1088 bytes\n"
             "Auxiliary Block:          3840 bytes\n"
+            "Public key (sha1):        5227b569de003adc7f8ec3fc03e05dfbd969abad\n"
             "Algorithm:                SHA256_RSA8192\n"
             "Rollback Index:           0\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    Chain Partition descriptor:\n"
@@ -886,9 +894,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     576 bytes\n"
             "Auxiliary Block:          2176 bytes\n"
+            "Public key (sha1):        2597c218aae470a130f61162feaae70afd97f011\n"
             "Algorithm:                SHA256_RSA4096\n"
             "Rollback Index:           0\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    Chain Partition descriptor:\n"
@@ -936,9 +946,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     320 bytes\n"
             "Auxiliary Block:          960 bytes\n"
+            "Public key (sha1):        cdbb77177f731920bbe0a0f94f84d9038ae0617d\n"
             "Algorithm:                SHA256_RSA2048\n"
             "Rollback Index:           0\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    Hash descriptor:\n"
diff --git a/fs_mgr/libfs_avb/tests/basic_test.cpp b/fs_mgr/libfs_avb/tests/basic_test.cpp
index 5a1cd0d..1c47c07 100644
--- a/fs_mgr/libfs_avb/tests/basic_test.cpp
+++ b/fs_mgr/libfs_avb/tests/basic_test.cpp
@@ -59,9 +59,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     320 bytes\n"
             "Auxiliary Block:          576 bytes\n"
+            "Public key (sha1):        cdbb77177f731920bbe0a0f94f84d9038ae0617d\n"
             "Algorithm:                SHA256_RSA2048\n"
             "Rollback Index:           0\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    (none)\n",
@@ -89,9 +91,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     576 bytes\n"
             "Auxiliary Block:          1216 bytes\n"
+            "Public key (sha1):        2597c218aae470a130f61162feaae70afd97f011\n"
             "Algorithm:                SHA256_RSA4096\n"
             "Rollback Index:           10\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    Hash descriptor:\n"
@@ -126,9 +130,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     1088 bytes\n"
             "Auxiliary Block:          2304 bytes\n"
+            "Public key (sha1):        5227b569de003adc7f8ec3fc03e05dfbd969abad\n"
             "Algorithm:                SHA512_RSA8192\n"
             "Rollback Index:           20\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    Hashtree descriptor:\n"
@@ -180,9 +186,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     320 bytes\n"
             "Auxiliary Block:          960 bytes\n"
+            "Public key (sha1):        cdbb77177f731920bbe0a0f94f84d9038ae0617d\n"
             "Algorithm:                SHA256_RSA2048\n"
             "Rollback Index:           0\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    Hash descriptor:\n"
@@ -249,9 +257,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     1088 bytes\n"
             "Auxiliary Block:          3840 bytes\n"
+            "Public key (sha1):        5227b569de003adc7f8ec3fc03e05dfbd969abad\n"
             "Algorithm:                SHA256_RSA8192\n"
             "Rollback Index:           0\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    Chain Partition descriptor:\n"
diff --git a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
index 17f4c4e..1c95cf0 100644
--- a/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
+++ b/fs_mgr/libfs_avb/tests/fs_avb_test_util.cpp
@@ -122,6 +122,7 @@
                                                  const size_t padding_size) {
     VBMetaImage vbmeta_image;
     vbmeta_image.path = test_dir_.Append(output_file_name);
+    GTEST_LOG_(INFO) << "ExtractVBMetaImage: " << image_path << " to " << output_file_name;
     EXPECT_COMMAND(0,
                    "avbtool extract_vbmeta_image"
                    " --image %s"
diff --git a/fs_mgr/libfs_avb/tests/fs_avb_util_test.cpp b/fs_mgr/libfs_avb/tests/fs_avb_util_test.cpp
index 7c34009..5ec1e90 100644
--- a/fs_mgr/libfs_avb/tests/fs_avb_util_test.cpp
+++ b/fs_mgr/libfs_avb/tests/fs_avb_util_test.cpp
@@ -57,9 +57,11 @@
             "Header Block:             256 bytes\n"
             "Authentication Block:     576 bytes\n"
             "Auxiliary Block:          1280 bytes\n"
+            "Public key (sha1):        2597c218aae470a130f61162feaae70afd97f011\n"
             "Algorithm:                SHA512_RSA4096\n"
             "Rollback Index:           20\n"
             "Flags:                    0\n"
+            "Rollback Index Location:  0\n"
             "Release String:           'unit test'\n"
             "Descriptors:\n"
             "    Hashtree descriptor:\n"
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index 9517bd3..7e528b1 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 liblp_lib_deps = [
     "libbase",
     "liblog",
diff --git a/fs_mgr/liblp/builder.cpp b/fs_mgr/liblp/builder.cpp
index 623293e..6cb2c51 100644
--- a/fs_mgr/liblp/builder.cpp
+++ b/fs_mgr/liblp/builder.cpp
@@ -383,11 +383,6 @@
                << " partition alignment is not sector-aligned.";
         return false;
     }
-    if (device_info.alignment_offset > device_info.alignment) {
-        LERROR << "Block device " << device_info.partition_name
-               << " partition alignment offset is greater than its alignment.";
-        return false;
-    }
     return true;
 }
 
@@ -489,7 +484,7 @@
     // Compute the first free sector, factoring in alignment.
     uint64_t free_area_start = total_reserved;
     bool ok;
-    if (super.alignment || super.alignment_offset) {
+    if (super.alignment) {
         ok = AlignTo(free_area_start, super.alignment, &free_area_start);
     } else {
         ok = AlignTo(free_area_start, logical_block_size, &free_area_start);
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index e4b617a..72827eb 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -176,10 +176,10 @@
     ASSERT_NE(super_device, nullptr);
     EXPECT_EQ(super_device->first_logical_sector, 1536);
 
-    // Alignment offset without alignment doesn't mean anything.
+    // Alignment offset without alignment is ignored.
     device_info.alignment = 0;
     builder = MetadataBuilder::New(device_info, 1024, 2);
-    ASSERT_EQ(builder, nullptr);
+    ASSERT_NE(builder, nullptr);
 
     // Test a small alignment with an alignment offset.
     device_info.alignment = 12 * 1024;
@@ -444,11 +444,6 @@
     device_info.alignment = 131072;
     builder = MetadataBuilder::New(device_info, kMetadataSize, 1);
     EXPECT_EQ(builder, nullptr);
-
-    device_info.alignment = 0;
-    device_info.alignment_offset = 32768 - LP_SECTOR_SIZE;
-    builder = MetadataBuilder::New(device_info, kMetadataSize, 1);
-    EXPECT_EQ(builder, nullptr);
 }
 
 TEST_F(BuilderTest, UpdateBlockDeviceInfo) {
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 910911e..6a764e4 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_defaults {
     name: "libsnapshot_defaults",
     defaults: ["fs_mgr_defaults"],
@@ -24,6 +28,7 @@
     ],
     shared_libs: [
         "libbase",
+        "libchrome",
         "libcutils",
         "liblog",
     ],
@@ -44,7 +49,6 @@
         "libz",
     ],
     header_libs: [
-        "libchrome",
         "libfiemap_headers",
         "libstorage_literals_headers",
         "libupdate_engine_headers",
@@ -174,6 +178,7 @@
         "libz",
     ],
     ramdisk_available: true,
+    vendor_ramdisk_available: true,
 }
 
 cc_defaults {
@@ -246,6 +251,7 @@
         "snapshot_metadata_updater_test.cpp",
         "snapshot_reader_test.cpp",
         "snapshot_test.cpp",
+        "snapshot_writer_test.cpp",
     ],
     shared_libs: [
         "libbinder",
@@ -259,7 +265,8 @@
         "android.hardware.boot@1.0",
         "android.hardware.boot@1.1",
         "libbrotli",
-        "libfs_mgr",
+        "libc++fs",
+        "libfs_mgr_binder",
         "libgsi",
         "libgmock",
         "liblp",
@@ -292,6 +299,7 @@
     ],
     static_libs: [
         "libbrotli",
+        "libc++fs",
         "libfstab",
         "libsnapshot",
         "libsnapshot_cow",
@@ -321,6 +329,7 @@
         "power_test.cpp",
     ],
     static_libs: [
+        "libc++fs",
         "libsnapshot",
         "update_metadata-protos",
     ],
@@ -350,6 +359,8 @@
     static_libs: [
         "libbase",
         "libbrotli",
+        "libc++fs",
+        "libchrome",
         "libcrypto_static",
         "libcutils",
         "libext2_uuid",
@@ -366,7 +377,6 @@
         "libz",
     ],
     header_libs: [
-        "libchrome",
         "libfiemap_headers",
         "libstorage_literals_headers",
         "libupdate_engine_headers",
@@ -408,9 +418,11 @@
         "fs_mgr_defaults",
     ],
     srcs: [
-	"snapuserd_server.cpp",
-    "snapuserd.cpp",
-	"snapuserd_daemon.cpp",
+        "snapuserd_server.cpp",
+        "snapuserd.cpp",
+        "snapuserd_daemon.cpp",
+        "snapuserd_worker.cpp",
+        "snapuserd_readahead.cpp",
     ],
 
     cflags: [
@@ -421,11 +433,12 @@
     static_libs: [
         "libbase",
         "libbrotli",
-	"libcutils_sockets",
-        "liblog",
+        "libcutils_sockets",
         "libdm",
-        "libz",
+        "libgflags",
+        "liblog",
         "libsnapshot_cow",
+        "libz",
     ],
 }
 
@@ -436,15 +449,10 @@
         "snapuserd.rc",
     ],
     static_executable: true,
-}
-
-cc_binary {
-    name: "snapuserd_ramdisk",
-    stem: "snapuserd",
-    defaults: ["snapuserd_defaults"],
-
-    ramdisk: true,
-    static_executable: true,
+    system_shared_libs: [],
+    ramdisk_available: true,
+    vendor_ramdisk_available: true,
+    recovery_available: true,
 }
 
 cc_test {
@@ -471,6 +479,9 @@
         "libgtest",
         "libsnapshot_cow",
     ],
+    test_suites: [
+        "device-tests"
+    ],
     test_min_api_level: 30,
     auto_gen_config: true,
     require_root: false,
@@ -552,6 +563,8 @@
     ],
     srcs: [
         "cow_snapuserd_test.cpp",
+        "snapuserd.cpp",
+        "snapuserd_worker.cpp",
     ],
     cflags: [
         "-Wall",
@@ -568,7 +581,8 @@
         "libsnapshot_snapuserd",
         "libcutils_sockets",
         "libz",
-	"libdm",
+        "libfs_mgr",
+        "libdm",
     ],
     header_libs: [
         "libstorage_literals_headers",
diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
index 0328132..92aa55c 100644
--- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
+++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto
@@ -34,7 +34,19 @@
     MERGE_COMPLETED = 3;
 }
 
-// Next: 9
+// Next: 3
+enum MergePhase {
+    // No merge is in progress.
+    NO_MERGE = 0;
+
+    // Shrunk partitions can merge.
+    FIRST_PHASE = 1;
+
+    // Grown partitions can merge.
+    SECOND_PHASE = 2;
+}
+
+// Next: 13
 message SnapshotStatus {
     // Name of the snapshot. This is usually the name of the snapshotted
     // logical partition; for example, "system_b".
@@ -84,6 +96,18 @@
     // the merge process.
     // This is non-zero when |state| == MERGING or MERGE_COMPLETED.
     uint64 metadata_sectors = 8;
+
+    // True if compression is enabled, false otherwise.
+    bool compression_enabled = 9;
+
+    // The old partition size (if none existed, this will be zero).
+    uint64 old_partition_size = 10;
+
+    // Compression algorithm (none, gz, or brotli).
+    string compression_algorithm = 11;
+
+    // Estimated COW size from OTA manifest.
+    uint64 estimated_cow_size = 12;
 }
 
 // Next: 8
@@ -115,7 +139,28 @@
     Cancelled = 7;
 };
 
-// Next: 5
+// Next 14:
+//
+// To understand the source of each failure, read snapshot.cpp. To handle new
+// sources of failure, avoid reusing an existing code; add a new code instead.
+enum MergeFailureCode {
+    Ok = 0;
+    ReadStatus = 1;
+    GetTableInfo = 2;
+    UnknownTable = 3;
+    GetTableParams = 4;
+    ActivateNewTable = 5;
+    AcquireLock = 6;
+    ListSnapshots = 7;
+    WriteStatus = 8;
+    UnknownTargetType = 9;
+    QuerySnapshotStatus = 10;
+    ExpectedMergeTarget = 11;
+    UnmergedSectorsAfterCompletion = 12;
+    UnexpectedMergeState = 13;
+};
+
+// Next: 8
 message SnapshotUpdateStatus {
     UpdateState state = 1;
 
@@ -130,9 +175,18 @@
 
     // Sectors allocated for metadata in all the snapshot devices.
     uint64 metadata_sectors = 4;
+
+    // Whether compression/dm-user was used for any snapshots.
+    bool compression_enabled = 5;
+
+    // Merge phase (if state == MERGING).
+    MergePhase merge_phase = 6;
+
+    // Merge failure code, filled if state == MergeFailed.
+    MergeFailureCode merge_failure_code = 7;
 }
 
-// Next: 4
+// Next: 10
 message SnapshotMergeReport {
     // Status of the update after the merge attempts.
     UpdateState state = 1;
@@ -143,4 +197,22 @@
 
     // Total size of all the COW images before the update.
     uint64 cow_file_size = 3;
+
+    // Whether compression/dm-user was used for any snapshots.
+    bool compression_enabled = 4;
+
+    // Total size used by COWs, including /data and the super partition.
+    uint64 total_cow_size_bytes = 5;
+
+    // Sum of the estimated COW fields in the OTA manifest.
+    uint64 estimated_cow_size_bytes = 6;
+
+    // Time from boot to sys.boot_completed, in milliseconds.
+    uint32 boot_complete_time_ms = 7;
+
+    // Time from sys.boot_completed to merge start, in milliseconds.
+    uint32 boot_complete_to_merge_start_time_ms = 8;
+
+    // Merge failure code, filled if state == MergeFailed.
+    MergeFailureCode merge_failure_code = 9;
 }
diff --git a/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt b/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt
new file mode 100644
index 0000000..c474f4c
--- /dev/null
+++ b/fs_mgr/libsnapshot/corpus/avoid-io-in-fuzzer.txt
@@ -0,0 +1,41 @@
+device_info_data {
+  allow_set_slot_as_unbootable: true
+  is_recovery: true
+}
+is_super_metadata_valid: true
+super_data {
+  partitions {
+    partition_name: "sys_a"
+    new_partition_info {
+      size: 3145728
+    }
+  }
+  partitions {
+    partition_name: "vnnd_"
+    new_partition_info {
+      size: 3145728
+    }
+  }
+  partitions {
+    partition_name: "prd_a"
+    new_partition_info {
+    }
+  }
+  dynamic_partition_metadata {
+    groups {
+      name: "group_google_dp_a"
+      size: 34375467008
+      partition_names: "sys_a"
+      partition_names: "vnd_a"
+      partition_names: "prd_a"
+    }
+  }
+}
+has_metadata_snapshots_dir: true
+actions {
+  handle_imminent_data_wipe: true
+}
+actions {
+  begin_update {
+  }
+}
diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp
index 4db6584..b75b154 100644
--- a/fs_mgr/libsnapshot/cow_api_test.cpp
+++ b/fs_mgr/libsnapshot/cow_api_test.cpp
@@ -25,6 +25,10 @@
 #include <libsnapshot/cow_reader.h>
 #include <libsnapshot/cow_writer.h>
 
+using testing::AssertionFailure;
+using testing::AssertionResult;
+using testing::AssertionSuccess;
+
 namespace android {
 namespace snapshot {
 
@@ -60,6 +64,7 @@
 
 TEST_F(CowTest, ReadWrite) {
     CowOptions options;
+    options.cluster_ops = 0;
     CowWriter writer(options);
 
     ASSERT_TRUE(writer.Initialize(cow_->fd));
@@ -137,6 +142,7 @@
 
 TEST_F(CowTest, CompressGz) {
     CowOptions options;
+    options.cluster_ops = 0;
     options.compression = "gz";
     CowWriter writer(options);
 
@@ -171,9 +177,74 @@
     ASSERT_TRUE(iter->Done());
 }
 
+TEST_F(CowTest, ClusterCompressGz) {
+    CowOptions options;
+    options.compression = "gz";
+    options.cluster_ops = 2;
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_->fd));
+
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer.AddRawBlocks(50, data.data(), data.size()));
+
+    std::string data2 = "More data!";
+    data2.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer.AddRawBlocks(51, data2.data(), data2.size()));
+
+    ASSERT_TRUE(writer.Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+    ASSERT_FALSE(iter->Done());
+    auto op = &iter->Get();
+
+    StringSink sink;
+
+    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_EQ(op->compression, kCowCompressGz);
+    ASSERT_EQ(op->data_length, 56);  // compressed!
+    ASSERT_EQ(op->new_block, 50);
+    ASSERT_TRUE(reader.ReadData(*op, &sink));
+    ASSERT_EQ(sink.stream(), data);
+
+    iter->Next();
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+
+    ASSERT_EQ(op->type, kCowClusterOp);
+
+    iter->Next();
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+
+    sink.Reset();
+    ASSERT_EQ(op->compression, kCowCompressGz);
+    ASSERT_EQ(op->data_length, 41);  // compressed!
+    ASSERT_EQ(op->new_block, 51);
+    ASSERT_TRUE(reader.ReadData(*op, &sink));
+    ASSERT_EQ(sink.stream(), data2);
+
+    iter->Next();
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+
+    ASSERT_EQ(op->type, kCowClusterOp);
+
+    iter->Next();
+    ASSERT_TRUE(iter->Done());
+}
+
 TEST_F(CowTest, CompressTwoBlocks) {
     CowOptions options;
     options.compression = "gz";
+    options.cluster_ops = 0;
     CowWriter writer(options);
 
     ASSERT_TRUE(writer.Initialize(cow_->fd));
@@ -216,6 +287,7 @@
 TEST_P(CompressionTest, HorribleSink) {
     CowOptions options;
     options.compression = GetParam();
+    options.cluster_ops = 0;
     CowWriter writer(options);
 
     ASSERT_TRUE(writer.Initialize(cow_->fd));
@@ -245,6 +317,7 @@
 
 TEST_F(CowTest, GetSize) {
     CowOptions options;
+    options.cluster_ops = 0;
     CowWriter writer(options);
     if (ftruncate(cow_->fd, 0) < 0) {
         perror("Fails to set temp file size");
@@ -270,6 +343,7 @@
 
 TEST_F(CowTest, AppendLabelSmall) {
     CowOptions options;
+    options.cluster_ops = 0;
     auto writer = std::make_unique<CowWriter>(options);
     ASSERT_TRUE(writer->Initialize(cow_->fd));
 
@@ -335,6 +409,7 @@
 
 TEST_F(CowTest, AppendLabelMissing) {
     CowOptions options;
+    options.cluster_ops = 0;
     auto writer = std::make_unique<CowWriter>(options);
     ASSERT_TRUE(writer->Initialize(cow_->fd));
 
@@ -388,6 +463,7 @@
 
 TEST_F(CowTest, AppendExtendedCorrupted) {
     CowOptions options;
+    options.cluster_ops = 0;
     auto writer = std::make_unique<CowWriter>(options);
     ASSERT_TRUE(writer->Initialize(cow_->fd));
 
@@ -440,6 +516,7 @@
 
 TEST_F(CowTest, AppendbyLabel) {
     CowOptions options;
+    options.cluster_ops = 0;
     auto writer = std::make_unique<CowWriter>(options);
     ASSERT_TRUE(writer->Initialize(cow_->fd));
 
@@ -525,6 +602,385 @@
     ASSERT_TRUE(iter->Done());
 }
 
+TEST_F(CowTest, ClusterTest) {
+    CowOptions options;
+    options.cluster_ops = 4;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
+
+    ASSERT_TRUE(writer->AddLabel(4));
+
+    ASSERT_TRUE(writer->AddZeroBlocks(50, 2));  // Cluster split in middle
+
+    ASSERT_TRUE(writer->AddLabel(5));
+
+    ASSERT_TRUE(writer->AddCopy(5, 6));
+
+    // Cluster split
+
+    ASSERT_TRUE(writer->AddLabel(6));
+
+    ASSERT_TRUE(writer->Finalize());  // No data for cluster, so no cluster split needed
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    // Read back all ops
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    StringSink sink;
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+
+    ASSERT_FALSE(iter->Done());
+    auto op = &iter->Get();
+    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_TRUE(reader.ReadData(*op, &sink));
+    ASSERT_EQ(sink.stream(), data.substr(0, options.block_size));
+
+    iter->Next();
+    sink.Reset();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowLabelOp);
+    ASSERT_EQ(op->source, 4);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowZeroOp);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowClusterOp);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowZeroOp);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowLabelOp);
+    ASSERT_EQ(op->source, 5);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowCopyOp);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowClusterOp);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowLabelOp);
+    ASSERT_EQ(op->source, 6);
+
+    iter->Next();
+
+    ASSERT_TRUE(iter->Done());
+}
+
+TEST_F(CowTest, ClusterAppendTest) {
+    CowOptions options;
+    options.cluster_ops = 3;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(writer->AddLabel(50));
+    ASSERT_TRUE(writer->Finalize());  // Adds a cluster op, should be dropped on append
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 50));
+
+    std::string data2 = "More data!";
+    data2.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size()));
+    ASSERT_TRUE(writer->Finalize());  // Adds a cluster op
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    struct stat buf;
+    ASSERT_EQ(fstat(cow_->fd, &buf), 0);
+    ASSERT_EQ(buf.st_size, writer->GetCowSize());
+
+    // Read back both operations, plus cluster op at end
+    CowReader reader;
+    uint64_t label;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+    ASSERT_TRUE(reader.GetLastLabel(&label));
+    ASSERT_EQ(label, 50);
+
+    StringSink sink;
+
+    auto iter = reader.GetOpIter();
+    ASSERT_NE(iter, nullptr);
+
+    ASSERT_FALSE(iter->Done());
+    auto op = &iter->Get();
+    ASSERT_EQ(op->type, kCowLabelOp);
+    ASSERT_EQ(op->source, 50);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowReplaceOp);
+    ASSERT_TRUE(reader.ReadData(*op, &sink));
+    ASSERT_EQ(sink.stream(), data2);
+
+    iter->Next();
+
+    ASSERT_FALSE(iter->Done());
+    op = &iter->Get();
+    ASSERT_EQ(op->type, kCowClusterOp);
+
+    iter->Next();
+
+    ASSERT_TRUE(iter->Done());
+}
+
+TEST_F(CowTest, AppendAfterFinalize) {
+    CowOptions options;
+    options.cluster_ops = 0;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    std::string data = "This is some data, believe it";
+    data.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size()));
+    ASSERT_TRUE(writer->AddLabel(3));
+    ASSERT_TRUE(writer->Finalize());
+
+    std::string data2 = "More data!";
+    data2.resize(options.block_size, '\0');
+    ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size()));
+    ASSERT_TRUE(writer->Finalize());
+
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    // COW should be valid.
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+}
+
+AssertionResult WriteDataBlock(CowWriter* writer, uint64_t new_block, std::string data) {
+    data.resize(writer->options().block_size, '\0');
+    if (!writer->AddRawBlocks(new_block, data.data(), data.size())) {
+        return AssertionFailure() << "Failed to add raw block";
+    }
+    return AssertionSuccess();
+}
+
+AssertionResult CompareDataBlock(CowReader* reader, const CowOperation& op,
+                                 const std::string& data) {
+    CowHeader header;
+    reader->GetHeader(&header);
+
+    std::string cmp = data;
+    cmp.resize(header.block_size, '\0');
+
+    StringSink sink;
+    if (!reader->ReadData(op, &sink)) {
+        return AssertionFailure() << "Failed to read data block";
+    }
+    if (cmp != sink.stream()) {
+        return AssertionFailure() << "Data blocks did not match, expected " << cmp << ", got "
+                                  << sink.stream();
+    }
+
+    return AssertionSuccess();
+}
+
+TEST_F(CowTest, ResumeMidCluster) {
+    CowOptions options;
+    options.cluster_ops = 7;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+    ASSERT_TRUE(writer->AddLabel(2));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto iter = reader.GetOpIter();
+    size_t num_replace = 0;
+    size_t max_in_cluster = 0;
+    size_t num_in_cluster = 0;
+    size_t num_clusters = 0;
+    while (!iter->Done()) {
+        const auto& op = iter->Get();
+
+        num_in_cluster++;
+        max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+
+        if (op.type == kCowReplaceOp) {
+            num_replace++;
+
+            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+        } else if (op.type == kCowClusterOp) {
+            num_in_cluster = 0;
+            num_clusters++;
+        }
+
+        iter->Next();
+    }
+    ASSERT_EQ(num_replace, 8);
+    ASSERT_EQ(max_in_cluster, 7);
+    ASSERT_EQ(num_clusters, 2);
+}
+
+TEST_F(CowTest, ResumeEndCluster) {
+    CowOptions options;
+    int cluster_ops = 5;
+    options.cluster_ops = cluster_ops;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 7, "Block 7"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 8, "Block 8"));
+    ASSERT_TRUE(writer->AddLabel(2));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto iter = reader.GetOpIter();
+    size_t num_replace = 0;
+    size_t max_in_cluster = 0;
+    size_t num_in_cluster = 0;
+    size_t num_clusters = 0;
+    while (!iter->Done()) {
+        const auto& op = iter->Get();
+
+        num_in_cluster++;
+        max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+
+        if (op.type == kCowReplaceOp) {
+            num_replace++;
+
+            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+        } else if (op.type == kCowClusterOp) {
+            num_in_cluster = 0;
+            num_clusters++;
+        }
+
+        iter->Next();
+    }
+    ASSERT_EQ(num_replace, 8);
+    ASSERT_EQ(max_in_cluster, cluster_ops);
+    ASSERT_EQ(num_clusters, 3);
+}
+
+TEST_F(CowTest, DeleteMidCluster) {
+    CowOptions options;
+    options.cluster_ops = 7;
+    auto writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->Initialize(cow_->fd));
+
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 1, "Block 1"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 2, "Block 2"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 3, "Block 3"));
+    ASSERT_TRUE(writer->AddLabel(1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 4, "Block 4"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 5, "Block 5"));
+    ASSERT_TRUE(WriteDataBlock(writer.get(), 6, "Block 6"));
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    writer = std::make_unique<CowWriter>(options);
+    ASSERT_TRUE(writer->InitializeAppend(cow_->fd, 1));
+    ASSERT_TRUE(writer->Finalize());
+    ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0);
+
+    CowReader reader;
+    ASSERT_TRUE(reader.Parse(cow_->fd));
+
+    auto iter = reader.GetOpIter();
+    size_t num_replace = 0;
+    size_t max_in_cluster = 0;
+    size_t num_in_cluster = 0;
+    size_t num_clusters = 0;
+    while (!iter->Done()) {
+        const auto& op = iter->Get();
+
+        num_in_cluster++;
+        max_in_cluster = std::max(max_in_cluster, num_in_cluster);
+        if (op.type == kCowReplaceOp) {
+            num_replace++;
+
+            ASSERT_EQ(op.new_block, num_replace);
+            ASSERT_TRUE(CompareDataBlock(&reader, op, "Block " + std::to_string(num_replace)));
+        } else if (op.type == kCowClusterOp) {
+            num_in_cluster = 0;
+            num_clusters++;
+        }
+
+        iter->Next();
+    }
+    ASSERT_EQ(num_replace, 3);
+    ASSERT_EQ(max_in_cluster, 5);  // 3 data, 1 label, 1 cluster op
+    ASSERT_EQ(num_clusters, 1);
+}
+
 }  // namespace snapshot
 }  // namespace android
 
diff --git a/fs_mgr/libsnapshot/cow_format.cpp b/fs_mgr/libsnapshot/cow_format.cpp
index 49ba11f..0753c49 100644
--- a/fs_mgr/libsnapshot/cow_format.cpp
+++ b/fs_mgr/libsnapshot/cow_format.cpp
@@ -35,6 +35,10 @@
         os << "kCowFooterOp,  ";
     else if (op.type == kCowLabelOp)
         os << "kCowLabelOp,   ";
+    else if (op.type == kCowClusterOp)
+        os << "kCowClusterOp  ";
+    else if (op.type == kCowFooterOp)
+        os << "kCowFooterOp  ";
     else
         os << (int)op.type << "?,";
     os << "compression:";
@@ -52,11 +56,35 @@
     return os;
 }
 
-int64_t GetNextOpOffset(const CowOperation& op) {
-    if (op.type == kCowReplaceOp)
+int64_t GetNextOpOffset(const CowOperation& op, uint32_t cluster_ops) {
+    if (op.type == kCowClusterOp) {
+        return op.source;
+    } else if (op.type == kCowReplaceOp && cluster_ops == 0) {
         return op.data_length;
-    else
+    } else {
         return 0;
+    }
+}
+
+int64_t GetNextDataOffset(const CowOperation& op, uint32_t cluster_ops) {
+    if (op.type == kCowClusterOp) {
+        return cluster_ops * sizeof(CowOperation);
+    } else if (cluster_ops == 0) {
+        return sizeof(CowOperation);
+    } else {
+        return 0;
+    }
+}
+
+bool IsMetadataOp(const CowOperation& op) {
+    switch (op.type) {
+        case kCowLabelOp:
+        case kCowClusterOp:
+        case kCowFooterOp:
+            return true;
+        default:
+            return false;
+    }
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 6b7ada5..2349e4a 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -42,6 +42,29 @@
 #endif
 }
 
+bool CowReader::InitForMerge(android::base::unique_fd&& fd) {
+    owned_fd_ = std::move(fd);
+    fd_ = owned_fd_.get();
+
+    auto pos = lseek(fd_.get(), 0, SEEK_END);
+    if (pos < 0) {
+        PLOG(ERROR) << "lseek end failed";
+        return false;
+    }
+    fd_size_ = pos;
+
+    if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek header failed";
+        return false;
+    }
+    if (!android::base::ReadFully(fd_, &header_, sizeof(header_))) {
+        PLOG(ERROR) << "read header failed";
+        return false;
+    }
+
+    return true;
+}
+
 bool CowReader::Parse(android::base::unique_fd&& fd, std::optional<uint64_t> label) {
     owned_fd_ = std::move(fd);
     return Parse(android::base::borrowed_fd{owned_fd_}, label);
@@ -71,19 +94,31 @@
                    << "Expected: " << kCowMagicNumber;
         return false;
     }
-    if (header_.header_size != sizeof(CowHeader)) {
-        LOG(ERROR) << "Header size unknown, read " << header_.header_size << ", expected "
-                   << sizeof(CowHeader);
-        return false;
-    }
     if (header_.footer_size != sizeof(CowFooter)) {
         LOG(ERROR) << "Footer size unknown, read " << header_.footer_size << ", expected "
                    << sizeof(CowFooter);
         return false;
     }
+    if (header_.op_size != sizeof(CowOperation)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperation);
+        return false;
+    }
+    if (header_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
+    if (header_.op_size != sizeof(CowOperation)) {
+        LOG(ERROR) << "Operation size unknown, read " << header_.op_size << ", expected "
+                   << sizeof(CowOperation);
+        return false;
+    }
+    if (header_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
 
-    if ((header_.major_version != kCowVersionMajor) ||
-        (header_.minor_version != kCowVersionMinor)) {
+    if ((header_.major_version > kCowVersionMajor) || (header_.minor_version != kCowVersionMinor)) {
         LOG(ERROR) << "Header version mismatch";
         LOG(ERROR) << "Major version: " << header_.major_version
                    << "Expected: " << kCowVersionMajor;
@@ -96,54 +131,89 @@
 }
 
 bool CowReader::ParseOps(std::optional<uint64_t> label) {
-    uint64_t pos = lseek(fd_.get(), sizeof(header_), SEEK_SET);
-    if (pos != sizeof(header_)) {
-        PLOG(ERROR) << "lseek ops failed";
-        return false;
+    uint64_t pos;
+
+    // Skip the scratch space
+    if (header_.major_version >= 2 && (header_.buffer_size > 0)) {
+        LOG(DEBUG) << " Scratch space found of size: " << header_.buffer_size;
+        size_t init_offset = header_.header_size + header_.buffer_size;
+        pos = lseek(fd_.get(), init_offset, SEEK_SET);
+        if (pos != init_offset) {
+            PLOG(ERROR) << "lseek ops failed";
+            return false;
+        }
+    } else {
+        pos = lseek(fd_.get(), header_.header_size, SEEK_SET);
+        if (pos != header_.header_size) {
+            PLOG(ERROR) << "lseek ops failed";
+            return false;
+        }
+        // Reading a v1 version of COW which doesn't have buffer_size.
+        header_.buffer_size = 0;
     }
 
     auto ops_buffer = std::make_shared<std::vector<CowOperation>>();
+    uint64_t current_op_num = 0;
+    uint64_t cluster_ops = header_.cluster_ops ?: 1;
+    bool done = false;
 
-    // Alternating op and data
-    while (true) {
-        ops_buffer->emplace_back();
-        if (!android::base::ReadFully(fd_, &ops_buffer->back(), sizeof(CowOperation))) {
+    // Alternating op clusters and data
+    while (!done) {
+        uint64_t to_add = std::min(cluster_ops, (fd_size_ - pos) / sizeof(CowOperation));
+        if (to_add == 0) break;
+        ops_buffer->resize(current_op_num + to_add);
+        if (!android::base::ReadFully(fd_, &ops_buffer->data()[current_op_num],
+                                      to_add * sizeof(CowOperation))) {
             PLOG(ERROR) << "read op failed";
             return false;
         }
+        // Parse current cluster to find start of next cluster
+        while (current_op_num < ops_buffer->size()) {
+            auto& current_op = ops_buffer->data()[current_op_num];
+            current_op_num++;
+            pos += sizeof(CowOperation) + GetNextOpOffset(current_op, header_.cluster_ops);
 
-        auto& current_op = ops_buffer->back();
-        off_t offs = lseek(fd_.get(), GetNextOpOffset(current_op), SEEK_CUR);
-        if (offs < 0) {
+            if (current_op.type == kCowClusterOp) {
+                break;
+            } else if (current_op.type == kCowLabelOp) {
+                last_label_ = {current_op.source};
+
+                // If we reach the requested label, stop reading.
+                if (label && label.value() == current_op.source) {
+                    done = true;
+                    break;
+                }
+            } else if (current_op.type == kCowFooterOp) {
+                footer_.emplace();
+                CowFooter* footer = &footer_.value();
+                memcpy(&footer_->op, &current_op, sizeof(footer->op));
+                off_t offs = lseek(fd_.get(), pos, SEEK_SET);
+                if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
+                    PLOG(ERROR) << "lseek next op failed";
+                    return false;
+                }
+                if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
+                    LOG(ERROR) << "Could not read COW footer";
+                    return false;
+                }
+
+                // Drop the footer from the op stream.
+                current_op_num--;
+                done = true;
+                break;
+            }
+        }
+
+        // Position for next cluster read
+        off_t offs = lseek(fd_.get(), pos, SEEK_SET);
+        if (offs < 0 || pos != static_cast<uint64_t>(offs)) {
             PLOG(ERROR) << "lseek next op failed";
             return false;
         }
-        pos = static_cast<uint64_t>(offs);
-
-        if (current_op.type == kCowLabelOp) {
-            last_label_ = {current_op.source};
-
-            // If we reach the requested label, stop reading.
-            if (label && label.value() == current_op.source) {
-                break;
-            }
-        } else if (current_op.type == kCowFooterOp) {
-            footer_.emplace();
-
-            CowFooter* footer = &footer_.value();
-            memcpy(&footer_->op, &current_op, sizeof(footer->op));
-
-            if (!android::base::ReadFully(fd_, &footer->data, sizeof(footer->data))) {
-                LOG(ERROR) << "Could not read COW footer";
-                return false;
-            }
-
-            // Drop the footer from the op stream.
-            ops_buffer->pop_back();
-            break;
-        }
+        ops_buffer->resize(current_op_num);
     }
 
+    LOG(DEBUG) << "COW file read complete. Total ops: " << ops_buffer->size();
     // To successfully parse a COW file, we need either:
     //  (1) a label to read up to, and for that label to be found, or
     //  (2) a valid footer.
@@ -168,7 +238,8 @@
 
     if (footer_) {
         if (ops_buffer->size() != footer_->op.num_ops) {
-            LOG(ERROR) << "num ops does not match";
+            LOG(ERROR) << "num ops does not match, expected " << footer_->op.num_ops << ", found "
+                       << ops_buffer->size();
             return false;
         }
         if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) {
@@ -185,33 +256,148 @@
             LOG(ERROR) << "ops checksum does not match";
             return false;
         }
-    } else {
-        LOG(INFO) << "No COW Footer, recovered data";
-    }
-
-    if (header_.num_merge_ops > 0) {
-        uint64_t merge_ops = header_.num_merge_ops;
-        uint64_t metadata_ops = 0;
-        uint64_t current_op_num = 0;
-
-        CHECK(ops_buffer->size() >= merge_ops);
-        while (merge_ops) {
-            auto& current_op = ops_buffer->data()[current_op_num];
-            if (current_op.type == kCowLabelOp || current_op.type == kCowFooterOp) {
-                metadata_ops += 1;
-            } else {
-                merge_ops -= 1;
-            }
-            current_op_num += 1;
-        }
-        ops_buffer->erase(ops_buffer.get()->begin(),
-                          ops_buffer.get()->begin() + header_.num_merge_ops + metadata_ops);
     }
 
     ops_ = ops_buffer;
+    ops_->shrink_to_fit();
+
     return true;
 }
 
+void CowReader::InitializeMerge() {
+    uint64_t num_copy_ops = 0;
+
+    // Remove all the metadata operations
+    ops_->erase(std::remove_if(ops_.get()->begin(), ops_.get()->end(),
+                               [](CowOperation& op) { return IsMetadataOp(op); }),
+                ops_.get()->end());
+
+    set_total_data_ops(ops_->size());
+    // We will re-arrange the vector in such a way that
+    // kernel can batch merge. Ex:
+    //
+    // Existing COW format; All the copy operations
+    // are at the beginning.
+    // =======================================
+    // Copy-op-1    - cow_op->new_block = 1
+    // Copy-op-2    - cow_op->new_block = 2
+    // Copy-op-3    - cow_op->new_block = 3
+    // Replace-op-4 - cow_op->new_block = 6
+    // Replace-op-5 - cow_op->new_block = 4
+    // Replace-op-6 - cow_op->new_block = 8
+    // Replace-op-7 - cow_op->new_block = 9
+    // Zero-op-8    - cow_op->new_block = 7
+    // Zero-op-9    - cow_op->new_block = 5
+    // =======================================
+    //
+    // First find the operation which isn't a copy-op
+    // and then sort all the operations in descending order
+    // with the key being cow_op->new_block (source block)
+    //
+    // The data-structure will look like:
+    //
+    // =======================================
+    // Copy-op-1    - cow_op->new_block = 1
+    // Copy-op-2    - cow_op->new_block = 2
+    // Copy-op-3    - cow_op->new_block = 3
+    // Replace-op-7 - cow_op->new_block = 9
+    // Replace-op-6 - cow_op->new_block = 8
+    // Zero-op-8    - cow_op->new_block = 7
+    // Replace-op-4 - cow_op->new_block = 6
+    // Zero-op-9    - cow_op->new_block = 5
+    // Replace-op-5 - cow_op->new_block = 4
+    // =======================================
+    //
+    // Daemon will read the above data-structure in reverse-order
+    // when reading metadata. Thus, kernel will get the metadata
+    // in the following order:
+    //
+    // ========================================
+    // Replace-op-5 - cow_op->new_block = 4
+    // Zero-op-9    - cow_op->new_block = 5
+    // Replace-op-4 - cow_op->new_block = 6
+    // Zero-op-8    - cow_op->new_block = 7
+    // Replace-op-6 - cow_op->new_block = 8
+    // Replace-op-7 - cow_op->new_block = 9
+    // Copy-op-3    - cow_op->new_block = 3
+    // Copy-op-2    - cow_op->new_block = 2
+    // Copy-op-1    - cow_op->new_block = 1
+    // ===========================================
+    //
+    // When merging begins, kernel will start from the last
+    // metadata which was read: In the above format, Copy-op-1
+    // will be the first merge operation.
+    //
+    // Now, batching of the merge operations happens only when
+    // 1: origin block numbers in the base device are contiguous
+    // (cow_op->new_block) and,
+    // 2: cow block numbers which are assigned by daemon in ReadMetadata()
+    // are contiguous. These are monotonically increasing numbers.
+    //
+    // When both (1) and (2) are true, kernel will batch merge the operations.
+    // In the above case, we have to ensure that the copy operations
+    // are merged first before replace operations are done. Hence,
+    // we will not change the order of copy operations. Since,
+    // cow_op->new_block numbers are contiguous, we will ensure that the
+    // cow block numbers assigned in ReadMetadata() for these respective copy
+    // operations are not contiguous forcing kernel to issue merge for each
+    // copy operations without batch merging.
+    //
+    // For all the other operations viz. Replace and Zero op, the cow block
+    // numbers assigned by daemon will be contiguous allowing kernel to batch
+    // merge.
+    //
+    // The final format after assiging COW block numbers by the daemon will
+    // look something like:
+    //
+    // =========================================================
+    // Replace-op-5 - cow_op->new_block = 4  cow-block-num = 2
+    // Zero-op-9    - cow_op->new_block = 5  cow-block-num = 3
+    // Replace-op-4 - cow_op->new_block = 6  cow-block-num = 4
+    // Zero-op-8    - cow_op->new_block = 7  cow-block-num = 5
+    // Replace-op-6 - cow_op->new_block = 8  cow-block-num = 6
+    // Replace-op-7 - cow_op->new_block = 9  cow-block-num = 7
+    // Copy-op-3    - cow_op->new_block = 3  cow-block-num = 9
+    // Copy-op-2    - cow_op->new_block = 2  cow-block-num = 11
+    // Copy-op-1    - cow_op->new_block = 1  cow-block-num = 13
+    // ==========================================================
+    //
+    // Merge sequence will look like:
+    //
+    // Merge-1 - Batch-merge { Copy-op-1, Copy-op-2, Copy-op-3 }
+    // Merge-2 - Batch-merge {Replace-op-7, Replace-op-6, Zero-op-8,
+    //                        Replace-op-4, Zero-op-9, Replace-op-5 }
+    //==============================================================
+
+    num_copy_ops = FindNumCopyops();
+
+    std::sort(ops_.get()->begin() + num_copy_ops, ops_.get()->end(),
+              [](CowOperation& op1, CowOperation& op2) -> bool {
+                  return op1.new_block > op2.new_block;
+              });
+
+    if (header_.num_merge_ops > 0) {
+        ops_->erase(ops_.get()->begin(), ops_.get()->begin() + header_.num_merge_ops);
+    }
+
+    num_copy_ops = FindNumCopyops();
+    set_copy_ops(num_copy_ops);
+}
+
+uint64_t CowReader::FindNumCopyops() {
+    uint64_t num_copy_ops = 0;
+
+    for (uint64_t i = 0; i < ops_->size(); i++) {
+        auto& current_op = ops_->data()[i];
+        if (current_op.type != kCowCopyOp) {
+            break;
+        }
+        num_copy_ops += 1;
+    }
+
+    return num_copy_ops;
+}
+
 bool CowReader::GetHeader(CowHeader* header) {
     *header = header_;
     return true;
@@ -303,7 +489,7 @@
 
 bool CowReader::GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read) {
     // Validate the offset, taking care to acknowledge possible overflow of offset+len.
-    if (offset < sizeof(header_) || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
+    if (offset < header_.header_size || offset >= fd_size_ - sizeof(CowFooter) || len >= fd_size_ ||
         offset + len > fd_size_ - sizeof(CowFooter)) {
         LOG(ERROR) << "invalid data offset: " << offset << ", " << len << " bytes";
         return false;
diff --git a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
index 5483fd0..3888eb1 100644
--- a/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/cow_snapuserd_test.cpp
@@ -12,10 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <fcntl.h>
 #include <linux/fs.h>
+#include <linux/memfd.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
+#include <sys/syscall.h>
 #include <sys/types.h>
+#include <unistd.h>
 
 #include <chrono>
 #include <iostream>
@@ -24,12 +28,16 @@
 
 #include <android-base/file.h>
 #include <android-base/unique_fd.h>
+#include <fs_mgr/file_wait.h>
 #include <gtest/gtest.h>
+#include <libdm/dm.h>
 #include <libdm/loop_control.h>
 #include <libsnapshot/cow_writer.h>
 #include <libsnapshot/snapuserd_client.h>
 #include <storage_literals/storage_literals.h>
 
+#include "snapuserd.h"
+
 namespace android {
 namespace snapshot {
 
@@ -37,428 +45,868 @@
 using android::base::unique_fd;
 using LoopDevice = android::dm::LoopDevice;
 using namespace std::chrono_literals;
+using namespace android::dm;
+using namespace std;
 
-class SnapuserdTest : public ::testing::Test {
-  protected:
-    void SetUp() override {
-        // TODO: Daemon started through first stage
-        // init does not have permission to read files
-        // from /data/nativetest.
-        system("setenforce 0");
-        cow_system_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_system_->fd, 0) << strerror(errno);
+static constexpr char kSnapuserdSocketTest[] = "snapuserdTest";
 
-        cow_product_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_product_->fd, 0) << strerror(errno);
+class TempDevice {
+  public:
+    TempDevice(const std::string& name, const DmTable& table)
+        : dm_(DeviceMapper::Instance()), name_(name), valid_(false) {
+        valid_ = dm_.CreateDevice(name, table, &path_, std::chrono::seconds(5));
+    }
+    TempDevice(TempDevice&& other) noexcept
+        : dm_(other.dm_), name_(other.name_), path_(other.path_), valid_(other.valid_) {
+        other.valid_ = false;
+    }
+    ~TempDevice() {
+        if (valid_) {
+            dm_.DeleteDevice(name_);
+        }
+    }
+    bool Destroy() {
+        if (!valid_) {
+            return false;
+        }
+        valid_ = false;
+        return dm_.DeleteDevice(name_);
+    }
+    const std::string& path() const { return path_; }
+    const std::string& name() const { return name_; }
+    bool valid() const { return valid_; }
 
-        cow_system_1_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_system_1_->fd, 0) << strerror(errno);
+    TempDevice(const TempDevice&) = delete;
+    TempDevice& operator=(const TempDevice&) = delete;
 
-        cow_product_1_ = std::make_unique<TemporaryFile>();
-        ASSERT_GE(cow_product_1_->fd, 0) << strerror(errno);
-
-        std::string path = android::base::GetExecutableDirectory();
-
-        system_a_ = std::make_unique<TemporaryFile>(path);
-        ASSERT_GE(system_a_->fd, 0) << strerror(errno);
-
-        product_a_ = std::make_unique<TemporaryFile>(path);
-        ASSERT_GE(product_a_->fd, 0) << strerror(errno);
-
-        size_ = 100_MiB;
+    TempDevice& operator=(TempDevice&& other) noexcept {
+        name_ = other.name_;
+        valid_ = other.valid_;
+        other.valid_ = false;
+        return *this;
     }
 
-    void TearDown() override {
-        system("setenforce 1");
-        cow_system_ = nullptr;
-        cow_product_ = nullptr;
-
-        cow_system_1_ = nullptr;
-        cow_product_1_ = nullptr;
-    }
-
-    std::unique_ptr<TemporaryFile> system_a_;
-    std::unique_ptr<TemporaryFile> product_a_;
-
-    std::unique_ptr<LoopDevice> system_a_loop_;
-    std::unique_ptr<LoopDevice> product_a_loop_;
-
-    std::unique_ptr<TemporaryFile> cow_system_;
-    std::unique_ptr<TemporaryFile> cow_product_;
-
-    std::unique_ptr<TemporaryFile> cow_system_1_;
-    std::unique_ptr<TemporaryFile> cow_product_1_;
-
-    unique_fd sys_fd_;
-    unique_fd product_fd_;
-    size_t size_;
-
-    int system_blksize_;
-    int product_blksize_;
-    std::string system_device_name_;
-    std::string product_device_name_;
-
-    std::string system_device_ctrl_name_;
-    std::string product_device_ctrl_name_;
-
-    std::unique_ptr<uint8_t[]> random_buffer_1_;
-    std::unique_ptr<uint8_t[]> random_buffer_2_;
-    std::unique_ptr<uint8_t[]> zero_buffer_;
-    std::unique_ptr<uint8_t[]> system_buffer_;
-    std::unique_ptr<uint8_t[]> product_buffer_;
-
-    void Init();
-    void InitCowDevices();
-    void InitDaemon();
-    void CreateCowDevice(std::unique_ptr<TemporaryFile>& cow);
-    void CreateSystemDmUser(std::unique_ptr<TemporaryFile>& cow);
-    void CreateProductDmUser(std::unique_ptr<TemporaryFile>& cow);
-    void DeleteDmUser(std::unique_ptr<TemporaryFile>& cow, std::string snapshot_device);
-    void StartSnapuserdDaemon();
-    void CreateSnapshotDevices();
-    void SwitchSnapshotDevices();
-
-    std::string GetSystemControlPath() {
-        return std::string("/dev/dm-user/") + system_device_ctrl_name_;
-    }
-    std::string GetProductControlPath() {
-        return std::string("/dev/dm-user/") + product_device_ctrl_name_;
-    }
-
-    void TestIO(unique_fd& snapshot_fd, std::unique_ptr<uint8_t[]>& buffer);
-    std::unique_ptr<SnapuserdClient> client_;
+  private:
+    DeviceMapper& dm_;
+    std::string name_;
+    std::string path_;
+    bool valid_;
 };
 
-void SnapuserdTest::Init() {
+class CowSnapuserdTest final {
+  public:
+    bool Setup();
+    bool SetupCopyOverlap_1();
+    bool SetupCopyOverlap_2();
+    bool Merge();
+    void ValidateMerge();
+    void ReadSnapshotDeviceAndValidate();
+    void Shutdown();
+    void MergeInterrupt();
+
+    std::string snapshot_dev() const { return snapshot_dev_->path(); }
+
+    static const uint64_t kSectorSize = 512;
+
+  private:
+    void SetupImpl();
+
+    void MergeImpl();
+    void SimulateDaemonRestart();
+    void StartMerge();
+
+    void CreateCowDevice();
+    void CreateCowDeviceWithCopyOverlap_1();
+    void CreateCowDeviceWithCopyOverlap_2();
+    bool SetupDaemon();
+    void CreateBaseDevice();
+    void InitCowDevice();
+    void SetDeviceControlName();
+    void InitDaemon();
+    void CreateDmUserDevice();
+    void StartSnapuserdDaemon();
+    void CreateSnapshotDevice();
+
+    unique_ptr<LoopDevice> base_loop_;
+    unique_ptr<TempDevice> dmuser_dev_;
+    unique_ptr<TempDevice> snapshot_dev_;
+
+    std::string system_device_ctrl_name_;
+    std::string system_device_name_;
+
+    unique_fd base_fd_;
+    std::unique_ptr<TemporaryFile> cow_system_;
+    std::unique_ptr<SnapuserdClient> client_;
+    std::unique_ptr<uint8_t[]> orig_buffer_;
+    std::unique_ptr<uint8_t[]> merged_buffer_;
+    bool setup_ok_ = false;
+    bool merge_ok_ = false;
+    size_t size_ = 50_MiB;
+    int cow_num_sectors_;
+    int total_base_size_;
+};
+
+class CowSnapuserdMetadataTest final {
+  public:
+    void Setup();
+    void SetupPartialArea();
+    void ValidateMetadata();
+    void ValidatePartialFilledArea();
+
+  private:
+    void InitMetadata();
+    void CreateCowDevice();
+    void CreateCowPartialFilledArea();
+
+    std::unique_ptr<Snapuserd> snapuserd_;
+    std::unique_ptr<TemporaryFile> cow_system_;
+    size_t size_ = 1_MiB;
+};
+
+static unique_fd CreateTempFile(const std::string& name, size_t size) {
+    unique_fd fd(syscall(__NR_memfd_create, name.c_str(), MFD_ALLOW_SEALING));
+    if (fd < 0) {
+        return {};
+    }
+    if (size) {
+        if (ftruncate(fd, size) < 0) {
+            perror("ftruncate");
+            return {};
+        }
+        if (fcntl(fd, F_ADD_SEALS, F_SEAL_GROW | F_SEAL_SHRINK) < 0) {
+            perror("fcntl");
+            return {};
+        }
+    }
+    return fd;
+}
+
+void CowSnapuserdTest::Shutdown() {
+    ASSERT_TRUE(snapshot_dev_->Destroy());
+    ASSERT_TRUE(dmuser_dev_->Destroy());
+
+    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
+    ASSERT_TRUE(client_->WaitForDeviceDelete(system_device_ctrl_name_));
+    ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted(misc_device, 10s));
+    ASSERT_TRUE(client_->DetachSnapuserd());
+}
+
+bool CowSnapuserdTest::Setup() {
+    SetupImpl();
+    return setup_ok_;
+}
+
+bool CowSnapuserdTest::SetupCopyOverlap_1() {
+    CreateBaseDevice();
+    CreateCowDeviceWithCopyOverlap_1();
+    return SetupDaemon();
+}
+
+bool CowSnapuserdTest::SetupCopyOverlap_2() {
+    CreateBaseDevice();
+    CreateCowDeviceWithCopyOverlap_2();
+    return SetupDaemon();
+}
+
+bool CowSnapuserdTest::SetupDaemon() {
+    SetDeviceControlName();
+
+    StartSnapuserdDaemon();
+    InitCowDevice();
+
+    CreateDmUserDevice();
+    InitDaemon();
+
+    CreateSnapshotDevice();
+    setup_ok_ = true;
+
+    return setup_ok_;
+}
+
+void CowSnapuserdTest::StartSnapuserdDaemon() {
+    pid_t pid = fork();
+    ASSERT_GE(pid, 0);
+    if (pid == 0) {
+        std::string arg0 = "/system/bin/snapuserd";
+        std::string arg1 = "-socket="s + kSnapuserdSocketTest;
+        char* const argv[] = {arg0.data(), arg1.data(), nullptr};
+        ASSERT_GE(execv(arg0.c_str(), argv), 0);
+    } else {
+        client_ = SnapuserdClient::Connect(kSnapuserdSocketTest, 10s);
+        ASSERT_NE(client_, nullptr);
+    }
+}
+
+void CowSnapuserdTest::CreateBaseDevice() {
     unique_fd rnd_fd;
-    loff_t offset = 0;
-    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
+
+    total_base_size_ = (size_ * 4);
+    base_fd_ = CreateTempFile("base_device", total_base_size_);
+    ASSERT_GE(base_fd_, 0);
 
     rnd_fd.reset(open("/dev/random", O_RDONLY));
     ASSERT_TRUE(rnd_fd > 0);
 
-    random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
-    random_buffer_2_ = std::make_unique<uint8_t[]>(size_);
-    system_buffer_ = std::make_unique<uint8_t[]>(size_);
-    product_buffer_ = std::make_unique<uint8_t[]>(size_);
-    zero_buffer_ = std::make_unique<uint8_t[]>(size_);
+    std::unique_ptr<uint8_t[]> random_buffer = std::make_unique<uint8_t[]>(1_MiB);
+
+    for (size_t j = 0; j < ((total_base_size_) / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
+        ASSERT_EQ(android::base::WriteFully(base_fd_, random_buffer.get(), 1_MiB), true);
+    }
+
+    ASSERT_EQ(lseek(base_fd_, 0, SEEK_SET), 0);
+
+    base_loop_ = std::make_unique<LoopDevice>(base_fd_, 10s);
+    ASSERT_TRUE(base_loop_->valid());
+}
+
+void CowSnapuserdTest::ReadSnapshotDeviceAndValidate() {
+    unique_fd snapshot_fd(open(snapshot_dev_->path().c_str(), O_RDONLY));
+    ASSERT_TRUE(snapshot_fd > 0);
+
+    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
+
+    // COPY
+    loff_t offset = 0;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), orig_buffer_.get(), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + size_, size_), 0);
+
+    // ZERO
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 2), size_), 0);
+
+    // REPLACE
+    offset += size_;
+    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
+    ASSERT_EQ(memcmp(snapuserd_buffer.get(), (char*)orig_buffer_.get() + (size_ * 3), size_), 0);
+}
+
+void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_2() {
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t x = num_blocks;
+    size_t blk_src_copy = 0;
+
+    // Create overlapping copy operations
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(blk_src_copy, blk_src_copy + 1));
+        x -= 1;
+        if (x == 1) {
+            break;
+        }
+        blk_src_copy += 1;
+    }
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+
+    // Merged operations required for validation
+    int block_size = 4096;
+    x = num_blocks;
+    loff_t src_offset = block_size;
+    loff_t dest_offset = 0;
+
+    while (1) {
+        memmove((char*)orig_buffer_.get() + dest_offset, (char*)orig_buffer_.get() + src_offset,
+                block_size);
+        x -= 1;
+        if (x == 1) {
+            break;
+        }
+        src_offset += block_size;
+        dest_offset += block_size;
+    }
+}
+
+void CowSnapuserdTest::CreateCowDeviceWithCopyOverlap_1() {
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+    size_t x = num_blocks;
+    size_t blk_src_copy = num_blocks - 1;
+
+    // Create overlapping copy operations
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(blk_src_copy + 1, blk_src_copy));
+        x -= 1;
+        if (x == 0) {
+            ASSERT_EQ(blk_src_copy, 0);
+            break;
+        }
+        blk_src_copy -= 1;
+    }
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+
+    // Read the entire base device
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), total_base_size_, 0),
+              true);
+
+    // Merged operations
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), options.block_size, 0),
+              true);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(
+                      base_fd_, (char*)orig_buffer_.get() + options.block_size, size_, 0),
+              true);
+}
+
+void CowSnapuserdTest::CreateCowDevice() {
+    unique_fd rnd_fd;
+    loff_t offset = 0;
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
 
     // Fill random data
     for (size_t j = 0; j < (size_ / 1_MiB); j++) {
         ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
                   true);
 
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_2_.get() + offset, 1_MiB, 0),
-                  true);
-
         offset += 1_MiB;
     }
 
-    for (size_t j = 0; j < (800_MiB / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
-        ASSERT_EQ(android::base::WriteFully(system_a_->fd, random_buffer.get(), 1_MiB), true);
-    }
-
-    for (size_t j = 0; j < (800_MiB / 1_MiB); j++) {
-        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer.get(), 1_MiB, 0), true);
-        ASSERT_EQ(android::base::WriteFully(product_a_->fd, random_buffer.get(), 1_MiB), true);
-    }
-
-    // Create loopback devices
-    system_a_loop_ = std::make_unique<LoopDevice>(std::string(system_a_->path), 10s);
-    ASSERT_TRUE(system_a_loop_->valid());
-
-    product_a_loop_ = std::make_unique<LoopDevice>(std::string(product_a_->path), 10s);
-    ASSERT_TRUE(product_a_loop_->valid());
-
-    sys_fd_.reset(open(system_a_loop_->device().c_str(), O_RDONLY));
-    ASSERT_TRUE(sys_fd_ > 0);
-
-    product_fd_.reset(open(product_a_loop_->device().c_str(), O_RDONLY));
-    ASSERT_TRUE(product_fd_ > 0);
-
-    // Read from system partition from offset 0 of size 100MB
-    ASSERT_EQ(ReadFullyAtOffset(sys_fd_, system_buffer_.get(), size_, 0), true);
-
-    // Read from product partition from offset 0 of size 100MB
-    ASSERT_EQ(ReadFullyAtOffset(product_fd_, product_buffer_.get(), size_, 0), true);
-}
-
-void SnapuserdTest::CreateCowDevice(std::unique_ptr<TemporaryFile>& cow) {
-    //================Create a COW file with the following operations===========
-    //
-    // Create COW file which is gz compressed
-    //
-    // 0-100 MB of replace operation with random data
-    // 100-200 MB of copy operation
-    // 200-300 MB of zero operation
-    // 300-400 MB of replace operation with random data
-
     CowOptions options;
     options.compression = "gz";
     CowWriter writer(options);
 
-    ASSERT_TRUE(writer.Initialize(cow->fd));
-
-    // Write 100MB random data to COW file which is gz compressed from block 0
-    ASSERT_TRUE(writer.AddRawBlocks(0, random_buffer_1_.get(), size_));
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
 
     size_t num_blocks = size_ / options.block_size;
-    size_t blk_start_copy = num_blocks;
-    size_t blk_end_copy = blk_start_copy + num_blocks;
-    size_t source_blk = 0;
+    size_t blk_end_copy = num_blocks * 2;
+    size_t source_blk = num_blocks - 1;
+    size_t blk_src_copy = blk_end_copy - 1;
 
-    // Copy blocks - source_blk starts from 0 as snapuserd
-    // has to read from block 0 in system_a partition
-    //
-    // This initializes copy operation from block 0 of size 100 MB from
-    // /dev/block/mapper/system_a or product_a
-    for (size_t i = blk_start_copy; i < blk_end_copy; i++) {
-        ASSERT_TRUE(writer.AddCopy(i, source_blk));
-        source_blk += 1;
+    size_t x = num_blocks;
+    while (1) {
+        ASSERT_TRUE(writer.AddCopy(source_blk, blk_src_copy));
+        x -= 1;
+        if (x == 0) {
+            break;
+        }
+        source_blk -= 1;
+        blk_src_copy -= 1;
     }
 
-    size_t blk_zero_copy_start = blk_end_copy;
+    source_blk = num_blocks;
+    blk_src_copy = blk_end_copy;
+
+    ASSERT_TRUE(writer.AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
+
+    size_t blk_zero_copy_start = source_blk + num_blocks;
     size_t blk_zero_copy_end = blk_zero_copy_start + num_blocks;
 
-    // 100 MB filled with zeroes
     ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks));
 
-    // Final 100MB filled with random data which is gz compressed
     size_t blk_random2_replace_start = blk_zero_copy_end;
 
-    ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_2_.get(), size_));
+    ASSERT_TRUE(writer.AddRawBlocks(blk_random2_replace_start, random_buffer_1_.get(), size_));
 
     // Flush operations
     ASSERT_TRUE(writer.Finalize());
-
-    ASSERT_EQ(lseek(cow->fd, 0, SEEK_SET), 0);
+    // Construct the buffer required for validation
+    orig_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    std::string zero_buffer(size_, 0);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, orig_buffer_.get(), size_, size_), true);
+    memcpy((char*)orig_buffer_.get() + size_, random_buffer_1_.get(), size_);
+    memcpy((char*)orig_buffer_.get() + (size_ * 2), (void*)zero_buffer.c_str(), size_);
+    memcpy((char*)orig_buffer_.get() + (size_ * 3), random_buffer_1_.get(), size_);
 }
 
-void SnapuserdTest::CreateSystemDmUser(std::unique_ptr<TemporaryFile>& cow) {
-    std::string cmd;
+void CowSnapuserdTest::InitCowDevice() {
+    cow_num_sectors_ = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
+                                              base_loop_->device());
+    ASSERT_NE(cow_num_sectors_, 0);
+}
+
+void CowSnapuserdTest::SetDeviceControlName() {
     system_device_name_.clear();
     system_device_ctrl_name_.clear();
 
-    std::string str(cow->path);
+    std::string str(cow_system_->path);
     std::size_t found = str.find_last_of("/\\");
     ASSERT_NE(found, std::string::npos);
     system_device_name_ = str.substr(found + 1);
 
-    // Create a control device
     system_device_ctrl_name_ = system_device_name_ + "-ctrl";
-    cmd = "dmctl create " + system_device_name_ + " user 0 " + std::to_string(system_blksize_);
-    cmd += " " + system_device_ctrl_name_;
-
-    system(cmd.c_str());
 }
 
-void SnapuserdTest::DeleteDmUser(std::unique_ptr<TemporaryFile>& cow, std::string snapshot_device) {
-    std::string cmd;
+void CowSnapuserdTest::CreateDmUserDevice() {
+    DmTable dmuser_table;
+    ASSERT_TRUE(dmuser_table.AddTarget(
+            std::make_unique<DmTargetUser>(0, cow_num_sectors_, system_device_ctrl_name_)));
+    ASSERT_TRUE(dmuser_table.valid());
 
-    cmd = "dmctl delete " + snapshot_device;
-    system(cmd.c_str());
+    dmuser_dev_ = std::make_unique<TempDevice>(system_device_name_, dmuser_table);
+    ASSERT_TRUE(dmuser_dev_->valid());
+    ASSERT_FALSE(dmuser_dev_->path().empty());
 
-    cmd.clear();
-
-    std::string str(cow->path);
-    std::size_t found = str.find_last_of("/\\");
-    ASSERT_NE(found, std::string::npos);
-    std::string device_name = str.substr(found + 1);
-
-    cmd = "dmctl delete " + device_name;
-
-    system(cmd.c_str());
+    auto misc_device = "/dev/dm-user/" + system_device_ctrl_name_;
+    ASSERT_TRUE(android::fs_mgr::WaitForFile(misc_device, 10s));
 }
 
-void SnapuserdTest::CreateProductDmUser(std::unique_ptr<TemporaryFile>& cow) {
-    std::string cmd;
-    product_device_name_.clear();
-    product_device_ctrl_name_.clear();
-
-    std::string str(cow->path);
-    std::size_t found = str.find_last_of("/\\");
-    ASSERT_NE(found, std::string::npos);
-    product_device_name_ = str.substr(found + 1);
-    product_device_ctrl_name_ = product_device_name_ + "-ctrl";
-    cmd = "dmctl create " + product_device_name_ + " user 0 " + std::to_string(product_blksize_);
-    cmd += " " + product_device_ctrl_name_;
-
-    system(cmd.c_str());
-}
-
-void SnapuserdTest::InitCowDevices() {
-    system_blksize_ = client_->InitDmUserCow(system_device_ctrl_name_, cow_system_->path,
-                                             system_a_loop_->device());
-    ASSERT_NE(system_blksize_, 0);
-
-    product_blksize_ = client_->InitDmUserCow(product_device_ctrl_name_, cow_product_->path,
-                                              product_a_loop_->device());
-    ASSERT_NE(product_blksize_, 0);
-}
-
-void SnapuserdTest::InitDaemon() {
+void CowSnapuserdTest::InitDaemon() {
     bool ok = client_->AttachDmUser(system_device_ctrl_name_);
     ASSERT_TRUE(ok);
-
-    ok = client_->AttachDmUser(product_device_ctrl_name_);
-    ASSERT_TRUE(ok);
 }
 
-void SnapuserdTest::StartSnapuserdDaemon() {
-    ASSERT_TRUE(EnsureSnapuserdStarted());
+void CowSnapuserdTest::CreateSnapshotDevice() {
+    DmTable snap_table;
+    ASSERT_TRUE(snap_table.AddTarget(std::make_unique<DmTargetSnapshot>(
+            0, total_base_size_ / kSectorSize, base_loop_->device(), dmuser_dev_->path(),
+            SnapshotStorageMode::Persistent, 8)));
+    ASSERT_TRUE(snap_table.valid());
 
-    client_ = SnapuserdClient::Connect(kSnapuserdSocket, 5s);
-    ASSERT_NE(client_, nullptr);
+    snap_table.set_readonly(true);
+
+    snapshot_dev_ = std::make_unique<TempDevice>("cowsnapuserd-test-dm-snapshot", snap_table);
+    ASSERT_TRUE(snapshot_dev_->valid());
+    ASSERT_FALSE(snapshot_dev_->path().empty());
 }
 
-void SnapuserdTest::CreateSnapshotDevices() {
-    std::string cmd;
+void CowSnapuserdTest::SetupImpl() {
+    CreateBaseDevice();
+    CreateCowDevice();
 
-    cmd = "dmctl create system-snapshot -ro snapshot 0 " + std::to_string(system_blksize_);
-    cmd += " " + system_a_loop_->device();
-    cmd += " /dev/block/mapper/" + system_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-
-    cmd.clear();
-
-    cmd = "dmctl create product-snapshot -ro snapshot 0 " + std::to_string(product_blksize_);
-    cmd += " " + product_a_loop_->device();
-    cmd += " /dev/block/mapper/" + product_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-}
-
-void SnapuserdTest::SwitchSnapshotDevices() {
-    std::string cmd;
-
-    cmd = "dmctl create system-snapshot-1 -ro snapshot 0 " + std::to_string(system_blksize_);
-    cmd += " " + system_a_loop_->device();
-    cmd += " /dev/block/mapper/" + system_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-
-    cmd.clear();
-
-    cmd = "dmctl create product-snapshot-1 -ro snapshot 0 " + std::to_string(product_blksize_);
-    cmd += " " + product_a_loop_->device();
-    cmd += " /dev/block/mapper/" + product_device_name_;
-    cmd += " P 8";
-
-    system(cmd.c_str());
-}
-
-void SnapuserdTest::TestIO(unique_fd& snapshot_fd, std::unique_ptr<uint8_t[]>& buffer) {
-    loff_t offset = 0;
-    // std::unique_ptr<uint8_t[]> buffer = std::move(buf);
-
-    std::unique_ptr<uint8_t[]> snapuserd_buffer = std::make_unique<uint8_t[]>(size_);
-
-    //================Start IO operation on dm-snapshot device=================
-    // This will test the following paths:
-    //
-    // 1: IO path for all three operations and interleaving of operations.
-    // 2: Merging of blocks in kernel during metadata read
-    // 3: Bulk IO issued by kernel duing merge operation
-
-    // Read from snapshot device of size 100MB from offset 0. This tests the
-    // 1st replace operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_compressed_cow (replace
-    // op)->decompress_cow->return
-
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Update the offset
-    offset += size_;
-
-    // Compare data with random_buffer_1_.
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_1_.get(), size_), 0);
-
-    // Clear the buffer
-    memset(snapuserd_buffer.get(), 0, size_);
-
-    // Read from snapshot device of size 100MB from offset 100MB. This tests the
-    // copy operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_from_(system_a/product_a) partition
-    // (copy op) -> return
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Update the offset
-    offset += size_;
-
-    // Compare data with buffer.
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), buffer.get(), size_), 0);
-
-    // Read from snapshot device of size 100MB from offset 200MB. This tests the
-    // zero operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->fill_memory_with_zero
-    // (zero op) -> return
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Compare data with zero filled buffer
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), zero_buffer_.get(), size_), 0);
-
-    // Update the offset
-    offset += size_;
-
-    // Read from snapshot device of size 100MB from offset 300MB. This tests the
-    // final replace operation.
-    //
-    // IO path:
-    //
-    // dm-snap->dm-snap-persistent->dm-user->snapuserd->read_compressed_cow (replace
-    // op)->decompress_cow->return
-    ASSERT_EQ(ReadFullyAtOffset(snapshot_fd, snapuserd_buffer.get(), size_, offset), true);
-
-    // Compare data with random_buffer_2_.
-    ASSERT_EQ(memcmp(snapuserd_buffer.get(), random_buffer_2_.get(), size_), 0);
-}
-
-TEST_F(SnapuserdTest, ReadWrite) {
-    unique_fd snapshot_fd;
-
-    Init();
-
-    CreateCowDevice(cow_system_);
-    CreateCowDevice(cow_product_);
+    SetDeviceControlName();
 
     StartSnapuserdDaemon();
-    InitCowDevices();
+    InitCowDevice();
 
-    CreateSystemDmUser(cow_system_);
-    CreateProductDmUser(cow_product_);
-
+    CreateDmUserDevice();
     InitDaemon();
 
-    CreateSnapshotDevices();
+    CreateSnapshotDevice();
+    setup_ok_ = true;
+}
 
-    snapshot_fd.reset(open("/dev/block/mapper/system-snapshot", O_RDONLY));
-    ASSERT_TRUE(snapshot_fd > 0);
-    TestIO(snapshot_fd, system_buffer_);
+bool CowSnapuserdTest::Merge() {
+    MergeImpl();
+    return merge_ok_;
+}
 
-    snapshot_fd.reset(open("/dev/block/mapper/product-snapshot", O_RDONLY));
-    ASSERT_TRUE(snapshot_fd > 0);
-    TestIO(snapshot_fd, product_buffer_);
+void CowSnapuserdTest::StartMerge() {
+    DmTable merge_table;
+    ASSERT_TRUE(merge_table.AddTarget(std::make_unique<DmTargetSnapshot>(
+            0, total_base_size_ / kSectorSize, base_loop_->device(), dmuser_dev_->path(),
+            SnapshotStorageMode::Merge, 8)));
+    ASSERT_TRUE(merge_table.valid());
+    ASSERT_EQ(total_base_size_ / kSectorSize, merge_table.num_sectors());
 
-    snapshot_fd.reset(-1);
+    DeviceMapper& dm = DeviceMapper::Instance();
+    ASSERT_TRUE(dm.LoadTableAndActivate("cowsnapuserd-test-dm-snapshot", merge_table));
+}
 
-    DeleteDmUser(cow_system_, "system-snapshot");
-    DeleteDmUser(cow_product_, "product-snapshot");
+void CowSnapuserdTest::MergeImpl() {
+    StartMerge();
+    DeviceMapper& dm = DeviceMapper::Instance();
 
-    ASSERT_TRUE(client_->StopSnapuserd());
+    while (true) {
+        vector<DeviceMapper::TargetInfo> status;
+        ASSERT_TRUE(dm.GetTableStatus("cowsnapuserd-test-dm-snapshot", &status));
+        ASSERT_EQ(status.size(), 1);
+        ASSERT_EQ(strncmp(status[0].spec.target_type, "snapshot-merge", strlen("snapshot-merge")),
+                  0);
+
+        DmTargetSnapshot::Status merge_status;
+        ASSERT_TRUE(DmTargetSnapshot::ParseStatusText(status[0].data, &merge_status));
+        ASSERT_TRUE(merge_status.error.empty());
+        if (merge_status.sectors_allocated == merge_status.metadata_sectors) {
+            break;
+        }
+
+        std::this_thread::sleep_for(250ms);
+    }
+
+    merge_ok_ = true;
+}
+
+void CowSnapuserdTest::ValidateMerge() {
+    merged_buffer_ = std::make_unique<uint8_t[]>(total_base_size_);
+    ASSERT_EQ(android::base::ReadFullyAtOffset(base_fd_, merged_buffer_.get(), total_base_size_, 0),
+              true);
+    ASSERT_EQ(memcmp(merged_buffer_.get(), orig_buffer_.get(), total_base_size_), 0);
+}
+
+void CowSnapuserdTest::SimulateDaemonRestart() {
+    Shutdown();
+    SetDeviceControlName();
+    StartSnapuserdDaemon();
+    InitCowDevice();
+    CreateDmUserDevice();
+    InitDaemon();
+    CreateSnapshotDevice();
+}
+
+void CowSnapuserdTest::MergeInterrupt() {
+    // Interrupt merge at various intervals
+    StartMerge();
+    std::this_thread::sleep_for(250ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(250ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(150ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(100ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(800ms);
+    SimulateDaemonRestart();
+
+    StartMerge();
+    std::this_thread::sleep_for(600ms);
+    SimulateDaemonRestart();
+
+    ASSERT_TRUE(Merge());
+}
+
+void CowSnapuserdMetadataTest::CreateCowPartialFilledArea() {
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    // Area 0 is completely filled with 256 exceptions
+    for (int i = 0; i < 256; i++) {
+        ASSERT_TRUE(writer.AddCopy(i, 256 + i));
+    }
+
+    // Area 1 is partially filled with 2 copy ops and 10 zero ops
+    ASSERT_TRUE(writer.AddCopy(500, 1000));
+    ASSERT_TRUE(writer.AddCopy(501, 1001));
+
+    ASSERT_TRUE(writer.AddZeroBlocks(300, 10));
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+}
+
+void CowSnapuserdMetadataTest::ValidatePartialFilledArea() {
+    int area_sz = snapuserd_->GetMetadataAreaSize();
+
+    ASSERT_EQ(area_sz, 2);
+
+    size_t new_chunk = 263;
+    // Verify the partially filled area
+    void* buffer = snapuserd_->GetExceptionBuffer(1);
+    loff_t offset = 0;
+    struct disk_exception* de;
+    for (int i = 0; i < 12; i++) {
+        de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+        ASSERT_EQ(de->old_chunk, i);
+        ASSERT_EQ(de->new_chunk, new_chunk);
+        offset += sizeof(struct disk_exception);
+        new_chunk += 1;
+    }
+
+    de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+    ASSERT_EQ(de->old_chunk, 0);
+    ASSERT_EQ(de->new_chunk, 0);
+}
+
+void CowSnapuserdMetadataTest::SetupPartialArea() {
+    CreateCowPartialFilledArea();
+    InitMetadata();
+}
+
+void CowSnapuserdMetadataTest::CreateCowDevice() {
+    unique_fd rnd_fd;
+    loff_t offset = 0;
+
+    std::string path = android::base::GetExecutableDirectory();
+    cow_system_ = std::make_unique<TemporaryFile>(path);
+
+    rnd_fd.reset(open("/dev/random", O_RDONLY));
+    ASSERT_TRUE(rnd_fd > 0);
+
+    std::unique_ptr<uint8_t[]> random_buffer_1_ = std::make_unique<uint8_t[]>(size_);
+
+    // Fill random data
+    for (size_t j = 0; j < (size_ / 1_MiB); j++) {
+        ASSERT_EQ(ReadFullyAtOffset(rnd_fd, (char*)random_buffer_1_.get() + offset, 1_MiB, 0),
+                  true);
+
+        offset += 1_MiB;
+    }
+
+    CowOptions options;
+    options.compression = "gz";
+    CowWriter writer(options);
+
+    ASSERT_TRUE(writer.Initialize(cow_system_->fd));
+
+    size_t num_blocks = size_ / options.block_size;
+
+    // Overlapping region. This has to be split
+    // into two batch operations
+    ASSERT_TRUE(writer.AddCopy(23, 20));
+    ASSERT_TRUE(writer.AddCopy(22, 19));
+    ASSERT_TRUE(writer.AddCopy(21, 18));
+    ASSERT_TRUE(writer.AddCopy(20, 17));
+    ASSERT_TRUE(writer.AddCopy(19, 16));
+    ASSERT_TRUE(writer.AddCopy(18, 15));
+
+    // Contiguous region but blocks in ascending order
+    // Daemon has to ensure that these blocks are merged
+    // in a batch
+    ASSERT_TRUE(writer.AddCopy(50, 75));
+    ASSERT_TRUE(writer.AddCopy(51, 76));
+    ASSERT_TRUE(writer.AddCopy(52, 77));
+    ASSERT_TRUE(writer.AddCopy(53, 78));
+
+    // Dis-contiguous region
+    ASSERT_TRUE(writer.AddCopy(110, 130));
+    ASSERT_TRUE(writer.AddCopy(105, 125));
+    ASSERT_TRUE(writer.AddCopy(100, 120));
+
+    // Overlap
+    ASSERT_TRUE(writer.AddCopy(25, 30));
+    ASSERT_TRUE(writer.AddCopy(30, 31));
+
+    size_t source_blk = num_blocks;
+
+    ASSERT_TRUE(writer.AddRawBlocks(source_blk, random_buffer_1_.get(), size_));
+
+    size_t blk_zero_copy_start = source_blk + num_blocks;
+
+    ASSERT_TRUE(writer.AddZeroBlocks(blk_zero_copy_start, num_blocks));
+
+    // Flush operations
+    ASSERT_TRUE(writer.Finalize());
+}
+
+void CowSnapuserdMetadataTest::InitMetadata() {
+    snapuserd_ = std::make_unique<Snapuserd>("", cow_system_->path, "");
+    ASSERT_TRUE(snapuserd_->InitCowDevice());
+}
+
+void CowSnapuserdMetadataTest::Setup() {
+    CreateCowDevice();
+    InitMetadata();
+}
+
+void CowSnapuserdMetadataTest::ValidateMetadata() {
+    int area_sz = snapuserd_->GetMetadataAreaSize();
+    ASSERT_EQ(area_sz, 3);
+
+    size_t old_chunk;
+    size_t new_chunk;
+
+    for (int i = 0; i < area_sz; i++) {
+        void* buffer = snapuserd_->GetExceptionBuffer(i);
+        loff_t offset = 0;
+        if (i == 0) {
+            old_chunk = 256;
+            new_chunk = 2;
+        } else if (i == 1) {
+            old_chunk = 512;
+            new_chunk = 259;
+        }
+        for (int j = 0; j < 256; j++) {
+            struct disk_exception* de =
+                    reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+
+            if (i != 2) {
+                ASSERT_EQ(de->old_chunk, old_chunk);
+                ASSERT_EQ(de->new_chunk, new_chunk);
+                old_chunk += 1;
+                new_chunk += 1;
+            } else {
+                break;
+            }
+            offset += sizeof(struct disk_exception);
+        }
+
+        if (i == 2) {
+            // The first 5 copy operation is not batch merged
+            // as the sequence is discontiguous
+            struct disk_exception* de =
+                    reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 30);
+            ASSERT_EQ(de->new_chunk, 518);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 25);
+            ASSERT_EQ(de->new_chunk, 520);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 100);
+            ASSERT_EQ(de->new_chunk, 522);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 105);
+            ASSERT_EQ(de->new_chunk, 524);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 110);
+            ASSERT_EQ(de->new_chunk, 526);
+            offset += sizeof(struct disk_exception);
+
+            // The next 4 operations are batch merged as
+            // both old and new chunk are contiguous
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 50);
+            ASSERT_EQ(de->new_chunk, 528);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 51);
+            ASSERT_EQ(de->new_chunk, 529);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 52);
+            ASSERT_EQ(de->new_chunk, 530);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 53);
+            ASSERT_EQ(de->new_chunk, 531);
+            offset += sizeof(struct disk_exception);
+
+            // This is handling overlap operation with
+            // two batch merge operations.
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 18);
+            ASSERT_EQ(de->new_chunk, 533);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 19);
+            ASSERT_EQ(de->new_chunk, 534);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 20);
+            ASSERT_EQ(de->new_chunk, 535);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 21);
+            ASSERT_EQ(de->new_chunk, 537);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 22);
+            ASSERT_EQ(de->new_chunk, 538);
+            offset += sizeof(struct disk_exception);
+
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 23);
+            ASSERT_EQ(de->new_chunk, 539);
+            offset += sizeof(struct disk_exception);
+
+            // End of metadata
+            de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
+            ASSERT_EQ(de->old_chunk, 0);
+            ASSERT_EQ(de->new_chunk, 0);
+            offset += sizeof(struct disk_exception);
+        }
+    }
+}
+
+TEST(Snapuserd_Test, Snapshot_Metadata) {
+    CowSnapuserdMetadataTest harness;
+    harness.Setup();
+    harness.ValidateMetadata();
+}
+
+TEST(Snapuserd_Test, Snapshot_Metadata_Overlap) {
+    CowSnapuserdMetadataTest harness;
+    harness.SetupPartialArea();
+    harness.ValidatePartialFilledArea();
+}
+
+TEST(Snapuserd_Test, Snapshot_Merge_Resume) {
+    CowSnapuserdTest harness;
+    ASSERT_TRUE(harness.Setup());
+    harness.MergeInterrupt();
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_IO_TEST) {
+    CowSnapuserdTest harness;
+    ASSERT_TRUE(harness.Setup());
+    harness.ReadSnapshotDeviceAndValidate();
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_1) {
+    CowSnapuserdTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_1());
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_TEST_2) {
+    CowSnapuserdTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_2());
+    ASSERT_TRUE(harness.Merge());
+    harness.ValidateMerge();
+    harness.Shutdown();
+}
+
+TEST(Snapuserd_Test, Snapshot_COPY_Overlap_Merge_Resume_TEST) {
+    CowSnapuserdTest harness;
+    ASSERT_TRUE(harness.SetupCopyOverlap_1());
+    harness.MergeInterrupt();
+    harness.ValidateMerge();
+    harness.Shutdown();
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp
index 957ba35..51c00a9 100644
--- a/fs_mgr/libsnapshot/cow_writer.cpp
+++ b/fs_mgr/libsnapshot/cow_writer.cpp
@@ -90,8 +90,11 @@
     header_.minor_version = kCowVersionMinor;
     header_.header_size = sizeof(CowHeader);
     header_.footer_size = sizeof(CowFooter);
+    header_.op_size = sizeof(CowOperation);
     header_.block_size = options_.block_size;
     header_.num_merge_ops = 0;
+    header_.cluster_ops = options_.cluster_ops;
+    header_.buffer_size = 0;
     footer_ = {};
     footer_.op.data_length = 64;
     footer_.op.type = kCowFooterOp;
@@ -108,6 +111,10 @@
         LOG(ERROR) << "unrecognized compression: " << options_.compression;
         return false;
     }
+    if (options_.cluster_ops == 1) {
+        LOG(ERROR) << "Clusters must contain at least two operations to function.";
+        return false;
+    }
     return true;
 }
 
@@ -133,12 +140,6 @@
     return true;
 }
 
-void CowWriter::InitializeMerge(borrowed_fd fd, CowHeader* header) {
-    fd_ = fd;
-    memcpy(&header_, header, sizeof(CowHeader));
-    merge_in_progress_ = true;
-}
-
 bool CowWriter::Initialize(unique_fd&& fd) {
     owned_fd_ = std::move(fd);
     return Initialize(borrowed_fd{owned_fd_});
@@ -165,6 +166,19 @@
     return OpenForAppend(label);
 }
 
+void CowWriter::InitPos() {
+    next_op_pos_ = sizeof(header_) + header_.buffer_size;
+    cluster_size_ = header_.cluster_ops * sizeof(CowOperation);
+    if (header_.cluster_ops) {
+        next_data_pos_ = next_op_pos_ + cluster_size_;
+    } else {
+        next_data_pos_ = next_op_pos_ + sizeof(CowOperation);
+    }
+    ops_.clear();
+    current_cluster_size_ = 0;
+    current_data_size_ = 0;
+}
+
 bool CowWriter::OpenForWrite() {
     // This limitation is tied to the data field size in CowOperation.
     if (header_.block_size > std::numeric_limits<uint16_t>::max()) {
@@ -177,6 +191,10 @@
         return false;
     }
 
+    if (options_.scratch_space) {
+        header_.buffer_size = BUFFER_REGION_DEFAULT_SIZE;
+    }
+
     // Headers are not complete, but this ensures the file is at the right
     // position.
     if (!android::base::WriteFully(fd_, &header_, sizeof(header_))) {
@@ -184,7 +202,27 @@
         return false;
     }
 
-    next_op_pos_ = sizeof(header_);
+    if (options_.scratch_space) {
+        // Initialize the scratch space
+        std::string data(header_.buffer_size, 0);
+        if (!android::base::WriteFully(fd_, data.data(), header_.buffer_size)) {
+            PLOG(ERROR) << "writing scratch space failed";
+            return false;
+        }
+    }
+
+    if (!Sync()) {
+        LOG(ERROR) << "Header sync failed";
+        return false;
+    }
+
+    if (lseek(fd_.get(), sizeof(header_) + header_.buffer_size, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek failed";
+        return false;
+    }
+
+    InitPos();
+
     return true;
 }
 
@@ -197,13 +235,14 @@
     }
 
     options_.block_size = header_.block_size;
+    options_.cluster_ops = header_.cluster_ops;
 
     // Reset this, since we're going to reimport all operations.
     footer_.op.num_ops = 0;
-    next_op_pos_ = sizeof(header_);
-    ops_.resize(0);
+    InitPos();
 
     auto iter = reader->GetOpIter();
+
     while (!iter->Done()) {
         AddOperation(iter->Get());
         iter->Next();
@@ -212,15 +251,11 @@
     // Free reader so we own the descriptor position again.
     reader = nullptr;
 
-    // Remove excess data
-    if (!Truncate(next_op_pos_)) {
-        return false;
-    }
     if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
         PLOG(ERROR) << "lseek failed";
         return false;
     }
-    return true;
+    return EmitClusterIfNeeded();
 }
 
 bool CowWriter::EmitCopy(uint64_t new_block, uint64_t old_block) {
@@ -234,14 +269,12 @@
 
 bool CowWriter::EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) {
     const uint8_t* iter = reinterpret_cast<const uint8_t*>(data);
-    uint64_t pos;
     CHECK(!merge_in_progress_);
     for (size_t i = 0; i < size / header_.block_size; i++) {
         CowOperation op = {};
         op.type = kCowReplaceOp;
         op.new_block = new_block_start + i;
-        GetDataPos(&pos);
-        op.source = pos + sizeof(op);
+        op.source = next_data_pos_;
 
         if (compression_) {
             auto data = Compress(iter, header_.block_size);
@@ -293,6 +326,22 @@
     return WriteOperation(op) && Sync();
 }
 
+bool CowWriter::EmitCluster() {
+    CowOperation op = {};
+    op.type = kCowClusterOp;
+    // Next cluster starts after remainder of current cluster and the next data block.
+    op.source = current_data_size_ + cluster_size_ - current_cluster_size_ - sizeof(CowOperation);
+    return WriteOperation(op);
+}
+
+bool CowWriter::EmitClusterIfNeeded() {
+    // If there isn't room for another op and the cluster end op, end the current cluster
+    if (cluster_size_ && cluster_size_ < current_cluster_size_ + 2 * sizeof(CowOperation)) {
+        if (!EmitCluster()) return false;
+    }
+    return true;
+}
+
 std::basic_string<uint8_t> CowWriter::Compress(const void* data, size_t length) {
     switch (compression_) {
         case kCowCompressGz: {
@@ -345,11 +394,39 @@
 }
 
 bool CowWriter::Finalize() {
-    footer_.op.ops_size = ops_.size();
-    uint64_t pos;
+    auto continue_cluster_size = current_cluster_size_;
+    auto continue_data_size = current_data_size_;
+    auto continue_data_pos = next_data_pos_;
+    auto continue_op_pos = next_op_pos_;
+    auto continue_size = ops_.size();
+    auto continue_num_ops = footer_.op.num_ops;
+    bool extra_cluster = false;
 
-    if (!GetDataPos(&pos)) {
-        PLOG(ERROR) << "failed to get file position";
+    // Blank out extra ops, in case we're in append mode and dropped ops.
+    if (cluster_size_) {
+        auto unused_cluster_space = cluster_size_ - current_cluster_size_;
+        std::string clr;
+        clr.resize(unused_cluster_space, '\0');
+        if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
+            PLOG(ERROR) << "Failed to seek to footer position.";
+            return false;
+        }
+        if (!android::base::WriteFully(fd_, clr.data(), clr.size())) {
+            PLOG(ERROR) << "clearing unused cluster area failed";
+            return false;
+        }
+    }
+
+    // Footer should be at the end of a file, so if there is data after the current block, end it
+    // and start a new cluster.
+    if (cluster_size_ && current_data_size_ > 0) {
+        EmitCluster();
+        extra_cluster = true;
+    }
+
+    footer_.op.ops_size = ops_.size();
+    if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
+        PLOG(ERROR) << "Failed to seek to footer position.";
         return false;
     }
     memset(&footer_.data.ops_checksum, 0, sizeof(uint8_t) * 32);
@@ -364,16 +441,35 @@
         return false;
     }
 
-    // Re-position for any subsequent writes.
-    if (lseek(fd_.get(), pos, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek ops failed";
+    // Remove excess data, if we're in append mode and threw away more data
+    // than we wrote before.
+    off_t offs = lseek(fd_.get(), 0, SEEK_CUR);
+    if (offs < 0) {
+        PLOG(ERROR) << "Failed to lseek to find current position";
         return false;
     }
+    if (!Truncate(offs)) {
+        return false;
+    }
+
+    // Reposition for additional Writing
+    if (extra_cluster) {
+        current_cluster_size_ = continue_cluster_size;
+        current_data_size_ = continue_data_size;
+        next_data_pos_ = continue_data_pos;
+        next_op_pos_ = continue_op_pos;
+        footer_.op.num_ops = continue_num_ops;
+        ops_.resize(continue_size);
+    }
     return Sync();
 }
 
 uint64_t CowWriter::GetCowSize() {
-    return next_op_pos_ + sizeof(footer_);
+    if (current_data_size_ > 0) {
+        return next_data_pos_ + sizeof(footer_);
+    } else {
+        return next_op_pos_ + sizeof(footer_);
+    }
 }
 
 bool CowWriter::GetDataPos(uint64_t* pos) {
@@ -387,6 +483,10 @@
 }
 
 bool CowWriter::WriteOperation(const CowOperation& op, const void* data, size_t size) {
+    if (lseek(fd_.get(), next_op_pos_, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek failed for writing operation.";
+        return false;
+    }
     if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&op), sizeof(op))) {
         return false;
     }
@@ -394,16 +494,31 @@
         if (!WriteRawData(data, size)) return false;
     }
     AddOperation(op);
-    return true;
+    return EmitClusterIfNeeded();
 }
 
 void CowWriter::AddOperation(const CowOperation& op) {
     footer_.op.num_ops++;
-    next_op_pos_ += sizeof(CowOperation) + GetNextOpOffset(op);
+
+    if (op.type == kCowClusterOp) {
+        current_cluster_size_ = 0;
+        current_data_size_ = 0;
+    } else if (header_.cluster_ops) {
+        current_cluster_size_ += sizeof(op);
+        current_data_size_ += op.data_length;
+    }
+
+    next_data_pos_ += op.data_length + GetNextDataOffset(op, header_.cluster_ops);
+    next_op_pos_ += sizeof(CowOperation) + GetNextOpOffset(op, header_.cluster_ops);
     ops_.insert(ops_.size(), reinterpret_cast<const uint8_t*>(&op), sizeof(op));
 }
 
 bool CowWriter::WriteRawData(const void* data, size_t size) {
+    if (lseek(fd_.get(), next_data_pos_, SEEK_SET) < 0) {
+        PLOG(ERROR) << "lseek failed for writing data.";
+        return false;
+    }
+
     if (!android::base::WriteFully(fd_, data, size)) {
         return false;
     }
@@ -421,24 +536,6 @@
     return true;
 }
 
-bool CowWriter::CommitMerge(int merged_ops) {
-    CHECK(merge_in_progress_);
-    header_.num_merge_ops += merged_ops;
-
-    if (lseek(fd_.get(), 0, SEEK_SET) < 0) {
-        PLOG(ERROR) << "lseek failed";
-        return false;
-    }
-
-    if (!android::base::WriteFully(fd_, reinterpret_cast<const uint8_t*>(&header_),
-                                   sizeof(header_))) {
-        PLOG(ERROR) << "WriteFully failed";
-        return false;
-    }
-
-    return Sync();
-}
-
 bool CowWriter::Truncate(off_t length) {
     if (is_dev_null_ || is_block_device_) {
         return true;
diff --git a/fs_mgr/libsnapshot/device_info.cpp b/fs_mgr/libsnapshot/device_info.cpp
index 8770255..14ce0ee 100644
--- a/fs_mgr/libsnapshot/device_info.cpp
+++ b/fs_mgr/libsnapshot/device_info.cpp
@@ -17,6 +17,7 @@
 #include <android-base/logging.h>
 #include <fs_mgr.h>
 #include <fs_mgr_overlayfs.h>
+#include <libfiemap/image_manager.h>
 
 namespace android {
 namespace snapshot {
@@ -26,6 +27,7 @@
 using android::hardware::boot::V1_0::CommandResult;
 #endif
 
+using namespace std::chrono_literals;
 using namespace std::string_literals;
 
 #ifdef __ANDROID_RECOVERY__
@@ -34,10 +36,6 @@
 constexpr bool kIsRecovery = false;
 #endif
 
-std::string DeviceInfo::GetGsidDir() const {
-    return "ota"s;
-}
-
 std::string DeviceInfo::GetMetadataDir() const {
     return "/metadata/ota"s;
 }
@@ -100,6 +98,10 @@
     return kIsRecovery;
 }
 
+bool DeviceInfo::IsFirstStageInit() const {
+    return first_stage_init_;
+}
+
 bool DeviceInfo::SetSlotAsUnbootable([[maybe_unused]] unsigned int slot) {
 #ifdef LIBSNAPSHOT_USE_HAL
     if (!EnsureBootHal()) {
@@ -120,8 +122,21 @@
 #endif
 }
 
-std::string DeviceInfo::GetSnapuserdFirstStagePidVar() const {
-    return kSnapuserdFirstStagePidVar;
+std::unique_ptr<android::fiemap::IImageManager> DeviceInfo::OpenImageManager() const {
+    return IDeviceInfo::OpenImageManager("ota");
+}
+
+std::unique_ptr<android::fiemap::IImageManager> ISnapshotManager::IDeviceInfo::OpenImageManager(
+        const std::string& gsid_dir) const {
+    if (IsRecovery() || IsFirstStageInit()) {
+        android::fiemap::ImageManager::DeviceInfo device_info = {
+                .is_recovery = {IsRecovery()},
+        };
+        return android::fiemap::ImageManager::Open(gsid_dir, device_info);
+    } else {
+        // For now, use a preset timeout.
+        return android::fiemap::IImageManager::Open(gsid_dir, 15000ms);
+    }
 }
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/device_info.h b/fs_mgr/libsnapshot/device_info.h
index 1f08860..7999c99 100644
--- a/fs_mgr/libsnapshot/device_info.h
+++ b/fs_mgr/libsnapshot/device_info.h
@@ -29,7 +29,6 @@
     using MergeStatus = android::hardware::boot::V1_1::MergeStatus;
 
   public:
-    std::string GetGsidDir() const override;
     std::string GetMetadataDir() const override;
     std::string GetSlotSuffix() const override;
     std::string GetOtherSlotSuffix() const override;
@@ -39,12 +38,16 @@
     bool SetBootControlMergeStatus(MergeStatus status) override;
     bool SetSlotAsUnbootable(unsigned int slot) override;
     bool IsRecovery() const override;
-    std::string GetSnapuserdFirstStagePidVar() const override;
+    std::unique_ptr<IImageManager> OpenImageManager() const override;
+    bool IsFirstStageInit() const override;
+
+    void set_first_stage_init(bool value) { first_stage_init_ = value; }
 
   private:
     bool EnsureBootHal();
 
     android::fs_mgr::PartitionOpener opener_;
+    bool first_stage_init_ = false;
 #ifdef LIBSNAPSHOT_USE_HAL
     android::sp<android::hardware::boot::V1_1::IBootControl> boot_control_;
 #endif
diff --git a/fs_mgr/libsnapshot/dm_snapshot_internals.h b/fs_mgr/libsnapshot/dm_snapshot_internals.h
index fef256d..ed77c15 100644
--- a/fs_mgr/libsnapshot/dm_snapshot_internals.h
+++ b/fs_mgr/libsnapshot/dm_snapshot_internals.h
@@ -14,8 +14,10 @@
 
 #pragma once
 
+#include <android-base/logging.h>
 #include <stdint.h>
 
+#include <optional>
 #include <vector>
 
 namespace android {
@@ -26,19 +28,46 @@
     DmSnapCowSizeCalculator(unsigned int sector_bytes, unsigned int chunk_sectors)
         : sector_bytes_(sector_bytes),
           chunk_sectors_(chunk_sectors),
-          exceptions_per_chunk(chunk_sectors_ * sector_bytes_ / (64 * 2 / 8)) {}
+          exceptions_per_chunk(chunk_sectors_ * sector_bytes_ / exception_size_bytes) {}
 
     void WriteByte(uint64_t address) { WriteSector(address / sector_bytes_); }
     void WriteSector(uint64_t sector) { WriteChunk(sector / chunk_sectors_); }
     void WriteChunk(uint64_t chunk_id) {
-        if (modified_chunks_.size() <= chunk_id) {
-            modified_chunks_.resize(chunk_id + 1, false);
+        if (!valid_) {
+            return;
         }
+
+        if (modified_chunks_.size() <= chunk_id) {
+            if (modified_chunks_.max_size() <= chunk_id) {
+                LOG(ERROR) << "Invalid COW size, chunk_id is too large.";
+                valid_ = false;
+                return;
+            }
+            modified_chunks_.resize(chunk_id + 1, false);
+            if (modified_chunks_.size() <= chunk_id) {
+                LOG(ERROR) << "Invalid COW size, chunk_id is too large.";
+                valid_ = false;
+                return;
+            }
+        }
+
         modified_chunks_[chunk_id] = true;
     }
 
-    uint64_t cow_size_bytes() const { return cow_size_sectors() * sector_bytes_; }
-    uint64_t cow_size_sectors() const { return cow_size_chunks() * chunk_sectors_; }
+    std::optional<uint64_t> cow_size_bytes() const {
+        auto sectors = cow_size_sectors();
+        if (!sectors) {
+            return std::nullopt;
+        }
+        return sectors.value() * sector_bytes_;
+    }
+    std::optional<uint64_t> cow_size_sectors() const {
+        auto chunks = cow_size_chunks();
+        if (!chunks) {
+            return std::nullopt;
+        }
+        return chunks.value() * chunk_sectors_;
+    }
 
     /*
      * The COW device has a precise internal structure as follows:
@@ -56,7 +85,12 @@
      *   - chunks addressable by previous map (exceptions_per_chunk)
      * - 1 extra chunk
      */
-    uint64_t cow_size_chunks() const {
+    std::optional<uint64_t> cow_size_chunks() const {
+        if (!valid_) {
+            LOG(ERROR) << "Invalid COW size.";
+            return std::nullopt;
+        }
+
         uint64_t modified_chunks_count = 0;
         uint64_t cow_chunks = 0;
 
@@ -90,19 +124,30 @@
     const uint64_t chunk_sectors_;
 
     /*
-     * The COW device stores tables to map the modified chunks. Each table
-     * has the size of exactly 1 chunk.
-     * Each row of the table (also called exception in the kernel) contains two
-     * 64 bit indices to identify the corresponding chunk, and this 128 bit row
-     * size is a constant.
-     * The number of exceptions that each table can contain determines the
-     * number of data chunks that separate two consecutive tables. This value
-     * is then fundamental to compute the space overhead introduced by the
-     * tables in COW devices.
+     * The COW device stores tables to map the modified chunks. Each table has
+     * the size of exactly 1 chunk.
+     * Each entry of the table is called exception and the number of exceptions
+     * that each table can contain determines the number of data chunks that
+     * separate two consecutive tables. This value is then fundamental to
+     * compute the space overhead introduced by the tables in COW devices.
      */
     const uint64_t exceptions_per_chunk;
 
     /*
+     * Each row of the table (called exception in the kernel) contains two
+     * 64 bit indices to identify the corresponding chunk, and this 128 bit
+     * pair is constant in size.
+     */
+    static constexpr unsigned int exception_size_bytes = 64 * 2 / 8;
+
+    /*
+     * Validity check for the container.
+     * It may happen that the caller attempts the write of an invalid chunk
+     * identifier, and this misbehavior is accounted and stored in this value.
+     */
+    bool valid_ = true;
+
+    /*
      * |modified_chunks_| is a container that keeps trace of the modified
      * chunks.
      * Multiple options were considered when choosing the most appropriate data
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
index 80766ff..000e5e1 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_format.h
@@ -21,15 +21,22 @@
 namespace snapshot {
 
 static constexpr uint64_t kCowMagicNumber = 0x436f77634f572121ULL;
-static constexpr uint32_t kCowVersionMajor = 1;
+static constexpr uint32_t kCowVersionMajor = 2;
 static constexpr uint32_t kCowVersionMinor = 0;
 
+static constexpr uint32_t kCowVersionManifest = 2;
+
+static constexpr uint32_t BLOCK_SZ = 4096;
+static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SZ) - 1);
+
 // This header appears as the first sequence of bytes in the COW. All fields
 // in the layout are little-endian encoded. The on-disk layout is:
 //
 //      +-----------------------+
 //      |     Header (fixed)    |
 //      +-----------------------+
+//      |     Scratch space     |
+//      +-----------------------+
 //      | Operation  (variable) |
 //      | Data       (variable) |
 //      +-----------------------+
@@ -57,11 +64,20 @@
     // Size of footer struct
     uint16_t footer_size;
 
+    // Size of op struct
+    uint16_t op_size;
+
     // The size of block operations, in bytes.
     uint32_t block_size;
 
+    // The number of ops to cluster together. 0 For no clustering. Cannot be 1.
+    uint32_t cluster_ops;
+
     // Tracks merge operations completed
     uint64_t num_merge_ops;
+
+    // Scratch space used during merge
+    uint32_t buffer_size;
 } __attribute__((packed));
 
 // This structure is the same size of a normal Operation, but is repurposed for the footer.
@@ -113,13 +129,15 @@
     // For copy operations, this is a block location in the source image.
     //
     // For replace operations, this is a byte offset within the COW's data
-    // section (eg, not landing within the header or metadata). It is an
+    // sections (eg, not landing within the header or metadata). It is an
     // absolute position within the image.
     //
     // For zero operations (replace with all zeroes), this is unused and must
     // be zero.
     //
     // For Label operations, this is the value of the applied label.
+    //
+    // For Cluster operations, this is the length of the following data region
     uint64_t source;
 } __attribute__((packed));
 
@@ -129,20 +147,44 @@
 static constexpr uint8_t kCowReplaceOp = 2;
 static constexpr uint8_t kCowZeroOp = 3;
 static constexpr uint8_t kCowLabelOp = 4;
+static constexpr uint8_t kCowClusterOp = 5;
 static constexpr uint8_t kCowFooterOp = -1;
 
 static constexpr uint8_t kCowCompressNone = 0;
 static constexpr uint8_t kCowCompressGz = 1;
 static constexpr uint8_t kCowCompressBrotli = 2;
 
+static constexpr uint8_t kCowReadAheadNotStarted = 0;
+static constexpr uint8_t kCowReadAheadInProgress = 1;
+static constexpr uint8_t kCowReadAheadDone = 2;
+
 struct CowFooter {
     CowFooterOperation op;
     CowFooterData data;
 } __attribute__((packed));
 
+struct ScratchMetadata {
+    // Block of data in the image that operation modifies
+    // and read-ahead thread stores the modified data
+    // in the scratch space
+    uint64_t new_block;
+    // Offset within the file to read the data
+    uint64_t file_offset;
+} __attribute__((packed));
+
+struct BufferState {
+    uint8_t read_ahead_state;
+} __attribute__((packed));
+
+// 2MB Scratch space used for read-ahead
+static constexpr uint64_t BUFFER_REGION_DEFAULT_SIZE = (1ULL << 21);
+
 std::ostream& operator<<(std::ostream& os, CowOperation const& arg);
 
-int64_t GetNextOpOffset(const CowOperation& op);
+int64_t GetNextOpOffset(const CowOperation& op, uint32_t cluster_size);
+int64_t GetNextDataOffset(const CowOperation& op, uint32_t cluster_size);
+
+bool IsMetadataOp(const CowOperation& op);
 
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
index be69225..9ebcfd9 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h
@@ -116,12 +116,15 @@
 class CowReader : public ICowReader {
   public:
     CowReader();
+    ~CowReader() { owned_fd_ = {}; }
 
     // Parse the COW, optionally, up to the given label. If no label is
     // specified, the COW must have an intact footer.
     bool Parse(android::base::unique_fd&& fd, std::optional<uint64_t> label = {});
     bool Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label = {});
 
+    bool InitForMerge(android::base::unique_fd&& fd);
+
     bool GetHeader(CowHeader* header) override;
     bool GetFooter(CowFooter* footer) override;
 
@@ -138,10 +141,21 @@
 
     bool GetRawBytes(uint64_t offset, void* buffer, size_t len, size_t* read);
 
-    void UpdateMergeProgress(uint64_t merge_ops) { header_.num_merge_ops += merge_ops; }
+    void InitializeMerge();
+
+    void set_total_data_ops(uint64_t size) { total_data_ops_ = size; }
+
+    uint64_t total_data_ops() { return total_data_ops_; }
+
+    void set_copy_ops(uint64_t size) { copy_ops_ = size; }
+
+    uint64_t total_copy_ops() { return copy_ops_; }
+
+    void CloseCowFd() { owned_fd_ = {}; }
 
   private:
     bool ParseOps(std::optional<uint64_t> label);
+    uint64_t FindNumCopyops();
 
     android::base::unique_fd owned_fd_;
     android::base::borrowed_fd fd_;
@@ -150,6 +164,8 @@
     uint64_t fd_size_;
     std::optional<uint64_t> last_label_;
     std::shared_ptr<std::vector<CowOperation>> ops_;
+    uint64_t total_data_ops_;
+    uint64_t copy_ops_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
index e9320b0..f43ea68 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h
@@ -33,6 +33,11 @@
 
     // Maximum number of blocks that can be written.
     std::optional<uint64_t> max_blocks;
+
+    // Number of CowOperations in a cluster. 0 for no clustering. Cannot be 1.
+    uint32_t cluster_ops = 200;
+
+    bool scratch_space = true;
 };
 
 // Interface for writing to a snapuserd COW. All operations are ordered; merges
@@ -97,13 +102,12 @@
     bool InitializeAppend(android::base::unique_fd&&, uint64_t label);
     bool InitializeAppend(android::base::borrowed_fd fd, uint64_t label);
 
-    void InitializeMerge(android::base::borrowed_fd fd, CowHeader* header);
-    bool CommitMerge(int merged_ops);
-
     bool Finalize() override;
 
     uint64_t GetCowSize() override;
 
+    uint32_t GetCowVersion() { return header_.major_version; }
+
   protected:
     virtual bool EmitCopy(uint64_t new_block, uint64_t old_block) override;
     virtual bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override;
@@ -111,6 +115,8 @@
     virtual bool EmitLabel(uint64_t label) override;
 
   private:
+    bool EmitCluster();
+    bool EmitClusterIfNeeded();
     void SetupHeaders();
     bool ParseOptions();
     bool OpenForWrite();
@@ -120,6 +126,7 @@
     bool WriteOperation(const CowOperation& op, const void* data = nullptr, size_t size = 0);
     void AddOperation(const CowOperation& op);
     std::basic_string<uint8_t> Compress(const void* data, size_t length);
+    void InitPos();
 
     bool SetFd(android::base::borrowed_fd fd);
     bool Sync();
@@ -132,6 +139,10 @@
     CowFooter footer_{};
     int compression_ = 0;
     uint64_t next_op_pos_ = 0;
+    uint64_t next_data_pos_ = 0;
+    uint32_t cluster_size_ = 0;
+    uint32_t current_cluster_size_ = 0;
+    uint64_t current_data_size_ = 0;
     bool is_dev_null_ = false;
     bool merge_in_progress_ = false;
     bool is_block_device_ = false;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
index d5c263d..573a85b 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
@@ -22,7 +22,6 @@
 
 class MockDeviceInfo : public SnapshotManager::IDeviceInfo {
   public:
-    MOCK_METHOD(std::string, GetGsidDir, (), (const, override));
     MOCK_METHOD(std::string, GetMetadataDir, (), (const, override));
     MOCK_METHOD(std::string, GetSlotSuffix, (), (const, override));
     MOCK_METHOD(std::string, GetOtherSlotSuffix, (), (const, override));
@@ -32,7 +31,9 @@
     MOCK_METHOD(bool, SetBootControlMergeStatus, (MergeStatus status), (override));
     MOCK_METHOD(bool, SetSlotAsUnbootable, (unsigned int slot), (override));
     MOCK_METHOD(bool, IsRecovery, (), (const, override));
-    MOCK_METHOD(std::string, GetSnapuserdFirstStagePidVar, (), (const, override));
+    MOCK_METHOD(bool, IsFirstStageInit, (), (const, override));
+    MOCK_METHOD(std::unique_ptr<android::fiemap::IImageManager>, OpenImageManager, (),
+                (const, override));
 };
 
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
index 92e7910..94d5055 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -26,12 +26,15 @@
     MOCK_METHOD(bool, BeginUpdate, (), (override));
     MOCK_METHOD(bool, CancelUpdate, (), (override));
     MOCK_METHOD(bool, FinishedSnapshotWrites, (bool wipe), (override));
-    MOCK_METHOD(bool, InitiateMerge, (uint64_t * cow_file_size), (override));
+    MOCK_METHOD(void, UpdateCowStats, (ISnapshotMergeStats * stats), (override));
+    MOCK_METHOD(MergeFailureCode, ReadMergeFailureCode, (), (override));
+    MOCK_METHOD(bool, InitiateMerge, (), (override));
 
     MOCK_METHOD(UpdateState, ProcessUpdateState,
                 (const std::function<bool()>& callback, const std::function<bool()>& before_cancel),
                 (override));
     MOCK_METHOD(UpdateState, GetUpdateState, (double* progress), (override));
+    MOCK_METHOD(bool, UpdateUsesCompression, (), (override));
     MOCK_METHOD(Return, CreateUpdateSnapshots,
                 (const chromeos_update_engine::DeltaArchiveManifest& manifest), (override));
     MOCK_METHOD(bool, MapUpdateSnapshot,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
new file mode 100644
index 0000000..067f99c
--- /dev/null
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot_merge_stats.h
@@ -0,0 +1,50 @@
+//
+// 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 <memory>
+
+#include <gmock/gmock.h>
+#include <libsnapshot/snapshot_stats.h>
+
+namespace android::snapshot {
+
+class MockSnapshotMergeStats final : public ISnapshotMergeStats {
+  public:
+    virtual ~MockSnapshotMergeStats() = default;
+    // Called when merge starts or resumes.
+    MOCK_METHOD(bool, Start, (), (override));
+    MOCK_METHOD(void, set_state, (android::snapshot::UpdateState, bool), (override));
+    MOCK_METHOD(void, set_cow_file_size, (uint64_t), ());
+    MOCK_METHOD(void, set_total_cow_size_bytes, (uint64_t), (override));
+    MOCK_METHOD(void, set_estimated_cow_size_bytes, (uint64_t), (override));
+    MOCK_METHOD(void, set_boot_complete_time_ms, (uint32_t), (override));
+    MOCK_METHOD(void, set_boot_complete_to_merge_start_time_ms, (uint32_t), (override));
+    MOCK_METHOD(void, set_merge_failure_code, (MergeFailureCode), (override));
+    MOCK_METHOD(uint64_t, cow_file_size, (), (override));
+    MOCK_METHOD(uint64_t, total_cow_size_bytes, (), (override));
+    MOCK_METHOD(uint64_t, estimated_cow_size_bytes, (), (override));
+    MOCK_METHOD(uint32_t, boot_complete_time_ms, (), (override));
+    MOCK_METHOD(uint32_t, boot_complete_to_merge_start_time_ms, (), (override));
+    MOCK_METHOD(MergeFailureCode, merge_failure_code, (), (override));
+    MOCK_METHOD(std::unique_ptr<Result>, Finish, (), (override));
+
+    using ISnapshotMergeStats::Result;
+    // Return nullptr if any failure.
+};
+
+}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 351dce7..603e896 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -94,8 +94,9 @@
     // Dependency injection for testing.
     class IDeviceInfo {
       public:
+        using IImageManager = android::fiemap::IImageManager;
+
         virtual ~IDeviceInfo() {}
-        virtual std::string GetGsidDir() const = 0;
         virtual std::string GetMetadataDir() const = 0;
         virtual std::string GetSlotSuffix() const = 0;
         virtual std::string GetOtherSlotSuffix() const = 0;
@@ -107,7 +108,11 @@
         virtual bool SetSlotAsUnbootable(unsigned int slot) = 0;
         virtual bool IsRecovery() const = 0;
         virtual bool IsTestDevice() const { return false; }
-        virtual std::string GetSnapuserdFirstStagePidVar() const = 0;
+        virtual bool IsFirstStageInit() const = 0;
+        virtual std::unique_ptr<IImageManager> OpenImageManager() const = 0;
+
+        // Helper method for implementing OpenImageManager.
+        std::unique_ptr<IImageManager> OpenImageManager(const std::string& gsid_dir) const;
     };
     virtual ~ISnapshotManager() = default;
 
@@ -128,9 +133,14 @@
     // may need to be merged before wiping.
     virtual bool FinishedSnapshotWrites(bool wipe) = 0;
 
+    // Update an ISnapshotMergeStats object with statistics about COW usage.
+    // This should be called before the merge begins as otherwise snapshots
+    // may be deleted.
+    virtual void UpdateCowStats(ISnapshotMergeStats* stats) = 0;
+
     // Initiate a merge on all snapshot devices. This should only be used after an
     // update has been marked successful after booting.
-    virtual bool InitiateMerge(uint64_t* cow_file_size = nullptr) = 0;
+    virtual bool InitiateMerge() = 0;
 
     // Perform any necessary post-boot actions. This should be run soon after
     // /data is mounted.
@@ -163,6 +173,10 @@
     virtual UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
                                            const std::function<bool()>& before_cancel = {}) = 0;
 
+    // If ProcessUpdateState() returned MergeFailed, this returns the appropriate
+    // code. Otherwise, MergeFailureCode::Ok is returned.
+    virtual MergeFailureCode ReadMergeFailureCode() = 0;
+
     // Find the status of the current update, if any.
     //
     // |progress| depends on the returned status:
@@ -171,6 +185,10 @@
     //   Other: 0
     virtual UpdateState GetUpdateState(double* progress = nullptr) = 0;
 
+    // Returns true if compression is enabled for the current update. This always returns false if
+    // UpdateState is None, or no snapshots have been created.
+    virtual bool UpdateUsesCompression() = 0;
+
     // Create necessary COW device / files for OTA clients. New logical partitions will be added to
     // group "cow" in target_metadata. Regions of partitions of current_metadata will be
     // "write-protected" and snapshotted.
@@ -307,22 +325,29 @@
     // Helper function for second stage init to restorecon on the rollback indicator.
     static std::string GetGlobalRollbackIndicatorPath();
 
-    // Initiate the transition from first-stage to second-stage snapuserd. This
-    // process involves re-creating the dm-user table entries for each device,
-    // so that they connect to the new daemon. Once all new tables have been
-    // activated, we ask the first-stage daemon to cleanly exit.
-    //
-    // The caller must pass a function which starts snapuserd.
-    bool PerformSecondStageTransition();
+    // Detach dm-user devices from the current snapuserd, and populate
+    // |snapuserd_argv| with the necessary arguments to restart snapuserd
+    // and reattach them.
+    bool DetachSnapuserdForSelinux(std::vector<std::string>* snapuserd_argv);
+
+    // Perform the transition from the selinux stage of snapuserd into the
+    // second-stage of snapuserd. This process involves re-creating the dm-user
+    // table entries for each device, so that they connect to the new daemon.
+    // Once all new tables have been activated, we ask the first-stage daemon
+    // to cleanly exit.
+    bool PerformSecondStageInitTransition();
 
     // ISnapshotManager overrides.
     bool BeginUpdate() override;
     bool CancelUpdate() override;
     bool FinishedSnapshotWrites(bool wipe) override;
-    bool InitiateMerge(uint64_t* cow_file_size = nullptr) override;
+    void UpdateCowStats(ISnapshotMergeStats* stats) override;
+    MergeFailureCode ReadMergeFailureCode() override;
+    bool InitiateMerge() override;
     UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
                                    const std::function<bool()>& before_cancel = {}) override;
     UpdateState GetUpdateState(double* progress = nullptr) override;
+    bool UpdateUsesCompression() override;
     Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) override;
     bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
                            std::string* snapshot_path) override;
@@ -345,6 +370,18 @@
     bool MapAllSnapshots(const std::chrono::milliseconds& timeout_ms = {}) override;
     bool UnmapAllSnapshots() override;
 
+    // We can't use WaitForFile during first-stage init, because ueventd is not
+    // running and therefore will not automatically create symlinks. Instead,
+    // we let init provide us with the correct function to use to ensure
+    // uevents have been processed and symlink/mknod calls completed.
+    void SetUeventRegenCallback(std::function<bool(const std::string&)> callback) {
+        uevent_regen_callback_ = callback;
+    }
+
+    // If true, compression is enabled for this update. This is used by
+    // first-stage to decide whether to launch snapuserd.
+    bool IsSnapuserdRequired();
+
   private:
     FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
     FRIEND_TEST(SnapshotTest, CreateSnapshot);
@@ -355,15 +392,18 @@
     FRIEND_TEST(SnapshotTest, MapPartialSnapshot);
     FRIEND_TEST(SnapshotTest, MapSnapshot);
     FRIEND_TEST(SnapshotTest, Merge);
+    FRIEND_TEST(SnapshotTest, MergeFailureCode);
     FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
     FRIEND_TEST(SnapshotTest, UpdateBootControlHal);
     FRIEND_TEST(SnapshotUpdateTest, DaemonTransition);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeAfterRollback);
     FRIEND_TEST(SnapshotUpdateTest, DataWipeRollbackInRecovery);
+    FRIEND_TEST(SnapshotUpdateTest, DataWipeWithStaleSnapshots);
     FRIEND_TEST(SnapshotUpdateTest, FullUpdateFlow);
     FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow);
     FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery);
     FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
+    FRIEND_TEST(SnapshotUpdateTest, SpaceSwapUpdate);
     friend class SnapshotTest;
     friend class SnapshotUpdateTest;
     friend class FlashAfterUpdateTest;
@@ -386,7 +426,6 @@
     bool EnsureSnapuserdConnected();
 
     // Helpers for first-stage init.
-    bool ForceLocalImageManager();
     const std::unique_ptr<IDeviceInfo>& device() const { return device_; }
 
     // Helper functions for tests.
@@ -426,7 +465,7 @@
     //
     // All sizes are specified in bytes, and the device, snapshot, COW partition and COW file sizes
     // must be a multiple of the sector size (512 bytes).
-    bool CreateSnapshot(LockedFile* lock, SnapshotStatus* status);
+    bool CreateSnapshot(LockedFile* lock, PartitionCowCreator* cow_creator, SnapshotStatus* status);
 
     // |name| should be the base partition name (e.g. "system_a"). Create the
     // backing COW image using the size previously passed to CreateSnapshot().
@@ -448,6 +487,10 @@
                       const std::string& base_device, const std::chrono::milliseconds& timeout_ms,
                       std::string* path);
 
+    // Map the source device used for dm-user.
+    bool MapSourceDevice(LockedFile* lock, const std::string& name,
+                         const std::chrono::milliseconds& timeout_ms, std::string* path);
+
     // Map a COW image that was previous created with CreateCowImage.
     std::optional<std::string> MapCowImage(const std::string& name,
                                            const std::chrono::milliseconds& timeout_ms);
@@ -462,11 +505,15 @@
     // Unmap a COW image device previously mapped with MapCowImage().
     bool UnmapCowImage(const std::string& name);
 
+    // Unmap a COW and remove it from a MetadataBuilder.
+    void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata);
+
     // Unmap and remove all known snapshots.
     bool RemoveAllSnapshots(LockedFile* lock);
 
     // List the known snapshot names.
-    bool ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots);
+    bool ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
+                       const std::string& suffix = "");
 
     // Check for a cancelled or rolled back merge, returning true if such a
     // condition was detected and handled.
@@ -497,7 +544,8 @@
     // Interact with /metadata/ota/state.
     UpdateState ReadUpdateState(LockedFile* file);
     SnapshotUpdateStatus ReadSnapshotUpdateStatus(LockedFile* file);
-    bool WriteUpdateState(LockedFile* file, UpdateState state);
+    bool WriteUpdateState(LockedFile* file, UpdateState state,
+                          MergeFailureCode failure_code = MergeFailureCode::Ok);
     bool WriteSnapshotUpdateStatus(LockedFile* file, const SnapshotUpdateStatus& status);
     std::string GetStateFilePath() const;
 
@@ -506,11 +554,13 @@
     std::string GetMergeStateFilePath() const;
 
     // Helpers for merging.
-    bool SwitchSnapshotToMerge(LockedFile* lock, const std::string& name);
-    bool RewriteSnapshotDeviceTable(const std::string& dm_name);
+    MergeFailureCode MergeSecondPhaseSnapshots(LockedFile* lock);
+    MergeFailureCode SwitchSnapshotToMerge(LockedFile* lock, const std::string& name);
+    MergeFailureCode RewriteSnapshotDeviceTable(const std::string& dm_name);
     bool MarkSnapshotMergeCompleted(LockedFile* snapshot_lock, const std::string& snapshot_name);
     void AcknowledgeMergeSuccess(LockedFile* lock);
-    void AcknowledgeMergeFailure();
+    void AcknowledgeMergeFailure(MergeFailureCode failure_code);
+    MergePhase DecideMergePhase(const SnapshotStatus& status);
     std::unique_ptr<LpMetadata> ReadCurrentMetadata();
 
     enum class MetadataPartitionState {
@@ -536,14 +586,23 @@
                                  const SnapshotStatus& status);
     bool CollapseSnapshotDevice(const std::string& name, const SnapshotStatus& status);
 
+    struct MergeResult {
+        explicit MergeResult(UpdateState state,
+                             MergeFailureCode failure_code = MergeFailureCode::Ok)
+            : state(state), failure_code(failure_code) {}
+        UpdateState state;
+        MergeFailureCode failure_code;
+    };
+
     // Only the following UpdateStates are used here:
     //   UpdateState::Merging
     //   UpdateState::MergeCompleted
     //   UpdateState::MergeFailed
     //   UpdateState::MergeNeedsReboot
-    UpdateState CheckMergeState(const std::function<bool()>& before_cancel);
-    UpdateState CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel);
-    UpdateState CheckTargetMergeState(LockedFile* lock, const std::string& name);
+    MergeResult CheckMergeState(const std::function<bool()>& before_cancel);
+    MergeResult CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel);
+    MergeResult CheckTargetMergeState(LockedFile* lock, const std::string& name,
+                                      const SnapshotUpdateStatus& update_status);
 
     // Interact with status files under /metadata/ota/snapshots.
     bool WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status);
@@ -553,12 +612,9 @@
     std::string GetSnapshotBootIndicatorPath();
     std::string GetRollbackIndicatorPath();
     std::string GetForwardMergeIndicatorPath();
+    std::string GetOldPartitionMetadataPath();
 
-    // Return the name of the device holding the "snapshot" or "snapshot-merge"
-    // target. This may not be the final device presented via MapSnapshot(), if
-    // for example there is a linear segment.
-    std::string GetSnapshotDeviceName(const std::string& snapshot_name,
-                                      const SnapshotStatus& status);
+    const LpMetadata* ReadOldPartitionMetadata(LockedFile* lock);
 
     bool MapAllPartitions(LockedFile* lock, const std::string& super_device, uint32_t slot,
                           const std::chrono::milliseconds& timeout_ms);
@@ -615,6 +671,9 @@
     // The reverse of MapPartitionWithSnapshot.
     bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name);
 
+    // Unmap a dm-user device through snapuserd.
+    bool UnmapDmUserDevice(const std::string& snapshot_name);
+
     // If there isn't a previous update, return true. |needs_merge| is set to false.
     // If there is a previous update but the device has not boot into it, tries to cancel the
     //   update and delete any snapshots. Return true if successful. |needs_merge| is set to false.
@@ -651,12 +710,15 @@
     friend std::ostream& operator<<(std::ostream& os, SnapshotManager::Slot slot);
     Slot GetCurrentSlot();
 
+    // Return the suffix we expect snapshots to have.
+    std::string GetSnapshotSlotSuffix();
+
     std::string ReadUpdateSourceSlotSuffix();
 
     // Helper for RemoveAllSnapshots.
     // Check whether |name| should be deleted as a snapshot name.
-    bool ShouldDeleteSnapshot(LockedFile* lock, const std::map<std::string, bool>& flashing_status,
-                              Slot current_slot, const std::string& name);
+    bool ShouldDeleteSnapshot(const std::map<std::string, bool>& flashing_status, Slot current_slot,
+                              const std::string& name);
 
     // Create or delete forward merge indicator given |wipe|. Iff wipe is scheduled,
     // allow forward merge on FDR.
@@ -666,8 +728,8 @@
     // Call ProcessUpdateState and handle states with special rules before data wipe. Specifically,
     // if |allow_forward_merge| and allow-forward-merge indicator exists, initiate merge if
     // necessary.
-    bool ProcessUpdateStateOnDataWipe(bool allow_forward_merge,
-                                      const std::function<bool()>& callback);
+    UpdateState ProcessUpdateStateOnDataWipe(bool allow_forward_merge,
+                                             const std::function<bool()>& callback);
 
     // Return device string of a mapped image, or if it is not available, the mapped image path.
     bool GetMappedImageDeviceStringOrPath(const std::string& device_name,
@@ -676,14 +738,43 @@
     // Same as above, but for paths only (no major:minor device strings).
     bool GetMappedImageDevicePath(const std::string& device_name, std::string* device_path);
 
+    // Wait for a device to be created by ueventd (eg, its symlink or node to be populated).
+    // This is needed for any code that uses device-mapper path in first-stage init. If
+    // |timeout_ms| is empty or the given device is not a path, WaitForDevice immediately
+    // returns true.
+    bool WaitForDevice(const std::string& device, std::chrono::milliseconds timeout_ms);
+
+    enum class InitTransition { SELINUX_DETACH, SECOND_STAGE };
+
+    // Initiate the transition from first-stage to second-stage snapuserd. This
+    // process involves re-creating the dm-user table entries for each device,
+    // so that they connect to the new daemon. Once all new tables have been
+    // activated, we ask the first-stage daemon to cleanly exit.
+    //
+    // If the mode is SELINUX_DETACH, snapuserd_argv must be non-null and will
+    // be populated with a list of snapuserd arguments to pass to execve(). It
+    // is otherwise ignored.
+    bool PerformInitTransition(InitTransition transition,
+                               std::vector<std::string>* snapuserd_argv = nullptr);
+
+    SnapuserdClient* snapuserd_client() const { return snapuserd_client_.get(); }
+
+    // Helper of UpdateUsesCompression
+    bool UpdateUsesCompression(LockedFile* lock);
+
+    // Wrapper around libdm, with diagnostics.
+    bool DeleteDeviceIfExists(const std::string& name,
+                              const std::chrono::milliseconds& timeout_ms = {});
+
     std::string gsid_dir_;
     std::string metadata_dir_;
     std::unique_ptr<IDeviceInfo> device_;
     std::unique_ptr<IImageManager> images_;
-    bool has_local_image_manager_ = false;
     bool use_first_stage_snapuserd_ = false;
     bool in_factory_data_reset_ = false;
+    std::function<bool(const std::string&)> uevent_regen_callback_;
     std::unique_ptr<SnapuserdClient> snapuserd_client_;
+    std::unique_ptr<LpMetadata> old_partition_metadata_;
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
index d691d4f..4ce5077 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
@@ -28,9 +28,19 @@
     virtual ~ISnapshotMergeStats() = default;
     // Called when merge starts or resumes.
     virtual bool Start() = 0;
-    virtual void set_state(android::snapshot::UpdateState state) = 0;
+    virtual void set_state(android::snapshot::UpdateState state, bool using_compression) = 0;
     virtual void set_cow_file_size(uint64_t cow_file_size) = 0;
+    virtual void set_total_cow_size_bytes(uint64_t bytes) = 0;
+    virtual void set_estimated_cow_size_bytes(uint64_t bytes) = 0;
+    virtual void set_boot_complete_time_ms(uint32_t ms) = 0;
+    virtual void set_boot_complete_to_merge_start_time_ms(uint32_t ms) = 0;
+    virtual void set_merge_failure_code(MergeFailureCode code) = 0;
     virtual uint64_t cow_file_size() = 0;
+    virtual uint64_t total_cow_size_bytes() = 0;
+    virtual uint64_t estimated_cow_size_bytes() = 0;
+    virtual uint32_t boot_complete_time_ms() = 0;
+    virtual uint32_t boot_complete_to_merge_start_time_ms() = 0;
+    virtual MergeFailureCode merge_failure_code() = 0;
 
     // Called when merge ends. Properly clean up permanent storage.
     class Result {
@@ -51,9 +61,19 @@
 
     // ISnapshotMergeStats overrides
     bool Start() override;
-    void set_state(android::snapshot::UpdateState state) override;
+    void set_state(android::snapshot::UpdateState state, bool using_compression) override;
     void set_cow_file_size(uint64_t cow_file_size) override;
     uint64_t cow_file_size() override;
+    void set_total_cow_size_bytes(uint64_t bytes) override;
+    void set_estimated_cow_size_bytes(uint64_t bytes) override;
+    uint64_t total_cow_size_bytes() override;
+    uint64_t estimated_cow_size_bytes() override;
+    void set_boot_complete_time_ms(uint32_t ms) override;
+    uint32_t boot_complete_time_ms() override;
+    void set_boot_complete_to_merge_start_time_ms(uint32_t ms) override;
+    uint32_t boot_complete_to_merge_start_time_ms() override;
+    void set_merge_failure_code(MergeFailureCode code) override;
+    MergeFailureCode merge_failure_code() override;
     std::unique_ptr<Result> Finish() override;
 
   private:
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
index cba3560..a7cd939 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -28,10 +28,13 @@
     bool BeginUpdate() override;
     bool CancelUpdate() override;
     bool FinishedSnapshotWrites(bool wipe) override;
-    bool InitiateMerge(uint64_t* cow_file_size = nullptr) override;
+    void UpdateCowStats(ISnapshotMergeStats* stats) override;
+    MergeFailureCode ReadMergeFailureCode() override;
+    bool InitiateMerge() override;
     UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
                                    const std::function<bool()>& before_cancel = {}) override;
     UpdateState GetUpdateState(double* progress = nullptr) override;
+    bool UpdateUsesCompression() override;
     Return CreateUpdateSnapshots(
             const chromeos_update_engine::DeltaArchiveManifest& manifest) override;
     bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
deleted file mode 100644
index 24b44fa..0000000
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd.h
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#include <linux/types.h>
-#include <stdint.h>
-#include <stdlib.h>
-
-#include <csignal>
-#include <cstring>
-#include <iostream>
-#include <limits>
-#include <string>
-#include <thread>
-#include <unordered_map>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/stringprintf.h>
-#include <android-base/unique_fd.h>
-#include <libdm/dm.h>
-#include <libsnapshot/cow_reader.h>
-#include <libsnapshot/cow_writer.h>
-#include <libsnapshot/snapuserd_kernel.h>
-
-namespace android {
-namespace snapshot {
-
-using android::base::unique_fd;
-
-class BufferSink : public IByteSink {
-  public:
-    void Initialize(size_t size);
-    void* GetBufPtr() { return buffer_.get(); }
-    void Clear() { memset(GetBufPtr(), 0, buffer_size_); }
-    void* GetPayloadBuffer(size_t size);
-    void* GetBuffer(size_t requested, size_t* actual) override;
-    void UpdateBufferOffset(size_t size) { buffer_offset_ += size; }
-    struct dm_user_header* GetHeaderPtr();
-    bool ReturnData(void*, size_t) override { return true; }
-    void ResetBufferOffset() { buffer_offset_ = 0; }
-
-  private:
-    std::unique_ptr<uint8_t[]> buffer_;
-    loff_t buffer_offset_;
-    size_t buffer_size_;
-};
-
-class Snapuserd final {
-  public:
-    Snapuserd(const std::string& misc_name, const std::string& cow_device,
-              const std::string& backing_device);
-    bool InitBackingAndControlDevice();
-    bool InitCowDevice();
-    bool Run();
-    const std::string& GetControlDevicePath() { return control_device_; }
-    const std::string& GetMiscName() { return misc_name_; }
-    uint64_t GetNumSectors() { return num_sectors_; }
-    bool IsAttached() const { return ctrl_fd_ >= 0; }
-
-  private:
-    bool ReadDmUserHeader();
-    bool ReadDmUserPayload(void* buffer, size_t size);
-    bool WriteDmUserPayload(size_t size);
-    void ConstructKernelCowHeader();
-    bool ReadMetadata();
-    bool ZerofillDiskExceptions(size_t read_size);
-    bool ReadDiskExceptions(chunk_t chunk, size_t size);
-    bool ReadData(chunk_t chunk, size_t size);
-    bool IsChunkIdMetadata(chunk_t chunk);
-    chunk_t GetNextAllocatableChunkId(chunk_t chunk);
-
-    bool ProcessReplaceOp(const CowOperation* cow_op);
-    bool ProcessCopyOp(const CowOperation* cow_op);
-    bool ProcessZeroOp();
-
-    loff_t GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
-                               int* unmerged_exceptions);
-    int GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
-                             int unmerged_exceptions);
-    bool AdvanceMergedOps(int merged_ops_cur_iter);
-    bool ProcessMergeComplete(chunk_t chunk, void* buffer);
-    sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
-    chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
-
-    std::string cow_device_;
-    std::string backing_store_device_;
-    std::string control_device_;
-    std::string misc_name_;
-
-    unique_fd cow_fd_;
-    unique_fd backing_store_fd_;
-    unique_fd ctrl_fd_;
-
-    uint32_t exceptions_per_area_;
-    uint64_t num_sectors_;
-
-    std::unique_ptr<ICowOpIter> cowop_iter_;
-    std::unique_ptr<ICowOpReverseIter> cowop_riter_;
-    std::unique_ptr<CowReader> reader_;
-    std::unique_ptr<CowWriter> writer_;
-
-    // Vector of disk exception which is a
-    // mapping of old-chunk to new-chunk
-    std::vector<std::unique_ptr<uint8_t[]>> vec_;
-
-    // Key - Chunk ID
-    // Value - cow operation
-    std::unordered_map<chunk_t, const CowOperation*> chunk_map_;
-
-    bool metadata_read_done_ = false;
-    BufferSink bufsink_;
-};
-
-}  // namespace snapshot
-}  // namespace android
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
index 0e9ba9e..280e857 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_client.h
@@ -30,18 +30,11 @@
 
 static constexpr uint32_t PACKET_SIZE = 512;
 
-static constexpr char kSnapuserdSocketFirstStage[] = "snapuserd_first_stage";
 static constexpr char kSnapuserdSocket[] = "snapuserd";
 
-static constexpr char kSnapuserdFirstStagePidVar[] = "FIRST_STAGE_SNAPUSERD_PID";
-
 // Ensure that the second-stage daemon for snapuserd is running.
 bool EnsureSnapuserdStarted();
 
-// Start the first-stage version of snapuserd, returning its pid. This is used
-// by first-stage init, as well as vts_libsnapshot_test. On failure, -1 is returned.
-pid_t StartFirstStageSnapuserd();
-
 class SnapuserdClient {
   private:
     android::base::unique_fd sockfd_;
@@ -75,6 +68,13 @@
     // Wait for snapuserd to disassociate with a dm-user control device. This
     // must ONLY be called if the control device has already been deleted.
     bool WaitForDeviceDelete(const std::string& control_device);
+
+    void CloseConnection() { sockfd_ = {}; }
+
+    // Detach snapuserd. This shuts down the listener socket, and will cause
+    // snapuserd to gracefully exit once all handler threads have terminated.
+    // This should only be used on first-stage instances of snapuserd.
+    bool DetachSnapuserd();
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
index e8dbe6e..6bb7a39 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_kernel.h
@@ -47,8 +47,7 @@
 static constexpr uint32_t CHUNK_SIZE = 8;
 static constexpr uint32_t CHUNK_SHIFT = (__builtin_ffs(CHUNK_SIZE) - 1);
 
-static constexpr uint32_t BLOCK_SIZE = 4096;
-static constexpr uint32_t BLOCK_SHIFT = (__builtin_ffs(BLOCK_SIZE) - 1);
+#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d))
 
 // This structure represents the kernel COW header.
 // All the below fields should be in Little Endian format.
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index 1d1510a..4e7ccf1 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -51,8 +51,8 @@
 extern std::unique_ptr<SnapshotManager> sm;
 extern class TestDeviceInfo* test_device;
 extern std::string fake_super;
-static constexpr uint64_t kSuperSize = 16_MiB + 4_KiB;
-static constexpr uint64_t kGroupSize = 16_MiB;
+static constexpr uint64_t kSuperSize = 32_MiB + 4_KiB;
+static constexpr uint64_t kGroupSize = 32_MiB;
 
 // Redirect requests for "super" to our fake super partition.
 class TestPartitionOpener final : public android::fs_mgr::PartitionOpener {
@@ -77,7 +77,6 @@
         : TestDeviceInfo(fake_super) {
         set_slot_suffix(slot_suffix);
     }
-    std::string GetGsidDir() const override { return "ota/test"s; }
     std::string GetMetadataDir() const override { return "/metadata/ota/test"s; }
     std::string GetSlotSuffix() const override { return slot_suffix_; }
     std::string GetOtherSlotSuffix() const override { return slot_suffix_ == "_a" ? "_b" : "_a"; }
@@ -96,7 +95,10 @@
         return true;
     }
     bool IsTestDevice() const override { return true; }
-    std::string GetSnapuserdFirstStagePidVar() const override { return {}; }
+    bool IsFirstStageInit() const override { return first_stage_init_; }
+    std::unique_ptr<IImageManager> OpenImageManager() const override {
+        return IDeviceInfo::OpenImageManager("ota/test");
+    }
 
     bool IsSlotUnbootable(uint32_t slot) { return unbootable_slots_.count(slot) != 0; }
 
@@ -105,6 +107,7 @@
         opener_ = std::make_unique<TestPartitionOpener>(path);
     }
     void set_recovery(bool value) { recovery_ = value; }
+    void set_first_stage_init(bool value) { first_stage_init_ = value; }
     MergeStatus merge_status() const { return merge_status_; }
 
   private:
@@ -112,6 +115,7 @@
     std::unique_ptr<TestPartitionOpener> opener_;
     MergeStatus merge_status_;
     bool recovery_ = false;
+    bool first_stage_init_ = false;
     std::unordered_set<uint32_t> unbootable_slots_;
 };
 
@@ -147,6 +151,7 @@
 bool WriteRandomData(const std::string& path, std::optional<size_t> expect_size = std::nullopt,
                      std::string* hash = nullptr);
 bool WriteRandomData(ICowWriter* writer, std::string* hash = nullptr);
+std::string HashSnapshot(ISnapshotWriter* writer);
 
 std::optional<std::string> GetHash(const std::string& path);
 
diff --git a/fs_mgr/libsnapshot/inspect_cow.cpp b/fs_mgr/libsnapshot/inspect_cow.cpp
index 6046bad..4a84fba 100644
--- a/fs_mgr/libsnapshot/inspect_cow.cpp
+++ b/fs_mgr/libsnapshot/inspect_cow.cpp
@@ -14,6 +14,7 @@
 // limitations under the License.
 //
 #include <stdio.h>
+#include <unistd.h>
 
 #include <iostream>
 #include <string>
@@ -34,7 +35,50 @@
     }
 }
 
-static bool Inspect(const std::string& path) {
+static void usage(void) {
+    LOG(ERROR) << "Usage: inspect_cow [-sd] <COW_FILE>";
+    LOG(ERROR) << "\t -s Run Silent";
+    LOG(ERROR) << "\t -d Attempt to decompress";
+    LOG(ERROR) << "\t -b Show data for failed decompress\n";
+}
+
+// Sink that always appends to the end of a string.
+class StringSink : public IByteSink {
+  public:
+    void* GetBuffer(size_t requested, size_t* actual) override {
+        size_t old_size = stream_.size();
+        stream_.resize(old_size + requested, '\0');
+        *actual = requested;
+        return stream_.data() + old_size;
+    }
+    bool ReturnData(void*, size_t) override { return true; }
+    void Reset() { stream_.clear(); }
+
+    std::string& stream() { return stream_; }
+
+  private:
+    std::string stream_;
+};
+
+static void ShowBad(CowReader& reader, const struct CowOperation& op) {
+    size_t count;
+    auto buffer = std::make_unique<uint8_t[]>(op.data_length);
+
+    if (!reader.GetRawBytes(op.source, buffer.get(), op.data_length, &count)) {
+        std::cerr << "Failed to read at all!\n";
+    } else {
+        std::cout << "The Block data is:\n";
+        for (int i = 0; i < op.data_length; i++) {
+            std::cout << std::hex << (int)buffer[i];
+        }
+        std::cout << std::dec << "\n\n";
+        if (op.data_length >= sizeof(CowOperation)) {
+            std::cout << "The start, as an op, would be " << *(CowOperation*)buffer.get() << "\n";
+        }
+    }
+}
+
+static bool Inspect(const std::string& path, bool silent, bool decompress, bool show_bad) {
     android::base::unique_fd fd(open(path.c_str(), O_RDONLY));
     if (fd < 0) {
         PLOG(ERROR) << "open failed: " << path;
@@ -52,38 +96,78 @@
         LOG(ERROR) << "could not get header: " << path;
         return false;
     }
+    CowFooter footer;
+    bool has_footer = false;
+    if (reader.GetFooter(&footer)) has_footer = true;
 
-    std::cout << "Major version: " << header.major_version << "\n";
-    std::cout << "Minor version: " << header.minor_version << "\n";
-    std::cout << "Header size: " << header.header_size << "\n";
-    std::cout << "Footer size: " << header.footer_size << "\n";
-    std::cout << "Block size: " << header.block_size << "\n";
-    std::cout << "\n";
+    if (!silent) {
+        std::cout << "Major version: " << header.major_version << "\n";
+        std::cout << "Minor version: " << header.minor_version << "\n";
+        std::cout << "Header size: " << header.header_size << "\n";
+        std::cout << "Footer size: " << header.footer_size << "\n";
+        std::cout << "Block size: " << header.block_size << "\n";
+        std::cout << "\n";
+        if (has_footer) {
+            std::cout << "Total Ops size: " << footer.op.ops_size << "\n";
+            std::cout << "Number of Ops: " << footer.op.num_ops << "\n";
+            std::cout << "\n";
+        }
+    }
 
     auto iter = reader.GetOpIter();
+    StringSink sink;
+    bool success = true;
     while (!iter->Done()) {
         const CowOperation& op = iter->Get();
 
-        std::cout << op << "\n";
+        if (!silent) std::cout << op << "\n";
+
+        if (decompress && op.type == kCowReplaceOp && op.compression != kCowCompressNone) {
+            if (!reader.ReadData(op, &sink)) {
+                std::cerr << "Failed to decompress for :" << op << "\n";
+                success = false;
+                if (show_bad) ShowBad(reader, op);
+            }
+            sink.Reset();
+        }
 
         iter->Next();
     }
 
-    return true;
+    return success;
 }
 
 }  // namespace snapshot
 }  // namespace android
 
 int main(int argc, char** argv) {
+    int ch;
+    bool silent = false;
+    bool decompress = false;
+    bool show_bad = false;
+    while ((ch = getopt(argc, argv, "sdb")) != -1) {
+        switch (ch) {
+            case 's':
+                silent = true;
+                break;
+            case 'd':
+                decompress = true;
+                break;
+            case 'b':
+                show_bad = true;
+                break;
+            default:
+                android::snapshot::usage();
+        }
+    }
     android::base::InitLogging(argv, android::snapshot::MyLogger);
 
-    if (argc < 2) {
-        LOG(ERROR) << "Usage: inspect_cow <COW_FILE>";
+    if (argc < optind + 1) {
+        android::snapshot::usage();
         return 1;
     }
 
-    if (!android::snapshot::Inspect(argv[1])) {
+    if (!android::snapshot::Inspect(argv[optind], silent, decompress, show_bad)) {
         return 1;
     }
     return 0;
diff --git a/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp b/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp
index d0b8f52..6a5754d 100644
--- a/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp
+++ b/fs_mgr/libsnapshot/make_cow_from_ab_ota.cpp
@@ -53,6 +53,7 @@
 
 DEFINE_string(source_tf, "", "Source target files (dir or zip file) for incremental payloads");
 DEFINE_string(compression, "gz", "Compression type to use (none or gz)");
+DEFINE_uint32(cluster_ops, 0, "Number of Cow Ops per cluster (0 or >1)");
 
 void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
               unsigned int, const char* message) {
@@ -189,6 +190,7 @@
     CowOptions options;
     options.block_size = kBlockSize;
     options.compression = FLAGS_compression;
+    options.cluster_ops = FLAGS_cluster_ops;
 
     writer_ = std::make_unique<CowWriter>(options);
     if (!writer_->Initialize(std::move(fd))) {
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.cpp b/fs_mgr/libsnapshot/partition_cow_creator.cpp
index da6fc9d..5569da0 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator.cpp
@@ -142,11 +142,11 @@
     }
 }
 
-uint64_t PartitionCowCreator::GetCowSize() {
+std::optional<uint64_t> PartitionCowCreator::GetCowSize() {
     if (compression_enabled) {
         if (update == nullptr || !update->has_estimate_cow_size()) {
             LOG(ERROR) << "Update manifest does not include a COW size";
-            return 0;
+            return std::nullopt;
         }
 
         // Add an extra 2MB of wiggle room for any minor differences in labels/metadata
@@ -202,6 +202,10 @@
     ret.snapshot_status.set_device_size(target_partition->size());
     ret.snapshot_status.set_snapshot_size(target_partition->size());
 
+    if (update && update->has_estimate_cow_size()) {
+        ret.snapshot_status.set_estimated_cow_size(update->estimate_cow_size());
+    }
+
     if (ret.snapshot_status.snapshot_size() == 0) {
         LOG(INFO) << "Not creating snapshot for partition " << ret.snapshot_status.name();
         ret.snapshot_status.set_cow_partition_size(0);
@@ -239,7 +243,7 @@
     }
 
     // Compute the COW partition size.
-    uint64_t cow_partition_size = std::min(cow_size, free_region_length);
+    uint64_t cow_partition_size = std::min(cow_size.value(), free_region_length);
     // Round it down to the nearest logical block. Logical partitions must be a multiple
     // of logical blocks.
     cow_partition_size &= ~(logical_block_size - 1);
@@ -247,7 +251,7 @@
     // Assign cow_partition_usable_regions to indicate what regions should the COW partition uses.
     ret.cow_partition_usable_regions = std::move(free_regions);
 
-    auto cow_file_size = cow_size - cow_partition_size;
+    auto cow_file_size = cow_size.value() - cow_partition_size;
     // Round it up to the nearest sector.
     cow_file_size += kSectorSize - 1;
     cow_file_size &= ~(kSectorSize - 1);
diff --git a/fs_mgr/libsnapshot/partition_cow_creator.h b/fs_mgr/libsnapshot/partition_cow_creator.h
index 64d186b..34b39ca 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator.h
+++ b/fs_mgr/libsnapshot/partition_cow_creator.h
@@ -58,6 +58,7 @@
     std::vector<ChromeOSExtent> extra_extents = {};
     // True if compression is enabled.
     bool compression_enabled = false;
+    std::string compression_algorithm;
 
     struct Return {
         SnapshotStatus snapshot_status;
@@ -68,7 +69,7 @@
 
   private:
     bool HasExtent(Partition* p, Extent* e);
-    uint64_t GetCowSize();
+    std::optional<uint64_t> GetCowSize();
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
index e4b476f..de35c13 100644
--- a/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
+++ b/fs_mgr/libsnapshot/partition_cow_creator_test.cpp
@@ -308,6 +308,10 @@
         cc.WriteByte(b);
         ASSERT_EQ(cc.cow_size_sectors(), 40);
     }
+
+    // Write a byte that would surely overflow the counter
+    cc.WriteChunk(std::numeric_limits<uint64_t>::max());
+    ASSERT_FALSE(cc.cow_size_sectors().has_value());
 }
 
 void BlocksToExtents(const std::vector<uint64_t>& blocks,
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 6595707..e2c03ae 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -21,6 +21,7 @@
 #include <sys/types.h>
 #include <sys/unistd.h>
 
+#include <filesystem>
 #include <optional>
 #include <thread>
 #include <unordered_set>
@@ -98,10 +99,12 @@
 }
 
 std::unique_ptr<SnapshotManager> SnapshotManager::NewForFirstStageMount(IDeviceInfo* info) {
-    auto sm = New(info);
-    if (!sm || !sm->ForceLocalImageManager()) {
-        return nullptr;
+    if (!info) {
+        DeviceInfo* impl = new DeviceInfo();
+        impl->set_first_stage_init(true);
+        info = impl;
     }
+    auto sm = New(info);
 
     // The first-stage version of snapuserd is explicitly started by init. Do
     // not attempt to using it during tests (which run in normal AOSP).
@@ -112,7 +115,6 @@
 }
 
 SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) {
-    gsid_dir_ = device_->GetGsidDir();
     metadata_dir_ = device_->GetMetadataDir();
 }
 
@@ -132,8 +134,8 @@
     return partition_name + "-base";
 }
 
-static std::string GetSnapshotExtraDeviceName(const std::string& snapshot_name) {
-    return snapshot_name + "-inner";
+static std::string GetSourceDeviceName(const std::string& partition_name) {
+    return partition_name + "-src";
 }
 
 bool SnapshotManager::BeginUpdate() {
@@ -157,6 +159,9 @@
         images_->RemoveAllImages();
     }
 
+    // Clear any cached metadata (this allows re-using one manager across tests).
+    old_partition_metadata_ = nullptr;
+
     auto state = ReadUpdateState(file.get());
     if (state != UpdateState::None) {
         LOG(ERROR) << "An update is already in progress, cannot begin a new update";
@@ -223,6 +228,15 @@
     return Slot::Target;
 }
 
+std::string SnapshotManager::GetSnapshotSlotSuffix() {
+    switch (GetCurrentSlot()) {
+        case Slot::Target:
+            return device_->GetSlotSuffix();
+        default:
+            return device_->GetOtherSlotSuffix();
+    }
+}
+
 static bool RemoveFileIfExists(const std::string& path) {
     std::string message;
     if (!android::base::RemoveFileIfExists(path, &message)) {
@@ -255,6 +269,7 @@
             GetSnapshotBootIndicatorPath(),
             GetRollbackIndicatorPath(),
             GetForwardMergeIndicatorPath(),
+            GetOldPartitionMetadataPath(),
     };
     for (const auto& file : files) {
         RemoveFileIfExists(file);
@@ -280,7 +295,7 @@
         return false;
     }
 
-    if (!IsCompressionEnabled() && !EnsureNoOverflowSnapshot(lock.get())) {
+    if (!EnsureNoOverflowSnapshot(lock.get())) {
         LOG(ERROR) << "Cannot ensure there are no overflow snapshots.";
         return false;
     }
@@ -308,7 +323,8 @@
     return WriteUpdateState(lock.get(), UpdateState::Unverified);
 }
 
-bool SnapshotManager::CreateSnapshot(LockedFile* lock, SnapshotStatus* status) {
+bool SnapshotManager::CreateSnapshot(LockedFile* lock, PartitionCowCreator* cow_creator,
+                                     SnapshotStatus* status) {
     CHECK(lock);
     CHECK(lock->lock_mode() == LOCK_EX);
     CHECK(status);
@@ -349,6 +365,8 @@
     status->set_state(SnapshotState::CREATED);
     status->set_sectors_allocated(0);
     status->set_metadata_sectors(0);
+    status->set_compression_enabled(cow_creator->compression_enabled);
+    status->set_compression_algorithm(cow_creator->compression_algorithm);
 
     if (!WriteSnapshotStatus(lock, *status)) {
         PLOG(ERROR) << "Could not write snapshot status: " << status->name();
@@ -408,10 +426,12 @@
     if (!dm.CreateDevice(name, table, path, timeout_ms)) {
         return false;
     }
+    if (!WaitForDevice(*path, timeout_ms)) {
+        return false;
+    }
 
     auto control_device = "/dev/dm-user/" + misc_name;
-    if (!android::fs_mgr::WaitForFile(control_device, timeout_ms)) {
-        LOG(ERROR) << "Timed out waiting for dm-user misc device: " << control_device;
+    if (!WaitForDevice(control_device, timeout_ms)) {
         return false;
     }
 
@@ -462,8 +482,13 @@
         LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size();
         return false;
     }
+    if (status.device_size() != status.snapshot_size()) {
+        LOG(ERROR) << "Device size and snapshot size must be the same (device size = "
+                   << status.device_size() << ", snapshot size = " << status.snapshot_size();
+        return false;
+    }
+
     uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
-    uint64_t linear_sectors = (status.device_size() - status.snapshot_size()) / kSectorSize;
 
     auto& dm = DeviceMapper::Instance();
 
@@ -471,7 +496,8 @@
     // have completed merging, but the start of the merge process is considered
     // atomic.
     SnapshotStorageMode mode;
-    switch (ReadUpdateState(lock)) {
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+    switch (update_status.state()) {
         case UpdateState::MergeCompleted:
         case UpdateState::MergeNeedsReboot:
             LOG(ERROR) << "Should not create a snapshot device for " << name
@@ -481,52 +507,24 @@
         case UpdateState::MergeFailed:
             // Note: MergeFailed indicates that a merge is in progress, but
             // is possibly stalled. We still have to honor the merge.
-            mode = SnapshotStorageMode::Merge;
+            if (DecideMergePhase(status) == update_status.merge_phase()) {
+                mode = SnapshotStorageMode::Merge;
+            } else {
+                mode = SnapshotStorageMode::Persistent;
+            }
             break;
         default:
             mode = SnapshotStorageMode::Persistent;
             break;
     }
 
-    // The kernel (tested on 4.19) crashes horribly if a device has both a snapshot
-    // and a linear target in the same table. Instead, we stack them, and give the
-    // snapshot device a different name. It is not exposed to the caller in this
-    // case.
-    auto snap_name = (linear_sectors > 0) ? GetSnapshotExtraDeviceName(name) : name;
-
     DmTable table;
     table.Emplace<DmTargetSnapshot>(0, snapshot_sectors, base_device, cow_device, mode,
                                     kSnapshotChunkSize);
-    if (!dm.CreateDevice(snap_name, table, dev_path, timeout_ms)) {
-        LOG(ERROR) << "Could not create snapshot device: " << snap_name;
+    if (!dm.CreateDevice(name, table, dev_path, timeout_ms)) {
+        LOG(ERROR) << "Could not create snapshot device: " << name;
         return false;
     }
-
-    if (linear_sectors) {
-        std::string snap_dev;
-        if (!dm.GetDeviceString(snap_name, &snap_dev)) {
-            LOG(ERROR) << "Cannot determine major/minor for: " << snap_name;
-            return false;
-        }
-
-        // Our stacking will looks like this:
-        //     [linear, linear] ; to snapshot, and non-snapshot region of base device
-        //     [snapshot-inner]
-        //     [base device]   [cow]
-        DmTable table;
-        table.Emplace<DmTargetLinear>(0, snapshot_sectors, snap_dev, 0);
-        table.Emplace<DmTargetLinear>(snapshot_sectors, linear_sectors, base_device,
-                                      snapshot_sectors);
-        if (!dm.CreateDevice(name, table, dev_path, timeout_ms)) {
-            LOG(ERROR) << "Could not create outer snapshot device: " << name;
-            dm.DeleteDevice(snap_name);
-            return false;
-        }
-    }
-
-    // :TODO: when merging is implemented, we need to add an argument to the
-    // status indicating how much progress is left to merge. (device-mapper
-    // does not retain the initial values, so we can't derive them.)
     return true;
 }
 
@@ -537,9 +535,7 @@
 
     bool ok;
     std::string cow_dev;
-    if (has_local_image_manager_) {
-        // If we forced a local image manager, it means we don't have binder,
-        // which means first-stage init. We must use device-mapper.
+    if (device_->IsRecovery() || device_->IsFirstStageInit()) {
         const auto& opener = device_->GetPartitionOpener();
         ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, &cow_dev);
     } else {
@@ -554,21 +550,43 @@
     return std::nullopt;
 }
 
+bool SnapshotManager::MapSourceDevice(LockedFile* lock, const std::string& name,
+                                      const std::chrono::milliseconds& timeout_ms,
+                                      std::string* path) {
+    CHECK(lock);
+
+    auto metadata = ReadOldPartitionMetadata(lock);
+    if (!metadata) {
+        LOG(ERROR) << "Could not map source device due to missing or corrupt metadata";
+        return false;
+    }
+
+    auto old_name = GetOtherPartitionName(name);
+    auto slot_suffix = device_->GetSlotSuffix();
+    auto slot = SlotNumberForSlotSuffix(slot_suffix);
+
+    CreateLogicalPartitionParams params = {
+            .block_device = device_->GetSuperDevice(slot),
+            .metadata = metadata,
+            .partition_name = old_name,
+            .timeout_ms = timeout_ms,
+            .device_name = GetSourceDeviceName(name),
+            .partition_opener = &device_->GetPartitionOpener(),
+    };
+    if (!CreateLogicalPartition(std::move(params), path)) {
+        LOG(ERROR) << "Could not create source device for snapshot " << name;
+        return false;
+    }
+    return true;
+}
+
 bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
     CHECK(lock);
 
-    auto& dm = DeviceMapper::Instance();
-    if (!dm.DeleteDeviceIfExists(name)) {
+    if (!DeleteDeviceIfExists(name)) {
         LOG(ERROR) << "Could not delete snapshot device: " << name;
         return false;
     }
-
-    auto snapshot_extra_device = GetSnapshotExtraDeviceName(name);
-    if (!dm.DeleteDeviceIfExists(snapshot_extra_device)) {
-        LOG(ERROR) << "Could not delete snapshot inner device: " << snapshot_extra_device;
-        return false;
-    }
-
     return true;
 }
 
@@ -610,7 +628,7 @@
     return true;
 }
 
-bool SnapshotManager::InitiateMerge(uint64_t* cow_file_size) {
+bool SnapshotManager::InitiateMerge() {
     auto lock = LockExclusive();
     if (!lock) return false;
 
@@ -673,7 +691,10 @@
         }
     }
 
-    uint64_t total_cow_file_size = 0;
+    bool compression_enabled = false;
+
+    std::vector<std::string> first_merge_group;
+
     DmTargetSnapshot::Status initial_target_values = {};
     for (const auto& snapshot : snapshots) {
         DmTargetSnapshot::Status current_status;
@@ -688,11 +709,11 @@
         if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) {
             return false;
         }
-        total_cow_file_size += snapshot_status.cow_file_size();
-    }
 
-    if (cow_file_size) {
-        *cow_file_size = total_cow_file_size;
+        compression_enabled |= snapshot_status.compression_enabled();
+        if (DecideMergePhase(snapshot_status) == MergePhase::FIRST_PHASE) {
+            first_merge_group.emplace_back(snapshot);
+        }
     }
 
     SnapshotUpdateStatus initial_status;
@@ -700,39 +721,55 @@
     initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
     initial_status.set_total_sectors(initial_target_values.total_sectors);
     initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
+    initial_status.set_compression_enabled(compression_enabled);
+
+    // If any partitions shrunk, we need to merge them before we merge any other
+    // partitions (see b/177935716). Otherwise, a merge from another partition
+    // may overwrite the source block of a copy operation.
+    const std::vector<std::string>* merge_group;
+    if (first_merge_group.empty()) {
+        merge_group = &snapshots;
+        initial_status.set_merge_phase(MergePhase::SECOND_PHASE);
+    } else {
+        merge_group = &first_merge_group;
+        initial_status.set_merge_phase(MergePhase::FIRST_PHASE);
+    }
 
     // Point of no return - mark that we're starting a merge. From now on every
-    // snapshot must be a merge target.
+    // eligible snapshot must be a merge target.
     if (!WriteSnapshotUpdateStatus(lock.get(), initial_status)) {
         return false;
     }
 
-    bool rewrote_all = true;
-    for (const auto& snapshot : snapshots) {
+    auto reported_code = MergeFailureCode::Ok;
+    for (const auto& snapshot : *merge_group) {
         // If this fails, we have no choice but to continue. Everything must
         // be merged. This is not an ideal state to be in, but it is safe,
         // because we the next boot will try again.
-        if (!SwitchSnapshotToMerge(lock.get(), snapshot)) {
+        auto code = SwitchSnapshotToMerge(lock.get(), snapshot);
+        if (code != MergeFailureCode::Ok) {
             LOG(ERROR) << "Failed to switch snapshot to a merge target: " << snapshot;
-            rewrote_all = false;
+            if (reported_code == MergeFailureCode::Ok) {
+                reported_code = code;
+            }
         }
     }
 
     // If we couldn't switch everything to a merge target, pre-emptively mark
     // this merge as failed. It will get acknowledged when WaitForMerge() is
     // called.
-    if (!rewrote_all) {
-        WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+    if (reported_code != MergeFailureCode::Ok) {
+        WriteUpdateState(lock.get(), UpdateState::MergeFailed, reported_code);
     }
 
     // Return true no matter what, because a merge was initiated.
     return true;
 }
 
-bool SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) {
+MergeFailureCode SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) {
     SnapshotStatus status;
     if (!ReadSnapshotStatus(lock, name, &status)) {
-        return false;
+        return MergeFailureCode::ReadStatus;
     }
     if (status.state() != SnapshotState::CREATED) {
         LOG(WARNING) << "Snapshot " << name
@@ -741,53 +778,52 @@
 
     // After this, we return true because we technically did switch to a merge
     // target. Everything else we do here is just informational.
-    auto dm_name = GetSnapshotDeviceName(name, status);
-    if (!RewriteSnapshotDeviceTable(dm_name)) {
-        return false;
+    if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
+        return code;
     }
 
     status.set_state(SnapshotState::MERGING);
 
     DmTargetSnapshot::Status dm_status;
-    if (!QuerySnapshotStatus(dm_name, nullptr, &dm_status)) {
-        LOG(ERROR) << "Could not query merge status for snapshot: " << dm_name;
+    if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
+        LOG(ERROR) << "Could not query merge status for snapshot: " << name;
     }
     status.set_sectors_allocated(dm_status.sectors_allocated);
     status.set_metadata_sectors(dm_status.metadata_sectors);
     if (!WriteSnapshotStatus(lock, status)) {
         LOG(ERROR) << "Could not update status file for snapshot: " << name;
     }
-    return true;
+    return MergeFailureCode::Ok;
 }
 
-bool SnapshotManager::RewriteSnapshotDeviceTable(const std::string& dm_name) {
+MergeFailureCode SnapshotManager::RewriteSnapshotDeviceTable(const std::string& name) {
     auto& dm = DeviceMapper::Instance();
 
     std::vector<DeviceMapper::TargetInfo> old_targets;
-    if (!dm.GetTableInfo(dm_name, &old_targets)) {
-        LOG(ERROR) << "Could not read snapshot device table: " << dm_name;
-        return false;
+    if (!dm.GetTableInfo(name, &old_targets)) {
+        LOG(ERROR) << "Could not read snapshot device table: " << name;
+        return MergeFailureCode::GetTableInfo;
     }
     if (old_targets.size() != 1 || DeviceMapper::GetTargetType(old_targets[0].spec) != "snapshot") {
-        LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << dm_name;
-        return false;
+        LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << name;
+        return MergeFailureCode::UnknownTable;
     }
 
     std::string base_device, cow_device;
     if (!DmTargetSnapshot::GetDevicesFromParams(old_targets[0].data, &base_device, &cow_device)) {
-        LOG(ERROR) << "Could not derive underlying devices for snapshot: " << dm_name;
-        return false;
+        LOG(ERROR) << "Could not derive underlying devices for snapshot: " << name;
+        return MergeFailureCode::GetTableParams;
     }
 
     DmTable table;
     table.Emplace<DmTargetSnapshot>(0, old_targets[0].spec.length, base_device, cow_device,
                                     SnapshotStorageMode::Merge, kSnapshotChunkSize);
-    if (!dm.LoadTableAndActivate(dm_name, table)) {
-        LOG(ERROR) << "Could not swap device-mapper tables on snapshot device " << dm_name;
-        return false;
+    if (!dm.LoadTableAndActivate(name, table)) {
+        LOG(ERROR) << "Could not swap device-mapper tables on snapshot device " << name;
+        return MergeFailureCode::ActivateNewTable;
     }
-    LOG(INFO) << "Successfully switched snapshot device to a merge target: " << dm_name;
-    return true;
+    LOG(INFO) << "Successfully switched snapshot device to a merge target: " << name;
+    return MergeFailureCode::Ok;
 }
 
 enum class TableQuery {
@@ -859,18 +895,20 @@
 UpdateState SnapshotManager::ProcessUpdateState(const std::function<bool()>& callback,
                                                 const std::function<bool()>& before_cancel) {
     while (true) {
-        UpdateState state = CheckMergeState(before_cancel);
-        if (state == UpdateState::MergeFailed) {
-            AcknowledgeMergeFailure();
+        auto result = CheckMergeState(before_cancel);
+        LOG(INFO) << "ProcessUpdateState handling state: " << result.state;
+
+        if (result.state == UpdateState::MergeFailed) {
+            AcknowledgeMergeFailure(result.failure_code);
         }
-        if (state != UpdateState::Merging) {
+        if (result.state != UpdateState::Merging) {
             // Either there is no merge, or the merge was finished, so no need
             // to keep waiting.
-            return state;
+            return result.state;
         }
 
         if (callback && !callback()) {
-            return state;
+            return result.state;
         }
 
         // This wait is not super time sensitive, so we have a relatively
@@ -879,34 +917,36 @@
     }
 }
 
-UpdateState SnapshotManager::CheckMergeState(const std::function<bool()>& before_cancel) {
+auto SnapshotManager::CheckMergeState(const std::function<bool()>& before_cancel) -> MergeResult {
     auto lock = LockExclusive();
     if (!lock) {
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::AcquireLock);
     }
 
-    UpdateState state = CheckMergeState(lock.get(), before_cancel);
-    if (state == UpdateState::MergeCompleted) {
+    auto result = CheckMergeState(lock.get(), before_cancel);
+    LOG(INFO) << "CheckMergeState for snapshots returned: " << result.state;
+
+    if (result.state == UpdateState::MergeCompleted) {
         // Do this inside the same lock. Failures get acknowledged without the
         // lock, because flock() might have failed.
         AcknowledgeMergeSuccess(lock.get());
-    } else if (state == UpdateState::Cancelled) {
-        if (!RemoveAllUpdateState(lock.get(), before_cancel)) {
-            return ReadSnapshotUpdateStatus(lock.get()).state();
+    } else if (result.state == UpdateState::Cancelled) {
+        if (!device_->IsRecovery() && !RemoveAllUpdateState(lock.get(), before_cancel)) {
+            LOG(ERROR) << "Failed to remove all update state after acknowleding cancelled update.";
         }
     }
-    return state;
+    return result;
 }
 
-UpdateState SnapshotManager::CheckMergeState(LockedFile* lock,
-                                             const std::function<bool()>& before_cancel) {
-    UpdateState state = ReadUpdateState(lock);
-    switch (state) {
+auto SnapshotManager::CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel)
+        -> MergeResult {
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+    switch (update_status.state()) {
         case UpdateState::None:
         case UpdateState::MergeCompleted:
             // Harmless races are allowed between two callers of WaitForMerge,
             // so in both of these cases we just propagate the state.
-            return state;
+            return MergeResult(update_status.state());
 
         case UpdateState::Merging:
         case UpdateState::MergeNeedsReboot:
@@ -921,28 +961,42 @@
             // via the merge poll below, but if we never started a merge, we
             // need to also check here.
             if (HandleCancelledUpdate(lock, before_cancel)) {
-                return UpdateState::Cancelled;
+                return MergeResult(UpdateState::Cancelled);
             }
-            return state;
+            return MergeResult(update_status.state());
 
         default:
-            return state;
+            return MergeResult(update_status.state());
     }
 
     std::vector<std::string> snapshots;
     if (!ListSnapshots(lock, &snapshots)) {
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ListSnapshots);
     }
 
+    auto other_suffix = device_->GetOtherSlotSuffix();
+
     bool cancelled = false;
-    bool failed = false;
     bool merging = false;
     bool needs_reboot = false;
+    bool wrong_phase = false;
+    MergeFailureCode failure_code = MergeFailureCode::Ok;
     for (const auto& snapshot : snapshots) {
-        UpdateState snapshot_state = CheckTargetMergeState(lock, snapshot);
-        switch (snapshot_state) {
+        if (android::base::EndsWith(snapshot, other_suffix)) {
+            // This will have triggered an error message in InitiateMerge already.
+            LOG(INFO) << "Skipping merge validation of unexpected snapshot: " << snapshot;
+            continue;
+        }
+
+        auto result = CheckTargetMergeState(lock, snapshot, update_status);
+        LOG(INFO) << "CheckTargetMergeState for " << snapshot << " returned: " << result.state;
+
+        switch (result.state) {
             case UpdateState::MergeFailed:
-                failed = true;
+                // Take the first failure code in case other failures compound.
+                if (failure_code == MergeFailureCode::Ok) {
+                    failure_code = result.failure_code;
+                }
                 break;
             case UpdateState::Merging:
                 merging = true;
@@ -955,10 +1009,15 @@
             case UpdateState::Cancelled:
                 cancelled = true;
                 break;
+            case UpdateState::None:
+                wrong_phase = true;
+                break;
             default:
                 LOG(ERROR) << "Unknown merge status for \"" << snapshot << "\": "
-                           << "\"" << snapshot_state << "\"";
-                failed = true;
+                           << "\"" << result.state << "\"";
+                if (failure_code == MergeFailureCode::Ok) {
+                    failure_code = MergeFailureCode::UnexpectedMergeState;
+                }
                 break;
         }
     }
@@ -967,16 +1026,25 @@
         // Note that we handle "Merging" before we handle anything else. We
         // want to poll until *nothing* is merging if we can, so everything has
         // a chance to get marked as completed or failed.
-        return UpdateState::Merging;
+        return MergeResult(UpdateState::Merging);
     }
-    if (failed) {
+    if (failure_code != MergeFailureCode::Ok) {
         // Note: since there are many drop-out cases for failure, we acknowledge
         // it in WaitForMerge rather than here and elsewhere.
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, failure_code);
+    }
+    if (wrong_phase) {
+        // If we got here, no other partitions are being merged, and nothing
+        // failed to merge. It's safe to move to the next merge phase.
+        auto code = MergeSecondPhaseSnapshots(lock);
+        if (code != MergeFailureCode::Ok) {
+            return MergeResult(UpdateState::MergeFailed, code);
+        }
+        return MergeResult(UpdateState::Merging);
     }
     if (needs_reboot) {
         WriteUpdateState(lock, UpdateState::MergeNeedsReboot);
-        return UpdateState::MergeNeedsReboot;
+        return MergeResult(UpdateState::MergeNeedsReboot);
     }
     if (cancelled) {
         // This is an edge case, that we handle as correctly as we sensibly can.
@@ -984,22 +1052,22 @@
         // removed the snapshot as a result. The exact state of the update is
         // undefined now, but this can only happen on an unlocked device where
         // partitions can be flashed without wiping userdata.
-        return UpdateState::Cancelled;
+        return MergeResult(UpdateState::Cancelled);
     }
-    return UpdateState::MergeCompleted;
+    return MergeResult(UpdateState::MergeCompleted);
 }
 
-UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name) {
+auto SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name,
+                                            const SnapshotUpdateStatus& update_status)
+        -> MergeResult {
     SnapshotStatus snapshot_status;
     if (!ReadSnapshotStatus(lock, name, &snapshot_status)) {
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ReadStatus);
     }
 
-    std::string dm_name = GetSnapshotDeviceName(name, snapshot_status);
-
     std::unique_ptr<LpMetadata> current_metadata;
 
-    if (!IsSnapshotDevice(dm_name)) {
+    if (!IsSnapshotDevice(name)) {
         if (!current_metadata) {
             current_metadata = ReadCurrentMetadata();
         }
@@ -1007,22 +1075,22 @@
         if (!current_metadata ||
             GetMetadataPartitionState(*current_metadata, name) != MetadataPartitionState::Updated) {
             DeleteSnapshot(lock, name);
-            return UpdateState::Cancelled;
+            return MergeResult(UpdateState::Cancelled);
         }
 
         // During a check, we decided the merge was complete, but we were unable to
         // collapse the device-mapper stack and perform COW cleanup. If we haven't
         // rebooted after this check, the device will still be a snapshot-merge
-        // target. If the have rebooted, the device will now be a linear target,
+        // target. If we have rebooted, the device will now be a linear target,
         // and we can try cleanup again.
         if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
             // NB: It's okay if this fails now, we gave cleanup our best effort.
             OnSnapshotMergeComplete(lock, name, snapshot_status);
-            return UpdateState::MergeCompleted;
+            return MergeResult(UpdateState::MergeCompleted);
         }
 
-        LOG(ERROR) << "Expected snapshot or snapshot-merge for device: " << dm_name;
-        return UpdateState::MergeFailed;
+        LOG(ERROR) << "Expected snapshot or snapshot-merge for device: " << name;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
     }
 
     // This check is expensive so it is only enabled for debugging.
@@ -1031,24 +1099,31 @@
 
     std::string target_type;
     DmTargetSnapshot::Status status;
-    if (!QuerySnapshotStatus(dm_name, &target_type, &status)) {
-        return UpdateState::MergeFailed;
+    if (!QuerySnapshotStatus(name, &target_type, &status)) {
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
+    }
+    if (target_type == "snapshot" &&
+        DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
+        update_status.merge_phase() == MergePhase::FIRST_PHASE) {
+        // The snapshot is not being merged because it's in the wrong phase.
+        return MergeResult(UpdateState::None);
     }
     if (target_type != "snapshot-merge") {
         // We can get here if we failed to rewrite the target type in
         // InitiateMerge(). If we failed to create the target in first-stage
         // init, boot would not succeed.
         LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
     }
 
     // These two values are equal when merging is complete.
     if (status.sectors_allocated != status.metadata_sectors) {
         if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
             LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
-            return UpdateState::MergeFailed;
+            return MergeResult(UpdateState::MergeFailed,
+                               MergeFailureCode::UnmergedSectorsAfterCompletion);
         }
-        return UpdateState::Merging;
+        return MergeResult(UpdateState::Merging);
     }
 
     // Merging is done. First, update the status file to indicate the merge
@@ -1061,12 +1136,47 @@
     // snapshot device for this partition.
     snapshot_status.set_state(SnapshotState::MERGE_COMPLETED);
     if (!WriteSnapshotStatus(lock, snapshot_status)) {
-        return UpdateState::MergeFailed;
+        return MergeResult(UpdateState::MergeFailed, MergeFailureCode::WriteStatus);
     }
     if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) {
-        return UpdateState::MergeNeedsReboot;
+        return MergeResult(UpdateState::MergeNeedsReboot);
     }
-    return UpdateState::MergeCompleted;
+    return MergeResult(UpdateState::MergeCompleted, MergeFailureCode::Ok);
+}
+
+MergeFailureCode SnapshotManager::MergeSecondPhaseSnapshots(LockedFile* lock) {
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock, &snapshots)) {
+        return MergeFailureCode::ListSnapshots;
+    }
+
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+    CHECK(update_status.state() == UpdateState::Merging);
+    CHECK(update_status.merge_phase() == MergePhase::FIRST_PHASE);
+
+    update_status.set_merge_phase(MergePhase::SECOND_PHASE);
+    if (!WriteSnapshotUpdateStatus(lock, update_status)) {
+        return MergeFailureCode::WriteStatus;
+    }
+
+    MergeFailureCode result = MergeFailureCode::Ok;
+    for (const auto& snapshot : snapshots) {
+        SnapshotStatus snapshot_status;
+        if (!ReadSnapshotStatus(lock, snapshot, &snapshot_status)) {
+            return MergeFailureCode::ReadStatus;
+        }
+        if (DecideMergePhase(snapshot_status) != MergePhase::SECOND_PHASE) {
+            continue;
+        }
+        auto code = SwitchSnapshotToMerge(lock, snapshot);
+        if (code != MergeFailureCode::Ok) {
+            LOG(ERROR) << "Failed to switch snapshot to a second-phase merge target: " << snapshot;
+            if (result == MergeFailureCode::Ok) {
+                result = code;
+            }
+        }
+    }
+    return result;
 }
 
 std::string SnapshotManager::GetSnapshotBootIndicatorPath() {
@@ -1081,12 +1191,16 @@
     return metadata_dir_ + "/allow-forward-merge";
 }
 
+std::string SnapshotManager::GetOldPartitionMetadataPath() {
+    return metadata_dir_ + "/old-partition-metadata";
+}
+
 void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) {
     // It's not possible to remove update state in recovery, so write an
     // indicator that cleanup is needed on reboot. If a factory data reset
     // was requested, it doesn't matter, everything will get wiped anyway.
     // To make testing easier we consider a /data wipe as cleaned up.
-    if (device_->IsRecovery() && !in_factory_data_reset_) {
+    if (device_->IsRecovery()) {
         WriteUpdateState(lock, UpdateState::MergeCompleted);
         return;
     }
@@ -1094,7 +1208,7 @@
     RemoveAllUpdateState(lock);
 }
 
-void SnapshotManager::AcknowledgeMergeFailure() {
+void SnapshotManager::AcknowledgeMergeFailure(MergeFailureCode failure_code) {
     // Log first, so worst case, we always have a record of why the calls below
     // were being made.
     LOG(ERROR) << "Merge could not be completed and will be marked as failed.";
@@ -1111,26 +1225,25 @@
         return;
     }
 
-    WriteUpdateState(lock.get(), UpdateState::MergeFailed);
+    WriteUpdateState(lock.get(), UpdateState::MergeFailed, failure_code);
 }
 
 bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
                                               const SnapshotStatus& status) {
-    auto dm_name = GetSnapshotDeviceName(name, status);
-    if (IsSnapshotDevice(dm_name)) {
+    if (IsSnapshotDevice(name)) {
         // We are extra-cautious here, to avoid deleting the wrong table.
         std::string target_type;
         DmTargetSnapshot::Status dm_status;
-        if (!QuerySnapshotStatus(dm_name, &target_type, &dm_status)) {
+        if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
             return false;
         }
         if (target_type != "snapshot-merge") {
             LOG(ERROR) << "Unexpected target type " << target_type
-                       << " for snapshot device: " << dm_name;
+                       << " for snapshot device: " << name;
             return false;
         }
         if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
-            LOG(ERROR) << "Merge is unexpectedly incomplete for device " << dm_name;
+            LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
             return false;
         }
         if (!CollapseSnapshotDevice(name, status)) {
@@ -1151,23 +1264,21 @@
 bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
                                              const SnapshotStatus& status) {
     auto& dm = DeviceMapper::Instance();
-    auto dm_name = GetSnapshotDeviceName(name, status);
 
     // Verify we have a snapshot-merge device.
     DeviceMapper::TargetInfo target;
-    if (!GetSingleTarget(dm_name, TableQuery::Table, &target)) {
+    if (!GetSingleTarget(name, TableQuery::Table, &target)) {
         return false;
     }
     if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
         // This should be impossible, it was checked earlier.
-        LOG(ERROR) << "Snapshot device has invalid target type: " << dm_name;
+        LOG(ERROR) << "Snapshot device has invalid target type: " << name;
         return false;
     }
 
     std::string base_device, cow_device;
     if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
-        LOG(ERROR) << "Could not parse snapshot device " << dm_name
-                   << " parameters: " << target.data;
+        LOG(ERROR) << "Could not parse snapshot device " << name << " parameters: " << target.data;
         return false;
     }
 
@@ -1178,42 +1289,6 @@
         return false;
     }
 
-    if (dm_name != name) {
-        // We've derived the base device, but we actually need to replace the
-        // table of the outermost device. Do a quick verification that this
-        // device looks like we expect it to.
-        std::vector<DeviceMapper::TargetInfo> outer_table;
-        if (!dm.GetTableInfo(name, &outer_table)) {
-            LOG(ERROR) << "Could not validate outer snapshot table: " << name;
-            return false;
-        }
-        if (outer_table.size() != 2) {
-            LOG(ERROR) << "Expected 2 dm-linear targets for table " << name
-                       << ", got: " << outer_table.size();
-            return false;
-        }
-        for (const auto& target : outer_table) {
-            auto target_type = DeviceMapper::GetTargetType(target.spec);
-            if (target_type != "linear") {
-                LOG(ERROR) << "Outer snapshot table may only contain linear targets, but " << name
-                           << " has target: " << target_type;
-                return false;
-            }
-        }
-        if (outer_table[0].spec.length != snapshot_sectors) {
-            LOG(ERROR) << "dm-snapshot " << name << " should have " << snapshot_sectors
-                       << " sectors, got: " << outer_table[0].spec.length;
-            return false;
-        }
-        uint64_t expected_device_sectors = status.device_size() / kSectorSize;
-        uint64_t actual_device_sectors = outer_table[0].spec.length + outer_table[1].spec.length;
-        if (expected_device_sectors != actual_device_sectors) {
-            LOG(ERROR) << "Outer device " << name << " should have " << expected_device_sectors
-                       << " sectors, got: " << actual_device_sectors;
-            return false;
-        }
-    }
-
     uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
     // Create a DmTable that is identical to the base device.
     CreateLogicalPartitionParams base_device_params{
@@ -1228,7 +1303,6 @@
         return false;
     }
 
-    // Note: we are replacing the *outer* table here, so we do not use dm_name.
     if (!dm.LoadTableAndActivate(name, table)) {
         return false;
     }
@@ -1238,18 +1312,18 @@
     // flushed remaining I/O. We could in theory replace with dm-zero (or
     // re-use the table above), but for now it's better to know why this
     // would fail.
-    if (dm_name != name && !dm.DeleteDeviceIfExists(dm_name)) {
-        LOG(ERROR) << "Unable to delete snapshot device " << dm_name << ", COW cannot be "
-                   << "reclaimed until after reboot.";
-        return false;
+    if (status.compression_enabled()) {
+        UnmapDmUserDevice(name);
     }
-
-    // Cleanup the base device as well, since it is no longer used. This does
-    // not block cleanup.
     auto base_name = GetBaseDeviceName(name);
-    if (!dm.DeleteDeviceIfExists(base_name)) {
+    if (!DeleteDeviceIfExists(base_name)) {
         LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name;
     }
+
+    if (!DeleteDeviceIfExists(GetSourceDeviceName(name), 4000ms)) {
+        LOG(ERROR) << "Unable to delete source device for snapshot: " << GetSourceDeviceName(name);
+    }
+
     return true;
 }
 
@@ -1288,12 +1362,13 @@
     return RemoveAllUpdateState(lock, before_cancel);
 }
 
-bool SnapshotManager::PerformSecondStageTransition() {
-    LOG(INFO) << "Performing second-stage transition for snapuserd.";
+bool SnapshotManager::PerformInitTransition(InitTransition transition,
+                                            std::vector<std::string>* snapuserd_argv) {
+    LOG(INFO) << "Performing transition for snapuserd.";
 
     // Don't use EnsuerSnapuserdConnected() because this is called from init,
     // and attempting to do so will deadlock.
-    if (!snapuserd_client_) {
+    if (!snapuserd_client_ && transition != InitTransition::SELINUX_DETACH) {
         snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
         if (!snapuserd_client_) {
             LOG(ERROR) << "Unable to connect to snapuserd";
@@ -1339,16 +1414,18 @@
             continue;
         }
 
+        auto misc_name = user_cow_name;
+
         DmTable table;
-        table.Emplace<DmTargetUser>(0, target.spec.length, user_cow_name);
+        table.Emplace<DmTargetUser>(0, target.spec.length, misc_name);
         if (!dm.LoadTableAndActivate(user_cow_name, table)) {
-            LOG(ERROR) << "Unable to swap tables for " << user_cow_name;
+            LOG(ERROR) << "Unable to swap tables for " << misc_name;
             continue;
         }
 
-        std::string backing_device;
-        if (!dm.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &backing_device)) {
-            LOG(ERROR) << "Could not get device path for " << GetBaseDeviceName(snapshot);
+        std::string source_device;
+        if (!dm.GetDmDevicePathByName(GetSourceDeviceName(snapshot), &source_device)) {
+            LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot);
             continue;
         }
 
@@ -1368,23 +1445,34 @@
         }
 
         // Wait for ueventd to acknowledge and create the control device node.
-        std::string control_device = "/dev/dm-user/" + user_cow_name;
-        if (!android::fs_mgr::WaitForFile(control_device, 10s)) {
-            LOG(ERROR) << "Could not find control device: " << control_device;
+        std::string control_device = "/dev/dm-user/" + misc_name;
+        if (!WaitForDevice(control_device, 10s)) {
+            LOG(ERROR) << "dm-user control device no found:  " << misc_name;
+            continue;
+        }
+
+        if (transition == InitTransition::SELINUX_DETACH) {
+            auto message = misc_name + "," + cow_image_device + "," + source_device;
+            snapuserd_argv->emplace_back(std::move(message));
+
+            // Do not attempt to connect to the new snapuserd yet, it hasn't
+            // been started. We do however want to wait for the misc device
+            // to have been created.
+            ok_cows++;
             continue;
         }
 
         uint64_t base_sectors =
-                snapuserd_client_->InitDmUserCow(user_cow_name, cow_image_device, backing_device);
+                snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
         if (base_sectors == 0) {
             // Unrecoverable as metadata reads from cow device failed
             LOG(FATAL) << "Failed to retrieve base_sectors from Snapuserd";
             return false;
         }
 
-        CHECK(base_sectors == target.spec.length);
+        CHECK(base_sectors <= target.spec.length);
 
-        if (!snapuserd_client_->AttachDmUser(user_cow_name)) {
+        if (!snapuserd_client_->AttachDmUser(misc_name)) {
             // This error is unrecoverable. We cannot proceed because reads to
             // the underlying device will fail.
             LOG(FATAL) << "Could not initialize snapuserd for " << user_cow_name;
@@ -1398,24 +1486,6 @@
         LOG(ERROR) << "Could not transition all snapuserd consumers.";
         return false;
     }
-
-    std::string pid_var = device_->GetSnapuserdFirstStagePidVar();
-    if (pid_var.empty()) {
-        return true;
-    }
-
-    int pid;
-    const char* pid_str = getenv(pid_var.c_str());
-    if (pid_str && android::base::ParseInt(pid_str, &pid)) {
-        if (kill(pid, SIGTERM) < 0 && errno != ESRCH) {
-            LOG(ERROR) << "kill snapuserd failed";
-            return false;
-        }
-    } else {
-        LOG(ERROR) << "Could not find or parse " << kSnapuserdFirstStagePidVar
-                   << " for snapuserd pid";
-        return false;
-    }
     return true;
 }
 
@@ -1538,7 +1608,19 @@
         //    Otherwise (UPDATED flag), only delete snapshots if they are not mapped
         //    as dm-snapshot (for example, after merge completes).
         bool should_unmap = current_slot != Slot::Target;
-        bool should_delete = ShouldDeleteSnapshot(lock, flashing_status, current_slot, name);
+        bool should_delete = ShouldDeleteSnapshot(flashing_status, current_slot, name);
+        if (should_unmap && android::base::EndsWith(name, device_->GetSlotSuffix())) {
+            // Something very unexpected has happened - we want to unmap this
+            // snapshot, but it's on the wrong slot. We can't unmap an active
+            // partition. If this is not really a snapshot, skip the unmap
+            // step.
+            auto& dm = DeviceMapper::Instance();
+            if (dm.GetState(name) == DmDeviceState::INVALID || !IsSnapshotDevice(name)) {
+                LOG(ERROR) << "Detected snapshot " << name << " on " << current_slot << " slot"
+                           << " for source partition; removing without unmap.";
+                should_unmap = false;
+            }
+        }
 
         bool partition_ok = true;
         if (should_unmap && !UnmapPartitionWithSnapshot(lock, name)) {
@@ -1572,8 +1654,7 @@
 }
 
 // See comments in RemoveAllSnapshots().
-bool SnapshotManager::ShouldDeleteSnapshot(LockedFile* lock,
-                                           const std::map<std::string, bool>& flashing_status,
+bool SnapshotManager::ShouldDeleteSnapshot(const std::map<std::string, bool>& flashing_status,
                                            Slot current_slot, const std::string& name) {
     if (current_slot != Slot::Target) {
         return true;
@@ -1587,16 +1668,7 @@
         // partition flashed, okay to delete obsolete snapshots
         return true;
     }
-    // partition updated, only delete if not dm-snapshot
-    SnapshotStatus status;
-    if (!ReadSnapshotStatus(lock, name, &status)) {
-        LOG(WARNING) << "Unable to read snapshot status for " << name
-                     << ", guessing snapshot device name";
-        auto extra_name = GetSnapshotExtraDeviceName(name);
-        return !IsSnapshotDevice(name) && !IsSnapshotDevice(extra_name);
-    }
-    auto dm_name = GetSnapshotDeviceName(name, status);
-    return !IsSnapshotDevice(dm_name);
+    return !IsSnapshotDevice(name);
 }
 
 UpdateState SnapshotManager::GetUpdateState(double* progress) {
@@ -1640,6 +1712,7 @@
     for (const auto& snapshot : snapshots) {
         DmTargetSnapshot::Status current_status;
 
+        if (!IsSnapshotDevice(snapshot)) continue;
         if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
 
         fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
@@ -1653,7 +1726,19 @@
     return state;
 }
 
-bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots) {
+bool SnapshotManager::UpdateUsesCompression() {
+    auto lock = LockShared();
+    if (!lock) return false;
+    return UpdateUsesCompression(lock.get());
+}
+
+bool SnapshotManager::UpdateUsesCompression(LockedFile* lock) {
+    SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
+    return update_status.compression_enabled();
+}
+
+bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
+                                    const std::string& suffix) {
     CHECK(lock);
 
     auto dir_path = metadata_dir_ + "/snapshots"s;
@@ -1666,7 +1751,12 @@
     struct dirent* dp;
     while ((dp = readdir(dir.get())) != nullptr) {
         if (dp->d_type != DT_REG) continue;
-        snapshots->emplace_back(dp->d_name);
+
+        std::string name(dp->d_name);
+        if (!suffix.empty() && !android::base::EndsWith(name, suffix)) {
+            continue;
+        }
+        snapshots->emplace_back(std::move(name));
     }
     return true;
 }
@@ -1741,6 +1831,10 @@
         return false;
     }
 
+    if (!EnsureImageManager()) {
+        return false;
+    }
+
     for (const auto& partition : metadata->partitions) {
         if (GetPartitionGroupName(metadata->groups[partition.group_index]) == kCowGroupName) {
             LOG(INFO) << "Skip mapping partition " << GetPartitionName(partition) << " in group "
@@ -1760,15 +1854,6 @@
         }
     }
 
-    if (use_first_stage_snapuserd_) {
-        // Remove the first-stage socket as a precaution, there is no need to
-        // access the daemon anymore and we'll be killing it once second-stage
-        // is running.
-        auto socket = ANDROID_SOCKET_DIR + "/"s + kSnapuserdSocketFirstStage;
-        snapuserd_client_ = nullptr;
-        unlink(socket.c_str());
-    }
-
     LOG(INFO) << "Created logical partitions with snapshot.";
     return true;
 }
@@ -1914,24 +1999,42 @@
     remaining_time = GetRemainingTime(params.timeout_ms, begin);
     if (remaining_time.count() < 0) return false;
 
-    if (context == SnapshotContext::Update && IsCompressionEnabled()) {
+    if (context == SnapshotContext::Update && live_snapshot_status->compression_enabled()) {
         // Stop here, we can't run dm-user yet, the COW isn't built.
         created_devices.Release();
         return true;
     }
 
-    if (IsCompressionEnabled()) {
-        auto name = GetDmUserCowName(params.GetPartitionName());
+    if (live_snapshot_status->compression_enabled()) {
+        // Get the source device (eg the view of the partition from before it was resized).
+        std::string source_device_path;
+        if (!MapSourceDevice(lock, params.GetPartitionName(), remaining_time,
+                             &source_device_path)) {
+            LOG(ERROR) << "Could not map source device for: " << cow_name;
+            return false;
+        }
 
-        // :TODO: need to force init to process uevents for these in first-stage.
+        auto source_device = GetSourceDeviceName(params.GetPartitionName());
+        created_devices.EmplaceBack<AutoUnmapDevice>(&dm, source_device);
+
+        if (!WaitForDevice(source_device_path, remaining_time)) {
+            return false;
+        }
+
         std::string cow_path;
         if (!GetMappedImageDevicePath(cow_name, &cow_path)) {
             LOG(ERROR) << "Could not determine path for: " << cow_name;
             return false;
         }
+        if (!WaitForDevice(cow_path, remaining_time)) {
+            return false;
+        }
+
+        auto name = GetDmUserCowName(params.GetPartitionName());
 
         std::string new_cow_device;
-        if (!MapDmUserCow(lock, name, cow_path, base_path, remaining_time, &new_cow_device)) {
+        if (!MapDmUserCow(lock, name, cow_path, source_device_path, remaining_time,
+                          &new_cow_device)) {
             LOG(ERROR) << "Could not map dm-user device for partition "
                        << params.GetPartitionName();
             return false;
@@ -1974,13 +2077,18 @@
         return false;
     }
 
-    auto& dm = DeviceMapper::Instance();
-    std::string base_name = GetBaseDeviceName(target_partition_name);
-    if (!dm.DeleteDeviceIfExists(base_name)) {
+    auto base_name = GetBaseDeviceName(target_partition_name);
+    if (!DeleteDeviceIfExists(base_name)) {
         LOG(ERROR) << "Cannot delete base device: " << base_name;
         return false;
     }
 
+    auto source_name = GetSourceDeviceName(target_partition_name);
+    if (!DeleteDeviceIfExists(source_name)) {
+        LOG(ERROR) << "Cannot delete source device: " << source_name;
+        return false;
+    }
+
     LOG(INFO) << "Successfully unmapped snapshot " << target_partition_name;
 
     return true;
@@ -2062,34 +2170,12 @@
     CHECK(lock);
     if (!EnsureImageManager()) return false;
 
-    auto& dm = DeviceMapper::Instance();
-
-    auto dm_user_name = GetDmUserCowName(name);
-    if (IsCompressionEnabled() && dm.GetState(dm_user_name) != DmDeviceState::INVALID) {
-        if (!EnsureSnapuserdConnected()) {
-            return false;
-        }
-        if (!dm.DeleteDevice(dm_user_name)) {
-            LOG(ERROR) << "Cannot unmap " << dm_user_name;
-            return false;
-        }
-
-        if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) {
-            LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
-            return false;
-        }
-
-        // Ensure the control device is gone so we don't run into ABA problems.
-        auto control_device = "/dev/dm-user/" + dm_user_name;
-        if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) {
-            LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink";
-            return false;
-        }
+    if (UpdateUsesCompression(lock) && !UnmapDmUserDevice(name)) {
+        return false;
     }
 
-    auto cow_name = GetCowName(name);
-    if (!dm.DeleteDeviceIfExists(cow_name)) {
-        LOG(ERROR) << "Cannot unmap " << cow_name;
+    if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) {
+        LOG(ERROR) << "Cannot unmap: " << GetCowName(name);
         return false;
     }
 
@@ -2101,6 +2187,35 @@
     return true;
 }
 
+bool SnapshotManager::UnmapDmUserDevice(const std::string& snapshot_name) {
+    auto& dm = DeviceMapper::Instance();
+
+    auto dm_user_name = GetDmUserCowName(snapshot_name);
+    if (dm.GetState(dm_user_name) == DmDeviceState::INVALID) {
+        return true;
+    }
+
+    if (!DeleteDeviceIfExists(dm_user_name)) {
+        LOG(ERROR) << "Cannot unmap " << dm_user_name;
+        return false;
+    }
+
+    if (EnsureSnapuserdConnected()) {
+        if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) {
+            LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
+            return false;
+        }
+    }
+
+    // Ensure the control device is gone so we don't run into ABA problems.
+    auto control_device = "/dev/dm-user/" + dm_user_name;
+    if (!android::fs_mgr::WaitForFileDeleted(control_device, 10s)) {
+        LOG(ERROR) << "Timed out waiting for " << control_device << " to unlink";
+        return false;
+    }
+    return true;
+}
+
 bool SnapshotManager::MapAllSnapshots(const std::chrono::milliseconds& timeout_ms) {
     auto lock = LockExclusive();
     if (!lock) return false;
@@ -2116,12 +2231,44 @@
         return false;
     }
 
-    if (!UnmapAllSnapshots(lock.get())) {
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock.get(), &snapshots)) {
         return false;
     }
 
-    uint32_t slot = SlotNumberForSlotSuffix(device_->GetOtherSlotSuffix());
-    return MapAllPartitions(lock.get(), device_->GetSuperDevice(slot), slot, timeout_ms);
+    const auto& opener = device_->GetPartitionOpener();
+    auto slot_suffix = device_->GetOtherSlotSuffix();
+    auto slot_number = SlotNumberForSlotSuffix(slot_suffix);
+    auto super_device = device_->GetSuperDevice(slot_number);
+    auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot_number);
+    if (!metadata) {
+        LOG(ERROR) << "MapAllSnapshots could not read dynamic partition metadata for device: "
+                   << super_device;
+        return false;
+    }
+
+    for (const auto& snapshot : snapshots) {
+        if (!UnmapPartitionWithSnapshot(lock.get(), snapshot)) {
+            LOG(ERROR) << "MapAllSnapshots could not unmap snapshot: " << snapshot;
+            return false;
+        }
+
+        CreateLogicalPartitionParams params = {
+                .block_device = super_device,
+                .metadata = metadata.get(),
+                .partition_name = snapshot,
+                .partition_opener = &opener,
+                .timeout_ms = timeout_ms,
+        };
+        if (!MapPartitionWithSnapshot(lock.get(), std::move(params), SnapshotContext::Mount,
+                                      nullptr)) {
+            LOG(ERROR) << "MapAllSnapshots failed to map: " << snapshot;
+            return false;
+        }
+    }
+
+    LOG(INFO) << "MapAllSnapshots succeeded.";
+    return true;
 }
 
 bool SnapshotManager::UnmapAllSnapshots() {
@@ -2143,6 +2290,17 @@
             return false;
         }
     }
+
+    // Terminate the daemon and release the snapuserd_client_ object.
+    // If we need to re-connect with the daemon, EnsureSnapuserdConnected()
+    // will re-create the object and establish the socket connection.
+    if (snapuserd_client_) {
+        LOG(INFO) << "Shutdown snapuserd daemon";
+        snapuserd_client_->DetachSnapuserd();
+        snapuserd_client_->CloseConnection();
+        snapuserd_client_ = nullptr;
+    }
+
     return true;
 }
 
@@ -2269,9 +2427,21 @@
     return status;
 }
 
-bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state) {
-    SnapshotUpdateStatus status = {};
+bool SnapshotManager::WriteUpdateState(LockedFile* lock, UpdateState state,
+                                       MergeFailureCode failure_code) {
+    SnapshotUpdateStatus status;
     status.set_state(state);
+
+    if (state == UpdateState::MergeFailed) {
+        status.set_merge_failure_code(failure_code);
+    }
+
+    // If we're transitioning between two valid states (eg, we're not beginning
+    // or ending an OTA), then make sure to propagate the compression bit.
+    if (!(state == UpdateState::Initiated || state == UpdateState::None)) {
+        SnapshotUpdateStatus old_status = ReadSnapshotUpdateStatus(lock);
+        status.set_compression_enabled(old_status.compression_enabled());
+    }
     return WriteSnapshotUpdateStatus(lock, status);
 }
 
@@ -2382,19 +2552,10 @@
     return true;
 }
 
-std::string SnapshotManager::GetSnapshotDeviceName(const std::string& snapshot_name,
-                                                   const SnapshotStatus& status) {
-    if (status.device_size() != status.snapshot_size()) {
-        return GetSnapshotExtraDeviceName(snapshot_name);
-    }
-    return snapshot_name;
-}
-
 bool SnapshotManager::EnsureImageManager() {
     if (images_) return true;
 
-    // For now, use a preset timeout.
-    images_ = android::fiemap::IImageManager::Open(gsid_dir_, 15000ms);
+    images_ = device_->OpenImageManager();
     if (!images_) {
         LOG(ERROR) << "Could not open ImageManager";
         return false;
@@ -2407,28 +2568,11 @@
         return true;
     }
 
-    std::string socket;
-    if (use_first_stage_snapuserd_) {
-        auto pid = StartFirstStageSnapuserd();
-        if (pid < 0) {
-            LOG(ERROR) << "Failed to start snapuserd";
-            return false;
-        }
-
-        auto pid_str = std::to_string(static_cast<int>(pid));
-        if (setenv(kSnapuserdFirstStagePidVar, pid_str.c_str(), 1) < 0) {
-            PLOG(ERROR) << "setenv failed storing the snapuserd pid";
-        }
-
-        socket = kSnapuserdSocketFirstStage;
-    } else {
-        if (!EnsureSnapuserdStarted()) {
-            return false;
-        }
-        socket = kSnapuserdSocket;
+    if (!use_first_stage_snapuserd_ && !EnsureSnapuserdStarted()) {
+        return false;
     }
 
-    snapuserd_client_ = SnapuserdClient::Connect(socket, 10s);
+    snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
     if (!snapuserd_client_) {
         LOG(ERROR) << "Unable to connect to snapuserd";
         return false;
@@ -2436,21 +2580,10 @@
     return true;
 }
 
-bool SnapshotManager::ForceLocalImageManager() {
-    images_ = android::fiemap::ImageManager::Open(gsid_dir_);
-    if (!images_) {
-        LOG(ERROR) << "Could not open ImageManager";
-        return false;
-    }
-    has_local_image_manager_ = true;
-    return true;
-}
-
-static void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata) {
-    auto& dm = DeviceMapper::Instance();
+void SnapshotManager::UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata) {
     std::vector<std::string> to_delete;
     for (auto* existing_cow_partition : current_metadata->ListPartitionsInGroup(kCowGroupName)) {
-        if (!dm.DeleteDeviceIfExists(existing_cow_partition->name())) {
+        if (!DeleteDeviceIfExists(existing_cow_partition->name())) {
             LOG(WARNING) << existing_cow_partition->name()
                          << " cannot be unmapped and its space cannot be reclaimed";
             continue;
@@ -2478,6 +2611,12 @@
     auto lock = LockExclusive();
     if (!lock) return Return::Error();
 
+    auto update_state = ReadUpdateState(lock.get());
+    if (update_state != UpdateState::Initiated) {
+        LOG(ERROR) << "Cannot create update snapshots in state " << update_state;
+        return Return::Error();
+    }
+
     // TODO(b/134949511): remove this check. Right now, with overlayfs mounted, the scratch
     // partition takes up a big chunk of space in super, causing COW images to be created on
     // retrofit Virtual A/B devices.
@@ -2540,6 +2679,31 @@
     // these devices.
     AutoDeviceList created_devices;
 
+    const auto& dap_metadata = manifest.dynamic_partition_metadata();
+    CowOptions options;
+    CowWriter writer(options);
+    bool cow_format_support = true;
+    if (dap_metadata.cow_version() < writer.GetCowVersion()) {
+        cow_format_support = false;
+    }
+
+    LOG(INFO) << " dap_metadata.cow_version(): " << dap_metadata.cow_version()
+              << " writer.GetCowVersion(): " << writer.GetCowVersion();
+
+    bool use_compression = IsCompressionEnabled() && dap_metadata.vabc_enabled() &&
+                           !device_->IsRecovery() && cow_format_support;
+
+    std::string compression_algorithm;
+    if (use_compression) {
+        compression_algorithm = dap_metadata.vabc_compression_param();
+        if (compression_algorithm.empty()) {
+            // Older OTAs don't set an explicit compression type, so default to gz.
+            compression_algorithm = "gz";
+        }
+    } else {
+        compression_algorithm = "none";
+    }
+
     PartitionCowCreator cow_creator{
             .target_metadata = target_metadata.get(),
             .target_suffix = target_suffix,
@@ -2548,7 +2712,8 @@
             .current_suffix = current_suffix,
             .update = nullptr,
             .extra_extents = {},
-            .compression_enabled = IsCompressionEnabled(),
+            .compression_enabled = use_compression,
+            .compression_algorithm = compression_algorithm,
     };
 
     auto ret = CreateUpdateSnapshotsInternal(lock.get(), manifest, &cow_creator, &created_devices,
@@ -2572,6 +2737,32 @@
         return Return::Error();
     }
 
+    // If compression is enabled, we need to retain a copy of the old metadata
+    // so we can access original blocks in case they are moved around. We do
+    // not want to rely on the old super metadata slot because we don't
+    // guarantee its validity after the slot switch is successful.
+    if (cow_creator.compression_enabled) {
+        auto metadata = current_metadata->Export();
+        if (!metadata) {
+            LOG(ERROR) << "Could not export current metadata";
+            return Return::Error();
+        }
+
+        auto path = GetOldPartitionMetadataPath();
+        if (!android::fs_mgr::WriteToImageFile(path, *metadata.get())) {
+            LOG(ERROR) << "Cannot write old metadata to " << path;
+            return Return::Error();
+        }
+    }
+
+    SnapshotUpdateStatus status = {};
+    status.set_state(update_state);
+    status.set_compression_enabled(cow_creator.compression_enabled);
+    if (!WriteSnapshotUpdateStatus(lock.get(), status)) {
+        LOG(ERROR) << "Unable to write new update state";
+        return Return::Error();
+    }
+
     created_devices.Release();
     LOG(INFO) << "Successfully created all snapshots for target slot " << target_suffix;
 
@@ -2664,8 +2855,17 @@
             continue;
         }
 
+        // Find the original partition size.
+        auto name = target_partition->name();
+        auto old_partition_name =
+                name.substr(0, name.size() - target_suffix.size()) + cow_creator->current_suffix;
+        auto old_partition = cow_creator->current_metadata->FindPartition(old_partition_name);
+        if (old_partition) {
+            cow_creator_ret->snapshot_status.set_old_partition_size(old_partition->size());
+        }
+
         // Store these device sizes to snapshot status file.
-        if (!CreateSnapshot(lock, &cow_creator_ret->snapshot_status)) {
+        if (!CreateSnapshot(lock, cow_creator, &cow_creator_ret->snapshot_status)) {
             return Return::Error();
         }
         created_devices->EmplaceBack<AutoDeleteSnapshot>(this, lock, target_partition->name());
@@ -2750,7 +2950,7 @@
             return Return::Error();
         }
 
-        if (IsCompressionEnabled()) {
+        if (it->second.compression_enabled()) {
             unique_fd fd(open(cow_path.c_str(), O_RDWR | O_CLOEXEC));
             if (fd < 0) {
                 PLOG(ERROR) << "open " << cow_path << " failed for snapshot "
@@ -2758,7 +2958,13 @@
                 return Return::Error();
             }
 
-            CowWriter writer(CowOptions{});
+            CowOptions options;
+            if (device()->IsTestDevice()) {
+                options.scratch_space = false;
+            }
+            options.compression = it->second.compression_algorithm();
+
+            CowWriter writer(options);
             if (!writer.Initialize(fd) || !writer.Finalize()) {
                 LOG(ERROR) << "Could not initialize COW device for " << target_partition->name();
                 return Return::Error();
@@ -2778,11 +2984,6 @@
 
 bool SnapshotManager::MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
                                         std::string* snapshot_path) {
-    if (IsCompressionEnabled()) {
-        LOG(ERROR) << "MapUpdateSnapshot cannot be used in compression mode.";
-        return false;
-    }
-
     auto lock = LockShared();
     if (!lock) return false;
     if (!UnmapPartitionWithSnapshot(lock.get(), params.GetPartitionName())) {
@@ -2791,6 +2992,15 @@
         return false;
     }
 
+    SnapshotStatus status;
+    if (!ReadSnapshotStatus(lock.get(), params.GetPartitionName(), &status)) {
+        return false;
+    }
+    if (status.compression_enabled()) {
+        LOG(ERROR) << "Cannot use MapUpdateSnapshot with compressed snapshots";
+        return false;
+    }
+
     SnapshotPaths paths;
     if (!MapPartitionWithSnapshot(lock.get(), params, SnapshotContext::Update, &paths)) {
         return false;
@@ -2844,7 +3054,7 @@
         return nullptr;
     }
 
-    if (IsCompressionEnabled()) {
+    if (status.compression_enabled()) {
         return OpenCompressedSnapshotWriter(lock.get(), source_device, params.GetPartitionName(),
                                             status, paths);
     }
@@ -2861,8 +3071,12 @@
     CHECK(lock);
 
     CowOptions cow_options;
-    cow_options.compression = "gz";
+    cow_options.compression = status.compression_algorithm();
     cow_options.max_blocks = {status.device_size() / cow_options.block_size};
+    // Disable scratch space for vts tests
+    if (device()->IsTestDevice()) {
+        cow_options.scratch_space = false;
+    }
 
     // Currently we don't support partial snapshots, since partition_cow_creator
     // never creates this scenario.
@@ -2968,7 +3182,8 @@
     std::stringstream ss;
 
     ss << "Update state: " << ReadUpdateState(file.get()) << std::endl;
-
+    ss << "Compression: " << ReadSnapshotUpdateStatus(file.get()).compression_enabled()
+       << std::endl;
     ss << "Current slot: " << device_->GetSlotSuffix() << std::endl;
     ss << "Boot indicator: booting from " << GetCurrentSlot() << " slot" << std::endl;
     ss << "Rollback indicator: "
@@ -2999,6 +3214,7 @@
         ss << "    cow file size (bytes): " << status.cow_file_size() << std::endl;
         ss << "    allocated sectors: " << status.sectors_allocated() << std::endl;
         ss << "    metadata sectors: " << status.metadata_sectors() << std::endl;
+        ss << "    compression: " << status.compression_algorithm() << std::endl;
     }
     os << ss.rdbuf();
     return ok;
@@ -3050,7 +3266,7 @@
 
     auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
     auto super_path = device_->GetSuperDevice(slot_number);
-    if (!CreateLogicalAndSnapshotPartitions(super_path)) {
+    if (!CreateLogicalAndSnapshotPartitions(super_path, 20s)) {
         LOG(ERROR) << "Unable to map partitions to complete merge.";
         return false;
     }
@@ -3063,10 +3279,11 @@
     };
 
     in_factory_data_reset_ = true;
-    bool ok = ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback);
+    UpdateState state =
+            ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback);
     in_factory_data_reset_ = false;
 
-    if (!ok) {
+    if (state == UpdateState::MergeFailed) {
         return false;
     }
 
@@ -3074,6 +3291,16 @@
     if (!UnmapAllPartitionsInRecovery()) {
         LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash.";
     }
+
+    if (state != UpdateState::None) {
+        auto lock = LockExclusive();
+        if (!lock) return false;
+
+        // Zap the update state so the bootloader doesn't think we're still
+        // merging. It's okay if this fails, it's informative only at this
+        // point.
+        WriteUpdateState(lock.get(), UpdateState::None);
+    }
     return true;
 }
 
@@ -3090,7 +3317,7 @@
 
     auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
     auto super_path = device_->GetSuperDevice(slot_number);
-    if (!CreateLogicalAndSnapshotPartitions(super_path)) {
+    if (!CreateLogicalAndSnapshotPartitions(super_path, 20s)) {
         LOG(ERROR) << "Unable to map partitions to complete merge.";
         return false;
     }
@@ -3108,15 +3335,15 @@
     return true;
 }
 
-bool SnapshotManager::ProcessUpdateStateOnDataWipe(bool allow_forward_merge,
-                                                   const std::function<bool()>& callback) {
+UpdateState SnapshotManager::ProcessUpdateStateOnDataWipe(bool allow_forward_merge,
+                                                          const std::function<bool()>& callback) {
     auto slot_number = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
     UpdateState state = ProcessUpdateState(callback);
     LOG(INFO) << "Update state in recovery: " << state;
     switch (state) {
         case UpdateState::MergeFailed:
             LOG(ERROR) << "Unrecoverable merge failure detected.";
-            return false;
+            return state;
         case UpdateState::Unverified: {
             // If an OTA was just applied but has not yet started merging:
             //
@@ -3136,8 +3363,12 @@
                 if (allow_forward_merge &&
                     access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0) {
                     LOG(INFO) << "Forward merge allowed, initiating merge now.";
-                    return InitiateMerge() &&
-                           ProcessUpdateStateOnDataWipe(false /* allow_forward_merge */, callback);
+
+                    if (!InitiateMerge()) {
+                        LOG(ERROR) << "Failed to initiate merge on data wipe.";
+                        return UpdateState::MergeFailed;
+                    }
+                    return ProcessUpdateStateOnDataWipe(false /* allow_forward_merge */, callback);
                 }
 
                 LOG(ERROR) << "Reverting to old slot since update will be deleted.";
@@ -3155,7 +3386,7 @@
         default:
             break;
     }
-    return true;
+    return state;
 }
 
 bool SnapshotManager::EnsureNoOverflowSnapshot(LockedFile* lock) {
@@ -3169,6 +3400,14 @@
 
     auto& dm = DeviceMapper::Instance();
     for (const auto& snapshot : snapshots) {
+        SnapshotStatus status;
+        if (!ReadSnapshotStatus(lock, snapshot, &status)) {
+            return false;
+        }
+        if (status.compression_enabled()) {
+            continue;
+        }
+
         std::vector<DeviceMapper::TargetInfo> targets;
         if (!dm.GetTableStatus(snapshot, &targets)) {
             LOG(ERROR) << "Could not read snapshot device table: " << snapshot;
@@ -3228,7 +3467,7 @@
     auto slot_suffix = device_->GetOtherSlotSuffix();
     auto slot_number = SlotNumberForSlotSuffix(slot_suffix);
     auto super_path = device_->GetSuperDevice(slot_number);
-    if (!CreateLogicalAndSnapshotPartitions(super_path)) {
+    if (!CreateLogicalAndSnapshotPartitions(super_path, 20s)) {
         LOG(ERROR) << "Unable to map partitions.";
         return CreateResult::ERROR;
     }
@@ -3293,5 +3532,182 @@
     return true;
 }
 
+bool SnapshotManager::WaitForDevice(const std::string& device,
+                                    std::chrono::milliseconds timeout_ms) {
+    if (!android::base::StartsWith(device, "/")) {
+        return true;
+    }
+
+    // In first-stage init, we rely on init setting a callback which can
+    // regenerate uevents and populate /dev for us.
+    if (uevent_regen_callback_) {
+        if (!uevent_regen_callback_(device)) {
+            LOG(ERROR) << "Failed to find device after regenerating uevents: " << device;
+            return false;
+        }
+        return true;
+    }
+
+    // Otherwise, the only kind of device we need to wait for is a dm-user
+    // misc device. Normal calls to DeviceMapper::CreateDevice() guarantee
+    // the path has been created.
+    if (!android::base::StartsWith(device, "/dev/dm-user/")) {
+        return true;
+    }
+
+    if (timeout_ms.count() == 0) {
+        LOG(ERROR) << "No timeout was specified to wait for device: " << device;
+        return false;
+    }
+    if (!android::fs_mgr::WaitForFile(device, timeout_ms)) {
+        LOG(ERROR) << "Timed out waiting for device to appear: " << device;
+        return false;
+    }
+    return true;
+}
+
+bool SnapshotManager::IsSnapuserdRequired() {
+    auto lock = LockExclusive();
+    if (!lock) return false;
+
+    auto status = ReadSnapshotUpdateStatus(lock.get());
+    return status.state() != UpdateState::None && status.compression_enabled();
+}
+
+bool SnapshotManager::DetachSnapuserdForSelinux(std::vector<std::string>* snapuserd_argv) {
+    return PerformInitTransition(InitTransition::SELINUX_DETACH, snapuserd_argv);
+}
+
+bool SnapshotManager::PerformSecondStageInitTransition() {
+    return PerformInitTransition(InitTransition::SECOND_STAGE);
+}
+
+const LpMetadata* SnapshotManager::ReadOldPartitionMetadata(LockedFile* lock) {
+    CHECK(lock);
+
+    if (!old_partition_metadata_) {
+        auto path = GetOldPartitionMetadataPath();
+        old_partition_metadata_ = android::fs_mgr::ReadFromImageFile(path);
+        if (!old_partition_metadata_) {
+            LOG(ERROR) << "Could not read old partition metadata from " << path;
+            return nullptr;
+        }
+    }
+    return old_partition_metadata_.get();
+}
+
+MergePhase SnapshotManager::DecideMergePhase(const SnapshotStatus& status) {
+    if (status.compression_enabled() && status.device_size() < status.old_partition_size()) {
+        return MergePhase::FIRST_PHASE;
+    }
+    return MergePhase::SECOND_PHASE;
+}
+
+void SnapshotManager::UpdateCowStats(ISnapshotMergeStats* stats) {
+    auto lock = LockExclusive();
+    if (!lock) return;
+
+    std::vector<std::string> snapshots;
+    if (!ListSnapshots(lock.get(), &snapshots, GetSnapshotSlotSuffix())) {
+        LOG(ERROR) << "Could not list snapshots";
+        return;
+    }
+
+    uint64_t cow_file_size = 0;
+    uint64_t total_cow_size = 0;
+    uint64_t estimated_cow_size = 0;
+    for (const auto& snapshot : snapshots) {
+        SnapshotStatus status;
+        if (!ReadSnapshotStatus(lock.get(), snapshot, &status)) {
+            return;
+        }
+
+        cow_file_size += status.cow_file_size();
+        total_cow_size += status.cow_file_size() + status.cow_partition_size();
+        estimated_cow_size += status.estimated_cow_size();
+    }
+
+    stats->set_cow_file_size(cow_file_size);
+    stats->set_total_cow_size_bytes(total_cow_size);
+    stats->set_estimated_cow_size_bytes(estimated_cow_size);
+}
+
+bool SnapshotManager::DeleteDeviceIfExists(const std::string& name,
+                                           const std::chrono::milliseconds& timeout_ms) {
+    auto& dm = DeviceMapper::Instance();
+    auto start = std::chrono::steady_clock::now();
+    while (true) {
+        if (dm.DeleteDeviceIfExists(name)) {
+            return true;
+        }
+        auto now = std::chrono::steady_clock::now();
+        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
+        if (elapsed >= timeout_ms) {
+            break;
+        }
+        std::this_thread::sleep_for(400ms);
+    }
+
+    // Try to diagnose why this failed. First get the actual device path.
+    std::string full_path;
+    if (!dm.GetDmDevicePathByName(name, &full_path)) {
+        LOG(ERROR) << "Unable to diagnose DM_DEV_REMOVE failure.";
+        return false;
+    }
+
+    // Check for child dm-devices.
+    std::string block_name = android::base::Basename(full_path);
+    std::string sysfs_holders = "/sys/class/block/" + block_name + "/holders";
+
+    std::error_code ec;
+    std::filesystem::directory_iterator dir_iter(sysfs_holders, ec);
+    if (auto begin = std::filesystem::begin(dir_iter); begin != std::filesystem::end(dir_iter)) {
+        LOG(ERROR) << "Child device-mapper device still mapped: " << begin->path();
+        return false;
+    }
+
+    // Check for mounted partitions.
+    android::fs_mgr::Fstab fstab;
+    android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab);
+    for (const auto& entry : fstab) {
+        if (android::base::Basename(entry.blk_device) == block_name) {
+            LOG(ERROR) << "Partition still mounted: " << entry.mount_point;
+            return false;
+        }
+    }
+
+    // Check for detached mounted partitions.
+    for (const auto& fs : std::filesystem::directory_iterator("/sys/fs", ec)) {
+        std::string fs_type = android::base::Basename(fs.path().c_str());
+        if (!(fs_type == "ext4" || fs_type == "f2fs")) {
+            continue;
+        }
+
+        std::string path = fs.path().c_str() + "/"s + block_name;
+        if (access(path.c_str(), F_OK) == 0) {
+            LOG(ERROR) << "Block device was lazily unmounted and is still in-use: " << full_path
+                       << "; possibly open file descriptor or attached loop device.";
+            return false;
+        }
+    }
+
+    LOG(ERROR) << "Device-mapper device " << name << "(" << full_path << ")"
+               << " still in use."
+               << "  Probably a file descriptor was leaked or held open, or a loop device is"
+               << " attached.";
+    return false;
+}
+
+MergeFailureCode SnapshotManager::ReadMergeFailureCode() {
+    auto lock = LockExclusive();
+    if (!lock) return MergeFailureCode::AcquireLock;
+
+    SnapshotUpdateStatus status = ReadSnapshotUpdateStatus(lock.get());
+    if (status.state() != UpdateState::MergeFailed) {
+        return MergeFailureCode::Ok;
+    }
+    return status.merge_failure_code();
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
index 8926535..0096f85 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.cpp
@@ -381,8 +381,9 @@
     CheckDetachLoopDevices({Basename(fake_super_), Basename(fake_data_block_device_)});
 }
 
-std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager(
-        const std::string& metadata_dir, const std::string& data_dir) {
+std::unique_ptr<IImageManager> SnapshotFuzzEnv::CheckCreateFakeImageManager() {
+    auto metadata_dir = fake_root_->tmp_path() + "/images_manager_metadata";
+    auto data_dir = fake_data_mount_point_ + "/image_manager_data";
     PCHECK(Mkdir(metadata_dir));
     PCHECK(Mkdir(data_dir));
     return SnapshotFuzzImageManager::Open(metadata_dir, data_dir);
@@ -428,13 +429,9 @@
         PCHECK(Mkdir(metadata_dir + "/snapshots"));
     }
 
-    ret.device_info = new SnapshotFuzzDeviceInfo(data.device_info_data(),
+    ret.device_info = new SnapshotFuzzDeviceInfo(this, data.device_info_data(),
                                                  std::move(partition_opener), metadata_dir);
     auto snapshot = SnapshotManager::New(ret.device_info /* takes ownership */);
-    snapshot->images_ =
-            CheckCreateFakeImageManager(fake_root_->tmp_path() + "/images_manager_metadata",
-                                        fake_data_mount_point_ + "/image_manager_data");
-    snapshot->has_local_image_manager_ = data.manager_data().is_local_image_manager();
     ret.snapshot = std::move(snapshot);
 
     return ret;
diff --git a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
index 092ee0b..3ed27c8 100644
--- a/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
+++ b/fs_mgr/libsnapshot/snapshot_fuzz_utils.h
@@ -65,6 +65,8 @@
     // ISnapshotManager.
     SnapshotTestModule CheckCreateSnapshotManager(const SnapshotFuzzData& data);
 
+    std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager();
+
     // Return path to super partition.
     const std::string& super() const;
 
@@ -79,8 +81,6 @@
     std::string fake_data_block_device_;
     std::unique_ptr<AutoDevice> mounted_data_;
 
-    static std::unique_ptr<android::fiemap::IImageManager> CheckCreateFakeImageManager(
-            const std::string& metadata_dir, const std::string& data_dir);
     static std::unique_ptr<AutoDevice> CheckMapImage(const std::string& fake_persist_path,
                                                      uint64_t size,
                                                      android::dm::LoopControl* control,
@@ -95,15 +95,15 @@
 class SnapshotFuzzDeviceInfo : public ISnapshotManager::IDeviceInfo {
   public:
     // Client is responsible for maintaining the lifetime of |data|.
-    SnapshotFuzzDeviceInfo(const FuzzDeviceInfoData& data,
+    SnapshotFuzzDeviceInfo(SnapshotFuzzEnv* env, const FuzzDeviceInfoData& data,
                            std::unique_ptr<TestPartitionOpener>&& partition_opener,
                            const std::string& metadata_dir)
-        : data_(&data),
+        : env_(env),
+          data_(&data),
           partition_opener_(std::move(partition_opener)),
           metadata_dir_(metadata_dir) {}
 
     // Following APIs are mocked.
-    std::string GetGsidDir() const override { return "fuzz_ota"; }
     std::string GetMetadataDir() const override { return metadata_dir_; }
     std::string GetSuperDevice(uint32_t) const override {
         // TestPartitionOpener can recognize this.
@@ -124,11 +124,15 @@
         return data_->allow_set_slot_as_unbootable();
     }
     bool IsRecovery() const override { return data_->is_recovery(); }
-    std::string GetSnapuserdFirstStagePidVar() const override { return {}; }
+    bool IsFirstStageInit() const override { return false; }
+    std::unique_ptr<IImageManager> OpenImageManager() const {
+        return env_->CheckCreateFakeImageManager();
+    }
 
     void SwitchSlot() { switched_slot_ = !switched_slot_; }
 
   private:
+    SnapshotFuzzEnv* env_;
     const FuzzDeviceInfoData* data_;
     std::unique_ptr<TestPartitionOpener> partition_opener_;
     std::string metadata_dir_;
diff --git a/fs_mgr/libsnapshot/snapshot_reader.cpp b/fs_mgr/libsnapshot/snapshot_reader.cpp
index b56d879..5ee8e25 100644
--- a/fs_mgr/libsnapshot/snapshot_reader.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader.cpp
@@ -90,7 +90,7 @@
     op_iter_ = cow_->GetOpIter();
     while (!op_iter_->Done()) {
         const CowOperation* op = &op_iter_->Get();
-        if (op->type == kCowLabelOp || op->type == kCowFooterOp) {
+        if (IsMetadataOp(*op)) {
             op_iter_->Next();
             continue;
         }
diff --git a/fs_mgr/libsnapshot/snapshot_reader_test.cpp b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
index 4202d22..9373059 100644
--- a/fs_mgr/libsnapshot/snapshot_reader_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_reader_test.cpp
@@ -150,6 +150,7 @@
     CowOptions options;
     options.compression = "gz";
     options.max_blocks = {kBlockCount};
+    options.scratch_space = false;
 
     unique_fd cow_fd(dup(cow_->fd));
     ASSERT_GE(cow_fd, 0);
diff --git a/fs_mgr/libsnapshot/snapshot_stats.cpp b/fs_mgr/libsnapshot/snapshot_stats.cpp
index 3723730..4a93d65 100644
--- a/fs_mgr/libsnapshot/snapshot_stats.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stats.cpp
@@ -84,8 +84,9 @@
     return WriteState();
 }
 
-void SnapshotMergeStats::set_state(android::snapshot::UpdateState state) {
+void SnapshotMergeStats::set_state(android::snapshot::UpdateState state, bool using_compression) {
     report_.set_state(state);
+    report_.set_compression_enabled(using_compression);
 }
 
 void SnapshotMergeStats::set_cow_file_size(uint64_t cow_file_size) {
@@ -97,6 +98,46 @@
     return report_.cow_file_size();
 }
 
+void SnapshotMergeStats::set_total_cow_size_bytes(uint64_t bytes) {
+    report_.set_total_cow_size_bytes(bytes);
+}
+
+void SnapshotMergeStats::set_estimated_cow_size_bytes(uint64_t bytes) {
+    report_.set_estimated_cow_size_bytes(bytes);
+}
+
+uint64_t SnapshotMergeStats::total_cow_size_bytes() {
+    return report_.total_cow_size_bytes();
+}
+
+uint64_t SnapshotMergeStats::estimated_cow_size_bytes() {
+    return report_.estimated_cow_size_bytes();
+}
+
+void SnapshotMergeStats::set_boot_complete_time_ms(uint32_t ms) {
+    report_.set_boot_complete_time_ms(ms);
+}
+
+uint32_t SnapshotMergeStats::boot_complete_time_ms() {
+    return report_.boot_complete_time_ms();
+}
+
+void SnapshotMergeStats::set_boot_complete_to_merge_start_time_ms(uint32_t ms) {
+    report_.set_boot_complete_to_merge_start_time_ms(ms);
+}
+
+uint32_t SnapshotMergeStats::boot_complete_to_merge_start_time_ms() {
+    return report_.boot_complete_to_merge_start_time_ms();
+}
+
+void SnapshotMergeStats::set_merge_failure_code(MergeFailureCode code) {
+    report_.set_merge_failure_code(code);
+}
+
+MergeFailureCode SnapshotMergeStats::merge_failure_code() {
+    return report_.merge_failure_code();
+}
+
 class SnapshotMergeStatsResultImpl : public SnapshotMergeStats::Result {
   public:
     SnapshotMergeStatsResultImpl(const SnapshotMergeReport& report,
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
index 26b9129..1a9eda5 100644
--- a/fs_mgr/libsnapshot/snapshot_stub.cpp
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -43,7 +43,7 @@
     return false;
 }
 
-bool SnapshotManagerStub::InitiateMerge(uint64_t*) {
+bool SnapshotManagerStub::InitiateMerge() {
     LOG(ERROR) << __FUNCTION__ << " should never be called.";
     return false;
 }
@@ -116,12 +116,27 @@
     return nullptr;
 }
 
+bool SnapshotManagerStub::UpdateUsesCompression() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
 class SnapshotMergeStatsStub : public ISnapshotMergeStats {
     bool Start() override { return false; }
-    void set_state(android::snapshot::UpdateState) override {}
+    void set_state(android::snapshot::UpdateState, bool) override {}
     void set_cow_file_size(uint64_t) override {}
     uint64_t cow_file_size() override { return 0; }
     std::unique_ptr<Result> Finish() override { return nullptr; }
+    void set_total_cow_size_bytes(uint64_t) override {}
+    void set_estimated_cow_size_bytes(uint64_t) override {}
+    uint64_t total_cow_size_bytes() override { return 0; }
+    uint64_t estimated_cow_size_bytes() override { return 0; }
+    void set_boot_complete_time_ms(uint32_t) override {}
+    uint32_t boot_complete_time_ms() override { return 0; }
+    void set_boot_complete_to_merge_start_time_ms(uint32_t) override {}
+    uint32_t boot_complete_to_merge_start_time_ms() override { return 0; }
+    void set_merge_failure_code(MergeFailureCode) override {}
+    MergeFailureCode merge_failure_code() { return MergeFailureCode::Ok; }
 };
 
 ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() {
@@ -146,4 +161,13 @@
     return false;
 }
 
+void SnapshotManagerStub::UpdateCowStats(ISnapshotMergeStats*) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+}
+
+auto SnapshotManagerStub::ReadMergeFailureCode() -> MergeFailureCode {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return MergeFailureCode::Ok;
+}
+
 }  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 74558ab..6018643 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <libsnapshot/cow_format.h>
 #include <libsnapshot/snapshot.h>
 
 #include <fcntl.h>
@@ -41,6 +42,7 @@
 
 #include <android/snapshot/snapshot.pb.h>
 #include <libsnapshot/test_helpers.h>
+#include "partition_cow_creator.h"
 #include "utility.h"
 
 // Mock classes are not used. Header included to ensure mocked class definition aligns with the
@@ -146,6 +148,7 @@
                 "base-device",
                 "test_partition_b",
                 "test_partition_b-base",
+                "test_partition_b-base",
         };
         for (const auto& partition : partitions) {
             DeleteDevice(partition);
@@ -180,12 +183,22 @@
     }
 
     // If |path| is non-null, the partition will be mapped after creation.
-    bool CreatePartition(const std::string& name, uint64_t size, std::string* path = nullptr) {
+    bool CreatePartition(const std::string& name, uint64_t size, std::string* path = nullptr,
+                         const std::optional<std::string> group = {}) {
         TestPartitionOpener opener(fake_super);
         auto builder = MetadataBuilder::New(opener, "super", 0);
         if (!builder) return false;
 
-        auto partition = builder->AddPartition(name, 0);
+        std::string partition_group = std::string(android::fs_mgr::kDefaultGroup);
+        if (group) {
+            partition_group = *group;
+        }
+        return CreatePartition(builder.get(), name, size, path, partition_group);
+    }
+
+    bool CreatePartition(MetadataBuilder* builder, const std::string& name, uint64_t size,
+                         std::string* path, const std::string& group) {
+        auto partition = builder->AddPartition(name, group, 0);
         if (!partition) return false;
         if (!builder->ResizePartition(partition, size)) {
             return false;
@@ -194,6 +207,8 @@
         // Update the source slot.
         auto metadata = builder->Export();
         if (!metadata) return false;
+
+        TestPartitionOpener opener(fake_super);
         if (!UpdatePartitionTable(opener, "super", *metadata.get(), 0)) {
             return false;
         }
@@ -210,45 +225,62 @@
         return CreateLogicalPartition(params, path);
     }
 
-    bool MapUpdatePartitions() {
+    AssertionResult MapUpdateSnapshot(const std::string& name,
+                                      std::unique_ptr<ISnapshotWriter>* writer) {
         TestPartitionOpener opener(fake_super);
-        auto builder = MetadataBuilder::NewForUpdate(opener, "super", 0, 1);
-        if (!builder) return false;
+        CreateLogicalPartitionParams params{
+                .block_device = fake_super,
+                .metadata_slot = 1,
+                .partition_name = name,
+                .timeout_ms = 10s,
+                .partition_opener = &opener,
+        };
 
-        auto metadata = builder->Export();
-        if (!metadata) return false;
-
-        // Update the destination slot, mark it as updated.
-        if (!UpdatePartitionTable(opener, "super", *metadata.get(), 1)) {
-            return false;
+        auto old_partition = "/dev/block/mapper/" + GetOtherPartitionName(name);
+        auto result = sm->OpenSnapshotWriter(params, {old_partition});
+        if (!result) {
+            return AssertionFailure() << "Cannot open snapshot for writing: " << name;
+        }
+        if (!result->Initialize()) {
+            return AssertionFailure() << "Cannot initialize snapshot for writing: " << name;
         }
 
-        for (const auto& partition : metadata->partitions) {
-            CreateLogicalPartitionParams params = {
-                    .block_device = fake_super,
-                    .metadata = metadata.get(),
-                    .partition = &partition,
-                    .force_writable = true,
-                    .timeout_ms = 10s,
-                    .device_name = GetPartitionName(partition) + "-base",
-            };
-            std::string ignore_path;
-            if (!CreateLogicalPartition(params, &ignore_path)) {
-                return false;
-            }
+        if (writer) {
+            *writer = std::move(result);
         }
-        return true;
+        return AssertionSuccess();
+    }
+
+    AssertionResult MapUpdateSnapshot(const std::string& name, std::string* path) {
+        TestPartitionOpener opener(fake_super);
+        CreateLogicalPartitionParams params{
+                .block_device = fake_super,
+                .metadata_slot = 1,
+                .partition_name = name,
+                .timeout_ms = 10s,
+                .partition_opener = &opener,
+        };
+
+        auto result = sm->MapUpdateSnapshot(params, path);
+        if (!result) {
+            return AssertionFailure() << "Cannot open snapshot for writing: " << name;
+        }
+        return AssertionSuccess();
     }
 
     AssertionResult DeleteSnapshotDevice(const std::string& snapshot) {
         AssertionResult res = AssertionSuccess();
         if (!(res = DeleteDevice(snapshot))) return res;
+        if (!sm->UnmapDmUserDevice(snapshot)) {
+            return AssertionFailure() << "Cannot delete dm-user device for " << snapshot;
+        }
         if (!(res = DeleteDevice(snapshot + "-inner"))) return res;
         if (!(res = DeleteDevice(snapshot + "-cow"))) return res;
         if (!image_manager_->UnmapImageIfExists(snapshot + "-cow-img")) {
             return AssertionFailure() << "Cannot unmap image " << snapshot << "-cow-img";
         }
         if (!(res = DeleteDevice(snapshot + "-base"))) return res;
+        if (!(res = DeleteDevice(snapshot + "-src"))) return res;
         return AssertionSuccess();
     }
 
@@ -289,37 +321,59 @@
 
     // Prepare A/B slot for a partition named "test_partition".
     AssertionResult PrepareOneSnapshot(uint64_t device_size,
-                                       std::string* out_snap_device = nullptr) {
-        std::string base_device, cow_device, snap_device;
-        if (!CreatePartition("test_partition_a", device_size)) {
-            return AssertionFailure();
+                                       std::unique_ptr<ISnapshotWriter>* writer = nullptr) {
+        lock_ = nullptr;
+
+        DeltaArchiveManifest manifest;
+
+        auto dynamic_partition_metadata = manifest.mutable_dynamic_partition_metadata();
+        dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+        dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
+
+        auto group = dynamic_partition_metadata->add_groups();
+        group->set_name("group");
+        group->set_size(device_size * 2);
+        group->add_partition_names("test_partition");
+
+        auto pu = manifest.add_partitions();
+        pu->set_partition_name("test_partition");
+        pu->set_estimate_cow_size(device_size);
+        SetSize(pu, device_size);
+
+        auto extent = pu->add_operations()->add_dst_extents();
+        extent->set_start_block(0);
+        if (device_size) {
+            extent->set_num_blocks(device_size / manifest.block_size());
         }
-        if (!MapUpdatePartitions()) {
-            return AssertionFailure();
+
+        TestPartitionOpener opener(fake_super);
+        auto builder = MetadataBuilder::New(opener, "super", 0);
+        if (!builder) {
+            return AssertionFailure() << "Failed to open MetadataBuilder";
         }
-        if (!dm_.GetDmDevicePathByName("test_partition_b-base", &base_device)) {
-            return AssertionFailure();
+        builder->AddGroup("group_a", 16_GiB);
+        builder->AddGroup("group_b", 16_GiB);
+        if (!CreatePartition(builder.get(), "test_partition_a", device_size, nullptr, "group_a")) {
+            return AssertionFailure() << "Failed create test_partition_a";
         }
-        SnapshotStatus status;
-        status.set_name("test_partition_b");
-        status.set_device_size(device_size);
-        status.set_snapshot_size(device_size);
-        status.set_cow_file_size(device_size);
-        if (!sm->CreateSnapshot(lock_.get(), &status)) {
-            return AssertionFailure();
+
+        if (!sm->CreateUpdateSnapshots(manifest)) {
+            return AssertionFailure() << "Failed to create update snapshots";
         }
-        if (!CreateCowImage("test_partition_b")) {
-            return AssertionFailure();
+
+        if (writer) {
+            auto res = MapUpdateSnapshot("test_partition_b", writer);
+            if (!res) {
+                return res;
+            }
+        } else if (!IsCompressionEnabled()) {
+            std::string ignore;
+            if (!MapUpdateSnapshot("test_partition_b", &ignore)) {
+                return AssertionFailure() << "Failed to map test_partition_b";
+            }
         }
-        if (!MapCowImage("test_partition_b", 10s, &cow_device)) {
-            return AssertionFailure();
-        }
-        if (!sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, cow_device, 10s,
-                             &snap_device)) {
-            return AssertionFailure();
-        }
-        if (out_snap_device) {
-            *out_snap_device = std::move(snap_device);
+        if (!AcquireLock()) {
+            return AssertionFailure() << "Failed to acquire lock";
         }
         return AssertionSuccess();
     }
@@ -328,20 +382,38 @@
     AssertionResult SimulateReboot() {
         lock_ = nullptr;
         if (!sm->FinishedSnapshotWrites(false)) {
-            return AssertionFailure();
+            return AssertionFailure() << "Failed to finish snapshot writes";
         }
-        if (!dm_.DeleteDevice("test_partition_b")) {
-            return AssertionFailure();
+        if (!sm->UnmapUpdateSnapshot("test_partition_b")) {
+            return AssertionFailure() << "Failed to unmap COW for test_partition_b";
         }
-        if (!DestroyLogicalPartition("test_partition_b-base")) {
-            return AssertionFailure();
+        if (!dm_.DeleteDeviceIfExists("test_partition_b")) {
+            return AssertionFailure() << "Failed to delete test_partition_b";
         }
-        if (!sm->UnmapCowImage("test_partition_b")) {
-            return AssertionFailure();
+        if (!dm_.DeleteDeviceIfExists("test_partition_b-base")) {
+            return AssertionFailure() << "Failed to destroy test_partition_b-base";
         }
         return AssertionSuccess();
     }
 
+    std::unique_ptr<SnapshotManager> NewManagerForFirstStageMount(
+            const std::string& slot_suffix = "_a") {
+        auto info = new TestDeviceInfo(fake_super, slot_suffix);
+        return NewManagerForFirstStageMount(info);
+    }
+
+    std::unique_ptr<SnapshotManager> NewManagerForFirstStageMount(TestDeviceInfo* info) {
+        info->set_first_stage_init(true);
+        auto init = SnapshotManager::NewForFirstStageMount(info);
+        if (!init) {
+            return nullptr;
+        }
+        init->SetUeventRegenCallback([](const std::string& device) -> bool {
+            return android::fs_mgr::WaitForFile(device, snapshot_timeout_);
+        });
+        return init;
+    }
+
     static constexpr std::chrono::milliseconds snapshot_timeout_ = 5s;
     DeviceMapper& dm_;
     std::unique_ptr<SnapshotManager::LockedFile> lock_;
@@ -352,13 +424,21 @@
 TEST_F(SnapshotTest, CreateSnapshot) {
     ASSERT_TRUE(AcquireLock());
 
+    PartitionCowCreator cow_creator;
+    cow_creator.compression_enabled = IsCompressionEnabled();
+    if (cow_creator.compression_enabled) {
+        cow_creator.compression_algorithm = "gz";
+    } else {
+        cow_creator.compression_algorithm = "none";
+    }
+
     static const uint64_t kDeviceSize = 1024 * 1024;
     SnapshotStatus status;
     status.set_name("test-snapshot");
     status.set_device_size(kDeviceSize);
     status.set_snapshot_size(kDeviceSize);
     status.set_cow_file_size(kDeviceSize);
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status));
     ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::vector<std::string> snapshots;
@@ -373,6 +453,8 @@
         ASSERT_EQ(status.state(), SnapshotState::CREATED);
         ASSERT_EQ(status.device_size(), kDeviceSize);
         ASSERT_EQ(status.snapshot_size(), kDeviceSize);
+        ASSERT_EQ(status.compression_enabled(), cow_creator.compression_enabled);
+        ASSERT_EQ(status.compression_algorithm(), cow_creator.compression_algorithm);
     }
 
     ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot"));
@@ -383,38 +465,16 @@
 TEST_F(SnapshotTest, MapSnapshot) {
     ASSERT_TRUE(AcquireLock());
 
+    PartitionCowCreator cow_creator;
+    cow_creator.compression_enabled = IsCompressionEnabled();
+
     static const uint64_t kDeviceSize = 1024 * 1024;
     SnapshotStatus status;
     status.set_name("test-snapshot");
     status.set_device_size(kDeviceSize);
     status.set_snapshot_size(kDeviceSize);
     status.set_cow_file_size(kDeviceSize);
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
-    ASSERT_TRUE(CreateCowImage("test-snapshot"));
-
-    std::string base_device;
-    ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
-
-    std::string cow_device;
-    ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
-
-    std::string snap_device;
-    ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
-                                &snap_device));
-    ASSERT_TRUE(android::base::StartsWith(snap_device, "/dev/block/dm-"));
-}
-
-TEST_F(SnapshotTest, MapPartialSnapshot) {
-    ASSERT_TRUE(AcquireLock());
-
-    static const uint64_t kSnapshotSize = 1024 * 1024;
-    static const uint64_t kDeviceSize = 1024 * 1024 * 2;
-    SnapshotStatus status;
-    status.set_name("test-snapshot");
-    status.set_device_size(kDeviceSize);
-    status.set_snapshot_size(kSnapshotSize);
-    status.set_cow_file_size(kSnapshotSize);
-    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
+    ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status));
     ASSERT_TRUE(CreateCowImage("test-snapshot"));
 
     std::string base_device;
@@ -439,8 +499,7 @@
 TEST_F(SnapshotTest, CleanFirstStageMount) {
     // If there's no update in progress, there should be no first-stage mount
     // needed.
-    TestDeviceInfo* info = new TestDeviceInfo(fake_super);
-    auto sm = SnapshotManager::NewForFirstStageMount(info);
+    auto sm = NewManagerForFirstStageMount();
     ASSERT_NE(sm, nullptr);
     ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount());
 }
@@ -449,8 +508,7 @@
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // We didn't change the slot, so we shouldn't need snapshots.
-    TestDeviceInfo* info = new TestDeviceInfo(fake_super);
-    auto sm = SnapshotManager::NewForFirstStageMount(info);
+    auto sm = NewManagerForFirstStageMount();
     ASSERT_NE(sm, nullptr);
     ASSERT_FALSE(sm->NeedSnapshotsInFirstStageMount());
 
@@ -462,32 +520,30 @@
     ASSERT_TRUE(AcquireLock());
 
     static const uint64_t kDeviceSize = 1024 * 1024;
-    std::string snap_device;
-    ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &snap_device));
 
-    std::string test_string = "This is a test string.";
-    {
-        unique_fd fd(open(snap_device.c_str(), O_RDWR | O_CLOEXEC | O_SYNC));
-        ASSERT_GE(fd, 0);
-        ASSERT_TRUE(android::base::WriteFully(fd, test_string.data(), test_string.size()));
-    }
-
-    // Note: we know there is no inner/outer dm device since we didn't request
-    // a linear segment.
-    DeviceMapper::TargetInfo target;
-    ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target));
-    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+    std::unique_ptr<ISnapshotWriter> writer;
+    ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer));
 
     // Release the lock.
     lock_ = nullptr;
 
+    std::string test_string = "This is a test string.";
+    test_string.resize(writer->options().block_size);
+    ASSERT_TRUE(writer->AddRawBlocks(0, test_string.data(), test_string.size()));
+    ASSERT_TRUE(writer->Finalize());
+    writer = nullptr;
+
     // Done updating.
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
+    ASSERT_TRUE(sm->UnmapUpdateSnapshot("test_partition_b"));
+
     test_device->set_slot_suffix("_b");
+    ASSERT_TRUE(sm->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
     ASSERT_TRUE(sm->InitiateMerge());
 
     // The device should have been switched to a snapshot-merge target.
+    DeviceMapper::TargetInfo target;
     ASSERT_TRUE(sm->IsSnapshotDevice("test_partition_b", &target));
     ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
 
@@ -503,7 +559,7 @@
     // Test that we can read back the string we wrote to the snapshot. Note
     // that the base device is gone now. |snap_device| contains the correct
     // partition.
-    unique_fd fd(open(snap_device.c_str(), O_RDONLY | O_CLOEXEC));
+    unique_fd fd(open("/dev/block/mapper/test_partition_b", O_RDONLY | O_CLOEXEC));
     ASSERT_GE(fd, 0);
 
     std::string buffer(test_string.size(), '\0');
@@ -518,7 +574,7 @@
     ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
     ASSERT_TRUE(SimulateReboot());
 
-    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    auto init = NewManagerForFirstStageMount("_b");
     ASSERT_NE(init, nullptr);
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -529,10 +585,14 @@
     SnapshotStatus status;
     ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
     ASSERT_EQ(status.state(), SnapshotState::CREATED);
+    if (IsCompressionEnabled()) {
+        ASSERT_EQ(status.compression_algorithm(), "gz");
+    } else {
+        ASSERT_EQ(status.compression_algorithm(), "none");
+    }
 
     DeviceMapper::TargetInfo target;
-    auto dm_name = init->GetSnapshotDeviceName("test_partition_b", status);
-    ASSERT_TRUE(init->IsSnapshotDevice(dm_name, &target));
+    ASSERT_TRUE(init->IsSnapshotDevice("test_partition_b", &target));
     ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
 }
 
@@ -547,7 +607,7 @@
     FormatFakeSuper();
     ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize));
 
-    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    auto init = NewManagerForFirstStageMount("_b");
     ASSERT_NE(init, nullptr);
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -559,8 +619,7 @@
 
     // We should not get a snapshot device now.
     DeviceMapper::TargetInfo target;
-    auto dm_name = init->GetSnapshotDeviceName("test_partition_b", status);
-    ASSERT_FALSE(init->IsSnapshotDevice(dm_name, &target));
+    ASSERT_FALSE(init->IsSnapshotDevice("test_partition_b", &target));
 
     // We should see a cancelled update as well.
     lock_ = nullptr;
@@ -574,7 +633,7 @@
     ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize));
     ASSERT_TRUE(SimulateReboot());
 
-    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    auto init = NewManagerForFirstStageMount("_b");
     ASSERT_NE(init, nullptr);
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -591,8 +650,8 @@
 
     // Because the status is Merging, we must call ProcessUpdateState, which should
     // detect a cancelled update.
-    ASSERT_EQ(sm->ProcessUpdateState(), UpdateState::Cancelled);
-    ASSERT_EQ(sm->GetUpdateState(), UpdateState::None);
+    ASSERT_EQ(init->ProcessUpdateState(), UpdateState::Cancelled);
+    ASSERT_EQ(init->GetUpdateState(), UpdateState::None);
 }
 
 TEST_F(SnapshotTest, UpdateBootControlHal) {
@@ -620,6 +679,18 @@
     ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING);
 }
 
+TEST_F(SnapshotTest, MergeFailureCode) {
+    ASSERT_TRUE(AcquireLock());
+
+    ASSERT_TRUE(sm->WriteUpdateState(lock_.get(), UpdateState::MergeFailed,
+                                     MergeFailureCode::ListSnapshots));
+    ASSERT_EQ(test_device->merge_status(), MergeStatus::MERGING);
+
+    SnapshotUpdateStatus status = sm->ReadSnapshotUpdateStatus(lock_.get());
+    ASSERT_EQ(status.state(), UpdateState::MergeFailed);
+    ASSERT_EQ(status.merge_failure_code(), MergeFailureCode::ListSnapshots);
+}
+
 enum class Request { UNKNOWN, LOCK_SHARED, LOCK_EXCLUSIVE, UNLOCK, EXIT };
 std::ostream& operator<<(std::ostream& os, Request request) {
     switch (request) {
@@ -783,11 +854,15 @@
 
         opener_ = std::make_unique<TestPartitionOpener>(fake_super);
 
+        auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
+        dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+        dynamic_partition_metadata->set_cow_version(android::snapshot::kCowVersionMajor);
+
         // Create a fake update package metadata.
         // Not using full name "system", "vendor", "product" because these names collide with the
         // mapped partitions on the running device.
         // Each test modifies manifest_ slightly to indicate changes to the partition layout.
-        group_ = manifest_.mutable_dynamic_partition_metadata()->add_groups();
+        group_ = dynamic_partition_metadata->add_groups();
         group_->set_name("group");
         group_->set_size(kGroupSize);
         group_->add_partition_names("sys");
@@ -795,15 +870,15 @@
         group_->add_partition_names("prd");
         sys_ = manifest_.add_partitions();
         sys_->set_partition_name("sys");
-        sys_->set_estimate_cow_size(6_MiB);
+        sys_->set_estimate_cow_size(2_MiB);
         SetSize(sys_, 3_MiB);
         vnd_ = manifest_.add_partitions();
         vnd_->set_partition_name("vnd");
-        vnd_->set_estimate_cow_size(6_MiB);
+        vnd_->set_estimate_cow_size(2_MiB);
         SetSize(vnd_, 3_MiB);
         prd_ = manifest_.add_partitions();
         prd_->set_partition_name("prd");
-        prd_->set_estimate_cow_size(6_MiB);
+        prd_->set_estimate_cow_size(2_MiB);
         SetSize(prd_, 3_MiB);
 
         // Initialize source partition metadata using |manifest_|.
@@ -899,49 +974,9 @@
         return AssertionSuccess();
     }
 
-    AssertionResult MapUpdateSnapshot(const std::string& name,
-                                      std::unique_ptr<ICowWriter>* writer) {
-        CreateLogicalPartitionParams params{
-                .block_device = fake_super,
-                .metadata_slot = 1,
-                .partition_name = name,
-                .timeout_ms = 10s,
-                .partition_opener = opener_.get(),
-        };
-
-        auto result = sm->OpenSnapshotWriter(params, {});
-        if (!result) {
-            return AssertionFailure() << "Cannot open snapshot for writing: " << name;
-        }
-        if (!result->Initialize()) {
-            return AssertionFailure() << "Cannot initialize snapshot for writing: " << name;
-        }
-
-        if (writer) {
-            *writer = std::move(result);
-        }
-        return AssertionSuccess();
-    }
-
-    AssertionResult MapUpdateSnapshot(const std::string& name, std::string* path) {
-        CreateLogicalPartitionParams params{
-                .block_device = fake_super,
-                .metadata_slot = 1,
-                .partition_name = name,
-                .timeout_ms = 10s,
-                .partition_opener = opener_.get(),
-        };
-
-        auto result = sm->MapUpdateSnapshot(params, path);
-        if (!result) {
-            return AssertionFailure() << "Cannot open snapshot for writing: " << name;
-        }
-        return AssertionSuccess();
-    }
-
-    AssertionResult MapUpdateSnapshot(const std::string& name) {
+    AssertionResult MapOneUpdateSnapshot(const std::string& name) {
         if (IsCompressionEnabled()) {
-            std::unique_ptr<ICowWriter> writer;
+            std::unique_ptr<ISnapshotWriter> writer;
             return MapUpdateSnapshot(name, &writer);
         } else {
             std::string path;
@@ -951,7 +986,7 @@
 
     AssertionResult WriteSnapshotAndHash(const std::string& name) {
         if (IsCompressionEnabled()) {
-            std::unique_ptr<ICowWriter> writer;
+            std::unique_ptr<ISnapshotWriter> writer;
             auto res = MapUpdateSnapshot(name, &writer);
             if (!res) {
                 return res;
@@ -980,10 +1015,46 @@
                                   << ", hash: " << hashes_[name];
     }
 
+    // Generate a snapshot that moves all the upper blocks down to the start.
+    // It doesn't really matter the order, we just want copies that reference
+    // blocks that won't exist if the partition shrinks.
+    AssertionResult ShiftAllSnapshotBlocks(const std::string& name, uint64_t old_size) {
+        std::unique_ptr<ISnapshotWriter> writer;
+        if (auto res = MapUpdateSnapshot(name, &writer); !res) {
+            return res;
+        }
+        if (!writer->options().max_blocks || !*writer->options().max_blocks) {
+            return AssertionFailure() << "No max blocks set for " << name << " writer";
+        }
+
+        uint64_t src_block = (old_size / writer->options().block_size) - 1;
+        uint64_t dst_block = 0;
+        uint64_t max_blocks = *writer->options().max_blocks;
+        while (dst_block < max_blocks && dst_block < src_block) {
+            if (!writer->AddCopy(dst_block, src_block)) {
+                return AssertionFailure() << "Unable to add copy for " << name << " for blocks "
+                                          << src_block << ", " << dst_block;
+            }
+            dst_block++;
+            src_block--;
+        }
+        if (!writer->Finalize()) {
+            return AssertionFailure() << "Unable to finalize writer for " << name;
+        }
+
+        auto hash = HashSnapshot(writer.get());
+        if (hash.empty()) {
+            return AssertionFailure() << "Unable to hash snapshot writer for " << name;
+        }
+        hashes_[name] = hash;
+
+        return AssertionSuccess();
+    }
+
     AssertionResult MapUpdateSnapshots(const std::vector<std::string>& names = {"sys_b", "vnd_b",
                                                                                 "prd_b"}) {
         for (const auto& name : names) {
-            auto res = MapUpdateSnapshot(name);
+            auto res = MapOneUpdateSnapshot(name);
             if (!res) {
                 return res;
             }
@@ -1031,11 +1102,17 @@
         ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
     }
 
-    // Grow all partitions.
+    // Grow all partitions. Set |prd| large enough that |sys| and |vnd|'s COWs
+    // fit in super, but not |prd|.
     constexpr uint64_t partition_size = 3788_KiB;
     SetSize(sys_, partition_size);
     SetSize(vnd_, partition_size);
-    SetSize(prd_, partition_size);
+    SetSize(prd_, 18_MiB);
+
+    // Make sure |prd| does not fit in super at all. On VABC, this means we
+    // fake an extra large COW for |vnd| to fill up super.
+    vnd_->set_estimate_cow_size(30_MiB);
+    prd_->set_estimate_cow_size(30_MiB);
 
     AddOperationForPartitions();
 
@@ -1047,11 +1124,7 @@
     auto tgt = MetadataBuilder::New(*opener_, "super", 1);
     ASSERT_NE(tgt, nullptr);
     ASSERT_NE(nullptr, tgt->FindPartition("sys_b-cow"));
-    if (IsCompressionEnabled()) {
-        ASSERT_EQ(nullptr, tgt->FindPartition("vnd_b-cow"));
-    } else {
-        ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow"));
-    }
+    ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow"));
     ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow"));
 
     // Write some data to target partitions.
@@ -1070,7 +1143,7 @@
     ASSERT_TRUE(UnmapAll());
 
     // After reboot, init does first stage mount.
-    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    auto init = NewManagerForFirstStageMount("_b");
     ASSERT_NE(init, nullptr);
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -1085,8 +1158,138 @@
 
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
+    ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
+    {
+        // We should have started in SECOND_PHASE since nothing shrinks.
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+        auto status = init->ReadSnapshotUpdateStatus(local_lock.get());
+        ASSERT_EQ(status.merge_phase(), MergePhase::SECOND_PHASE);
+    }
     ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
 
+    // Make sure the second phase ran and deleted snapshots.
+    {
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+        std::vector<std::string> snapshots;
+        ASSERT_TRUE(init->ListSnapshots(local_lock.get(), &snapshots));
+        ASSERT_TRUE(snapshots.empty());
+    }
+
+    // Check that the target partitions have the same content after the merge.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name))
+                << "Content of " << name << " changes after the merge";
+    }
+}
+
+// Test that shrinking and growing partitions at the same time is handled
+// correctly in VABC.
+TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) {
+    if (!IsCompressionEnabled()) {
+        // b/179111359
+        GTEST_SKIP() << "Skipping Virtual A/B Compression test";
+    }
+
+    // OTA client blindly unmaps all partitions that are possibly mapped.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
+    }
+
+    auto old_sys_size = GetSize(sys_);
+    auto old_prd_size = GetSize(prd_);
+
+    // Grow |sys| but shrink |prd|.
+    SetSize(sys_, old_sys_size * 2);
+    sys_->set_estimate_cow_size(8_MiB);
+    SetSize(prd_, old_prd_size / 2);
+    prd_->set_estimate_cow_size(1_MiB);
+
+    AddOperationForPartitions();
+
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    // Check that the old partition sizes were saved correctly.
+    {
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+
+        SnapshotStatus status;
+        ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "prd_b", &status));
+        ASSERT_EQ(status.old_partition_size(), 3145728);
+        ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "sys_b", &status));
+        ASSERT_EQ(status.old_partition_size(), 3145728);
+    }
+
+    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
+    ASSERT_TRUE(WriteSnapshotAndHash("vnd_b"));
+    ASSERT_TRUE(ShiftAllSnapshotBlocks("prd_b", old_prd_size));
+
+    sync();
+
+    // Assert that source partitions aren't affected.
+    for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // After reboot, init does first stage mount.
+    auto init = NewManagerForFirstStageMount("_b");
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+    auto indicator = sm->GetRollbackIndicatorPath();
+    ASSERT_NE(access(indicator.c_str(), R_OK), 0);
+
+    // Check that the target partitions have the same content.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    // Initiate the merge and wait for it to be completed.
+    ASSERT_TRUE(init->InitiateMerge());
+    ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
+    {
+        // Check that the merge phase is FIRST_PHASE until at least one call
+        // to ProcessUpdateState() occurs.
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+        auto status = init->ReadSnapshotUpdateStatus(local_lock.get());
+        ASSERT_EQ(status.merge_phase(), MergePhase::FIRST_PHASE);
+    }
+
+    // Simulate shutting down the device and creating partitions again.
+    ASSERT_TRUE(UnmapAll());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+    // Check that we used the correct types after rebooting mid-merge.
+    DeviceMapper::TargetInfo target;
+    ASSERT_TRUE(init->IsSnapshotDevice("prd_b", &target));
+    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+    ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
+    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+    ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
+    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+
+    // Complete the merge.
+    ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+
+    // Make sure the second phase ran and deleted snapshots.
+    {
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+        std::vector<std::string> snapshots;
+        ASSERT_TRUE(init->ListSnapshots(local_lock.get(), &snapshots));
+        ASSERT_TRUE(snapshots.empty());
+    }
+
     // Check that the target partitions have the same content after the merge.
     for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
         ASSERT_TRUE(IsPartitionUnchanged(name))
@@ -1109,6 +1312,7 @@
     SetSize(sys_, 4_MiB);  // grows
     SetSize(vnd_, 2_MiB);  // shrinks
     // prd_b is unchanged
+    ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
     ASSERT_EQ(4_MiB, GetSnapshotSize("sys_b").value_or(0));
 }
@@ -1118,6 +1322,7 @@
 TEST_F(SnapshotUpdateTest, CowPartitionDoNotTakeOldPartitions) {
     SetSize(sys_, 2_MiB);  // shrinks
     // vnd_b and prd_b are unchanged.
+    ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
 
     auto tgt = MetadataBuilder::New(*opener_, "super", 1);
@@ -1202,7 +1407,7 @@
     ASSERT_TRUE(UnmapAll());
 
     // After reboot, init does first stage mount.
-    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    auto init = NewManagerForFirstStageMount("_b");
     ASSERT_NE(init, nullptr);
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -1214,7 +1419,7 @@
 
     // Simulate shutting down the device again.
     ASSERT_TRUE(UnmapAll());
-    init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_a"));
+    init = NewManagerForFirstStageMount("_a");
     ASSERT_NE(init, nullptr);
     ASSERT_FALSE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -1241,6 +1446,11 @@
 
 // Test that at the second update, old COW partition spaces are reclaimed.
 TEST_F(SnapshotUpdateTest, ReclaimCow) {
+    // Make sure VABC cows are small enough that they fit in fake_super.
+    sys_->set_estimate_cow_size(64_KiB);
+    vnd_->set_estimate_cow_size(64_KiB);
+    prd_->set_estimate_cow_size(64_KiB);
+
     // Execute the first update.
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
@@ -1251,7 +1461,7 @@
     ASSERT_TRUE(UnmapAll());
 
     // After reboot, init does first stage mount.
-    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    auto init = NewManagerForFirstStageMount("_b");
     ASSERT_NE(init, nullptr);
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -1357,9 +1567,13 @@
 
 TEST_F(SnapshotUpdateTest, MergeCannotRemoveCow) {
     // Make source partitions as big as possible to force COW image to be created.
-    SetSize(sys_, 5_MiB);
-    SetSize(vnd_, 5_MiB);
-    SetSize(prd_, 5_MiB);
+    SetSize(sys_, 10_MiB);
+    SetSize(vnd_, 10_MiB);
+    SetSize(prd_, 10_MiB);
+    sys_->set_estimate_cow_size(12_MiB);
+    vnd_->set_estimate_cow_size(12_MiB);
+    prd_->set_estimate_cow_size(12_MiB);
+
     src_ = MetadataBuilder::New(*opener_, "super", 0);
     ASSERT_NE(src_, nullptr);
     src_->RemoveGroupAndPartitions(group_->name() + "_a");
@@ -1387,8 +1601,8 @@
     ASSERT_TRUE(UnmapAll());
 
     // After reboot, init does first stage mount.
-    // Normally we should use NewForFirstStageMount, but if so, "gsid.mapped_image.sys_b-cow-img"
-    // won't be set.
+    // Normally we should use NewManagerForFirstStageMount, but if so,
+    // "gsid.mapped_image.sys_b-cow-img" won't be set.
     auto init = SnapshotManager::New(new TestDeviceInfo(fake_super, "_b"));
     ASSERT_NE(init, nullptr);
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -1495,7 +1709,7 @@
     ASSERT_TRUE(UnmapAll());
 
     // After reboot, init does first stage mount.
-    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    auto init = NewManagerForFirstStageMount("_b");
     ASSERT_NE(init, nullptr);
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -1509,7 +1723,7 @@
     // Simulate a reboot into recovery.
     auto test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
     test_device->set_recovery(true);
-    new_sm = SnapshotManager::NewForFirstStageMount(test_device.release());
+    new_sm = NewManagerForFirstStageMount(test_device.release());
 
     ASSERT_TRUE(new_sm->HandleImminentDataWipe());
     ASSERT_EQ(new_sm->GetUpdateState(), UpdateState::None);
@@ -1527,7 +1741,7 @@
     ASSERT_TRUE(UnmapAll());
 
     // After reboot, init does first stage mount.
-    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    auto init = NewManagerForFirstStageMount("_b");
     ASSERT_NE(init, nullptr);
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -1541,7 +1755,7 @@
     // Simulate a reboot into recovery.
     auto test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
     test_device->set_recovery(true);
-    new_sm = SnapshotManager::NewForFirstStageMount(test_device.release());
+    new_sm = NewManagerForFirstStageMount(test_device.release());
 
     ASSERT_TRUE(new_sm->FinishMergeInRecovery());
 
@@ -1551,12 +1765,12 @@
 
     // Finish the merge in a normal boot.
     test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
-    init = SnapshotManager::NewForFirstStageMount(test_device.release());
+    init = NewManagerForFirstStageMount(test_device.release());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
     init = nullptr;
 
     test_device = std::make_unique<TestDeviceInfo>(fake_super, "_b");
-    new_sm = SnapshotManager::NewForFirstStageMount(test_device.release());
+    new_sm = NewManagerForFirstStageMount(test_device.release());
     ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::MergeCompleted);
     ASSERT_EQ(new_sm->ProcessUpdateState(), UpdateState::None);
 }
@@ -1575,12 +1789,12 @@
     // Simulate a reboot into recovery.
     auto test_device = new TestDeviceInfo(fake_super, "_b");
     test_device->set_recovery(true);
-    auto new_sm = SnapshotManager::NewForFirstStageMount(test_device);
+    auto new_sm = NewManagerForFirstStageMount(test_device);
 
     ASSERT_TRUE(new_sm->HandleImminentDataWipe());
     // Manually mount metadata so that we can call GetUpdateState() below.
     MountMetadata();
-    EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::Unverified);
+    EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
     EXPECT_TRUE(test_device->IsSlotUnbootable(1));
     EXPECT_FALSE(test_device->IsSlotUnbootable(0));
 }
@@ -1600,7 +1814,7 @@
     // Simulate a rollback, with reboot into recovery.
     auto test_device = new TestDeviceInfo(fake_super, "_a");
     test_device->set_recovery(true);
-    auto new_sm = SnapshotManager::NewForFirstStageMount(test_device);
+    auto new_sm = NewManagerForFirstStageMount(test_device);
 
     ASSERT_TRUE(new_sm->HandleImminentDataWipe());
     EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
@@ -1628,7 +1842,7 @@
     // Simulate a reboot into recovery.
     auto test_device = new TestDeviceInfo(fake_super, "_b");
     test_device->set_recovery(true);
-    auto new_sm = SnapshotManager::NewForFirstStageMount(test_device);
+    auto new_sm = NewManagerForFirstStageMount(test_device);
 
     ASSERT_TRUE(new_sm->HandleImminentDataWipe());
     // Manually mount metadata so that we can call GetUpdateState() below.
@@ -1639,7 +1853,68 @@
 
     // Now reboot into new slot.
     test_device = new TestDeviceInfo(fake_super, "_b");
-    auto init = SnapshotManager::NewForFirstStageMount(test_device);
+    auto init = NewManagerForFirstStageMount(test_device);
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+    // Verify that we are on the downgraded build.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name)) << name;
+    }
+}
+
+// Test update package that requests data wipe.
+TEST_F(SnapshotUpdateTest, DataWipeWithStaleSnapshots) {
+    AddOperationForPartitions();
+
+    // Execute the update.
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    // Write some data to target partitions.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(WriteSnapshotAndHash(name)) << name;
+    }
+
+    // Create a stale snapshot that should not exist.
+    {
+        ASSERT_TRUE(AcquireLock());
+
+        PartitionCowCreator cow_creator = {
+                .compression_enabled = IsCompressionEnabled(),
+                .compression_algorithm = IsCompressionEnabled() ? "gz" : "none",
+        };
+        SnapshotStatus status;
+        status.set_name("sys_a");
+        status.set_device_size(1_MiB);
+        status.set_snapshot_size(2_MiB);
+        status.set_cow_partition_size(2_MiB);
+
+        ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &cow_creator, &status));
+        lock_ = nullptr;
+
+        ASSERT_TRUE(sm->EnsureImageManager());
+        ASSERT_TRUE(sm->image_manager()->CreateBackingImage("sys_a", 1_MiB, 0));
+    }
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(true /* wipe */));
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // Simulate a reboot into recovery.
+    auto test_device = new TestDeviceInfo(fake_super, "_b");
+    test_device->set_recovery(true);
+    auto new_sm = NewManagerForFirstStageMount(test_device);
+
+    ASSERT_TRUE(new_sm->HandleImminentDataWipe());
+    // Manually mount metadata so that we can call GetUpdateState() below.
+    MountMetadata();
+    EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
+    ASSERT_FALSE(test_device->IsSlotUnbootable(1));
+    ASSERT_FALSE(test_device->IsSlotUnbootable(0));
+
+    // Now reboot into new slot.
+    test_device = new TestDeviceInfo(fake_super, "_b");
+    auto init = NewManagerForFirstStageMount(test_device);
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
     // Verify that we are on the downgraded build.
     for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
@@ -1657,6 +1932,8 @@
     SetSize(sys_, partition_size);
     AddOperation(sys_, data_size);
 
+    sys_->set_estimate_cow_size(partition_size + data_size);
+
     // Set hastree extents.
     sys_->mutable_hash_tree_data_extent()->set_start_block(0);
     sys_->mutable_hash_tree_data_extent()->set_num_blocks(data_size / block_size);
@@ -1685,7 +1962,7 @@
     ASSERT_TRUE(UnmapAll());
 
     // After reboot, init does first stage mount.
-    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    auto init = NewManagerForFirstStageMount("_b");
     ASSERT_NE(init, nullptr);
     ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
     ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
@@ -1697,6 +1974,10 @@
 
 // Test for overflow bit after update
 TEST_F(SnapshotUpdateTest, Overflow) {
+    if (IsCompressionEnabled()) {
+        GTEST_SKIP() << "No overflow bit set for userspace COWs";
+    }
+
     const auto actual_write_size = GetSize(sys_);
     const auto declared_write_size = actual_write_size - 1_MiB;
 
@@ -1724,12 +2005,15 @@
     auto userdata = std::make_unique<LowSpaceUserdata>();
     ASSERT_TRUE(userdata->Init(kMaxFree));
 
-    // Grow all partitions to 5_MiB, total 15_MiB. This requires 15 MiB of CoW space. After
-    // using the empty space in super (< 1 MiB), it uses at least 14 MiB of /userdata space.
-    constexpr uint64_t partition_size = 5_MiB;
+    // Grow all partitions to 10_MiB, total 30_MiB. This requires 30 MiB of CoW space. After
+    // using the empty space in super (< 1 MiB), it uses 30 MiB of /userdata space.
+    constexpr uint64_t partition_size = 10_MiB;
     SetSize(sys_, partition_size);
     SetSize(vnd_, partition_size);
     SetSize(prd_, partition_size);
+    sys_->set_estimate_cow_size(partition_size);
+    vnd_->set_estimate_cow_size(partition_size);
+    prd_->set_estimate_cow_size(partition_size);
 
     AddOperationForPartitions();
 
@@ -1739,7 +2023,7 @@
     ASSERT_FALSE(res);
     ASSERT_EQ(Return::ErrorCode::NO_SPACE, res.error_code());
     ASSERT_GE(res.required_size(), 14_MiB);
-    ASSERT_LT(res.required_size(), 15_MiB);
+    ASSERT_LT(res.required_size(), 40_MiB);
 }
 
 class AutoKill final {
@@ -1760,9 +2044,6 @@
         GTEST_SKIP() << "Skipping Virtual A/B Compression test";
     }
 
-    AutoKill auto_kill(StartFirstStageSnapuserd());
-    ASSERT_TRUE(auto_kill.valid());
-
     // Ensure a connection to the second-stage daemon, but use the first-stage
     // code paths thereafter.
     ASSERT_TRUE(sm->EnsureSnapuserdConnected());
@@ -1776,7 +2057,7 @@
     ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
     ASSERT_TRUE(UnmapAll());
 
-    auto init = SnapshotManager::NewForFirstStageMount(new TestDeviceInfo(fake_super, "_b"));
+    auto init = NewManagerForFirstStageMount("_b");
     ASSERT_NE(init, nullptr);
 
     ASSERT_TRUE(init->EnsureSnapuserdConnected());
@@ -1788,7 +2069,14 @@
     ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow-init", F_OK), 0);
     ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), -1);
 
-    ASSERT_TRUE(init->PerformSecondStageTransition());
+    ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
+
+    // :TODO: this is a workaround to ensure the handler list stays empty. We
+    // should make this test more like actual init, and spawn two copies of
+    // snapuserd, given how many other tests we now have for normal snapuserd.
+    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-user-cow-init"));
+    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-user-cow-init"));
+    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-user-cow-init"));
 
     // The control device should have been renamed.
     ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-user-cow-init", 10s));
@@ -1808,6 +2096,36 @@
 
     // Read bytes back and verify they match the cache.
     ASSERT_TRUE(IsPartitionUnchanged("sys_b"));
+
+    ASSERT_TRUE(sm->UnmapAllSnapshots());
+}
+
+TEST_F(SnapshotUpdateTest, CancelOnTargetSlot) {
+    AddOperationForPartitions();
+
+    // Execute the update from B->A.
+    test_device->set_slot_suffix("_b");
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    std::string path;
+    ASSERT_TRUE(CreateLogicalPartition(
+            CreateLogicalPartitionParams{
+                    .block_device = fake_super,
+                    .metadata_slot = 0,
+                    .partition_name = "sys_a",
+                    .timeout_ms = 1s,
+                    .partition_opener = opener_.get(),
+            },
+            &path));
+
+    // Hold sys_a open so it can't be unmapped.
+    unique_fd fd(open(path.c_str(), O_RDONLY));
+
+    // Switch back to "A", make sure we can cancel. Instead of unmapping sys_a
+    // we should simply delete the old snapshots.
+    test_device->set_slot_suffix("_a");
+    ASSERT_TRUE(sm->BeginUpdate());
 }
 
 class FlashAfterUpdateTest : public SnapshotUpdateTest,
@@ -1890,8 +2208,7 @@
     ASSERT_TRUE(UnmapAll());
 
     // Simulate reboot. After reboot, init does first stage mount.
-    auto init = SnapshotManager::NewForFirstStageMount(
-            new TestDeviceInfo(fake_super, flashed_slot_suffix));
+    auto init = NewManagerForFirstStageMount(flashed_slot_suffix);
     ASSERT_NE(init, nullptr);
 
     if (flashed_slot && after_merge) {
@@ -1906,8 +2223,12 @@
 
     // There should be no snapshot to merge.
     auto new_sm = SnapshotManager::New(new TestDeviceInfo(fake_super, flashed_slot_suffix));
-    // update_enigne calls ProcessUpdateState first -- should see Cancelled.
-    ASSERT_EQ(UpdateState::Cancelled, new_sm->ProcessUpdateState());
+    if (flashed_slot == 0 && after_merge) {
+        ASSERT_EQ(UpdateState::MergeCompleted, new_sm->ProcessUpdateState());
+    } else {
+        // update_engine calls ProcessUpdateState first -- should see Cancelled.
+        ASSERT_EQ(UpdateState::Cancelled, new_sm->ProcessUpdateState());
+    }
 
     // Next OTA calls CancelUpdate no matter what.
     ASSERT_TRUE(new_sm->CancelUpdate());
@@ -1988,9 +2309,27 @@
     void TearDown() override;
 
   private:
+    bool CreateFakeSuper();
+
     std::unique_ptr<IImageManager> super_images_;
 };
 
+bool SnapshotTestEnvironment::CreateFakeSuper() {
+    // Create and map the fake super partition.
+    static constexpr int kImageFlags =
+            IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL;
+    if (!super_images_->CreateBackingImage("fake-super", kSuperSize, kImageFlags)) {
+        LOG(ERROR) << "Could not create fake super partition";
+        return false;
+    }
+    if (!super_images_->MapImageDevice("fake-super", 10s, &fake_super)) {
+        LOG(ERROR) << "Could not map fake super partition";
+        return false;
+    }
+    test_device->set_fake_super(fake_super);
+    return true;
+}
+
 void SnapshotTestEnvironment::SetUp() {
     // b/163082876: GTEST_SKIP in Environment will make atest report incorrect results. Until
     // that is fixed, don't call GTEST_SKIP here, but instead call GTEST_SKIP in individual test
@@ -2016,27 +2355,36 @@
     sm = SnapshotManager::New(test_device);
     ASSERT_NE(nullptr, sm) << "Could not create snapshot manager";
 
+    // Use a separate image manager for our fake super partition.
+    super_images_ = IImageManager::Open("ota/test/super", 10s);
+    ASSERT_NE(nullptr, super_images_) << "Could not create image manager";
+
+    // Map the old image if one exists so we can safely unmap everything that
+    // depends on it.
+    bool recreate_fake_super;
+    if (super_images_->BackingImageExists("fake-super")) {
+        if (super_images_->IsImageMapped("fake-super")) {
+            ASSERT_TRUE(super_images_->GetMappedImageDevice("fake-super", &fake_super));
+        } else {
+            ASSERT_TRUE(super_images_->MapImageDevice("fake-super", 10s, &fake_super));
+        }
+        test_device->set_fake_super(fake_super);
+        recreate_fake_super = true;
+    } else {
+        ASSERT_TRUE(CreateFakeSuper());
+        recreate_fake_super = false;
+    }
+
     // Clean up previous run.
     MetadataMountedTest().TearDown();
     SnapshotUpdateTest().Cleanup();
     SnapshotTest().Cleanup();
 
-    // Use a separate image manager for our fake super partition.
-    super_images_ = IImageManager::Open("ota/test/super", 10s);
-    ASSERT_NE(nullptr, super_images_) << "Could not create image manager";
-
-    // Clean up any old copy.
-    DeleteBackingImage(super_images_.get(), "fake-super");
-
-    // Create and map the fake super partition.
-    static constexpr int kImageFlags =
-            IImageManager::CREATE_IMAGE_DEFAULT | IImageManager::CREATE_IMAGE_ZERO_FILL;
-    ASSERT_TRUE(super_images_->CreateBackingImage("fake-super", kSuperSize, kImageFlags))
-            << "Could not create fake super partition";
-
-    ASSERT_TRUE(super_images_->MapImageDevice("fake-super", 10s, &fake_super))
-            << "Could not map fake super partition";
-    test_device->set_fake_super(fake_super);
+    if (recreate_fake_super) {
+        // Clean up any old copy.
+        DeleteBackingImage(super_images_.get(), "fake-super");
+        ASSERT_TRUE(CreateFakeSuper());
+    }
 }
 
 void SnapshotTestEnvironment::TearDown() {
diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp
index 8e379e4..080f3b7 100644
--- a/fs_mgr/libsnapshot/snapshot_writer.cpp
+++ b/fs_mgr/libsnapshot/snapshot_writer.cpp
@@ -90,7 +90,9 @@
     }
 
     const auto& cow_options = options();
-    reader->SetBlockDeviceSize(*cow_options.max_blocks * cow_options.block_size);
+    if (cow_options.max_blocks) {
+        reader->SetBlockDeviceSize(*cow_options.max_blocks * cow_options.block_size);
+    }
 
     return reader;
 }
diff --git a/fs_mgr/libsnapshot/snapshot_writer_test.cpp b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
new file mode 100644
index 0000000..da48eb9
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapshot_writer_test.cpp
@@ -0,0 +1,62 @@
+//
+// 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 <libsnapshot/snapshot.h>
+
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+#include <libsnapshot/snapshot_writer.h>
+#include <payload_consumer/file_descriptor.h>
+
+namespace android::snapshot {
+class CompressedSnapshotWriterTest : public ::testing::Test {
+  public:
+    static constexpr size_t BLOCK_SIZE = 4096;
+};
+
+TEST_F(CompressedSnapshotWriterTest, ReadAfterWrite) {
+    TemporaryFile cow_device_file{};
+    android::snapshot::CowOptions options{.block_size = BLOCK_SIZE};
+    android::snapshot::CompressedSnapshotWriter snapshot_writer{options};
+    snapshot_writer.SetCowDevice(android::base::unique_fd{cow_device_file.fd});
+    snapshot_writer.Initialize();
+    std::vector<unsigned char> buffer;
+    buffer.resize(BLOCK_SIZE);
+    std::fill(buffer.begin(), buffer.end(), 123);
+
+    ASSERT_TRUE(snapshot_writer.AddRawBlocks(0, buffer.data(), buffer.size()));
+    ASSERT_TRUE(snapshot_writer.Finalize());
+    auto cow_reader = snapshot_writer.OpenReader();
+    ASSERT_NE(cow_reader, nullptr);
+    ASSERT_TRUE(snapshot_writer.AddRawBlocks(1, buffer.data(), buffer.size()));
+    ASSERT_TRUE(snapshot_writer.AddRawBlocks(2, buffer.data(), buffer.size()));
+    ASSERT_TRUE(snapshot_writer.Finalize());
+    // After wrigin some data, if we call OpenReader() again, writes should
+    // be visible to the newly opened reader. update_engine relies on this
+    // behavior for verity writes.
+    cow_reader = snapshot_writer.OpenReader();
+    ASSERT_NE(cow_reader, nullptr);
+    std::vector<unsigned char> read_back;
+    read_back.resize(buffer.size());
+    cow_reader->Seek(BLOCK_SIZE, SEEK_SET);
+    const auto bytes_read = cow_reader->Read(read_back.data(), read_back.size());
+    ASSERT_EQ((size_t)(bytes_read), BLOCK_SIZE);
+    ASSERT_EQ(read_back, buffer);
+}
+
+}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index a44de84..5eb2003 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -48,6 +48,17 @@
     return SnapshotManager::New()->Dump(std::cout);
 }
 
+bool MapCmdHandler(int, char** argv) {
+    android::base::InitLogging(argv, &android::base::StderrLogger);
+    using namespace std::chrono_literals;
+    return SnapshotManager::New()->MapAllSnapshots(5000ms);
+}
+
+bool UnmapCmdHandler(int, char** argv) {
+    android::base::InitLogging(argv, &android::base::StderrLogger);
+    return SnapshotManager::New()->UnmapAllSnapshots();
+}
+
 bool MergeCmdHandler(int /*argc*/, char** argv) {
     android::base::InitLogging(argv, &android::base::StderrLogger);
     LOG(WARNING) << "Deprecated. Call update_engine_client --merge instead.";
@@ -58,6 +69,8 @@
         // clang-format off
         {"dump", DumpCmdHandler},
         {"merge", MergeCmdHandler},
+        {"map", MapCmdHandler},
+        {"unmap", UnmapCmdHandler},
         // clang-format on
 };
 
diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp
index 954699c..03c2ef6 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-#include <csignal>
+#include "snapuserd.h"
 
-#include <libsnapshot/snapuserd.h>
+#include <csignal>
+#include <optional>
+#include <set>
+
 #include <libsnapshot/snapuserd_client.h>
-#include <libsnapshot/snapuserd_daemon.h>
-#include <libsnapshot/snapuserd_server.h>
 
 namespace android {
 namespace snapshot {
@@ -28,40 +29,8 @@
 using namespace android::dm;
 using android::base::unique_fd;
 
-static constexpr size_t PAYLOAD_SIZE = (1UL << 16);
-
-static_assert(PAYLOAD_SIZE >= BLOCK_SIZE);
-
-void BufferSink::Initialize(size_t size) {
-    buffer_size_ = size;
-    buffer_offset_ = 0;
-    buffer_ = std::make_unique<uint8_t[]>(size);
-}
-
-void* BufferSink::GetPayloadBuffer(size_t size) {
-    if ((buffer_size_ - buffer_offset_) < size) return nullptr;
-
-    char* buffer = reinterpret_cast<char*>(GetBufPtr());
-    struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
-    return (char*)msg->payload.buf + buffer_offset_;
-}
-
-void* BufferSink::GetBuffer(size_t requested, size_t* actual) {
-    void* buf = GetPayloadBuffer(requested);
-    if (!buf) {
-        *actual = 0;
-        return nullptr;
-    }
-    *actual = requested;
-    return buf;
-}
-
-struct dm_user_header* BufferSink::GetHeaderPtr() {
-    CHECK(sizeof(struct dm_user_header) <= buffer_size_);
-    char* buf = reinterpret_cast<char*>(GetBufPtr());
-    struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
-    return header;
-}
+#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
+#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
 
 Snapuserd::Snapuserd(const std::string& misc_name, const std::string& cow_device,
                      const std::string& backing_device) {
@@ -71,372 +40,212 @@
     control_device_ = "/dev/dm-user/" + misc_name;
 }
 
-// Construct kernel COW header in memory
-// This header will be in sector 0. The IO
-// request will always be 4k. After constructing
-// the header, zero out the remaining block.
-void Snapuserd::ConstructKernelCowHeader() {
-    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SIZE);
-    CHECK(buffer != nullptr);
+bool Snapuserd::InitializeWorkers() {
+    for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) {
+        std::unique_ptr<WorkerThread> wt = std::make_unique<WorkerThread>(
+                cow_device_, backing_store_device_, control_device_, misc_name_, GetSharedPtr());
 
-    memset(buffer, 0, BLOCK_SIZE);
+        worker_threads_.push_back(std::move(wt));
+    }
 
-    struct disk_header* dh = reinterpret_cast<struct disk_header*>(buffer);
-
-    dh->magic = SNAP_MAGIC;
-    dh->valid = SNAPSHOT_VALID;
-    dh->version = SNAPSHOT_DISK_VERSION;
-    dh->chunk_size = CHUNK_SIZE;
+    read_ahead_thread_ = std::make_unique<ReadAheadThread>(cow_device_, backing_store_device_,
+                                                           misc_name_, GetSharedPtr());
+    return true;
 }
 
-// Start the replace operation. This will read the
-// internal COW format and if the block is compressed,
-// it will be de-compressed.
-bool Snapuserd::ProcessReplaceOp(const CowOperation* cow_op) {
-    if (!reader_->ReadData(*cow_op, &bufsink_)) {
-        LOG(ERROR) << "ReadData failed for chunk: " << cow_op->new_block;
+bool Snapuserd::CommitMerge(int num_merge_ops) {
+    struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+    ch->num_merge_ops += num_merge_ops;
+
+    if (read_ahead_feature_ && read_ahead_ops_.size() > 0) {
+        struct BufferState* ra_state = GetBufferState();
+        ra_state->read_ahead_state = kCowReadAheadInProgress;
+    }
+
+    int ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+    if (ret < 0) {
+        PLOG(ERROR) << "msync header failed: " << ret;
         return false;
     }
 
+    merge_initiated_ = true;
+
     return true;
 }
 
-// Start the copy operation. This will read the backing
-// block device which is represented by cow_op->source.
-bool Snapuserd::ProcessCopyOp(const CowOperation* cow_op) {
-    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SIZE);
-    CHECK(buffer != nullptr);
-
-    // Issue a single 4K IO. However, this can be optimized
-    // if the successive blocks are contiguous.
-    if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SIZE,
-                                          cow_op->source * BLOCK_SIZE)) {
-        LOG(ERROR) << "Copy-op failed. Read from backing store at: " << cow_op->source;
-        return false;
+void Snapuserd::PrepareReadAhead() {
+    if (!read_ahead_feature_) {
+        return;
     }
 
-    return true;
-}
-
-bool Snapuserd::ProcessZeroOp() {
-    // Zero out the entire block
-    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SIZE);
-    CHECK(buffer != nullptr);
-
-    memset(buffer, 0, BLOCK_SIZE);
-    return true;
-}
-
-/*
- * Read the data of size bytes from a given chunk.
- *
- * Kernel can potentially merge the blocks if the
- * successive chunks are contiguous. For chunk size of 8,
- * there can be 256 disk exceptions; and if
- * all 256 disk exceptions are contiguous, kernel can merge
- * them into a single IO.
- *
- * Since each chunk in the disk exception
- * mapping represents a 4k block, kernel can potentially
- * issue 256*4k = 1M IO in one shot.
- *
- * Even though kernel assumes that the blocks are
- * contiguous, we need to split the 1M IO into 4k chunks
- * as each operation represents 4k and it can either be:
- *
- * 1: Replace operation
- * 2: Copy operation
- * 3: Zero operation
- *
- */
-bool Snapuserd::ReadData(chunk_t chunk, size_t size) {
-    size_t read_size = size;
-    bool ret = true;
-    chunk_t chunk_key = chunk;
-    uint32_t stride;
-    lldiv_t divresult;
-
-    // Size should always be aligned
-    CHECK((read_size & (BLOCK_SIZE - 1)) == 0);
-
-    while (read_size > 0) {
-        const CowOperation* cow_op = chunk_map_[chunk_key];
-        CHECK(cow_op != nullptr);
-
-        switch (cow_op->type) {
-            case kCowReplaceOp: {
-                ret = ProcessReplaceOp(cow_op);
-                break;
-            }
-
-            case kCowZeroOp: {
-                ret = ProcessZeroOp();
-                break;
-            }
-
-            case kCowCopyOp: {
-                ret = ProcessCopyOp(cow_op);
-                break;
-            }
-
-            default: {
-                LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
-                ret = false;
-                break;
-            }
-        }
-
-        if (!ret) {
-            LOG(ERROR) << "ReadData failed for operation: " << cow_op->type;
-            return false;
-        }
-
-        // Update the buffer offset
-        bufsink_.UpdateBufferOffset(BLOCK_SIZE);
-
-        read_size -= BLOCK_SIZE;
-
-        // Start iterating the chunk incrementally; Since while
-        // constructing the metadata, we know that the chunk IDs
-        // are contiguous
-        chunk_key += 1;
-
-        if (cow_op->type == kCowCopyOp) CHECK(read_size == 0);
-
-        // This is similar to the way when chunk IDs were assigned
-        // in ReadMetadata().
-        //
-        // Skip if the chunk id represents a metadata chunk.
-        stride = exceptions_per_area_ + 1;
-        divresult = lldiv(chunk_key, stride);
-        if (divresult.rem == NUM_SNAPSHOT_HDR_CHUNKS) {
-            // Crossing exception boundary. Kernel will never
-            // issue IO which is spanning between a data chunk
-            // and a metadata chunk. This should be perfectly aligned.
-            //
-            // Since the input read_size is 4k aligned, we will
-            // always end up reading all 256 data chunks in one area.
-            // Thus, every multiple of 4K IO represents 256 data chunks
-            CHECK(read_size == 0);
-            break;
-        }
-    }
-
-    // Reset the buffer offset
-    bufsink_.ResetBufferOffset();
-    return ret;
-}
-
-/*
- * dm-snap does prefetch reads while reading disk-exceptions.
- * By default, prefetch value is set to 12; this means that
- * dm-snap will issue 12 areas wherein each area is a 4k page
- * of disk-exceptions.
- *
- * If during prefetch, if the chunk-id seen is beyond the
- * actual number of metadata page, fill the buffer with zero.
- * When dm-snap starts parsing the buffer, it will stop
- * reading metadata page once the buffer content is zero.
- */
-bool Snapuserd::ZerofillDiskExceptions(size_t read_size) {
-    size_t size = exceptions_per_area_ * sizeof(struct disk_exception);
-
-    if (read_size > size) {
-        return false;
-    }
-
-    void* buffer = bufsink_.GetPayloadBuffer(size);
-    CHECK(buffer != nullptr);
-
-    memset(buffer, 0, size);
-    return true;
-}
-
-/*
- * A disk exception is a simple mapping of old_chunk to new_chunk.
- * When dm-snapshot device is created, kernel requests these mapping.
- *
- * Each disk exception is of size 16 bytes. Thus a single 4k page can
- * have:
- *
- * exceptions_per_area_ = 4096/16 = 256. This entire 4k page
- * is considered a metadata page and it is represented by chunk ID.
- *
- * Convert the chunk ID to index into the vector which gives us
- * the metadata page.
- */
-bool Snapuserd::ReadDiskExceptions(chunk_t chunk, size_t read_size) {
-    uint32_t stride = exceptions_per_area_ + 1;
-    size_t size;
-
-    // ChunkID to vector index
-    lldiv_t divresult = lldiv(chunk, stride);
-
-    if (divresult.quot < vec_.size()) {
-        size = exceptions_per_area_ * sizeof(struct disk_exception);
-
-        if (read_size > size) {
-            return false;
-        }
-
-        void* buffer = bufsink_.GetPayloadBuffer(size);
-        CHECK(buffer != nullptr);
-
-        memcpy(buffer, vec_[divresult.quot].get(), size);
+    struct BufferState* ra_state = GetBufferState();
+    // Check if the data has to be re-constructed from COW device
+    if (ra_state->read_ahead_state == kCowReadAheadDone) {
+        populate_data_from_cow_ = true;
     } else {
-        return ZerofillDiskExceptions(read_size);
+        populate_data_from_cow_ = false;
     }
 
+    StartReadAhead();
+}
+
+bool Snapuserd::GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer) {
+    if (!lock->owns_lock()) {
+        SNAP_LOG(ERROR) << "GetRABuffer - Lock not held";
+        return false;
+    }
+    std::unordered_map<uint64_t, void*>::iterator it = read_ahead_buffer_map_.find(block);
+
+    // This will be true only for IO's generated as part of reading a root
+    // filesystem. IO's related to merge should always be in read-ahead cache.
+    if (it == read_ahead_buffer_map_.end()) {
+        return false;
+    }
+
+    // Theoretically, we can send the data back from the read-ahead buffer
+    // all the way to the kernel without memcpy. However, if the IO is
+    // un-aligned, the wrapper function will need to touch the read-ahead
+    // buffers and transitions will be bit more complicated.
+    memcpy(buffer, it->second, BLOCK_SZ);
     return true;
 }
 
-loff_t Snapuserd::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
-                                      int* unmerged_exceptions) {
-    loff_t offset = 0;
-    *unmerged_exceptions = 0;
+// ========== State transition functions for read-ahead operations ===========
 
-    while (*unmerged_exceptions <= exceptions_per_area_) {
-        struct disk_exception* merged_de =
-                reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
-        struct disk_exception* cow_de =
-                reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
-
-        // Unmerged op by the kernel
-        if (merged_de->old_chunk != 0) {
-            CHECK(merged_de->new_chunk != 0);
-            CHECK(merged_de->old_chunk == cow_de->old_chunk);
-            CHECK(merged_de->new_chunk == cow_de->new_chunk);
-
-            offset += sizeof(struct disk_exception);
-            *unmerged_exceptions += 1;
-            continue;
-        }
-
-        // Merge complete on this exception. However, we don't know how many
-        // merged in this cycle; hence break here.
-        CHECK(merged_de->new_chunk == 0);
-        CHECK(merged_de->old_chunk == 0);
-
-        break;
+bool Snapuserd::GetReadAheadPopulatedBuffer(uint64_t block, void* buffer) {
+    if (!read_ahead_feature_) {
+        return false;
     }
 
-    CHECK(!(*unmerged_exceptions == exceptions_per_area_));
-
-    LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
-    return offset;
-}
-
-int Snapuserd::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
-                                    int unmerged_exceptions) {
-    int merged_ops_cur_iter = 0;
-
-    // Find the operations which are merged in this cycle.
-    while ((unmerged_exceptions + merged_ops_cur_iter) <= exceptions_per_area_) {
-        struct disk_exception* merged_de =
-                reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
-        struct disk_exception* cow_de =
-                reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
-
-        CHECK(merged_de->new_chunk == 0);
-        CHECK(merged_de->old_chunk == 0);
-
-        if (cow_de->new_chunk != 0) {
-            merged_ops_cur_iter += 1;
-            offset += sizeof(struct disk_exception);
-            // zero out to indicate that operation is merged.
-            cow_de->old_chunk = 0;
-            cow_de->new_chunk = 0;
-        } else if (cow_de->old_chunk == 0) {
-            // Already merged op in previous iteration or
-            // This could also represent a partially filled area.
-            //
-            // If the op was merged in previous cycle, we don't have
-            // to count them.
-            CHECK(cow_de->new_chunk == 0);
-            break;
-        } else {
-            LOG(ERROR) << "Error in merge operation. Found invalid metadata";
-            LOG(ERROR) << "merged_de-old-chunk: " << merged_de->old_chunk;
-            LOG(ERROR) << "merged_de-new-chunk: " << merged_de->new_chunk;
-            LOG(ERROR) << "cow_de-old-chunk: " << cow_de->old_chunk;
-            LOG(ERROR) << "cow_de-new-chunk: " << cow_de->new_chunk;
-            return -1;
-        }
-    }
-
-    return merged_ops_cur_iter;
-}
-
-bool Snapuserd::AdvanceMergedOps(int merged_ops_cur_iter) {
-    // Advance the merge operation pointer in the
-    // vector.
-    //
-    // cowop_iter_ is already initialized in ReadMetadata(). Just resume the
-    // merge process
-    while (!cowop_iter_->Done() && merged_ops_cur_iter) {
-        const CowOperation* cow_op = &cowop_iter_->Get();
-        CHECK(cow_op != nullptr);
-
-        if (cow_op->type == kCowFooterOp || cow_op->type == kCowLabelOp) {
-            cowop_iter_->Next();
-            continue;
-        }
-
-        if (!(cow_op->type == kCowReplaceOp || cow_op->type == kCowZeroOp ||
-              cow_op->type == kCowCopyOp)) {
-            LOG(ERROR) << "Unknown operation-type found during merge: " << cow_op->type;
+    {
+        std::unique_lock<std::mutex> lock(lock_);
+        if (io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE) {
             return false;
         }
 
-        merged_ops_cur_iter -= 1;
-        LOG(DEBUG) << "Merge op found of type " << cow_op->type
-                   << "Pending-merge-ops: " << merged_ops_cur_iter;
-        cowop_iter_->Next();
+        if (io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS) {
+            return GetRABuffer(&lock, block, buffer);
+        }
     }
 
-    if (cowop_iter_->Done()) {
-        CHECK(merged_ops_cur_iter == 0);
-        LOG(DEBUG) << "All cow operations merged successfully in this cycle";
+    {
+        // Read-ahead thread IO is in-progress. Wait for it to complete
+        std::unique_lock<std::mutex> lock(lock_);
+        while (!(io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE ||
+                 io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS)) {
+            cv.wait(lock);
+        }
+
+        return GetRABuffer(&lock, block, buffer);
+    }
+}
+
+// This is invoked by read-ahead thread waiting for merge IO's
+// to complete
+bool Snapuserd::WaitForMergeToComplete() {
+    {
+        std::unique_lock<std::mutex> lock(lock_);
+        while (!(io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_BEGIN ||
+                 io_state_ == READ_AHEAD_IO_TRANSITION::IO_TERMINATED)) {
+            cv.wait(lock);
+        }
+
+        if (io_state_ == READ_AHEAD_IO_TRANSITION::IO_TERMINATED) {
+            return false;
+        }
+
+        io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_IN_PROGRESS;
+        return true;
+    }
+}
+
+// This is invoked during the launch of worker threads. We wait
+// for read-ahead thread to by fully up before worker threads
+// are launched; else we will have a race between worker threads
+// and read-ahead thread specifically during re-construction.
+bool Snapuserd::WaitForReadAheadToStart() {
+    {
+        std::unique_lock<std::mutex> lock(lock_);
+        while (!(io_state_ == READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS ||
+                 io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE)) {
+            cv.wait(lock);
+        }
+
+        if (io_state_ == READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE) {
+            return false;
+        }
+
+        return true;
+    }
+}
+
+// Invoked by worker threads when a sequence of merge operation
+// is complete notifying read-ahead thread to make forward
+// progress.
+void Snapuserd::StartReadAhead() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_BEGIN;
     }
 
+    cv.notify_one();
+}
+
+void Snapuserd::MergeCompleted() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = READ_AHEAD_IO_TRANSITION::IO_TERMINATED;
+    }
+
+    cv.notify_one();
+}
+
+bool Snapuserd::ReadAheadIOCompleted(bool sync) {
+    if (sync) {
+        // Flush the entire buffer region
+        int ret = msync(mapped_addr_, total_mapped_addr_length_, MS_SYNC);
+        if (ret < 0) {
+            PLOG(ERROR) << "msync failed after ReadAheadIOCompleted: " << ret;
+            return false;
+        }
+
+        // Metadata and data are synced. Now, update the state.
+        // We need to update the state after flushing data; if there is a crash
+        // when read-ahead IO is in progress, the state of data in the COW file
+        // is unknown. kCowReadAheadDone acts as a checkpoint wherein the data
+        // in the scratch space is good and during next reboot, read-ahead thread
+        // can safely re-construct the data.
+        struct BufferState* ra_state = GetBufferState();
+        ra_state->read_ahead_state = kCowReadAheadDone;
+
+        ret = msync(mapped_addr_, BLOCK_SZ, MS_SYNC);
+        if (ret < 0) {
+            PLOG(ERROR) << "msync failed to flush Readahead completion state...";
+            return false;
+        }
+    }
+
+    // Notify the worker threads
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = READ_AHEAD_IO_TRANSITION::IO_IN_PROGRESS;
+    }
+
+    cv.notify_all();
     return true;
 }
 
-bool Snapuserd::ProcessMergeComplete(chunk_t chunk, void* buffer) {
-    uint32_t stride = exceptions_per_area_ + 1;
-    CowHeader header;
-
-    if (!reader_->GetHeader(&header)) {
-        LOG(ERROR) << "Failed to get header";
-        return false;
+void Snapuserd::ReadAheadIOFailed() {
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        io_state_ = READ_AHEAD_IO_TRANSITION::READ_AHEAD_FAILURE;
     }
 
-    // ChunkID to vector index
-    lldiv_t divresult = lldiv(chunk, stride);
-    CHECK(divresult.quot < vec_.size());
-    LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk << " Metadata-Index: " << divresult.quot;
-
-    int unmerged_exceptions = 0;
-    loff_t offset = GetMergeStartOffset(buffer, vec_[divresult.quot].get(), &unmerged_exceptions);
-
-    int merged_ops_cur_iter =
-            GetNumberOfMergedOps(buffer, vec_[divresult.quot].get(), offset, unmerged_exceptions);
-
-    // There should be at least one operation merged in this cycle
-    CHECK(merged_ops_cur_iter > 0);
-    if (!AdvanceMergedOps(merged_ops_cur_iter)) return false;
-
-    header.num_merge_ops += merged_ops_cur_iter;
-    reader_->UpdateMergeProgress(merged_ops_cur_iter);
-    if (!writer_->CommitMerge(merged_ops_cur_iter)) {
-        LOG(ERROR) << "CommitMerge failed...";
-        return false;
-    }
-
-    LOG(DEBUG) << "Merge success";
-    return true;
+    cv.notify_all();
 }
 
+//========== End of state transition functions ====================
+
 bool Snapuserd::IsChunkIdMetadata(chunk_t chunk) {
     uint32_t stride = exceptions_per_area_ + 1;
     lldiv_t divresult = lldiv(chunk, stride);
@@ -455,6 +264,18 @@
     return next_chunk;
 }
 
+void Snapuserd::CheckMergeCompletionStatus() {
+    if (!merge_initiated_) {
+        SNAP_LOG(INFO) << "Merge was not initiated. Total-data-ops: " << reader_->total_data_ops();
+        return;
+    }
+
+    struct CowHeader* ch = reinterpret_cast<struct CowHeader*>(mapped_addr_);
+
+    SNAP_LOG(INFO) << "Merge-status: Total-Merged-ops: " << ch->num_merge_ops
+                   << " Total-data-ops: " << reader_->total_data_ops();
+}
+
 /*
  * Read the metadata from COW device and
  * construct the metadata as required by the kernel.
@@ -485,12 +306,13 @@
  *      during merge; specifically when the merge operation has dependency.
  *      These dependencies can only happen during copy operations.
  *
- *      To avoid this problem, we make sure that no two copy-operations
- *      do not have contiguous chunk IDs. Additionally, we make sure
- *      that each copy operation is merged individually.
+ *      To avoid this problem, we make sure overlap copy operations
+ *      are not batch merged.
  * 6: Use a monotonically increasing chunk number to assign the
  *    new_chunk
- * 7: Each chunk-id represents either a: Metadata page or b: Data page
+ * 7: Each chunk-id represents either
+ *        a: Metadata page or
+ *        b: Data page
  * 8: Chunk-id representing a data page is stored in a map.
  * 9: Chunk-id representing a metadata page is converted into a vector
  *    index. We store this in vector as kernel requests metadata during
@@ -510,27 +332,33 @@
     reader_ = std::make_unique<CowReader>();
     CowHeader header;
     CowOptions options;
-    bool prev_copy_op = false;
     bool metadata_found = false;
+    int replace_ops = 0, zero_ops = 0, copy_ops = 0;
 
-    LOG(DEBUG) << "ReadMetadata Start...";
+    SNAP_LOG(DEBUG) << "ReadMetadata: Parsing cow file";
 
     if (!reader_->Parse(cow_fd_)) {
-        LOG(ERROR) << "Failed to parse";
+        SNAP_LOG(ERROR) << "Failed to parse";
         return false;
     }
 
     if (!reader_->GetHeader(&header)) {
-        LOG(ERROR) << "Failed to get header";
+        SNAP_LOG(ERROR) << "Failed to get header";
         return false;
     }
 
-    CHECK(header.block_size == BLOCK_SIZE);
+    if (!(header.block_size == BLOCK_SZ)) {
+        SNAP_LOG(ERROR) << "Invalid header block size found: " << header.block_size;
+        return false;
+    }
 
-    LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops;
+    reader_->InitializeMerge();
+    SNAP_LOG(DEBUG) << "Merge-ops: " << header.num_merge_ops;
 
-    writer_ = std::make_unique<CowWriter>(options);
-    writer_->InitializeMerge(cow_fd_.get(), &header);
+    if (!MmapMetadata()) {
+        SNAP_LOG(ERROR) << "mmap failed";
+        return false;
+    }
 
     // Initialize the iterator for reading metadata
     cowop_riter_ = reader_->GetRevOpIter();
@@ -539,7 +367,8 @@
 
     // Start from chunk number 2. Chunk 0 represents header and chunk 1
     // represents first metadata page.
-    chunk_t next_free = NUM_SNAPSHOT_HDR_CHUNKS + 1;
+    chunk_t data_chunk_id = NUM_SNAPSHOT_HDR_CHUNKS + 1;
+    size_t num_ops = 0;
 
     loff_t offset = 0;
     std::unique_ptr<uint8_t[]> de_ptr =
@@ -549,45 +378,48 @@
     // is 0. When Area is not filled completely with all 256 exceptions,
     // this memset will ensure that metadata read is completed.
     memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
-    size_t num_ops = 0;
 
     while (!cowop_riter_->Done()) {
         const CowOperation* cow_op = &cowop_riter_->Get();
         struct disk_exception* de =
                 reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
 
-        if (cow_op->type == kCowFooterOp || cow_op->type == kCowLabelOp) {
+        if (IsMetadataOp(*cow_op)) {
             cowop_riter_->Next();
             continue;
         }
 
-        if (!(cow_op->type == kCowReplaceOp || cow_op->type == kCowZeroOp ||
-              cow_op->type == kCowCopyOp)) {
-            LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
-            return false;
-        }
-
         metadata_found = true;
-        if ((cow_op->type == kCowCopyOp || prev_copy_op)) {
-            next_free = GetNextAllocatableChunkId(next_free);
+        // This loop will handle all the replace and zero ops.
+        // We will handle the copy ops later as it requires special
+        // handling of assigning chunk-id's. Furthermore, we make
+        // sure that replace/zero and copy ops are not batch merged; hence,
+        // the bump in the chunk_id before break of this loop
+        if (cow_op->type == kCowCopyOp) {
+            data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
+            break;
         }
 
-        prev_copy_op = (cow_op->type == kCowCopyOp);
+        if (cow_op->type == kCowReplaceOp) {
+            replace_ops++;
+        } else if (cow_op->type == kCowZeroOp) {
+            zero_ops++;
+        }
 
         // Construct the disk-exception
         de->old_chunk = cow_op->new_block;
-        de->new_chunk = next_free;
+        de->new_chunk = data_chunk_id;
 
-        LOG(DEBUG) << "Old-chunk: " << de->old_chunk << "New-chunk: " << de->new_chunk;
 
         // Store operation pointer.
-        chunk_map_[next_free] = cow_op;
+        chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), cow_op));
         num_ops += 1;
-
         offset += sizeof(struct disk_exception);
-
         cowop_riter_->Next();
 
+        SNAP_LOG(DEBUG) << num_ops << ":"
+                        << " Old-chunk: " << de->old_chunk << " New-chunk: " << de->new_chunk;
+
         if (num_ops == exceptions_per_area_) {
             // Store it in vector at the right index. This maps the chunk-id to
             // vector index.
@@ -602,11 +434,210 @@
 
             if (cowop_riter_->Done()) {
                 vec_.push_back(std::move(de_ptr));
-                LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
             }
         }
 
-        next_free = GetNextAllocatableChunkId(next_free);
+        data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
+    }
+
+    int num_ra_ops_per_iter = ((GetBufferDataSize()) / BLOCK_SZ);
+    std::optional<chunk_t> prev_id = {};
+    std::map<uint64_t, const CowOperation*> map;
+    std::set<uint64_t> dest_blocks;
+    size_t pending_copy_ops = exceptions_per_area_ - num_ops;
+    uint64_t total_copy_ops = reader_->total_copy_ops();
+
+    SNAP_LOG(DEBUG) << " Processing copy-ops at Area: " << vec_.size()
+                    << " Number of replace/zero ops completed in this area: " << num_ops
+                    << " Pending copy ops for this area: " << pending_copy_ops;
+    while (!cowop_riter_->Done()) {
+        do {
+            const CowOperation* cow_op = &cowop_riter_->Get();
+            if (IsMetadataOp(*cow_op)) {
+                cowop_riter_->Next();
+                continue;
+            }
+
+            // We have two cases specific cases:
+            //
+            // =====================================================
+            // Case 1: Overlapping copy regions
+            //
+            // Ex:
+            //
+            // Source -> Destination
+            //
+            // 1: 15 -> 18
+            // 2: 16 -> 19
+            // 3: 17 -> 20
+            // 4: 18 -> 21
+            // 5: 19 -> 22
+            // 6: 20 -> 23
+            //
+            // We have 6 copy operations to be executed in OTA and there is a overlap. Update-engine
+            // will write to COW file as follows:
+            //
+            // Op-1: 20 -> 23
+            // Op-2: 19 -> 22
+            // Op-3: 18 -> 21
+            // Op-4: 17 -> 20
+            // Op-5: 16 -> 19
+            // Op-6: 15 -> 18
+            //
+            // Note that the blocks numbers are contiguous. Hence, all 6 copy
+            // operations can be batch merged. However, that will be
+            // problematic if we have a crash as block 20, 19, 18 would have
+            // been overwritten and hence subsequent recovery may end up with
+            // a silent data corruption when op-1, op-2 and op-3 are
+            // re-executed.
+            //
+            // To address the above problem, read-ahead thread will
+            // read all the 6 source blocks, cache them in the scratch
+            // space of the COW file. During merge, read-ahead
+            // thread will serve the blocks from the read-ahead cache.
+            // If there is a crash during merge; on subsequent reboot,
+            // read-ahead thread will recover the data from the
+            // scratch space and re-construct it thereby there
+            // is no loss of data.
+            //
+            //===========================================================
+            //
+            // Case 2:
+            //
+            // Let's say we have three copy operations written to COW file
+            // in the following order:
+            //
+            // op-1: 15 -> 18
+            // op-2: 16 -> 19
+            // op-3: 17 -> 20
+            //
+            // As aforementioned, kernel will initiate merge in reverse order.
+            // Hence, we will read these ops in reverse order so that all these
+            // ops are exectued in the same order as requested. Thus, we will
+            // read the metadata in reverse order and for the kernel it will
+            // look like:
+            //
+            // op-3: 17 -> 20
+            // op-2: 16 -> 19
+            // op-1: 15 -> 18   <-- Merge starts here in the kernel
+            //
+            // Now, this is problematic as kernel cannot batch merge them.
+            //
+            // Merge sequence will look like:
+            //
+            // Merge-1: op-1: 15 -> 18
+            // Merge-2: op-2: 16 -> 19
+            // Merge-3: op-3: 17 -> 20
+            //
+            // We have three merge operations.
+            //
+            // Even though the blocks are contiguous, kernel can batch merge
+            // them if the blocks are in descending order. Update engine
+            // addresses this issue partially for overlapping operations as
+            // we see that op-1 to op-3 and op-4 to op-6 operatiosn are in
+            // descending order. However, if the copy operations are not
+            // overlapping, update engine cannot write these blocks
+            // in descending order. Hence, we will try to address it.
+            // Thus, we will send these blocks to the kernel and it will
+            // look like:
+            //
+            // op-3: 15 -> 18
+            // op-2: 16 -> 19
+            // op-1: 17 -> 20  <-- Merge starts here in the kernel
+            //
+            // Now with this change, we can batch merge all these three
+            // operations. Merge sequence will look like:
+            //
+            // Merge-1: {op-1: 17 -> 20, op-2: 16 -> 19, op-3: 15 -> 18}
+            //
+            // Note that we have changed the ordering of merge; However, this
+            // is ok as each of these copy operations are independent and there
+            // is no overlap.
+            //
+            //===================================================================
+            if (prev_id.has_value()) {
+                chunk_t diff = (cow_op->new_block > prev_id.value())
+                                       ? (cow_op->new_block - prev_id.value())
+                                       : (prev_id.value() - cow_op->new_block);
+                if (diff != 1) {
+                    break;
+                }
+
+                if (dest_blocks.count(cow_op->new_block) || map.count(cow_op->source) > 0) {
+                    break;
+                }
+            }
+            metadata_found = true;
+            pending_copy_ops -= 1;
+            map[cow_op->new_block] = cow_op;
+            dest_blocks.insert(cow_op->source);
+            prev_id = cow_op->new_block;
+            cowop_riter_->Next();
+        } while (!cowop_riter_->Done() && pending_copy_ops);
+
+        data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
+        SNAP_LOG(DEBUG) << "Batch Merge copy-ops of size: " << map.size()
+                        << " Area: " << vec_.size() << " Area offset: " << offset
+                        << " Pending-copy-ops in this area: " << pending_copy_ops;
+
+        for (auto it = map.begin(); it != map.end(); it++) {
+            struct disk_exception* de =
+                    reinterpret_cast<struct disk_exception*>((char*)de_ptr.get() + offset);
+            de->old_chunk = it->first;
+            de->new_chunk = data_chunk_id;
+
+            // Store operation pointer.
+            chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), it->second));
+            offset += sizeof(struct disk_exception);
+            num_ops += 1;
+            copy_ops++;
+            if (read_ahead_feature_) {
+                read_ahead_ops_.push_back(it->second);
+            }
+
+            SNAP_LOG(DEBUG) << num_ops << ":"
+                            << " Copy-op: "
+                            << " Old-chunk: " << de->old_chunk << " New-chunk: " << de->new_chunk;
+
+            if (num_ops == exceptions_per_area_) {
+                // Store it in vector at the right index. This maps the chunk-id to
+                // vector index.
+                vec_.push_back(std::move(de_ptr));
+                num_ops = 0;
+                offset = 0;
+
+                // Create buffer for next area
+                de_ptr = std::make_unique<uint8_t[]>(exceptions_per_area_ *
+                                                     sizeof(struct disk_exception));
+                memset(de_ptr.get(), 0, (exceptions_per_area_ * sizeof(struct disk_exception)));
+
+                if (cowop_riter_->Done()) {
+                    vec_.push_back(std::move(de_ptr));
+                    SNAP_LOG(DEBUG) << "ReadMetadata() completed; Number of Areas: " << vec_.size();
+                }
+
+                if (!(pending_copy_ops == 0)) {
+                    SNAP_LOG(ERROR)
+                            << "Invalid pending_copy_ops: expected: 0 found: " << pending_copy_ops;
+                    return false;
+                }
+                pending_copy_ops = exceptions_per_area_;
+            }
+
+            data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
+            total_copy_ops -= 1;
+            /*
+             * Split the number of ops based on the size of read-ahead buffer
+             * region. We need to ensure that kernel doesn't issue IO on blocks
+             * which are not read by the read-ahead thread.
+             */
+            if (read_ahead_feature_ && (total_copy_ops % num_ra_ops_per_iter == 0)) {
+                data_chunk_id = GetNextAllocatableChunkId(data_chunk_id);
+            }
+        }
+        map.clear();
+        dest_blocks.clear();
+        prev_id.reset();
     }
 
     // Partially filled area or there is no metadata
@@ -614,22 +645,62 @@
     // is aware that merge is completed.
     if (num_ops || !metadata_found) {
         vec_.push_back(std::move(de_ptr));
-        LOG(DEBUG) << "ReadMetadata() completed. Partially filled area num_ops: " << num_ops
-                   << "Areas : " << vec_.size();
+        SNAP_LOG(DEBUG) << "ReadMetadata() completed. Partially filled area num_ops: " << num_ops
+                        << "Areas : " << vec_.size();
     }
 
-    LOG(DEBUG) << "ReadMetadata() completed. chunk_id: " << next_free
-               << "Num Sector: " << ChunkToSector(next_free);
+    chunk_vec_.shrink_to_fit();
+    vec_.shrink_to_fit();
+    read_ahead_ops_.shrink_to_fit();
 
-    // Initialize the iterator for merging
-    cowop_iter_ = reader_->GetOpIter();
+    // Sort the vector based on sectors as we need this during un-aligned access
+    std::sort(chunk_vec_.begin(), chunk_vec_.end(), compare);
+
+    SNAP_LOG(INFO) << "ReadMetadata completed. Final-chunk-id: " << data_chunk_id
+                   << " Num Sector: " << ChunkToSector(data_chunk_id)
+                   << " Replace-ops: " << replace_ops << " Zero-ops: " << zero_ops
+                   << " Copy-ops: " << copy_ops << " Areas: " << vec_.size()
+                   << " Num-ops-merged: " << header.num_merge_ops
+                   << " Total-data-ops: " << reader_->total_data_ops();
 
     // Total number of sectors required for creating dm-user device
-    num_sectors_ = ChunkToSector(next_free);
-    metadata_read_done_ = true;
+    num_sectors_ = ChunkToSector(data_chunk_id);
+    merge_initiated_ = false;
+    PrepareReadAhead();
+
     return true;
 }
 
+bool Snapuserd::MmapMetadata() {
+    CowHeader header;
+    reader_->GetHeader(&header);
+
+    if (header.major_version >= 2 && header.buffer_size > 0) {
+        total_mapped_addr_length_ = header.header_size + BUFFER_REGION_DEFAULT_SIZE;
+        read_ahead_feature_ = true;
+    } else {
+        // mmap the first 4k page - older COW format
+        total_mapped_addr_length_ = BLOCK_SZ;
+        read_ahead_feature_ = false;
+    }
+
+    mapped_addr_ = mmap(NULL, total_mapped_addr_length_, PROT_READ | PROT_WRITE, MAP_SHARED,
+                        cow_fd_.get(), 0);
+    if (mapped_addr_ == MAP_FAILED) {
+        SNAP_LOG(ERROR) << "mmap metadata failed";
+        return false;
+    }
+
+    return true;
+}
+
+void Snapuserd::UnmapBufferRegion() {
+    int ret = munmap(mapped_addr_, total_mapped_addr_length_);
+    if (ret < 0) {
+        SNAP_PLOG(ERROR) << "munmap failed";
+    }
+}
+
 void MyLogger(android::base::LogId, android::base::LogSeverity severity, const char*, const char*,
               unsigned int, const char* message) {
     if (severity == android::base::ERROR) {
@@ -639,223 +710,112 @@
     }
 }
 
-// Read Header from dm-user misc device. This gives
-// us the sector number for which IO is issued by dm-snapshot device
-bool Snapuserd::ReadDmUserHeader() {
-    if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
-        PLOG(ERROR) << "ReadDmUserHeader failed";
-        return false;
-    }
-
-    return true;
-}
-
-// Send the payload/data back to dm-user misc device.
-bool Snapuserd::WriteDmUserPayload(size_t size) {
-    if (!android::base::WriteFully(ctrl_fd_, bufsink_.GetBufPtr(),
-                                   sizeof(struct dm_user_header) + size)) {
-        PLOG(ERROR) << "Write to dm-user failed";
-        return false;
-    }
-
-    return true;
-}
-
-bool Snapuserd::ReadDmUserPayload(void* buffer, size_t size) {
-    if (!android::base::ReadFully(ctrl_fd_, buffer, size)) {
-        PLOG(ERROR) << "ReadDmUserPayload failed";
-        return false;
-    }
-
-    return true;
-}
-
 bool Snapuserd::InitCowDevice() {
     cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
     if (cow_fd_ < 0) {
-        PLOG(ERROR) << "Open Failed: " << cow_device_;
+        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
         return false;
     }
 
-    // Allocate the buffer which is used to communicate between
-    // daemon and dm-user. The buffer comprises of header and a fixed payload.
-    // If the dm-user requests a big IO, the IO will be broken into chunks
-    // of PAYLOAD_SIZE.
-    size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
-    bufsink_.Initialize(buf_size);
-
     return ReadMetadata();
 }
 
-bool Snapuserd::InitBackingAndControlDevice() {
-    backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
-    if (backing_store_fd_ < 0) {
-        PLOG(ERROR) << "Open Failed: " << backing_store_device_;
-        return false;
+/*
+ * Entry point to launch threads
+ */
+bool Snapuserd::Start() {
+    std::vector<std::future<bool>> threads;
+    std::future<bool> ra_thread;
+    bool rathread = (read_ahead_feature_ && (read_ahead_ops_.size() > 0));
+
+    // Start the read-ahead thread and wait
+    // for it as the data has to be re-constructed
+    // from COW device.
+    if (rathread) {
+        ra_thread = std::async(std::launch::async, &ReadAheadThread::RunThread,
+                               read_ahead_thread_.get());
+        if (!WaitForReadAheadToStart()) {
+            SNAP_LOG(ERROR) << "Failed to start Read-ahead thread...";
+            return false;
+        }
+
+        SNAP_LOG(INFO) << "Read-ahead thread started...";
     }
 
-    ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
-    if (ctrl_fd_ < 0) {
-        PLOG(ERROR) << "Unable to open " << control_device_;
-        return false;
+    // Launch worker threads
+    for (int i = 0; i < worker_threads_.size(); i++) {
+        threads.emplace_back(
+                std::async(std::launch::async, &WorkerThread::RunThread, worker_threads_[i].get()));
     }
 
-    return true;
+    bool ret = true;
+    for (auto& t : threads) {
+        ret = t.get() && ret;
+    }
+
+    if (rathread) {
+        // Notify the read-ahead thread that all worker threads
+        // are done. We need this explicit notification when
+        // there is an IO failure or there was a switch
+        // of dm-user table; thus, forcing the read-ahead
+        // thread to wake up.
+        MergeCompleted();
+        ret = ret && ra_thread.get();
+    }
+
+    return ret;
 }
 
-bool Snapuserd::Run() {
-    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+uint64_t Snapuserd::GetBufferMetadataOffset() {
+    CowHeader header;
+    reader_->GetHeader(&header);
 
-    bufsink_.Clear();
+    size_t size = header.header_size + sizeof(BufferState);
+    return size;
+}
 
-    if (!ReadDmUserHeader()) {
-        LOG(ERROR) << "ReadDmUserHeader failed";
-        return false;
-    }
+/*
+ * Metadata for read-ahead is 16 bytes. For a 2 MB region, we will
+ * end up with 8k (2 PAGE) worth of metadata. Thus, a 2MB buffer
+ * region is split into:
+ *
+ * 1: 8k metadata
+ *
+ */
+size_t Snapuserd::GetBufferMetadataSize() {
+    CowHeader header;
+    reader_->GetHeader(&header);
 
-    LOG(DEBUG) << "msg->seq: " << std::hex << header->seq;
-    LOG(DEBUG) << "msg->type: " << std::hex << header->type;
-    LOG(DEBUG) << "msg->flags: " << std::hex << header->flags;
-    LOG(DEBUG) << "msg->sector: " << std::hex << header->sector;
-    LOG(DEBUG) << "msg->len: " << std::hex << header->len;
+    size_t metadata_bytes = (header.buffer_size * sizeof(struct ScratchMetadata)) / BLOCK_SZ;
+    return metadata_bytes;
+}
 
-    switch (header->type) {
-        case DM_USER_REQ_MAP_READ: {
-            size_t remaining_size = header->len;
-            loff_t offset = 0;
-            do {
-                size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
-                header->type = DM_USER_RESP_SUCCESS;
+size_t Snapuserd::GetBufferDataOffset() {
+    CowHeader header;
+    reader_->GetHeader(&header);
 
-                // Request to sector 0 is always for kernel
-                // representation of COW header. This IO should be only
-                // once during dm-snapshot device creation. We should
-                // never see multiple IO requests. Additionally this IO
-                // will always be a single 4k.
-                if (header->sector == 0) {
-                    CHECK(metadata_read_done_ == true);
-                    CHECK(read_size == BLOCK_SIZE);
-                    ConstructKernelCowHeader();
-                    LOG(DEBUG) << "Kernel header constructed";
-                } else {
-                    // Convert the sector number to a chunk ID.
-                    //
-                    // Check if the chunk ID represents a metadata
-                    // page. If the chunk ID is not found in the
-                    // vector, then it points to a metadata page.
-                    chunk_t chunk = SectorToChunk(header->sector);
+    return (header.header_size + GetBufferMetadataSize());
+}
 
-                    if (chunk_map_.find(chunk) == chunk_map_.end()) {
-                        if (!ReadDiskExceptions(chunk, read_size)) {
-                            LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk
-                                       << "Sector: " << header->sector;
-                            header->type = DM_USER_RESP_ERROR;
-                        } else {
-                            LOG(DEBUG) << "ReadDiskExceptions success for chunk id: " << chunk
-                                       << "Sector: " << header->sector;
-                        }
-                    } else {
-                        chunk_t num_chunks_read = (offset >> BLOCK_SHIFT);
-                        if (!ReadData(chunk + num_chunks_read, read_size)) {
-                            LOG(ERROR) << "ReadData failed for chunk id: " << chunk
-                                       << "Sector: " << header->sector;
-                            header->type = DM_USER_RESP_ERROR;
-                        } else {
-                            LOG(DEBUG) << "ReadData success for chunk id: " << chunk
-                                       << "Sector: " << header->sector;
-                        }
-                    }
-                }
+/*
+ * (2MB - 8K = 2088960 bytes) will be the buffer region to hold the data.
+ */
+size_t Snapuserd::GetBufferDataSize() {
+    CowHeader header;
+    reader_->GetHeader(&header);
 
-                // Daemon will not be terminated if there is any error. We will
-                // just send the error back to dm-user.
-                if (!WriteDmUserPayload(read_size)) {
-                    return false;
-                }
+    size_t size = header.buffer_size - GetBufferMetadataSize();
+    return size;
+}
 
-                remaining_size -= read_size;
-                offset += read_size;
-            } while (remaining_size);
+struct BufferState* Snapuserd::GetBufferState() {
+    CowHeader header;
+    reader_->GetHeader(&header);
 
-            break;
-        }
-
-        case DM_USER_REQ_MAP_WRITE: {
-            // device mapper has the capability to allow
-            // targets to flush the cache when writes are completed. This
-            // is controlled by each target by a flag "flush_supported".
-            // This flag is set by dm-user. When flush is supported,
-            // a number of zero-length bio's will be submitted to
-            // the target for the purpose of flushing cache. It is the
-            // responsibility of the target driver - which is dm-user in this
-            // case, to remap these bio's to the underlying device. Since,
-            // there is no underlying device for dm-user, this zero length
-            // bio's gets routed to daemon.
-            //
-            // Flush operations are generated post merge by dm-snap by having
-            // REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything
-            // to flush per se; hence, just respond back with a success message.
-            if (header->sector == 0) {
-                CHECK(header->len == 0);
-                header->type = DM_USER_RESP_SUCCESS;
-                if (!WriteDmUserPayload(0)) {
-                    return false;
-                }
-                break;
-            }
-
-            size_t remaining_size = header->len;
-            size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
-            CHECK(read_size == BLOCK_SIZE);
-
-            CHECK(header->sector > 0);
-            chunk_t chunk = SectorToChunk(header->sector);
-            CHECK(chunk_map_.find(chunk) == chunk_map_.end());
-
-            void* buffer = bufsink_.GetPayloadBuffer(read_size);
-            CHECK(buffer != nullptr);
-            header->type = DM_USER_RESP_SUCCESS;
-
-            if (!ReadDmUserPayload(buffer, read_size)) {
-                LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
-                           << "Sector: " << header->sector;
-                header->type = DM_USER_RESP_ERROR;
-            }
-
-            if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
-                LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
-                           << "Sector: " << header->sector;
-                header->type = DM_USER_RESP_ERROR;
-            } else {
-                LOG(DEBUG) << "ProcessMergeComplete success for chunk id: " << chunk
-                           << "Sector: " << header->sector;
-            }
-
-            if (!WriteDmUserPayload(0)) {
-                return false;
-            }
-
-            break;
-        }
-    }
-
-    return true;
+    struct BufferState* ra_state =
+            reinterpret_cast<struct BufferState*>((char*)mapped_addr_ + header.header_size);
+    return ra_state;
 }
 
 }  // namespace snapshot
 }  // namespace android
-
-int main([[maybe_unused]] int argc, char** argv) {
-    android::base::InitLogging(argv, &android::base::KernelLogger);
-
-    android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance();
-
-    std::string socket = android::snapshot::kSnapuserdSocket;
-    if (argc >= 2) {
-        socket = argv[1];
-    }
-    daemon.StartServer(socket);
-    daemon.Run();
-
-    return 0;
-}
diff --git a/fs_mgr/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/snapuserd.h
new file mode 100644
index 0000000..212c78e
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd.h
@@ -0,0 +1,343 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <linux/types.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+
+#include <bitset>
+#include <condition_variable>
+#include <csignal>
+#include <cstring>
+#include <future>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <libdm/dm.h>
+#include <libsnapshot/cow_reader.h>
+#include <libsnapshot/cow_writer.h>
+#include <libsnapshot/snapuserd_kernel.h>
+
+namespace android {
+namespace snapshot {
+
+using android::base::unique_fd;
+using namespace std::chrono_literals;
+
+static constexpr size_t PAYLOAD_SIZE = (1UL << 20);
+static_assert(PAYLOAD_SIZE >= BLOCK_SZ);
+
+/*
+ * With 4 threads, we get optimal performance
+ * when update_verifier reads the partition during
+ * boot.
+ */
+static constexpr int NUM_THREADS_PER_PARTITION = 4;
+
+/*
+ * State transitions between worker threads and read-ahead
+ * threads.
+ *
+ * READ_AHEAD_BEGIN: Worker threads initiates the read-ahead
+ *                   thread to begin reading the copy operations
+ *                   for each bounded region.
+ *
+ * READ_AHEAD_IN_PROGRESS: When read ahead thread is in-flight
+ *                         and reading the copy operations.
+ *
+ * IO_IN_PROGRESS: Merge operation is in-progress by worker threads.
+ *
+ * IO_TERMINATED: When all the worker threads are done, request the
+ *                read-ahead thread to terminate
+ *
+ * READ_AHEAD_FAILURE: If there are any IO failures when read-ahead
+ *                     thread is reading from COW device.
+ *
+ * The transition of each states is described in snapuserd_readahead.cpp
+ */
+enum class READ_AHEAD_IO_TRANSITION {
+    READ_AHEAD_BEGIN,
+    READ_AHEAD_IN_PROGRESS,
+    IO_IN_PROGRESS,
+    IO_TERMINATED,
+    READ_AHEAD_FAILURE,
+};
+
+class BufferSink : public IByteSink {
+  public:
+    void Initialize(size_t size);
+    void* GetBufPtr() { return buffer_.get(); }
+    void Clear() { memset(GetBufPtr(), 0, buffer_size_); }
+    void* GetPayloadBuffer(size_t size);
+    void* GetBuffer(size_t requested, size_t* actual) override;
+    void UpdateBufferOffset(size_t size) { buffer_offset_ += size; }
+    struct dm_user_header* GetHeaderPtr();
+    bool ReturnData(void*, size_t) override { return true; }
+    void ResetBufferOffset() { buffer_offset_ = 0; }
+    void* GetPayloadBufPtr();
+
+  private:
+    std::unique_ptr<uint8_t[]> buffer_;
+    loff_t buffer_offset_;
+    size_t buffer_size_;
+};
+
+class Snapuserd;
+
+class ReadAheadThread {
+  public:
+    ReadAheadThread(const std::string& cow_device, const std::string& backing_device,
+                    const std::string& misc_name, std::shared_ptr<Snapuserd> snapuserd);
+    bool RunThread();
+
+  private:
+    void InitializeIter();
+    bool IterDone();
+    void IterNext();
+    const CowOperation* GetIterOp();
+    void InitializeBuffer();
+
+    bool InitializeFds();
+    void CloseFds() {
+        cow_fd_ = {};
+        backing_store_fd_ = {};
+    }
+
+    bool ReadAheadIOStart();
+    void PrepareReadAhead(uint64_t* source_block, int* pending_ops, std::vector<uint64_t>& blocks);
+    bool ReconstructDataFromCow();
+    void CheckOverlap(const CowOperation* cow_op);
+
+    void* read_ahead_buffer_;
+    void* metadata_buffer_;
+    std::vector<const CowOperation*>::reverse_iterator read_ahead_iter_;
+    std::string cow_device_;
+    std::string backing_store_device_;
+    std::string misc_name_;
+
+    unique_fd cow_fd_;
+    unique_fd backing_store_fd_;
+
+    std::shared_ptr<Snapuserd> snapuserd_;
+
+    std::unordered_set<uint64_t> dest_blocks_;
+    std::unordered_set<uint64_t> source_blocks_;
+    bool overlap_;
+};
+
+class WorkerThread {
+  public:
+    WorkerThread(const std::string& cow_device, const std::string& backing_device,
+                 const std::string& control_device, const std::string& misc_name,
+                 std::shared_ptr<Snapuserd> snapuserd);
+    bool RunThread();
+
+  private:
+    // Initialization
+    void InitializeBufsink();
+    bool InitializeFds();
+    bool InitReader();
+    void CloseFds() {
+        ctrl_fd_ = {};
+        backing_store_fd_ = {};
+    }
+
+    // Functions interacting with dm-user
+    bool ReadDmUserHeader();
+    bool DmuserReadRequest();
+    bool DmuserWriteRequest();
+    bool ReadDmUserPayload(void* buffer, size_t size);
+    bool WriteDmUserPayload(size_t size, bool header_response);
+
+    bool ReadDiskExceptions(chunk_t chunk, size_t size);
+    bool ZerofillDiskExceptions(size_t read_size);
+    void ConstructKernelCowHeader();
+
+    // IO Path
+    bool ProcessIORequest();
+    int ReadData(sector_t sector, size_t size);
+    int ReadUnalignedSector(sector_t sector, size_t size,
+                            std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it);
+
+    // Processing COW operations
+    bool ProcessCowOp(const CowOperation* cow_op);
+    bool ProcessReplaceOp(const CowOperation* cow_op);
+    bool ProcessCopyOp(const CowOperation* cow_op);
+    bool ProcessZeroOp();
+
+    bool ReadFromBaseDevice(const CowOperation* cow_op);
+    bool GetReadAheadPopulatedBuffer(const CowOperation* cow_op);
+
+    // Merge related functions
+    bool ProcessMergeComplete(chunk_t chunk, void* buffer);
+    loff_t GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
+                               int* unmerged_exceptions);
+
+    int GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
+                             int unmerged_exceptions, bool* copy_op, bool* commit);
+
+    sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
+    chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
+
+    std::unique_ptr<CowReader> reader_;
+    BufferSink bufsink_;
+
+    std::string cow_device_;
+    std::string backing_store_device_;
+    std::string control_device_;
+    std::string misc_name_;
+
+    unique_fd cow_fd_;
+    unique_fd backing_store_fd_;
+    unique_fd ctrl_fd_;
+
+    std::shared_ptr<Snapuserd> snapuserd_;
+    uint32_t exceptions_per_area_;
+};
+
+class Snapuserd : public std::enable_shared_from_this<Snapuserd> {
+  public:
+    Snapuserd(const std::string& misc_name, const std::string& cow_device,
+              const std::string& backing_device);
+    bool InitCowDevice();
+    bool Start();
+    const std::string& GetControlDevicePath() { return control_device_; }
+    const std::string& GetMiscName() { return misc_name_; }
+    uint64_t GetNumSectors() { return num_sectors_; }
+    bool IsAttached() const { return attached_; }
+    void AttachControlDevice() { attached_ = true; }
+
+    void CheckMergeCompletionStatus();
+    bool CommitMerge(int num_merge_ops);
+
+    void CloseFds() { cow_fd_ = {}; }
+    void FreeResources() {
+        worker_threads_.clear();
+        read_ahead_thread_ = nullptr;
+    }
+    size_t GetMetadataAreaSize() { return vec_.size(); }
+    void* GetExceptionBuffer(size_t i) { return vec_[i].get(); }
+
+    bool InitializeWorkers();
+    std::shared_ptr<Snapuserd> GetSharedPtr() { return shared_from_this(); }
+
+    std::vector<std::pair<sector_t, const CowOperation*>>& GetChunkVec() { return chunk_vec_; }
+    const std::vector<std::unique_ptr<uint8_t[]>>& GetMetadataVec() const { return vec_; }
+
+    static bool compare(std::pair<sector_t, const CowOperation*> p1,
+                        std::pair<sector_t, const CowOperation*> p2) {
+        return p1.first < p2.first;
+    }
+
+    void UnmapBufferRegion();
+    bool MmapMetadata();
+
+    // Read-ahead related functions
+    std::vector<const CowOperation*>& GetReadAheadOpsVec() { return read_ahead_ops_; }
+    std::unordered_map<uint64_t, void*>& GetReadAheadMap() { return read_ahead_buffer_map_; }
+    void* GetMappedAddr() { return mapped_addr_; }
+    bool IsReadAheadFeaturePresent() { return read_ahead_feature_; }
+    void PrepareReadAhead();
+    void StartReadAhead();
+    void MergeCompleted();
+    bool ReadAheadIOCompleted(bool sync);
+    void ReadAheadIOFailed();
+    bool WaitForMergeToComplete();
+    bool GetReadAheadPopulatedBuffer(uint64_t block, void* buffer);
+    bool ReconstructDataFromCow() { return populate_data_from_cow_; }
+    void ReconstructDataFromCowFinish() { populate_data_from_cow_ = false; }
+    bool WaitForReadAheadToStart();
+
+    uint64_t GetBufferMetadataOffset();
+    size_t GetBufferMetadataSize();
+    size_t GetBufferDataOffset();
+    size_t GetBufferDataSize();
+
+    // Final block to be merged in a given read-ahead buffer region
+    void SetFinalBlockMerged(uint64_t x) { final_block_merged_ = x; }
+    uint64_t GetFinalBlockMerged() { return final_block_merged_; }
+    // Total number of blocks to be merged in a given read-ahead buffer region
+    void SetTotalRaBlocksMerged(int x) { total_ra_blocks_merged_ = x; }
+    int GetTotalRaBlocksMerged() { return total_ra_blocks_merged_; }
+
+  private:
+    bool IsChunkIdMetadata(chunk_t chunk);
+    chunk_t GetNextAllocatableChunkId(chunk_t chunk_id);
+
+    bool GetRABuffer(std::unique_lock<std::mutex>* lock, uint64_t block, void* buffer);
+    bool ReadMetadata();
+    sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; }
+    chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; }
+    bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); }
+    struct BufferState* GetBufferState();
+
+    std::string cow_device_;
+    std::string backing_store_device_;
+    std::string control_device_;
+    std::string misc_name_;
+
+    unique_fd cow_fd_;
+
+    uint32_t exceptions_per_area_;
+    uint64_t num_sectors_;
+
+    std::unique_ptr<ICowOpIter> cowop_iter_;
+    std::unique_ptr<ICowOpReverseIter> cowop_riter_;
+    std::unique_ptr<CowReader> reader_;
+
+    // Vector of disk exception which is a
+    // mapping of old-chunk to new-chunk
+    std::vector<std::unique_ptr<uint8_t[]>> vec_;
+
+    // chunk_vec stores the pseudo mapping of sector
+    // to COW operations.
+    std::vector<std::pair<sector_t, const CowOperation*>> chunk_vec_;
+
+    std::mutex lock_;
+    std::condition_variable cv;
+
+    void* mapped_addr_;
+    size_t total_mapped_addr_length_;
+
+    std::vector<std::unique_ptr<WorkerThread>> worker_threads_;
+    // Read-ahead related
+    std::unordered_map<uint64_t, void*> read_ahead_buffer_map_;
+    std::vector<const CowOperation*> read_ahead_ops_;
+    bool populate_data_from_cow_ = false;
+    bool read_ahead_feature_;
+    uint64_t final_block_merged_;
+    int total_ra_blocks_merged_ = 0;
+    READ_AHEAD_IO_TRANSITION io_state_;
+    std::unique_ptr<ReadAheadThread> read_ahead_thread_;
+
+    bool merge_initiated_ = false;
+    bool attached_ = false;
+};
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd.rc b/fs_mgr/libsnapshot/snapuserd.rc
index f2d21ac..4bf34a2 100644
--- a/fs_mgr/libsnapshot/snapuserd.rc
+++ b/fs_mgr/libsnapshot/snapuserd.rc
@@ -4,3 +4,4 @@
     disabled
     user root
     group root system
+    seclabel u:r:snapuserd:s0
diff --git a/fs_mgr/libsnapshot/snapuserd_client.cpp b/fs_mgr/libsnapshot/snapuserd_client.cpp
index a5d2061..41ab344 100644
--- a/fs_mgr/libsnapshot/snapuserd_client.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_client.cpp
@@ -54,29 +54,10 @@
     return true;
 }
 
-pid_t StartFirstStageSnapuserd() {
-    pid_t pid = fork();
-    if (pid < 0) {
-        PLOG(ERROR) << "fork failed";
-        return pid;
-    }
-    if (pid != 0) {
-        return pid;
-    }
-
-    std::string arg0 = "/system/bin/snapuserd";
-    std::string arg1 = kSnapuserdSocketFirstStage;
-    char* const argv[] = {arg0.data(), arg1.data(), nullptr};
-    if (execv(arg0.c_str(), argv) < 0) {
-        PLOG(FATAL) << "execv failed";
-    }
-    return pid;
-}
-
 SnapuserdClient::SnapuserdClient(android::base::unique_fd&& sockfd) : sockfd_(std::move(sockfd)) {}
 
 static inline bool IsRetryErrno() {
-    return errno == ECONNREFUSED || errno == EINTR;
+    return errno == ECONNREFUSED || errno == EINTR || errno == ENOENT;
 }
 
 std::unique_ptr<SnapuserdClient> SnapuserdClient::Connect(const std::string& socket_name,
@@ -131,7 +112,8 @@
 }
 
 bool SnapuserdClient::Sendmsg(const std::string& msg) {
-    ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), 0));
+    LOG(DEBUG) << "Sendmsg: msg " << msg << " sockfd: " << sockfd_;
+    ssize_t numBytesSent = TEMP_FAILURE_RETRY(send(sockfd_, msg.data(), msg.size(), MSG_NOSIGNAL));
     if (numBytesSent < 0) {
         PLOG(ERROR) << "Send failed";
         return false;
@@ -229,5 +211,13 @@
     return num_sectors;
 }
 
+bool SnapuserdClient::DetachSnapuserd() {
+    if (!Sendmsg("detach")) {
+        LOG(ERROR) << "Failed to detach snapuserd.";
+        return false;
+    }
+    return true;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd_daemon.cpp
index 4c8fa57..7fa01b7 100644
--- a/fs_mgr/libsnapshot/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_daemon.cpp
@@ -14,17 +14,44 @@
  * limitations under the License.
  */
 
+#include "snapuserd_daemon.h"
+
 #include <android-base/logging.h>
-#include <libsnapshot/snapuserd_daemon.h>
+#include <android-base/strings.h>
+#include <gflags/gflags.h>
+#include <libsnapshot/snapuserd_client.h>
+
+#include "snapuserd_server.h"
+
+DEFINE_string(socket, android::snapshot::kSnapuserdSocket, "Named socket or socket path.");
+DEFINE_bool(no_socket, false,
+            "If true, no socket is used. Each additional argument is an INIT message.");
 
 namespace android {
 namespace snapshot {
 
-bool Daemon::StartServer(const std::string& socketname) {
-    if (!server_.Start(socketname)) {
-        LOG(ERROR) << "Snapuserd daemon failed to start...";
-        exit(EXIT_FAILURE);
+bool Daemon::StartServer(int argc, char** argv) {
+    int arg_start = gflags::ParseCommandLineFlags(&argc, &argv, true);
+
+    if (!FLAGS_no_socket) {
+        return server_.Start(FLAGS_socket);
     }
+
+    for (int i = arg_start; i < argc; i++) {
+        auto parts = android::base::Split(argv[i], ",");
+        if (parts.size() != 3) {
+            LOG(ERROR) << "Malformed message, expected three sub-arguments.";
+            return false;
+        }
+        auto handler = server_.AddHandler(parts[0], parts[1], parts[2]);
+        if (!handler || !server_.StartHandler(handler)) {
+            return false;
+        }
+    }
+
+    // Skip the accept() call to avoid spurious log spam. The server will still
+    // run until all handlers have completed.
+    server_.SetTerminating();
     return true;
 }
 
@@ -89,3 +116,17 @@
 
 }  // namespace snapshot
 }  // namespace android
+
+int main(int argc, char** argv) {
+    android::base::InitLogging(argv, &android::base::KernelLogger);
+
+    android::snapshot::Daemon& daemon = android::snapshot::Daemon::Instance();
+
+    if (!daemon.StartServer(argc, argv)) {
+        LOG(ERROR) << "Snapuserd daemon failed to start.";
+        exit(EXIT_FAILURE);
+    }
+    daemon.Run();
+
+    return 0;
+}
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_daemon.h b/fs_mgr/libsnapshot/snapuserd_daemon.h
similarity index 91%
rename from fs_mgr/libsnapshot/include/libsnapshot/snapuserd_daemon.h
rename to fs_mgr/libsnapshot/snapuserd_daemon.h
index c6779b8..f8afac5 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_daemon.h
+++ b/fs_mgr/libsnapshot/snapuserd_daemon.h
@@ -16,7 +16,10 @@
 
 #include <poll.h>
 
-#include <libsnapshot/snapuserd_server.h>
+#include <string>
+#include <vector>
+
+#include "snapuserd_server.h"
 
 namespace android {
 namespace snapshot {
@@ -32,7 +35,7 @@
         return instance;
     }
 
-    bool StartServer(const std::string& socketname);
+    bool StartServer(int argc, char** argv);
     void Run();
     void Interrupt();
 
diff --git a/fs_mgr/libsnapshot/snapuserd_readahead.cpp b/fs_mgr/libsnapshot/snapuserd_readahead.cpp
new file mode 100644
index 0000000..16d5919
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd_readahead.cpp
@@ -0,0 +1,463 @@
+/*
+ * 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 "snapuserd.h"
+
+#include <csignal>
+#include <optional>
+#include <set>
+
+#include <libsnapshot/snapuserd_client.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
+#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
+
+/*
+ * Merging a copy operation involves the following flow:
+ *
+ * 1: dm-snapshot layer requests merge for a 4k block. dm-user sends the request
+ *    to the daemon
+ * 2: daemon reads the source block
+ * 3: daemon copies the source data
+ * 4: IO completion sent back to dm-user (a switch from user space to kernel)
+ * 5: dm-snapshot merges the data to base device
+ * 6: dm-snapshot sends the merge-completion IO to dm-user
+ * 7: dm-user re-directs the merge completion IO to daemon (one more switch)
+ * 8: daemon updates the COW file about the completed merge request (a write syscall) and followed
+ * by a fysnc. 9: Send the IO completion back to dm-user
+ *
+ * The above sequence is a significant overhead especially when merging one 4k
+ * block at a time.
+ *
+ * Read-ahead layer will optimize the above path by reading the data from base
+ * device in the background so that merging thread can retrieve the data from
+ * the read-ahead cache. Additionally, syncing of merged data is deferred to
+ * read-ahead thread threadby the IO path is not bottlenecked.
+ *
+ * We create a scratch space of 2MB to store the read-ahead data in the COW
+ * device.
+ *
+ *      +-----------------------+
+ *      |     Header (fixed)    |
+ *      +-----------------------+
+ *      |    Scratch space      |  <-- 2MB
+ *      +-----------------------+
+ *
+ *      Scratch space is as follows:
+ *
+ *      +-----------------------+
+ *      |       Metadata        | <- 4k page
+ *      +-----------------------+
+ *      |       Metadata        | <- 4k page
+ *      +-----------------------+
+ *      |                       |
+ *      |    Read-ahead data    |
+ *      |                       |
+ *      +-----------------------+
+ *
+ * State transitions and communication between read-ahead thread and worker
+ * thread during merge:
+ * =====================================================================
+ *
+ *   Worker Threads                                 Read-Ahead thread
+ *   ------------------------------------------------------------------
+ *
+ *      |
+ *      |
+ *  --> -----------------READ_AHEAD_BEGIN------------->|
+ *  |   |                                              | READ_AHEAD_IN_PROGRESS
+ *  |  WAIT                                            |
+ *  |   |                                              |
+ *  |   |<-----------------IO_IN_PROGRESS---------------
+ *  |   |                                              |
+ *  |   | IO_IN_PRGRESS                               WAIT
+ *  |   |                                              |
+ *  |<--|                                              |
+ *      |                                              |
+ *      ------------------IO_TERMINATED--------------->|
+ *                                                     END
+ *
+ *
+ * ===================================================================
+ *
+ * Example:
+ *
+ * We have 6 copy operations to be executed in OTA and there is a overlap. Update-engine
+ * will write to COW file as follows:
+ *
+ * Op-1: 20 -> 23
+ * Op-2: 19 -> 22
+ * Op-3: 18 -> 21
+ * Op-4: 17 -> 20
+ * Op-5: 16 -> 19
+ * Op-6: 15 -> 18
+ *
+ * Read-ahead thread will read all the 6 source blocks and store the data in the
+ * scratch space. Metadata will contain the destination block numbers. Thus,
+ * scratch space will look something like this:
+ *
+ * +--------------+
+ * | Block   23   |
+ * | offset - 1   |
+ * +--------------+
+ * | Block   22   |
+ * | offset - 2   |
+ * +--------------+
+ * | Block   21   |
+ * | offset - 3   |
+ * +--------------+
+ *    ...
+ *    ...
+ * +--------------+
+ * | Data-Block 20| <-- offset - 1
+ * +--------------+
+ * | Data-Block 19| <-- offset - 2
+ * +--------------+
+ * | Data-Block 18| <-- offset - 3
+ * +--------------+
+ *     ...
+ *     ...
+ *
+ * ====================================================================
+ * IO Path:
+ *
+ * Read-ahead will serve the data to worker threads during merge only
+ * after metadata and data are persisted to the scratch space. Worker
+ * threads during merge will always retrieve the data from cache; if the
+ * cache is not populated, it will wait for the read-ahead thread to finish.
+ * Furthermore, the number of operations merged will by synced to the header
+ * only when all the blocks in the read-ahead cache are merged. In the above
+ * case, when all 6 operations are merged, COW Header is updated with
+ * num_merge_ops = 6.
+ *
+ * Merge resume after crash:
+ *
+ * Let's say we have a crash after 5 operations are merged. i.e. after
+ * Op-5: 16->19 is completed but before the Op-6 is merged. Thus, COW Header
+ * num_merge_ops will be 0 as the all the ops were not merged yet. During next
+ * reboot, read-ahead thread will re-construct the data in-memory from the
+ * scratch space; when merge resumes, Op-1 will be re-exectued. However,
+ * data will be served from read-ahead cache safely even though, block 20
+ * was over-written by Op-4.
+ *
+ */
+
+ReadAheadThread::ReadAheadThread(const std::string& cow_device, const std::string& backing_device,
+                                 const std::string& misc_name,
+                                 std::shared_ptr<Snapuserd> snapuserd) {
+    cow_device_ = cow_device;
+    backing_store_device_ = backing_device;
+    misc_name_ = misc_name;
+    snapuserd_ = snapuserd;
+}
+
+void ReadAheadThread::CheckOverlap(const CowOperation* cow_op) {
+    if (dest_blocks_.count(cow_op->new_block) || source_blocks_.count(cow_op->source)) {
+        overlap_ = true;
+    }
+
+    dest_blocks_.insert(cow_op->source);
+    source_blocks_.insert(cow_op->new_block);
+}
+
+void ReadAheadThread::PrepareReadAhead(uint64_t* source_block, int* pending_ops,
+                                       std::vector<uint64_t>& blocks) {
+    int num_ops = *pending_ops;
+    int nr_consecutive = 0;
+
+    if (!IterDone() && num_ops) {
+        // Get the first block
+        const CowOperation* cow_op = GetIterOp();
+        *source_block = cow_op->source;
+        IterNext();
+        num_ops -= 1;
+        nr_consecutive = 1;
+        blocks.push_back(cow_op->new_block);
+
+        if (!overlap_) {
+            CheckOverlap(cow_op);
+        }
+
+        /*
+         * Find number of consecutive blocks working backwards.
+         */
+        while (!IterDone() && num_ops) {
+            const CowOperation* op = GetIterOp();
+            if (op->source != (*source_block - nr_consecutive)) {
+                break;
+            }
+            nr_consecutive += 1;
+            num_ops -= 1;
+            blocks.push_back(op->new_block);
+            IterNext();
+
+            if (!overlap_) {
+                CheckOverlap(op);
+            }
+        }
+    }
+}
+
+bool ReadAheadThread::ReconstructDataFromCow() {
+    std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+    read_ahead_buffer_map.clear();
+    loff_t metadata_offset = 0;
+    loff_t start_data_offset = snapuserd_->GetBufferDataOffset();
+    int num_ops = 0;
+    int total_blocks_merged = 0;
+
+    while (true) {
+        struct ScratchMetadata* bm = reinterpret_cast<struct ScratchMetadata*>(
+                (char*)metadata_buffer_ + metadata_offset);
+
+        // Done reading metadata
+        if (bm->new_block == 0 && bm->file_offset == 0) {
+            break;
+        }
+
+        loff_t buffer_offset = bm->file_offset - start_data_offset;
+        void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + buffer_offset);
+        read_ahead_buffer_map[bm->new_block] = bufptr;
+        num_ops += 1;
+        total_blocks_merged += 1;
+
+        metadata_offset += sizeof(struct ScratchMetadata);
+    }
+
+    // We are done re-constructing the mapping; however, we need to make sure
+    // all the COW operations to-be merged are present in the re-constructed
+    // mapping.
+    while (!IterDone()) {
+        const CowOperation* op = GetIterOp();
+        if (read_ahead_buffer_map.find(op->new_block) != read_ahead_buffer_map.end()) {
+            num_ops -= 1;
+            snapuserd_->SetFinalBlockMerged(op->new_block);
+            IterNext();
+        } else {
+            // Verify that we have covered all the ops which were re-constructed
+            // from COW device - These are the ops which are being
+            // re-constructed after crash.
+            if (!(num_ops == 0)) {
+                SNAP_LOG(ERROR) << "ReconstructDataFromCow failed. Not all ops recoverd "
+                                << " Pending ops: " << num_ops;
+                snapuserd_->ReadAheadIOFailed();
+                return false;
+            }
+            break;
+        }
+    }
+
+    snapuserd_->SetTotalRaBlocksMerged(total_blocks_merged);
+
+    snapuserd_->ReconstructDataFromCowFinish();
+
+    if (!snapuserd_->ReadAheadIOCompleted(true)) {
+        SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+        snapuserd_->ReadAheadIOFailed();
+        return false;
+    }
+
+    SNAP_LOG(INFO) << "ReconstructDataFromCow success";
+    return true;
+}
+
+bool ReadAheadThread::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_->ReconstructDataFromCow()) {
+        return ReconstructDataFromCow();
+    }
+
+    std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+    read_ahead_buffer_map.clear();
+
+    int num_ops = (snapuserd_->GetBufferDataSize()) / BLOCK_SZ;
+    loff_t metadata_offset = 0;
+
+    struct ScratchMetadata* bm =
+            reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ + metadata_offset);
+
+    bm->new_block = 0;
+    bm->file_offset = 0;
+
+    std::vector<uint64_t> blocks;
+
+    loff_t buffer_offset = 0;
+    loff_t offset = 0;
+    loff_t file_offset = snapuserd_->GetBufferDataOffset();
+    int total_blocks_merged = 0;
+    overlap_ = false;
+    dest_blocks_.clear();
+    source_blocks_.clear();
+
+    while (true) {
+        uint64_t source_block;
+        int linear_blocks;
+
+        PrepareReadAhead(&source_block, &num_ops, blocks);
+        linear_blocks = blocks.size();
+        if (linear_blocks == 0) {
+            // No more blocks to read
+            SNAP_LOG(DEBUG) << " Read-ahead completed....";
+            break;
+        }
+
+        // Get the first block in the consecutive set of blocks
+        source_block = source_block + 1 - linear_blocks;
+        size_t io_size = (linear_blocks * BLOCK_SZ);
+        num_ops -= linear_blocks;
+        total_blocks_merged += linear_blocks;
+
+        // Mark the block number as the one which will
+        // be the final block to be merged in this entire region.
+        // Read-ahead thread will get
+        // notified when this block is merged to make
+        // forward progress
+        snapuserd_->SetFinalBlockMerged(blocks.back());
+
+        while (linear_blocks) {
+            uint64_t new_block = blocks.back();
+            blocks.pop_back();
+            // Assign the mapping
+            void* bufptr = static_cast<void*>((char*)read_ahead_buffer_ + offset);
+            read_ahead_buffer_map[new_block] = bufptr;
+            offset += BLOCK_SZ;
+
+            bm = reinterpret_cast<struct ScratchMetadata*>((char*)metadata_buffer_ +
+                                                           metadata_offset);
+            bm->new_block = new_block;
+            bm->file_offset = file_offset;
+
+            metadata_offset += sizeof(struct ScratchMetadata);
+            file_offset += BLOCK_SZ;
+
+            linear_blocks -= 1;
+        }
+
+        // Read from the base device consecutive set of blocks in one shot
+        if (!android::base::ReadFullyAtOffset(backing_store_fd_,
+                                              (char*)read_ahead_buffer_ + buffer_offset, io_size,
+                                              source_block * BLOCK_SZ)) {
+            SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
+                             << "at block :" << source_block << " buffer_offset : " << buffer_offset
+                             << " io_size : " << io_size << " buf-addr : " << read_ahead_buffer_;
+
+            snapuserd_->ReadAheadIOFailed();
+            return false;
+        }
+
+        // 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*)metadata_buffer_ + metadata_offset);
+        bm->new_block = 0;
+        bm->file_offset = 0;
+
+        buffer_offset += io_size;
+    }
+
+    snapuserd_->SetTotalRaBlocksMerged(total_blocks_merged);
+
+    // Flush the data only if we have a overlapping blocks in the region
+    if (!snapuserd_->ReadAheadIOCompleted(overlap_)) {
+        SNAP_LOG(ERROR) << "ReadAheadIOCompleted failed...";
+        snapuserd_->ReadAheadIOFailed();
+        return false;
+    }
+
+    return true;
+}
+
+bool ReadAheadThread::RunThread() {
+    if (!InitializeFds()) {
+        return false;
+    }
+
+    InitializeIter();
+    InitializeBuffer();
+
+    while (!IterDone()) {
+        if (!ReadAheadIOStart()) {
+            return false;
+        }
+
+        bool status = snapuserd_->WaitForMergeToComplete();
+
+        if (status && !snapuserd_->CommitMerge(snapuserd_->GetTotalRaBlocksMerged())) {
+            return false;
+        }
+
+        if (!status) break;
+    }
+
+    CloseFds();
+    SNAP_LOG(INFO) << " ReadAhead thread terminating....";
+    return true;
+}
+
+// Initialization
+bool ReadAheadThread::InitializeFds() {
+    backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+    if (backing_store_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+        return false;
+    }
+
+    cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+    if (cow_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+        return false;
+    }
+
+    return true;
+}
+
+void ReadAheadThread::InitializeIter() {
+    std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
+    read_ahead_iter_ = read_ahead_ops.rbegin();
+}
+
+bool ReadAheadThread::IterDone() {
+    std::vector<const CowOperation*>& read_ahead_ops = snapuserd_->GetReadAheadOpsVec();
+    return read_ahead_iter_ == read_ahead_ops.rend();
+}
+
+void ReadAheadThread::IterNext() {
+    read_ahead_iter_++;
+}
+
+const CowOperation* ReadAheadThread::GetIterOp() {
+    return *read_ahead_iter_;
+}
+
+void ReadAheadThread::InitializeBuffer() {
+    void* mapped_addr = snapuserd_->GetMappedAddr();
+    // Map the scratch space region into memory
+    metadata_buffer_ =
+            static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferMetadataOffset());
+    read_ahead_buffer_ = static_cast<void*>((char*)mapped_addr + snapuserd_->GetBufferDataOffset());
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp
index 9d57ab0..8339690 100644
--- a/fs_mgr/libsnapshot/snapuserd_server.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_server.cpp
@@ -26,8 +26,9 @@
 #include <unistd.h>
 
 #include <android-base/logging.h>
-#include <libsnapshot/snapuserd.h>
-#include <libsnapshot/snapuserd_server.h>
+
+#include "snapuserd.h"
+#include "snapuserd_server.h"
 
 namespace android {
 namespace snapshot {
@@ -38,6 +39,7 @@
     if (input == "stop") return DaemonOperations::STOP;
     if (input == "query") return DaemonOperations::QUERY;
     if (input == "delete") return DaemonOperations::DELETE;
+    if (input == "detach") return DaemonOperations::DETACH;
 
     return DaemonOperations::INVALID;
 }
@@ -72,27 +74,14 @@
 
 void SnapuserdServer::ShutdownThreads() {
     StopThreads();
-
-    // Acquire the thread list within the lock.
-    std::vector<std::shared_ptr<DmUserHandler>> dm_users;
-    {
-        std::lock_guard<std::mutex> guard(lock_);
-        dm_users = std::move(dm_users_);
-    }
-
-    for (auto& client : dm_users) {
-        auto& th = client->thread();
-
-        if (th.joinable()) th.join();
-    }
+    JoinAllThreads();
 }
 
-const std::string& DmUserHandler::GetMiscName() const {
-    return snapuserd_->GetMiscName();
-}
+DmUserHandler::DmUserHandler(std::shared_ptr<Snapuserd> snapuserd)
+    : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {}
 
 bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) {
-    ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), 0));
+    ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), MSG_NOSIGNAL));
     if (ret < 0) {
         PLOG(ERROR) << "Snapuserd:server: send() failed";
         return false;
@@ -126,7 +115,7 @@
     switch (op) {
         case DaemonOperations::INIT: {
             // Message format:
-            // init,<misc_name>,<cow_device_path>,<control_device>
+            // init,<misc_name>,<cow_device_path>,<backing_device>
             //
             // Reads the metadata and send the number of sectors
             if (out.size() != 4) {
@@ -134,24 +123,12 @@
                 return Sendmsg(fd, "fail");
             }
 
-            auto snapuserd = std::make_unique<Snapuserd>(out[1], out[2], out[3]);
-            if (!snapuserd->InitCowDevice()) {
-                LOG(ERROR) << "Failed to initialize Snapuserd";
+            auto handler = AddHandler(out[1], out[2], out[3]);
+            if (!handler) {
                 return Sendmsg(fd, "fail");
             }
 
-            std::string retval = "success," + std::to_string(snapuserd->GetNumSectors());
-
-            auto handler = std::make_unique<DmUserHandler>(std::move(snapuserd));
-            {
-                std::lock_guard<std::mutex> lock(lock_);
-                if (FindHandler(&lock, out[1]) != dm_users_.end()) {
-                    LOG(ERROR) << "Handler already exists: " << out[1];
-                    return Sendmsg(fd, "fail");
-                }
-                dm_users_.push_back(std::move(handler));
-            }
-
+            auto retval = "success," + std::to_string(handler->snapuserd()->GetNumSectors());
             return Sendmsg(fd, retval);
         }
         case DaemonOperations::START: {
@@ -170,15 +147,13 @@
                 LOG(ERROR) << "Could not find handler: " << out[1];
                 return Sendmsg(fd, "fail");
             }
-            if ((*iter)->snapuserd()->IsAttached()) {
+            if (!(*iter)->snapuserd() || (*iter)->snapuserd()->IsAttached()) {
                 LOG(ERROR) << "Tried to re-attach control device: " << out[1];
                 return Sendmsg(fd, "fail");
             }
-            if (!((*iter)->snapuserd()->InitBackingAndControlDevice())) {
-                LOG(ERROR) << "Failed to initialize control device: " << out[1];
+            if (!StartHandler(*iter)) {
                 return Sendmsg(fd, "fail");
             }
-            (*iter)->thread() = std::thread(std::bind(&SnapuserdServer::RunThread, this, *iter));
             return Sendmsg(fd, "success");
         }
         case DaemonOperations::STOP: {
@@ -209,11 +184,15 @@
                 LOG(ERROR) << "Malformed delete message, " << out.size() << " parts";
                 return Sendmsg(fd, "fail");
             }
-            if (!RemoveHandler(out[1], true)) {
+            if (!RemoveAndJoinHandler(out[1])) {
                 return Sendmsg(fd, "fail");
             }
             return Sendmsg(fd, "success");
         }
+        case DaemonOperations::DETACH: {
+            terminating_ = true;
+            return true;
+        }
         default: {
             LOG(ERROR) << "Received unknown message type from client";
             Sendmsg(fd, "fail");
@@ -223,20 +202,46 @@
 }
 
 void SnapuserdServer::RunThread(std::shared_ptr<DmUserHandler> handler) {
-    LOG(INFO) << "Entering thread for handler: " << handler->GetMiscName();
+    LOG(INFO) << "Entering thread for handler: " << handler->misc_name();
 
-    while (!StopRequested()) {
-        if (!handler->snapuserd()->Run()) {
-            LOG(INFO) << "Snapuserd: Thread terminating";
-            break;
-        }
+    if (!handler->snapuserd()->Start()) {
+        LOG(ERROR) << " Failed to launch all worker threads";
     }
 
-    LOG(INFO) << "Exiting thread for handler: " << handler->GetMiscName();
+    handler->snapuserd()->CloseFds();
+    handler->snapuserd()->CheckMergeCompletionStatus();
+    handler->snapuserd()->UnmapBufferRegion();
 
-    // If the main thread called /emoveHandler, the handler was already removed
-    // from within the lock, and calling RemoveHandler again has no effect.
-    RemoveHandler(handler->GetMiscName(), false);
+    auto misc_name = handler->misc_name();
+    LOG(INFO) << "Handler thread about to exit: " << misc_name;
+
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        auto iter = FindHandler(&lock, handler->misc_name());
+        if (iter == dm_users_.end()) {
+            // RemoveAndJoinHandler() already removed us from the list, and is
+            // now waiting on a join(), so just return. Additionally, release
+            // all the resources held by snapuserd object which are shared
+            // by worker threads. This should be done when the last reference
+            // of "handler" is released; but we will explicitly release here
+            // to make sure snapuserd object is freed as it is the biggest
+            // consumer of memory in the daemon.
+            handler->FreeResources();
+            LOG(INFO) << "Exiting handler thread to allow for join: " << misc_name;
+            return;
+        }
+
+        LOG(INFO) << "Exiting handler thread and freeing resources: " << misc_name;
+
+        if (handler->snapuserd()->IsAttached()) {
+            handler->thread().detach();
+        }
+
+        // Important: free resources within the lock. This ensures that if
+        // WaitForDelete() is called, the handler is either in the list, or
+        // it's not and its resources are guaranteed to be freed.
+        handler->FreeResources();
+    }
 }
 
 bool SnapuserdServer::Start(const std::string& socketname) {
@@ -286,9 +291,26 @@
             }
         }
     }
+
+    JoinAllThreads();
     return true;
 }
 
+void SnapuserdServer::JoinAllThreads() {
+    // Acquire the thread list within the lock.
+    std::vector<std::shared_ptr<DmUserHandler>> dm_users;
+    {
+        std::lock_guard<std::mutex> guard(lock_);
+        dm_users = std::move(dm_users_);
+    }
+
+    for (auto& client : dm_users) {
+        auto& th = client->thread();
+
+        if (th.joinable()) th.join();
+    }
+}
+
 void SnapuserdServer::AddWatchedFd(android::base::borrowed_fd fd) {
     struct pollfd p = {};
     p.fd = fd.get();
@@ -329,19 +351,57 @@
     SetTerminating();
 }
 
+std::shared_ptr<DmUserHandler> SnapuserdServer::AddHandler(const std::string& misc_name,
+                                                           const std::string& cow_device_path,
+                                                           const std::string& backing_device) {
+    auto snapuserd = std::make_shared<Snapuserd>(misc_name, cow_device_path, backing_device);
+    if (!snapuserd->InitCowDevice()) {
+        LOG(ERROR) << "Failed to initialize Snapuserd";
+        return nullptr;
+    }
+
+    if (!snapuserd->InitializeWorkers()) {
+        LOG(ERROR) << "Failed to initialize workers";
+        return nullptr;
+    }
+
+    auto handler = std::make_shared<DmUserHandler>(snapuserd);
+    {
+        std::lock_guard<std::mutex> lock(lock_);
+        if (FindHandler(&lock, misc_name) != dm_users_.end()) {
+            LOG(ERROR) << "Handler already exists: " << misc_name;
+            return nullptr;
+        }
+        dm_users_.push_back(handler);
+    }
+    return handler;
+}
+
+bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) {
+    if (handler->snapuserd()->IsAttached()) {
+        LOG(ERROR) << "Handler already attached";
+        return false;
+    }
+
+    handler->snapuserd()->AttachControlDevice();
+
+    handler->thread() = std::thread(std::bind(&SnapuserdServer::RunThread, this, handler));
+    return true;
+}
+
 auto SnapuserdServer::FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
                                   const std::string& misc_name) -> HandlerList::iterator {
     CHECK(proof_of_lock);
 
     for (auto iter = dm_users_.begin(); iter != dm_users_.end(); iter++) {
-        if ((*iter)->GetMiscName() == misc_name) {
+        if ((*iter)->misc_name() == misc_name) {
             return iter;
         }
     }
     return dm_users_.end();
 }
 
-bool SnapuserdServer::RemoveHandler(const std::string& misc_name, bool wait) {
+bool SnapuserdServer::RemoveAndJoinHandler(const std::string& misc_name) {
     std::shared_ptr<DmUserHandler> handler;
     {
         std::lock_guard<std::mutex> lock(lock_);
@@ -356,10 +416,8 @@
     }
 
     auto& th = handler->thread();
-    if (th.joinable() && wait) {
+    if (th.joinable()) {
         th.join();
-    } else if (handler->snapuserd()->IsAttached()) {
-        th.detach();
     }
     return true;
 }
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd_server.h
similarity index 75%
rename from fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h
rename to fs_mgr/libsnapshot/snapuserd_server.h
index cadfd71..6699189 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapuserd_server.h
+++ b/fs_mgr/libsnapshot/snapuserd_server.h
@@ -28,7 +28,7 @@
 #include <vector>
 
 #include <android-base/unique_fd.h>
-#include <libsnapshot/snapuserd.h>
+#include "snapuserd.h"
 
 namespace android {
 namespace snapshot {
@@ -41,22 +41,32 @@
     QUERY,
     STOP,
     DELETE,
+    DETACH,
     INVALID,
 };
 
 class DmUserHandler {
-  private:
-    std::thread thread_;
-    std::unique_ptr<Snapuserd> snapuserd_;
-
   public:
-    explicit DmUserHandler(std::unique_ptr<Snapuserd>&& snapuserd)
-        : snapuserd_(std::move(snapuserd)) {}
+    explicit DmUserHandler(std::shared_ptr<Snapuserd> snapuserd);
 
-    const std::unique_ptr<Snapuserd>& snapuserd() const { return snapuserd_; }
+    void FreeResources() {
+        // Each worker thread holds a reference to snapuserd.
+        // Clear them so that all the resources
+        // held by snapuserd is released
+        if (snapuserd_) {
+            snapuserd_->FreeResources();
+            snapuserd_ = nullptr;
+        }
+    }
+    const std::shared_ptr<Snapuserd>& snapuserd() const { return snapuserd_; }
     std::thread& thread() { return thread_; }
 
-    const std::string& GetMiscName() const;
+    const std::string& misc_name() const { return misc_name_; }
+
+  private:
+    std::thread thread_;
+    std::shared_ptr<Snapuserd> snapuserd_;
+    std::string misc_name_;
 };
 
 class Stoppable {
@@ -70,8 +80,9 @@
 
     bool StopRequested() {
         // checks if value in future object is available
-        if (futureObj_.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout)
+        if (futureObj_.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout) {
             return false;
+        }
         return true;
     }
     // Request the thread to stop by setting value in promise object
@@ -97,15 +108,15 @@
     bool Receivemsg(android::base::borrowed_fd fd, const std::string& str);
 
     void ShutdownThreads();
-    bool RemoveHandler(const std::string& control_device, bool wait);
+    bool RemoveAndJoinHandler(const std::string& control_device);
     DaemonOperations Resolveop(std::string& input);
     std::string GetDaemonStatus();
     void Parsemsg(std::string const& msg, const char delim, std::vector<std::string>& out);
 
-    void SetTerminating() { terminating_ = true; }
     bool IsTerminating() { return terminating_; }
 
     void RunThread(std::shared_ptr<DmUserHandler> handler);
+    void JoinAllThreads();
 
     // Find a DmUserHandler within a lock.
     HandlerList::iterator FindHandler(std::lock_guard<std::mutex>* proof_of_lock,
@@ -118,6 +129,13 @@
     bool Start(const std::string& socketname);
     bool Run();
     void Interrupt();
+
+    std::shared_ptr<DmUserHandler> AddHandler(const std::string& misc_name,
+                                              const std::string& cow_device_path,
+                                              const std::string& backing_device);
+    bool StartHandler(const std::shared_ptr<DmUserHandler>& handler);
+
+    void SetTerminating() { terminating_ = true; }
 };
 
 }  // namespace snapshot
diff --git a/fs_mgr/libsnapshot/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd_worker.cpp
new file mode 100644
index 0000000..682f9da
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapuserd_worker.cpp
@@ -0,0 +1,877 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd.h"
+
+#include <csignal>
+#include <optional>
+#include <set>
+
+#include <libsnapshot/snapuserd_client.h>
+
+namespace android {
+namespace snapshot {
+
+using namespace android;
+using namespace android::dm;
+using android::base::unique_fd;
+
+#define SNAP_LOG(level) LOG(level) << misc_name_ << ": "
+#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": "
+
+void BufferSink::Initialize(size_t size) {
+    buffer_size_ = size;
+    buffer_offset_ = 0;
+    buffer_ = std::make_unique<uint8_t[]>(size);
+}
+
+void* BufferSink::GetPayloadBuffer(size_t size) {
+    if ((buffer_size_ - buffer_offset_) < size) return nullptr;
+
+    char* buffer = reinterpret_cast<char*>(GetBufPtr());
+    struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
+    return (char*)msg->payload.buf + buffer_offset_;
+}
+
+void* BufferSink::GetBuffer(size_t requested, size_t* actual) {
+    void* buf = GetPayloadBuffer(requested);
+    if (!buf) {
+        *actual = 0;
+        return nullptr;
+    }
+    *actual = requested;
+    return buf;
+}
+
+struct dm_user_header* BufferSink::GetHeaderPtr() {
+    if (!(sizeof(struct dm_user_header) <= buffer_size_)) {
+        return nullptr;
+    }
+    char* buf = reinterpret_cast<char*>(GetBufPtr());
+    struct dm_user_header* header = (struct dm_user_header*)(&(buf[0]));
+    return header;
+}
+
+void* BufferSink::GetPayloadBufPtr() {
+    char* buffer = reinterpret_cast<char*>(GetBufPtr());
+    struct dm_user_message* msg = reinterpret_cast<struct dm_user_message*>(&(buffer[0]));
+    return msg->payload.buf;
+}
+
+WorkerThread::WorkerThread(const std::string& cow_device, const std::string& backing_device,
+                           const std::string& control_device, const std::string& misc_name,
+                           std::shared_ptr<Snapuserd> snapuserd) {
+    cow_device_ = cow_device;
+    backing_store_device_ = backing_device;
+    control_device_ = control_device;
+    misc_name_ = misc_name;
+    snapuserd_ = snapuserd;
+    exceptions_per_area_ = (CHUNK_SIZE << SECTOR_SHIFT) / sizeof(struct disk_exception);
+}
+
+bool WorkerThread::InitializeFds() {
+    backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY));
+    if (backing_store_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_;
+        return false;
+    }
+
+    cow_fd_.reset(open(cow_device_.c_str(), O_RDWR));
+    if (cow_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_;
+        return false;
+    }
+
+    ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR));
+    if (ctrl_fd_ < 0) {
+        SNAP_PLOG(ERROR) << "Unable to open " << control_device_;
+        return false;
+    }
+
+    return true;
+}
+
+bool WorkerThread::InitReader() {
+    reader_ = std::make_unique<CowReader>();
+    if (!reader_->InitForMerge(std::move(cow_fd_))) {
+        return false;
+    }
+
+    return true;
+}
+
+// Construct kernel COW header in memory
+// This header will be in sector 0. The IO
+// request will always be 4k. After constructing
+// the header, zero out the remaining block.
+void WorkerThread::ConstructKernelCowHeader() {
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+
+    memset(buffer, 0, BLOCK_SZ);
+
+    struct disk_header* dh = reinterpret_cast<struct disk_header*>(buffer);
+
+    dh->magic = SNAP_MAGIC;
+    dh->valid = SNAPSHOT_VALID;
+    dh->version = SNAPSHOT_DISK_VERSION;
+    dh->chunk_size = CHUNK_SIZE;
+}
+
+// Start the replace operation. This will read the
+// internal COW format and if the block is compressed,
+// it will be de-compressed.
+bool WorkerThread::ProcessReplaceOp(const CowOperation* cow_op) {
+    if (!reader_->ReadData(*cow_op, &bufsink_)) {
+        SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block;
+        return false;
+    }
+
+    return true;
+}
+
+bool WorkerThread::ReadFromBaseDevice(const CowOperation* cow_op) {
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "ReadFromBaseDevice: Failed to get payload buffer";
+        return false;
+    }
+    SNAP_LOG(DEBUG) << " ReadFromBaseDevice...: new-block: " << cow_op->new_block
+                    << " Source: " << cow_op->source;
+    if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ,
+                                          cow_op->source * BLOCK_SZ)) {
+        SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_
+                         << "at block :" << cow_op->source;
+        return false;
+    }
+
+    return true;
+}
+
+bool WorkerThread::GetReadAheadPopulatedBuffer(const CowOperation* cow_op) {
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "GetReadAheadPopulatedBuffer: Failed to get payload buffer";
+        return false;
+    }
+
+    if (!snapuserd_->GetReadAheadPopulatedBuffer(cow_op->new_block, buffer)) {
+        return false;
+    }
+
+    return true;
+}
+
+// Start the copy operation. This will read the backing
+// block device which is represented by cow_op->source.
+bool WorkerThread::ProcessCopyOp(const CowOperation* cow_op) {
+    if (!GetReadAheadPopulatedBuffer(cow_op)) {
+        SNAP_LOG(DEBUG) << " GetReadAheadPopulatedBuffer failed..."
+                        << " new_block: " << cow_op->new_block;
+        if (!ReadFromBaseDevice(cow_op)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool WorkerThread::ProcessZeroOp() {
+    // Zero out the entire block
+    void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ);
+    if (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "ProcessZeroOp: Failed to get payload buffer";
+        return false;
+    }
+
+    memset(buffer, 0, BLOCK_SZ);
+    return true;
+}
+
+bool WorkerThread::ProcessCowOp(const CowOperation* cow_op) {
+    if (cow_op == nullptr) {
+        SNAP_LOG(ERROR) << "ProcessCowOp: Invalid cow_op";
+        return false;
+    }
+
+    switch (cow_op->type) {
+        case kCowReplaceOp: {
+            return ProcessReplaceOp(cow_op);
+        }
+
+        case kCowZeroOp: {
+            return ProcessZeroOp();
+        }
+
+        case kCowCopyOp: {
+            return ProcessCopyOp(cow_op);
+        }
+
+        default: {
+            SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type;
+        }
+    }
+    return false;
+}
+
+int WorkerThread::ReadUnalignedSector(
+        sector_t sector, size_t size,
+        std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it) {
+    size_t skip_sector_size = 0;
+
+    SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size
+                    << " Aligned sector: " << it->first;
+
+    if (!ProcessCowOp(it->second)) {
+        SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size
+                        << " Aligned sector: " << it->first;
+        return -1;
+    }
+
+    int num_sectors_skip = sector - it->first;
+
+    if (num_sectors_skip > 0) {
+        skip_sector_size = num_sectors_skip << SECTOR_SHIFT;
+        char* buffer = reinterpret_cast<char*>(bufsink_.GetBufPtr());
+        struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0]));
+
+        if (skip_sector_size == BLOCK_SZ) {
+            SNAP_LOG(ERROR) << "Invalid un-aligned IO request at sector: " << sector
+                            << " Base-sector: " << it->first;
+            return -1;
+        }
+
+        memmove(msg->payload.buf, (char*)msg->payload.buf + skip_sector_size,
+                (BLOCK_SZ - skip_sector_size));
+    }
+
+    bufsink_.ResetBufferOffset();
+    return std::min(size, (BLOCK_SZ - skip_sector_size));
+}
+
+/*
+ * Read the data for a given COW Operation.
+ *
+ * Kernel can issue IO at a sector granularity.
+ * Hence, an IO may end up with reading partial
+ * data from a COW operation or we may also
+ * end up with interspersed request between
+ * two COW operations.
+ *
+ */
+int WorkerThread::ReadData(sector_t sector, size_t size) {
+    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+    std::vector<std::pair<sector_t, const CowOperation*>>::iterator it;
+    /*
+     * chunk_map stores COW operation at 4k granularity.
+     * If the requested IO with the sector falls on the 4k
+     * boundary, then we can read the COW op directly without
+     * any issue.
+     *
+     * However, if the requested sector is not 4K aligned,
+     * then we will have the find the nearest COW operation
+     * and chop the 4K block to fetch the requested sector.
+     */
+    it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr),
+                          Snapuserd::compare);
+
+    if (!(it != chunk_vec.end())) {
+        SNAP_LOG(ERROR) << "ReadData: Sector " << sector << " not found in chunk_vec";
+        return -1;
+    }
+
+    // We didn't find the required sector; hence find the previous sector
+    // as lower_bound will gives us the value greater than
+    // the requested sector
+    if (it->first != sector) {
+        if (it != chunk_vec.begin()) {
+            --it;
+        }
+
+        /*
+         * If the IO is spanned between two COW operations,
+         * split the IO into two parts:
+         *
+         * 1: Read the first part from the single COW op
+         * 2: Read the second part from the next COW op.
+         *
+         * Ex: Let's say we have a 1024 Bytes IO request.
+         *
+         * 0       COW OP-1  4096     COW OP-2  8192
+         * |******************|*******************|
+         *              |*****|*****|
+         *           3584           4608
+         *              <- 1024B - >
+         *
+         * We have two COW operations which are 4k blocks.
+         * The IO is requested for 1024 Bytes which are spanned
+         * between two COW operations. We will split this IO
+         * into two parts:
+         *
+         * 1: IO of size 512B from offset 3584 bytes (COW OP-1)
+         * 2: IO of size 512B from offset 4096 bytes (COW OP-2)
+         */
+        return ReadUnalignedSector(sector, size, it);
+    }
+
+    int num_ops = DIV_ROUND_UP(size, BLOCK_SZ);
+    sector_t read_sector = sector;
+    while (num_ops) {
+        // We have to make sure that the reads are
+        // sequential; there shouldn't be a data
+        // request merged with a metadata IO.
+        if (it->first != read_sector) {
+            SNAP_LOG(ERROR) << "Invalid IO request: read_sector: " << read_sector
+                            << " cow-op sector: " << it->first;
+            return -1;
+        } else if (!ProcessCowOp(it->second)) {
+            return -1;
+        }
+        num_ops -= 1;
+        read_sector += (BLOCK_SZ >> SECTOR_SHIFT);
+
+        it++;
+
+        if (it == chunk_vec.end() && num_ops) {
+            SNAP_LOG(ERROR) << "Invalid IO request at sector " << sector
+                            << " COW ops completed; pending read-request: " << num_ops;
+            return -1;
+        }
+        // Update the buffer offset
+        bufsink_.UpdateBufferOffset(BLOCK_SZ);
+    }
+
+    // Reset the buffer offset
+    bufsink_.ResetBufferOffset();
+    return size;
+}
+
+/*
+ * dm-snap does prefetch reads while reading disk-exceptions.
+ * By default, prefetch value is set to 12; this means that
+ * dm-snap will issue 12 areas wherein each area is a 4k page
+ * of disk-exceptions.
+ *
+ * If during prefetch, if the chunk-id seen is beyond the
+ * actual number of metadata page, fill the buffer with zero.
+ * When dm-snap starts parsing the buffer, it will stop
+ * reading metadata page once the buffer content is zero.
+ */
+bool WorkerThread::ZerofillDiskExceptions(size_t read_size) {
+    size_t size = exceptions_per_area_ * sizeof(struct disk_exception);
+
+    if (read_size > size) {
+        return false;
+    }
+
+    void* buffer = bufsink_.GetPayloadBuffer(size);
+    if (buffer == nullptr) {
+        SNAP_LOG(ERROR) << "ZerofillDiskExceptions: Failed to get payload buffer";
+        return false;
+    }
+
+    memset(buffer, 0, size);
+    return true;
+}
+
+/*
+ * A disk exception is a simple mapping of old_chunk to new_chunk.
+ * When dm-snapshot device is created, kernel requests these mapping.
+ *
+ * Each disk exception is of size 16 bytes. Thus a single 4k page can
+ * have:
+ *
+ * exceptions_per_area_ = 4096/16 = 256. This entire 4k page
+ * is considered a metadata page and it is represented by chunk ID.
+ *
+ * Convert the chunk ID to index into the vector which gives us
+ * the metadata page.
+ */
+bool WorkerThread::ReadDiskExceptions(chunk_t chunk, size_t read_size) {
+    uint32_t stride = exceptions_per_area_ + 1;
+    size_t size;
+    const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec();
+
+    // ChunkID to vector index
+    lldiv_t divresult = lldiv(chunk, stride);
+
+    if (divresult.quot < vec.size()) {
+        size = exceptions_per_area_ * sizeof(struct disk_exception);
+
+        if (read_size != size) {
+            SNAP_LOG(ERROR) << "ReadDiskExceptions: read_size: " << read_size
+                            << " does not match with size: " << size;
+            return false;
+        }
+
+        void* buffer = bufsink_.GetPayloadBuffer(size);
+        if (buffer == nullptr) {
+            SNAP_LOG(ERROR) << "ReadDiskExceptions: Failed to get payload buffer of size: " << size;
+            return false;
+        }
+
+        memcpy(buffer, vec[divresult.quot].get(), size);
+    } else {
+        return ZerofillDiskExceptions(read_size);
+    }
+
+    return true;
+}
+
+loff_t WorkerThread::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer,
+                                         int* unmerged_exceptions) {
+    loff_t offset = 0;
+    *unmerged_exceptions = 0;
+
+    while (*unmerged_exceptions <= exceptions_per_area_) {
+        struct disk_exception* merged_de =
+                reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
+        struct disk_exception* cow_de =
+                reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
+
+        // Unmerged op by the kernel
+        if (merged_de->old_chunk != 0 || merged_de->new_chunk != 0) {
+            if (!(merged_de->old_chunk == cow_de->old_chunk)) {
+                SNAP_LOG(ERROR) << "GetMergeStartOffset: merged_de->old_chunk: "
+                                << merged_de->old_chunk
+                                << "cow_de->old_chunk: " << cow_de->old_chunk;
+                return -1;
+            }
+
+            if (!(merged_de->new_chunk == cow_de->new_chunk)) {
+                SNAP_LOG(ERROR) << "GetMergeStartOffset: merged_de->new_chunk: "
+                                << merged_de->new_chunk
+                                << "cow_de->new_chunk: " << cow_de->new_chunk;
+                return -1;
+            }
+
+            offset += sizeof(struct disk_exception);
+            *unmerged_exceptions += 1;
+            continue;
+        }
+
+        break;
+    }
+
+    SNAP_LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset;
+    return offset;
+}
+
+int WorkerThread::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
+                                       int unmerged_exceptions, bool* copy_op, bool* commit) {
+    int merged_ops_cur_iter = 0;
+    std::unordered_map<uint64_t, void*>& read_ahead_buffer_map = snapuserd_->GetReadAheadMap();
+    *copy_op = false;
+    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+
+    // Find the operations which are merged in this cycle.
+    while ((unmerged_exceptions + merged_ops_cur_iter) < exceptions_per_area_) {
+        struct disk_exception* merged_de =
+                reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset);
+        struct disk_exception* cow_de =
+                reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset);
+
+        if (!(merged_de->new_chunk == 0)) {
+            SNAP_LOG(ERROR) << "GetNumberOfMergedOps: Invalid new-chunk: " << merged_de->new_chunk;
+            return -1;
+        }
+
+        if (!(merged_de->old_chunk == 0)) {
+            SNAP_LOG(ERROR) << "GetNumberOfMergedOps: Invalid old-chunk: " << merged_de->old_chunk;
+            return -1;
+        }
+
+        if (cow_de->new_chunk != 0) {
+            merged_ops_cur_iter += 1;
+            offset += sizeof(struct disk_exception);
+            auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
+                                       std::make_pair(ChunkToSector(cow_de->new_chunk), nullptr),
+                                       Snapuserd::compare);
+
+            if (!(it != chunk_vec.end())) {
+                SNAP_LOG(ERROR) << "Sector not found: " << ChunkToSector(cow_de->new_chunk);
+                return -1;
+            }
+
+            if (!(it->first == ChunkToSector(cow_de->new_chunk))) {
+                SNAP_LOG(ERROR) << "Invalid sector: " << ChunkToSector(cow_de->new_chunk);
+                return -1;
+            }
+            const CowOperation* cow_op = it->second;
+
+            if (snapuserd_->IsReadAheadFeaturePresent() && cow_op->type == kCowCopyOp) {
+                *copy_op = true;
+                // Every single copy operation has to come from read-ahead
+                // cache.
+                if (read_ahead_buffer_map.find(cow_op->new_block) == read_ahead_buffer_map.end()) {
+                    SNAP_LOG(ERROR)
+                            << " Block: " << cow_op->new_block << " not found in read-ahead cache"
+                            << " Source: " << cow_op->source;
+                    return -1;
+                }
+                // If this is a final block merged in the read-ahead buffer
+                // region, notify the read-ahead thread to make forward
+                // progress
+                if (cow_op->new_block == snapuserd_->GetFinalBlockMerged()) {
+                    *commit = true;
+                }
+            }
+
+            // zero out to indicate that operation is merged.
+            cow_de->old_chunk = 0;
+            cow_de->new_chunk = 0;
+        } else if (cow_de->old_chunk == 0) {
+            // Already merged op in previous iteration or
+            // This could also represent a partially filled area.
+            //
+            // If the op was merged in previous cycle, we don't have
+            // to count them.
+            break;
+        } else {
+            SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata: "
+                            << " merged_de-old-chunk: " << merged_de->old_chunk
+                            << " merged_de-new-chunk: " << merged_de->new_chunk
+                            << " cow_de-old-chunk: " << cow_de->old_chunk
+                            << " cow_de-new-chunk: " << cow_de->new_chunk
+                            << " unmerged_exceptions: " << unmerged_exceptions
+                            << " merged_ops_cur_iter: " << merged_ops_cur_iter
+                            << " offset: " << offset;
+            return -1;
+        }
+    }
+    return merged_ops_cur_iter;
+}
+
+bool WorkerThread::ProcessMergeComplete(chunk_t chunk, void* buffer) {
+    uint32_t stride = exceptions_per_area_ + 1;
+    const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec();
+    bool copy_op = false;
+    bool commit = false;
+
+    // ChunkID to vector index
+    lldiv_t divresult = lldiv(chunk, stride);
+
+    if (!(divresult.quot < vec.size())) {
+        SNAP_LOG(ERROR) << "ProcessMergeComplete: Invalid chunk: " << chunk
+                        << " Metadata-Index: " << divresult.quot << " Area-size: " << vec.size();
+        return false;
+    }
+
+    SNAP_LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk
+                    << " Metadata-Index: " << divresult.quot;
+
+    int unmerged_exceptions = 0;
+    loff_t offset = GetMergeStartOffset(buffer, vec[divresult.quot].get(), &unmerged_exceptions);
+
+    if (offset < 0) {
+        SNAP_LOG(ERROR) << "GetMergeStartOffset failed: unmerged_exceptions: "
+                        << unmerged_exceptions;
+        return false;
+    }
+
+    int merged_ops_cur_iter = GetNumberOfMergedOps(buffer, vec[divresult.quot].get(), offset,
+                                                   unmerged_exceptions, &copy_op, &commit);
+
+    // There should be at least one operation merged in this cycle
+    if (!(merged_ops_cur_iter > 0)) {
+        SNAP_LOG(ERROR) << "Merge operation failed: " << merged_ops_cur_iter;
+        return false;
+    }
+
+    if (copy_op) {
+        if (commit) {
+            // Push the flushing logic to read-ahead thread so that merge thread
+            // can make forward progress. Sync will happen in the background
+            snapuserd_->StartReadAhead();
+        }
+    } else {
+        // Non-copy ops and all ops in older COW format
+        if (!snapuserd_->CommitMerge(merged_ops_cur_iter)) {
+            SNAP_LOG(ERROR) << "CommitMerge failed...";
+            return false;
+        }
+    }
+
+    SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk;
+    return true;
+}
+
+// Read Header from dm-user misc device. This gives
+// us the sector number for which IO is issued by dm-snapshot device
+bool WorkerThread::ReadDmUserHeader() {
+    if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) {
+        if (errno != ENOTBLK) {
+            SNAP_PLOG(ERROR) << "Control-read failed";
+        }
+
+        return false;
+    }
+
+    return true;
+}
+
+// Send the payload/data back to dm-user misc device.
+bool WorkerThread::WriteDmUserPayload(size_t size, bool header_response) {
+    size_t payload_size = size;
+    void* buf = bufsink_.GetPayloadBufPtr();
+    if (header_response) {
+        payload_size += sizeof(struct dm_user_header);
+        buf = bufsink_.GetBufPtr();
+    }
+
+    if (!android::base::WriteFully(ctrl_fd_, buf, payload_size)) {
+        SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << payload_size;
+        return false;
+    }
+
+    return true;
+}
+
+bool WorkerThread::ReadDmUserPayload(void* buffer, size_t size) {
+    if (!android::base::ReadFully(ctrl_fd_, buffer, size)) {
+        SNAP_PLOG(ERROR) << "ReadDmUserPayload failed size: " << size;
+        return false;
+    }
+
+    return true;
+}
+
+bool WorkerThread::DmuserWriteRequest() {
+    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+    // device mapper has the capability to allow
+    // targets to flush the cache when writes are completed. This
+    // is controlled by each target by a flag "flush_supported".
+    // This flag is set by dm-user. When flush is supported,
+    // a number of zero-length bio's will be submitted to
+    // the target for the purpose of flushing cache. It is the
+    // responsibility of the target driver - which is dm-user in this
+    // case, to remap these bio's to the underlying device. Since,
+    // there is no underlying device for dm-user, this zero length
+    // bio's gets routed to daemon.
+    //
+    // Flush operations are generated post merge by dm-snap by having
+    // REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything
+    // to flush per se; hence, just respond back with a success message.
+    if (header->sector == 0) {
+        if (!(header->len == 0)) {
+            SNAP_LOG(ERROR) << "Invalid header length received from sector 0: " << header->len;
+            header->type = DM_USER_RESP_ERROR;
+        } else {
+            header->type = DM_USER_RESP_SUCCESS;
+        }
+
+        if (!WriteDmUserPayload(0, true)) {
+            return false;
+        }
+        return true;
+    }
+
+    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+    size_t remaining_size = header->len;
+    size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+
+    chunk_t chunk = SectorToChunk(header->sector);
+    auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
+                               std::make_pair(header->sector, nullptr), Snapuserd::compare);
+
+    bool not_found = (it == chunk_vec.end() || it->first != header->sector);
+
+    if (not_found) {
+        void* buffer = bufsink_.GetPayloadBuffer(read_size);
+        if (buffer == nullptr) {
+            SNAP_LOG(ERROR) << "DmuserWriteRequest: Failed to get payload buffer of size: "
+                            << read_size;
+            header->type = DM_USER_RESP_ERROR;
+        } else {
+            header->type = DM_USER_RESP_SUCCESS;
+
+            if (!ReadDmUserPayload(buffer, read_size)) {
+                SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk
+                                << "Sector: " << header->sector;
+                header->type = DM_USER_RESP_ERROR;
+            }
+
+            if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) {
+                SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk
+                                << "Sector: " << header->sector;
+                header->type = DM_USER_RESP_ERROR;
+            }
+        }
+    } else {
+        SNAP_LOG(ERROR) << "DmuserWriteRequest: Invalid sector received: header->sector";
+        header->type = DM_USER_RESP_ERROR;
+    }
+
+    if (!WriteDmUserPayload(0, true)) {
+        return false;
+    }
+
+    return true;
+}
+
+bool WorkerThread::DmuserReadRequest() {
+    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+    size_t remaining_size = header->len;
+    loff_t offset = 0;
+    sector_t sector = header->sector;
+    std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
+    bool header_response = true;
+    do {
+        size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
+
+        int ret = read_size;
+        header->type = DM_USER_RESP_SUCCESS;
+        chunk_t chunk = SectorToChunk(header->sector);
+
+        // Request to sector 0 is always for kernel
+        // representation of COW header. This IO should be only
+        // once during dm-snapshot device creation. We should
+        // never see multiple IO requests. Additionally this IO
+        // will always be a single 4k.
+        if (header->sector == 0) {
+            if (read_size == BLOCK_SZ) {
+                ConstructKernelCowHeader();
+                SNAP_LOG(DEBUG) << "Kernel header constructed";
+            } else {
+                SNAP_LOG(ERROR) << "Invalid read_size: " << read_size << " for sector 0";
+                header->type = DM_USER_RESP_ERROR;
+            }
+        } else {
+            auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(),
+                                       std::make_pair(header->sector, nullptr), Snapuserd::compare);
+            bool not_found = (it == chunk_vec.end() || it->first != header->sector);
+            if (!offset && (read_size == BLOCK_SZ) && not_found) {
+                if (!ReadDiskExceptions(chunk, read_size)) {
+                    SNAP_LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk
+                                    << "Sector: " << header->sector;
+                    header->type = DM_USER_RESP_ERROR;
+                } else {
+                    SNAP_LOG(DEBUG) << "ReadDiskExceptions success for chunk id: " << chunk
+                                    << "Sector: " << header->sector;
+                }
+            } else {
+                chunk_t num_sectors_read = (offset >> SECTOR_SHIFT);
+
+                ret = ReadData(sector + num_sectors_read, read_size);
+                if (ret < 0) {
+                    SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk
+                                    << " Sector: " << (sector + num_sectors_read)
+                                    << " size: " << read_size << " header-len: " << header->len;
+                    header->type = DM_USER_RESP_ERROR;
+                } else {
+                    SNAP_LOG(DEBUG) << "ReadData success for chunk id: " << chunk
+                                    << "Sector: " << header->sector;
+                }
+            }
+        }
+
+        // Just return the header if it is an error
+        if (header->type == DM_USER_RESP_ERROR) {
+            SNAP_LOG(ERROR) << "IO read request failed...";
+            ret = 0;
+        }
+
+        if (!header_response) {
+            CHECK(header->type == DM_USER_RESP_SUCCESS)
+                    << " failed for sector: " << sector << " header->len: " << header->len
+                    << " remaining_size: " << remaining_size;
+        }
+
+        // Daemon will not be terminated if there is any error. We will
+        // just send the error back to dm-user.
+        if (!WriteDmUserPayload(ret, header_response)) {
+            return false;
+        }
+
+        if (header->type == DM_USER_RESP_ERROR) {
+            break;
+        }
+
+        remaining_size -= ret;
+        offset += ret;
+        header_response = false;
+    } while (remaining_size > 0);
+
+    return true;
+}
+
+void WorkerThread::InitializeBufsink() {
+    // Allocate the buffer which is used to communicate between
+    // daemon and dm-user. The buffer comprises of header and a fixed payload.
+    // If the dm-user requests a big IO, the IO will be broken into chunks
+    // of PAYLOAD_SIZE.
+    size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE;
+    bufsink_.Initialize(buf_size);
+}
+
+bool WorkerThread::RunThread() {
+    InitializeBufsink();
+
+    if (!InitializeFds()) {
+        return false;
+    }
+
+    if (!InitReader()) {
+        return false;
+    }
+
+    // Start serving IO
+    while (true) {
+        if (!ProcessIORequest()) {
+            break;
+        }
+    }
+
+    CloseFds();
+    reader_->CloseCowFd();
+
+    return true;
+}
+
+bool WorkerThread::ProcessIORequest() {
+    struct dm_user_header* header = bufsink_.GetHeaderPtr();
+
+    if (!ReadDmUserHeader()) {
+        return false;
+    }
+
+    SNAP_LOG(DEBUG) << "Daemon: msg->seq: " << std::dec << header->seq;
+    SNAP_LOG(DEBUG) << "Daemon: msg->len: " << std::dec << header->len;
+    SNAP_LOG(DEBUG) << "Daemon: msg->sector: " << std::dec << header->sector;
+    SNAP_LOG(DEBUG) << "Daemon: msg->type: " << std::dec << header->type;
+    SNAP_LOG(DEBUG) << "Daemon: msg->flags: " << std::dec << header->flags;
+
+    switch (header->type) {
+        case DM_USER_REQ_MAP_READ: {
+            if (!DmuserReadRequest()) {
+                return false;
+            }
+            break;
+        }
+
+        case DM_USER_REQ_MAP_WRITE: {
+            if (!DmuserWriteRequest()) {
+                return false;
+            }
+            break;
+        }
+    }
+
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index 6104c82..e3e3af8 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -23,6 +23,7 @@
 #include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
 #include <openssl/sha.h>
+#include <payload_consumer/file_descriptor.h>
 
 namespace android {
 namespace snapshot {
@@ -169,6 +170,37 @@
     return true;
 }
 
+std::string HashSnapshot(ISnapshotWriter* writer) {
+    auto reader = writer->OpenReader();
+    if (!reader) {
+        return {};
+    }
+
+    SHA256_CTX ctx;
+    SHA256_Init(&ctx);
+
+    uint64_t remaining = reader->BlockDevSize();
+    char buffer[4096];
+    while (remaining) {
+        size_t to_read =
+                static_cast<size_t>(std::min(remaining, static_cast<uint64_t>(sizeof(buffer))));
+        ssize_t read = reader->Read(&buffer, to_read);
+        if (read <= 0) {
+            if (read < 0) {
+                LOG(ERROR) << "Failed to read from snapshot writer";
+                return {};
+            }
+            break;
+        }
+        SHA256_Update(&ctx, buffer, to_read);
+        remaining -= static_cast<size_t>(read);
+    }
+
+    uint8_t out[32];
+    SHA256_Final(out, &ctx);
+    return ToHexString(out, sizeof(out));
+}
+
 std::optional<std::string> GetHash(const std::string& path) {
     std::string content;
     if (!android::base::ReadFileToString(path, &content, true)) {
diff --git a/fs_mgr/libsnapshot/update_engine/update_metadata.proto b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
index dda214e..69d72e1 100644
--- a/fs_mgr/libsnapshot/update_engine/update_metadata.proto
+++ b/fs_mgr/libsnapshot/update_engine/update_metadata.proto
@@ -73,6 +73,9 @@
 
 message DynamicPartitionMetadata {
     repeated DynamicPartitionGroup groups = 1;
+    optional bool vabc_enabled = 3;
+    optional string vabc_compression_param = 4;
+    optional uint32 cow_version = 5;
 }
 
 message DeltaArchiveManifest {
diff --git a/fs_mgr/libsnapshot/utility.cpp b/fs_mgr/libsnapshot/utility.cpp
index 7342fd4..4a2af1c 100644
--- a/fs_mgr/libsnapshot/utility.cpp
+++ b/fs_mgr/libsnapshot/utility.cpp
@@ -187,5 +187,13 @@
     return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false);
 }
 
+std::string GetOtherPartitionName(const std::string& name) {
+    auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name);
+    CHECK(suffix == "_a" || suffix == "_b");
+
+    auto other_suffix = (suffix == "_a") ? "_b" : "_a";
+    return name.substr(0, name.size() - suffix.size()) + other_suffix;
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/utility.h b/fs_mgr/libsnapshot/utility.h
index 3e6873b..671de9d 100644
--- a/fs_mgr/libsnapshot/utility.h
+++ b/fs_mgr/libsnapshot/utility.h
@@ -131,5 +131,8 @@
 
 bool IsCompressionEnabled();
 
+// Swap the suffix of a partition name.
+std::string GetOtherPartitionName(const std::string& name);
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libstorage_literals/Android.bp b/fs_mgr/libstorage_literals/Android.bp
index beb18ef..5b07168 100644
--- a/fs_mgr/libstorage_literals/Android.bp
+++ b/fs_mgr/libstorage_literals/Android.bp
@@ -1,7 +1,16 @@
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_headers {
     name: "libstorage_literals_headers",
     host_supported: true,
     recovery_available: true,
     export_include_dirs: ["."],
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
 }
diff --git a/fs_mgr/libvbmeta/Android.bp b/fs_mgr/libvbmeta/Android.bp
index bceabab..c197097 100644
--- a/fs_mgr/libvbmeta/Android.bp
+++ b/fs_mgr/libvbmeta/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 libvbmeta_lib_deps = [
     "libbase",
     "libcrypto",
@@ -50,4 +54,11 @@
         "avbtool",
         "vbmake",
     ],
-}
\ No newline at end of file
+    data: [
+        "data/*",
+    ],
+    // Not unit tests due to several binary and lib dependencies currently hard to replicate in continuous execution
+    test_options: {
+        unit_test: false,
+    },
+}
diff --git a/fs_mgr/libvbmeta/data/testkey_rsa2048.pem b/fs_mgr/libvbmeta/data/testkey_rsa2048.pem
new file mode 100644
index 0000000..867dcff
--- /dev/null
+++ b/fs_mgr/libvbmeta/data/testkey_rsa2048.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAxlVR3TIkouAOvH79vaJTgFhpfvVKQIeVkFRZPVXK/zY0Gvrh
+4JAqGjJoW/PfrQv5sdD36qtHH3a+G5hLZ6Ni+t/mtfjucxZfuLGC3kmJ1T3XqEKZ
+gXXI2IR7vVSoImREvDQGEDyJwtHzLANlkbGg0cghVhWZSCAndO8BenalC2v94/rt
+DfkPekH6dgU3Sf40T0sBSeSY94mOzTaqOR2pfV1rWlLRdWmo33zeHBv52Rlbt0dM
+uXAureXWiHztkm5GCBC1dgM+CaxNtizNEgC91KcD0xuRCCM2WxH+r1lpszyIJDct
+YbrFmVEYl/kjQpafhy7Nsk1fqSTyRdriZSYmTQIDAQABAoIBAQC+kJgaCuX8wYAn
+SXWQ0fmdZlXnMNRpcF0a0pD0SAzGb1RdYBXMaXiqtyhiwc53PPxsCDdNecjayIMd
+jJVXPTwLhTruOgMS/bp3gcgWwV34UHV4LJXGOGAE+jbS0hbDBMiudOYmj6RmVshp
+z9G1zZCSQNMXHaWsEYkX59XpzzoB384nRul2QgEtwzUNR9XlpzgtJBLk3SACkvsN
+mQ/DW8IWHXLg8vLn1LzVJ2e3B16H4MoE2TCHxqfMgr03IDRRJogkenQuQsFhevYT
+o/mJyHSWavVgzMHG9I5m+eepF4Wyhj1Y4WyKAuMI+9dHAX/h7Lt8XFCQCh5DbkVG
+zGr34sWBAoGBAOs7n7YZqNaaguovfIdRRsxxZr1yJAyDsr6w3yGImDZYju4c4WY9
+5esO2kP3FA4p0c7FhQF5oOb1rBuHEPp36cpL4aGeK87caqTfq63WZAujoTZpr9Lp
+BRbkL7w/xG7jpQ/clpA8sHzHGQs/nelxoOtC7E118FiRgvD/jdhlMyL9AoGBANfX
+vyoN1pplfT2xR8QOjSZ+Q35S/+SAtMuBnHx3l0qH2bbBjcvM1MNDWjnRDyaYhiRu
+i+KA7tqfib09+XpB3g5D6Ov7ls/Ldx0S/VcmVWtia2HK8y8iLGtokoBZKQ5AaFX2
+iQU8+tC4h69GnJYQKqNwgCUzh8+gHX5Y46oDiTmRAoGAYpOx8lX+czB8/Da6MNrW
+mIZNT8atZLEsDs2ANEVRxDSIcTCZJId7+m1W+nRoaycLTWNowZ1+2ErLvR10+AGY
+b7Ys79Wg9idYaY9yGn9lnZsMzAiuLeyIvXcSqgjvAKlVWrhOQFOughvNWvFl85Yy
+oWSCMlPiTLtt7CCsCKsgKuECgYBgdIp6GZsIfkgclKe0hqgvRoeU4TR3gcjJlM9A
+lBTo+pKhaBectplx9RxR8AnsPobbqwcaHnIfAuKDzjk5mEvKZjClnFXF4HAHbyAF
+nRzZEy9XkWFhc80T5rRpZO7C7qdxmu2aiKixM3V3L3/0U58qULEDbubHMw9bEhAT
+PudI8QKBgHEEiMm/hr9T41hbQi/LYanWnlFw1ue+osKuF8bXQuxnnHNuFT/c+9/A
+vWhgqG6bOEHu+p/IPrYm4tBMYlwsyh4nXCyGgDJLbLIfzKwKAWCtH9LwnyDVhOow
+GH9shdR+sW3Ew97xef02KAH4VlNANEmBV4sQNqWWvsYrcFm2rOdL
+-----END RSA PRIVATE KEY-----
diff --git a/fs_mgr/libvbmeta/super_vbmeta_test.cpp b/fs_mgr/libvbmeta/super_vbmeta_test.cpp
index daed0d1..7329a61 100644
--- a/fs_mgr/libvbmeta/super_vbmeta_test.cpp
+++ b/fs_mgr/libvbmeta/super_vbmeta_test.cpp
@@ -54,7 +54,7 @@
     cmd << "avbtool add_hashtree_footer"
         << " --image " << file_name << " --partition_name " << partition_name
         << " --partition_size " << FAKE_PARTITION_SIZE << " --algorithm SHA256_RSA2048"
-        << " --key external/avb/test/data/testkey_rsa2048.pem";
+        << " --key data/testkey_rsa2048.pem";
 
     int rc = system(cmd.str().c_str());
     EXPECT_TRUE(WIFEXITED(rc));
diff --git a/fs_mgr/tests/Android.bp b/fs_mgr/tests/Android.bp
index 9ed283a..335da9e 100644
--- a/fs_mgr/tests/Android.bp
+++ b/fs_mgr/tests/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_test {
     name: "CtsFsMgrTestCases",
     test_suites: [
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index f5bbe35..242fa93 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -213,6 +213,13 @@
     return ${ret}
 }
 
+[ "USAGE: adb_test <expression>
+
+Returns: exit status of the test expression" ]
+adb_test() {
+  adb_sh test "${@}" </dev/null
+}
+
 [ "USAGE: adb_reboot
 
 Returns: true if the reboot command succeeded" ]
@@ -740,7 +747,7 @@
   grep -v \
     -e "^\(overlay\|tmpfs\|none\|sysfs\|proc\|selinuxfs\|debugfs\|bpf\) " \
     -e "^\(binfmt_misc\|cg2_bpf\|pstore\|tracefs\|adb\|mtp\|ptp\|devpts\) " \
-    -e "^\(ramdumpfs\|binder\|/sys/kernel/debug\) " \
+    -e "^\(ramdumpfs\|binder\|/sys/kernel/debug\|securityfs\) " \
     -e " functionfs " \
     -e "^\(/data/media\|/dev/block/loop[0-9]*\) " \
     -e "^rootfs / rootfs rw," \
@@ -844,6 +851,9 @@
   NORMAL=""
 fi
 
+# Set an ERR trap handler to report any unhandled error
+trap 'die "line ${LINENO}: unhandled error"' ERR
+
 if ${print_time}; then
   echo "${BLUE}[     INFO ]${NORMAL}" start `date` >&2
 fi
@@ -871,10 +881,10 @@
 [ -z "${D}" -o -n "${ANDROID_SERIAL}" ] || ANDROID_SERIAL=${D}
 USB_SERIAL=
 if [ -n "${ANDROID_SERIAL}" -a "Darwin" != "${HOSTOS}" ]; then
-  USB_SERIAL="`find /sys/devices -name serial | grep usb`"
+  USB_SERIAL="`find /sys/devices -name serial | grep usb || true`"
   if [ -n "${USB_SERIAL}" ]; then
     USB_SERIAL=`echo "${USB_SERIAL}" |
-                  xargs grep -l ${ANDROID_SERIAL}`
+                  xargs grep -l ${ANDROID_SERIAL} || true`
   fi
 fi
 USB_ADDRESS=
@@ -956,7 +966,7 @@
     if inAdb; then
       reboot=false
       for d in ${OVERLAYFS_BACKING}; do
-        if adb_su ls -d /${d}/overlay </dev/null >/dev/null 2>/dev/null; then
+        if adb_test -d /${d}/overlay; then
           adb_su rm -rf /${d}/overlay </dev/null
           reboot=true
         fi
@@ -1010,7 +1020,10 @@
 echo "${GREEN}[ RUN      ]${NORMAL} Testing kernel support for overlayfs" >&2
 
 adb_wait || die "wait for device failed"
-adb_sh ls -d /sys/module/overlay </dev/null >/dev/null 2>/dev/null ||
+adb_root ||
+  die "initial setup"
+
+adb_test -d /sys/module/overlay ||
   adb_sh grep "nodev${TAB}overlay" /proc/filesystems </dev/null >/dev/null 2>/dev/null &&
   echo "${GREEN}[       OK ]${NORMAL} overlay module present" >&2 ||
   (
@@ -1019,7 +1032,7 @@
   ) ||
   overlayfs_supported=false
 if ${overlayfs_supported}; then
-  adb_su ls /sys/module/overlay/parameters/override_creds </dev/null >/dev/null 2>/dev/null &&
+  adb_test -f /sys/module/overlay/parameters/override_creds &&
     echo "${GREEN}[       OK ]${NORMAL} overlay module supports override_creds" >&2 ||
     case `adb_sh uname -r </dev/null` in
       4.[456789].* | 4.[1-9][0-9]* | [56789].*)
@@ -1032,9 +1045,6 @@
     esac
 fi
 
-adb_root ||
-  die "initial setup"
-
 echo "${GREEN}[ RUN      ]${NORMAL} Checking current overlayfs status" >&2
 
 # We can not universally use adb enable-verity to ensure device is
@@ -1044,7 +1054,7 @@
 # having to go through enable-verity transition.
 reboot=false
 for d in ${OVERLAYFS_BACKING}; do
-  if adb_sh ls -d /${d}/overlay </dev/null >/dev/null 2>/dev/null; then
+  if adb_test -d /${d}/overlay; then
     echo "${YELLOW}[  WARNING ]${NORMAL} /${d}/overlay is setup, surgically wiping" >&2
     adb_sh rm -rf /${d}/overlay </dev/null ||
       die "/${d}/overlay wipe"
@@ -1171,7 +1181,7 @@
 
 # Feed log with selinux denials as baseline before overlays
 adb_unroot
-adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null
+adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null || true
 adb_root
 
 D=`adb remount 2>&1`
@@ -1220,7 +1230,7 @@
     die "scratch size"
   echo "${BLUE}[     INFO ]${NORMAL} scratch size ${scratch_size}KB" >&2
   for d in ${OVERLAYFS_BACKING}; do
-    if adb_sh ls -d /${d}/overlay/system/upper </dev/null >/dev/null 2>/dev/null; then
+    if adb_test -d /${d}/overlay/system/upper; then
       echo "${BLUE}[     INFO ]${NORMAL} /${d}/overlay is setup" >&2
     fi
   done
@@ -1305,7 +1315,7 @@
 [ -n "${VENDOR_DEVT%[0-9a-fA-F][0-9a-fA-F]}" ] ||
   echo "${YELLOW}[  WARNING ]${NORMAL} vendor devt ${VENDOR_DEVT} major 0" >&2
 
-# Download libc.so, append some gargage, push back, and check if the file
+# Download libc.so, append some garbage, push back, and check if the file
 # is updated.
 tempdir="`mktemp -d`"
 cleanup() {
@@ -1313,8 +1323,8 @@
 }
 adb pull /system/lib/bootstrap/libc.so ${tempdir} >/dev/null ||
   die "pull libc.so from device"
-garbage="`hexdump -n 16 -e '4/4 "%08X" 1 "\n"' /dev/random`"
-echo ${garbage} >> ${tempdir}/libc.so
+garbage="D105225BBFCB1EB8AB8EBDB7094646F0"
+echo "${garbage}" >> ${tempdir}/libc.so
 adb push ${tempdir}/libc.so /system/lib/bootstrap/libc.so >/dev/null ||
   die "push libc.so to device"
 adb pull /system/lib/bootstrap/libc.so ${tempdir}/libc.so.fromdevice >/dev/null ||
@@ -1358,7 +1368,7 @@
   echo "${GREEN}[       OK ]${NORMAL} /vendor content correct MAC after reboot" >&2
   # Feed unprivileged log with selinux denials as a result of overlays
   wait_for_screen
-  adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null
+  adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null || true
 fi
 # If overlayfs has a nested security problem, this will fail.
 B="`adb_ls /system/`" ||
@@ -1385,7 +1395,7 @@
 check_eq "${BASE_SYSTEM_DEVT}" "`adb_sh stat --format=%D /system/xbin/su </dev/null`" --warning devt for su after reboot
 
 # Feed log with selinux denials as a result of overlays
-adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null
+adb_sh find ${MOUNTS} </dev/null >/dev/null 2>/dev/null || true
 
 # Check if the updated libc.so is persistent after reboot.
 adb_root &&
@@ -1656,8 +1666,10 @@
 # This also saves a lot of 'noise' from the command doing a mkfs on backing
 # storage and all the related tuning and adjustment.
 for d in ${OVERLAYFS_BACKING}; do
-  adb_su rm -rf /${d}/overlay </dev/null ||
-    die "/${d}/overlay wipe"
+  if adb_test -d /${d}/overlay; then
+    adb_su rm -rf /${d}/overlay </dev/null ||
+      die "/${d}/overlay wipe"
+  fi
 done
 adb_reboot &&
   adb_wait ${ADB_WAIT} ||
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 46f1c59..eb2919b 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -119,10 +119,105 @@
         {"terminator", "truncated"},
 };
 
+const std::string bootconfig =
+        "androidboot.bootdevice  = \" \"1d84000.ufshc\"\n"
+        "androidboot.boot_devices = \"dev1\", \"dev2,withcomma\", \"dev3\"\n"
+        "androidboot.baseband = \"sdy\"\n"
+        "androidboot.keymaster = \"1\"\n"
+        "androidboot.serialno = \"BLAHBLAHBLAH\"\n"
+        "androidboot.slot_suffix = \"_a\"\n"
+        "androidboot.hardware.platform = \"sdw813\"\n"
+        "hardware = \"foo\"\n"
+        "androidboot.revision = \"EVT1.0\"\n"
+        "androidboot.bootloader = \"burp-0.1-7521\"\n"
+        "androidboot.hardware.sku = \"mary\"\n"
+        "androidboot.hardware.radio.subtype = \"0\"\n"
+        "androidboot.dtbo_idx = \"2\"\n"
+        "androidboot.mode = \"normal\"\n"
+        "androidboot.hardware.ddr = \"1GB,combuchi,LPDDR4X\"\n"
+        "androidboot.ddr_info = \"combuchiandroidboot.ddr_size=2GB\"\n"
+        "androidboot.hardware.ufs = \"2GB,combushi\"\n"
+        "androidboot.boottime = \"0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123\"\n"
+        "androidboot.ramdump = \"disabled\"\n"
+        "androidboot.vbmeta.device = \"PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb\"\n"
+        "androidboot.vbmeta.avb_version = \"1.1\"\n"
+        "androidboot.vbmeta.device_state = \"unlocked\"\n"
+        "androidboot.vbmeta.hash_alg = \"sha256\"\n"
+        "androidboot.vbmeta.size = \"5248\"\n"
+        "androidboot.vbmeta.digest = \""
+        "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860\"\n"
+        "androidboot.vbmeta.invalidate_on_error = \"yes\"\n"
+        "androidboot.veritymode = \"enforcing\"\n"
+        "androidboot.verifiedbootstate = \"orange\"\n"
+        "androidboot.space = \"sha256 5248 androidboot.nospace = nope\"\n";
+
+const std::vector<std::pair<std::string, std::string>> bootconfig_result_space = {
+        {"androidboot.bootdevice", "1d84000.ufshc"},
+        {"androidboot.boot_devices", "dev1, dev2,withcomma, dev3"},
+        {"androidboot.baseband", "sdy"},
+        {"androidboot.keymaster", "1"},
+        {"androidboot.serialno", "BLAHBLAHBLAH"},
+        {"androidboot.slot_suffix", "_a"},
+        {"androidboot.hardware.platform", "sdw813"},
+        {"hardware", "foo"},
+        {"androidboot.revision", "EVT1.0"},
+        {"androidboot.bootloader", "burp-0.1-7521"},
+        {"androidboot.hardware.sku", "mary"},
+        {"androidboot.hardware.radio.subtype", "0"},
+        {"androidboot.dtbo_idx", "2"},
+        {"androidboot.mode", "normal"},
+        {"androidboot.hardware.ddr", "1GB,combuchi,LPDDR4X"},
+        {"androidboot.ddr_info", "combuchiandroidboot.ddr_size=2GB"},
+        {"androidboot.hardware.ufs", "2GB,combushi"},
+        {"androidboot.boottime", "0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123"},
+        {"androidboot.ramdump", "disabled"},
+        {"androidboot.vbmeta.device", "PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb"},
+        {"androidboot.vbmeta.avb_version", "1.1"},
+        {"androidboot.vbmeta.device_state", "unlocked"},
+        {"androidboot.vbmeta.hash_alg", "sha256"},
+        {"androidboot.vbmeta.size", "5248"},
+        {"androidboot.vbmeta.digest",
+         "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860"},
+        {"androidboot.vbmeta.invalidate_on_error", "yes"},
+        {"androidboot.veritymode", "enforcing"},
+        {"androidboot.verifiedbootstate", "orange"},
+        {"androidboot.space", "sha256 5248 androidboot.nospace = nope"},
+};
+
+bool CompareFlags(FstabEntry::FsMgrFlags& lhs, FstabEntry::FsMgrFlags& rhs) {
+    // clang-format off
+    return lhs.wait == rhs.wait &&
+           lhs.check == rhs.check &&
+           lhs.crypt == rhs.crypt &&
+           lhs.nonremovable == rhs.nonremovable &&
+           lhs.vold_managed == rhs.vold_managed &&
+           lhs.recovery_only == rhs.recovery_only &&
+           lhs.verify == rhs.verify &&
+           lhs.force_crypt == rhs.force_crypt &&
+           lhs.no_emulated_sd == rhs.no_emulated_sd &&
+           lhs.no_trim == rhs.no_trim &&
+           lhs.file_encryption == rhs.file_encryption &&
+           lhs.formattable == rhs.formattable &&
+           lhs.slot_select == rhs.slot_select &&
+           lhs.force_fde_or_fbe == rhs.force_fde_or_fbe &&
+           lhs.late_mount == rhs.late_mount &&
+           lhs.no_fail == rhs.no_fail &&
+           lhs.verify_at_boot == rhs.verify_at_boot &&
+           lhs.quota == rhs.quota &&
+           lhs.avb == rhs.avb &&
+           lhs.logical == rhs.logical &&
+           lhs.checkpoint_blk == rhs.checkpoint_blk &&
+           lhs.checkpoint_fs == rhs.checkpoint_fs &&
+           lhs.first_stage_mount == rhs.first_stage_mount &&
+           lhs.slot_select_other == rhs.slot_select_other &&
+           lhs.fs_verity == rhs.fs_verity;
+    // clang-format on
+}
+
 }  // namespace
 
-TEST(fs_mgr, fs_mgr_parse_boot_config) {
-    EXPECT_EQ(result_space, fs_mgr_parse_boot_config(cmdline));
+TEST(fs_mgr, fs_mgr_parse_cmdline) {
+    EXPECT_EQ(result_space, fs_mgr_parse_cmdline(cmdline));
 }
 
 TEST(fs_mgr, fs_mgr_get_boot_config_from_kernel_cmdline) {
@@ -140,6 +235,27 @@
     EXPECT_TRUE(content.empty()) << content;
 }
 
+TEST(fs_mgr, fs_mgr_parse_bootconfig) {
+    EXPECT_EQ(bootconfig_result_space, fs_mgr_parse_proc_bootconfig(bootconfig));
+}
+
+TEST(fs_mgr, fs_mgr_get_boot_config_from_bootconfig) {
+    std::string content;
+    for (const auto& entry : bootconfig_result_space) {
+        static constexpr char androidboot[] = "androidboot.";
+        if (!android::base::StartsWith(entry.first, androidboot)) continue;
+        auto key = entry.first.substr(strlen(androidboot));
+        EXPECT_TRUE(fs_mgr_get_boot_config_from_bootconfig(bootconfig, key, &content))
+                << " for " << key;
+        EXPECT_EQ(entry.second, content);
+    }
+
+    EXPECT_FALSE(fs_mgr_get_boot_config_from_bootconfig(bootconfig, "vbmeta.avb_versio", &content));
+    EXPECT_TRUE(content.empty()) << content;
+    EXPECT_FALSE(fs_mgr_get_boot_config_from_bootconfig(bootconfig, "nospace", &content));
+    EXPECT_TRUE(content.empty()) << content;
+}
+
 TEST(fs_mgr, fs_mgr_read_fstab_file_proc_mounts) {
     Fstab fstab;
     ASSERT_TRUE(ReadFstabFromFile("/proc/mounts", &fstab));
@@ -205,19 +321,18 @@
     EXPECT_EQ(i, fstab.size());
 }
 
-// TODO(124837435): enable it later when it can pass TreeHugger.
-TEST(fs_mgr, DISABLED_ReadFstabFromFile_MountOptions) {
+TEST(fs_mgr, ReadFstabFromFile_MountOptions) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
     std::string fstab_contents = R"fs(
-source /            ext4    ro,barrier=1                    wait,slotselect,avb
+source /            ext4    ro,barrier=1                    wait,avb
 source /metadata    ext4    noatime,nosuid,nodev,discard    wait,formattable
 
 source /data        f2fs    noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier    latemount,wait,check,fileencryption=ice,keydirectory=/metadata/vold/metadata_encryption,quota,formattable,sysfs_path=/sys/devices/platform/soc/1d84000.ufshc,reservedsize=128M
 
 source /misc        emmc    defaults                        defaults
 
-source /vendor/firmware_mnt    vfat    ro,shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0    wait,slotselect
+source /vendor/firmware_mnt    vfat    ro,shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0    wait
 
 source auto         vfat    defaults                        voldmanaged=usb:auto
 source none         swap    defaults                        zramsize=1073741824,max_comp_streams=8
@@ -230,94 +345,75 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(11U, fstab.size());
+    ASSERT_LE(11U, fstab.size());
 
-    EXPECT_EQ("/", fstab[0].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), fstab[0].flags);
-    EXPECT_EQ("barrier=1", fstab[0].fs_options);
+    FstabEntry* entry = GetEntryForMountPoint(&fstab, "/");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), entry->flags);
+    EXPECT_EQ("barrier=1", entry->fs_options);
 
-    EXPECT_EQ("/metadata", fstab[1].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), fstab[1].flags);
-    EXPECT_EQ("discard", fstab[1].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "/metadata");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags);
+    EXPECT_EQ("discard", entry->fs_options);
 
-    EXPECT_EQ("/data", fstab[2].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), fstab[2].flags);
-    EXPECT_EQ("discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier", fstab[2].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "/data");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags);
+    EXPECT_EQ("discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier", entry->fs_options);
 
-    EXPECT_EQ("/misc", fstab[3].mount_point);
-    EXPECT_EQ(0U, fstab[3].flags);
-    EXPECT_EQ("", fstab[3].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "/misc");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(0U, entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("/vendor/firmware_mnt", fstab[4].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), fstab[4].flags);
+    entry = GetEntryForMountPoint(&fstab, "/vendor/firmware_mnt");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), entry->flags);
     EXPECT_EQ(
             "shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,"
             "context=u:object_r:firmware_file:s0",
-            fstab[4].fs_options);
+            entry->fs_options);
 
-    EXPECT_EQ("auto", fstab[5].mount_point);
-    EXPECT_EQ(0U, fstab[5].flags);
-    EXPECT_EQ("", fstab[5].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "auto");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(0U, entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("none", fstab[6].mount_point);
-    EXPECT_EQ(0U, fstab[6].flags);
-    EXPECT_EQ("", fstab[6].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "none");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(0U, entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("none2", fstab[7].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_NODIRATIME | MS_REMOUNT | MS_BIND), fstab[7].flags);
-    EXPECT_EQ("", fstab[7].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "none2");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_NODIRATIME | MS_REMOUNT | MS_BIND), entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("none3", fstab[8].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE), fstab[8].flags);
-    EXPECT_EQ("", fstab[8].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "none3");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE), entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("none4", fstab[9].mount_point);
-    EXPECT_EQ(static_cast<unsigned long>(MS_NOEXEC | MS_SHARED | MS_REC), fstab[9].flags);
-    EXPECT_EQ("", fstab[9].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "none4");
+    ASSERT_NE(nullptr, entry);
+    EXPECT_EQ(static_cast<unsigned long>(MS_NOEXEC | MS_SHARED | MS_REC), entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 
-    EXPECT_EQ("none5", fstab[10].mount_point);
-    EXPECT_EQ(0U, fstab[10].flags);  // rw is the same as defaults
-    EXPECT_EQ("", fstab[10].fs_options);
+    entry = GetEntryForMountPoint(&fstab, "none5");
+    ASSERT_NE(nullptr, entry);
+    // rw is the default.
+    EXPECT_EQ(0U, entry->flags);
+    EXPECT_EQ("", entry->fs_options);
 }
 
-static bool CompareFlags(FstabEntry::FsMgrFlags& lhs, FstabEntry::FsMgrFlags& rhs) {
-    // clang-format off
-    return lhs.wait == rhs.wait &&
-           lhs.check == rhs.check &&
-           lhs.crypt == rhs.crypt &&
-           lhs.nonremovable == rhs.nonremovable &&
-           lhs.vold_managed == rhs.vold_managed &&
-           lhs.recovery_only == rhs.recovery_only &&
-           lhs.verify == rhs.verify &&
-           lhs.force_crypt == rhs.force_crypt &&
-           lhs.no_emulated_sd == rhs.no_emulated_sd &&
-           lhs.no_trim == rhs.no_trim &&
-           lhs.file_encryption == rhs.file_encryption &&
-           lhs.formattable == rhs.formattable &&
-           lhs.slot_select == rhs.slot_select &&
-           lhs.force_fde_or_fbe == rhs.force_fde_or_fbe &&
-           lhs.late_mount == rhs.late_mount &&
-           lhs.no_fail == rhs.no_fail &&
-           lhs.verify_at_boot == rhs.verify_at_boot &&
-           lhs.quota == rhs.quota &&
-           lhs.avb == rhs.avb &&
-           lhs.logical == rhs.logical &&
-           lhs.checkpoint_blk == rhs.checkpoint_blk &&
-           lhs.checkpoint_fs == rhs.checkpoint_fs &&
-           lhs.first_stage_mount == rhs.first_stage_mount &&
-           lhs.slot_select_other == rhs.slot_select_other &&
-           lhs.fs_verity == rhs.fs_verity;
-    // clang-format on
-}
-
-// TODO(124837435): enable it later when it can pass TreeHugger.
-TEST(fs_mgr, DISABLED_ReadFstabFromFile_FsMgrFlags) {
+TEST(fs_mgr, ReadFstabFromFile_FsMgrFlags) {
     TemporaryFile tf;
     ASSERT_TRUE(tf.fd != -1);
     std::string fstab_contents = R"fs(
 source none0       swap   defaults      wait,check,nonremovable,recoveryonly,verifyatboot,verify
-source none1       swap   defaults      avb,noemulatedsd,notrim,formattable,slotselect,nofail
-source none2       swap   defaults      first_stage_mount,latemount,quota,logical,slotselect_other
+source none1       swap   defaults      avb,noemulatedsd,notrim,formattable,nofail
+source none2       swap   defaults      first_stage_mount,latemount,quota,logical
 source none3       swap   defaults      checkpoint=block
 source none4       swap   defaults      checkpoint=fs
 source none5       swap   defaults      defaults
@@ -326,10 +422,10 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(6U, fstab.size());
+    ASSERT_LE(6U, fstab.size());
 
-    auto entry = fstab.begin();
-    EXPECT_EQ("none0", entry->mount_point);
+    FstabEntry* entry = GetEntryForMountPoint(&fstab, "none0");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         flags.wait = true;
@@ -340,50 +436,48 @@
         flags.verify = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    entry++;
 
-    EXPECT_EQ("none1", entry->mount_point);
+    entry = GetEntryForMountPoint(&fstab, "none1");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         flags.avb = true;
         flags.no_emulated_sd = true;
         flags.no_trim = true;
         flags.formattable = true;
-        flags.slot_select = true;
         flags.no_fail = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    entry++;
 
-    EXPECT_EQ("none2", entry->mount_point);
+    entry = GetEntryForMountPoint(&fstab, "none2");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         flags.first_stage_mount = true;
         flags.late_mount = true;
         flags.quota = true;
         flags.logical = true;
-        flags.slot_select_other = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    entry++;
 
-    EXPECT_EQ("none3", entry->mount_point);
+    entry = GetEntryForMountPoint(&fstab, "none3");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         flags.checkpoint_blk = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    entry++;
 
-    EXPECT_EQ("none4", entry->mount_point);
+    entry = GetEntryForMountPoint(&fstab, "none4");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         flags.checkpoint_fs = true;
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
     }
-    entry++;
 
-    EXPECT_EQ("none5", entry->mount_point);
+    entry = GetEntryForMountPoint(&fstab, "none5");
+    ASSERT_NE(nullptr, entry);
     {
         FstabEntry::FsMgrFlags flags = {};
         EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
@@ -405,7 +499,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(3U, fstab.size());
+    ASSERT_LE(3U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -475,7 +569,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
     flags.crypt = true;
@@ -499,7 +593,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(4U, fstab.size());
+    ASSERT_LE(4U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
     flags.vold_managed = true;
@@ -540,7 +634,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(2U, fstab.size());
+    ASSERT_LE(2U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -566,7 +660,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(2U, fstab.size());
+    ASSERT_LE(2U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -596,7 +690,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(6U, fstab.size());
+    ASSERT_LE(6U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -642,7 +736,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -665,7 +759,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -689,7 +783,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
     flags.file_encryption = true;
@@ -711,7 +805,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(2U, fstab.size());
+    ASSERT_LE(2U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -739,7 +833,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(4U, fstab.size());
+    ASSERT_LE(4U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -777,7 +871,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(4U, fstab.size());
+    ASSERT_LE(4U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -815,7 +909,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(4U, fstab.size());
+    ASSERT_LE(4U, fstab.size());
 
     FstabEntry::FsMgrFlags flags = {};
 
@@ -852,7 +946,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(2U, fstab.size());
+    ASSERT_LE(2U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -881,7 +975,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -903,7 +997,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("adiantum", entry->metadata_encryption);
@@ -920,7 +1014,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("aes-256-xts:wrappedkey_v0", entry->metadata_encryption);
@@ -941,7 +1035,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(1U, fstab.size());
+    ASSERT_LE(1U, fstab.size());
 
     auto entry = fstab.begin();
     EXPECT_EQ("none0", entry->mount_point);
@@ -967,7 +1061,7 @@
 
     Fstab fstab;
     EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
-    ASSERT_EQ(4U, fstab.size());
+    ASSERT_LE(4U, fstab.size());
 
     auto entry = fstab.begin();
 
@@ -1011,3 +1105,59 @@
     ASSERT_NE(nullptr, fs_mgr_get_mounted_entry_for_userdata(&fstab, block_device))
             << "/data wasn't mounted from default fstab";
 }
+
+TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Readahead_Size_KB) {
+    TemporaryFile tf;
+    ASSERT_TRUE(tf.fd != -1);
+    std::string fstab_contents = R"fs(
+source none0       swap   defaults      readahead_size_kb=blah
+source none1       swap   defaults      readahead_size_kb=128
+source none2       swap   defaults      readahead_size_kb=5%
+source none3       swap   defaults      readahead_size_kb=5kb
+source none4       swap   defaults      readahead_size_kb=16385
+source none5       swap   defaults      readahead_size_kb=-128
+source none6       swap   defaults      readahead_size_kb=0
+)fs";
+    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));
+
+    Fstab fstab;
+    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
+    ASSERT_LE(7U, fstab.size());
+
+    FstabEntry::FsMgrFlags flags = {};
+
+    auto entry = fstab.begin();
+    EXPECT_EQ("none0", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(-1, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none1", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(128, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none2", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(-1, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none3", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(-1, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none4", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(-1, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none5", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(-1, entry->readahead_size_kb);
+    entry++;
+
+    EXPECT_EQ("none6", entry->mount_point);
+    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
+    EXPECT_EQ(0, entry->readahead_size_kb);
+}
diff --git a/fs_mgr/tools/Android.bp b/fs_mgr/tools/Android.bp
index d6ccc4b..462777d 100644
--- a/fs_mgr/tools/Android.bp
+++ b/fs_mgr/tools/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "dmctl",
     srcs: ["dmctl.cpp"],
diff --git a/fs_mgr/tools/dmuserd.cpp b/fs_mgr/tools/dmuserd.cpp
index 92f5878..6b68b28 100644
--- a/fs_mgr/tools/dmuserd.cpp
+++ b/fs_mgr/tools/dmuserd.cpp
@@ -76,12 +76,12 @@
 
 static bool verbose = false;
 
-size_t write_all(int fd, void* buf, size_t len) {
+ssize_t write_all(int fd, void* buf, size_t len) {
     char* buf_c = (char*)buf;
     ssize_t total = 0;
     ssize_t once;
 
-    while (total < len) {
+    while (total < static_cast<ssize_t>(len)) {
         once = write(fd, buf_c + total, len - total);
         if (once < 0) return once;
         if (once == 0) {
@@ -94,12 +94,12 @@
     return total;
 }
 
-size_t read_all(int fd, void* buf, size_t len) {
+ssize_t read_all(int fd, void* buf, size_t len) {
     char* buf_c = (char*)buf;
     ssize_t total = 0;
     ssize_t once;
 
-    while (total < len) {
+    while (total < static_cast<ssize_t>(len)) {
         once = read(fd, buf_c + total, len - total);
         if (once < 0) return once;
         if (once == 0) {
diff --git a/gatekeeperd/Android.bp b/gatekeeperd/Android.bp
index 27a6452..95e814b 100644
--- a/gatekeeperd/Android.bp
+++ b/gatekeeperd/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "gatekeeperd",
     cflags: [
@@ -28,6 +32,7 @@
 
     shared_libs: [
         "libbinder",
+        "libbinder_ndk",
         "libgatekeeper",
         "libgsi",
         "liblog",
@@ -35,11 +40,11 @@
         "libbase",
         "libutils",
         "libcrypto",
-        "libkeystore_aidl",
-        "libkeystore_binder",
         "libhidlbase",
         "android.hardware.gatekeeper@1.0",
         "libgatekeeper_aidl",
+        "android.hardware.security.keymint-V1-ndk_platform",
+        "android.security.authorization-ndk_platform",
     ],
 
     static_libs: ["libscrypt_static"],
diff --git a/gatekeeperd/gatekeeperd.cpp b/gatekeeperd/gatekeeperd.cpp
index b982dbc..8792c83 100644
--- a/gatekeeperd/gatekeeperd.cpp
+++ b/gatekeeperd/gatekeeperd.cpp
@@ -19,42 +19,43 @@
 #include <android/service/gatekeeper/BnGateKeeperService.h>
 #include <gatekeeper/GateKeeperResponse.h>
 
+#include <endian.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <inttypes.h>
-#include <stdint.h>
 #include <unistd.h>
 #include <memory>
 
-#include <android/security/keystore/IKeystoreService.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/PermissionCache.h>
-#include <gatekeeper/password_handle.h> // for password_handle_t
-#include <hardware/gatekeeper.h>
+#include <gatekeeper/password_handle.h>  // for password_handle_t
 #include <hardware/hw_auth_token.h>
-#include <keystore/keystore.h> // For error code
-#include <keystore/keystore_return_types.h>
 #include <libgsi/libgsi.h>
 #include <log/log.h>
-#include <utils/Log.h>
 #include <utils/String16.h>
 
-#include <hidl/HidlSupport.h>
+#include <aidl/android/hardware/security/keymint/HardwareAuthToken.h>
+#include <aidl/android/security/authorization/IKeystoreAuthorization.h>
 #include <android/hardware/gatekeeper/1.0/IGatekeeper.h>
+#include <hidl/HidlSupport.h>
 
 using android::sp;
-using android::hardware::gatekeeper::V1_0::IGatekeeper;
-using android::hardware::gatekeeper::V1_0::GatekeeperStatusCode;
-using android::hardware::gatekeeper::V1_0::GatekeeperResponse;
 using android::hardware::Return;
+using android::hardware::gatekeeper::V1_0::GatekeeperResponse;
+using android::hardware::gatekeeper::V1_0::GatekeeperStatusCode;
+using android::hardware::gatekeeper::V1_0::IGatekeeper;
 
 using ::android::binder::Status;
 using ::android::service::gatekeeper::BnGateKeeperService;
 using GKResponse = ::android::service::gatekeeper::GateKeeperResponse;
 using GKResponseCode = ::android::service::gatekeeper::ResponseCode;
+using ::aidl::android::hardware::security::keymint::HardwareAuthenticatorType;
+using ::aidl::android::hardware::security::keymint::HardwareAuthToken;
+using ::aidl::android::security::authorization::IKeystoreAuthorization;
 
 namespace android {
 
@@ -62,7 +63,7 @@
 static const String16 DUMP_PERMISSION("android.permission.DUMP");
 
 class GateKeeperProxy : public BnGateKeeperService {
-public:
+  public:
     GateKeeperProxy() {
         clear_state_if_needed_done = false;
         hw_device = IGatekeeper::getService();
@@ -73,8 +74,7 @@
         }
     }
 
-    virtual ~GateKeeperProxy() {
-    }
+    virtual ~GateKeeperProxy() {}
 
     void store_sid(uint32_t userId, uint64_t sid) {
         char filename[21];
@@ -96,7 +96,7 @@
         if (mark_cold_boot() && !is_running_gsi) {
             ALOGI("cold boot: clearing state");
             if (hw_device) {
-                hw_device->deleteAllUsers([](const GatekeeperResponse &){});
+                hw_device->deleteAllUsers([](const GatekeeperResponse&) {});
             }
         }
 
@@ -104,7 +104,7 @@
     }
 
     bool mark_cold_boot() {
-        const char *filename = ".coldboot";
+        const char* filename = ".coldboot";
         if (access(filename, F_OK) == -1) {
             int fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, S_IRUSR | S_IWUSR);
             if (fd < 0) {
@@ -299,20 +299,37 @@
 
         if (gkResponse->response_code() == GKResponseCode::OK) {
             if (gkResponse->payload().size() != 0) {
-                sp<IServiceManager> sm = defaultServiceManager();
-                sp<IBinder> binder = sm->getService(String16("android.security.keystore"));
-                sp<security::keystore::IKeystoreService> service =
-                        interface_cast<security::keystore::IKeystoreService>(binder);
+                // try to connect to IKeystoreAuthorization AIDL service first.
+                AIBinder* authzAIBinder =
+                        AServiceManager_getService("android.security.authorization");
+                ::ndk::SpAIBinder authzBinder(authzAIBinder);
+                auto authzService = IKeystoreAuthorization::fromBinder(authzBinder);
+                if (authzService) {
+                    if (gkResponse->payload().size() != sizeof(hw_auth_token_t)) {
+                        LOG(ERROR) << "Incorrect size of AuthToken payload.";
+                        return GK_ERROR;
+                    }
 
-                if (service) {
-                    int result = 0;
-                    auto binder_result = service->addAuthToken(gkResponse->payload(), &result);
-                    if (!binder_result.isOk() ||
-                        !keystore::KeyStoreServiceReturnCode(result).isOk()) {
-                        LOG(ERROR) << "Failure sending auth token to KeyStore: " << result;
+                    const hw_auth_token_t* hwAuthToken =
+                            reinterpret_cast<const hw_auth_token_t*>(gkResponse->payload().data());
+                    HardwareAuthToken authToken;
+
+                    authToken.timestamp.milliSeconds = betoh64(hwAuthToken->timestamp);
+                    authToken.challenge = hwAuthToken->challenge;
+                    authToken.userId = hwAuthToken->user_id;
+                    authToken.authenticatorId = hwAuthToken->authenticator_id;
+                    authToken.authenticatorType = static_cast<HardwareAuthenticatorType>(
+                            betoh32(hwAuthToken->authenticator_type));
+                    authToken.mac.assign(&hwAuthToken->hmac[0], &hwAuthToken->hmac[32]);
+                    auto result = authzService->addAuthToken(authToken);
+                    if (!result.isOk()) {
+                        LOG(ERROR) << "Failure in sending AuthToken to AuthorizationService.";
+                        return GK_ERROR;
                     }
                 } else {
-                    LOG(ERROR) << "Cannot deliver auth token. Unable to communicate with Keystore.";
+                    LOG(ERROR) << "Cannot deliver auth token. Unable to communicate with "
+                                  "Keystore.";
+                    return GK_ERROR;
                 }
             }
 
@@ -366,23 +383,23 @@
         }
 
         if (hw_device == NULL) {
-            const char *result = "Device not available";
+            const char* result = "Device not available";
             write(fd, result, strlen(result) + 1);
         } else {
-            const char *result = "OK";
+            const char* result = "OK";
             write(fd, result, strlen(result) + 1);
         }
 
         return OK;
     }
 
-private:
+  private:
     sp<IGatekeeper> hw_device;
 
     bool clear_state_if_needed_done;
     bool is_running_gsi;
 };
-}// namespace android
+}  // namespace android
 
 int main(int argc, char* argv[]) {
     ALOGI("Starting gatekeeperd...");
diff --git a/healthd/Android.bp b/healthd/Android.bp
index b3de9c4..ec47f68 100644
--- a/healthd/Android.bp
+++ b/healthd/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_headers {
     name: "libhealthd_headers",
     vendor_available: true,
@@ -62,29 +66,6 @@
     srcs: [
         "HealthServiceDefault.cpp",
     ],
-
-    overrides: [
-        "healthd",
-    ]
-}
-
-cc_binary {
-    name: "healthd",
-    defaults: ["android.hardware.health@2.0-service_defaults"],
-
-    init_rc: ["healthd.rc"],
-    srcs: [
-        "HealthServiceHealthd.cpp",
-    ],
-    local_include_dirs: ["include"],
-
-    shared_libs: [
-        "android.hardware.health@1.0",
-    ],
-
-    vintf_fragments: [
-        "manifest_healthd.xml"
-    ],
 }
 
 cc_library_static {
diff --git a/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index fd810cb..377acb7 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -349,9 +349,14 @@
 }
 
 void BatteryMonitor::logValues(void) {
+    logValues(*mHealthInfo, *mHealthdConfig);
+}
+
+void BatteryMonitor::logValues(const android::hardware::health::V2_1::HealthInfo& health_info,
+                               const struct healthd_config& healthd_config) {
     char dmesgline[256];
     size_t len;
-    const HealthInfo_1_0& props = mHealthInfo->legacy.legacy;
+    const HealthInfo_1_0& props = health_info.legacy.legacy;
     if (props.batteryPresent) {
         snprintf(dmesgline, sizeof(dmesgline), "battery l=%d v=%d t=%s%d.%d h=%d st=%d",
                  props.batteryLevel, props.batteryVoltage, props.batteryTemperature < 0 ? "-" : "",
@@ -359,17 +364,17 @@
                  props.batteryHealth, props.batteryStatus);
 
         len = strlen(dmesgline);
-        if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
+        if (!healthd_config.batteryCurrentNowPath.isEmpty()) {
             len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " c=%d",
                             props.batteryCurrent);
         }
 
-        if (!mHealthdConfig->batteryFullChargePath.isEmpty()) {
+        if (!healthd_config.batteryFullChargePath.isEmpty()) {
             len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " fc=%d",
                             props.batteryFullCharge);
         }
 
-        if (!mHealthdConfig->batteryCycleCountPath.isEmpty()) {
+        if (!healthd_config.batteryCycleCountPath.isEmpty()) {
             len += snprintf(dmesgline + len, sizeof(dmesgline) - len, " cc=%d",
                             props.batteryCycleCount);
         }
diff --git a/healthd/HealthServiceHealthd.cpp b/healthd/HealthServiceHealthd.cpp
deleted file mode 100644
index 5fd2597..0000000
--- a/healthd/HealthServiceHealthd.cpp
+++ /dev/null
@@ -1,93 +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.
- */
-
-#define LOG_TAG "healthd"
-#include <android-base/logging.h>
-
-#include <android/hardware/health/1.0/IHealth.h>
-#include <android/hardware/health/1.0/types.h>
-#include <hal_conversion.h>
-#include <health2/service.h>
-#include <healthd/healthd.h>
-#include <hidl/HidlTransportSupport.h>
-
-using android::OK;
-using android::NAME_NOT_FOUND;
-using android::hardware::health::V1_0::HealthConfig;
-using android::hardware::health::V1_0::HealthInfo;
-using android::hardware::health::V1_0::Result;
-using android::hardware::health::V1_0::hal_conversion::convertFromHealthConfig;
-using android::hardware::health::V1_0::hal_conversion::convertToHealthConfig;
-using android::hardware::health::V1_0::hal_conversion::convertFromHealthInfo;
-using android::hardware::health::V1_0::hal_conversion::convertToHealthInfo;
-
-using IHealthLegacy = android::hardware::health::V1_0::IHealth;
-
-static android::sp<IHealthLegacy> gHealth_1_0;
-
-static int healthd_board_get_energy_counter(int64_t* energy) {
-    if (gHealth_1_0 == nullptr) {
-        return NAME_NOT_FOUND;
-    }
-
-    Result result = Result::NOT_SUPPORTED;
-    gHealth_1_0->energyCounter([energy, &result](Result ret, int64_t energyOut) {
-        result = ret;
-        *energy = energyOut;
-    });
-
-    return result == Result::SUCCESS ? OK : NAME_NOT_FOUND;
-}
-
-void healthd_board_init(struct healthd_config* config) {
-    gHealth_1_0 = IHealthLegacy::getService();
-
-    if (gHealth_1_0 == nullptr) {
-        return;
-    }
-
-    HealthConfig halConfig{};
-    convertToHealthConfig(config, halConfig);
-    gHealth_1_0->init(halConfig, [config](const auto& halConfigOut) {
-        convertFromHealthConfig(halConfigOut, config);
-        // always redirect energy counter queries
-        config->energyCounter = healthd_board_get_energy_counter;
-    });
-    LOG(INFO) << LOG_TAG << ": redirecting calls to 1.0 health HAL";
-}
-
-// TODO(b/68724651): Move this function into healthd_mode_service_2_0_battery_update
-// with logthis returned.
-int healthd_board_battery_update(struct android::BatteryProperties* props) {
-    int logthis = 0;
-
-    if (gHealth_1_0 == nullptr) {
-        return logthis;
-    }
-
-    HealthInfo info;
-    convertToHealthInfo(props, info);
-    gHealth_1_0->update(info, [props, &logthis](int32_t ret, const auto& infoOut) {
-        logthis = ret;
-        convertFromHealthInfo(infoOut, props);
-    });
-
-    return logthis;
-}
-
-int main() {
-    return health_service_main("backup");
-}
diff --git a/healthd/include/healthd/BatteryMonitor.h b/healthd/include/healthd/BatteryMonitor.h
index fadb5a5..3cda727 100644
--- a/healthd/include/healthd/BatteryMonitor.h
+++ b/healthd/include/healthd/BatteryMonitor.h
@@ -66,6 +66,9 @@
     void logValues(void);
     bool isChargerOnline();
 
+    static void logValues(const android::hardware::health::V2_1::HealthInfo& health_info,
+                          const struct healthd_config& healthd_config);
+
   private:
     struct healthd_config *mHealthdConfig;
     Vector<String8> mChargerNames;
diff --git a/healthd/manifest_healthd.xml b/healthd/manifest_healthd.xml
deleted file mode 100644
index 097a7d8..0000000
--- a/healthd/manifest_healthd.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<manifest version="1.0" type="framework">
-    <hal>
-        <name>android.hardware.health</name>
-        <transport>hwbinder</transport>
-        <version>2.0</version>
-        <interface>
-            <name>IHealth</name>
-            <instance>backup</instance>
-        </interface>
-    </hal>
-</manifest>
diff --git a/healthd/testdata/Android.bp b/healthd/testdata/Android.bp
index 110c79a..f212538 100644
--- a/healthd/testdata/Android.bp
+++ b/healthd/testdata/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 filegroup {
     name: "libhealthd_charger_test_data",
     srcs: ["**/*.*"],
diff --git a/init/Android.bp b/init/Android.bp
index 19ba21b0..1381c1d 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -14,6 +14,23 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["system_core_init_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+    name: "system_core_init_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "NOTICE",
+    ],
+}
+
 init_common_sources = [
     "action.cpp",
     "action_manager.cpp",
@@ -60,6 +77,7 @@
     "selabel.cpp",
     "selinux.cpp",
     "sigchld_handler.cpp",
+    "snapuserd_transition.cpp",
     "switch_root.cpp",
     "uevent_listener.cpp",
     "ueventd.cpp",
@@ -73,7 +91,6 @@
 
 cc_defaults {
     name: "init_defaults",
-    cpp_std: "experimental",
     sanitize: {
         misc_undefined: ["signed-integer-overflow"],
     },
@@ -126,6 +143,7 @@
         "libcgrouprc_format",
         "liblmkd_utils",
         "libmodprobe",
+        "libprocinfo",
         "libprotobuf-cpp-lite",
         "libpropertyinfoserializer",
         "libpropertyinfoparser",
@@ -232,6 +250,121 @@
             ],
         },
     },
+    visibility: ["//packages/modules/Virtualization/microdroid"],
+}
+
+// This currently is only for the VM usecase.
+// TODO(jiyong): replace init_first_stage in Android.mk with this
+cc_binary {
+    name: "init_first_stage_soong",
+    stem: "init_vendor",
+
+    srcs: [
+        "block_dev_initializer.cpp",
+        "devices.cpp",
+        "first_stage_console.cpp",
+        "first_stage_init.cpp",
+        "first_stage_main.cpp",
+        "first_stage_mount.cpp",
+        "reboot_utils.cpp",
+        "selabel.cpp",
+        "selinux.cpp",
+        "service_utils.cpp",
+        "snapuserd_transition.cpp",
+        "switch_root.cpp",
+        "uevent_listener.cpp",
+        "util.cpp",
+    ],
+
+    static_libs: [
+        "libc++fs",
+        "libfs_avb",
+        "libfs_mgr",
+        "libfec",
+        "libfec_rs",
+        "libsquashfs_utils",
+        "liblogwrap",
+        "libext4_utils",
+        "libcrypto_utils",
+        "libsparse",
+        "libavb",
+        "libkeyutils",
+        "liblp",
+        "libcutils",
+        "libbase",
+        "liblog",
+        "libcrypto_static",
+        "libdl",
+        "libz",
+        "libselinux",
+        "libcap",
+        "libgsi",
+        "libcom.android.sysprop.apex",
+        "liblzma",
+        "libunwindstack_no_dex",
+        "libbacktrace_no_dex",
+        "libmodprobe",
+        "libext2_uuid",
+        "libprotobuf-cpp-lite",
+        "libsnapshot_cow",
+        "libsnapshot_init",
+        "update_metadata-protos",
+        "libprocinfo",
+    ],
+
+    static_executable: true,
+
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Wno-unused-parameter",
+        "-Werror",
+        "-DALLOW_FIRST_STAGE_CONSOLE=0",
+        "-DALLOW_LOCAL_PROP_OVERRIDE=0",
+        "-DALLOW_PERMISSIVE_SELINUX=0",
+        "-DREBOOT_BOOTLOADER_ON_PANIC=0",
+        "-DWORLD_WRITABLE_KMSG=0",
+        "-DDUMP_ON_UMOUNT_FAILURE=0",
+        "-DSHUTDOWN_ZERO_TIMEOUT=0",
+        "-DLOG_UEVENTS=0",
+        "-DSEPOLICY_VERSION=30", // TODO(jiyong): externalize the version number
+    ],
+
+    product_variables: {
+        debuggable: {
+            cflags: [
+                "-UALLOW_FIRST_STAGE_CONSOLE",
+                "-DALLOW_FIRST_STAGE_CONSOLE=1",
+
+                "-UALLOW_LOCAL_PROP_OVERRIDE",
+                "-DALLOW_LOCAL_PROP_OVERRIDE=1",
+
+                "-UALLOW_PERMISSIVE_SELINUX",
+                "-DALLOW_PERMISSIVE_SELINUX=1",
+
+                "-UREBOOT_BOOTLOADER_ON_PANIC",
+                "-DREBOOT_BOOTLOADER_ON_PANIC=1",
+
+                "-UWORLD_WRITABLE_KMSG",
+                "-DWORLD_WRITABLE_KMSG=1",
+
+                "-UDUMP_ON_UMOUNT_FAILURE",
+                "-DDUMP_ON_UMOUNT_FAILURE=1",
+            ],
+        },
+
+        eng: {
+            cflags: [
+                "-USHUTDOWN_ZERO_TIMEOUT",
+                "-DSHUTDOWN_ZERO_TIMEOUT=1",
+            ],
+        },
+    },
+
+    sanitize: {
+        misc_undefined: ["signed-integer-overflow"],
+        hwaddress: false,
+    },
 }
 
 // Tests
@@ -334,7 +467,6 @@
 cc_binary {
     name: "host_init_verifier",
     host_supported: true,
-    cpp_std: "experimental",
     cflags: [
         "-Wall",
         "-Wextra",
diff --git a/init/Android.mk b/init/Android.mk
index 4c1665b..3c7d95a 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -57,11 +57,16 @@
     reboot_utils.cpp \
     selabel.cpp \
     selinux.cpp \
+    service_utils.cpp \
+    snapuserd_transition.cpp \
     switch_root.cpp \
     uevent_listener.cpp \
     util.cpp \
 
 LOCAL_MODULE := init_first_stage
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
 LOCAL_MODULE_STEM := init
 
 LOCAL_FORCE_STATIC_EXECUTABLE := true
@@ -75,14 +80,22 @@
    adb_debug.prop \
 
 # Set up the directories that first stage init mounts on.
-LOCAL_POST_INSTALL_CMD := mkdir -p \
-    $(TARGET_RAMDISK_OUT)/debug_ramdisk \
-    $(TARGET_RAMDISK_OUT)/dev \
-    $(TARGET_RAMDISK_OUT)/mnt \
-    $(TARGET_RAMDISK_OUT)/proc \
-    $(TARGET_RAMDISK_OUT)/second_stage_resources \
-    $(TARGET_RAMDISK_OUT)/sys \
-    $(TARGET_RAMDISK_OUT)/metadata \
+
+my_ramdisk_dirs := \
+    debug_ramdisk \
+    dev \
+    metadata \
+    mnt \
+    proc \
+    second_stage_resources \
+    sys \
+
+LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_RAMDISK_OUT)/,$(my_ramdisk_dirs))
+ifeq (true,$(BOARD_USES_GENERIC_KERNEL_IMAGE))
+    LOCAL_POST_INSTALL_CMD += $(addprefix $(TARGET_RAMDISK_OUT)/first_stage_ramdisk/,$(my_ramdisk_dirs))
+endif
+
+my_ramdisk_dirs :=
 
 LOCAL_STATIC_LIBRARIES := \
     libc++fs \
@@ -117,6 +130,7 @@
     libsnapshot_cow \
     libsnapshot_init \
     update_metadata-protos \
+    libprocinfo \
 
 LOCAL_SANITIZE := signed-integer-overflow
 # First stage init is weird: it may start without stdout/stderr, and no /proc.
@@ -127,6 +141,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := init_system
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
 LOCAL_REQUIRED_MODULES := \
    init_second_stage \
 
@@ -135,6 +152,9 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := init_vendor
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
+LOCAL_NOTICE_FILE := $(LOCAL_PATH)/NOTICE
 ifneq ($(BOARD_BUILD_SYSTEM_ROOT_IMAGE),true)
 LOCAL_REQUIRED_MODULES := \
    init_first_stage \
diff --git a/init/OWNERS b/init/OWNERS
index babbe4d..9e70e7d 100644
--- a/init/OWNERS
+++ b/init/OWNERS
@@ -1 +1 @@
-tomcherry@google.com
+dvander@google.com
diff --git a/init/README.md b/init/README.md
index ab6a885..75dc328 100644
--- a/init/README.md
+++ b/init/README.md
@@ -178,6 +178,8 @@
   will reboot into _fatal reboot target_.
   The default value of _fatal crash window mins_ is 4, and default value
   of _fatal reboot target_ is 'bootloader'.
+  For tests, the fatal reboot can be skipped by setting property
+  `init.svc_debug.no_fatal.<service-name>` to `true` for specified critical service.
 
 `disabled`
 > This service will not automatically start with its class.
@@ -275,6 +277,8 @@
   CLD_EXITED or an status other than '0', reboot the system with the target specified in
   _target_. _target_ takes the same format as the parameter to sys.powerctl. This is particularly
   intended to be used with the `exec_start` builtin for any must-have checks during boot.
+  A service being stopped by init (e.g. using the `stop` or `class_reset` commands) is not
+  considered a failure for the purpose of this setting.
 
 `restart_period <seconds>`
 > If a non-oneshot service exits, it will be restarted at its start time plus
@@ -451,6 +455,10 @@
   exist. And it will be truncated if dst file is a normal regular file and
   already exists.
 
+`copy_per_line <src> <dst>`
+> Copies a file line by line. Similar to copy, but useful for dst is a sysfs node
+  that doesn't handle multiple lines of data.
+
 `domainname <name>`
 > Set the domain name.
 
@@ -517,6 +525,11 @@
 `interface_start aidl/aidl_lazy_test_1` will start the AIDL service that
 provides the `aidl_lazy_test_1` interface.
 
+`load_exports <path>`
+> Open the file at _path_ and export global environment variables declared
+  there. Each line must be in the format `export <name> <value>`, as described
+  above.
+
 `load_system_props`
 > (This action is deprecated and no-op.)
 
@@ -789,6 +802,9 @@
 `ro.boottime.init.selinux`
 > How long in ns it took to run SELinux stage.
 
+`ro.boottime.init.modules`
+> How long in ms it took to load kernel modules.
+
 `ro.boottime.init.cold_boot_wait`
 > How long init waited for ueventd's coldboot phase to end.
 
diff --git a/init/README.ueventd.md b/init/README.ueventd.md
index 4363f3c..76f5193 100644
--- a/init/README.ueventd.md
+++ b/init/README.ueventd.md
@@ -13,6 +13,16 @@
     uevent_socket_rcvbuf_size 16M
 Sets the uevent socket rcvbuf_size to 16 megabytes.
 
+## Importing configuration files
+--------------------------------
+Ueventd reads /system/etc/ueventd.rc, all other files are imported via the `import` command, which
+takes the format of
+
+    import <path>
+This command parses an ueventd config file, extending the current configuration.  If _path_ is a
+directory, each file in the directory is parsed as a config file. It is not recursive, nested
+directories will not be parsed.  Imported files are parsed after the current file has been parsed.
+
 ## /dev
 ----
 Ueventd listens to the kernel uevent sockets and creates/deletes nodes in `/dev` based on the
@@ -32,7 +42,7 @@
 The permissions can be modified using a ueventd.rc script and a line that beings with `/dev`. These
 lines take the format of
 
-    devname mode uid gid
+    devname mode uid gid [options]
 For example
 
     /dev/null 0666 root root
@@ -70,7 +80,7 @@
 certain files in `/sys` when matching uevents are generated. This is done using a ueventd.rc script
 and a line that begins with `/sys`. These lines take the format of
 
-    nodename attr mode uid gid
+    nodename attr mode uid gid [options]
 For example
 
     /sys/devices/system/cpu/cpu* cpufreq/scaling_max_freq 0664 system system
@@ -78,7 +88,15 @@
 attribute, `cpufreq/scaling_max_freq`, will have its mode set to `0664`, its user to to `system` and
 its group set to `system`.
 
-Note that `*` matches as a wildcard and can be used anywhere in a path.
+## Path matching
+----------------
+The path for a `/dev` or `/sys` entry can contain a `*` anywhere in the path.
+1. If the only `*` appears at the end of the string or if the _options_ parameter is set to
+`no_fnm_pathname`, ueventd matches the entry by `fnmatch(entry_path, incoming_path, 0)`
+2. Otherwise, ueventd matches the entry by `fnmatch(entry_path, incoming_path, FNM_PATHNAME)`
+
+See the [man page for fnmatch](https://www.man7.org/linux/man-pages/man3/fnmatch.3.html) for more
+details.
 
 ## Firmware loading
 ----------------
@@ -105,13 +123,20 @@
 The exact firmware file to be served can be customized by running an external program by a
 `external_firmware_handler` line in a ueventd.rc file. This line takes the format of
 
-    external_firmware_handler <devpath> <user name to run as> <path to external program>
+    external_firmware_handler <devpath> <user [group]> <path to external program>
+
+The handler will be run as the given user, or if a group is provided, as the given user and group.
+
 For example
 
     external_firmware_handler /devices/leds/red/firmware/coeffs.bin system /vendor/bin/led_coeffs.bin
 Will launch `/vendor/bin/led_coeffs.bin` as the system user instead of serving the default firmware
 for `/devices/leds/red/firmware/coeffs.bin`.
 
+The `devpath` argument may include asterisks (`*`) to match multiple paths. For example, the string
+`/dev/*/red` will match `/dev/leds/red` as well as `/dev/lights/red`. The pattern matching follows
+the rules of the fnmatch() function.
+
 Ueventd will provide the uevent `DEVPATH` and `FIRMWARE` to this external program on the environment
 via environment variables with the same names. Ueventd will use the string written to stdout as the
 new name of the firmware to load. It will still look for the new firmware in the list of firmware
@@ -133,8 +158,8 @@
 For boot time purposes, this is done in parallel across a set of child processes. `ueventd.cpp` in
 this directory contains documentation on how the parallelization is done.
 
-There is an option to parallelize the restorecon function during cold boot as well. This should only
-be done for devices that do not use genfscon, which is the recommended method for labeling sysfs
-nodes. To enable this option, use the below line in a ueventd.rc script:
+There is an option to parallelize the restorecon function during cold boot as well. It is
+recommended that devices use genfscon for labeling sysfs nodes. However, some devices may benefit
+from enabling the parallelization option:
 
     parallel_restorecon enabled
diff --git a/init/block_dev_initializer.cpp b/init/block_dev_initializer.cpp
index 8db9793..9c2a7bb 100644
--- a/init/block_dev_initializer.cpp
+++ b/init/block_dev_initializer.cpp
@@ -40,8 +40,8 @@
     return InitMiscDevice("device-mapper");
 }
 
-bool BlockDevInitializer::InitDmUser() {
-    return InitMiscDevice("dm-user");
+bool BlockDevInitializer::InitDmUser(const std::string& name) {
+    return InitMiscDevice("dm-user!" + name);
 }
 
 bool BlockDevInitializer::InitMiscDevice(const std::string& name) {
diff --git a/init/block_dev_initializer.h b/init/block_dev_initializer.h
index b8dd3f1..ec39ce0 100644
--- a/init/block_dev_initializer.h
+++ b/init/block_dev_initializer.h
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#pragma once
+
 #include <memory>
 #include <set>
 #include <string>
@@ -27,7 +29,7 @@
     BlockDevInitializer();
 
     bool InitDeviceMapper();
-    bool InitDmUser();
+    bool InitDmUser(const std::string& name);
     bool InitDevices(std::set<std::string> devices);
     bool InitDmDevice(const std::string& device);
 
diff --git a/init/builtins.cpp b/init/builtins.cpp
index d00d1b1..035038f 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -88,6 +88,7 @@
 
 using android::base::Basename;
 using android::base::SetProperty;
+using android::base::Split;
 using android::base::StartsWith;
 using android::base::StringPrintf;
 using android::base::unique_fd;
@@ -291,6 +292,38 @@
     return {};
 }
 
+static Result<void> do_load_exports(const BuiltinArguments& args) {
+    auto file_contents = ReadFile(args[1]);
+    if (!file_contents.ok()) {
+        return Error() << "Could not read input file '" << args[1]
+                       << "': " << file_contents.error();
+    }
+
+    auto lines = Split(*file_contents, "\n");
+    for (const auto& line : lines) {
+        if (line.empty()) {
+            continue;
+        }
+
+        auto env = Split(line, " ");
+
+        if (env.size() != 3) {
+            return ErrnoError() << "Expected a line as `export <name> <value>`, found: `" << line
+                                << "`";
+        }
+
+        if (env[0] != "export") {
+            return ErrnoError() << "Unknown action: '" << env[0] << "', expected 'export'";
+        }
+
+        if (setenv(env[1].c_str(), env[2].c_str(), 1) == -1) {
+            return ErrnoError() << "Failed to export '" << line << "' from " << args[1];
+        }
+    }
+
+    return {};
+}
+
 static Result<void> do_hostname(const BuiltinArguments& args) {
     if (auto result = WriteFile("/proc/sys/kernel/hostname", args[1]); !result.ok()) {
         return Error() << "Unable to write to /proc/sys/kernel/hostname: " << result.error();
@@ -860,6 +893,11 @@
         // for system as root, so it has property [partition.system.verified].
         std::string partition = entry.mount_point == "/" ? "system" : Basename(entry.mount_point);
         SetProperty("partition." + partition + ".verified", std::to_string(mode));
+
+        std::string hash_alg = fs_mgr_get_hashtree_algorithm(entry);
+        if (!hash_alg.empty()) {
+            SetProperty("partition." + partition + ".verified.hash_alg", hash_alg);
+        }
     }
 
     return {};
@@ -968,6 +1006,23 @@
     return {};
 }
 
+static Result<void> do_copy_per_line(const BuiltinArguments& args) {
+    std::string file_contents;
+    if (!android::base::ReadFileToString(args[1], &file_contents, true)) {
+        return Error() << "Could not read input file '" << args[1] << "'";
+    }
+    auto lines = Split(file_contents, "\n");
+    for (const auto& line : lines) {
+        auto result = WriteFile(args[2], line);
+        if (!result.ok()) {
+            LOG(VERBOSE) << "Could not write to output file '" << args[2] << "' with '" << line
+                         << "' : " << result.error();
+        }
+    }
+
+    return {};
+}
+
 static Result<void> do_chown(const BuiltinArguments& args) {
     auto uid = DecodeUid(args[1]);
     if (!uid.ok()) {
@@ -1214,7 +1269,7 @@
 }
 
 static Result<void> GenerateLinkerConfiguration() {
-    const char* linkerconfig_binary = "/system/bin/linkerconfig";
+    const char* linkerconfig_binary = "/apex/com.android.runtime/bin/linkerconfig";
     const char* linkerconfig_target = "/linkerconfig";
     const char* arguments[] = {linkerconfig_binary, "--target", linkerconfig_target};
 
@@ -1223,6 +1278,14 @@
         return ErrnoError() << "failed to execute linkerconfig";
     }
 
+    auto current_mount_ns = GetCurrentMountNamespace();
+    if (!current_mount_ns.ok()) {
+        return current_mount_ns.error();
+    }
+    if (*current_mount_ns == NS_DEFAULT) {
+        SetDefaultMountNamespaceReady();
+    }
+
     LOG(INFO) << "linkerconfig generated " << linkerconfig_target
               << " with mounted APEX modules info";
 
@@ -1366,6 +1429,7 @@
         {"class_start_post_data",   {1,     1,    {false,  do_class_start_post_data}}},
         {"class_stop",              {1,     1,    {false,  do_class_stop}}},
         {"copy",                    {2,     2,    {true,   do_copy}}},
+        {"copy_per_line",           {2,     2,    {true,   do_copy_per_line}}},
         {"domainname",              {1,     1,    {true,   do_domainname}}},
         {"enable",                  {1,     1,    {false,  do_enable}}},
         {"exec",                    {1,     kMax, {false,  do_exec}}},
@@ -1380,6 +1444,7 @@
         {"interface_restart",       {1,     1,    {false,  do_interface_restart}}},
         {"interface_start",         {1,     1,    {false,  do_interface_start}}},
         {"interface_stop",          {1,     1,    {false,  do_interface_stop}}},
+        {"load_exports",            {1,     1,    {false,  do_load_exports}}},
         {"load_persist_props",      {0,     0,    {false,  do_load_persist_props}}},
         {"load_system_props",       {0,     0,    {false,  do_load_system_props}}},
         {"loglevel",                {1,     1,    {false,  do_loglevel}}},
diff --git a/init/devices.cpp b/init/devices.cpp
index 5888c06..ce6298a 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -124,8 +124,15 @@
     return true;
 }
 
-Permissions::Permissions(const std::string& name, mode_t perm, uid_t uid, gid_t gid)
-    : name_(name), perm_(perm), uid_(uid), gid_(gid), prefix_(false), wildcard_(false) {
+Permissions::Permissions(const std::string& name, mode_t perm, uid_t uid, gid_t gid,
+                         bool no_fnm_pathname)
+    : name_(name),
+      perm_(perm),
+      uid_(uid),
+      gid_(gid),
+      prefix_(false),
+      wildcard_(false),
+      no_fnm_pathname_(no_fnm_pathname) {
     // Set 'prefix_' or 'wildcard_' based on the below cases:
     //
     // 1) No '*' in 'name' -> Neither are set and Match() checks a given path for strict
@@ -136,7 +143,6 @@
     //
     // 3) '*' appears elsewhere -> 'wildcard_' is set to true and Match() uses fnmatch()
     //    with FNM_PATHNAME to compare 'name' to a given path.
-
     auto wildcard_position = name_.find('*');
     if (wildcard_position != std::string::npos) {
         if (wildcard_position == name_.length() - 1) {
@@ -150,7 +156,8 @@
 
 bool Permissions::Match(const std::string& path) const {
     if (prefix_) return StartsWith(path, name_);
-    if (wildcard_) return fnmatch(name_.c_str(), path.c_str(), FNM_PATHNAME) == 0;
+    if (wildcard_)
+        return fnmatch(name_.c_str(), path.c_str(), no_fnm_pathname_ ? 0 : FNM_PATHNAME) == 0;
     return path == name_;
 }
 
@@ -461,9 +468,10 @@
 }
 
 void DeviceHandler::HandleUevent(const Uevent& uevent) {
-    if (uevent.action == "add" || uevent.action == "change" || uevent.action == "online") {
-        FixupSysPermissions(uevent.path, uevent.subsystem);
-    }
+  if (uevent.action == "add" || uevent.action == "change" ||
+      uevent.action == "bind" || uevent.action == "online") {
+    FixupSysPermissions(uevent.path, uevent.subsystem);
+  }
 
     // if it's not a /dev device, nothing to do
     if (uevent.major < 0 || uevent.minor < 0) return;
diff --git a/init/devices.h b/init/devices.h
index 05d64da..d70d746 100644
--- a/init/devices.h
+++ b/init/devices.h
@@ -38,7 +38,7 @@
   public:
     friend void TestPermissions(const Permissions& expected, const Permissions& test);
 
-    Permissions(const std::string& name, mode_t perm, uid_t uid, gid_t gid);
+    Permissions(const std::string& name, mode_t perm, uid_t uid, gid_t gid, bool no_fnm_pathname);
 
     bool Match(const std::string& path) const;
 
@@ -56,6 +56,7 @@
     gid_t gid_;
     bool prefix_;
     bool wildcard_;
+    bool no_fnm_pathname_;
 };
 
 class SysfsPermissions : public Permissions {
@@ -63,8 +64,8 @@
     friend void TestSysfsPermissions(const SysfsPermissions& expected, const SysfsPermissions& test);
 
     SysfsPermissions(const std::string& name, const std::string& attribute, mode_t perm, uid_t uid,
-                     gid_t gid)
-        : Permissions(name, perm, uid, gid), attribute_(attribute) {}
+                     gid_t gid, bool no_fnm_pathname)
+        : Permissions(name, perm, uid, gid, no_fnm_pathname), attribute_(attribute) {}
 
     bool MatchWithSubsystem(const std::string& path, const std::string& subsystem) const;
     void SetPermissions(const std::string& path) const;
diff --git a/init/devices_test.cpp b/init/devices_test.cpp
index c408bc1..e7bac68 100644
--- a/init/devices_test.cpp
+++ b/init/devices_test.cpp
@@ -221,7 +221,7 @@
 TEST(device_handler, DevPermissionsMatchNormal) {
     // Basic from ueventd.rc
     // /dev/null                 0666   root       root
-    Permissions permissions("/dev/null", 0666, 0, 0);
+    Permissions permissions("/dev/null", 0666, 0, 0, false);
     EXPECT_TRUE(permissions.Match("/dev/null"));
     EXPECT_FALSE(permissions.Match("/dev/nullsuffix"));
     EXPECT_FALSE(permissions.Match("/dev/nul"));
@@ -233,7 +233,7 @@
 TEST(device_handler, DevPermissionsMatchPrefix) {
     // Prefix from ueventd.rc
     // /dev/dri/*                0666   root       graphics
-    Permissions permissions("/dev/dri/*", 0666, 0, 1000);
+    Permissions permissions("/dev/dri/*", 0666, 0, 1000, false);
     EXPECT_TRUE(permissions.Match("/dev/dri/some_dri_device"));
     EXPECT_TRUE(permissions.Match("/dev/dri/some_other_dri_device"));
     EXPECT_TRUE(permissions.Match("/dev/dri/"));
@@ -246,7 +246,7 @@
 TEST(device_handler, DevPermissionsMatchWildcard) {
     // Wildcard example
     // /dev/device*name                0666   root       graphics
-    Permissions permissions("/dev/device*name", 0666, 0, 1000);
+    Permissions permissions("/dev/device*name", 0666, 0, 1000, false);
     EXPECT_TRUE(permissions.Match("/dev/devicename"));
     EXPECT_TRUE(permissions.Match("/dev/device123name"));
     EXPECT_TRUE(permissions.Match("/dev/deviceabcname"));
@@ -260,13 +260,31 @@
 TEST(device_handler, DevPermissionsMatchWildcardPrefix) {
     // Wildcard+Prefix example
     // /dev/device*name*                0666   root       graphics
-    Permissions permissions("/dev/device*name*", 0666, 0, 1000);
+    Permissions permissions("/dev/device*name*", 0666, 0, 1000, false);
     EXPECT_TRUE(permissions.Match("/dev/devicename"));
     EXPECT_TRUE(permissions.Match("/dev/device123name"));
     EXPECT_TRUE(permissions.Match("/dev/deviceabcname"));
     EXPECT_TRUE(permissions.Match("/dev/device123namesomething"));
     // FNM_PATHNAME doesn't match '/' with *
     EXPECT_FALSE(permissions.Match("/dev/device123name/something"));
+    EXPECT_FALSE(permissions.Match("/dev/device/1/2/3name/something"));
+    EXPECT_FALSE(permissions.Match("/dev/deviceame"));
+    EXPECT_EQ(0666U, permissions.perm());
+    EXPECT_EQ(0U, permissions.uid());
+    EXPECT_EQ(1000U, permissions.gid());
+}
+
+TEST(device_handler, DevPermissionsMatchWildcardPrefix_NoFnmPathName) {
+    // Wildcard+Prefix example with no_fnm_pathname
+    // /dev/device*name*                0666   root       graphics
+    Permissions permissions("/dev/device*name*", 0666, 0, 1000, true);
+    EXPECT_TRUE(permissions.Match("/dev/devicename"));
+    EXPECT_TRUE(permissions.Match("/dev/device123name"));
+    EXPECT_TRUE(permissions.Match("/dev/deviceabcname"));
+    EXPECT_TRUE(permissions.Match("/dev/device123namesomething"));
+    // With NoFnmPathName, the below matches, unlike DevPermissionsMatchWildcardPrefix.
+    EXPECT_TRUE(permissions.Match("/dev/device123name/something"));
+    EXPECT_TRUE(permissions.Match("/dev/device/1/2/3name/something"));
     EXPECT_FALSE(permissions.Match("/dev/deviceame"));
     EXPECT_EQ(0666U, permissions.perm());
     EXPECT_EQ(0U, permissions.uid());
@@ -275,7 +293,8 @@
 
 TEST(device_handler, SysfsPermissionsMatchWithSubsystemNormal) {
     // /sys/devices/virtual/input/input*   enable      0660  root   input
-    SysfsPermissions permissions("/sys/devices/virtual/input/input*", "enable", 0660, 0, 1001);
+    SysfsPermissions permissions("/sys/devices/virtual/input/input*", "enable", 0660, 0, 1001,
+                                 false);
     EXPECT_TRUE(permissions.MatchWithSubsystem("/sys/devices/virtual/input/input0", "input"));
     EXPECT_FALSE(permissions.MatchWithSubsystem("/sys/devices/virtual/input/not_input0", "input"));
     EXPECT_EQ(0660U, permissions.perm());
@@ -285,7 +304,7 @@
 
 TEST(device_handler, SysfsPermissionsMatchWithSubsystemClass) {
     // /sys/class/input/event*   enable      0660  root   input
-    SysfsPermissions permissions("/sys/class/input/event*", "enable", 0660, 0, 1001);
+    SysfsPermissions permissions("/sys/class/input/event*", "enable", 0660, 0, 1001, false);
     EXPECT_TRUE(permissions.MatchWithSubsystem(
         "/sys/devices/soc.0/f9924000.i2c/i2c-2/2-0020/input/input0/event0", "input"));
     EXPECT_FALSE(permissions.MatchWithSubsystem(
@@ -299,7 +318,7 @@
 
 TEST(device_handler, SysfsPermissionsMatchWithSubsystemBus) {
     // /sys/bus/i2c/devices/i2c-*   enable      0660  root   input
-    SysfsPermissions permissions("/sys/bus/i2c/devices/i2c-*", "enable", 0660, 0, 1001);
+    SysfsPermissions permissions("/sys/bus/i2c/devices/i2c-*", "enable", 0660, 0, 1001, false);
     EXPECT_TRUE(permissions.MatchWithSubsystem("/sys/devices/soc.0/f9967000.i2c/i2c-5", "i2c"));
     EXPECT_FALSE(permissions.MatchWithSubsystem("/sys/devices/soc.0/f9967000.i2c/not-i2c", "i2c"));
     EXPECT_FALSE(
diff --git a/init/firmware_handler.cpp b/init/firmware_handler.cpp
index ba7e6bd..30e808d 100644
--- a/init/firmware_handler.cpp
+++ b/init/firmware_handler.cpp
@@ -17,7 +17,9 @@
 #include "firmware_handler.h"
 
 #include <fcntl.h>
+#include <fnmatch.h>
 #include <glob.h>
+#include <grp.h>
 #include <pwd.h>
 #include <signal.h>
 #include <stdlib.h>
@@ -46,6 +48,20 @@
 namespace android {
 namespace init {
 
+namespace {
+bool PrefixMatch(const std::string& pattern, const std::string& path) {
+    return android::base::StartsWith(path, pattern);
+}
+
+bool FnMatch(const std::string& pattern, const std::string& path) {
+    return fnmatch(pattern.c_str(), path.c_str(), 0) == 0;
+}
+
+bool EqualMatch(const std::string& pattern, const std::string& path) {
+    return pattern == path;
+}
+}  // namespace
+
 static void LoadFirmware(const std::string& firmware, const std::string& root, int fw_fd,
                          size_t fw_size, int loading_fd, int data_fd) {
     // Start transfer.
@@ -66,13 +82,33 @@
     return access("/dev/.booting", F_OK) == 0;
 }
 
+ExternalFirmwareHandler::ExternalFirmwareHandler(std::string devpath, uid_t uid, gid_t gid,
+                                                 std::string handler_path)
+    : devpath(std::move(devpath)), uid(uid), gid(gid), handler_path(std::move(handler_path)) {
+    auto wildcard_position = this->devpath.find('*');
+    if (wildcard_position != std::string::npos) {
+        if (wildcard_position == this->devpath.length() - 1) {
+            this->devpath.pop_back();
+            match = std::bind(PrefixMatch, this->devpath, std::placeholders::_1);
+        } else {
+            match = std::bind(FnMatch, this->devpath, std::placeholders::_1);
+        }
+    } else {
+        match = std::bind(EqualMatch, this->devpath, std::placeholders::_1);
+    }
+}
+
+ExternalFirmwareHandler::ExternalFirmwareHandler(std::string devpath, uid_t uid,
+                                                 std::string handler_path)
+    : ExternalFirmwareHandler(devpath, uid, 0, handler_path) {}
+
 FirmwareHandler::FirmwareHandler(std::vector<std::string> firmware_directories,
                                  std::vector<ExternalFirmwareHandler> external_firmware_handlers)
     : firmware_directories_(std::move(firmware_directories)),
       external_firmware_handlers_(std::move(external_firmware_handlers)) {}
 
 Result<std::string> FirmwareHandler::RunExternalHandler(const std::string& handler, uid_t uid,
-                                                        const Uevent& uevent) const {
+                                                        gid_t gid, const Uevent& uevent) const {
     unique_fd child_stdout;
     unique_fd parent_stdout;
     if (!Socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, &child_stdout, &parent_stdout)) {
@@ -109,6 +145,13 @@
         }
         c_args.emplace_back(nullptr);
 
+        if (gid != 0) {
+            if (setgid(gid) != 0) {
+                fprintf(stderr, "setgid() failed: %s", strerror(errno));
+                _exit(EXIT_FAILURE);
+            }
+        }
+
         if (setuid(uid) != 0) {
             fprintf(stderr, "setuid() failed: %s", strerror(errno));
             _exit(EXIT_FAILURE);
@@ -160,13 +203,13 @@
 
 std::string FirmwareHandler::GetFirmwarePath(const Uevent& uevent) const {
     for (const auto& external_handler : external_firmware_handlers_) {
-        if (external_handler.devpath == uevent.path) {
+        if (external_handler.match(uevent.path)) {
             LOG(INFO) << "Launching external firmware handler '" << external_handler.handler_path
                       << "' for devpath: '" << uevent.path << "' firmware: '" << uevent.firmware
                       << "'";
 
-            auto result =
-                    RunExternalHandler(external_handler.handler_path, external_handler.uid, uevent);
+            auto result = RunExternalHandler(external_handler.handler_path, external_handler.uid,
+                                             external_handler.gid, uevent);
             if (!result.ok()) {
                 LOG(ERROR) << "Using default firmware; External firmware handler failed: "
                            << result.error();
diff --git a/init/firmware_handler.h b/init/firmware_handler.h
index 8b758ae..d2f7347 100644
--- a/init/firmware_handler.h
+++ b/init/firmware_handler.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <grp.h>
 #include <pwd.h>
 
 #include <functional>
@@ -30,11 +31,15 @@
 namespace init {
 
 struct ExternalFirmwareHandler {
-    ExternalFirmwareHandler(std::string devpath, uid_t uid, std::string handler_path)
-        : devpath(std::move(devpath)), uid(uid), handler_path(std::move(handler_path)) {}
+    ExternalFirmwareHandler(std::string devpath, uid_t uid, std::string handler_path);
+    ExternalFirmwareHandler(std::string devpath, uid_t uid, gid_t gid, std::string handler_path);
+
     std::string devpath;
     uid_t uid;
+    gid_t gid;
     std::string handler_path;
+
+    std::function<bool(const std::string&)> match;
 };
 
 class FirmwareHandler : public UeventHandler {
@@ -49,7 +54,7 @@
     friend void FirmwareTestWithExternalHandler(const std::string& test_name,
                                                 bool expect_new_firmware);
 
-    Result<std::string> RunExternalHandler(const std::string& handler, uid_t uid,
+    Result<std::string> RunExternalHandler(const std::string& handler, uid_t uid, gid_t gid,
                                            const Uevent& uevent) const;
     std::string GetFirmwarePath(const Uevent& uevent) const;
     void ProcessFirmwareEvent(const std::string& root, const std::string& firmware) const;
diff --git a/init/firmware_handler_test.cpp b/init/firmware_handler_test.cpp
index 7bb603c..f6e75b0 100644
--- a/init/firmware_handler_test.cpp
+++ b/init/firmware_handler_test.cpp
@@ -79,6 +79,8 @@
 }
 
 int HandleAbort(int argc, char** argv) {
+    // Since this is an expected failure, disable debuggerd to not generate a tombstone.
+    signal(SIGABRT, SIG_DFL);
     abort();
     return 0;
 }
@@ -101,6 +103,23 @@
     return 0;
 }
 
+TEST(firmware_handler, Matching) {
+    ExternalFirmwareHandler h("/dev/path/a.bin", getuid(), "/test");
+    ASSERT_TRUE(h.match("/dev/path/a.bin"));
+    ASSERT_FALSE(h.match("/dev/path/a.bi"));
+
+    h = ExternalFirmwareHandler("/dev/path/a.*", getuid(), "/test");
+    ASSERT_TRUE(h.match("/dev/path/a.bin"));
+    ASSERT_TRUE(h.match("/dev/path/a.bix"));
+    ASSERT_FALSE(h.match("/dev/path/b.bin"));
+
+    h = ExternalFirmwareHandler("/dev/*/a.bin", getuid(), "/test");
+    ASSERT_TRUE(h.match("/dev/path/a.bin"));
+    ASSERT_TRUE(h.match("/dev/other/a.bin"));
+    ASSERT_FALSE(h.match("/dev/other/c.bin"));
+    ASSERT_FALSE(h.match("/dev/path/b.bin"));
+}
+
 }  // namespace init
 }  // namespace android
 
diff --git a/init/first_stage_console.cpp b/init/first_stage_console.cpp
index 0f01166..e2ea0ab 100644
--- a/init/first_stage_console.cpp
+++ b/init/first_stage_console.cpp
@@ -105,8 +105,20 @@
     _exit(127);
 }
 
-int FirstStageConsole(const std::string& cmdline) {
-    auto pos = cmdline.find("androidboot.first_stage_console=");
+int FirstStageConsole(const std::string& cmdline, const std::string& bootconfig) {
+    auto pos = bootconfig.find("androidboot.first_stage_console =");
+    if (pos != std::string::npos) {
+        int val = 0;
+        if (sscanf(bootconfig.c_str() + pos, "androidboot.first_stage_console = \"%d\"", &val) !=
+            1) {
+            return FirstStageConsoleParam::DISABLED;
+        }
+        if (val <= FirstStageConsoleParam::MAX_PARAM_VALUE && val >= 0) {
+            return val;
+        }
+    }
+
+    pos = cmdline.find("androidboot.first_stage_console=");
     if (pos != std::string::npos) {
         int val = 0;
         if (sscanf(cmdline.c_str() + pos, "androidboot.first_stage_console=%d", &val) != 1) {
diff --git a/init/first_stage_console.h b/init/first_stage_console.h
index d5744df..4a30d35 100644
--- a/init/first_stage_console.h
+++ b/init/first_stage_console.h
@@ -29,7 +29,7 @@
 };
 
 void StartConsole(const std::string& cmdline);
-int FirstStageConsole(const std::string& cmdline);
+int FirstStageConsole(const std::string& cmdline, const std::string& bootconfig);
 
 }  // namespace init
 }  // namespace android
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 91aaffd..78e5b60 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -42,6 +42,7 @@
 #include "first_stage_mount.h"
 #include "reboot_utils.h"
 #include "second_stage_resources.h"
+#include "snapuserd_transition.h"
 #include "switch_root.h"
 #include "util.h"
 
@@ -90,41 +91,20 @@
                     }
                 }
             }
+        } else if (de->d_type == DT_REG) {
+            // Do not free snapuserd if we will need the ramdisk copy during the
+            // selinux transition.
+            if (de->d_name == "snapuserd"s && IsFirstStageSnapuserdRunning()) {
+                continue;
+            }
         }
         unlinkat(dfd, de->d_name, is_dir ? AT_REMOVEDIR : 0);
     }
 }
 
-bool ForceNormalBoot(const std::string& cmdline) {
-    return cmdline.find("androidboot.force_normal_boot=1") != std::string::npos;
-}
-
-// Move e2fsck before switching root, so that it is available at the same path
-// after switching root.
-void PrepareSwitchRoot() {
-    constexpr const char* src = "/system/bin/e2fsck";
-    constexpr const char* dst = "/first_stage_ramdisk/system/bin/e2fsck";
-
-    if (access(dst, X_OK) == 0) {
-        LOG(INFO) << dst << " already exists and it can be executed";
-        return;
-    }
-
-    if (access(src, F_OK) != 0) {
-        PLOG(INFO) << "Not moving " << src << " because it cannot be accessed";
-        return;
-    }
-
-    auto dst_dir = android::base::Dirname(dst);
-    std::error_code ec;
-    if (!fs::create_directories(dst_dir, ec)) {
-        LOG(FATAL) << "Cannot create " << dst_dir << ": " << ec.message();
-    }
-    if (rename(src, dst) != 0) {
-        PLOG(FATAL) << "Cannot move " << src << " to " << dst
-                    << ". Either install e2fsck.ramdisk so that it is at the correct place (" << dst
-                    << "), or make ramdisk writable";
-    }
+bool ForceNormalBoot(const std::string& cmdline, const std::string& bootconfig) {
+    return bootconfig.find("androidboot.force_normal_boot = \"1\"") != std::string::npos ||
+           cmdline.find("androidboot.force_normal_boot=1") != std::string::npos;
 }
 
 }  // namespace
@@ -143,7 +123,7 @@
 }
 
 #define MODULE_BASE_DIR "/lib/modules"
-bool LoadKernelModules(bool recovery, bool want_console) {
+bool LoadKernelModules(bool recovery, bool want_console, int& modules_loaded) {
     struct utsname uts;
     if (uname(&uts)) {
         LOG(FATAL) << "Failed to get kernel version.";
@@ -185,7 +165,7 @@
         dir_path.append(module_dir);
         Modprobe m({dir_path}, GetModuleLoadList(recovery, dir_path));
         bool retval = m.LoadListedModules(!want_console);
-        int modules_loaded = m.GetModuleCount();
+        modules_loaded = m.GetModuleCount();
         if (modules_loaded > 0) {
             return retval;
         }
@@ -193,7 +173,7 @@
 
     Modprobe m({MODULE_BASE_DIR}, GetModuleLoadList(recovery, MODULE_BASE_DIR));
     bool retval = m.LoadListedModules(!want_console);
-    int modules_loaded = m.GetModuleCount();
+    modules_loaded = m.GetModuleCount();
     if (modules_loaded > 0) {
         return retval;
     }
@@ -230,6 +210,10 @@
     CHECKCALL(chmod("/proc/cmdline", 0440));
     std::string cmdline;
     android::base::ReadFileToString("/proc/cmdline", &cmdline);
+    // Don't expose the raw bootconfig to unprivileged processes.
+    chmod("/proc/bootconfig", 0440);
+    std::string bootconfig;
+    android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
     gid_t groups[] = {AID_READPROC};
     CHECKCALL(setgroups(arraysize(groups), groups));
     CHECKCALL(mount("sysfs", "/sys", "sysfs", 0, NULL));
@@ -297,17 +281,35 @@
         old_root_dir.reset();
     }
 
-    auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline) : 0;
+    auto want_console = ALLOW_FIRST_STAGE_CONSOLE ? FirstStageConsole(cmdline, bootconfig) : 0;
 
-    if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline), want_console)) {
+    boot_clock::time_point module_start_time = boot_clock::now();
+    int module_count = 0;
+    if (!LoadKernelModules(IsRecoveryMode() && !ForceNormalBoot(cmdline, bootconfig), want_console,
+                           module_count)) {
         if (want_console != FirstStageConsoleParam::DISABLED) {
             LOG(ERROR) << "Failed to load kernel modules, starting console";
         } else {
             LOG(FATAL) << "Failed to load kernel modules";
         }
     }
+    if (module_count > 0) {
+        auto module_elapse_time = std::chrono::duration_cast<std::chrono::milliseconds>(
+                boot_clock::now() - module_start_time);
+        setenv(kEnvInitModuleDurationMs, std::to_string(module_elapse_time.count()).c_str(), 1);
+        LOG(INFO) << "Loaded " << module_count << " kernel modules took "
+                  << module_elapse_time.count() << " ms";
+    }
 
+
+    bool created_devices = false;
     if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) {
+        if (!IsRecoveryMode()) {
+            created_devices = DoCreateDevices();
+            if (!created_devices){
+                LOG(ERROR) << "Failed to create device nodes early";
+            }
+        }
         StartConsole(cmdline);
     }
 
@@ -315,7 +317,7 @@
         std::string dest = GetRamdiskPropForSecondStage();
         std::string dir = android::base::Dirname(dest);
         std::error_code ec;
-        if (!fs::create_directories(dir, ec)) {
+        if (!fs::create_directories(dir, ec) && !!ec) {
             LOG(FATAL) << "Can't mkdir " << dir << ": " << ec.message();
         }
         if (!fs::copy_file(kBootImageRamdiskProp, dest, ec)) {
@@ -325,19 +327,8 @@
         LOG(INFO) << "Copied ramdisk prop to " << dest;
     }
 
-    if (ForceNormalBoot(cmdline)) {
-        mkdir("/first_stage_ramdisk", 0755);
-        PrepareSwitchRoot();
-        // SwitchRoot() must be called with a mount point as the target, so we bind mount the
-        // target directory to itself here.
-        if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
-            LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
-        }
-        SwitchRoot("/first_stage_ramdisk");
-    }
-
-    // If this file is present, the second-stage init will use a userdebug sepolicy
-    // and load adb_debug.prop to allow adb root, if the device is unlocked.
+    // If "/force_debuggable" is present, the second-stage init will use a userdebug
+    // sepolicy and load adb_debug.prop to allow adb root, if the device is unlocked.
     if (access("/force_debuggable", F_OK) == 0) {
         std::error_code ec;  // to invoke the overloaded copy_file() that won't throw.
         if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) ||
@@ -349,7 +340,17 @@
         }
     }
 
-    if (!DoFirstStageMount()) {
+    if (ForceNormalBoot(cmdline, bootconfig)) {
+        mkdir("/first_stage_ramdisk", 0755);
+        // SwitchRoot() must be called with a mount point as the target, so we bind mount the
+        // target directory to itself here.
+        if (mount("/first_stage_ramdisk", "/first_stage_ramdisk", nullptr, MS_BIND, nullptr) != 0) {
+            LOG(FATAL) << "Could not bind mount /first_stage_ramdisk to itself";
+        }
+        SwitchRoot("/first_stage_ramdisk");
+    }
+
+    if (!DoFirstStageMount(!created_devices)) {
         LOG(FATAL) << "Failed to mount required partitions early ...";
     }
 
diff --git a/init/first_stage_init.h b/init/first_stage_init.h
index 7de816f..1211f29 100644
--- a/init/first_stage_init.h
+++ b/init/first_stage_init.h
@@ -22,6 +22,7 @@
 int FirstStageMain(int argc, char** argv);
 
 static constexpr char kEnvFirstStageStartedAt[] = "FIRST_STAGE_STARTED_AT";
+static constexpr char kEnvInitModuleDurationMs[] = "INIT_MODULE_DURATION_MS";
 
 }  // namespace init
 }  // namespace android
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index b7d50cf..616d285 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -44,12 +44,15 @@
 
 #include "block_dev_initializer.h"
 #include "devices.h"
+#include "result.h"
+#include "snapuserd_transition.h"
 #include "switch_root.h"
 #include "uevent.h"
 #include "uevent_listener.h"
 #include "util.h"
 
 using android::base::ReadFileToString;
+using android::base::Result;
 using android::base::Split;
 using android::base::StringPrintf;
 using android::base::Timer;
@@ -80,19 +83,20 @@
 
     // The factory method to create either FirstStageMountVBootV1 or FirstStageMountVBootV2
     // based on device tree configurations.
-    static std::unique_ptr<FirstStageMount> Create();
+    static Result<std::unique_ptr<FirstStageMount>> Create();
+    bool DoCreateDevices();    // Creates devices and logical partitions from storage devices
     bool DoFirstStageMount();  // Mounts fstab entries read from device tree.
     bool InitDevices();
 
   protected:
     bool InitRequiredDevices(std::set<std::string> devices);
     bool CreateLogicalPartitions();
+    bool CreateSnapshotPartitions(android::snapshot::SnapshotManager* sm);
     bool MountPartition(const Fstab::iterator& begin, bool erase_same_mounts,
                         Fstab::iterator* end = nullptr);
 
     bool MountPartitions();
     bool TrySwitchSystemAsRoot();
-    bool TrySkipMountingPartitions();
     bool IsDmLinearEnabled();
     void GetSuperDeviceName(std::set<std::string>* devices);
     bool InitDmLinearBackingDevices(const android::fs_mgr::LpMetadata& metadata);
@@ -109,6 +113,7 @@
 
     bool need_dm_verity_;
     bool dsu_not_on_userdata_ = false;
+    bool use_snapuserd_ = false;
 
     Fstab fstab_;
     // The super path is only set after InitDevices, and is invalid before.
@@ -156,7 +161,7 @@
     return is_android_dt_value_expected("vbmeta/compatible", "android,vbmeta");
 }
 
-static Fstab ReadFirstStageFstab() {
+static Result<Fstab> ReadFirstStageFstab() {
     Fstab fstab;
     if (!ReadFstabFromDt(&fstab)) {
         if (ReadDefaultFstab(&fstab)) {
@@ -166,7 +171,7 @@
                                        }),
                         fstab.end());
         } else {
-            LOG(INFO) << "Failed to fstab for first stage mount";
+            return Error() << "failed to read default fstab for first stage mount";
         }
     }
     return fstab;
@@ -232,13 +237,39 @@
     super_partition_name_ = fs_mgr_get_super_partition_name();
 }
 
-std::unique_ptr<FirstStageMount> FirstStageMount::Create() {
+Result<std::unique_ptr<FirstStageMount>> FirstStageMount::Create() {
     auto fstab = ReadFirstStageFstab();
-    if (IsDtVbmetaCompatible(fstab)) {
-        return std::make_unique<FirstStageMountVBootV2>(std::move(fstab));
-    } else {
-        return std::make_unique<FirstStageMountVBootV1>(std::move(fstab));
+    if (!fstab.ok()) {
+        return fstab.error();
     }
+
+    if (IsDtVbmetaCompatible(*fstab)) {
+        return std::make_unique<FirstStageMountVBootV2>(std::move(*fstab));
+    } else {
+        return std::make_unique<FirstStageMountVBootV1>(std::move(*fstab));
+    }
+}
+
+bool FirstStageMount::DoCreateDevices() {
+    if (!InitDevices()) return false;
+
+    // Mount /metadata before creating logical partitions, since we need to
+    // know whether a snapshot merge is in progress.
+    auto metadata_partition = std::find_if(fstab_.begin(), fstab_.end(), [](const auto& entry) {
+        return entry.mount_point == "/metadata";
+    });
+    if (metadata_partition != fstab_.end()) {
+        if (MountPartition(metadata_partition, true /* erase_same_mounts */)) {
+            // Copies DSU AVB keys from the ramdisk to /metadata.
+            // Must be done before the following TrySwitchSystemAsRoot().
+            // Otherwise, ramdisk will be inaccessible after switching root.
+            CopyDsuAvbKeys();
+        }
+    }
+
+    if (!CreateLogicalPartitions()) return false;
+
+    return true;
 }
 
 bool FirstStageMount::DoFirstStageMount() {
@@ -248,8 +279,6 @@
         return true;
     }
 
-    if (!InitDevices()) return false;
-
     if (!MountPartitions()) return false;
 
     return true;
@@ -338,12 +367,7 @@
             return false;
         }
         if (sm->NeedSnapshotsInFirstStageMount()) {
-            // When COW images are present for snapshots, they are stored on
-            // the data partition.
-            if (!InitRequiredDevices({"userdata"})) {
-                return false;
-            }
-            return sm->CreateLogicalAndSnapshotPartitions(super_path_);
+            return CreateSnapshotPartitions(sm.get());
         }
     }
 
@@ -358,6 +382,37 @@
     return android::fs_mgr::CreateLogicalPartitions(*metadata.get(), super_path_);
 }
 
+bool FirstStageMount::CreateSnapshotPartitions(SnapshotManager* sm) {
+    // When COW images are present for snapshots, they are stored on
+    // the data partition.
+    if (!InitRequiredDevices({"userdata"})) {
+        return false;
+    }
+
+    use_snapuserd_ = sm->IsSnapuserdRequired();
+    if (use_snapuserd_) {
+        LaunchFirstStageSnapuserd();
+    }
+
+    sm->SetUeventRegenCallback([this](const std::string& device) -> bool {
+        if (android::base::StartsWith(device, "/dev/block/dm-")) {
+            return block_dev_init_.InitDmDevice(device);
+        }
+        if (android::base::StartsWith(device, "/dev/dm-user/")) {
+            return block_dev_init_.InitDmUser(android::base::Basename(device));
+        }
+        return block_dev_init_.InitDevices({device});
+    });
+    if (!sm->CreateLogicalAndSnapshotPartitions(super_path_)) {
+        return false;
+    }
+
+    if (use_snapuserd_) {
+        CleanupSnapuserdSocket();
+    }
+    return true;
+}
+
 bool FirstStageMount::MountPartition(const Fstab::iterator& begin, bool erase_same_mounts,
                                      Fstab::iterator* end) {
     // Sets end to begin + 1, so we can just return on failure below.
@@ -457,6 +512,10 @@
 
     if (system_partition == fstab_.end()) return true;
 
+    if (use_snapuserd_) {
+        SaveRamdiskPathToSnapuserd();
+    }
+
     if (MountPartition(system_partition, false /* erase_same_mounts */)) {
         if (dsu_not_on_userdata_ && fs_mgr_verity_is_check_at_most_once(*system_partition)) {
             LOG(ERROR) << "check_most_at_once forbidden on external media";
@@ -472,25 +531,9 @@
 }
 
 bool FirstStageMount::MountPartitions() {
-    // Mount /metadata before creating logical partitions, since we need to
-    // know whether a snapshot merge is in progress.
-    auto metadata_partition = std::find_if(fstab_.begin(), fstab_.end(), [](const auto& entry) {
-        return entry.mount_point == "/metadata";
-    });
-    if (metadata_partition != fstab_.end()) {
-        if (MountPartition(metadata_partition, true /* erase_same_mounts */)) {
-            // Copies DSU AVB keys from the ramdisk to /metadata.
-            // Must be done before the following TrySwitchSystemAsRoot().
-            // Otherwise, ramdisk will be inaccessible after switching root.
-            CopyDsuAvbKeys();
-        }
-    }
-
-    if (!CreateLogicalPartitions()) return false;
-
     if (!TrySwitchSystemAsRoot()) return false;
 
-    if (!SkipMountingPartitions(&fstab_)) return false;
+    if (!SkipMountingPartitions(&fstab_, true /* verbose */)) return false;
 
     for (auto current = fstab_.begin(); current != fstab_.end();) {
         // We've already mounted /system above.
@@ -499,6 +542,12 @@
             continue;
         }
 
+        // Handle overlayfs entries later.
+        if (current->fs_type == "overlay") {
+            ++current;
+            continue;
+        }
+
         // Skip raw partition entries such as boot, dtbo, etc.
         // Having emmc fstab entries allows us to probe current->vbmeta_partition
         // in InitDevices() when they are AVB chained partitions.
@@ -523,6 +572,12 @@
         current = end;
     }
 
+    for (const auto& entry : fstab_) {
+        if (entry.fs_type == "overlay") {
+            fs_mgr_overlayfs_mount_fstab_entry(entry);
+        }
+    }
+
     // If we don't see /system or / in the fstab, then we need to create an root entry for
     // overlayfs.
     if (!GetEntryForMountPoint(&fstab_, "/system") && !GetEntryForMountPoint(&fstab_, "/")) {
@@ -634,6 +689,10 @@
     // Includes the partition names of fstab records.
     // Notes that fstab_rec->blk_device has A/B suffix updated by fs_mgr when A/B is used.
     for (const auto& fstab_entry : fstab_) {
+        // Skip pseudo filesystems.
+        if (fstab_entry.fs_type == "overlay") {
+            continue;
+        }
         if (!fstab_entry.fs_mgr_flags.logical) {
             devices->emplace(basename(fstab_entry.blk_device.c_str()));
         }
@@ -696,6 +755,10 @@
         if (fstab_entry.fs_mgr_flags.avb) {
             need_dm_verity_ = true;
         }
+        // Skip pseudo filesystems.
+        if (fstab_entry.fs_type == "overlay") {
+            continue;
+        }
         if (fstab_entry.fs_mgr_flags.logical) {
             // Don't try to find logical partitions via uevent regeneration.
             logical_partitions.emplace(basename(fstab_entry.blk_device.c_str()));
@@ -796,20 +859,35 @@
 
 // Public functions
 // ----------------
+// Creates devices and logical partitions from storage devices
+bool DoCreateDevices() {
+    auto fsm = FirstStageMount::Create();
+    if (!fsm.ok()) {
+        LOG(ERROR) << "Failed to create FirstStageMount: " << fsm.error();
+        return false;
+    }
+    return (*fsm)->DoCreateDevices();
+}
+
 // Mounts partitions specified by fstab in device tree.
-bool DoFirstStageMount() {
+bool DoFirstStageMount(bool create_devices) {
     // Skips first stage mount if we're in recovery mode.
     if (IsRecoveryMode()) {
         LOG(INFO) << "First stage mount skipped (recovery mode)";
         return true;
     }
 
-    std::unique_ptr<FirstStageMount> handle = FirstStageMount::Create();
-    if (!handle) {
-        LOG(ERROR) << "Failed to create FirstStageMount";
+    auto fsm = FirstStageMount::Create();
+    if (!fsm.ok()) {
+        LOG(ERROR) << "Failed to create FirstStageMount " << fsm.error();
         return false;
     }
-    return handle->DoFirstStageMount();
+
+    if (create_devices) {
+        if (!(*fsm)->DoCreateDevices()) return false;
+    }
+
+    return (*fsm)->DoFirstStageMount();
 }
 
 void SetInitAvbVersionInRecovery() {
@@ -819,8 +897,12 @@
     }
 
     auto fstab = ReadFirstStageFstab();
+    if (!fstab.ok()) {
+        LOG(ERROR) << fstab.error();
+        return;
+    }
 
-    if (!IsDtVbmetaCompatible(fstab)) {
+    if (!IsDtVbmetaCompatible(*fstab)) {
         LOG(INFO) << "Skipped setting INIT_AVB_VERSION (not vbmeta compatible)";
         return;
     }
@@ -830,7 +912,7 @@
     // We only set INIT_AVB_VERSION when the AVB verification succeeds, i.e., the
     // Open() function returns a valid handle.
     // We don't need to mount partitions here in recovery mode.
-    FirstStageMountVBootV2 avb_first_mount(std::move(fstab));
+    FirstStageMountVBootV2 avb_first_mount(std::move(*fstab));
     if (!avb_first_mount.InitDevices()) {
         LOG(ERROR) << "Failed to init devices for INIT_AVB_VERSION";
         return;
diff --git a/init/first_stage_mount.h b/init/first_stage_mount.h
index 21d87fd..2f4e663 100644
--- a/init/first_stage_mount.h
+++ b/init/first_stage_mount.h
@@ -19,7 +19,8 @@
 namespace android {
 namespace init {
 
-bool DoFirstStageMount();
+bool DoCreateDevices();
+bool DoFirstStageMount(bool create_devices);
 void SetInitAvbVersionInRecovery();
 
 }  // namespace init
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index ef9a451..db127d3 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -25,6 +25,8 @@
 #include <fstream>
 #include <iostream>
 #include <iterator>
+#include <map>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -51,6 +53,7 @@
 
 using namespace std::literals;
 
+using android::base::EndsWith;
 using android::base::ParseInt;
 using android::base::ReadFileToString;
 using android::base::Split;
@@ -61,6 +64,10 @@
 
 static std::vector<std::string> passwd_files;
 
+// NOTE: Keep this in sync with the order used by init.cpp LoadBootScripts()
+static const std::vector<std::string> partition_search_order =
+        std::vector<std::string>({"system", "system_ext", "odm", "vendor", "product"});
+
 static std::vector<std::pair<std::string, int>> GetVendorPasswd(const std::string& passwd_file) {
     std::string passwd;
     if (!ReadFileToString(passwd_file, &passwd)) {
@@ -148,13 +155,24 @@
 #include "generated_stub_builtin_function_map.h"
 
 void PrintUsage() {
-    std::cout << "usage: host_init_verifier [options] <init rc file>\n"
-                 "\n"
-                 "Tests an init script for correctness\n"
-                 "\n"
-                 "-p FILE\tSearch this passwd file for users and groups\n"
-                 "--property_contexts=FILE\t Use this file for property_contexts\n"
-              << std::endl;
+    fprintf(stdout, R"(usage: host_init_verifier [options]
+
+Tests init script(s) for correctness.
+
+Generic options:
+  -p FILE                     Search this passwd file for users and groups.
+  --property_contexts=FILE    Use this file for property_contexts.
+
+Single script mode options:
+  [init rc file]              Positional argument; test this init script.
+
+Multiple script mode options:
+  --out_system=DIR            Path to the output product directory for the system partition.
+  --out_system_ext=DIR        Path to the output product directory for the system_ext partition.
+  --out_odm=DIR               Path to the output product directory for the odm partition.
+  --out_vendor=DIR            Path to the output product directory for the vendor partition.
+  --out_product=DIR           Path to the output product directory for the product partition.
+)");
 }
 
 Result<InterfaceInheritanceHierarchyMap> ReadInterfaceInheritanceHierarchy() {
@@ -203,12 +221,18 @@
     android::base::SetMinimumLogSeverity(android::base::ERROR);
 
     auto property_infos = std::vector<PropertyInfoEntry>();
+    std::map<std::string, std::string> partition_map;
 
     while (true) {
         static const char kPropertyContexts[] = "property-contexts=";
         static const struct option long_options[] = {
                 {"help", no_argument, nullptr, 'h'},
                 {kPropertyContexts, required_argument, nullptr, 0},
+                {"out_system", required_argument, nullptr, 0},
+                {"out_system_ext", required_argument, nullptr, 0},
+                {"out_odm", required_argument, nullptr, 0},
+                {"out_vendor", required_argument, nullptr, 0},
+                {"out_product", required_argument, nullptr, 0},
                 {nullptr, 0, nullptr, 0},
         };
 
@@ -224,6 +248,16 @@
                 if (long_options[option_index].name == kPropertyContexts) {
                     HandlePropertyContexts(optarg, &property_infos);
                 }
+                for (const auto& p : partition_search_order) {
+                    if (long_options[option_index].name == "out_" + p) {
+                        if (partition_map.find(p) != partition_map.end()) {
+                            PrintUsage();
+                            return EXIT_FAILURE;
+                        }
+                        partition_map[p] =
+                                EndsWith(optarg, "/") ? optarg : std::string(optarg) + "/";
+                    }
+                }
                 break;
             case 'h':
                 PrintUsage();
@@ -240,7 +274,9 @@
     argc -= optind;
     argv += optind;
 
-    if (argc != 1) {
+    // If provided, use the partition map to check multiple init rc files.
+    // Otherwise, check a single init rc file.
+    if ((!partition_map.empty() && argc != 0) || (partition_map.empty() && argc != 1)) {
         PrintUsage();
         return EXIT_FAILURE;
     }
@@ -262,24 +298,42 @@
 
     property_info_area = reinterpret_cast<const PropertyInfoArea*>(serialized_contexts.c_str());
 
+    if (!partition_map.empty()) {
+        std::vector<std::string> vendor_prefixes;
+        for (const auto& partition : {"vendor", "odm"}) {
+            if (partition_map.find(partition) != partition_map.end()) {
+                vendor_prefixes.push_back(partition_map.at(partition));
+            }
+        }
+        InitializeHostSubcontext(vendor_prefixes);
+    }
+
     const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap();
     Action::set_function_map(&function_map);
     ActionManager& am = ActionManager::GetInstance();
     ServiceList& sl = ServiceList::GetInstance();
     Parser parser;
-    parser.AddSectionParser("service", std::make_unique<ServiceParser>(
-                                               &sl, nullptr, *interface_inheritance_hierarchy_map));
-    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, nullptr));
+    parser.AddSectionParser("service",
+                            std::make_unique<ServiceParser>(&sl, GetSubcontext(),
+                                                            *interface_inheritance_hierarchy_map));
+    parser.AddSectionParser("on", std::make_unique<ActionParser>(&am, GetSubcontext()));
     parser.AddSectionParser("import", std::make_unique<HostImportParser>());
 
-    if (!parser.ParseConfigFileInsecure(*argv)) {
-        LOG(ERROR) << "Failed to open init rc script '" << *argv << "'";
-        return EXIT_FAILURE;
+    if (!partition_map.empty()) {
+        for (const auto& p : partition_search_order) {
+            if (partition_map.find(p) != partition_map.end()) {
+                parser.ParseConfig(partition_map.at(p) + "etc/init");
+            }
+        }
+    } else {
+        if (!parser.ParseConfigFileInsecure(*argv)) {
+            LOG(ERROR) << "Failed to open init rc script '" << *argv << "'";
+            return EXIT_FAILURE;
+        }
     }
     size_t failures = parser.parse_error_count() + am.CheckAllCommands() + sl.CheckAllCommands();
     if (failures > 0) {
-        LOG(ERROR) << "Failed to parse init script '" << *argv << "' with " << failures
-                   << " errors";
+        LOG(ERROR) << "Failed to parse init scripts with " << failures << " error(s).";
         return EXIT_FAILURE;
     }
     return EXIT_SUCCESS;
diff --git a/init/init.cpp b/init/init.cpp
index c6f2066..a7325ca 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -79,6 +79,7 @@
 #include "service.h"
 #include "service_parser.h"
 #include "sigchld_handler.h"
+#include "snapuserd_transition.h"
 #include "subcontext.h"
 #include "system/core/init/property_service.pb.h"
 #include "util.h"
@@ -266,12 +267,10 @@
     if (shutdown_state.do_shutdown()) {
         LOG(ERROR) << "sys.powerctl set while a previous shutdown command has not been handled";
         UnwindMainThreadStack();
-        DumpShutdownDebugInformation();
     }
     if (IsShuttingDown()) {
         LOG(ERROR) << "sys.powerctl set while init is already shutting down";
         UnwindMainThreadStack();
-        DumpShutdownDebugInformation();
     }
 }
 
@@ -519,11 +518,9 @@
     if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) {
         return;
     }
-    ImportKernelCmdline([](const std::string& key, const std::string& value) {
-        if (key == "androidboot.verifiedbootstate") {
-            SetProperty("ro.boot.flash.locked", value == "orange" ? "0" : "1");
-        }
-    });
+    SetProperty(
+            "ro.boot.flash.locked",
+            android::base::GetProperty("ro.boot.verifiedbootstate", "") == "orange" ? "0" : "1");
 }
 
 static Result<void> property_enable_triggers_action(const BuiltinArguments& args) {
@@ -714,6 +711,10 @@
     SetProperty("ro.boottime.init.selinux",
                 std::to_string(second_stage_start_time.time_since_epoch().count() -
                                selinux_start_time_ns));
+    if (auto init_module_time_str = getenv(kEnvInitModuleDurationMs); init_module_time_str) {
+        SetProperty("ro.boottime.init.modules", init_module_time_str);
+        unsetenv(kEnvInitModuleDurationMs);
+    }
 }
 
 void SendLoadPersistentPropertiesMessage() {
@@ -724,32 +725,6 @@
     }
 }
 
-static Result<void> TransitionSnapuserdAction(const BuiltinArguments&) {
-    if (!SnapshotManager::IsSnapshotManagerNeeded() ||
-        !android::base::GetBoolProperty(android::snapshot::kVirtualAbCompressionProp, false)) {
-        return {};
-    }
-
-    auto sm = SnapshotManager::New();
-    if (!sm) {
-        LOG(FATAL) << "Failed to create SnapshotManager, will not transition snapuserd";
-        return {};
-    }
-
-    ServiceList& service_list = ServiceList::GetInstance();
-    auto svc = service_list.FindService("snapuserd");
-    if (!svc) {
-        LOG(FATAL) << "Failed to find snapuserd service, aborting transition";
-        return {};
-    }
-    svc->Start();
-
-    if (!sm->PerformSecondStageTransition()) {
-        LOG(FATAL) << "Failed to transition snapuserd to second-stage";
-    }
-    return {};
-}
-
 int SecondStageMain(int argc, char** argv) {
     if (REBOOT_BOOTLOADER_ON_PANIC) {
         InstallRebootSignalHandlers();
@@ -760,7 +735,7 @@
     trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); };
 
     SetStdioToDevNull(argv);
-    InitSecondStageLogging(argv);
+    InitKernelLogging(argv);
     LOG(INFO) << "init second stage started!";
 
     // Update $PATH in the case the second stage init is newer than first stage init, where it is
@@ -875,7 +850,6 @@
     SetProperty(gsi::kGsiInstalledProp, is_installed);
 
     am.QueueBuiltinAction(SetupCgroupsAction, "SetupCgroups");
-    am.QueueBuiltinAction(TransitionSnapuserdAction, "TransitionSnapuserd");
     am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict");
     am.QueueBuiltinAction(TestPerfEventSelinuxAction, "TestPerfEventSelinux");
     am.QueueEventTrigger("early-init");
@@ -883,7 +857,6 @@
     // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
     am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
     // ... so that we can start queuing up actions that require stuff from /dev.
-    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
     am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits");
     Keychords keychords;
     am.QueueBuiltinAction(
@@ -899,10 +872,6 @@
     // Trigger all the boot actions to get us started.
     am.QueueEventTrigger("init");
 
-    // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
-    // wasn't ready immediately after wait_for_coldboot_done
-    am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng");
-
     // Don't mount filesystems or start core system services in charger mode.
     std::string bootmode = GetProperty("ro.bootmode", "");
     if (bootmode == "charger") {
diff --git a/init/init_test.cpp b/init/init_test.cpp
index fa65740..8550ec8 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -17,6 +17,7 @@
 #include <functional>
 
 #include <android-base/file.h>
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <gtest/gtest.h>
 
@@ -268,6 +269,17 @@
     ASSERT_EQ(1u, parser.parse_error_count());
 }
 
+class TestCaseLogger : public ::testing::EmptyTestEventListener {
+    void OnTestStart(const ::testing::TestInfo& test_info) override {
+#ifdef __ANDROID__
+        LOG(INFO) << "===== " << test_info.test_suite_name() << "::" << test_info.name() << " ("
+                  << test_info.file() << ":" << test_info.line() << ")";
+#else
+        UNUSED(test_info);
+#endif
+    }
+};
+
 }  // namespace init
 }  // namespace android
 
@@ -284,5 +296,6 @@
     }
 
     testing::InitGoogleTest(&argc, argv);
+    testing::UnitTest::GetInstance()->listeners().Append(new android::init::TestCaseLogger());
     return RUN_ALL_TESTS();
 }
diff --git a/init/lmkd_service.cpp b/init/lmkd_service.cpp
index dd1ab4d..c982925 100644
--- a/init/lmkd_service.cpp
+++ b/init/lmkd_service.cpp
@@ -79,7 +79,7 @@
 }
 
 static void RegisterServices(pid_t exclude_pid) {
-    for (const auto& service : ServiceList::GetInstance().services()) {
+    for (const auto& service : ServiceList::GetInstance()) {
         auto svc = service.get();
         if (svc->oom_score_adjust() != DEFAULT_OOM_SCORE_ADJUST) {
             // skip if process is excluded or not yet forked (pid==0)
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index 59cc140..15252a6 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -115,22 +115,29 @@
         return {};
     }
     dirent* entry;
+    std::vector<std::string> entries;
+
     while ((entry = readdir(dir.get())) != nullptr) {
         if (entry->d_name[0] == '.') continue;
         if (entry->d_type == DT_DIR) {
-            const std::string apex_path = from_dir + "/" + entry->d_name;
-            const auto apex_manifest = GetApexManifest(apex_path);
-            if (!apex_manifest.ok()) {
-                LOG(ERROR) << apex_path << " is not an APEX directory: " << apex_manifest.error();
-                continue;
-            }
-            const std::string mount_path = to_dir + "/" + apex_manifest->name();
-            if (auto result = MountDir(apex_path, mount_path); !result.ok()) {
-                return result;
-            }
-            on_activate(apex_path, *apex_manifest);
+            entries.push_back(entry->d_name);
         }
     }
+
+    std::sort(entries.begin(), entries.end());
+    for (const auto& name : entries) {
+        const std::string apex_path = from_dir + "/" + name;
+        const auto apex_manifest = GetApexManifest(apex_path);
+        if (!apex_manifest.ok()) {
+            LOG(ERROR) << apex_path << " is not an APEX directory: " << apex_manifest.error();
+            continue;
+        }
+        const std::string mount_path = to_dir + "/" + apex_manifest->name();
+        if (auto result = MountDir(apex_path, mount_path); !result.ok()) {
+            return result;
+        }
+        on_activate(apex_path, *apex_manifest);
+    }
     return {};
 }
 
@@ -294,5 +301,20 @@
     return {};
 }
 
+base::Result<MountNamespace> GetCurrentMountNamespace() {
+    std::string current_namespace_id = GetMountNamespaceId();
+    if (current_namespace_id == "") {
+        return Error() << "Failed to get current mount namespace ID";
+    }
+
+    if (current_namespace_id == bootstrap_ns_id) {
+        return NS_BOOTSTRAP;
+    } else if (current_namespace_id == default_ns_id) {
+        return NS_DEFAULT;
+    }
+
+    return Error() << "Failed to find current mount namespace";
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/mount_namespace.h b/init/mount_namespace.h
index d4d6f82..5e3dab2 100644
--- a/init/mount_namespace.h
+++ b/init/mount_namespace.h
@@ -26,5 +26,7 @@
 bool SetupMountNamespaces();
 base::Result<void> SwitchToMountNamespaceIfNeeded(MountNamespace target_mount_namespace);
 
+base::Result<MountNamespace> GetCurrentMountNamespace();
+
 }  // namespace init
 }  // namespace android
diff --git a/init/property_service.cpp b/init/property_service.cpp
index e71c386..ff9da42 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -44,6 +44,7 @@
 #include <mutex>
 #include <optional>
 #include <queue>
+#include <string_view>
 #include <thread>
 #include <vector>
 
@@ -93,6 +94,12 @@
 
 namespace android {
 namespace init {
+constexpr auto FINGERPRINT_PROP = "ro.build.fingerprint";
+constexpr auto LEGACY_FINGERPRINT_PROP = "ro.build.legacy.fingerprint";
+constexpr auto ID_PROP = "ro.build.id";
+constexpr auto LEGACY_ID_PROP = "ro.build.legacy.id";
+constexpr auto VBMETA_DIGEST_PROP = "ro.boot.vbmeta.digest";
+constexpr auto DIGEST_SIZE_USED = 8;
 
 static bool persistent_properties_loaded = false;
 
@@ -856,15 +863,33 @@
     }
 }
 
-// If the ro.build.fingerprint property has not been set, derive it from constituent pieces
-static void property_derive_build_fingerprint() {
-    std::string build_fingerprint = GetProperty("ro.build.fingerprint", "");
-    if (!build_fingerprint.empty()) {
+static void property_initialize_build_id() {
+    std::string build_id = GetProperty(ID_PROP, "");
+    if (!build_id.empty()) {
         return;
     }
 
+    std::string legacy_build_id = GetProperty(LEGACY_ID_PROP, "");
+    std::string vbmeta_digest = GetProperty(VBMETA_DIGEST_PROP, "");
+    if (vbmeta_digest.size() < DIGEST_SIZE_USED) {
+        LOG(ERROR) << "vbmeta digest size too small " << vbmeta_digest;
+        // Still try to set the id field in the unexpected case.
+        build_id = legacy_build_id;
+    } else {
+        // Derive the ro.build.id by appending the vbmeta digest to the base value.
+        build_id = legacy_build_id + "." + vbmeta_digest.substr(0, DIGEST_SIZE_USED);
+    }
+
+    std::string error;
+    auto res = PropertySet(ID_PROP, build_id, &error);
+    if (res != PROP_SUCCESS) {
+        LOG(ERROR) << "Failed to set " << ID_PROP << " to " << build_id;
+    }
+}
+
+static std::string ConstructBuildFingerprint(bool legacy) {
     const std::string UNKNOWN = "unknown";
-    build_fingerprint = GetProperty("ro.product.brand", UNKNOWN);
+    std::string build_fingerprint = GetProperty("ro.product.brand", UNKNOWN);
     build_fingerprint += '/';
     build_fingerprint += GetProperty("ro.product.name", UNKNOWN);
     build_fingerprint += '/';
@@ -872,7 +897,10 @@
     build_fingerprint += ':';
     build_fingerprint += GetProperty("ro.build.version.release_or_codename", UNKNOWN);
     build_fingerprint += '/';
-    build_fingerprint += GetProperty("ro.build.id", UNKNOWN);
+
+    std::string build_id =
+            legacy ? GetProperty(LEGACY_ID_PROP, UNKNOWN) : GetProperty(ID_PROP, UNKNOWN);
+    build_fingerprint += build_id;
     build_fingerprint += '/';
     build_fingerprint += GetProperty("ro.build.version.incremental", UNKNOWN);
     build_fingerprint += ':';
@@ -880,13 +908,112 @@
     build_fingerprint += '/';
     build_fingerprint += GetProperty("ro.build.tags", UNKNOWN);
 
-    LOG(INFO) << "Setting property 'ro.build.fingerprint' to '" << build_fingerprint << "'";
+    return build_fingerprint;
+}
+
+// Derive the legacy build fingerprint if we overwrite the build id at runtime.
+static void property_derive_legacy_build_fingerprint() {
+    std::string legacy_build_fingerprint = GetProperty(LEGACY_FINGERPRINT_PROP, "");
+    if (!legacy_build_fingerprint.empty()) {
+        return;
+    }
+
+    // The device doesn't have a legacy build id, skipping the legacy fingerprint.
+    std::string legacy_build_id = GetProperty(LEGACY_ID_PROP, "");
+    if (legacy_build_id.empty()) {
+        return;
+    }
+
+    legacy_build_fingerprint = ConstructBuildFingerprint(true /* legacy fingerprint */);
+    LOG(INFO) << "Setting property '" << LEGACY_FINGERPRINT_PROP << "' to '"
+              << legacy_build_fingerprint << "'";
 
     std::string error;
-    uint32_t res = PropertySet("ro.build.fingerprint", build_fingerprint, &error);
+    uint32_t res = PropertySet(LEGACY_FINGERPRINT_PROP, legacy_build_fingerprint, &error);
     if (res != PROP_SUCCESS) {
-        LOG(ERROR) << "Error setting property 'ro.build.fingerprint': err=" << res << " (" << error
-                   << ")";
+        LOG(ERROR) << "Error setting property '" << LEGACY_FINGERPRINT_PROP << "': err=" << res
+                   << " (" << error << ")";
+    }
+}
+
+// If the ro.build.fingerprint property has not been set, derive it from constituent pieces
+static void property_derive_build_fingerprint() {
+    std::string build_fingerprint = GetProperty("ro.build.fingerprint", "");
+    if (!build_fingerprint.empty()) {
+        return;
+    }
+
+    build_fingerprint = ConstructBuildFingerprint(false /* legacy fingerprint */);
+    LOG(INFO) << "Setting property '" << FINGERPRINT_PROP << "' to '" << build_fingerprint << "'";
+
+    std::string error;
+    uint32_t res = PropertySet(FINGERPRINT_PROP, build_fingerprint, &error);
+    if (res != PROP_SUCCESS) {
+        LOG(ERROR) << "Error setting property '" << FINGERPRINT_PROP << "': err=" << res << " ("
+                   << error << ")";
+    }
+}
+
+// If the ro.product.cpu.abilist* properties have not been explicitly
+// set, derive them from ro.${partition}.product.cpu.abilist* properties.
+static void property_initialize_ro_cpu_abilist() {
+    // From high to low priority.
+    const char* kAbilistSources[] = {
+            "product",
+            "odm",
+            "vendor",
+            "system",
+    };
+    const std::string EMPTY = "";
+    const char* kAbilistProp = "ro.product.cpu.abilist";
+    const char* kAbilist32Prop = "ro.product.cpu.abilist32";
+    const char* kAbilist64Prop = "ro.product.cpu.abilist64";
+
+    // If the properties are defined explicitly, just use them.
+    if (GetProperty(kAbilistProp, EMPTY) != EMPTY) {
+        return;
+    }
+
+    // Find the first source defining these properties by order.
+    std::string abilist32_prop_val;
+    std::string abilist64_prop_val;
+    for (const auto& source : kAbilistSources) {
+        const auto abilist32_prop = std::string("ro.") + source + ".product.cpu.abilist32";
+        const auto abilist64_prop = std::string("ro.") + source + ".product.cpu.abilist64";
+        abilist32_prop_val = GetProperty(abilist32_prop, EMPTY);
+        abilist64_prop_val = GetProperty(abilist64_prop, EMPTY);
+        // The properties could be empty on 32-bit-only or 64-bit-only devices,
+        // but we cannot identify a property is empty or undefined by GetProperty().
+        // So, we assume both of these 2 properties are empty as undefined.
+        if (abilist32_prop_val != EMPTY || abilist64_prop_val != EMPTY) {
+            break;
+        }
+    }
+
+    // Merge ABI lists for ro.product.cpu.abilist
+    auto abilist_prop_val = abilist64_prop_val;
+    if (abilist32_prop_val != EMPTY) {
+        if (abilist_prop_val != EMPTY) {
+            abilist_prop_val += ",";
+        }
+        abilist_prop_val += abilist32_prop_val;
+    }
+
+    // Set these properties
+    const std::pair<const char*, const std::string&> set_prop_list[] = {
+            {kAbilistProp, abilist_prop_val},
+            {kAbilist32Prop, abilist32_prop_val},
+            {kAbilist64Prop, abilist64_prop_val},
+    };
+    for (const auto& [prop, prop_val] : set_prop_list) {
+        LOG(INFO) << "Setting property '" << prop << "' to '" << prop_val << "'";
+
+        std::string error;
+        uint32_t res = PropertySet(prop, prop_val, &error);
+        if (res != PROP_SUCCESS) {
+            LOG(ERROR) << "Error setting property '" << prop << "': err=" << res << " (" << error
+                       << ")";
+        }
     }
 }
 
@@ -957,8 +1084,6 @@
     load_properties_from_partition("odm", /* support_legacy_path_until */ 28);
     load_properties_from_partition("product", /* support_legacy_path_until */ 30);
 
-    load_properties_from_file("/factory/factory.prop", "ro.*", &properties);
-
     if (access(kDebugRamdiskProp, R_OK) == 0) {
         LOG(INFO) << "Loading " << kDebugRamdiskProp;
         load_properties_from_file(kDebugRamdiskProp, nullptr, &properties);
@@ -973,7 +1098,10 @@
     }
 
     property_initialize_ro_product_props();
+    property_initialize_build_id();
     property_derive_build_fingerprint();
+    property_derive_legacy_build_fingerprint();
+    property_initialize_ro_cpu_abilist();
 
     update_sys_usb_config();
 }
@@ -1100,22 +1228,33 @@
     }
 }
 
+constexpr auto ANDROIDBOOT_PREFIX = "androidboot."sv;
+
 static void ProcessKernelCmdline() {
-    bool for_emulator = false;
     ImportKernelCmdline([&](const std::string& key, const std::string& value) {
-        if (key == "qemu") {
-            for_emulator = true;
-        } else if (StartsWith(key, "androidboot.")) {
-            InitPropertySet("ro.boot." + key.substr(12), value);
+        if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
+            InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
         }
     });
+}
 
-    if (for_emulator) {
-        ImportKernelCmdline([&](const std::string& key, const std::string& value) {
-            // In the emulator, export any kernel option with the "ro.kernel." prefix.
-            InitPropertySet("ro.kernel." + key, value);
-        });
-    }
+// bootconfig does not allow to populate `key=value` simultaneously with
+// `key.subkey=value` which does not work with the existing code for
+// `hardware` (e.g. we want both `ro.boot.hardware=value` and
+// `ro.boot.hardware.sku=value`) and for `qemu` (Android Stidio Emulator
+// specific).
+static bool IsAllowedBootconfigKey(const std::string_view key) {
+    return (key == "hardware"sv) || (key == "qemu"sv);
+}
+
+static void ProcessBootconfig() {
+    ImportBootconfig([&](const std::string& key, const std::string& value) {
+        if (StartsWith(key, ANDROIDBOOT_PREFIX)) {
+            InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value);
+        } else if (IsAllowedBootconfigKey(key)) {
+            InitPropertySet("ro.boot." + key, value);
+        }
+    });
 }
 
 void PropertyInit() {
@@ -1136,6 +1275,7 @@
     // properties set in DT always have priority over the command-line ones.
     ProcessKernelDt();
     ProcessKernelCmdline();
+    ProcessBootconfig();
 
     // Propagate the kernel variables to internal variables
     // used by init as well as the current required properties.
diff --git a/init/property_service_test.cpp b/init/property_service_test.cpp
index c6dcfa2..ac6b7b2 100644
--- a/init/property_service_test.cpp
+++ b/init/property_service_test.cpp
@@ -23,6 +23,7 @@
 
 #include <android-base/properties.h>
 #include <android-base/scopeguard.h>
+#include <android-base/strings.h>
 #include <gtest/gtest.h>
 
 using android::base::GetProperty;
@@ -90,5 +91,39 @@
     EXPECT_FALSE(SetProperty("sys.powerctl", "reboot,userspace"));
 }
 
+TEST(property_service, check_fingerprint_with_legacy_build_id) {
+    std::string legacy_build_id = GetProperty("ro.build.legacy.id", "");
+    if (legacy_build_id.empty()) {
+        GTEST_SKIP() << "Skipping test, legacy build id isn't set.";
+    }
+
+    std::string vbmeta_digest = GetProperty("ro.boot.vbmeta.digest", "");
+    ASSERT_GE(vbmeta_digest.size(), 8u);
+    std::string build_id = GetProperty("ro.boot.build.id", "");
+    // Check that the build id is constructed with the prefix of vbmeta digest
+    std::string expected_build_id = legacy_build_id + "." + vbmeta_digest.substr(0, 8);
+    ASSERT_EQ(expected_build_id, build_id);
+    // Check that the fingerprint is constructed with the expected format.
+    std::string fingerprint = GetProperty("ro.build.fingerprint", "");
+    std::vector<std::string> fingerprint_fields = {
+            GetProperty("ro.product.brand", ""),
+            "/",
+            GetProperty("ro.product.name", ""),
+            "/",
+            GetProperty("ro.product.device", ""),
+            ":",
+            GetProperty("ro.build.version.release_or_codename", ""),
+            "/",
+            expected_build_id,
+            "/",
+            GetProperty("ro.build.version.incremental", ""),
+            ":",
+            GetProperty("ro.build.type", ""),
+            "/",
+            GetProperty("ro.build.tags", "")};
+
+    ASSERT_EQ(android::base::Join(fingerprint_fields, ""), fingerprint);
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 49baf9e..ab0e48e 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -85,12 +85,11 @@
 
 static const std::set<std::string> kDebuggingServices{"tombstoned", "logd", "adbd", "console"};
 
-static std::vector<Service*> GetDebuggingServices(bool only_post_data) {
-    std::vector<Service*> ret;
-    ret.reserve(kDebuggingServices.size());
+static std::set<std::string> GetPostDataDebuggingServices() {
+    std::set<std::string> ret;
     for (const auto& s : ServiceList::GetInstance()) {
-        if (kDebuggingServices.count(s->name()) && (!only_post_data || s->is_post_data())) {
-            ret.push_back(s.get());
+        if (kDebuggingServices.count(s->name()) && s->is_post_data()) {
+            ret.insert(s->name());
         }
     }
     return ret;
@@ -451,10 +450,22 @@
 
 // zram is able to use backing device on top of a loopback device.
 // In order to unmount /data successfully, we have to kill the loopback device first
-#define ZRAM_DEVICE   "/dev/block/zram0"
-#define ZRAM_RESET    "/sys/block/zram0/reset"
-#define ZRAM_BACK_DEV "/sys/block/zram0/backing_dev"
+#define ZRAM_DEVICE       "/dev/block/zram0"
+#define ZRAM_RESET        "/sys/block/zram0/reset"
+#define ZRAM_BACK_DEV     "/sys/block/zram0/backing_dev"
+#define ZRAM_INITSTATE    "/sys/block/zram0/initstate"
 static Result<void> KillZramBackingDevice() {
+    std::string zram_initstate;
+    if (!android::base::ReadFileToString(ZRAM_INITSTATE, &zram_initstate)) {
+        return ErrnoError() << "Failed to read " << ZRAM_INITSTATE;
+    }
+
+    zram_initstate.erase(zram_initstate.length() - 1);
+    if (zram_initstate == "0") {
+        LOG(INFO) << "Zram has not been swapped on";
+        return {};
+    }
+
     if (access(ZRAM_BACK_DEV, F_OK) != 0 && errno == ENOENT) {
         LOG(INFO) << "No zram backing device configured";
         return {};
@@ -503,13 +514,18 @@
 
 // Stops given services, waits for them to be stopped for |timeout| ms.
 // If terminate is true, then SIGTERM is sent to services, otherwise SIGKILL is sent.
-static void StopServices(const std::vector<Service*>& services, std::chrono::milliseconds timeout,
+// Note that services are stopped in order given by |ServiceList::services_in_shutdown_order|
+// function.
+static void StopServices(const std::set<std::string>& services, std::chrono::milliseconds timeout,
                          bool terminate) {
     LOG(INFO) << "Stopping " << services.size() << " services by sending "
               << (terminate ? "SIGTERM" : "SIGKILL");
     std::vector<pid_t> pids;
     pids.reserve(services.size());
-    for (const auto& s : services) {
+    for (const auto& s : ServiceList::GetInstance().services_in_shutdown_order()) {
+        if (services.count(s->name()) == 0) {
+            continue;
+        }
         if (s->pid() > 0) {
             pids.push_back(s->pid());
         }
@@ -529,12 +545,12 @@
 
 // Like StopServices, but also logs all the services that failed to stop after the provided timeout.
 // Returns number of violators.
-static int StopServicesAndLogViolations(const std::vector<Service*>& services,
+static int StopServicesAndLogViolations(const std::set<std::string>& services,
                                         std::chrono::milliseconds timeout, bool terminate) {
     StopServices(services, timeout, terminate);
     int still_running = 0;
-    for (const auto& s : services) {
-        if (s->IsRunning()) {
+    for (const auto& s : ServiceList::GetInstance()) {
+        if (s->IsRunning() && services.count(s->name())) {
             LOG(ERROR) << "[service-misbehaving] : service '" << s->name() << "' is still running "
                        << timeout.count() << "ms after receiving "
                        << (terminate ? "SIGTERM" : "SIGKILL");
@@ -620,8 +636,7 @@
 
     // watchdogd is a vendor specific component but should be alive to complete shutdown safely.
     const std::set<std::string> to_starts{"watchdogd"};
-    std::vector<Service*> stop_first;
-    stop_first.reserve(ServiceList::GetInstance().services().size());
+    std::set<std::string> stop_first;
     for (const auto& s : ServiceList::GetInstance()) {
         if (kDebuggingServices.count(s->name())) {
             // keep debugging tools until non critical ones are all gone.
@@ -639,7 +654,7 @@
                            << "': " << result.error();
             }
         } else {
-            stop_first.push_back(s.get());
+            stop_first.insert(s->name());
         }
     }
 
@@ -655,6 +670,7 @@
 
         if (do_shutdown_animation) {
             SetProperty("service.bootanim.exit", "0");
+            SetProperty("service.bootanim.progress", "0");
             // Could be in the middle of animation. Stop and start so that it can pick
             // up the right mode.
             boot_anim->Stop();
@@ -702,7 +718,7 @@
         LOG(INFO) << "vold not running, skipping vold shutdown";
     }
     // logcat stopped here
-    StopServices(GetDebuggingServices(false /* only_post_data */), 0ms, false /* SIGKILL */);
+    StopServices(kDebuggingServices, 0ms, false /* SIGKILL */);
     // 4. sync, try umount, and optionally run fsck for user shutdown
     {
         Timer sync_timer;
@@ -784,17 +800,17 @@
         sub_reason = "resetprop";
         return Error() << "Failed to reset sys.powerctl property";
     }
-    std::vector<Service*> stop_first;
+    std::set<std::string> stop_first;
     // Remember the services that were enabled. We will need to manually enable them again otherwise
     // triggers like class_start won't restart them.
-    std::vector<Service*> were_enabled;
-    stop_first.reserve(ServiceList::GetInstance().services().size());
+    std::set<std::string> were_enabled;
     for (const auto& s : ServiceList::GetInstance().services_in_shutdown_order()) {
         if (s->is_post_data() && !kDebuggingServices.count(s->name())) {
-            stop_first.push_back(s);
+            stop_first.insert(s->name());
         }
+        // TODO(ioffe): we should also filter out temporary services here.
         if (s->is_post_data() && s->IsEnabled()) {
-            were_enabled.push_back(s);
+            were_enabled.insert(s->name());
         }
     }
     {
@@ -814,8 +830,8 @@
         r > 0) {
         auto fd = unique_fd(TEMP_FAILURE_RETRY(open(services_file_name.c_str(), flags, 0666)));
         android::base::WriteStringToFd("Post-data services still running: \n", fd);
-        for (const auto& s : stop_first) {
-            if (s->IsRunning()) {
+        for (const auto& s : ServiceList::GetInstance()) {
+            if (s->IsRunning() && stop_first.count(s->name())) {
                 android::base::WriteStringToFd(s->name() + "\n", fd);
             }
         }
@@ -830,13 +846,14 @@
         sub_reason = "vold_reset";
         return result;
     }
-    if (int r = StopServicesAndLogViolations(GetDebuggingServices(true /* only_post_data */),
-                                             sigkill_timeout, false /* SIGKILL */);
+    const auto& debugging_services = GetPostDataDebuggingServices();
+    if (int r = StopServicesAndLogViolations(debugging_services, sigkill_timeout,
+                                             false /* SIGKILL */);
         r > 0) {
         auto fd = unique_fd(TEMP_FAILURE_RETRY(open(services_file_name.c_str(), flags, 0666)));
         android::base::WriteStringToFd("Debugging services still running: \n", fd);
-        for (const auto& s : GetDebuggingServices(true)) {
-            if (s->IsRunning()) {
+        for (const auto& s : ServiceList::GetInstance()) {
+            if (s->IsRunning() && debugging_services.count(s->name())) {
                 android::base::WriteStringToFd(s->name() + "\n", fd);
             }
         }
@@ -853,7 +870,7 @@
         sub_reason = "apex";
         return result;
     }
-    if (!SwitchToMountNamespaceIfNeeded(NS_BOOTSTRAP)) {
+    if (!SwitchToMountNamespaceIfNeeded(NS_BOOTSTRAP).ok()) {
         sub_reason = "ns_switch";
         return Error() << "Failed to switch to bootstrap namespace";
     }
@@ -866,9 +883,11 @@
         return false;
     });
     // Re-enable services
-    for (const auto& s : were_enabled) {
-        LOG(INFO) << "Re-enabling service '" << s->name() << "'";
-        s->Enable();
+    for (const auto& s : ServiceList::GetInstance()) {
+        if (were_enabled.count(s->name())) {
+            LOG(INFO) << "Re-enabling service '" << s->name() << "'";
+            s->Enable();
+        }
     }
     ServiceList::GetInstance().ResetState();
     LeaveShutdown();
diff --git a/init/security.cpp b/init/security.cpp
index ac784a3..970696e 100644
--- a/init/security.cpp
+++ b/init/security.cpp
@@ -36,59 +36,6 @@
 namespace android {
 namespace init {
 
-// Writes 512 bytes of output from Hardware RNG (/dev/hw_random, backed
-// by Linux kernel's hw_random framework) into Linux RNG's via /dev/urandom.
-// Does nothing if Hardware RNG is not present.
-//
-// Since we don't yet trust the quality of Hardware RNG, these bytes are not
-// mixed into the primary pool of Linux RNG and the entropy estimate is left
-// unmodified.
-//
-// If the HW RNG device /dev/hw_random is present, we require that at least
-// 512 bytes read from it are written into Linux RNG. QA is expected to catch
-// devices/configurations where these I/O operations are blocking for a long
-// time. We do not reboot or halt on failures, as this is a best-effort
-// attempt.
-Result<void> MixHwrngIntoLinuxRngAction(const BuiltinArguments&) {
-    unique_fd hwrandom_fd(
-        TEMP_FAILURE_RETRY(open("/dev/hw_random", O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
-    if (hwrandom_fd == -1) {
-        if (errno == ENOENT) {
-            LOG(INFO) << "/dev/hw_random not found";
-            // It's not an error to not have a Hardware RNG.
-            return {};
-        }
-        return ErrnoError() << "Failed to open /dev/hw_random";
-    }
-
-    unique_fd urandom_fd(
-        TEMP_FAILURE_RETRY(open("/dev/urandom", O_WRONLY | O_NOFOLLOW | O_CLOEXEC)));
-    if (urandom_fd == -1) {
-        return ErrnoError() << "Failed to open /dev/urandom";
-    }
-
-    char buf[512];
-    size_t total_bytes_written = 0;
-    while (total_bytes_written < sizeof(buf)) {
-        ssize_t chunk_size =
-            TEMP_FAILURE_RETRY(read(hwrandom_fd, buf, sizeof(buf) - total_bytes_written));
-        if (chunk_size == -1) {
-            return ErrnoError() << "Failed to read from /dev/hw_random";
-        } else if (chunk_size == 0) {
-            return Error() << "Failed to read from /dev/hw_random: EOF";
-        }
-
-        chunk_size = TEMP_FAILURE_RETRY(write(urandom_fd, buf, chunk_size));
-        if (chunk_size == -1) {
-            return ErrnoError() << "Failed to write to /dev/urandom";
-        }
-        total_bytes_written += chunk_size;
-    }
-
-    LOG(INFO) << "Mixed " << total_bytes_written << " bytes from /dev/hw_random into /dev/urandom";
-    return {};
-}
-
 static bool SetHighestAvailableOptionValue(const std::string& path, int min, int max) {
     std::ifstream inf(path, std::fstream::in);
     if (!inf) {
diff --git a/init/security.h b/init/security.h
index 43c2739..e8bec6a 100644
--- a/init/security.h
+++ b/init/security.h
@@ -26,7 +26,6 @@
 namespace android {
 namespace init {
 
-Result<void> MixHwrngIntoLinuxRngAction(const BuiltinArguments&);
 Result<void> SetMmapRndBitsAction(const BuiltinArguments&);
 Result<void> SetKptrRestrictAction(const BuiltinArguments&);
 Result<void> TestPerfEventSelinuxAction(const BuiltinArguments&);
diff --git a/init/selinux.cpp b/init/selinux.cpp
index f03ca6b..42d3023 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -45,7 +45,7 @@
 // 2) If these hashes do not match, then either /system or /system_ext or /product (or some of them)
 //    have been updated out of sync with /vendor (or /odm if it is present) and the init needs to
 //    compile the SEPolicy.  /system contains the SEPolicy compiler, secilc, and it is used by the
-//    LoadSplitPolicy() function below to compile the SEPolicy to a temp directory and load it.
+//    OpenSplitPolicy() function below to compile the SEPolicy to a temp directory and load it.
 //    That function contains even more documentation with the specific implementation details of how
 //    the SEPolicy is compiled if needed.
 
@@ -63,6 +63,7 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
+#include <android-base/result.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <fs_avb/fs_avb.h>
@@ -74,6 +75,7 @@
 #include "block_dev_initializer.h"
 #include "debug_ramdisk.h"
 #include "reboot_utils.h"
+#include "snapuserd_transition.h"
 #include "util.h"
 
 using namespace std::string_literals;
@@ -91,7 +93,7 @@
 
 enum EnforcingStatus { SELINUX_PERMISSIVE, SELINUX_ENFORCING };
 
-EnforcingStatus StatusFromCmdline() {
+EnforcingStatus StatusFromProperty() {
     EnforcingStatus status = SELINUX_ENFORCING;
 
     ImportKernelCmdline([&](const std::string& key, const std::string& value) {
@@ -100,12 +102,20 @@
         }
     });
 
+    if (status == SELINUX_ENFORCING) {
+        ImportBootconfig([&](const std::string& key, const std::string& value) {
+            if (key == "androidboot.selinux" && value == "permissive") {
+                status = SELINUX_PERMISSIVE;
+            }
+        });
+    }
+
     return status;
 }
 
 bool IsEnforcing() {
     if (ALLOW_PERMISSIVE_SELINUX) {
-        return StatusFromCmdline() == SELINUX_ENFORCING;
+        return StatusFromProperty() == SELINUX_ENFORCING;
     }
     return true;
 }
@@ -213,8 +223,8 @@
     return true;
 }
 
-bool FindPrecompiledSplitPolicy(std::string* file) {
-    file->clear();
+Result<std::string> FindPrecompiledSplitPolicy() {
+    std::string precompiled_sepolicy;
     // If there is an odm partition, precompiled_sepolicy will be in
     // odm/etc/selinux. Otherwise it will be in vendor/etc/selinux.
     static constexpr const char vendor_precompiled_sepolicy[] =
@@ -222,62 +232,49 @@
     static constexpr const char odm_precompiled_sepolicy[] =
         "/odm/etc/selinux/precompiled_sepolicy";
     if (access(odm_precompiled_sepolicy, R_OK) == 0) {
-        *file = odm_precompiled_sepolicy;
+        precompiled_sepolicy = odm_precompiled_sepolicy;
     } else if (access(vendor_precompiled_sepolicy, R_OK) == 0) {
-        *file = vendor_precompiled_sepolicy;
+        precompiled_sepolicy = vendor_precompiled_sepolicy;
     } else {
-        PLOG(INFO) << "No precompiled sepolicy";
-        return false;
-    }
-    std::string actual_plat_id;
-    if (!ReadFirstLine("/system/etc/selinux/plat_sepolicy_and_mapping.sha256", &actual_plat_id)) {
-        PLOG(INFO) << "Failed to read "
-                      "/system/etc/selinux/plat_sepolicy_and_mapping.sha256";
-        return false;
-    }
-    std::string actual_system_ext_id;
-    if (!ReadFirstLine("/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256",
-                       &actual_system_ext_id)) {
-        PLOG(INFO) << "Failed to read "
-                      "/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256";
-        return false;
-    }
-    std::string actual_product_id;
-    if (!ReadFirstLine("/product/etc/selinux/product_sepolicy_and_mapping.sha256",
-                       &actual_product_id)) {
-        PLOG(INFO) << "Failed to read "
-                      "/product/etc/selinux/product_sepolicy_and_mapping.sha256";
-        return false;
+        return ErrnoError() << "No precompiled sepolicy at " << vendor_precompiled_sepolicy;
     }
 
-    std::string precompiled_plat_id;
-    std::string precompiled_plat_sha256 = *file + ".plat_sepolicy_and_mapping.sha256";
-    if (!ReadFirstLine(precompiled_plat_sha256.c_str(), &precompiled_plat_id)) {
-        PLOG(INFO) << "Failed to read " << precompiled_plat_sha256;
-        file->clear();
-        return false;
+    // Use precompiled sepolicy only when all corresponding hashes are equal.
+    std::vector<std::pair<std::string, std::string>> sepolicy_hashes{
+            {"/system/etc/selinux/plat_sepolicy_and_mapping.sha256",
+             precompiled_sepolicy + ".plat_sepolicy_and_mapping.sha256"},
+            {"/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256",
+             precompiled_sepolicy + ".system_ext_sepolicy_and_mapping.sha256"},
+            {"/product/etc/selinux/product_sepolicy_and_mapping.sha256",
+             precompiled_sepolicy + ".product_sepolicy_and_mapping.sha256"},
+    };
+
+    for (const auto& [actual_id_path, precompiled_id_path] : sepolicy_hashes) {
+        // Both of them should exist or both of them shouldn't exist.
+        if (access(actual_id_path.c_str(), R_OK) != 0) {
+            if (access(precompiled_id_path.c_str(), R_OK) == 0) {
+                return Error() << precompiled_id_path << " exists but " << actual_id_path
+                               << " doesn't";
+            }
+            continue;
+        }
+
+        std::string actual_id;
+        if (!ReadFirstLine(actual_id_path.c_str(), &actual_id)) {
+            return ErrnoError() << "Failed to read " << actual_id_path;
+        }
+
+        std::string precompiled_id;
+        if (!ReadFirstLine(precompiled_id_path.c_str(), &precompiled_id)) {
+            return ErrnoError() << "Failed to read " << precompiled_id_path;
+        }
+
+        if (actual_id.empty() || actual_id != precompiled_id) {
+            return Error() << actual_id_path << " and " << precompiled_id_path << " differ";
+        }
     }
-    std::string precompiled_system_ext_id;
-    std::string precompiled_system_ext_sha256 = *file + ".system_ext_sepolicy_and_mapping.sha256";
-    if (!ReadFirstLine(precompiled_system_ext_sha256.c_str(), &precompiled_system_ext_id)) {
-        PLOG(INFO) << "Failed to read " << precompiled_system_ext_sha256;
-        file->clear();
-        return false;
-    }
-    std::string precompiled_product_id;
-    std::string precompiled_product_sha256 = *file + ".product_sepolicy_and_mapping.sha256";
-    if (!ReadFirstLine(precompiled_product_sha256.c_str(), &precompiled_product_id)) {
-        PLOG(INFO) << "Failed to read " << precompiled_product_sha256;
-        file->clear();
-        return false;
-    }
-    if (actual_plat_id.empty() || actual_plat_id != precompiled_plat_id ||
-        actual_system_ext_id.empty() || actual_system_ext_id != precompiled_system_ext_id ||
-        actual_product_id.empty() || actual_product_id != precompiled_product_id) {
-        file->clear();
-        return false;
-    }
-    return true;
+
+    return precompiled_sepolicy;
 }
 
 bool GetVendorMappingVersion(std::string* plat_vers) {
@@ -298,7 +295,12 @@
     return access(plat_policy_cil_file, R_OK) != -1;
 }
 
-bool LoadSplitPolicy() {
+struct PolicyFile {
+    unique_fd fd;
+    std::string path;
+};
+
+bool OpenSplitPolicy(PolicyFile* policy_file) {
     // IMPLEMENTATION NOTE: Split policy consists of three CIL files:
     // * platform -- policy needed due to logic contained in the system image,
     // * non-platform -- policy needed due to logic contained in the vendor image,
@@ -319,17 +321,18 @@
 
     // Load precompiled policy from vendor image, if a matching policy is found there. The policy
     // must match the platform policy on the system image.
-    std::string precompiled_sepolicy_file;
     // use_userdebug_policy requires compiling sepolicy with userdebug_plat_sepolicy.cil.
     // Thus it cannot use the precompiled policy from vendor image.
-    if (!use_userdebug_policy && FindPrecompiledSplitPolicy(&precompiled_sepolicy_file)) {
-        unique_fd fd(open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
-        if (fd != -1) {
-            if (selinux_android_load_policy_from_fd(fd, precompiled_sepolicy_file.c_str()) < 0) {
-                LOG(ERROR) << "Failed to load SELinux policy from " << precompiled_sepolicy_file;
-                return false;
+    if (!use_userdebug_policy) {
+        if (auto res = FindPrecompiledSplitPolicy(); res.ok()) {
+            unique_fd fd(open(res->c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
+            if (fd != -1) {
+                policy_file->fd = std::move(fd);
+                policy_file->path = std::move(*res);
+                return true;
             }
-            return true;
+        } else {
+            LOG(INFO) << res.error();
         }
     }
     // No suitable precompiled policy could be loaded
@@ -369,6 +372,12 @@
         system_ext_mapping_file.clear();
     }
 
+    std::string system_ext_compat_cil_file("/system_ext/etc/selinux/mapping/" + vend_plat_vers +
+                                           ".compat.cil");
+    if (access(system_ext_compat_cil_file.c_str(), F_OK) == -1) {
+        system_ext_compat_cil_file.clear();
+    }
+
     std::string product_policy_cil_file("/product/etc/selinux/product_sepolicy.cil");
     if (access(product_policy_cil_file.c_str(), F_OK) == -1) {
         product_policy_cil_file.clear();
@@ -423,6 +432,9 @@
     if (!system_ext_mapping_file.empty()) {
         compile_args.push_back(system_ext_mapping_file.c_str());
     }
+    if (!system_ext_compat_cil_file.empty()) {
+        compile_args.push_back(system_ext_compat_cil_file.c_str());
+    }
     if (!product_policy_cil_file.empty()) {
         compile_args.push_back(product_policy_cil_file.c_str());
     }
@@ -446,34 +458,39 @@
     }
     unlink(compiled_sepolicy);
 
-    LOG(INFO) << "Loading compiled SELinux policy";
-    if (selinux_android_load_policy_from_fd(compiled_sepolicy_fd, compiled_sepolicy) < 0) {
-        LOG(ERROR) << "Failed to load SELinux policy from " << compiled_sepolicy;
-        return false;
-    }
-
+    policy_file->fd = std::move(compiled_sepolicy_fd);
+    policy_file->path = compiled_sepolicy;
     return true;
 }
 
-bool LoadMonolithicPolicy() {
-    LOG(VERBOSE) << "Loading SELinux policy from monolithic file";
-    if (selinux_android_load_policy() < 0) {
-        PLOG(ERROR) << "Failed to load monolithic SELinux policy";
+bool OpenMonolithicPolicy(PolicyFile* policy_file) {
+    static constexpr char kSepolicyFile[] = "/sepolicy";
+
+    LOG(VERBOSE) << "Opening SELinux policy from monolithic file";
+    policy_file->fd.reset(open(kSepolicyFile, O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
+    if (policy_file->fd < 0) {
+        PLOG(ERROR) << "Failed to open monolithic SELinux policy";
         return false;
     }
+    policy_file->path = kSepolicyFile;
     return true;
 }
 
-bool LoadPolicy() {
-    return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy();
-}
+void ReadPolicy(std::string* policy) {
+    PolicyFile policy_file;
 
-void SelinuxInitialize() {
-    LOG(INFO) << "Loading SELinux policy";
-    if (!LoadPolicy()) {
-        LOG(FATAL) << "Unable to load SELinux policy";
+    bool ok = IsSplitPolicyDevice() ? OpenSplitPolicy(&policy_file)
+                                    : OpenMonolithicPolicy(&policy_file);
+    if (!ok) {
+        LOG(FATAL) << "Unable to open SELinux policy";
     }
 
+    if (!android::base::ReadFdToString(policy_file.fd, policy)) {
+        PLOG(FATAL) << "Failed to read policy file: " << policy_file.path;
+    }
+}
+
+void SelinuxSetEnforcement() {
     bool kernel_enforcing = (security_getenforce() == 1);
     bool is_enforcing = IsEnforcing();
     if (kernel_enforcing != is_enforcing) {
@@ -650,7 +667,7 @@
         extra_fstab.emplace_back(std::move(entry));
     }
 
-    SkipMountingPartitions(&extra_fstab);
+    SkipMountingPartitions(&extra_fstab, true /* verbose */);
     if (extra_fstab.empty()) {
         return;
     }
@@ -670,6 +687,30 @@
     }
 }
 
+static void LoadSelinuxPolicy(std::string& policy) {
+    LOG(INFO) << "Loading SELinux policy";
+
+    set_selinuxmnt("/sys/fs/selinux");
+    if (security_load_policy(policy.data(), policy.size()) < 0) {
+        PLOG(FATAL) << "SELinux:  Could not load policy";
+    }
+}
+
+// The SELinux setup process is carefully orchestrated around snapuserd. Policy
+// must be loaded off dynamic partitions, and during an OTA, those partitions
+// cannot be read without snapuserd. But, with kernel-privileged snapuserd
+// running, loading the policy will immediately trigger audits.
+//
+// We use a five-step process to address this:
+//  (1) Read the policy into a string, with snapuserd running.
+//  (2) Rewrite the snapshot device-mapper tables, to generate new dm-user
+//      devices and to flush I/O.
+//  (3) Kill snapuserd, which no longer has any dm-user devices to attach to.
+//  (4) Load the sepolicy and issue critical restorecons in /dev, carefully
+//      avoiding anything that would read from /system.
+//  (5) Re-launch snapuserd and attach it to the dm-user devices from step (2).
+//
+// After this sequence, it is safe to enable enforcing mode and continue booting.
 int SetupSelinux(char** argv) {
     SetStdioToDevNull(argv);
     InitKernelLogging(argv);
@@ -682,9 +723,31 @@
 
     MountMissingSystemPartitions();
 
-    // Set up SELinux, loading the SELinux policy.
     SelinuxSetupKernelLogging();
-    SelinuxInitialize();
+
+    LOG(INFO) << "Opening SELinux policy";
+
+    // Read the policy before potentially killing snapuserd.
+    std::string policy;
+    ReadPolicy(&policy);
+
+    auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();
+    if (snapuserd_helper) {
+        // Kill the old snapused to avoid audit messages. After this we cannot
+        // read from /system (or other dynamic partitions) until we call
+        // FinishTransition().
+        snapuserd_helper->StartTransition();
+    }
+
+    LoadSelinuxPolicy(policy);
+
+    if (snapuserd_helper) {
+        // Before enforcing, finish the pending snapuserd transition.
+        snapuserd_helper->FinishTransition();
+        snapuserd_helper = nullptr;
+    }
+
+    SelinuxSetEnforcement();
 
     // We're in the kernel domain and want to transition to the init domain.  File systems that
     // store SELabels in their xattrs, such as ext4 do not need an explicit restorecon here,
diff --git a/init/service.cpp b/init/service.cpp
index 7b98392..5af81bf 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -52,6 +52,7 @@
 #endif
 
 using android::base::boot_clock;
+using android::base::GetBoolProperty;
 using android::base::GetProperty;
 using android::base::Join;
 using android::base::make_scope_guard;
@@ -72,12 +73,12 @@
     if (getcon(&raw_con) == -1) {
         return Error() << "Could not get security context";
     }
-    std::unique_ptr<char> mycon(raw_con);
+    std::unique_ptr<char, decltype(&freecon)> mycon(raw_con, freecon);
 
     if (getfilecon(service_path.c_str(), &raw_filecon) == -1) {
         return Error() << "Could not get file context";
     }
-    std::unique_ptr<char> filecon(raw_filecon);
+    std::unique_ptr<char, decltype(&freecon)> filecon(raw_filecon, freecon);
 
     char* new_con = nullptr;
     int rc = security_compute_create(mycon.get(), filecon.get(),
@@ -124,12 +125,6 @@
     return execv(c_strings[0], c_strings.data()) == 0;
 }
 
-static bool AreRuntimeApexesReady() {
-    struct stat buf;
-    return stat("/apex/com.android.art/", &buf) == 0 &&
-           stat("/apex/com.android.runtime/", &buf) == 0;
-}
-
 unsigned long Service::next_start_order_ = 1;
 bool Service::is_exec_service_running_ = false;
 
@@ -154,6 +149,7 @@
                  .priority = 0},
       namespaces_{.flags = namespace_flags},
       seclabel_(seclabel),
+      subcontext_(subcontext_for_restart_commands),
       onrestart_(false, subcontext_for_restart_commands, "<Service '" + name + "' onrestart>", 0,
                  "onrestart", {}),
       oom_score_adjust_(DEFAULT_OOM_SCORE_ADJUST),
@@ -198,6 +194,8 @@
                   << ") process group...";
         int max_processes = 0;
         int r;
+
+        flags_ |= SVC_STOPPING;
         if (signal == SIGTERM) {
             r = killProcessGroupOnce(proc_attr_.uid, pid_, signal, &max_processes);
         } else {
@@ -281,7 +279,8 @@
         f(siginfo);
     }
 
-    if ((siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) && on_failure_reboot_target_) {
+    if ((siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) && on_failure_reboot_target_ &&
+        !(flags_ & SVC_STOPPING)) {
         LOG(ERROR) << "Service with 'reboot_on_failure' option failed, shutting down system.";
         trigger_shutdown(*on_failure_reboot_target_);
     }
@@ -291,7 +290,7 @@
     if (flags_ & SVC_TEMPORARY) return;
 
     pid_ = 0;
-    flags_ &= (~SVC_RUNNING);
+    flags_ &= ~(SVC_RUNNING | SVC_STOPPING);
     start_order_ = 0;
 
     // Oneshot processes go into the disabled state on exit,
@@ -311,23 +310,25 @@
 #else
     static bool is_apex_updatable = false;
 #endif
-    const bool is_process_updatable = !pre_apexd_ && is_apex_updatable;
+    const bool is_process_updatable = !use_bootstrap_ns_ && is_apex_updatable;
 
     // If we crash > 4 times in 'fatal_crash_window_' minutes or before boot_completed,
     // reboot into bootloader or set crashing property
     boot_clock::time_point now = boot_clock::now();
     if (((flags_ & SVC_CRITICAL) || is_process_updatable) && !(flags_ & SVC_RESTART)) {
-        bool boot_completed = android::base::GetBoolProperty("sys.boot_completed", false);
+        bool boot_completed = GetBoolProperty("sys.boot_completed", false);
         if (now < time_crashed_ + fatal_crash_window_ || !boot_completed) {
             if (++crash_count_ > 4) {
                 auto exit_reason = boot_completed ?
                     "in " + std::to_string(fatal_crash_window_.count()) + " minutes" :
                     "before boot completed";
                 if (flags_ & SVC_CRITICAL) {
-                    // Aborts into `fatal_reboot_target_'.
-                    SetFatalRebootTarget(fatal_reboot_target_);
-                    LOG(FATAL) << "critical process '" << name_ << "' exited 4 times "
-                               << exit_reason;
+                    if (!GetBoolProperty("init.svc_debug.no_fatal." + name_, false)) {
+                        // Aborts into `fatal_reboot_target_'.
+                        SetFatalRebootTarget(fatal_reboot_target_);
+                        LOG(FATAL) << "critical process '" << name_ << "' exited 4 times "
+                                   << exit_reason;
+                    }
                 } else {
                     LOG(ERROR) << "process with updatable components '" << name_
                                << "' exited 4 times " << exit_reason;
@@ -413,7 +414,8 @@
     bool disabled = (flags_ & (SVC_DISABLED | SVC_RESET));
     // Starting a service removes it from the disabled or reset state and
     // immediately takes it out of the restarting state if it was in there.
-    flags_ &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
+    flags_ &= (~(SVC_DISABLED | SVC_RESTARTING | SVC_RESET | SVC_RESTART | SVC_DISABLED_START |
+                 SVC_STOPPING));
 
     // Running processes require no additional work --- if they're in the
     // process of exiting, we've ensured that they will immediately restart
@@ -462,12 +464,16 @@
         scon = *result;
     }
 
-    if (!AreRuntimeApexesReady() && !pre_apexd_) {
-        // If this service is started before the Runtime and ART APEXes get
-        // available, mark it as pre-apexd one. Note that this marking is
+    // APEXd is always started in the "current" namespace because it is the process to set up
+    // the current namespace.
+    const bool is_apexd = args_[0] == "/system/bin/apexd";
+
+    if (!IsDefaultMountNamespaceReady() && !is_apexd) {
+        // If this service is started before APEXes and corresponding linker configuration
+        // get available, mark it as pre-apexd one. Note that this marking is
         // permanent. So for example, if the service is re-launched (e.g., due
         // to crash), it is still recognized as pre-apexd... for consistency.
-        pre_apexd_ = true;
+        use_bootstrap_ns_ = true;
     }
 
     // For pre-apexd services, override mount namespace as "bootstrap" one before starting.
@@ -476,7 +482,7 @@
     std::optional<MountNamespace> override_mount_namespace;
     if (name_ == "ueventd") {
         override_mount_namespace = NS_DEFAULT;
-    } else if (pre_apexd_) {
+    } else if (use_bootstrap_ns_) {
         override_mount_namespace = NS_BOOTSTRAP;
     }
 
diff --git a/init/service.h b/init/service.h
index bc5c90f..89b1f09 100644
--- a/init/service.h
+++ b/init/service.h
@@ -54,6 +54,7 @@
                                      // should not be killed during shutdown
 #define SVC_TEMPORARY 0x1000  // This service was started by 'exec' and should be removed from the
                               // service list once it is reaped.
+#define SVC_STOPPING 0x2000  // service is being stopped by init
 
 #define NR_SVC_SUPP_GIDS 12    // twelve supplementary groups
 
@@ -137,6 +138,7 @@
             flags_ &= ~SVC_ONESHOT;
         }
     }
+    Subcontext* subcontext() const { return subcontext_; }
 
   private:
     void NotifyStateChange(const std::string& new_state) const;
@@ -168,6 +170,7 @@
     std::vector<FileDescriptor> files_;
     std::vector<std::pair<std::string, std::string>> environment_vars_;
 
+    Subcontext* subcontext_;
     Action onrestart_;  // Commands to execute on restart.
 
     std::vector<std::string> writepid_files_;
@@ -205,7 +208,7 @@
 
     std::vector<std::function<void(const siginfo_t& siginfo)>> reap_callbacks_;
 
-    bool pre_apexd_ = false;
+    bool use_bootstrap_ns_ = false;
 
     bool post_data_ = false;
 
diff --git a/init/service_list.h b/init/service_list.h
index 3b9018b..555da25 100644
--- a/init/service_list.h
+++ b/init/service_list.h
@@ -66,7 +66,6 @@
 
     auto begin() const { return services_.begin(); }
     auto end() const { return services_.end(); }
-    const std::vector<std::unique_ptr<Service>>& services() const { return services_; }
     const std::vector<Service*> services_in_shutdown_order() const;
 
     void MarkPostData();
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 97621da..57c311a 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -657,6 +657,14 @@
                            << "' with a config in APEX";
         }
 
+        std::string context = service_->subcontext() ? service_->subcontext()->context() : "";
+        std::string old_context =
+                old_service->subcontext() ? old_service->subcontext()->context() : "";
+        if (context != old_context) {
+            return Error() << "service '" << service_->name() << "' overrides another service "
+                           << "across the treble boundary.";
+        }
+
         service_list_->RemoveService(*old_service);
         old_service = nullptr;
     }
diff --git a/init/service_utils.h b/init/service_utils.h
index e74f8c1..1e0b4bd 100644
--- a/init/service_utils.h
+++ b/init/service_utils.h
@@ -37,6 +37,8 @@
     Descriptor(const std::string& name, android::base::unique_fd fd)
         : name_(name), fd_(std::move(fd)){};
 
+    // Publish() unsets FD_CLOEXEC from the FD and publishes its name via setenv().  It should be
+    // called when starting a service after fork() and before exec().
     void Publish() const;
 
   private:
@@ -53,6 +55,9 @@
     std::string context;
     bool passcred = false;
 
+    // Create() creates the named unix domain socket in /dev/socket and returns a Descriptor object.
+    // It should be called when starting a service, before calling fork(), such that the socket is
+    // synchronously created before starting any other services, which may depend on it.
     Result<Descriptor> Create(const std::string& global_context) const;
 };
 
diff --git a/init/snapuserd_transition.cpp b/init/snapuserd_transition.cpp
new file mode 100644
index 0000000..40467b7
--- /dev/null
+++ b/init/snapuserd_transition.cpp
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "snapuserd_transition.h"
+
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include <filesystem>
+#include <string>
+#include <string_view>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <cutils/sockets.h>
+#include <libsnapshot/snapshot.h>
+#include <libsnapshot/snapuserd_client.h>
+#include <private/android_filesystem_config.h>
+#include <procinfo/process_map.h>
+#include <selinux/android.h>
+
+#include "block_dev_initializer.h"
+#include "service_utils.h"
+#include "util.h"
+
+namespace android {
+namespace init {
+
+using namespace std::string_literals;
+
+using android::base::unique_fd;
+using android::snapshot::SnapshotManager;
+using android::snapshot::SnapuserdClient;
+
+static constexpr char kSnapuserdPath[] = "/system/bin/snapuserd";
+static constexpr char kSnapuserdFirstStagePidVar[] = "FIRST_STAGE_SNAPUSERD_PID";
+static constexpr char kSnapuserdFirstStageFdVar[] = "FIRST_STAGE_SNAPUSERD_FD";
+static constexpr char kSnapuserdLabel[] = "u:object_r:snapuserd_exec:s0";
+static constexpr char kSnapuserdSocketLabel[] = "u:object_r:snapuserd_socket:s0";
+
+void LaunchFirstStageSnapuserd() {
+    SocketDescriptor socket_desc;
+    socket_desc.name = android::snapshot::kSnapuserdSocket;
+    socket_desc.type = SOCK_STREAM;
+    socket_desc.perm = 0660;
+    socket_desc.uid = AID_SYSTEM;
+    socket_desc.gid = AID_SYSTEM;
+
+    // We specify a label here even though it technically is not needed. During
+    // first_stage_mount there is no sepolicy loaded. Once sepolicy is loaded,
+    // we bypass the socket entirely.
+    auto socket = socket_desc.Create(kSnapuserdSocketLabel);
+    if (!socket.ok()) {
+        LOG(FATAL) << "Could not create snapuserd socket: " << socket.error();
+    }
+
+    pid_t pid = fork();
+    if (pid < 0) {
+        PLOG(FATAL) << "Cannot launch snapuserd; fork failed";
+    }
+    if (pid == 0) {
+        socket->Publish();
+        char arg0[] = "/system/bin/snapuserd";
+        char* const argv[] = {arg0, nullptr};
+        if (execv(arg0, argv) < 0) {
+            PLOG(FATAL) << "Cannot launch snapuserd; execv failed";
+        }
+        _exit(127);
+    }
+
+    setenv(kSnapuserdFirstStagePidVar, std::to_string(pid).c_str(), 1);
+
+    LOG(INFO) << "Relaunched snapuserd with pid: " << pid;
+}
+
+std::optional<pid_t> GetSnapuserdFirstStagePid() {
+    const char* pid_str = getenv(kSnapuserdFirstStagePidVar);
+    if (!pid_str) {
+        return {};
+    }
+
+    int pid = 0;
+    if (!android::base::ParseInt(pid_str, &pid)) {
+        LOG(FATAL) << "Could not parse pid in environment, " << kSnapuserdFirstStagePidVar << "="
+                   << pid_str;
+    }
+    return {pid};
+}
+
+static void RelabelLink(const std::string& link) {
+    selinux_android_restorecon(link.c_str(), 0);
+
+    std::string path;
+    if (android::base::Readlink(link, &path)) {
+        selinux_android_restorecon(path.c_str(), 0);
+    }
+}
+
+static void RelabelDeviceMapper() {
+    selinux_android_restorecon("/dev/device-mapper", 0);
+
+    std::error_code ec;
+    for (auto& iter : std::filesystem::directory_iterator("/dev/block", ec)) {
+        const auto& path = iter.path();
+        if (android::base::StartsWith(path.string(), "/dev/block/dm-")) {
+            selinux_android_restorecon(path.string().c_str(), 0);
+        }
+    }
+}
+
+static std::optional<int> GetRamdiskSnapuserdFd() {
+    const char* fd_str = getenv(kSnapuserdFirstStageFdVar);
+    if (!fd_str) {
+        return {};
+    }
+
+    int fd;
+    if (!android::base::ParseInt(fd_str, &fd)) {
+        LOG(FATAL) << "Could not parse fd in environment, " << kSnapuserdFirstStageFdVar << "="
+                   << fd_str;
+    }
+    return {fd};
+}
+
+void RestoreconRamdiskSnapuserd(int fd) {
+    if (fsetxattr(fd, XATTR_NAME_SELINUX, kSnapuserdLabel, strlen(kSnapuserdLabel) + 1, 0) < 0) {
+        PLOG(FATAL) << "fsetxattr snapuserd failed";
+    }
+}
+
+SnapuserdSelinuxHelper::SnapuserdSelinuxHelper(std::unique_ptr<SnapshotManager>&& sm, pid_t old_pid)
+    : sm_(std::move(sm)), old_pid_(old_pid) {
+    // Only dm-user device names change during transitions, so the other
+    // devices are expected to be present.
+    sm_->SetUeventRegenCallback([this](const std::string& device) -> bool {
+        if (android::base::StartsWith(device, "/dev/dm-user/")) {
+            return block_dev_init_.InitDmUser(android::base::Basename(device));
+        }
+        return true;
+    });
+}
+
+static void LockAllSystemPages() {
+    bool ok = true;
+    auto callback = [&](const android::procinfo::MapInfo& map) -> void {
+        if (!ok || android::base::StartsWith(map.name, "/dev/") ||
+            !android::base::StartsWith(map.name, "/")) {
+            return;
+        }
+        auto start = reinterpret_cast<const void*>(map.start);
+        auto len = map.end - map.start;
+        if (!len) {
+            return;
+        }
+        if (mlock(start, len) < 0) {
+            LOG(ERROR) << "mlock failed, " << start << " for " << len << " bytes.";
+            ok = false;
+        }
+    };
+
+    if (!android::procinfo::ReadProcessMaps(getpid(), callback) || !ok) {
+        LOG(FATAL) << "Could not process /proc/" << getpid() << "/maps file for init, "
+                   << "falling back to mlockall().";
+        if (mlockall(MCL_CURRENT) < 0) {
+            LOG(FATAL) << "mlockall failed";
+        }
+    }
+}
+
+void SnapuserdSelinuxHelper::StartTransition() {
+    LOG(INFO) << "Starting SELinux transition of snapuserd";
+
+    // The restorecon path reads from /system etc, so make sure any reads have
+    // been cached before proceeding.
+    auto handle = selinux_android_file_context_handle();
+    if (!handle) {
+        LOG(FATAL) << "Could not create SELinux file context handle";
+    }
+    selinux_android_set_sehandle(handle);
+
+    // We cannot access /system after the transition, so make sure init is
+    // pinned in memory.
+    LockAllSystemPages();
+
+    argv_.emplace_back("snapuserd");
+    argv_.emplace_back("-no_socket");
+    if (!sm_->DetachSnapuserdForSelinux(&argv_)) {
+        LOG(FATAL) << "Could not perform selinux transition";
+    }
+
+    // Make sure the process is gone so we don't have any selinux audits.
+    KillFirstStageSnapuserd(old_pid_);
+}
+
+void SnapuserdSelinuxHelper::FinishTransition() {
+    RelabelLink("/dev/block/by-name/super");
+    RelabelDeviceMapper();
+
+    selinux_android_restorecon("/dev/null", 0);
+    selinux_android_restorecon("/dev/urandom", 0);
+    selinux_android_restorecon("/dev/kmsg", 0);
+    selinux_android_restorecon("/dev/dm-user", SELINUX_ANDROID_RESTORECON_RECURSE);
+
+    RelaunchFirstStageSnapuserd();
+
+    if (munlockall() < 0) {
+        PLOG(ERROR) << "munlockall failed";
+    }
+}
+
+void SnapuserdSelinuxHelper::RelaunchFirstStageSnapuserd() {
+    auto fd = GetRamdiskSnapuserdFd();
+    if (!fd) {
+        LOG(FATAL) << "Environment variable " << kSnapuserdFirstStageFdVar << " was not set!";
+    }
+    unsetenv(kSnapuserdFirstStageFdVar);
+
+    RestoreconRamdiskSnapuserd(fd.value());
+
+    pid_t pid = fork();
+    if (pid < 0) {
+        PLOG(FATAL) << "Fork to relaunch snapuserd failed";
+    }
+    if (pid > 0) {
+        // We don't need the descriptor anymore, and it should be closed to
+        // avoid leaking into subprocesses.
+        close(fd.value());
+
+        setenv(kSnapuserdFirstStagePidVar, std::to_string(pid).c_str(), 1);
+
+        LOG(INFO) << "Relaunched snapuserd with pid: " << pid;
+        return;
+    }
+
+    // Make sure the descriptor is gone after we exec.
+    if (fcntl(fd.value(), F_SETFD, FD_CLOEXEC) < 0) {
+        PLOG(FATAL) << "fcntl FD_CLOEXEC failed for snapuserd fd";
+    }
+
+    std::vector<char*> argv;
+    for (auto& arg : argv_) {
+        argv.emplace_back(arg.data());
+    }
+    argv.emplace_back(nullptr);
+
+    int rv = syscall(SYS_execveat, fd.value(), "", reinterpret_cast<char* const*>(argv.data()),
+                     nullptr, AT_EMPTY_PATH);
+    if (rv < 0) {
+        PLOG(FATAL) << "Failed to execveat() snapuserd";
+    }
+}
+
+std::unique_ptr<SnapuserdSelinuxHelper> SnapuserdSelinuxHelper::CreateIfNeeded() {
+    if (IsRecoveryMode()) {
+        return nullptr;
+    }
+
+    auto old_pid = GetSnapuserdFirstStagePid();
+    if (!old_pid) {
+        return nullptr;
+    }
+
+    auto sm = SnapshotManager::NewForFirstStageMount();
+    if (!sm) {
+        LOG(FATAL) << "Unable to create SnapshotManager";
+    }
+    return std::make_unique<SnapuserdSelinuxHelper>(std::move(sm), old_pid.value());
+}
+
+void KillFirstStageSnapuserd(pid_t pid) {
+    if (kill(pid, SIGTERM) < 0 && errno != ESRCH) {
+        LOG(ERROR) << "Kill snapuserd pid failed: " << pid;
+    } else {
+        LOG(INFO) << "Sent SIGTERM to snapuserd process " << pid;
+    }
+}
+
+void CleanupSnapuserdSocket() {
+    auto socket_path = ANDROID_SOCKET_DIR "/"s + android::snapshot::kSnapuserdSocket;
+    if (access(socket_path.c_str(), F_OK) != 0) {
+        return;
+    }
+
+    // Tell the daemon to stop accepting connections and to gracefully exit
+    // once all outstanding handlers have terminated.
+    if (auto client = SnapuserdClient::Connect(android::snapshot::kSnapuserdSocket, 3s)) {
+        client->DetachSnapuserd();
+    }
+
+    // Unlink the socket so we can create it again in second-stage.
+    if (unlink(socket_path.c_str()) < 0) {
+        PLOG(FATAL) << "unlink " << socket_path << " failed";
+    }
+}
+
+void SaveRamdiskPathToSnapuserd() {
+    int fd = open(kSnapuserdPath, O_PATH);
+    if (fd < 0) {
+        PLOG(FATAL) << "Unable to open snapuserd: " << kSnapuserdPath;
+    }
+
+    auto value = std::to_string(fd);
+    if (setenv(kSnapuserdFirstStageFdVar, value.c_str(), 1) < 0) {
+        PLOG(FATAL) << "setenv failed: " << kSnapuserdFirstStageFdVar << "=" << value;
+    }
+}
+
+bool IsFirstStageSnapuserdRunning() {
+    return GetSnapuserdFirstStagePid().has_value();
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/snapuserd_transition.h b/init/snapuserd_transition.h
new file mode 100644
index 0000000..a5ab652
--- /dev/null
+++ b/init/snapuserd_transition.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <libsnapshot/snapshot.h>
+
+#include "block_dev_initializer.h"
+
+namespace android {
+namespace init {
+
+// Fork and exec a new copy of snapuserd.
+void LaunchFirstStageSnapuserd();
+
+class SnapuserdSelinuxHelper final {
+    using SnapshotManager = android::snapshot::SnapshotManager;
+
+  public:
+    SnapuserdSelinuxHelper(std::unique_ptr<SnapshotManager>&& sm, pid_t old_pid);
+
+    void StartTransition();
+    void FinishTransition();
+
+    // Return a helper for facilitating the selinux transition of snapuserd.
+    // If snapuserd is not in use, null is returned. StartTransition() should
+    // be called after reading policy. FinishTransition() should be called
+    // after loading policy. In between, no reads of /system or other dynamic
+    // partitions are possible.
+    static std::unique_ptr<SnapuserdSelinuxHelper> CreateIfNeeded();
+
+  private:
+    void RelaunchFirstStageSnapuserd();
+    void ExecSnapuserd();
+
+    std::unique_ptr<SnapshotManager> sm_;
+    BlockDevInitializer block_dev_init_;
+    pid_t old_pid_;
+    std::vector<std::string> argv_;
+};
+
+// Remove /dev/socket/snapuserd. This ensures that (1) the existing snapuserd
+// will receive no new requests, and (2) the next copy we transition to can
+// own the socket.
+void CleanupSnapuserdSocket();
+
+// Kill an instance of snapuserd given a pid.
+void KillFirstStageSnapuserd(pid_t pid);
+
+// Save an open fd to /system/bin (in the ramdisk) into an environment. This is
+// used to later execveat() snapuserd.
+void SaveRamdiskPathToSnapuserd();
+
+// Returns true if first-stage snapuserd is running.
+bool IsFirstStageSnapuserdRunning();
+
+// Return the pid of the first-stage instances of snapuserd, if it was started.
+std::optional<pid_t> GetSnapuserdFirstStagePid();
+
+// Save an open fd to /system/bin (in the ramdisk) into an environment. This is
+// used to later execveat() snapuserd.
+void SaveRamdiskPathToSnapuserd();
+
+// Returns true if first-stage snapuserd is running.
+bool IsFirstStageSnapuserdRunning();
+
+}  // namespace init
+}  // namespace android
diff --git a/init/subcontext.cpp b/init/subcontext.cpp
index dc2455e..f1fbffe 100644
--- a/init/subcontext.cpp
+++ b/init/subcontext.cpp
@@ -342,6 +342,9 @@
                 new Subcontext(std::vector<std::string>{"/vendor", "/odm"}, kVendorContext));
     }
 }
+void InitializeHostSubcontext(std::vector<std::string> vendor_prefixes) {
+    subcontext.reset(new Subcontext(vendor_prefixes, kVendorContext, /*host=*/true));
+}
 
 Subcontext* GetSubcontext() {
     return subcontext.get();
diff --git a/init/subcontext.h b/init/subcontext.h
index 788d3be..cb4138e 100644
--- a/init/subcontext.h
+++ b/init/subcontext.h
@@ -36,9 +36,11 @@
 
 class Subcontext {
   public:
-    Subcontext(std::vector<std::string> path_prefixes, std::string context)
+    Subcontext(std::vector<std::string> path_prefixes, std::string context, bool host = false)
         : path_prefixes_(std::move(path_prefixes)), context_(std::move(context)), pid_(0) {
-        Fork();
+        if (!host) {
+            Fork();
+        }
     }
 
     Result<void> Execute(const std::vector<std::string>& args);
@@ -61,6 +63,7 @@
 
 int SubcontextMain(int argc, char** argv, const BuiltinFunctionMap* function_map);
 void InitializeSubcontext();
+void InitializeHostSubcontext(std::vector<std::string> vendor_prefixes);
 Subcontext* GetSubcontext();
 bool SubcontextChildReap(pid_t pid);
 void SubcontextTerminate();
diff --git a/init/subcontext_test.cpp b/init/subcontext_test.cpp
index ee765a7..da1f455 100644
--- a/init/subcontext_test.cpp
+++ b/init/subcontext_test.cpp
@@ -202,6 +202,8 @@
 
     // For RecoverAfterAbort
     auto do_cause_log_fatal = [](const BuiltinArguments& args) -> Result<void> {
+        // Since this is an expected failure, disable debuggerd to not generate a tombstone.
+        signal(SIGABRT, SIG_DFL);
         return Error() << std::string(4097, 'f');
     };
     auto do_generate_sane_error = [](const BuiltinArguments& args) -> Result<void> {
diff --git a/init/sysprop/Android.bp b/init/sysprop/Android.bp
index 7582875..296cdc1 100644
--- a/init/sysprop/Android.bp
+++ b/init/sysprop/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_core_init_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_core_init_license"],
+}
+
 sysprop_library {
   name: "com.android.sysprop.init",
   srcs: ["InitProperties.sysprop"],
diff --git a/init/test_kill_services/Android.bp b/init/test_kill_services/Android.bp
index d59e548..37361a8 100644
--- a/init/test_kill_services/Android.bp
+++ b/init/test_kill_services/Android.bp
@@ -1,3 +1,12 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_core_init_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_core_init_license"],
+}
+
 cc_test {
     name: "init_kill_services_test",
     srcs: ["init_kill_services_test.cpp"],
diff --git a/init/test_service/Android.bp b/init/test_service/Android.bp
index 8bd16a7..37bc371 100644
--- a/init/test_service/Android.bp
+++ b/init/test_service/Android.bp
@@ -14,6 +14,15 @@
 // limitations under the License.
 //
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_core_init_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["system_core_init_license"],
+}
+
 cc_binary {
     name: "test_service",
     srcs: ["test_service.cpp"],
diff --git a/init/test_utils/service_utils.cpp b/init/test_utils/service_utils.cpp
index ae68679..6426ed9 100644
--- a/init/test_utils/service_utils.cpp
+++ b/init/test_utils/service_utils.cpp
@@ -44,7 +44,7 @@
     }
 
     ServiceInterfacesMap result;
-    for (const auto& service : service_list.services()) {
+    for (const auto& service : service_list) {
         // Create an entry for all services, including services that may not
         // have any declared interfaces.
         result[service->name()] = service->interfaces();
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 54659c5..331255b 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -16,6 +16,7 @@
 
 #include "ueventd.h"
 
+#include <android/api-level.h>
 #include <ctype.h>
 #include <dirent.h>
 #include <fcntl.h>
@@ -266,6 +267,17 @@
     LOG(INFO) << "Coldboot took " << cold_boot_timer.duration().count() / 1000.0f << " seconds";
 }
 
+static UeventdConfiguration GetConfiguration() {
+    // TODO: Remove these legacy paths once Android S is no longer supported.
+    if (android::base::GetIntProperty("ro.product.first_api_level", 10000) <= __ANDROID_API_S__) {
+        auto hardware = android::base::GetProperty("ro.hardware", "");
+        return ParseConfig({"/system/etc/ueventd.rc", "/vendor/ueventd.rc", "/odm/ueventd.rc",
+                            "/ueventd." + hardware + ".rc"});
+    }
+
+    return ParseConfig({"/system/etc/ueventd.rc"});
+}
+
 int ueventd_main(int argc, char** argv) {
     /*
      * init sets the umask to 077 for forked processes. We need to
@@ -283,12 +295,7 @@
 
     std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;
 
-    // Keep the current product name base configuration so we remain backwards compatible and
-    // allow it to override everything.
-    auto hardware = android::base::GetProperty("ro.hardware", "");
-
-    auto ueventd_configuration = ParseConfig({"/system/etc/ueventd.rc", "/vendor/ueventd.rc",
-                                              "/odm/ueventd.rc", "/ueventd." + hardware + ".rc"});
+    auto ueventd_configuration = GetConfiguration();
 
     uevent_handlers.emplace_back(std::make_unique<DeviceHandler>(
             std::move(ueventd_configuration.dev_permissions),
diff --git a/init/ueventd_parser.cpp b/init/ueventd_parser.cpp
index 09dce44..9a14406 100644
--- a/init/ueventd_parser.cpp
+++ b/init/ueventd_parser.cpp
@@ -21,6 +21,7 @@
 
 #include <android-base/parseint.h>
 
+#include "import_parser.h"
 #include "keyword_map.h"
 #include "parser.h"
 
@@ -33,12 +34,12 @@
                                   std::vector<SysfsPermissions>* out_sysfs_permissions,
                                   std::vector<Permissions>* out_dev_permissions) {
     bool is_sysfs = out_sysfs_permissions != nullptr;
-    if (is_sysfs && args.size() != 5) {
-        return Error() << "/sys/ lines must have 5 entries";
+    if (is_sysfs && !(args.size() == 5 || args.size() == 6)) {
+        return Error() << "/sys/ lines must have 5 or 6 entries";
     }
 
-    if (!is_sysfs && args.size() != 4) {
-        return Error() << "/dev/ lines must have 4 entries";
+    if (!is_sysfs && !(args.size() == 4 || args.size() == 5)) {
+        return Error() << "/dev/ lines must have 4 or 5 entries";
     }
 
     auto it = args.begin();
@@ -69,10 +70,19 @@
     }
     gid_t gid = grp->gr_gid;
 
+    bool no_fnm_pathname = false;
+    if (it != args.end()) {
+        std::string& flags = *it++;
+        if (flags != "no_fnm_pathname") {
+            return Error() << "invalid option '" << flags << "', only no_fnm_pathname is supported";
+        }
+        no_fnm_pathname = true;
+    }
+
     if (is_sysfs) {
-        out_sysfs_permissions->emplace_back(name, sysfs_attribute, perm, uid, gid);
+        out_sysfs_permissions->emplace_back(name, sysfs_attribute, perm, uid, gid, no_fnm_pathname);
     } else {
-        out_dev_permissions->emplace_back(name, perm, uid, gid);
+        out_dev_permissions->emplace_back(name, perm, uid, gid, no_fnm_pathname);
     }
     return {};
 }
@@ -91,15 +101,15 @@
 Result<void> ParseExternalFirmwareHandlerLine(
         std::vector<std::string>&& args,
         std::vector<ExternalFirmwareHandler>* external_firmware_handlers) {
-    if (args.size() != 4) {
-        return Error() << "external_firmware_handler lines must have exactly 3 parameters";
+    if (args.size() != 4 && args.size() != 5) {
+        return Error() << "external_firmware_handler lines must have 3 or 4 parameters";
     }
 
     if (std::find_if(external_firmware_handlers->begin(), external_firmware_handlers->end(),
-                     [&args](const auto& other) { return other.devpath == args[2]; }) !=
+                     [&args](const auto& other) { return other.devpath == args[1]; }) !=
         external_firmware_handlers->end()) {
         return Error() << "found a previous external_firmware_handler with the same devpath, '"
-                       << args[2] << "'";
+                       << args[1] << "'";
     }
 
     passwd* pwd = getpwnam(args[2].c_str());
@@ -107,7 +117,19 @@
         return ErrnoError() << "invalid handler uid'" << args[2] << "'";
     }
 
-    ExternalFirmwareHandler handler(std::move(args[1]), pwd->pw_uid, std::move(args[3]));
+    gid_t gid = 0;
+    int handler_index = 3;
+    if (args.size() == 5) {
+        struct group* grp = getgrnam(args[3].c_str());
+        if (!grp) {
+            return ErrnoError() << "invalid handler gid '" << args[3] << "'";
+        }
+        gid = grp->gr_gid;
+        handler_index = 4;
+    }
+
+    ExternalFirmwareHandler handler(std::move(args[1]), pwd->pw_uid, gid,
+                                    std::move(args[handler_index]));
     external_firmware_handlers->emplace_back(std::move(handler));
 
     return {};
@@ -224,6 +246,7 @@
     Parser parser;
     UeventdConfiguration ueventd_configuration;
 
+    parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
     parser.AddSectionParser("subsystem",
                             std::make_unique<SubsystemParser>(&ueventd_configuration.subsystems));
 
diff --git a/init/ueventd_parser_test.cpp b/init/ueventd_parser_test.cpp
index 172ba0b..d77cb03 100644
--- a/init/ueventd_parser_test.cpp
+++ b/init/ueventd_parser_test.cpp
@@ -45,6 +45,14 @@
     EXPECT_EQ(expected.attribute_, test.attribute_);
 }
 
+void TestExternalFirmwareHandler(const ExternalFirmwareHandler& expected,
+                                 const ExternalFirmwareHandler& test) {
+    EXPECT_EQ(expected.devpath, test.devpath) << expected.devpath;
+    EXPECT_EQ(expected.uid, test.uid) << expected.uid;
+    EXPECT_EQ(expected.gid, test.gid) << expected.gid;
+    EXPECT_EQ(expected.handler_path, test.handler_path) << expected.handler_path;
+}
+
 template <typename T, typename F>
 void TestVector(const T& expected, const T& test, F function) {
     ASSERT_EQ(expected.size(), test.size());
@@ -67,6 +75,8 @@
     TestVector(expected.sysfs_permissions, result.sysfs_permissions, TestSysfsPermissions);
     TestVector(expected.dev_permissions, result.dev_permissions, TestPermissions);
     EXPECT_EQ(expected.firmware_directories, result.firmware_directories);
+    TestVector(expected.external_firmware_handlers, result.external_firmware_handlers,
+               TestExternalFirmwareHandler);
 }
 
 TEST(ueventd_parser, EmptyFile) {
@@ -104,21 +114,21 @@
 /dev/graphics/*           0660   root       graphics
 /dev/*/test               0660   root       system
 
-/sys/devices/platform/trusty.*      trusty_version        0440  root   log
-/sys/devices/virtual/input/input   enable      0660  root   input
-/sys/devices/virtual/*/input   poll_delay  0660  root   input
+/sys/devices/platform/trusty.*      trusty_version    0440  root   log
+/sys/devices/virtual/input/input    enable            0660  root   input
+/sys/devices/virtual/*/input        poll_delay        0660  root   input    no_fnm_pathname
 )";
 
     auto permissions = std::vector<Permissions>{
-            {"/dev/rtc0", 0640, AID_SYSTEM, AID_SYSTEM},
-            {"/dev/graphics/*", 0660, AID_ROOT, AID_GRAPHICS},
-            {"/dev/*/test", 0660, AID_ROOT, AID_SYSTEM},
+            {"/dev/rtc0", 0640, AID_SYSTEM, AID_SYSTEM, false},
+            {"/dev/graphics/*", 0660, AID_ROOT, AID_GRAPHICS, false},
+            {"/dev/*/test", 0660, AID_ROOT, AID_SYSTEM, false},
     };
 
     auto sysfs_permissions = std::vector<SysfsPermissions>{
-            {"/sys/devices/platform/trusty.*", "trusty_version", 0440, AID_ROOT, AID_LOG},
-            {"/sys/devices/virtual/input/input", "enable", 0660, AID_ROOT, AID_INPUT},
-            {"/sys/devices/virtual/*/input", "poll_delay", 0660, AID_ROOT, AID_INPUT},
+            {"/sys/devices/platform/trusty.*", "trusty_version", 0440, AID_ROOT, AID_LOG, false},
+            {"/sys/devices/virtual/input/input", "enable", 0660, AID_ROOT, AID_INPUT, false},
+            {"/sys/devices/virtual/*/input", "poll_delay", 0660, AID_ROOT, AID_INPUT, true},
     };
 
     TestUeventdFile(ueventd_file, {{}, sysfs_permissions, permissions, {}, {}});
@@ -144,22 +154,60 @@
     auto ueventd_file = R"(
 external_firmware_handler devpath root handler_path
 external_firmware_handler /devices/path/firmware/something001.bin system /vendor/bin/firmware_handler.sh
-external_firmware_handler /devices/path/firmware/something001.bin radio "/vendor/bin/firmware_handler.sh --has --arguments"
+external_firmware_handler /devices/path/firmware/something002.bin radio "/vendor/bin/firmware_handler.sh --has --arguments"
+external_firmware_handler /devices/path/firmware/* root "/vendor/bin/firmware_handler.sh"
+external_firmware_handler /devices/path/firmware/something* system "/vendor/bin/firmware_handler.sh"
+external_firmware_handler /devices/path/*/firmware/something*.bin radio "/vendor/bin/firmware_handler.sh"
+external_firmware_handler /devices/path/firmware/something003.bin system system /vendor/bin/firmware_handler.sh
+external_firmware_handler /devices/path/firmware/something004.bin radio radio "/vendor/bin/firmware_handler.sh --has --arguments"
 )";
 
     auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
             {
                     "devpath",
                     AID_ROOT,
+                    AID_ROOT,
                     "handler_path",
             },
             {
                     "/devices/path/firmware/something001.bin",
                     AID_SYSTEM,
+                    AID_ROOT,
                     "/vendor/bin/firmware_handler.sh",
             },
             {
-                    "/devices/path/firmware/something001.bin",
+                    "/devices/path/firmware/something002.bin",
+                    AID_RADIO,
+                    AID_ROOT,
+                    "/vendor/bin/firmware_handler.sh --has --arguments",
+            },
+            {
+                    "/devices/path/firmware/",
+                    AID_ROOT,
+                    AID_ROOT,
+                    "/vendor/bin/firmware_handler.sh",
+            },
+            {
+                    "/devices/path/firmware/something",
+                    AID_SYSTEM,
+                    AID_ROOT,
+                    "/vendor/bin/firmware_handler.sh",
+            },
+            {
+                    "/devices/path/*/firmware/something*.bin",
+                    AID_RADIO,
+                    AID_ROOT,
+                    "/vendor/bin/firmware_handler.sh",
+            },
+            {
+                    "/devices/path/firmware/something003.bin",
+                    AID_SYSTEM,
+                    AID_SYSTEM,
+                    "/vendor/bin/firmware_handler.sh",
+            },
+            {
+                    "/devices/path/firmware/something004.bin",
+                    AID_RADIO,
                     AID_RADIO,
                     "/vendor/bin/firmware_handler.sh --has --arguments",
             },
@@ -178,6 +226,7 @@
             {
                     "devpath",
                     AID_ROOT,
+                    AID_ROOT,
                     "handler_path",
             },
     };
@@ -240,7 +289,7 @@
     dirname /dev/graphics
 
 /dev/*/test               0660   root       system
-/sys/devices/virtual/*/input   poll_delay  0660  root   input
+/sys/devices/virtual/*/input   poll_delay  0660  root   input    no_fnm_pathname
 firmware_directories /more
 
 external_firmware_handler /devices/path/firmware/firmware001.bin root /vendor/bin/touch.sh
@@ -259,15 +308,15 @@
             {"test_devpath_dirname", Subsystem::DEVNAME_UEVENT_DEVPATH, "/dev/graphics"}};
 
     auto permissions = std::vector<Permissions>{
-            {"/dev/rtc0", 0640, AID_SYSTEM, AID_SYSTEM},
-            {"/dev/graphics/*", 0660, AID_ROOT, AID_GRAPHICS},
-            {"/dev/*/test", 0660, AID_ROOT, AID_SYSTEM},
+            {"/dev/rtc0", 0640, AID_SYSTEM, AID_SYSTEM, false},
+            {"/dev/graphics/*", 0660, AID_ROOT, AID_GRAPHICS, false},
+            {"/dev/*/test", 0660, AID_ROOT, AID_SYSTEM, false},
     };
 
     auto sysfs_permissions = std::vector<SysfsPermissions>{
-            {"/sys/devices/platform/trusty.*", "trusty_version", 0440, AID_ROOT, AID_LOG},
-            {"/sys/devices/virtual/input/input", "enable", 0660, AID_ROOT, AID_INPUT},
-            {"/sys/devices/virtual/*/input", "poll_delay", 0660, AID_ROOT, AID_INPUT},
+            {"/sys/devices/platform/trusty.*", "trusty_version", 0440, AID_ROOT, AID_LOG, false},
+            {"/sys/devices/virtual/input/input", "enable", 0660, AID_ROOT, AID_INPUT, false},
+            {"/sys/devices/virtual/*/input", "poll_delay", 0660, AID_ROOT, AID_INPUT, true},
     };
 
     auto firmware_directories = std::vector<std::string>{
@@ -278,7 +327,7 @@
     };
 
     auto external_firmware_handlers = std::vector<ExternalFirmwareHandler>{
-            {"/devices/path/firmware/firmware001.bin", AID_ROOT, "/vendor/bin/touch.sh"},
+            {"/devices/path/firmware/firmware001.bin", AID_ROOT, AID_ROOT, "/vendor/bin/touch.sh"},
     };
 
     size_t uevent_socket_rcvbuf_size = 6 * 1024 * 1024;
@@ -299,6 +348,7 @@
 /sys/devices/platform/trusty.*      trusty_version        badmode  root   log
 /sys/devices/platform/trusty.*      trusty_version        0440  baduidbad   log
 /sys/devices/platform/trusty.*      trusty_version        0440  root   baduidbad
+/sys/devices/platform/trusty.*      trusty_version        0440  root   root    bad_option
 
 uevent_socket_rcvbuf_size blah
 
diff --git a/init/util.cpp b/init/util.cpp
index aec3173..a40d104 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -30,9 +30,7 @@
 #include <time.h>
 #include <unistd.h>
 
-#include <mutex>
 #include <thread>
-#include <vector>
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -248,6 +246,19 @@
     }
 }
 
+void ImportBootconfig(const std::function<void(const std::string&, const std::string&)>& fn) {
+    std::string bootconfig;
+    android::base::ReadFileToString("/proc/bootconfig", &bootconfig);
+
+    for (const auto& entry : android::base::Split(bootconfig, "\n")) {
+        std::vector<std::string> pieces = android::base::Split(entry, "=");
+        if (pieces.size() == 2) {
+            pieces[1].erase(std::remove(pieces[1].begin(), pieces[1].end(), '"'), pieces[1].end());
+            fn(android::base::Trim(pieces[0]), android::base::Trim(pieces[1]));
+        }
+    }
+}
+
 bool make_dir(const std::string& path, mode_t mode) {
     std::string secontext;
     if (SelabelLookupFileContext(path, mode, &secontext) && !secontext.empty()) {
@@ -365,6 +376,15 @@
             android_dt_dir = value;
         }
     });
+    // ..Or bootconfig
+    if (android_dt_dir == kDefaultAndroidDtDir) {
+        ImportBootconfig([&](const std::string& key, const std::string& value) {
+            if (key == "androidboot.android_dt_dir") {
+                android_dt_dir = value;
+            }
+        });
+    }
+
     LOG(INFO) << "Using Android DT directory " << android_dt_dir;
     return android_dt_dir;
 }
@@ -724,49 +744,15 @@
     return access("/system/bin/recovery", F_OK) == 0;
 }
 
-// TODO(b/155203339): remove this
-// Devices in the lab seem to be stuck during shutdown, but the logs don't capture the last actions
-// before shutdown started, so we record those lines, ignoring requests to shutdown, and replay them
-// if we identify that the device is stuck.
-constexpr size_t kRecordedLogsSize = 30;
-std::string recorded_logs[kRecordedLogsSize];
-size_t recorded_log_position = 0;
-std::mutex recorded_logs_lock;
+// Check if default mount namespace is ready to be used with APEX modules
+static bool is_default_mount_namespace_ready = false;
 
-void InitSecondStageLogging(char** argv) {
-    SetFatalRebootTarget();
-    auto second_stage_logger = [](android::base::LogId log_id, android::base::LogSeverity severity,
-                                  const char* tag, const char* file, unsigned int line,
-                                  const char* message) {
-        // We only store logs for init, not its children, and only if they're not related to
-        // sys.powerctl.
-        if (getpid() == 1 && strstr(message, "sys.powerctl") == nullptr) {
-            auto lock = std::lock_guard{recorded_logs_lock};
-            recorded_logs[recorded_log_position++] = message;
-            if (recorded_log_position == kRecordedLogsSize) {
-                recorded_log_position = 0;
-            }
-        }
-        android::base::KernelLogger(log_id, severity, tag, file, line, message);
-    };
-    android::base::InitLogging(argv, second_stage_logger, InitAborter);
+bool IsDefaultMountNamespaceReady() {
+    return is_default_mount_namespace_ready;
 }
 
-void DumpShutdownDebugInformation() {
-    auto lock = std::lock_guard{recorded_logs_lock};
-    android::base::KernelLogger(
-            android::base::MAIN, android::base::ERROR, "init", nullptr, 0,
-            "===================== Dumping previous init lines =====================");
-    for (size_t i = recorded_log_position; i < kRecordedLogsSize; ++i) {
-        android::base::KernelLogger(android::base::MAIN, android::base::ERROR, "init", nullptr, 0,
-                                    recorded_logs[i].c_str());
-    }
-    for (size_t i = 0; i < recorded_log_position; ++i) {
-        android::base::KernelLogger(android::base::MAIN, android::base::ERROR, "init", nullptr, 0,
-                                    recorded_logs[i].c_str());
-    }
-    android::base::KernelLogger(android::base::MAIN, android::base::ERROR, "init", nullptr, 0,
-                                "===================== End of dump =====================");
+void SetDefaultMountNamespaceReady() {
+    is_default_mount_namespace_ready = true;
 }
 
 }  // namespace init
diff --git a/init/util.h b/init/util.h
index 8a6aa60..daba852 100644
--- a/init/util.h
+++ b/init/util.h
@@ -55,6 +55,7 @@
 bool mkdir_recursive(const std::string& pathname, mode_t mode);
 int wait_for_file(const char *filename, std::chrono::nanoseconds timeout);
 void ImportKernelCmdline(const std::function<void(const std::string&, const std::string&)>&);
+void ImportBootconfig(const std::function<void(const std::string&, const std::string&)>&);
 bool make_dir(const std::string& path, mode_t mode);
 bool is_dir(const char* pathname);
 Result<std::string> ExpandProps(const std::string& src);
@@ -98,8 +99,9 @@
 
 void SetStdioToDevNull(char** argv);
 void InitKernelLogging(char** argv);
-void InitSecondStageLogging(char** argv);
-void DumpShutdownDebugInformation();
 bool IsRecoveryMode();
+
+bool IsDefaultMountNamespaceReady();
+void SetDefaultMountNamespaceReady();
 }  // namespace init
 }  // namespace android
diff --git a/libappfuse/Android.bp b/libappfuse/Android.bp
index ae1481f..a9bd94e 100644
--- a/libappfuse/Android.bp
+++ b/libappfuse/Android.bp
@@ -1,5 +1,9 @@
 // Copyright 2016 The Android Open Source Project
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_defaults {
     name: "libappfuse_defaults",
     local_include_dirs: ["include"],
diff --git a/libappfuse/tests/FuseAppLoopTest.cc b/libappfuse/tests/FuseAppLoopTest.cc
index 98e3665..ea98ae2 100644
--- a/libappfuse/tests/FuseAppLoopTest.cc
+++ b/libappfuse/tests/FuseAppLoopTest.cc
@@ -167,7 +167,7 @@
   EXPECT_EQ(0u, response_.entry_out.attr.gid);
   EXPECT_EQ(0u, response_.entry_out.attr.rdev);
   EXPECT_EQ(0u, response_.entry_out.attr.blksize);
-  EXPECT_EQ(0u, response_.entry_out.attr.padding);
+  EXPECT_EQ(0u, response_.entry_out.attr.flags);
 }
 
 TEST_F(FuseAppLoopTest, LookUp_InvalidName) {
@@ -226,7 +226,7 @@
   EXPECT_EQ(0u, response_.attr_out.attr.gid);
   EXPECT_EQ(0u, response_.attr_out.attr.rdev);
   EXPECT_EQ(0u, response_.attr_out.attr.blksize);
-  EXPECT_EQ(0u, response_.attr_out.attr.padding);
+  EXPECT_EQ(0u, response_.attr_out.attr.flags);
 }
 
 TEST_F(FuseAppLoopTest, GetAttr_Root) {
@@ -259,7 +259,7 @@
   EXPECT_EQ(0u, response_.attr_out.attr.gid);
   EXPECT_EQ(0u, response_.attr_out.attr.rdev);
   EXPECT_EQ(0u, response_.attr_out.attr.blksize);
-  EXPECT_EQ(0u, response_.attr_out.attr.padding);
+  EXPECT_EQ(0u, response_.attr_out.attr.flags);
 }
 
 TEST_F(FuseAppLoopTest, Open) {
diff --git a/libasyncio/Android.bp b/libasyncio/Android.bp
index 44e7933..692e223 100644
--- a/libasyncio/Android.bp
+++ b/libasyncio/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_defaults {
     name: "libasyncio_defaults",
     cflags: [
diff --git a/libbinderwrapper/Android.bp b/libbinderwrapper/Android.bp
index d2487e2..75f43ee 100644
--- a/libbinderwrapper/Android.bp
+++ b/libbinderwrapper/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_defaults {
     name: "libbinderwrapper_defaults",
 
diff --git a/libcrypto_utils/Android.bp b/libcrypto_utils/Android.bp
index 923b2915..b33d46d 100644
--- a/libcrypto_utils/Android.bp
+++ b/libcrypto_utils/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library {
     name: "libcrypto_utils",
     vendor_available: true,
diff --git a/libcrypto_utils/tests/Android.bp b/libcrypto_utils/tests/Android.bp
index 5aadfe2..087e83b 100644
--- a/libcrypto_utils/tests/Android.bp
+++ b/libcrypto_utils/tests/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_test_host {
     name: "libcrypto_utils_test",
     srcs: ["android_pubkey_test.cpp"],
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 284c0b9..68b21c6 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -1,51 +1,58 @@
-//
-// Copyright (C) 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.
-//
+package {
+    default_applicable_licenses: ["system_core_libcutils_license"],
+}
+
+license {
+    name: "system_core_libcutils_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+        "SPDX-license-identifier-BSD",
+        "SPDX-license-identifier-MIT", // strlcpy.c
+    ],
+    license_text: [
+        "NOTICE",
+    ],
+}
 
 filegroup {
     name: "android_filesystem_config_header",
     srcs: ["include/private/android_filesystem_config.h"],
 }
 
-// some files must not be compiled when building against Mingw
-// they correspond to features not used by our host development tools
-// which are also hard or even impossible to port to native Win32
-libcutils_nonwindows_sources = [
-    "fs.cpp",
-    "hashmap.cpp",
-    "multiuser.cpp",
-    "str_parms.cpp",
-]
+cc_defaults {
+    name: "libcutils_defaults",
+    cflags: [
+        "-Wno-exit-time-destructors",
+    ],
 
-cc_library_headers {
-    name: "libcutils_headers",
-    vendor_available: true,
-    recovery_available: true,
+    product_available: true,
     ramdisk_available: true,
+    recovery_available: true,
+    vendor_available: true,
     vendor_ramdisk_available: true,
+
     host_supported: true,
+    native_bridge_supported: true,
+
     apex_available: [
         "//apex_available:platform",
         "//apex_available:anyapex",
     ],
     min_sdk_version: "29",
-    native_bridge_supported: true,
+}
+
+cc_library_headers {
+    name: "libcutils_headers",
+    defaults: ["libcutils_defaults"],
+
     export_include_dirs: ["include"],
     target: {
         vendor: {
-            override_export_include_dirs: ["include_vndk"],
+            override_export_include_dirs: ["include_outside_system"],
+        },
+        product: {
+            override_export_include_dirs: ["include_outside_system"],
         },
         linux_bionic: {
             enabled: true,
@@ -59,17 +66,7 @@
 // Socket specific parts of libcutils that are safe to statically link into an APEX.
 cc_library {
     name: "libcutils_sockets",
-    vendor_available: true,
-    recovery_available: true,
-    ramdisk_available: true,
-    vendor_ramdisk_available: true,
-    host_supported: true,
-    native_bridge_supported: true,
-    apex_available: [
-        "//apex_available:platform",
-        "//apex_available:anyapex",
-    ],
-    min_sdk_version: "29",
+    defaults: ["libcutils_defaults"],
 
     export_include_dirs: ["include"],
 
@@ -140,28 +137,28 @@
     },
 }
 
+// some files must not be compiled when building against Mingw
+// they correspond to features not used by our host development tools
+// which are also hard or even impossible to port to native Win32
+libcutils_nonwindows_sources = [
+    "fs.cpp",
+    "hashmap.cpp",
+    "multiuser.cpp",
+    "str_parms.cpp",
+]
+
 cc_library {
     name: "libcutils",
-    vendor_available: true,
+    defaults: ["libcutils_defaults"],
     vndk: {
         enabled: true,
         support_system_process: true,
     },
-    recovery_available: true,
-    vendor_ramdisk_available: true,
-    host_supported: true,
-    apex_available: [
-        "//apex_available:platform",
-        "//apex_available:anyapex",
-    ],
-    min_sdk_version: "29",
-    native_bridge_supported: true,
     srcs: [
         "config_utils.cpp",
         "canned_fs_config.cpp",
         "iosched_policy.cpp",
         "load_file.cpp",
-        "memory.cpp",
         "native_handle.cpp",
         "properties.cpp",
         "record_stream.cpp",
@@ -204,9 +201,6 @@
                 "uevent.cpp",
             ],
         },
-        bionic: {
-            header_libs: ["bionic_libc_platform_headers"],
-        },
 
         android_arm: {
             sanitize: {
@@ -234,14 +228,19 @@
             },
         },
 
+        // qtaguid.cpp loads libnetd_client.so with dlopen().  Since
+        // the interface of libnetd_client.so may vary between AOSP
+        // releases, exclude qtaguid.cpp from the VNDK-SP variant.
         vendor: {
             exclude_srcs: [
-                // qtaguid.cpp loads libnetd_client.so with dlopen().  Since
-                // the interface of libnetd_client.so may vary between AOSP
-                // releases, exclude qtaguid.cpp from the VNDK-SP variant.
                 "qtaguid.cpp",
             ],
-        }
+        },
+        product: {
+            exclude_srcs: [
+                "qtaguid.cpp",
+            ],
+        },
     },
 
     whole_static_libs: ["libcutils_sockets"],
@@ -252,7 +251,6 @@
     header_libs: [
         "libbase_headers",
         "libcutils_headers",
-        "libutils_headers",
         "libprocessgroup_headers",
     ],
     export_header_lib_headers: [
@@ -356,3 +354,18 @@
     defaults: ["libcutils_test_static_defaults"],
     test_config: "KernelLibcutilsTest.xml",
 }
+
+rust_bindgen {
+    name: "libcutils_bindgen",
+    wrapper_src: "rust/cutils.h",
+    crate_name: "cutils_bindgen",
+    source_stem: "bindings",
+    local_include_dirs: ["include"],
+    bindgen_flags: [
+        "--allowlist-function", "multiuser_get_app_id",
+        "--allowlist-function", "multiuser_get_uid",
+        "--allowlist-function", "multiuser_get_user_id",
+        "--allowlist-var", "AID_KEYSTORE",
+        "--allowlist-var", "AID_USER_OFFSET",
+    ],
+}
diff --git a/libcutils/OWNERS b/libcutils/OWNERS
index c18ed51..7529cb9 100644
--- a/libcutils/OWNERS
+++ b/libcutils/OWNERS
@@ -1,4 +1 @@
-cferris@google.com
-enh@google.com
-jmgao@google.com
-tomcherry@google.com
+include platform/system/core:/janitors/OWNERS
diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp
index 31e1679..e9497a8 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -35,9 +35,9 @@
 #include <string>
 
 #include <android-base/strings.h>
+#include <cutils/fs.h>
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
-#include <utils/Compat.h>
 
 #include "fs_config.h"
 
@@ -86,7 +86,7 @@
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/bin" },
     { 00755, AID_ROOT,         AID_ROOT,         0, "system/etc/ppp" },
     { 00755, AID_ROOT,         AID_SHELL,        0, "system/vendor" },
-    { 00751, AID_ROOT,         AID_SHELL,        0, "system/xbin" },
+    { 00750, AID_ROOT,         AID_SHELL,        0, "system/xbin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system/apex/*/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/bin" },
     { 00751, AID_ROOT,         AID_SHELL,        0, "system_ext/apex/*/bin" },
@@ -203,9 +203,14 @@
                                            CAP_MASK_LONG(CAP_SETGID),
                                               "system/bin/simpleperf_app_runner" },
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/e2fsck" },
-    { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/tune2fs" },
+#ifdef __LP64__
+    { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/linker64" },
+#else
+    { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/linker" },
+#endif
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/resize2fs" },
     { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/snapuserd" },
+    { 00755, AID_ROOT,      AID_ROOT,      0, "first_stage_ramdisk/system/bin/tune2fs" },
     // generic defaults
     { 00755, AID_ROOT,      AID_ROOT,      0, "bin/*" },
     { 00640, AID_ROOT,      AID_SHELL,     0, "fstab.*" },
diff --git a/libcutils/include/cutils/memory.h b/libcutils/include/cutils/memory.h
index 0fba53c..c6476c1 100644
--- a/libcutils/include/cutils/memory.h
+++ b/libcutils/include/cutils/memory.h
@@ -28,9 +28,6 @@
 size_t strlcpy(char *dst, const char *src, size_t size);
 #endif
 
-// Disables memory mitigations for the entire process, and logs appropriately.
-void process_disable_memory_mitigations();
-
 #ifdef __cplusplus
 } // extern "C"
 #endif
diff --git a/libcutils/include/cutils/threads.h b/libcutils/include/cutils/threads.h
index ba4846e..0082c6c 100644
--- a/libcutils/include/cutils/threads.h
+++ b/libcutils/include/cutils/threads.h
@@ -14,15 +14,14 @@
  * limitations under the License.
  */
 
-#ifndef _LIBS_CUTILS_THREADS_H
-#define _LIBS_CUTILS_THREADS_H
+#pragma once
 
 #include  <sys/types.h>
 
-#if !defined(_WIN32)
-#include <pthread.h>
-#else
+#if defined(_WIN32)
 #include <windows.h>
+#else
+#include <pthread.h>
 #endif
 
 #ifdef __cplusplus
@@ -32,46 +31,10 @@
 //
 // Deprecated: use android::base::GetThreadId instead, which doesn't truncate on Mac/Windows.
 //
-
+#if !defined(__GLIBC__) || __GLIBC__ >= 2 && __GLIBC_MINOR__ < 32
 extern pid_t gettid();
-
-//
-// Deprecated: use `_Thread_local` in C or `thread_local` in C++.
-//
-
-#if !defined(_WIN32)
-
-typedef struct {
-    pthread_mutex_t   lock;
-    int               has_tls;
-    pthread_key_t     tls;
-} thread_store_t;
-
-#define  THREAD_STORE_INITIALIZER  { PTHREAD_MUTEX_INITIALIZER, 0, 0 }
-
-#else // !defined(_WIN32)
-
-typedef struct {
-    int               lock_init;
-    int               has_tls;
-    DWORD             tls;
-    CRITICAL_SECTION  lock;
-} thread_store_t;
-
-#define  THREAD_STORE_INITIALIZER  { 0, 0, 0, {0, 0, 0, 0, 0, 0} }
-
-#endif // !defined(_WIN32)
-
-typedef void  (*thread_store_destruct_t)(void*  value);
-
-extern void*  thread_store_get(thread_store_t*  store);
-
-extern void   thread_store_set(thread_store_t*          store,
-                               void*                    value,
-                               thread_store_destruct_t  destroy);
+#endif
 
 #ifdef __cplusplus
 }
 #endif
-
-#endif /* _LIBS_CUTILS_THREADS_H */
diff --git a/libcutils/include/cutils/trace.h b/libcutils/include/cutils/trace.h
index 793e2ce..ef426ff 100644
--- a/libcutils/include/cutils/trace.h
+++ b/libcutils/include/cutils/trace.h
@@ -104,14 +104,6 @@
 void atrace_update_tags();
 
 /**
- * Set whether the process is debuggable.  By default the process is not
- * considered debuggable.  If the process is not debuggable then application-
- * level tracing is not allowed unless the ro.debuggable system property is
- * set to '1'.
- */
-void atrace_set_debuggable(bool debuggable);
-
-/**
  * Set whether tracing is enabled for the current process.  This is used to
  * prevent tracing within the Zygote process.
  */
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index b4fe2e6..8f22d89 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -127,6 +127,9 @@
 #define AID_EXT_DATA_RW 1078      /* GID for app-private data directories on external storage */
 #define AID_EXT_OBB_RW 1079       /* GID for OBB directories on external storage */
 #define AID_CONTEXT_HUB 1080      /* GID for access to the Context Hub */
+#define AID_VIRTMANAGER 1081      /* VirtManager daemon */
+#define AID_ARTD 1082             /* ART Service daemon */
+#define AID_UWB 1083              /* UWB subsystem */
 /* Changes to this file must be made in AOSP, *not* in internal branches. */
 
 #define AID_SHELL 2000 /* adb and debug shell user */
diff --git a/libcutils/include_vndk/cutils/android_filesystem_config.h b/libcutils/include_outside_system/cutils/android_filesystem_config.h
similarity index 100%
rename from libcutils/include_vndk/cutils/android_filesystem_config.h
rename to libcutils/include_outside_system/cutils/android_filesystem_config.h
diff --git a/libcutils/include_vndk/cutils/android_get_control_file.h b/libcutils/include_outside_system/cutils/android_get_control_file.h
similarity index 100%
rename from libcutils/include_vndk/cutils/android_get_control_file.h
rename to libcutils/include_outside_system/cutils/android_get_control_file.h
diff --git a/libcutils/include_vndk/cutils/android_reboot.h b/libcutils/include_outside_system/cutils/android_reboot.h
similarity index 100%
rename from libcutils/include_vndk/cutils/android_reboot.h
rename to libcutils/include_outside_system/cutils/android_reboot.h
diff --git a/libcutils/include_vndk/cutils/ashmem.h b/libcutils/include_outside_system/cutils/ashmem.h
similarity index 100%
rename from libcutils/include_vndk/cutils/ashmem.h
rename to libcutils/include_outside_system/cutils/ashmem.h
diff --git a/libcutils/include_vndk/cutils/atomic.h b/libcutils/include_outside_system/cutils/atomic.h
similarity index 100%
rename from libcutils/include_vndk/cutils/atomic.h
rename to libcutils/include_outside_system/cutils/atomic.h
diff --git a/libcutils/include_vndk/cutils/bitops.h b/libcutils/include_outside_system/cutils/bitops.h
similarity index 100%
rename from libcutils/include_vndk/cutils/bitops.h
rename to libcutils/include_outside_system/cutils/bitops.h
diff --git a/libcutils/include_vndk/cutils/compiler.h b/libcutils/include_outside_system/cutils/compiler.h
similarity index 100%
rename from libcutils/include_vndk/cutils/compiler.h
rename to libcutils/include_outside_system/cutils/compiler.h
diff --git a/libcutils/include_vndk/cutils/config_utils.h b/libcutils/include_outside_system/cutils/config_utils.h
similarity index 100%
rename from libcutils/include_vndk/cutils/config_utils.h
rename to libcutils/include_outside_system/cutils/config_utils.h
diff --git a/libcutils/include_vndk/cutils/fs.h b/libcutils/include_outside_system/cutils/fs.h
similarity index 100%
rename from libcutils/include_vndk/cutils/fs.h
rename to libcutils/include_outside_system/cutils/fs.h
diff --git a/libcutils/include_vndk/cutils/hashmap.h b/libcutils/include_outside_system/cutils/hashmap.h
similarity index 100%
rename from libcutils/include_vndk/cutils/hashmap.h
rename to libcutils/include_outside_system/cutils/hashmap.h
diff --git a/libcutils/include_vndk/cutils/iosched_policy.h b/libcutils/include_outside_system/cutils/iosched_policy.h
similarity index 100%
rename from libcutils/include_vndk/cutils/iosched_policy.h
rename to libcutils/include_outside_system/cutils/iosched_policy.h
diff --git a/libcutils/include_vndk/cutils/klog.h b/libcutils/include_outside_system/cutils/klog.h
similarity index 100%
rename from libcutils/include_vndk/cutils/klog.h
rename to libcutils/include_outside_system/cutils/klog.h
diff --git a/libcutils/include_vndk/cutils/list.h b/libcutils/include_outside_system/cutils/list.h
similarity index 100%
rename from libcutils/include_vndk/cutils/list.h
rename to libcutils/include_outside_system/cutils/list.h
diff --git a/libcutils/include_vndk/cutils/log.h b/libcutils/include_outside_system/cutils/log.h
similarity index 100%
rename from libcutils/include_vndk/cutils/log.h
rename to libcutils/include_outside_system/cutils/log.h
diff --git a/libcutils/include_vndk/cutils/memory.h b/libcutils/include_outside_system/cutils/memory.h
similarity index 100%
rename from libcutils/include_vndk/cutils/memory.h
rename to libcutils/include_outside_system/cutils/memory.h
diff --git a/libcutils/include_vndk/cutils/misc.h b/libcutils/include_outside_system/cutils/misc.h
similarity index 100%
rename from libcutils/include_vndk/cutils/misc.h
rename to libcutils/include_outside_system/cutils/misc.h
diff --git a/libcutils/include_vndk/cutils/multiuser.h b/libcutils/include_outside_system/cutils/multiuser.h
similarity index 100%
rename from libcutils/include_vndk/cutils/multiuser.h
rename to libcutils/include_outside_system/cutils/multiuser.h
diff --git a/libcutils/include_vndk/cutils/native_handle.h b/libcutils/include_outside_system/cutils/native_handle.h
similarity index 100%
rename from libcutils/include_vndk/cutils/native_handle.h
rename to libcutils/include_outside_system/cutils/native_handle.h
diff --git a/libcutils/include_vndk/cutils/partition_utils.h b/libcutils/include_outside_system/cutils/partition_utils.h
similarity index 100%
rename from libcutils/include_vndk/cutils/partition_utils.h
rename to libcutils/include_outside_system/cutils/partition_utils.h
diff --git a/libcutils/include_vndk/cutils/properties.h b/libcutils/include_outside_system/cutils/properties.h
similarity index 100%
rename from libcutils/include_vndk/cutils/properties.h
rename to libcutils/include_outside_system/cutils/properties.h
diff --git a/libcutils/include_vndk/cutils/qtaguid.h b/libcutils/include_outside_system/cutils/qtaguid.h
similarity index 100%
rename from libcutils/include_vndk/cutils/qtaguid.h
rename to libcutils/include_outside_system/cutils/qtaguid.h
diff --git a/libcutils/include_vndk/cutils/record_stream.h b/libcutils/include_outside_system/cutils/record_stream.h
similarity index 100%
rename from libcutils/include_vndk/cutils/record_stream.h
rename to libcutils/include_outside_system/cutils/record_stream.h
diff --git a/libcutils/include_vndk/cutils/sched_policy.h b/libcutils/include_outside_system/cutils/sched_policy.h
similarity index 100%
rename from libcutils/include_vndk/cutils/sched_policy.h
rename to libcutils/include_outside_system/cutils/sched_policy.h
diff --git a/libcutils/include_vndk/cutils/sockets.h b/libcutils/include_outside_system/cutils/sockets.h
similarity index 100%
rename from libcutils/include_vndk/cutils/sockets.h
rename to libcutils/include_outside_system/cutils/sockets.h
diff --git a/libcutils/include_vndk/cutils/str_parms.h b/libcutils/include_outside_system/cutils/str_parms.h
similarity index 100%
rename from libcutils/include_vndk/cutils/str_parms.h
rename to libcutils/include_outside_system/cutils/str_parms.h
diff --git a/libcutils/include_vndk/cutils/threads.h b/libcutils/include_outside_system/cutils/threads.h
similarity index 100%
rename from libcutils/include_vndk/cutils/threads.h
rename to libcutils/include_outside_system/cutils/threads.h
diff --git a/libcutils/include_vndk/cutils/trace.h b/libcutils/include_outside_system/cutils/trace.h
similarity index 100%
rename from libcutils/include_vndk/cutils/trace.h
rename to libcutils/include_outside_system/cutils/trace.h
diff --git a/libcutils/include_vndk/cutils/uevent.h b/libcutils/include_outside_system/cutils/uevent.h
similarity index 100%
rename from libcutils/include_vndk/cutils/uevent.h
rename to libcutils/include_outside_system/cutils/uevent.h
diff --git a/libcutils/memory.cpp b/libcutils/memory.cpp
deleted file mode 100644
index f526520..0000000
--- a/libcutils/memory.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cutils/memory.h>
-
-#include <log/log.h>
-
-#ifdef __BIONIC__
-#include <bionic/malloc.h>
-#endif
-
-void process_disable_memory_mitigations() {
-    bool success = false;
-#ifdef __BIONIC__
-    // TODO(b/158870657) is fixed and scudo is used globally, we can assert when an
-    // an error is returned.
-
-    success = android_mallopt(M_DISABLE_MEMORY_MITIGATIONS, nullptr, 0);
-#endif
-
-    if (success) {
-        ALOGI("Disabled memory mitigations for process.");
-    } else {
-        ALOGE("Could not disable memory mitigations for process.");
-    }
-}
diff --git a/libcutils/rust/cutils.h b/libcutils/rust/cutils.h
new file mode 100644
index 0000000..9b78af6
--- /dev/null
+++ b/libcutils/rust/cutils.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#include <cutils/multiuser.h>
+#include <private/android_filesystem_config.h>
diff --git a/libcutils/threads.cpp b/libcutils/threads.cpp
index a7e6b2d..6ece7a3 100644
--- a/libcutils/threads.cpp
+++ b/libcutils/threads.cpp
@@ -16,23 +16,19 @@
 
 #include <cutils/threads.h>
 
-// For gettid.
 #if defined(__APPLE__)
-#include "AvailabilityMacros.h"  // For MAC_OS_X_VERSION_MAX_ALLOWED
 #include <stdint.h>
-#include <stdlib.h>
-#include <sys/syscall.h>
-#include <sys/time.h>
-#include <unistd.h>
-#elif defined(__linux__) && !defined(__ANDROID__)
+#elif defined(__linux__)
 #include <syscall.h>
 #include <unistd.h>
 #elif defined(_WIN32)
 #include <windows.h>
 #endif
 
+#if defined(__BIONIC__) || defined(__GLIBC__) && __GLIBC_MINOR__ >= 32
 // No definition needed for Android because we'll just pick up bionic's copy.
-#ifndef __ANDROID__
+// No definition needed for Glibc >= 2.32 because it exposes its own copy.
+#else
 pid_t gettid() {
 #if defined(__APPLE__)
   uint64_t tid;
@@ -44,68 +40,4 @@
   return GetCurrentThreadId();
 #endif
 }
-#endif  // __ANDROID__
-
-#if !defined(_WIN32)
-
-void*  thread_store_get( thread_store_t*  store )
-{
-    if (!store->has_tls)
-        return NULL;
-
-    return pthread_getspecific( store->tls );
-}
-
-extern void   thread_store_set( thread_store_t*          store,
-                                void*                    value,
-                                thread_store_destruct_t  destroy)
-{
-    pthread_mutex_lock( &store->lock );
-    if (!store->has_tls) {
-        if (pthread_key_create( &store->tls, destroy) != 0) {
-            pthread_mutex_unlock(&store->lock);
-            return;
-        }
-        store->has_tls = 1;
-    }
-    pthread_mutex_unlock( &store->lock );
-
-    pthread_setspecific( store->tls, value );
-}
-
-#else /* !defined(_WIN32) */
-void*  thread_store_get( thread_store_t*  store )
-{
-    if (!store->has_tls)
-        return NULL;
-
-    return (void*) TlsGetValue( store->tls );
-}
-
-void   thread_store_set( thread_store_t*          store,
-                         void*                    value,
-                         thread_store_destruct_t  /*destroy*/ )
-{
-    /* XXX: can't use destructor on thread exit */
-    if (!store->lock_init) {
-        store->lock_init = -1;
-        InitializeCriticalSection( &store->lock );
-        store->lock_init = -2;
-    } else while (store->lock_init != -2) {
-        Sleep(10); /* 10ms */
-    }
-
-    EnterCriticalSection( &store->lock );
-    if (!store->has_tls) {
-        store->tls = TlsAlloc();
-        if (store->tls == TLS_OUT_OF_INDEXES) {
-            LeaveCriticalSection( &store->lock );
-            return;
-        }
-        store->has_tls = 1;
-    }
-    LeaveCriticalSection( &store->lock );
-
-    TlsSetValue( store->tls, value );
-}
-#endif /* !defined(_WIN32) */
+#endif
diff --git a/libcutils/trace-dev.inc b/libcutils/trace-dev.inc
index 6543426..3b459e0 100644
--- a/libcutils/trace-dev.inc
+++ b/libcutils/trace-dev.inc
@@ -21,6 +21,7 @@
 
 #include <errno.h>
 #include <fcntl.h>
+#include <fnmatch.h>
 #include <limits.h>
 #include <pthread.h>
 #include <stdatomic.h>
@@ -51,7 +52,6 @@
 atomic_bool              atrace_is_ready      = ATOMIC_VAR_INIT(false);
 int                      atrace_marker_fd     = -1;
 uint64_t                 atrace_enabled_tags  = ATRACE_TAG_NOT_READY;
-static bool              atrace_is_debuggable = false;
 static atomic_bool       atrace_is_enabled    = ATOMIC_VAR_INIT(true);
 static pthread_mutex_t   atrace_tags_mutex    = PTHREAD_MUTEX_INITIALIZER;
 
@@ -95,15 +95,6 @@
     return atrace_enabled_tags;
 }
 
-// Set whether this process is debuggable, which determines whether
-// application-level tracing is allowed when the ro.debuggable system property
-// is not set to '1'.
-void atrace_set_debuggable(bool debuggable)
-{
-    atrace_is_debuggable = debuggable;
-    atrace_update_tags();
-}
-
 // Check whether the given command line matches one of the comma-separated
 // values listed in the app_cmdlines property.
 static bool atrace_is_cmdline_match(const char* cmdline)
@@ -116,7 +107,7 @@
     for (int i = 0; i < count; i++) {
         snprintf(buf, sizeof(buf), "debug.atrace.app_%d", i);
         property_get(buf, value, "");
-        if (strcmp(value, "*") == 0 || strcmp(value, cmdline) == 0) {
+        if (fnmatch(value, cmdline, FNM_NOESCAPE) == 0) {
             return true;
         }
     }
@@ -127,24 +118,21 @@
 // Determine whether application-level tracing is enabled for this process.
 static bool atrace_is_app_tracing_enabled()
 {
-    bool sys_debuggable = property_get_bool("ro.debuggable", 0);
     bool result = false;
 
-    if (sys_debuggable || atrace_is_debuggable) {
-        // Check whether tracing is enabled for this process.
-        FILE * file = fopen("/proc/self/cmdline", "re");
-        if (file) {
-            char cmdline[4096];
-            if (fgets(cmdline, sizeof(cmdline), file)) {
-                result = atrace_is_cmdline_match(cmdline);
-            } else {
-                ALOGE("Error reading cmdline: %s (%d)", strerror(errno), errno);
-            }
-            fclose(file);
+    // Check whether tracing is enabled for this process.
+    FILE * file = fopen("/proc/self/cmdline", "re");
+    if (file) {
+        char cmdline[4096];
+        if (fgets(cmdline, sizeof(cmdline), file)) {
+            result = atrace_is_cmdline_match(cmdline);
         } else {
-            ALOGE("Error opening /proc/self/cmdline: %s (%d)", strerror(errno),
-                    errno);
+            ALOGE("Error reading cmdline: %s (%d)", strerror(errno), errno);
         }
+        fclose(file);
+    } else {
+        ALOGE("Error opening /proc/self/cmdline: %s (%d)", strerror(errno),
+                errno);
     }
 
     return result;
diff --git a/libdiskconfig/Android.bp b/libdiskconfig/Android.bp
index b92f086..a3d643e 100644
--- a/libdiskconfig/Android.bp
+++ b/libdiskconfig/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library {
     name: "libdiskconfig",
     vendor_available: true,
diff --git a/libgrallocusage/Android.bp b/libgrallocusage/Android.bp
index 33ae13d..f31b5f1 100644
--- a/libgrallocusage/Android.bp
+++ b/libgrallocusage/Android.bp
@@ -12,6 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["system_core_libgrallocusage_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+    name: "system_core_libgrallocusage_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "NOTICE",
+    ],
+}
+
 cc_library {
     name: "libgrallocusage",
     vendor_available: true,
diff --git a/libgrallocusage/OWNERS b/libgrallocusage/OWNERS
index 154dc6d..de2bf16 100644
--- a/libgrallocusage/OWNERS
+++ b/libgrallocusage/OWNERS
@@ -1,3 +1,2 @@
-jessehall@google.com
-olv@google.com
-stoza@google.com
+jreck@google.com
+lpy@google.com
diff --git a/libkeyutils/Android.bp b/libkeyutils/Android.bp
index b388e95..86f68fb 100644
--- a/libkeyutils/Android.bp
+++ b/libkeyutils/Android.bp
@@ -1,3 +1,16 @@
+package {
+    default_applicable_licenses: ["system_core_libkeyutils_license"],
+}
+
+license {
+    name: "system_core_libkeyutils_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-BSD",
+    ],
+    // large-scale-change unable to identify any license_text files
+}
+
 cc_library {
     name: "libkeyutils",
     cflags: ["-Werror"],
diff --git a/libmodprobe/Android.bp b/libmodprobe/Android.bp
index 78da46c..ba11dc9 100644
--- a/libmodprobe/Android.bp
+++ b/libmodprobe/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_static {
     name: "libmodprobe",
     cflags: [
diff --git a/libmodprobe/OWNERS b/libmodprobe/OWNERS
index 4b770b1..a6796cb 100644
--- a/libmodprobe/OWNERS
+++ b/libmodprobe/OWNERS
@@ -1,2 +1,2 @@
-tomcherry@google.com
-smuckle@google.com
+dvander@google.com
+willmcvicker@google.com
diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index baee4f9..c934860 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -24,7 +24,8 @@
 
 class Modprobe {
   public:
-    Modprobe(const std::vector<std::string>&, const std::string load_file = "modules.load");
+    Modprobe(const std::vector<std::string>&, const std::string load_file = "modules.load",
+             bool use_blocklist = true);
 
     bool LoadListedModules(bool strict = true);
     bool LoadWithAliases(const std::string& module_name, bool strict,
@@ -36,7 +37,6 @@
                             std::vector<std::string>* post_dependencies);
     void ResetModuleCount() { module_count_ = 0; }
     int GetModuleCount() { return module_count_; }
-    void EnableBlocklist(bool enable);
 
   private:
     std::string MakeCanonical(const std::string& module_path);
@@ -48,6 +48,7 @@
     void AddOption(const std::string& module_name, const std::string& option_name,
                    const std::string& value);
     std::string GetKernelCmdline();
+    bool IsBlocklisted(const std::string& module_name);
 
     bool ParseDepCallback(const std::string& base_path, const std::vector<std::string>& args);
     bool ParseAliasCallback(const std::vector<std::string>& args);
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index b3ae937..1a9d364 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -313,7 +313,9 @@
     }
 }
 
-Modprobe::Modprobe(const std::vector<std::string>& base_paths, const std::string load_file) {
+Modprobe::Modprobe(const std::vector<std::string>& base_paths, const std::string load_file,
+                   bool use_blocklist)
+    : blocklist_enabled(use_blocklist) {
     using namespace std::placeholders;
 
     for (const auto& base_path : base_paths) {
@@ -339,10 +341,6 @@
     ParseKernelCmdlineOptions();
 }
 
-void Modprobe::EnableBlocklist(bool enable) {
-    blocklist_enabled = enable;
-}
-
 std::vector<std::string> Modprobe::GetDependencies(const std::string& module) {
     auto it = module_deps_.find(module);
     if (it == module_deps_.end()) {
@@ -427,10 +425,23 @@
     return true;
 }
 
+bool Modprobe::IsBlocklisted(const std::string& module_name) {
+    if (!blocklist_enabled) return false;
+
+    auto canonical_name = MakeCanonical(module_name);
+    auto dependencies = GetDependencies(canonical_name);
+    for (auto dep = dependencies.begin(); dep != dependencies.end(); ++dep) {
+        if (module_blocklist_.count(MakeCanonical(*dep))) return true;
+    }
+
+    return module_blocklist_.count(canonical_name) > 0;
+}
+
 bool Modprobe::LoadListedModules(bool strict) {
     auto ret = true;
     for (const auto& module : module_load_) {
         if (!LoadWithAliases(module, true)) {
+            if (IsBlocklisted(module)) continue;
             ret = false;
             if (strict) break;
         }
@@ -440,16 +451,10 @@
 
 bool Modprobe::Remove(const std::string& module_name) {
     auto dependencies = GetDependencies(MakeCanonical(module_name));
-    if (dependencies.empty()) {
-        LOG(ERROR) << "Empty dependencies for module " << module_name;
-        return false;
-    }
-    if (!Rmmod(dependencies[0])) {
-        return false;
-    }
-    for (auto dep = dependencies.begin() + 1; dep != dependencies.end(); ++dep) {
+    for (auto dep = dependencies.begin(); dep != dependencies.end(); ++dep) {
         Rmmod(*dep);
     }
+    Rmmod(module_name);
     return true;
 }
 
diff --git a/libmodprobe/libmodprobe_test.cpp b/libmodprobe/libmodprobe_test.cpp
index d50c10d..f960b61 100644
--- a/libmodprobe/libmodprobe_test.cpp
+++ b/libmodprobe/libmodprobe_test.cpp
@@ -78,6 +78,18 @@
             "/test13.ko",
     };
 
+    std::vector<std::string> expected_modules_blocklist_enabled = {
+            "/test1.ko option1=50 option2=60",
+            "/test6.ko",
+            "/test2.ko",
+            "/test5.ko option1=",
+            "/test8.ko",
+            "/test7.ko param1=4",
+            "/test12.ko",
+            "/test11.ko",
+            "/test13.ko",
+    };
+
     const std::string modules_dep =
             "test1.ko:\n"
             "test2.ko:\n"
@@ -146,7 +158,7 @@
         *i = dir.path + *i;
     }
 
-    Modprobe m({dir.path});
+    Modprobe m({dir.path}, "modules.load", false);
     EXPECT_TRUE(m.LoadListedModules());
 
     GTEST_LOG_(INFO) << "Expected modules loaded (in order):";
@@ -176,8 +188,22 @@
 
     EXPECT_TRUE(modules_loaded == expected_after_remove);
 
-    m.EnableBlocklist(true);
+    m = Modprobe({dir.path});
     EXPECT_FALSE(m.LoadWithAliases("test4", true));
+    while (modules_loaded.size() > 0) EXPECT_TRUE(m.Remove(modules_loaded.front()));
+    EXPECT_TRUE(m.LoadListedModules());
+
+    GTEST_LOG_(INFO) << "Expected modules loaded after enabling blocklist (in order):";
+    for (auto i = expected_modules_blocklist_enabled.begin();
+         i != expected_modules_blocklist_enabled.end(); ++i) {
+        *i = dir.path + *i;
+        GTEST_LOG_(INFO) << "\"" << *i << "\"";
+    }
+    GTEST_LOG_(INFO) << "Actual modules loaded with blocklist enabled (in order):";
+    for (auto i = modules_loaded.begin(); i != modules_loaded.end(); ++i) {
+        GTEST_LOG_(INFO) << "\"" << *i << "\"";
+    }
+    EXPECT_TRUE(modules_loaded == expected_modules_blocklist_enabled);
 }
 
 TEST(libmodprobe, ModuleDepLineWithoutColonIsSkipped) {
diff --git a/libnetutils/Android.bp b/libnetutils/Android.bp
index 65371fa..2864ad0 100644
--- a/libnetutils/Android.bp
+++ b/libnetutils/Android.bp
@@ -1,3 +1,20 @@
+package {
+    default_applicable_licenses: ["system_core_libnetutils_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+    name: "system_core_libnetutils_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "NOTICE",
+    ],
+}
+
 cc_library_shared {
     name: "libnetutils",
     vendor_available: true,
@@ -21,6 +38,11 @@
     cflags: ["-Werror"],
 
     export_include_dirs: ["include"],
+    // TODO: remove connectivity module dependency, or have this lib build against the ndk
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.tethering",
+    ],
 }
 
 cc_library_static {
diff --git a/libpackagelistparser/Android.bp b/libpackagelistparser/Android.bp
index c3f8692..81885d7 100644
--- a/libpackagelistparser/Android.bp
+++ b/libpackagelistparser/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library {
     name: "libpackagelistparser",
     ramdisk_available: true,
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index 71e2b91..c68552d 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -1,6 +1,11 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_headers {
     name: "libprocessgroup_headers",
     vendor_available: true,
+    product_available: true,
     ramdisk_available: true,
     vendor_ramdisk_available: true,
     recovery_available: true,
@@ -36,6 +41,7 @@
     vendor_ramdisk_available: true,
     recovery_available: true,
     vendor_available: true,
+    product_available: true,
     vndk: {
         enabled: true,
         support_system_process: true,
diff --git a/libprocessgroup/OWNERS b/libprocessgroup/OWNERS
index 27b9a03..8ebb8cc 100644
--- a/libprocessgroup/OWNERS
+++ b/libprocessgroup/OWNERS
@@ -1,3 +1,2 @@
 ccross@google.com
 surenb@google.com
-tomcherry@google.com
diff --git a/libprocessgroup/cgroup_map.cpp b/libprocessgroup/cgroup_map.cpp
index b82b0ab..5ca0967 100644
--- a/libprocessgroup/cgroup_map.cpp
+++ b/libprocessgroup/cgroup_map.cpp
@@ -71,7 +71,7 @@
     if (!HasValue()) return false;
 
     if (state_ == UNKNOWN) {
-        if (ACgroupController_getFlags != nullptr) {
+        if (__builtin_available(android 30, *)) {
             uint32_t flags = ACgroupController_getFlags(controller_);
             state_ = (flags & CGROUPRC_CONTROLLER_FLAG_MOUNTED) != 0 ? USABLE : MISSING;
         } else {
@@ -172,7 +172,7 @@
     auto controller_count = ACgroupFile_getControllerCount();
     for (uint32_t i = 0; i < controller_count; ++i) {
         const ACgroupController* controller = ACgroupFile_getController(i);
-        if (ACgroupController_getFlags != nullptr) {
+        if (__builtin_available(android 30, *)) {
             LOG(INFO) << "\t" << ACgroupController_getName(controller) << " ver "
                       << ACgroupController_getVersion(controller) << " path "
                       << ACgroupController_getPath(controller) << " flags "
diff --git a/libprocessgroup/cgrouprc/Android.bp b/libprocessgroup/cgrouprc/Android.bp
index bb59942..7522cfe 100644
--- a/libprocessgroup/cgrouprc/Android.bp
+++ b/libprocessgroup/cgrouprc/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library {
     name: "libcgrouprc",
     host_supported: true,
@@ -24,7 +28,9 @@
     // defined below. The static library is built for tests.
     vendor_available: false,
     native_bridge_supported: true,
-    llndk_stubs: "libcgrouprc.llndk",
+    llndk: {
+        symbol_file: "libcgrouprc.map.txt",
+    },
     srcs: [
         "cgroup_controller.cpp",
         "cgroup_file.cpp",
@@ -46,21 +52,12 @@
         "libcgrouprc_format",
     ],
     stubs: {
-        symbol_file: "libcgrouprc.llndk.txt",
+        symbol_file: "libcgrouprc.map.txt",
         versions: ["29"],
     },
     target: {
         linux: {
-            version_script: "libcgrouprc.llndk.txt",
+            version_script: "libcgrouprc.map.txt",
         },
     },
 }
-
-llndk_library {
-    name: "libcgrouprc.llndk",
-    symbol_file: "libcgrouprc.llndk.txt",
-    native_bridge_supported: true,
-    export_include_dirs: [
-        "include",
-    ],
-}
diff --git a/libprocessgroup/cgrouprc/include/android/cgrouprc.h b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
index 7e74432..100d60e 100644
--- a/libprocessgroup/cgrouprc/include/android/cgrouprc.h
+++ b/libprocessgroup/cgrouprc/include/android/cgrouprc.h
@@ -28,8 +28,6 @@
 struct ACgroupController;
 typedef struct ACgroupController ACgroupController;
 
-#if __ANDROID_API__ >= __ANDROID_API_Q__
-
 // ACgroupFile
 
 /**
@@ -70,8 +68,7 @@
  */
 #define CGROUPRC_CONTROLLER_FLAG_MOUNTED 0x1
 #define CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION 0x2
-
-#if __ANDROID_API__ >= __ANDROID_API_R__
+#define CGROUPRC_CONTROLLER_FLAG_OPTIONAL 0x4
 
 /**
  * Returns the flags bitmask of the given controller.
@@ -80,8 +77,6 @@
 __attribute__((warn_unused_result, weak)) uint32_t ACgroupController_getFlags(
         const ACgroupController*) __INTRODUCED_IN(30);
 
-#endif
-
 /**
  * Returns the name of the given controller.
  * If the given controller is null, return nullptr.
@@ -97,5 +92,3 @@
         __INTRODUCED_IN(29);
 
 __END_DECLS
-
-#endif
diff --git a/libprocessgroup/cgrouprc/libcgrouprc.llndk.txt b/libprocessgroup/cgrouprc/libcgrouprc.map.txt
similarity index 100%
rename from libprocessgroup/cgrouprc/libcgrouprc.llndk.txt
rename to libprocessgroup/cgrouprc/libcgrouprc.map.txt
diff --git a/libprocessgroup/cgrouprc_format/Android.bp b/libprocessgroup/cgrouprc_format/Android.bp
index 6428930..0590924 100644
--- a/libprocessgroup/cgrouprc_format/Android.bp
+++ b/libprocessgroup/cgrouprc_format/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_static {
     name: "libcgrouprc_format",
     host_supported: true,
diff --git a/libprocessgroup/include/processgroup/processgroup.h b/libprocessgroup/include/processgroup/processgroup.h
index 4aa439a..fa2642d 100644
--- a/libprocessgroup/include/processgroup/processgroup.h
+++ b/libprocessgroup/include/processgroup/processgroup.h
@@ -65,6 +65,10 @@
 
 void removeAllProcessGroups(void);
 
+// Provides the path for an attribute in a specific process group
+// Returns false in case of error, true in case of success
+bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path);
+
 #endif // __ANDROID_VNDK__
 
 __END_DECLS
diff --git a/libprocessgroup/include/processgroup/sched_policy.h b/libprocessgroup/include/processgroup/sched_policy.h
index 945d90c..a18847e 100644
--- a/libprocessgroup/include/processgroup/sched_policy.h
+++ b/libprocessgroup/include/processgroup/sched_policy.h
@@ -42,7 +42,7 @@
     SP_DEFAULT = -1,
     SP_BACKGROUND = 0,
     SP_FOREGROUND = 1,
-    SP_SYSTEM = 2,  // can't be used with set_sched_policy()
+    SP_SYSTEM = 2,
     SP_AUDIO_APP = 3,
     SP_AUDIO_SYS = 4,
     SP_TOP_APP = 5,
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index d669ebe..815d2bb 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -131,13 +131,25 @@
     return StringPrintf("%s/uid_%d/pid_%d", cgroup, uid, pid);
 }
 
-static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid) {
-    int ret;
-
+static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid, unsigned int retries) {
+    int ret = 0;
     auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, pid);
-    ret = rmdir(uid_pid_path.c_str());
-
     auto uid_path = ConvertUidToPath(cgroup, uid);
+
+    if (retries == 0) {
+        retries = 1;
+    }
+
+    while (retries--) {
+        ret = rmdir(uid_pid_path.c_str());
+        if (!ret || errno != EBUSY) break;
+        std::this_thread::sleep_for(5ms);
+    }
+
+    // With the exception of boot or shutdown, system uid_ folders are always populated. Spinning
+    // here would needlessly delay most pid removals. Additionally, once empty a uid_ cgroup won't
+    // have processes hanging on it (we've already spun for all its pid_), so there's no need to
+    // spin anyway.
     rmdir(uid_path.c_str());
 
     return ret;
@@ -176,7 +188,7 @@
     std::vector<std::string> cgroups;
     std::string path;
 
-    if (CgroupGetControllerPath("cpuacct", &path)) {
+    if (CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &path)) {
         cgroups.push_back(path);
     }
     if (CgroupGetControllerPath("memory", &path)) {
@@ -212,19 +224,49 @@
     }
 }
 
+/**
+ * Process groups are primarily created by the Zygote, meaning that uid/pid groups are created by
+ * the user root. Ownership for the newly created cgroup and all of its files must thus be
+ * transferred for the user/group passed as uid/gid before system_server can properly access them.
+ */
 static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
     if (mkdir(path.c_str(), mode) == -1 && errno != EEXIST) {
         return false;
     }
 
-    if (chown(path.c_str(), uid, gid) == -1) {
-        int saved_errno = errno;
-        rmdir(path.c_str());
-        errno = saved_errno;
-        return false;
+    auto dir = std::unique_ptr<DIR, decltype(&closedir)>(opendir(path.c_str()), closedir);
+
+    if (dir == NULL) {
+        PLOG(ERROR) << "opendir failed for " << path;
+        goto err;
+    }
+
+    struct dirent* dir_entry;
+    while ((dir_entry = readdir(dir.get()))) {
+        if (!strcmp("..", dir_entry->d_name)) {
+            continue;
+        }
+
+        std::string file_path = path + "/" + dir_entry->d_name;
+
+        if (lchown(file_path.c_str(), uid, gid) < 0) {
+            PLOG(ERROR) << "lchown failed for " << file_path;
+            goto err;
+        }
+
+        if (fchmodat(AT_FDCWD, file_path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) {
+            PLOG(ERROR) << "fchmodat failed for " << file_path;
+            goto err;
+        }
     }
 
     return true;
+err:
+    int saved_errno = errno;
+    rmdir(path.c_str());
+    errno = saved_errno;
+
+    return false;
 }
 
 // Returns number of processes killed on success
@@ -302,17 +344,9 @@
 
 static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries,
                             int* max_processes) {
-    std::string cpuacct_path;
-    std::string memory_path;
-
-    CgroupGetControllerPath("cpuacct", &cpuacct_path);
-    CgroupGetControllerPath("memory", &memory_path);
-    memory_path += "/apps";
-
-    const char* cgroup =
-            (!access(ConvertUidPidToPath(cpuacct_path.c_str(), uid, initialPid).c_str(), F_OK))
-                    ? cpuacct_path.c_str()
-                    : memory_path.c_str();
+    std::string hierarchy_root_path;
+    CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
+    const char* cgroup = hierarchy_root_path.c_str();
 
     std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
 
@@ -355,7 +389,17 @@
             LOG(INFO) << "Successfully killed process cgroup uid " << uid << " pid " << initialPid
                       << " in " << static_cast<int>(ms) << "ms";
         }
-        return RemoveProcessGroup(cgroup, uid, initialPid);
+
+        int err = RemoveProcessGroup(cgroup, uid, initialPid, retries);
+
+        if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
+            std::string memory_path;
+            CgroupGetControllerPath("memory", &memory_path);
+            memory_path += "/apps";
+            if (RemoveProcessGroup(memory_path.c_str(), uid, initialPid, retries)) return -1;
+        }
+
+        return err;
     } else {
         if (retries > 0) {
             LOG(ERROR) << "Failed to kill process cgroup uid " << uid << " pid " << initialPid
@@ -374,25 +418,30 @@
     return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/, max_processes);
 }
 
-int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
-    std::string cgroup;
-    if (isMemoryCgroupSupported() && (memControl || UsePerAppMemcg())) {
-        CgroupGetControllerPath("memory", &cgroup);
-        cgroup += "/apps";
-    } else {
-        CgroupGetControllerPath("cpuacct", &cgroup);
-    }
-
+static int createProcessGroupInternal(uid_t uid, int initialPid, std::string cgroup) {
     auto uid_path = ConvertUidToPath(cgroup.c_str(), uid);
 
-    if (!MkdirAndChown(uid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
+    struct stat cgroup_stat;
+    mode_t cgroup_mode = 0750;
+    gid_t cgroup_uid = AID_SYSTEM;
+    uid_t cgroup_gid = AID_SYSTEM;
+
+    if (stat(cgroup.c_str(), &cgroup_stat) == 1) {
+        PLOG(ERROR) << "Failed to get stats for " << cgroup;
+    } else {
+        cgroup_mode = cgroup_stat.st_mode;
+        cgroup_uid = cgroup_stat.st_uid;
+        cgroup_gid = cgroup_stat.st_gid;
+    }
+
+    if (!MkdirAndChown(uid_path, cgroup_mode, cgroup_uid, cgroup_gid)) {
         PLOG(ERROR) << "Failed to make and chown " << uid_path;
         return -errno;
     }
 
     auto uid_pid_path = ConvertUidPidToPath(cgroup.c_str(), uid, initialPid);
 
-    if (!MkdirAndChown(uid_pid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
+    if (!MkdirAndChown(uid_pid_path, cgroup_mode, cgroup_uid, cgroup_gid)) {
         PLOG(ERROR) << "Failed to make and chown " << uid_pid_path;
         return -errno;
     }
@@ -408,6 +457,27 @@
     return ret;
 }
 
+int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
+    std::string cgroup;
+
+    if (memControl && !UsePerAppMemcg()) {
+        PLOG(ERROR) << "service memory controls are used without per-process memory cgroup support";
+        return -EINVAL;
+    }
+
+    if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
+        CgroupGetControllerPath("memory", &cgroup);
+        cgroup += "/apps";
+        int ret = createProcessGroupInternal(uid, initialPid, cgroup);
+        if (ret != 0) {
+            return ret;
+        }
+    }
+
+    CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cgroup);
+    return createProcessGroupInternal(uid, initialPid, cgroup);
+}
+
 static bool SetProcessGroupValue(int tid, const std::string& attr_name, int64_t value) {
     if (!isMemoryCgroupSupported()) {
         PLOG(ERROR) << "Memcg is not mounted.";
@@ -438,3 +508,7 @@
 bool setProcessGroupLimit(uid_t, int pid, int64_t limit_in_bytes) {
     return SetProcessGroupValue(pid, "MemLimit", limit_in_bytes);
 }
+
+bool getAttributePathForTask(const std::string& attr_name, int tid, std::string* path) {
+    return CgroupGetAttributePathForTask(attr_name, tid, path);
+}
diff --git a/libprocessgroup/profiles/Android.bp b/libprocessgroup/profiles/Android.bp
index c371ef7..885971a 100644
--- a/libprocessgroup/profiles/Android.bp
+++ b/libprocessgroup/profiles/Android.bp
@@ -12,9 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 prebuilt_etc {
     name: "cgroups.json",
     src: "cgroups.json",
+    required: [
+        "cgroups_28.json",
+        "cgroups_29.json",
+        "cgroups_30.json",
+    ],
 }
 
 prebuilt_etc {
@@ -25,8 +34,49 @@
 }
 
 prebuilt_etc {
+    name: "cgroups_28.json",
+    src: "cgroups_28.json",
+    sub_dir: "task_profiles",
+}
+
+prebuilt_etc {
+    name: "cgroups_29.json",
+    src: "cgroups_29.json",
+    sub_dir: "task_profiles",
+}
+
+prebuilt_etc {
+    name: "cgroups_30.json",
+    src: "cgroups_30.json",
+    sub_dir: "task_profiles",
+}
+
+prebuilt_etc {
     name: "task_profiles.json",
     src: "task_profiles.json",
+    required: [
+        "task_profiles_28.json",
+        "task_profiles_29.json",
+        "task_profiles_30.json",
+    ],
+}
+
+prebuilt_etc {
+    name: "task_profiles_28.json",
+    src: "task_profiles_28.json",
+    sub_dir: "task_profiles",
+}
+
+prebuilt_etc {
+    name: "task_profiles_29.json",
+    src: "task_profiles_29.json",
+    sub_dir: "task_profiles",
+}
+
+prebuilt_etc {
+    name: "task_profiles_30.json",
+    src: "task_profiles_30.json",
+    sub_dir: "task_profiles",
 }
 
 cc_defaults {
@@ -77,9 +127,6 @@
         "cgroups.recovery.json",
         "task_profiles.json",
     ],
-    test_suites: [
-        "general-tests",
-    ],
 }
 
 cc_test {
diff --git a/libprocessgroup/profiles/TEST_MAPPING b/libprocessgroup/profiles/TEST_MAPPING
deleted file mode 100644
index 5ff4112..0000000
--- a/libprocessgroup/profiles/TEST_MAPPING
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "libprocessgroup_proto_test",
-      "host": true
-    }
-  ]
-}
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index 5b7a28a..0634220 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -15,11 +15,6 @@
       "GID": "system"
     },
     {
-      "Controller": "cpuacct",
-      "Path": "/acct",
-      "Mode": "0555"
-    },
-    {
       "Controller": "cpuset",
       "Path": "/dev/cpuset",
       "Mode": "0755",
@@ -31,7 +26,8 @@
       "Path": "/dev/memcg",
       "Mode": "0700",
       "UID": "root",
-      "GID": "system"
+      "GID": "system",
+      "Optional": true
     }
   ],
   "Cgroups2": {
@@ -42,7 +38,7 @@
     "Controllers": [
       {
         "Controller": "freezer",
-        "Path": "freezer",
+        "Path": ".",
         "Mode": "0755",
         "UID": "system",
         "GID": "system"
diff --git a/libprocessgroup/profiles/cgroups.proto b/libprocessgroup/profiles/cgroups.proto
index 13adcae..f2de345 100644
--- a/libprocessgroup/profiles/cgroups.proto
+++ b/libprocessgroup/profiles/cgroups.proto
@@ -24,7 +24,7 @@
     Cgroups2 cgroups2 = 2 [json_name = "Cgroups2"];
 }
 
-// Next: 7
+// Next: 8
 message Cgroup {
     string controller = 1 [json_name = "Controller"];
     string path = 2 [json_name = "Path"];
@@ -35,6 +35,7 @@
 // when a boolean is specified as false, so leave unspecified in that case
 // https://developers.google.com/protocol-buffers/docs/proto3#default
     bool needs_activation = 6 [json_name = "NeedsActivation"];
+    bool is_optional = 7 [json_name = "Optional"];
 }
 
 // Next: 6
diff --git a/libprocessgroup/profiles/cgroups.recovery.json b/libprocessgroup/profiles/cgroups.recovery.json
index f0bf5fd..e275252 100644
--- a/libprocessgroup/profiles/cgroups.recovery.json
+++ b/libprocessgroup/profiles/cgroups.recovery.json
@@ -1,9 +1,8 @@
 {
-  "Cgroups": [
-    {
-      "Controller": "cpuacct",
-      "Path": "/acct",
-      "Mode": "0555"
-    }
-  ]
+  "Cgroups2": {
+    "Path": "/sys/fs/cgroup",
+    "Mode": "0755",
+    "UID": "root",
+    "GID": "root"
+  }
 }
diff --git a/libprocessgroup/profiles/cgroups_28.json b/libprocessgroup/profiles/cgroups_28.json
new file mode 100644
index 0000000..17d4929
--- /dev/null
+++ b/libprocessgroup/profiles/cgroups_28.json
@@ -0,0 +1,11 @@
+{
+  "Cgroups": [
+    {
+      "Controller": "schedtune",
+      "Path": "/dev/stune",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    }
+  ]
+}
diff --git a/libprocessgroup/profiles/cgroups_29.json b/libprocessgroup/profiles/cgroups_29.json
new file mode 100644
index 0000000..17d4929
--- /dev/null
+++ b/libprocessgroup/profiles/cgroups_29.json
@@ -0,0 +1,11 @@
+{
+  "Cgroups": [
+    {
+      "Controller": "schedtune",
+      "Path": "/dev/stune",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system"
+    }
+  ]
+}
diff --git a/libprocessgroup/profiles/cgroups_30.json b/libprocessgroup/profiles/cgroups_30.json
new file mode 100644
index 0000000..80a074b
--- /dev/null
+++ b/libprocessgroup/profiles/cgroups_30.json
@@ -0,0 +1,12 @@
+{
+  "Cgroups": [
+    {
+      "Controller": "schedtune",
+      "Path": "/dev/stune",
+      "Mode": "0755",
+      "UID": "system",
+      "GID": "system",
+      "Optional": true
+    }
+  ]
+}
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index ea0064f..bd94621 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -70,11 +70,11 @@
       "Name": "Frozen",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "freezer",
-            "Path": ""
+            "Name": "FreezerState",
+            "Value": "1"
           }
         }
       ]
@@ -83,11 +83,11 @@
       "Name": "Unfrozen",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "freezer",
-            "Path": "../"
+            "Name": "FreezerState",
+            "Value": "0"
           }
         }
       ]
@@ -100,7 +100,20 @@
           "Params":
           {
             "Controller": "cpu",
-            "Path": ""
+            "Path": "system"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "ServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpu",
+            "Path": "system-background"
           }
         }
       ]
@@ -545,32 +558,6 @@
         }
       ]
     },
-    {
-      "Name": "FreezerDisabled",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "FreezerState",
-            "Value": "0"
-          }
-        }
-      ]
-    },
-    {
-      "Name": "FreezerEnabled",
-      "Actions": [
-        {
-          "Name": "SetAttribute",
-          "Params":
-          {
-            "Name": "FreezerState",
-            "Value": "1"
-          }
-        }
-      ]
-    }
   ],
 
   "AggregateProfiles": [
@@ -591,6 +578,10 @@
       "Profiles": [ "MaxPerformance", "MaxIoPriority", "TimerSlackNormal" ]
     },
     {
+      "Name": "SCHED_SP_SYSTEM",
+      "Profiles": [ "ServicePerformance", "LowIoPriority", "TimerSlackNormal" ]
+    },
+    {
       "Name": "SCHED_SP_RT_APP",
       "Profiles": [ "RealtimePerformance", "MaxIoPriority", "TimerSlackNormal" ]
     },
diff --git a/libprocessgroup/profiles/task_profiles_28.json b/libprocessgroup/profiles/task_profiles_28.json
new file mode 100644
index 0000000..9f83785
--- /dev/null
+++ b/libprocessgroup/profiles/task_profiles_28.json
@@ -0,0 +1,135 @@
+{
+  "Attributes": [
+    {
+      "Name": "STuneBoost",
+      "Controller": "schedtune",
+      "File": "schedtune.boost"
+    },
+    {
+      "Name": "STunePreferIdle",
+      "Controller": "schedtune",
+      "File": "schedtune.prefer_idle"
+    }
+  ],
+
+  "Profiles": [
+    {
+      "Name": "HighEnergySaving",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NormalPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "foreground"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "MaxPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "top-app"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "RealtimePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "rt"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CameraServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "camera-daemon"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NNApiHALPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "nnapi-hal"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "CpuPolicySpread",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "1"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CpuPolicyPack",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "0"
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/libprocessgroup/profiles/task_profiles_29.json b/libprocessgroup/profiles/task_profiles_29.json
new file mode 100644
index 0000000..9f83785
--- /dev/null
+++ b/libprocessgroup/profiles/task_profiles_29.json
@@ -0,0 +1,135 @@
+{
+  "Attributes": [
+    {
+      "Name": "STuneBoost",
+      "Controller": "schedtune",
+      "File": "schedtune.boost"
+    },
+    {
+      "Name": "STunePreferIdle",
+      "Controller": "schedtune",
+      "File": "schedtune.prefer_idle"
+    }
+  ],
+
+  "Profiles": [
+    {
+      "Name": "HighEnergySaving",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NormalPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "foreground"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "MaxPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "top-app"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "RealtimePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "rt"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CameraServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "camera-daemon"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NNApiHALPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "nnapi-hal"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "CpuPolicySpread",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "1"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CpuPolicyPack",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "0"
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/libprocessgroup/profiles/task_profiles_30.json b/libprocessgroup/profiles/task_profiles_30.json
new file mode 100644
index 0000000..9f83785
--- /dev/null
+++ b/libprocessgroup/profiles/task_profiles_30.json
@@ -0,0 +1,135 @@
+{
+  "Attributes": [
+    {
+      "Name": "STuneBoost",
+      "Controller": "schedtune",
+      "File": "schedtune.boost"
+    },
+    {
+      "Name": "STunePreferIdle",
+      "Controller": "schedtune",
+      "File": "schedtune.prefer_idle"
+    }
+  ],
+
+  "Profiles": [
+    {
+      "Name": "HighEnergySaving",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "background"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NormalPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": ""
+          }
+        }
+      ]
+    },
+    {
+      "Name": "HighPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "foreground"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "MaxPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "top-app"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "RealtimePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "rt"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CameraServicePerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "camera-daemon"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "NNApiHALPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "schedtune",
+            "Path": "nnapi-hal"
+          }
+        }
+      ]
+    },
+
+    {
+      "Name": "CpuPolicySpread",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "1"
+          }
+        }
+      ]
+    },
+    {
+      "Name": "CpuPolicyPack",
+      "Actions": [
+        {
+          "Name": "SetAttribute",
+          "Params":
+          {
+            "Name": "STunePreferIdle",
+            "Value": "0"
+          }
+        }
+      ]
+    }
+  ]
+}
diff --git a/libprocessgroup/sched_policy.cpp b/libprocessgroup/sched_policy.cpp
index 698e74d..1a4196a 100644
--- a/libprocessgroup/sched_policy.cpp
+++ b/libprocessgroup/sched_policy.cpp
@@ -124,6 +124,8 @@
             return SetTaskProfiles(tid, {"SCHED_SP_FOREGROUND"}, true) ? 0 : -1;
         case SP_TOP_APP:
             return SetTaskProfiles(tid, {"SCHED_SP_TOP_APP"}, true) ? 0 : -1;
+        case SP_SYSTEM:
+            return SetTaskProfiles(tid, {"SCHED_SP_SYSTEM"}, true) ? 0 : -1;
         case SP_RT_APP:
             return SetTaskProfiles(tid, {"SCHED_SP_RT_APP"}, true) ? 0 : -1;
         default:
@@ -157,10 +159,9 @@
 
     if (!controller.IsUsable()) return -1;
 
-    if (!controller.GetTaskGroup(tid, &subgroup)) {
-        LOG(ERROR) << "Failed to find cgroup for tid " << tid;
+    if (!controller.GetTaskGroup(tid, &subgroup))
         return -1;
-    }
+
     return 0;
 }
 
@@ -172,11 +173,16 @@
     std::string group;
     if (schedboost_enabled()) {
         if ((getCGroupSubsys(tid, "schedtune", group) < 0) &&
-            (getCGroupSubsys(tid, "cpu", group) < 0))
-		return -1;
+            (getCGroupSubsys(tid, "cpu", group) < 0)) {
+                LOG(ERROR) << "Failed to find cpu cgroup for tid " << tid;
+                return -1;
+        }
     }
     if (group.empty() && cpusets_enabled()) {
-        if (getCGroupSubsys(tid, "cpuset", group) < 0) return -1;
+        if (getCGroupSubsys(tid, "cpuset", group) < 0) {
+            LOG(ERROR) << "Failed to find cpuset cgroup for tid " << tid;
+            return -1;
+        }
     }
 
     // TODO: replace hardcoded directories
@@ -258,7 +264,7 @@
      */
     static constexpr const char* kSchedProfiles[SP_CNT + 1] = {
             "SCHED_SP_DEFAULT", "SCHED_SP_BACKGROUND", "SCHED_SP_FOREGROUND",
-            "SCHED_SP_DEFAULT", "SCHED_SP_FOREGROUND", "SCHED_SP_FOREGROUND",
+            "SCHED_SP_SYSTEM",  "SCHED_SP_FOREGROUND", "SCHED_SP_FOREGROUND",
             "SCHED_SP_TOP_APP", "SCHED_SP_RT_APP",     "SCHED_SP_DEFAULT"};
     if (policy < SP_DEFAULT || policy >= SP_CNT) {
         return nullptr;
diff --git a/libprocessgroup/setup/Android.bp b/libprocessgroup/setup/Android.bp
index f6fc066..ea6c247 100644
--- a/libprocessgroup/setup/Android.bp
+++ b/libprocessgroup/setup/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_shared {
     name: "libprocessgroup_setup",
     recovery_available: true,
diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp
index 25f16a6..3121d24 100644
--- a/libprocessgroup/setup/cgroup_map_write.cpp
+++ b/libprocessgroup/setup/cgroup_map_write.cpp
@@ -45,7 +45,7 @@
 
 #include "cgroup_descriptor.h"
 
-using android::base::GetBoolProperty;
+using android::base::GetUintProperty;
 using android::base::StringPrintf;
 using android::base::unique_fd;
 
@@ -55,6 +55,8 @@
 static constexpr const char* CGROUPS_DESC_FILE = "/etc/cgroups.json";
 static constexpr const char* CGROUPS_DESC_VENDOR_FILE = "/vendor/etc/cgroups.json";
 
+static constexpr const char* TEMPLATE_CGROUPS_DESC_API_FILE = "/etc/task_profiles/cgroups_%u.json";
+
 static bool ChangeDirModeAndOwner(const std::string& path, mode_t mode, const std::string& uid,
                                   const std::string& gid, bool permissive_mode = false) {
     uid_t pw_uid = -1;
@@ -159,6 +161,10 @@
         controller_flags |= CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION;
     }
 
+    if (cgroup["Optional"].isBool() && cgroup["Optional"].asBool()) {
+        controller_flags |= CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+    }
+
     CgroupDescriptor descriptor(
             cgroups_version, name, path, std::strtoul(cgroup["Mode"].asString().c_str(), 0, 8),
             cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags);
@@ -181,10 +187,12 @@
         return false;
     }
 
-    Json::Reader reader;
+    Json::CharReaderBuilder builder;
+    std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
     Json::Value root;
-    if (!reader.parse(json_doc, root)) {
-        LOG(ERROR) << "Failed to parse cgroups description: " << reader.getFormattedErrorMessages();
+    std::string errorMessage;
+    if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), &root, &errorMessage)) {
+        LOG(ERROR) << "Failed to parse cgroups description: " << errorMessage;
         return false;
     }
 
@@ -217,6 +225,18 @@
         return false;
     }
 
+    // load API-level specific system cgroups descriptors if available
+    unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
+    if (api_level > 0) {
+        std::string api_cgroups_path =
+                android::base::StringPrintf(TEMPLATE_CGROUPS_DESC_API_FILE, api_level);
+        if (!access(api_cgroups_path.c_str(), F_OK) || errno != ENOENT) {
+            if (!ReadDescriptorsFromFile(api_cgroups_path, descriptors)) {
+                return false;
+            }
+        }
+    }
+
     // load vendor cgroup descriptors if the file exists
     if (!access(CGROUPS_DESC_VENDOR_FILE, F_OK) &&
         !ReadDescriptorsFromFile(CGROUPS_DESC_VENDOR_FILE, descriptors)) {
@@ -251,8 +271,6 @@
                                        descriptor.gid())) {
                 LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup";
                 result = -1;
-            } else {
-                LOG(ERROR) << "restored ownership for " << controller->name() << " cgroup";
             }
         } else {
             if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) {
@@ -294,8 +312,15 @@
     }
 
     if (result < 0) {
-        PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
-        return false;
+        bool optional = controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL;
+
+        if (optional && errno == EINVAL) {
+            // Optional controllers are allowed to fail to mount if kernel does not support them
+            LOG(INFO) << "Optional " << controller->name() << " cgroup controller is not mounted";
+        } else {
+            PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup";
+            return false;
+        }
     }
 
     return true;
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 4e767db..cf74e65 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -23,6 +23,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/threads.h>
@@ -38,13 +39,17 @@
 #endif
 
 using android::base::GetThreadId;
+using android::base::GetUintProperty;
 using android::base::StringPrintf;
 using android::base::StringReplace;
 using android::base::unique_fd;
 using android::base::WriteStringToFile;
 
-#define TASK_PROFILE_DB_FILE "/etc/task_profiles.json"
-#define TASK_PROFILE_DB_VENDOR_FILE "/vendor/etc/task_profiles.json"
+static constexpr const char* TASK_PROFILE_DB_FILE = "/etc/task_profiles.json";
+static constexpr const char* TASK_PROFILE_DB_VENDOR_FILE = "/vendor/etc/task_profiles.json";
+
+static constexpr const char* TEMPLATE_TASK_PROFILE_API_FILE =
+        "/etc/task_profiles/task_profiles_%u.json";
 
 void ProfileAttribute::Reset(const CgroupController& controller, const std::string& file_name) {
     controller_ = controller;
@@ -268,7 +273,7 @@
     value = StringReplace(value, "<pid>", std::to_string(pid), true);
 
     if (!WriteStringToFile(value, filepath)) {
-        PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+        if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
         return false;
     }
 
@@ -285,7 +290,7 @@
     value = StringReplace(value, "<pid>", std::to_string(tid), true);
 
     if (!WriteStringToFile(value, filepath)) {
-        PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+        if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
         return false;
     }
 
@@ -303,9 +308,7 @@
 
 bool ApplyProfileAction::ExecuteForTask(int tid) const {
     for (const auto& profile : profiles_) {
-        if (!profile->ExecuteForTask(tid)) {
-            PLOG(WARNING) << "ExecuteForTask failed for aggregate profile";
-        }
+        profile->ExecuteForTask(tid);
     }
     return true;
 }
@@ -391,6 +394,19 @@
         LOG(ERROR) << "Loading " << TASK_PROFILE_DB_FILE << " for [" << getpid() << "] failed";
     }
 
+    // load API-level specific system task profiles if available
+    unsigned int api_level = GetUintProperty<unsigned int>("ro.product.first_api_level", 0);
+    if (api_level > 0) {
+        std::string api_profiles_path =
+                android::base::StringPrintf(TEMPLATE_TASK_PROFILE_API_FILE, api_level);
+        if (!access(api_profiles_path.c_str(), F_OK) || errno != ENOENT) {
+            if (!Load(CgroupMap::GetInstance(), api_profiles_path)) {
+                LOG(ERROR) << "Loading " << api_profiles_path << " for [" << getpid()
+                           << "] failed";
+            }
+        }
+    }
+
     // load vendor task profiles if the file exists
     if (!access(TASK_PROFILE_DB_VENDOR_FILE, F_OK) &&
         !Load(CgroupMap::GetInstance(), TASK_PROFILE_DB_VENDOR_FILE)) {
@@ -407,10 +423,12 @@
         return false;
     }
 
-    Json::Reader reader;
+    Json::CharReaderBuilder builder;
+    std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
     Json::Value root;
-    if (!reader.parse(json_doc, root)) {
-        LOG(ERROR) << "Failed to parse task profiles: " << reader.getFormattedErrorMessages();
+    std::string errorMessage;
+    if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), &root, &errorMessage)) {
+        LOG(ERROR) << "Failed to parse task profiles: " << errorMessage;
         return false;
     }
 
@@ -498,7 +516,10 @@
                 std::string attr_filepath = params_val["FilePath"].asString();
                 std::string attr_value = params_val["Value"].asString();
                 if (!attr_filepath.empty() && !attr_value.empty()) {
-                    profile->Add(std::make_unique<WriteFileAction>(attr_filepath, attr_value));
+                    std::string attr_logfailures = params_val["LogFailures"].asString();
+                    bool logfailures = attr_logfailures.empty() || attr_logfailures == "true";
+                    profile->Add(std::make_unique<WriteFileAction>(attr_filepath, attr_value,
+                                                                   logfailures));
                 } else if (attr_filepath.empty()) {
                     LOG(WARNING) << "WriteFile: invalid parameter: "
                                  << "empty filepath";
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 98bcb0e..25a84b0 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -142,14 +142,16 @@
 // Write to file action
 class WriteFileAction : public ProfileAction {
   public:
-    WriteFileAction(const std::string& filepath, const std::string& value) noexcept
-        : filepath_(filepath), value_(value) {}
+    WriteFileAction(const std::string& filepath, const std::string& value,
+                    bool logfailures) noexcept
+        : filepath_(filepath), value_(value), logfailures_(logfailures) {}
 
     virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
     virtual bool ExecuteForTask(int tid) const;
 
   private:
     std::string filepath_, value_;
+    bool logfailures_;
 };
 
 class TaskProfile {
diff --git a/libqtaguid/Android.bp b/libqtaguid/Android.bp
index de632ca..64db095 100644
--- a/libqtaguid/Android.bp
+++ b/libqtaguid/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_headers {
     name: "libqtaguid_headers",
     vendor_available: false,
diff --git a/libsparse/Android.bp b/libsparse/Android.bp
index bf06bbc..0b4b640 100644
--- a/libsparse/Android.bp
+++ b/libsparse/Android.bp
@@ -1,9 +1,14 @@
 // Copyright 2010 The Android Open Source Project
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library {
     name: "libsparse",
     host_supported: true,
     ramdisk_available: true,
+    vendor_ramdisk_available: true,
     recovery_available: true,
     unique_host_soname: true,
     vendor_available: true,
@@ -27,6 +32,10 @@
             enabled: true,
         },
     },
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
 }
 
 cc_binary {
diff --git a/libstats/OWNERS b/libstats/OWNERS
index 7855774..d391679 100644
--- a/libstats/OWNERS
+++ b/libstats/OWNERS
@@ -1,6 +1,7 @@
-joeo@google.com
+jeffreyhuang@google.com
+jtnguyen@google.com
 muhammadq@google.com
-ruchirr@google.com
+sharaienko@google.com
 singhtejinder@google.com
 tsaichristine@google.com
 yaochen@google.com
diff --git a/libstats/pull/Android.bp b/libstats/pull/Android.bp
deleted file mode 100644
index a8b4a4f..0000000
--- a/libstats/pull/Android.bp
+++ /dev/null
@@ -1,110 +0,0 @@
-//
-// Copyright (C) 2019 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.
-//
-
-// ==========================================================
-// Native library to register a pull atom callback with statsd
-// ==========================================================
-cc_defaults {
-    name: "libstatspull_defaults",
-    srcs: [
-        "stats_pull_atom_callback.cpp",
-    ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-    export_include_dirs: ["include"],
-    shared_libs: [
-        "libbinder_ndk",
-        "liblog",
-        "libstatssocket",
-    ],
-    static_libs: [
-        "libutils",
-        "statsd-aidl-ndk_platform",
-    ],
-}
-cc_library_shared {
-    name: "libstatspull",
-    defaults: [
-        "libstatspull_defaults"
-    ],
-    // enumerate stable entry points for APEX use
-    stubs: {
-        symbol_file: "libstatspull.map.txt",
-        versions: [
-            "30",
-        ],
-    },
-    apex_available: [
-        "com.android.os.statsd",
-        "test_com.android.os.statsd",
-    ],
-
-    stl: "libc++_static",
-
-    // TODO(b/151102177): Enable it when the build error is fixed.
-    header_abi_checker: {
-        enabled: false,
-    },
-}
-
-// ONLY USE IN TESTS.
-cc_library_static {
-    name: "libstatspull_private",
-    defaults: [
-        "libstatspull_defaults",
-    ],
-    visibility: [
-        "//frameworks/base/apex/statsd/tests/libstatspull",
-    ],
-}
-
-// Note: These unit tests only test PullAtomMetadata.
-// For full E2E tests of libstatspull, use LibStatsPullTests
-cc_test {
-    name: "libstatspull_test",
-    srcs: [
-        "tests/pull_atom_metadata_test.cpp",
-    ],
-    shared_libs: [
-        "libstatspull",
-        "libstatssocket",
-    ],
-    test_suites: ["general-tests", "mts"],
-    test_config: "libstatspull_test.xml",
-
-    //TODO(b/153588990): Remove when the build system properly separates 
-    //32bit and 64bit architectures.
-    compile_multilib: "both",
-    multilib: {
-        lib64: {
-            suffix: "64",
-        },
-        lib32: {
-            suffix: "32",
-        },
-    },
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wno-missing-field-initializers",
-        "-Wno-unused-variable",
-        "-Wno-unused-function",
-        "-Wno-unused-parameter",
-    ],
-    require_root: true,
-}
diff --git a/libstats/pull/OWNERS b/libstats/pull/OWNERS
deleted file mode 100644
index 7855774..0000000
--- a/libstats/pull/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-joeo@google.com
-muhammadq@google.com
-ruchirr@google.com
-singhtejinder@google.com
-tsaichristine@google.com
-yaochen@google.com
-yro@google.com
diff --git a/libstats/pull/TEST_MAPPING b/libstats/pull/TEST_MAPPING
deleted file mode 100644
index 76f4f02..0000000
--- a/libstats/pull/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit" : [
-    {
-      "name" : "libstatspull_test"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/libstats/pull/include/stats_pull_atom_callback.h b/libstats/pull/include/stats_pull_atom_callback.h
deleted file mode 100644
index 17df584..0000000
--- a/libstats/pull/include/stats_pull_atom_callback.h
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2019, 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 <stats_event.h>
-
-#include <stdbool.h>
-#include <stdint.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * Opaque struct representing the metadata for registering an AStatsManager_PullAtomCallback.
- */
-struct AStatsManager_PullAtomMetadata;
-typedef struct AStatsManager_PullAtomMetadata AStatsManager_PullAtomMetadata;
-
-/**
- * Allocate and initialize new PullAtomMetadata.
- *
- * Must call AStatsManager_PullAtomMetadata_release to free the memory.
- */
-AStatsManager_PullAtomMetadata* AStatsManager_PullAtomMetadata_obtain();
-
-/**
- * Frees the memory held by this PullAtomMetadata
- *
- * After calling this, the PullAtomMetadata must not be used or modified in any way.
- */
-void AStatsManager_PullAtomMetadata_release(AStatsManager_PullAtomMetadata* metadata);
-
-/**
- * Set the cool down time of the pull in milliseconds. If two successive pulls are issued
- * within the cool down, a cached version of the first will be used for the second. The minimum
- * allowed cool down is one second.
- */
-void AStatsManager_PullAtomMetadata_setCoolDownMillis(AStatsManager_PullAtomMetadata* metadata,
-                                                      int64_t cool_down_millis);
-
-/**
- * Get the cool down time of the pull in milliseconds.
- */
-int64_t AStatsManager_PullAtomMetadata_getCoolDownMillis(AStatsManager_PullAtomMetadata* metadata);
-
-/**
- * Set the maximum time the pull can take in milliseconds.
- * The maximum allowed timeout is 10 seconds.
- */
-void AStatsManager_PullAtomMetadata_setTimeoutMillis(AStatsManager_PullAtomMetadata* metadata,
-                                                     int64_t timeout_millis);
-
-/**
- * Get the maximum time the pull can take in milliseconds.
- */
-int64_t AStatsManager_PullAtomMetadata_getTimeoutMillis(AStatsManager_PullAtomMetadata* metadata);
-
-/**
- * Set the additive fields of this pulled atom.
- *
- * This is only applicable for atoms which have a uid field. When tasks are run in
- * isolated processes, the data will be attributed to the host uid. Additive fields
- * will be combined when the non-additive fields are the same.
- */
-void AStatsManager_PullAtomMetadata_setAdditiveFields(AStatsManager_PullAtomMetadata* metadata,
-                                                      int32_t* additive_fields, int32_t num_fields);
-
-/**
- * Get the number of additive fields for this pulled atom. This is intended to be called before
- * AStatsManager_PullAtomMetadata_getAdditiveFields to determine the size of the array.
- */
-int32_t AStatsManager_PullAtomMetadata_getNumAdditiveFields(
-        AStatsManager_PullAtomMetadata* metadata);
-
-/**
- * Get the additive fields of this pulled atom.
- *
- * \param fields an output parameter containing the additive fields for this PullAtomMetadata.
- *               Fields is an array and it is assumed that it is at least as large as the number of
- *               additive fields, which can be obtained by calling
- *               AStatsManager_PullAtomMetadata_getNumAdditiveFields.
- */
-void AStatsManager_PullAtomMetadata_getAdditiveFields(AStatsManager_PullAtomMetadata* metadata,
-                                                      int32_t* fields);
-
-/**
- * Return codes for the result of a pull.
- */
-typedef int32_t AStatsManager_PullAtomCallbackReturn;
-enum {
-    // Value indicating that this pull was successful and that the result should be used.
-    AStatsManager_PULL_SUCCESS = 0,
-    // Value indicating that this pull was unsuccessful and that the result should not be used.
-    AStatsManager_PULL_SKIP = 1,
-};
-
-/**
- * Opaque struct representing a list of AStatsEvent objects.
- */
-struct AStatsEventList;
-typedef struct AStatsEventList AStatsEventList;
-
-/**
- * Appends and returns an AStatsEvent to the end of the AStatsEventList.
- *
- * If an AStatsEvent is obtained in this manner, the memory is internally managed and
- * AStatsEvent_release does not need to be called. The lifetime of the AStatsEvent is that of the
- * AStatsEventList.
- *
- * The AStatsEvent does still need to be built by calling AStatsEvent_build.
- */
-AStatsEvent* AStatsEventList_addStatsEvent(AStatsEventList* pull_data);
-
-/**
- * Callback interface for pulling atoms requested by the stats service.
- *
- * \param atom_tag the tag of the atom to pull.
- * \param data an output parameter in which the caller should fill the results of the pull. This
- *             param cannot be NULL and it's lifetime is as long as the execution of the callback.
- *             It must not be accessed or modified after returning from the callback.
- * \param cookie the opaque pointer passed in AStatsManager_registerPullAtomCallback.
- * \return AStatsManager_PULL_SUCCESS if the pull was successful, or AStatsManager_PULL_SKIP if not.
- */
-typedef AStatsManager_PullAtomCallbackReturn (*AStatsManager_PullAtomCallback)(
-        int32_t atom_tag, AStatsEventList* data, void* cookie);
-/**
- * Sets a callback for an atom when that atom is to be pulled. The stats service will
- * invoke the callback when the stats service determines that this atom needs to be
- * pulled.
- *
- * Requires the REGISTER_STATS_PULL_ATOM permission.
- *
- * \param atom_tag          The tag of the atom for this pull atom callback.
- * \param metadata          Optional metadata specifying the timeout, cool down time, and
- *                          additive fields for mapping isolated to host uids.
- *                          This param is nullable, in which case defaults will be used.
- * \param callback          The callback to be invoked when the stats service pulls the atom.
- * \param cookie            A pointer that will be passed back to the callback.
- *                          It has no meaning to statsd.
- */
-void AStatsManager_setPullAtomCallback(int32_t atom_tag, AStatsManager_PullAtomMetadata* metadata,
-                                       AStatsManager_PullAtomCallback callback, void* cookie);
-
-/**
- * Clears a callback for an atom when that atom is to be pulled. Note that any ongoing
- * pulls will still occur.
- *
- * Requires the REGISTER_STATS_PULL_ATOM permission.
- *
- * \param atomTag           The tag of the atom of which to unregister
- */
-void AStatsManager_clearPullAtomCallback(int32_t atom_tag);
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/libstats/pull/libstatspull.map.txt b/libstats/pull/libstatspull.map.txt
deleted file mode 100644
index e0e851a..0000000
--- a/libstats/pull/libstatspull.map.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-LIBSTATSPULL {
-    global:
-        AStatsManager_PullAtomMetadata_obtain; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_release; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_setCoolDownMillis; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_getCoolDownMillis; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_setTimeoutMillis; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_getTimeoutMillis; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_setAdditiveFields; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_getNumAdditiveFields; # apex # introduced=30
-        AStatsManager_PullAtomMetadata_getAdditiveFields; # apex # introduced=30
-        AStatsEventList_addStatsEvent; # apex # introduced=30
-        AStatsManager_setPullAtomCallback; # apex # introduced=30
-        AStatsManager_clearPullAtomCallback; # apex # introduced=30
-    local:
-        *;
-};
diff --git a/libstats/pull/stats_pull_atom_callback.cpp b/libstats/pull/stats_pull_atom_callback.cpp
deleted file mode 100644
index 478cae7..0000000
--- a/libstats/pull/stats_pull_atom_callback.cpp
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2019, 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 <map>
-#include <thread>
-#include <vector>
-
-#include <stats_event.h>
-#include <stats_pull_atom_callback.h>
-
-#include <aidl/android/os/BnPullAtomCallback.h>
-#include <aidl/android/os/IPullAtomResultReceiver.h>
-#include <aidl/android/os/IStatsd.h>
-#include <aidl/android/util/StatsEventParcel.h>
-#include <android/binder_auto_utils.h>
-#include <android/binder_ibinder.h>
-#include <android/binder_manager.h>
-
-using Status = ::ndk::ScopedAStatus;
-using aidl::android::os::BnPullAtomCallback;
-using aidl::android::os::IPullAtomResultReceiver;
-using aidl::android::os::IStatsd;
-using aidl::android::util::StatsEventParcel;
-using ::ndk::SharedRefBase;
-
-struct AStatsEventList {
-    std::vector<AStatsEvent*> data;
-};
-
-AStatsEvent* AStatsEventList_addStatsEvent(AStatsEventList* pull_data) {
-    AStatsEvent* event = AStatsEvent_obtain();
-    pull_data->data.push_back(event);
-    return event;
-}
-
-static const int64_t DEFAULT_COOL_DOWN_MILLIS = 1000LL;  // 1 second.
-static const int64_t DEFAULT_TIMEOUT_MILLIS = 2000LL;    // 2 seconds.
-
-struct AStatsManager_PullAtomMetadata {
-    int64_t cool_down_millis;
-    int64_t timeout_millis;
-    std::vector<int32_t> additive_fields;
-};
-
-AStatsManager_PullAtomMetadata* AStatsManager_PullAtomMetadata_obtain() {
-    AStatsManager_PullAtomMetadata* metadata = new AStatsManager_PullAtomMetadata();
-    metadata->cool_down_millis = DEFAULT_COOL_DOWN_MILLIS;
-    metadata->timeout_millis = DEFAULT_TIMEOUT_MILLIS;
-    metadata->additive_fields = std::vector<int32_t>();
-    return metadata;
-}
-
-void AStatsManager_PullAtomMetadata_release(AStatsManager_PullAtomMetadata* metadata) {
-    delete metadata;
-}
-
-void AStatsManager_PullAtomMetadata_setCoolDownMillis(AStatsManager_PullAtomMetadata* metadata,
-                                                      int64_t cool_down_millis) {
-    metadata->cool_down_millis = cool_down_millis;
-}
-
-int64_t AStatsManager_PullAtomMetadata_getCoolDownMillis(AStatsManager_PullAtomMetadata* metadata) {
-    return metadata->cool_down_millis;
-}
-
-void AStatsManager_PullAtomMetadata_setTimeoutMillis(AStatsManager_PullAtomMetadata* metadata,
-                                                     int64_t timeout_millis) {
-    metadata->timeout_millis = timeout_millis;
-}
-
-int64_t AStatsManager_PullAtomMetadata_getTimeoutMillis(AStatsManager_PullAtomMetadata* metadata) {
-    return metadata->timeout_millis;
-}
-
-void AStatsManager_PullAtomMetadata_setAdditiveFields(AStatsManager_PullAtomMetadata* metadata,
-                                                      int32_t* additive_fields,
-                                                      int32_t num_fields) {
-    metadata->additive_fields.assign(additive_fields, additive_fields + num_fields);
-}
-
-int32_t AStatsManager_PullAtomMetadata_getNumAdditiveFields(
-        AStatsManager_PullAtomMetadata* metadata) {
-    return metadata->additive_fields.size();
-}
-
-void AStatsManager_PullAtomMetadata_getAdditiveFields(AStatsManager_PullAtomMetadata* metadata,
-                                                      int32_t* fields) {
-    std::copy(metadata->additive_fields.begin(), metadata->additive_fields.end(), fields);
-}
-
-class StatsPullAtomCallbackInternal : public BnPullAtomCallback {
-  public:
-    StatsPullAtomCallbackInternal(const AStatsManager_PullAtomCallback callback, void* cookie,
-                                  const int64_t coolDownMillis, const int64_t timeoutMillis,
-                                  const std::vector<int32_t> additiveFields)
-        : mCallback(callback),
-          mCookie(cookie),
-          mCoolDownMillis(coolDownMillis),
-          mTimeoutMillis(timeoutMillis),
-          mAdditiveFields(additiveFields) {}
-
-    Status onPullAtom(int32_t atomTag,
-                      const std::shared_ptr<IPullAtomResultReceiver>& resultReceiver) override {
-        AStatsEventList statsEventList;
-        int successInt = mCallback(atomTag, &statsEventList, mCookie);
-        bool success = successInt == AStatsManager_PULL_SUCCESS;
-
-        // Convert stats_events into StatsEventParcels.
-        std::vector<StatsEventParcel> parcels;
-        for (int i = 0; i < statsEventList.data.size(); i++) {
-            size_t size;
-            uint8_t* buffer = AStatsEvent_getBuffer(statsEventList.data[i], &size);
-
-            StatsEventParcel p;
-            // vector.assign() creates a copy, but this is inevitable unless
-            // stats_event.h/c uses a vector as opposed to a buffer.
-            p.buffer.assign(buffer, buffer + size);
-            parcels.push_back(std::move(p));
-        }
-
-        Status status = resultReceiver->pullFinished(atomTag, success, parcels);
-        if (!status.isOk()) {
-            std::vector<StatsEventParcel> emptyParcels;
-            resultReceiver->pullFinished(atomTag, /*success=*/false, emptyParcels);
-        }
-        for (int i = 0; i < statsEventList.data.size(); i++) {
-            AStatsEvent_release(statsEventList.data[i]);
-        }
-        return Status::ok();
-    }
-
-    int64_t getCoolDownMillis() const { return mCoolDownMillis; }
-    int64_t getTimeoutMillis() const { return mTimeoutMillis; }
-    const std::vector<int32_t>& getAdditiveFields() const { return mAdditiveFields; }
-
-  private:
-    const AStatsManager_PullAtomCallback mCallback;
-    void* mCookie;
-    const int64_t mCoolDownMillis;
-    const int64_t mTimeoutMillis;
-    const std::vector<int32_t> mAdditiveFields;
-};
-
-static std::mutex pullAtomMutex;
-static std::shared_ptr<IStatsd> sStatsd = nullptr;
-
-static std::map<int32_t, std::shared_ptr<StatsPullAtomCallbackInternal>> mPullers;
-static std::shared_ptr<IStatsd> getStatsService();
-
-static void binderDied(void* /*cookie*/) {
-    {
-        std::lock_guard<std::mutex> lock(pullAtomMutex);
-        sStatsd = nullptr;
-    }
-
-    std::shared_ptr<IStatsd> statsService = getStatsService();
-    if (statsService == nullptr) {
-        return;
-    }
-
-    // Since we do not want to make an IPC with the lock held, we first create a
-    // copy of the data with the lock held before iterating through the map.
-    std::map<int32_t, std::shared_ptr<StatsPullAtomCallbackInternal>> pullersCopy;
-    {
-        std::lock_guard<std::mutex> lock(pullAtomMutex);
-        pullersCopy = mPullers;
-    }
-    for (const auto& it : pullersCopy) {
-        statsService->registerNativePullAtomCallback(it.first, it.second->getCoolDownMillis(),
-                                                     it.second->getTimeoutMillis(),
-                                                     it.second->getAdditiveFields(), it.second);
-    }
-}
-
-static ::ndk::ScopedAIBinder_DeathRecipient sDeathRecipient(
-        AIBinder_DeathRecipient_new(binderDied));
-
-static std::shared_ptr<IStatsd> getStatsService() {
-    std::lock_guard<std::mutex> lock(pullAtomMutex);
-    if (!sStatsd) {
-        // Fetch statsd
-        ::ndk::SpAIBinder binder(AServiceManager_getService("stats"));
-        sStatsd = IStatsd::fromBinder(binder);
-        if (sStatsd) {
-            AIBinder_linkToDeath(binder.get(), sDeathRecipient.get(), /*cookie=*/nullptr);
-        }
-    }
-    return sStatsd;
-}
-
-void registerStatsPullAtomCallbackBlocking(int32_t atomTag,
-                                           std::shared_ptr<StatsPullAtomCallbackInternal> cb) {
-    const std::shared_ptr<IStatsd> statsService = getStatsService();
-    if (statsService == nullptr) {
-        // Statsd not available
-        return;
-    }
-
-    statsService->registerNativePullAtomCallback(
-            atomTag, cb->getCoolDownMillis(), cb->getTimeoutMillis(), cb->getAdditiveFields(), cb);
-}
-
-void unregisterStatsPullAtomCallbackBlocking(int32_t atomTag) {
-    const std::shared_ptr<IStatsd> statsService = getStatsService();
-    if (statsService == nullptr) {
-        // Statsd not available
-        return;
-    }
-
-    statsService->unregisterNativePullAtomCallback(atomTag);
-}
-
-void AStatsManager_setPullAtomCallback(int32_t atom_tag, AStatsManager_PullAtomMetadata* metadata,
-                                       AStatsManager_PullAtomCallback callback, void* cookie) {
-    int64_t coolDownMillis =
-            metadata == nullptr ? DEFAULT_COOL_DOWN_MILLIS : metadata->cool_down_millis;
-    int64_t timeoutMillis = metadata == nullptr ? DEFAULT_TIMEOUT_MILLIS : metadata->timeout_millis;
-
-    std::vector<int32_t> additiveFields;
-    if (metadata != nullptr) {
-        additiveFields = metadata->additive_fields;
-    }
-
-    std::shared_ptr<StatsPullAtomCallbackInternal> callbackBinder =
-            SharedRefBase::make<StatsPullAtomCallbackInternal>(callback, cookie, coolDownMillis,
-                                                               timeoutMillis, additiveFields);
-
-    {
-        std::lock_guard<std::mutex> lg(pullAtomMutex);
-        // Always add to the map. If statsd is dead, we will add them when it comes back.
-        mPullers[atom_tag] = callbackBinder;
-    }
-
-    std::thread registerThread(registerStatsPullAtomCallbackBlocking, atom_tag, callbackBinder);
-    registerThread.detach();
-}
-
-void AStatsManager_clearPullAtomCallback(int32_t atom_tag) {
-    {
-        std::lock_guard<std::mutex> lg(pullAtomMutex);
-        // Always remove the puller from our map.
-        // If statsd is down, we will not register it when it comes back.
-        mPullers.erase(atom_tag);
-    }
-    std::thread unregisterThread(unregisterStatsPullAtomCallbackBlocking, atom_tag);
-    unregisterThread.detach();
-}
diff --git a/libstats/pull/tests/pull_atom_metadata_test.cpp b/libstats/pull/tests/pull_atom_metadata_test.cpp
deleted file mode 100644
index abc8e47..0000000
--- a/libstats/pull/tests/pull_atom_metadata_test.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2020, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-
-#include <stats_pull_atom_callback.h>
-
-namespace {
-
-static const int64_t DEFAULT_COOL_DOWN_MILLIS = 1000LL;  // 1 second.
-static const int64_t DEFAULT_TIMEOUT_MILLIS = 2000LL;    // 2 seconds.
-
-}  // anonymous namespace
-
-TEST(AStatsManager_PullAtomMetadataTest, TestEmpty) {
-    AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain();
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getCoolDownMillis(metadata), DEFAULT_COOL_DOWN_MILLIS);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getTimeoutMillis(metadata), DEFAULT_TIMEOUT_MILLIS);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getNumAdditiveFields(metadata), 0);
-    AStatsManager_PullAtomMetadata_release(metadata);
-}
-
-TEST(AStatsManager_PullAtomMetadataTest, TestSetTimeoutMillis) {
-    int64_t timeoutMillis = 500;
-    AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain();
-    AStatsManager_PullAtomMetadata_setTimeoutMillis(metadata, timeoutMillis);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getCoolDownMillis(metadata), DEFAULT_COOL_DOWN_MILLIS);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getTimeoutMillis(metadata), timeoutMillis);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getNumAdditiveFields(metadata), 0);
-    AStatsManager_PullAtomMetadata_release(metadata);
-}
-
-TEST(AStatsManager_PullAtomMetadataTest, TestSetCoolDownMillis) {
-    int64_t coolDownMillis = 10000;
-    AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain();
-    AStatsManager_PullAtomMetadata_setCoolDownMillis(metadata, coolDownMillis);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getCoolDownMillis(metadata), coolDownMillis);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getTimeoutMillis(metadata), DEFAULT_TIMEOUT_MILLIS);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getNumAdditiveFields(metadata), 0);
-    AStatsManager_PullAtomMetadata_release(metadata);
-}
-
-TEST(AStatsManager_PullAtomMetadataTest, TestSetAdditiveFields) {
-    const int numFields = 3;
-    int inputFields[numFields] = {2, 4, 6};
-    AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain();
-    AStatsManager_PullAtomMetadata_setAdditiveFields(metadata, inputFields, numFields);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getCoolDownMillis(metadata), DEFAULT_COOL_DOWN_MILLIS);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getTimeoutMillis(metadata), DEFAULT_TIMEOUT_MILLIS);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getNumAdditiveFields(metadata), numFields);
-    int outputFields[numFields];
-    AStatsManager_PullAtomMetadata_getAdditiveFields(metadata, outputFields);
-    for (int i = 0; i < numFields; i++) {
-        EXPECT_EQ(inputFields[i], outputFields[i]);
-    }
-    AStatsManager_PullAtomMetadata_release(metadata);
-}
-
-TEST(AStatsManager_PullAtomMetadataTest, TestSetAllElements) {
-    int64_t timeoutMillis = 500;
-    int64_t coolDownMillis = 10000;
-    const int numFields = 3;
-    int inputFields[numFields] = {2, 4, 6};
-
-    AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain();
-    AStatsManager_PullAtomMetadata_setTimeoutMillis(metadata, timeoutMillis);
-    AStatsManager_PullAtomMetadata_setCoolDownMillis(metadata, coolDownMillis);
-    AStatsManager_PullAtomMetadata_setAdditiveFields(metadata, inputFields, numFields);
-
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getCoolDownMillis(metadata), coolDownMillis);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getTimeoutMillis(metadata), timeoutMillis);
-    EXPECT_EQ(AStatsManager_PullAtomMetadata_getNumAdditiveFields(metadata), numFields);
-    int outputFields[numFields];
-    AStatsManager_PullAtomMetadata_getAdditiveFields(metadata, outputFields);
-    for (int i = 0; i < numFields; i++) {
-        EXPECT_EQ(inputFields[i], outputFields[i]);
-    }
-    AStatsManager_PullAtomMetadata_release(metadata);
-}
diff --git a/libstats/pull_lazy/Android.bp b/libstats/pull_lazy/Android.bp
new file mode 100644
index 0000000..65dce26
--- /dev/null
+++ b/libstats/pull_lazy/Android.bp
@@ -0,0 +1,48 @@
+// Lazy loading version of libstatspull that can be used by code
+// that is running before the statsd APEX is mounted and
+// libstatspull.so is available.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libstatspull_lazy",
+    header_libs: [
+        "libstatspull_headers",
+        "libstatssocket_headers",
+    ],
+    export_header_lib_headers: [
+        "libstatspull_headers",
+    ],
+    apex_available: ["//apex_available:platform"],
+    srcs: ["libstatspull_lazy.cpp"],
+}
+
+cc_test {
+    name: "libstatspull_lazy_test",
+    srcs: [
+        "tests/libstatspull_lazy_test.cpp",
+    ],
+    static_libs: [
+        "libstatspull_lazy",
+        "libstatssocket_lazy",
+    ],
+    shared_libs: ["liblog"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    test_suites: ["device-tests", "mts-statsd"],
+    test_config: "libstatspull_lazy_test.xml",
+    // TODO(b/153588990): Remove when the build system properly separates.
+    // 32bit and 64bit architectures.
+    compile_multilib: "both",
+    multilib: {
+        lib64: {
+            suffix: "64",
+        },
+        lib32: {
+            suffix: "32",
+        },
+    },
+}
diff --git a/libstats/pull_lazy/TEST_MAPPING b/libstats/pull_lazy/TEST_MAPPING
new file mode 100644
index 0000000..89b8c2a
--- /dev/null
+++ b/libstats/pull_lazy/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit" : [
+    {
+      "name" : "libstatspull_lazy_test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/libstats/pull_lazy/libstatspull_lazy.cpp b/libstats/pull_lazy/libstatspull_lazy.cpp
new file mode 100644
index 0000000..b11fcee
--- /dev/null
+++ b/libstats/pull_lazy/libstatspull_lazy.cpp
@@ -0,0 +1,190 @@
+/*
+ * 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 "libstatspull_lazy.h"
+
+#include <mutex>
+
+#include <dlfcn.h>
+#include <stdatomic.h>
+
+#include "log/log.h"
+
+#include "stats_pull_atom_callback.h"
+
+// This file provides a lazy interface to libstatspull.so to address early boot dependencies.
+// Specifically bootanimation, surfaceflinger, and lmkd run before the statsd APEX is loaded and
+// libstatspull.so is in the statsd APEX.
+
+// Method pointers to libstatspull methods are held in an array which simplifies checking
+// all pointers are initialized.
+enum MethodIndex {
+    // PullAtomMetadata APIs in stats_pull_atom_callback.h.
+    k_AStatsManager_PullAtomMetadata_obtain,
+    k_AStatsManager_PullAtomMetadata_release,
+    k_AStatsManager_PullAtomMetadata_setCoolDownMillis,
+    k_AStatsManager_PullAtomMetadata_getCoolDownMillis,
+    k_AStatsManager_PullAtomMetadata_setTimeoutMillis,
+    k_AStatsManager_PullAtomMetadata_getTimeoutMillis,
+    k_AStatsManager_PullAtomMetadata_setAdditiveFields,
+    k_AStatsManager_PullAtomMetadata_getNumAdditiveFields,
+    k_AStatsManager_PullAtomMetadata_getAdditiveFields,
+
+    // AStatsEventList APIs in stats_pull_atom_callback.h
+    k_AStatsEventList_addStatsEvent,
+
+    // PullAtomCallback APIs in stats_pull_atom_callback.h
+    k_AStatsManager_setPullAtomCallback,
+    k_AStatsManager_clearPullAtomCallback,
+
+    // Marker for count of methods
+    k_MethodCount
+};
+
+// Table of methods pointers in libstatspull APIs.
+static void* g_Methods[k_MethodCount];
+
+//
+// Libstatspull lazy loading.
+//
+
+static atomic_bool gPreventLibstatspullLoading = false;  // Allows tests to block loading.
+
+void PreventLibstatspullLazyLoadingForTests() {
+    gPreventLibstatspullLoading.store(true);
+}
+
+static void* LoadLibstatspull(int dlopen_flags) {
+    if (gPreventLibstatspullLoading.load()) {
+        return nullptr;
+    }
+    return dlopen("libstatspull.so", dlopen_flags);
+}
+
+//
+// Initialization and symbol binding.
+
+static void BindSymbol(void* handle, const char* name, enum MethodIndex index) {
+    void* symbol = dlsym(handle, name);
+    LOG_ALWAYS_FATAL_IF(symbol == nullptr, "Failed to find symbol '%s' in libstatspull.so: %s",
+                        name, dlerror());
+    g_Methods[index] = symbol;
+}
+
+static void InitializeOnce() {
+    void* handle = LoadLibstatspull(RTLD_NOW);
+    LOG_ALWAYS_FATAL_IF(handle == nullptr, "Failed to load libstatspull.so: %s", dlerror());
+
+#undef BIND_SYMBOL
+#define BIND_SYMBOL(name) BindSymbol(handle, #name, k_##name);
+    // PullAtomMetadata APIs in stats_pull_atom_callback.h.
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_obtain);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_release);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_setCoolDownMillis);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_getCoolDownMillis);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_setTimeoutMillis);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_getTimeoutMillis);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_setAdditiveFields);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_getNumAdditiveFields);
+    BIND_SYMBOL(AStatsManager_PullAtomMetadata_getAdditiveFields);
+
+    // AStatsEventList APIs in stats_pull_atom_callback.h
+    BIND_SYMBOL(AStatsEventList_addStatsEvent);
+
+    // PullAtomCallback APIs in stats_pull_atom_callback.h
+    BIND_SYMBOL(AStatsManager_setPullAtomCallback);
+    BIND_SYMBOL(AStatsManager_clearPullAtomCallback);
+
+#undef BIND_SYMBOL
+
+    // Check every symbol is bound.
+    for (int i = 0; i < k_MethodCount; ++i) {
+        LOG_ALWAYS_FATAL_IF(g_Methods[i] == nullptr,
+                            "Uninitialized method in libstatspull_lazy at index: %d", i);
+    }
+}
+
+static void EnsureInitialized() {
+    static std::once_flag initialize_flag;
+    std::call_once(initialize_flag, InitializeOnce);
+}
+
+#define INVOKE_METHOD(name, args...)                            \
+    do {                                                        \
+        EnsureInitialized();                                    \
+        void* method = g_Methods[k_##name];                     \
+        return reinterpret_cast<decltype(&name)>(method)(args); \
+    } while (0)
+
+//
+// Forwarding for methods in stats_pull_atom_callback.h.
+//
+
+AStatsManager_PullAtomMetadata* AStatsManager_PullAtomMetadata_obtain() {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_obtain);
+}
+
+void AStatsManager_PullAtomMetadata_release(AStatsManager_PullAtomMetadata* metadata) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_release, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_setCoolDownMillis(AStatsManager_PullAtomMetadata* metadata,
+                                                      int64_t cool_down_millis) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_setCoolDownMillis, metadata, cool_down_millis);
+}
+
+int64_t AStatsManager_PullAtomMetadata_getCoolDownMillis(AStatsManager_PullAtomMetadata* metadata) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_getCoolDownMillis, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_setTimeoutMillis(AStatsManager_PullAtomMetadata* metadata,
+                                                     int64_t timeout_millis) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_setTimeoutMillis, metadata, timeout_millis);
+}
+
+int64_t AStatsManager_PullAtomMetadata_getTimeoutMillis(AStatsManager_PullAtomMetadata* metadata) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_getTimeoutMillis, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_setAdditiveFields(AStatsManager_PullAtomMetadata* metadata,
+                                                      int32_t* additive_fields,
+                                                      int32_t num_fields) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_setAdditiveFields, metadata, additive_fields,
+                  num_fields);
+}
+
+int32_t AStatsManager_PullAtomMetadata_getNumAdditiveFields(
+        AStatsManager_PullAtomMetadata* metadata) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_getNumAdditiveFields, metadata);
+}
+
+void AStatsManager_PullAtomMetadata_getAdditiveFields(AStatsManager_PullAtomMetadata* metadata,
+                                                      int32_t* fields) {
+    INVOKE_METHOD(AStatsManager_PullAtomMetadata_getAdditiveFields, metadata, fields);
+}
+
+AStatsEvent* AStatsEventList_addStatsEvent(AStatsEventList* pull_data) {
+    INVOKE_METHOD(AStatsEventList_addStatsEvent, pull_data);
+}
+
+void AStatsManager_setPullAtomCallback(int32_t atom_tag, AStatsManager_PullAtomMetadata* metadata,
+                                       AStatsManager_PullAtomCallback callback, void* cookie) {
+    INVOKE_METHOD(AStatsManager_setPullAtomCallback, atom_tag, metadata, callback, cookie);
+}
+
+void AStatsManager_clearPullAtomCallback(int32_t atom_tag) {
+    INVOKE_METHOD(AStatsManager_clearPullAtomCallback, atom_tag);
+}
diff --git a/libstats/socket/stats_socket.c b/libstats/pull_lazy/libstatspull_lazy.h
similarity index 75%
copy from libstats/socket/stats_socket.c
copy to libstats/pull_lazy/libstatspull_lazy.h
index 09f8967..2edddc7 100644
--- a/libstats/socket/stats_socket.c
+++ b/libstats/pull_lazy/libstatspull_lazy.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#include "include/stats_socket.h"
-#include "stats_buffer_writer.h"
+#pragma once
 
-void AStatsSocket_close() {
-    stats_log_close();
-}
+extern "C" void PreventLibstatspullLazyLoadingForTests();
\ No newline at end of file
diff --git a/libstats/pull/libstatspull_test.xml b/libstats/pull_lazy/libstatspull_lazy_test.xml
similarity index 77%
rename from libstats/pull/libstatspull_test.xml
rename to libstats/pull_lazy/libstatspull_lazy_test.xml
index 233fc1f..1b619af 100644
--- a/libstats/pull/libstatspull_test.xml
+++ b/libstats/pull_lazy/libstatspull_lazy_test.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!-- 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.
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<configuration description="Runs libstatspull_test.">
+<configuration description="Runs libstatspull_lazy_test.">
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="apct-native" />
     <option name="test-suite-tag" value="mts" />
@@ -21,17 +21,17 @@
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
 
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-       <option name="cleanup" value="true" />
-       <option name="push" value="libstatspull_test->/data/local/tmp/libstatspull_test" />
-       <option name="append-bitness" value="true" />
+        <option name="cleanup" value="true" />
+        <option name="push" value="libstatspull_lazy_test->/data/local/tmp/libstatspull_lazy_test" />
+        <option name="append-bitness" value="true" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="libstatspull_test" />
+        <option name="module-name" value="libstatspull_lazy_test" />
     </test>
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
         <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
     </object>
-</configuration>
+</configuration>
\ No newline at end of file
diff --git a/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp b/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp
new file mode 100644
index 0000000..41f82d0
--- /dev/null
+++ b/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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 "../libstatspull_lazy.h"
+
+#include <gtest/gtest.h>
+
+#include "stats_pull_atom_callback.h"
+//#include "stats_event.h"
+
+// The tests here are just for the case when libstatspull.so cannot be loaded by
+// libstatspull_lazy.
+class LibstatspullLazyTest : public ::testing::Test {
+  protected:
+    virtual void SetUp() {
+        ::testing::Test::SetUp();
+        PreventLibstatspullLazyLoadingForTests();
+    }
+};
+
+static const char* kLoadFailed = "Failed to load libstatspull.so";
+
+TEST_F(LibstatspullLazyTest, NoLibstatspullForPullAtomMetadata) {
+    AStatsManager_PullAtomMetadata* metadata = NULL;
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_obtain(), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_release(metadata), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_setCoolDownMillis(metadata, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_getCoolDownMillis(metadata), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_setTimeoutMillis(metadata, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_getTimeoutMillis(metadata), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_setAdditiveFields(metadata, NULL, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_getNumAdditiveFields(metadata), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_PullAtomMetadata_getAdditiveFields(metadata, NULL), kLoadFailed);
+}
+
+TEST_F(LibstatspullLazyTest, NoLibstatspullForAStatsEventList) {
+    AStatsEventList* event_list = NULL;
+    EXPECT_DEATH(AStatsEventList_addStatsEvent(event_list), kLoadFailed);
+}
+
+TEST_F(LibstatspullLazyTest, NoLibstatspullForPullAtomCallback) {
+    AStatsManager_PullAtomCallback callback = NULL;
+    EXPECT_DEATH(AStatsManager_setPullAtomCallback(0, NULL, callback, NULL), kLoadFailed);
+    EXPECT_DEATH(AStatsManager_clearPullAtomCallback(0), kLoadFailed);
+}
\ No newline at end of file
diff --git a/libstats/pull_rust/Android.bp b/libstats/pull_rust/Android.bp
new file mode 100644
index 0000000..2a89e29
--- /dev/null
+++ b/libstats/pull_rust/Android.bp
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_bindgen {
+    name: "libstatspull_bindgen",
+    wrapper_src: "statslog.h",
+    crate_name: "statspull_bindgen",
+    source_stem: "bindings",
+    bindgen_flags: [
+        "--size_t-is-usize",
+        "--allowlist-function=AStatsEventList_addStatsEvent",
+        "--allowlist-function=AStatsEvent_.*",
+        "--allowlist-function=AStatsManager_.*",
+        "--allowlist-var=AStatsManager_.*",
+    ],
+    target: {
+        android: {
+            shared_libs: [
+                "libstatspull",
+                "libstatssocket",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libstatspull",
+                "libstatssocket",
+            ],
+        },
+    },
+}
+
+rust_library {
+    name: "libstatspull_rust",
+    crate_name: "statspull_rust",
+    srcs: ["stats_pull.rs"],
+    rustlibs: [
+        "liblazy_static",
+        "liblog_rust",
+        "libstatslog_rust_header",
+        "libstatspull_bindgen",
+    ],
+}
diff --git a/libstats/pull_rust/stats_pull.rs b/libstats/pull_rust/stats_pull.rs
new file mode 100644
index 0000000..174125e
--- /dev/null
+++ b/libstats/pull_rust/stats_pull.rs
@@ -0,0 +1,170 @@
+// Copyright 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.
+
+//! A Rust interface for the StatsD pull API.
+
+use lazy_static::lazy_static;
+use statslog_rust_header::{Atoms, Stat, StatsError};
+use statspull_bindgen::*;
+use std::collections::HashMap;
+use std::convert::TryInto;
+use std::os::raw::c_void;
+use std::sync::Mutex;
+
+/// The return value of callbacks.
+pub type StatsPullResult = Vec<Box<dyn Stat>>;
+
+/// A wrapper for AStatsManager_PullAtomMetadata.
+/// It calls AStatsManager_PullAtomMetadata_release on drop.
+pub struct Metadata {
+    metadata: *mut AStatsManager_PullAtomMetadata,
+}
+
+impl Metadata {
+    /// Calls AStatsManager_PullAtomMetadata_obtain.
+    pub fn new() -> Self {
+        // Safety: We panic if the memory allocation fails.
+        let metadata = unsafe { AStatsManager_PullAtomMetadata_obtain() };
+        if metadata.is_null() {
+            panic!("Cannot obtain pull atom metadata.");
+        } else {
+            Metadata { metadata }
+        }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_setCoolDownMillis.
+    pub fn set_cooldown_millis(&mut self, cooldown_millis: i64) {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe { AStatsManager_PullAtomMetadata_setCoolDownMillis(self.metadata, cooldown_millis) }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_getCoolDownMillis.
+    pub fn get_cooldown_millis(&self) -> i64 {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe { AStatsManager_PullAtomMetadata_getCoolDownMillis(self.metadata) }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_setTimeoutMillis.
+    pub fn set_timeout_millis(&mut self, timeout_millis: i64) {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe { AStatsManager_PullAtomMetadata_setTimeoutMillis(self.metadata, timeout_millis) }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_getTimeoutMillis.
+    pub fn get_timeout_millis(&self) -> i64 {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe { AStatsManager_PullAtomMetadata_getTimeoutMillis(self.metadata) }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_setAdditiveFields.
+    pub fn set_additive_fields(&mut self, additive_fields: &mut Vec<i32>) {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe {
+            AStatsManager_PullAtomMetadata_setAdditiveFields(
+                self.metadata,
+                additive_fields.as_mut_ptr(),
+                additive_fields.len().try_into().expect("Cannot convert length to i32"),
+            )
+        }
+    }
+
+    /// Calls AStatsManager_PullAtomMetadata_getAdditiveFields.
+    pub fn get_additive_fields(&self) -> Vec<i32> {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        // We call getNumAdditiveFields to ensure we pass getAdditiveFields a large enough array.
+        unsafe {
+            let num_fields = AStatsManager_PullAtomMetadata_getNumAdditiveFields(self.metadata)
+                .try_into()
+                .expect("Cannot convert num additive fields to usize");
+            let mut fields = vec![0; num_fields];
+            AStatsManager_PullAtomMetadata_getAdditiveFields(self.metadata, fields.as_mut_ptr());
+            fields
+        }
+    }
+}
+
+impl Drop for Metadata {
+    fn drop(&mut self) {
+        // Safety: Metadata::new ensures that self.metadata is a valid object.
+        unsafe { AStatsManager_PullAtomMetadata_release(self.metadata) }
+    }
+}
+
+impl Default for Metadata {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+lazy_static! {
+    static ref COOKIES: Mutex<HashMap<i32, fn() -> StatsPullResult>> = Mutex::new(HashMap::new());
+}
+
+// Safety: We store our callbacks in the global so they are valid.
+unsafe extern "C" fn callback_wrapper(
+    atom_tag: i32,
+    data: *mut AStatsEventList,
+    _cookie: *mut c_void,
+) -> AStatsManager_PullAtomCallbackReturn {
+    if !data.is_null() {
+        let map = COOKIES.lock().unwrap();
+        let cb = map.get(&atom_tag);
+        match cb {
+            None => log::error!("No callback found for {}", atom_tag),
+            Some(cb) => {
+                let stats = cb();
+                let result = stats
+                    .iter()
+                    .map(|stat| stat.add_astats_event(&mut *data))
+                    .collect::<Result<Vec<()>, StatsError>>();
+                match result {
+                    Ok(_) => {
+                        return AStatsManager_PULL_SUCCESS as AStatsManager_PullAtomCallbackReturn
+                    }
+                    _ => log::error!("Error adding astats events: {:?}", result),
+                }
+            }
+        }
+    }
+    AStatsManager_PULL_SKIP as AStatsManager_PullAtomCallbackReturn
+}
+
+/// Rust wrapper for AStatsManager_setPullAtomCallback.
+pub fn set_pull_atom_callback(
+    atom: Atoms,
+    metadata: Option<&Metadata>,
+    callback: fn() -> StatsPullResult,
+) {
+    COOKIES.lock().unwrap().insert(atom as i32, callback);
+    let metadata_raw = match metadata {
+        Some(m) => m.metadata,
+        None => std::ptr::null_mut(),
+    };
+    // Safety: We pass a valid function as the callback.
+    unsafe {
+        AStatsManager_setPullAtomCallback(
+            atom as i32,
+            metadata_raw,
+            Some(callback_wrapper),
+            std::ptr::null_mut(),
+        );
+    }
+}
+
+/// Rust wrapper for AStatsManager_clearPullAtomCallback.
+pub fn clear_pull_atom_callback(atom: Atoms) {
+    COOKIES.lock().unwrap().remove(&(atom as i32));
+    // Safety: No memory allocations.
+    unsafe { AStatsManager_clearPullAtomCallback(atom as i32) }
+}
diff --git a/libstats/pull_rust/statslog.h b/libstats/pull_rust/statslog.h
new file mode 100644
index 0000000..983fb7b
--- /dev/null
+++ b/libstats/pull_rust/statslog.h
@@ -0,0 +1,3 @@
+#pragma once
+
+#include "stats_pull_atom_callback.h"
diff --git a/libstats/push_compat/Android.bp b/libstats/push_compat/Android.bp
index 43ae69d..819066e 100644
--- a/libstats/push_compat/Android.bp
+++ b/libstats/push_compat/Android.bp
@@ -19,6 +19,10 @@
 // protocols. This library should only be used by DNS resolver or other
 // native modules on Q that log pushed atoms to statsd.
 // =========================================================================
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_defaults {
     name: "libstatspush_compat_defaults",
     srcs: [
@@ -51,7 +55,7 @@
     export_header_lib_headers: [
         "libstatssocket_headers",
     ],
-    static_libs: ["libgtest_prod"],
+    header_libs: ["libgtest_prod_headers"],
     apex_available: ["com.android.resolv"],
     min_sdk_version: "29",
 }
@@ -65,4 +69,3 @@
     ],
     static_libs: ["libgmock"],
 }
-
diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp
deleted file mode 100644
index 89cdfe5..0000000
--- a/libstats/socket/Android.bp
+++ /dev/null
@@ -1,125 +0,0 @@
-//
-// Copyright (C) 2018 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.
-//
-
-// =========================================================================
-// Native library to write stats log to statsd socket on Android R and later
-// =========================================================================
-cc_defaults {
-    name: "libstatssocket_defaults",
-    srcs: [
-        "stats_buffer_writer.c",
-        "stats_event.c",
-        "stats_socket.c",
-        "statsd_writer.c",
-    ],
-    export_include_dirs: ["include"],
-    static_libs: [
-        "libcutils", // does not expose a stable C API
-    ],
-    header_libs: ["liblog_headers"],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-}
-
-cc_library {
-    name: "libstatssocket",
-    defaults: [
-        "libstatssocket_defaults",
-    ],
-    host_supported: true,
-    target: {
-        // On android, libstatssocket should only be linked as a shared lib
-        android: {
-            static: {
-                enabled: false,
-            },
-        },
-        host: {
-            shared: {
-                enabled: false,
-            },
-        },
-    },
-    stl: "libc++_static",
-
-    // enumerate stable entry points for APEX use
-    stubs: {
-        symbol_file: "libstatssocket.map.txt",
-        versions: [
-            "30",
-        ],
-    },
-    apex_available: [
-        "com.android.os.statsd",
-        "test_com.android.os.statsd",
-    ],
-}
-
-//TODO (b/149842105): Figure out if there is a better solution for this.
-cc_test_library {
-    name: "libstatssocket_private",
-    defaults: [
-        "libstatssocket_defaults",
-    ],
-    visibility: [
-        "//frameworks/base/apex/statsd/tests/libstatspull",
-        "//frameworks/base/cmds/statsd",
-    ],
-}
-
-cc_library_headers {
-    name: "libstatssocket_headers",
-    export_include_dirs: ["include"],
-    host_supported: true,
-    apex_available: ["com.android.resolv"],
-    min_sdk_version: "29",
-}
-
-cc_test {
-    name: "libstatssocket_test",
-    srcs: [
-        "tests/stats_event_test.cpp",
-        "tests/stats_writer_test.cpp",
-    ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-    ],
-    static_libs: [
-        "libgmock",
-        "libstatssocket_private",
-    ],
-    shared_libs: [
-        "libcutils",
-        "libutils",
-    ],
-    test_suites: ["device-tests", "mts"],
-    test_config: "libstatssocket_test.xml",
-    //TODO(b/153588990): Remove when the build system properly separates.
-    //32bit and 64bit architectures.
-    compile_multilib: "both",
-    multilib: {
-        lib64: {
-            suffix: "64",
-        },
-        lib32: {
-            suffix: "32",
-        },
-    },
-    require_root: true,
-}
diff --git a/libstats/socket/TEST_MAPPING b/libstats/socket/TEST_MAPPING
deleted file mode 100644
index 0224998..0000000
--- a/libstats/socket/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit" : [
-    {
-      "name" : "libstatssocket_test"
-    }
-  ]
-}
\ No newline at end of file
diff --git a/libstats/socket/include/stats_buffer_writer.h b/libstats/socket/include/stats_buffer_writer.h
deleted file mode 100644
index 5b41f0e..0000000
--- a/libstats/socket/include/stats_buffer_writer.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2019 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 <stddef.h>
-#include <stdint.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif  // __CPLUSPLUS
-void stats_log_close();
-int stats_log_is_closed();
-int write_buffer_to_statsd(void* buffer, size_t size, uint32_t atomId);
-#ifdef __cplusplus
-}
-#endif  // __CPLUSPLUS
diff --git a/libstats/socket/include/stats_event.h b/libstats/socket/include/stats_event.h
deleted file mode 100644
index 3d3baf9..0000000
--- a/libstats/socket/include/stats_event.h
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2019 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 ANDROID_STATS_LOG_STATS_EVENT_H
-#define ANDROID_STATS_LOG_STATS_EVENT_H
-
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-
-/*
- * Functionality to build and store the buffer sent over the statsd socket.
- * This code defines and encapsulates the socket protocol.
- *
- * Usage:
- *      AStatsEvent* event = AStatsEvent_obtain();
- *
- *      AStatsEvent_setAtomId(event, atomId);
- *      AStatsEvent_addBoolAnnotation(event, 5, false); // atom-level annotation
- *      AStatsEvent_writeInt32(event, 24);
- *      AStatsEvent_addBoolAnnotation(event, 1, true); // annotation for preceding atom field
- *      AStatsEvent_addInt32Annotation(event, 2, 128);
- *      AStatsEvent_writeFloat(event, 2.0);
- *
- *      AStatsEvent_write(event);
- *      AStatsEvent_release(event);
- *
- * Note that calls to add atom fields and annotations should be made in the
- * order that they are defined in the atom.
- */
-
-#ifdef __cplusplus
-extern "C" {
-#endif  // __CPLUSPLUS
-
-/**
- * Opaque struct use to represent a StatsEvent. It builds and stores the data that is sent to
- * statsd.
- */
-struct AStatsEvent;
-typedef struct AStatsEvent AStatsEvent;
-
-/**
- * Returns a new AStatsEvent. If you call this function, you must call AStatsEvent_release to free
- * the allocated memory.
- */
-AStatsEvent* AStatsEvent_obtain();
-
-/**
- * Builds and finalizes the AStatsEvent for a pulled event.
- * This should only be called for pulled AStatsEvents.
- *
- * After this function, the StatsEvent must not be modified in any way other than calling release or
- * write.
- *
- * Build can be called multiple times without error.
- * If the event has been built before, this function is a no-op.
- */
-void AStatsEvent_build(AStatsEvent* event);
-
-/**
- * Writes the StatsEvent to the stats log.
- *
- * After calling this, AStatsEvent_release must be called,
- * and is the only function that can be safely called.
- */
-int AStatsEvent_write(AStatsEvent* event);
-
-/**
- * Frees the memory held by this StatsEvent.
- *
- * After calling this, the StatsEvent must not be used or modified in any way.
- */
-void AStatsEvent_release(AStatsEvent* event);
-
-/**
- * Sets the atom id for this StatsEvent.
- *
- * This function should be called immediately after AStatsEvent_obtain. It may
- * be called additional times as well, but subsequent calls will have no effect.
- **/
-void AStatsEvent_setAtomId(AStatsEvent* event, uint32_t atomId);
-
-/**
- * Writes an int32_t field to this StatsEvent.
- **/
-void AStatsEvent_writeInt32(AStatsEvent* event, int32_t value);
-
-/**
- * Writes an int64_t field to this StatsEvent.
- **/
-void AStatsEvent_writeInt64(AStatsEvent* event, int64_t value);
-
-/**
- * Writes a float field to this StatsEvent.
- **/
-void AStatsEvent_writeFloat(AStatsEvent* event, float value);
-
-/**
- * Write a bool field to this StatsEvent.
- **/
-void AStatsEvent_writeBool(AStatsEvent* event, bool value);
-
-/**
- * Write a byte array field to this StatsEvent.
- **/
-void AStatsEvent_writeByteArray(AStatsEvent* event, const uint8_t* buf, size_t numBytes);
-
-/**
- * Write a string field to this StatsEvent.
- *
- * The string must be null-terminated.
- **/
-void AStatsEvent_writeString(AStatsEvent* event, const char* value);
-
-/**
- * Write an attribution chain field to this StatsEvent.
- *
- * The sizes of uids and tags must be equal. The AttributionNode at position i is
- * made up of uids[i] and tags[i].
- *
- * \param uids array of uids in the attribution chain.
- * \param tags array of tags in the attribution chain. Each tag must be null-terminated.
- * \param numNodes the number of AttributionNodes in the attribution chain. This is the length of
- *                 the uids and the tags.
- **/
-void AStatsEvent_writeAttributionChain(AStatsEvent* event, const uint32_t* uids,
-                                       const char* const* tags, uint8_t numNodes);
-
-/**
- * Write a bool annotation for the previous field written.
- **/
-void AStatsEvent_addBoolAnnotation(AStatsEvent* event, uint8_t annotationId, bool value);
-
-/**
- * Write an integer annotation for the previous field written.
- **/
-void AStatsEvent_addInt32Annotation(AStatsEvent* event, uint8_t annotationId, int32_t value);
-
-// Internal/test APIs. Should not be exposed outside of the APEX.
-void AStatsEvent_overwriteTimestamp(AStatsEvent* event, uint64_t timestampNs);
-uint32_t AStatsEvent_getAtomId(AStatsEvent* event);
-// Size is an output parameter.
-uint8_t* AStatsEvent_getBuffer(AStatsEvent* event, size_t* size);
-uint32_t AStatsEvent_getErrors(AStatsEvent* event);
-
-#ifdef __cplusplus
-}
-#endif  // __CPLUSPLUS
-
-#endif  // ANDROID_STATS_LOG_STATS_EVENT_H
diff --git a/libstats/socket/libstatssocket.map.txt b/libstats/socket/libstatssocket.map.txt
deleted file mode 100644
index 5c13904..0000000
--- a/libstats/socket/libstatssocket.map.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-LIBSTATSSOCKET {
-    global:
-        AStatsEvent_obtain; # apex # introduced=30
-        AStatsEvent_build; # apex # introduced=30
-        AStatsEvent_write; # apex # introduced=30
-        AStatsEvent_release; # apex # introduced=30
-        AStatsEvent_setAtomId; # apex # introduced=30
-        AStatsEvent_writeInt32; # apex # introduced=30
-        AStatsEvent_writeInt64; # apex # introduced=30
-        AStatsEvent_writeFloat; # apex # introduced=30
-        AStatsEvent_writeBool; # apex # introduced=30
-        AStatsEvent_writeByteArray; # apex # introduced=30
-        AStatsEvent_writeString; # apex # introduced=30
-        AStatsEvent_writeAttributionChain; # apex # introduced=30
-        AStatsEvent_addBoolAnnotation; # apex # introduced=30
-        AStatsEvent_addInt32Annotation; # apex # introduced=30
-        AStatsSocket_close; # apex # introduced=30
-    local:
-        *;
-};
diff --git a/libstats/socket/libstatssocket_test.xml b/libstats/socket/libstatssocket_test.xml
deleted file mode 100644
index d2694d1..0000000
--- a/libstats/socket/libstatssocket_test.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Runs libstatssocket_test.">
-    <option name="test-suite-tag" value="apct" />
-    <option name="test-suite-tag" value="apct-native" />
-    <option name="test-suite-tag" value="mts" />
-
-    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
-
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-       <option name="cleanup" value="true" />
-       <option name="push" value="libstatssocket_test->/data/local/tmp/libstatssocket_test" />
-       <option name="append-bitness" value="true" />
-    </target_preparer>
-
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="libstatssocket_test" />
-    </test>
-
-    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
-        <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
-    </object>
-</configuration>
-
diff --git a/libstats/socket/stats_buffer_writer.c b/libstats/socket/stats_buffer_writer.c
deleted file mode 100644
index 549acdc..0000000
--- a/libstats/socket/stats_buffer_writer.c
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2019 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 "include/stats_buffer_writer.h"
-#ifdef __ANDROID__
-#include <cutils/properties.h>
-#endif
-#include <errno.h>
-#include <sys/time.h>
-#include <sys/uio.h>
-#include "statsd_writer.h"
-
-static const uint32_t kStatsEventTag = 1937006964;
-
-extern struct android_log_transport_write statsdLoggerWrite;
-
-static int __write_to_statsd_init(struct iovec* vec, size_t nr);
-static int (*__write_to_statsd)(struct iovec* vec, size_t nr) = __write_to_statsd_init;
-
-void note_log_drop(int error, int atomId) {
-    statsdLoggerWrite.noteDrop(error, atomId);
-}
-
-void stats_log_close() {
-    statsd_writer_init_lock();
-    __write_to_statsd = __write_to_statsd_init;
-    if (statsdLoggerWrite.close) {
-        (*statsdLoggerWrite.close)();
-    }
-    statsd_writer_init_unlock();
-}
-
-int stats_log_is_closed() {
-    return statsdLoggerWrite.isClosed && (*statsdLoggerWrite.isClosed)();
-}
-
-int write_buffer_to_statsd(void* buffer, size_t size, uint32_t atomId) {
-    int ret = 1;
-
-    struct iovec vecs[2];
-    vecs[0].iov_base = (void*)&kStatsEventTag;
-    vecs[0].iov_len = sizeof(kStatsEventTag);
-    vecs[1].iov_base = buffer;
-    vecs[1].iov_len = size;
-
-    ret = __write_to_statsd(vecs, 2);
-
-    if (ret < 0) {
-        note_log_drop(ret, atomId);
-    }
-
-    return ret;
-}
-
-static int __write_to_stats_daemon(struct iovec* vec, size_t nr) {
-    int save_errno;
-    struct timespec ts;
-    size_t len, i;
-
-    for (len = i = 0; i < nr; ++i) {
-        len += vec[i].iov_len;
-    }
-    if (!len) {
-        return -EINVAL;
-    }
-
-    save_errno = errno;
-#if defined(__ANDROID__)
-    clock_gettime(CLOCK_REALTIME, &ts);
-#else
-    struct timeval tv;
-    gettimeofday(&tv, NULL);
-    ts.tv_sec = tv.tv_sec;
-    ts.tv_nsec = tv.tv_usec * 1000;
-#endif
-
-    int ret = (int)(*statsdLoggerWrite.write)(&ts, vec, nr);
-    errno = save_errno;
-    return ret;
-}
-
-static int __write_to_statsd_initialize_locked() {
-    if (!statsdLoggerWrite.open || ((*statsdLoggerWrite.open)() < 0)) {
-        if (statsdLoggerWrite.close) {
-            (*statsdLoggerWrite.close)();
-            return -ENODEV;
-        }
-    }
-    return 1;
-}
-
-static int __write_to_statsd_init(struct iovec* vec, size_t nr) {
-    int ret, save_errno = errno;
-
-    statsd_writer_init_lock();
-
-    if (__write_to_statsd == __write_to_statsd_init) {
-        ret = __write_to_statsd_initialize_locked();
-        if (ret < 0) {
-            statsd_writer_init_unlock();
-            errno = save_errno;
-            return ret;
-        }
-
-        __write_to_statsd = __write_to_stats_daemon;
-    }
-
-    statsd_writer_init_unlock();
-
-    ret = __write_to_statsd(vec, nr);
-    errno = save_errno;
-    return ret;
-}
diff --git a/libstats/socket/stats_event.c b/libstats/socket/stats_event.c
deleted file mode 100644
index f3e8087..0000000
--- a/libstats/socket/stats_event.c
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Copyright (C) 2019 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 "include/stats_event.h"
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include "stats_buffer_writer.h"
-
-#define LOGGER_ENTRY_MAX_PAYLOAD 4068
-// Max payload size is 4 bytes less as 4 bytes are reserved for stats_eventTag.
-// See android_util_Stats_Log.cpp
-#define MAX_PUSH_EVENT_PAYLOAD (LOGGER_ENTRY_MAX_PAYLOAD - 4)
-
-#define MAX_PULL_EVENT_PAYLOAD (50 * 1024)  // 50 KB
-
-/* POSITIONS */
-#define POS_NUM_ELEMENTS 1
-#define POS_TIMESTAMP (POS_NUM_ELEMENTS + sizeof(uint8_t))
-#define POS_ATOM_ID (POS_TIMESTAMP + sizeof(uint8_t) + sizeof(uint64_t))
-
-/* LIMITS */
-#define MAX_ANNOTATION_COUNT 15
-#define MAX_BYTE_VALUE 127  // parsing side requires that lengths fit in 7 bits
-
-/* ERRORS */
-#define ERROR_NO_TIMESTAMP 0x1
-#define ERROR_NO_ATOM_ID 0x2
-#define ERROR_OVERFLOW 0x4
-#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
-#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
-#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
-#define ERROR_INVALID_ANNOTATION_ID 0x40
-#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
-#define ERROR_TOO_MANY_ANNOTATIONS 0x100
-#define ERROR_TOO_MANY_FIELDS 0x200
-#define ERROR_INVALID_VALUE_TYPE 0x400
-#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
-#define ERROR_ATOM_ID_INVALID_POSITION 0x2000
-
-/* TYPE IDS */
-#define INT32_TYPE 0x00
-#define INT64_TYPE 0x01
-#define STRING_TYPE 0x02
-#define LIST_TYPE 0x03
-#define FLOAT_TYPE 0x04
-#define BOOL_TYPE 0x05
-#define BYTE_ARRAY_TYPE 0x06
-#define OBJECT_TYPE 0x07
-#define KEY_VALUE_PAIRS_TYPE 0x08
-#define ATTRIBUTION_CHAIN_TYPE 0x09
-#define ERROR_TYPE 0x0F
-
-// The AStatsEvent struct holds the serialized encoding of an event
-// within a buf. Also includes other required fields.
-struct AStatsEvent {
-    uint8_t* buf;
-    // Location of last field within the buf. Here, field denotes either a
-    // metadata field (e.g. timestamp) or an atom field.
-    size_t lastFieldPos;
-    // Number of valid bytes within the buffer.
-    size_t numBytesWritten;
-    uint32_t numElements;
-    uint32_t atomId;
-    uint32_t errors;
-    bool built;
-    size_t bufSize;
-};
-
-static int64_t get_elapsed_realtime_ns() {
-    struct timespec t;
-    t.tv_sec = t.tv_nsec = 0;
-    clock_gettime(CLOCK_BOOTTIME, &t);
-    return (int64_t)t.tv_sec * 1000000000LL + t.tv_nsec;
-}
-
-AStatsEvent* AStatsEvent_obtain() {
-    AStatsEvent* event = malloc(sizeof(AStatsEvent));
-    event->lastFieldPos = 0;
-    event->numBytesWritten = 2;  // reserve first 2 bytes for root event type and number of elements
-    event->numElements = 0;
-    event->atomId = 0;
-    event->errors = 0;
-    event->built = false;
-    event->bufSize = MAX_PUSH_EVENT_PAYLOAD;
-    event->buf = (uint8_t*)calloc(event->bufSize, 1);
-
-    event->buf[0] = OBJECT_TYPE;
-    AStatsEvent_writeInt64(event, get_elapsed_realtime_ns());  // write the timestamp
-
-    return event;
-}
-
-void AStatsEvent_release(AStatsEvent* event) {
-    free(event->buf);
-    free(event);
-}
-
-void AStatsEvent_setAtomId(AStatsEvent* event, uint32_t atomId) {
-    if (event->atomId != 0) return;
-    if (event->numElements != 1) {
-        event->errors |= ERROR_ATOM_ID_INVALID_POSITION;
-        return;
-    }
-
-    event->atomId = atomId;
-    AStatsEvent_writeInt32(event, atomId);
-}
-
-// Overwrites the timestamp populated in AStatsEvent_obtain with a custom
-// timestamp. Should only be called from test code.
-void AStatsEvent_overwriteTimestamp(AStatsEvent* event, uint64_t timestampNs) {
-    memcpy(&event->buf[POS_TIMESTAMP + sizeof(uint8_t)], &timestampNs, sizeof(timestampNs));
-    // Do not increment numElements because we already accounted for the timestamp
-    // within AStatsEvent_obtain.
-}
-
-// Side-effect: modifies event->errors if the buffer would overflow
-static bool overflows(AStatsEvent* event, size_t size) {
-    const size_t totalBytesNeeded = event->numBytesWritten + size;
-    if (totalBytesNeeded > MAX_PULL_EVENT_PAYLOAD) {
-        event->errors |= ERROR_OVERFLOW;
-        return true;
-    }
-
-    // Expand buffer if needed.
-    if (event->bufSize < MAX_PULL_EVENT_PAYLOAD && totalBytesNeeded > event->bufSize) {
-        do {
-            event->bufSize *= 2;
-        } while (event->bufSize <= totalBytesNeeded);
-
-        if (event->bufSize > MAX_PULL_EVENT_PAYLOAD) {
-            event->bufSize = MAX_PULL_EVENT_PAYLOAD;
-        }
-
-        event->buf = (uint8_t*)realloc(event->buf, event->bufSize);
-    }
-    return false;
-}
-
-// Side-effect: all append functions increment event->numBytesWritten if there is
-// sufficient space within the buffer to place the value
-static void append_byte(AStatsEvent* event, uint8_t value) {
-    if (!overflows(event, sizeof(value))) {
-        event->buf[event->numBytesWritten] = value;
-        event->numBytesWritten += sizeof(value);
-    }
-}
-
-static void append_bool(AStatsEvent* event, bool value) {
-    append_byte(event, (uint8_t)value);
-}
-
-static void append_int32(AStatsEvent* event, int32_t value) {
-    if (!overflows(event, sizeof(value))) {
-        memcpy(&event->buf[event->numBytesWritten], &value, sizeof(value));
-        event->numBytesWritten += sizeof(value);
-    }
-}
-
-static void append_int64(AStatsEvent* event, int64_t value) {
-    if (!overflows(event, sizeof(value))) {
-        memcpy(&event->buf[event->numBytesWritten], &value, sizeof(value));
-        event->numBytesWritten += sizeof(value);
-    }
-}
-
-static void append_float(AStatsEvent* event, float value) {
-    if (!overflows(event, sizeof(value))) {
-        memcpy(&event->buf[event->numBytesWritten], &value, sizeof(value));
-        event->numBytesWritten += sizeof(float);
-    }
-}
-
-static void append_byte_array(AStatsEvent* event, const uint8_t* buf, size_t size) {
-    if (!overflows(event, size)) {
-        memcpy(&event->buf[event->numBytesWritten], buf, size);
-        event->numBytesWritten += size;
-    }
-}
-
-// Side-effect: modifies event->errors if buf is not properly null-terminated
-static void append_string(AStatsEvent* event, const char* buf) {
-    size_t size = strnlen(buf, MAX_PULL_EVENT_PAYLOAD);
-    if (size == MAX_PULL_EVENT_PAYLOAD) {
-        event->errors |= ERROR_STRING_NOT_NULL_TERMINATED;
-        return;
-    }
-
-    append_int32(event, size);
-    append_byte_array(event, (uint8_t*)buf, size);
-}
-
-static void start_field(AStatsEvent* event, uint8_t typeId) {
-    event->lastFieldPos = event->numBytesWritten;
-    append_byte(event, typeId);
-    event->numElements++;
-}
-
-void AStatsEvent_writeInt32(AStatsEvent* event, int32_t value) {
-    start_field(event, INT32_TYPE);
-    append_int32(event, value);
-}
-
-void AStatsEvent_writeInt64(AStatsEvent* event, int64_t value) {
-    start_field(event, INT64_TYPE);
-    append_int64(event, value);
-}
-
-void AStatsEvent_writeFloat(AStatsEvent* event, float value) {
-    start_field(event, FLOAT_TYPE);
-    append_float(event, value);
-}
-
-void AStatsEvent_writeBool(AStatsEvent* event, bool value) {
-    start_field(event, BOOL_TYPE);
-    append_bool(event, value);
-}
-
-void AStatsEvent_writeByteArray(AStatsEvent* event, const uint8_t* buf, size_t numBytes) {
-    start_field(event, BYTE_ARRAY_TYPE);
-    append_int32(event, numBytes);
-    append_byte_array(event, buf, numBytes);
-}
-
-// Value is assumed to be encoded using UTF8
-void AStatsEvent_writeString(AStatsEvent* event, const char* value) {
-    start_field(event, STRING_TYPE);
-    append_string(event, value);
-}
-
-// Tags are assumed to be encoded using UTF8
-void AStatsEvent_writeAttributionChain(AStatsEvent* event, const uint32_t* uids,
-                                       const char* const* tags, uint8_t numNodes) {
-    if (numNodes > MAX_BYTE_VALUE) {
-        event->errors |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG;
-        return;
-    }
-
-    start_field(event, ATTRIBUTION_CHAIN_TYPE);
-    append_byte(event, numNodes);
-
-    for (uint8_t i = 0; i < numNodes; i++) {
-        append_int32(event, uids[i]);
-        append_string(event, tags[i]);
-    }
-}
-
-// Side-effect: modifies event->errors if field has too many annotations
-static void increment_annotation_count(AStatsEvent* event) {
-    uint8_t fieldType = event->buf[event->lastFieldPos] & 0x0F;
-    uint32_t oldAnnotationCount = (event->buf[event->lastFieldPos] & 0xF0) >> 4;
-    uint32_t newAnnotationCount = oldAnnotationCount + 1;
-
-    if (newAnnotationCount > MAX_ANNOTATION_COUNT) {
-        event->errors |= ERROR_TOO_MANY_ANNOTATIONS;
-        return;
-    }
-
-    event->buf[event->lastFieldPos] = (((uint8_t)newAnnotationCount << 4) & 0xF0) | fieldType;
-}
-
-void AStatsEvent_addBoolAnnotation(AStatsEvent* event, uint8_t annotationId, bool value) {
-    if (event->numElements < 2) {
-        event->errors |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
-        return;
-    } else if (annotationId > MAX_BYTE_VALUE) {
-        event->errors |= ERROR_ANNOTATION_ID_TOO_LARGE;
-        return;
-    }
-
-    append_byte(event, annotationId);
-    append_byte(event, BOOL_TYPE);
-    append_bool(event, value);
-    increment_annotation_count(event);
-}
-
-void AStatsEvent_addInt32Annotation(AStatsEvent* event, uint8_t annotationId, int32_t value) {
-    if (event->numElements < 2) {
-        event->errors |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD;
-        return;
-    } else if (annotationId > MAX_BYTE_VALUE) {
-        event->errors |= ERROR_ANNOTATION_ID_TOO_LARGE;
-        return;
-    }
-
-    append_byte(event, annotationId);
-    append_byte(event, INT32_TYPE);
-    append_int32(event, value);
-    increment_annotation_count(event);
-}
-
-uint32_t AStatsEvent_getAtomId(AStatsEvent* event) {
-    return event->atomId;
-}
-
-uint8_t* AStatsEvent_getBuffer(AStatsEvent* event, size_t* size) {
-    if (size) *size = event->numBytesWritten;
-    return event->buf;
-}
-
-uint32_t AStatsEvent_getErrors(AStatsEvent* event) {
-    return event->errors;
-}
-
-static void build_internal(AStatsEvent* event, const bool push) {
-    if (event->numElements > MAX_BYTE_VALUE) event->errors |= ERROR_TOO_MANY_FIELDS;
-    if (0 == event->atomId) event->errors |= ERROR_NO_ATOM_ID;
-    if (push && event->numBytesWritten > MAX_PUSH_EVENT_PAYLOAD) event->errors |= ERROR_OVERFLOW;
-
-    // If there are errors, rewrite buffer.
-    if (event->errors) {
-        // Discard everything after the atom id (including atom-level
-        // annotations). This leaves only two elements (timestamp and atom id).
-        event->numElements = 2;
-        // Reset number of atom-level annotations to 0.
-        event->buf[POS_ATOM_ID] = INT32_TYPE;
-        // Now, write errors to the buffer immediately after the atom id.
-        event->numBytesWritten = POS_ATOM_ID + sizeof(uint8_t) + sizeof(uint32_t);
-        start_field(event, ERROR_TYPE);
-        append_int32(event, event->errors);
-    }
-
-    event->buf[POS_NUM_ELEMENTS] = event->numElements;
-}
-
-void AStatsEvent_build(AStatsEvent* event) {
-    if (event->built) return;
-
-    build_internal(event, false /* push */);
-
-    event->built = true;
-}
-
-int AStatsEvent_write(AStatsEvent* event) {
-    build_internal(event, true /* push */);
-    return write_buffer_to_statsd(event->buf, event->numBytesWritten, event->atomId);
-}
diff --git a/libstats/socket/statsd_writer.c b/libstats/socket/statsd_writer.c
deleted file mode 100644
index 73b7a7e..0000000
--- a/libstats/socket/statsd_writer.c
+++ /dev/null
@@ -1,294 +0,0 @@
-/*
- * Copyright (C) 2018, 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 "statsd_writer.h"
-
-#include <cutils/fs.h>
-#include <cutils/sockets.h>
-#include <cutils/threads.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <poll.h>
-#include <private/android_filesystem_config.h>
-#include <private/android_logger.h>
-#include <stdarg.h>
-#include <stdatomic.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/uio.h>
-#include <sys/un.h>
-#include <time.h>
-#include <unistd.h>
-
-static pthread_mutex_t log_init_lock = PTHREAD_MUTEX_INITIALIZER;
-static atomic_int dropped = 0;
-static atomic_int log_error = 0;
-static atomic_int atom_tag = 0;
-
-void statsd_writer_init_lock() {
-    /*
-     * If we trigger a signal handler in the middle of locked activity and the
-     * signal handler logs a message, we could get into a deadlock state.
-     */
-    pthread_mutex_lock(&log_init_lock);
-}
-
-int statd_writer_trylock() {
-    return pthread_mutex_trylock(&log_init_lock);
-}
-
-void statsd_writer_init_unlock() {
-    pthread_mutex_unlock(&log_init_lock);
-}
-
-static int statsdAvailable();
-static int statsdOpen();
-static void statsdClose();
-static int statsdWrite(struct timespec* ts, struct iovec* vec, size_t nr);
-static void statsdNoteDrop();
-static int statsdIsClosed();
-
-struct android_log_transport_write statsdLoggerWrite = {
-        .name = "statsd",
-        .sock = -EBADF,
-        .available = statsdAvailable,
-        .open = statsdOpen,
-        .close = statsdClose,
-        .write = statsdWrite,
-        .noteDrop = statsdNoteDrop,
-        .isClosed = statsdIsClosed,
-};
-
-/* log_init_lock assumed */
-static int statsdOpen() {
-    int i, ret = 0;
-
-    i = atomic_load(&statsdLoggerWrite.sock);
-    if (i < 0) {
-        int flags = SOCK_DGRAM;
-#ifdef SOCK_CLOEXEC
-        flags |= SOCK_CLOEXEC;
-#endif
-#ifdef SOCK_NONBLOCK
-        flags |= SOCK_NONBLOCK;
-#endif
-        int sock = TEMP_FAILURE_RETRY(socket(PF_UNIX, flags, 0));
-        if (sock < 0) {
-            ret = -errno;
-        } else {
-            int sndbuf = 1 * 1024 * 1024;  // set max send buffer size 1MB
-            socklen_t bufLen = sizeof(sndbuf);
-            // SO_RCVBUF does not have an effect on unix domain socket, but SO_SNDBUF does.
-            // Proceed to connect even setsockopt fails.
-            setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sndbuf, bufLen);
-            struct sockaddr_un un;
-            memset(&un, 0, sizeof(struct sockaddr_un));
-            un.sun_family = AF_UNIX;
-            strcpy(un.sun_path, "/dev/socket/statsdw");
-
-            if (TEMP_FAILURE_RETRY(
-                        connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) < 0) {
-                ret = -errno;
-                switch (ret) {
-                    case -ENOTCONN:
-                    case -ECONNREFUSED:
-                    case -ENOENT:
-                        i = atomic_exchange(&statsdLoggerWrite.sock, ret);
-                    /* FALLTHRU */
-                    default:
-                        break;
-                }
-                close(sock);
-            } else {
-                ret = atomic_exchange(&statsdLoggerWrite.sock, sock);
-                if ((ret >= 0) && (ret != sock)) {
-                    close(ret);
-                }
-                ret = 0;
-            }
-        }
-    }
-
-    return ret;
-}
-
-static void __statsdClose(int negative_errno) {
-    int sock = atomic_exchange(&statsdLoggerWrite.sock, negative_errno);
-    if (sock >= 0) {
-        close(sock);
-    }
-}
-
-static void statsdClose() {
-    __statsdClose(-EBADF);
-}
-
-static int statsdAvailable() {
-    if (atomic_load(&statsdLoggerWrite.sock) < 0) {
-        if (access("/dev/socket/statsdw", W_OK) == 0) {
-            return 0;
-        }
-        return -EBADF;
-    }
-    return 1;
-}
-
-static void statsdNoteDrop(int error, int tag) {
-    atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed);
-    atomic_exchange_explicit(&log_error, error, memory_order_relaxed);
-    atomic_exchange_explicit(&atom_tag, tag, memory_order_relaxed);
-}
-
-static int statsdIsClosed() {
-    if (atomic_load(&statsdLoggerWrite.sock) < 0) {
-        return 1;
-    }
-    return 0;
-}
-
-static int statsdWrite(struct timespec* ts, struct iovec* vec, size_t nr) {
-    ssize_t ret;
-    int sock;
-    static const unsigned headerLength = 1;
-    struct iovec newVec[nr + headerLength];
-    android_log_header_t header;
-    size_t i, payloadSize;
-
-    sock = atomic_load(&statsdLoggerWrite.sock);
-    if (sock < 0) switch (sock) {
-            case -ENOTCONN:
-            case -ECONNREFUSED:
-            case -ENOENT:
-                break;
-            default:
-                return -EBADF;
-        }
-    /*
-     *  struct {
-     *      // what we provide to socket
-     *      android_log_header_t header;
-     *      // caller provides
-     *      union {
-     *          struct {
-     *              char     prio;
-     *              char     payload[];
-     *          } string;
-     *          struct {
-     *              uint32_t tag
-     *              char     payload[];
-     *          } binary;
-     *      };
-     *  };
-     */
-
-    header.tid = gettid();
-    header.realtime.tv_sec = ts->tv_sec;
-    header.realtime.tv_nsec = ts->tv_nsec;
-
-    newVec[0].iov_base = (unsigned char*)&header;
-    newVec[0].iov_len = sizeof(header);
-
-    // If we dropped events before, try to tell statsd.
-    if (sock >= 0) {
-        int32_t snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
-        if (snapshot) {
-            android_log_event_long_t buffer;
-            header.id = LOG_ID_STATS;
-            // store the last log error in the tag field. This tag field is not used by statsd.
-            buffer.header.tag = atomic_load(&log_error);
-            buffer.payload.type = EVENT_TYPE_LONG;
-            // format:
-            // |atom_tag|dropped_count|
-            int64_t composed_long = atomic_load(&atom_tag);
-            // Send 2 int32's via an int64.
-            composed_long = ((composed_long << 32) | ((int64_t)snapshot));
-            buffer.payload.data = composed_long;
-
-            newVec[headerLength].iov_base = &buffer;
-            newVec[headerLength].iov_len = sizeof(buffer);
-
-            ret = TEMP_FAILURE_RETRY(writev(sock, newVec, 2));
-            if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
-                atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed);
-            }
-        }
-    }
-
-    header.id = LOG_ID_STATS;
-
-    for (payloadSize = 0, i = headerLength; i < nr + headerLength; i++) {
-        newVec[i].iov_base = vec[i - headerLength].iov_base;
-        payloadSize += newVec[i].iov_len = vec[i - headerLength].iov_len;
-
-        if (payloadSize > LOGGER_ENTRY_MAX_PAYLOAD) {
-            newVec[i].iov_len -= payloadSize - LOGGER_ENTRY_MAX_PAYLOAD;
-            if (newVec[i].iov_len) {
-                ++i;
-            }
-            break;
-        }
-    }
-
-    /*
-     * The write below could be lost, but will never block.
-     *
-     * ENOTCONN occurs if statsd has died.
-     * ENOENT occurs if statsd is not running and socket is missing.
-     * ECONNREFUSED occurs if we can not reconnect to statsd.
-     * EAGAIN occurs if statsd is overloaded.
-     */
-    if (sock < 0) {
-        ret = sock;
-    } else {
-        ret = TEMP_FAILURE_RETRY(writev(sock, newVec, i));
-        if (ret < 0) {
-            ret = -errno;
-        }
-    }
-    switch (ret) {
-        case -ENOTCONN:
-        case -ECONNREFUSED:
-        case -ENOENT:
-            if (statd_writer_trylock()) {
-                return ret; /* in a signal handler? try again when less stressed
-                             */
-            }
-            __statsdClose(ret);
-            ret = statsdOpen();
-            statsd_writer_init_unlock();
-
-            if (ret < 0) {
-                return ret;
-            }
-
-            ret = TEMP_FAILURE_RETRY(writev(atomic_load(&statsdLoggerWrite.sock), newVec, i));
-            if (ret < 0) {
-                ret = -errno;
-            }
-        /* FALLTHRU */
-        default:
-            break;
-    }
-
-    if (ret > (ssize_t)sizeof(header)) {
-        ret -= sizeof(header);
-    }
-
-    return ret;
-}
diff --git a/libstats/socket/statsd_writer.h b/libstats/socket/statsd_writer.h
deleted file mode 100644
index 562bda5..0000000
--- a/libstats/socket/statsd_writer.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2018, 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 ANDROID_STATS_LOG_STATS_WRITER_H
-#define ANDROID_STATS_LOG_STATS_WRITER_H
-
-#include <pthread.h>
-#include <stdatomic.h>
-#include <sys/socket.h>
-
-/**
- * Internal lock should not be exposed. This is bad design.
- * TODO: rewrite it in c++ code and encapsulate the functionality in a
- * StatsdWriter class.
- */
-void statsd_writer_init_lock();
-int statsd_writer_init_trylock();
-void statsd_writer_init_unlock();
-
-struct android_log_transport_write {
-    const char* name; /* human name to describe the transport */
-    atomic_int sock;
-    int (*available)(); /* Does not cause resources to be taken */
-    int (*open)();      /* can be called multiple times, reusing current resources */
-    void (*close)();    /* free up resources */
-    /* write log to transport, returns number of bytes propagated, or -errno */
-    int (*write)(struct timespec* ts, struct iovec* vec, size_t nr);
-    /* note one log drop */
-    void (*noteDrop)(int error, int tag);
-    /* checks if the socket is closed */
-    int (*isClosed)();
-};
-
-#endif  // ANDROID_STATS_LOG_STATS_WRITER_H
diff --git a/libstats/socket/tests/stats_event_test.cpp b/libstats/socket/tests/stats_event_test.cpp
deleted file mode 100644
index 9a1fac8..0000000
--- a/libstats/socket/tests/stats_event_test.cpp
+++ /dev/null
@@ -1,460 +0,0 @@
-/*
- * Copyright (C) 2019 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 "stats_event.h"
-#include <gtest/gtest.h>
-#include <utils/SystemClock.h>
-
-// Keep in sync with stats_event.c. Consider moving to separate header file to avoid duplication.
-/* ERRORS */
-#define ERROR_NO_TIMESTAMP 0x1
-#define ERROR_NO_ATOM_ID 0x2
-#define ERROR_OVERFLOW 0x4
-#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
-#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
-#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
-#define ERROR_INVALID_ANNOTATION_ID 0x40
-#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
-#define ERROR_TOO_MANY_ANNOTATIONS 0x100
-#define ERROR_TOO_MANY_FIELDS 0x200
-#define ERROR_INVALID_VALUE_TYPE 0x400
-#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
-#define ERROR_ATOM_ID_INVALID_POSITION 0x2000
-
-/* TYPE IDS */
-#define INT32_TYPE 0x00
-#define INT64_TYPE 0x01
-#define STRING_TYPE 0x02
-#define LIST_TYPE 0x03
-#define FLOAT_TYPE 0x04
-#define BOOL_TYPE 0x05
-#define BYTE_ARRAY_TYPE 0x06
-#define OBJECT_TYPE 0x07
-#define KEY_VALUE_PAIRS_TYPE 0x08
-#define ATTRIBUTION_CHAIN_TYPE 0x09
-#define ERROR_TYPE 0x0F
-
-using std::string;
-using std::vector;
-
-// Side-effect: this function moves the start of the buffer past the read value
-template <class T>
-T readNext(uint8_t** buffer) {
-    T value;
-    if ((reinterpret_cast<uintptr_t>(*buffer) % alignof(T)) == 0) {
-        value = *(T*)(*buffer);
-    } else {
-        memcpy(&value, *buffer, sizeof(T));
-    }
-    *buffer += sizeof(T);
-    return value;
-}
-
-void checkTypeHeader(uint8_t** buffer, uint8_t typeId, uint8_t numAnnotations = 0) {
-    uint8_t typeHeader = (numAnnotations << 4) | typeId;
-    EXPECT_EQ(readNext<uint8_t>(buffer), typeHeader);
-}
-
-template <class T>
-void checkScalar(uint8_t** buffer, T expectedValue) {
-    EXPECT_EQ(readNext<T>(buffer), expectedValue);
-}
-
-void checkString(uint8_t** buffer, const string& expectedString) {
-    uint32_t size = readNext<uint32_t>(buffer);
-    string parsedString((char*)(*buffer), size);
-    EXPECT_EQ(parsedString, expectedString);
-    *buffer += size;  // move buffer past string we just read
-}
-
-void checkByteArray(uint8_t** buffer, const vector<uint8_t>& expectedByteArray) {
-    uint32_t size = readNext<uint32_t>(buffer);
-    vector<uint8_t> parsedByteArray(*buffer, *buffer + size);
-    EXPECT_EQ(parsedByteArray, expectedByteArray);
-    *buffer += size;  // move buffer past byte array we just read
-}
-
-template <class T>
-void checkAnnotation(uint8_t** buffer, uint8_t annotationId, uint8_t typeId, T annotationValue) {
-    EXPECT_EQ(readNext<uint8_t>(buffer), annotationId);
-    EXPECT_EQ(readNext<uint8_t>(buffer), typeId);
-    checkScalar<T>(buffer, annotationValue);
-}
-
-void checkMetadata(uint8_t** buffer, uint8_t numElements, int64_t startTime, int64_t endTime,
-                   uint32_t atomId, uint8_t numAtomLevelAnnotations = 0) {
-    // All events start with OBJECT_TYPE id.
-    checkTypeHeader(buffer, OBJECT_TYPE);
-
-    // We increment by 2 because the number of elements listed in the
-    // serialization accounts for the timestamp and atom id as well.
-    checkScalar(buffer, static_cast<uint8_t>(numElements + 2));
-
-    // Check timestamp
-    checkTypeHeader(buffer, INT64_TYPE);
-    int64_t timestamp = readNext<int64_t>(buffer);
-    EXPECT_GE(timestamp, startTime);
-    EXPECT_LE(timestamp, endTime);
-
-    // Check atom id
-    checkTypeHeader(buffer, INT32_TYPE, numAtomLevelAnnotations);
-    checkScalar(buffer, atomId);
-}
-
-TEST(StatsEventTest, TestScalars) {
-    uint32_t atomId = 100;
-    int32_t int32Value = -5;
-    int64_t int64Value = -2 * android::elapsedRealtimeNano();
-    float floatValue = 2.0;
-    bool boolValue = false;
-
-    int64_t startTime = android::elapsedRealtimeNano();
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, atomId);
-    AStatsEvent_writeInt32(event, int32Value);
-    AStatsEvent_writeInt64(event, int64Value);
-    AStatsEvent_writeFloat(event, floatValue);
-    AStatsEvent_writeBool(event, boolValue);
-    AStatsEvent_build(event);
-    int64_t endTime = android::elapsedRealtimeNano();
-
-    size_t bufferSize;
-    uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize);
-    uint8_t* bufferEnd = buffer + bufferSize;
-
-    checkMetadata(&buffer, /*numElements=*/4, startTime, endTime, atomId);
-
-    // check int32 element
-    checkTypeHeader(&buffer, INT32_TYPE);
-    checkScalar(&buffer, int32Value);
-
-    // check int64 element
-    checkTypeHeader(&buffer, INT64_TYPE);
-    checkScalar(&buffer, int64Value);
-
-    // check float element
-    checkTypeHeader(&buffer, FLOAT_TYPE);
-    checkScalar(&buffer, floatValue);
-
-    // check bool element
-    checkTypeHeader(&buffer, BOOL_TYPE);
-    checkScalar(&buffer, boolValue);
-
-    EXPECT_EQ(buffer, bufferEnd);  // ensure that we have read the entire buffer
-    EXPECT_EQ(AStatsEvent_getErrors(event), 0);
-    AStatsEvent_release(event);
-}
-
-TEST(StatsEventTest, TestStrings) {
-    uint32_t atomId = 100;
-    string str = "test_string";
-
-    int64_t startTime = android::elapsedRealtimeNano();
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, atomId);
-    AStatsEvent_writeString(event, str.c_str());
-    AStatsEvent_build(event);
-    int64_t endTime = android::elapsedRealtimeNano();
-
-    size_t bufferSize;
-    uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize);
-    uint8_t* bufferEnd = buffer + bufferSize;
-
-    checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId);
-
-    checkTypeHeader(&buffer, STRING_TYPE);
-    checkString(&buffer, str);
-
-    EXPECT_EQ(buffer, bufferEnd);  // ensure that we have read the entire buffer
-    EXPECT_EQ(AStatsEvent_getErrors(event), 0);
-    AStatsEvent_release(event);
-}
-
-TEST(StatsEventTest, TestByteArrays) {
-    uint32_t atomId = 100;
-    vector<uint8_t> message = {'b', 'y', 't', '\0', 'e', 's'};
-
-    int64_t startTime = android::elapsedRealtimeNano();
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, atomId);
-    AStatsEvent_writeByteArray(event, message.data(), message.size());
-    AStatsEvent_build(event);
-    int64_t endTime = android::elapsedRealtimeNano();
-
-    size_t bufferSize;
-    uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize);
-    uint8_t* bufferEnd = buffer + bufferSize;
-
-    checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId);
-
-    checkTypeHeader(&buffer, BYTE_ARRAY_TYPE);
-    checkByteArray(&buffer, message);
-
-    EXPECT_EQ(buffer, bufferEnd);  // ensure that we have read the entire buffer
-    EXPECT_EQ(AStatsEvent_getErrors(event), 0);
-    AStatsEvent_release(event);
-}
-
-TEST(StatsEventTest, TestAttributionChains) {
-    uint32_t atomId = 100;
-
-    uint8_t numNodes = 50;
-    uint32_t uids[numNodes];
-    vector<string> tags(numNodes);  // storage that cTag elements point to
-    const char* cTags[numNodes];
-    for (int i = 0; i < (int)numNodes; i++) {
-        uids[i] = i;
-        tags.push_back("test" + std::to_string(i));
-        cTags[i] = tags[i].c_str();
-    }
-
-    int64_t startTime = android::elapsedRealtimeNano();
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, atomId);
-    AStatsEvent_writeAttributionChain(event, uids, cTags, numNodes);
-    AStatsEvent_build(event);
-    int64_t endTime = android::elapsedRealtimeNano();
-
-    size_t bufferSize;
-    uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize);
-    uint8_t* bufferEnd = buffer + bufferSize;
-
-    checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId);
-
-    checkTypeHeader(&buffer, ATTRIBUTION_CHAIN_TYPE);
-    checkScalar(&buffer, numNodes);
-    for (int i = 0; i < numNodes; i++) {
-        checkScalar(&buffer, uids[i]);
-        checkString(&buffer, tags[i]);
-    }
-
-    EXPECT_EQ(buffer, bufferEnd);  // ensure that we have read the entire buffer
-    EXPECT_EQ(AStatsEvent_getErrors(event), 0);
-    AStatsEvent_release(event);
-}
-
-TEST(StatsEventTest, TestFieldAnnotations) {
-    uint32_t atomId = 100;
-
-    // first element information
-    bool boolValue = false;
-    uint8_t boolAnnotation1Id = 1;
-    uint8_t boolAnnotation2Id = 2;
-    bool boolAnnotation1Value = true;
-    int32_t boolAnnotation2Value = 3;
-
-    // second element information
-    float floatValue = -5.0;
-    uint8_t floatAnnotation1Id = 3;
-    uint8_t floatAnnotation2Id = 4;
-    int32_t floatAnnotation1Value = 8;
-    bool floatAnnotation2Value = false;
-
-    int64_t startTime = android::elapsedRealtimeNano();
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, atomId);
-    AStatsEvent_writeBool(event, boolValue);
-    AStatsEvent_addBoolAnnotation(event, boolAnnotation1Id, boolAnnotation1Value);
-    AStatsEvent_addInt32Annotation(event, boolAnnotation2Id, boolAnnotation2Value);
-    AStatsEvent_writeFloat(event, floatValue);
-    AStatsEvent_addInt32Annotation(event, floatAnnotation1Id, floatAnnotation1Value);
-    AStatsEvent_addBoolAnnotation(event, floatAnnotation2Id, floatAnnotation2Value);
-    AStatsEvent_build(event);
-    int64_t endTime = android::elapsedRealtimeNano();
-
-    size_t bufferSize;
-    uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize);
-    uint8_t* bufferEnd = buffer + bufferSize;
-
-    checkMetadata(&buffer, /*numElements=*/2, startTime, endTime, atomId);
-
-    // check first element
-    checkTypeHeader(&buffer, BOOL_TYPE, /*numAnnotations=*/2);
-    checkScalar(&buffer, boolValue);
-    checkAnnotation(&buffer, boolAnnotation1Id, BOOL_TYPE, boolAnnotation1Value);
-    checkAnnotation(&buffer, boolAnnotation2Id, INT32_TYPE, boolAnnotation2Value);
-
-    // check second element
-    checkTypeHeader(&buffer, FLOAT_TYPE, /*numAnnotations=*/2);
-    checkScalar(&buffer, floatValue);
-    checkAnnotation(&buffer, floatAnnotation1Id, INT32_TYPE, floatAnnotation1Value);
-    checkAnnotation(&buffer, floatAnnotation2Id, BOOL_TYPE, floatAnnotation2Value);
-
-    EXPECT_EQ(buffer, bufferEnd);  // ensure that we have read the entire buffer
-    EXPECT_EQ(AStatsEvent_getErrors(event), 0);
-    AStatsEvent_release(event);
-}
-
-TEST(StatsEventTest, TestAtomLevelAnnotations) {
-    uint32_t atomId = 100;
-    // atom-level annotation information
-    uint8_t boolAnnotationId = 1;
-    uint8_t int32AnnotationId = 2;
-    bool boolAnnotationValue = false;
-    int32_t int32AnnotationValue = 5;
-
-    float fieldValue = -3.5;
-
-    int64_t startTime = android::elapsedRealtimeNano();
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, atomId);
-    AStatsEvent_addBoolAnnotation(event, boolAnnotationId, boolAnnotationValue);
-    AStatsEvent_addInt32Annotation(event, int32AnnotationId, int32AnnotationValue);
-    AStatsEvent_writeFloat(event, fieldValue);
-    AStatsEvent_build(event);
-    int64_t endTime = android::elapsedRealtimeNano();
-
-    size_t bufferSize;
-    uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize);
-    uint8_t* bufferEnd = buffer + bufferSize;
-
-    checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId,
-                  /*numAtomLevelAnnotations=*/2);
-
-    // check atom-level annotations
-    checkAnnotation(&buffer, boolAnnotationId, BOOL_TYPE, boolAnnotationValue);
-    checkAnnotation(&buffer, int32AnnotationId, INT32_TYPE, int32AnnotationValue);
-
-    // check first element
-    checkTypeHeader(&buffer, FLOAT_TYPE);
-    checkScalar(&buffer, fieldValue);
-
-    EXPECT_EQ(buffer, bufferEnd);  // ensure that we have read the entire buffer
-    EXPECT_EQ(AStatsEvent_getErrors(event), 0);
-    AStatsEvent_release(event);
-}
-
-TEST(StatsEventTest, TestNoAtomIdError) {
-    AStatsEvent* event = AStatsEvent_obtain();
-    // Don't set the atom id in order to trigger the error.
-    AStatsEvent_build(event);
-
-    uint32_t errors = AStatsEvent_getErrors(event);
-    EXPECT_EQ(errors & ERROR_NO_ATOM_ID, ERROR_NO_ATOM_ID);
-
-    AStatsEvent_release(event);
-}
-
-TEST(StatsEventTest, TestPushOverflowError) {
-    const char* str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
-    const int writeCount = 120;  // Number of times to write str in the event.
-
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, 100);
-
-    // Add str to the event 120 times. Each str takes >35 bytes so this will
-    // overflow the 4068 byte buffer.
-    // We want to keep writeCount less than 127 to avoid hitting
-    // ERROR_TOO_MANY_FIELDS.
-    for (int i = 0; i < writeCount; i++) {
-        AStatsEvent_writeString(event, str);
-    }
-    AStatsEvent_write(event);
-
-    uint32_t errors = AStatsEvent_getErrors(event);
-    EXPECT_EQ(errors & ERROR_OVERFLOW, ERROR_OVERFLOW);
-
-    AStatsEvent_release(event);
-}
-
-TEST(StatsEventTest, TestPullOverflowError) {
-    const uint32_t atomId = 10100;
-    const vector<uint8_t> bytes(430 /* number of elements */, 1 /* value of each element */);
-    const int writeCount = 120;  // Number of times to write bytes in the event.
-
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, atomId);
-
-    // Add bytes to the event 120 times. Size of bytes is 430 so this will
-    // overflow the 50 KB pulled event buffer.
-    // We want to keep writeCount less than 127 to avoid hitting
-    // ERROR_TOO_MANY_FIELDS.
-    for (int i = 0; i < writeCount; i++) {
-        AStatsEvent_writeByteArray(event, bytes.data(), bytes.size());
-    }
-    AStatsEvent_build(event);
-
-    uint32_t errors = AStatsEvent_getErrors(event);
-    EXPECT_EQ(errors & ERROR_OVERFLOW, ERROR_OVERFLOW);
-
-    AStatsEvent_release(event);
-}
-
-TEST(StatsEventTest, TestLargePull) {
-    const uint32_t atomId = 100;
-    const string str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
-    const int writeCount = 120;  // Number of times to write str in the event.
-    const int64_t startTime = android::elapsedRealtimeNano();
-
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, atomId);
-
-    // Add str to the event 120 times.
-    // We want to keep writeCount less than 127 to avoid hitting
-    // ERROR_TOO_MANY_FIELDS.
-    for (int i = 0; i < writeCount; i++) {
-        AStatsEvent_writeString(event, str.c_str());
-    }
-    AStatsEvent_build(event);
-    int64_t endTime = android::elapsedRealtimeNano();
-
-    size_t bufferSize;
-    uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize);
-    uint8_t* bufferEnd = buffer + bufferSize;
-
-    checkMetadata(&buffer, writeCount, startTime, endTime, atomId);
-
-    // Check all instances of str have been written.
-    for (int i = 0; i < writeCount; i++) {
-        checkTypeHeader(&buffer, STRING_TYPE);
-        checkString(&buffer, str);
-    }
-
-    EXPECT_EQ(buffer, bufferEnd);  // Ensure that we have read the entire buffer.
-    EXPECT_EQ(AStatsEvent_getErrors(event), 0);
-    AStatsEvent_release(event);
-}
-
-TEST(StatsEventTest, TestAtomIdInvalidPositionError) {
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_writeInt32(event, 0);
-    AStatsEvent_setAtomId(event, 100);
-    AStatsEvent_writeBool(event, true);
-    AStatsEvent_build(event);
-
-    uint32_t errors = AStatsEvent_getErrors(event);
-    EXPECT_EQ(errors & ERROR_ATOM_ID_INVALID_POSITION, ERROR_ATOM_ID_INVALID_POSITION);
-
-    AStatsEvent_release(event);
-}
-
-TEST(StatsEventTest, TestOverwriteTimestamp) {
-    uint32_t atomId = 100;
-    int64_t expectedTimestamp = 0x123456789;
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, atomId);
-    AStatsEvent_overwriteTimestamp(event, expectedTimestamp);
-    AStatsEvent_build(event);
-
-    uint8_t* buffer = AStatsEvent_getBuffer(event, NULL);
-
-    // Make sure that the timestamp is being overwritten.
-    checkMetadata(&buffer, /*numElements=*/0, /*startTime=*/expectedTimestamp,
-                  /*endTime=*/expectedTimestamp, atomId);
-
-    EXPECT_EQ(AStatsEvent_getErrors(event), 0);
-    AStatsEvent_release(event);
-}
diff --git a/libstats/socket/tests/stats_writer_test.cpp b/libstats/socket/tests/stats_writer_test.cpp
deleted file mode 100644
index 749599f..0000000
--- a/libstats/socket/tests/stats_writer_test.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-#include "stats_buffer_writer.h"
-#include "stats_event.h"
-#include "stats_socket.h"
-
-TEST(StatsWriterTest, TestSocketClose) {
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, 100);
-    AStatsEvent_writeInt32(event, 5);
-    int successResult = AStatsEvent_write(event);
-    AStatsEvent_release(event);
-
-    // In the case of a successful write, we return the number of bytes written.
-    EXPECT_GT(successResult, 0);
-    EXPECT_FALSE(stats_log_is_closed());
-
-    AStatsSocket_close();
-
-    EXPECT_TRUE(stats_log_is_closed());
-}
diff --git a/libstats/socket_lazy/Android.bp b/libstats/socket_lazy/Android.bp
new file mode 100644
index 0000000..b2cd7b2
--- /dev/null
+++ b/libstats/socket_lazy/Android.bp
@@ -0,0 +1,44 @@
+// Lazy loading version of libstatssocket that can be used by code
+// that is running before the statsd APEX is mounted and
+// libstatssocket.so is available.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libstatssocket_lazy",
+    header_libs: [
+        "libstatssocket_headers",
+    ],
+    export_header_lib_headers: [
+        "libstatssocket_headers",
+    ],
+    apex_available: ["//apex_available:platform"],
+    srcs: ["libstatssocket_lazy.cpp"],
+}
+
+cc_test {
+    name: "libstatssocket_lazy_test",
+    srcs: [
+        "tests/libstatssocket_lazy_test.cpp",
+    ],
+    static_libs: ["libstatssocket_lazy"],
+    shared_libs: ["liblog"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    test_suites: ["device-tests", "mts-statsd"],
+    test_config: "libstatssocket_lazy_test.xml",
+    // TODO(b/153588990): Remove when the build system properly separates.
+    // 32bit and 64bit architectures.
+    compile_multilib: "both",
+    multilib: {
+        lib64: {
+            suffix: "64",
+        },
+        lib32: {
+            suffix: "32",
+        },
+    },
+}
diff --git a/libstats/socket_lazy/TEST_MAPPING b/libstats/socket_lazy/TEST_MAPPING
new file mode 100644
index 0000000..13afc00
--- /dev/null
+++ b/libstats/socket_lazy/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit" : [
+    {
+      "name" : "libstatssocket_lazy_test"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/libstats/socket_lazy/libstatssocket_lazy.cpp b/libstats/socket_lazy/libstatssocket_lazy.cpp
new file mode 100644
index 0000000..dd93eeb
--- /dev/null
+++ b/libstats/socket_lazy/libstatssocket_lazy.cpp
@@ -0,0 +1,201 @@
+/*
+ * 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 "libstatssocket_lazy.h"
+
+#include <mutex>
+
+#include <dlfcn.h>
+#include <stdatomic.h>
+
+#include "log/log.h"
+
+#include "stats_event.h"
+#include "stats_socket.h"
+
+// This file provides a lazy interface to libstatssocket.so to address early boot dependencies.
+// Specifically bootanimation, surfaceflinger, and lmkd run before the statsd APEX is loaded and
+// libstatssocket.so is in the statsd APEX.
+
+// Method pointers to libstatssocket methods are held in an array which simplifies checking
+// all pointers are initialized.
+enum MethodIndex {
+    // Stats Event APIs in stats_event.h.
+    k_AStatsEvent_obtain,
+    k_AStatsEvent_build,
+    k_AStatsEvent_write,
+    k_AStatsEvent_release,
+    k_AStatsEvent_setAtomId,
+    k_AStatsEvent_writeInt32,
+    k_AStatsEvent_writeInt64,
+    k_AStatsEvent_writeFloat,
+    k_AStatsEvent_writeBool,
+    k_AStatsEvent_writeByteArray,
+    k_AStatsEvent_writeString,
+    k_AStatsEvent_writeAttributionChain,
+    k_AStatsEvent_addBoolAnnotation,
+    k_AStatsEvent_addInt32Annotation,
+
+    // Stats Socket APIs in stats_socket.h.
+    k_AStatsSocket_close,
+
+    // Marker for count of methods
+    k_MethodCount
+};
+
+// Table of methods pointers in libstatssocket APIs.
+static void* g_Methods[k_MethodCount];
+
+//
+// Libstatssocket lazy loading.
+//
+
+static atomic_bool gPreventLibstatssocketLoading = false;  // Allows tests to block loading.
+
+void PreventLibstatssocketLazyLoadingForTests() {
+    gPreventLibstatssocketLoading.store(true);
+}
+
+static void* LoadLibstatssocket(int dlopen_flags) {
+    if (gPreventLibstatssocketLoading.load()) {
+        return nullptr;
+    }
+    return dlopen("libstatssocket.so", dlopen_flags);
+}
+
+//
+// Initialization and symbol binding.
+
+static void BindSymbol(void* handle, const char* name, enum MethodIndex index) {
+    void* symbol = dlsym(handle, name);
+    LOG_ALWAYS_FATAL_IF(symbol == nullptr, "Failed to find symbol '%s' in libstatssocket.so: %s",
+                        name, dlerror());
+    g_Methods[index] = symbol;
+}
+
+static void InitializeOnce() {
+    void* handle = LoadLibstatssocket(RTLD_NOW);
+    LOG_ALWAYS_FATAL_IF(handle == nullptr, "Failed to load libstatssocket.so: %s", dlerror());
+
+#undef BIND_SYMBOL
+#define BIND_SYMBOL(name) BindSymbol(handle, #name, k_##name);
+    // Methods in stats_event.h.
+    BIND_SYMBOL(AStatsEvent_obtain);
+    BIND_SYMBOL(AStatsEvent_build);
+    BIND_SYMBOL(AStatsEvent_write);
+    BIND_SYMBOL(AStatsEvent_release);
+    BIND_SYMBOL(AStatsEvent_setAtomId);
+    BIND_SYMBOL(AStatsEvent_writeInt32);
+    BIND_SYMBOL(AStatsEvent_writeInt64);
+    BIND_SYMBOL(AStatsEvent_writeFloat);
+    BIND_SYMBOL(AStatsEvent_writeBool);
+    BIND_SYMBOL(AStatsEvent_writeByteArray);
+    BIND_SYMBOL(AStatsEvent_writeString);
+    BIND_SYMBOL(AStatsEvent_writeAttributionChain);
+    BIND_SYMBOL(AStatsEvent_addBoolAnnotation);
+    BIND_SYMBOL(AStatsEvent_addInt32Annotation);
+
+    // Methods in stats_socket.h.
+    BIND_SYMBOL(AStatsSocket_close);
+#undef BIND_SYMBOL
+
+    // Check every symbol is bound.
+    for (int i = 0; i < k_MethodCount; ++i) {
+        LOG_ALWAYS_FATAL_IF(g_Methods[i] == nullptr,
+                            "Uninitialized method in libstatssocket_lazy at index: %d", i);
+    }
+}
+
+static void EnsureInitialized() {
+    static std::once_flag initialize_flag;
+    std::call_once(initialize_flag, InitializeOnce);
+}
+
+#define INVOKE_METHOD(name, args...)                            \
+    do {                                                        \
+        EnsureInitialized();                                    \
+        void* method = g_Methods[k_##name];                     \
+        return reinterpret_cast<decltype(&name)>(method)(args); \
+    } while (0)
+
+//
+// Forwarding for methods in stats_event.h.
+//
+
+AStatsEvent* AStatsEvent_obtain() {
+    INVOKE_METHOD(AStatsEvent_obtain);
+}
+
+void AStatsEvent_build(AStatsEvent* event) {
+    INVOKE_METHOD(AStatsEvent_build, event);
+}
+
+int AStatsEvent_write(AStatsEvent* event) {
+    INVOKE_METHOD(AStatsEvent_write, event);
+}
+
+void AStatsEvent_release(AStatsEvent* event) {
+    INVOKE_METHOD(AStatsEvent_release, event);
+}
+
+void AStatsEvent_setAtomId(AStatsEvent* event, uint32_t atomId) {
+    INVOKE_METHOD(AStatsEvent_setAtomId, event, atomId);
+}
+
+void AStatsEvent_writeInt32(AStatsEvent* event, int32_t value) {
+    INVOKE_METHOD(AStatsEvent_writeInt32, event, value);
+}
+
+void AStatsEvent_writeInt64(AStatsEvent* event, int64_t value) {
+    INVOKE_METHOD(AStatsEvent_writeInt64, event, value);
+}
+
+void AStatsEvent_writeFloat(AStatsEvent* event, float value) {
+    INVOKE_METHOD(AStatsEvent_writeFloat, event, value);
+}
+
+void AStatsEvent_writeBool(AStatsEvent* event, bool value) {
+    INVOKE_METHOD(AStatsEvent_writeBool, event, value);
+}
+
+void AStatsEvent_writeByteArray(AStatsEvent* event, const uint8_t* buf, size_t numBytes) {
+    INVOKE_METHOD(AStatsEvent_writeByteArray, event, buf, numBytes);
+}
+
+void AStatsEvent_writeString(AStatsEvent* event, const char* value) {
+    INVOKE_METHOD(AStatsEvent_writeString, event, value);
+}
+
+void AStatsEvent_writeAttributionChain(AStatsEvent* event, const uint32_t* uids,
+                                       const char* const* tags, uint8_t numNodes) {
+    INVOKE_METHOD(AStatsEvent_writeAttributionChain, event, uids, tags, numNodes);
+}
+
+void AStatsEvent_addBoolAnnotation(AStatsEvent* event, uint8_t annotationId, bool value) {
+    INVOKE_METHOD(AStatsEvent_addBoolAnnotation, event, annotationId, value);
+}
+
+void AStatsEvent_addInt32Annotation(AStatsEvent* event, uint8_t annotationId, int32_t value) {
+    INVOKE_METHOD(AStatsEvent_addInt32Annotation, event, annotationId, value);
+}
+
+//
+// Forwarding for methods in stats_socket.h.
+//
+
+void AStatsSocket_close() {
+    INVOKE_METHOD(AStatsSocket_close);
+}
\ No newline at end of file
diff --git a/libstats/socket/stats_socket.c b/libstats/socket_lazy/libstatssocket_lazy.h
similarity index 75%
rename from libstats/socket/stats_socket.c
rename to libstats/socket_lazy/libstatssocket_lazy.h
index 09f8967..3ff87cb 100644
--- a/libstats/socket/stats_socket.c
+++ b/libstats/socket_lazy/libstatssocket_lazy.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#include "include/stats_socket.h"
-#include "stats_buffer_writer.h"
+#pragma once
 
-void AStatsSocket_close() {
-    stats_log_close();
-}
+extern "C" void PreventLibstatssocketLazyLoadingForTests();
\ No newline at end of file
diff --git a/libstats/pull/libstatspull_test.xml b/libstats/socket_lazy/libstatssocket_lazy_test.xml
similarity index 76%
copy from libstats/pull/libstatspull_test.xml
copy to libstats/socket_lazy/libstatssocket_lazy_test.xml
index 233fc1f..ca6339b 100644
--- a/libstats/pull/libstatspull_test.xml
+++ b/libstats/socket_lazy/libstatssocket_lazy_test.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<!-- 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.
@@ -13,7 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<configuration description="Runs libstatspull_test.">
+<configuration description="Runs libstatssocket_lazy_test.">
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="apct-native" />
     <option name="test-suite-tag" value="mts" />
@@ -21,17 +21,17 @@
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
 
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
-       <option name="cleanup" value="true" />
-       <option name="push" value="libstatspull_test->/data/local/tmp/libstatspull_test" />
-       <option name="append-bitness" value="true" />
+        <option name="cleanup" value="true" />
+        <option name="push" value="libstatssocket_lazy_test->/data/local/tmp/libstatssocket_lazy_test" />
+        <option name="append-bitness" value="true" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="libstatspull_test" />
+        <option name="module-name" value="libstatssocket_lazy_test" />
     </test>
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
         <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
     </object>
-</configuration>
+</configuration>
\ No newline at end of file
diff --git a/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp
new file mode 100644
index 0000000..fe13598
--- /dev/null
+++ b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp
@@ -0,0 +1,58 @@
+/*
+ * 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 "../libstatssocket_lazy.h"
+
+#include <gtest/gtest.h>
+
+#include "stats_event.h"
+#include "stats_socket.h"
+
+// The tests here are just for the case when libstatssocket.so cannot be loaded by
+// libstatssocket_lazy.
+class LibstatssocketLazyTest : public ::testing::Test {
+  protected:
+    virtual void SetUp() {
+        ::testing::Test::SetUp();
+        PreventLibstatssocketLazyLoadingForTests();
+    }
+};
+
+static const char* kLoadFailed = "Failed to load libstatssocket.so";
+
+TEST_F(LibstatssocketLazyTest, NoLibstatssocketForStatsEvent) {
+    AStatsEvent* event = NULL;
+    EXPECT_DEATH(AStatsEvent_obtain(), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_build(event), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_write(event), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_release(event), kLoadFailed);
+
+    EXPECT_DEATH(AStatsEvent_setAtomId(event, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeInt32(event, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeInt64(event, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeFloat(event, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeBool(event, false), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeByteArray(event, NULL, 0), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeString(event, NULL), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_writeAttributionChain(event, NULL, NULL, 0), kLoadFailed);
+
+    EXPECT_DEATH(AStatsEvent_addBoolAnnotation(event, 0, false), kLoadFailed);
+    EXPECT_DEATH(AStatsEvent_addInt32Annotation(event, 0, 0), kLoadFailed);
+}
+
+TEST_F(LibstatssocketLazyTest, NoLibstatssocketForStatsSocket) {
+    EXPECT_DEATH(AStatsSocket_close(), kLoadFailed);
+}
\ No newline at end of file
diff --git a/libsuspend/Android.bp b/libsuspend/Android.bp
index c5f1f5e..671de4d 100644
--- a/libsuspend/Android.bp
+++ b/libsuspend/Android.bp
@@ -1,5 +1,9 @@
 // Copyright 2012 The Android Open Source Project
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library {
     name: "libsuspend",
     srcs: [
diff --git a/libsync/Android.bp b/libsync/Android.bp
index 4828892..99c88cf 100644
--- a/libsync/Android.bp
+++ b/libsync/Android.bp
@@ -1,3 +1,20 @@
+package {
+    default_applicable_licenses: ["system_core_libsync_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+    name: "system_core_libsync_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "NOTICE",
+    ],
+}
+
 ndk_headers {
     name: "libsync_headers",
     from: "include/ndk",
@@ -25,7 +42,9 @@
     recovery_available: true,
     native_bridge_supported: true,
     defaults: ["libsync_defaults"],
-    llndk_stubs: "libsync.llndk",
+    llndk: {
+        symbol_file: "libsync.map.txt",
+    },
     stubs: {
         symbol_file: "libsync.map.txt",
         versions: [
@@ -34,12 +53,6 @@
     },
 }
 
-llndk_library {
-    name: "libsync.llndk",
-    symbol_file: "libsync.map.txt",
-    export_include_dirs: ["include"],
-}
-
 cc_test {
     name: "sync-unit-tests",
     shared_libs: ["libsync"],
diff --git a/libsync/include/ndk/sync.h b/libsync/include/ndk/sync.h
index 2a59e35..38ccb68 100644
--- a/libsync/include/ndk/sync.h
+++ b/libsync/include/ndk/sync.h
@@ -33,8 +33,6 @@
 
 __BEGIN_DECLS
 
-#if __ANDROID_API__ >= 26
-
 /* Fences indicate the status of an asynchronous task. They are initially
  * in unsignaled state (0), and make a one-time transition to either signaled
  * (1) or error (< 0) state. A sync file is a collection of one or more fences;
@@ -101,8 +99,6 @@
  */
 void sync_file_info_free(struct sync_file_info* info) __INTRODUCED_IN(26);
 
-#endif /* __ANDROID_API__ >= 26 */
-
 __END_DECLS
 
 #endif /* ANDROID_SYNC_H */
diff --git a/libsystem/Android.bp b/libsystem/Android.bp
index 12c946c..dacc8ca 100644
--- a/libsystem/Android.bp
+++ b/libsystem/Android.bp
@@ -1,6 +1,11 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_headers {
     name: "libsystem_headers",
     vendor_available: true,
+    product_available: true,
     recovery_available: true,
     vendor_ramdisk_available: true,
     host_supported: true,
diff --git a/libsystem/OWNERS b/libsystem/OWNERS
index 4f800d4..9bda04c 100644
--- a/libsystem/OWNERS
+++ b/libsystem/OWNERS
@@ -1,8 +1,6 @@
 # graphics/composer
 adyabr@google.com
 lpy@google.com
-stoza@google.com
-vhau@google.com
 
 # camera
 etalvala@google.com
diff --git a/libsysutils/Android.bp b/libsysutils/Android.bp
index 3b98bab..5f472b2 100644
--- a/libsysutils/Android.bp
+++ b/libsysutils/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library {
     name: "libsysutils",
     vendor_available: true,
diff --git a/libusbhost/Android.bp b/libusbhost/Android.bp
index fc6f305..3883317 100644
--- a/libusbhost/Android.bp
+++ b/libusbhost/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library {
     name: "libusbhost",
     vendor_available: true,
diff --git a/libutils/Android.bp b/libutils/Android.bp
index 8ee16f3..13e4c02 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -1,20 +1,22 @@
-// Copyright (C) 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.
+package {
+    default_applicable_licenses: ["system_core_libutils_license"],
+}
+
+license {
+    name: "system_core_libutils_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "NOTICE",
+    ],
+}
 
 cc_library_headers {
     name: "libutils_headers",
     vendor_available: true,
+    product_available: true,
     recovery_available: true,
     vendor_ramdisk_available: true,
     host_supported: true,
@@ -62,6 +64,7 @@
 cc_defaults {
     name: "libutils_defaults",
     vendor_available: true,
+    product_available: true,
     recovery_available: true,
     vndk: {
         enabled: true,
@@ -72,6 +75,7 @@
     cflags: [
         "-Wall",
         "-Werror",
+        "-Wno-exit-time-destructors",
     ],
     header_libs: [
         "libbase_headers",
@@ -137,6 +141,7 @@
         "Errors.cpp",
         "FileMap.cpp",
         "JenkinsHash.cpp",
+        "LightRefBase.cpp",
         "NativeHandle.cpp",
         "Printer.cpp",
         "RefBase.cpp",
@@ -270,12 +275,6 @@
 }
 
 cc_fuzz {
-    name: "libutils_fuzz_stopwatch",
-    defaults: ["libutils_fuzz_defaults"],
-    srcs: ["StopWatch_fuzz.cpp"],
-}
-
-cc_fuzz {
     name: "libutils_fuzz_refbase",
     defaults: ["libutils_fuzz_defaults"],
     srcs: ["RefBase_fuzz.cpp"],
diff --git a/libstats/socket/stats_socket.c b/libutils/LightRefBase.cpp
similarity index 61%
copy from libstats/socket/stats_socket.c
copy to libutils/LightRefBase.cpp
index 09f8967..e08ffec 100644
--- a/libstats/socket/stats_socket.c
+++ b/libutils/LightRefBase.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,9 +14,16 @@
  * limitations under the License.
  */
 
-#include "include/stats_socket.h"
-#include "stats_buffer_writer.h"
+#define LOG_TAG "LightRefBase"
 
-void AStatsSocket_close() {
-    stats_log_close();
+#include <utils/LightRefBase.h>
+
+#include <log/log.h>
+
+namespace android {
+
+void LightRefBase_reportIncStrongRequireStrongFailed(const void* thiz) {
+    LOG_ALWAYS_FATAL("incStrongRequireStrong() called on %p which isn't already owned", thiz);
 }
+
+}  // namespace android
diff --git a/libutils/README b/libutils/README
deleted file mode 100644
index 01741e0..0000000
--- a/libutils/README
+++ /dev/null
@@ -1,289 +0,0 @@
-Android Utility Function Library
-================================
-
-
-If you need a feature that is native to Linux but not present on other
-platforms, construct a platform-dependent implementation that shares
-the Linux interface.  That way the actual device runs as "light" as
-possible.
-
-If that isn't feasible, create a system-independent interface and hide
-the details.
-
-The ultimate goal is *not* to create a super-duper platform abstraction
-layer.  The goal is to provide an optimized solution for Linux with
-reasonable implementations for other platforms.
-
-
-
-Resource overlay
-================
-
-
-Introduction
-------------
-
-Overlay packages are special .apk files which provide no code but
-additional resource values (and possibly new configurations) for
-resources in other packages. When an application requests resources,
-the system will return values from either the application's original
-package or any associated overlay package. Any redirection is completely
-transparent to the calling application.
-
-Resource values have the following precedence table, listed in
-descending precedence.
-
- * overlay package, matching config (eg res/values-en-land)
-
- * original package, matching config
-
- * overlay package, no config (eg res/values)
-
- * original package, no config
-
-During compilation, overlay packages are differentiated from regular
-packages by passing the -o flag to aapt.
-
-
-Background
-----------
-
-This section provides generic background material on resources in
-Android.
-
-
-How resources are bundled in .apk files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Android .apk files are .zip files, usually housing .dex code,
-certificates and resources, though packages containing resources but
-no code are possible. Resources can be divided into the following
-categories; a `configuration' indicates a set of phone language, display
-density, network operator, etc.
-
- * assets: uncompressed, raw files packaged as part of an .apk and
-           explicitly referenced by filename. These files are
-           independent of configuration.
-
- * res/drawable: bitmap or xml graphics. Each file may have different
-                 values depending on configuration.
-
- * res/values: integers, strings, etc. Each resource may have different
-               values depending on configuration.
-
-Resource meta information and information proper is stored in a binary
-format in a named file resources.arsc, bundled as part of the .apk.
-
-Resource IDs and lookup
-~~~~~~~~~~~~~~~~~~~~~~~
-During compilation, the aapt tool gathers application resources and
-generates a resources.arsc file. Each resource name is assigned an
-integer ID 0xppttiii (translated to a symbolic name via R.java), where
-
- * pp: corresponds to the package namespace (details below).
-
- * tt: corresponds to the resource type (string, int, etc). Every
-       resource of the same type within the same package has the same
-       tt value, but depending on available types, the actual numerical
-       value may be different between packages.
-
- * iiii: sequential number, assigned in the order resources are found.
-
-Resource values are specified paired with a set of configuration
-constraints (the default being the empty set), eg res/values-sv-port
-which imposes restrictions on language (Swedish) and display orientation
-(portrait). During lookup, every constraint set is matched against the
-current configuration, and the value corresponding to the best matching
-constraint set is returned (ResourceTypes.{h,cpp}).
-
-Parsing of resources.arsc is handled by ResourceTypes.cpp; this utility
-is governed by AssetManager.cpp, which tracks loaded resources per
-process.
-
-Assets are looked up by path and filename in AssetManager.cpp. The path
-to resources in res/drawable are located by ResourceTypes.cpp and then
-handled like assets by AssetManager.cpp. Other resources are handled
-solely by ResourceTypes.cpp.
-
-Package ID as namespace
-~~~~~~~~~~~~~~~~~~~~~~~
-The pp part of a resource ID defines a namespace. Android currently
-defines two namespaces:
-
- * 0x01: system resources (pre-installed in framework-res.apk)
-
- * 0x7f: application resources (bundled in the application .apk)
-
-ResourceTypes.cpp supports package IDs between 0x01 and 0x7f
-(inclusive); values outside this range are invalid.
-
-Each running (Dalvik) process is assigned a unique instance of
-AssetManager, which in turn keeps a forest structure of loaded
-resource.arsc files. Normally, this forest is structured as follows,
-where mPackageMap is the internal vector employed in ResourceTypes.cpp.
-
-mPackageMap[0x00] -> system package
-mPackageMap[0x01] -> NULL
-mPackageMap[0x02] -> NULL
-...
-mPackageMap[0x7f - 2] -> NULL
-mPackageMap[0x7f - 1] -> application package
-
-
-
-The resource overlay extension
-------------------------------
-
-The resource overlay mechanism aims to (partly) shadow and extend
-existing resources with new values for defined and new configurations.
-Technically, this is achieved by adding resource-only packages (called
-overlay packages) to existing resource namespaces, like so:
-
-mPackageMap[0x00] -> system package -> system overlay package
-mPackageMap[0x01] -> NULL
-mPackageMap[0x02] -> NULL
-...
-mPackageMap[0x7f - 2] -> NULL
-mPackageMap[0x7f - 1] -> application package -> overlay 1 -> overlay 2
-
-The use of overlay resources is completely transparent to
-applications; no additional resource identifiers are introduced, only
-configuration/value pairs. Any number of overlay packages may be loaded
-at a time; overlay packages are agnostic to what they target -- both
-system and application resources are fair game.
-
-The package targeted by an overlay package is called the target or
-original package.
-
-Resource overlay operates on symbolic resources names. Hence, to
-override the string/str1 resources in a package, the overlay package
-would include a resource also named string/str1. The end user does not
-have to worry about the numeric resources IDs assigned by aapt, as this
-is resolved automatically by the system.
-
-As of this writing, the use of resource overlay has not been fully
-explored. Until it has, only OEMs are trusted to use resource overlay.
-For this reason, overlay packages must reside in /system/overlay.
-
-
-Resource ID mapping
-~~~~~~~~~~~~~~~~~~~
-Resource identifiers must be coherent within the same namespace (ie
-PackageGroup in ResourceTypes.cpp). Calling applications will refer to
-resources using the IDs defined in the original package, but there is no
-guarantee aapt has assigned the same ID to the corresponding resource in
-an overlay package. To translate between the two, a resource ID mapping
-{original ID -> overlay ID} is created during package installation
-(PackageManagerService.java) and used during resource lookup. The
-mapping is stored in /data/resource-cache, with a @idmap file name
-suffix.
-
-The idmap file format is documented in a separate section, below.
-
-
-Package management
-~~~~~~~~~~~~~~~~~~
-Packages are managed by the PackageManagerService. Addition and removal
-of packages are monitored via the inotify framework, exposed via
-android.os.FileObserver.
-
-During initialization of a Dalvik process, ActivityThread.java requests
-the process' AssetManager (by proxy, via AssetManager.java and JNI)
-to load a list of packages. This list includes overlay packages, if
-present.
-
-When a target package or a corresponding overlay package is installed,
-the target package's process is stopped and a new idmap is generated.
-This is similar to how applications are stopped when their packages are
-upgraded.
-
-
-Creating overlay packages
--------------------------
-
-Overlay packages should contain no code, define (some) resources with
-the same type and name as in the original package, and be compiled with
-the -o flag passed to aapt.
-
-The aapt -o flag instructs aapt to create an overlay package.
-Technically, this means the package will be assigned package id 0x00.
-
-There are no restrictions on overlay packages names, though the naming
-convention <original.package.name>.overlay.<name> is recommended.
-
-
-Example overlay package
-~~~~~~~~~~~~~~~~~~~~~~~
-
-To overlay the resource bool/b in package com.foo.bar, to be applied
-when the display is in landscape mode, create a new package with
-no source code and a single .xml file under res/values-land, with
-an entry for bool/b. Compile with aapt -o and place the results in
-/system/overlay by adding the following to Android.mk:
-
-LOCAL_AAPT_FLAGS := -o com.foo.bar
-LOCAL_MODULE_PATH := $(TARGET_OUT)/overlay
-
-
-The ID map (idmap) file format
-------------------------------
-
-The idmap format is designed for lookup performance. However, leading
-and trailing undefined overlay values are discarded to reduce the memory
-footprint.
-
-
-idmap grammar
-~~~~~~~~~~~~~
-All atoms (names in square brackets) are uint32_t integers. The
-idmap-magic constant spells "idmp" in ASCII. Offsets are given relative
-to the data_header, not to the beginning of the file.
-
-map          := header data
-header       := idmap-magic <crc32-original-pkg> <crc32-overlay-pkg>
-idmap-magic  := <0x706d6469>
-data         := data_header type_block+
-data_header  := <m> header_block{m}
-header_block := <0> | <type_block_offset>
-type_block   := <n> <id_offset> entry{n}
-entry        := <resource_id_in_target_package>
-
-
-idmap example
-~~~~~~~~~~~~~
-Given a pair of target and overlay packages with CRC sums 0x216a8fe2
-and 0x6b9beaec, each defining the following resources
-
-Name          Target package  Overlay package
-string/str0   0x7f010000      -
-string/str1   0x7f010001      0x7f010000
-string/str2   0x7f010002      -
-string/str3   0x7f010003      0x7f010001
-string/str4   0x7f010004      -
-bool/bool0    0x7f020000      -
-integer/int0  0x7f030000      0x7f020000
-integer/int1  0x7f030001      -
-
-the corresponding resource map is
-
-0x706d6469 0x216a8fe2 0x6b9beaec 0x00000003 \
-0x00000004 0x00000000 0x00000009 0x00000003 \
-0x00000001 0x7f010000 0x00000000 0x7f010001 \
-0x00000001 0x00000000 0x7f020000
-
-or, formatted differently
-
-0x706d6469  # magic: all idmap files begin with this constant
-0x216a8fe2  # CRC32 of the resources.arsc file in the original package
-0x6b9beaec  # CRC32 of the resources.arsc file in the overlay package
-0x00000003  # header; three types (string, bool, integer) in the target package
-0x00000004  #   header_block for type 0 (string) is located at offset 4
-0x00000000  #   no bool type exists in overlay package -> no header_block
-0x00000009  #   header_block for type 2 (integer) is located at offset 9
-0x00000003  # header_block for string; overlay IDs span 3 elements
-0x00000001  #   the first string in target package is entry 1 == offset
-0x7f010000  #   target 0x7f01001 -> overlay 0x7f010000
-0x00000000  #   str2 not defined in overlay package
-0x7f010001  #   target 0x7f010003 -> overlay 0x7f010001
-0x00000001  # header_block for integer; overlay IDs span 1 element
-0x00000000  #   offset == 0
-0x7f020000  #   target 0x7f030000 -> overlay 0x7f020000
diff --git a/libutils/RefBase.cpp b/libutils/RefBase.cpp
index 8e45226..b57e287 100644
--- a/libutils/RefBase.cpp
+++ b/libutils/RefBase.cpp
@@ -443,6 +443,20 @@
     refs->mBase->onFirstRef();
 }
 
+void RefBase::incStrongRequireStrong(const void* id) const {
+    weakref_impl* const refs = mRefs;
+    refs->incWeak(id);
+
+    refs->addStrongRef(id);
+    const int32_t c = refs->mStrong.fetch_add(1, std::memory_order_relaxed);
+
+    LOG_ALWAYS_FATAL_IF(c <= 0 || c == INITIAL_STRONG_VALUE,
+                        "incStrongRequireStrong() called on %p which isn't already owned", refs);
+#if PRINT_REFS
+    ALOGD("incStrong (requiring strong) of %p from %p: cnt=%d\n", this, id, c);
+#endif
+}
+
 void RefBase::decStrong(const void* id) const
 {
     weakref_impl* const refs = mRefs;
@@ -521,6 +535,14 @@
     ALOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this);
 }
 
+void RefBase::weakref_type::incWeakRequireWeak(const void* id)
+{
+    weakref_impl* const impl = static_cast<weakref_impl*>(this);
+    impl->addWeakRef(id);
+    const int32_t c __unused = impl->mWeak.fetch_add(1,
+            std::memory_order_relaxed);
+    LOG_ALWAYS_FATAL_IF(c <= 0, "incWeakRequireWeak called on %p which has no weak refs", this);
+}
 
 void RefBase::weakref_type::decWeak(const void* id)
 {
diff --git a/libutils/RefBase_fuzz.cpp b/libutils/RefBase_fuzz.cpp
old mode 100755
new mode 100644
index 2a92531..69288b3
--- a/libutils/RefBase_fuzz.cpp
+++ b/libutils/RefBase_fuzz.cpp
@@ -14,66 +14,156 @@
  * limitations under the License.
  */
 
-#include <atomic>
+#define LOG_TAG "RefBaseFuzz"
+
 #include <thread>
 
 #include "fuzzer/FuzzedDataProvider.h"
+#include "utils/Log.h"
+#include "utils/RWLock.h"
 #include "utils/RefBase.h"
 #include "utils/StrongPointer.h"
+
 using android::RefBase;
+using android::RWLock;
 using android::sp;
 using android::wp;
 
-static constexpr int REFBASE_INITIAL_STRONG_VALUE = (1 << 28);
-static constexpr int REFBASE_MAX_COUNT = 0xfffff;
-
-static constexpr int MAX_OPERATIONS = 100;
-static constexpr int MAX_THREADS = 10;
-
-bool canDecrementStrong(RefBase* ref) {
-    // There's an assert around decrementing the strong count too much that causes an artificial
-    // crash This is just running BAD_STRONG from RefBase
-    const int32_t count = ref->getStrongCount() - 1;
-    return !(count == 0 || ((count) & (~(REFBASE_MAX_COUNT | REFBASE_INITIAL_STRONG_VALUE))) != 0);
-}
-bool canDecrementWeak(RefBase* ref) {
-    const int32_t count = ref->getWeakRefs()->getWeakCount() - 1;
-    return !((count) == 0 || ((count) & (~REFBASE_MAX_COUNT)) != 0);
-}
-
+static constexpr int kMaxOperations = 100;
+static constexpr int kMaxThreads = 10;
 struct RefBaseSubclass : public RefBase {
-    RefBaseSubclass() {}
-    virtual ~RefBaseSubclass() {}
+  public:
+    RefBaseSubclass(bool* deletedCheck, RWLock& deletedMtx)
+        : mDeleted(deletedCheck), mRwLock(deletedMtx) {
+        RWLock::AutoWLock lock(mRwLock);
+        *mDeleted = false;
+        extendObjectLifetime(OBJECT_LIFETIME_WEAK);
+    }
+
+    virtual ~RefBaseSubclass() {
+        RWLock::AutoWLock lock(mRwLock);
+        *mDeleted = true;
+    }
+
+  private:
+    bool* mDeleted;
+    android::RWLock& mRwLock;
 };
 
-std::vector<std::function<void(RefBaseSubclass*)>> operations = {
-        [](RefBaseSubclass* ref) -> void { ref->getStrongCount(); },
-        [](RefBaseSubclass* ref) -> void { ref->printRefs(); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->printRefs(); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->getWeakCount(); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->refBase(); },
-        [](RefBaseSubclass* ref) -> void { ref->incStrong(nullptr); },
-        [](RefBaseSubclass* ref) -> void {
-            if (canDecrementStrong(ref)) {
+// A thread-specific state object for ref
+struct RefThreadState {
+    size_t strongCount = 0;
+    size_t weakCount = 0;
+};
+
+RWLock gRefDeletedLock;
+bool gRefDeleted = false;
+bool gHasModifiedRefs = false;
+RefBaseSubclass* ref;
+RefBase::weakref_type* weakRefs;
+
+// These operations don't need locks as they explicitly check per-thread counts before running
+// they also have the potential to write to gRefDeleted, so must not be locked.
+const std::vector<std::function<void(RefThreadState*)>> kUnlockedOperations = {
+        [](RefThreadState* refState) -> void {
+            if (refState->strongCount > 0) {
                 ref->decStrong(nullptr);
+                gHasModifiedRefs = true;
+                refState->strongCount--;
             }
         },
-        [](RefBaseSubclass* ref) -> void { ref->forceIncStrong(nullptr); },
-        [](RefBaseSubclass* ref) -> void { ref->createWeak(nullptr); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->attemptIncStrong(nullptr); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->attemptIncWeak(nullptr); },
-        [](RefBaseSubclass* ref) -> void {
-            if (canDecrementWeak(ref)) {
-                ref->getWeakRefs()->decWeak(nullptr);
+        [](RefThreadState* refState) -> void {
+            if (refState->weakCount > 0) {
+                weakRefs->decWeak(nullptr);
+                gHasModifiedRefs = true;
+                refState->weakCount--;
             }
         },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->incWeak(nullptr); },
-        [](RefBaseSubclass* ref) -> void { ref->getWeakRefs()->printRefs(); },
 };
 
-void loop(RefBaseSubclass* loopRef, const std::vector<uint8_t>& fuzzOps) {
+const std::vector<std::function<void(RefThreadState*)>> kMaybeLockedOperations = {
+        // Read-only operations
+        [](RefThreadState*) -> void { ref->getStrongCount(); },
+        [](RefThreadState*) -> void { weakRefs->getWeakCount(); },
+        [](RefThreadState*) -> void { ref->printRefs(); },
+
+        // Read/write operations
+        [](RefThreadState* refState) -> void {
+            ref->incStrong(nullptr);
+            gHasModifiedRefs = true;
+            refState->strongCount++;
+        },
+        [](RefThreadState* refState) -> void {
+            ref->forceIncStrong(nullptr);
+            gHasModifiedRefs = true;
+            refState->strongCount++;
+        },
+        [](RefThreadState* refState) -> void {
+            ref->createWeak(nullptr);
+            gHasModifiedRefs = true;
+            refState->weakCount++;
+        },
+        [](RefThreadState* refState) -> void {
+            // This will increment weak internally, then attempt to
+            // promote it to strong. If it fails, it decrements weak.
+            // If it succeeds, the weak is converted to strong.
+            // Both cases net no weak reference change.
+            if (weakRefs->attemptIncStrong(nullptr)) {
+                refState->strongCount++;
+                gHasModifiedRefs = true;
+            }
+        },
+        [](RefThreadState* refState) -> void {
+            if (weakRefs->attemptIncWeak(nullptr)) {
+                refState->weakCount++;
+                gHasModifiedRefs = true;
+            }
+        },
+        [](RefThreadState* refState) -> void {
+            weakRefs->incWeak(nullptr);
+            gHasModifiedRefs = true;
+            refState->weakCount++;
+        },
+};
+
+void loop(const std::vector<uint8_t>& fuzzOps) {
+    RefThreadState state;
+    uint8_t lockedOpSize = kMaybeLockedOperations.size();
+    uint8_t totalOperationTypes = lockedOpSize + kUnlockedOperations.size();
     for (auto op : fuzzOps) {
-        operations[op % operations.size()](loopRef);
+        auto opVal = op % totalOperationTypes;
+        if (opVal >= lockedOpSize) {
+            kUnlockedOperations[opVal % lockedOpSize](&state);
+        } else {
+            // We only need to lock if we have no strong or weak count
+            bool shouldLock = state.strongCount == 0 && state.weakCount == 0;
+            if (shouldLock) {
+                gRefDeletedLock.readLock();
+                // If ref has deleted itself, we can no longer fuzz on this thread.
+                if (gRefDeleted) {
+                    // Unlock since we're exiting the loop here.
+                    gRefDeletedLock.unlock();
+                    return;
+                }
+            }
+            // Execute the locked operation
+            kMaybeLockedOperations[opVal](&state);
+            // Unlock if we locked.
+            if (shouldLock) {
+                gRefDeletedLock.unlock();
+            }
+        }
+    }
+
+    // Instead of explicitly freeing this, we're going to remove our weak and
+    // strong references.
+    for (; state.weakCount > 0; state.weakCount--) {
+        weakRefs->decWeak(nullptr);
+    }
+
+    // Clean up any strong references
+    for (; state.strongCount > 0; state.strongCount--) {
+        ref->decStrong(nullptr);
     }
 }
 
@@ -81,23 +171,35 @@
     std::vector<std::thread> threads = std::vector<std::thread>();
 
     // Get the number of threads to generate
-    uint8_t count = dataProvider->ConsumeIntegralInRange<uint8_t>(1, MAX_THREADS);
-
+    uint8_t count = dataProvider->ConsumeIntegralInRange<uint8_t>(1, kMaxThreads);
     // Generate threads
     for (uint8_t i = 0; i < count; i++) {
-        RefBaseSubclass* threadRef = new RefBaseSubclass();
-        uint8_t opCount = dataProvider->ConsumeIntegralInRange<uint8_t>(1, MAX_OPERATIONS);
+        uint8_t opCount = dataProvider->ConsumeIntegralInRange<uint8_t>(1, kMaxOperations);
         std::vector<uint8_t> threadOperations = dataProvider->ConsumeBytes<uint8_t>(opCount);
-        std::thread tmp = std::thread(loop, threadRef, threadOperations);
-        threads.push_back(move(tmp));
+        std::thread tmpThread = std::thread(loop, threadOperations);
+        threads.push_back(move(tmpThread));
     }
 
     for (auto& th : threads) {
         th.join();
     }
 }
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    gHasModifiedRefs = false;
+    ref = new RefBaseSubclass(&gRefDeleted, gRefDeletedLock);
+    weakRefs = ref->getWeakRefs();
+    // Since we are modifying flags, (flags & OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK
+    // is true. The destructor for RefBase should clean up weakrefs because of this.
     FuzzedDataProvider dataProvider(data, size);
     spawnThreads(&dataProvider);
+    LOG_ALWAYS_FATAL_IF(!gHasModifiedRefs && gRefDeleted, "ref(%p) was prematurely deleted!", ref);
+    // We need to explicitly delete this object
+    // if no refs have been added or deleted.
+    if (!gHasModifiedRefs && !gRefDeleted) {
+        delete ref;
+    }
+    LOG_ALWAYS_FATAL_IF(gHasModifiedRefs && !gRefDeleted,
+                        "ref(%p) should be deleted, is it leaking?", ref);
     return 0;
 }
diff --git a/libutils/RefBase_test.cpp b/libutils/RefBase_test.cpp
index c9b4894..93f9654 100644
--- a/libutils/RefBase_test.cpp
+++ b/libutils/RefBase_test.cpp
@@ -241,6 +241,30 @@
     ASSERT_FALSE(wp1 != wp2);
 }
 
+TEST(RefBase, AssertWeakRefExistsSuccess) {
+    bool isDeleted;
+    sp<Foo> foo = sp<Foo>::make(&isDeleted);
+    wp<Foo> weakFoo = foo;
+
+    EXPECT_EQ(weakFoo, wp<Foo>::fromExisting(foo.get()));
+    EXPECT_EQ(weakFoo.unsafe_get(), wp<Foo>::fromExisting(foo.get()).unsafe_get());
+
+    EXPECT_FALSE(isDeleted);
+    foo = nullptr;
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST(RefBase, AssertWeakRefExistsDeath) {
+    // uses some other refcounting method, or none at all
+    bool isDeleted;
+    Foo* foo = new Foo(&isDeleted);
+
+    // can only get a valid wp<> object when you construct it from an sp<>
+    EXPECT_DEATH(wp<Foo>::fromExisting(foo), "");
+
+    delete foo;
+}
+
 // Set up a situation in which we race with visit2AndRremove() to delete
 // 2 strong references.  Bar destructor checks that there are no early
 // deletions and prior updates are visible to destructor.
diff --git a/libutils/SharedBuffer_test.cpp b/libutils/SharedBuffer_test.cpp
index 3f960d2..1d6317f 100644
--- a/libutils/SharedBuffer_test.cpp
+++ b/libutils/SharedBuffer_test.cpp
@@ -32,10 +32,25 @@
     EXPECT_DEATH(android::SharedBuffer::alloc(SIZE_MAX - sizeof(android::SharedBuffer)), "");
 }
 
-TEST(SharedBufferTest, alloc_null) {
-    // Big enough to fail, not big enough to abort.
+TEST(SharedBufferTest, alloc_max) {
     SKIP_WITH_HWASAN;  // hwasan has a 2GiB allocation limit.
-    ASSERT_EQ(nullptr, android::SharedBuffer::alloc(SIZE_MAX / 2));
+
+    android::SharedBuffer* buf =
+            android::SharedBuffer::alloc(SIZE_MAX - sizeof(android::SharedBuffer) - 1);
+    if (buf != nullptr) {
+        EXPECT_NE(nullptr, buf->data());
+        buf->release();
+    }
+}
+
+TEST(SharedBufferTest, alloc_big) {
+    SKIP_WITH_HWASAN;  // hwasan has a 2GiB allocation limit.
+
+    android::SharedBuffer* buf = android::SharedBuffer::alloc(SIZE_MAX / 2);
+    if (buf != nullptr) {
+        EXPECT_NE(nullptr, buf->data());
+        buf->release();
+    }
 }
 
 TEST(SharedBufferTest, alloc_zero_size) {
@@ -56,7 +71,13 @@
     // Big enough to fail, not big enough to abort.
     SKIP_WITH_HWASAN;  // hwasan has a 2GiB allocation limit.
     android::SharedBuffer* buf = android::SharedBuffer::alloc(10);
-    ASSERT_EQ(nullptr, buf->editResize(SIZE_MAX / 2));
+    android::SharedBuffer* buf2 = buf->editResize(SIZE_MAX / 2);
+    if (buf2 == nullptr) {
+        buf->release();
+    } else {
+        EXPECT_NE(nullptr, buf2->data());
+        buf2->release();
+    }
 }
 
 TEST(SharedBufferTest, editResize_zero_size) {
diff --git a/libutils/StopWatch.cpp b/libutils/StopWatch.cpp
index d01865e..28e2d76 100644
--- a/libutils/StopWatch.cpp
+++ b/libutils/StopWatch.cpp
@@ -26,58 +26,26 @@
 
 #include <utils/Log.h>
 
-/*****************************************************************************/
-
 namespace android {
 
 StopWatch::StopWatch(const char* name, int clock) : mName(name), mClock(clock) {
     reset();
 }
 
-StopWatch::~StopWatch()
-{
-    nsecs_t elapsed = elapsedTime();
-    const int n = mNumLaps;
-    ALOGD("StopWatch %s (us): %" PRId64 " ", mName, ns2us(elapsed));
-    for (int i=0 ; i<n ; i++) {
-        const nsecs_t soFar = mLaps[i].soFar;
-        const nsecs_t thisLap = mLaps[i].thisLap;
-        ALOGD(" [%d: %" PRId64 ", %" PRId64, i, ns2us(soFar), ns2us(thisLap));
-    }
+StopWatch::~StopWatch() {
+    ALOGD("StopWatch %s (us): %" PRId64 " ", name(), ns2us(elapsedTime()));
 }
 
-const char* StopWatch::name() const
-{
+const char* StopWatch::name() const {
     return mName;
 }
 
-nsecs_t StopWatch::lap()
-{
-    nsecs_t elapsed = elapsedTime();
-    if (mNumLaps >= 8) {
-        elapsed = 0;
-    } else {
-        const int n = mNumLaps;
-        mLaps[n].soFar   = elapsed;
-        mLaps[n].thisLap = n ? (elapsed - mLaps[n-1].soFar) : elapsed;
-        mNumLaps = n+1;
-    }
-    return elapsed;
-}
-
-nsecs_t StopWatch::elapsedTime() const
-{
+nsecs_t StopWatch::elapsedTime() const {
     return systemTime(mClock) - mStartTime;
 }
 
-void StopWatch::reset()
-{
-    mNumLaps = 0;
+void StopWatch::reset() {
     mStartTime = systemTime(mClock);
 }
 
-
-/*****************************************************************************/
-
-}; // namespace android
-
+}  // namespace android
diff --git a/libutils/StopWatch_fuzz.cpp b/libutils/StopWatch_fuzz.cpp
deleted file mode 100644
index 63d8a28..0000000
--- a/libutils/StopWatch_fuzz.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "fuzzer/FuzzedDataProvider.h"
-#include "utils/StopWatch.h"
-
-static constexpr int MAX_OPERATIONS = 100;
-static constexpr int MAX_NAME_LEN = 2048;
-
-static const std::vector<std::function<void(android::StopWatch)>> operations = {
-        [](android::StopWatch stopWatch) -> void { stopWatch.reset(); },
-        [](android::StopWatch stopWatch) -> void { stopWatch.lap(); },
-        [](android::StopWatch stopWatch) -> void { stopWatch.elapsedTime(); },
-        [](android::StopWatch stopWatch) -> void { stopWatch.name(); },
-};
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    FuzzedDataProvider dataProvider(data, size);
-    std::string nameStr = dataProvider.ConsumeRandomLengthString(MAX_NAME_LEN);
-    int clockVal = dataProvider.ConsumeIntegral<int>();
-    android::StopWatch stopWatch = android::StopWatch(nameStr.c_str(), clockVal);
-    std::vector<uint8_t> opsToRun = dataProvider.ConsumeRemainingBytes<uint8_t>();
-    int opsRun = 0;
-    for (auto it : opsToRun) {
-        if (opsRun++ >= MAX_OPERATIONS) {
-            break;
-        }
-        it = it % operations.size();
-        operations[it](stopWatch);
-    }
-    return 0;
-}
diff --git a/libutils/String16.cpp b/libutils/String16.cpp
index 70bf5a0..faf90c2 100644
--- a/libutils/String16.cpp
+++ b/libutils/String16.cpp
@@ -390,28 +390,6 @@
     return static_cast<size_t>(*(p - 1));
 }
 
-status_t String16::makeLower()
-{
-    const size_t N = size();
-    const char16_t* str = string();
-    char16_t* edited = nullptr;
-    for (size_t i=0; i<N; i++) {
-        const char16_t v = str[i];
-        if (v >= 'A' && v <= 'Z') {
-            if (!edited) {
-                SharedBuffer* buf = static_cast<SharedBuffer*>(edit());
-                if (!buf) {
-                    return NO_MEMORY;
-                }
-                edited = (char16_t*)buf->data();
-                mString = str = edited;
-            }
-            edited[i] = tolower((char)v);
-        }
-    }
-    return OK;
-}
-
 status_t String16::replaceAll(char16_t replaceThis, char16_t withThis)
 {
     const size_t N = size();
@@ -433,36 +411,4 @@
     return OK;
 }
 
-status_t String16::remove(size_t len, size_t begin)
-{
-    const size_t N = size();
-    if (begin >= N) {
-        release();
-        mString = getEmptyString();
-        return OK;
-    }
-    if (len > N || len > N - begin) len = N - begin;
-    if (begin == 0 && len == N) {
-        return OK;
-    }
-
-    if (begin > 0) {
-        SharedBuffer* buf = static_cast<SharedBuffer*>(editResize((N + 1) * sizeof(char16_t)));
-        if (!buf) {
-            return NO_MEMORY;
-        }
-        char16_t* str = (char16_t*)buf->data();
-        memmove(str, str+begin, (N-begin+1)*sizeof(char16_t));
-        mString = str;
-    }
-    SharedBuffer* buf = static_cast<SharedBuffer*>(editResize((len + 1) * sizeof(char16_t)));
-    if (buf) {
-        char16_t* str = (char16_t*)buf->data();
-        str[len] = 0;
-        mString = str;
-        return OK;
-    }
-    return NO_MEMORY;
-}
-
 }; // namespace android
diff --git a/libutils/String16_fuzz.cpp b/libutils/String16_fuzz.cpp
index 63c2800..d7e5ec7 100644
--- a/libutils/String16_fuzz.cpp
+++ b/libutils/String16_fuzz.cpp
@@ -34,11 +34,6 @@
                     str1.size();
                 }),
 
-                // Casing
-                ([](FuzzedDataProvider&, android::String16 str1, android::String16) -> void {
-                    str1.makeLower();
-                }),
-
                 // Comparison
                 ([](FuzzedDataProvider&, android::String16 str1, android::String16 str2) -> void {
                     str1.startsWith(str2);
@@ -77,12 +72,6 @@
                     char16_t replaceChar = dataProvider.ConsumeIntegral<char16_t>();
                     str1.replaceAll(findChar, replaceChar);
                 }),
-                ([](FuzzedDataProvider& dataProvider, android::String16 str1,
-                    android::String16) -> void {
-                    size_t len = dataProvider.ConsumeIntegral<size_t>();
-                    size_t begin = dataProvider.ConsumeIntegral<size_t>();
-                    str1.remove(len, begin);
-                }),
 };
 
 void callFunc(uint8_t index, FuzzedDataProvider& dataProvider, android::String16 str1,
@@ -116,7 +105,5 @@
         callFunc(op, dataProvider, str_one_utf16, str_two_utf16);
     }
 
-    str_one_utf16.remove(0, str_one_utf16.size());
-    str_two_utf16.remove(0, str_two_utf16.size());
     return 0;
 }
diff --git a/libutils/String16_test.cpp b/libutils/String16_test.cpp
index 2505f44..54662ac 100644
--- a/libutils/String16_test.cpp
+++ b/libutils/String16_test.cpp
@@ -90,20 +90,6 @@
     EXPECT_STR16EQ(u"VerifyInsert me", tmp);
 }
 
-TEST(String16Test, Remove) {
-    String16 tmp("Verify me");
-    tmp.remove(2, 6);
-    EXPECT_EQ(2U, tmp.size());
-    EXPECT_STR16EQ(u" m", tmp);
-}
-
-TEST(String16Test, MakeLower) {
-    String16 tmp("Verify Me!");
-    tmp.makeLower();
-    EXPECT_EQ(10U, tmp.size());
-    EXPECT_STR16EQ(u"verify me!", tmp);
-}
-
 TEST(String16Test, ReplaceAll) {
     String16 tmp("Verify verify Verify");
     tmp.replaceAll(u'r', u'!');
@@ -168,22 +154,6 @@
     EXPECT_FALSE(tmp.isStaticString());
 }
 
-TEST(String16Test, StaticStringRemove) {
-    StaticString16 tmp(u"Verify me");
-    tmp.remove(2, 6);
-    EXPECT_EQ(2U, tmp.size());
-    EXPECT_STR16EQ(u" m", tmp);
-    EXPECT_FALSE(tmp.isStaticString());
-}
-
-TEST(String16Test, StaticStringMakeLower) {
-    StaticString16 tmp(u"Verify me!");
-    tmp.makeLower();
-    EXPECT_EQ(10U, tmp.size());
-    EXPECT_STR16EQ(u"verify me!", tmp);
-    EXPECT_FALSE(tmp.isStaticString());
-}
-
 TEST(String16Test, StaticStringReplaceAll) {
     StaticString16 tmp(u"Verify verify Verify");
     tmp.replaceAll(u'r', u'!');
diff --git a/libutils/String8.cpp b/libutils/String8.cpp
index 3dc2026..195e122 100644
--- a/libutils/String8.cpp
+++ b/libutils/String8.cpp
@@ -25,6 +25,8 @@
 
 #include <ctype.h>
 
+#include <string>
+
 #include "SharedBuffer.h"
 
 /*
@@ -163,9 +165,7 @@
 }
 
 String8::String8(const char32_t* o)
-    : mString(allocFromUTF32(o, strlen32(o)))
-{
-}
+    : mString(allocFromUTF32(o, std::char_traits<char32_t>::length(o))) {}
 
 String8::String8(const char32_t* o, size_t len)
     : mString(allocFromUTF32(o, len))
@@ -415,50 +415,15 @@
 
 void String8::toLower()
 {
-    toLower(0, size());
-}
+    const size_t length = size();
+    if (length == 0) return;
 
-void String8::toLower(size_t start, size_t length)
-{
-    const size_t len = size();
-    if (start >= len) {
-        return;
-    }
-    if (start+length > len) {
-        length = len-start;
-    }
-    char* buf = lockBuffer(len);
-    buf += start;
-    while (length > 0) {
+    char* buf = lockBuffer(length);
+    for (size_t i = length; i > 0; --i) {
         *buf = static_cast<char>(tolower(*buf));
         buf++;
-        length--;
     }
-    unlockBuffer(len);
-}
-
-void String8::toUpper()
-{
-    toUpper(0, size());
-}
-
-void String8::toUpper(size_t start, size_t length)
-{
-    const size_t len = size();
-    if (start >= len) {
-        return;
-    }
-    if (start+length > len) {
-        length = len-start;
-    }
-    char* buf = lockBuffer(len);
-    buf += start;
-    while (length > 0) {
-        *buf = static_cast<char>(toupper(*buf));
-        buf++;
-        length--;
-    }
-    unlockBuffer(len);
+    unlockBuffer(length);
 }
 
 // ---------------------------------------------------------------------------
diff --git a/libutils/String8_fuzz.cpp b/libutils/String8_fuzz.cpp
index b02683c..a45d675 100644
--- a/libutils/String8_fuzz.cpp
+++ b/libutils/String8_fuzz.cpp
@@ -42,9 +42,6 @@
 
                 // Casing
                 [](FuzzedDataProvider*, android::String8* str1, android::String8*) -> void {
-                    str1->toUpper();
-                },
-                [](FuzzedDataProvider*, android::String8* str1, android::String8*) -> void {
                     str1->toLower();
                 },
                 [](FuzzedDataProvider*, android::String8* str1, android::String8* str2) -> void {
diff --git a/libutils/StrongPointer_test.cpp b/libutils/StrongPointer_test.cpp
index d37c1de..f27c1f1 100644
--- a/libutils/StrongPointer_test.cpp
+++ b/libutils/StrongPointer_test.cpp
@@ -21,8 +21,8 @@
 
 using namespace android;
 
-class SPFoo : public LightRefBase<SPFoo> {
-public:
+class SPFoo : virtual public RefBase {
+  public:
     explicit SPFoo(bool* deleted_check) : mDeleted(deleted_check) {
         *mDeleted = false;
     }
@@ -30,17 +30,34 @@
     ~SPFoo() {
         *mDeleted = true;
     }
-private:
+
+  private:
     bool* mDeleted;
 };
 
-TEST(StrongPointer, move) {
+class SPLightFoo : virtual public VirtualLightRefBase {
+  public:
+    explicit SPLightFoo(bool* deleted_check) : mDeleted(deleted_check) { *mDeleted = false; }
+
+    ~SPLightFoo() { *mDeleted = true; }
+
+  private:
+    bool* mDeleted;
+};
+
+template <typename T>
+class StrongPointer : public ::testing::Test {};
+
+using RefBaseTypes = ::testing::Types<SPFoo, SPLightFoo>;
+TYPED_TEST_CASE(StrongPointer, RefBaseTypes);
+
+TYPED_TEST(StrongPointer, move) {
     bool isDeleted;
-    sp<SPFoo> sp1 = sp<SPFoo>::make(&isDeleted);
-    SPFoo* foo = sp1.get();
+    sp<TypeParam> sp1 = sp<TypeParam>::make(&isDeleted);
+    TypeParam* foo = sp1.get();
     ASSERT_EQ(1, foo->getStrongCount());
     {
-        sp<SPFoo> sp2 = std::move(sp1);
+        sp<TypeParam> sp2 = std::move(sp1);
         ASSERT_EQ(1, foo->getStrongCount()) << "std::move failed, incremented refcnt";
         ASSERT_EQ(nullptr, sp1.get()) << "std::move failed, sp1 is still valid";
         // The strong count isn't increasing, let's double check the old object
@@ -50,22 +67,42 @@
     ASSERT_FALSE(isDeleted) << "deleted too early! still has a reference!";
     {
         // Now let's double check it deletes on time
-        sp<SPFoo> sp2 = std::move(sp1);
+        sp<TypeParam> sp2 = std::move(sp1);
     }
     ASSERT_TRUE(isDeleted) << "foo was leaked!";
 }
 
-TEST(StrongPointer, NullptrComparison) {
-    sp<SPFoo> foo;
+TYPED_TEST(StrongPointer, NullptrComparison) {
+    sp<TypeParam> foo;
     ASSERT_EQ(foo, nullptr);
     ASSERT_EQ(nullptr, foo);
 }
 
-TEST(StrongPointer, PointerComparison) {
+TYPED_TEST(StrongPointer, PointerComparison) {
     bool isDeleted;
-    sp<SPFoo> foo = sp<SPFoo>::make(&isDeleted);
+    sp<TypeParam> foo = sp<TypeParam>::make(&isDeleted);
     ASSERT_EQ(foo.get(), foo);
     ASSERT_EQ(foo, foo.get());
     ASSERT_NE(nullptr, foo);
     ASSERT_NE(foo, nullptr);
 }
+
+TYPED_TEST(StrongPointer, Deleted) {
+    bool isDeleted;
+    sp<TypeParam> foo = sp<TypeParam>::make(&isDeleted);
+
+    auto foo2 = sp<TypeParam>::fromExisting(foo.get());
+
+    EXPECT_FALSE(isDeleted);
+    foo = nullptr;
+    EXPECT_FALSE(isDeleted);
+    foo2 = nullptr;
+    EXPECT_TRUE(isDeleted);
+}
+
+TYPED_TEST(StrongPointer, AssertStrongRefExists) {
+    bool isDeleted;
+    TypeParam* foo = new TypeParam(&isDeleted);
+    EXPECT_DEATH(sp<TypeParam>::fromExisting(foo), "");
+    delete foo;
+}
diff --git a/libutils/Timers.cpp b/libutils/Timers.cpp
index fd3f4a9..4cfac57 100644
--- a/libutils/Timers.cpp
+++ b/libutils/Timers.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-//
-// Timer functions.
-//
 #include <utils/Timers.h>
 
 #include <limits.h>
@@ -24,11 +21,12 @@
 #include <time.h>
 
 #include <android-base/macros.h>
+#include <utils/Log.h>
 
 static constexpr size_t clock_id_max = 5;
 
 static void checkClockId(int clock) {
-    if (clock < 0 || clock >= clock_id_max) abort();
+    LOG_ALWAYS_FATAL_IF(clock < 0 || clock >= clock_id_max, "invalid clock id");
 }
 
 #if defined(__linux__)
@@ -56,18 +54,10 @@
 }
 #endif
 
-int toMillisecondTimeoutDelay(nsecs_t referenceTime, nsecs_t timeoutTime)
-{
-    nsecs_t timeoutDelayMillis;
-    if (timeoutTime > referenceTime) {
-        uint64_t timeoutDelay = uint64_t(timeoutTime - referenceTime);
-        if (timeoutDelay > uint64_t((INT_MAX - 1) * 1000000LL)) {
-            timeoutDelayMillis = -1;
-        } else {
-            timeoutDelayMillis = (timeoutDelay + 999999LL) / 1000000LL;
-        }
-    } else {
-        timeoutDelayMillis = 0;
-    }
-    return (int)timeoutDelayMillis;
+int toMillisecondTimeoutDelay(nsecs_t referenceTime, nsecs_t timeoutTime) {
+    if (timeoutTime <= referenceTime) return 0;
+
+    uint64_t timeoutDelay = uint64_t(timeoutTime - referenceTime);
+    if (timeoutDelay > uint64_t((INT_MAX - 1) * 1000000LL)) return -1;
+    return (timeoutDelay + 999999LL) / 1000000LL;
 }
diff --git a/libutils/Timers_test.cpp b/libutils/Timers_test.cpp
index ec0051e..c0a6d49 100644
--- a/libutils/Timers_test.cpp
+++ b/libutils/Timers_test.cpp
@@ -27,3 +27,12 @@
     systemTime(SYSTEM_TIME_BOOTTIME);
     EXPECT_EXIT(systemTime(SYSTEM_TIME_BOOTTIME + 1), testing::KilledBySignal(SIGABRT), "");
 }
+
+TEST(Timers, toMillisecondTimeoutDelay) {
+    EXPECT_EQ(0, toMillisecondTimeoutDelay(100, 100));
+    EXPECT_EQ(0, toMillisecondTimeoutDelay(100, 10));
+
+    EXPECT_EQ(-1, toMillisecondTimeoutDelay(0, INT_MAX * 1000000LL));
+
+    EXPECT_EQ(123, toMillisecondTimeoutDelay(0, 123000000));
+}
diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp
index 843a81a..3ffcf7e 100644
--- a/libutils/Unicode.cpp
+++ b/libutils/Unicode.cpp
@@ -22,20 +22,6 @@
 
 #include <log/log.h>
 
-#if defined(_WIN32)
-# undef  nhtol
-# undef  htonl
-# undef  nhtos
-# undef  htons
-
-# define ntohl(x)    ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) )
-# define htonl(x)    ntohl(x)
-# define ntohs(x)    ( (((x) << 8) & 0xff00) | (((x) >> 8) & 255) )
-# define htons(x)    ntohs(x)
-#else
-# include <netinet/in.h>
-#endif
-
 extern "C" {
 
 static const char32_t kByteMask = 0x000000BF;
@@ -115,24 +101,6 @@
     }
 }
 
-size_t strlen32(const char32_t *s)
-{
-  const char32_t *ss = s;
-  while ( *ss )
-    ss++;
-  return ss-s;
-}
-
-size_t strnlen32(const char32_t *s, size_t maxlen)
-{
-  const char32_t *ss = s;
-  while ((maxlen > 0) && *ss) {
-    ss++;
-    maxlen--;
-  }
-  return ss-s;
-}
-
 static inline int32_t utf32_at_internal(const char* cur, size_t *num_read)
 {
     const char first_char = *cur;
@@ -254,19 +222,6 @@
   return d;
 }
 
-char16_t *strcpy16(char16_t *dst, const char16_t *src)
-{
-  char16_t *q = dst;
-  const char16_t *p = src;
-  char16_t ch;
-
-  do {
-    *q++ = ch = *p++;
-  } while ( ch );
-
-  return dst;
-}
-
 size_t strlen16(const char16_t *s)
 {
   const char16_t *ss = s;
diff --git a/libutils/include/utils/LightRefBase.h b/libutils/include/utils/LightRefBase.h
index b04e5c1..40edf67 100644
--- a/libutils/include/utils/LightRefBase.h
+++ b/libutils/include/utils/LightRefBase.h
@@ -28,6 +28,8 @@
 
 class ReferenceRenamer;
 
+void LightRefBase_reportIncStrongRequireStrongFailed(const void* thiz);
+
 template <class T>
 class LightRefBase
 {
@@ -36,6 +38,11 @@
     inline void incStrong(__attribute__((unused)) const void* id) const {
         mCount.fetch_add(1, std::memory_order_relaxed);
     }
+    inline void incStrongRequireStrong(__attribute__((unused)) const void* id) const {
+        if (0 == mCount.fetch_add(1, std::memory_order_relaxed)) {
+            LightRefBase_reportIncStrongRequireStrongFailed(this);
+        }
+    }
     inline void decStrong(__attribute__((unused)) const void* id) const {
         if (mCount.fetch_sub(1, std::memory_order_release) == 1) {
             std::atomic_thread_fence(std::memory_order_acquire);
@@ -59,7 +66,6 @@
     mutable std::atomic<int32_t> mCount;
 };
 
-
 // This is a wrapper around LightRefBase that simply enforces a virtual
 // destructor to eliminate the template requirement of LightRefBase
 class VirtualLightRefBase : public LightRefBase<VirtualLightRefBase> {
diff --git a/libutils/include/utils/RefBase.h b/libutils/include/utils/RefBase.h
index e7acd17..e07f574 100644
--- a/libutils/include/utils/RefBase.h
+++ b/libutils/include/utils/RefBase.h
@@ -140,7 +140,9 @@
 // count, and accidentally passed to f(sp<T>), a strong pointer to the object
 // will be temporarily constructed and destroyed, prematurely deallocating the
 // object, and resulting in heap corruption. None of this would be easily
-// visible in the source.
+// visible in the source. See below on
+// ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION for a compile time
+// option which helps avoid this case.
 
 // Extra Features:
 
@@ -167,6 +169,42 @@
 // to THE SAME sp<> or wp<>.  In effect, their thread-safety properties are
 // exactly like those of T*, NOT atomic<T*>.
 
+// Safety option: ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION
+//
+// This flag makes the semantics for using a RefBase object with wp<> and sp<>
+// much stricter by disabling implicit conversion from raw pointers to these
+// objects. In order to use this, apply this flag in Android.bp like so:
+//
+//    cflags: [
+//        "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
+//    ],
+//
+// REGARDLESS of whether this flag is on, best usage of sp<> is shown below. If
+// this flag is on, no other usage is possible (directly calling RefBase methods
+// is possible, but seeing code using 'incStrong' instead of 'sp<>', for
+// instance, should already set off big alarm bells. With carefully constructed
+// data structures, it should NEVER be necessary to directly use RefBase
+// methods). Proper RefBase usage:
+//
+//    class Foo : virtual public RefBase { ... };
+//
+//    // always construct an sp object with sp::make
+//    sp<Foo> myFoo = sp<Foo>::make(/*args*/);
+//
+//    // if you need a weak pointer, it must be constructed from a strong
+//    // pointer
+//    wp<Foo> weakFoo = myFoo; // NOT myFoo.get()
+//
+//    // If you are inside of a method of Foo and need access to a strong
+//    // explicitly call this function. This documents your intention to code
+//    // readers, and it will give a runtime error for what otherwise would
+//    // be potential double ownership
+//    .... Foo::someMethod(...) {
+//        // asserts if there is a memory issue
+//        sp<Foo> thiz = sp<Foo>::fromExisting(this);
+//    }
+//
+
 #ifndef ANDROID_REF_BASE_H
 #define ANDROID_REF_BASE_H
 
@@ -244,6 +282,7 @@
 {
 public:
             void            incStrong(const void* id) const;
+            void            incStrongRequireStrong(const void* id) const;
             void            decStrong(const void* id) const;
     
             void            forceIncStrong(const void* id) const;
@@ -257,6 +296,7 @@
         RefBase*            refBase() const;
 
         void                incWeak(const void* id);
+        void                incWeakRequireWeak(const void* id);
         void                decWeak(const void* id);
 
         // acquires a strong reference if there is already one.
@@ -365,10 +405,27 @@
 
     inline wp() : m_ptr(nullptr), m_refs(nullptr) { }
 
+    // if nullptr, returns nullptr
+    //
+    // if a weak pointer is already available, this will retrieve it,
+    // otherwise, this will abort
+    static inline wp<T> fromExisting(T* other);
+
+    // for more information about this flag, see above
+#if defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
+    wp(std::nullptr_t) : wp() {}
+#else
     wp(T* other);  // NOLINT(implicit)
+    template <typename U>
+    wp(U* other);  // NOLINT(implicit)
+    wp& operator=(T* other);
+    template <typename U>
+    wp& operator=(U* other);
+#endif
+
     wp(const wp<T>& other);
     explicit wp(const sp<T>& other);
-    template<typename U> wp(U* other);  // NOLINT(implicit)
+
     template<typename U> wp(const sp<U>& other);  // NOLINT(implicit)
     template<typename U> wp(const wp<U>& other);  // NOLINT(implicit)
 
@@ -376,11 +433,9 @@
 
     // Assignment
 
-    wp& operator = (T* other);
     wp& operator = (const wp<T>& other);
     wp& operator = (const sp<T>& other);
 
-    template<typename U> wp& operator = (U* other);
     template<typename U> wp& operator = (const wp<U>& other);
     template<typename U> wp& operator = (const sp<U>& other);
 
@@ -481,6 +536,20 @@
 // Note that the above comparison operations go out of their way to provide an ordering consistent
 // with ordinary pointer comparison; otherwise they could ignore m_ptr, and just compare m_refs.
 
+template <typename T>
+wp<T> wp<T>::fromExisting(T* other) {
+    if (!other) return nullptr;
+
+    auto refs = other->getWeakRefs();
+    refs->incWeakRequireWeak(other);
+
+    wp<T> ret;
+    ret.m_ptr = other;
+    ret.m_refs = refs;
+    return ret;
+}
+
+#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
 template<typename T>
 wp<T>::wp(T* other)
     : m_ptr(other)
@@ -488,6 +557,32 @@
     m_refs = other ? m_refs = other->createWeak(this) : nullptr;
 }
 
+template <typename T>
+template <typename U>
+wp<T>::wp(U* other) : m_ptr(other) {
+    m_refs = other ? other->createWeak(this) : nullptr;
+}
+
+template <typename T>
+wp<T>& wp<T>::operator=(T* other) {
+    weakref_type* newRefs = other ? other->createWeak(this) : nullptr;
+    if (m_ptr) m_refs->decWeak(this);
+    m_ptr = other;
+    m_refs = newRefs;
+    return *this;
+}
+
+template <typename T>
+template <typename U>
+wp<T>& wp<T>::operator=(U* other) {
+    weakref_type* newRefs = other ? other->createWeak(this) : 0;
+    if (m_ptr) m_refs->decWeak(this);
+    m_ptr = other;
+    m_refs = newRefs;
+    return *this;
+}
+#endif
+
 template<typename T>
 wp<T>::wp(const wp<T>& other)
     : m_ptr(other.m_ptr), m_refs(other.m_refs)
@@ -503,13 +598,6 @@
 }
 
 template<typename T> template<typename U>
-wp<T>::wp(U* other)
-    : m_ptr(other)
-{
-    m_refs = other ? other->createWeak(this) : nullptr;
-}
-
-template<typename T> template<typename U>
 wp<T>::wp(const wp<U>& other)
     : m_ptr(other.m_ptr)
 {
@@ -535,17 +623,6 @@
 }
 
 template<typename T>
-wp<T>& wp<T>::operator = (T* other)
-{
-    weakref_type* newRefs =
-        other ? other->createWeak(this) : nullptr;
-    if (m_ptr) m_refs->decWeak(this);
-    m_ptr = other;
-    m_refs = newRefs;
-    return *this;
-}
-
-template<typename T>
 wp<T>& wp<T>::operator = (const wp<T>& other)
 {
     weakref_type* otherRefs(other.m_refs);
@@ -570,17 +647,6 @@
 }
 
 template<typename T> template<typename U>
-wp<T>& wp<T>::operator = (U* other)
-{
-    weakref_type* newRefs =
-        other ? other->createWeak(this) : 0;
-    if (m_ptr) m_refs->decWeak(this);
-    m_ptr = other;
-    m_refs = newRefs;
-    return *this;
-}
-
-template<typename T> template<typename U>
 wp<T>& wp<T>::operator = (const wp<U>& other)
 {
     weakref_type* otherRefs(other.m_refs);
diff --git a/libutils/include/utils/StopWatch.h b/libutils/include/utils/StopWatch.h
index 9b14ac8..4e53eda 100644
--- a/libutils/include/utils/StopWatch.h
+++ b/libutils/include/utils/StopWatch.h
@@ -14,46 +14,30 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_STOPWATCH_H
-#define ANDROID_STOPWATCH_H
+#pragma once
 
 #include <stdint.h>
 #include <sys/types.h>
 
 #include <utils/Timers.h>
 
-// ---------------------------------------------------------------------------
-
 namespace android {
 
-class StopWatch
-{
-public:
-  StopWatch(const char* name, int clock = SYSTEM_TIME_MONOTONIC);
-  ~StopWatch();
+class StopWatch {
+  public:
+    StopWatch(const char* name, int clock = SYSTEM_TIME_MONOTONIC);
+    ~StopWatch();
 
-  const char* name() const;
-  nsecs_t lap();
-  nsecs_t elapsedTime() const;
+    const char* name() const;
+    nsecs_t elapsedTime() const;
 
-  void reset();
+    void reset();
 
-private:
-    const char*     mName;
-    int             mClock;
-    
-    struct lap_t {
-        nsecs_t     soFar;
-        nsecs_t     thisLap;
-    };
-    
-    nsecs_t         mStartTime;
-    lap_t           mLaps[8];
-    int             mNumLaps;
+  private:
+    const char* mName;
+    int mClock;
+
+    nsecs_t mStartTime;
 };
 
 }  // namespace android
-
-// ---------------------------------------------------------------------------
-
-#endif // ANDROID_STOPWATCH_H
diff --git a/libutils/include/utils/String16.h b/libutils/include/utils/String16.h
index 1a4b47e..60d523a 100644
--- a/libutils/include/utils/String16.h
+++ b/libutils/include/utils/String16.h
@@ -85,13 +85,9 @@
 
             bool                contains(const char16_t* chrs) const;
 
-            status_t            makeLower();
-
             status_t            replaceAll(char16_t replaceThis,
                                            char16_t withThis);
 
-            status_t            remove(size_t len, size_t begin=0);
-
     inline  int                 compare(const String16& other) const;
 
     inline  bool                operator<(const String16& other) const;
diff --git a/libutils/include/utils/String8.h b/libutils/include/utils/String8.h
index 0bcb716..cee5dc6 100644
--- a/libutils/include/utils/String8.h
+++ b/libutils/include/utils/String8.h
@@ -130,9 +130,6 @@
             bool                removeAll(const char* other);
 
             void                toLower();
-            void                toLower(size_t start, size_t numChars);
-            void                toUpper();
-            void                toUpper(size_t start, size_t numChars);
 
 
     /*
diff --git a/libutils/include/utils/StrongPointer.h b/libutils/include/utils/StrongPointer.h
index 11128f2..bb1941b 100644
--- a/libutils/include/utils/StrongPointer.h
+++ b/libutils/include/utils/StrongPointer.h
@@ -32,30 +32,64 @@
 public:
     inline sp() : m_ptr(nullptr) { }
 
-    // TODO: switch everyone to using this over new, and make RefBase operator
-    // new private to that class so that we can avoid RefBase being used with
-    // other memory management mechanisms.
+    // The old way of using sp<> was like this. This is bad because it relies
+    // on implicit conversion to sp<>, which we would like to remove (if an
+    // object is being managed some other way, this is double-ownership). We
+    // want to move away from this:
+    //
+    //     sp<Foo> foo = new Foo(...); // DO NOT DO THIS
+    //
+    // Instead, prefer to do this:
+    //
+    //     sp<Foo> foo = sp<Foo>::make(...); // DO THIS
+    //
+    // Sometimes, in order to use this, when a constructor is marked as private,
+    // you may need to add this to your class:
+    //
+    //     friend class sp<Foo>;
     template <typename... Args>
     static inline sp<T> make(Args&&... args);
 
+    // if nullptr, returns nullptr
+    //
+    // if a strong pointer is already available, this will retrieve it,
+    // otherwise, this will abort
+    static inline sp<T> fromExisting(T* other);
+
+    // for more information about this macro and correct RefBase usage, see
+    // the comment at the top of utils/RefBase.h
+#if defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
+    sp(std::nullptr_t) : sp() {}
+#else
     sp(T* other);  // NOLINT(implicit)
+    template <typename U>
+    sp(U* other);  // NOLINT(implicit)
+    sp& operator=(T* other);
+    template <typename U>
+    sp& operator=(U* other);
+#endif
+
     sp(const sp<T>& other);
     sp(sp<T>&& other) noexcept;
-    template<typename U> sp(U* other);  // NOLINT(implicit)
+
     template<typename U> sp(const sp<U>& other);  // NOLINT(implicit)
     template<typename U> sp(sp<U>&& other);  // NOLINT(implicit)
 
+    // Cast a strong pointer directly from one type to another. Constructors
+    // allow changing types, but only if they are pointer-compatible. This does
+    // a static_cast internally.
+    template <typename U>
+    static inline sp<T> cast(const sp<U>& other);
+
     ~sp();
 
     // Assignment
 
-    sp& operator = (T* other);
     sp& operator = (const sp<T>& other);
     sp& operator=(sp<T>&& other) noexcept;
 
     template<typename U> sp& operator = (const sp<U>& other);
     template<typename U> sp& operator = (sp<U>&& other);
-    template<typename U> sp& operator = (U* other);
 
     //! Special optimization for use by ProcessState (and nobody else).
     void force_set(T* other);
@@ -189,6 +223,19 @@
     return result;
 }
 
+template <typename T>
+sp<T> sp<T>::fromExisting(T* other) {
+    if (other) {
+        check_not_on_stack(other);
+        other->incStrongRequireStrong(other);
+        sp<T> result;
+        result.m_ptr = other;
+        return result;
+    }
+    return nullptr;
+}
+
+#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
 template<typename T>
 sp<T>::sp(T* other)
         : m_ptr(other) {
@@ -198,6 +245,29 @@
     }
 }
 
+template <typename T>
+template <typename U>
+sp<T>::sp(U* other) : m_ptr(other) {
+    if (other) {
+        check_not_on_stack(other);
+        (static_cast<T*>(other))->incStrong(this);
+    }
+}
+
+template <typename T>
+sp<T>& sp<T>::operator=(T* other) {
+    T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
+    if (other) {
+        check_not_on_stack(other);
+        other->incStrong(this);
+    }
+    if (oldPtr) oldPtr->decStrong(this);
+    if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
+    m_ptr = other;
+    return *this;
+}
+#endif
+
 template<typename T>
 sp<T>::sp(const sp<T>& other)
         : m_ptr(other.m_ptr) {
@@ -211,15 +281,6 @@
 }
 
 template<typename T> template<typename U>
-sp<T>::sp(U* other)
-        : m_ptr(other) {
-    if (other) {
-        check_not_on_stack(other);
-        (static_cast<T*>(other))->incStrong(this);
-    }
-}
-
-template<typename T> template<typename U>
 sp<T>::sp(const sp<U>& other)
         : m_ptr(other.m_ptr) {
     if (m_ptr)
@@ -232,6 +293,12 @@
     other.m_ptr = nullptr;
 }
 
+template <typename T>
+template <typename U>
+sp<T> sp<T>::cast(const sp<U>& other) {
+    return sp<T>::fromExisting(static_cast<T*>(other.get()));
+}
+
 template<typename T>
 sp<T>::~sp() {
     if (m_ptr)
@@ -260,19 +327,6 @@
     return *this;
 }
 
-template<typename T>
-sp<T>& sp<T>::operator =(T* other) {
-    T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
-    if (other) {
-        check_not_on_stack(other);
-        other->incStrong(this);
-    }
-    if (oldPtr) oldPtr->decStrong(this);
-    if (oldPtr != *const_cast<T* volatile*>(&m_ptr)) sp_report_race();
-    m_ptr = other;
-    return *this;
-}
-
 template<typename T> template<typename U>
 sp<T>& sp<T>::operator =(const sp<U>& other) {
     T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
@@ -294,6 +348,7 @@
     return *this;
 }
 
+#if !defined(ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION)
 template<typename T> template<typename U>
 sp<T>& sp<T>::operator =(U* other) {
     T* oldPtr(*const_cast<T* volatile*>(&m_ptr));
@@ -303,6 +358,7 @@
     m_ptr = other;
     return *this;
 }
+#endif
 
 template<typename T>
 void sp<T>::force_set(T* other) {
diff --git a/libutils/include/utils/SystemClock.h b/libutils/include/utils/SystemClock.h
index 892104c..3c59297 100644
--- a/libutils/include/utils/SystemClock.h
+++ b/libutils/include/utils/SystemClock.h
@@ -20,11 +20,18 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+// See https://developer.android.com/reference/android/os/SystemClock
+// to learn more about Android's timekeeping facilities.
+
 namespace android {
 
+// Returns milliseconds since boot, not counting time spent in deep sleep.
 int64_t uptimeMillis();
+// Returns nanoseconds since boot, not counting time spent in deep sleep.
 int64_t uptimeNanos();
+// Returns milliseconds since boot, including time spent in sleep.
 int64_t elapsedRealtime();
+// Returns nanoseconds since boot, including time spent in sleep.
 int64_t elapsedRealtimeNano();
 
 }  // namespace android
diff --git a/libutils/include/utils/Unicode.h b/libutils/include/utils/Unicode.h
index 0087383..d60d5d6 100644
--- a/libutils/include/utils/Unicode.h
+++ b/libutils/include/utils/Unicode.h
@@ -27,7 +27,6 @@
 int strncmp16(const char16_t *s1, const char16_t *s2, size_t n);
 size_t strlen16(const char16_t *);
 size_t strnlen16(const char16_t *, size_t);
-char16_t *strcpy16(char16_t *, const char16_t *);
 char16_t *strstr16(const char16_t*, const char16_t*);
 
 // Version of comparison that supports embedded NULs.
@@ -39,10 +38,6 @@
 // equivalent result as strcmp16 (unlike strncmp16).
 int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2);
 
-// Standard string functions on char32_t strings.
-size_t strlen32(const char32_t *);
-size_t strnlen32(const char32_t *, size_t);
-
 /**
  * Measure the length of a UTF-32 string in UTF-8. If the string is invalid
  * such as containing a surrogate character, -1 will be returned.
diff --git a/libvndksupport/Android.bp b/libvndksupport/Android.bp
index a5cece4..f800bf7 100644
--- a/libvndksupport/Android.bp
+++ b/libvndksupport/Android.bp
@@ -1,7 +1,13 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library {
     name: "libvndksupport",
     native_bridge_supported: true,
-    llndk_stubs: "libvndksupport.llndk",
+    llndk: {
+        symbol_file: "libvndksupport.map.txt",
+    },
     srcs: ["linker.cpp"],
     cflags: [
         "-Wall",
@@ -19,10 +25,3 @@
         versions: ["29"],
     },
 }
-
-llndk_library {
-    name: "libvndksupport.llndk",
-    native_bridge_supported: true,
-    symbol_file: "libvndksupport.map.txt",
-    export_include_dirs: ["include"],
-}
diff --git a/libvndksupport/tests/Android.bp b/libvndksupport/tests/Android.bp
index 2570cce..ba700cb 100644
--- a/libvndksupport/tests/Android.bp
+++ b/libvndksupport/tests/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_test {
     name: "libvndksupport-tests",
     srcs: [
diff --git a/llkd/Android.bp b/llkd/Android.bp
index 62a637d..1c0e0f0 100644
--- a/llkd/Android.bp
+++ b/llkd/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_headers {
     name: "llkd_headers",
 
diff --git a/llkd/README.md b/llkd/README.md
index 6f92f14..9bcf806 100644
--- a/llkd/README.md
+++ b/llkd/README.md
@@ -207,7 +207,7 @@
 
 The `llkd` does not monitor the specified subset of processes for live lock stack
 signatures. Default is process names
-`init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd`. Prevents the sepolicy
+`init,lmkd.llkd,llkd,keystore,keystore2,ueventd,apexd,logd`. Prevents the sepolicy
 violation associated with processes that block `ptrace` (as these can't be
 checked). **Active only on userdebug and eng builds**. For details on build
 types, refer to [Building Android](/setup/build/building#choose-a-target).
diff --git a/llkd/include/llkd.h b/llkd/include/llkd.h
index 4b20a56..0822a3e 100644
--- a/llkd/include/llkd.h
+++ b/llkd/include/llkd.h
@@ -60,7 +60,7 @@
 #define LLK_IGNORELIST_UID_PROPERTY     "ro.llk.ignorelist.uid"
 #define LLK_IGNORELIST_UID_DEFAULT      ""
 #define LLK_IGNORELIST_STACK_PROPERTY   "ro.llk.ignorelist.process.stack"
-#define LLK_IGNORELIST_STACK_DEFAULT    "init,lmkd.llkd,llkd,keystore,ueventd,apexd"
+#define LLK_IGNORELIST_STACK_DEFAULT    "init,lmkd.llkd,llkd,keystore,keystore2,ueventd,apexd"
 /* clang-format on */
 
 __END_DECLS
diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp
index a24d900..c4c58ee 100644
--- a/llkd/libllkd.cpp
+++ b/llkd/libllkd.cpp
@@ -115,8 +115,8 @@
 // list of uids, and uid names, to skip, default nothing
 std::unordered_set<std::string> llkIgnorelistUid;
 #ifdef __PTRACE_ENABLED__
-// list of names to skip stack checking. "init", "lmkd", "llkd", "keystore" or
-// "logd" (if not userdebug).
+// list of names to skip stack checking. "init", "lmkd", "llkd", "keystore",
+// "keystore2", or "logd" (if not userdebug).
 std::unordered_set<std::string> llkIgnorelistStack;
 #endif
 
@@ -962,7 +962,8 @@
     //
     // This alarm is effectively the live lock detection of llkd, as
     // we understandably can not monitor ourselves otherwise.
-    ::alarm(duration_cast<seconds>(llkTimeoutMs * 2).count());
+    ::alarm(duration_cast<seconds>(llkTimeoutMs * 2 * android::base::HwTimeoutMultiplier())
+                    .count());
 
     // kernel jiffy precision fastest acquisition
     static timespec last;
diff --git a/llkd/tests/Android.bp b/llkd/tests/Android.bp
index 6dd5938..3db6e85 100644
--- a/llkd/tests/Android.bp
+++ b/llkd/tests/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_test {
     name: "llkd_unit_test",
 
diff --git a/libkeyutils/mini_keyctl/Android.bp b/mini_keyctl/Android.bp
similarity index 87%
rename from libkeyutils/mini_keyctl/Android.bp
rename to mini_keyctl/Android.bp
index a04a3db..417ddac 100644
--- a/libkeyutils/mini_keyctl/Android.bp
+++ b/mini_keyctl/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_static {
     name: "libmini_keyctl_static",
     srcs: [
diff --git a/libkeyutils/mini_keyctl/mini_keyctl.cpp b/mini_keyctl/mini_keyctl.cpp
similarity index 100%
rename from libkeyutils/mini_keyctl/mini_keyctl.cpp
rename to mini_keyctl/mini_keyctl.cpp
diff --git a/libkeyutils/mini_keyctl/mini_keyctl_utils.cpp b/mini_keyctl/mini_keyctl_utils.cpp
similarity index 100%
rename from libkeyutils/mini_keyctl/mini_keyctl_utils.cpp
rename to mini_keyctl/mini_keyctl_utils.cpp
diff --git a/libkeyutils/mini_keyctl/mini_keyctl_utils.h b/mini_keyctl/mini_keyctl_utils.h
similarity index 100%
rename from libkeyutils/mini_keyctl/mini_keyctl_utils.h
rename to mini_keyctl/mini_keyctl_utils.h
diff --git a/mkbootfs/Android.bp b/mkbootfs/Android.bp
new file mode 100644
index 0000000..cd2a624
--- /dev/null
+++ b/mkbootfs/Android.bp
@@ -0,0 +1,19 @@
+// Copyright 2005 The Android Open Source Project
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary_host {
+    name: "mkbootfs",
+    srcs: ["mkbootfs.c"],
+    cflags: ["-Werror"],
+    static_libs: [
+        "libbase",
+        "libcutils",
+        "liblog",
+    ],
+    dist: {
+        targets: ["dist_files"],
+    },
+}
diff --git a/cpio/mkbootfs.c b/mkbootfs/mkbootfs.c
similarity index 100%
rename from cpio/mkbootfs.c
rename to mkbootfs/mkbootfs.c
diff --git a/property_service/OWNERS b/property_service/OWNERS
index babbe4d..7529cb9 100644
--- a/property_service/OWNERS
+++ b/property_service/OWNERS
@@ -1 +1 @@
-tomcherry@google.com
+include platform/system/core:/janitors/OWNERS
diff --git a/property_service/libpropertyinfoparser/Android.bp b/property_service/libpropertyinfoparser/Android.bp
index 2d7e9cb..6861456 100644
--- a/property_service/libpropertyinfoparser/Android.bp
+++ b/property_service/libpropertyinfoparser/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_static {
     name: "libpropertyinfoparser",
     host_supported: true,
@@ -8,7 +12,6 @@
     native_bridge_supported: true,
     srcs: ["property_info_parser.cpp"],
 
-    cpp_std: "experimental",
     cppflags: [
         "-Wall",
         "-Wextra",
diff --git a/property_service/libpropertyinfoserializer/Android.bp b/property_service/libpropertyinfoserializer/Android.bp
index aa02a3a..f260450 100644
--- a/property_service/libpropertyinfoserializer/Android.bp
+++ b/property_service/libpropertyinfoserializer/Android.bp
@@ -1,7 +1,10 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_defaults {
     name: "propertyinfoserializer_defaults",
     host_supported: true,
-    cpp_std: "experimental",
     cppflags: [
         "-Wall",
         "-Wextra",
diff --git a/property_service/property_info_checker/Android.bp b/property_service/property_info_checker/Android.bp
index 65e660a..d26e359 100644
--- a/property_service/property_info_checker/Android.bp
+++ b/property_service/property_info_checker/Android.bp
@@ -1,8 +1,11 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "property_info_checker",
     host_supported: true,
     static_executable: true,
-    cpp_std: "experimental",
     static_libs: [
         "libpropertyinfoserializer",
         "libpropertyinfoparser",
diff --git a/qemu_pipe/Android.bp b/qemu_pipe/Android.bp
deleted file mode 100644
index ad86a4e..0000000
--- a/qemu_pipe/Android.bp
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2011 The Android Open Source Project
-
-cc_library_static {
-    name: "libqemu_pipe",
-    vendor_available: true,
-    recovery_available: true,
-    apex_available: [
-        "com.android.adbd",
-        // TODO(b/151398197) remove the below
-        "//apex_available:platform",
-    ],
-    sanitize: {
-        misc_undefined: ["integer"],
-    },
-    srcs: ["qemu_pipe.cpp"],
-    local_include_dirs: ["include"],
-    static_libs: ["libbase"],
-    export_include_dirs: ["include"],
-    cflags: ["-Werror"],
-}
diff --git a/qemu_pipe/OWNERS b/qemu_pipe/OWNERS
deleted file mode 100644
index dbc1bf6..0000000
--- a/qemu_pipe/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-bohu@google.com
diff --git a/qemu_pipe/include/qemu_pipe.h b/qemu_pipe/include/qemu_pipe.h
deleted file mode 100644
index 0987498..0000000
--- a/qemu_pipe/include/qemu_pipe.h
+++ /dev/null
@@ -1,64 +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 ANDROID_CORE_INCLUDE_QEMU_PIPE_H
-#define ANDROID_CORE_INCLUDE_QEMU_PIPE_H
-
-#include <stddef.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-// Try to open a new Qemu fast-pipe. This function returns a file descriptor
-// that can be used to communicate with a named service managed by the
-// emulator.
-//
-// This file descriptor can be used as a standard pipe/socket descriptor.
-//
-// 'pipeName' is the name of the emulator service you want to connect to,
-// and should begin with 'pipe:' (e.g. 'pipe:camera' or 'pipe:opengles').
-// For backward compatibility, the 'pipe:' prefix can be omitted, and in
-// that case, qemu_pipe_open will add it for you.
-
-// On success, return a valid file descriptor, or -1/errno on failure. E.g.:
-//
-// EINVAL  -> unknown/unsupported pipeName
-// ENOSYS  -> fast pipes not available in this system.
-//
-// ENOSYS should never happen, except if you're trying to run within a
-// misconfigured emulator.
-//
-// You should be able to open several pipes to the same pipe service,
-// except for a few special cases (e.g. GSM modem), where EBUSY will be
-// returned if more than one client tries to connect to it.
-int qemu_pipe_open(const char* pipeName);
-
-// Send a framed message |buff| of |len| bytes through the |fd| descriptor.
-// This really adds a 4-hexchar prefix describing the payload size.
-// Returns 0 on success, and -1 on error.
-int qemu_pipe_frame_send(int fd, const void* buff, size_t len);
-
-// Read a frame message from |fd|, and store it into |buff| of |len| bytes.
-// If the framed message is larger than |len|, then this returns -1 and the
-// content is lost. Otherwise, this returns the size of the message. NOTE:
-// empty messages are possible in a framed wire protocol and do not mean
-// end-of-stream.
-int qemu_pipe_frame_recv(int fd, void* buff, size_t len);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* ANDROID_CORE_INCLUDE_QEMU_PIPE_H */
diff --git a/qemu_pipe/qemu_pipe.cpp b/qemu_pipe/qemu_pipe.cpp
deleted file mode 100644
index 03afb21..0000000
--- a/qemu_pipe/qemu_pipe.cpp
+++ /dev/null
@@ -1,107 +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.
- */
-
-#include "qemu_pipe.h"
-
-#include <unistd.h>
-#include <fcntl.h>
-#include <string.h>
-#include <errno.h>
-#include <stdio.h>
-
-#include <android-base/file.h>
-
-using android::base::ReadFully;
-using android::base::WriteFully;
-
-// Define QEMU_PIPE_DEBUG if you want to print error messages when an error
-// occurs during pipe operations. The macro should simply take a printf-style
-// formatting string followed by optional arguments.
-#ifndef QEMU_PIPE_DEBUG
-#  define  QEMU_PIPE_DEBUG(...)   (void)0
-#endif
-
-int qemu_pipe_open(const char* pipeName) {
-    if (!pipeName) {
-        errno = EINVAL;
-        return -1;
-    }
-
-    int fd = TEMP_FAILURE_RETRY(open("/dev/qemu_pipe", O_RDWR));
-    if (fd < 0) {
-        QEMU_PIPE_DEBUG("%s: Could not open /dev/qemu_pipe: %s", __FUNCTION__,
-                        strerror(errno));
-        return -1;
-    }
-
-    // Write the pipe name, *including* the trailing zero which is necessary.
-    size_t pipeNameLen = strlen(pipeName);
-    if (WriteFully(fd, pipeName, pipeNameLen + 1U)) {
-        return fd;
-    }
-
-    // now, add 'pipe:' prefix and try again
-    // Note: host side will wait for the trailing '\0' to start
-    // service lookup.
-    const char pipe_prefix[] = "pipe:";
-    if (WriteFully(fd, pipe_prefix, strlen(pipe_prefix)) &&
-            WriteFully(fd, pipeName, pipeNameLen + 1U)) {
-        return fd;
-    }
-    QEMU_PIPE_DEBUG("%s: Could not write to %s pipe service: %s",
-            __FUNCTION__, pipeName, strerror(errno));
-    close(fd);
-    return -1;
-}
-
-int qemu_pipe_frame_send(int fd, const void* buff, size_t len) {
-    char header[5];
-    snprintf(header, sizeof(header), "%04zx", len);
-    if (!WriteFully(fd, header, 4)) {
-        QEMU_PIPE_DEBUG("Can't write qemud frame header: %s", strerror(errno));
-        return -1;
-    }
-    if (!WriteFully(fd, buff, len)) {
-        QEMU_PIPE_DEBUG("Can't write qemud frame payload: %s", strerror(errno));
-        return -1;
-    }
-    return 0;
-}
-
-int qemu_pipe_frame_recv(int fd, void* buff, size_t len) {
-    char header[5];
-    if (!ReadFully(fd, header, 4)) {
-        QEMU_PIPE_DEBUG("Can't read qemud frame header: %s", strerror(errno));
-        return -1;
-    }
-    header[4] = '\0';
-    size_t size;
-    if (sscanf(header, "%04zx", &size) != 1) {
-        QEMU_PIPE_DEBUG("Malformed qemud frame header: [%.*s]", 4, header);
-        return -1;
-    }
-    if (size > len) {
-        QEMU_PIPE_DEBUG("Oversized qemud frame (% bytes, expected <= %)", size,
-                        len);
-        return -1;
-    }
-    if (!ReadFully(fd, buff, size)) {
-        QEMU_PIPE_DEBUG("Could not read qemud frame payload: %s",
-                        strerror(errno));
-        return -1;
-    }
-    return size;
-}
diff --git a/reboot/Android.bp b/reboot/Android.bp
index cc71723..7b243bd 100644
--- a/reboot/Android.bp
+++ b/reboot/Android.bp
@@ -1,5 +1,9 @@
 // Copyright 2013 The Android Open Source Project
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "reboot",
     srcs: ["reboot.c"],
diff --git a/rootdir/Android.bp b/rootdir/Android.bp
index a21f686..ae21633 100644
--- a/rootdir/Android.bp
+++ b/rootdir/Android.bp
@@ -12,11 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 prebuilt_etc {
     name: "init.rc",
     src: "init.rc",
     sub_dir: "init/hw",
-    required: ["fsverity_init"],
+    required: [
+        "fsverity_init",
+        "platform-bootclasspath",
+    ],
 }
 
 prebuilt_etc {
@@ -29,4 +36,13 @@
 linker_config {
     name: "system_linker_config",
     src: "etc/linker.config.json",
+    installable: false,
 }
+
+// TODO(b/185211376) Scope the native APIs that microdroid will provide to the app payload
+prebuilt_etc {
+    name: "public.libraries.android.txt",
+    src: "etc/public.libraries.android.txt",
+    filename: "public.libraries.txt",
+    installable: false,
+}
\ No newline at end of file
diff --git a/rootdir/Android.mk b/rootdir/Android.mk
index 2bceb75..99d8f9a 100644
--- a/rootdir/Android.mk
+++ b/rootdir/Android.mk
@@ -5,6 +5,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := init-debug.rc
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_SRC_FILES := $(LOCAL_MODULE)
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/init
@@ -18,6 +20,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := asan.options
+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 := $(TARGET_OUT)
@@ -29,6 +33,8 @@
 ifeq ($(SANITIZE_TARGET_SYSTEM),true)
 include $(CLEAR_VARS)
 LOCAL_MODULE:= asan_extract
+LOCAL_LICENSE_KINDS:= SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS:= notice
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_SRC_FILES := asan_extract.sh
@@ -47,6 +53,8 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE := init.environ.rc
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)
 
 EXPORT_GLOBAL_ASAN_OPTIONS :=
@@ -69,7 +77,7 @@
 
 EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS :=
 ifeq ($(CLANG_COVERAGE),true)
-  EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS := export LLVM_PROFILE_FILE /data/misc/trace/clang-%p-%m.profraw
+  EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS := export LLVM_PROFILE_FILE /data/misc/trace/clang-%20m.profraw
 endif
 
 # Put it here instead of in init.rc module definition,
@@ -78,7 +86,7 @@
 # create some directories (some are mount points) and symlinks
 LOCAL_POST_INSTALL_CMD := mkdir -p $(addprefix $(TARGET_ROOT_OUT)/, \
     dev proc sys system data data_mirror odm oem acct config storage mnt apex debug_ramdisk \
-    linkerconfig second_stage_resources $(BOARD_ROOT_EXTRA_FOLDERS)); \
+    linkerconfig second_stage_resources postinstall $(BOARD_ROOT_EXTRA_FOLDERS)); \
     ln -sf /system/bin $(TARGET_ROOT_OUT)/bin; \
     ln -sf /system/etc $(TARGET_ROOT_OUT)/etc; \
     ln -sf /data/user_de/0/com.android.shell/files/bugreports $(TARGET_ROOT_OUT)/bugreports; \
@@ -153,11 +161,6 @@
     ; mkdir -p $(dir $(TARGET_ROOT_OUT)/$(word 2,$(p))) \
     ; ln -sf $(word 1,$(p)) $(TARGET_ROOT_OUT)/$(word 2,$(p)))
 endif
-# The A/B updater uses a top-level /postinstall directory to mount the new
-# system before reboot.
-ifeq ($(AB_OTA_UPDATER),true)
-  LOCAL_POST_INSTALL_CMD += ; mkdir -p $(TARGET_ROOT_OUT)/postinstall
-endif
 
 # The init symlink must be a post install command of a file that is to TARGET_ROOT_OUT.
 # Since init.environ.rc is required for init and satisfies that requirement, we hijack it to create the symlink.
@@ -168,9 +171,7 @@
 $(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/init.environ.rc.in
 	@echo "Generate: $< -> $@"
 	@mkdir -p $(dir $@)
-	$(hide) sed -e 's?%BOOTCLASSPATH%?$(PRODUCT_BOOTCLASSPATH)?g' $< >$@
-	$(hide) sed -i -e 's?%DEX2OATBOOTCLASSPATH%?$(PRODUCT_DEX2OAT_BOOTCLASSPATH)?g' $@
-	$(hide) sed -i -e 's?%SYSTEMSERVERCLASSPATH%?$(PRODUCT_SYSTEM_SERVER_CLASSPATH)?g' $@
+	$(hide) cp $< $@
 	$(hide) sed -i -e 's?%EXPORT_GLOBAL_ASAN_OPTIONS%?$(EXPORT_GLOBAL_ASAN_OPTIONS)?g' $@
 	$(hide) sed -i -e 's?%EXPORT_GLOBAL_GCOV_OPTIONS%?$(EXPORT_GLOBAL_GCOV_OPTIONS)?g' $@
 	$(hide) sed -i -e 's?%EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS%?$(EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS)?g' $@
@@ -187,6 +188,8 @@
 # sanitizer.libraries.txt
 include $(CLEAR_VARS)
 LOCAL_MODULE := sanitizer.libraries.txt
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
 LOCAL_MODULE_STEM := $(LOCAL_MODULE)
@@ -211,6 +214,8 @@
 # adb_debug.prop in debug ramdisk
 include $(CLEAR_VARS)
 LOCAL_MODULE := adb_debug.prop
+LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
+LOCAL_LICENSE_CONDITIONS := notice
 LOCAL_SRC_FILES := $(LOCAL_MODULE)
 LOCAL_MODULE_CLASS := ETC
 LOCAL_MODULE_PATH := $(TARGET_DEBUG_RAMDISK_OUT)
diff --git a/rootdir/OWNERS b/rootdir/OWNERS
index ca22eb8..5d0d673 100644
--- a/rootdir/OWNERS
+++ b/rootdir/OWNERS
@@ -1,5 +1,7 @@
+bowgotsai@google.com
 ccross@google.com
+dvander@google.com
+elsk@google.com
 jeffv@google.com
 jiyong@google.com
 smoreland@google.com
-tomcherry@google.com
diff --git a/rootdir/avb/Android.bp b/rootdir/avb/Android.bp
index 85d2786..cfc59a7 100644
--- a/rootdir/avb/Android.bp
+++ b/rootdir/avb/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 filegroup {
     name: "q-gsi_avbpubkey",
     srcs: [
@@ -18,3 +22,10 @@
         "s-gsi.avbpubkey",
     ],
 }
+
+filegroup {
+    name: "qcar-gsi_avbpubkey",
+    srcs: [
+        "qcar-gsi.avbpubkey",
+    ],
+}
diff --git a/rootdir/avb/Android.mk b/rootdir/avb/Android.mk
index c8fc1d6..647cfa2 100644
--- a/rootdir/avb/Android.mk
+++ b/rootdir/avb/Android.mk
@@ -19,6 +19,8 @@
 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)
@@ -30,6 +32,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := q-developer-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)
@@ -41,6 +45,8 @@
 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)
@@ -52,6 +58,8 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := r-developer-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)
@@ -63,6 +71,8 @@
 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)
@@ -74,6 +84,21 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := s-developer-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)
+
+#######################################
+# 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)
diff --git a/rootdir/avb/qcar-gsi.avbpubkey b/rootdir/avb/qcar-gsi.avbpubkey
new file mode 100644
index 0000000..ce56646
--- /dev/null
+++ b/rootdir/avb/qcar-gsi.avbpubkey
Binary files differ
diff --git a/rootdir/etc/linker.config.json b/rootdir/etc/linker.config.json
index d66ab73..6b03a1d 100644
--- a/rootdir/etc/linker.config.json
+++ b/rootdir/etc/linker.config.json
@@ -1,56 +1,14 @@
 {
-  // These are list of libraries which has stub interface and installed
-  // in system image so other partition and APEX modules can link to it.
-  // TODO(b/147210213) : Generate this list on build and read from the file
-  "provideLibs": [
-    // LLNDK libraries
-    "libEGL.so",
-    "libGLESv1_CM.so",
-    "libGLESv2.so",
-    "libGLESv3.so",
-    "libRS.so",
-    "libandroid_net.so",
-    "libbinder_ndk.so",
-    "libc.so",
-    "libcgrouprc.so",
-    "libclang_rt.asan-arm-android.so",
-    "libclang_rt.asan-i686-android.so",
-    "libclang_rt.asan-x86_64-android.so",
-    "libdl.so",
-    "libft2.so",
-    "liblog.so",
-    "libm.so",
-    "libmediandk.so",
-    "libnativewindow.so",
-    "libsync.so",
-    "libvndksupport.so",
-    "libvulkan.so",
-    // NDK libraries
-    "libaaudio.so",
-    "libandroid.so",
-    // adb
-    "libadbd_auth.so",
-    "libadbd_fs.so",
-    // bionic
-    "libdl_android.so",
-    // statsd
-    "libincident.so",
-    // media
-    "libmediametrics.so",
-    // nn
-    "libneuralnetworks_packageinfo.so",
-    // SELinux
-    "libselinux.so"
-  ],
   "requireLibs": [
-    // Keep in sync with the "platform" namespace in art/build/apex/ld.config.txt.
-    "libdexfile_external.so",
-    "libdexfiled_external.so",
+    "libandroidicu.so",
+    "libdexfile.so",
+    "libdexfiled.so",
+    "libicu.so",
+    "libjdwp.so",
     "libnativebridge.so",
     "libnativehelper.so",
     "libnativeloader.so",
-    "libandroidicu.so",
-    "libicu.so",
+    "libsigchain.so",
     // TODO(b/122876336): Remove libpac.so once it's migrated to Webview
     "libpac.so",
     // TODO(b/120786417 or b/134659294): libicuuc.so
@@ -61,6 +19,7 @@
     "libnetd_resolv.so",
     // nn
     "libneuralnetworks.so",
+    "libneuralnetworks_shim.so",
     // statsd
     "libstatspull.so",
     "libstatssocket.so",
@@ -69,4 +28,4 @@
     "libadb_pairing_connection.so",
     "libadb_pairing_server.so"
   ]
-}
\ No newline at end of file
+}
diff --git a/rootdir/init-debug.rc b/rootdir/init-debug.rc
index 435d4cb..77a80cd 100644
--- a/rootdir/init-debug.rc
+++ b/rootdir/init-debug.rc
@@ -6,3 +6,11 @@
 
 on property:persist.mmc.cache_size=*
     write /sys/block/mmcblk0/cache_size ${persist.mmc.cache_size}
+
+on early-init && property:ro.product.debugfs_restrictions.enabled=true
+    mount debugfs debugfs /sys/kernel/debug
+    chmod 0755 /sys/kernel/debug
+
+on property:sys.boot_completed=1 && property:ro.product.debugfs_restrictions.enabled=true && \
+   property:persist.dbg.keep_debugfs_mounted=""
+   umount /sys/kernel/debug
diff --git a/rootdir/init.environ.rc.in b/rootdir/init.environ.rc.in
index fdaaf1a..bf6e986 100644
--- a/rootdir/init.environ.rc.in
+++ b/rootdir/init.environ.rc.in
@@ -10,9 +10,6 @@
     export ANDROID_TZDATA_ROOT /apex/com.android.tzdata
     export EXTERNAL_STORAGE /sdcard
     export ASEC_MOUNTPOINT /mnt/asec
-    export BOOTCLASSPATH %BOOTCLASSPATH%
-    export DEX2OATBOOTCLASSPATH %DEX2OATBOOTCLASSPATH%
-    export SYSTEMSERVERCLASSPATH %SYSTEMSERVERCLASSPATH%
     %EXPORT_GLOBAL_ASAN_OPTIONS%
     %EXPORT_GLOBAL_GCOV_OPTIONS%
     %EXPORT_GLOBAL_CLANG_COVERAGE_OPTIONS%
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 4219e32..6e85da5 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -56,7 +56,7 @@
     write /sys/module/dm_verity/parameters/prefetch_cluster 0
 
     # Generate ld.config.txt for early executed processes
-    exec -- /system/bin/linkerconfig --target /linkerconfig/bootstrap
+    exec -- /system/bin/bootstrap/linkerconfig --target /linkerconfig/bootstrap
     chmod 644 /linkerconfig/bootstrap/ld.config.txt
     copy /linkerconfig/bootstrap/ld.config.txt /linkerconfig/default/ld.config.txt
     chmod 644 /linkerconfig/default/ld.config.txt
@@ -153,21 +153,76 @@
     mkdir /dev/cpuctl/background
     mkdir /dev/cpuctl/top-app
     mkdir /dev/cpuctl/rt
+    mkdir /dev/cpuctl/system
+    mkdir /dev/cpuctl/system-background
     chown system system /dev/cpuctl
     chown system system /dev/cpuctl/foreground
     chown system system /dev/cpuctl/background
     chown system system /dev/cpuctl/top-app
     chown system system /dev/cpuctl/rt
+    chown system system /dev/cpuctl/system
+    chown system system /dev/cpuctl/system-background
     chown system system /dev/cpuctl/tasks
     chown system system /dev/cpuctl/foreground/tasks
     chown system system /dev/cpuctl/background/tasks
     chown system system /dev/cpuctl/top-app/tasks
     chown system system /dev/cpuctl/rt/tasks
+    chown system system /dev/cpuctl/system/tasks
+    chown system system /dev/cpuctl/system-background/tasks
     chmod 0664 /dev/cpuctl/tasks
     chmod 0664 /dev/cpuctl/foreground/tasks
     chmod 0664 /dev/cpuctl/background/tasks
     chmod 0664 /dev/cpuctl/top-app/tasks
     chmod 0664 /dev/cpuctl/rt/tasks
+    chmod 0664 /dev/cpuctl/system/tasks
+    chmod 0664 /dev/cpuctl/system-background/tasks
+
+    # Create a cpu group for NNAPI HAL processes
+    mkdir /dev/cpuctl/nnapi-hal
+    chown system system /dev/cpuctl/nnapi-hal
+    chown system system /dev/cpuctl/nnapi-hal/tasks
+    chmod 0664 /dev/cpuctl/nnapi-hal/tasks
+    write /dev/cpuctl/nnapi-hal/cpu.uclamp.min 1
+    write /dev/cpuctl/nnapi-hal/cpu.uclamp.latency_sensitive 1
+
+    # Create a cpu group for camera daemon processes
+    mkdir /dev/cpuctl/camera-daemon
+    chown system system /dev/cpuctl/camera-daemon
+    chown system system /dev/cpuctl/camera-daemon/tasks
+    chmod 0664 /dev/cpuctl/camera-daemon/tasks
+
+    # Android only use global RT throttling and doesn't use CONFIG_RT_GROUP_SCHED
+    # for RT group throttling. These values here are just to make sure RT threads
+    # can be migrated to those groups. These settings can be removed once we migrate
+    # to GKI kernel.
+    write /dev/cpuctl/cpu.rt_period_us 1000000
+    write /dev/cpuctl/cpu.rt_runtime_us 950000
+    # Surfaceflinger is in FG group so giving it a bit more
+    write /dev/cpuctl/foreground/cpu.rt_runtime_us 450000
+    write /dev/cpuctl/foreground/cpu.rt_period_us 1000000
+    write /dev/cpuctl/background/cpu.rt_runtime_us 50000
+    write /dev/cpuctl/background/cpu.rt_period_us 1000000
+    write /dev/cpuctl/top-app/cpu.rt_runtime_us 100000
+    write /dev/cpuctl/top-app/cpu.rt_period_us 1000000
+    write /dev/cpuctl/rt/cpu.rt_runtime_us 100000
+    write /dev/cpuctl/rt/cpu.rt_period_us 1000000
+    write /dev/cpuctl/system/cpu.rt_runtime_us 100000
+    write /dev/cpuctl/system/cpu.rt_period_us 1000000
+    write /dev/cpuctl/system-background/cpu.rt_runtime_us 50000
+    write /dev/cpuctl/system-background/cpu.rt_period_us 1000000
+    write /dev/cpuctl/nnapi-hal/cpu.rt_runtime_us 50000
+    write /dev/cpuctl/nnapi-hal/cpu.rt_period_us 1000000
+    write /dev/cpuctl/camera-daemon/cpu.rt_runtime_us 50000
+    write /dev/cpuctl/camera-daemon/cpu.rt_period_us 1000000
+
+    # Migrate root group to system subgroup
+    copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks
+
+    # Create an stune group for camera-specific processes
+    mkdir /dev/stune/camera-daemon
+    chown system system /dev/stune/camera-daemon
+    chown system system /dev/stune/camera-daemon/tasks
+    chmod 0664 /dev/stune/camera-daemon/tasks
 
     # Create an stune group for NNAPI HAL processes
     mkdir /dev/stune/nnapi-hal
@@ -177,14 +232,6 @@
     write /dev/stune/nnapi-hal/schedtune.boost 1
     write /dev/stune/nnapi-hal/schedtune.prefer_idle 1
 
-    # cpuctl hierarchy for devices using utilclamp
-    mkdir /dev/cpuctl/nnapi-hal
-    chown system system /dev/cpuctl/nnapi-hal
-    chown system system /dev/cpuctl/nnapi-hal/tasks
-    chmod 0664 /dev/cpuctl/nnapi-hal/tasks
-    write /dev/cpuctl/nnapi-hal/cpu.uclamp.min 1
-    write /dev/cpuctl/nnapi-hal/cpu.uclamp.latency_sensitive 1
-
     # Create blkio group and apply initial settings.
     # This feature needs kernel to support it, and the
     # device's init.rc must actually set the correct values.
@@ -197,6 +244,7 @@
     chmod 0664 /dev/blkio/background/tasks
     write /dev/blkio/blkio.weight 1000
     write /dev/blkio/background/blkio.weight 200
+    write /dev/blkio/background/blkio.bfq.weight 10
     write /dev/blkio/blkio.group_idle 0
     write /dev/blkio/background/blkio.group_idle 0
 
@@ -275,8 +323,6 @@
     write /proc/sys/vm/mmap_min_addr 32768
     write /proc/sys/net/ipv4/ping_group_range "0 2147483647"
     write /proc/sys/net/unix/max_dgram_qlen 600
-    write /proc/sys/kernel/sched_rt_runtime_us 950000
-    write /proc/sys/kernel/sched_rt_period_us 1000000
 
     # Assign reasonable ceiling values for socket rcv/snd buffers.
     # These should almost always be overridden by the target per the
@@ -298,13 +344,6 @@
     # /proc/net/fib_trie leaks interface IP addresses
     chmod 0400 /proc/net/fib_trie
 
-    # Create cgroup mount points for process groups
-    chown system system /dev/cpuctl
-    chown system system /dev/cpuctl/tasks
-    chmod 0666 /dev/cpuctl/tasks
-    write /dev/cpuctl/cpu.rt_period_us 1000000
-    write /dev/cpuctl/cpu.rt_runtime_us 950000
-
     # sets up initial cpusets for ActivityManager
     # this ensures that the cpusets are present and usable, but the device's
     # init.rc must actually set the correct cpus
@@ -317,7 +356,6 @@
 
     # system-background is for system tasks that should only run on
     # little cores, not on bigs
-    # to be used only by init, so don't change system-bg permissions
     mkdir /dev/cpuset/system-background
     copy /dev/cpuset/cpus /dev/cpuset/system-background/cpus
     copy /dev/cpuset/mems /dev/cpuset/system-background/mems
@@ -332,6 +370,11 @@
     copy /dev/cpuset/cpus /dev/cpuset/top-app/cpus
     copy /dev/cpuset/mems /dev/cpuset/top-app/mems
 
+    # create a cpuset for camera daemon processes
+    mkdir /dev/cpuset/camera-daemon
+    copy /dev/cpuset/cpus /dev/cpuset/camera-daemon/cpus
+    copy /dev/cpuset/mems /dev/cpuset/camera-daemon/mems
+
     # change permissions for all cpusets we'll touch at runtime
     chown system system /dev/cpuset
     chown system system /dev/cpuset/foreground
@@ -339,12 +382,14 @@
     chown system system /dev/cpuset/system-background
     chown system system /dev/cpuset/top-app
     chown system system /dev/cpuset/restricted
+    chown system system /dev/cpuset/camera-daemon
     chown system system /dev/cpuset/tasks
     chown system system /dev/cpuset/foreground/tasks
     chown system system /dev/cpuset/background/tasks
     chown system system /dev/cpuset/system-background/tasks
     chown system system /dev/cpuset/top-app/tasks
     chown system system /dev/cpuset/restricted/tasks
+    chown system system /dev/cpuset/camera-daemon/tasks
 
     # set system-background to 0775 so SurfaceFlinger can touch it
     chmod 0775 /dev/cpuset/system-background
@@ -355,6 +400,7 @@
     chmod 0664 /dev/cpuset/top-app/tasks
     chmod 0664 /dev/cpuset/restricted/tasks
     chmod 0664 /dev/cpuset/tasks
+    chmod 0664 /dev/cpuset/camera-daemon/tasks
 
     # make the PSI monitor accessible to others
     chown system system /proc/pressure/memory
@@ -547,6 +593,7 @@
     mkdir /metadata/ota 0700 root system
     mkdir /metadata/ota/snapshots 0700 root system
     mkdir /metadata/userspacereboot 0770 root system
+    mkdir /metadata/watchdog 0770 root system
 
     mkdir /metadata/apex 0700 root system
     mkdir /metadata/apex/sessions 0700 root system
@@ -567,7 +614,20 @@
     # HALs required before storage encryption can get unlocked (FBE/FDE)
     class_start early_hal
 
+    # Load trusted keys from dm-verity protected partitions
+    exec -- /system/bin/fsverity_init --load-verified-keys
+
+    # Set up a tracing instance for system_server to monitor error_report_end events.
+    # These are sent by kernel tools like KASAN and KFENCE when a memory corruption
+    # is detected.
+    mkdir /sys/kernel/tracing/instances/bootreceiver 0700 system system
+    restorecon_recursive /sys/kernel/tracing/instances/bootreceiver
+    write /sys/kernel/tracing/instances/bootreceiver/buffer_size_kb 1
+    write /sys/kernel/tracing/instances/bootreceiver/trace_options disable_on_free
+    write /sys/kernel/tracing/instances/bootreceiver/events/error_report/error_report_end/enable 1
+
 on post-fs-data
+
     mark_post_data
 
     # Start checkpoint before we touch data
@@ -587,8 +647,8 @@
     mkdir /data/bootchart 0755 shell shell encryption=Require
     bootchart start
 
-    # Make sure that apexd is started in the default namespace
-    enter_default_mount_ns
+    # Avoid predictable entropy pool. Carry over entropy from previous boot.
+    copy /data/system/entropy.dat /dev/urandom
 
     mkdir /data/vendor 0771 root root encryption=Require
     mkdir /data/vendor_ce 0771 root root encryption=None
@@ -596,25 +656,51 @@
     mkdir /data/vendor/hardware 0771 root root
 
     # Start tombstoned early to be able to store tombstones.
+    mkdir /data/anr 0775 system system encryption=Require
     mkdir /data/tombstones 0771 system system encryption=Require
     mkdir /data/vendor/tombstones 0771 root root
     mkdir /data/vendor/tombstones/wifi 0771 wifi wifi
     start tombstoned
 
+    # Make sure that apexd is started in the default namespace
+    enter_default_mount_ns
+
+    # set up keystore directory structure first so that we can end early boot
+    # and start apexd
+    mkdir /data/misc 01771 system misc encryption=Require
+    mkdir /data/misc/keystore 0700 keystore keystore
+    # work around b/183668221
+    restorecon /data/misc /data/misc/keystore
+
+    # Boot level 30
+    # odsign signing keys have MAX_BOOT_LEVEL=30
+    # This is currently the earliest boot level, but we start at 30
+    # to leave room for earlier levels.
+    setprop keystore.boot_level 30
+
+    # Now that /data is mounted and we have created /data/misc/keystore,
+    # we can tell keystore to stop allowing use of early-boot keys,
+    # and access its database for the first time to support creation and
+    # use of MAX_BOOT_LEVEL keys.
+    exec - system system -- /system/bin/vdc keymaster earlyBootEnded
+
     # /data/apex is now available. Start apexd to scan and activate APEXes.
+    #
+    # To handle userspace reboots as well as devices that use FDE, make sure
+    # that apexd is started cleanly here (set apexd.status="") and that it is
+    # restarted if it's already running.
     mkdir /data/apex 0755 root system encryption=None
     mkdir /data/apex/active 0755 root system
     mkdir /data/apex/backup 0700 root system
+    mkdir /data/apex/decompressed 0755 root system encryption=Require
     mkdir /data/apex/hashtree 0700 root system
     mkdir /data/apex/sessions 0700 root system
-    mkdir /data/app-staging 0750 system system encryption=DeleteIfNecessary
-    start apexd
+    mkdir /data/app-staging 0751 system system encryption=DeleteIfNecessary
+    mkdir /data/apex/ota_reserved 0700 root system encryption=Require
+    setprop apexd.status ""
+    restart apexd
 
-    # Avoid predictable entropy pool. Carry over entropy from previous boot.
-    copy /data/system/entropy.dat /dev/urandom
-
-    # create basic filesystem structure
-    mkdir /data/misc 01771 system misc encryption=Require
+    # create rest of basic filesystem structure
     mkdir /data/misc/recovery 0770 system log
     copy /data/misc/recovery/ro.build.fingerprint /data/misc/recovery/ro.build.fingerprint.1
     chmod 0440 /data/misc/recovery/ro.build.fingerprint.1
@@ -638,7 +724,6 @@
     mkdir /data/misc/nfc 0770 nfc nfc
     mkdir /data/misc/nfc/logs 0770 nfc nfc
     mkdir /data/misc/credstore 0700 credstore credstore
-    mkdir /data/misc/keystore 0700 keystore keystore
     mkdir /data/misc/gatekeeper 0700 system system
     mkdir /data/misc/keychain 0771 system system
     mkdir /data/misc/net 0750 root shell
@@ -672,18 +757,25 @@
     mkdir /data/misc/trace 0700 root root
     # create location to store surface and window trace files
     mkdir /data/misc/wmtrace 0700 system system
+    # create location to store accessibility trace files
+    mkdir /data/misc/a11ytrace 0700 system system
     # profile file layout
     mkdir /data/misc/profiles 0771 system system
     mkdir /data/misc/profiles/cur 0771 system system
-    mkdir /data/misc/profiles/ref 0770 system system
+    mkdir /data/misc/profiles/ref 0771 system system
     mkdir /data/misc/profman 0770 system shell
     mkdir /data/misc/gcov 0770 root root
     mkdir /data/misc/installd 0700 root root
     mkdir /data/misc/apexdata 0711 root root
     mkdir /data/misc/apexrollback 0700 root root
+    mkdir /data/misc/appcompat/ 0700 system system
     mkdir /data/misc/snapshotctl_log 0755 root root
     # create location to store pre-reboot information
     mkdir /data/misc/prereboot 0700 system system
+    # directory used for on-device refresh metrics file.
+    mkdir /data/misc/odrefresh 0777 system system
+    # directory used for on-device signing key blob
+    mkdir /data/misc/odsign 0700 root root
 
     mkdir /data/preloads 0775 system system encryption=None
 
@@ -699,6 +791,11 @@
     mkdir /data/app 0771 system system encryption=Require
     mkdir /data/property 0700 root root encryption=Require
 
+    # create directory for updated font files.
+    mkdir /data/fonts/ 0771 root root encryption=Require
+    mkdir /data/fonts/files 0771 system system
+    mkdir /data/fonts/config 0770 system system
+
     # Create directories to push tests to for each linker namespace.
     # Create the subdirectories in case the first test is run as root
     # so it doesn't end up owned by root.
@@ -733,8 +830,6 @@
     # the following directory.
     mkdir /data/mediadrm 0770 mediadrm mediadrm encryption=Require
 
-    mkdir /data/anr 0775 system system encryption=Require
-
     # NFC: create data/nfc for nv storage
     mkdir /data/nfc 0770 nfc nfc encryption=Require
     mkdir /data/nfc/param 0770 nfc nfc
@@ -745,6 +840,9 @@
     mkdir /data/ss 0700 system system encryption=Require
 
     mkdir /data/system 0775 system system encryption=Require
+    mkdir /data/system/environ 0700 system system
+    # b/183861600 attempt to fix selinux label before running derive_classpath service
+    restorecon /data/system/environ
     mkdir /data/system/dropbox 0700 system system
     mkdir /data/system/heapdump 0700 system system
     mkdir /data/system/users 0775 system system
@@ -808,10 +906,6 @@
     wait_for_prop apexd.status activated
     perform_apex_config
 
-    # After apexes are mounted, tell keymaster early boot has ended, so it will
-    # stop allowing use of early-boot keys
-    exec - system system -- /system/bin/vdc keymaster early-boot-ended
-
     # Special-case /data/media/obb per b/64566063
     mkdir /data/media 0770 media_rw media_rw encryption=None
     exec - media_rw media_rw -- /system/bin/chattr +F /data/media
@@ -821,15 +915,37 @@
 
     init_user0
 
+    # Set SELinux security contexts on upgrade or policy update.
+    restorecon --recursive --skip-ce /data
+
+    # Define and export *CLASSPATH variables
+    # Must start before 'odsign', as odsign depends on *CLASSPATH variables
+    exec_start derive_classpath
+    load_exports /data/system/environ/classpath
+
+    # Start the on-device signing daemon, and wait for it to finish, to ensure
+    # ART artifacts are generated if needed.
+    # Must start after 'derive_classpath' to have *CLASSPATH variables set.
+    start odsign
+
+    # Before we can lock keys and proceed to the next boot stage, wait for
+    # odsign to be done with the key
+    wait_for_prop odsign.key.done 1
+
+    # Lock the fs-verity keyring, so no more keys can be added
+    exec -- /system/bin/fsverity_init --lock
+
+    # Bump the boot level to 1000000000; this prevents further on-device signing.
+    # This is a special value that shuts down the thread which listens for
+    # further updates.
+    setprop keystore.boot_level 1000000000
+
     # Allow apexd to snapshot and restore device encrypted apex data in the case
     # of a rollback. This should be done immediately after DE_user data keys
     # are loaded. APEXes should not access this data until this has been
     # completed and apexd.status becomes "ready".
     exec_start apexd-snapshotde
 
-    # Set SELinux security contexts on upgrade or policy update.
-    restorecon --recursive --skip-ce /data
-
     # Check any timezone data in /data is newer than the copy in the time zone data
     # module, delete if not.
     exec - system system -- /system/bin/tzdatacheck /apex/com.android.tzdata/etc/tz /data/misc/zoneinfo
@@ -855,6 +971,7 @@
 # It is recommended to put unnecessary data/ initialization from post-fs-data
 # to start-zygote in device's init.rc to unblock zygote start.
 on zygote-start && property:ro.crypto.state=unencrypted
+    wait_for_prop odsign.verification.done 1
     # A/B update verifier that marks a successful boot.
     exec_start update_verifier_nonencrypted
     start statsd
@@ -863,6 +980,7 @@
     start zygote_secondary
 
 on zygote-start && property:ro.crypto.state=unsupported
+    wait_for_prop odsign.verification.done 1
     # A/B update verifier that marks a successful boot.
     exec_start update_verifier_nonencrypted
     start statsd
@@ -871,6 +989,7 @@
     start zygote_secondary
 
 on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file
+    wait_for_prop odsign.verification.done 1
     # A/B update verifier that marks a successful boot.
     exec_start update_verifier_nonencrypted
     start statsd
@@ -982,16 +1101,13 @@
     chown root radio /proc/cmdline
 
     # Define default initial receive window size in segments.
-    setprop net.tcp.default_init_rwnd 60
+    setprop net.tcp_def_init_rwnd 60
 
     # Start standard binderized HAL daemons
     class_start hal
 
     class_start core
 
-    # Requires keystore (currently a core service) to be ready first.
-    exec -- /system/bin/fsverity_init
-
 on nonencrypted
     class_start main
     class_start late_start
@@ -1024,6 +1140,7 @@
     class_start main
     class_start late_start
     setprop service.bootanim.exit 0
+    setprop service.bootanim.progress 0
     start bootanim
 
 on property:vold.decrypt=trigger_shutdown_framework
@@ -1044,6 +1161,11 @@
 on property:sys.sysctl.extra_free_kbytes=*
     write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}
 
+# Allow users to drop caches
+on property:perf.drop_caches=3
+    write /proc/sys/vm/drop_caches 3
+    setprop perf.drop_caches 0
+
 # "tcp_default_init_rwnd" Is too long!
 on property:net.tcp_def_init_rwnd=*
     write /proc/sys/net/ipv4/tcp_default_init_rwnd ${net.tcp_def_init_rwnd}
@@ -1111,6 +1233,8 @@
     chmod 0773 /data/misc/trace
     # Give reads to anyone for the window trace folder on debug builds.
     chmod 0775 /data/misc/wmtrace
+    # Give reads to anyone for the accessibility trace folder on debug builds.
+    chmod 0775 /data/misc/a11ytrace
 
 on init && property:ro.debuggable=1
     start console
@@ -1121,10 +1245,10 @@
   setprop dev.bootcomplete ""
   setprop sys.init.updatable_crashing ""
   setprop sys.init.updatable_crashing_process_name ""
-  setprop apexd.status ""
   setprop sys.user.0.ce_available ""
   setprop sys.shutdown.requested ""
   setprop service.bootanim.exit ""
+  setprop service.bootanim.progress ""
 
 on userspace-reboot-fs-remount
   # Make sure that vold is running.
@@ -1151,3 +1275,7 @@
 
 on property:sys.boot_completed=1 && property:sys.init.userspace_reboot.in_progress=1
   setprop sys.init.userspace_reboot.in_progress ""
+
+# Migrate tasks again in case kernel threads are created during boot
+on property:sys.boot_completed=1
+  copy_per_line /dev/cpuctl/tasks /dev/cpuctl/system/tasks
diff --git a/rootdir/init.usb.rc b/rootdir/init.usb.rc
index 27b05ec..0730cce 100644
--- a/rootdir/init.usb.rc
+++ b/rootdir/init.usb.rc
@@ -19,6 +19,9 @@
     updatable
     seclabel u:r:adbd:s0
 
+on property:vendor.sys.usb.adb.disabled=*
+    setprop sys.usb.adb.disabled ${vendor.sys.usb.adb.disabled}
+
 # Set default value on sys.usb.configfs early in boot sequence. It will be
 # overridden in `on boot` action of init.hardware.rc.
 on init
diff --git a/rootdir/ueventd.rc b/rootdir/ueventd.rc
index 1994bdb..56e774b 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -1,3 +1,6 @@
+import /vendor/etc/ueventd.rc
+import /odm/etc/ueventd.rc
+
 firmware_directories /etc/firmware/ /odm/firmware/ /vendor/firmware/ /firmware/image/
 uevent_socket_rcvbuf_size 16M
 
@@ -34,15 +37,15 @@
 /dev/tty                  0666   root       root
 /dev/random               0666   root       root
 /dev/urandom              0666   root       root
-# Make HW RNG readable by group system to let EntropyMixer read it.
-/dev/hw_random            0440   root       system
 /dev/ashmem*              0666   root       root
 /dev/binder               0666   root       root
 /dev/hwbinder             0666   root       root
 /dev/vndbinder            0666   root       root
 
 /dev/pmsg0                0222   root       log
-/dev/dma_heap/system      0666   system     system
+/dev/dma_heap/system      0444   system     system
+/dev/dma_heap/system-uncached      0444   system     system
+/dev/dma_heap/system-secure        0444   system     system
 
 # kms driver for drm based gpu
 /dev/dri/*                0666   root       graphics
@@ -64,6 +67,10 @@
 # CDMA radio interface MUX
 /dev/ppp                  0660   radio      vpn
 
+# Virtualisation is managed by Virt Manager
+/dev/kvm                  0600   virtmanager root
+/dev/vhost-vsock          0600   virtmanager root
+
 # sysfs properties
 /sys/devices/platform/trusty.*      trusty_version        0440  root   log
 /sys/devices/virtual/input/input*   enable      0660  root   input
@@ -71,3 +78,5 @@
 /sys/devices/virtual/usb_composite/*   enable      0664  root   system
 /sys/devices/system/cpu/cpu*   cpufreq/scaling_max_freq   0664  system system
 /sys/devices/system/cpu/cpu*   cpufreq/scaling_min_freq   0664  system system
+/sys/devices/virtual/misc/uhid/*/leds/* brightness   0664  system system
+/sys/devices/virtual/misc/uhid/*/leds/* multi_intensity   0664  system system
diff --git a/run-as/Android.bp b/run-as/Android.bp
index accd07d..9baee8f 100644
--- a/run-as/Android.bp
+++ b/run-as/Android.bp
@@ -14,6 +14,23 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["system_core_run-as_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+    name: "system_core_run-as_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "NOTICE",
+    ],
+}
+
 cc_binary {
     name: "run-as",
     srcs: [
diff --git a/sdcard/Android.bp b/sdcard/Android.bp
index c096587..10a7d16 100644
--- a/sdcard/Android.bp
+++ b/sdcard/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     srcs: ["sdcard.cpp"],
     name: "sdcard",
diff --git a/set-verity-state/Android.bp b/set-verity-state/Android.bp
index da112c9..2f0cb10 100644
--- a/set-verity-state/Android.bp
+++ b/set-verity-state/Android.bp
@@ -1,5 +1,9 @@
 // Copyright 2019 The Android Open Source Project
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "set-verity-state",
     srcs: ["set-verity-state.cpp"],
diff --git a/shell_and_utilities/Android.bp b/shell_and_utilities/Android.bp
index 5e013fe..b7d7490 100644
--- a/shell_and_utilities/Android.bp
+++ b/shell_and_utilities/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 phony {
     name: "shell_and_utilities",
     required: [
diff --git a/shell_and_utilities/README.md b/shell_and_utilities/README.md
index d169d29..f339553 100644
--- a/shell_and_utilities/README.md
+++ b/shell_and_utilities/README.md
@@ -26,7 +26,8 @@
 because the toolbox implementations did have bugs fixed and options added
 over the years. Gingerbread's rm, for example, supported `-r`/`-R` but not
 `-f`. But this gives you an idea of what was available in any given release,
-and how usable it was likely to be.
+and how usable it was likely to be. (**Bold** marks where we switched to toybox
+or first added something to toybox.)
 
 Also note that in any given release `toybox` probably contains more
 commands than there are symlinks for in `/system/bin`. You can get the
@@ -118,18 +119,18 @@
 toolbox: getevent iftop ioctl log nandread newfs\_msdos ps prlimit
 sendevent start stop top
 
-toybox (0.7.0-ish): acpi base64 basename blockdev bzcat cal cat chcon chgrp chmod
-chown chroot cksum clear comm cmp cp cpio cut date df dirname dmesg
-dos2unix du echo env expand expr fallocate false find flock free
+toybox (0.7.0-ish): acpi **base64** basename blockdev bzcat cal cat chcon chgrp chmod
+chown chroot cksum clear comm cmp cp cpio cut date **df** dirname dmesg
+dos2unix **du** echo env expand expr fallocate false find **flock** free
 getenforce getprop groups head hostname hwclock id ifconfig inotifyd
-insmod ionice iorenice kill killall load\_policy ln logname losetup ls
-lsmod lsof lsusb md5sum mkdir mknod mkswap mktemp modinfo more mount
+insmod **ionice** **iorenice** kill **killall** load\_policy ln logname losetup **ls**
+lsmod **lsof** lsusb md5sum mkdir mknod mkswap mktemp modinfo more *mount*
 mountpoint mv netstat nice nl nohup od paste patch pgrep pidof pkill
-pmap printenv printf pwd readlink realpath renice restorecon rm rmdir
+pmap printenv printf pwd readlink realpath **renice** restorecon rm rmdir
 rmmod route runcon sed seq setenforce setprop setsid sha1sum sleep sort
 split stat strings swapoff swapon sync sysctl tac tail tar taskset tee
-time timeout touch tr true truncate tty ulimit umount uname uniq unix2dos
-uptime usleep vmstat wc which whoami xargs xxd yes
+time timeout touch tr true truncate **tty** **ulimit** umount uname uniq unix2dos
+**uptime** usleep vmstat wc which whoami xargs **xxd** yes
 
 
 ## Android 8.0 (Oreo)
@@ -141,19 +142,19 @@
 toolbox: getevent newfs\_msdos
 
 toybox (0.7.3-ish): acpi base64 basename blockdev cal cat chcon chgrp chmod chown
-chroot chrt cksum clear cmp comm cp cpio cut date df diff dirname dmesg
-dos2unix du echo env expand expr fallocate false file find flock free
-getenforce getprop groups gunzip gzip head hostname hwclock id ifconfig
-inotifyd insmod ionice iorenice kill killall ln load\_policy log logname
-losetup ls lsmod lsof lspci lsusb md5sum microcom mkdir mkfifo mknod
-mkswap mktemp modinfo modprobe more mount mountpoint mv netstat nice
-nl nohup od paste patch pgrep pidof pkill pmap printenv printf ps pwd
-readlink realpath renice restorecon rm rmdir rmmod runcon sed sendevent
-seq setenforce setprop setsid sha1sum sha224sum sha256sum sha384sum
-sha512sum sleep sort split start stat stop strings swapoff swapon sync
-sysctl tac tail tar taskset tee time timeout top touch tr true truncate
-tty ulimit umount uname uniq unix2dos uptime usleep uudecode uuencode
-vmstat wc which whoami xargs xxd yes zcat
+chroot chrt cksum clear cmp comm cp cpio cut date df **diff** dirname dmesg
+dos2unix du echo env expand expr fallocate false **file** find flock free
+getenforce getprop groups **gunzip** **gzip** head hostname hwclock id ifconfig
+inotifyd insmod ionice iorenice kill killall ln load\_policy **log** logname
+losetup ls lsmod lsof **lspci** lsusb md5sum **microcom** mkdir **mkfifo** mknod
+mkswap mktemp modinfo **modprobe** more mount mountpoint mv netstat nice
+nl nohup od paste patch pgrep pidof pkill pmap printenv printf **ps** pwd
+readlink realpath renice restorecon rm rmdir rmmod runcon sed **sendevent**
+seq setenforce setprop setsid sha1sum **sha224sum** **sha256sum** **sha384sum**
+**sha512sum** sleep sort split start stat stop strings swapoff swapon sync
+sysctl tac tail tar taskset tee time timeout **top** touch tr true truncate
+tty ulimit umount uname uniq unix2dos uptime usleep **uudecode** **uuencode**
+vmstat wc which whoami xargs xxd yes **zcat**
 
 
 ## Android 9.0 (Pie)
@@ -168,7 +169,7 @@
 
 toybox (0.7.6-ish): acpi base64 basename blockdev cal cat chcon chgrp chmod chown
 chroot chrt cksum clear cmp comm cp cpio cut date df diff dirname dmesg
-dos2unix du echo env expand expr fallocate false file find flock fmt free
+dos2unix du echo env expand expr fallocate false file find flock **fmt** free
 getenforce groups gunzip gzip head hostname hwclock id ifconfig inotifyd
 insmod ionice iorenice kill killall ln load\_policy log logname losetup ls
 lsmod lsof lspci lsusb md5sum microcom mkdir mkfifo mknod mkswap mktemp
@@ -176,7 +177,7 @@
 patch pgrep pidof pkill pmap printenv printf ps pwd readlink realpath
 renice restorecon rm rmdir rmmod runcon sed sendevent seq setenforce
 setprop setsid sha1sum sha224sum sha256sum sha384sum sha512sum sleep
-sort split start stat stop strings stty swapoff swapon sync sysctl tac
+sort split start stat stop strings **stty** swapoff swapon sync sysctl tac
 tail tar taskset tee time timeout top touch tr true truncate tty ulimit
 umount uname uniq unix2dos uptime usleep uudecode uuencode vmstat wc
 which whoami xargs xxd yes zcat
@@ -192,24 +193,24 @@
 
 toolbox: getevent getprop
 
-toybox (0.8.0-ish): acpi base64 basename bc blkid blockdev cal cat chattr chcon chgrp
+toybox (0.8.0-ish): acpi base64 basename **bc** **blkid** blockdev cal cat **chattr** chcon chgrp
 chmod chown chroot chrt cksum clear cmp comm cp cpio cut date dd df
-diff dirname dmesg dos2unix du echo egrep env expand expr fallocate
-false fgrep file find flock fmt free freeramdisk fsfreeze getconf
-getenforce getfattr grep groups gunzip gzip head help hostname hwclock
-i2cdetect i2cdump i2cget i2cset iconv id ifconfig inotifyd insmod
-install ionice iorenice iotop kill killall ln load\_policy log logname
-losetup ls lsattr lsmod lsof lspci lsusb makedevs md5sum microcom
+diff dirname dmesg dos2unix du echo **egrep** env expand expr fallocate
+false **fgrep** file find flock fmt free **freeramdisk** **fsfreeze** **getconf**
+getenforce **getfattr** grep groups gunzip gzip head **help** hostname hwclock
+**i2cdetect** **i2cdump** **i2cget** **i2cset** **iconv** id ifconfig inotifyd insmod
+**install** ionice iorenice **iotop** kill killall ln load\_policy log logname
+losetup ls **lsattr** lsmod lsof lspci lsusb **makedevs** md5sum microcom
 mkdir mkfifo mknod mkswap mktemp modinfo modprobe more mount mountpoint
-mv nbd-client nc netcat netstat nice nl nohup nproc nsenter od partprobe
-paste patch pgrep pidof ping ping6 pivot\_root pkill pmap printenv
-printf prlimit ps pwd pwdx readlink realpath renice restorecon rev
-rfkill rm rmdir rmmod runcon sed sendevent seq setenforce setfattr
+mv **nbd-client** **nc** **netcat** netstat nice nl nohup **nproc** **nsenter** od **partprobe**
+paste patch pgrep pidof **ping** **ping6** **pivot\_root** pkill pmap printenv
+printf **prlimit** ps pwd **pwdx** readlink realpath renice restorecon **rev**
+**rfkill** rm rmdir rmmod runcon sed sendevent seq setenforce **setfattr**
 setprop setsid sha1sum sha224sum sha256sum sha384sum sha512sum sleep
 sort split start stat stop strings stty swapoff swapon sync sysctl
-tac tail tar taskset tee time timeout top touch tr traceroute traceroute6
-true truncate tty tunctl ulimit umount uname uniq unix2dos unlink
-unshare uptime usleep uudecode uuencode uuidgen vconfig vmstat watch
+tac tail tar taskset tee time timeout top touch tr **traceroute** **traceroute6**
+true truncate tty **tunctl** ulimit umount uname uniq unix2dos **unlink**
+**unshare** uptime usleep uudecode uuencode **uuidgen** **vconfig** vmstat **watch**
 wc which whoami xargs xxd yes zcat
 
 ## Android 11 ("R")
@@ -225,21 +226,53 @@
 toolbox: getevent getprop setprop start stop
 
 toybox (0.8.3-ish): acpi base64 basename blkid blockdev cal cat chattr chcon chgrp chmod
-chown chroot chrt cksum clear cmp comm cp cpio cut date dd devmem
+chown chroot chrt cksum clear cmp comm cp cpio cut date dd **devmem**
 df diff dirname dmesg dos2unix du echo egrep env expand expr fallocate
-false fgrep file find flock fmt free freeramdisk fsfreeze fsync getconf
-getenforce getfattr getopt grep groups gunzip gzip head help hostname
+false fgrep file find flock fmt free freeramdisk fsfreeze **fsync** getconf
+getenforce getfattr **getopt** grep groups gunzip gzip head help hostname
 hwclock i2cdetect i2cdump i2cget i2cset iconv id ifconfig inotifyd
 insmod install ionice iorenice iotop kill killall ln load\_policy log
 logname losetup ls lsattr lsmod lsof lspci lsusb makedevs md5sum microcom
 mkdir mkfifo mknod mkswap mktemp modinfo modprobe more mount mountpoint
 mv nbd-client nc netcat netstat nice nl nohup nproc nsenter od partprobe
 paste patch pgrep pidof ping ping6 pivot\_root pkill pmap printenv
-printf prlimit ps pwd pwdx readelf readlink realpath renice restorecon
+printf prlimit ps pwd pwdx **readelf** readlink realpath renice restorecon
 rev rfkill rm rmdir rmmod runcon sed sendevent seq setenforce setfattr
 setsid sha1sum sha224sum sha256sum sha384sum sha512sum sleep sort
 split stat strings stty swapoff swapon sync sysctl tac tail tar taskset
 tee time timeout top touch tr traceroute traceroute6 true truncate
 tty tunctl ulimit umount uname uniq unix2dos unlink unshare uptime
-usleep uudecode uuencode uuidgen vconfig vi vmstat watch wc which
+usleep uudecode uuencode uuidgen vconfig **vi** vmstat watch wc which
 whoami xargs xxd yes zcat
+
+## Android ("S")
+
+BSD: fsck\_msdos newfs\_msdos
+
+bzip2: bzcat bzip2 bunzip2
+
+gavinhoward/bc: bc
+
+one-true-awk: awk
+
+toolbox: getevent getprop setprop start stop
+
+toybox (0.8.4-ish): **[** acpi base64 basename **blkdiscard** blkid blockdev cal cat chattr chcon
+chgrp chmod chown chroot chrt cksum clear cmp comm cp cpio cut date
+dd devmem df diff dirname dmesg dos2unix du echo egrep env expand
+expr fallocate false fgrep file find flock fmt free freeramdisk fsfreeze
+fsync getconf getenforce getfattr getopt grep groups gunzip gzip head
+help hostname hwclock i2cdetect i2cdump i2cget i2cset iconv id ifconfig
+inotifyd insmod install ionice iorenice iotop kill killall ln load\_policy
+log logname losetup ls lsattr lsmod lsof lspci lsusb makedevs md5sum
+microcom mkdir mkfifo mknod mkswap mktemp modinfo modprobe more mount
+mountpoint mv nbd-client nc netcat netstat nice nl nohup nproc nsenter
+od partprobe paste patch pgrep pidof ping ping6 pivot\_root pkill pmap
+printenv printf prlimit ps pwd pwdx readelf readlink realpath renice
+restorecon rev rfkill rm rmdir rmmod **rtcwake** runcon sed sendevent
+seq setenforce setfattr setsid sha1sum sha224sum sha256sum sha384sum
+sha512sum sleep sort split stat strings stty swapoff swapon sync sysctl
+tac tail tar taskset tee **test** time timeout top touch tr traceroute
+traceroute6 true truncate tty tunctl ulimit umount uname uniq unix2dos
+unlink unshare uptime usleep uudecode uuencode uuidgen vconfig vi
+vmstat watch wc which whoami xargs xxd yes zcat
diff --git a/storaged/Android.bp b/storaged/Android.bp
index cc19481..ec27a08 100644
--- a/storaged/Android.bp
+++ b/storaged/Android.bp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_defaults {
     name: "storaged_defaults",
 
diff --git a/storaged/include/storaged.h b/storaged/include/storaged.h
index 6f92048..79b5d41 100644
--- a/storaged/include/storaged.h
+++ b/storaged/include/storaged.h
@@ -86,6 +86,7 @@
     sp<android::hardware::health::V2_0::IHealth> health;
     unique_ptr<storage_info_t> storage_info;
     static const uint32_t current_version;
+    Mutex proto_lock;
     unordered_map<userid_t, bool> proto_loaded;
     void load_proto(userid_t user_id);
     char* prepare_proto(userid_t user_id, StoragedProto* proto);
diff --git a/storaged/storaged.cpp b/storaged/storaged.cpp
index 573b8c5..b7aa89f 100644
--- a/storaged/storaged.cpp
+++ b/storaged/storaged.cpp
@@ -162,6 +162,8 @@
 }
 
 void storaged_t::add_user_ce(userid_t user_id) {
+    Mutex::Autolock _l(proto_lock);
+
     if (!proto_loaded[user_id]) {
         load_proto(user_id);
         proto_loaded[user_id] = true;
@@ -169,6 +171,8 @@
 }
 
 void storaged_t::remove_user_ce(userid_t user_id) {
+    Mutex::Autolock _l(proto_lock);
+
     proto_loaded[user_id] = false;
     mUidm.clear_user_history(user_id);
     RemoveFileIfExists(proto_path(user_id), nullptr);
@@ -298,6 +302,8 @@
 }
 
 void storaged_t::flush_protos(unordered_map<int, StoragedProto>* protos) {
+    Mutex::Autolock _l(proto_lock);
+
     for (auto& it : *protos) {
         /*
          * Don't flush proto if we haven't attempted to load it from file.
diff --git a/toolbox/Android.bp b/toolbox/Android.bp
index 4ca5f5a..8594ec4 100644
--- a/toolbox/Android.bp
+++ b/toolbox/Android.bp
@@ -1,3 +1,20 @@
+package {
+    default_applicable_licenses: ["system_core_toolbox_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+    name: "system_core_toolbox_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+    ],
+    license_text: [
+        "NOTICE",
+    ],
+}
+
 cc_defaults {
     name: "toolbox_defaults",
     cflags: [
@@ -19,7 +36,6 @@
 cc_defaults {
     name: "toolbox_binary_defaults",
     defaults: ["toolbox_defaults"],
-    cpp_std: "experimental",
     srcs: [
         "toolbox.c",
         "getevent.c",
diff --git a/toolbox/OWNERS b/toolbox/OWNERS
index 7529cb9..5e2c581 100644
--- a/toolbox/OWNERS
+++ b/toolbox/OWNERS
@@ -1 +1,2 @@
 include platform/system/core:/janitors/OWNERS
+per-file modprobe.c=willmcvicker@google.com,dvander@google.com
diff --git a/toolbox/modprobe.cpp b/toolbox/modprobe.cpp
index 7df7b71..711586a 100644
--- a/toolbox/modprobe.cpp
+++ b/toolbox/modprobe.cpp
@@ -215,10 +215,7 @@
         return EXIT_FAILURE;
     }
 
-    Modprobe m(mod_dirs);
-    if (blocklist) {
-        m.EnableBlocklist(true);
-    }
+    Modprobe m(mod_dirs, "modules.load", blocklist);
 
     for (const auto& module : modules) {
         switch (mode) {
diff --git a/trusty/Android.bp b/trusty/Android.bp
new file mode 100644
index 0000000..38c6204
--- /dev/null
+++ b/trusty/Android.bp
@@ -0,0 +1,42 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["system_core_trusty_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+    name: "system_core_trusty_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-Apache-2.0",
+        "SPDX-license-identifier-MIT",
+    ],
+    // large-scale-change unable to identify any license_text files
+}
diff --git a/trusty/OWNERS b/trusty/OWNERS
index 1fb473e..5c4e03a 100644
--- a/trusty/OWNERS
+++ b/trusty/OWNERS
@@ -1,7 +1,7 @@
-arve@google.com
-dkrahn@google.com
-drewry@google.com
+armellel@google.com
+arve@android.com
 gmar@google.com
+marcone@google.com
 mmaurer@google.com
 ncbray@google.com
 swillden@google.com
diff --git a/deprecated-adf/libadfhwc/Android.bp b/trusty/apploader/Android.bp
similarity index 63%
rename from deprecated-adf/libadfhwc/Android.bp
rename to trusty/apploader/Android.bp
index 57a8d76..4411964 100644
--- a/deprecated-adf/libadfhwc/Android.bp
+++ b/trusty/apploader/Android.bp
@@ -1,4 +1,5 @@
-// Copyright (C) 2013 The Android Open Source Project
+//
+// Copyright (C) 2020 The Android Open-Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -11,19 +12,29 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 
-cc_library_static {
-    name: "libadfhwc",
-    srcs: ["adfhwc.cpp"],
-    static_libs: [
-        "libadf",
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "trusty_apploader",
+    vendor: true,
+
+    srcs: [
+        "apploader.cpp",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libc",
         "liblog",
-        "libutils",
+        "libtrusty",
+        "libdmabufheap",
     ],
     cflags: [
-        "-DLOG_TAG=\"adfhwc\"",
+        "-Wall",
         "-Werror",
     ],
-    local_include_dirs: ["include"],
-    export_include_dirs: ["include"],
 }
diff --git a/trusty/apploader/apploader.cpp b/trusty/apploader/apploader.cpp
new file mode 100644
index 0000000..4aca375
--- /dev/null
+++ b/trusty/apploader/apploader.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TrustyAppLoader"
+
+#include <BufferAllocator/BufferAllocator.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/sendfile.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+#include <algorithm>
+#include <string>
+
+#include "apploader_ipc.h"
+
+using android::base::unique_fd;
+using std::string;
+
+constexpr const char kTrustyDefaultDeviceName[] = "/dev/trusty-ipc-dev0";
+
+static const char* dev_name = kTrustyDefaultDeviceName;
+
+static const char* _sopts = "hD:";
+static const struct option _lopts[] = {
+        {"help", no_argument, 0, 'h'},
+        {"dev", required_argument, 0, 'D'},
+        {0, 0, 0, 0},
+};
+
+static const char* usage =
+        "Usage: %s [options] package-file\n"
+        "\n"
+        "options:\n"
+        "  -h, --help            prints this message and exit\n"
+        "  -D, --dev name        Trusty device name\n"
+        "\n";
+
+static void print_usage_and_exit(const char* prog, int code) {
+    fprintf(stderr, usage, prog);
+    exit(code);
+}
+
+static void parse_options(int argc, char** argv) {
+    int c;
+    int oidx = 0;
+
+    while (1) {
+        c = getopt_long(argc, argv, _sopts, _lopts, &oidx);
+        if (c == -1) {
+            break; /* done */
+        }
+
+        switch (c) {
+            case 'h':
+                print_usage_and_exit(argv[0], EXIT_SUCCESS);
+                break;
+
+            case 'D':
+                dev_name = strdup(optarg);
+                break;
+
+            default:
+                print_usage_and_exit(argv[0], EXIT_FAILURE);
+        }
+    }
+}
+
+static unique_fd read_file(const char* file_name, off64_t* out_file_size) {
+    int rc;
+    long page_size = sysconf(_SC_PAGESIZE);
+    off64_t file_size, file_page_offset, file_page_size;
+    struct stat64 st;
+
+    unique_fd file_fd(TEMP_FAILURE_RETRY(open(file_name, O_RDONLY)));
+    if (!file_fd.ok()) {
+        PLOG(ERROR) << "Error opening file " << file_name;
+        return {};
+    }
+
+    rc = fstat64(file_fd, &st);
+    if (rc < 0) {
+        PLOG(ERROR) << "Error calling stat on file '" << file_name << "'";
+        return {};
+    }
+
+    assert(st.st_size >= 0);
+    file_size = st.st_size;
+
+    /* The dmabuf size needs to be a multiple of the page size */
+    file_page_offset = file_size & (page_size - 1);
+    if (file_page_offset) {
+        file_page_offset = page_size - file_page_offset;
+    }
+    if (__builtin_add_overflow(file_size, file_page_offset, &file_page_size)) {
+        LOG(ERROR) << "Failed to page-align file size";
+        return {};
+    }
+
+    BufferAllocator alloc;
+    unique_fd dmabuf_fd(alloc.Alloc(kDmabufSystemHeapName, file_page_size));
+    if (!dmabuf_fd.ok()) {
+        LOG(ERROR) << "Error creating dmabuf: " << dmabuf_fd.get();
+        return dmabuf_fd;
+    }
+
+    void* shm = mmap(0, file_page_size, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf_fd, 0);
+    if (shm == MAP_FAILED) {
+        return {};
+    }
+
+    off64_t file_offset = 0;
+    while (file_offset < file_size) {
+        ssize_t num_read = TEMP_FAILURE_RETRY(
+                pread(file_fd, (char*)shm + file_offset, file_size - file_offset, file_offset));
+
+        if (num_read < 0) {
+            PLOG(ERROR) << "Error reading package file '" << file_name << "'";
+            break;
+        }
+
+        if (num_read == 0) {
+            LOG(ERROR) << "Unexpected end of file '" << file_name << "'";
+            break;
+        }
+
+        file_offset += (off64_t)num_read;
+    }
+
+    munmap(shm, file_page_size);
+
+    if (file_offset < file_size) {
+        return {};
+    }
+
+    assert(file_offset == file_size);
+    if (out_file_size) {
+        *out_file_size = file_size;
+    }
+
+    return dmabuf_fd;
+}
+
+static ssize_t send_load_message(int tipc_fd, int package_fd, off64_t package_size) {
+    struct apploader_header hdr = {
+            .cmd = APPLOADER_CMD_LOAD_APPLICATION,
+    };
+    struct apploader_load_app_req req = {
+            .package_size = static_cast<uint64_t>(package_size),
+    };
+    struct iovec tx[2] = {{&hdr, sizeof(hdr)}, {&req, sizeof(req)}};
+    struct trusty_shm shm = {
+            .fd = package_fd,
+            .transfer = TRUSTY_SHARE,
+    };
+    return tipc_send(tipc_fd, tx, 2, &shm, 1);
+}
+
+static ssize_t read_response(int tipc_fd) {
+    struct apploader_resp resp;
+    ssize_t rc = read(tipc_fd, &resp, sizeof(resp));
+    if (rc < 0) {
+        PLOG(ERROR) << "Failed to read response";
+        return rc;
+    }
+
+    if (rc < sizeof(resp)) {
+        LOG(ERROR) << "Not enough data in response: " << rc;
+        return -EIO;
+    }
+
+    if (resp.hdr.cmd != (APPLOADER_CMD_LOAD_APPLICATION | APPLOADER_RESP_BIT)) {
+        LOG(ERROR) << "Invalid command in response: " << resp.hdr.cmd;
+        return -EINVAL;
+    }
+
+    switch (resp.error) {
+        case APPLOADER_NO_ERROR:
+            break;
+        case APPLOADER_ERR_UNKNOWN_CMD:
+            LOG(ERROR) << "Error: unknown command";
+            break;
+        case APPLOADER_ERR_INVALID_CMD:
+            LOG(ERROR) << "Error: invalid command arguments";
+            break;
+        case APPLOADER_ERR_NO_MEMORY:
+            LOG(ERROR) << "Error: out of Trusty memory";
+            break;
+        case APPLOADER_ERR_VERIFICATION_FAILED:
+            LOG(ERROR) << "Error: failed to verify the package";
+            break;
+        case APPLOADER_ERR_LOADING_FAILED:
+            LOG(ERROR) << "Error: failed to load the package";
+            break;
+        case APPLOADER_ERR_ALREADY_EXISTS:
+            LOG(ERROR) << "Error: application already exists";
+            break;
+        case APPLOADER_ERR_INTERNAL:
+            LOG(ERROR) << "Error: internal apploader error";
+            break;
+        default:
+            LOG(ERROR) << "Unrecognized error: " << resp.error;
+            break;
+    }
+
+    return static_cast<ssize_t>(resp.error);
+}
+
+static ssize_t send_app_package(const char* package_file_name) {
+    ssize_t rc = 0;
+    int tipc_fd = -1;
+    off64_t package_size;
+
+    unique_fd package_fd = read_file(package_file_name, &package_size);
+    if (!package_fd.ok()) {
+        rc = -1;
+        goto err_read_file;
+    }
+
+    tipc_fd = tipc_connect(dev_name, APPLOADER_PORT);
+    if (tipc_fd < 0) {
+        LOG(ERROR) << "Failed to connect to Trusty app loader: " << strerror(-tipc_fd);
+        rc = tipc_fd;
+        goto err_tipc_connect;
+    }
+
+    rc = send_load_message(tipc_fd, package_fd, package_size);
+    if (rc < 0) {
+        LOG(ERROR) << "Failed to send package: " << rc;
+        goto err_send;
+    }
+
+    rc = read_response(tipc_fd);
+
+err_send:
+    tipc_close(tipc_fd);
+err_tipc_connect:
+err_read_file:
+    return rc;
+}
+
+int main(int argc, char** argv) {
+    parse_options(argc, argv);
+    if (optind + 1 != argc) {
+        print_usage_and_exit(argv[0], EXIT_FAILURE);
+    }
+
+    int rc = send_app_package(argv[optind]);
+    return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/trusty/apploader/apploader_ipc.h b/trusty/apploader/apploader_ipc.h
new file mode 100644
index 0000000..d8c915e
--- /dev/null
+++ b/trusty/apploader/apploader_ipc.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#define APPLOADER_PORT "com.android.trusty.apploader"
+
+enum apploader_command : uint32_t {
+    APPLOADER_REQ_SHIFT = 1,
+    APPLOADER_RESP_BIT = 1,
+
+    APPLOADER_CMD_LOAD_APPLICATION = (0 << APPLOADER_REQ_SHIFT),
+    APPLOADER_CMD_GET_VERSION = (1 << APPLOADER_REQ_SHIFT),
+    APPLOADER_CMD_UNLOAD_APPLICATION = (2 << APPLOADER_REQ_SHIFT),
+};
+
+/**
+ * enum apploader_error - error codes for apploader
+ * @APPLOADER_NO_ERROR:                 no error
+ * @APPLOADER_ERR_UNKNOWN_CMD:          unknown or not implemented command
+ * @APPLOADER_ERR_INVALID_CMD:          invalid arguments or inputs passed to
+ *                                      command
+ * @APPLOADER_ERR_NO_MEMORY:            failed to allocate memory
+ * @APPLOADER_ERR_VERIFICATION_FAILED:  failed to verify input application
+ *                                      package for any reason, e.g., signature
+ *                                      verification failed
+ * @APPLOADER_ERR_LOADING_FAILED:       Trusty kernel or apploader service
+ *                                      failed to load application
+ * @APPLOADER_ERR_ALREADY_EXISTS:       application has already been loaded
+ * @APPLOADER_ERR_INTERNAL:             miscellaneous or internal apploader
+ *                                      error not covered by the above
+ */
+enum apploader_error : uint32_t {
+    APPLOADER_NO_ERROR = 0,
+    APPLOADER_ERR_UNKNOWN_CMD,
+    APPLOADER_ERR_INVALID_CMD,
+    APPLOADER_ERR_NO_MEMORY,
+    APPLOADER_ERR_VERIFICATION_FAILED,
+    APPLOADER_ERR_LOADING_FAILED,
+    APPLOADER_ERR_ALREADY_EXISTS,
+    APPLOADER_ERR_INTERNAL,
+};
+
+/**
+ * apploader_header - Serial header for communicating with apploader
+ * @cmd: the command; one of &enum apploader_command values.
+ */
+struct apploader_header {
+    uint32_t cmd;
+} __packed;
+
+/**
+ * apploader_load_app_req - Serial arguments for LOAD_APPLICATION command
+ * @package_size: size of the application package.
+ *
+ * Load an application from a given memory region. The request message also
+ * contains a handle for a memfd that contains the application package.
+ *
+ * The response is a &struct apploader_resp with the error code or
+ * %APPLOADER_NO_ERROR on success.
+ */
+struct apploader_load_app_req {
+    uint64_t package_size;
+} __packed;
+
+/**
+ * apploader_resp - Common header for all apploader responses
+ * @hdr - header with command value.
+ * @error - error code returned by peer; one of &enum apploader_error values.
+ *
+ * This structure is followed by the response-specific payload, if the command
+ * has one.
+ */
+struct apploader_resp {
+    struct apploader_header hdr;
+    uint32_t error;
+} __packed;
diff --git a/trusty/apploader/fuzz/Android.bp b/trusty/apploader/fuzz/Android.bp
new file mode 100644
index 0000000..e37dab1
--- /dev/null
+++ b/trusty/apploader/fuzz/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Fuzz Trusty IPC messages sent to apploader.
+cc_fuzz {
+    name: "trusty_apploader_tipc_fuzzer",
+    defaults: ["trusty_fuzzer_defaults"],
+    srcs: [":trusty_tipc_fuzzer"],
+    cflags: [
+        "-DTRUSTY_APP_PORT=\"com.android.trusty.apploader\"",
+        "-DTRUSTY_APP_UUID=\"081ba88f-f1ee-452e-b5e8-a7e9ef173a97\"",
+        "-DTRUSTY_APP_FILENAME=\"apploader.syms.elf\"",
+    ]
+}
+
+// Fuzz app package sent to apploader.
+cc_fuzz {
+    name: "trusty_apploader_app_fuzzer",
+    defaults: ["trusty_fuzzer_defaults"],
+    srcs: ["app_fuzzer.cpp"],
+    include_dirs: ["system/core/trusty/apploader"],
+    shared_libs: [
+        "libdmabufheap",
+    ],
+}
diff --git a/trusty/apploader/fuzz/app_fuzzer.cpp b/trusty/apploader/fuzz/app_fuzzer.cpp
new file mode 100644
index 0000000..aa0caca
--- /dev/null
+++ b/trusty/apploader/fuzz/app_fuzzer.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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 <BufferAllocator/BufferAllocator.h>
+#include <android-base/unique_fd.h>
+#include <apploader_ipc.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <trusty/coverage/coverage.h>
+#include <trusty/fuzz/counters.h>
+#include <trusty/fuzz/utils.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+#include <iostream>
+
+using android::base::unique_fd;
+using android::trusty::coverage::CoverageRecord;
+using android::trusty::fuzz::ExtraCounters;
+using android::trusty::fuzz::TrustyApp;
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define APPLOADER_PORT "com.android.trusty.apploader"
+#define APPLOADER_MODULE_NAME "apploader.syms.elf"
+
+/* Apploader TA's UUID is 081ba88f-f1ee-452e-b5e8-a7e9ef173a97 */
+static struct uuid apploader_uuid = {
+        0x081ba88f,
+        0xf1ee,
+        0x452e,
+        {0xb5, 0xe8, 0xa7, 0xe9, 0xef, 0x17, 0x3a, 0x97},
+};
+
+static inline uintptr_t RoundPageUp(uintptr_t val) {
+    return (val + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+}
+
+static bool SendLoadMsg(int chan, int dma_buf, size_t dma_buf_size) {
+    apploader_header hdr = {
+            .cmd = APPLOADER_CMD_LOAD_APPLICATION,
+    };
+    apploader_load_app_req req = {
+            .package_size = static_cast<uint64_t>(dma_buf_size),
+    };
+    iovec iov[] = {
+            {
+                    .iov_base = &hdr,
+                    .iov_len = sizeof(hdr),
+            },
+            {
+                    .iov_base = &req,
+                    .iov_len = sizeof(req),
+            },
+    };
+    trusty_shm shm = {
+            .fd = dma_buf,
+            .transfer = TRUSTY_SHARE,
+    };
+
+    int rc = tipc_send(chan, iov, 2, &shm, 1);
+    if (rc != static_cast<int>(sizeof(hdr) + sizeof(req))) {
+        std::cerr << "Failed to send request" << std::endl;
+        return false;
+    }
+
+    apploader_resp resp;
+    rc = read(chan, &resp, sizeof(resp));
+    if (rc != static_cast<int>(sizeof(resp))) {
+        std::cerr << "Failed to receive response" << std::endl;
+        return false;
+    }
+
+    return true;
+}
+
+static CoverageRecord record(TIPC_DEV, &apploader_uuid, APPLOADER_MODULE_NAME);
+
+extern "C" int LLVMFuzzerInitialize(int* /* argc */, char*** /* argv */) {
+    auto ret = record.Open();
+    if (!ret.ok()) {
+        std::cerr << ret.error() << std::endl;
+        exit(-1);
+    }
+    return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    ExtraCounters counters(&record);
+    counters.Reset();
+
+    android::trusty::fuzz::TrustyApp ta(TIPC_DEV, APPLOADER_PORT);
+    auto ret = ta.Connect();
+    if (!ret.ok()) {
+        std::cerr << ret.error() << std::endl;
+        android::trusty::fuzz::Abort();
+    }
+
+    uint64_t shm_len = size ? RoundPageUp(size) : PAGE_SIZE;
+    BufferAllocator alloc;
+    unique_fd dma_buf(alloc.Alloc(kDmabufSystemHeapName, shm_len));
+    if (dma_buf < 0) {
+        std::cerr << "Failed to create dmabuf of size: " << shm_len << std::endl;
+        android::trusty::fuzz::Abort();
+    }
+
+    void* shm_base = mmap(0, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, dma_buf, 0);
+    if (shm_base == MAP_FAILED) {
+        std::cerr << "Failed to mmap() dmabuf" << std::endl;
+        android::trusty::fuzz::Abort();
+    }
+
+    memcpy(shm_base, data, size);
+
+    bool success = SendLoadMsg(*ta.GetRawFd(), dma_buf, shm_len);
+    if (!success) {
+        std::cerr << "Failed to send load message" << std::endl;
+        android::trusty::fuzz::Abort();
+    }
+
+    munmap(shm_base, shm_len);
+    return 0;
+}
diff --git a/trusty/confirmationui/Android.bp b/trusty/confirmationui/Android.bp
index 60e0e71..0922415 100644
--- a/trusty/confirmationui/Android.bp
+++ b/trusty/confirmationui/Android.bp
@@ -19,6 +19,10 @@
 // to only building on ARM if they include assembly. Individual makefiles
 // are responsible for having their own logic, for fine-grained control.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "android.hardware.confirmationui@1.0-service.trusty",
     relative_install_path: "hw",
@@ -54,6 +58,7 @@
         "android.hardware.confirmationui@1.0",
         "android.hardware.keymaster@4.0",
         "libbase",
+        "libdmabufheap",
         "libhidlbase",
         "libteeui_hal_support",
         "libtrusty",
@@ -92,4 +97,4 @@
         "-Werror",
         "-DTEEUI_USE_STD_VECTOR",
     ],
-}
\ No newline at end of file
+}
diff --git a/trusty/confirmationui/TrustyApp.cpp b/trusty/confirmationui/TrustyApp.cpp
index e4c68f9..cee8655 100644
--- a/trusty/confirmationui/TrustyApp.cpp
+++ b/trusty/confirmationui/TrustyApp.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020, The Android Open Source Project
+ * Copyright 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.
@@ -16,141 +16,152 @@
 
 #include "TrustyApp.h"
 
+#include <BufferAllocator/BufferAllocator.h>
 #include <android-base/logging.h>
+#include <sys/mman.h>
 #include <sys/uio.h>
 #include <trusty/tipc.h>
 
+#define countof(arr) (sizeof(arr) / sizeof(arr[0]))
+
 namespace android {
 namespace trusty {
+namespace confirmationui {
 
-// 0x1000 is the message buffer size but we need to leave some space for a protocol header.
-// This assures that packets can always be read/written in one read/write operation.
-static constexpr const uint32_t kPacketSize = 0x1000 - 32;
+using ::android::base::unique_fd;
 
-enum class PacketType : uint32_t {
-    SND,
-    RCV,
-    ACK,
-};
-
-struct PacketHeader {
-    PacketType type;
-    uint32_t remaining;
-};
-
-const char* toString(PacketType t) {
-    switch (t) {
-    case PacketType::SND:
-        return "SND";
-    case PacketType::RCV:
-        return "RCV";
-    case PacketType::ACK:
-        return "ACK";
-    default:
-        return "UNKNOWN";
-    }
+static inline uintptr_t RoundPageUp(uintptr_t val) {
+    return (val + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
 }
 
-static constexpr const uint32_t kHeaderSize = sizeof(PacketHeader);
-static constexpr const uint32_t kPayloadSize = kPacketSize - kHeaderSize;
+ssize_t TrustyApp::TrustyRpc(const uint8_t* obegin, const uint8_t* oend, uint8_t* ibegin,
+                             uint8_t* iend) {
+    uint32_t olen = oend - obegin;
 
-ssize_t TrustyRpc(int handle, const uint8_t* obegin, const uint8_t* oend, uint8_t* ibegin,
-                  uint8_t* iend) {
-    while (obegin != oend) {
-        PacketHeader header = {
-            .type = PacketType::SND,
-            .remaining = uint32_t(oend - obegin),
-        };
-        uint32_t body_size = std::min(kPayloadSize, header.remaining);
-        iovec iov[] = {
-            {
-                .iov_base = &header,
-                .iov_len = kHeaderSize,
-            },
-            {
-                .iov_base = const_cast<uint8_t*>(obegin),
-                .iov_len = body_size,
-            },
-        };
-        int rc = writev(handle, iov, 2);
-        if (!rc) {
-            PLOG(ERROR) << "Error sending SND message. " << rc;
-            return rc;
-        }
-
-        obegin += body_size;
-
-        rc = read(handle, &header, kHeaderSize);
-        if (!rc) {
-            PLOG(ERROR) << "Error reading ACK. " << rc;
-            return rc;
-        }
-
-        if (header.type != PacketType::ACK || header.remaining != oend - obegin) {
-            LOG(ERROR) << "malformed ACK";
-            return -1;
-        }
+    if (olen > shm_len_) {
+        LOG(ERROR) << AT << "request message too long to fit in shared memory";
+        return -1;
     }
 
-    ssize_t remaining = 0;
-    auto begin = ibegin;
-    do {
-        PacketHeader header = {
-            .type = PacketType::RCV,
-            .remaining = 0,
-        };
+    memcpy(shm_base_, obegin, olen);
 
-        iovec iov[] = {
-            {
-                .iov_base = &header,
-                .iov_len = kHeaderSize,
-            },
-            {
-                .iov_base = begin,
-                .iov_len = uint32_t(iend - begin),
-            },
-        };
+    confirmationui_hdr hdr = {
+        .cmd = CONFIRMATIONUI_CMD_MSG,
+    };
+    confirmationui_msg_args args = {
+        .msg_len = olen,
+    };
+    iovec iov[] = {
+        {
+            .iov_base = &hdr,
+            .iov_len = sizeof(hdr),
+        },
+        {
+            .iov_base = &args,
+            .iov_len = sizeof(args),
+        },
+    };
 
-        ssize_t rc = writev(handle, iov, 1);
-        if (!rc) {
-            PLOG(ERROR) << "Error sending RCV message. " << rc;
-            return rc;
-        }
+    int rc = tipc_send(handle_, iov, countof(iov), NULL, 0);
+    if (rc != static_cast<int>(sizeof(hdr) + sizeof(args))) {
+        LOG(ERROR) << AT << "failed to send MSG request";
+        return -1;
+    }
 
-        rc = readv(handle, iov, 2);
-        if (rc < 0) {
-            PLOG(ERROR) << "Error reading response. " << rc;
-            return rc;
-        }
+    rc = readv(handle_, iov, countof(iov));
+    if (rc != static_cast<int>(sizeof(hdr) + sizeof(args))) {
+        LOG(ERROR) << AT << "failed to receive MSG response";
+        return -1;
+    }
 
-        uint32_t body_size = std::min(kPayloadSize, header.remaining);
-        if (body_size != rc - kHeaderSize) {
-            LOG(ERROR) << "Unexpected amount of data: " << rc;
-            return -1;
-        }
+    if (hdr.cmd != (CONFIRMATIONUI_CMD_MSG | CONFIRMATIONUI_RESP_BIT)) {
+        LOG(ERROR) << AT << "unknown response command: " << hdr.cmd;
+        return -1;
+    }
 
-        remaining = header.remaining - body_size;
-        begin += body_size;
-    } while (remaining);
+    uint32_t ilen = iend - ibegin;
+    if (args.msg_len > ilen) {
+        LOG(ERROR) << AT << "response message too long to fit in return buffer";
+        return -1;
+    }
 
-    return begin - ibegin;
+    memcpy(ibegin, shm_base_, args.msg_len);
+
+    return args.msg_len;
 }
 
 TrustyApp::TrustyApp(const std::string& path, const std::string& appname)
     : handle_(kInvalidHandle) {
-    handle_ = tipc_connect(path.c_str(), appname.c_str());
-    if (handle_ == kInvalidHandle) {
+    unique_fd tipc_handle(tipc_connect(path.c_str(), appname.c_str()));
+    if (tipc_handle < 0) {
         LOG(ERROR) << AT << "failed to connect to Trusty TA \"" << appname << "\" using dev:"
                    << "\"" << path << "\"";
+        return;
     }
+
+    uint32_t shm_len = RoundPageUp(CONFIRMATIONUI_MAX_MSG_SIZE);
+    BufferAllocator allocator;
+    unique_fd dma_buf(allocator.Alloc("system", shm_len));
+    if (dma_buf < 0) {
+        LOG(ERROR) << AT << "failed to allocate shared memory buffer";
+        return;
+    }
+
+    confirmationui_hdr hdr = {
+        .cmd = CONFIRMATIONUI_CMD_INIT,
+    };
+    confirmationui_init_req args = {
+        .shm_len = shm_len,
+    };
+    iovec iov[] = {
+        {
+            .iov_base = &hdr,
+            .iov_len = sizeof(hdr),
+        },
+        {
+            .iov_base = &args,
+            .iov_len = sizeof(args),
+        },
+    };
+    trusty_shm shm = {
+        .fd = dma_buf,
+        .transfer = TRUSTY_SHARE,
+    };
+
+    int rc = tipc_send(tipc_handle, iov, 2, &shm, 1);
+    if (rc != static_cast<int>(sizeof(hdr) + sizeof(args))) {
+        LOG(ERROR) << AT << "failed to send INIT request";
+        return;
+    }
+
+    rc = read(tipc_handle, &hdr, sizeof(hdr));
+    if (rc != static_cast<int>(sizeof(hdr))) {
+        LOG(ERROR) << AT << "failed to receive INIT response";
+        return;
+    }
+
+    if (hdr.cmd != (CONFIRMATIONUI_CMD_INIT | CONFIRMATIONUI_RESP_BIT)) {
+        LOG(ERROR) << AT << "unknown response command: " << hdr.cmd;
+        return;
+    }
+
+    void* shm_base = mmap(0, shm_len, PROT_READ | PROT_WRITE, MAP_SHARED, dma_buf, 0);
+    if (shm_base == MAP_FAILED) {
+        LOG(ERROR) << AT << "failed to mmap() shared memory buffer";
+        return;
+    }
+
+    handle_ = std::move(tipc_handle);
+    shm_base_ = shm_base;
+    shm_len_ = shm_len;
+
     LOG(INFO) << AT << "succeeded to connect to Trusty TA \"" << appname << "\"";
 }
+
 TrustyApp::~TrustyApp() {
-    if (handle_ != kInvalidHandle) {
-        tipc_close(handle_);
-    }
     LOG(INFO) << "Done shutting down TrustyApp";
 }
 
+}  // namespace confirmationui
 }  // namespace trusty
 }  // namespace android
diff --git a/trusty/confirmationui/TrustyApp.h b/trusty/confirmationui/TrustyApp.h
index 05a25f6..fb57828 100644
--- a/trusty/confirmationui/TrustyApp.h
+++ b/trusty/confirmationui/TrustyApp.h
@@ -16,7 +16,10 @@
 
 #pragma once
 
+#include <TrustyIpc.h>
+
 #include <android-base/logging.h>
+#include <android-base/unique_fd.h>
 #include <errno.h>
 #include <poll.h>
 #include <stdio.h>
@@ -38,6 +41,7 @@
 
 namespace android {
 namespace trusty {
+namespace confirmationui {
 
 using ::teeui::Message;
 using ::teeui::msg2tuple_t;
@@ -60,19 +64,11 @@
     MSG_TOO_LONG = -2,
 };
 
-/*
- * There is a hard limitation of 0x1800 bytes for the to-be-signed message size. The protocol
- * overhead is limited, so that 0x2000 is a buffer size that will be sufficient in any benign
- * mode of operation.
- */
-static constexpr const size_t kSendBufferSize = 0x2000;
-
-ssize_t TrustyRpc(int handle, const uint8_t* obegin, const uint8_t* oend, uint8_t* ibegin,
-                  uint8_t* iend);
-
 class TrustyApp {
   private:
-    int handle_;
+    android::base::unique_fd handle_;
+    void* shm_base_;
+    size_t shm_len_;
     static constexpr const int kInvalidHandle = -1;
     /*
      * This mutex serializes communication with the trusted app, not handle_.
@@ -84,6 +80,8 @@
     TrustyApp(const std::string& path, const std::string& appname);
     ~TrustyApp();
 
+    ssize_t TrustyRpc(const uint8_t* obegin, const uint8_t* oend, uint8_t* ibegin, uint8_t* iend);
+
     template <typename Request, typename Response, typename... T>
     std::tuple<TrustyAppError, msg2tuple_t<Response>> issueCmd(const T&... args) {
         std::lock_guard<std::mutex> lock(mutex_);
@@ -93,7 +91,7 @@
             return {TrustyAppError::ERROR, {}};
         }
 
-        uint8_t buffer[kSendBufferSize];
+        uint8_t buffer[CONFIRMATIONUI_MAX_MSG_SIZE];
         WriteStream out(buffer);
 
         out = write(Request(), out, args...);
@@ -102,8 +100,8 @@
             return {TrustyAppError::MSG_TOO_LONG, {}};
         }
 
-        auto rc = TrustyRpc(handle_, &buffer[0], const_cast<const uint8_t*>(out.pos()), &buffer[0],
-                            &buffer[kSendBufferSize]);
+        auto rc = TrustyRpc(&buffer[0], const_cast<const uint8_t*>(out.pos()), &buffer[0],
+                            &buffer[CONFIRMATIONUI_MAX_MSG_SIZE]);
         if (rc < 0) return {TrustyAppError::ERROR, {}};
 
         ReadStream in(&buffer[0], rc);
@@ -125,7 +123,7 @@
             return TrustyAppError::ERROR;
         }
 
-        uint8_t buffer[kSendBufferSize];
+        uint8_t buffer[CONFIRMATIONUI_MAX_MSG_SIZE];
         WriteStream out(buffer);
 
         out = write(Request(), out, args...);
@@ -134,8 +132,8 @@
             return TrustyAppError::MSG_TOO_LONG;
         }
 
-        auto rc = TrustyRpc(handle_, &buffer[0], const_cast<const uint8_t*>(out.pos()), &buffer[0],
-                            &buffer[kSendBufferSize]);
+        auto rc = TrustyRpc(&buffer[0], const_cast<const uint8_t*>(out.pos()), &buffer[0],
+                            &buffer[CONFIRMATIONUI_MAX_MSG_SIZE]);
         if (rc < 0) {
             LOG(ERROR) << "send command failed: " << strerror(errno) << " (" << errno << ")";
             return TrustyAppError::ERROR;
@@ -151,5 +149,6 @@
     operator bool() const { return handle_ != kInvalidHandle; }
 };
 
+}  // namespace confirmationui
 }  // namespace trusty
 }  // namespace android
diff --git a/trusty/confirmationui/TrustyConfirmationUI.cpp b/trusty/confirmationui/TrustyConfirmationUI.cpp
index 6b25893..c6625e0 100644
--- a/trusty/confirmationui/TrustyConfirmationUI.cpp
+++ b/trusty/confirmationui/TrustyConfirmationUI.cpp
@@ -50,7 +50,7 @@
 
 using namespace secure_input;
 
-using ::android::trusty::TrustyAppError;
+using ::android::trusty::confirmationui::TrustyAppError;
 
 using ::teeui::AbortMsg;
 using ::teeui::DeliverTestCommandMessage;
@@ -71,7 +71,7 @@
 using TeeuiRc = ::teeui::ResponseCode;
 
 constexpr const char kTrustyDeviceName[] = "/dev/trusty-ipc-dev0";
-constexpr const char kConfirmationuiAppName[] = "com.android.trusty.confirmationui";
+constexpr const char kConfirmationuiAppName[] = CONFIRMATIONUI_PORT;
 
 namespace {
 
diff --git a/trusty/confirmationui/TrustyConfirmationUI.h b/trusty/confirmationui/TrustyConfirmationUI.h
index 3a7c7ef..0bd703c 100644
--- a/trusty/confirmationui/TrustyConfirmationUI.h
+++ b/trusty/confirmationui/TrustyConfirmationUI.h
@@ -43,7 +43,7 @@
 using ::android::hardware::Return;
 using ::android::hardware::Void;
 
-using ::android::trusty::TrustyApp;
+using ::android::trusty::confirmationui::TrustyApp;
 
 class TrustyConfirmationUI : public IConfirmationUI {
   public:
diff --git a/trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.rc b/trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.rc
index dc7a03b..3ba6fc0 100644
--- a/trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.rc
+++ b/trusty/confirmationui/android.hardware.confirmationui@1.0-service.trusty.rc
@@ -1,4 +1,4 @@
 service confirmationui-1-0 /vendor/bin/hw/android.hardware.confirmationui@1.0-service.trusty
     class hal
-    user nobody
-    group drmrpc input
+    user system
+    group drmrpc input system
diff --git a/trusty/confirmationui/fuzz/Android.bp b/trusty/confirmationui/fuzz/Android.bp
new file mode 100644
index 0000000..ba57191
--- /dev/null
+++ b/trusty/confirmationui/fuzz/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_fuzz {
+    name: "trusty_confirmationui_tipc_fuzzer",
+    defaults: ["trusty_fuzzer_defaults"],
+    srcs: [":trusty_tipc_fuzzer"],
+    cflags: [
+        "-DTRUSTY_APP_PORT=\"com.android.trusty.confirmationui\"",
+        "-DTRUSTY_APP_UUID=\"7dee2364-c036-425b-b086-df0f6c233c1b\"",
+        "-DTRUSTY_APP_FILENAME=\"confirmationui.syms.elf\"",
+    ],
+
+}
+
+cc_fuzz {
+    name: "trusty_confirmationui_msg_fuzzer",
+    defaults: ["trusty_fuzzer_defaults"],
+    srcs: ["msg_fuzzer.cpp"],
+    include_dirs: ["system/core/trusty/confirmationui/include"],
+    shared_libs: [
+        "libdmabufheap",
+    ],
+
+    // The initial corpus for this fuzzer was derived by dumping messages from/to
+    // HAL to/from TA triggered by VtsHalConfirmationUIV1_0TargetTest.
+    corpus: ["msg_corpus/*"],
+}
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-0AD0Mc b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-0AD0Mc
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-0AD0Mc
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-1b1UIl b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-1b1UIl
new file mode 100644
index 0000000..c8741fb
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-1b1UIl
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-3hmWyl b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-3hmWyl
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-3hmWyl
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-7FNOdd b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-7FNOdd
new file mode 100644
index 0000000..556828d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-7FNOdd
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-7T30a0 b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-7T30a0
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-7T30a0
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-86EumR b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-86EumR
new file mode 100644
index 0000000..556828d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-86EumR
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-89b64b b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-89b64b
new file mode 100644
index 0000000..1682427
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-89b64b
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-8UVUCK b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-8UVUCK
new file mode 100644
index 0000000..556828d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-8UVUCK
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-BSmqJ0 b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-BSmqJ0
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-BSmqJ0
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-BdUGLb b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-BdUGLb
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-BdUGLb
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-D2ENNi b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-D2ENNi
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-D2ENNi
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-EwBsPi b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-EwBsPi
new file mode 100644
index 0000000..d48e5a1
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-EwBsPi
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-HjE2Ko b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-HjE2Ko
new file mode 100644
index 0000000..556828d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-HjE2Ko
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-J5OABY b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-J5OABY
new file mode 100644
index 0000000..556828d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-J5OABY
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-LUVKQn b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-LUVKQn
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-LUVKQn
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-MdY9ZS b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-MdY9ZS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-MdY9ZS
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-NZ8yUq b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-NZ8yUq
new file mode 100644
index 0000000..6f72ad5
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-NZ8yUq
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-OP4Vff b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-OP4Vff
new file mode 100644
index 0000000..64a159c
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-OP4Vff
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-OizTST b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-OizTST
new file mode 100644
index 0000000..556828d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-OizTST
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-QTsc3y b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-QTsc3y
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-QTsc3y
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-S055ei b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-S055ei
new file mode 100644
index 0000000..556828d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-S055ei
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-VDguJL b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-VDguJL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-VDguJL
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-ZjDqjf b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-ZjDqjf
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-ZjDqjf
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-bMNGfb b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-bMNGfb
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-bMNGfb
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-bm0GEm b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-bm0GEm
new file mode 100644
index 0000000..556828d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-bm0GEm
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-cT2nt8 b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-cT2nt8
new file mode 100644
index 0000000..556828d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-cT2nt8
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-e1NLbb b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-e1NLbb
new file mode 100644
index 0000000..64a159c
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-e1NLbb
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-eOCb7t b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-eOCb7t
new file mode 100644
index 0000000..64a159c
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-eOCb7t
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-h7Gpzu b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-h7Gpzu
new file mode 100644
index 0000000..556828d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-h7Gpzu
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-ikJlIo b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-ikJlIo
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-ikJlIo
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-kxugwp b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-kxugwp
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-kxugwp
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-mY8uM5 b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-mY8uM5
new file mode 100644
index 0000000..556828d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-mY8uM5
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-nuYOin b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-nuYOin
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-nuYOin
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-obk0rP b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-obk0rP
new file mode 100644
index 0000000..8be96c5
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-obk0rP
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-vg2hAB b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-vg2hAB
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-vg2hAB
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-ysk3Rj b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-ysk3Rj
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-recv-ysk3Rj
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-2upXHa b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-2upXHa
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-2upXHa
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-3n7SWz b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-3n7SWz
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-3n7SWz
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-5SZG4U b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-5SZG4U
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-5SZG4U
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-8uL1hT b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-8uL1hT
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-8uL1hT
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-Anu8LZ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-Anu8LZ
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-Anu8LZ
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-BFP3vG b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-BFP3vG
new file mode 100644
index 0000000..b944d94
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-BFP3vG
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-BjxIpX b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-BjxIpX
new file mode 100644
index 0000000..1d9374d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-BjxIpX
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-DBzfWz b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-DBzfWz
new file mode 100644
index 0000000..b3be8cd
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-DBzfWz
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-GPOMKC b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-GPOMKC
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-GPOMKC
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-GWcpFn b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-GWcpFn
new file mode 100644
index 0000000..4190adf
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-GWcpFn
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-HkRYSS b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-HkRYSS
new file mode 100644
index 0000000..1d9374d
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-HkRYSS
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-LAyw30 b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-LAyw30
new file mode 100644
index 0000000..38e3fca
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-LAyw30
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-MtGRnC b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-MtGRnC
new file mode 100644
index 0000000..4190adf
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-MtGRnC
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-PpfYNn b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-PpfYNn
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-PpfYNn
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-SVKqZi b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-SVKqZi
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-SVKqZi
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-Suxofv b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-Suxofv
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-Suxofv
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-UQPTAG b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-UQPTAG
new file mode 100644
index 0000000..4190adf
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-UQPTAG
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-Up2pbn b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-Up2pbn
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-Up2pbn
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ZjgVzs b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ZjgVzs
new file mode 100644
index 0000000..cbfd07a
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ZjgVzs
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ZuQuBC b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ZuQuBC
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ZuQuBC
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-bWlzZp b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-bWlzZp
new file mode 100644
index 0000000..ecaec12
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-bWlzZp
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-dPozfE b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-dPozfE
new file mode 100644
index 0000000..58b1526
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-dPozfE
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-e952U6 b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-e952U6
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-e952U6
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-f7ly1r b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-f7ly1r
new file mode 100644
index 0000000..af570ea
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-f7ly1r
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-hme7P0 b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-hme7P0
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-hme7P0
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-k7J5LL b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-k7J5LL
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-k7J5LL
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-rUtYXs b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-rUtYXs
new file mode 100644
index 0000000..e4b99fb
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-rUtYXs
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-sq5ang b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-sq5ang
new file mode 100644
index 0000000..d114956
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-sq5ang
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-uOtedb b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-uOtedb
new file mode 100644
index 0000000..6caf7dd
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-uOtedb
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-vGoOUt b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-vGoOUt
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-vGoOUt
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-vqAG14 b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-vqAG14
new file mode 100644
index 0000000..ecaec12
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-vqAG14
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-xKDdTw b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-xKDdTw
new file mode 100644
index 0000000..36445d9
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-xKDdTw
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-xT4sJC b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-xT4sJC
new file mode 100644
index 0000000..f6c6dcf
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-xT4sJC
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ypshr5 b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ypshr5
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ypshr5
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ypzCDH b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ypzCDH
new file mode 100644
index 0000000..d6ba1fc
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-ypzCDH
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-zZNPRC b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-zZNPRC
new file mode 100644
index 0000000..7392034
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_corpus/confirmationui-send-zZNPRC
Binary files differ
diff --git a/trusty/confirmationui/fuzz/msg_fuzzer.cpp b/trusty/confirmationui/fuzz/msg_fuzzer.cpp
new file mode 100644
index 0000000..8e4443c
--- /dev/null
+++ b/trusty/confirmationui/fuzz/msg_fuzzer.cpp
@@ -0,0 +1,173 @@
+/*
+ * 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 <BufferAllocator/BufferAllocator.h>
+#include <TrustyIpc.h>
+#include <iostream>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <trusty/coverage/coverage.h>
+#include <trusty/fuzz/counters.h>
+#include <trusty/fuzz/utils.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+
+using android::trusty::coverage::CoverageRecord;
+using android::trusty::fuzz::ExtraCounters;
+using android::trusty::fuzz::TrustyApp;
+
+#define countof(arr) (sizeof(arr) / sizeof(arr[0]))
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define CONFIRMATIONUI_PORT "com.android.trusty.confirmationui"
+#define CONFIRMATIONUI_MODULE_NAME "confirmationui.syms.elf"
+
+/* A request to render to screen may take a while. */
+const size_t kTimeoutSeconds = 30;
+
+/* ConfirmationUI TA's UUID is 7dee2364-c036-425b-b086-df0f6c233c1b */
+static struct uuid confirmationui_uuid = {
+    0x7dee2364,
+    0xc036,
+    0x425b,
+    {0xb0, 0x86, 0xdf, 0x0f, 0x6c, 0x23, 0x3c, 0x1b},
+};
+
+static CoverageRecord record(TIPC_DEV, &confirmationui_uuid, CONFIRMATIONUI_MODULE_NAME);
+
+static android::base::unique_fd dma_buf;
+static void* shm_base;
+
+extern "C" int LLVMFuzzerInitialize(int* /* argc */, char*** /* argv */) {
+    auto ret = record.Open();
+    if (!ret.ok()) {
+        std::cerr << ret.error() << std::endl;
+        exit(-1);
+    }
+
+    BufferAllocator allocator;
+    dma_buf.reset(allocator.Alloc(kDmabufSystemHeapName, CONFIRMATIONUI_MAX_MSG_SIZE));
+    if (dma_buf < 0) {
+        std::cerr << "Failed to allocate dma_buf" << std::endl;
+        exit(-1);
+    }
+
+    shm_base = mmap(0, CONFIRMATIONUI_MAX_MSG_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, dma_buf, 0);
+    if (shm_base == MAP_FAILED) {
+        std::cerr << "Failed to mmap() dma_buf" << std::endl;
+        exit(-1);
+    }
+
+    return 0;
+}
+
+static bool Init(int chan, int dma_buf) {
+    confirmationui_hdr hdr = {
+        .cmd = CONFIRMATIONUI_CMD_INIT,
+    };
+    confirmationui_init_req args = {
+        .shm_len = CONFIRMATIONUI_MAX_MSG_SIZE,
+    };
+    iovec iov[] = {
+        {
+            .iov_base = &hdr,
+            .iov_len = sizeof(hdr),
+        },
+        {
+            .iov_base = &args,
+            .iov_len = sizeof(args),
+        },
+    };
+    trusty_shm shm = {
+        .fd = dma_buf,
+        .transfer = TRUSTY_SHARE,
+    };
+
+    int rc = tipc_send(chan, iov, countof(iov), &shm, 1);
+    if (rc != static_cast<int>(sizeof(hdr) + sizeof(args))) {
+        return false;
+    }
+
+    rc = read(chan, &hdr, sizeof(hdr));
+    if (rc != static_cast<int>(sizeof(hdr))) {
+        return false;
+    }
+
+    return true;
+}
+
+static bool Msg(int chan, const uint8_t* data, size_t size) {
+    confirmationui_hdr hdr = {
+        .cmd = CONFIRMATIONUI_CMD_MSG,
+    };
+    confirmationui_msg_args args = {
+        .msg_len = static_cast<uint32_t>(size),
+    };
+    iovec iov[] = {
+        {
+            .iov_base = &hdr,
+            .iov_len = sizeof(hdr),
+        },
+        {
+            .iov_base = &args,
+            .iov_len = sizeof(args),
+        },
+    };
+
+    memset(shm_base, 0, CONFIRMATIONUI_MAX_MSG_SIZE);
+    memcpy(shm_base, data, size);
+
+    int rc = tipc_send(chan, iov, countof(iov), NULL, 0);
+    if (rc != static_cast<int>(sizeof(hdr) + sizeof(args))) {
+        return false;
+    }
+
+    rc = readv(chan, iov, countof(iov));
+    if (rc != static_cast<int>(sizeof(hdr) + sizeof(args))) {
+        return false;
+    }
+
+    return true;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    ExtraCounters counters(&record);
+    counters.Reset();
+
+    TrustyApp ta(TIPC_DEV, CONFIRMATIONUI_PORT);
+    auto ret = ta.Connect();
+    if (!ret.ok()) {
+        android::trusty::fuzz::Abort();
+    }
+    int chan = *ta.GetRawFd();
+
+    alarm(kTimeoutSeconds);
+    bool success = Init(chan, dma_buf);
+    alarm(0);
+    if (!success) {
+        android::trusty::fuzz::Abort();
+    }
+
+    alarm(kTimeoutSeconds);
+    success = Msg(chan, data, size);
+    alarm(0);
+    if (!success) {
+        android::trusty::fuzz::Abort();
+    }
+
+    return 0;
+}
diff --git a/trusty/confirmationui/include/TrustyIpc.h b/trusty/confirmationui/include/TrustyIpc.h
new file mode 100644
index 0000000..eb764bc
--- /dev/null
+++ b/trusty/confirmationui/include/TrustyIpc.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 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 <stdint.h>
+
+/*
+ * This interface is shared between Android and Trusty. There is a copy in each
+ * repository. They must be kept in sync.
+ */
+
+#define CONFIRMATIONUI_PORT "com.android.trusty.confirmationui"
+
+/**
+ * enum confirmationui_cmd - command identifiers for ConfirmationUI interface
+ * @CONFIRMATIONUI_RESP_BIT:  response bit set as part of response
+ * @CONFIRMATIONUI_REQ_SHIFT: number of bits used by response bit
+ * @CONFIRMATIONUI_CMD_INIT:  command to initialize session
+ * @CONFIRMATIONUI_CMD_MSG:   command to send ConfirmationUI messages
+ */
+enum confirmationui_cmd : uint32_t {
+    CONFIRMATIONUI_RESP_BIT = 1,
+    CONFIRMATIONUI_REQ_SHIFT = 1,
+
+    CONFIRMATIONUI_CMD_INIT = (1 << CONFIRMATIONUI_REQ_SHIFT),
+    CONFIRMATIONUI_CMD_MSG = (2 << CONFIRMATIONUI_REQ_SHIFT),
+};
+
+/**
+ * struct confirmationui_hdr - header for ConfirmationUI messages
+ * @cmd: command identifier
+ *
+ * Note that no messages return a status code. Any error on the server side
+ * results in the connection being closed. So, operations can be assumed to be
+ * successful if they return a response.
+ */
+struct confirmationui_hdr {
+    uint32_t cmd;
+};
+
+/**
+ * struct confirmationui_init_req - arguments for request to initialize a
+ *                                  session
+ * @shm_len: length of memory region being shared
+ *
+ * A handle to a memory region must be sent along with this message. This memory
+ * is send to ConfirmationUI messages.
+ */
+struct confirmationui_init_req {
+    uint32_t shm_len;
+};
+
+/**
+ * struct confirmationui_msg_args - arguments for sending a message
+ * @msg_len: length of message being sent
+ *
+ * Contents of the message are located in the shared memory region that is
+ * established using %CONFIRMATIONUI_CMD_INIT.
+ *
+ * ConfirmationUI messages can travel both ways.
+ */
+struct confirmationui_msg_args {
+    uint32_t msg_len;
+};
+
+#define CONFIRMATIONUI_MAX_MSG_SIZE 0x2000
diff --git a/trusty/coverage/Android.bp b/trusty/coverage/Android.bp
new file mode 100644
index 0000000..0453f3f
--- /dev/null
+++ b/trusty/coverage/Android.bp
@@ -0,0 +1,53 @@
+// Copyright (C) 2020 The Android Open-Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+    name: "libtrusty_coverage",
+    vendor_available: true,
+    srcs: [
+        "coverage.cpp",
+        "uuid.cpp",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+    shared_libs: [
+        "libbase",
+        "libext2_uuid",
+        "liblog",
+        "libdmabufheap",
+        "libtrusty",
+    ],
+}
+
+cc_test {
+    name: "libtrusty_coverage_test",
+    srcs: [
+        "coverage_test.cpp",
+    ],
+    static_libs: [
+        "libtrusty_coverage",
+        "libtrusty",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libdmabufheap",
+    ],
+    require_root: true,
+}
diff --git a/trusty/coverage/coverage.cpp b/trusty/coverage/coverage.cpp
new file mode 100644
index 0000000..3c6b5c5
--- /dev/null
+++ b/trusty/coverage/coverage.cpp
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2020 The Android Open Sourete 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_TAG "coverage"
+
+#include <BufferAllocator/BufferAllocator.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <assert.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/uio.h>
+#include <trusty/coverage/coverage.h>
+#include <trusty/coverage/record.h>
+#include <trusty/coverage/tipc.h>
+#include <trusty/tipc.h>
+#include <iostream>
+
+#define COVERAGE_CLIENT_PORT "com.android.trusty.coverage.client"
+
+namespace android {
+namespace trusty {
+namespace coverage {
+
+using android::base::ErrnoError;
+using android::base::Error;
+using std::string;
+using std::to_string;
+using std::unique_ptr;
+
+static inline uintptr_t RoundPageUp(uintptr_t val) {
+    return (val + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+}
+
+CoverageRecord::CoverageRecord(string tipc_dev, struct uuid* uuid)
+    : tipc_dev_(std::move(tipc_dev)),
+      coverage_srv_fd_(-1),
+      uuid_(*uuid),
+      sancov_filename_(),
+      record_len_(0),
+      shm_(NULL),
+      shm_len_(0) {}
+
+CoverageRecord::CoverageRecord(string tipc_dev, struct uuid* uuid, string module_name)
+    : tipc_dev_(std::move(tipc_dev)),
+      coverage_srv_fd_(-1),
+      uuid_(*uuid),
+      sancov_filename_(module_name + "." + to_string(getpid()) + ".sancov"),
+      record_len_(0),
+      shm_(NULL),
+      shm_len_(0) {}
+
+CoverageRecord::~CoverageRecord() {
+    if (shm_) {
+        if (sancov_filename_) {
+            auto res = SaveSancovFile(*sancov_filename_);
+            if (!res.ok()) {
+                ALOGE("Could not write sancov file for module: %s\n", sancov_filename_->c_str());
+            }
+        }
+
+        munmap((void*)shm_, shm_len_);
+    }
+}
+
+Result<void> CoverageRecord::Rpc(coverage_client_req* req, int req_fd, coverage_client_resp* resp) {
+    int rc;
+
+    if (req_fd < 0) {
+        rc = write(coverage_srv_fd_, req, sizeof(*req));
+    } else {
+        iovec iov = {
+                .iov_base = req,
+                .iov_len = sizeof(*req),
+        };
+
+        trusty_shm shm = {
+                .fd = req_fd,
+                .transfer = TRUSTY_SHARE,
+        };
+
+        rc = tipc_send(coverage_srv_fd_, &iov, 1, &shm, 1);
+    }
+
+    if (rc != (int)sizeof(*req)) {
+        return ErrnoError() << "failed to send request to coverage server: ";
+    }
+
+    rc = read(coverage_srv_fd_, resp, sizeof(*resp));
+    if (rc != (int)sizeof(*resp)) {
+        return ErrnoError() << "failed to read reply from coverage server: ";
+    }
+
+    if (resp->hdr.cmd != (req->hdr.cmd | COVERAGE_CLIENT_CMD_RESP_BIT)) {
+        return ErrnoError() << "unknown response cmd: " << resp->hdr.cmd;
+    }
+
+    return {};
+}
+
+Result<void> CoverageRecord::Open() {
+    coverage_client_req req;
+    coverage_client_resp resp;
+
+    if (shm_) {
+        return {}; /* already initialized */
+    }
+
+    int fd = tipc_connect(tipc_dev_.c_str(), COVERAGE_CLIENT_PORT);
+    if (fd < 0) {
+        // Don't error out to support fuzzing builds without coverage, e.g. for repros.
+        std::cerr << "WARNING!!! Failed to connect to Trusty coverarge server." << std::endl;
+        return {};
+    }
+    coverage_srv_fd_.reset(fd);
+
+    req.hdr.cmd = COVERAGE_CLIENT_CMD_OPEN;
+    req.open_args.uuid = uuid_;
+    auto ret = Rpc(&req, -1, &resp);
+    if (!ret.ok()) {
+        return Error() << "failed to open coverage client: " << ret.error();
+    }
+    record_len_ = resp.open_args.record_len;
+    shm_len_ = RoundPageUp(record_len_);
+
+    BufferAllocator allocator;
+
+    fd = allocator.Alloc("system", shm_len_);
+    if (fd < 0) {
+        return ErrnoError() << "failed to create dmabuf of size " << shm_len_
+                            << " err code: " << fd;
+    }
+    unique_fd dma_buf(fd);
+
+    void* shm = mmap(0, shm_len_, PROT_READ | PROT_WRITE, MAP_SHARED, dma_buf, 0);
+    if (shm == MAP_FAILED) {
+        return ErrnoError() << "failed to map memfd: ";
+    }
+
+    req.hdr.cmd = COVERAGE_CLIENT_CMD_SHARE_RECORD;
+    req.share_record_args.shm_len = shm_len_;
+    ret = Rpc(&req, dma_buf, &resp);
+    if (!ret.ok()) {
+        return Error() << "failed to send shared memory: " << ret.error();
+    }
+
+    shm_ = shm;
+    return {};
+}
+
+bool CoverageRecord::IsOpen() {
+    return shm_;
+}
+
+void CoverageRecord::ResetFullRecord() {
+    auto header_region = GetRegionBounds(COV_START);
+    if (!header_region.ok()) {
+        // If the header cannot be parsed, we can't reset the proper region yet.
+        return;
+    }
+
+    for (size_t i = header_region->second; i < shm_len_; i++) {
+        *((volatile uint8_t*)shm_ + i) = 0;
+    }
+}
+
+void CoverageRecord::ResetCounts() {
+    volatile uint8_t* begin = nullptr;
+    volatile uint8_t* end = nullptr;
+    GetRawCounts(&begin, &end);
+
+    for (volatile uint8_t* x = begin; x < end; x++) {
+        *x = 0;
+    }
+}
+
+void CoverageRecord::ResetPCs() {
+    volatile uintptr_t* begin = nullptr;
+    volatile uintptr_t* end = nullptr;
+    GetRawPCs(&begin, &end);
+
+    for (volatile uintptr_t* x = begin; x < end; x++) {
+        *x = 0;
+    }
+}
+
+Result<std::pair<size_t, size_t>> CoverageRecord::GetRegionBounds(uint32_t region_type) {
+    assert(shm_);
+
+    auto header = (volatile struct coverage_record_header*)shm_;
+
+    if (header->type != COV_START) {
+        return Error() << "Header not yet valid";
+    }
+
+    for (++header; header->type != COV_TOTAL_LENGTH; ++header) {
+        if (header->type == region_type) {
+            // Coverage record must end with a COV_TOTAL_LENGTH header entry, so
+            // it is always safe to read the next entry since we don't iterate
+            // over the COV_TOTAL_LENGTH entry.
+            return {{header->offset, (header + 1)->offset}};
+        }
+    }
+
+    return Error() << "Could not find coverage region type: " << region_type;
+}
+
+void CoverageRecord::GetRawData(volatile void** begin, volatile void** end) {
+    assert(shm_);
+
+    *begin = shm_;
+    *end = (uint8_t*)(*begin) + record_len_;
+}
+
+void CoverageRecord::GetRawCounts(volatile uint8_t** begin, volatile uint8_t** end) {
+    auto region = GetRegionBounds(COV_8BIT_COUNTERS);
+    if (!region.ok()) {
+        *begin = 0;
+        *end = 0;
+        return;
+    }
+
+    assert(region->second <= record_len_);
+
+    *begin = (volatile uint8_t*)shm_ + region->first;
+    *end = (volatile uint8_t*)shm_ + region->second;
+}
+
+void CoverageRecord::GetRawPCs(volatile uintptr_t** begin, volatile uintptr_t** end) {
+    auto region = GetRegionBounds(COV_INSTR_PCS);
+    if (!region.ok()) {
+        *begin = 0;
+        *end = 0;
+        return;
+    }
+
+    assert(region->second <= record_len_);
+
+    *begin = (volatile uintptr_t*)((volatile uint8_t*)shm_ + region->first);
+    *end = (volatile uintptr_t*)((volatile uint8_t*)shm_ + region->second);
+}
+
+uint64_t CoverageRecord::TotalEdgeCounts() {
+    assert(shm_);
+
+    uint64_t counter = 0;
+
+    volatile uint8_t* begin = NULL;
+    volatile uint8_t* end = NULL;
+
+    GetRawCounts(&begin, &end);
+
+    for (volatile uint8_t* x = begin; x < end; x++) {
+        counter += *x;
+    }
+
+    return counter;
+}
+
+Result<void> CoverageRecord::SaveSancovFile(const std::string& filename) {
+    android::base::unique_fd output_fd(TEMP_FAILURE_RETRY(creat(filename.c_str(), 00644)));
+    if (!output_fd.ok()) {
+        return ErrnoError() << "Could not open sancov file";
+    }
+
+    uint64_t magic;
+    if (sizeof(uintptr_t) == 8) {
+        magic = 0xC0BFFFFFFFFFFF64;
+    } else if (sizeof(uintptr_t) == 4) {
+        magic = 0xC0BFFFFFFFFFFF32;
+    }
+    WriteFully(output_fd, &magic, sizeof(magic));
+
+    volatile uintptr_t* begin = nullptr;
+    volatile uintptr_t* end = nullptr;
+
+    GetRawPCs(&begin, &end);
+
+    for (volatile uintptr_t* pc_ptr = begin; pc_ptr < end; pc_ptr++) {
+        uintptr_t pc = *pc_ptr;
+        if (pc) {
+            WriteFully(output_fd, &pc, sizeof(pc));
+        }
+    }
+
+    return {};
+}
+
+}  // namespace coverage
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/coverage/coverage_test.cpp b/trusty/coverage/coverage_test.cpp
new file mode 100644
index 0000000..c1efca6
--- /dev/null
+++ b/trusty/coverage/coverage_test.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/stringprintf.h>
+#include <gtest/gtest.h>
+#include <trusty/coverage/coverage.h>
+#include <trusty/tipc.h>
+#include <array>
+#include <memory>
+
+using android::base::unique_fd;
+using std::array;
+using std::make_unique;
+using std::unique_ptr;
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define TEST_SRV_PORT "com.android.trusty.sancov.test.srv"
+#define TEST_SRV_MODULE "srv.syms.elf"
+
+namespace android {
+namespace trusty {
+namespace coverage {
+
+/* Test server's UUID is 77f68803-c514-43ba-bdce-3254531c3d24 */
+static struct uuid test_srv_uuid = {
+        0x77f68803,
+        0xc514,
+        0x43ba,
+        {0xbd, 0xce, 0x32, 0x54, 0x53, 0x1c, 0x3d, 0x24},
+};
+
+class CoverageTest : public ::testing::Test {
+  public:
+    void SetUp() override {
+        record_ = make_unique<CoverageRecord>(TIPC_DEV, &test_srv_uuid);
+        auto ret = record_->Open();
+        ASSERT_TRUE(ret.ok()) << ret.error();
+    }
+
+    void TearDown() override { record_.reset(); }
+
+    unique_ptr<CoverageRecord> record_;
+};
+
+TEST_F(CoverageTest, CoverageReset) {
+    record_->ResetFullRecord();
+    auto counter = record_->TotalEdgeCounts();
+    ASSERT_EQ(counter, 0);
+}
+
+TEST_F(CoverageTest, TestServerCoverage) {
+    unique_fd test_srv(tipc_connect(TIPC_DEV, TEST_SRV_PORT));
+    ASSERT_GE(test_srv, 0);
+
+    uint32_t mask = (uint32_t)-1;
+    uint32_t magic = 0xdeadbeef;
+    uint64_t high_watermark = 0;
+
+    for (size_t i = 1; i < sizeof(magic) * 8; i++) {
+        /* Reset coverage */
+        record_->ResetCounts();
+
+        /* Send message to test server */
+        uint32_t msg = magic & ~(mask << i);
+        int rc = write(test_srv, &msg, sizeof(msg));
+        ASSERT_EQ(rc, sizeof(msg));
+
+        /* Read message from test server */
+        rc = read(test_srv, &msg, sizeof(msg));
+        ASSERT_EQ(rc, sizeof(msg));
+
+        /* Count number of non-unique blocks executed */
+        auto counter = record_->TotalEdgeCounts();
+        /* Each consecutive input should exercise more or same blocks */
+        ASSERT_GE(counter, high_watermark);
+        high_watermark = counter;
+
+        auto sancov_filename = android::base::StringPrintf(
+                "/data/local/tmp/" TEST_SRV_MODULE ".%d.sancov", getpid());
+        auto res = record_->SaveSancovFile(sancov_filename);
+        ASSERT_TRUE(res.ok());
+    }
+
+    ASSERT_GT(high_watermark, 0);
+}
+
+}  // namespace coverage
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/coverage/include/trusty/coverage/coverage.h b/trusty/coverage/include/trusty/coverage/coverage.h
new file mode 100644
index 0000000..9ccc981
--- /dev/null
+++ b/trusty/coverage/include/trusty/coverage/coverage.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <optional>
+#include <string>
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+#include <stdint.h>
+#include <trusty/coverage/tipc.h>
+
+namespace android {
+namespace trusty {
+namespace coverage {
+
+using android::base::Result;
+using android::base::unique_fd;
+
+class CoverageRecord {
+  public:
+    /**
+     * Create a coverage record interface. Coverage will not be written to a
+     * sancov output file on completion.
+     */
+    CoverageRecord(std::string tipc_dev, struct uuid* uuid);
+
+    /**
+     * Create a coverage record interface. On destruction, write this coverage
+     * to the given sancov filename.
+     */
+    CoverageRecord(std::string tipc_dev, struct uuid* uuid, std::string module_name);
+
+    ~CoverageRecord();
+    Result<void> Open();
+    bool IsOpen();
+    void ResetFullRecord();
+    void ResetCounts();
+    void ResetPCs();
+    void GetRawData(volatile void** begin, volatile void** end);
+    void GetRawCounts(volatile uint8_t** begin, volatile uint8_t** end);
+    void GetRawPCs(volatile uintptr_t** begin, volatile uintptr_t** end);
+    uint64_t TotalEdgeCounts();
+
+    /**
+     * Save the current set of observed PCs to the given filename.
+     * The resulting .sancov file can be parsed via the LLVM sancov tool to see
+     * coverage statistics and visualize coverage.
+     */
+    Result<void> SaveSancovFile(const std::string& filename);
+
+  private:
+    Result<void> Rpc(coverage_client_req* req, int req_fd, coverage_client_resp* resp);
+
+    Result<std::pair<size_t, size_t>> GetRegionBounds(uint32_t region_type);
+
+    std::string tipc_dev_;
+    unique_fd coverage_srv_fd_;
+    struct uuid uuid_;
+    std::optional<std::string> sancov_filename_;
+    size_t record_len_;
+    volatile void* shm_;
+    size_t shm_len_;
+};
+
+}  // namespace coverage
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/coverage/include/trusty/coverage/record.h b/trusty/coverage/include/trusty/coverage/record.h
new file mode 100644
index 0000000..bfe06f3
--- /dev/null
+++ b/trusty/coverage/include/trusty/coverage/record.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* This file needs to be kept in-sync with its counterpart on Trusty side:
+ * trusty/user/base/lib/coverage/common/include/lib/coverage/common/record.h */
+
+#pragma once
+
+#include <stdint.h>
+
+/**
+ * enum coverage_record_type - Coverage region header type
+ * @COV_START: Magic header start marker
+ * @COV_8BIT_COUNTERS: 8bit counter for each instrumentation point
+ * @COV_INSTR_PCS: Pointer length offset of each instrumentation point from the
+ *                 start of the binary
+ * @COV_TOTAL_LENGTH: Total length of the entire coverage record, must be the
+ *                    last header item.
+ *
+ * Describes the type of a region of the coverage record. See &struct
+ * coverage_record_header.
+ */
+enum coverage_record_type {
+    COV_START = 0x434f5652,
+    COV_8BIT_COUNTERS = 1,
+    COV_INSTR_PCS = 2,
+    COV_TOTAL_LENGTH = 0,
+};
+
+/**
+ * struct coverage_record_header - Header entry describing a region of the
+ * coverage record.
+ * @type: type of the region, must be one of @enum coverage_record_type
+ * @offset: offset from the beginning of the header to the start of the region
+ *
+ * Coverage records start with a header which is a list of struct
+ * coverage_record_header, beginning with an entry with type COV_START and
+ * terminated with an entry with type COV_TOTAL_LENGTH. Each of these header
+ * entries corresponds to a region of the record, with the offset indicating the
+ * offset of the start of that region from the beginning of the record (i.e. the
+ * beginning of the header). Each record type and offset is 32-bit field with
+ * native endianness. The first header item must be COV_START with a 0 offset.
+ * The COV_START entry should be initialized when the coverage header is
+ * complete and ready for consumption by the client, because coverage record
+ * initialization happens asynchronously. The final header item,
+ * COV_TOTAL_LENGTH, which must always be present, indicates the total length of
+ * the coverage record, including the header.
+ *
+ * Coverage regions should be contiguous, so the end of one region is the start
+ * of the next, and the coverage header must be in the same order as the regions
+ * in the record body. Thus we can compute the length of a region by subtracting
+ * the region's offset from the offset of the next header item.
+ */
+struct coverage_record_header {
+    uint32_t type;
+    uint32_t offset;
+};
diff --git a/trusty/coverage/include/trusty/coverage/tipc.h b/trusty/coverage/include/trusty/coverage/tipc.h
new file mode 100644
index 0000000..386b2bb
--- /dev/null
+++ b/trusty/coverage/include/trusty/coverage/tipc.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* This file needs to be kept in-sync with it's counterpart on Trusty side */
+
+#pragma once
+
+#include <stdint.h>
+#include <trusty/coverage/uuid.h>
+
+#define COVERAGE_CLIENT_PORT "com.android.trusty.coverage.client"
+
+enum coverage_client_cmd {
+    COVERAGE_CLIENT_CMD_RESP_BIT = 1U,
+    COVERAGE_CLIENT_CMD_SHIFT = 1U,
+    COVERAGE_CLIENT_CMD_OPEN = (1U << COVERAGE_CLIENT_CMD_SHIFT),
+    COVERAGE_CLIENT_CMD_SHARE_RECORD = (2U << COVERAGE_CLIENT_CMD_SHIFT),
+};
+
+struct coverage_client_hdr {
+    uint32_t cmd;
+};
+
+struct coverage_client_open_req {
+    struct uuid uuid;
+};
+
+struct coverage_client_open_resp {
+    uint32_t record_len;
+};
+
+struct coverage_client_share_record_req {
+    uint32_t shm_len;
+};
+
+struct coverage_client_req {
+    struct coverage_client_hdr hdr;
+    union {
+        struct coverage_client_open_req open_args;
+        struct coverage_client_share_record_req share_record_args;
+    };
+};
+
+struct coverage_client_resp {
+    struct coverage_client_hdr hdr;
+    union {
+        struct coverage_client_open_resp open_args;
+    };
+};
diff --git a/trusty/coverage/include/trusty/coverage/uuid.h b/trusty/coverage/include/trusty/coverage/uuid.h
new file mode 100644
index 0000000..c77d275
--- /dev/null
+++ b/trusty/coverage/include/trusty/coverage/uuid.h
@@ -0,0 +1,35 @@
+/*
+ * 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 <stdint.h>
+
+struct uuid {
+    uint32_t time_low;
+    uint16_t time_mid;
+    uint16_t time_hi_and_version;
+    uint8_t clock_seq_and_node[8];
+};
+
+/**
+ * str_to_uuid() - Converts a C string into a uuid
+ * @str: C-string representation of the uuid
+ * @uuid: &struct uuid to fill with the converted uuid
+ *
+ * Return: true on success, false otherwise
+ */
+bool str_to_uuid(const char* str, struct uuid* uuid);
diff --git a/trusty/coverage/uuid.cpp b/trusty/coverage/uuid.cpp
new file mode 100644
index 0000000..f0a6c0e
--- /dev/null
+++ b/trusty/coverage/uuid.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 The Android Open Sourete 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 <string.h>
+#include <trusty/coverage/uuid.h>
+#include <uuid.h>
+
+#include <stdio.h>
+
+static uint16_t reverse_u16(uint16_t u) {
+    return u << 8 | u >> 8;
+}
+
+static uint32_t reverse_u32(uint32_t u) {
+    return reverse_u16((uint16_t)u) << 16 | reverse_u16(u >> 16);
+}
+
+bool str_to_uuid(const char* str, struct uuid* uuid) {
+    uuid_t uu;
+    static_assert(sizeof(uu) == sizeof(*uuid));
+
+    if (uuid_parse(str, uu)) {
+        return false;
+    }
+
+    memcpy(uuid, uu, sizeof(*uuid));
+    uuid->time_low = reverse_u32(uuid->time_low);
+    uuid->time_mid = reverse_u16(uuid->time_mid);
+    uuid->time_hi_and_version = reverse_u16(uuid->time_hi_and_version);
+    return true;
+}
diff --git a/trusty/fuzz/Android.bp b/trusty/fuzz/Android.bp
index 969431c..5d0ff79 100644
--- a/trusty/fuzz/Android.bp
+++ b/trusty/fuzz/Android.bp
@@ -12,12 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_defaults {
     name: "trusty_fuzzer_defaults",
-    static_libs: [
-        "libtrusty_fuzz_utils",
-    ],
     shared_libs: [
+        "libtrusty",
+        "libtrusty_coverage",
+        "libtrusty_fuzz_utils",
         "libbase",
         "liblog",
     ],
@@ -26,17 +30,33 @@
         "-Werror",
     ],
     fuzz_config: {
-        fuzz_on_haiku_device: false,
         fuzz_on_haiku_host: false,
     },
 }
 
 cc_library {
     name: "libtrusty_fuzz_utils",
-    srcs: ["utils.cpp"],
+    srcs: [
+        "counters.cpp",
+        "utils.cpp",
+    ],
     export_include_dirs: ["include"],
+    static_libs: [
+        "libFuzzer",
+    ],
     shared_libs: [
+        "libtrusty_coverage",
         "libbase",
         "liblog",
+        "libtrusty",
     ],
 }
+
+// Generic TIPC fuzzer, must parameterized using:
+//  -DTRUSTY_APP_PORT=<port name of TA being fuzzed>
+//  -DTRUSTY_APP_UUID=<UUID of TA being fuzzed>
+//  -DTRUSTY_APP_FILENAME=<name of symbolized elf binary of the TA>
+filegroup {
+    name: "trusty_tipc_fuzzer",
+    srcs: ["tipc_fuzzer.cpp"],
+}
diff --git a/trusty/fuzz/counters.cpp b/trusty/fuzz/counters.cpp
new file mode 100644
index 0000000..c28fd05
--- /dev/null
+++ b/trusty/fuzz/counters.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 The Android Open Sourete 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_TAG "trusty-fuzz-counters"
+
+#include <FuzzerDefs.h>
+
+#include <trusty/fuzz/counters.h>
+
+#include <android-base/logging.h>
+#include <log/log.h>
+#include <trusty/coverage/coverage.h>
+#include <trusty/coverage/tipc.h>
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+
+/*
+ * We don't know how many counters the coverage record will contain. So, eyeball
+ * the size of this section.
+ */
+static const size_t kMaxNumCounters = 0x8000;
+__attribute__((section("__libfuzzer_extra_counters"))) volatile uint8_t counters[kMaxNumCounters];
+
+namespace android {
+namespace trusty {
+namespace fuzz {
+
+ExtraCounters::ExtraCounters(coverage::CoverageRecord* record) : record_(record) {
+    if (!record_->IsOpen()) {
+        return;
+    }
+
+    assert(fuzzer::ExtraCountersBegin());
+    assert(fuzzer::ExtraCountersEnd());
+
+    volatile uint8_t* begin = NULL;
+    volatile uint8_t* end = NULL;
+    record_->GetRawCounts(&begin, &end);
+    assert(end - begin <= sizeof(counters));
+}
+
+ExtraCounters::~ExtraCounters() {
+    if (!record_->IsOpen()) {
+        return;
+    }
+
+    Flush();
+}
+
+void ExtraCounters::Reset() {
+    if (!record_->IsOpen()) {
+        return;
+    }
+
+    record_->ResetCounts();
+    fuzzer::ClearExtraCounters();
+}
+
+void ExtraCounters::Flush() {
+    volatile uint8_t* begin = NULL;
+    volatile uint8_t* end = NULL;
+
+    record_->GetRawCounts(&begin, &end);
+    if (!begin || !end) {
+        ALOGE("Could not get raw counts from coverage record\n");
+        return;
+    }
+
+    size_t num_counters = end - begin;
+    if (num_counters > kMaxNumCounters) {
+        ALOGE("Too many counters (%zu) to fit in the extra counters section!\n", num_counters);
+        num_counters = kMaxNumCounters;
+    }
+    for (size_t i = 0; i < num_counters; i++) {
+        *(counters + i) = *(begin + i);
+    }
+}
+
+}  // namespace fuzz
+}  // namespace trusty
+}  // namespace android
diff --git a/libstats/socket/include/stats_socket.h b/trusty/fuzz/include/trusty/fuzz/counters.h
similarity index 60%
rename from libstats/socket/include/stats_socket.h
rename to trusty/fuzz/include/trusty/fuzz/counters.h
index 5a75fc0..db933d9 100644
--- a/libstats/socket/include/stats_socket.h
+++ b/trusty/fuzz/include/trusty/fuzz/counters.h
@@ -16,18 +16,27 @@
 
 #pragma once
 
-/**
- * Helpers to manage the statsd socket.
- **/
+#include <string>
 
-#ifdef __cplusplus
-extern "C" {
-#endif  // __CPLUSPLUS
+#include <android-base/result.h>
+#include <trusty/coverage/coverage.h>
 
-/**
- * Closes the statsd socket file descriptor.
- **/
-void AStatsSocket_close();
-#ifdef __cplusplus
-}
-#endif  // __CPLUSPLUS
+namespace android {
+namespace trusty {
+namespace fuzz {
+
+class ExtraCounters {
+  public:
+    ExtraCounters(coverage::CoverageRecord* record);
+    ~ExtraCounters();
+
+    void Reset();
+    void Flush();
+
+  private:
+    coverage::CoverageRecord* record_;
+};
+
+}  // namespace fuzz
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/fuzz/include/trusty/fuzz/utils.h b/trusty/fuzz/include/trusty/fuzz/utils.h
index bca84e9..c906412 100644
--- a/trusty/fuzz/include/trusty/fuzz/utils.h
+++ b/trusty/fuzz/include/trusty/fuzz/utils.h
@@ -34,6 +34,7 @@
     android::base::Result<void> Connect();
     android::base::Result<void> Read(void* buf, size_t len);
     android::base::Result<void> Write(const void* buf, size_t len);
+    void Disconnect();
 
     android::base::Result<int> GetRawFd();
 
diff --git a/trusty/fuzz/test/Android.bp b/trusty/fuzz/test/Android.bp
new file mode 100644
index 0000000..e0bca55
--- /dev/null
+++ b/trusty/fuzz/test/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_fuzz {
+    name: "trusty_test_fuzzer",
+    defaults: ["trusty_fuzzer_defaults"],
+    srcs: [":trusty_tipc_fuzzer"],
+    cflags: [
+        "-DTRUSTY_APP_PORT=\"com.android.trusty.sancov.test.srv\"",
+        "-DTRUSTY_APP_UUID=\"77f68803-c514-43ba-bdce-3254531c3d24\"",
+        "-DTRUSTY_APP_FILENAME=\"srv.syms.elf\"",
+    ],
+    fuzz_config: {
+        fuzz_on_haiku_device: false,
+    },
+}
diff --git a/trusty/fuzz/tipc_fuzzer.cpp b/trusty/fuzz/tipc_fuzzer.cpp
new file mode 100644
index 0000000..f265ced
--- /dev/null
+++ b/trusty/fuzz/tipc_fuzzer.cpp
@@ -0,0 +1,96 @@
+/*
+ * 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 <stdlib.h>
+#include <trusty/coverage/coverage.h>
+#include <trusty/coverage/uuid.h>
+#include <trusty/fuzz/counters.h>
+#include <trusty/fuzz/utils.h>
+#include <unistd.h>
+#include <iostream>
+#include <memory>
+
+using android::trusty::coverage::CoverageRecord;
+using android::trusty::fuzz::ExtraCounters;
+using android::trusty::fuzz::TrustyApp;
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+
+#ifndef TRUSTY_APP_PORT
+#error "Port name must be parameterized using -DTRUSTY_APP_PORT."
+#endif
+
+#ifndef TRUSTY_APP_UUID
+#error "UUID must be parameterized using -DTRUSTY_APP_UUID."
+#endif
+
+#ifndef TRUSTY_APP_FILENAME
+#error "Binary file name must be parameterized using -DTRUSTY_APP_FILENAME."
+#endif
+
+static TrustyApp kTrustyApp(TIPC_DEV, TRUSTY_APP_PORT);
+static std::unique_ptr<CoverageRecord> record;
+
+extern "C" int LLVMFuzzerInitialize(int* /* argc */, char*** /* argv */) {
+    uuid module_uuid;
+
+    if (!str_to_uuid(TRUSTY_APP_UUID, &module_uuid)) {
+        std::cerr << "Failed to parse UUID: " << TRUSTY_APP_UUID << std::endl;
+        exit(-1);
+    }
+
+    /* Make sure lazy-loaded TAs have started and connected to coverage service. */
+    auto ret = kTrustyApp.Connect();
+    if (!ret.ok()) {
+        std::cerr << ret.error() << std::endl;
+        exit(-1);
+    }
+
+    record = std::make_unique<CoverageRecord>(TIPC_DEV, &module_uuid, TRUSTY_APP_FILENAME);
+    if (!record) {
+        std::cerr << "Failed to allocate coverage record" << std::endl;
+        exit(-1);
+    }
+
+    ret = record->Open();
+    if (!ret.ok()) {
+        std::cerr << ret.error() << std::endl;
+        exit(-1);
+    }
+    return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    static uint8_t buf[TIPC_MAX_MSG_SIZE];
+
+    ExtraCounters counters(record.get());
+    counters.Reset();
+
+    auto ret = kTrustyApp.Write(data, size);
+    if (ret.ok()) {
+        ret = kTrustyApp.Read(&buf, sizeof(buf));
+    }
+
+    // Reconnect to ensure that the service is still up
+    kTrustyApp.Disconnect();
+    ret = kTrustyApp.Connect();
+    if (!ret.ok()) {
+        std::cerr << ret.error() << std::endl;
+        android::trusty::fuzz::Abort();
+    }
+
+    return ret.ok() ? 0 : -1;
+}
diff --git a/trusty/fuzz/utils.cpp b/trusty/fuzz/utils.cpp
index 240afe7..bb096be 100644
--- a/trusty/fuzz/utils.cpp
+++ b/trusty/fuzz/utils.cpp
@@ -25,6 +25,7 @@
 #include <linux/uio.h>
 #include <log/log_read.h>
 #include <time.h>
+#include <trusty/tipc.h>
 #include <iostream>
 
 using android::base::ErrnoError;
@@ -32,9 +33,6 @@
 using android::base::Result;
 using android::base::unique_fd;
 
-#define TIPC_IOC_MAGIC 'r'
-#define TIPC_IOC_CONNECT _IOW(TIPC_IOC_MAGIC, 0x80, char*)
-
 namespace {
 
 const size_t kTimeoutSeconds = 5;
@@ -80,27 +78,14 @@
     : tipc_dev_(tipc_dev), ta_port_(ta_port), ta_fd_(-1) {}
 
 Result<void> TrustyApp::Connect() {
-    /*
-     * TODO: We can't use libtrusty because (yet)
-     * (1) cc_fuzz can't deal with vendor components (b/170753563)
-     * (2) We need non-blocking behavior to detect Trusty going down.
-     * (we could implement the timeout in the fuzzing code though, as
-     * it needs to be around the call to read())
-     */
     alarm(kTimeoutSeconds);
-    int fd = open(tipc_dev_.c_str(), O_RDWR);
+    int fd = tipc_connect(tipc_dev_.c_str(), ta_port_.c_str());
     alarm(0);
     if (fd < 0) {
         return ErrnoError() << "failed to open TIPC device: ";
     }
     ta_fd_.reset(fd);
 
-    // This ioctl will time out in the kernel if it can't connect.
-    int rc = TEMP_FAILURE_RETRY(ioctl(ta_fd_, TIPC_IOC_CONNECT, ta_port_.c_str()));
-    if (rc < 0) {
-        return ErrnoError() << "failed to connect to TIPC service: ";
-    }
-
     return {};
 }
 
@@ -128,7 +113,7 @@
     int rc = write(ta_fd_, buf, len);
     alarm(0);
     if (rc < 0) {
-        return Error() << "failed to read TIPC message from TA: ";
+        return Error() << "failed to write TIPC message to TA: ";
     }
 
     return {};
@@ -142,6 +127,10 @@
     return ta_fd_;
 }
 
+void TrustyApp::Disconnect() {
+    ta_fd_.reset();
+}
+
 void Abort() {
     PrintTrustyLog();
     exit(-1);
diff --git a/trusty/gatekeeper/Android.bp b/trusty/gatekeeper/Android.bp
index e553af1..81f012f 100644
--- a/trusty/gatekeeper/Android.bp
+++ b/trusty/gatekeeper/Android.bp
@@ -19,6 +19,10 @@
 // to only building on ARM if they include assembly. Individual makefiles
 // are responsible for having their own logic, for fine-grained control.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "android.hardware.gatekeeper@1.0-service.trusty",
     defaults: ["hidl_defaults"],
diff --git a/trusty/gatekeeper/fuzz/Android.bp b/trusty/gatekeeper/fuzz/Android.bp
index 7ffa776..d084cb6 100644
--- a/trusty/gatekeeper/fuzz/Android.bp
+++ b/trusty/gatekeeper/fuzz/Android.bp
@@ -12,10 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_fuzz {
     name: "trusty_gatekeeper_fuzzer",
     defaults: ["trusty_fuzzer_defaults"],
-    srcs: ["fuzz.cpp"],
+    srcs: [":trusty_tipc_fuzzer"],
+    cflags: [
+        "-DTRUSTY_APP_PORT=\"com.android.trusty.gatekeeper\"",
+        "-DTRUSTY_APP_UUID=\"38ba0cdc-df0e-11e4-9869-233fb6ae4795\"",
+        "-DTRUSTY_APP_FILENAME=\"gatekeeper.syms.elf\"",
+    ],
 
     // The initial corpus for this fuzzer was derived by dumping messages from
     // the `secure_env` emulator interface for cuttlefish while enrolling a new
diff --git a/trusty/gatekeeper/fuzz/fuzz.cpp b/trusty/gatekeeper/fuzz/fuzz.cpp
deleted file mode 100644
index f8ec931..0000000
--- a/trusty/gatekeeper/fuzz/fuzz.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef NDEBUG
-
-#include <assert.h>
-#include <log/log.h>
-#include <stdlib.h>
-#include <trusty/fuzz/utils.h>
-#include <unistd.h>
-
-#define TIPC_DEV "/dev/trusty-ipc-dev0"
-#define GATEKEEPER_PORT "com.android.trusty.gatekeeper"
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    static uint8_t buf[TIPC_MAX_MSG_SIZE];
-
-    android::trusty::fuzz::TrustyApp ta(TIPC_DEV, GATEKEEPER_PORT);
-
-    auto ret = ta.Connect();
-    /*
-     * If we can't connect, then assume TA crashed.
-     * TODO: Get some more info, e.g. stacks, to help Haiku dedup crashes.
-     */
-    if (!ret.ok()) {
-        android::trusty::fuzz::Abort();
-    }
-
-    /* Send message to test server */
-    ret = ta.Write(data, size);
-    if (!ret.ok()) {
-        return -1;
-    }
-
-    /* Read message from test server */
-    ret = ta.Read(&buf, sizeof(buf));
-    if (!ret.ok()) {
-        return -1;
-    }
-
-    return 0;
-}
diff --git a/trusty/gatekeeper/gatekeeper_ipc.h b/trusty/gatekeeper/gatekeeper_ipc.h
index b05dcd8..8709d1a 100644
--- a/trusty/gatekeeper/gatekeeper_ipc.h
+++ b/trusty/gatekeeper/gatekeeper_ipc.h
@@ -20,11 +20,13 @@
 #define GATEKEEPER_MAX_BUFFER_LENGTH 1024
 
 enum gatekeeper_command {
-	GK_REQ_SHIFT = 1,
-	GK_RESP_BIT  = 1,
+    GK_REQ_SHIFT = 1,
+    GK_RESP_BIT = 1,
 
-	GK_ENROLL       = (0 << GK_REQ_SHIFT),
-	GK_VERIFY       = (1 << GK_REQ_SHIFT),
+    GK_ENROLL = (0 << GK_REQ_SHIFT),
+    GK_VERIFY = (1 << GK_REQ_SHIFT),
+    GK_DELETE_USER = (2 << GK_REQ_SHIFT),
+    GK_DELETE_ALL_USERS = (3 << GK_REQ_SHIFT),
 };
 
 /**
diff --git a/trusty/gatekeeper/trusty_gatekeeper.cpp b/trusty/gatekeeper/trusty_gatekeeper.cpp
index e416fb2..ec4f81b 100644
--- a/trusty/gatekeeper/trusty_gatekeeper.cpp
+++ b/trusty/gatekeeper/trusty_gatekeeper.cpp
@@ -133,13 +133,48 @@
     return {};
 }
 
-Return<void> TrustyGateKeeperDevice::deleteUser(uint32_t /*uid*/, deleteUser_cb _hidl_cb) {
-    _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+Return<void> TrustyGateKeeperDevice::deleteUser(uint32_t uid, deleteUser_cb _hidl_cb) {
+    if (error_ != 0) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return {};
+    }
+
+    DeleteUserRequest request(uid);
+    DeleteUserResponse response;
+    auto error = Send(request, &response);
+
+    if (error != ERROR_NONE) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+    } else if (response.error == ERROR_NOT_IMPLEMENTED) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+    } else if (response.error != ERROR_NONE) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+    } else {
+        _hidl_cb({GatekeeperStatusCode::STATUS_OK, response.retry_timeout, {}});
+    }
     return {};
 }
 
 Return<void> TrustyGateKeeperDevice::deleteAllUsers(deleteAllUsers_cb _hidl_cb) {
-    _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+    if (error_ != 0) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return {};
+    }
+
+    DeleteAllUsersRequest request;
+    DeleteAllUsersResponse response;
+    auto error = Send(request, &response);
+
+    if (error != ERROR_NONE) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+    } else if (response.error == ERROR_NOT_IMPLEMENTED) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+    } else if (response.error != ERROR_NONE) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+    } else {
+        _hidl_cb({GatekeeperStatusCode::STATUS_OK, response.retry_timeout, {}});
+    }
+
     return {};
 }
 
diff --git a/trusty/gatekeeper/trusty_gatekeeper.h b/trusty/gatekeeper/trusty_gatekeeper.h
index c0713f4..420dd7a 100644
--- a/trusty/gatekeeper/trusty_gatekeeper.h
+++ b/trusty/gatekeeper/trusty_gatekeeper.h
@@ -81,6 +81,15 @@
         return Send(GK_VERIFY, request, response);
     }
 
+    gatekeeper_error_t Send(const DeleteUserRequest& request, DeleteUserResponse* response) {
+        return Send(GK_DELETE_USER, request, response);
+    }
+
+    gatekeeper_error_t Send(const DeleteAllUsersRequest& request,
+                            DeleteAllUsersResponse* response) {
+        return Send(GK_DELETE_ALL_USERS, request, response);
+    }
+
     int error_;
 };
 
diff --git a/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp b/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp
index 98cbcc3..d787f7a 100644
--- a/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp
+++ b/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp
@@ -221,10 +221,10 @@
 
 Return<ErrorCode> TrustyKeymaster3Device::addRngEntropy(const hidl_vec<uint8_t>& data) {
     if (data.size() == 0) return ErrorCode::OK;
-    AddEntropyRequest request;
+    AddEntropyRequest request(impl_->message_version());
     request.random_data.Reinitialize(data.data(), data.size());
 
-    AddEntropyResponse response;
+    AddEntropyResponse response(impl_->message_version());
     impl_->AddRngEntropy(request, &response);
 
     return legacy_enum_conversion(response.error);
@@ -232,10 +232,10 @@
 
 Return<void> TrustyKeymaster3Device::generateKey(const hidl_vec<KeyParameter>& keyParams,
                                                  generateKey_cb _hidl_cb) {
-    GenerateKeyRequest request;
+    GenerateKeyRequest request(impl_->message_version());
     request.key_description.Reinitialize(KmParamSet(keyParams));
 
-    GenerateKeyResponse response;
+    GenerateKeyResponse response(impl_->message_version());
     impl_->GenerateKey(request, &response);
 
     KeyCharacteristics resultCharacteristics;
@@ -253,11 +253,11 @@
                                                            const hidl_vec<uint8_t>& clientId,
                                                            const hidl_vec<uint8_t>& appData,
                                                            getKeyCharacteristics_cb _hidl_cb) {
-    GetKeyCharacteristicsRequest request;
+    GetKeyCharacteristicsRequest request(impl_->message_version());
     request.SetKeyMaterial(keyBlob.data(), keyBlob.size());
     addClientAndAppData(clientId, appData, &request.additional_params);
 
-    GetKeyCharacteristicsResponse response;
+    GetKeyCharacteristicsResponse response(impl_->message_version());
     impl_->GetKeyCharacteristics(request, &response);
 
     KeyCharacteristics resultCharacteristics;
@@ -273,12 +273,12 @@
                                                KeyFormat keyFormat,
                                                const hidl_vec<uint8_t>& keyData,
                                                importKey_cb _hidl_cb) {
-    ImportKeyRequest request;
+    ImportKeyRequest request(impl_->message_version());
     request.key_description.Reinitialize(KmParamSet(params));
     request.key_format = legacy_enum_conversion(keyFormat);
-    request.SetKeyMaterial(keyData.data(), keyData.size());
+    request.key_data = KeymasterKeyBlob(keyData.data(), keyData.size());
 
-    ImportKeyResponse response;
+    ImportKeyResponse response(impl_->message_version());
     impl_->ImportKey(request, &response);
 
     KeyCharacteristics resultCharacteristics;
@@ -297,12 +297,12 @@
                                                const hidl_vec<uint8_t>& clientId,
                                                const hidl_vec<uint8_t>& appData,
                                                exportKey_cb _hidl_cb) {
-    ExportKeyRequest request;
+    ExportKeyRequest request(impl_->message_version());
     request.key_format = legacy_enum_conversion(exportFormat);
     request.SetKeyMaterial(keyBlob.data(), keyBlob.size());
     addClientAndAppData(clientId, appData, &request.additional_params);
 
-    ExportKeyResponse response;
+    ExportKeyResponse response(impl_->message_version());
     impl_->ExportKey(request, &response);
 
     hidl_vec<uint8_t> resultKeyBlob;
@@ -316,11 +316,11 @@
 Return<void> TrustyKeymaster3Device::attestKey(const hidl_vec<uint8_t>& keyToAttest,
                                                const hidl_vec<KeyParameter>& attestParams,
                                                attestKey_cb _hidl_cb) {
-    AttestKeyRequest request;
+    AttestKeyRequest request(impl_->message_version());
     request.SetKeyMaterial(keyToAttest.data(), keyToAttest.size());
     request.attest_params.Reinitialize(KmParamSet(attestParams));
 
-    AttestKeyResponse response;
+    AttestKeyResponse response(impl_->message_version());
     impl_->AttestKey(request, &response);
 
     hidl_vec<hidl_vec<uint8_t>> resultCertChain;
@@ -334,11 +334,11 @@
 Return<void> TrustyKeymaster3Device::upgradeKey(const hidl_vec<uint8_t>& keyBlobToUpgrade,
                                                 const hidl_vec<KeyParameter>& upgradeParams,
                                                 upgradeKey_cb _hidl_cb) {
-    UpgradeKeyRequest request;
+    UpgradeKeyRequest request(impl_->message_version());
     request.SetKeyMaterial(keyBlobToUpgrade.data(), keyBlobToUpgrade.size());
     request.upgrade_params.Reinitialize(KmParamSet(upgradeParams));
 
-    UpgradeKeyResponse response;
+    UpgradeKeyResponse response(impl_->message_version());
     impl_->UpgradeKey(request, &response);
 
     if (response.error == KM_ERROR_OK) {
@@ -350,18 +350,18 @@
 }
 
 Return<ErrorCode> TrustyKeymaster3Device::deleteKey(const hidl_vec<uint8_t>& keyBlob) {
-    DeleteKeyRequest request;
+    DeleteKeyRequest request(impl_->message_version());
     request.SetKeyMaterial(keyBlob.data(), keyBlob.size());
 
-    DeleteKeyResponse response;
+    DeleteKeyResponse response(impl_->message_version());
     impl_->DeleteKey(request, &response);
 
     return legacy_enum_conversion(response.error);
 }
 
 Return<ErrorCode> TrustyKeymaster3Device::deleteAllKeys() {
-    DeleteAllKeysRequest request;
-    DeleteAllKeysResponse response;
+    DeleteAllKeysRequest request(impl_->message_version());
+    DeleteAllKeysResponse response(impl_->message_version());
     impl_->DeleteAllKeys(request, &response);
 
     return legacy_enum_conversion(response.error);
@@ -374,15 +374,15 @@
 Return<void> TrustyKeymaster3Device::begin(KeyPurpose purpose, const hidl_vec<uint8_t>& key,
                                            const hidl_vec<KeyParameter>& inParams,
                                            begin_cb _hidl_cb) {
-    BeginOperationRequest request;
+    BeginOperationRequest request(impl_->message_version());
     request.purpose = legacy_enum_conversion(purpose);
     request.SetKeyMaterial(key.data(), key.size());
     request.additional_params.Reinitialize(KmParamSet(inParams));
 
-    BeginOperationResponse response;
+    BeginOperationResponse response(impl_->message_version());
     impl_->BeginOperation(request, &response);
 
-    hidl_vec<KeyParameter> resultParams;
+    hidl_vec<KeyParameter> resultParams(impl_->message_version());
     if (response.error == KM_ERROR_OK) {
         resultParams = kmParamSet2Hidl(response.output_params);
     }
@@ -394,8 +394,8 @@
 Return<void> TrustyKeymaster3Device::update(uint64_t operationHandle,
                                             const hidl_vec<KeyParameter>& inParams,
                                             const hidl_vec<uint8_t>& input, update_cb _hidl_cb) {
-    UpdateOperationRequest request;
-    UpdateOperationResponse response;
+    UpdateOperationRequest request(impl_->message_version());
+    UpdateOperationResponse response(impl_->message_version());
     hidl_vec<KeyParameter> resultParams;
     hidl_vec<uint8_t> resultBlob;
     uint32_t resultConsumed = 0;
@@ -431,13 +431,13 @@
                                             const hidl_vec<uint8_t>& input,
                                             const hidl_vec<uint8_t>& signature,
                                             finish_cb _hidl_cb) {
-    FinishOperationRequest request;
+    FinishOperationRequest request(impl_->message_version());
     request.op_handle = operationHandle;
     request.input.Reinitialize(input.data(), input.size());
     request.signature.Reinitialize(signature.data(), signature.size());
     request.additional_params.Reinitialize(KmParamSet(inParams));
 
-    FinishOperationResponse response;
+    FinishOperationResponse response(impl_->message_version());
     impl_->FinishOperation(request, &response);
 
     hidl_vec<KeyParameter> resultParams;
@@ -451,10 +451,10 @@
 }
 
 Return<ErrorCode> TrustyKeymaster3Device::abort(uint64_t operationHandle) {
-    AbortOperationRequest request;
+    AbortOperationRequest request(impl_->message_version());
     request.op_handle = operationHandle;
 
-    AbortOperationResponse response;
+    AbortOperationResponse response(impl_->message_version());
     impl_->AbortOperation(request, &response);
 
     return legacy_enum_conversion(response.error);
diff --git a/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp b/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
index ec2ba12..e68ba82 100644
--- a/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
+++ b/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
@@ -284,7 +284,7 @@
 
 Return<void> TrustyKeymaster4Device::computeSharedHmac(
         const hidl_vec<HmacSharingParameters>& params, computeSharedHmac_cb _hidl_cb) {
-    ComputeSharedHmacRequest request;
+    ComputeSharedHmacRequest request(impl_->message_version());
     request.params_array.params_array = new keymaster::HmacSharingParameters[params.size()];
     request.params_array.num_params = params.size();
     for (size_t i = 0; i < params.size(); ++i) {
@@ -309,7 +309,7 @@
 Return<void> TrustyKeymaster4Device::verifyAuthorization(
         uint64_t challenge, const hidl_vec<KeyParameter>& parametersToVerify,
         const HardwareAuthToken& authToken, verifyAuthorization_cb _hidl_cb) {
-    VerifyAuthorizationRequest request;
+    VerifyAuthorizationRequest request(impl_->message_version());
     request.challenge = challenge;
     request.parameters_to_verify.Reinitialize(KmParamSet(parametersToVerify));
     request.auth_token.challenge = authToken.challenge;
@@ -336,10 +336,10 @@
 
 Return<ErrorCode> TrustyKeymaster4Device::addRngEntropy(const hidl_vec<uint8_t>& data) {
     if (data.size() == 0) return ErrorCode::OK;
-    AddEntropyRequest request;
+    AddEntropyRequest request(impl_->message_version());
     request.random_data.Reinitialize(data.data(), data.size());
 
-    AddEntropyResponse response;
+    AddEntropyResponse response(impl_->message_version());
     impl_->AddRngEntropy(request, &response);
 
     return legacy_enum_conversion(response.error);
@@ -347,10 +347,10 @@
 
 Return<void> TrustyKeymaster4Device::generateKey(const hidl_vec<KeyParameter>& keyParams,
                                                  generateKey_cb _hidl_cb) {
-    GenerateKeyRequest request;
+    GenerateKeyRequest request(impl_->message_version());
     request.key_description.Reinitialize(KmParamSet(keyParams));
 
-    GenerateKeyResponse response;
+    GenerateKeyResponse response(impl_->message_version());
     impl_->GenerateKey(request, &response);
 
     KeyCharacteristics resultCharacteristics;
@@ -368,11 +368,11 @@
                                                            const hidl_vec<uint8_t>& clientId,
                                                            const hidl_vec<uint8_t>& appData,
                                                            getKeyCharacteristics_cb _hidl_cb) {
-    GetKeyCharacteristicsRequest request;
+    GetKeyCharacteristicsRequest request(impl_->message_version());
     request.SetKeyMaterial(keyBlob.data(), keyBlob.size());
     addClientAndAppData(clientId, appData, &request.additional_params);
 
-    GetKeyCharacteristicsResponse response;
+    GetKeyCharacteristicsResponse response(impl_->message_version());
     impl_->GetKeyCharacteristics(request, &response);
 
     KeyCharacteristics resultCharacteristics;
@@ -388,12 +388,12 @@
                                                KeyFormat keyFormat,
                                                const hidl_vec<uint8_t>& keyData,
                                                importKey_cb _hidl_cb) {
-    ImportKeyRequest request;
+    ImportKeyRequest request(impl_->message_version());
     request.key_description.Reinitialize(KmParamSet(params));
     request.key_format = legacy_enum_conversion(keyFormat);
-    request.SetKeyMaterial(keyData.data(), keyData.size());
+    request.key_data = KeymasterKeyBlob(keyData.data(), keyData.size());
 
-    ImportKeyResponse response;
+    ImportKeyResponse response(impl_->message_version());
     impl_->ImportKey(request, &response);
 
     KeyCharacteristics resultCharacteristics;
@@ -411,7 +411,7 @@
         const hidl_vec<uint8_t>& wrappedKeyData, const hidl_vec<uint8_t>& wrappingKeyBlob,
         const hidl_vec<uint8_t>& maskingKey, const hidl_vec<KeyParameter>& unwrappingParams,
         uint64_t passwordSid, uint64_t biometricSid, importWrappedKey_cb _hidl_cb) {
-    ImportWrappedKeyRequest request;
+    ImportWrappedKeyRequest request(impl_->message_version());
     request.SetWrappedMaterial(wrappedKeyData.data(), wrappedKeyData.size());
     request.SetWrappingMaterial(wrappingKeyBlob.data(), wrappingKeyBlob.size());
     request.SetMaskingKeyMaterial(maskingKey.data(), maskingKey.size());
@@ -419,7 +419,7 @@
     request.password_sid = passwordSid;
     request.biometric_sid = biometricSid;
 
-    ImportWrappedKeyResponse response;
+    ImportWrappedKeyResponse response(impl_->message_version());
     impl_->ImportWrappedKey(request, &response);
 
     KeyCharacteristics resultCharacteristics;
@@ -438,12 +438,12 @@
                                                const hidl_vec<uint8_t>& clientId,
                                                const hidl_vec<uint8_t>& appData,
                                                exportKey_cb _hidl_cb) {
-    ExportKeyRequest request;
+    ExportKeyRequest request(impl_->message_version());
     request.key_format = legacy_enum_conversion(exportFormat);
     request.SetKeyMaterial(keyBlob.data(), keyBlob.size());
     addClientAndAppData(clientId, appData, &request.additional_params);
 
-    ExportKeyResponse response;
+    ExportKeyResponse response(impl_->message_version());
     impl_->ExportKey(request, &response);
 
     hidl_vec<uint8_t> resultKeyBlob;
@@ -457,11 +457,11 @@
 Return<void> TrustyKeymaster4Device::attestKey(const hidl_vec<uint8_t>& keyToAttest,
                                                const hidl_vec<KeyParameter>& attestParams,
                                                attestKey_cb _hidl_cb) {
-    AttestKeyRequest request;
+    AttestKeyRequest request(impl_->message_version());
     request.SetKeyMaterial(keyToAttest.data(), keyToAttest.size());
     request.attest_params.Reinitialize(KmParamSet(attestParams));
 
-    AttestKeyResponse response;
+    AttestKeyResponse response(impl_->message_version());
     impl_->AttestKey(request, &response);
 
     hidl_vec<hidl_vec<uint8_t>> resultCertChain;
@@ -475,11 +475,11 @@
 Return<void> TrustyKeymaster4Device::upgradeKey(const hidl_vec<uint8_t>& keyBlobToUpgrade,
                                                 const hidl_vec<KeyParameter>& upgradeParams,
                                                 upgradeKey_cb _hidl_cb) {
-    UpgradeKeyRequest request;
+    UpgradeKeyRequest request(impl_->message_version());
     request.SetKeyMaterial(keyBlobToUpgrade.data(), keyBlobToUpgrade.size());
     request.upgrade_params.Reinitialize(KmParamSet(upgradeParams));
 
-    UpgradeKeyResponse response;
+    UpgradeKeyResponse response(impl_->message_version());
     impl_->UpgradeKey(request, &response);
 
     if (response.error == KM_ERROR_OK) {
@@ -491,18 +491,18 @@
 }
 
 Return<ErrorCode> TrustyKeymaster4Device::deleteKey(const hidl_vec<uint8_t>& keyBlob) {
-    DeleteKeyRequest request;
+    DeleteKeyRequest request(impl_->message_version());
     request.SetKeyMaterial(keyBlob.data(), keyBlob.size());
 
-    DeleteKeyResponse response;
+    DeleteKeyResponse response(impl_->message_version());
     impl_->DeleteKey(request, &response);
 
     return legacy_enum_conversion(response.error);
 }
 
 Return<ErrorCode> TrustyKeymaster4Device::deleteAllKeys() {
-    DeleteAllKeysRequest request;
-    DeleteAllKeysResponse response;
+    DeleteAllKeysRequest request(impl_->message_version());
+    DeleteAllKeysResponse response(impl_->message_version());
     impl_->DeleteAllKeys(request, &response);
 
     return legacy_enum_conversion(response.error);
@@ -516,12 +516,12 @@
                                            const hidl_vec<KeyParameter>& inParams,
                                            const HardwareAuthToken& authToken, begin_cb _hidl_cb) {
     hidl_vec<KeyParameter> extendedParams = injectAuthToken(inParams, authToken);
-    BeginOperationRequest request;
+    BeginOperationRequest request(impl_->message_version());
     request.purpose = legacy_enum_conversion(purpose);
     request.SetKeyMaterial(key.data(), key.size());
     request.additional_params.Reinitialize(KmParamSet(extendedParams));
 
-    BeginOperationResponse response;
+    BeginOperationResponse response(impl_->message_version());
     impl_->BeginOperation(request, &response);
 
     hidl_vec<KeyParameter> resultParams;
@@ -540,8 +540,8 @@
                                             const VerificationToken& verificationToken,
                                             update_cb _hidl_cb) {
     (void)verificationToken;
-    UpdateOperationRequest request;
-    UpdateOperationResponse response;
+    UpdateOperationRequest request(impl_->message_version());
+    UpdateOperationResponse response(impl_->message_version());
     hidl_vec<KeyParameter> resultParams;
     hidl_vec<uint8_t> resultBlob;
     hidl_vec<KeyParameter> extendedParams = injectAuthToken(inParams, authToken);
@@ -581,14 +581,14 @@
                                             const VerificationToken& verificationToken,
                                             finish_cb _hidl_cb) {
     (void)verificationToken;
-    FinishOperationRequest request;
+    FinishOperationRequest request(impl_->message_version());
     hidl_vec<KeyParameter> extendedParams = injectAuthToken(inParams, authToken);
     request.op_handle = operationHandle;
     request.input.Reinitialize(input.data(), input.size());
     request.signature.Reinitialize(signature.data(), signature.size());
     request.additional_params.Reinitialize(KmParamSet(extendedParams));
 
-    FinishOperationResponse response;
+    FinishOperationResponse response(impl_->message_version());
     impl_->FinishOperation(request, &response);
 
     hidl_vec<KeyParameter> resultParams;
@@ -602,10 +602,10 @@
 }
 
 Return<ErrorCode> TrustyKeymaster4Device::abort(uint64_t operationHandle) {
-    AbortOperationRequest request;
+    AbortOperationRequest request(impl_->message_version());
     request.op_handle = operationHandle;
 
-    AbortOperationResponse response;
+    AbortOperationResponse response(impl_->message_version());
     impl_->AbortOperation(request, &response);
 
     return legacy_enum_conversion(response.error);
diff --git a/trusty/keymaster/Android.bp b/trusty/keymaster/Android.bp
index 27e1a3f..6d24e84 100644
--- a/trusty/keymaster/Android.bp
+++ b/trusty/keymaster/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "android.hardware.keymaster@3.0-service.trusty",
     defaults: ["hidl_defaults"],
@@ -107,4 +111,3 @@
         "-Werror",
     ],
 }
-
diff --git a/trusty/keymaster/TrustyKeymaster.cpp b/trusty/keymaster/TrustyKeymaster.cpp
index 750a9d7..23e0433 100644
--- a/trusty/keymaster/TrustyKeymaster.cpp
+++ b/trusty/keymaster/TrustyKeymaster.cpp
@@ -31,11 +31,41 @@
         return err;
     }
 
-    ConfigureRequest req;
+    // Try GetVersion2 first.
+    GetVersion2Request versionReq;
+    GetVersion2Response versionRsp = GetVersion2(versionReq);
+    if (versionRsp.error != KM_ERROR_OK) {
+        ALOGW("TA appears not to support GetVersion2, falling back (err = %d)", versionRsp.error);
+
+        err = trusty_keymaster_connect();
+        if (err) {
+            ALOGE("Failed to connect to trusty keymaster %d", err);
+            return err;
+        }
+
+        GetVersionRequest versionReq;
+        GetVersionResponse versionRsp;
+        GetVersion(versionReq, &versionRsp);
+        if (versionRsp.error != KM_ERROR_OK) {
+            ALOGE("Failed to get TA version %d", versionRsp.error);
+            return -1;
+        } else {
+            keymaster_error_t error;
+            message_version_ = NegotiateMessageVersion(versionRsp, &error);
+            if (error != KM_ERROR_OK) {
+                ALOGE("Failed to negotiate message version %d", error);
+                return -1;
+            }
+        }
+    } else {
+        message_version_ = NegotiateMessageVersion(versionReq, versionRsp);
+    }
+
+    ConfigureRequest req(message_version());
     req.os_version = GetOsVersion();
     req.os_patchlevel = GetOsPatchlevel();
 
-    ConfigureResponse rsp;
+    ConfigureResponse rsp(message_version());
     Configure(req, &rsp);
 
     if (rsp.error != KM_ERROR_OK) {
@@ -52,7 +82,7 @@
     trusty_keymaster_disconnect();
 }
 
-static void ForwardCommand(enum keymaster_command command, const Serializable& req,
+static void ForwardCommand(enum keymaster_command command, const KeymasterMessage& req,
                            KeymasterResponse* rsp) {
     keymaster_error_t err;
     err = trusty_keymaster_send(command, req, rsp);
@@ -173,25 +203,30 @@
 }
 
 GetHmacSharingParametersResponse TrustyKeymaster::GetHmacSharingParameters() {
-    // Empty buffer to allow ForwardCommand to have something to serialize
-    Buffer request;
-    GetHmacSharingParametersResponse response;
+    GetHmacSharingParametersRequest request(message_version());
+    GetHmacSharingParametersResponse response(message_version());
     ForwardCommand(KM_GET_HMAC_SHARING_PARAMETERS, request, &response);
     return response;
 }
 
 ComputeSharedHmacResponse TrustyKeymaster::ComputeSharedHmac(
         const ComputeSharedHmacRequest& request) {
-    ComputeSharedHmacResponse response;
+    ComputeSharedHmacResponse response(message_version());
     ForwardCommand(KM_COMPUTE_SHARED_HMAC, request, &response);
     return response;
 }
 
 VerifyAuthorizationResponse TrustyKeymaster::VerifyAuthorization(
         const VerifyAuthorizationRequest& request) {
-    VerifyAuthorizationResponse response;
+    VerifyAuthorizationResponse response(message_version());
     ForwardCommand(KM_VERIFY_AUTHORIZATION, request, &response);
     return response;
 }
 
+GetVersion2Response TrustyKeymaster::GetVersion2(const GetVersion2Request& request) {
+    GetVersion2Response response(message_version());
+    ForwardCommand(KM_GET_VERSION_2, request, &response);
+    return response;
+}
+
 }  // namespace keymaster
diff --git a/trusty/keymaster/fuzz/Android.bp b/trusty/keymaster/fuzz/Android.bp
new file mode 100644
index 0000000..8d7ee00
--- /dev/null
+++ b/trusty/keymaster/fuzz/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_fuzz {
+    name: "trusty_keymaster_fuzzer",
+    defaults: ["trusty_fuzzer_defaults"],
+    srcs: [":trusty_tipc_fuzzer"],
+    cflags: [
+        "-DTRUSTY_APP_PORT=\"com.android.trusty.keymaster\"",
+        "-DTRUSTY_APP_UUID=\"5f902ace-5e5c-4cd8-ae54-87b88c22ddaf\"",
+        "-DTRUSTY_APP_FILENAME=\"keymaster.syms.elf\"",
+    ],
+
+    // The initial corpus for this fuzzer was derived by dumping messages from
+    // the `secure_env` emulator interface for cuttlefish while running the
+    // keystore2 tests in the emulator.
+    corpus: ["corpus/*"],
+}
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-1x0hJ5 b/trusty/keymaster/fuzz/corpus/keymaster-recv-1x0hJ5
new file mode 100644
index 0000000..4a292c1
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-1x0hJ5
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-5GV6mx b/trusty/keymaster/fuzz/corpus/keymaster-recv-5GV6mx
new file mode 100644
index 0000000..60301f6
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-5GV6mx
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-7RVbJ8 b/trusty/keymaster/fuzz/corpus/keymaster-recv-7RVbJ8
new file mode 100644
index 0000000..9ed8279
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-7RVbJ8
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-9ElJHi b/trusty/keymaster/fuzz/corpus/keymaster-recv-9ElJHi
new file mode 100644
index 0000000..69caf33
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-9ElJHi
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-9czLCR b/trusty/keymaster/fuzz/corpus/keymaster-recv-9czLCR
new file mode 100644
index 0000000..e9d7daf
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-9czLCR
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-BFx3FN b/trusty/keymaster/fuzz/corpus/keymaster-recv-BFx3FN
new file mode 100644
index 0000000..3ce16c3
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-BFx3FN
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-BXWpRA b/trusty/keymaster/fuzz/corpus/keymaster-recv-BXWpRA
new file mode 100644
index 0000000..c290b52
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-BXWpRA
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-DanwgH b/trusty/keymaster/fuzz/corpus/keymaster-recv-DanwgH
new file mode 100644
index 0000000..b1fb022
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-DanwgH
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-JP2pXq b/trusty/keymaster/fuzz/corpus/keymaster-recv-JP2pXq
new file mode 100644
index 0000000..2f9abcf
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-JP2pXq
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-T0YO5T b/trusty/keymaster/fuzz/corpus/keymaster-recv-T0YO5T
new file mode 100644
index 0000000..9ed8279
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-T0YO5T
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-TM26dO b/trusty/keymaster/fuzz/corpus/keymaster-recv-TM26dO
new file mode 100644
index 0000000..ec374e3
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-TM26dO
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-XcPQ60 b/trusty/keymaster/fuzz/corpus/keymaster-recv-XcPQ60
new file mode 100644
index 0000000..3ce16c3
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-XcPQ60
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-ZU4x5D b/trusty/keymaster/fuzz/corpus/keymaster-recv-ZU4x5D
new file mode 100644
index 0000000..1641d95
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-ZU4x5D
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-Zbzv1t b/trusty/keymaster/fuzz/corpus/keymaster-recv-Zbzv1t
new file mode 100644
index 0000000..96b965e
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-Zbzv1t
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-ZvweQK b/trusty/keymaster/fuzz/corpus/keymaster-recv-ZvweQK
new file mode 100644
index 0000000..e3a04ae
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-ZvweQK
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-d3OcR1 b/trusty/keymaster/fuzz/corpus/keymaster-recv-d3OcR1
new file mode 100644
index 0000000..920c5ed
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-d3OcR1
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-dc6Hmg b/trusty/keymaster/fuzz/corpus/keymaster-recv-dc6Hmg
new file mode 100644
index 0000000..49453d4
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-dc6Hmg
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-fn8Ksu b/trusty/keymaster/fuzz/corpus/keymaster-recv-fn8Ksu
new file mode 100644
index 0000000..b0408c6
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-fn8Ksu
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-ldnX1U b/trusty/keymaster/fuzz/corpus/keymaster-recv-ldnX1U
new file mode 100644
index 0000000..8d77e0e
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-ldnX1U
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-pqvh4n b/trusty/keymaster/fuzz/corpus/keymaster-recv-pqvh4n
new file mode 100644
index 0000000..8ac5b90
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-pqvh4n
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-pvwjne b/trusty/keymaster/fuzz/corpus/keymaster-recv-pvwjne
new file mode 100644
index 0000000..22d5232
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-pvwjne
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-pzxe39 b/trusty/keymaster/fuzz/corpus/keymaster-recv-pzxe39
new file mode 100644
index 0000000..8e955c1
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-pzxe39
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-tpykrY b/trusty/keymaster/fuzz/corpus/keymaster-recv-tpykrY
new file mode 100644
index 0000000..16d2121
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-tpykrY
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-tq6MsH b/trusty/keymaster/fuzz/corpus/keymaster-recv-tq6MsH
new file mode 100644
index 0000000..d5d7f02
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-tq6MsH
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-recv-zt2UIA b/trusty/keymaster/fuzz/corpus/keymaster-recv-zt2UIA
new file mode 100644
index 0000000..23c3ce8
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-recv-zt2UIA
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-3aKtgr b/trusty/keymaster/fuzz/corpus/keymaster-send-3aKtgr
new file mode 100644
index 0000000..bfee18a
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-3aKtgr
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-5Ays9I b/trusty/keymaster/fuzz/corpus/keymaster-send-5Ays9I
new file mode 100644
index 0000000..3e446c5
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-5Ays9I
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-7X098Z b/trusty/keymaster/fuzz/corpus/keymaster-send-7X098Z
new file mode 100644
index 0000000..5b1c30b
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-7X098Z
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-B6LYU4 b/trusty/keymaster/fuzz/corpus/keymaster-send-B6LYU4
new file mode 100644
index 0000000..e841836
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-B6LYU4
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-BZU7LF b/trusty/keymaster/fuzz/corpus/keymaster-send-BZU7LF
new file mode 100644
index 0000000..72ba6c4
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-BZU7LF
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-FxXsxg b/trusty/keymaster/fuzz/corpus/keymaster-send-FxXsxg
new file mode 100644
index 0000000..5dfd4f8
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-FxXsxg
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-NlxYoC b/trusty/keymaster/fuzz/corpus/keymaster-send-NlxYoC
new file mode 100644
index 0000000..992e3f5
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-NlxYoC
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-PzXetK b/trusty/keymaster/fuzz/corpus/keymaster-send-PzXetK
new file mode 100644
index 0000000..18506f2
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-PzXetK
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-RFmR3D b/trusty/keymaster/fuzz/corpus/keymaster-send-RFmR3D
new file mode 100644
index 0000000..6845257
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-RFmR3D
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-Tp6AJW b/trusty/keymaster/fuzz/corpus/keymaster-send-Tp6AJW
new file mode 100644
index 0000000..90df6da
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-Tp6AJW
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-V0leT7 b/trusty/keymaster/fuzz/corpus/keymaster-send-V0leT7
new file mode 100644
index 0000000..79512e4
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-V0leT7
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-X4Plz3 b/trusty/keymaster/fuzz/corpus/keymaster-send-X4Plz3
new file mode 100644
index 0000000..1423e64
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-X4Plz3
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-Xd5KiX b/trusty/keymaster/fuzz/corpus/keymaster-send-Xd5KiX
new file mode 100644
index 0000000..18506f2
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-Xd5KiX
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-Ztr5Rk b/trusty/keymaster/fuzz/corpus/keymaster-send-Ztr5Rk
new file mode 100644
index 0000000..9af5af3
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-Ztr5Rk
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-f6d6wM b/trusty/keymaster/fuzz/corpus/keymaster-send-f6d6wM
new file mode 100644
index 0000000..e8f79be
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-f6d6wM
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-jbzgHv b/trusty/keymaster/fuzz/corpus/keymaster-send-jbzgHv
new file mode 100644
index 0000000..3ee5434
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-jbzgHv
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-jiL5yp b/trusty/keymaster/fuzz/corpus/keymaster-send-jiL5yp
new file mode 100644
index 0000000..90beb99
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-jiL5yp
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-l5kqxc b/trusty/keymaster/fuzz/corpus/keymaster-send-l5kqxc
new file mode 100644
index 0000000..b2f606d
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-l5kqxc
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-l6zX2y b/trusty/keymaster/fuzz/corpus/keymaster-send-l6zX2y
new file mode 100644
index 0000000..77705e7
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-l6zX2y
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-ltPKls b/trusty/keymaster/fuzz/corpus/keymaster-send-ltPKls
new file mode 100644
index 0000000..fb637aa
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-ltPKls
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-n7sdVP b/trusty/keymaster/fuzz/corpus/keymaster-send-n7sdVP
new file mode 100644
index 0000000..054a7ed
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-n7sdVP
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-pKSjkT b/trusty/keymaster/fuzz/corpus/keymaster-send-pKSjkT
new file mode 100644
index 0000000..3ed7246
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-pKSjkT
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-rhVedc b/trusty/keymaster/fuzz/corpus/keymaster-send-rhVedc
new file mode 100644
index 0000000..bd545f1
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-rhVedc
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-tZJ2Ex b/trusty/keymaster/fuzz/corpus/keymaster-send-tZJ2Ex
new file mode 100644
index 0000000..72ee499
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-tZJ2Ex
Binary files differ
diff --git a/trusty/keymaster/fuzz/corpus/keymaster-send-tZlTSQ b/trusty/keymaster/fuzz/corpus/keymaster-send-tZlTSQ
new file mode 100644
index 0000000..e841836
--- /dev/null
+++ b/trusty/keymaster/fuzz/corpus/keymaster-send-tZlTSQ
Binary files differ
diff --git a/trusty/keymaster/include/trusty_keymaster/TrustyKeymaster.h b/trusty/keymaster/include/trusty_keymaster/TrustyKeymaster.h
index 030b645..bec2a2a 100644
--- a/trusty/keymaster/include/trusty_keymaster/TrustyKeymaster.h
+++ b/trusty/keymaster/include/trusty_keymaster/TrustyKeymaster.h
@@ -59,6 +59,12 @@
     GetHmacSharingParametersResponse GetHmacSharingParameters();
     ComputeSharedHmacResponse ComputeSharedHmac(const ComputeSharedHmacRequest& request);
     VerifyAuthorizationResponse VerifyAuthorization(const VerifyAuthorizationRequest& request);
+    GetVersion2Response GetVersion2(const GetVersion2Request& request);
+
+    uint32_t message_version() const { return message_version_; }
+
+  private:
+    uint32_t message_version_;
 };
 
 }  // namespace keymaster
diff --git a/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h b/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
index ce2cc2e..419c96f 100644
--- a/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
+++ b/trusty/keymaster/include/trusty_keymaster/ipc/keymaster_ipc.h
@@ -53,6 +53,7 @@
     KM_DELETE_ALL_KEYS              = (23 << KEYMASTER_REQ_SHIFT),
     KM_DESTROY_ATTESTATION_IDS      = (24 << KEYMASTER_REQ_SHIFT),
     KM_IMPORT_WRAPPED_KEY           = (25 << KEYMASTER_REQ_SHIFT),
+    KM_GET_VERSION_2                = (28 << KEYMASTER_REQ_SHIFT),
 
     // Bootloader/provisioning calls.
     KM_SET_BOOT_PARAMS = (0x1000 << KEYMASTER_REQ_SHIFT),
diff --git a/trusty/keymaster/set_attestation_key/set_attestation_key.cpp b/trusty/keymaster/set_attestation_key/set_attestation_key.cpp
index 6f74833..df6b0f8 100644
--- a/trusty/keymaster/set_attestation_key/set_attestation_key.cpp
+++ b/trusty/keymaster/set_attestation_key/set_attestation_key.cpp
@@ -70,7 +70,7 @@
 }
 
 struct SetAttestationKeyRequest : public keymaster::KeymasterMessage {
-    explicit SetAttestationKeyRequest(int32_t ver = keymaster::MAX_MESSAGE_VERSION)
+    explicit SetAttestationKeyRequest(int32_t ver = keymaster::kDefaultMessageVersion)
         : KeymasterMessage(ver) {}
 
     size_t SerializedSize() const override { return sizeof(uint32_t) + key_data.SerializedSize(); }
@@ -88,7 +88,7 @@
 };
 
 struct KeymasterNoResponse : public keymaster::KeymasterResponse {
-    explicit KeymasterNoResponse(int32_t ver = keymaster::MAX_MESSAGE_VERSION)
+    explicit KeymasterNoResponse(int32_t ver = keymaster::kDefaultMessageVersion)
         : keymaster::KeymasterResponse(ver) {}
 
     size_t NonErrorSerializedSize() const override { return 0; }
@@ -99,7 +99,7 @@
 struct SetAttestationKeyResponse : public KeymasterNoResponse {};
 
 struct ClearAttestationCertChainRequest : public keymaster::KeymasterMessage {
-    explicit ClearAttestationCertChainRequest(int32_t ver = keymaster::MAX_MESSAGE_VERSION)
+    explicit ClearAttestationCertChainRequest(int32_t ver = keymaster::kDefaultMessageVersion)
         : KeymasterMessage(ver) {}
 
     size_t SerializedSize() const override { return sizeof(uint32_t); }
diff --git a/trusty/libtrusty/Android.bp b/trusty/libtrusty/Android.bp
index 8dba78d..086051d 100644
--- a/trusty/libtrusty/Android.bp
+++ b/trusty/libtrusty/Android.bp
@@ -12,10 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-cc_library {
-    name: "libtrusty",
-    vendor: true,
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
 
+cc_defaults {
+    name: "libtrusty_defaults",
     srcs: ["trusty.c"],
     export_include_dirs: ["include"],
     cflags: [
@@ -25,3 +27,11 @@
 
     shared_libs: ["liblog"],
 }
+
+cc_library {
+    name: "libtrusty",
+    // TODO(b/170753563): cc_fuzz can't deal with vendor components. Build
+    // libtrusty for system and vendor.
+    vendor_available: true,
+    defaults: ["libtrusty_defaults"],
+}
diff --git a/trusty/libtrusty/tipc-test/Android.bp b/trusty/libtrusty/tipc-test/Android.bp
index 9676b79..c7a8ae1 100644
--- a/trusty/libtrusty/tipc-test/Android.bp
+++ b/trusty/libtrusty/tipc-test/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_test {
     name: "tipc-test",
     vendor: true,
@@ -19,6 +23,7 @@
     srcs: ["tipc_test.c"],
     shared_libs: [
         "libc",
+        "libdmabufheap",
         "liblog",
         "libtrusty",
     ],
diff --git a/trusty/libtrusty/tipc-test/tipc_test.c b/trusty/libtrusty/tipc-test/tipc_test.c
index ca581dc..29c6f93 100644
--- a/trusty/libtrusty/tipc-test/tipc_test.c
+++ b/trusty/libtrusty/tipc-test/tipc_test.c
@@ -25,6 +25,8 @@
 #include <sys/mman.h>
 #include <sys/uio.h>
 
+#include <BufferAllocator/BufferAllocatorWrapper.h>
+
 #include <trusty/tipc.h>
 
 #define TIPC_DEFAULT_DEVNAME "/dev/trusty-ipc-dev0"
@@ -86,7 +88,7 @@
         "   ta-access    - test ta-access flags\n"
         "   writev       - writev test\n"
         "   readv        - readv test\n"
-        "   send-fd      - transmit memfd to trusty, use as shm\n"
+        "   send-fd      - transmit dma_buf to trusty, use as shm\n"
         "\n";
 
 static uint opt_repeat  = 1;
@@ -890,9 +892,12 @@
 
 static int send_fd_test(void) {
     int ret;
-    int memfd = -1;
+    int dma_buf = -1;
     int fd = -1;
     volatile char* buf = MAP_FAILED;
+    BufferAllocator* allocator = NULL;
+
+    const size_t num_pages = 10;
 
     fd = tipc_connect(dev_name, receiver_name);
     if (fd < 0) {
@@ -901,22 +906,24 @@
         goto cleanup;
     }
 
-    memfd = memfd_create("tipc-send-fd", 0);
-    if (memfd < 0) {
-        fprintf(stderr, "Failed to create memfd: %s\n", strerror(errno));
+    allocator = CreateDmabufHeapBufferAllocator();
+    if (!allocator) {
+        fprintf(stderr, "Failed to create dma-buf allocator.\n");
         ret = -1;
         goto cleanup;
     }
 
-    if (ftruncate(memfd, PAGE_SIZE) < 0) {
-        fprintf(stderr, "Failed to resize memfd: %s\n", strerror(errno));
-        ret = -1;
+    size_t buf_size = PAGE_SIZE * num_pages;
+    dma_buf = DmabufHeapAlloc(allocator, "system", buf_size, 0, 0 /* legacy align */);
+    if (dma_buf < 0) {
+        ret = dma_buf;
+        fprintf(stderr, "Failed to create dma-buf fd of size %zu err (%d)\n", buf_size, ret);
         goto cleanup;
     }
 
-    buf = mmap(0, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, memfd, 0);
+    buf = mmap(0, buf_size, PROT_READ | PROT_WRITE, MAP_SHARED, dma_buf, 0);
     if (buf == MAP_FAILED) {
-        fprintf(stderr, "Failed to map memfd: %s\n", strerror(errno));
+        fprintf(stderr, "Failed to map dma-buf: %s\n", strerror(errno));
         ret = -1;
         goto cleanup;
     }
@@ -924,13 +931,13 @@
     strcpy((char*)buf, "From NS");
 
     struct trusty_shm shm = {
-            .fd = memfd,
+            .fd = dma_buf,
             .transfer = TRUSTY_SHARE,
     };
 
     ssize_t rc = tipc_send(fd, NULL, 0, &shm, 1);
     if (rc < 0) {
-        fprintf(stderr, "tipc_send failed\n");
+        fprintf(stderr, "tipc_send failed: %zd\n", rc);
         ret = rc;
         goto cleanup;
     }
@@ -938,13 +945,19 @@
     read(fd, &c, 1);
     tipc_close(fd);
 
-    ret = strcmp("Hello from Trusty!", (const char*)buf) ? (-1) : 0;
+    ret = 0;
+    for (size_t skip = 0; skip < num_pages; skip++) {
+        ret |= strcmp("Hello from Trusty!", (const char*)&buf[skip * PAGE_SIZE]) ? (-1) : 0;
+    }
 
 cleanup:
     if (buf != MAP_FAILED) {
         munmap((char*)buf, PAGE_SIZE);
     }
-    close(memfd);
+    close(dma_buf);
+    if (allocator) {
+        FreeDmabufHeapBufferAllocator(allocator);
+    }
     tipc_close(fd);
     return ret;
 }
diff --git a/trusty/metrics/Android.bp b/trusty/metrics/Android.bp
new file mode 100644
index 0000000..e0533bc
--- /dev/null
+++ b/trusty/metrics/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 2021 The Android Open-Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+    name: "libtrusty_metrics",
+    vendor: true,
+    srcs: [
+        "metrics.cpp",
+    ],
+    export_include_dirs: [
+        "include",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+        "libtrusty",
+    ],
+}
+
+cc_test {
+    name: "libtrusty_metrics_test",
+    vendor: true,
+    srcs: [
+        "metrics_test.cpp",
+    ],
+    static_libs: [
+        "libtrusty_metrics",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "liblog",
+        "libtrusty",
+    ],
+    require_root: true,
+}
diff --git a/trusty/metrics/include/trusty/metrics/metrics.h b/trusty/metrics/include/trusty/metrics/metrics.h
new file mode 100644
index 0000000..6949e9b
--- /dev/null
+++ b/trusty/metrics/include/trusty/metrics/metrics.h
@@ -0,0 +1,57 @@
+/*
+ * 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 <functional>
+#include <memory>
+#include <string>
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::Result;
+using android::base::unique_fd;
+
+class TrustyMetrics {
+  public:
+    /* Wait for next event with a given timeout. Negative timeout means infinite timeout. */
+    Result<void> WaitForEvent(int timeout_ms = -1);
+    /* Attempt to handle an event from Metrics TA in a non-blocking manner. */
+    Result<void> HandleEvent();
+    /* Expose TIPC channel so that client can integrate it into an event loop with other fds. */
+    int GetRawFd() { return metrics_fd_; };
+
+  protected:
+    TrustyMetrics(std::string tipc_dev) : tipc_dev_(std::move(tipc_dev)), metrics_fd_(-1) {}
+    virtual ~TrustyMetrics(){};
+
+    Result<void> Open();
+    virtual void HandleCrash(const std::string& app_id) = 0;
+    virtual void HandleEventDrop() = 0;
+
+  private:
+    std::string tipc_dev_;
+    unique_fd metrics_fd_;
+};
+
+}  // namespace metrics
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/metrics/include/trusty/metrics/tipc.h b/trusty/metrics/include/trusty/metrics/tipc.h
new file mode 100644
index 0000000..66d0876
--- /dev/null
+++ b/trusty/metrics/include/trusty/metrics/tipc.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 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 <stdint.h>
+
+/**
+ * DOC: Metrics
+ *
+ * Metrics interface provides a way for Android to get Trusty metrics data.
+ *
+ * Currently, only "push" model is supported. Clients are expected to connect to
+ * metrics service, listen for events, e.g. app crash events, and respond to
+ * every event with a &struct metrics_req.
+ *
+ * Communication is driven by metrics service, i.e. requests/responses are all
+ * sent from/to metrics service.
+ *
+ * Note that the type of the event is not known to the client ahead of time.
+ *
+ * In the future, if we need to have Android "pull" metrics data from Trusty,
+ * that can be done by introducing a separate port.
+ *
+ * This interface is shared between Android and Trusty. There is a copy in each
+ * repository. They must be kept in sync.
+ */
+
+#define METRICS_PORT "com.android.trusty.metrics"
+
+/**
+ * enum metrics_cmd - command identifiers for metrics interface
+ * @METRICS_CMD_RESP_BIT:          message is a response
+ * @METRICS_CMD_REQ_SHIFT:         number of bits used by @METRICS_CMD_RESP_BIT
+ * @METRICS_CMD_REPORT_EVENT_DROP: report gaps in the event stream
+ * @METRICS_CMD_REPORT_CRASH:      report an app crash event
+ */
+enum metrics_cmd {
+    METRICS_CMD_RESP_BIT = 1,
+    METRICS_CMD_REQ_SHIFT = 1,
+
+    METRICS_CMD_REPORT_EVENT_DROP = (1 << METRICS_CMD_REQ_SHIFT),
+    METRICS_CMD_REPORT_CRASH = (2 << METRICS_CMD_REQ_SHIFT),
+};
+
+/**
+ * enum metrics_error - metrics error codes
+ * @METRICS_NO_ERROR:        no error
+ * @METRICS_ERR_UNKNOWN_CMD: unknown or not implemented command
+ */
+enum metrics_error {
+    METRICS_NO_ERROR = 0,
+    METRICS_ERR_UNKNOWN_CMD = 1,
+};
+
+/**
+ * struct metrics_req - common structure for metrics requests
+ * @cmd:      command identifier - one of &enum metrics_cmd
+ * @reserved: must be 0
+ */
+struct metrics_req {
+    uint32_t cmd;
+    uint32_t reserved;
+} __attribute__((__packed__));
+
+/**
+ * struct metrics_resp - common structure for metrics responses
+ * @cmd: command identifier - %METRICS_CMD_RESP_BIT or'ed with a cmd in
+ *                            one of &enum metrics_cmd
+ * @status: response status, one of &enum metrics_error
+ */
+struct metrics_resp {
+    uint32_t cmd;
+    uint32_t status;
+} __attribute__((__packed__));
+
+/**
+ * struct metrics_report_crash_req - arguments of %METRICS_CMD_REPORT_CRASH
+ *                                   requests
+ * @app_id_len: length of app ID that follows this structure
+ */
+struct metrics_report_crash_req {
+    uint32_t app_id_len;
+} __attribute__((__packed__));
+
+#define METRICS_MAX_APP_ID_LEN 256
+
+#define METRICS_MAX_MSG_SIZE                                                \
+    (sizeof(struct metrics_req) + sizeof(struct metrics_report_crash_req) + \
+     METRICS_MAX_APP_ID_LEN)
diff --git a/trusty/metrics/metrics.cpp b/trusty/metrics/metrics.cpp
new file mode 100644
index 0000000..3ac128a
--- /dev/null
+++ b/trusty/metrics/metrics.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 The Android Open Sourete 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_TAG "metrics"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <trusty/metrics/metrics.h>
+#include <trusty/metrics/tipc.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::ErrnoError;
+using android::base::Error;
+
+Result<void> TrustyMetrics::Open() {
+    int fd = tipc_connect(tipc_dev_.c_str(), METRICS_PORT);
+    if (fd < 0) {
+        return ErrnoError() << "failed to connect to Trusty metrics TA";
+    }
+
+    int flags = fcntl(fd, F_GETFL, 0);
+    if (flags < 0) {
+        return ErrnoError() << "failed F_GETFL";
+    }
+
+    int rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+    if (rc < 0) {
+        return ErrnoError() << "failed F_SETFL";
+    }
+
+    metrics_fd_.reset(fd);
+    return {};
+}
+
+Result<void> TrustyMetrics::WaitForEvent(int timeout_ms) {
+    if (!metrics_fd_.ok()) {
+        return Error() << "connection to Metrics TA has not been initialized yet";
+    }
+
+    struct pollfd pfd = {
+            .fd = metrics_fd_,
+            .events = POLLIN,
+    };
+
+    int rc = poll(&pfd, 1, timeout_ms);
+    if (rc != 1) {
+        return ErrnoError() << "failed poll()";
+    }
+
+    if (!(pfd.revents & POLLIN)) {
+        return ErrnoError() << "channel not ready";
+    }
+
+    return {};
+}
+
+Result<void> TrustyMetrics::HandleEvent() {
+    if (!metrics_fd_.ok()) {
+        return Error() << "connection to Metrics TA has not been initialized yet";
+    }
+
+    uint8_t msg[METRICS_MAX_MSG_SIZE];
+
+    auto rc = read(metrics_fd_, msg, sizeof(msg));
+    if (rc < 0) {
+        return ErrnoError() << "failed to read metrics message";
+    }
+    size_t msg_len = rc;
+
+    if (msg_len < sizeof(metrics_req)) {
+        return Error() << "message too small: " << rc;
+    }
+    auto req = reinterpret_cast<metrics_req*>(msg);
+    size_t offset = sizeof(metrics_req);
+    uint32_t status = METRICS_NO_ERROR;
+
+    switch (req->cmd) {
+        case METRICS_CMD_REPORT_CRASH: {
+            if (msg_len < offset + sizeof(metrics_report_crash_req)) {
+                return Error() << "message too small: " << rc;
+            }
+            auto crash_args = reinterpret_cast<metrics_report_crash_req*>(msg + offset);
+            offset += sizeof(metrics_report_crash_req);
+
+            if (msg_len < offset + crash_args->app_id_len) {
+                return Error() << "message too small: " << rc;
+            }
+            auto app_id_ptr = reinterpret_cast<char*>(msg + offset);
+            std::string app_id(app_id_ptr, crash_args->app_id_len);
+
+            HandleCrash(app_id);
+            break;
+        }
+
+        case METRICS_CMD_REPORT_EVENT_DROP:
+            HandleEventDrop();
+            break;
+
+        default:
+            status = METRICS_ERR_UNKNOWN_CMD;
+            break;
+    }
+
+    metrics_resp resp = {
+            .cmd = req->cmd | METRICS_CMD_RESP_BIT,
+            .status = status,
+    };
+
+    rc = write(metrics_fd_, &resp, sizeof(resp));
+    if (rc < 0) {
+        return ErrnoError() << "failed to request next metrics event";
+    }
+
+    if (rc != (int)sizeof(resp)) {
+        return Error() << "unexpected number of bytes sent event: " << rc;
+    }
+
+    return {};
+}
+
+}  // namespace metrics
+}  // namespace trusty
+}  // namespace android
diff --git a/trusty/metrics/metrics_test.cpp b/trusty/metrics/metrics_test.cpp
new file mode 100644
index 0000000..407ddf2
--- /dev/null
+++ b/trusty/metrics/metrics_test.cpp
@@ -0,0 +1,139 @@
+/*
+ * 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 <android-base/unique_fd.h>
+#include <binder/IPCThreadState.h>
+#include <gtest/gtest.h>
+#include <poll.h>
+#include <trusty/metrics/metrics.h>
+#include <trusty/tipc.h>
+
+#define TIPC_DEV "/dev/trusty-ipc-dev0"
+#define CRASHER_PORT "com.android.trusty.metrics.test.crasher"
+
+namespace android {
+namespace trusty {
+namespace metrics {
+
+using android::base::unique_fd;
+
+static void TriggerCrash() {
+    size_t num_retries = 3;
+    int fd = -1;
+
+    for (size_t i = 0; i < num_retries; i++) {
+        /* It's possible to time out waiting for crasher TA to restart. */
+        fd = tipc_connect(TIPC_DEV, CRASHER_PORT);
+        if (fd >= 0) {
+            break;
+        }
+    }
+
+    unique_fd crasher(fd);
+    ASSERT_GE(crasher, 0);
+
+    int msg = 0;
+    int rc = write(crasher, &msg, sizeof(msg));
+    ASSERT_EQ(rc, sizeof(msg));
+}
+
+class TrustyMetricsTest : public TrustyMetrics, public ::testing::Test {
+  public:
+    TrustyMetricsTest() : TrustyMetrics(TIPC_DEV) {}
+
+    virtual void HandleCrash(const std::string& app_id) override { crashed_app_ = app_id; }
+
+    virtual void HandleEventDrop() override { event_drop_count_++; }
+
+    virtual void SetUp() override {
+        auto ret = Open();
+        ASSERT_TRUE(ret.ok()) << ret.error();
+    }
+
+    void WaitForAndHandleEvent() {
+        auto ret = WaitForEvent(30000 /* 30 second timeout */);
+        ASSERT_TRUE(ret.ok()) << ret.error();
+
+        ret = HandleEvent();
+        ASSERT_TRUE(ret.ok()) << ret.error();
+    }
+
+    std::string crashed_app_;
+    size_t event_drop_count_;
+};
+
+TEST_F(TrustyMetricsTest, Crash) {
+    TriggerCrash();
+    WaitForAndHandleEvent();
+
+    /* Check that correct TA crashed. */
+    ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
+}
+
+TEST_F(TrustyMetricsTest, PollSet) {
+    int binder_fd;
+    int rc = IPCThreadState::self()->setupPolling(&binder_fd);
+    ASSERT_EQ(rc, 0);
+    ASSERT_GE(binder_fd, 0);
+
+    TriggerCrash();
+
+    struct pollfd pfds[] = {
+            {
+                    .fd = binder_fd,
+                    .events = POLLIN,
+            },
+            {
+                    .fd = GetRawFd(),
+                    .events = POLLIN,
+            },
+    };
+
+    rc = poll(pfds, 2, 30000 /* 30 second timeout */);
+    /* We expect one event on the metrics fd. */
+    ASSERT_EQ(rc, 1);
+    ASSERT_TRUE(pfds[1].revents & POLLIN);
+
+    auto ret = HandleEvent();
+    ASSERT_TRUE(ret.ok()) << ret.error();
+
+    /* Check that correct TA crashed. */
+    ASSERT_EQ(crashed_app_, "36f5b435-5bd3-4526-8b76-200e3a7e79f3:crasher");
+}
+
+TEST_F(TrustyMetricsTest, EventDrop) {
+    /* We know the size of the internal event queue is less than this. */
+    size_t num_events = 3;
+
+    ASSERT_EQ(event_drop_count_, 0);
+
+    for (auto i = 0; i < num_events; i++) {
+        TriggerCrash();
+    }
+
+    for (auto i = 0; i < num_events; i++) {
+        WaitForAndHandleEvent();
+        if (event_drop_count_ > 0) {
+            break;
+        }
+    }
+
+    ASSERT_EQ(event_drop_count_, 1);
+}
+
+}  // namespace metrics
+}  // namespace trusty
+}  // namespace android
diff --git a/deprecated-adf/libadf/tests/Android.bp b/trusty/secure_dpu/Android.bp
similarity index 70%
rename from deprecated-adf/libadf/tests/Android.bp
rename to trusty/secure_dpu/Android.bp
index 9b3430e..39dd0b9 100644
--- a/deprecated-adf/libadf/tests/Android.bp
+++ b/trusty/secure_dpu/Android.bp
@@ -1,5 +1,4 @@
-//
-// Copyright (C) 2013 The Android Open Source Project
+// 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.
@@ -12,12 +11,14 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-//
 
-cc_test {
-    name: "adf-unit-tests",
-    srcs: ["adf_test.cpp"],
-    shared_libs: ["libsync"],
-    static_libs: ["libadf"],
-    cflags: ["-Werror"],
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_headers {
+    name: "secure_dpu_headers",
+    vendor: true,
+
+    export_include_dirs: ["include"],
 }
diff --git a/trusty/secure_dpu/include/trusty/secure_dpu/SecureDpu.h b/trusty/secure_dpu/include/trusty/secure_dpu/SecureDpu.h
new file mode 100644
index 0000000..b939d44
--- /dev/null
+++ b/trusty/secure_dpu/include/trusty/secure_dpu/SecureDpu.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+/**
+ * DOC: Secure DPU
+ *
+ * The Secure DPU works as the persistent channel between the non-secure and the
+ * secure world. The channel is established during the boot up stage of the
+ * non-secure world system. In general, the established channel allows the
+ * secure world applications initiate requests or notifications to the non-secure
+ * world.
+ *
+ * For particular devices, the secure world can only perform operations on the
+ * display when in the TUI session if device-specific setup is done by the
+ * non-secure world. Besides, the non-secure world could allocate framebuffer
+ * for the secure world application if the memory is limited in the secure world
+ * on specific devices.
+ *
+ * Currently, supported requests are to start / stop the secure display mode and
+ * to allocate framebuffer.
+ *
+ * This header file needs to be synced on both the Trusty and the Android
+ * codebase.
+ */
+
+#define SECURE_DPU_PORT_NAME "com.android.trusty.secure_dpu"
+#define SECURE_DPU_MAX_MSG_SIZE 64
+
+/**
+ * enum secure_dpu_cmd - command identifiers for secure_fb interface
+ * @SECURE_DPU_CMD_RESP_BIT:
+ *      Message is a response.
+ * @SECURE_DPU_CMD_REQ_SHIFT:
+ *      Number of bits used by @SECURE_DPU_CMD_RESP_BIT.
+ * @SECURE_DPU_CMD_START_SECURE_DISPLAY:
+ *      Notify the system to start secure display mode
+ * @SECURE_DPU_CMD_STOP_SECURE_DISPLAY:
+ *      Notify the system to stop secure display mode
+ * @SECURE_DPU_CMD_ALLOCATE_BUFFER:
+ *      Request non-secure world to allocate the buffer
+ */
+enum secure_dpu_cmd {
+    SECURE_DPU_CMD_RESP_BIT = 1,
+    SECURE_DPU_CMD_REQ_SHIFT = 1,
+    SECURE_DPU_CMD_START_SECURE_DISPLAY = (1 << SECURE_DPU_CMD_REQ_SHIFT),
+    SECURE_DPU_CMD_STOP_SECURE_DISPLAY = (2 << SECURE_DPU_CMD_REQ_SHIFT),
+    SECURE_DPU_CMD_ALLOCATE_BUFFER = (3 << SECURE_DPU_CMD_REQ_SHIFT),
+};
+
+/**
+ * struct secure_dpu_allocate_buffer_req - payload for
+ *                                         %SECURE_DPU_CMD_ALLOCATE_BUFFER
+ *                                         request
+ * @buffer_len: Requested length
+ */
+struct secure_dpu_allocate_buffer_req {
+    uint64_t buffer_len;
+};
+
+/**
+ * struct secure_dpu_allocate_buffer_resp - payload for
+ *                                          %SECURE_DPU_CMD_ALLOCATE_BUFFER
+ *                                          response
+ * @buffer_len: Allocated length
+ */
+struct secure_dpu_allocate_buffer_resp {
+    uint64_t buffer_len;
+};
+
+/**
+ * struct secure_fb_req - common structure for secure_fb requests.
+ * @cmd: Command identifier - one of &enum secure_dpu_cmd.
+ */
+struct secure_dpu_req {
+    uint32_t cmd;
+};
+
+/**
+ * struct secure_dpu_resp - common structure for secure_dpu responses.
+ * @cmd:    Command identifier - %SECURE_DPU_CMD_RESP_BIT or'ed with the
+ *                               command identifier of the corresponding
+ *                               request.
+ * @status: Status of requested operation. One of &enum secure_dpu_error.
+ */
+struct secure_dpu_resp {
+    uint32_t cmd;
+    int32_t status;
+};
+
+enum secure_dpu_error {
+    SECURE_DPU_ERROR_OK = 0,
+    SECURE_DPU_ERROR_FAIL = -1,
+    SECURE_DPU_ERROR_UNINITIALIZED = -2,
+    SECURE_DPU_ERROR_PARAMETERS = -3,
+    SECURE_DPU_ERROR_NO_MEMORY = -4,
+};
diff --git a/trusty/storage/interface/Android.bp b/trusty/storage/interface/Android.bp
index 18b4a5f..d031b0c 100644
--- a/trusty/storage/interface/Android.bp
+++ b/trusty/storage/interface/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_static {
     name: "libtrustystorageinterface",
     vendor: true,
diff --git a/trusty/storage/lib/Android.bp b/trusty/storage/lib/Android.bp
index 2fba17e..f28308b 100644
--- a/trusty/storage/lib/Android.bp
+++ b/trusty/storage/lib/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_library_static {
     name: "libtrustystorage",
     vendor: true,
diff --git a/trusty/storage/proxy/Android.bp b/trusty/storage/proxy/Android.bp
index b93facb..a471435 100644
--- a/trusty/storage/proxy/Android.bp
+++ b/trusty/storage/proxy/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "storageproxyd",
     vendor: true,
diff --git a/trusty/storage/tests/Android.bp b/trusty/storage/tests/Android.bp
index 536c3ca..891f543 100644
--- a/trusty/storage/tests/Android.bp
+++ b/trusty/storage/tests/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_test {
     name: "secure-storage-unit-test",
     vendor: true,
diff --git a/trusty/trusty-base.mk b/trusty/trusty-base.mk
index fd8daa8..12521b0 100644
--- a/trusty/trusty-base.mk
+++ b/trusty/trusty-base.mk
@@ -24,7 +24,8 @@
 
 PRODUCT_PACKAGES += \
 	android.hardware.keymaster@4.0-service.trusty \
-	android.hardware.gatekeeper@1.0-service.trusty
+	android.hardware.gatekeeper@1.0-service.trusty \
+	trusty_apploader
 
 PRODUCT_PROPERTY_OVERRIDES += \
 	ro.hardware.keystore=trusty \
diff --git a/trusty/trusty-test.mk b/trusty/trusty-test.mk
index dc4c962..74106ec 100644
--- a/trusty/trusty-test.mk
+++ b/trusty/trusty-test.mk
@@ -15,4 +15,5 @@
 PRODUCT_PACKAGES += \
 	spiproxyd \
 	trusty_keymaster_set_attestation_key \
-	keymaster_soft_attestation_keys.xml \
\ No newline at end of file
+	keymaster_soft_attestation_keys.xml \
+
diff --git a/trusty/utils/acvp/Android.bp b/trusty/utils/acvp/Android.bp
new file mode 100644
index 0000000..b851e39
--- /dev/null
+++ b/trusty/utils/acvp/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at //
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+    name: "trusty_acvp_modulewrapper",
+    vendor: true,
+
+    srcs: [
+        "trusty_modulewrapper.cpp",
+    ],
+    static_libs: [
+        "libacvp_modulewrapper",
+    ],
+    shared_libs: [
+        "libbase",
+        "libc",
+        "libdmabufheap",
+        "liblog",
+        "libtrusty",
+        "libssl",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+}
diff --git a/trusty/utils/acvp/acvp_ipc.h b/trusty/utils/acvp/acvp_ipc.h
new file mode 100644
index 0000000..8b48ae3
--- /dev/null
+++ b/trusty/utils/acvp/acvp_ipc.h
@@ -0,0 +1,77 @@
+/*
+ * 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 <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ACVP_PORT "com.android.trusty.acvp"
+
+/*
+ * Maximum number of arguments
+ */
+#define ACVP_MAX_NUM_ARGUMENTS 8
+
+/*
+ * Maximum length of an algorithm name
+ */
+#define ACVP_MAX_NAME_LENGTH 30
+
+/*
+ * Maximum length of an ACVP request message
+ */
+#define ACVP_MAX_MESSAGE_LENGTH sizeof(struct acvp_req)
+
+/*
+ * Minimum length of the shared memory buffer
+ *
+ * This must be at least as long as the longest reply from the ACVP service
+ * (currently the reply from getConfig()).
+ */
+#define ACVP_MIN_SHARED_MEMORY 16384
+
+/**
+ * acvp_req - Request for the Trusty ACVP app
+ * @num_args: Number of acvp_arg structures following this struct
+ * @buffer_size: Total size of shared memory buffer
+ * @lengths: Length of each argument in the shared memory buffer
+ *
+ * @num_args copies of the acvp_arg struct follow this structure.
+ */
+struct acvp_req {
+    uint32_t num_args;
+    uint32_t buffer_size;
+    uint32_t lengths[ACVP_MAX_NUM_ARGUMENTS];
+};
+
+/**
+ * acvp_resp - Response to a ACVP request
+ *
+ * @num_spans: Number of response sections
+ * @lengths: Length of each response section
+ */
+struct acvp_resp {
+    uint32_t num_spans;
+    uint32_t lengths[ACVP_MAX_NUM_ARGUMENTS];
+};
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
diff --git a/trusty/utils/acvp/trusty_modulewrapper.cpp b/trusty/utils/acvp/trusty_modulewrapper.cpp
new file mode 100644
index 0000000..70ffb52
--- /dev/null
+++ b/trusty/utils/acvp/trusty_modulewrapper.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "TrustyAcvpModulewrapper"
+
+#include <BufferAllocator/BufferAllocator.h>
+#include <android-base/file.h>
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+#include <errno.h>
+#include <log/log.h>
+#include <modulewrapper.h>
+#include <openssl/span.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+#include <iostream>
+
+#include "acvp_ipc.h"
+
+constexpr const char kTrustyDeviceName[] = "/dev/trusty-ipc-dev0";
+
+using android::base::ErrnoError;
+using android::base::Error;
+using android::base::Result;
+using android::base::unique_fd;
+using android::base::WriteFully;
+
+static inline size_t AlignUpToPage(size_t size) {
+    return (size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
+}
+
+namespace {
+
+class ModuleWrapper {
+  private:
+    static const char* kAcvpPort_;
+    static const char* kTrustyDeviceName_;
+
+  public:
+    ModuleWrapper();
+    ~ModuleWrapper();
+
+    Result<void> SendMessage(bssl::Span<const bssl::Span<const uint8_t>>);
+
+    Result<void> ForwardResponse();
+
+  private:
+    // Connection to the Trusty ACVP service
+    int tipc_fd_ = -1;
+
+    // Shared memory DMA buf
+    unique_fd dmabuf_fd_;
+
+    // Size of shared memory mapping
+    size_t shm_size_ = 0;
+
+    // Shared memory mapping
+    uint8_t* shm_buffer_ = nullptr;
+};
+
+}  // namespace
+
+const char* ModuleWrapper::kAcvpPort_ = ACVP_PORT;
+const char* ModuleWrapper::kTrustyDeviceName_ = kTrustyDeviceName;
+
+ModuleWrapper::ModuleWrapper() {
+    tipc_fd_ = tipc_connect(kTrustyDeviceName_, kAcvpPort_);
+    if (tipc_fd_ < 0) {
+        fprintf(stderr, "Failed to connect to Trusty ACVP test app: %s\n", strerror(-tipc_fd_));
+    }
+}
+
+ModuleWrapper::~ModuleWrapper() {
+    if (tipc_fd_ >= 0) {
+        tipc_close(tipc_fd_);
+    }
+
+    if (shm_buffer_) {
+        munmap(shm_buffer_, shm_size_);
+    }
+}
+
+Result<void> ModuleWrapper::SendMessage(bssl::Span<const bssl::Span<const uint8_t>> args) {
+    assert(args.size() < ACVP_MAX_NUM_ARGUMENTS);
+    assert(args[0].size() < ACVP_MAX_NAME_LENGTH);
+
+    struct acvp_req request;
+    request.num_args = args.size();
+
+    size_t total_args_size = 0;
+    for (auto arg : args) {
+        total_args_size += arg.size();
+    }
+
+    shm_size_ = ACVP_MIN_SHARED_MEMORY;
+    if (total_args_size > shm_size_) {
+        shm_size_ = AlignUpToPage(total_args_size);
+    }
+    request.buffer_size = shm_size_;
+
+    struct iovec iov = {
+            .iov_base = &request,
+            .iov_len = sizeof(struct acvp_req),
+    };
+
+    BufferAllocator alloc;
+    dmabuf_fd_.reset(alloc.Alloc(kDmabufSystemHeapName, shm_size_));
+    if (!dmabuf_fd_.ok()) {
+        return ErrnoError() << "Error creating dmabuf";
+    }
+
+    shm_buffer_ = (uint8_t*)mmap(0, shm_size_, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf_fd_, 0);
+    if (shm_buffer_ == MAP_FAILED) {
+        return ErrnoError() << "Failed to map shared memory dmabuf";
+    }
+
+    size_t cur_offset = 0;
+    for (int i = 0; i < args.size(); ++i) {
+        request.lengths[i] = args[i].size();
+        memcpy(shm_buffer_ + cur_offset, args[i].data(), args[i].size());
+        cur_offset += args[i].size();
+    }
+
+    struct trusty_shm shm = {
+            .fd = dmabuf_fd_.get(),
+            .transfer = TRUSTY_SHARE,
+    };
+
+    int rc = tipc_send(tipc_fd_, &iov, 1, &shm, 1);
+    if (rc != sizeof(struct acvp_req)) {
+        return ErrnoError() << "Failed to send request to Trusty ACVP service";
+    }
+
+    return {};
+}
+
+Result<void> ModuleWrapper::ForwardResponse() {
+    struct acvp_resp resp;
+    int bytes_read = read(tipc_fd_, &resp, sizeof(struct acvp_resp));
+    if (bytes_read < 0) {
+        return ErrnoError() << "Failed to read response from Trusty ACVP service";
+    }
+
+    if (bytes_read != sizeof(struct acvp_resp)) {
+        return Error() << "Trusty ACVP response overflowed expected size";
+    }
+
+    size_t total_args_size = 0;
+    for (size_t i = 0; i < resp.num_spans; i++) {
+        total_args_size += resp.lengths[i];
+    }
+
+    iovec iovs[2];
+    iovs[0].iov_base = &resp;
+    iovs[0].iov_len = sizeof(uint32_t) * (1 + resp.num_spans);
+
+    iovs[1].iov_base = shm_buffer_;
+    iovs[1].iov_len = total_args_size;
+
+    size_t iov_done = 0;
+    while (iov_done < 2) {
+        ssize_t r;
+        do {
+            r = writev(STDOUT_FILENO, &iovs[iov_done], 2 - iov_done);
+        } while (r == -1 && errno == EINTR);
+
+        if (r <= 0) {
+            return Error() << "Failed to write ACVP response to standard out";
+        }
+
+        size_t written = r;
+        for (size_t i = iov_done; i < 2 && written > 0; i++) {
+            iovec& iov = iovs[i];
+
+            size_t done = written;
+            if (done > iov.iov_len) {
+                done = iov.iov_len;
+            }
+
+            iov.iov_base = reinterpret_cast<uint8_t*>(iov.iov_base) + done;
+            iov.iov_len -= done;
+            written -= done;
+
+            if (iov.iov_len == 0) {
+                iov_done++;
+            }
+        }
+
+        assert(written == 0);
+    }
+
+    return {};
+}
+
+int main() {
+    for (;;) {
+        auto buffer = bssl::acvp::RequestBuffer::New();
+        auto args = bssl::acvp::ParseArgsFromFd(STDIN_FILENO, buffer.get());
+        if (args.empty()) {
+            ALOGE("Could not parse arguments\n");
+            return EXIT_FAILURE;
+        }
+
+        ModuleWrapper wrapper;
+        auto res = wrapper.SendMessage(args);
+        if (!res.ok()) {
+            std::cerr << res.error() << std::endl;
+            return EXIT_FAILURE;
+        }
+
+        res = wrapper.ForwardResponse();
+        if (!res.ok()) {
+            std::cerr << res.error() << std::endl;
+            return EXIT_FAILURE;
+        }
+    }
+
+    return EXIT_SUCCESS;
+};
diff --git a/trusty/utils/rpmb_dev/Android.bp b/trusty/utils/rpmb_dev/Android.bp
index e923e82..c5853ef 100644
--- a/trusty/utils/rpmb_dev/Android.bp
+++ b/trusty/utils/rpmb_dev/Android.bp
@@ -11,6 +11,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "system_core_trusty_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    //   SPDX-license-identifier-MIT
+    default_applicable_licenses: ["system_core_trusty_license"],
+}
+
 cc_binary {
     name: "rpmb_dev",
     vendor: true,
diff --git a/trusty/utils/spiproxyd/Android.bp b/trusty/utils/spiproxyd/Android.bp
index c1d0987..81f3e73 100644
--- a/trusty/utils/spiproxyd/Android.bp
+++ b/trusty/utils/spiproxyd/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "spiproxyd",
     vendor: true,
diff --git a/trusty/utils/trusty-ut-ctrl/Android.bp b/trusty/utils/trusty-ut-ctrl/Android.bp
index 9c8af7b..6fc2a48 100644
--- a/trusty/utils/trusty-ut-ctrl/Android.bp
+++ b/trusty/utils/trusty-ut-ctrl/Android.bp
@@ -12,7 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-cc_test {
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
     name: "trusty-ut-ctrl",
     vendor: true,
 
@@ -24,7 +28,6 @@
     static_libs: [
         "libtrusty",
     ],
-    gtest: false,
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/usbd/Android.bp b/usbd/Android.bp
index 6a339a1..22d171d 100644
--- a/usbd/Android.bp
+++ b/usbd/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "usbd",
     init_rc: ["usbd.rc"],
diff --git a/watchdogd/Android.bp b/watchdogd/Android.bp
index 0fbc33c..0388208 100644
--- a/watchdogd/Android.bp
+++ b/watchdogd/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_binary {
     name: "watchdogd",
     recovery_available: true,