Merge "adb: add helper binary to do a usb reset on a device."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 44c47f3..9b6213a 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -67,5 +67,11 @@
     {
       "name": "ziparchive-tests"
     }
+  ],
+
+  "postsubmit": [
+    {
+      "name": "ziparchive_tests_large"
+    }
   ]
 }
diff --git a/adb/Android.bp b/adb/Android.bp
index d8fa713..19e921f 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -114,6 +114,46 @@
     },
 }
 
+cc_defaults {
+    name: "libadbd_binary_dependencies",
+    static_libs: [
+        "libadb_crypto",
+        "libadb_pairing_connection",
+        "libadb_tls_connection",
+        "libadbd",
+        "libadbd_core",
+        "libadbconnection_server",
+        "libasyncio",
+        "libbrotli",
+        "libcutils_sockets",
+        "libdiagnose_usb",
+        "libmdnssd",
+        "libbase",
+
+        "libadb_protos",
+        "libapp_processes_protos_lite",
+        "libprotobuf-cpp-lite",
+    ],
+
+    shared_libs: [
+        "libadbd_auth",
+        "libadbd_fs",
+        "libcrypto",
+        "libcrypto_utils",
+        "liblog",
+        "libselinux",
+    ],
+
+    target: {
+        recovery: {
+            exclude_static_libs: [
+                "libadb_pairing_auth",
+                "libadb_pairing_connection",
+            ],
+        },
+    },
+}
+
 // libadb
 // =========================================================
 // These files are compiled for both the host and the device.
@@ -133,7 +173,6 @@
     "transport.cpp",
     "transport_fd.cpp",
     "transport_local.cpp",
-    "transport_usb.cpp",
     "types.cpp",
 ]
 
@@ -169,6 +208,7 @@
         "client/usb_libusb.cpp",
         "client/usb_dispatch.cpp",
         "client/transport_mdns.cpp",
+        "client/transport_usb.cpp",
         "client/pairing/pairing_client.cpp",
     ],
 
@@ -246,39 +286,6 @@
     },
 }
 
-cc_benchmark {
-    name: "adb_benchmark",
-    defaults: ["adb_defaults"],
-
-    srcs: ["transport_benchmark.cpp"],
-    target: {
-        android: {
-            static_libs: [
-                "libadbd",
-            ],
-        },
-        host: {
-            static_libs: [
-                "libadb_host",
-            ],
-        },
-    },
-
-    static_libs: [
-        "libadb_crypto_static",
-        "libadb_tls_connection_static",
-        "libadbd_auth",
-        "libbase",
-        "libcutils",
-        "libcrypto_utils",
-        "libcrypto_static",
-        "libdiagnose_usb",
-        "liblog",
-        "libssl",
-        "libusb",
-    ],
-}
-
 cc_binary_host {
     name: "adb",
 
@@ -298,6 +305,7 @@
         "client/fastdeploycallbacks.cpp",
         "client/incremental.cpp",
         "client/incremental_server.cpp",
+        "client/incremental_utils.cpp",
         "shell_service_protocol.cpp",
     ],
 
@@ -309,13 +317,14 @@
     static_libs: [
         "libadb_crypto",
         "libadb_host",
-	"libadb_pairing_auth",
-	"libadb_pairing_connection",
+        "libadb_pairing_auth",
+        "libadb_pairing_connection",
         "libadb_protos",
         "libadb_tls_connection",
         "libandroidfw",
         "libapp_processes_protos_full",
         "libbase",
+        "libbrotli",
         "libcutils",
         "libcrypto_utils",
         "libcrypto",
@@ -380,10 +389,6 @@
         "daemon/adb_wifi.cpp",
     ],
 
-    local_include_dirs: [
-        "daemon/include",
-    ],
-
     generated_headers: ["platform_tools_version"],
 
     static_libs: [
@@ -402,6 +407,7 @@
         "libbase",
         "libcrypto",
         "libcrypto_utils",
+        "libcutils_sockets",
         "liblog",
     ],
 
@@ -420,12 +426,6 @@
                 "daemon/transport_qemu.cpp",
                 "daemon/usb.cpp",
                 "daemon/usb_ffs.cpp",
-                "daemon/usb_legacy.cpp",
-            ]
-        },
-        linux_glibc: {
-            srcs: [
-                "daemon/usb_dummy.cpp",
             ]
         },
         recovery: {
@@ -436,6 +436,15 @@
             ],
         }
     },
+
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.adbd",
+    ],
+    visibility: [
+        "//bootable/recovery/minadbd",
+        "//system/core/adb",
+    ],
 }
 
 cc_library {
@@ -459,7 +468,9 @@
     static_libs: [
         "libadbconnection_server",
         "libadbd_core",
+        "libbrotli",
         "libdiagnose_usb",
+        "liblz4",
     ],
 
     shared_libs: [
@@ -467,16 +478,18 @@
         "libadb_pairing_connection",
         "libadb_protos",
         "libadb_tls_connection",
-        "libadbd_auth",
-        "libadbd_fs",
         "libapp_processes_protos_lite",
         "libasyncio",
         "libbase",
-        "libcrypto",
         "libcrypto_utils",
         "libcutils_sockets",
-        "liblog",
         "libprotobuf-cpp-lite",
+
+        // APEX dependencies.
+        "libadbd_auth",
+        "libadbd_fs",
+        "libcrypto",
+        "liblog",
     ],
 
     target: {
@@ -502,12 +515,22 @@
             ],
         },
     },
+
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.adbd",
+    ],
+    visibility: [
+        "//system/core/adb",
+    ],
+
 }
 
 cc_library {
     name: "libadbd",
     defaults: ["adbd_defaults", "host_adbd_supported"],
     recovery_available: true,
+    apex_available: ["com.android.adbd"],
 
     // avoid getting duplicate symbol of android::build::getbuildnumber().
     use_version_lib: false,
@@ -515,10 +538,6 @@
     // libminadbd wants both, as it's used to build native tests.
     compile_multilib: "both",
 
-    whole_static_libs: [
-        "libadbd_core",
-    ],
-
     shared_libs: [
         "libadbconnection_server",
         "libapp_processes_protos_lite",
@@ -526,15 +545,17 @@
         "libadb_crypto",
         "libadb_pairing_connection",
         "libadb_tls_connection",
-        "libadbd_auth",
-        "libadbd_fs",
-        "libadbd_services",
         "libasyncio",
         "libbase",
         "libcrypto",
         "libcrypto_utils",
         "liblog",
         "libselinux",
+
+        // APEX dependencies on the system image.
+        "libadbd_auth",
+        "libadbd_fs",
+        "libadbd_services",
     ],
 
     target: {
@@ -547,21 +568,26 @@
     },
 
     static_libs: [
+        "libadbd_core",
+        "libbrotli",
         "libcutils_sockets",
         "libdiagnose_usb",
+        "liblz4",
         "libmdnssd",
     ],
 
-    export_include_dirs: [
-        "daemon/include",
+    visibility: [
+        "//bootable/recovery/minadbd",
+        "//system/core/adb",
     ],
 }
 
 cc_binary {
     name: "adbd",
-    defaults: ["adbd_defaults", "host_adbd_supported"],
+    defaults: ["adbd_defaults", "host_adbd_supported", "libadbd_binary_dependencies"],
     stl: "libc++_static",
     recovery_available: true,
+    apex_available: ["com.android.adbd"],
 
     srcs: [
         "daemon/main.cpp",
@@ -577,32 +603,18 @@
     },
 
     static_libs: [
-        "libadb_crypto",
-        "libadb_tls_connection",
-        "libadbconnection_server",
         "libadbd",
         "libadbd_services",
-        "libapp_processes_protos_lite",
         "libasyncio",
-        "libbase",
         "libcap",
-        "libcrypto_utils",
-        "libcutils_sockets",
-        "libdiagnose_usb",
-        "libmdnssd",
+        "liblz4",
         "libminijail",
-        "libprotobuf-cpp-lite",
         "libssl",
     ],
 
     shared_libs: [
-        "libadb_pairing_connection",
         "libadb_protos",
         "libadbd_auth",
-        "libadbd_fs",
-        "libcrypto",
-        "liblog",
-        "libselinux",
     ],
 
     target: {
@@ -674,8 +686,7 @@
 cc_test {
     name: "adbd_test",
 
-    defaults: ["adbd_defaults"],
-    stl: "libc++_static",
+    defaults: ["adbd_defaults", "libadbd_binary_dependencies"],
 
     recovery_available: false,
     srcs: libadb_test_srcs + [
@@ -684,23 +695,19 @@
         "daemon/shell_service_test.cpp",
         "shell_service_protocol.cpp",
         "shell_service_protocol_test.cpp",
+        "mdns_test.cpp",
+    ],
+
+    shared_libs: [
+        "liblog",
     ],
 
     static_libs: [
         "libadbd",
         "libadbd_auth",
-        "libadb_crypto_static",
-        "libadb_pairing_connection_static",
-        "libadb_tls_connection_static",
         "libbase",
         "libcrypto_utils",
-        "libcrypto_static",
-        "libcutils_sockets",
-        "libdiagnose_usb",
-        "liblog",
         "libusb",
-        "libmdnssd",
-        "libselinux",
     ],
     test_suites: ["device-tests", "mts"],
     require_root: true,
diff --git a/adb/adb.cpp b/adb/adb.cpp
index 98db191..06fdb69 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -66,6 +66,10 @@
 #include "daemon/logging.h"
 #endif
 
+#if ADB_HOST
+#include "client/usb.h"
+#endif
+
 std::string adb_version() {
     // Don't change the format of this --- it's parsed by ddmlib.
     return android::base::StringPrintf(
@@ -145,7 +149,7 @@
     case A_WRTE: tag = "WRTE"; break;
     case A_AUTH: tag = "AUTH"; break;
     case A_STLS:
-        tag = "ATLS";
+        tag = "STLS";
         break;
     default: tag = "????"; break;
     }
@@ -1016,8 +1020,12 @@
         if (kill_forward) {
             r = remove_listener(pieces[0].c_str(), transport);
         } else {
-            r = install_listener(pieces[0], pieces[1].c_str(), transport, no_rebind,
-                                 &resolved_tcp_port, &error);
+            int flags = 0;
+            if (no_rebind) {
+                flags |= INSTALL_LISTENER_NO_REBIND;
+            }
+            r = install_listener(pieces[0], pieces[1].c_str(), transport, flags, &resolved_tcp_port,
+                                 &error);
         }
         if (r == INSTALL_STATUS_OK) {
 #if ADB_HOST
@@ -1063,19 +1071,25 @@
     return 0;
 }
 
+static bool g_reject_kill_server = false;
+void adb_set_reject_kill_server(bool value) {
+    g_reject_kill_server = value;
+}
+
 HostRequestResult handle_host_request(std::string_view service, TransportType type,
                                       const char* serial, TransportId transport_id, int reply_fd,
                                       asocket* s) {
     if (service == "kill") {
-        fprintf(stderr, "adb server killed by remote request\n");
-        fflush(stdout);
+        if (g_reject_kill_server) {
+            LOG(WARNING) << "adb server ignoring kill-server";
+            SendFail(reply_fd, "kill-server rejected by remote server");
+        } else {
+            fprintf(stderr, "adb server killed by remote request\n");
+            SendOkay(reply_fd);
 
-        // Send a reply even though we don't read it anymore, so that old versions
-        // of adb that do read it don't spew error messages.
-        SendOkay(reply_fd);
-
-        // Rely on process exit to close the socket for us.
-        exit(0);
+            // Rely on process exit to close the socket for us.
+            exit(0);
+        }
     }
 
     LOG(DEBUG) << "handle_host_request(" << service << ")";
@@ -1184,9 +1198,9 @@
         FeatureSet features = supported_features();
         // Abuse features to report libusb status.
         if (should_use_libusb()) {
-            features.insert(kFeatureLibusb);
+            features.emplace_back(kFeatureLibusb);
         }
-        features.insert(kFeaturePushSync);
+        features.emplace_back(kFeaturePushSync);
         SendOkay(reply_fd, FeatureSetToString(features));
         return HostRequestResult::Handled;
     }
diff --git a/adb/adb.h b/adb/adb.h
index 9f8d299..476ed9b 100644
--- a/adb/adb.h
+++ b/adb/adb.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef __ADB_H
-#define __ADB_H
+#pragma once
 
 #include <limits.h>
 #include <stdint.h>
@@ -29,7 +28,6 @@
 #include "fdevent/fdevent.h"
 #include "socket.h"
 #include "types.h"
-#include "usb.h"
 
 constexpr size_t MAX_PAYLOAD_V1 = 4 * 1024;
 constexpr size_t MAX_PAYLOAD = 1024 * 1024;
@@ -139,7 +137,6 @@
 
 /* initialize a transport object's func pointers and state */
 int init_socket_transport(atransport* t, unique_fd s, int port, int local);
-void init_usb_transport(atransport* t, usb_handle* usb);
 
 std::string getEmulatorSerialString(int console_port);
 #if ADB_HOST
@@ -239,6 +236,7 @@
 
 void parse_banner(const std::string&, atransport* t);
 
+#if ADB_HOST
 // On startup, the adb server needs to wait until all of the connected devices are ready.
 // To do this, we need to know when the scan has identified all of the potential new transports, and
 // when each transport becomes ready.
@@ -252,5 +250,12 @@
 
 // Wait until device scan has completed and every transport is ready, or a timeout elapses.
 void adb_wait_for_device_initialization();
+#endif  // ADB_HOST
 
+#if ADB_HOST
+// When ssh-forwarding to a remote adb server, kill-server is almost never what you actually want,
+// and unfortunately, many other tools issue it. This adds a knob to reject kill-servers.
+void adb_set_reject_kill_server(bool reject);
 #endif
+
+void usb_init();
diff --git a/adb/adb_listeners.cpp b/adb/adb_listeners.cpp
index 29909a5..43a9252 100644
--- a/adb/adb_listeners.cpp
+++ b/adb/adb_listeners.cpp
@@ -164,6 +164,15 @@
     }
 }
 
+void enable_daemon_sockets() EXCLUDES(listener_list_mutex) {
+    std::lock_guard<std::mutex> lock(listener_list_mutex);
+    for (auto& l : listener_list) {
+        if (l->connect_to == "*smartsocket*") {
+            fdevent_set(l->fde, FDE_READ);
+        }
+    }
+}
+
 void close_smartsockets() EXCLUDES(listener_list_mutex) {
     std::lock_guard<std::mutex> lock(listener_list_mutex);
     auto pred = [](const std::unique_ptr<alistener>& listener) {
@@ -173,7 +182,7 @@
 }
 
 InstallStatus install_listener(const std::string& local_name, const char* connect_to,
-                               atransport* transport, int no_rebind, int* resolved_tcp_port,
+                               atransport* transport, int flags, int* resolved_tcp_port,
                                std::string* error) EXCLUDES(listener_list_mutex) {
     std::lock_guard<std::mutex> lock(listener_list_mutex);
     for (auto& l : listener_list) {
@@ -184,8 +193,8 @@
                 return INSTALL_STATUS_INTERNAL_ERROR;
             }
 
-            // Can't repurpose a listener if 'no_rebind' is true.
-            if (no_rebind) {
+            // Can't repurpose a listener if INSTALL_LISTENER_NO_REBIND is set
+            if (flags & INSTALL_LISTENER_NO_REBIND) {
                 *error = "cannot rebind";
                 return INSTALL_STATUS_CANNOT_REBIND;
             }
@@ -222,7 +231,9 @@
     } else {
         listener->fde = fdevent_create(listener->fd, listener_event_func, listener.get());
     }
-    fdevent_set(listener->fde, FDE_READ);
+    if ((flags & INSTALL_LISTENER_DISABLED) == 0) {
+        fdevent_set(listener->fde, FDE_READ);
+    }
 
     listener->transport = transport;
 
diff --git a/adb/adb_listeners.h b/adb/adb_listeners.h
index 70a2ee1..354dcc5 100644
--- a/adb/adb_listeners.h
+++ b/adb/adb_listeners.h
@@ -32,8 +32,11 @@
   INSTALL_STATUS_LISTENER_NOT_FOUND = -4,
 };
 
+inline constexpr int INSTALL_LISTENER_NO_REBIND = 1 << 0;
+inline constexpr int INSTALL_LISTENER_DISABLED = 1 << 1;
+
 InstallStatus install_listener(const std::string& local_name, const char* connect_to,
-                               atransport* transport, int no_rebind, int* resolved_tcp_port,
+                               atransport* transport, int flags, int* resolved_tcp_port,
                                std::string* error);
 
 std::string format_listeners();
@@ -41,6 +44,7 @@
 InstallStatus remove_listener(const char* local_name, atransport* transport);
 void remove_all_listeners(void);
 
+void enable_daemon_sockets();
 void close_smartsockets();
 
 #endif /* __ADB_LISTENERS_H */
diff --git a/adb/adb_mdns.h b/adb/adb_mdns.h
index 6b37355..3111248 100644
--- a/adb/adb_mdns.h
+++ b/adb/adb_mdns.h
@@ -19,9 +19,14 @@
 
 #include <android-base/macros.h>
 
-const char* kADBServiceType = "_adb._tcp";
-const char* kADBSecurePairingServiceType = "_adb_secure_pairing._tcp";
-const char* kADBSecureConnectServiceType = "_adb_secure_connect._tcp";
+// The rules for Service Names [RFC6335] state that they may be no more
+// than fifteen characters long (not counting the mandatory underscore),
+// consisting of only letters, digits, and hyphens, must begin and end
+// with a letter or digit, must not contain consecutive hyphens, and
+// must contain at least one letter.
+#define ADB_MDNS_SERVICE_TYPE "adb"
+#define ADB_MDNS_TLS_PAIRING_TYPE "adb-tls-pairing"
+#define ADB_MDNS_TLS_CONNECT_TYPE "adb-tls-connect"
 
 const int kADBTransportServiceRefIndex = 0;
 const int kADBSecurePairingServiceRefIndex = 1;
@@ -71,11 +76,10 @@
 const char* kADBSecureConnectServiceTxtRecord =
         ADB_SECURE_SERVICE_VERSION_TXT_RECORD(ADB_SECURE_SERVICE_VERSION);
 
-const char* kADBDNSServices[] = {
-        kADBServiceType,
-        kADBSecurePairingServiceType,
-        kADBSecureConnectServiceType,
-};
+#define ADB_FULL_MDNS_SERVICE_TYPE(atype) ("_" atype "._tcp")
+const char* kADBDNSServices[] = {ADB_FULL_MDNS_SERVICE_TYPE(ADB_MDNS_SERVICE_TYPE),
+                                 ADB_FULL_MDNS_SERVICE_TYPE(ADB_MDNS_TLS_PAIRING_TYPE),
+                                 ADB_FULL_MDNS_SERVICE_TYPE(ADB_MDNS_TLS_CONNECT_TYPE)};
 
 const char* kADBDNSServiceTxtRecords[] = {
         nullptr,
diff --git a/adb/client/adb_client.cpp b/adb/client/adb_client.cpp
index f724cb5..31c938c 100644
--- a/adb/client/adb_client.cpp
+++ b/adb/client/adb_client.cpp
@@ -37,6 +37,7 @@
 #include <vector>
 
 #include <android-base/file.h>
+#include <android-base/no_destructor.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/thread_annotations.h>
@@ -204,9 +205,25 @@
         return false;
     }
 
-    // The server might send OKAY, so consume that.
     char buf[4];
-    ReadFdExactly(fd.get(), buf, 4);
+    if (!ReadFdExactly(fd.get(), buf, 4)) {
+        fprintf(stderr, "error: failed to read response from server\n");
+        return false;
+    }
+
+    if (memcmp(buf, "OKAY", 4) == 0) {
+        // Nothing to do.
+    } else if (memcmp(buf, "FAIL", 4) == 0) {
+        std::string output, error;
+        if (!ReadProtocolString(fd.get(), &output, &error)) {
+            fprintf(stderr, "error: %s\n", error.c_str());
+            return false;
+        }
+
+        fprintf(stderr, "error: %s\n", output.c_str());
+        return false;
+    }
+
     // Now that no more data is expected, wait for socket orderly shutdown or error, indicating
     // server death.
     ReadOrderlyShutdown(fd.get());
@@ -399,18 +416,18 @@
     return android::base::StringPrintf("%s:%s", prefix, command);
 }
 
-bool adb_get_feature_set(FeatureSet* feature_set, std::string* error) {
-    static FeatureSet* features = nullptr;
-    if (!features) {
-        std::string result;
-        if (adb_query(format_host_command("features"), &result, error)) {
-            features = new FeatureSet(StringToFeatureSet(result));
-        }
+const std::optional<FeatureSet>& adb_get_feature_set(std::string* error) {
+    static std::string cached_error [[clang::no_destroy]];
+    static const std::optional<FeatureSet> features
+            [[clang::no_destroy]] ([]() -> std::optional<FeatureSet> {
+                std::string result;
+                if (adb_query(format_host_command("features"), &result, &cached_error)) {
+                    return StringToFeatureSet(result);
+                }
+                return std::nullopt;
+            }());
+    if (!features && error) {
+        *error = cached_error;
     }
-    if (features) {
-        *feature_set = *features;
-        return true;
-    }
-    feature_set->clear();
-    return false;
+    return features;
 }
diff --git a/adb/client/adb_client.h b/adb/client/adb_client.h
index 1c6cde7..27be28f 100644
--- a/adb/client/adb_client.h
+++ b/adb/client/adb_client.h
@@ -76,7 +76,7 @@
 std::string format_host_command(const char* _Nonnull command);
 
 // Get the feature set of the current preferred transport.
-bool adb_get_feature_set(FeatureSet* _Nonnull feature_set, std::string* _Nonnull error);
+const std::optional<FeatureSet>& adb_get_feature_set(std::string* _Nullable error);
 
 #if defined(__linux__)
 // Get the path of a file containing the path to the server executable, if the socket spec set via
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp
index 21b8f49..59c8563 100644
--- a/adb/client/adb_install.cpp
+++ b/adb/client/adb_install.cpp
@@ -22,11 +22,12 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <algorithm>
-#include <iostream>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include <android-base/file.h>
+#include <android-base/parsebool.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 
@@ -39,8 +40,9 @@
 #include "fastdeploy.h"
 #include "incremental.h"
 
+using namespace std::literals;
+
 static constexpr int kFastDeployMinApi = 24;
-static constexpr int kIncrementalMinApi = 29;
 
 namespace {
 
@@ -50,16 +52,17 @@
     INSTALL_STREAM,
     INSTALL_INCREMENTAL,
 };
+
+enum class CmdlineOption { None, Enable, Disable };
 }
 
 static bool can_use_feature(const char* feature) {
-    FeatureSet features;
-    std::string error;
-    if (!adb_get_feature_set(&features, &error)) {
-        fprintf(stderr, "error: %s\n", error.c_str());
+    // We ignore errors here, if the device is missing, we'll notice when we try to push install.
+    auto&& features = adb_get_feature_set(nullptr);
+    if (!features) {
         return false;
     }
-    return CanUseFeature(features, feature);
+    return CanUseFeature(*features, feature);
 }
 
 static InstallMode best_install_mode() {
@@ -286,7 +289,7 @@
         }
     }
 
-    if (do_sync_push(apk_file, apk_dest.c_str(), false)) {
+    if (do_sync_push(apk_file, apk_dest.c_str(), false, CompressionType::Any, false)) {
         result = pm_command(argc, argv);
         delete_device_file(apk_dest);
     }
@@ -299,45 +302,52 @@
     return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
 }
 
-static int install_app_incremental(int argc, const char** argv) {
-    printf("Performing Incremental Install\n");
+static int install_app_incremental(int argc, const char** argv, bool wait, bool silent) {
     using clock = std::chrono::high_resolution_clock;
     const auto start = clock::now();
     int first_apk = -1;
     int last_apk = -1;
-    std::string cert_path;
-    bool wait = false;
-    std::vector<std::string_view> args = {"package"};
+    std::vector<std::string_view> args = {"package"sv};
     for (int i = 0; i < argc; ++i) {
         const auto arg = std::string_view(argv[i]);
-        if (android::base::EndsWithIgnoreCase(arg, ".apk")) {
+        if (android::base::EndsWithIgnoreCase(arg, ".apk"sv)) {
             last_apk = i;
             if (first_apk == -1) {
                 first_apk = i;
             }
-        } else if (arg == "--wait") {
-            wait = true;
-        } else if (arg.starts_with("install-")) {
+        } else if (arg.starts_with("install-"sv)) {
             // incremental installation command on the device is the same for all its variations in
             // the adb, e.g. install-multiple or install-multi-package
-            args.push_back("install");
+            args.push_back("install"sv);
         } else {
             args.push_back(arg);
         }
     }
 
-    if (first_apk == -1) error_exit("Need at least one APK file on command line");
+    if (first_apk == -1) {
+        if (!silent) {
+            fprintf(stderr, "error: need at least one APK file on command line\n");
+        }
+        return -1;
+    }
 
-    const auto afterApk = clock::now();
+    auto files = incremental::Files{argv + first_apk, argv + last_apk + 1};
+    if (silent) {
+        // For a silent installation we want to do the lightweight check first and bail early and
+        // quietly if it fails.
+        if (!incremental::can_install(files)) {
+            return -1;
+        }
+    }
 
-    auto server_process = incremental::install({argv + first_apk, argv + last_apk + 1});
+    printf("Performing Incremental Install\n");
+    auto server_process = incremental::install(files, silent);
     if (!server_process) {
         return -1;
     }
 
     const auto end = clock::now();
-    printf("Install command complete (ms: %d total, %d apk prep, %d install)\n",
-           msBetween(start, end), msBetween(start, afterApk), msBetween(afterApk, end));
+    printf("Install command complete in %d ms\n", msBetween(start, end));
 
     if (wait) {
         (*server_process).wait();
@@ -346,66 +356,134 @@
     return 0;
 }
 
+static std::pair<InstallMode, std::optional<InstallMode>> calculateInstallMode(
+        InstallMode modeFromArgs, bool fastdeploy, CmdlineOption incrementalRequest) {
+    if (incrementalRequest == CmdlineOption::Enable) {
+        if (fastdeploy) {
+            error_exit(
+                    "--incremental and --fast-deploy options are incompatible. "
+                    "Please choose one");
+        }
+    }
+
+    if (modeFromArgs != INSTALL_DEFAULT) {
+        if (incrementalRequest == CmdlineOption::Enable) {
+            error_exit("--incremental is not compatible with other installation modes");
+        }
+        return {modeFromArgs, std::nullopt};
+    }
+
+    if (incrementalRequest != CmdlineOption::Disable && !is_abb_exec_supported()) {
+        if (incrementalRequest == CmdlineOption::None) {
+            incrementalRequest = CmdlineOption::Disable;
+        } else {
+            error_exit("Device doesn't support incremental installations");
+        }
+    }
+    if (incrementalRequest == CmdlineOption::None) {
+        // check if the host is ok with incremental by default
+        if (const char* incrementalFromEnv = getenv("ADB_INSTALL_DEFAULT_INCREMENTAL")) {
+            using namespace android::base;
+            auto val = ParseBool(incrementalFromEnv);
+            if (val == ParseBoolResult::kFalse) {
+                incrementalRequest = CmdlineOption::Disable;
+            }
+        }
+    }
+    if (incrementalRequest == CmdlineOption::None) {
+        // still ok: let's see if the device allows using incremental by default
+        // it starts feeling like we're looking for an excuse to not to use incremental...
+        std::string error;
+        std::vector<std::string> args = {"settings", "get",
+                                         "enable_adb_incremental_install_default"};
+        auto fd = send_abb_exec_command(args, &error);
+        if (!fd.ok()) {
+            fprintf(stderr, "adb: retrieving the default device installation mode failed: %s",
+                    error.c_str());
+        } else {
+            char buf[BUFSIZ] = {};
+            read_status_line(fd.get(), buf, sizeof(buf));
+            using namespace android::base;
+            auto val = ParseBool(buf);
+            if (val == ParseBoolResult::kFalse) {
+                incrementalRequest = CmdlineOption::Disable;
+            }
+        }
+    }
+
+    if (incrementalRequest == CmdlineOption::Enable) {
+        // explicitly requested - no fallback
+        return {INSTALL_INCREMENTAL, std::nullopt};
+    }
+    const auto bestMode = best_install_mode();
+    if (incrementalRequest == CmdlineOption::None) {
+        // no opinion - use incremental, fallback to regular on a failure.
+        return {INSTALL_INCREMENTAL, bestMode};
+    }
+    // incremental turned off - use the regular best mode without a fallback.
+    return {bestMode, std::nullopt};
+}
+
 int install_app(int argc, const char** argv) {
     std::vector<int> processedArgIndices;
     InstallMode installMode = INSTALL_DEFAULT;
     bool use_fastdeploy = false;
     bool is_reinstall = false;
+    bool wait = false;
+    auto incremental_request = CmdlineOption::None;
     FastDeploy_AgentUpdateStrategy agent_update_strategy = FastDeploy_AgentUpdateDifferentVersion;
 
     for (int i = 1; i < argc; i++) {
-        if (!strcmp(argv[i], "--streaming")) {
+        if (argv[i] == "--streaming"sv) {
             processedArgIndices.push_back(i);
             installMode = INSTALL_STREAM;
-        } else if (!strcmp(argv[i], "--no-streaming")) {
+        } else if (argv[i] == "--no-streaming"sv) {
             processedArgIndices.push_back(i);
             installMode = INSTALL_PUSH;
-        } else if (!strcmp(argv[i], "-r")) {
+        } else if (argv[i] == "-r"sv) {
             // Note that this argument is not added to processedArgIndices because it
             // must be passed through to pm
             is_reinstall = true;
-        } else if (!strcmp(argv[i], "--fastdeploy")) {
+        } else if (argv[i] == "--fastdeploy"sv) {
             processedArgIndices.push_back(i);
             use_fastdeploy = true;
-        } else if (!strcmp(argv[i], "--no-fastdeploy")) {
+        } else if (argv[i] == "--no-fastdeploy"sv) {
             processedArgIndices.push_back(i);
             use_fastdeploy = false;
-        } else if (!strcmp(argv[i], "--force-agent")) {
+        } else if (argv[i] == "--force-agent"sv) {
             processedArgIndices.push_back(i);
             agent_update_strategy = FastDeploy_AgentUpdateAlways;
-        } else if (!strcmp(argv[i], "--date-check-agent")) {
+        } else if (argv[i] == "--date-check-agent"sv) {
             processedArgIndices.push_back(i);
             agent_update_strategy = FastDeploy_AgentUpdateNewerTimeStamp;
-        } else if (!strcmp(argv[i], "--version-check-agent")) {
+        } else if (argv[i] == "--version-check-agent"sv) {
             processedArgIndices.push_back(i);
             agent_update_strategy = FastDeploy_AgentUpdateDifferentVersion;
-        } else if (!strcmp(argv[i], "--incremental")) {
+        } else if (strlen(argv[i]) >= "--incr"sv.size() && "--incremental"sv.starts_with(argv[i])) {
             processedArgIndices.push_back(i);
-            installMode = INSTALL_INCREMENTAL;
-        } else if (!strcmp(argv[i], "--no-incremental")) {
+            incremental_request = CmdlineOption::Enable;
+        } else if (strlen(argv[i]) >= "--no-incr"sv.size() &&
+                   "--no-incremental"sv.starts_with(argv[i])) {
             processedArgIndices.push_back(i);
-            installMode = INSTALL_DEFAULT;
+            incremental_request = CmdlineOption::Disable;
+        } else if (argv[i] == "--wait"sv) {
+            processedArgIndices.push_back(i);
+            wait = true;
         }
     }
 
-    if (installMode == INSTALL_INCREMENTAL) {
-        if (get_device_api_level() < kIncrementalMinApi || !is_abb_exec_supported()) {
-            error_exit("Attempting to use incremental install on unsupported device");
-        }
-    }
-
-    if (installMode == INSTALL_DEFAULT) {
-        installMode = best_install_mode();
-    }
-
-    if (installMode == INSTALL_STREAM && best_install_mode() == INSTALL_PUSH) {
+    auto [primaryMode, fallbackMode] =
+            calculateInstallMode(installMode, use_fastdeploy, incremental_request);
+    if ((primaryMode == INSTALL_STREAM || fallbackMode.value_or(INSTALL_PUSH) == INSTALL_STREAM) &&
+        best_install_mode() == INSTALL_PUSH) {
         error_exit("Attempting to use streaming install on unsupported device");
     }
 
     if (use_fastdeploy && get_device_api_level() < kFastDeployMinApi) {
-        printf("Fast Deploy is only compatible with devices of API version %d or higher, "
-               "ignoring.\n",
-               kFastDeployMinApi);
+        fprintf(stderr,
+                "Fast Deploy is only compatible with devices of API version %d or higher, "
+                "ignoring.\n",
+                kFastDeployMinApi);
         use_fastdeploy = false;
     }
     fastdeploy_set_agent_update_strategy(agent_update_strategy);
@@ -421,19 +499,27 @@
         error_exit("install requires an apk argument");
     }
 
-    switch (installMode) {
-        case INSTALL_PUSH:
-            return install_app_legacy(passthrough_argv.size(), passthrough_argv.data(),
-                                      use_fastdeploy);
-        case INSTALL_STREAM:
-            return install_app_streamed(passthrough_argv.size(), passthrough_argv.data(),
-                                        use_fastdeploy);
-        case INSTALL_INCREMENTAL:
-            return install_app_incremental(passthrough_argv.size(), passthrough_argv.data());
-        case INSTALL_DEFAULT:
-        default:
-            return 1;
+    auto runInstallMode = [&](InstallMode installMode, bool silent) {
+        switch (installMode) {
+            case INSTALL_PUSH:
+                return install_app_legacy(passthrough_argv.size(), passthrough_argv.data(),
+                                          use_fastdeploy);
+            case INSTALL_STREAM:
+                return install_app_streamed(passthrough_argv.size(), passthrough_argv.data(),
+                                            use_fastdeploy);
+            case INSTALL_INCREMENTAL:
+                return install_app_incremental(passthrough_argv.size(), passthrough_argv.data(),
+                                               wait, silent);
+            case INSTALL_DEFAULT:
+            default:
+                return 1;
+        }
+    };
+    auto res = runInstallMode(primaryMode, fallbackMode.has_value());
+    if (res && fallbackMode.value_or(primaryMode) != primaryMode) {
+        res = runInstallMode(*fallbackMode, false);
     }
+    return res;
 }
 
 int install_multiple_app(int argc, const char** argv) {
diff --git a/adb/client/auth.cpp b/adb/client/auth.cpp
index 4b2fa04..b674a81 100644
--- a/adb/client/auth.cpp
+++ b/adb/client/auth.cpp
@@ -504,6 +504,12 @@
     }).detach();
 }
 
+// Callback given to SSL_set_cert_cb to select a certificate when server requests
+// for a certificate. This is where the server will give us a CA-issuer list, and
+// figure out if the server knows any of our public keys. We currently always return
+// 1 here to indicate success, since we always try a key here (in the case of no auth).
+// See https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_set_cert_cb
+// for more details.
 int adb_tls_set_certificate(SSL* ssl) {
     LOG(INFO) << __func__;
 
diff --git a/adb/client/bugreport.cpp b/adb/client/bugreport.cpp
index 8ca44e8..e162aaa 100644
--- a/adb/client/bugreport.cpp
+++ b/adb/client/bugreport.cpp
@@ -282,5 +282,5 @@
 
 bool Bugreport::DoSyncPull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
                            const char* name) {
-    return do_sync_pull(srcs, dst, copy_attrs, name);
+    return do_sync_pull(srcs, dst, copy_attrs, CompressionType::None, name);
 }
diff --git a/adb/client/commandline.cpp b/adb/client/commandline.cpp
index 7c7da08..29f9dc1 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -129,15 +129,23 @@
         " reverse --remove-all     remove all reverse socket connections from device\n"
         "\n"
         "file transfer:\n"
-        " push [--sync] LOCAL... REMOTE\n"
+        " push [--sync] [-z ALGORITHM] [-Z] LOCAL... REMOTE\n"
         "     copy local files/directories to device\n"
         "     --sync: only push files that are newer on the host than the device\n"
-        " pull [-a] REMOTE... LOCAL\n"
+        "     -n: dry run: push files to device without storing to the filesystem\n"
+        "     -z: enable compression with a specified algorithm (any, none, brotli)\n"
+        "     -Z: disable compression\n"
+        " pull [-a] [-z ALGORITHM] [-Z] REMOTE... LOCAL\n"
         "     copy files/dirs from device\n"
         "     -a: preserve file timestamp and mode\n"
-        " sync [all|data|odm|oem|product|system|system_ext|vendor]\n"
+        "     -z: enable compression with a specified algorithm (any, none, brotli)\n"
+        "     -Z: disable compression\n"
+        " sync [-l] [-z ALGORITHM] [-Z] [all|data|odm|oem|product|system|system_ext|vendor]\n"
         "     sync a local build from $ANDROID_PRODUCT_OUT to the device (default all)\n"
+        "     -n: dry run: push files to device without storing to the filesystem\n"
         "     -l: list files that would be copied, but don't copy them\n"
+        "     -z: enable compression with a specified algorithm (any, none, brotli)\n"
+        "     -Z: disable compression\n"
         "\n"
         "shell:\n"
         " shell [-e ESCAPE] [-n] [-Tt] [-x] [COMMAND...]\n"
@@ -664,18 +672,17 @@
 }
 
 static int adb_shell(int argc, const char** argv) {
-    FeatureSet features;
-    std::string error_message;
-    if (!adb_get_feature_set(&features, &error_message)) {
-        fprintf(stderr, "error: %s\n", error_message.c_str());
-        return 1;
+    std::string error;
+    auto&& features = adb_get_feature_set(&error);
+    if (!features) {
+        error_exit("%s", error.c_str());
     }
 
     enum PtyAllocationMode { kPtyAuto, kPtyNo, kPtyYes, kPtyDefinitely };
 
     // Defaults.
-    char escape_char = '~'; // -e
-    bool use_shell_protocol = CanUseFeature(features, kFeatureShell2); // -x
+    char escape_char = '~';                                                 // -e
+    bool use_shell_protocol = CanUseFeature(*features, kFeatureShell2);     // -x
     PtyAllocationMode tty = use_shell_protocol ? kPtyAuto : kPtyDefinitely; // -t/-T
 
     // Parse shell-specific command-line options.
@@ -751,7 +758,7 @@
     if (!use_shell_protocol) {
         if (shell_type_arg != kShellServiceArgPty) {
             fprintf(stderr, "error: %s only supports allocating a pty\n",
-                    !CanUseFeature(features, kFeatureShell2) ? "device" : "-x");
+                    !CanUseFeature(*features, kFeatureShell2) ? "device" : "-x");
             return 1;
         } else {
             // If we're not using the shell protocol, the type argument must be empty.
@@ -771,14 +778,13 @@
 }
 
 static int adb_abb(int argc, const char** argv) {
-    FeatureSet features;
-    std::string error_message;
-    if (!adb_get_feature_set(&features, &error_message)) {
-        fprintf(stderr, "error: %s\n", error_message.c_str());
+    std::string error;
+    auto&& features = adb_get_feature_set(&error);
+    if (!features) {
+        error_exit("%s", error.c_str());
         return 1;
     }
-
-    if (!CanUseFeature(features, kFeatureAbb)) {
+    if (!CanUseFeature(*features, kFeatureAbb)) {
         error_exit("abb is not supported by the device");
     }
 
@@ -1156,10 +1162,9 @@
         // Use shell protocol if it's supported and the caller doesn't explicitly
         // disable it.
         if (!disable_shell_protocol) {
-            FeatureSet features;
-            std::string error;
-            if (adb_get_feature_set(&features, &error)) {
-                use_shell_protocol = CanUseFeature(features, kFeatureShell2);
+            auto&& features = adb_get_feature_set(nullptr);
+            if (features) {
+                use_shell_protocol = CanUseFeature(*features, kFeatureShell2);
             } else {
                 // Device was unreachable.
                 attempt_connection = false;
@@ -1308,9 +1313,37 @@
     return 0;
 }
 
+static CompressionType parse_compression_type(const std::string& str, bool allow_numbers) {
+    if (allow_numbers) {
+        if (str == "0") {
+            return CompressionType::None;
+        } else if (str == "1") {
+            return CompressionType::Any;
+        }
+    }
+
+    if (str == "any") {
+        return CompressionType::Any;
+    } else if (str == "none") {
+        return CompressionType::None;
+    }
+
+    if (str == "brotli") {
+        return CompressionType::Brotli;
+    } else if (str == "lz4") {
+        return CompressionType::LZ4;
+    }
+
+    error_exit("unexpected compression type %s", str.c_str());
+}
+
 static void parse_push_pull_args(const char** arg, int narg, std::vector<const char*>* srcs,
-                                 const char** dst, bool* copy_attrs, bool* sync) {
+                                 const char** dst, bool* copy_attrs, bool* sync,
+                                 CompressionType* compression, bool* dry_run) {
     *copy_attrs = false;
+    if (const char* adb_compression = getenv("ADB_COMPRESSION")) {
+        *compression = parse_compression_type(adb_compression, true);
+    }
 
     srcs->clear();
     bool ignore_flags = false;
@@ -1322,6 +1355,16 @@
                 // Silently ignore for backwards compatibility.
             } else if (!strcmp(*arg, "-a")) {
                 *copy_attrs = true;
+            } else if (!strcmp(*arg, "-z")) {
+                if (narg < 2) {
+                    error_exit("-z requires an argument");
+                }
+                *compression = parse_compression_type(*++arg, false);
+                --narg;
+            } else if (!strcmp(*arg, "-Z")) {
+                *compression = CompressionType::None;
+            } else if (dry_run && !strcmp(*arg, "-n")) {
+                *dry_run = true;
             } else if (!strcmp(*arg, "--sync")) {
                 if (sync != nullptr) {
                     *sync = true;
@@ -1443,6 +1486,26 @@
 #endif
 }
 
+static bool _is_valid_os_fd(int fd) {
+    // Disallow invalid FDs and stdin/out/err as well.
+    if (fd < 3) {
+        return false;
+    }
+#ifdef _WIN32
+    auto handle = (HANDLE)fd;
+    DWORD info = 0;
+    if (GetHandleInformation(handle, &info) == 0) {
+        return false;
+    }
+#else
+    int flags = fcntl(fd, F_GETFD);
+    if (flags == -1) {
+        return false;
+    }
+#endif
+    return true;
+}
+
 int adb_commandline(int argc, const char** argv) {
     bool no_daemon = false;
     bool is_daemon = false;
@@ -1750,14 +1813,13 @@
         }
         return adb_connect_command(android::base::StringPrintf("tcpip:%d", port));
     } else if (!strcmp(argv[0], "remount")) {
-        FeatureSet features;
         std::string error;
-        if (!adb_get_feature_set(&features, &error)) {
-            fprintf(stderr, "error: %s\n", error.c_str());
-            return 1;
+        auto&& features = adb_get_feature_set(&error);
+        if (!features) {
+            error_exit("%s", error.c_str());
         }
 
-        if (CanUseFeature(features, kFeatureRemountShell)) {
+        if (CanUseFeature(*features, kFeatureRemountShell)) {
             std::vector<const char*> args = {"shell"};
             args.insert(args.cend(), argv, argv + argc);
             return adb_shell_noinput(args.size(), args.data());
@@ -1856,20 +1918,25 @@
     } else if (!strcmp(argv[0], "push")) {
         bool copy_attrs = false;
         bool sync = false;
+        bool dry_run = false;
+        CompressionType compression = CompressionType::Any;
         std::vector<const char*> srcs;
         const char* dst = nullptr;
 
-        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, &sync);
+        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, &sync, &compression,
+                             &dry_run);
         if (srcs.empty() || !dst) error_exit("push requires an argument");
-        return do_sync_push(srcs, dst, sync) ? 0 : 1;
+        return do_sync_push(srcs, dst, sync, compression, dry_run) ? 0 : 1;
     } else if (!strcmp(argv[0], "pull")) {
         bool copy_attrs = false;
+        CompressionType compression = CompressionType::Any;
         std::vector<const char*> srcs;
         const char* dst = ".";
 
-        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, nullptr);
+        parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, &copy_attrs, nullptr, &compression,
+                             nullptr);
         if (srcs.empty()) error_exit("pull requires an argument");
-        return do_sync_pull(srcs, dst, copy_attrs) ? 0 : 1;
+        return do_sync_pull(srcs, dst, copy_attrs, compression) ? 0 : 1;
     } else if (!strcmp(argv[0], "install")) {
         if (argc < 2) error_exit("install requires an argument");
         return install_app(argc, argv);
@@ -1885,18 +1952,41 @@
     } else if (!strcmp(argv[0], "sync")) {
         std::string src;
         bool list_only = false;
-        if (argc < 2) {
-            // No partition specified: sync all of them.
-        } else if (argc >= 2 && strcmp(argv[1], "-l") == 0) {
-            list_only = true;
-            if (argc == 3) src = argv[2];
-        } else if (argc == 2) {
-            src = argv[1];
-        } else {
-            error_exit("usage: adb sync [-l] [PARTITION]");
+        bool dry_run = false;
+        CompressionType compression = CompressionType::Any;
+
+        if (const char* adb_compression = getenv("ADB_COMPRESSION"); adb_compression) {
+            compression = parse_compression_type(adb_compression, true);
         }
 
-        if (src.empty()) src = "all";
+        int opt;
+        while ((opt = getopt(argc, const_cast<char**>(argv), "lnz:Z")) != -1) {
+            switch (opt) {
+                case 'l':
+                    list_only = true;
+                    break;
+                case 'n':
+                    dry_run = true;
+                    break;
+                case 'z':
+                    compression = parse_compression_type(optarg, false);
+                    break;
+                case 'Z':
+                    compression = CompressionType::None;
+                    break;
+                default:
+                    error_exit("usage: adb sync [-l] [-n]  [-z ALGORITHM] [-Z] [PARTITION]");
+            }
+        }
+
+        if (optind == argc) {
+            src = "all";
+        } else if (optind + 1 == argc) {
+            src = argv[optind];
+        } else {
+            error_exit("usage: adb sync [-l] [-n] [-z ALGORITHM] [-Z] [PARTITION]");
+        }
+
         std::vector<std::string> partitions{"data",   "odm",        "oem",   "product",
                                             "system", "system_ext", "vendor"};
         bool found = false;
@@ -1905,7 +1995,9 @@
                 std::string src_dir{product_file(partition)};
                 if (!directory_exists(src_dir)) continue;
                 found = true;
-                if (!do_sync_sync(src_dir, "/" + partition, list_only)) return 1;
+                if (!do_sync_sync(src_dir, "/" + partition, list_only, compression, dry_run)) {
+                    return 1;
+                }
             }
         }
         if (!found) error_exit("don't know how to sync %s partition", src.c_str());
@@ -1946,13 +2038,12 @@
     } else if (!strcmp(argv[0], "track-jdwp")) {
         return adb_connect_command("track-jdwp");
     } else if (!strcmp(argv[0], "track-app")) {
-        FeatureSet features;
         std::string error;
-        if (!adb_get_feature_set(&features, &error)) {
-            fprintf(stderr, "error: %s\n", error.c_str());
-            return 1;
+        auto&& features = adb_get_feature_set(&error);
+        if (!features) {
+            error_exit("%s", error.c_str());
         }
-        if (!CanUseFeature(features, kFeatureTrackApp)) {
+        if (!CanUseFeature(*features, kFeatureTrackApp)) {
             error_exit("track-app is not supported by the device");
         }
         TrackAppStreamsCallback callback;
@@ -1978,15 +2069,14 @@
         return 0;
     } else if (!strcmp(argv[0], "features")) {
         // Only list the features common to both the adb client and the device.
-        FeatureSet features;
         std::string error;
-        if (!adb_get_feature_set(&features, &error)) {
-            fprintf(stderr, "error: %s\n", error.c_str());
-            return 1;
+        auto&& features = adb_get_feature_set(&error);
+        if (!features) {
+            error_exit("%s", error.c_str());
         }
 
-        for (const std::string& name : features) {
-            if (CanUseFeature(features, name)) {
+        for (const std::string& name : *features) {
+            if (CanUseFeature(*features, name)) {
                 printf("%s\n", name.c_str());
             }
         }
@@ -2009,17 +2099,28 @@
             }
         }
     } else if (!strcmp(argv[0], "inc-server")) {
-        if (argc < 3) {
-            error_exit("usage: adb inc-server FD FILE1 FILE2 ...");
+        if (argc < 4) {
+#ifdef _WIN32
+            error_exit("usage: adb inc-server CONNECTION_HANDLE OUTPUT_HANDLE FILE1 FILE2 ...");
+#else
+            error_exit("usage: adb inc-server CONNECTION_FD OUTPUT_FD FILE1 FILE2 ...");
+#endif
         }
-        int fd = atoi(argv[1]);
-        if (fd < 3) {
-            // Disallow invalid FDs and stdin/out/err as well.
-            error_exit("Invalid fd number given: %d", fd);
+        int connection_fd = atoi(argv[1]);
+        if (!_is_valid_os_fd(connection_fd)) {
+            error_exit("Invalid connection_fd number given: %d", connection_fd);
         }
-        fd = adb_register_socket(fd);
-        close_on_exec(fd);
-        return incremental::serve(fd, argc - 2, argv + 2);
+
+        connection_fd = adb_register_socket(connection_fd);
+        close_on_exec(connection_fd);
+
+        int output_fd = atoi(argv[2]);
+        if (!_is_valid_os_fd(output_fd)) {
+            error_exit("Invalid output_fd number given: %d", output_fd);
+        }
+        output_fd = adb_register_socket(output_fd);
+        close_on_exec(output_fd);
+        return incremental::serve(connection_fd, output_fd, argc - 3, argv + 3);
     }
 
     error_exit("unknown command %s", argv[0]);
diff --git a/adb/client/fastdeploy.cpp b/adb/client/fastdeploy.cpp
index c5fc12f..bc4b91b 100644
--- a/adb/client/fastdeploy.cpp
+++ b/adb/client/fastdeploy.cpp
@@ -112,7 +112,7 @@
     // but can't be removed until after the push.
     unix_close(tf.release());
 
-    if (!do_sync_push(srcs, dst, sync)) {
+    if (!do_sync_push(srcs, dst, sync, CompressionType::Any, false)) {
         error_exit("Failed to push fastdeploy agent to device.");
     }
 }
diff --git a/adb/client/file_sync_client.cpp b/adb/client/file_sync_client.cpp
index 04ad536..7185939 100644
--- a/adb/client/file_sync_client.cpp
+++ b/adb/client/file_sync_client.cpp
@@ -34,6 +34,7 @@
 #include <memory>
 #include <sstream>
 #include <string>
+#include <variant>
 #include <vector>
 
 #include "sysdeps.h"
@@ -42,6 +43,7 @@
 #include "adb_client.h"
 #include "adb_io.h"
 #include "adb_utils.h"
+#include "compression_utils.h"
 #include "file_sync_protocol.h"
 #include "line_printer.h"
 #include "sysdeps/errno.h"
@@ -53,6 +55,8 @@
 #include <android-base/strings.h>
 #include <android-base/stringprintf.h>
 
+using namespace std::literals;
+
 typedef void(sync_ls_cb)(unsigned mode, uint64_t size, uint64_t time, const char* name);
 
 struct syncsendbuf {
@@ -113,6 +117,11 @@
     uint64_t bytes_expected;
     bool expect_multiple_files;
 
+  private:
+    std::string last_progress_str;
+    std::chrono::steady_clock::time_point last_progress_time;
+
+  public:
     TransferLedger() {
         Reset();
     }
@@ -132,6 +141,8 @@
         files_skipped = 0;
         bytes_transferred = 0;
         bytes_expected = 0;
+        last_progress_str.clear();
+        last_progress_time = {};
     }
 
     std::string TransferRate() {
@@ -151,6 +162,12 @@
 
     void ReportProgress(LinePrinter& lp, const std::string& file, uint64_t file_copied_bytes,
                         uint64_t file_total_bytes) {
+        static constexpr auto kProgressReportInterval = 100ms;
+
+        auto now = std::chrono::steady_clock::now();
+        if (now < last_progress_time + kProgressReportInterval) {
+            return;
+        }
         char overall_percentage_str[5] = "?";
         if (bytes_expected != 0 && bytes_transferred <= bytes_expected) {
             int overall_percentage = static_cast<int>(bytes_transferred * 100 / bytes_expected);
@@ -181,7 +198,11 @@
                     android::base::StringPrintf("[%4s] %s", overall_percentage_str, file.c_str());
             }
         }
-        lp.Print(output, LinePrinter::LineType::INFO);
+        if (output != last_progress_str) {
+            lp.Print(output, LinePrinter::LineType::INFO);
+            last_progress_str = std::move(output);
+            last_progress_time = now;
+        }
     }
 
     void ReportTransferRate(LinePrinter& lp, const std::string& name, TransferDirection direction) {
@@ -204,15 +225,23 @@
 
 class SyncConnection {
   public:
-    SyncConnection() {
+    SyncConnection() : acknowledgement_buffer_(sizeof(sync_status) + SYNC_DATA_MAX) {
+        acknowledgement_buffer_.resize(0);
         max = SYNC_DATA_MAX; // TODO: decide at runtime.
 
         std::string error;
-        if (!adb_get_feature_set(&features_, &error)) {
+        auto&& features = adb_get_feature_set(&error);
+        if (!features) {
             Error("failed to get feature set: %s", error.c_str());
         } else {
-            have_stat_v2_ = CanUseFeature(features_, kFeatureStat2);
-            have_ls_v2_ = CanUseFeature(features_, kFeatureLs2);
+            features_ = &*features;
+            have_stat_v2_ = CanUseFeature(*features, kFeatureStat2);
+            have_ls_v2_ = CanUseFeature(*features, kFeatureLs2);
+            have_sendrecv_v2_ = CanUseFeature(*features, kFeatureSendRecv2);
+            have_sendrecv_v2_brotli_ = CanUseFeature(*features, kFeatureSendRecv2Brotli);
+            have_sendrecv_v2_lz4_ = CanUseFeature(*features, kFeatureSendRecv2LZ4);
+            have_sendrecv_v2_dry_run_send_ = CanUseFeature(*features, kFeatureSendRecv2DryRunSend);
+            std::string error;
             fd.reset(adb_connect("sync:", &error));
             if (fd < 0) {
                 Error("connect failed: %s", error.c_str());
@@ -236,7 +265,26 @@
         line_printer_.KeepInfoLine();
     }
 
-    const FeatureSet& Features() const { return features_; }
+    bool HaveSendRecv2() const { return have_sendrecv_v2_; }
+    bool HaveSendRecv2Brotli() const { return have_sendrecv_v2_brotli_; }
+    bool HaveSendRecv2LZ4() const { return have_sendrecv_v2_lz4_; }
+    bool HaveSendRecv2DryRunSend() const { return have_sendrecv_v2_dry_run_send_; }
+
+    // Resolve a compression type which might be CompressionType::Any to a specific compression
+    // algorithm.
+    CompressionType ResolveCompressionType(CompressionType compression) const {
+        if (compression == CompressionType::Any) {
+            if (HaveSendRecv2LZ4()) {
+                return CompressionType::LZ4;
+            } else if (HaveSendRecv2Brotli()) {
+                return CompressionType::Brotli;
+            }
+            return CompressionType::None;
+        }
+        return compression;
+    }
+
+    const FeatureSet& Features() const { return *features_; }
 
     bool IsValid() { return fd >= 0; }
 
@@ -294,6 +342,96 @@
         req->path_length = path.length();
         char* data = reinterpret_cast<char*>(req + 1);
         memcpy(data, path.data(), path.length());
+        return WriteFdExactly(fd, buf.data(), buf.size());
+    }
+
+    bool SendSend2(std::string_view path, mode_t mode, CompressionType compression, bool dry_run) {
+        if (path.length() > 1024) {
+            Error("SendRequest failed: path too long: %zu", path.length());
+            errno = ENAMETOOLONG;
+            return false;
+        }
+
+        Block buf;
+
+        SyncRequest req;
+        req.id = ID_SEND_V2;
+        req.path_length = path.length();
+
+        syncmsg msg;
+        msg.send_v2_setup.id = ID_SEND_V2;
+        msg.send_v2_setup.mode = mode;
+        msg.send_v2_setup.flags = 0;
+        switch (compression) {
+            case CompressionType::None:
+                break;
+
+            case CompressionType::Brotli:
+                msg.send_v2_setup.flags = kSyncFlagBrotli;
+                break;
+
+            case CompressionType::LZ4:
+                msg.send_v2_setup.flags = kSyncFlagLZ4;
+                break;
+
+            case CompressionType::Any:
+                LOG(FATAL) << "unexpected CompressionType::Any";
+        }
+
+        if (dry_run) {
+            msg.send_v2_setup.flags |= kSyncFlagDryRun;
+        }
+
+        buf.resize(sizeof(SyncRequest) + path.length() + sizeof(msg.send_v2_setup));
+
+        void* p = buf.data();
+
+        p = mempcpy(p, &req, sizeof(SyncRequest));
+        p = mempcpy(p, path.data(), path.length());
+        p = mempcpy(p, &msg.send_v2_setup, sizeof(msg.send_v2_setup));
+
+        return WriteFdExactly(fd, buf.data(), buf.size());
+    }
+
+    bool SendRecv2(const std::string& path, CompressionType compression) {
+        if (path.length() > 1024) {
+            Error("SendRequest failed: path too long: %zu", path.length());
+            errno = ENAMETOOLONG;
+            return false;
+        }
+
+        Block buf;
+
+        SyncRequest req;
+        req.id = ID_RECV_V2;
+        req.path_length = path.length();
+
+        syncmsg msg;
+        msg.recv_v2_setup.id = ID_RECV_V2;
+        msg.recv_v2_setup.flags = 0;
+        switch (compression) {
+            case CompressionType::None:
+                break;
+
+            case CompressionType::Brotli:
+                msg.recv_v2_setup.flags |= kSyncFlagBrotli;
+                break;
+
+            case CompressionType::LZ4:
+                msg.recv_v2_setup.flags |= kSyncFlagLZ4;
+                break;
+
+            case CompressionType::Any:
+                LOG(FATAL) << "unexpected CompressionType::Any";
+        }
+
+        buf.resize(sizeof(SyncRequest) + path.length() + sizeof(msg.recv_v2_setup));
+
+        void* p = buf.data();
+
+        p = mempcpy(p, &req, sizeof(SyncRequest));
+        p = mempcpy(p, path.data(), path.length());
+        p = mempcpy(p, &msg.recv_v2_setup, sizeof(msg.recv_v2_setup));
 
         return WriteFdExactly(fd, buf.data(), buf.size());
     }
@@ -350,8 +488,8 @@
             }
 
             if (msg.stat_v1.id != ID_LSTAT_V1) {
-                PLOG(FATAL) << "protocol fault: stat response has wrong message id: "
-                            << msg.stat_v1.id;
+                LOG(FATAL) << "protocol fault: stat response has wrong message id: "
+                           << msg.stat_v1.id;
             }
 
             if (msg.stat_v1.mode == 0 && msg.stat_v1.size == 0 && msg.stat_v1.mtime == 0) {
@@ -412,7 +550,12 @@
     // difference to "adb sync" performance.
     bool SendSmallFile(const std::string& path, mode_t mode, const std::string& lpath,
                        const std::string& rpath, unsigned mtime, const char* data,
-                       size_t data_length) {
+                       size_t data_length, bool dry_run) {
+        if (dry_run) {
+            // We need to use send v2 for dry run.
+            return SendLargeFile(path, mode, lpath, rpath, mtime, CompressionType::None, dry_run);
+        }
+
         std::string path_and_mode = android::base::StringPrintf("%s,%d", path.c_str(), mode);
         if (path_and_mode.length() > 1024) {
             Error("SendSmallFile failed: path too long: %zu", path_and_mode.length());
@@ -425,7 +568,7 @@
         char* p = &buf[0];
 
         SyncRequest* req_send = reinterpret_cast<SyncRequest*>(p);
-        req_send->id = ID_SEND;
+        req_send->id = ID_SEND_V1;
         req_send->path_length = path_and_mode.length();
         p += sizeof(SyncRequest);
         memcpy(p, path_and_mode.data(), path_and_mode.size());
@@ -452,10 +595,117 @@
     }
 
     bool SendLargeFile(const std::string& path, mode_t mode, const std::string& lpath,
-                       const std::string& rpath, unsigned mtime) {
+                       const std::string& rpath, unsigned mtime, CompressionType compression,
+                       bool dry_run) {
+        if (dry_run && !HaveSendRecv2DryRunSend()) {
+            Error("dry-run not supported by the device");
+            return false;
+        }
+
+        if (!HaveSendRecv2()) {
+            return SendLargeFileLegacy(path, mode, lpath, rpath, mtime);
+        }
+
+        compression = ResolveCompressionType(compression);
+
+        if (!SendSend2(path, mode, compression, dry_run)) {
+            Error("failed to send ID_SEND_V2 message '%s': %s", path.c_str(), strerror(errno));
+            return false;
+        }
+
+        struct stat st;
+        if (stat(lpath.c_str(), &st) == -1) {
+            Error("cannot stat '%s': %s", lpath.c_str(), strerror(errno));
+            return false;
+        }
+
+        uint64_t total_size = st.st_size;
+        uint64_t bytes_copied = 0;
+
+        unique_fd lfd(adb_open(lpath.c_str(), O_RDONLY | O_CLOEXEC));
+        if (lfd < 0) {
+            Error("opening '%s' locally failed: %s", lpath.c_str(), strerror(errno));
+            return false;
+        }
+
+        syncsendbuf sbuf;
+        sbuf.id = ID_DATA;
+
+        std::variant<std::monostate, NullEncoder, BrotliEncoder, LZ4Encoder> encoder_storage;
+        Encoder* encoder = nullptr;
+        switch (compression) {
+            case CompressionType::None:
+                encoder = &encoder_storage.emplace<NullEncoder>(SYNC_DATA_MAX);
+                break;
+
+            case CompressionType::Brotli:
+                encoder = &encoder_storage.emplace<BrotliEncoder>(SYNC_DATA_MAX);
+                break;
+
+            case CompressionType::LZ4:
+                encoder = &encoder_storage.emplace<LZ4Encoder>(SYNC_DATA_MAX);
+                break;
+
+            case CompressionType::Any:
+                LOG(FATAL) << "unexpected CompressionType::Any";
+        }
+
+        bool sending = true;
+        while (sending) {
+            Block input(SYNC_DATA_MAX);
+            int r = adb_read(lfd.get(), input.data(), input.size());
+            if (r < 0) {
+                Error("reading '%s' locally failed: %s", lpath.c_str(), strerror(errno));
+                return false;
+            }
+
+            if (r == 0) {
+                encoder->Finish();
+            } else {
+                input.resize(r);
+                encoder->Append(std::move(input));
+                RecordBytesTransferred(r);
+                bytes_copied += r;
+                ReportProgress(rpath, bytes_copied, total_size);
+            }
+
+            while (true) {
+                Block output;
+                EncodeResult result = encoder->Encode(&output);
+                if (result == EncodeResult::Error) {
+                    Error("compressing '%s' locally failed", lpath.c_str());
+                    return false;
+                }
+
+                if (!output.empty()) {
+                    sbuf.size = output.size();
+                    memcpy(sbuf.data, output.data(), output.size());
+                    WriteOrDie(lpath, rpath, &sbuf, sizeof(SyncRequest) + output.size());
+                }
+
+                if (result == EncodeResult::Done) {
+                    sending = false;
+                    break;
+                } else if (result == EncodeResult::NeedInput) {
+                    break;
+                } else if (result == EncodeResult::MoreOutput) {
+                    continue;
+                }
+            }
+        }
+
+        syncmsg msg;
+        msg.data.id = ID_DONE;
+        msg.data.size = mtime;
+        RecordFileSent(lpath, rpath);
+        return WriteOrDie(lpath, rpath, &msg.data, sizeof(msg.data));
+    }
+
+    bool SendLargeFileLegacy(const std::string& path, mode_t mode, const std::string& lpath,
+                             const std::string& rpath, unsigned mtime) {
         std::string path_and_mode = android::base::StringPrintf("%s,%d", path.c_str(), mode);
-        if (!SendRequest(ID_SEND, path_and_mode)) {
-            Error("failed to send ID_SEND message '%s': %s", path_and_mode.c_str(),
+        if (!SendRequest(ID_SEND_V1, path_and_mode)) {
+            Error("failed to send ID_SEND_V1 message '%s': %s", path_and_mode.c_str(),
                   strerror(errno));
             return false;
         }
@@ -469,7 +719,7 @@
         uint64_t total_size = st.st_size;
         uint64_t bytes_copied = 0;
 
-        unique_fd lfd(adb_open(lpath.c_str(), O_RDONLY));
+        unique_fd lfd(adb_open(lpath.c_str(), O_RDONLY | O_CLOEXEC));
         if (lfd < 0) {
             Error("opening '%s' locally failed: %s", lpath.c_str(), strerror(errno));
             return false;
@@ -477,8 +727,9 @@
 
         syncsendbuf sbuf;
         sbuf.id = ID_DATA;
+
         while (true) {
-            int bytes_read = adb_read(lfd, sbuf.data, max - sizeof(SyncRequest));
+            int bytes_read = adb_read(lfd, sbuf.data, max);
             if (bytes_read == -1) {
                 Error("reading '%s' locally failed: %s", lpath.c_str(), strerror(errno));
                 return false;
@@ -491,7 +742,6 @@
 
             RecordBytesTransferred(bytes_read);
             bytes_copied += bytes_read;
-
             ReportProgress(rpath, bytes_copied, total_size);
         }
 
@@ -502,34 +752,6 @@
         return WriteOrDie(lpath, rpath, &msg.data, sizeof(msg.data));
     }
 
-    bool ReadAcknowledgments() {
-        bool result = true;
-        while (!deferred_acknowledgements_.empty()) {
-            auto [from, to] = std::move(deferred_acknowledgements_.front());
-            deferred_acknowledgements_.pop_front();
-            result &= CopyDone(from, to);
-        }
-        return result;
-    }
-
-    bool CopyDone(const std::string& from, const std::string& to) {
-        syncmsg msg;
-        if (!ReadFdExactly(fd, &msg.status, sizeof(msg.status))) {
-            Error("failed to copy '%s' to '%s': couldn't read from device", from.c_str(),
-                  to.c_str());
-            return false;
-        }
-        if (msg.status.id == ID_OKAY) {
-            return true;
-        }
-        if (msg.status.id != ID_FAIL) {
-            Error("failed to copy '%s' to '%s': unknown reason %d", from.c_str(), to.c_str(),
-                  msg.status.id);
-            return false;
-        }
-        return ReportCopyFailure(from, to, msg);
-    }
-
     bool ReportCopyFailure(const std::string& from, const std::string& to, const syncmsg& msg) {
         std::vector<char> buf(msg.status.msglen + 1);
         if (!ReadFdExactly(fd, &buf[0], msg.status.msglen)) {
@@ -542,6 +764,97 @@
         return false;
     }
 
+    void CopyDone() { deferred_acknowledgements_.pop_front(); }
+
+    void ReportDeferredCopyFailure(const std::string& msg) {
+        auto& [from, to] = deferred_acknowledgements_.front();
+        Error("failed to copy '%s' to '%s': remote %s", from.c_str(), to.c_str(), msg.c_str());
+        deferred_acknowledgements_.pop_front();
+    }
+
+    bool ReadAcknowledgements(bool read_all = false) {
+        // We need to read enough such that adbd's intermediate socket's write buffer can't be
+        // full. The default buffer on Linux is 212992 bytes, but there's 576 bytes of bookkeeping
+        // overhead per write. The worst case scenario is a continuous string of failures, since
+        // each logical packet is divided into two writes. If our packet size if conservatively 512
+        // bytes long, this leaves us with space for 128 responses.
+        constexpr size_t max_deferred_acks = 128;
+        auto& buf = acknowledgement_buffer_;
+        adb_pollfd pfd = {.fd = fd.get(), .events = POLLIN};
+        while (!deferred_acknowledgements_.empty()) {
+            bool should_block = read_all || deferred_acknowledgements_.size() >= max_deferred_acks;
+
+            ssize_t rc = adb_poll(&pfd, 1, should_block ? -1 : 0);
+            if (rc == 0) {
+                CHECK(!should_block);
+                return true;
+            }
+
+            if (acknowledgement_buffer_.size() < sizeof(sync_status)) {
+                const ssize_t header_bytes_left = sizeof(sync_status) - buf.size();
+                ssize_t rc = adb_read(fd, buf.end(), header_bytes_left);
+                if (rc <= 0) {
+                    Error("failed to read copy response");
+                    return false;
+                }
+
+                buf.resize(buf.size() + rc);
+                if (rc != header_bytes_left) {
+                    // Early exit if we run out of data in the socket.
+                    return true;
+                }
+
+                if (!should_block) {
+                    // We don't want to read again yet, because the socket might be empty.
+                    continue;
+                }
+            }
+
+            auto* hdr = reinterpret_cast<sync_status*>(buf.data());
+            if (hdr->id == ID_OKAY) {
+                buf.resize(0);
+                if (hdr->msglen != 0) {
+                    Error("received ID_OKAY with msg_len (%" PRIu32 " != 0", hdr->msglen);
+                    return false;
+                }
+                CopyDone();
+                continue;
+            } else if (hdr->id != ID_FAIL) {
+                Error("unexpected response from daemon: id = %#" PRIx32, hdr->id);
+                return false;
+            } else if (hdr->msglen > SYNC_DATA_MAX) {
+                Error("too-long message length from daemon: msglen = %" PRIu32, hdr->msglen);
+                return false;
+            }
+
+            const ssize_t msg_bytes_left = hdr->msglen + sizeof(sync_status) - buf.size();
+            CHECK_GE(msg_bytes_left, 0);
+            if (msg_bytes_left > 0) {
+                ssize_t rc = adb_read(fd, buf.end(), msg_bytes_left);
+                if (rc <= 0) {
+                    Error("failed to read copy failure message");
+                    return false;
+                }
+
+                buf.resize(buf.size() + rc);
+                if (rc != msg_bytes_left) {
+                    if (should_block) {
+                        continue;
+                    } else {
+                        return true;
+                    }
+                }
+
+                std::string msg(buf.begin() + sizeof(sync_status), buf.end());
+                ReportDeferredCopyFailure(msg);
+                buf.resize(0);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     void Printf(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
         std::string s;
 
@@ -608,9 +921,14 @@
 
   private:
     std::deque<std::pair<std::string, std::string>> deferred_acknowledgements_;
-    FeatureSet features_;
+    Block acknowledgement_buffer_;
+    const FeatureSet* features_ = nullptr;
     bool have_stat_v2_;
     bool have_ls_v2_;
+    bool have_sendrecv_v2_;
+    bool have_sendrecv_v2_brotli_;
+    bool have_sendrecv_v2_lz4_;
+    bool have_sendrecv_v2_dry_run_send_;
 
     TransferLedger global_ledger_;
     TransferLedger current_ledger_;
@@ -692,7 +1010,8 @@
 }
 
 static bool sync_send(SyncConnection& sc, const std::string& lpath, const std::string& rpath,
-                      unsigned mtime, mode_t mode, bool sync) {
+                      unsigned mtime, mode_t mode, bool sync, CompressionType compression,
+                      bool dry_run) {
     if (sync) {
         struct stat st;
         if (sync_lstat(sc, rpath, &st)) {
@@ -713,10 +1032,10 @@
         }
         buf[data_length++] = '\0';
 
-        if (!sc.SendSmallFile(rpath, mode, lpath, rpath, mtime, buf, data_length)) {
+        if (!sc.SendSmallFile(rpath, mode, lpath, rpath, mtime, buf, data_length, dry_run)) {
             return false;
         }
-        return true;
+        return sc.ReadAcknowledgements();
 #endif
     }
 
@@ -731,20 +1050,21 @@
             sc.Error("failed to read all of '%s': %s", lpath.c_str(), strerror(errno));
             return false;
         }
-        if (!sc.SendSmallFile(rpath, mode, lpath, rpath, mtime, data.data(), data.size())) {
+        if (!sc.SendSmallFile(rpath, mode, lpath, rpath, mtime, data.data(), data.size(),
+                              dry_run)) {
             return false;
         }
     } else {
-        if (!sc.SendLargeFile(rpath, mode, lpath, rpath, mtime)) {
+        if (!sc.SendLargeFile(rpath, mode, lpath, rpath, mtime, compression, dry_run)) {
             return false;
         }
     }
-    return true;
+    return sc.ReadAcknowledgements();
 }
 
-static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath,
-                      const char* name, uint64_t expected_size) {
-    if (!sc.SendRequest(ID_RECV, rpath)) return false;
+static bool sync_recv_v1(SyncConnection& sc, const char* rpath, const char* lpath, const char* name,
+                         uint64_t expected_size) {
+    if (!sc.SendRequest(ID_RECV_V1, rpath)) return false;
 
     adb_unlink(lpath);
     unique_fd lfd(adb_creat(lpath, 0644));
@@ -797,6 +1117,119 @@
     return true;
 }
 
+static bool sync_recv_v2(SyncConnection& sc, const char* rpath, const char* lpath, const char* name,
+                         uint64_t expected_size, CompressionType compression) {
+    compression = sc.ResolveCompressionType(compression);
+
+    if (!sc.SendRecv2(rpath, compression)) return false;
+
+    adb_unlink(lpath);
+    unique_fd lfd(adb_creat(lpath, 0644));
+    if (lfd < 0) {
+        sc.Error("cannot create '%s': %s", lpath, strerror(errno));
+        return false;
+    }
+
+    uint64_t bytes_copied = 0;
+
+    Block buffer(SYNC_DATA_MAX);
+    std::variant<std::monostate, NullDecoder, BrotliDecoder, LZ4Decoder> decoder_storage;
+    Decoder* decoder = nullptr;
+
+    std::span buffer_span(buffer.data(), buffer.size());
+    switch (compression) {
+        case CompressionType::None:
+            decoder = &decoder_storage.emplace<NullDecoder>(buffer_span);
+            break;
+
+        case CompressionType::Brotli:
+            decoder = &decoder_storage.emplace<BrotliDecoder>(buffer_span);
+            break;
+
+        case CompressionType::LZ4:
+            decoder = &decoder_storage.emplace<LZ4Decoder>(buffer_span);
+            break;
+
+        case CompressionType::Any:
+            LOG(FATAL) << "unexpected CompressionType::Any";
+    }
+
+    while (true) {
+        syncmsg msg;
+        if (!ReadFdExactly(sc.fd, &msg.data, sizeof(msg.data))) {
+            adb_unlink(lpath);
+            return false;
+        }
+
+        if (msg.data.id == ID_DONE) {
+            if (!decoder->Finish()) {
+                sc.Error("unexpected ID_DONE");
+                return false;
+            }
+        } else if (msg.data.id != ID_DATA) {
+            adb_unlink(lpath);
+            sc.ReportCopyFailure(rpath, lpath, msg);
+            return false;
+        } else {
+            if (msg.data.size > sc.max) {
+                sc.Error("msg.data.size too large: %u (max %zu)", msg.data.size, sc.max);
+                adb_unlink(lpath);
+                return false;
+            }
+
+            Block block(msg.data.size);
+            if (!ReadFdExactly(sc.fd, block.data(), msg.data.size)) {
+                adb_unlink(lpath);
+                return false;
+            }
+            decoder->Append(std::move(block));
+        }
+
+        while (true) {
+            std::span<char> output;
+            DecodeResult result = decoder->Decode(&output);
+
+            if (result == DecodeResult::Error) {
+                sc.Error("decompress failed");
+                adb_unlink(lpath);
+                return false;
+            }
+
+            if (!output.empty()) {
+                if (!WriteFdExactly(lfd, output.data(), output.size())) {
+                    sc.Error("cannot write '%s': %s", lpath, strerror(errno));
+                    adb_unlink(lpath);
+                    return false;
+                }
+            }
+
+            bytes_copied += output.size();
+            sc.RecordBytesTransferred(output.size());
+            sc.ReportProgress(name != nullptr ? name : rpath, bytes_copied, expected_size);
+
+            if (result == DecodeResult::NeedInput) {
+                break;
+            } else if (result == DecodeResult::MoreOutput) {
+                continue;
+            } else if (result == DecodeResult::Done) {
+                sc.RecordFilesTransferred(1);
+                return true;
+            } else {
+                LOG(FATAL) << "invalid DecodeResult: " << static_cast<int>(result);
+            }
+        }
+    }
+}
+
+static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath, const char* name,
+                      uint64_t expected_size, CompressionType compression) {
+    if (sc.HaveSendRecv2()) {
+        return sync_recv_v2(sc, rpath, lpath, name, expected_size, compression);
+    } else {
+        return sync_recv_v1(sc, rpath, lpath, name, expected_size);
+    }
+}
+
 bool do_sync_ls(const char* path) {
     SyncConnection sc;
     if (!sc.IsValid()) return false;
@@ -872,9 +1305,9 @@
     return true;
 }
 
-static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath,
-                                  std::string rpath, bool check_timestamps,
-                                  bool list_only) {
+static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath, std::string rpath,
+                                  bool check_timestamps, bool list_only,
+                                  CompressionType compression, bool dry_run) {
     sc.NewTransfer();
 
     // Make sure that both directory paths end in a slash.
@@ -956,7 +1389,8 @@
             if (list_only) {
                 sc.Println("would push: %s -> %s", ci.lpath.c_str(), ci.rpath.c_str());
             } else {
-                if (!sync_send(sc, ci.lpath, ci.rpath, ci.time, ci.mode, false)) {
+                if (!sync_send(sc, ci.lpath, ci.rpath, ci.time, ci.mode, false, compression,
+                               dry_run)) {
                     return false;
                 }
             }
@@ -966,11 +1400,13 @@
     }
 
     sc.RecordFilesSkipped(skipped);
+    bool success = sc.ReadAcknowledgements(true);
     sc.ReportTransferRate(lpath, TransferDirection::push);
-    return true;
+    return success;
 }
 
-bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync) {
+bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync,
+                  CompressionType compression, bool dry_run) {
     SyncConnection sc;
     if (!sc.IsValid()) return false;
 
@@ -1035,7 +1471,8 @@
                 dst_dir.append(android::base::Basename(src_path));
             }
 
-            success &= copy_local_dir_remote(sc, src_path, dst_dir, sync, false);
+            success &=
+                    copy_local_dir_remote(sc, src_path, dst_dir, sync, false, compression, dry_run);
             continue;
         } else if (!should_push_file(st.st_mode)) {
             sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, st.st_mode);
@@ -1056,11 +1493,12 @@
 
         sc.NewTransfer();
         sc.SetExpectedTotalBytes(st.st_size);
-        success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync);
+        success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync, compression,
+                             dry_run);
         sc.ReportTransferRate(src_path, TransferDirection::push);
     }
 
-    success &= sc.ReadAcknowledgments();
+    success &= sc.ReadAcknowledgements(true);
     sc.ReportOverallTransferRate(TransferDirection::push);
     return success;
 }
@@ -1141,8 +1579,8 @@
     return r1 ? r1 : r2;
 }
 
-static bool copy_remote_dir_local(SyncConnection& sc, std::string rpath,
-                                  std::string lpath, bool copy_attrs) {
+static bool copy_remote_dir_local(SyncConnection& sc, std::string rpath, std::string lpath,
+                                  bool copy_attrs, CompressionType compression) {
     sc.NewTransfer();
 
     // Make sure that both directory paths end in a slash.
@@ -1172,7 +1610,7 @@
                 continue;
             }
 
-            if (!sync_recv(sc, ci.rpath.c_str(), ci.lpath.c_str(), nullptr, ci.size)) {
+            if (!sync_recv(sc, ci.rpath.c_str(), ci.lpath.c_str(), nullptr, ci.size, compression)) {
                 return false;
             }
 
@@ -1189,8 +1627,8 @@
     return true;
 }
 
-bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
-                  bool copy_attrs, const char* name) {
+bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
+                  CompressionType compression, const char* name) {
     SyncConnection sc;
     if (!sc.IsValid()) return false;
 
@@ -1264,7 +1702,7 @@
                 dst_dir.append(android::base::Basename(src_path));
             }
 
-            success &= copy_remote_dir_local(sc, src_path, dst_dir, copy_attrs);
+            success &= copy_remote_dir_local(sc, src_path, dst_dir, copy_attrs, compression);
             continue;
         } else if (!should_pull_file(src_st.st_mode)) {
             sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, src_st.st_mode);
@@ -1283,7 +1721,7 @@
 
         sc.NewTransfer();
         sc.SetExpectedTotalBytes(src_st.st_size);
-        if (!sync_recv(sc, src_path, dst_path, name, src_st.st_size)) {
+        if (!sync_recv(sc, src_path, dst_path, name, src_st.st_size, compression)) {
             success = false;
             continue;
         }
@@ -1299,11 +1737,12 @@
     return success;
 }
 
-bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only) {
+bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only,
+                  CompressionType compression, bool dry_run) {
     SyncConnection sc;
     if (!sc.IsValid()) return false;
 
-    bool success = copy_local_dir_remote(sc, lpath, rpath, true, list_only);
+    bool success = copy_local_dir_remote(sc, lpath, rpath, true, list_only, compression, dry_run);
     if (!list_only) {
         sc.ReportOverallTransferRate(TransferDirection::push);
     }
diff --git a/adb/client/file_sync_client.h b/adb/client/file_sync_client.h
index df7f14c..cb8ca93 100644
--- a/adb/client/file_sync_client.h
+++ b/adb/client/file_sync_client.h
@@ -19,9 +19,13 @@
 #include <string>
 #include <vector>
 
-bool do_sync_ls(const char* path);
-bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync);
-bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
-                  const char* name = nullptr);
+#include "file_sync_protocol.h"
 
-bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only);
+bool do_sync_ls(const char* path);
+bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync,
+                  CompressionType compression, bool dry_run);
+bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
+                  CompressionType compression, const char* name = nullptr);
+
+bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only,
+                  CompressionType compression, bool dry_run);
diff --git a/adb/client/incremental.cpp b/adb/client/incremental.cpp
index 6499d46..2814932 100644
--- a/adb/client/incremental.cpp
+++ b/adb/client/incremental.cpp
@@ -16,13 +16,13 @@
 
 #include "incremental.h"
 
-#include <android-base/endian.h>
+#include "incremental_utils.h"
+
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <openssl/base64.h>
 
 #include "adb_client.h"
-#include "adb_io.h"
 #include "adb_utils.h"
 #include "commandline.h"
 #include "sysdeps.h"
@@ -31,99 +31,60 @@
 
 namespace incremental {
 
-namespace {
-
-static constexpr auto IDSIG = ".idsig"sv;
-
 using android::base::StringPrintf;
 
-using Size = int64_t;
-
-static inline int32_t read_int32(borrowed_fd fd) {
-    int32_t result;
-    ReadFully(fd, &result, sizeof(result));
-    return result;
-}
-
-static inline int32_t read_be_int32(borrowed_fd fd) {
-    return int32_t(be32toh(read_int32(fd)));
-}
-
-static inline void append_int(borrowed_fd fd, std::vector<char>* bytes) {
-    int32_t be_val = read_int32(fd);
-    auto old_size = bytes->size();
-    bytes->resize(old_size + sizeof(be_val));
-    memcpy(bytes->data() + old_size, &be_val, sizeof(be_val));
-}
-
-static inline void append_bytes_with_size(borrowed_fd fd, std::vector<char>* bytes) {
-    int32_t be_size = read_int32(fd);
-    int32_t size = int32_t(be32toh(be_size));
-    auto old_size = bytes->size();
-    bytes->resize(old_size + sizeof(be_size) + size);
-    memcpy(bytes->data() + old_size, &be_size, sizeof(be_size));
-    ReadFully(fd, bytes->data() + old_size + sizeof(be_size), size);
-}
-
-static inline std::pair<std::vector<char>, int32_t> read_id_sig_headers(borrowed_fd fd) {
-    std::vector<char> result;
-    append_int(fd, &result);              // version
-    append_bytes_with_size(fd, &result);  // verityRootHash
-    append_bytes_with_size(fd, &result);  // v3Digest
-    append_bytes_with_size(fd, &result);  // pkcs7SignatureBlock
-    auto tree_size = read_be_int32(fd);   // size of the verity tree
-    return {std::move(result), tree_size};
-}
-
-static inline Size verity_tree_size_for_file(Size fileSize) {
-    constexpr int INCFS_DATA_FILE_BLOCK_SIZE = 4096;
-    constexpr int SHA256_DIGEST_SIZE = 32;
-    constexpr int digest_size = SHA256_DIGEST_SIZE;
-    constexpr int hash_per_block = INCFS_DATA_FILE_BLOCK_SIZE / digest_size;
-
-    Size total_tree_block_count = 0;
-
-    auto block_count = 1 + (fileSize - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
-    auto hash_block_count = block_count;
-    for (auto i = 0; hash_block_count > 1; i++) {
-        hash_block_count = (hash_block_count + hash_per_block - 1) / hash_per_block;
-        total_tree_block_count += hash_block_count;
-    }
-    return total_tree_block_count * INCFS_DATA_FILE_BLOCK_SIZE;
-}
-
-// Base64-encode signature bytes. Keeping fd at the position of start of verity tree.
-static std::pair<unique_fd, std::string> read_and_encode_signature(Size file_size,
-                                                                   std::string signature_file) {
+// Read, verify and return the signature bytes. Keeping fd at the position of start of verity tree.
+static std::pair<unique_fd, std::vector<char>> read_signature(Size file_size,
+                                                              std::string signature_file,
+                                                              bool silent) {
     signature_file += IDSIG;
 
     struct stat st;
     if (stat(signature_file.c_str(), &st)) {
-        fprintf(stderr, "Failed to stat signature file %s. Abort.\n", signature_file.c_str());
+        if (!silent) {
+            fprintf(stderr, "Failed to stat signature file %s. Abort.\n", signature_file.c_str());
+        }
         return {};
     }
 
-    unique_fd fd(adb_open(signature_file.c_str(), O_RDONLY | O_CLOEXEC));
+    unique_fd fd(adb_open(signature_file.c_str(), O_RDONLY));
     if (fd < 0) {
-        fprintf(stderr, "Failed to open signature file: %s. Abort.\n", signature_file.c_str());
+        if (!silent) {
+            fprintf(stderr, "Failed to open signature file: %s. Abort.\n", signature_file.c_str());
+        }
         return {};
     }
 
     auto [signature, tree_size] = read_id_sig_headers(fd);
     if (auto expected = verity_tree_size_for_file(file_size); tree_size != expected) {
-        fprintf(stderr,
-                "Verity tree size mismatch in signature file: %s [was %lld, expected %lld].\n",
-                signature_file.c_str(), (long long)tree_size, (long long)expected);
+        if (!silent) {
+            fprintf(stderr,
+                    "Verity tree size mismatch in signature file: %s [was %lld, expected %lld].\n",
+                    signature_file.c_str(), (long long)tree_size, (long long)expected);
+        }
+        return {};
+    }
+
+    return {std::move(fd), std::move(signature)};
+}
+
+// Base64-encode signature bytes. Keeping fd at the position of start of verity tree.
+static std::pair<unique_fd, std::string> read_and_encode_signature(Size file_size,
+                                                                   std::string signature_file,
+                                                                   bool silent) {
+    auto [fd, signature] = read_signature(file_size, std::move(signature_file), silent);
+    if (!fd.ok()) {
         return {};
     }
 
     size_t base64_len = 0;
     if (!EVP_EncodedLength(&base64_len, signature.size())) {
-        fprintf(stderr, "Fail to estimate base64 encoded length. Abort.\n");
+        if (!silent) {
+            fprintf(stderr, "Fail to estimate base64 encoded length. Abort.\n");
+        }
         return {};
     }
-    std::string encoded_signature;
-    encoded_signature.resize(base64_len);
+    std::string encoded_signature(base64_len, '\0');
     encoded_signature.resize(EVP_EncodeBlock((uint8_t*)encoded_signature.data(),
                                              (const uint8_t*)signature.data(), signature.size()));
 
@@ -132,7 +93,7 @@
 
 // Send install-incremental to the device along with properly configured file descriptors in
 // streaming format. Once connection established, send all fs-verity tree bytes.
-static unique_fd start_install(const std::vector<std::string>& files) {
+static unique_fd start_install(const Files& files, bool silent) {
     std::vector<std::string> command_args{"package", "install-incremental"};
 
     // fd's with positions at the beginning of fs-verity
@@ -143,18 +104,19 @@
 
         struct stat st;
         if (stat(file.c_str(), &st)) {
-            fprintf(stderr, "Failed to stat input file %s. Abort.\n", file.c_str());
+            if (!silent) {
+                fprintf(stderr, "Failed to stat input file %s. Abort.\n", file.c_str());
+            }
             return {};
         }
 
-        auto [signature_fd, signature] = read_and_encode_signature(st.st_size, file);
+        auto [signature_fd, signature] = read_and_encode_signature(st.st_size, file, silent);
         if (!signature_fd.ok()) {
             return {};
         }
 
-        auto file_desc =
-                StringPrintf("%s:%lld:%s:%s", android::base::Basename(file).c_str(),
-                             (long long)st.st_size, std::to_string(i).c_str(), signature.c_str());
+        auto file_desc = StringPrintf("%s:%lld:%d:%s:1", android::base::Basename(file).c_str(),
+                                      (long long)st.st_size, i, signature.c_str());
         command_args.push_back(std::move(file_desc));
 
         signature_fds.push_back(std::move(signature_fd));
@@ -163,54 +125,115 @@
     std::string error;
     auto connection_fd = unique_fd(send_abb_exec_command(command_args, &error));
     if (connection_fd < 0) {
-        fprintf(stderr, "Failed to run: %s, error: %s\n",
-                android::base::Join(command_args, " ").c_str(), error.c_str());
-        return {};
-    }
-
-    // Pushing verity trees for all installation files.
-    for (auto&& local_fd : signature_fds) {
-        if (!copy_to_file(local_fd.get(), connection_fd.get())) {
-            fprintf(stderr, "Failed to stream tree bytes: %s. Abort.\n", strerror(errno));
-            return {};
+        if (!silent) {
+            fprintf(stderr, "Failed to run: %s, error: %s\n",
+                    android::base::Join(command_args, " ").c_str(), error.c_str());
         }
+        return {};
     }
 
     return connection_fd;
 }
 
-}  // namespace
+bool can_install(const Files& files) {
+    for (const auto& file : files) {
+        struct stat st;
+        if (stat(file.c_str(), &st)) {
+            return false;
+        }
 
-std::optional<Process> install(std::vector<std::string> files) {
-    auto connection_fd = start_install(files);
+        auto [fd, _] = read_signature(st.st_size, file, true);
+        if (!fd.ok()) {
+            return false;
+        }
+    }
+    return true;
+}
+
+std::optional<Process> install(const Files& files, bool silent) {
+    auto connection_fd = start_install(files, silent);
     if (connection_fd < 0) {
-        fprintf(stderr, "adb: failed to initiate installation on device.\n");
+        if (!silent) {
+            fprintf(stderr, "adb: failed to initiate installation on device.\n");
+        }
         return {};
     }
 
     std::string adb_path = android::base::GetExecutablePath();
 
-    auto osh = adb_get_os_handle(connection_fd.get());
-#ifdef _WIN32
-    auto fd_param = std::to_string(reinterpret_cast<intptr_t>(osh));
-#else /* !_WIN32 a.k.a. Unix */
+    auto osh = cast_handle_to_int(adb_get_os_handle(connection_fd.get()));
     auto fd_param = std::to_string(osh);
-#endif
+
+    // pipe for child process to write output
+    int print_fds[2];
+    if (adb_socketpair(print_fds) != 0) {
+        if (!silent) {
+            fprintf(stderr, "Failed to create socket pair for child to print to parent\n");
+        }
+        return {};
+    }
+    auto [pipe_read_fd, pipe_write_fd] = print_fds;
+    auto pipe_write_fd_param = std::to_string(cast_handle_to_int(adb_get_os_handle(pipe_write_fd)));
+    close_on_exec(pipe_read_fd);
 
     std::vector<std::string> args(std::move(files));
-    args.insert(args.begin(), {"inc-server", fd_param});
-    auto child = adb_launch_process(adb_path, std::move(args), {connection_fd.get()});
+    args.insert(args.begin(), {"inc-server", fd_param, pipe_write_fd_param});
+    auto child =
+            adb_launch_process(adb_path, std::move(args), {connection_fd.get(), pipe_write_fd});
     if (!child) {
-        fprintf(stderr, "adb: failed to fork: %s\n", strerror(errno));
+        if (!silent) {
+            fprintf(stderr, "adb: failed to fork: %s\n", strerror(errno));
+        }
         return {};
     }
 
+    adb_close(pipe_write_fd);
+
     auto killOnExit = [](Process* p) { p->kill(); };
     std::unique_ptr<Process, decltype(killOnExit)> serverKiller(&child, killOnExit);
-    // TODO: Terminate server process if installation fails.
-    serverKiller.release();
 
+    Result result = wait_for_installation(pipe_read_fd);
+    adb_close(pipe_read_fd);
+
+    if (result == Result::Success) {
+        // adb client exits now but inc-server can continue
+        serverKiller.release();
+    }
     return child;
 }
 
+Result wait_for_installation(int read_fd) {
+    static constexpr int maxMessageSize = 256;
+    std::vector<char> child_stdout(CHUNK_SIZE);
+    int bytes_read;
+    int buf_size = 0;
+    // TODO(b/150865433): optimize child's output parsing
+    while ((bytes_read = adb_read(read_fd, child_stdout.data() + buf_size,
+                                  child_stdout.size() - buf_size)) > 0) {
+        // print to parent's stdout
+        fprintf(stdout, "%.*s", bytes_read, child_stdout.data() + buf_size);
+
+        buf_size += bytes_read;
+        const std::string_view stdout_str(child_stdout.data(), buf_size);
+        // wait till installation either succeeds or fails
+        if (stdout_str.find("Success") != std::string::npos) {
+            return Result::Success;
+        }
+        // on failure, wait for full message
+        static constexpr auto failure_msg_head = "Failure ["sv;
+        if (const auto begin_itr = stdout_str.find(failure_msg_head);
+            begin_itr != std::string::npos) {
+            if (buf_size >= maxMessageSize) {
+                return Result::Failure;
+            }
+            const auto end_itr = stdout_str.rfind("]");
+            if (end_itr != std::string::npos && end_itr >= begin_itr + failure_msg_head.size()) {
+                return Result::Failure;
+            }
+        }
+        child_stdout.resize(buf_size + CHUNK_SIZE);
+    }
+    return Result::None;
+}
+
 }  // namespace incremental
diff --git a/adb/client/incremental.h b/adb/client/incremental.h
index 4b9f6bd..1fb1e0b 100644
--- a/adb/client/incremental.h
+++ b/adb/client/incremental.h
@@ -25,6 +25,12 @@
 
 namespace incremental {
 
-std::optional<Process> install(std::vector<std::string> files);
+using Files = std::vector<std::string>;
+
+bool can_install(const Files& files);
+std::optional<Process> install(const Files& files, bool silent);
+
+enum class Result { Success, Failure, None };
+Result wait_for_installation(int read_fd);
 
 }  // namespace incremental
diff --git a/adb/client/incremental_server.cpp b/adb/client/incremental_server.cpp
index 2512d05..bfe18c0 100644
--- a/adb/client/incremental_server.cpp
+++ b/adb/client/incremental_server.cpp
@@ -1,4 +1,4 @@
-/*
+/*
  * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,13 +18,6 @@
 
 #include "incremental_server.h"
 
-#include "adb.h"
-#include "adb_io.h"
-#include "adb_trace.h"
-#include "adb_unique_fd.h"
-#include "adb_utils.h"
-#include "sysdeps.h"
-
 #include <android-base/endian.h>
 #include <android-base/strings.h>
 #include <inttypes.h>
@@ -41,29 +34,42 @@
 #include <type_traits>
 #include <unordered_set>
 
+#include "adb.h"
+#include "adb_io.h"
+#include "adb_trace.h"
+#include "adb_unique_fd.h"
+#include "adb_utils.h"
+#include "incremental_utils.h"
+#include "sysdeps.h"
+
 namespace incremental {
 
-static constexpr int kBlockSize = 4096;
+static constexpr int kHashesPerBlock = kBlockSize / kDigestSize;
 static constexpr int kCompressedSizeMax = kBlockSize * 0.95;
-static constexpr short kCompressionNone = 0;
-static constexpr short kCompressionLZ4 = 1;
+static constexpr int8_t kTypeData = 0;
+static constexpr int8_t kTypeHash = 1;
+static constexpr int8_t kCompressionNone = 0;
+static constexpr int8_t kCompressionLZ4 = 1;
 static constexpr int kCompressBound = std::max(kBlockSize, LZ4_COMPRESSBOUND(kBlockSize));
 static constexpr auto kReadBufferSize = 128 * 1024;
+static constexpr int kPollTimeoutMillis = 300000;  // 5 minutes
 
 using BlockSize = int16_t;
 using FileId = int16_t;
 using BlockIdx = int32_t;
 using NumBlocks = int32_t;
-using CompressionType = int16_t;
+using BlockType = int8_t;
+using CompressionType = int8_t;
 using RequestType = int16_t;
 using ChunkHeader = int32_t;
 using MagicType = uint32_t;
 
 static constexpr MagicType INCR = 0x494e4352;  // LE INCR
 
-static constexpr RequestType EXIT = 0;
+static constexpr RequestType SERVING_COMPLETE = 0;
 static constexpr RequestType BLOCK_MISSING = 1;
 static constexpr RequestType PREFETCH = 2;
+static constexpr RequestType DESTROY = 3;
 
 static constexpr inline int64_t roundDownToBlockOffset(int64_t val) {
     return val & ~(kBlockSize - 1);
@@ -123,48 +129,77 @@
 // Placed before actual data bytes of each block
 struct ResponseHeader {
     FileId file_id;                    // 2 bytes
-    CompressionType compression_type;  // 2 bytes
+    BlockType block_type;              // 1 byte
+    CompressionType compression_type;  // 1 byte
     BlockIdx block_idx;                // 4 bytes
     BlockSize block_size;              // 2 bytes
+
+    static constexpr size_t responseSizeFor(size_t dataSize) {
+        return dataSize + sizeof(ResponseHeader);
+    }
+} __attribute__((packed));
+
+template <size_t Size = kBlockSize>
+struct BlockBuffer {
+    ResponseHeader header;
+    char data[Size];
 } __attribute__((packed));
 
 // Holds streaming state for a file
 class File {
   public:
     // Plain file
-    File(const char* filepath, FileId id, int64_t size, unique_fd fd) : File(filepath, id, size) {
+    File(const char* filepath, FileId id, int64_t size, unique_fd fd, int64_t tree_offset,
+         unique_fd tree_fd)
+        : File(filepath, id, size, tree_offset) {
         this->fd_ = std::move(fd);
+        this->tree_fd_ = std::move(tree_fd);
+        priority_blocks_ = PriorityBlocksForFile(filepath, fd_.get(), size);
     }
-    int64_t ReadBlock(BlockIdx block_idx, void* buf, bool* is_zip_compressed,
-                      std::string* error) const {
-        char* buf_ptr = static_cast<char*>(buf);
+    int64_t ReadDataBlock(BlockIdx block_idx, void* buf, bool* is_zip_compressed) const {
         int64_t bytes_read = -1;
         const off64_t offsetStart = blockIndexToOffset(block_idx);
-        bytes_read = adb_pread(fd_, &buf_ptr[sizeof(ResponseHeader)], kBlockSize, offsetStart);
+        bytes_read = adb_pread(fd_, buf, kBlockSize, offsetStart);
+        return bytes_read;
+    }
+    int64_t ReadTreeBlock(BlockIdx block_idx, void* buf) const {
+        int64_t bytes_read = -1;
+        const off64_t offsetStart = tree_offset_ + blockIndexToOffset(block_idx);
+        bytes_read = adb_pread(tree_fd_, buf, kBlockSize, offsetStart);
         return bytes_read;
     }
 
-    const unique_fd& RawFd() const { return fd_; }
+    const std::vector<BlockIdx>& PriorityBlocks() const { return priority_blocks_; }
 
     std::vector<bool> sentBlocks;
     NumBlocks sentBlocksCount = 0;
 
+    std::vector<bool> sentTreeBlocks;
+
     const char* const filepath;
     const FileId id;
     const int64_t size;
 
   private:
-    File(const char* filepath, FileId id, int64_t size) : filepath(filepath), id(id), size(size) {
+    File(const char* filepath, FileId id, int64_t size, int64_t tree_offset)
+        : filepath(filepath), id(id), size(size), tree_offset_(tree_offset) {
         sentBlocks.resize(numBytesToNumBlocks(size));
+        sentTreeBlocks.resize(verity_tree_blocks_for_file(size));
     }
     unique_fd fd_;
+    std::vector<BlockIdx> priority_blocks_;
+
+    unique_fd tree_fd_;
+    const int64_t tree_offset_;
 };
 
 class IncrementalServer {
   public:
-    IncrementalServer(unique_fd fd, std::vector<File> files)
-        : adb_fd_(std::move(fd)), files_(std::move(files)) {
+    IncrementalServer(unique_fd adb_fd, unique_fd output_fd, std::vector<File> files)
+        : adb_fd_(std::move(adb_fd)), output_fd_(std::move(output_fd)), files_(std::move(files)) {
         buffer_.reserve(kReadBufferSize);
+        pendingBlocksBuffer_.resize(kChunkFlushSize + 2 * kBlockSize);
+        pendingBlocks_ = pendingBlocksBuffer_.data() + sizeof(ChunkHeader);
     }
 
     bool Serve();
@@ -174,14 +209,23 @@
         const File* file;
         BlockIdx overallIndex = 0;
         BlockIdx overallEnd = 0;
+        BlockIdx priorityIndex = 0;
 
-        PrefetchState(const File& f) : file(&f), overallEnd((BlockIdx)f.sentBlocks.size()) {}
-        PrefetchState(const File& f, BlockIdx start, int count)
+        explicit PrefetchState(const File& f, BlockIdx start, int count)
             : file(&f),
               overallIndex(start),
               overallEnd(std::min<BlockIdx>(start + count, f.sentBlocks.size())) {}
 
-        bool done() const { return overallIndex >= overallEnd; }
+        explicit PrefetchState(const File& f)
+            : PrefetchState(f, 0, (BlockIdx)f.sentBlocks.size()) {}
+
+        bool done() const {
+            const bool overallSent = (overallIndex >= overallEnd);
+            if (file->PriorityBlocks().empty()) {
+                return overallSent;
+            }
+            return overallSent && (priorityIndex >= (BlockIdx)file->PriorityBlocks().size());
+        }
     };
 
     bool SkipToRequest(void* buffer, size_t* size, bool blocking);
@@ -190,16 +234,21 @@
     void erase_buffer_head(int count) { buffer_.erase(buffer_.begin(), buffer_.begin() + count); }
 
     enum class SendResult { Sent, Skipped, Error };
-    SendResult SendBlock(FileId fileId, BlockIdx blockIdx, bool flush = false);
+    SendResult SendDataBlock(FileId fileId, BlockIdx blockIdx, bool flush = false);
+
+    bool SendTreeBlock(FileId fileId, int32_t fileBlockIdx, BlockIdx blockIdx);
+    bool SendTreeBlocksForDataBlock(FileId fileId, BlockIdx blockIdx);
+
     bool SendDone();
     void RunPrefetching();
 
     void Send(const void* data, size_t size, bool flush);
     void Flush();
     using TimePoint = decltype(std::chrono::high_resolution_clock::now());
-    bool Exit(std::optional<TimePoint> startTime, int missesCount, int missesSent);
+    bool ServingComplete(std::optional<TimePoint> startTime, int missesCount, int missesSent);
 
     unique_fd const adb_fd_;
+    unique_fd const output_fd_;
     std::vector<File> files_;
 
     // Incoming data buffer.
@@ -209,7 +258,13 @@
     int compressed_ = 0, uncompressed_ = 0;
     long long sentSize_ = 0;
 
-    std::vector<char> pendingBlocks_;
+    static constexpr auto kChunkFlushSize = 31 * kBlockSize;
+
+    std::vector<char> pendingBlocksBuffer_;
+    char* pendingBlocks_ = nullptr;
+
+    // True when client notifies that all the data has been received
+    bool servingComplete_ = false;
 };
 
 bool IncrementalServer::SkipToRequest(void* buffer, size_t* size, bool blocking) {
@@ -217,7 +272,8 @@
         // Looking for INCR magic.
         bool magic_found = false;
         int bcur = 0;
-        for (int bsize = buffer_.size(); bcur + 4 < bsize; ++bcur) {
+        int bsize = buffer_.size();
+        for (bcur = 0; bcur + 4 < bsize; ++bcur) {
             uint32_t magic = be32toh(*(uint32_t*)(buffer_.data() + bcur));
             if (magic == INCR) {
                 magic_found = true;
@@ -226,8 +282,8 @@
         }
 
         if (bcur > 0) {
-            // Stream the rest to stderr.
-            fprintf(stderr, "%.*s", bcur, buffer_.data());
+            // output the rest.
+            (void)WriteFdExactly(output_fd_, buffer_.data(), bcur);
             erase_buffer_head(bcur);
         }
 
@@ -239,17 +295,27 @@
         }
 
         adb_pollfd pfd = {adb_fd_.get(), POLLIN, 0};
-        auto res = adb_poll(&pfd, 1, blocking ? -1 : 0);
+        auto res = adb_poll(&pfd, 1, blocking ? kPollTimeoutMillis : 0);
+
         if (res != 1) {
+            auto err = errno;
+            (void)WriteFdExactly(output_fd_, buffer_.data(), buffer_.size());
             if (res < 0) {
-                fprintf(stderr, "Failed to poll: %s\n", strerror(errno));
+                D("Failed to poll: %s", strerror(err));
+                return false;
+            }
+            if (blocking) {
+                fprintf(stderr, "Timed out waiting for data from device.\n");
+            }
+            if (blocking && servingComplete_) {
+                // timeout waiting from client. Serving is complete, so quit.
                 return false;
             }
             *size = 0;
             return true;
         }
 
-        auto bsize = buffer_.size();
+        bsize = buffer_.size();
         buffer_.resize(kReadBufferSize);
         int r = adb_read(adb_fd_, buffer_.data() + bsize, kReadBufferSize - bsize);
         if (r > 0) {
@@ -257,21 +323,19 @@
             continue;
         }
 
-        if (r == -1) {
-            fprintf(stderr, "Failed to read from fd %d: %d. Exit\n", adb_fd_.get(), errno);
-            return false;
-        }
-
-        // socket is closed
-        return false;
+        D("Failed to read from fd %d: %d. Exit", adb_fd_.get(), errno);
+        break;
     }
+    // socket is closed. print remaining messages
+    WriteFdExactly(output_fd_, buffer_.data(), buffer_.size());
+    return false;
 }
 
 std::optional<RequestCommand> IncrementalServer::ReadRequest(bool blocking) {
     uint8_t commandBuf[sizeof(RequestCommand)];
     auto size = sizeof(commandBuf);
     if (!SkipToRequest(&commandBuf, &size, blocking)) {
-        return {{EXIT}};
+        return {{DESTROY}};
     }
     if (size < sizeof(RequestCommand)) {
         return {};
@@ -283,60 +347,120 @@
     return request;
 }
 
-auto IncrementalServer::SendBlock(FileId fileId, BlockIdx blockIdx, bool flush) -> SendResult {
+bool IncrementalServer::SendTreeBlocksForDataBlock(const FileId fileId, const BlockIdx blockIdx) {
+    auto& file = files_[fileId];
+    const int32_t data_block_count = numBytesToNumBlocks(file.size);
+
+    const int32_t total_nodes_count(file.sentTreeBlocks.size());
+    const int32_t leaf_nodes_count = (data_block_count + kHashesPerBlock - 1) / kHashesPerBlock;
+
+    const int32_t leaf_nodes_offset = total_nodes_count - leaf_nodes_count;
+
+    // Leaf level, sending only 1 block.
+    const int32_t leaf_idx = leaf_nodes_offset + blockIdx / kHashesPerBlock;
+    if (file.sentTreeBlocks[leaf_idx]) {
+        return true;
+    }
+    if (!SendTreeBlock(fileId, blockIdx, leaf_idx)) {
+        return false;
+    }
+    file.sentTreeBlocks[leaf_idx] = true;
+
+    // Non-leaf, sending EVERYTHING. This should be done only once.
+    if (leaf_nodes_offset == 0 || file.sentTreeBlocks[0]) {
+        return true;
+    }
+
+    for (int32_t i = 0; i < leaf_nodes_offset; ++i) {
+        if (!SendTreeBlock(fileId, blockIdx, i)) {
+            return false;
+        }
+        file.sentTreeBlocks[i] = true;
+    }
+    return true;
+}
+
+bool IncrementalServer::SendTreeBlock(FileId fileId, int32_t fileBlockIdx, BlockIdx blockIdx) {
+    const auto& file = files_[fileId];
+
+    BlockBuffer buffer;
+    const int64_t bytesRead = file.ReadTreeBlock(blockIdx, buffer.data);
+    if (bytesRead <= 0) {
+        fprintf(stderr, "Failed to get data for %s.idsig at blockIdx=%d.\n", file.filepath,
+                blockIdx);
+        return false;
+    }
+
+    buffer.header.compression_type = kCompressionNone;
+    buffer.header.block_type = kTypeHash;
+    buffer.header.file_id = toBigEndian(fileId);
+    buffer.header.block_size = toBigEndian(int16_t(bytesRead));
+    buffer.header.block_idx = toBigEndian(blockIdx);
+
+    Send(&buffer, ResponseHeader::responseSizeFor(bytesRead), /*flush=*/false);
+
+    return true;
+}
+
+auto IncrementalServer::SendDataBlock(FileId fileId, BlockIdx blockIdx, bool flush) -> SendResult {
     auto& file = files_[fileId];
     if (blockIdx >= static_cast<long>(file.sentBlocks.size())) {
-        fprintf(stderr, "Failed to read file %s at block %" PRId32 " (past end).\n", file.filepath,
-                blockIdx);
-        return SendResult::Error;
+        // may happen as we schedule some extra blocks for reported page misses
+        D("Skipped reading file %s at block %" PRId32 " (past end).", file.filepath, blockIdx);
+        return SendResult::Skipped;
     }
     if (file.sentBlocks[blockIdx]) {
         return SendResult::Skipped;
     }
-    std::string error;
-    char raw[sizeof(ResponseHeader) + kBlockSize];
-    bool isZipCompressed = false;
-    const int64_t bytesRead = file.ReadBlock(blockIdx, &raw, &isZipCompressed, &error);
-    if (bytesRead < 0) {
-        fprintf(stderr, "Failed to get data for %s at blockIdx=%d (%s).\n", file.filepath, blockIdx,
-                error.c_str());
+
+    if (!SendTreeBlocksForDataBlock(fileId, blockIdx)) {
         return SendResult::Error;
     }
 
-    ResponseHeader* header = nullptr;
-    char data[sizeof(ResponseHeader) + kCompressBound];
-    char* compressed = data + sizeof(*header);
+    BlockBuffer raw;
+    bool isZipCompressed = false;
+    const int64_t bytesRead = file.ReadDataBlock(blockIdx, raw.data, &isZipCompressed);
+    if (bytesRead < 0) {
+        fprintf(stderr, "Failed to get data for %s at blockIdx=%d (%d).\n", file.filepath, blockIdx,
+                errno);
+        return SendResult::Error;
+    }
+
+    BlockBuffer<kCompressBound> compressed;
     int16_t compressedSize = 0;
     if (!isZipCompressed) {
-        compressedSize =
-                LZ4_compress_default(raw + sizeof(*header), compressed, bytesRead, kCompressBound);
+        compressedSize = LZ4_compress_default(raw.data, compressed.data, bytesRead, kCompressBound);
     }
     int16_t blockSize;
+    ResponseHeader* header;
     if (compressedSize > 0 && compressedSize < kCompressedSizeMax) {
         ++compressed_;
         blockSize = compressedSize;
-        header = reinterpret_cast<ResponseHeader*>(data);
-        header->compression_type = toBigEndian(kCompressionLZ4);
+        header = &compressed.header;
+        header->compression_type = kCompressionLZ4;
     } else {
         ++uncompressed_;
         blockSize = bytesRead;
-        header = reinterpret_cast<ResponseHeader*>(raw);
-        header->compression_type = toBigEndian(kCompressionNone);
+        header = &raw.header;
+        header->compression_type = kCompressionNone;
     }
 
+    header->block_type = kTypeData;
     header->file_id = toBigEndian(fileId);
     header->block_size = toBigEndian(blockSize);
     header->block_idx = toBigEndian(blockIdx);
 
     file.sentBlocks[blockIdx] = true;
     file.sentBlocksCount += 1;
-    Send(header, sizeof(*header) + blockSize, flush);
+    Send(header, ResponseHeader::responseSizeFor(blockSize), flush);
+
     return SendResult::Sent;
 }
 
 bool IncrementalServer::SendDone() {
     ResponseHeader header;
     header.file_id = -1;
+    header.block_type = 0;
     header.compression_type = 0;
     header.block_idx = 0;
     header.block_size = 0;
@@ -351,8 +475,20 @@
     while (!prefetches_.empty() && blocksToSend > 0) {
         auto& prefetch = prefetches_.front();
         const auto& file = *prefetch.file;
+        const auto& priority_blocks = file.PriorityBlocks();
+        if (!priority_blocks.empty()) {
+            for (auto& i = prefetch.priorityIndex;
+                 blocksToSend > 0 && i < (BlockIdx)priority_blocks.size(); ++i) {
+                if (auto res = SendDataBlock(file.id, priority_blocks[i]);
+                    res == SendResult::Sent) {
+                    --blocksToSend;
+                } else if (res == SendResult::Error) {
+                    fprintf(stderr, "Failed to send priority block %" PRId32 "\n", i);
+                }
+            }
+        }
         for (auto& i = prefetch.overallIndex; blocksToSend > 0 && i < prefetch.overallEnd; ++i) {
-            if (auto res = SendBlock(file.id, i); res == SendResult::Sent) {
+            if (auto res = SendDataBlock(file.id, i); res == SendResult::Sent) {
                 --blocksToSend;
             } else if (res == SendResult::Error) {
                 fprintf(stderr, "Failed to send block %" PRId32 "\n", i);
@@ -365,43 +501,38 @@
 }
 
 void IncrementalServer::Send(const void* data, size_t size, bool flush) {
-    constexpr auto kChunkFlushSize = 31 * kBlockSize;
-
-    if (pendingBlocks_.empty()) {
-        pendingBlocks_.resize(sizeof(ChunkHeader));
-    }
-    pendingBlocks_.insert(pendingBlocks_.end(), static_cast<const char*>(data),
-                          static_cast<const char*>(data) + size);
-    if (flush || pendingBlocks_.size() > kChunkFlushSize) {
+    pendingBlocks_ = std::copy_n(static_cast<const char*>(data), size, pendingBlocks_);
+    if (flush || pendingBlocks_ - pendingBlocksBuffer_.data() > kChunkFlushSize) {
         Flush();
     }
 }
 
 void IncrementalServer::Flush() {
-    if (pendingBlocks_.empty()) {
+    auto dataBytes = pendingBlocks_ - (pendingBlocksBuffer_.data() + sizeof(ChunkHeader));
+    if (dataBytes == 0) {
         return;
     }
 
-    *(ChunkHeader*)pendingBlocks_.data() =
-            toBigEndian<int32_t>(pendingBlocks_.size() - sizeof(ChunkHeader));
-    if (!WriteFdExactly(adb_fd_, pendingBlocks_.data(), pendingBlocks_.size())) {
-        fprintf(stderr, "Failed to write %d bytes\n", int(pendingBlocks_.size()));
+    *(ChunkHeader*)pendingBlocksBuffer_.data() = toBigEndian<int32_t>(dataBytes);
+    auto totalBytes = sizeof(ChunkHeader) + dataBytes;
+    if (!WriteFdExactly(adb_fd_, pendingBlocksBuffer_.data(), totalBytes)) {
+        fprintf(stderr, "Failed to write %d bytes\n", int(totalBytes));
     }
-    sentSize_ += pendingBlocks_.size();
-    pendingBlocks_.clear();
+    sentSize_ += totalBytes;
+    pendingBlocks_ = pendingBlocksBuffer_.data() + sizeof(ChunkHeader);
 }
 
-bool IncrementalServer::Exit(std::optional<TimePoint> startTime, int missesCount, int missesSent) {
+bool IncrementalServer::ServingComplete(std::optional<TimePoint> startTime, int missesCount,
+                                        int missesSent) {
+    servingComplete_ = true;
     using namespace std::chrono;
     auto endTime = high_resolution_clock::now();
-    fprintf(stderr,
-            "Connection failed or received exit command. Exit.\n"
-            "Misses: %d, of those unique: %d; sent compressed: %d, uncompressed: "
-            "%d, mb: %.3f\n"
-            "Total time taken: %.3fms\n",
-            missesCount, missesSent, compressed_, uncompressed_, sentSize_ / 1024.0 / 1024.0,
-            duration_cast<microseconds>(endTime - (startTime ? *startTime : endTime)).count() /
-                    1000.0);
+    D("Streaming completed.\n"
+      "Misses: %d, of those unique: %d; sent compressed: %d, uncompressed: "
+      "%d, mb: %.3f\n"
+      "Total time taken: %.3fms",
+      missesCount, missesSent, compressed_, uncompressed_, sentSize_ / 1024.0 / 1024.0,
+      duration_cast<microseconds>(endTime - (startTime ? *startTime : endTime)).count() / 1000.0);
     return true;
 }
 
@@ -425,7 +556,7 @@
             std::all_of(files_.begin(), files_.end(), [](const File& f) {
                 return f.sentBlocksCount == NumBlocks(f.sentBlocks.size());
             })) {
-            fprintf(stdout, "All files should be loaded. Notifying the device.\n");
+            fprintf(stderr, "All files should be loaded. Notifying the device.\n");
             SendDone();
             doneSent = true;
         }
@@ -446,9 +577,14 @@
             BlockIdx blockIdx = request->block_idx;
 
             switch (request->request_type) {
-                case EXIT: {
+                case DESTROY: {
                     // Stop everything.
-                    return Exit(startTime, missesCount, missesSent);
+                    return true;
+                }
+                case SERVING_COMPLETE: {
+                    // Not stopping the server here.
+                    ServingComplete(startTime, missesCount, missesSent);
+                    break;
                 }
                 case BLOCK_MISSING: {
                     ++missesCount;
@@ -461,9 +597,21 @@
                                 fileId, blockIdx);
                         break;
                     }
-                    // fprintf(stderr, "\treading file %d block %04d\n", (int)fileId,
-                    // (int)blockIdx);
-                    if (auto res = SendBlock(fileId, blockIdx, true); res == SendResult::Error) {
+
+                    if (VLOG_IS_ON(INCREMENTAL)) {
+                        auto& file = files_[fileId];
+                        auto posP = std::find(file.PriorityBlocks().begin(),
+                                              file.PriorityBlocks().end(), blockIdx);
+                        D("\tMISSING BLOCK: reading file %d block %04d (in priority: %d of %d)",
+                          (int)fileId, (int)blockIdx,
+                          posP == file.PriorityBlocks().end()
+                                  ? -1
+                                  : int(posP - file.PriorityBlocks().begin()),
+                          int(file.PriorityBlocks().size()));
+                    }
+
+                    if (auto res = SendDataBlock(fileId, blockIdx, true);
+                        res == SendResult::Error) {
                         fprintf(stderr, "Failed to send block %" PRId32 ".\n", blockIdx);
                     } else if (res == SendResult::Sent) {
                         ++missesSent;
@@ -487,7 +635,7 @@
                                 fileId);
                         break;
                     }
-                    D("Received prefetch request for file_id %" PRId16 ".\n", fileId);
+                    D("Received prefetch request for file_id %" PRId16 ".", fileId);
                     prefetches_.emplace_back(files_[fileId]);
                     break;
                 }
@@ -502,8 +650,46 @@
     }
 }
 
-bool serve(int adb_fd, int argc, const char** argv) {
-    auto connection_fd = unique_fd(adb_fd);
+static std::pair<unique_fd, int64_t> open_fd(const char* filepath) {
+    struct stat st;
+    if (stat(filepath, &st)) {
+        error_exit("inc-server: failed to stat input file '%s'.", filepath);
+    }
+
+    unique_fd fd(adb_open(filepath, O_RDONLY));
+    if (fd < 0) {
+        error_exit("inc-server: failed to open file '%s'.", filepath);
+    }
+
+    return {std::move(fd), st.st_size};
+}
+
+static std::pair<unique_fd, int64_t> open_signature(int64_t file_size, const char* filepath) {
+    std::string signature_file(filepath);
+    signature_file += IDSIG;
+
+    unique_fd fd(adb_open(signature_file.c_str(), O_RDONLY));
+    if (fd < 0) {
+        error_exit("inc-server: failed to open file '%s'.", signature_file.c_str());
+    }
+
+    auto [tree_offset, tree_size] = skip_id_sig_headers(fd);
+    if (auto expected = verity_tree_size_for_file(file_size); tree_size != expected) {
+        error_exit("Verity tree size mismatch in signature file: %s [was %lld, expected %lld].\n",
+                   signature_file.c_str(), (long long)tree_size, (long long)expected);
+    }
+
+    int32_t data_block_count = numBytesToNumBlocks(file_size);
+    int32_t leaf_nodes_count = (data_block_count + kHashesPerBlock - 1) / kHashesPerBlock;
+    D("Verity tree loaded: %s, tree size: %d (%d blocks, %d leafs)", signature_file.c_str(),
+      int(tree_size), int(numBytesToNumBlocks(tree_size)), int(leaf_nodes_count));
+
+    return {std::move(fd), tree_offset};
+}
+
+bool serve(int connection_fd, int output_fd, int argc, const char** argv) {
+    auto connection_ufd = unique_fd(connection_fd);
+    auto output_ufd = unique_fd(output_fd);
     if (argc <= 0) {
         error_exit("inc-server: must specify at least one file.");
     }
@@ -513,20 +699,14 @@
     for (int i = 0; i < argc; ++i) {
         auto filepath = argv[i];
 
-        struct stat st;
-        if (stat(filepath, &st)) {
-            fprintf(stderr, "Failed to stat input file %s. Abort.\n", filepath);
-            return {};
-        }
+        auto [file_fd, file_size] = open_fd(filepath);
+        auto [sign_fd, sign_offset] = open_signature(file_size, filepath);
 
-        unique_fd fd(adb_open(filepath, O_RDONLY));
-        if (fd < 0) {
-            error_exit("inc-server: failed to open file '%s'.", filepath);
-        }
-        files.emplace_back(filepath, i, st.st_size, std::move(fd));
+        files.emplace_back(filepath, i, file_size, std::move(file_fd), sign_offset,
+                           std::move(sign_fd));
     }
 
-    IncrementalServer server(std::move(connection_fd), std::move(files));
+    IncrementalServer server(std::move(connection_ufd), std::move(output_ufd), std::move(files));
     printf("Serving...\n");
     fclose(stdin);
     fclose(stdout);
diff --git a/adb/client/incremental_server.h b/adb/client/incremental_server.h
index 53f011e..55b8215 100644
--- a/adb/client/incremental_server.h
+++ b/adb/client/incremental_server.h
@@ -21,6 +21,6 @@
 // Expecting arguments like:
 // {FILE1 FILE2 ...}
 // Where FILE* are files to serve.
-bool serve(int adbFd, int argc, const char** argv);
+bool serve(int connection_fd, int output_fd, int argc, const char** argv);
 
 }  // namespace incremental
diff --git a/adb/client/incremental_utils.cpp b/adb/client/incremental_utils.cpp
new file mode 100644
index 0000000..dd117d2
--- /dev/null
+++ b/adb/client/incremental_utils.cpp
@@ -0,0 +1,378 @@
+/*
+ * 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 TRACE_TAG INCREMENTAL
+
+#include "incremental_utils.h"
+
+#include <android-base/endian.h>
+#include <android-base/mapped_file.h>
+#include <android-base/strings.h>
+#include <ziparchive/zip_archive.h>
+#include <ziparchive/zip_writer.h>
+
+#include <array>
+#include <cinttypes>
+#include <numeric>
+#include <unordered_set>
+
+#include "adb_io.h"
+#include "adb_trace.h"
+#include "sysdeps.h"
+
+using namespace std::literals;
+
+namespace incremental {
+
+static constexpr inline int32_t offsetToBlockIndex(int64_t offset) {
+    return (offset & ~(kBlockSize - 1)) >> 12;
+}
+
+Size verity_tree_blocks_for_file(Size fileSize) {
+    if (fileSize == 0) {
+        return 0;
+    }
+
+    constexpr int hash_per_block = kBlockSize / kDigestSize;
+
+    Size total_tree_block_count = 0;
+
+    auto block_count = 1 + (fileSize - 1) / kBlockSize;
+    auto hash_block_count = block_count;
+    for (auto i = 0; hash_block_count > 1; i++) {
+        hash_block_count = (hash_block_count + hash_per_block - 1) / hash_per_block;
+        total_tree_block_count += hash_block_count;
+    }
+    return total_tree_block_count;
+}
+
+Size verity_tree_size_for_file(Size fileSize) {
+    return verity_tree_blocks_for_file(fileSize) * kBlockSize;
+}
+
+static inline int32_t read_int32(borrowed_fd fd) {
+    int32_t result;
+    return ReadFdExactly(fd, &result, sizeof(result)) ? result : -1;
+}
+
+static inline int32_t skip_int(borrowed_fd fd) {
+    return adb_lseek(fd, 4, SEEK_CUR);
+}
+
+static inline void append_int(borrowed_fd fd, std::vector<char>* bytes) {
+    int32_t le_val = read_int32(fd);
+    auto old_size = bytes->size();
+    bytes->resize(old_size + sizeof(le_val));
+    memcpy(bytes->data() + old_size, &le_val, sizeof(le_val));
+}
+
+static inline void append_bytes_with_size(borrowed_fd fd, std::vector<char>* bytes) {
+    int32_t le_size = read_int32(fd);
+    if (le_size < 0) {
+        return;
+    }
+    int32_t size = int32_t(le32toh(le_size));
+    auto old_size = bytes->size();
+    bytes->resize(old_size + sizeof(le_size) + size);
+    memcpy(bytes->data() + old_size, &le_size, sizeof(le_size));
+    ReadFdExactly(fd, bytes->data() + old_size + sizeof(le_size), size);
+}
+
+static inline int32_t skip_bytes_with_size(borrowed_fd fd) {
+    int32_t le_size = read_int32(fd);
+    if (le_size < 0) {
+        return -1;
+    }
+    int32_t size = int32_t(le32toh(le_size));
+    return (int32_t)adb_lseek(fd, size, SEEK_CUR);
+}
+
+std::pair<std::vector<char>, int32_t> read_id_sig_headers(borrowed_fd fd) {
+    std::vector<char> result;
+    append_int(fd, &result);              // version
+    append_bytes_with_size(fd, &result);  // hashingInfo
+    append_bytes_with_size(fd, &result);  // signingInfo
+    auto le_tree_size = read_int32(fd);
+    auto tree_size = int32_t(le32toh(le_tree_size));  // size of the verity tree
+    return {std::move(result), tree_size};
+}
+
+std::pair<off64_t, ssize_t> skip_id_sig_headers(borrowed_fd fd) {
+    skip_int(fd);                            // version
+    skip_bytes_with_size(fd);                // hashingInfo
+    auto offset = skip_bytes_with_size(fd);  // signingInfo
+    auto le_tree_size = read_int32(fd);
+    auto tree_size = int32_t(le32toh(le_tree_size));  // size of the verity tree
+    return {offset + sizeof(le_tree_size), tree_size};
+}
+
+template <class T>
+static T valueAt(borrowed_fd fd, off64_t offset) {
+    T t;
+    memset(&t, 0, sizeof(T));
+    if (adb_pread(fd, &t, sizeof(T), offset) != sizeof(T)) {
+        memset(&t, -1, sizeof(T));
+    }
+
+    return t;
+}
+
+static void appendBlocks(int32_t start, int count, std::vector<int32_t>* blocks) {
+    if (count == 1) {
+        blocks->push_back(start);
+    } else {
+        auto oldSize = blocks->size();
+        blocks->resize(oldSize + count);
+        std::iota(blocks->begin() + oldSize, blocks->end(), start);
+    }
+}
+
+template <class T>
+static void unduplicate(std::vector<T>& v) {
+    std::unordered_set<T> uniques(v.size());
+    v.erase(std::remove_if(v.begin(), v.end(),
+                           [&uniques](T t) { return !uniques.insert(t).second; }),
+            v.end());
+}
+
+static off64_t CentralDirOffset(borrowed_fd fd, Size fileSize) {
+    static constexpr int kZipEocdRecMinSize = 22;
+    static constexpr int32_t kZipEocdRecSig = 0x06054b50;
+    static constexpr int kZipEocdCentralDirSizeFieldOffset = 12;
+    static constexpr int kZipEocdCommentLengthFieldOffset = 20;
+
+    int32_t sigBuf = 0;
+    off64_t eocdOffset = -1;
+    off64_t maxEocdOffset = fileSize - kZipEocdRecMinSize;
+    int16_t commentLenBuf = 0;
+
+    // Search from the end of zip, backward to find beginning of EOCD
+    for (int16_t commentLen = 0; commentLen < fileSize; ++commentLen) {
+        sigBuf = valueAt<int32_t>(fd, maxEocdOffset - commentLen);
+        if (sigBuf == kZipEocdRecSig) {
+            commentLenBuf = valueAt<int16_t>(
+                    fd, maxEocdOffset - commentLen + kZipEocdCommentLengthFieldOffset);
+            if (commentLenBuf == commentLen) {
+                eocdOffset = maxEocdOffset - commentLen;
+                break;
+            }
+        }
+    }
+
+    if (eocdOffset < 0) {
+        return -1;
+    }
+
+    off64_t cdLen = static_cast<int64_t>(
+            valueAt<int32_t>(fd, eocdOffset + kZipEocdCentralDirSizeFieldOffset));
+
+    return eocdOffset - cdLen;
+}
+
+// Does not support APKs larger than 4GB
+static off64_t SignerBlockOffset(borrowed_fd fd, Size fileSize) {
+    static constexpr int kApkSigBlockMinSize = 32;
+    static constexpr int kApkSigBlockFooterSize = 24;
+    static constexpr int64_t APK_SIG_BLOCK_MAGIC_HI = 0x3234206b636f6c42l;
+    static constexpr int64_t APK_SIG_BLOCK_MAGIC_LO = 0x20676953204b5041l;
+
+    off64_t cdOffset = CentralDirOffset(fd, fileSize);
+    if (cdOffset < 0) {
+        return -1;
+    }
+    // CD offset is where original signer block ends. Search backwards for magic and footer.
+    if (cdOffset < kApkSigBlockMinSize ||
+        valueAt<int64_t>(fd, cdOffset - 2 * sizeof(int64_t)) != APK_SIG_BLOCK_MAGIC_LO ||
+        valueAt<int64_t>(fd, cdOffset - sizeof(int64_t)) != APK_SIG_BLOCK_MAGIC_HI) {
+        return -1;
+    }
+    int32_t signerSizeInFooter = valueAt<int32_t>(fd, cdOffset - kApkSigBlockFooterSize);
+    off64_t signerBlockOffset = cdOffset - signerSizeInFooter - sizeof(int64_t);
+    if (signerBlockOffset < 0) {
+        return -1;
+    }
+    int32_t signerSizeInHeader = valueAt<int32_t>(fd, signerBlockOffset);
+    if (signerSizeInFooter != signerSizeInHeader) {
+        return -1;
+    }
+
+    return signerBlockOffset;
+}
+
+static std::vector<int32_t> ZipPriorityBlocks(off64_t signerBlockOffset, Size fileSize) {
+    int32_t signerBlockIndex = offsetToBlockIndex(signerBlockOffset);
+    int32_t lastBlockIndex = offsetToBlockIndex(fileSize);
+    const auto numPriorityBlocks = lastBlockIndex - signerBlockIndex + 1;
+
+    std::vector<int32_t> zipPriorityBlocks;
+
+    // Some magic here: most of zip libraries perform a scan for EOCD record starting at the offset
+    // of a maximum comment size from the end of the file. This means the last 65-ish KBs will be
+    // accessed first, followed by the rest of the central directory blocks. Make sure we
+    // send the data in the proper order, as central directory can be quite big by itself.
+    static constexpr auto kMaxZipCommentSize = 64 * 1024;
+    static constexpr auto kNumBlocksInEocdSearch = kMaxZipCommentSize / kBlockSize + 1;
+    if (numPriorityBlocks > kNumBlocksInEocdSearch) {
+        appendBlocks(lastBlockIndex - kNumBlocksInEocdSearch + 1, kNumBlocksInEocdSearch,
+                     &zipPriorityBlocks);
+        appendBlocks(signerBlockIndex, numPriorityBlocks - kNumBlocksInEocdSearch,
+                     &zipPriorityBlocks);
+    } else {
+        appendBlocks(signerBlockIndex, numPriorityBlocks, &zipPriorityBlocks);
+    }
+
+    // Somehow someone keeps accessing the start of the archive, even if there's nothing really
+    // interesting there...
+    appendBlocks(0, 1, &zipPriorityBlocks);
+    return zipPriorityBlocks;
+}
+
+[[maybe_unused]] static ZipArchiveHandle openZipArchiveFd(borrowed_fd fd) {
+    bool transferFdOwnership = false;
+#ifdef _WIN32
+    //
+    // Need to create a special CRT FD here as the current one is not compatible with
+    // normal read()/write() calls that libziparchive uses.
+    // To make this work we have to create a copy of the file handle, as CRT doesn't care
+    // and closes it together with the new descriptor.
+    //
+    // Note: don't move this into a helper function, it's better to be hard to reuse because
+    //       the code is ugly and won't work unless it's a last resort.
+    //
+    auto handle = adb_get_os_handle(fd);
+    HANDLE dupedHandle;
+    if (!::DuplicateHandle(::GetCurrentProcess(), handle, ::GetCurrentProcess(), &dupedHandle, 0,
+                           false, DUPLICATE_SAME_ACCESS)) {
+        D("%s failed at DuplicateHandle: %d", __func__, (int)::GetLastError());
+        return {};
+    }
+    int osfd = _open_osfhandle((intptr_t)dupedHandle, _O_RDONLY | _O_BINARY);
+    if (osfd < 0) {
+        D("%s failed at _open_osfhandle: %d", __func__, errno);
+        ::CloseHandle(handle);
+        return {};
+    }
+    transferFdOwnership = true;
+#else
+    int osfd = fd.get();
+#endif
+    ZipArchiveHandle zip;
+    if (OpenArchiveFd(osfd, "apk_fd", &zip, transferFdOwnership) != 0) {
+        D("%s failed at OpenArchiveFd: %d", __func__, errno);
+#ifdef _WIN32
+        // "_close()" is a secret WinCRT name for the regular close() function.
+        _close(osfd);
+#endif
+        return {};
+    }
+    return zip;
+}
+
+static std::pair<ZipArchiveHandle, std::unique_ptr<android::base::MappedFile>> openZipArchive(
+        borrowed_fd fd, Size fileSize) {
+#ifndef __LP64__
+    if (fileSize >= INT_MAX) {
+        return {openZipArchiveFd(fd), nullptr};
+    }
+#endif
+    auto mapping =
+            android::base::MappedFile::FromOsHandle(adb_get_os_handle(fd), 0, fileSize, PROT_READ);
+    if (!mapping) {
+        D("%s failed at FromOsHandle: %d", __func__, errno);
+        return {};
+    }
+    ZipArchiveHandle zip;
+    if (OpenArchiveFromMemory(mapping->data(), mapping->size(), "apk_mapping", &zip) != 0) {
+        D("%s failed at OpenArchiveFromMemory: %d", __func__, errno);
+        return {};
+    }
+    return {zip, std::move(mapping)};
+}
+
+static std::vector<int32_t> InstallationPriorityBlocks(borrowed_fd fd, Size fileSize) {
+    static constexpr std::array<std::string_view, 3> additional_matches = {
+            "resources.arsc"sv, "AndroidManifest.xml"sv, "classes.dex"sv};
+    auto [zip, _] = openZipArchive(fd, fileSize);
+    if (!zip) {
+        return {};
+    }
+
+    auto matcher = [](std::string_view entry_name) {
+        if (entry_name.starts_with("lib/"sv) && entry_name.ends_with(".so"sv)) {
+            return true;
+        }
+        return std::any_of(additional_matches.begin(), additional_matches.end(),
+                           [entry_name](std::string_view i) { return i == entry_name; });
+    };
+
+    void* cookie = nullptr;
+    if (StartIteration(zip, &cookie, std::move(matcher)) != 0) {
+        D("%s failed at StartIteration: %d", __func__, errno);
+        return {};
+    }
+
+    std::vector<int32_t> installationPriorityBlocks;
+    ZipEntry entry;
+    std::string_view entryName;
+    while (Next(cookie, &entry, &entryName) == 0) {
+        if (entryName == "classes.dex"sv) {
+            // Only the head is needed for installation
+            int32_t startBlockIndex = offsetToBlockIndex(entry.offset);
+            appendBlocks(startBlockIndex, 2, &installationPriorityBlocks);
+            D("\tadding to priority blocks: '%.*s' (%d)", (int)entryName.size(), entryName.data(),
+              2);
+        } else {
+            // Full entries are needed for installation
+            off64_t entryStartOffset = entry.offset;
+            off64_t entryEndOffset =
+                    entryStartOffset +
+                    (entry.method == kCompressStored ? entry.uncompressed_length
+                                                     : entry.compressed_length) +
+                    (entry.has_data_descriptor ? 16 /* sizeof(DataDescriptor) */ : 0);
+            int32_t startBlockIndex = offsetToBlockIndex(entryStartOffset);
+            int32_t endBlockIndex = offsetToBlockIndex(entryEndOffset);
+            int32_t numNewBlocks = endBlockIndex - startBlockIndex + 1;
+            appendBlocks(startBlockIndex, numNewBlocks, &installationPriorityBlocks);
+            D("\tadding to priority blocks: '%.*s' (%d)", (int)entryName.size(), entryName.data(),
+              numNewBlocks);
+        }
+    }
+
+    EndIteration(cookie);
+    CloseArchive(zip);
+    return installationPriorityBlocks;
+}
+
+std::vector<int32_t> PriorityBlocksForFile(const std::string& filepath, borrowed_fd fd,
+                                           Size fileSize) {
+    if (!android::base::EndsWithIgnoreCase(filepath, ".apk"sv)) {
+        return {};
+    }
+    off64_t signerOffset = SignerBlockOffset(fd, fileSize);
+    if (signerOffset < 0) {
+        // No signer block? not a valid APK
+        return {};
+    }
+    std::vector<int32_t> priorityBlocks = ZipPriorityBlocks(signerOffset, fileSize);
+    std::vector<int32_t> installationPriorityBlocks = InstallationPriorityBlocks(fd, fileSize);
+
+    priorityBlocks.insert(priorityBlocks.end(), installationPriorityBlocks.begin(),
+                          installationPriorityBlocks.end());
+    unduplicate(priorityBlocks);
+    return priorityBlocks;
+}
+
+}  // namespace incremental
diff --git a/adb/client/incremental_utils.h b/adb/client/incremental_utils.h
new file mode 100644
index 0000000..fe2914d
--- /dev/null
+++ b/adb/client/incremental_utils.h
@@ -0,0 +1,48 @@
+/*
+ * 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 "adb_unique_fd.h"
+
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <stdint.h>
+
+#include <android-base/off64_t.h>
+
+namespace incremental {
+
+using Size = int64_t;
+constexpr int kBlockSize = 4096;
+constexpr int kSha256DigestSize = 32;
+constexpr int kDigestSize = kSha256DigestSize;
+
+constexpr std::string_view IDSIG = ".idsig";
+
+std::vector<int32_t> PriorityBlocksForFile(const std::string& filepath, borrowed_fd fd,
+                                           Size fileSize);
+
+Size verity_tree_blocks_for_file(Size fileSize);
+Size verity_tree_size_for_file(Size fileSize);
+
+std::pair<std::vector<char>, int32_t> read_id_sig_headers(borrowed_fd fd);
+std::pair<off64_t, ssize_t> skip_id_sig_headers(borrowed_fd fd);
+
+}  // namespace incremental
diff --git a/adb/client/main.cpp b/adb/client/main.cpp
index a85a18c..05e210f 100644
--- a/adb/client/main.cpp
+++ b/adb/client/main.cpp
@@ -36,6 +36,7 @@
 #include "adb_listeners.h"
 #include "adb_utils.h"
 #include "adb_wifi.h"
+#include "client/usb.h"
 #include "commandline.h"
 #include "sysdeps/chrono.h"
 #include "transport.h"
@@ -104,7 +105,12 @@
         fdevent_run_on_main_thread([]() { exit(0); });
     });
 
-    char* leak = getenv("ADB_LEAK");
+    const char* reject_kill_server = getenv("ADB_REJECT_KILL_SERVER");
+    if (reject_kill_server && strcmp(reject_kill_server, "1") == 0) {
+        adb_set_reject_kill_server(true);
+    }
+
+    const char* leak = getenv("ADB_LEAK");
     if (leak && strcmp(leak, "1") == 0) {
         intentionally_leak();
     }
@@ -139,9 +145,10 @@
     auto start = std::chrono::steady_clock::now();
 
     // If we told a previous adb server to quit because of version mismatch, we can get to this
-    // point before it's finished exiting. Retry for a while to give it some time.
-    while (install_listener(socket_spec, "*smartsocket*", nullptr, 0, nullptr, &error) !=
-           INSTALL_STATUS_OK) {
+    // point before it's finished exiting. Retry for a while to give it some time. Don't actually
+    // accept any connections until adb_wait_for_device_initialization finishes below.
+    while (install_listener(socket_spec, "*smartsocket*", nullptr, INSTALL_LISTENER_DISABLED,
+                            nullptr, &error) != INSTALL_STATUS_OK) {
         if (std::chrono::steady_clock::now() - start > 0.5s) {
             LOG(FATAL) << "could not install *smartsocket* listener: " << error;
         }
@@ -162,12 +169,14 @@
             PLOG(FATAL) << "setsid() failed";
         }
 #endif
+    }
 
-        // Wait for the USB scan to complete before notifying the parent that we're up.
-        // We need to perform this in a thread, because we would otherwise block the event loop.
-        std::thread notify_thread([ack_reply_fd]() {
-            adb_wait_for_device_initialization();
+    // Wait for the USB scan to complete before notifying the parent that we're up.
+    // We need to perform this in a thread, because we would otherwise block the event loop.
+    std::thread notify_thread([ack_reply_fd]() {
+        adb_wait_for_device_initialization();
 
+        if (ack_reply_fd >= 0) {
             // Any error output written to stderr now goes to adb.log. We could
             // keep around a copy of the stderr fd and use that to write any errors
             // encountered by the following code, but that is probably overkill.
@@ -193,9 +202,13 @@
             }
             unix_close(ack_reply_fd);
 #endif
-        });
-        notify_thread.detach();
-    }
+        }
+        // We don't accept() client connections until this point: this way, clients
+        // can't see wonky state early in startup even if they're connecting directly
+        // to the server instead of going through the adb program.
+        fdevent_run_on_main_thread([] { enable_daemon_sockets(); });
+    });
+    notify_thread.detach();
 
 #if defined(__linux__)
     // Write our location to .android/adb.$PORT, so that older clients can exec us.
diff --git a/adb/client/pairing/pairing_client.cpp b/adb/client/pairing/pairing_client.cpp
index 2f878bf..937a5bd 100644
--- a/adb/client/pairing/pairing_client.cpp
+++ b/adb/client/pairing/pairing_client.cpp
@@ -141,7 +141,8 @@
                                           cert_.size(), priv_key_.data(), priv_key_.size()));
     CHECK(connection_);
 
-    if (!pairing_connection_start(connection_.get(), fd.release(), OnPairingResult, this)) {
+    int osh = cast_handle_to_int(adb_get_os_handle(fd.release()));
+    if (!pairing_connection_start(connection_.get(), osh, OnPairingResult, this)) {
         LOG(ERROR) << "PairingClient failed to start the PairingConnection";
         state_ = State::Stopped;
         return false;
diff --git a/adb/transport_usb.cpp b/adb/client/transport_usb.cpp
similarity index 96%
rename from adb/transport_usb.cpp
rename to adb/client/transport_usb.cpp
index fb81b37..777edde 100644
--- a/adb/transport_usb.cpp
+++ b/adb/client/transport_usb.cpp
@@ -16,6 +16,10 @@
 
 #define TRACE_TAG TRANSPORT
 
+#include "sysdeps.h"
+
+#include "client/usb.h"
+
 #include <memory>
 
 #include "sysdeps.h"
@@ -135,8 +139,8 @@
         }
 
         p->payload.resize(p->msg.data_length);
-        if (usb_read(usb, &p->payload[0], p->payload.size())
-                != static_cast<int>(p->payload.size())) {
+        if (usb_read(usb, &p->payload[0], p->payload.size()) !=
+            static_cast<int>(p->payload.size())) {
             PLOG(ERROR) << "remote usb: terminated (data)";
             return -1;
         }
diff --git a/adb/usb.h b/adb/client/usb.h
similarity index 72%
rename from adb/usb.h
rename to adb/client/usb.h
index eb8ca6c..b371788 100644
--- a/adb/usb.h
+++ b/adb/client/usb.h
@@ -18,6 +18,9 @@
 
 #include <sys/types.h>
 
+#include "adb.h"
+#include "transport.h"
+
 // USB host/client interface.
 
 #define ADB_USB_INTERFACE(handle_ref_type)                       \
@@ -30,35 +33,38 @@
     void usb_kick(handle_ref_type h);                            \
     size_t usb_get_max_packet_size(handle_ref_type)
 
-#if !ADB_HOST
-// The daemon has a single implementation.
-
-struct usb_handle;
-ADB_USB_INTERFACE(usb_handle*);
-
-#else // linux host || darwin
 // Linux and Darwin clients have native and libusb implementations.
 
 namespace libusb {
-    struct usb_handle;
-    ADB_USB_INTERFACE(libusb::usb_handle*);
-}
+struct usb_handle;
+ADB_USB_INTERFACE(libusb::usb_handle*);
+}  // namespace libusb
 
 namespace native {
-    struct usb_handle;
-    ADB_USB_INTERFACE(native::usb_handle*);
-}
+struct usb_handle;
+ADB_USB_INTERFACE(native::usb_handle*);
+}  // namespace native
 
 // Empty base that both implementations' opaque handles inherit from.
-struct usb_handle {
-};
+struct usb_handle {};
 
 ADB_USB_INTERFACE(::usb_handle*);
 
-#endif // linux host || darwin
-
-
 // USB device detection.
 int is_adb_interface(int usb_class, int usb_subclass, int usb_protocol);
 
 bool should_use_libusb();
+
+struct UsbConnection : public BlockingConnection {
+    explicit UsbConnection(usb_handle* handle) : handle_(handle) {}
+    ~UsbConnection();
+
+    bool Read(apacket* packet) override final;
+    bool Write(apacket* packet) override final;
+    bool DoTlsHandshake(RSA* key, std::string* auth_key) override final;
+
+    void Close() override final;
+    virtual void Reset() override final;
+
+    usb_handle* handle_;
+};
diff --git a/adb/client/usb_dispatch.cpp b/adb/client/usb_dispatch.cpp
index f55ae90..7b97117 100644
--- a/adb/client/usb_dispatch.cpp
+++ b/adb/client/usb_dispatch.cpp
@@ -15,7 +15,8 @@
  */
 
 #include <android-base/logging.h>
-#include "usb.h"
+
+#include "client/usb.h"
 
 void usb_init() {
     if (should_use_libusb()) {
diff --git a/adb/client/usb_libusb.cpp b/adb/client/usb_libusb.cpp
index 53f01a0..07cbc94 100644
--- a/adb/client/usb_libusb.cpp
+++ b/adb/client/usb_libusb.cpp
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#include "usb.h"
-
 #include "sysdeps.h"
 
+#include "client/usb.h"
+
 #include <stdint.h>
 #include <stdlib.h>
 
@@ -40,7 +40,6 @@
 #include "adb.h"
 #include "adb_utils.h"
 #include "transport.h"
-#include "usb.h"
 
 using android::base::StringPrintf;
 
diff --git a/adb/client/usb_linux.cpp b/adb/client/usb_linux.cpp
index 343e7b5..95b1817 100644
--- a/adb/client/usb_linux.cpp
+++ b/adb/client/usb_linux.cpp
@@ -18,6 +18,8 @@
 
 #include "sysdeps.h"
 
+#include "client/usb.h"
+
 #include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
@@ -48,7 +50,6 @@
 
 #include "adb.h"
 #include "transport.h"
-#include "usb.h"
 
 using namespace std::chrono_literals;
 using namespace std::literals;
diff --git a/adb/client/usb_osx.cpp b/adb/client/usb_osx.cpp
index 7207ca7..a93fa3a 100644
--- a/adb/client/usb_osx.cpp
+++ b/adb/client/usb_osx.cpp
@@ -18,6 +18,8 @@
 
 #include "sysdeps.h"
 
+#include "client/usb.h"
+
 #include <CoreFoundation/CoreFoundation.h>
 
 #include <IOKit/IOKitLib.h>
diff --git a/adb/client/usb_windows.cpp b/adb/client/usb_windows.cpp
index 197c6fa..e209230 100644
--- a/adb/client/usb_windows.cpp
+++ b/adb/client/usb_windows.cpp
@@ -18,6 +18,8 @@
 
 #include "sysdeps.h"
 
+#include "client/usb.h"
+
 // clang-format off
 #include <winsock2.h>  // winsock.h *must* be included before windows.h.
 #include <windows.h>
diff --git a/adb/compression_utils.h b/adb/compression_utils.h
new file mode 100644
index 0000000..a0c48a2
--- /dev/null
+++ b/adb/compression_utils.h
@@ -0,0 +1,383 @@
+/*
+ * 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 <algorithm>
+#include <memory>
+#include <span>
+
+#include <android-base/logging.h>
+
+#include <brotli/decode.h>
+#include <brotli/encode.h>
+#include <lz4frame.h>
+
+#include "types.h"
+
+enum class DecodeResult {
+    Error,
+    Done,
+    NeedInput,
+    MoreOutput,
+};
+
+enum class EncodeResult {
+    Error,
+    Done,
+    NeedInput,
+    MoreOutput,
+};
+
+struct Decoder {
+    void Append(Block&& block) { input_buffer_.append(std::move(block)); }
+    bool Finish() {
+        bool old = std::exchange(finished_, true);
+        if (old) {
+            LOG(FATAL) << "Decoder::Finish called while already finished?";
+            return false;
+        }
+        return true;
+    }
+
+    virtual DecodeResult Decode(std::span<char>* output) = 0;
+
+  protected:
+    Decoder(std::span<char> output_buffer) : output_buffer_(output_buffer) {}
+    ~Decoder() = default;
+
+    bool finished_ = false;
+    IOVector input_buffer_;
+    std::span<char> output_buffer_;
+};
+
+struct Encoder {
+    void Append(Block input) { input_buffer_.append(std::move(input)); }
+    bool Finish() {
+        bool old = std::exchange(finished_, true);
+        if (old) {
+            LOG(FATAL) << "Decoder::Finish called while already finished?";
+            return false;
+        }
+        return true;
+    }
+
+    virtual EncodeResult Encode(Block* output) = 0;
+
+  protected:
+    explicit Encoder(size_t output_block_size) : output_block_size_(output_block_size) {}
+    ~Encoder() = default;
+
+    const size_t output_block_size_;
+    bool finished_ = false;
+    IOVector input_buffer_;
+};
+
+struct NullDecoder final : public Decoder {
+    explicit NullDecoder(std::span<char> output_buffer) : Decoder(output_buffer) {}
+
+    DecodeResult Decode(std::span<char>* output) final {
+        size_t available_out = output_buffer_.size();
+        void* p = output_buffer_.data();
+        while (available_out > 0 && !input_buffer_.empty()) {
+            size_t len = std::min(available_out, input_buffer_.front_size());
+            p = mempcpy(p, input_buffer_.front_data(), len);
+            available_out -= len;
+            input_buffer_.drop_front(len);
+        }
+        *output = std::span(output_buffer_.data(), static_cast<char*>(p));
+        if (input_buffer_.empty()) {
+            return finished_ ? DecodeResult::Done : DecodeResult::NeedInput;
+        }
+        return DecodeResult::MoreOutput;
+    }
+};
+
+struct NullEncoder final : public Encoder {
+    explicit NullEncoder(size_t output_block_size) : Encoder(output_block_size) {}
+
+    EncodeResult Encode(Block* output) final {
+        output->clear();
+        output->resize(output_block_size_);
+
+        size_t available_out = output->size();
+        void* p = output->data();
+
+        while (available_out > 0 && !input_buffer_.empty()) {
+            size_t len = std::min(available_out, input_buffer_.front_size());
+            p = mempcpy(p, input_buffer_.front_data(), len);
+            available_out -= len;
+            input_buffer_.drop_front(len);
+        }
+
+        output->resize(output->size() - available_out);
+
+        if (input_buffer_.empty()) {
+            return finished_ ? EncodeResult::Done : EncodeResult::NeedInput;
+        }
+        return EncodeResult::MoreOutput;
+    }
+};
+
+struct BrotliDecoder final : public Decoder {
+    explicit BrotliDecoder(std::span<char> output_buffer)
+        : Decoder(output_buffer),
+          decoder_(BrotliDecoderCreateInstance(nullptr, nullptr, nullptr),
+                   BrotliDecoderDestroyInstance) {}
+
+    DecodeResult Decode(std::span<char>* output) final {
+        size_t available_in = input_buffer_.front_size();
+        const uint8_t* next_in = reinterpret_cast<const uint8_t*>(input_buffer_.front_data());
+
+        size_t available_out = output_buffer_.size();
+        uint8_t* next_out = reinterpret_cast<uint8_t*>(output_buffer_.data());
+
+        BrotliDecoderResult r = BrotliDecoderDecompressStream(
+                decoder_.get(), &available_in, &next_in, &available_out, &next_out, nullptr);
+
+        size_t bytes_consumed = input_buffer_.front_size() - available_in;
+        input_buffer_.drop_front(bytes_consumed);
+
+        size_t bytes_emitted = output_buffer_.size() - available_out;
+        *output = std::span<char>(output_buffer_.data(), bytes_emitted);
+
+        switch (r) {
+            case BROTLI_DECODER_RESULT_SUCCESS:
+                // We need to wait for ID_DONE from the other end.
+                return finished_ ? DecodeResult::Done : DecodeResult::NeedInput;
+            case BROTLI_DECODER_RESULT_ERROR:
+                return DecodeResult::Error;
+            case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
+                // Brotli guarantees as one of its invariants that if it returns NEEDS_MORE_INPUT,
+                // it will consume the entire input buffer passed in, so we don't have to worry
+                // about bytes left over in the front block with more input remaining.
+                return DecodeResult::NeedInput;
+            case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
+                return DecodeResult::MoreOutput;
+        }
+    }
+
+  private:
+    std::unique_ptr<BrotliDecoderState, void (*)(BrotliDecoderState*)> decoder_;
+};
+
+struct BrotliEncoder final : public Encoder {
+    explicit BrotliEncoder(size_t output_block_size)
+        : Encoder(output_block_size),
+          output_block_(output_block_size_),
+          output_bytes_left_(output_block_size_),
+          encoder_(BrotliEncoderCreateInstance(nullptr, nullptr, nullptr),
+                   BrotliEncoderDestroyInstance) {
+        BrotliEncoderSetParameter(encoder_.get(), BROTLI_PARAM_QUALITY, 1);
+    }
+
+    EncodeResult Encode(Block* output) final {
+        output->clear();
+
+        while (true) {
+            size_t available_in = input_buffer_.front_size();
+            const uint8_t* next_in = reinterpret_cast<const uint8_t*>(input_buffer_.front_data());
+
+            size_t available_out = output_bytes_left_;
+            uint8_t* next_out = reinterpret_cast<uint8_t*>(
+                    output_block_.data() + (output_block_size_ - output_bytes_left_));
+
+            BrotliEncoderOperation op = BROTLI_OPERATION_PROCESS;
+            if (finished_) {
+                op = BROTLI_OPERATION_FINISH;
+            }
+
+            if (!BrotliEncoderCompressStream(encoder_.get(), op, &available_in, &next_in,
+                                             &available_out, &next_out, nullptr)) {
+                return EncodeResult::Error;
+            }
+
+            size_t bytes_consumed = input_buffer_.front_size() - available_in;
+            input_buffer_.drop_front(bytes_consumed);
+
+            output_bytes_left_ = available_out;
+
+            if (BrotliEncoderIsFinished(encoder_.get())) {
+                output_block_.resize(output_block_size_ - output_bytes_left_);
+                *output = std::move(output_block_);
+                return EncodeResult::Done;
+            } else if (output_bytes_left_ == 0) {
+                *output = std::move(output_block_);
+                output_block_.resize(output_block_size_);
+                output_bytes_left_ = output_block_size_;
+                return EncodeResult::MoreOutput;
+            } else if (input_buffer_.empty()) {
+                return EncodeResult::NeedInput;
+            }
+        }
+    }
+
+  private:
+    Block output_block_;
+    size_t output_bytes_left_;
+    std::unique_ptr<BrotliEncoderState, void (*)(BrotliEncoderState*)> encoder_;
+};
+
+struct LZ4Decoder final : public Decoder {
+    explicit LZ4Decoder(std::span<char> output_buffer)
+        : Decoder(output_buffer), decoder_(nullptr, nullptr) {
+        LZ4F_dctx* dctx;
+        if (LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION) != 0) {
+            LOG(FATAL) << "failed to initialize LZ4 decompression context";
+        }
+        decoder_ = std::unique_ptr<LZ4F_dctx, decltype(&LZ4F_freeDecompressionContext)>(
+                dctx, LZ4F_freeDecompressionContext);
+    }
+
+    DecodeResult Decode(std::span<char>* output) final {
+        size_t available_in = input_buffer_.front_size();
+        const char* next_in = input_buffer_.front_data();
+
+        size_t available_out = output_buffer_.size();
+        char* next_out = output_buffer_.data();
+
+        size_t rc = LZ4F_decompress(decoder_.get(), next_out, &available_out, next_in,
+                                    &available_in, nullptr);
+        if (LZ4F_isError(rc)) {
+            LOG(ERROR) << "LZ4F_decompress failed: " << LZ4F_getErrorName(rc);
+            return DecodeResult::Error;
+        }
+
+        input_buffer_.drop_front(available_in);
+
+        if (rc == 0) {
+            if (!input_buffer_.empty()) {
+                LOG(ERROR) << "LZ4 stream hit end before reading all data";
+                return DecodeResult::Error;
+            }
+            lz4_done_ = true;
+        }
+
+        *output = std::span<char>(output_buffer_.data(), available_out);
+
+        if (finished_) {
+            return input_buffer_.empty() && lz4_done_ ? DecodeResult::Done
+                                                      : DecodeResult::MoreOutput;
+        }
+
+        return DecodeResult::NeedInput;
+    }
+
+  private:
+    bool lz4_done_ = false;
+    std::unique_ptr<LZ4F_dctx, LZ4F_errorCode_t (*)(LZ4F_dctx*)> decoder_;
+};
+
+struct LZ4Encoder final : public Encoder {
+    explicit LZ4Encoder(size_t output_block_size)
+        : Encoder(output_block_size), encoder_(nullptr, nullptr) {
+        LZ4F_cctx* cctx;
+        if (LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) != 0) {
+            LOG(FATAL) << "failed to initialize LZ4 compression context";
+        }
+        encoder_ = std::unique_ptr<LZ4F_cctx, decltype(&LZ4F_freeCompressionContext)>(
+                cctx, LZ4F_freeCompressionContext);
+        Block header(LZ4F_HEADER_SIZE_MAX);
+        size_t rc = LZ4F_compressBegin(encoder_.get(), header.data(), header.size(), nullptr);
+        if (LZ4F_isError(rc)) {
+            LOG(FATAL) << "LZ4F_compressBegin failed: %s", LZ4F_getErrorName(rc);
+        }
+        header.resize(rc);
+        output_buffer_.append(std::move(header));
+    }
+
+    // As an optimization, only emit a block if we have an entire output block ready, or we're done.
+    bool OutputReady() const {
+        return output_buffer_.size() >= output_block_size_ || lz4_finalized_;
+    }
+
+    // TODO: Switch the output type to IOVector to remove a copy?
+    EncodeResult Encode(Block* output) final {
+        size_t available_in = input_buffer_.front_size();
+        const char* next_in = input_buffer_.front_data();
+
+        // LZ4 makes no guarantees about being able to recover from trying to compress with an
+        // insufficiently large output buffer. LZ4F_compressBound tells us how much buffer we
+        // need to compress a given number of bytes, but the smallest value seems to be bigger
+        // than SYNC_DATA_MAX, so we need to buffer ourselves.
+
+        // Input size chosen to be a local maximum for LZ4F_compressBound (i.e. the block size).
+        constexpr size_t max_input_size = 65536;
+        const size_t encode_block_size = LZ4F_compressBound(max_input_size, nullptr);
+
+        if (available_in != 0) {
+            if (lz4_finalized_) {
+                LOG(ERROR) << "LZ4Encoder received data after Finish?";
+                return EncodeResult::Error;
+            }
+
+            available_in = std::min(available_in, max_input_size);
+
+            Block encode_block(encode_block_size);
+            size_t available_out = encode_block.capacity();
+            char* next_out = encode_block.data();
+
+            size_t rc = LZ4F_compressUpdate(encoder_.get(), next_out, available_out, next_in,
+                                            available_in, nullptr);
+            if (LZ4F_isError(rc)) {
+                LOG(ERROR) << "LZ4F_compressUpdate failed: " << LZ4F_getErrorName(rc);
+                return EncodeResult::Error;
+            }
+
+            input_buffer_.drop_front(available_in);
+
+            available_out -= rc;
+            next_out += rc;
+
+            encode_block.resize(encode_block_size - available_out);
+            output_buffer_.append(std::move(encode_block));
+        }
+
+        if (finished_ && !lz4_finalized_) {
+            lz4_finalized_ = true;
+
+            Block final_block(encode_block_size + 4);
+            size_t rc = LZ4F_compressEnd(encoder_.get(), final_block.data(), final_block.size(),
+                                         nullptr);
+            if (LZ4F_isError(rc)) {
+                LOG(ERROR) << "LZ4F_compressEnd failed: " << LZ4F_getErrorName(rc);
+                return EncodeResult::Error;
+            }
+
+            final_block.resize(rc);
+            output_buffer_.append(std::move(final_block));
+        }
+
+        if (OutputReady()) {
+            size_t len = std::min(output_block_size_, output_buffer_.size());
+            *output = output_buffer_.take_front(len).coalesce();
+        } else {
+            output->clear();
+        }
+
+        if (lz4_finalized_ && output_buffer_.empty()) {
+            return EncodeResult::Done;
+        } else if (OutputReady()) {
+            return EncodeResult::MoreOutput;
+        }
+        return EncodeResult::NeedInput;
+    }
+
+  private:
+    bool lz4_finalized_ = false;
+    std::unique_ptr<LZ4F_cctx, LZ4F_errorCode_t (*)(LZ4F_cctx*)> encoder_;
+    IOVector output_buffer_;
+};
diff --git a/adb/coverage.sh b/adb/coverage.sh
new file mode 100755
index 0000000..50ccdf5
--- /dev/null
+++ b/adb/coverage.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+# 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.
+
+set -euxo pipefail
+
+adb root
+adb shell logcat -c -G128M
+adb shell setprop persist.adb.trace_mask 1
+adb shell killall adbd
+
+# TODO: Add `adb transport-id` and wait-for-offline on it.
+sleep 5
+
+adb wait-for-device shell rm "/data/misc/trace/*"
+
+./test_device.py
+
+# Dump traces from the currently running adbd.
+adb shell killall -37 adbd
+
+echo Waiting for adbd to finish dumping traces
+sleep 5
+
+TRACEDIR=`mktemp -d`
+adb pull /data/misc/trace "$TRACEDIR"/
+echo Pulled traces to $TRACEDIR
+
+
+# Identify which of the trace files are actually adbd, in case something else exited simultaneously.
+ADBD_PIDS=$(adb shell "logcat -d -s adbd --format=process | grep 'adbd started' | cut -c 3-7 | tr -d ' ' | sort | uniq")
+mkdir "$TRACEDIR"/adbd_traces
+
+adb shell 'setprop persist.adb.trace_mask 0; killall adbd'
+
+IFS=$'\n'
+for PID in $ADBD_PIDS; do
+  cp "$TRACEDIR"/trace/clang-$PID-*.profraw "$TRACEDIR"/adbd_traces 2>/dev/null || true
+done
+
+llvm-profdata merge --output="$TRACEDIR"/adbd.profdata "$TRACEDIR"/adbd_traces/*
+
+cd $ANDROID_BUILD_TOP
+llvm-cov report --instr-profile="$TRACEDIR"/adbd.profdata \
+  $ANDROID_PRODUCT_OUT/apex/com.android.adbd/bin/adbd \
+  --show-region-summary=false \
+  /proc/self/cwd/system/core/adb
+
+llvm-cov show --instr-profile="$TRACEDIR"/adbd.profdata \
+  $ANDROID_PRODUCT_OUT/apex/com.android.adbd/bin/adbd \
+  --format=html \
+  /proc/self/cwd/system/core/adb > $TRACEDIR/report.html
diff --git a/adb/crypto/Android.bp b/adb/crypto/Android.bp
index ce1de4a..9d14b03 100644
--- a/adb/crypto/Android.bp
+++ b/adb/crypto/Android.bp
@@ -40,6 +40,7 @@
 
     visibility: [
         "//system/core/adb:__subpackages__",
+        "//bootable/recovery/minadbd:__subpackages__",
     ],
 
     host_supported: true,
diff --git a/adb/daemon/file_sync_service.cpp b/adb/daemon/file_sync_service.cpp
index edf5683..d58131e 100644
--- a/adb/daemon/file_sync_service.cpp
+++ b/adb/daemon/file_sync_service.cpp
@@ -32,7 +32,10 @@
 #include <utime.h>
 
 #include <memory>
+#include <optional>
+#include <span>
 #include <string>
+#include <variant>
 #include <vector>
 
 #include <android-base/file.h>
@@ -55,10 +58,12 @@
 #include "adb_io.h"
 #include "adb_trace.h"
 #include "adb_utils.h"
+#include "compression_utils.h"
 #include "file_sync_protocol.h"
 #include "security_log_tags.h"
 #include "sysdeps/errno.h"
 
+using android::base::borrowed_fd;
 using android::base::Dirname;
 using android::base::StringPrintf;
 
@@ -249,7 +254,7 @@
 // Make sure that SendFail from adb_io.cpp isn't accidentally used in this file.
 #pragma GCC poison SendFail
 
-static bool SendSyncFail(int fd, const std::string& reason) {
+static bool SendSyncFail(borrowed_fd fd, const std::string& reason) {
     D("sync: failure: %s", reason.c_str());
 
     syncmsg msg;
@@ -258,79 +263,131 @@
     return WriteFdExactly(fd, &msg.data, sizeof(msg.data)) && WriteFdExactly(fd, reason);
 }
 
-static bool SendSyncFailErrno(int fd, const std::string& reason) {
+static bool SendSyncFailErrno(borrowed_fd fd, const std::string& reason) {
     return SendSyncFail(fd, StringPrintf("%s: %s", reason.c_str(), strerror(errno)));
 }
 
-static bool handle_send_file(int s, const char* path, uint32_t* timestamp, uid_t uid, gid_t gid,
-                             uint64_t capabilities, mode_t mode, std::vector<char>& buffer,
-                             bool do_unlink) {
-    int rc;
+static bool handle_send_file_data(borrowed_fd s, unique_fd fd, uint32_t* timestamp,
+                                  CompressionType compression) {
     syncmsg msg;
+    Block buffer(SYNC_DATA_MAX);
+    std::span<char> buffer_span(buffer.data(), buffer.size());
+    std::variant<std::monostate, NullDecoder, BrotliDecoder, LZ4Decoder> decoder_storage;
+    Decoder* decoder = nullptr;
 
-    __android_log_security_bswrite(SEC_TAG_ADB_SEND_FILE, path);
+    switch (compression) {
+        case CompressionType::None:
+            decoder = &decoder_storage.emplace<NullDecoder>(buffer_span);
+            break;
 
-    unique_fd fd(adb_open_mode(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode));
+        case CompressionType::Brotli:
+            decoder = &decoder_storage.emplace<BrotliDecoder>(buffer_span);
+            break;
 
-    if (fd < 0 && errno == ENOENT) {
-        if (!secure_mkdirs(Dirname(path))) {
-            SendSyncFailErrno(s, "secure_mkdirs failed");
-            goto fail;
-        }
-        fd.reset(adb_open_mode(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode));
-    }
-    if (fd < 0 && errno == EEXIST) {
-        fd.reset(adb_open_mode(path, O_WRONLY | O_CLOEXEC, mode));
-    }
-    if (fd < 0) {
-        SendSyncFailErrno(s, "couldn't create file");
-        goto fail;
-    } else {
-        if (fchown(fd.get(), uid, gid) == -1) {
-            SendSyncFailErrno(s, "fchown failed");
-            goto fail;
-        }
+        case CompressionType::LZ4:
+            decoder = &decoder_storage.emplace<LZ4Decoder>(buffer_span);
+            break;
 
-#if defined(__ANDROID__)
-        // Not all filesystems support setting SELinux labels. http://b/23530370.
-        selinux_android_restorecon(path, 0);
-#endif
-
-        // fchown clears the setuid bit - restore it if present.
-        // Ignore the result of calling fchmod. It's not supported
-        // by all filesystems, so we don't check for success. b/12441485
-        fchmod(fd.get(), mode);
-    }
-
-    rc = posix_fadvise(fd.get(), 0, 0,
-                       POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED);
-    if (rc != 0) {
-        D("[ Failed to fadvise: %s ]", strerror(rc));
+        case CompressionType::Any:
+            LOG(FATAL) << "unexpected CompressionType::Any";
     }
 
     while (true) {
-        if (!ReadFdExactly(s, &msg.data, sizeof(msg.data))) goto fail;
+        if (!ReadFdExactly(s, &msg.data, sizeof(msg.data))) return false;
 
-        if (msg.data.id != ID_DATA) {
-            if (msg.data.id == ID_DONE) {
-                *timestamp = msg.data.size;
-                break;
-            }
+        if (msg.data.id == ID_DONE) {
+            *timestamp = msg.data.size;
+            decoder->Finish();
+        } else if (msg.data.id == ID_DATA) {
+            Block block(msg.data.size);
+            if (!ReadFdExactly(s, block.data(), msg.data.size)) return false;
+            decoder->Append(std::move(block));
+        } else {
             SendSyncFail(s, "invalid data message");
-            goto abort;
+            return false;
         }
 
-        if (msg.data.size > buffer.size()) {  // TODO: resize buffer?
-            SendSyncFail(s, "oversize data message");
-            goto abort;
+        while (true) {
+            std::span<char> output;
+            DecodeResult result = decoder->Decode(&output);
+            if (result == DecodeResult::Error) {
+                SendSyncFailErrno(s, "decompress failed");
+                return false;
+            }
+
+            // fd is -1 if the client is pushing with --dry-run.
+            if (fd != -1) {
+                if (!WriteFdExactly(fd, output.data(), output.size())) {
+                    SendSyncFailErrno(s, "write failed");
+                    return false;
+                }
+            }
+
+            if (result == DecodeResult::NeedInput) {
+                break;
+            } else if (result == DecodeResult::MoreOutput) {
+                continue;
+            } else if (result == DecodeResult::Done) {
+                return true;
+            } else {
+                LOG(FATAL) << "invalid DecodeResult: " << static_cast<int>(result);
+            }
         }
+    }
 
-        if (!ReadFdExactly(s, &buffer[0], msg.data.size)) goto abort;
+    __builtin_unreachable();
+}
 
-        if (!WriteFdExactly(fd.get(), &buffer[0], msg.data.size)) {
-            SendSyncFailErrno(s, "write failed");
+static bool handle_send_file(borrowed_fd s, const char* path, uint32_t* timestamp, uid_t uid,
+                             gid_t gid, uint64_t capabilities, mode_t mode,
+                             CompressionType compression, bool dry_run, std::vector<char>& buffer,
+                             bool do_unlink) {
+    syncmsg msg;
+    unique_fd fd;
+
+    if (!dry_run) {
+        __android_log_security_bswrite(SEC_TAG_ADB_SEND_FILE, path);
+        fd.reset(adb_open_mode(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode));
+
+        if (fd < 0 && errno == ENOENT) {
+            if (!secure_mkdirs(Dirname(path))) {
+                SendSyncFailErrno(s, "secure_mkdirs failed");
+                goto fail;
+            }
+            fd.reset(adb_open_mode(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode));
+        }
+        if (fd < 0 && errno == EEXIST) {
+            fd.reset(adb_open_mode(path, O_WRONLY | O_CLOEXEC, mode));
+        }
+        if (fd < 0) {
+            SendSyncFailErrno(s, "couldn't create file");
             goto fail;
+        } else {
+            if (fchown(fd.get(), uid, gid) == -1) {
+                SendSyncFailErrno(s, "fchown failed");
+                goto fail;
+            }
+
+#if defined(__ANDROID__)
+            // Not all filesystems support setting SELinux labels. http://b/23530370.
+            selinux_android_restorecon(path, 0);
+#endif
+
+            // fchown clears the setuid bit - restore it if present.
+            // Ignore the result of calling fchmod. It's not supported
+            // by all filesystems, so we don't check for success. b/12441485
+            fchmod(fd.get(), mode);
         }
+
+        int rc = posix_fadvise(fd.get(), 0, 0,
+                               POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED);
+        if (rc != 0) {
+            D("[ Failed to fadvise: %s ]", strerror(rc));
+        }
+    }
+
+    if (!handle_send_file_data(s, std::move(fd), timestamp, compression)) {
+        goto fail;
     }
 
     if (!update_capabilities(path, capabilities)) {
@@ -371,7 +428,6 @@
         if (!ReadFdExactly(s, &buffer[0], msg.data.size)) break;
     }
 
-abort:
     if (do_unlink) adb_unlink(path);
     return false;
 }
@@ -381,7 +437,7 @@
                              uint32_t* timestamp, std::vector<char>& buffer)
         __attribute__((error("no symlinks on Windows")));
 #else
-static bool handle_send_link(int s, const std::string& path, uint32_t* timestamp,
+static bool handle_send_link(int s, const std::string& path, uint32_t* timestamp, bool dry_run,
                              std::vector<char>& buffer) {
     syncmsg msg;
 
@@ -400,19 +456,21 @@
     if (!ReadFdExactly(s, &buffer[0], len)) return false;
 
     std::string buf_link;
-    if (!android::base::Readlink(path, &buf_link) || (buf_link != &buffer[0])) {
-        adb_unlink(path.c_str());
-        auto ret = symlink(&buffer[0], path.c_str());
-        if (ret && errno == ENOENT) {
-            if (!secure_mkdirs(Dirname(path))) {
-                SendSyncFailErrno(s, "secure_mkdirs failed");
+    if (!dry_run) {
+        if (!android::base::Readlink(path, &buf_link) || (buf_link != &buffer[0])) {
+            adb_unlink(path.c_str());
+            auto ret = symlink(&buffer[0], path.c_str());
+            if (ret && errno == ENOENT) {
+                if (!secure_mkdirs(Dirname(path))) {
+                    SendSyncFailErrno(s, "secure_mkdirs failed");
+                    return false;
+                }
+                ret = symlink(&buffer[0], path.c_str());
+            }
+            if (ret) {
+                SendSyncFailErrno(s, "symlink failed");
                 return false;
             }
-            ret = symlink(&buffer[0], path.c_str());
-        }
-        if (ret) {
-            SendSyncFailErrno(s, "symlink failed");
-            return false;
         }
     }
 
@@ -432,27 +490,15 @@
 }
 #endif
 
-static bool do_send(int s, const std::string& spec, std::vector<char>& buffer) {
-    // 'spec' is of the form "/some/path,0755". Break it up.
-    size_t comma = spec.find_last_of(',');
-    if (comma == std::string::npos) {
-        SendSyncFail(s, "missing , in ID_SEND");
-        return false;
-    }
-
-    std::string path = spec.substr(0, comma);
-
-    errno = 0;
-    mode_t mode = strtoul(spec.substr(comma + 1).c_str(), nullptr, 0);
-    if (errno != 0) {
-        SendSyncFail(s, "bad mode");
-        return false;
-    }
-
+static bool send_impl(int s, const std::string& path, mode_t mode, CompressionType compression,
+                      bool dry_run, std::vector<char>& buffer) {
     // Don't delete files before copying if they are not "regular" or symlinks.
     struct stat st;
-    bool do_unlink = (lstat(path.c_str(), &st) == -1) || S_ISREG(st.st_mode) ||
-                     (S_ISLNK(st.st_mode) && !S_ISLNK(mode));
+    bool do_unlink = false;
+    if (!dry_run) {
+        do_unlink = (lstat(path.c_str(), &st) == -1) || S_ISREG(st.st_mode) ||
+                    (S_ISLNK(st.st_mode) && !S_ISLNK(mode));
+    }
     if (do_unlink) {
         adb_unlink(path.c_str());
     }
@@ -460,7 +506,7 @@
     bool result;
     uint32_t timestamp;
     if (S_ISLNK(mode)) {
-        result = handle_send_link(s, path, &timestamp, buffer);
+        result = handle_send_link(s, path, &timestamp, dry_run, buffer);
     } else {
         // Copy user permission bits to "group" and "other" permissions.
         mode &= 0777;
@@ -470,12 +516,12 @@
         uid_t uid = -1;
         gid_t gid = -1;
         uint64_t capabilities = 0;
-        if (should_use_fs_config(path)) {
+        if (should_use_fs_config(path) && !dry_run) {
             adbd_fs_config(path.c_str(), 0, nullptr, &uid, &gid, &mode, &capabilities);
         }
 
-        result = handle_send_file(s, path.c_str(), &timestamp, uid, gid, capabilities, mode, buffer,
-                                  do_unlink);
+        result = handle_send_file(s, path.c_str(), &timestamp, uid, gid, capabilities, mode,
+                                  compression, dry_run, buffer, do_unlink);
     }
 
     if (!result) {
@@ -491,7 +537,76 @@
     return true;
 }
 
-static bool do_recv(int s, const char* path, std::vector<char>& buffer) {
+static bool do_send_v1(int s, const std::string& spec, std::vector<char>& buffer) {
+    // 'spec' is of the form "/some/path,0755". Break it up.
+    size_t comma = spec.find_last_of(',');
+    if (comma == std::string::npos) {
+        SendSyncFail(s, "missing , in ID_SEND_V1");
+        return false;
+    }
+
+    std::string path = spec.substr(0, comma);
+
+    errno = 0;
+    mode_t mode = strtoul(spec.substr(comma + 1).c_str(), nullptr, 0);
+    if (errno != 0) {
+        SendSyncFail(s, "bad mode");
+        return false;
+    }
+
+    return send_impl(s, path, mode, CompressionType::None, false, buffer);
+}
+
+static bool do_send_v2(int s, const std::string& path, std::vector<char>& buffer) {
+    // Read the setup packet.
+    syncmsg msg;
+    int rc = ReadFdExactly(s, &msg.send_v2_setup, sizeof(msg.send_v2_setup));
+    if (rc == 0) {
+        LOG(ERROR) << "failed to read send_v2 setup packet: EOF";
+        return false;
+    } else if (rc < 0) {
+        PLOG(ERROR) << "failed to read send_v2 setup packet";
+    }
+
+    bool dry_run = false;
+    std::optional<CompressionType> compression;
+
+    uint32_t orig_flags = msg.send_v2_setup.flags;
+    if (msg.send_v2_setup.flags & kSyncFlagBrotli) {
+        msg.send_v2_setup.flags &= ~kSyncFlagBrotli;
+        if (compression) {
+            SendSyncFail(s, android::base::StringPrintf("multiple compression flags received: %d",
+                                                        orig_flags));
+            return false;
+        }
+        compression = CompressionType::Brotli;
+    }
+    if (msg.send_v2_setup.flags & kSyncFlagLZ4) {
+        msg.send_v2_setup.flags &= ~kSyncFlagLZ4;
+        if (compression) {
+            SendSyncFail(s, android::base::StringPrintf("multiple compression flags received: %d",
+                                                        orig_flags));
+            return false;
+        }
+        compression = CompressionType::LZ4;
+    }
+    if (msg.send_v2_setup.flags & kSyncFlagDryRun) {
+        msg.send_v2_setup.flags &= ~kSyncFlagDryRun;
+        dry_run = true;
+    }
+
+    if (msg.send_v2_setup.flags) {
+        SendSyncFail(s, android::base::StringPrintf("unknown flags: %d", msg.send_v2_setup.flags));
+        return false;
+    }
+
+    errno = 0;
+    return send_impl(s, path, msg.send_v2_setup.mode, compression.value_or(CompressionType::None),
+                     dry_run, buffer);
+}
+
+static bool recv_impl(borrowed_fd s, const char* path, CompressionType compression,
+                      std::vector<char>& buffer) {
     __android_log_security_bswrite(SEC_TAG_ADB_RECV_FILE, path);
 
     unique_fd fd(adb_open(path, O_RDONLY | O_CLOEXEC));
@@ -507,16 +622,67 @@
 
     syncmsg msg;
     msg.data.id = ID_DATA;
-    while (true) {
-        int r = adb_read(fd.get(), &buffer[0], buffer.size() - sizeof(msg.data));
-        if (r <= 0) {
-            if (r == 0) break;
+
+    std::variant<std::monostate, NullEncoder, BrotliEncoder, LZ4Encoder> encoder_storage;
+    Encoder* encoder;
+
+    switch (compression) {
+        case CompressionType::None:
+            encoder = &encoder_storage.emplace<NullEncoder>(SYNC_DATA_MAX);
+            break;
+
+        case CompressionType::Brotli:
+            encoder = &encoder_storage.emplace<BrotliEncoder>(SYNC_DATA_MAX);
+            break;
+
+        case CompressionType::LZ4:
+            encoder = &encoder_storage.emplace<LZ4Encoder>(SYNC_DATA_MAX);
+            break;
+
+        case CompressionType::Any:
+            LOG(FATAL) << "unexpected CompressionType::Any";
+    }
+
+    bool sending = true;
+    while (sending) {
+        Block input(SYNC_DATA_MAX);
+        int r = adb_read(fd.get(), input.data(), input.size());
+        if (r < 0) {
             SendSyncFailErrno(s, "read failed");
             return false;
         }
-        msg.data.size = r;
-        if (!WriteFdExactly(s, &msg.data, sizeof(msg.data)) || !WriteFdExactly(s, &buffer[0], r)) {
-            return false;
+
+        if (r == 0) {
+            encoder->Finish();
+        } else {
+            input.resize(r);
+            encoder->Append(std::move(input));
+        }
+
+        while (true) {
+            Block output;
+            EncodeResult result = encoder->Encode(&output);
+            if (result == EncodeResult::Error) {
+                SendSyncFailErrno(s, "compress failed");
+                return false;
+            }
+
+            if (!output.empty()) {
+                msg.data.size = output.size();
+                if (!WriteFdExactly(s, &msg.data, sizeof(msg.data)) ||
+                    !WriteFdExactly(s, output.data(), output.size())) {
+                    return false;
+                }
+            }
+
+            if (result == EncodeResult::Done) {
+                sending = false;
+                break;
+            } else if (result == EncodeResult::NeedInput) {
+                break;
+            } else if (result == EncodeResult::MoreOutput) {
+                continue;
+            }
         }
     }
 
@@ -525,6 +691,50 @@
     return WriteFdExactly(s, &msg.data, sizeof(msg.data));
 }
 
+static bool do_recv_v1(borrowed_fd s, const char* path, std::vector<char>& buffer) {
+    return recv_impl(s, path, CompressionType::None, buffer);
+}
+
+static bool do_recv_v2(borrowed_fd s, const char* path, std::vector<char>& buffer) {
+    syncmsg msg;
+    // Read the setup packet.
+    int rc = ReadFdExactly(s, &msg.recv_v2_setup, sizeof(msg.recv_v2_setup));
+    if (rc == 0) {
+        LOG(ERROR) << "failed to read recv_v2 setup packet: EOF";
+        return false;
+    } else if (rc < 0) {
+        PLOG(ERROR) << "failed to read recv_v2 setup packet";
+    }
+
+    std::optional<CompressionType> compression;
+    uint32_t orig_flags = msg.recv_v2_setup.flags;
+    if (msg.recv_v2_setup.flags & kSyncFlagBrotli) {
+        msg.recv_v2_setup.flags &= ~kSyncFlagBrotli;
+        if (compression) {
+            SendSyncFail(s, android::base::StringPrintf("multiple compression flags received: %d",
+                                                        orig_flags));
+            return false;
+        }
+        compression = CompressionType::Brotli;
+    }
+    if (msg.recv_v2_setup.flags & kSyncFlagLZ4) {
+        msg.recv_v2_setup.flags &= ~kSyncFlagLZ4;
+        if (compression) {
+            SendSyncFail(s, android::base::StringPrintf("multiple compression flags received: %d",
+                                                        orig_flags));
+            return false;
+        }
+        compression = CompressionType::LZ4;
+    }
+
+    if (msg.recv_v2_setup.flags) {
+        SendSyncFail(s, android::base::StringPrintf("unknown flags: %d", msg.recv_v2_setup.flags));
+        return false;
+    }
+
+    return recv_impl(s, path, compression.value_or(CompressionType::None), buffer);
+}
+
 static const char* sync_id_to_name(uint32_t id) {
   switch (id) {
     case ID_LSTAT_V1:
@@ -537,10 +747,14 @@
       return "list_v1";
     case ID_LIST_V2:
       return "list_v2";
-    case ID_SEND:
-      return "send";
-    case ID_RECV:
-      return "recv";
+    case ID_SEND_V1:
+        return "send_v1";
+    case ID_SEND_V2:
+        return "send_v2";
+    case ID_RECV_V1:
+        return "recv_v1";
+    case ID_RECV_V2:
+        return "recv_v2";
     case ID_QUIT:
         return "quit";
     default:
@@ -585,11 +799,17 @@
         case ID_LIST_V2:
             if (!do_list_v2(fd, name)) return false;
             break;
-        case ID_SEND:
-            if (!do_send(fd, name, buffer)) return false;
+        case ID_SEND_V1:
+            if (!do_send_v1(fd, name, buffer)) return false;
             break;
-        case ID_RECV:
-            if (!do_recv(fd, name, buffer)) return false;
+        case ID_SEND_V2:
+            if (!do_send_v2(fd, name, buffer)) return false;
+            break;
+        case ID_RECV_V1:
+            if (!do_recv_v1(fd, name, buffer)) return false;
+            break;
+        case ID_RECV_V2:
+            if (!do_recv_v2(fd, name, buffer)) return false;
             break;
         case ID_QUIT:
             return false;
diff --git a/adb/daemon/main.cpp b/adb/daemon/main.cpp
index 9e02e89..55b7783 100644
--- a/adb/daemon/main.cpp
+++ b/adb/daemon/main.cpp
@@ -301,6 +301,8 @@
         setup_adb(addrs);
     }
 
+    LOG(INFO) << "adbd started";
+
     D("adbd_main(): pre init_jdwp()");
     init_jdwp();
     D("adbd_main(): post init_jdwp()");
diff --git a/adb/daemon/usb.cpp b/adb/daemon/usb.cpp
index 87937fb..7fff05a 100644
--- a/adb/daemon/usb.cpp
+++ b/adb/daemon/usb.cpp
@@ -45,19 +45,15 @@
 #include <android-base/properties.h>
 #include <android-base/thread_annotations.h>
 
-#include <adbd/usb.h>
-
 #include "adb_unique_fd.h"
 #include "adb_utils.h"
+#include "daemon/usb_ffs.h"
 #include "sysdeps/chrono.h"
 #include "transport.h"
 #include "types.h"
 
 using android::base::StringPrintf;
 
-// We can't find out whether we have support for AIO on ffs endpoints until we submit a read.
-static std::optional<bool> gFfsAioSupported;
-
 // Not all USB controllers support operations larger than 16k, so don't go above that.
 // Also, each submitted operation does an allocation in the kernel of that size, so we want to
 // minimize our queue depth while still maintaining a deep enough queue to keep the USB stack fed.
@@ -612,17 +608,10 @@
         block->pending = true;
         struct iocb* iocb = &block->control;
         if (io_submit(aio_context_.get(), 1, &iocb) != 1) {
-            if (errno == EINVAL && !gFfsAioSupported.has_value()) {
-                HandleError("failed to submit first read, AIO on FFS not supported");
-                gFfsAioSupported = false;
-                return false;
-            }
-
             HandleError(StringPrintf("failed to submit read: %s", strerror(errno)));
             return false;
         }
 
-        gFfsAioSupported = true;
         return true;
     }
 
@@ -741,17 +730,10 @@
     static constexpr int kInterruptionSignal = SIGUSR1;
 };
 
-void usb_init_legacy();
-
 static void usb_ffs_open_thread() {
     adb_thread_setname("usb ffs open");
 
     while (true) {
-        if (gFfsAioSupported.has_value() && !gFfsAioSupported.value()) {
-            LOG(INFO) << "failed to use nonblocking ffs, falling back to legacy";
-            return usb_init_legacy();
-        }
-
         unique_fd control;
         unique_fd bulk_out;
         unique_fd bulk_in;
@@ -773,13 +755,5 @@
 }
 
 void usb_init() {
-    bool use_nonblocking = android::base::GetBoolProperty(
-            "persist.adb.nonblocking_ffs",
-            android::base::GetBoolProperty("ro.adb.nonblocking_ffs", true));
-
-    if (use_nonblocking) {
-        std::thread(usb_ffs_open_thread).detach();
-    } else {
-        usb_init_legacy();
-    }
+    std::thread(usb_ffs_open_thread).detach();
 }
diff --git a/adb/daemon/usb_dummy.cpp b/adb/daemon/usb_dummy.cpp
deleted file mode 100644
index c9bf797..0000000
--- a/adb/daemon/usb_dummy.cpp
+++ /dev/null
@@ -1,42 +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 <adbd/usb.h>
-
-#include <android-base/logging.h>
-
-int usb_write(usb_handle*, const void*, int) {
-    LOG(FATAL) << "unimplemented";
-    return -1;
-}
-
-int usb_read(usb_handle*, void*, int) {
-    LOG(FATAL) << "unimplemented";
-    return -1;
-}
-
-int usb_close(usb_handle*) {
-    LOG(FATAL) << "unimplemented";
-    return -1;
-}
-
-void usb_reset(usb_handle*) {
-    LOG(FATAL) << "unimplemented";
-}
-
-void usb_kick(usb_handle*) {
-    LOG(FATAL) << "unimplemented";
-}
diff --git a/adb/daemon/usb_ffs.cpp b/adb/daemon/usb_ffs.cpp
index cb7e2fb..e538ca8 100644
--- a/adb/daemon/usb_ffs.cpp
+++ b/adb/daemon/usb_ffs.cpp
@@ -18,6 +18,8 @@
 
 #include "sysdeps.h"
 
+#include "daemon/usb_ffs.h"
+
 #include <linux/usb/ch9.h>
 #include <linux/usb/functionfs.h>
 
@@ -26,7 +28,6 @@
 #include <android-base/unique_fd.h>
 
 #include "adb.h"
-#include "adbd/usb.h"
 
 #define MAX_PACKET_SIZE_FS 64
 #define MAX_PACKET_SIZE_HS 512
@@ -299,7 +300,6 @@
         }
         // Signal only when writing the descriptors to ffs
         android::base::SetProperty("sys.usb.ffs.ready", "1");
-        *out_control = std::move(control);
     }
 
     bulk_out.reset(adb_open(USB_FFS_ADB_OUT, O_RDONLY));
@@ -314,6 +314,7 @@
         return false;
     }
 
+    *out_control = std::move(control);
     *out_bulk_in = std::move(bulk_in);
     *out_bulk_out = std::move(bulk_out);
     return true;
diff --git a/adb/daemon/usb_ffs.h b/adb/daemon/usb_ffs.h
new file mode 100644
index 0000000..a19d7cc
--- /dev/null
+++ b/adb/daemon/usb_ffs.h
@@ -0,0 +1,22 @@
+/*
+ * 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 <android-base/unique_fd.h>
+
+bool open_functionfs(android::base::unique_fd* control, android::base::unique_fd* bulk_out,
+                     android::base::unique_fd* bulk_in);
diff --git a/adb/fdevent/fdevent.cpp b/adb/fdevent/fdevent.cpp
index 562f587..fd55020 100644
--- a/adb/fdevent/fdevent.cpp
+++ b/adb/fdevent/fdevent.cpp
@@ -63,7 +63,10 @@
 
     int fd_num = fd.get();
 
-    fdevent* fde = new fdevent();
+    auto [it, inserted] = this->installed_fdevents_.emplace(fd_num, fdevent{});
+    CHECK(inserted);
+
+    fdevent* fde = &it->second;
     fde->id = fdevent_id_++;
     fde->state = 0;
     fde->fd = std::move(fd);
@@ -76,10 +79,6 @@
         LOG(ERROR) << "failed to set non-blocking mode for fd " << fde->fd.get();
     }
 
-    auto [it, inserted] = this->installed_fdevents_.emplace(fd_num, fde);
-    CHECK(inserted);
-    UNUSED(it);
-
     this->Register(fde);
     return fde;
 }
@@ -92,12 +91,12 @@
 
     this->Unregister(fde);
 
-    auto erased = this->installed_fdevents_.erase(fde->fd.get());
+    unique_fd fd = std::move(fde->fd);
+
+    auto erased = this->installed_fdevents_.erase(fd.get());
     CHECK_EQ(1UL, erased);
 
-    unique_fd result = std::move(fde->fd);
-    delete fde;
-    return result;
+    return fd;
 }
 
 void fdevent_context::Add(fdevent* fde, unsigned events) {
@@ -123,9 +122,9 @@
 
     for (const auto& [fd, fde] : this->installed_fdevents_) {
         UNUSED(fd);
-        auto timeout_opt = fde->timeout;
+        auto timeout_opt = fde.timeout;
         if (timeout_opt) {
-            auto deadline = fde->last_active + *timeout_opt;
+            auto deadline = fde.last_active + *timeout_opt;
             auto time_left = duration_cast<std::chrono::milliseconds>(deadline - now);
             if (time_left < 0ms) {
                 time_left = 0ms;
@@ -194,11 +193,13 @@
 #endif
 }
 
-static auto& g_ambient_fdevent_context =
-        *new std::unique_ptr<fdevent_context>(fdevent_create_context());
+static auto& g_ambient_fdevent_context() {
+    static auto context = fdevent_create_context().release();
+    return context;
+}
 
 static fdevent_context* fdevent_get_ambient() {
-    return g_ambient_fdevent_context.get();
+    return g_ambient_fdevent_context();
 }
 
 fdevent* fdevent_create(int fd, fd_func func, void* arg) {
@@ -256,5 +257,6 @@
 }
 
 void fdevent_reset() {
-    g_ambient_fdevent_context = fdevent_create_context();
+    auto old = std::exchange(g_ambient_fdevent_context(), fdevent_create_context().release());
+    delete old;
 }
diff --git a/adb/fdevent/fdevent.h b/adb/fdevent/fdevent.h
index 86814d7..9fc3b2c 100644
--- a/adb/fdevent/fdevent.h
+++ b/adb/fdevent/fdevent.h
@@ -52,6 +52,20 @@
     unsigned events;
 };
 
+struct fdevent final {
+    uint64_t id;
+
+    unique_fd fd;
+    int force_eof = 0;
+
+    uint16_t state = 0;
+    std::optional<std::chrono::milliseconds> timeout;
+    std::chrono::steady_clock::time_point last_active;
+
+    std::variant<fd_func, fd_func2> func;
+    void* arg = nullptr;
+};
+
 struct fdevent_context {
   public:
     virtual ~fdevent_context() = default;
@@ -113,7 +127,7 @@
     std::atomic<bool> terminate_loop_ = false;
 
   protected:
-    std::unordered_map<int, fdevent*> installed_fdevents_;
+    std::unordered_map<int, fdevent> installed_fdevents_;
 
   private:
     uint64_t fdevent_id_ = 0;
@@ -121,20 +135,6 @@
     std::deque<std::function<void()>> run_queue_ GUARDED_BY(run_queue_mutex_);
 };
 
-struct fdevent {
-    uint64_t id;
-
-    unique_fd fd;
-    int force_eof = 0;
-
-    uint16_t state = 0;
-    std::optional<std::chrono::milliseconds> timeout;
-    std::chrono::steady_clock::time_point last_active;
-
-    std::variant<fd_func, fd_func2> func;
-    void* arg = nullptr;
-};
-
 // Backwards compatibility shims that forward to the global fdevent_context.
 fdevent* fdevent_create(int fd, fd_func func, void* arg);
 fdevent* fdevent_create(int fd, fd_func2 func, void* arg);
diff --git a/adb/fdevent/fdevent_epoll.cpp b/adb/fdevent/fdevent_epoll.cpp
index e3d1674..4ef41d1 100644
--- a/adb/fdevent/fdevent_epoll.cpp
+++ b/adb/fdevent/fdevent_epoll.cpp
@@ -155,15 +155,15 @@
             event_map[fde] = events;
         }
 
-        for (const auto& [fd, fde] : installed_fdevents_) {
+        for (auto& [fd, fde] : installed_fdevents_) {
             unsigned events = 0;
-            if (auto it = event_map.find(fde); it != event_map.end()) {
+            if (auto it = event_map.find(&fde); it != event_map.end()) {
                 events = it->second;
             }
 
             if (events == 0) {
-                if (fde->timeout) {
-                    auto deadline = fde->last_active + *fde->timeout;
+                if (fde.timeout) {
+                    auto deadline = fde.last_active + *fde.timeout;
                     if (deadline < post_poll) {
                         events |= FDE_TIMEOUT;
                     }
@@ -171,13 +171,13 @@
             }
 
             if (events != 0) {
-                LOG(DEBUG) << dump_fde(fde) << " got events " << std::hex << std::showbase
+                LOG(DEBUG) << dump_fde(&fde) << " got events " << std::hex << std::showbase
                            << events;
-                fde_events.push_back({fde, events});
-                fde->last_active = post_poll;
+                fde_events.push_back({&fde, events});
+                fde.last_active = post_poll;
             }
         }
-        this->HandleEvents(std::move(fde_events));
+        this->HandleEvents(fde_events);
         fde_events.clear();
     }
 
diff --git a/adb/fdevent/fdevent_epoll.h b/adb/fdevent/fdevent_epoll.h
index 684fa32..6214d2e 100644
--- a/adb/fdevent/fdevent_epoll.h
+++ b/adb/fdevent/fdevent_epoll.h
@@ -47,12 +47,7 @@
   protected:
     virtual void Interrupt() final;
 
-  public:
-    // All operations to fdevent should happen only in the main thread.
-    // That's why we don't need a lock for fdevent.
-    std::unordered_map<int, fdevent*> epoll_node_map_;
-    std::list<fdevent*> pending_list_;
-
+  private:
     unique_fd epoll_fd_;
     unique_fd interrupt_fd_;
     fdevent* interrupt_fde_ = nullptr;
diff --git a/adb/fdevent/fdevent_poll.cpp b/adb/fdevent/fdevent_poll.cpp
index cc4a7a1..ac86c08 100644
--- a/adb/fdevent/fdevent_poll.cpp
+++ b/adb/fdevent/fdevent_poll.cpp
@@ -103,24 +103,27 @@
 void fdevent_context_poll::Loop() {
     main_thread_id_ = android::base::GetThreadId();
 
+    std::vector<adb_pollfd> pollfds;
+    std::vector<fdevent_event> poll_events;
+
     while (true) {
         if (terminate_loop_) {
             break;
         }
 
         D("--- --- waiting for events");
-        std::vector<adb_pollfd> pollfds;
+        pollfds.clear();
         for (const auto& [fd, fde] : this->installed_fdevents_) {
             adb_pollfd pfd;
             pfd.fd = fd;
             pfd.events = 0;
-            if (fde->state & FDE_READ) {
+            if (fde.state & FDE_READ) {
                 pfd.events |= POLLIN;
             }
-            if (fde->state & FDE_WRITE) {
+            if (fde.state & FDE_WRITE) {
                 pfd.events |= POLLOUT;
             }
-            if (fde->state & FDE_ERROR) {
+            if (fde.state & FDE_ERROR) {
                 pfd.events |= POLLERR;
             }
 #if defined(__linux__)
@@ -147,7 +150,6 @@
         }
 
         auto post_poll = std::chrono::steady_clock::now();
-        std::vector<fdevent_event> poll_events;
 
         for (const auto& pollfd : pollfds) {
             unsigned events = 0;
@@ -170,7 +172,7 @@
 
             auto it = this->installed_fdevents_.find(pollfd.fd);
             CHECK(it != this->installed_fdevents_.end());
-            fdevent* fde = it->second;
+            fdevent* fde = &it->second;
 
             if (events == 0) {
                 if (fde->timeout) {
@@ -187,7 +189,8 @@
                 fde->last_active = post_poll;
             }
         }
-        this->HandleEvents(std::move(poll_events));
+        this->HandleEvents(poll_events);
+        poll_events.clear();
     }
 
     main_thread_id_.reset();
diff --git a/adb/file_sync_protocol.h b/adb/file_sync_protocol.h
index 508c138..8f8f85f 100644
--- a/adb/file_sync_protocol.h
+++ b/adb/file_sync_protocol.h
@@ -27,8 +27,10 @@
 #define ID_DENT_V1 MKID('D', 'E', 'N', 'T')
 #define ID_DENT_V2 MKID('D', 'N', 'T', '2')
 
-#define ID_SEND MKID('S', 'E', 'N', 'D')
-#define ID_RECV MKID('R', 'E', 'C', 'V')
+#define ID_SEND_V1 MKID('S', 'E', 'N', 'D')
+#define ID_SEND_V2 MKID('S', 'N', 'D', '2')
+#define ID_RECV_V1 MKID('R', 'E', 'C', 'V')
+#define ID_RECV_V2 MKID('R', 'C', 'V', '2')
 #define ID_DONE MKID('D', 'O', 'N', 'E')
 #define ID_DATA MKID('D', 'A', 'T', 'A')
 #define ID_OKAY MKID('O', 'K', 'A', 'Y')
@@ -41,57 +43,100 @@
     // Followed by 'path_length' bytes of path (not NUL-terminated).
 } __attribute__((packed));
 
+struct __attribute__((packed)) sync_stat_v1 {
+    uint32_t id;
+    uint32_t mode;
+    uint32_t size;
+    uint32_t mtime;
+};
+
+struct __attribute__((packed)) sync_stat_v2 {
+    uint32_t id;
+    uint32_t error;
+    uint64_t dev;
+    uint64_t ino;
+    uint32_t mode;
+    uint32_t nlink;
+    uint32_t uid;
+    uint32_t gid;
+    uint64_t size;
+    int64_t atime;
+    int64_t mtime;
+    int64_t ctime;
+};
+
+struct __attribute__((packed)) sync_dent_v1 {
+    uint32_t id;
+    uint32_t mode;
+    uint32_t size;
+    uint32_t mtime;
+    uint32_t namelen;
+};  // followed by `namelen` bytes of the name.
+
+struct __attribute__((packed)) sync_dent_v2 {
+    uint32_t id;
+    uint32_t error;
+    uint64_t dev;
+    uint64_t ino;
+    uint32_t mode;
+    uint32_t nlink;
+    uint32_t uid;
+    uint32_t gid;
+    uint64_t size;
+    int64_t atime;
+    int64_t mtime;
+    int64_t ctime;
+    uint32_t namelen;
+};  // followed by `namelen` bytes of the name.
+
+enum SyncFlag : uint32_t {
+    kSyncFlagNone = 0,
+    kSyncFlagBrotli = 1,
+    kSyncFlagLZ4 = 2,
+    kSyncFlagDryRun = 0x8000'0000U,
+};
+
+enum class CompressionType {
+    None,
+    Any,
+    Brotli,
+    LZ4,
+};
+
+// send_v1 sent the path in a buffer, followed by a comma and the mode as a string.
+// send_v2 sends just the path in the first request, and then sends another syncmsg (with the
+// same ID!) with details.
+struct __attribute__((packed)) sync_send_v2 {
+    uint32_t id;
+    uint32_t mode;
+    uint32_t flags;
+};
+
+// Likewise, recv_v1 just sent the path without any accompanying data.
+struct __attribute__((packed)) sync_recv_v2 {
+    uint32_t id;
+    uint32_t flags;
+};
+
+struct __attribute__((packed)) sync_data {
+    uint32_t id;
+    uint32_t size;
+};  // followed by `size` bytes of data.
+
+struct __attribute__((packed)) sync_status {
+    uint32_t id;
+    uint32_t msglen;
+};  // followed by `msglen` bytes of error message, if id == ID_FAIL.
+
 union syncmsg {
-    struct __attribute__((packed)) {
-        uint32_t id;
-        uint32_t mode;
-        uint32_t size;
-        uint32_t mtime;
-    } stat_v1;
-    struct __attribute__((packed)) {
-        uint32_t id;
-        uint32_t error;
-        uint64_t dev;
-        uint64_t ino;
-        uint32_t mode;
-        uint32_t nlink;
-        uint32_t uid;
-        uint32_t gid;
-        uint64_t size;
-        int64_t atime;
-        int64_t mtime;
-        int64_t ctime;
-    } stat_v2;
-    struct __attribute__((packed)) {
-        uint32_t id;
-        uint32_t mode;
-        uint32_t size;
-        uint32_t mtime;
-        uint32_t namelen;
-    } dent_v1; // followed by `namelen` bytes of the name.
-    struct __attribute__((packed)) {
-        uint32_t id;
-        uint32_t error;
-        uint64_t dev;
-        uint64_t ino;
-        uint32_t mode;
-        uint32_t nlink;
-        uint32_t uid;
-        uint32_t gid;
-        uint64_t size;
-        int64_t atime;
-        int64_t mtime;
-        int64_t ctime;
-        uint32_t namelen;
-    } dent_v2; // followed by `namelen` bytes of the name.
-    struct __attribute__((packed)) {
-        uint32_t id;
-        uint32_t size;
-    } data; // followed by `size` bytes of data.
-    struct __attribute__((packed)) {
-        uint32_t id;
-        uint32_t msglen;
-    } status; // followed by `msglen` bytes of error message, if id == ID_FAIL.
+    sync_stat_v1 stat_v1;
+    sync_stat_v2 stat_v2;
+    sync_dent_v1 dent_v1;
+    sync_dent_v2 dent_v2;
+    sync_data data;
+    sync_status status;
+    sync_send_v2 send_v2_setup;
+    sync_recv_v2 recv_v2_setup;
 };
 
 #define SYNC_DATA_MAX (64 * 1024)
diff --git a/adb/libs/adbconnection/Android.bp b/adb/libs/adbconnection/Android.bp
index f6b0a42..f7d2dc1 100644
--- a/adb/libs/adbconnection/Android.bp
+++ b/adb/libs/adbconnection/Android.bp
@@ -18,6 +18,11 @@
     use_version_lib: false,
 
     recovery_available: true,
+    apex_available: [
+        "com.android.adbd",
+        // TODO(b/151398197) remove the below
+        "//apex_available:platform",
+    ],
     compile_multilib: "both",
 }
 
@@ -48,6 +53,7 @@
         linux: {
             version_script: "libadbconnection_client.map.txt",
         },
+        darwin: { enabled: false },
     },
     stubs: {
         symbol_file: "libadbconnection_client.map.txt",
diff --git a/adb/libs/adbconnection/adbconnection_client.cpp b/adb/libs/adbconnection/adbconnection_client.cpp
index c132342..7e16148 100644
--- a/adb/libs/adbconnection/adbconnection_client.cpp
+++ b/adb/libs/adbconnection/adbconnection_client.cpp
@@ -140,7 +140,13 @@
 
   int rc = connect(ctx->control_socket_.get(), reinterpret_cast<sockaddr*>(&addr), addr_len);
   if (rc != 0) {
-    PLOG(ERROR) << "failed to connect to jdwp control socket";
+    if (errno == ECONNREFUSED) {
+      // On userdebug devices, every Java process is debuggable, so if adbd is explicitly turned
+      // off, this would spew enormous amounts of red-herring errors.
+      LOG(DEBUG) << "failed to connect to jdwp control socket, adbd not running?";
+    } else {
+      PLOG(ERROR) << "failed to connect to jdwp control socket";
+    }
     return nullptr;
   }
 
diff --git a/adb/mdns_test.cpp b/adb/mdns_test.cpp
new file mode 100644
index 0000000..1f662c1
--- /dev/null
+++ b/adb/mdns_test.cpp
@@ -0,0 +1,107 @@
+/*
+ * 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 "adb_mdns.h"
+
+static bool isValidMdnsServiceName(std::string_view name) {
+    // The rules for Service Names [RFC6335] state that they may be no more
+    // than fifteen characters long (not counting the mandatory underscore),
+    // consisting of only letters, digits, and hyphens, must begin and end
+    // with a letter or digit, must not contain consecutive hyphens, and
+    // must contain at least one letter.
+
+    // No more than 15 characters long
+    if (name.empty() || name.size() > 15) {
+        return false;
+    }
+
+    bool hasAtLeastOneLetter = false;
+    bool sawHyphen = false;
+    for (size_t i = 0; i < name.size(); ++i) {
+        // Must contain at least one letter
+        // Only contains letters, digits and hyphens
+        if (name[i] == '-') {
+            // Cannot be at beginning or end
+            if (i == 0 || i == name.size() - 1) {
+                return false;
+            }
+            if (sawHyphen) {
+                // Consecutive hyphen found
+                return false;
+            }
+            sawHyphen = true;
+            continue;
+        }
+
+        sawHyphen = false;
+        if ((name[i] >= 'a' && name[i] <= 'z') || (name[i] >= 'A' && name[i] <= 'Z')) {
+            hasAtLeastOneLetter = true;
+            continue;
+        }
+
+        if (name[i] >= '0' && name[i] <= '9') {
+            continue;
+        }
+
+        // Invalid character
+        return false;
+    }
+
+    return hasAtLeastOneLetter;
+}
+
+TEST(mdns, test_isValidMdnsServiceName) {
+    // Longer than 15 characters
+    EXPECT_FALSE(isValidMdnsServiceName("abcd1234abcd1234"));
+
+    // Contains invalid characters
+    EXPECT_FALSE(isValidMdnsServiceName("a*a"));
+    EXPECT_FALSE(isValidMdnsServiceName("a_a"));
+    EXPECT_FALSE(isValidMdnsServiceName("_a"));
+
+    // Does not begin or end with letter or digit
+    EXPECT_FALSE(isValidMdnsServiceName(""));
+    EXPECT_FALSE(isValidMdnsServiceName("-"));
+    EXPECT_FALSE(isValidMdnsServiceName("-a"));
+    EXPECT_FALSE(isValidMdnsServiceName("-1"));
+    EXPECT_FALSE(isValidMdnsServiceName("a-"));
+    EXPECT_FALSE(isValidMdnsServiceName("1-"));
+
+    // Contains consecutive hyphens
+    EXPECT_FALSE(isValidMdnsServiceName("a--a"));
+
+    // Does not contain at least one letter
+    EXPECT_FALSE(isValidMdnsServiceName("1"));
+    EXPECT_FALSE(isValidMdnsServiceName("12"));
+    EXPECT_FALSE(isValidMdnsServiceName("1-2"));
+
+    // Some valid names
+    EXPECT_TRUE(isValidMdnsServiceName("a"));
+    EXPECT_TRUE(isValidMdnsServiceName("a1"));
+    EXPECT_TRUE(isValidMdnsServiceName("1A"));
+    EXPECT_TRUE(isValidMdnsServiceName("aZ"));
+    EXPECT_TRUE(isValidMdnsServiceName("a-Z"));
+    EXPECT_TRUE(isValidMdnsServiceName("a-b-Z"));
+    EXPECT_TRUE(isValidMdnsServiceName("abc-def-123-456"));
+}
+
+TEST(mdns, ServiceName_RFC6335) {
+    EXPECT_TRUE(isValidMdnsServiceName(ADB_MDNS_SERVICE_TYPE));
+    EXPECT_TRUE(isValidMdnsServiceName(ADB_MDNS_TLS_PAIRING_TYPE));
+    EXPECT_TRUE(isValidMdnsServiceName(ADB_MDNS_TLS_CONNECT_TYPE));
+}
diff --git a/adb/pairing_auth/aes_128_gcm.cpp b/adb/pairing_auth/aes_128_gcm.cpp
index 2978834..51520d8 100644
--- a/adb/pairing_auth/aes_128_gcm.cpp
+++ b/adb/pairing_auth/aes_128_gcm.cpp
@@ -19,7 +19,6 @@
 #include <android-base/endian.h>
 #include <android-base/logging.h>
 
-#include <openssl/crypto.h>
 #include <openssl/evp.h>
 #include <openssl/hkdf.h>
 #include <openssl/rand.h>
@@ -28,155 +27,64 @@
 namespace pairing {
 
 namespace {
-static const size_t kHkdfKeyLength = 256;
-
-struct Header {
-    uint32_t payload;
-    uint8_t iv[AES_128_GCM_IV_SIZE];
-    uint8_t tag[AES_128_GCM_TAG_SIZE];
-} __attribute__((packed));
+// Size of AES-128-GCM key, in bytes
+static constexpr size_t kHkdfKeyLength = 16;
 
 }  // namespace
 
-// static
-const EVP_CIPHER* Aes128Gcm::cipher_ = EVP_aes_128_gcm();
-
 Aes128Gcm::Aes128Gcm(const uint8_t* key_material, size_t key_material_len) {
     CHECK(key_material);
     CHECK_NE(key_material_len, 0ul);
-    context_.reset(EVP_CIPHER_CTX_new());
-    CHECK(context_.get());
 
-    // Start with a random number for our counter
-    CHECK_EQ(RAND_bytes(counter_.data(), counter_.size()), 1);
-
-    uint8_t key[kHkdfKeyLength] = {};
-    uint8_t salt[64] = "this is the salt";
-    uint8_t info[64] = "this is the info";
-    CHECK_EQ(HKDF(key, sizeof(key), EVP_sha256(), key_material, key_material_len, salt,
-                  sizeof(salt), info, sizeof(info)),
+    uint8_t key[kHkdfKeyLength];
+    uint8_t info[] = "adb pairing_auth aes-128-gcm key";
+    CHECK_EQ(HKDF(key, sizeof(key), EVP_sha256(), key_material, key_material_len, nullptr, 0, info,
+                  sizeof(info) - 1),
              1);
-    CHECK_EQ(AES_set_encrypt_key(key, sizeof(key), &aes_key_), 0);
+    CHECK(EVP_AEAD_CTX_init(context_.get(), EVP_aead_aes_128_gcm(), key, sizeof(key),
+                            EVP_AEAD_DEFAULT_TAG_LENGTH, nullptr));
 }
 
-int Aes128Gcm::Encrypt(const uint8_t* in, size_t in_len, uint8_t* out, size_t out_len) {
-    if (out_len < EncryptedSize(in_len)) {
-        LOG(ERROR) << "out buffer size (sz=" << out_len
-                   << ") not big enough (sz=" << EncryptedSize(in_len) << ")";
-        return -1;
-    }
-    auto& header = *reinterpret_cast<Header*>(out);
-    // Place the IV in the header
-    memcpy(header.iv, counter_.data(), counter_.size());
-    int status = EVP_EncryptInit_ex(context_.get(), cipher_, nullptr,
-                                    reinterpret_cast<const uint8_t*>(&aes_key_), counter_.data());
-    counter_.Increase();
-    if (status != 1) {
-        return -1;
+std::optional<size_t> Aes128Gcm::Encrypt(const uint8_t* in, size_t in_len, uint8_t* out,
+                                         size_t out_len) {
+    std::vector<uint8_t> nonce(EVP_AEAD_nonce_length(EVP_AEAD_CTX_aead(context_.get())), 0);
+    memcpy(nonce.data(), &enc_sequence_, sizeof(enc_sequence_));
+    size_t written_sz;
+    if (!EVP_AEAD_CTX_seal(context_.get(), out, &written_sz, out_len, nonce.data(), nonce.size(),
+                           in, in_len, nullptr, 0)) {
+        LOG(ERROR) << "Failed to encrypt (in_len=" << in_len << ", out_len=" << out_len
+                   << ", out_len_needed=" << EncryptedSize(in_len) << ")";
+        return std::nullopt;
     }
 
-    int cipherLen = 0;
-    out += sizeof(header);
-    status = EVP_EncryptUpdate(context_.get(), out, &cipherLen, in, in_len);
-    if (status != 1 || cipherLen < 0) {
-        return -1;
-    }
-
-    // Padding is enabled by default, so EVP_EncryptFinal_ex will pad any
-    // remaining partial data up to the block size.
-    int padding = 0;
-    status = EVP_EncryptFinal_ex(context_.get(), out + cipherLen, &padding);
-    if (status != 1 || padding < 0) {
-        return -1;
-    }
-
-    // Place the tag in the header
-    status = EVP_CIPHER_CTX_ctrl(context_.get(), EVP_CTRL_GCM_GET_TAG, sizeof(header.tag),
-                                 header.tag);
-    if (status != 1) {
-        return -1;
-    }
-    // Place the payload size in the header
-    uint32_t totalLen = sizeof(header) + cipherLen + padding;
-    header.payload = htonl(static_cast<uint32_t>(cipherLen) + static_cast<uint32_t>(padding));
-    return totalLen;
+    ++enc_sequence_;
+    return written_sz;
 }
 
-int Aes128Gcm::Decrypt(const uint8_t* in, size_t in_len, uint8_t* out, size_t out_len) {
-    if (in_len < sizeof(Header)) {
-        return 0;
-    }
-    if (out_len < DecryptedSize(in, in_len)) {
-        return 0;
-    }
-    const auto& header = *reinterpret_cast<const Header*>(in);
-    uint32_t payload = ntohl(header.payload);
-    uint32_t expected_inlen = sizeof(Header) + payload;
-    if (in_len < expected_inlen) {
-        // Not enough data available
-        return 0;
-    }
-    // Initialized with expected IV from header
-    int status = EVP_DecryptInit_ex(context_.get(), cipher_, nullptr,
-                                    reinterpret_cast<const uint8_t*>(&aes_key_), header.iv);
-    if (status != 1) {
-        return -1;
+std::optional<size_t> Aes128Gcm::Decrypt(const uint8_t* in, size_t in_len, uint8_t* out,
+                                         size_t out_len) {
+    std::vector<uint8_t> nonce(EVP_AEAD_nonce_length(EVP_AEAD_CTX_aead(context_.get())), 0);
+    memcpy(nonce.data(), &dec_sequence_, sizeof(dec_sequence_));
+    size_t written_sz;
+    if (!EVP_AEAD_CTX_open(context_.get(), out, &written_sz, out_len, nonce.data(), nonce.size(),
+                           in, in_len, nullptr, 0)) {
+        LOG(ERROR) << "Failed to decrypt (in_len=" << in_len << ", out_len=" << out_len
+                   << ", out_len_needed=" << DecryptedSize(in_len) << ")";
+        return std::nullopt;
     }
 
-    int decrypted_len = 0;
-    status = EVP_DecryptUpdate(context_.get(), out, &decrypted_len, in + sizeof(header), payload);
-    if (status != 1 || decrypted_len < 0) {
-        return -1;
-    }
-
-    // Set expected tag from header
-    status = EVP_CIPHER_CTX_ctrl(context_.get(), EVP_CTRL_GCM_SET_TAG, sizeof(header.tag),
-                                 const_cast<uint8_t*>(header.tag));
-    if (status != 1) {
-        return -1;
-    }
-
-    // This is the padding. It can be ignored.
-    int len = 0;
-    status = EVP_DecryptFinal_ex(context_.get(), out + decrypted_len, &len);
-    if (status != 1) {
-        LOG(ERROR) << "EVP_DecryptFinal_ex failed. Tag mismatch";
-        return -1;
-    }
-
-    // Return the length without the padding.
-    return decrypted_len;
+    ++dec_sequence_;
+    return written_sz;
 }
 
 size_t Aes128Gcm::EncryptedSize(size_t size) {
-    // We need to account for block alignment of the encrypted data.
-    // According to openssl.org/docs/man1.0.2/man3/EVP_EncryptUpdate.html,
-    // "The amount of data written depends on the block alignment of the
-    // encrypted data ..."
-    // ".. the amount of data written may be anything from zero bytes to
-    // (inl + cipher_block_size - 1) ..."
-    const size_t cipher_block_size = EVP_CIPHER_block_size(cipher_);
-    size_t padding = cipher_block_size - (size % cipher_block_size);
-    if (padding != cipher_block_size) {
-        size += padding;
-    }
-    return size + sizeof(Header);
+    // https://commondatastorage.googleapis.com/chromium-boringssl-docs/aead.h.html#EVP_AEAD_CTX_seal
+    return size + EVP_AEAD_max_overhead(EVP_AEAD_CTX_aead(context_.get()));
 }
 
-size_t Aes128Gcm::DecryptedSize(const uint8_t* encrypted_data, size_t encrypted_size) {
-    if (encrypted_size < sizeof(Header)) {
-        // Not enough data yet
-        return 0;
-    }
-    auto header = reinterpret_cast<const Header*>(encrypted_data);
-    uint32_t payload = ntohl(header->payload);
-    size_t total_size = payload + sizeof(Header);
-    if (encrypted_size < total_size) {
-        // There's enough data for the header but not enough data for the
-        // payload. Indicate that there's not enough data for now.
-        return 0;
-    }
-    return payload;
+size_t Aes128Gcm::DecryptedSize(size_t size) {
+    // https://commondatastorage.googleapis.com/chromium-boringssl-docs/aead.h.html#EVP_AEAD_CTX_open
+    return size;
 }
 
 }  // namespace pairing
diff --git a/adb/pairing_auth/include/adb/pairing/aes_128_gcm.h b/adb/pairing_auth/include/adb/pairing/aes_128_gcm.h
index 490dd12..6be5856 100644
--- a/adb/pairing_auth/include/adb/pairing/aes_128_gcm.h
+++ b/adb/pairing_auth/include/adb/pairing/aes_128_gcm.h
@@ -16,17 +16,12 @@
 
 #pragma once
 
-#include <openssl/aes.h>
-#include <openssl/cipher.h>
-
 #include <stdint.h>
 
-#include "adb/pairing/counter.h"
+#include <optional>
+#include <vector>
 
-// This is the default size of the initialization vector (iv) for AES-128-GCM
-#define AES_128_GCM_IV_SIZE 12
-// This is the full tag size for AES-128-GCM
-#define AES_128_GCM_TAG_SIZE 16
+#include <openssl/aead.h>
 
 namespace adb {
 namespace pairing {
@@ -42,7 +37,7 @@
     // suitable for decryption with this class.
     // The method returns the number of bytes placed in |out| on success and a
     // negative value if an error occurs.
-    int Encrypt(const uint8_t* in, size_t in_len, uint8_t* out, size_t out_len);
+    std::optional<size_t> Encrypt(const uint8_t* in, size_t in_len, uint8_t* out, size_t out_len);
     // Decrypt a block of data in |in| of length |in_len|, this consumes all data
     // in |in_len| bytes of data. The decrypted output is placed in the |out|
     // buffer of length |out_len|. On successful decryption the number of bytes in
@@ -50,22 +45,18 @@
     // The method returns the number of bytes consumed from the |in| buffer. If
     // there is not enough data available in |in| the method returns zero. If
     // an error occurs the method returns a negative value.
-    int Decrypt(const uint8_t* in, size_t in_len, uint8_t* out, size_t out_len);
+    std::optional<size_t> Decrypt(const uint8_t* in, size_t in_len, uint8_t* out, size_t out_len);
 
     // Return a safe amount of buffer storage needed to encrypt |size| bytes.
     size_t EncryptedSize(size_t size);
-    // Return a safe amount of buffer storage needed to decrypt the encrypted
-    // data in |encrypted_data| which is of length |encrypted_size|. Returns 0 if
-    // there is not enough data available to determine the required size.
-    size_t DecryptedSize(const uint8_t* encrypted_data, size_t encrypted_size);
-
-    static const EVP_CIPHER* cipher_;
+    // Return a safe amount of buffer storage needed to decrypt |size| bytes.
+    size_t DecryptedSize(size_t size);
 
   private:
-    bssl::UniquePtr<EVP_CIPHER_CTX> context_;
-    AES_KEY aes_key_;
-    // We're going to use this counter for our iv so that it never repeats
-    Counter<AES_128_GCM_IV_SIZE> counter_;
+    bssl::ScopedEVP_AEAD_CTX context_;
+    // Sequence numbers to use as nonces in the encryption scheme
+    uint64_t dec_sequence_ = 0;
+    uint64_t enc_sequence_ = 0;
 };
 
 }  // namespace pairing
diff --git a/adb/pairing_auth/include/adb/pairing/counter.h b/adb/pairing_auth/include/adb/pairing/counter.h
deleted file mode 100644
index 263ceb7..0000000
--- a/adb/pairing_auth/include/adb/pairing/counter.h
+++ /dev/null
@@ -1,49 +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 <stddef.h>
-#include <stdint.h>
-
-namespace adb {
-namespace pairing {
-
-template <size_t N>
-class Counter {
-  public:
-    void Increase() {
-        for (size_t i = sizeof(counter_) - 1; i < sizeof(counter_); --i) {
-            if (++counter_[i] != 0) {
-                break;
-            }
-        }
-    }
-
-    uint8_t* data() { return counter_; }
-    const uint8_t* data() const { return counter_; }
-
-    constexpr size_t size() const { return sizeof(counter_); }
-
-    uint8_t& operator[](size_t index) { return counter_[index]; }
-    const uint8_t& operator[](size_t index) const { return counter_[index]; }
-
-  private:
-    uint8_t counter_[N];
-};
-
-}  // namespace pairing
-}  // namespace adb
diff --git a/adb/pairing_auth/pairing_auth.cpp b/adb/pairing_auth/pairing_auth.cpp
index 96bc110..0ac04e6 100644
--- a/adb/pairing_auth/pairing_auth.cpp
+++ b/adb/pairing_auth/pairing_auth.cpp
@@ -75,8 +75,8 @@
     // Returns a safe buffer size for encrypting a buffer of size |len|.
     size_t SafeEncryptedSize(size_t len);
 
-    // Returns a safe buffer size for decrypting a buffer |buf|.
-    size_t SafeDecryptedSize(const Data& buf);
+    // Returns a safe buffer size for decrypting a buffer of size |len|.
+    size_t SafeDecryptedSize(size_t len);
 
   private:
     Data our_msg_;
@@ -167,12 +167,12 @@
 
     // Determine the size for the encrypted data based on the raw data.
     Data encrypted(cipher_->EncryptedSize(data.size()));
-    int bytes = cipher_->Encrypt(data.data(), data.size(), encrypted.data(), encrypted.size());
-    if (bytes < 0) {
+    auto out_size = cipher_->Encrypt(data.data(), data.size(), encrypted.data(), encrypted.size());
+    if (!out_size.has_value() || *out_size == 0) {
         LOG(ERROR) << "Unable to encrypt data";
         return Data();
     }
-    encrypted.resize(bytes);
+    encrypted.resize(*out_size);
 
     return encrypted;
 }
@@ -182,14 +182,14 @@
     CHECK(!data.empty());
 
     // Determine the size for the decrypted data based on the raw data.
-    Data decrypted(cipher_->DecryptedSize(data.data(), data.size()));
+    Data decrypted(cipher_->DecryptedSize(data.size()));
     size_t decrypted_size = decrypted.size();
-    int bytes = cipher_->Decrypt(data.data(), data.size(), decrypted.data(), decrypted_size);
-    if (bytes <= 0) {
+    auto out_size = cipher_->Decrypt(data.data(), data.size(), decrypted.data(), decrypted_size);
+    if (!out_size.has_value() || *out_size == 0) {
         LOG(ERROR) << "Unable to decrypt data";
         return Data();
     }
-    decrypted.resize(bytes);
+    decrypted.resize(*out_size);
 
     return decrypted;
 }
@@ -199,9 +199,9 @@
     return cipher_->EncryptedSize(len);
 }
 
-size_t PairingAuthCtx::SafeDecryptedSize(const PairingAuthCtx::Data& buf) {
+size_t PairingAuthCtx::SafeDecryptedSize(size_t len) {
     CHECK(cipher_);
-    return cipher_->DecryptedSize(buf.data(), buf.size());
+    return cipher_->DecryptedSize(len);
 }
 
 PairingAuthCtx* pairing_auth_server_new(const uint8_t* pswd, size_t len) {
@@ -271,8 +271,8 @@
     CHECK(ctx);
     CHECK(buf);
     CHECK_GT(len, 0U);
-    std::vector<uint8_t> p(buf, buf + len);
-    return ctx->SafeDecryptedSize(p);
+    // We no longer need buf for EVP_AEAD
+    return ctx->SafeDecryptedSize(len);
 }
 
 bool pairing_auth_decrypt(PairingAuthCtx* ctx, const uint8_t* inbuf, size_t inlen, uint8_t* outbuf,
diff --git a/adb/pairing_auth/tests/Android.bp b/adb/pairing_auth/tests/Android.bp
index 292fff5..213123d 100644
--- a/adb/pairing_auth/tests/Android.bp
+++ b/adb/pairing_auth/tests/Android.bp
@@ -18,7 +18,6 @@
     name: "adb_pairing_auth_test",
     srcs: [
         "aes_128_gcm_test.cpp",
-        "counter_test.cpp",
         "pairing_auth_test.cpp",
     ],
 
diff --git a/adb/pairing_auth/tests/aes_128_gcm_test.cpp b/adb/pairing_auth/tests/aes_128_gcm_test.cpp
index e1a20e8..55689d6 100644
--- a/adb/pairing_auth/tests/aes_128_gcm_test.cpp
+++ b/adb/pairing_auth/tests/aes_128_gcm_test.cpp
@@ -39,7 +39,7 @@
     const uint8_t msg[] = "alice and bob, sitting in a binary tree";
     uint8_t material[256];
     uint8_t encrypted[1024];
-    uint8_t out_buf[1024];
+    uint8_t out_buf[1024] = {};
 
     RAND_bytes(material, sizeof(material));
     Aes128Gcm alice(material, sizeof(material));
@@ -47,82 +47,16 @@
     ;
 
     ASSERT_GE(alice.EncryptedSize(sizeof(msg)), sizeof(msg));
-    int encrypted_size = alice.Encrypt(msg, sizeof(msg), encrypted, sizeof(encrypted));
-    ASSERT_GT(encrypted_size, 0);
+    auto encrypted_size = alice.Encrypt(msg, sizeof(msg), encrypted, sizeof(encrypted));
+    ASSERT_TRUE(encrypted_size.has_value());
+    ASSERT_GT(*encrypted_size, 0);
     size_t out_size = sizeof(out_buf);
-    ASSERT_GE(bob.DecryptedSize(encrypted, sizeof(encrypted)), sizeof(msg));
-    int decrypted_size = bob.Decrypt(encrypted, sizeof(encrypted), out_buf, out_size);
-    ASSERT_EQ(sizeof(msg), decrypted_size);
-    memset(out_buf + decrypted_size, 0, sizeof(out_buf) - decrypted_size);
+    ASSERT_GE(bob.DecryptedSize(*encrypted_size), sizeof(msg));
+    auto decrypted_size = bob.Decrypt(encrypted, *encrypted_size, out_buf, out_size);
+    ASSERT_TRUE(decrypted_size.has_value());
+    ASSERT_EQ(sizeof(msg), *decrypted_size);
     ASSERT_STREQ(reinterpret_cast<const char*>(msg), reinterpret_cast<const char*>(out_buf));
 }
 
-TEST(Aes128GcmTest, padding) {
-    // Test with block-align data as well as unaligned data.
-    const size_t cipher_block_size = EVP_CIPHER_block_size(Aes128Gcm::cipher_);
-    uint8_t material[256];
-    RAND_bytes(material, sizeof(material));
-    Aes128Gcm alice(material, sizeof(material));
-    Aes128Gcm bob(material, sizeof(material));
-    ;
-    std::vector<uint8_t> msg;
-    std::vector<uint8_t> encrypted;
-    std::vector<uint8_t> decrypted;
-
-    // Test with aligned data
-    {
-        msg.resize(cipher_block_size);
-        RAND_bytes(msg.data(), msg.size());
-
-        // encrypt
-        size_t safe_encrypted_sz = alice.EncryptedSize(msg.size());
-        ASSERT_GE(safe_encrypted_sz, msg.size());
-        encrypted.resize(safe_encrypted_sz);
-        int encrypted_size =
-                alice.Encrypt(msg.data(), msg.size(), encrypted.data(), encrypted.size());
-        ASSERT_GT(encrypted_size, 0);
-        ASSERT_LE(encrypted_size, safe_encrypted_sz);
-        encrypted.resize(encrypted_size);
-
-        // decrypt
-        size_t safe_decrypted_size = bob.DecryptedSize(encrypted.data(), encrypted.size());
-        ASSERT_GE(safe_decrypted_size, msg.size());
-        decrypted.resize(safe_decrypted_size);
-        int decrypted_size =
-                bob.Decrypt(encrypted.data(), encrypted.size(), decrypted.data(), decrypted.size());
-        ASSERT_GT(decrypted_size, 0);
-        ASSERT_LE(decrypted_size, safe_decrypted_size);
-        ASSERT_EQ(msg.size(), decrypted_size);
-        ASSERT_EQ(memcmp(msg.data(), decrypted.data(), decrypted.size()), 0);
-    }
-
-    // Test with unaligned data
-    {
-        msg.resize(cipher_block_size + 1);
-        RAND_bytes(msg.data(), msg.size());
-
-        // encrypt
-        size_t safe_encrypted_sz = alice.EncryptedSize(msg.size());
-        ASSERT_GE(safe_encrypted_sz, msg.size());
-        encrypted.resize(safe_encrypted_sz);
-        int encrypted_size =
-                alice.Encrypt(msg.data(), msg.size(), encrypted.data(), encrypted.size());
-        ASSERT_GT(encrypted_size, 0);
-        ASSERT_LE(encrypted_size, safe_encrypted_sz);
-        encrypted.resize(encrypted_size);
-
-        // decrypt
-        size_t safe_decrypted_size = bob.DecryptedSize(encrypted.data(), encrypted.size());
-        ASSERT_GE(safe_decrypted_size, msg.size());
-        decrypted.resize(safe_decrypted_size);
-        int decrypted_size =
-                bob.Decrypt(encrypted.data(), encrypted.size(), decrypted.data(), decrypted.size());
-        ASSERT_GT(decrypted_size, 0);
-        ASSERT_LE(decrypted_size, safe_decrypted_size);
-        ASSERT_EQ(msg.size(), decrypted_size);
-        ASSERT_EQ(memcmp(msg.data(), decrypted.data(), decrypted.size()), 0);
-    }
-}
-
 }  // namespace pairing
 }  // namespace adb
diff --git a/adb/pairing_auth/tests/counter_test.cpp b/adb/pairing_auth/tests/counter_test.cpp
deleted file mode 100644
index b338551..0000000
--- a/adb/pairing_auth/tests/counter_test.cpp
+++ /dev/null
@@ -1,70 +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 <adb/pairing/counter.h>
-
-namespace adb {
-namespace pairing {
-
-static constexpr size_t kTestCounterSize = 13;
-static const uint8_t kZeroes[64] = {0};
-
-TEST(AdbCounterTest, size_match) {
-    Counter<kTestCounterSize> counter;
-    ASSERT_EQ(kTestCounterSize, counter.size());
-}
-
-TEST(AdbCounterTest, Increase) {
-    Counter<kTestCounterSize> counter;
-    memset(counter.data(), 0, counter.size());
-    counter.Increase();
-    EXPECT_EQ(1, counter[counter.size() - 1]);
-    EXPECT_EQ(0, memcmp(counter.data(), kZeroes, counter.size() - 1));
-}
-
-TEST(AdbCounterTest, rollover_first_byte) {
-    Counter<kTestCounterSize> counter;
-    memset(counter.data(), 0, counter.size());
-    counter[counter.size() - 1] = 0xFF;
-    counter.Increase();
-    EXPECT_EQ(0, counter[counter.size() - 1]);
-    EXPECT_EQ(1, counter[counter.size() - 2]);
-    EXPECT_EQ(0, memcmp(counter.data(), kZeroes, counter.size() - 2));
-}
-
-TEST(AdbCounterTest, multiple_rollover) {
-    Counter<kTestCounterSize> counter;
-    memset(counter.data(), 0xFF, counter.size());
-    memset(counter.data(), 0, counter.size() - 3);
-    counter.Increase();
-    EXPECT_EQ(0, counter[counter.size() - 5]);
-    EXPECT_EQ(1, counter[counter.size() - 4]);
-    EXPECT_EQ(0, counter[counter.size() - 3]);
-    EXPECT_EQ(0, counter[counter.size() - 2]);
-    EXPECT_EQ(0, counter[counter.size() - 1]);
-}
-
-TEST(AdbCounterTest, full_rollover) {
-    Counter<kTestCounterSize> counter;
-    memset(counter.data(), 0xFF, counter.size());
-    counter.Increase();
-    EXPECT_EQ(0, memcmp(counter.data(), kZeroes, counter.size()));
-}
-
-}  // namespace pairing
-}  // namespace adb
diff --git a/adb/pairing_connection/Android.bp b/adb/pairing_connection/Android.bp
index bcde7b1..9595511 100644
--- a/adb/pairing_connection/Android.bp
+++ b/adb/pairing_connection/Android.bp
@@ -41,6 +41,9 @@
         "//art:__subpackages__",
         "//system/core/adb:__subpackages__",
         "//frameworks/base/services:__subpackages__",
+
+        // This needs to be visible to minadbd, even though it's removed via exclude_shared_libs.
+        "//bootable/recovery/minadbd:__subpackages__",
     ],
     apex_available: [
         "com.android.adbd",
@@ -81,7 +84,7 @@
     static_libs: [
         "libadb_protos",
         // Statically link libadb_tls_connection because it is not
-	// ABI-stable.
+        // ABI-stable.
         "libadb_tls_connection",
         "libprotobuf-cpp-lite",
     ],
@@ -130,7 +133,6 @@
         "//frameworks/base/services:__subpackages__",
     ],
 
-    host_supported: true,
     recovery_available: false,
 
     stl: "libc++_static",
diff --git a/adb/pairing_connection/pairing_connection.cpp b/adb/pairing_connection/pairing_connection.cpp
index a26a6b4..ffe49a9 100644
--- a/adb/pairing_connection/pairing_connection.cpp
+++ b/adb/pairing_connection/pairing_connection.cpp
@@ -278,13 +278,13 @@
     if (fd < 0) {
         return false;
     }
+    fd_.reset(fd);
 
     State expected = State::Ready;
     if (!state_.compare_exchange_strong(expected, State::ExchangingMsgs)) {
         return false;
     }
 
-    fd_.reset(fd);
     cb_ = cb;
     opaque_ = opaque;
 
diff --git a/adb/proto/Android.bp b/adb/proto/Android.bp
index fe828a0..086d10e 100644
--- a/adb/proto/Android.bp
+++ b/adb/proto/Android.bp
@@ -41,6 +41,9 @@
 
     visibility: [
         "//system/core/adb:__subpackages__",
+
+        // This needs to be visible to minadbd, even though it's removed via exclude_shared_libs.
+        "//bootable/recovery/minadbd:__subpackages__",
     ],
 
     stl: "libc++_static",
@@ -92,6 +95,9 @@
 
     visibility: [
         "//system/core/adb:__subpackages__",
+
+        // This needs to be visible to minadbd, even though it's removed via exclude_shared_libs.
+        "//bootable/recovery/minadbd:__subpackages__",
     ],
 
     stl: "libc++_static",
diff --git a/adb/sysdeps.h b/adb/sysdeps.h
index 4efbc02..7326ab1 100644
--- a/adb/sysdeps.h
+++ b/adb/sysdeps.h
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
+#pragma once
+
 /* this file contains system-dependent definitions used by ADB
  * they're related to threads, sockets and file descriptors
  */
-#ifndef _ADB_SYSDEPS_H
-#define _ADB_SYSDEPS_H
 
 #ifdef __CYGWIN__
 #  undef _WIN32
@@ -42,6 +42,12 @@
 #include "sysdeps/network.h"
 #include "sysdeps/stat.h"
 
+#if defined(__APPLE__)
+static inline void* mempcpy(void* dst, const void* src, size_t n) {
+    return static_cast<char*>(memcpy(dst, src, n)) + n;
+}
+#endif
+
 #ifdef _WIN32
 
 // Clang-only nullability specifiers
@@ -70,13 +76,13 @@
 #define OS_PATH_SEPARATOR_STR "\\"
 #define ENV_PATH_SEPARATOR_STR ";"
 
-static __inline__ bool adb_is_separator(char c) {
+static inline bool adb_is_separator(char c) {
     return c == '\\' || c == '/';
 }
 
 extern int adb_thread_setname(const std::string& name);
 
-static __inline__ void close_on_exec(borrowed_fd fd) {
+static inline void close_on_exec(borrowed_fd fd) {
     /* nothing really */
 }
 
@@ -107,7 +113,7 @@
 extern int adb_getlogin_r(char* buf, size_t bufsize);
 
 // See the comments for the !defined(_WIN32) version of unix_close().
-static __inline__ int unix_close(int fd) {
+static inline int unix_close(int fd) {
     return close(fd);
 }
 #undef close
@@ -117,7 +123,7 @@
 extern int unix_read_interruptible(borrowed_fd fd, void* buf, size_t len);
 
 // See the comments for the !defined(_WIN32) version of unix_read().
-static __inline__ int unix_read(borrowed_fd fd, void* buf, size_t len) {
+static inline int unix_read(borrowed_fd fd, void* buf, size_t len) {
     return TEMP_FAILURE_RETRY(unix_read_interruptible(fd, buf, len));
 }
 
@@ -128,7 +134,7 @@
 #define pread ___xxx_pread
 
 // See the comments for the !defined(_WIN32) version of unix_write().
-static __inline__ int unix_write(borrowed_fd fd, const void* buf, size_t len) {
+static inline int unix_write(borrowed_fd fd, const void* buf, size_t len) {
     return write(fd.get(), buf, len);
 }
 #undef   write
@@ -138,14 +144,14 @@
 #define pwrite ___xxx_pwrite
 
 // See the comments for the !defined(_WIN32) version of unix_lseek().
-static __inline__ int unix_lseek(borrowed_fd fd, int pos, int where) {
+static inline int unix_lseek(borrowed_fd fd, int pos, int where) {
     return lseek(fd.get(), pos, where);
 }
 #undef lseek
 #define lseek ___xxx_lseek
 
 // See the comments for the !defined(_WIN32) version of adb_open_mode().
-static __inline__ int adb_open_mode(const char* path, int options, int mode) {
+static inline int adb_open_mode(const char* path, int options, int mode) {
     return adb_open(path, options);
 }
 
@@ -202,7 +208,7 @@
 extern int adb_poll(adb_pollfd* fds, size_t nfds, int timeout);
 #define poll ___xxx_poll
 
-static __inline__ int adb_is_absolute_host_path(const char* path) {
+static inline int adb_is_absolute_host_path(const char* path) {
     return isalpha(path[0]) && path[1] == ':' && path[2] == '\\';
 }
 
@@ -276,6 +282,7 @@
 class Process {
   public:
     constexpr explicit Process(HANDLE h = nullptr) : h_(h) {}
+    constexpr Process(Process&& other) : h_(std::exchange(other.h_, nullptr)) {}
     ~Process() { close(); }
     constexpr explicit operator bool() const { return h_ != nullptr; }
 
@@ -292,6 +299,8 @@
     }
 
   private:
+    DISALLOW_COPY_AND_ASSIGN(Process);
+
     void close() {
         if (*this) {
             ::CloseHandle(h_);
@@ -383,15 +392,15 @@
 #define OS_PATH_SEPARATOR_STR "/"
 #define ENV_PATH_SEPARATOR_STR ":"
 
-static __inline__ bool adb_is_separator(char c) {
+static inline bool adb_is_separator(char c) {
     return c == '/';
 }
 
-static __inline__ int get_fd_flags(borrowed_fd fd) {
+static inline int get_fd_flags(borrowed_fd fd) {
     return fcntl(fd.get(), F_GETFD);
 }
 
-static __inline__ void close_on_exec(borrowed_fd fd) {
+static inline void close_on_exec(borrowed_fd fd) {
     int flags = get_fd_flags(fd);
     if (flags >= 0 && (flags & FD_CLOEXEC) == 0) {
         fcntl(fd.get(), F_SETFD, flags | FD_CLOEXEC);
@@ -407,7 +416,7 @@
 // by unix_read(), unix_write(), unix_close()). Also, the C Runtime has
 // configurable CR/LF translation which defaults to text mode, but is settable
 // with _setmode().
-static __inline__ int unix_open(std::string_view path, int options, ...) {
+static inline int unix_open(std::string_view path, int options, ...) {
     std::string zero_terminated(path.begin(), path.end());
     if ((options & O_CREAT) == 0) {
         return TEMP_FAILURE_RETRY(open(zero_terminated.c_str(), options));
@@ -423,7 +432,7 @@
 
 // Similar to the two-argument adb_open(), but takes a mode parameter for file
 // creation. See adb_open() for more info.
-static __inline__ int adb_open_mode(const char* pathname, int options, int mode) {
+static inline int adb_open_mode(const char* pathname, int options, int mode) {
     return TEMP_FAILURE_RETRY(open(pathname, options, mode));
 }
 
@@ -434,7 +443,7 @@
 // sysdeps_win32.cpp) uses Windows native file I/O and bypasses the C Runtime
 // and its CR/LF translation. The returned file descriptor should be used with
 // adb_read(), adb_write(), adb_close(), etc.
-static __inline__ int adb_open(const char* pathname, int options) {
+static inline int adb_open(const char* pathname, int options) {
     int fd = TEMP_FAILURE_RETRY(open(pathname, options));
     if (fd < 0) return -1;
     close_on_exec(fd);
@@ -443,7 +452,7 @@
 #undef open
 #define open ___xxx_open
 
-static __inline__ int adb_shutdown(borrowed_fd fd, int direction = SHUT_RDWR) {
+static inline int adb_shutdown(borrowed_fd fd, int direction = SHUT_RDWR) {
     return shutdown(fd.get(), direction);
 }
 
@@ -453,7 +462,7 @@
 // Closes a file descriptor that came from adb_open() or adb_open_mode(), but
 // not designed to take a file descriptor from unix_open(). See the comments
 // for adb_open() for more info.
-__inline__ int adb_close(int fd) {
+inline int adb_close(int fd) {
     return close(fd);
 }
 #undef close
@@ -462,23 +471,23 @@
 // On Windows, ADB has an indirection layer for file descriptors. If we get a
 // Win32 SOCKET object from an external library, we have to map it in to that
 // indirection layer, which this does.
-__inline__ int adb_register_socket(int s) {
+inline int adb_register_socket(int s) {
     return s;
 }
 
-static __inline__ int adb_gethostname(char* name, size_t len) {
+static inline int adb_gethostname(char* name, size_t len) {
     return gethostname(name, len);
 }
 
-static __inline__ int adb_getlogin_r(char* buf, size_t bufsize) {
+static inline int adb_getlogin_r(char* buf, size_t bufsize) {
     return getlogin_r(buf, bufsize);
 }
 
-static __inline__ int adb_read(borrowed_fd fd, void* buf, size_t len) {
+static inline int adb_read(borrowed_fd fd, void* buf, size_t len) {
     return TEMP_FAILURE_RETRY(read(fd.get(), buf, len));
 }
 
-static __inline__ int adb_pread(borrowed_fd fd, void* buf, size_t len, off64_t offset) {
+static inline int adb_pread(borrowed_fd fd, void* buf, size_t len, off64_t offset) {
 #if defined(__APPLE__)
     return TEMP_FAILURE_RETRY(pread(fd.get(), buf, len, offset));
 #else
@@ -487,7 +496,7 @@
 }
 
 // Like unix_read(), but does not handle EINTR.
-static __inline__ int unix_read_interruptible(borrowed_fd fd, void* buf, size_t len) {
+static inline int unix_read_interruptible(borrowed_fd fd, void* buf, size_t len) {
     return read(fd.get(), buf, len);
 }
 
@@ -496,11 +505,11 @@
 #undef pread
 #define pread ___xxx_pread
 
-static __inline__ int adb_write(borrowed_fd fd, const void* buf, size_t len) {
+static inline int adb_write(borrowed_fd fd, const void* buf, size_t len) {
     return TEMP_FAILURE_RETRY(write(fd.get(), buf, len));
 }
 
-static __inline__ int adb_pwrite(int fd, const void* buf, size_t len, off64_t offset) {
+static inline int adb_pwrite(int fd, const void* buf, size_t len, off64_t offset) {
 #if defined(__APPLE__)
     return TEMP_FAILURE_RETRY(pwrite(fd, buf, len, offset));
 #else
@@ -513,7 +522,7 @@
 #undef pwrite
 #define pwrite ___xxx_pwrite
 
-static __inline__ int64_t adb_lseek(borrowed_fd fd, int64_t pos, int where) {
+static inline int64_t adb_lseek(borrowed_fd fd, int64_t pos, int where) {
 #if defined(__APPLE__)
     return lseek(fd.get(), pos, where);
 #else
@@ -523,13 +532,13 @@
 #undef lseek
 #define lseek ___xxx_lseek
 
-static __inline__ int adb_unlink(const char* path) {
+static inline int adb_unlink(const char* path) {
     return unlink(path);
 }
 #undef unlink
 #define unlink ___xxx_unlink
 
-static __inline__ int adb_creat(const char* path, int mode) {
+static inline int adb_creat(const char* path, int mode) {
     int fd = TEMP_FAILURE_RETRY(creat(path, mode));
 
     if (fd < 0) return -1;
@@ -540,7 +549,7 @@
 #undef creat
 #define creat ___xxx_creat
 
-static __inline__ int unix_isatty(borrowed_fd fd) {
+static inline int unix_isatty(borrowed_fd fd) {
     return isatty(fd.get());
 }
 #define isatty ___xxx_isatty
@@ -567,8 +576,8 @@
 
 int network_connect(const std::string& host, int port, int type, int timeout, std::string* error);
 
-static __inline__ int adb_socket_accept(borrowed_fd serverfd, struct sockaddr* addr,
-                                        socklen_t* addrlen) {
+static inline int adb_socket_accept(borrowed_fd serverfd, struct sockaddr* addr,
+                                    socklen_t* addrlen) {
     int fd;
 
     fd = TEMP_FAILURE_RETRY(accept(serverfd.get(), addr, addrlen));
@@ -597,7 +606,7 @@
 #define unix_lseek adb_lseek
 #define unix_close adb_close
 
-static __inline__ int adb_thread_setname(const std::string& name) {
+static inline int adb_thread_setname(const std::string& name) {
 #ifdef __APPLE__
     return pthread_setname_np(name.c_str());
 #else
@@ -610,19 +619,19 @@
 #endif
 }
 
-static __inline__ int adb_setsockopt(borrowed_fd fd, int level, int optname, const void* optval,
-                                     socklen_t optlen) {
+static inline int adb_setsockopt(borrowed_fd fd, int level, int optname, const void* optval,
+                                 socklen_t optlen) {
     return setsockopt(fd.get(), level, optname, optval, optlen);
 }
 
 #undef setsockopt
 #define setsockopt ___xxx_setsockopt
 
-static __inline__ int unix_socketpair(int d, int type, int protocol, int sv[2]) {
+static inline int unix_socketpair(int d, int type, int protocol, int sv[2]) {
     return socketpair(d, type, protocol, sv);
 }
 
-static __inline__ int adb_socketpair(int sv[2]) {
+static inline int adb_socketpair(int sv[2]) {
     int rc;
 
     rc = unix_socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
@@ -637,35 +646,41 @@
 #define socketpair ___xxx_socketpair
 
 typedef struct pollfd adb_pollfd;
-static __inline__ int adb_poll(adb_pollfd* fds, size_t nfds, int timeout) {
+static inline int adb_poll(adb_pollfd* fds, size_t nfds, int timeout) {
     return TEMP_FAILURE_RETRY(poll(fds, nfds, timeout));
 }
 
 #define poll ___xxx_poll
 
-static __inline__ int adb_mkdir(const std::string& path, int mode) {
+static inline int adb_mkdir(const std::string& path, int mode) {
     return mkdir(path.c_str(), mode);
 }
 
 #undef mkdir
 #define mkdir ___xxx_mkdir
 
-static __inline__ int adb_rename(const char* oldpath, const char* newpath) {
+static inline int adb_rename(const char* oldpath, const char* newpath) {
     return rename(oldpath, newpath);
 }
 
-static __inline__ int adb_is_absolute_host_path(const char* path) {
+static inline int adb_is_absolute_host_path(const char* path) {
     return path[0] == '/';
 }
 
-static __inline__ int adb_get_os_handle(borrowed_fd fd) {
+static inline int adb_get_os_handle(borrowed_fd fd) {
     return fd.get();
 }
 
+static inline int cast_handle_to_int(int fd) {
+    return fd;
+}
+
 // A very simple wrapper over a launched child process
 class Process {
   public:
     constexpr explicit Process(pid_t pid) : pid_(pid) {}
+    constexpr Process(Process&& other) : pid_(std::exchange(other.pid_, -1)) {}
+
     constexpr explicit operator bool() const { return pid_ >= 0; }
 
     void wait() {
@@ -682,6 +697,8 @@
     }
 
   private:
+    DISALLOW_COPY_AND_ASSIGN(Process);
+
     pid_t pid_;
 };
 
@@ -704,5 +721,3 @@
 // Win32 defines ERROR, which we don't need, but which conflicts with google3 logging.
 #undef ERROR
 #endif
-
-#endif /* _ADB_SYSDEPS_H */
diff --git a/adb/test_device.py b/adb/test_device.py
index 6a9ff89..f5e4cbb 100755
--- a/adb/test_device.py
+++ b/adb/test_device.py
@@ -77,8 +77,7 @@
 
 
 class DeviceTest(unittest.TestCase):
-    def setUp(self):
-        self.device = adb.get_device()
+    device = adb.get_device()
 
 
 class AbbTest(DeviceTest):
@@ -753,535 +752,611 @@
     return files
 
 
-class FileOperationsTest(DeviceTest):
-    SCRATCH_DIR = '/data/local/tmp'
-    DEVICE_TEMP_FILE = SCRATCH_DIR + '/adb_test_file'
-    DEVICE_TEMP_DIR = SCRATCH_DIR + '/adb_test_dir'
+class FileOperationsTest:
+    class Base(DeviceTest):
+        SCRATCH_DIR = '/data/local/tmp'
+        DEVICE_TEMP_FILE = SCRATCH_DIR + '/adb_test_file'
+        DEVICE_TEMP_DIR = SCRATCH_DIR + '/adb_test_dir'
 
-    def _verify_remote(self, checksum, remote_path):
-        dev_md5, _ = self.device.shell([get_md5_prog(self.device),
-                                        remote_path])[0].split()
-        self.assertEqual(checksum, dev_md5)
+        def setUp(self):
+            self.previous_env = os.environ.get("ADB_COMPRESSION")
+            os.environ["ADB_COMPRESSION"] = self.compression
 
-    def _verify_local(self, checksum, local_path):
-        with open(local_path, 'rb') as host_file:
-            host_md5 = compute_md5(host_file.read())
-            self.assertEqual(host_md5, checksum)
+        def tearDown(self):
+            if self.previous_env is None:
+                del os.environ["ADB_COMPRESSION"]
+            else:
+                os.environ["ADB_COMPRESSION"] = self.previous_env
 
-    def test_push(self):
-        """Push a randomly generated file to specified device."""
-        kbytes = 512
-        tmp = tempfile.NamedTemporaryFile(mode='wb', delete=False)
-        rand_str = os.urandom(1024 * kbytes)
-        tmp.write(rand_str)
-        tmp.close()
+        def _verify_remote(self, checksum, remote_path):
+            dev_md5, _ = self.device.shell([get_md5_prog(self.device),
+                                            remote_path])[0].split()
+            self.assertEqual(checksum, dev_md5)
 
-        self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
-        self.device.push(local=tmp.name, remote=self.DEVICE_TEMP_FILE)
+        def _verify_local(self, checksum, local_path):
+            with open(local_path, 'rb') as host_file:
+                host_md5 = compute_md5(host_file.read())
+                self.assertEqual(host_md5, checksum)
 
-        self._verify_remote(compute_md5(rand_str), self.DEVICE_TEMP_FILE)
-        self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
+        def test_push(self):
+            """Push a randomly generated file to specified device."""
+            kbytes = 512
+            tmp = tempfile.NamedTemporaryFile(mode='wb', delete=False)
+            rand_str = os.urandom(1024 * kbytes)
+            tmp.write(rand_str)
+            tmp.close()
 
-        os.remove(tmp.name)
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
+            self.device.push(local=tmp.name, remote=self.DEVICE_TEMP_FILE)
 
-    def test_push_dir(self):
-        """Push a randomly generated directory of files to the device."""
-        self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
+            self._verify_remote(compute_md5(rand_str), self.DEVICE_TEMP_FILE)
+            self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
 
-        try:
-            host_dir = tempfile.mkdtemp()
+            os.remove(tmp.name)
 
-            # Make sure the temp directory isn't setuid, or else adb will complain.
-            os.chmod(host_dir, 0o700)
-
-            # Create 32 random files.
-            temp_files = make_random_host_files(in_dir=host_dir, num_files=32)
-            self.device.push(host_dir, self.DEVICE_TEMP_DIR)
-
-            for temp_file in temp_files:
-                remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
-                                             os.path.basename(host_dir),
-                                             temp_file.base_name)
-                self._verify_remote(temp_file.checksum, remote_path)
+        def test_push_dir(self):
+            """Push a randomly generated directory of files to the device."""
             self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        finally:
-            if host_dir is not None:
-                shutil.rmtree(host_dir)
+            self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
 
-    def disabled_test_push_empty(self):
-        """Push an empty directory to the device."""
-        self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
+            try:
+                host_dir = tempfile.mkdtemp()
 
-        try:
-            host_dir = tempfile.mkdtemp()
+                # Make sure the temp directory isn't setuid, or else adb will complain.
+                os.chmod(host_dir, 0o700)
 
-            # Make sure the temp directory isn't setuid, or else adb will complain.
-            os.chmod(host_dir, 0o700)
+                # Create 32 random files.
+                temp_files = make_random_host_files(in_dir=host_dir, num_files=32)
+                self.device.push(host_dir, self.DEVICE_TEMP_DIR)
 
-            # Create an empty directory.
-            empty_dir_path = os.path.join(host_dir, 'empty')
-            os.mkdir(empty_dir_path);
+                for temp_file in temp_files:
+                    remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
+                                                 os.path.basename(host_dir),
+                                                 temp_file.base_name)
+                    self._verify_remote(temp_file.checksum, remote_path)
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            finally:
+                if host_dir is not None:
+                    shutil.rmtree(host_dir)
 
-            self.device.push(empty_dir_path, self.DEVICE_TEMP_DIR)
-
-            remote_path = os.path.join(self.DEVICE_TEMP_DIR, "empty")
-            test_empty_cmd = ["[", "-d", remote_path, "]"]
-            rc, _, _ = self.device.shell_nocheck(test_empty_cmd)
-
-            self.assertEqual(rc, 0)
+        def disabled_test_push_empty(self):
+            """Push an empty directory to the device."""
             self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        finally:
-            if host_dir is not None:
-                shutil.rmtree(host_dir)
+            self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
 
-    @unittest.skipIf(sys.platform == "win32", "symlinks require elevated privileges on windows")
-    def test_push_symlink(self):
-        """Push a symlink.
+            try:
+                host_dir = tempfile.mkdtemp()
 
-        Bug: http://b/31491920
-        """
-        try:
-            host_dir = tempfile.mkdtemp()
+                # Make sure the temp directory isn't setuid, or else adb will complain.
+                os.chmod(host_dir, 0o700)
 
-            # Make sure the temp directory isn't setuid, or else adb will
-            # complain.
-            os.chmod(host_dir, 0o700)
+                # Create an empty directory.
+                empty_dir_path = os.path.join(host_dir, 'empty')
+                os.mkdir(empty_dir_path);
 
-            with open(os.path.join(host_dir, 'foo'), 'w') as f:
-                f.write('foo')
+                self.device.push(empty_dir_path, self.DEVICE_TEMP_DIR)
 
-            symlink_path = os.path.join(host_dir, 'symlink')
-            os.symlink('foo', symlink_path)
+                remote_path = os.path.join(self.DEVICE_TEMP_DIR, "empty")
+                test_empty_cmd = ["[", "-d", remote_path, "]"]
+                rc, _, _ = self.device.shell_nocheck(test_empty_cmd)
+
+                self.assertEqual(rc, 0)
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            finally:
+                if host_dir is not None:
+                    shutil.rmtree(host_dir)
+
+        @unittest.skipIf(sys.platform == "win32", "symlinks require elevated privileges on windows")
+        def test_push_symlink(self):
+            """Push a symlink.
+
+            Bug: http://b/31491920
+            """
+            try:
+                host_dir = tempfile.mkdtemp()
+
+                # Make sure the temp directory isn't setuid, or else adb will
+                # complain.
+                os.chmod(host_dir, 0o700)
+
+                with open(os.path.join(host_dir, 'foo'), 'w') as f:
+                    f.write('foo')
+
+                symlink_path = os.path.join(host_dir, 'symlink')
+                os.symlink('foo', symlink_path)
+
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
+                self.device.push(symlink_path, self.DEVICE_TEMP_DIR)
+                rc, out, _ = self.device.shell_nocheck(
+                    ['cat', posixpath.join(self.DEVICE_TEMP_DIR, 'symlink')])
+                self.assertEqual(0, rc)
+                self.assertEqual(out.strip(), 'foo')
+            finally:
+                if host_dir is not None:
+                    shutil.rmtree(host_dir)
+
+        def test_multiple_push(self):
+            """Push multiple files to the device in one adb push command.
+
+            Bug: http://b/25324823
+            """
 
             self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
             self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
-            self.device.push(symlink_path, self.DEVICE_TEMP_DIR)
-            rc, out, _ = self.device.shell_nocheck(
-                ['cat', posixpath.join(self.DEVICE_TEMP_DIR, 'symlink')])
-            self.assertEqual(0, rc)
-            self.assertEqual(out.strip(), 'foo')
-        finally:
-            if host_dir is not None:
-                shutil.rmtree(host_dir)
 
-    def test_multiple_push(self):
-        """Push multiple files to the device in one adb push command.
-
-        Bug: http://b/25324823
-        """
-
-        self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        self.device.shell(['mkdir', self.DEVICE_TEMP_DIR])
-
-        try:
-            host_dir = tempfile.mkdtemp()
-
-            # Create some random files and a subdirectory containing more files.
-            temp_files = make_random_host_files(in_dir=host_dir, num_files=4)
-
-            subdir = os.path.join(host_dir, 'subdir')
-            os.mkdir(subdir)
-            subdir_temp_files = make_random_host_files(in_dir=subdir,
-                                                       num_files=4)
-
-            paths = [x.full_path for x in temp_files]
-            paths.append(subdir)
-            self.device._simple_call(['push'] + paths + [self.DEVICE_TEMP_DIR])
-
-            for temp_file in temp_files:
-                remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
-                                             temp_file.base_name)
-                self._verify_remote(temp_file.checksum, remote_path)
-
-            for subdir_temp_file in subdir_temp_files:
-                remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
-                                             # BROKEN: http://b/25394682
-                                             # 'subdir';
-                                             temp_file.base_name)
-                self._verify_remote(temp_file.checksum, remote_path)
-
-
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        finally:
-            if host_dir is not None:
-                shutil.rmtree(host_dir)
-
-    @requires_non_root
-    def test_push_error_reporting(self):
-        """Make sure that errors that occur while pushing a file get reported
-
-        Bug: http://b/26816782
-        """
-        with tempfile.NamedTemporaryFile() as tmp_file:
-            tmp_file.write(b'\0' * 1024 * 1024)
-            tmp_file.flush()
             try:
-                self.device.push(local=tmp_file.name, remote='/system/')
-                self.fail('push should not have succeeded')
+                host_dir = tempfile.mkdtemp()
+
+                # Create some random files and a subdirectory containing more files.
+                temp_files = make_random_host_files(in_dir=host_dir, num_files=4)
+
+                subdir = os.path.join(host_dir, 'subdir')
+                os.mkdir(subdir)
+                subdir_temp_files = make_random_host_files(in_dir=subdir,
+                                                           num_files=4)
+
+                paths = [x.full_path for x in temp_files]
+                paths.append(subdir)
+                self.device._simple_call(['push'] + paths + [self.DEVICE_TEMP_DIR])
+
+                for temp_file in temp_files:
+                    remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
+                                                 temp_file.base_name)
+                    self._verify_remote(temp_file.checksum, remote_path)
+
+                for subdir_temp_file in subdir_temp_files:
+                    remote_path = posixpath.join(self.DEVICE_TEMP_DIR,
+                                                 # BROKEN: http://b/25394682
+                                                 # 'subdir';
+                                                 temp_file.base_name)
+                    self._verify_remote(temp_file.checksum, remote_path)
+
+
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            finally:
+                if host_dir is not None:
+                    shutil.rmtree(host_dir)
+
+        @requires_non_root
+        def test_push_error_reporting(self):
+            """Make sure that errors that occur while pushing a file get reported
+
+            Bug: http://b/26816782
+            """
+            with tempfile.NamedTemporaryFile() as tmp_file:
+                tmp_file.write(b'\0' * 1024 * 1024)
+                tmp_file.flush()
+                try:
+                    self.device.push(local=tmp_file.name, remote='/system/')
+                    self.fail('push should not have succeeded')
+                except subprocess.CalledProcessError as e:
+                    output = e.output
+
+                self.assertTrue(b'Permission denied' in output or
+                                b'Read-only file system' in output)
+
+        @requires_non_root
+        def test_push_directory_creation(self):
+            """Regression test for directory creation.
+
+            Bug: http://b/110953234
+            """
+            with tempfile.NamedTemporaryFile() as tmp_file:
+                tmp_file.write(b'\0' * 1024 * 1024)
+                tmp_file.flush()
+                remote_path = self.DEVICE_TEMP_DIR + '/test_push_directory_creation'
+                self.device.shell(['rm', '-rf', remote_path])
+
+                remote_path += '/filename'
+                self.device.push(local=tmp_file.name, remote=remote_path)
+
+        def disabled_test_push_multiple_slash_root(self):
+            """Regression test for pushing to //data/local/tmp.
+
+            Bug: http://b/141311284
+
+            Disabled because this broken on the adbd side as well: b/141943968
+            """
+            with tempfile.NamedTemporaryFile() as tmp_file:
+                tmp_file.write('\0' * 1024 * 1024)
+                tmp_file.flush()
+                remote_path = '/' + self.DEVICE_TEMP_DIR + '/test_push_multiple_slash_root'
+                self.device.shell(['rm', '-rf', remote_path])
+                self.device.push(local=tmp_file.name, remote=remote_path)
+
+        def _test_pull(self, remote_file, checksum):
+            tmp_write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
+            tmp_write.close()
+            self.device.pull(remote=remote_file, local=tmp_write.name)
+            with open(tmp_write.name, 'rb') as tmp_read:
+                host_contents = tmp_read.read()
+                host_md5 = compute_md5(host_contents)
+            self.assertEqual(checksum, host_md5)
+            os.remove(tmp_write.name)
+
+        @requires_non_root
+        def test_pull_error_reporting(self):
+            self.device.shell(['touch', self.DEVICE_TEMP_FILE])
+            self.device.shell(['chmod', 'a-rwx', self.DEVICE_TEMP_FILE])
+
+            try:
+                output = self.device.pull(remote=self.DEVICE_TEMP_FILE, local='x')
             except subprocess.CalledProcessError as e:
                 output = e.output
 
-            self.assertTrue(b'Permission denied' in output or
-                            b'Read-only file system' in output)
+            self.assertIn(b'Permission denied', output)
 
-    @requires_non_root
-    def test_push_directory_creation(self):
-        """Regression test for directory creation.
+            self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
 
-        Bug: http://b/110953234
-        """
-        with tempfile.NamedTemporaryFile() as tmp_file:
-            tmp_file.write(b'\0' * 1024 * 1024)
-            tmp_file.flush()
-            remote_path = self.DEVICE_TEMP_DIR + '/test_push_directory_creation'
-            self.device.shell(['rm', '-rf', remote_path])
+        def test_pull(self):
+            """Pull a randomly generated file from specified device."""
+            kbytes = 512
+            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
+            cmd = ['dd', 'if=/dev/urandom',
+                   'of={}'.format(self.DEVICE_TEMP_FILE), 'bs=1024',
+                   'count={}'.format(kbytes)]
+            self.device.shell(cmd)
+            dev_md5, _ = self.device.shell(
+                [get_md5_prog(self.device), self.DEVICE_TEMP_FILE])[0].split()
+            self._test_pull(self.DEVICE_TEMP_FILE, dev_md5)
+            self.device.shell_nocheck(['rm', self.DEVICE_TEMP_FILE])
 
-            remote_path += '/filename'
-            self.device.push(local=tmp_file.name, remote=remote_path)
+        def test_pull_dir(self):
+            """Pull a randomly generated directory of files from the device."""
+            try:
+                host_dir = tempfile.mkdtemp()
 
-    def disabled_test_push_multiple_slash_root(self):
-        """Regression test for pushing to //data/local/tmp.
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
 
-        Bug: http://b/141311284
+                # Populate device directory with random files.
+                temp_files = make_random_device_files(
+                    self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
 
-        Disabled because this broken on the adbd side as well: b/141943968
-        """
-        with tempfile.NamedTemporaryFile() as tmp_file:
-            tmp_file.write('\0' * 1024 * 1024)
-            tmp_file.flush()
-            remote_path = '/' + self.DEVICE_TEMP_DIR + '/test_push_multiple_slash_root'
-            self.device.shell(['rm', '-rf', remote_path])
-            self.device.push(local=tmp_file.name, remote=remote_path)
+                self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
 
-    def _test_pull(self, remote_file, checksum):
-        tmp_write = tempfile.NamedTemporaryFile(mode='wb', delete=False)
-        tmp_write.close()
-        self.device.pull(remote=remote_file, local=tmp_write.name)
-        with open(tmp_write.name, 'rb') as tmp_read:
-            host_contents = tmp_read.read()
-            host_md5 = compute_md5(host_contents)
-        self.assertEqual(checksum, host_md5)
-        os.remove(tmp_write.name)
+                for temp_file in temp_files:
+                    host_path = os.path.join(
+                        host_dir, posixpath.basename(self.DEVICE_TEMP_DIR),
+                        temp_file.base_name)
+                    self._verify_local(temp_file.checksum, host_path)
 
-    @requires_non_root
-    def test_pull_error_reporting(self):
-        self.device.shell(['touch', self.DEVICE_TEMP_FILE])
-        self.device.shell(['chmod', 'a-rwx', self.DEVICE_TEMP_FILE])
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            finally:
+                if host_dir is not None:
+                    shutil.rmtree(host_dir)
 
-        try:
-            output = self.device.pull(remote=self.DEVICE_TEMP_FILE, local='x')
-        except subprocess.CalledProcessError as e:
-            output = e.output
+        def test_pull_dir_symlink(self):
+            """Pull a directory into a symlink to a directory.
 
-        self.assertIn(b'Permission denied', output)
+            Bug: http://b/27362811
+            """
+            if os.name != 'posix':
+                raise unittest.SkipTest('requires POSIX')
 
-        self.device.shell(['rm', '-f', self.DEVICE_TEMP_FILE])
+            try:
+                host_dir = tempfile.mkdtemp()
+                real_dir = os.path.join(host_dir, 'dir')
+                symlink = os.path.join(host_dir, 'symlink')
+                os.mkdir(real_dir)
+                os.symlink(real_dir, symlink)
 
-    def test_pull(self):
-        """Pull a randomly generated file from specified device."""
-        kbytes = 512
-        self.device.shell(['rm', '-rf', self.DEVICE_TEMP_FILE])
-        cmd = ['dd', 'if=/dev/urandom',
-               'of={}'.format(self.DEVICE_TEMP_FILE), 'bs=1024',
-               'count={}'.format(kbytes)]
-        self.device.shell(cmd)
-        dev_md5, _ = self.device.shell(
-            [get_md5_prog(self.device), self.DEVICE_TEMP_FILE])[0].split()
-        self._test_pull(self.DEVICE_TEMP_FILE, dev_md5)
-        self.device.shell_nocheck(['rm', self.DEVICE_TEMP_FILE])
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
 
-    def test_pull_dir(self):
-        """Pull a randomly generated directory of files from the device."""
-        try:
-            host_dir = tempfile.mkdtemp()
+                # Populate device directory with random files.
+                temp_files = make_random_device_files(
+                    self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
 
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-            self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
+                self.device.pull(remote=self.DEVICE_TEMP_DIR, local=symlink)
 
-            # Populate device directory with random files.
-            temp_files = make_random_device_files(
-                self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
+                for temp_file in temp_files:
+                    host_path = os.path.join(
+                        real_dir, posixpath.basename(self.DEVICE_TEMP_DIR),
+                        temp_file.base_name)
+                    self._verify_local(temp_file.checksum, host_path)
 
-            self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            finally:
+                if host_dir is not None:
+                    shutil.rmtree(host_dir)
 
+        def test_pull_dir_symlink_collision(self):
+            """Pull a directory into a colliding symlink to directory."""
+            if os.name != 'posix':
+                raise unittest.SkipTest('requires POSIX')
+
+            try:
+                host_dir = tempfile.mkdtemp()
+                real_dir = os.path.join(host_dir, 'real')
+                tmp_dirname = os.path.basename(self.DEVICE_TEMP_DIR)
+                symlink = os.path.join(host_dir, tmp_dirname)
+                os.mkdir(real_dir)
+                os.symlink(real_dir, symlink)
+
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
+
+                # Populate device directory with random files.
+                temp_files = make_random_device_files(
+                    self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
+
+                self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
+
+                for temp_file in temp_files:
+                    host_path = os.path.join(real_dir, temp_file.base_name)
+                    self._verify_local(temp_file.checksum, host_path)
+
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            finally:
+                if host_dir is not None:
+                    shutil.rmtree(host_dir)
+
+        def test_pull_dir_nonexistent(self):
+            """Pull a directory of files from the device to a nonexistent path."""
+            try:
+                host_dir = tempfile.mkdtemp()
+                dest_dir = os.path.join(host_dir, 'dest')
+
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
+
+                # Populate device directory with random files.
+                temp_files = make_random_device_files(
+                    self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
+
+                self.device.pull(remote=self.DEVICE_TEMP_DIR, local=dest_dir)
+
+                for temp_file in temp_files:
+                    host_path = os.path.join(dest_dir, temp_file.base_name)
+                    self._verify_local(temp_file.checksum, host_path)
+
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            finally:
+                if host_dir is not None:
+                    shutil.rmtree(host_dir)
+
+        # selinux prevents adbd from accessing symlinks on /data/local/tmp.
+        def disabled_test_pull_symlink_dir(self):
+            """Pull a symlink to a directory of symlinks to files."""
+            try:
+                host_dir = tempfile.mkdtemp()
+
+                remote_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'contents')
+                remote_links = posixpath.join(self.DEVICE_TEMP_DIR, 'links')
+                remote_symlink = posixpath.join(self.DEVICE_TEMP_DIR, 'symlink')
+
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                self.device.shell(['mkdir', '-p', remote_dir, remote_links])
+                self.device.shell(['ln', '-s', remote_links, remote_symlink])
+
+                # Populate device directory with random files.
+                temp_files = make_random_device_files(
+                    self.device, in_dir=remote_dir, num_files=32)
+
+                for temp_file in temp_files:
+                    self.device.shell(
+                        ['ln', '-s', '../contents/{}'.format(temp_file.base_name),
+                         posixpath.join(remote_links, temp_file.base_name)])
+
+                self.device.pull(remote=remote_symlink, local=host_dir)
+
+                for temp_file in temp_files:
+                    host_path = os.path.join(
+                        host_dir, 'symlink', temp_file.base_name)
+                    self._verify_local(temp_file.checksum, host_path)
+
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            finally:
+                if host_dir is not None:
+                    shutil.rmtree(host_dir)
+
+        def test_pull_empty(self):
+            """Pull a directory containing an empty directory from the device."""
+            try:
+                host_dir = tempfile.mkdtemp()
+
+                remote_empty_path = posixpath.join(self.DEVICE_TEMP_DIR, 'empty')
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                self.device.shell(['mkdir', '-p', remote_empty_path])
+
+                self.device.pull(remote=remote_empty_path, local=host_dir)
+                self.assertTrue(os.path.isdir(os.path.join(host_dir, 'empty')))
+            finally:
+                if host_dir is not None:
+                    shutil.rmtree(host_dir)
+
+        def test_multiple_pull(self):
+            """Pull a randomly generated directory of files from the device."""
+
+            try:
+                host_dir = tempfile.mkdtemp()
+
+                subdir = posixpath.join(self.DEVICE_TEMP_DIR, 'subdir')
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                self.device.shell(['mkdir', '-p', subdir])
+
+                # Create some random files and a subdirectory containing more files.
+                temp_files = make_random_device_files(
+                    self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=4)
+
+                subdir_temp_files = make_random_device_files(
+                    self.device, in_dir=subdir, num_files=4, prefix='subdir_')
+
+                paths = [x.full_path for x in temp_files]
+                paths.append(subdir)
+                self.device._simple_call(['pull'] + paths + [host_dir])
+
+                for temp_file in temp_files:
+                    local_path = os.path.join(host_dir, temp_file.base_name)
+                    self._verify_local(temp_file.checksum, local_path)
+
+                for subdir_temp_file in subdir_temp_files:
+                    local_path = os.path.join(host_dir,
+                                              'subdir',
+                                              subdir_temp_file.base_name)
+                    self._verify_local(subdir_temp_file.checksum, local_path)
+
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            finally:
+                if host_dir is not None:
+                    shutil.rmtree(host_dir)
+
+        def verify_sync(self, device, temp_files, device_dir):
+            """Verifies that a list of temp files was synced to the device."""
+            # Confirm that every file on the device mirrors that on the host.
             for temp_file in temp_files:
-                host_path = os.path.join(
-                    host_dir, posixpath.basename(self.DEVICE_TEMP_DIR),
-                    temp_file.base_name)
-                self._verify_local(temp_file.checksum, host_path)
+                device_full_path = posixpath.join(
+                    device_dir, temp_file.base_name)
+                dev_md5, _ = device.shell(
+                    [get_md5_prog(self.device), device_full_path])[0].split()
+                self.assertEqual(temp_file.checksum, dev_md5)
 
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        finally:
-            if host_dir is not None:
-                shutil.rmtree(host_dir)
+        def test_sync(self):
+            """Sync a host directory to the data partition."""
 
-    def test_pull_dir_symlink(self):
-        """Pull a directory into a symlink to a directory.
+            try:
+                base_dir = tempfile.mkdtemp()
 
-        Bug: http://b/27362811
-        """
-        if os.name != 'posix':
-            raise unittest.SkipTest('requires POSIX')
+                # Create mirror device directory hierarchy within base_dir.
+                full_dir_path = base_dir + self.DEVICE_TEMP_DIR
+                os.makedirs(full_dir_path)
 
-        try:
-            host_dir = tempfile.mkdtemp()
-            real_dir = os.path.join(host_dir, 'dir')
-            symlink = os.path.join(host_dir, 'symlink')
-            os.mkdir(real_dir)
-            os.symlink(real_dir, symlink)
+                # Create 32 random files within the host mirror.
+                temp_files = make_random_host_files(
+                    in_dir=full_dir_path, num_files=32)
 
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-            self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
+                # Clean up any stale files on the device.
+                device = adb.get_device()  # pylint: disable=no-member
+                device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
 
-            # Populate device directory with random files.
-            temp_files = make_random_device_files(
-                self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
+                old_product_out = os.environ.get('ANDROID_PRODUCT_OUT')
+                os.environ['ANDROID_PRODUCT_OUT'] = base_dir
+                device.sync('data')
+                if old_product_out is None:
+                    del os.environ['ANDROID_PRODUCT_OUT']
+                else:
+                    os.environ['ANDROID_PRODUCT_OUT'] = old_product_out
 
-            self.device.pull(remote=self.DEVICE_TEMP_DIR, local=symlink)
+                self.verify_sync(device, temp_files, self.DEVICE_TEMP_DIR)
 
-            for temp_file in temp_files:
-                host_path = os.path.join(
-                    real_dir, posixpath.basename(self.DEVICE_TEMP_DIR),
-                    temp_file.base_name)
-                self._verify_local(temp_file.checksum, host_path)
+                #self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            finally:
+                if base_dir is not None:
+                    shutil.rmtree(base_dir)
 
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        finally:
-            if host_dir is not None:
-                shutil.rmtree(host_dir)
+        def test_push_sync(self):
+            """Sync a host directory to a specific path."""
 
-    def test_pull_dir_symlink_collision(self):
-        """Pull a directory into a colliding symlink to directory."""
-        if os.name != 'posix':
-            raise unittest.SkipTest('requires POSIX')
+            try:
+                temp_dir = tempfile.mkdtemp()
+                temp_files = make_random_host_files(in_dir=temp_dir, num_files=32)
 
-        try:
-            host_dir = tempfile.mkdtemp()
-            real_dir = os.path.join(host_dir, 'real')
-            tmp_dirname = os.path.basename(self.DEVICE_TEMP_DIR)
-            symlink = os.path.join(host_dir, tmp_dirname)
-            os.mkdir(real_dir)
-            os.symlink(real_dir, symlink)
+                device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'sync_src_dst')
 
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-            self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
+                # Clean up any stale files on the device.
+                device = adb.get_device()  # pylint: disable=no-member
+                device.shell(['rm', '-rf', device_dir])
 
-            # Populate device directory with random files.
-            temp_files = make_random_device_files(
-                self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
+                device.push(temp_dir, device_dir, sync=True)
 
-            self.device.pull(remote=self.DEVICE_TEMP_DIR, local=host_dir)
+                self.verify_sync(device, temp_files, device_dir)
 
-            for temp_file in temp_files:
-                host_path = os.path.join(real_dir, temp_file.base_name)
-                self._verify_local(temp_file.checksum, host_path)
+                self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+            finally:
+                if temp_dir is not None:
+                    shutil.rmtree(temp_dir)
 
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        finally:
-            if host_dir is not None:
-                shutil.rmtree(host_dir)
+        def test_push_dry_run_nonexistent_file(self):
+            """Push with dry run."""
 
-    def test_pull_dir_nonexistent(self):
-        """Pull a directory of files from the device to a nonexistent path."""
-        try:
-            host_dir = tempfile.mkdtemp()
-            dest_dir = os.path.join(host_dir, 'dest')
+            for file_size in [8, 1024 * 1024]:
+                try:
+                    device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'push_dry_run')
+                    device_file = posixpath.join(device_dir, 'file')
 
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-            self.device.shell(['mkdir', '-p', self.DEVICE_TEMP_DIR])
+                    self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                    self.device.shell(['mkdir', '-p', device_dir])
 
-            # Populate device directory with random files.
-            temp_files = make_random_device_files(
-                self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=32)
+                    host_dir = tempfile.mkdtemp()
+                    host_file = posixpath.join(host_dir, 'file')
 
-            self.device.pull(remote=self.DEVICE_TEMP_DIR, local=dest_dir)
+                    with open(host_file, "w") as f:
+                        f.write('x' * file_size)
 
-            for temp_file in temp_files:
-                host_path = os.path.join(dest_dir, temp_file.base_name)
-                self._verify_local(temp_file.checksum, host_path)
+                    self.device._simple_call(['push', '-n', host_file, device_file])
+                    rc, _, _ = self.device.shell_nocheck(['[', '-e', device_file, ']'])
+                    self.assertNotEqual(0, rc)
 
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        finally:
-            if host_dir is not None:
-                shutil.rmtree(host_dir)
+                    self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                finally:
+                    if host_dir is not None:
+                        shutil.rmtree(host_dir)
 
-    # selinux prevents adbd from accessing symlinks on /data/local/tmp.
-    def disabled_test_pull_symlink_dir(self):
-        """Pull a symlink to a directory of symlinks to files."""
-        try:
-            host_dir = tempfile.mkdtemp()
+        def test_push_dry_run_existent_file(self):
+            """Push with dry run."""
 
-            remote_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'contents')
-            remote_links = posixpath.join(self.DEVICE_TEMP_DIR, 'links')
-            remote_symlink = posixpath.join(self.DEVICE_TEMP_DIR, 'symlink')
+            for file_size in [8, 1024 * 1024]:
+                try:
+                    device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'push_dry_run')
+                    device_file = posixpath.join(device_dir, 'file')
 
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-            self.device.shell(['mkdir', '-p', remote_dir, remote_links])
-            self.device.shell(['ln', '-s', remote_links, remote_symlink])
+                    self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                    self.device.shell(['mkdir', '-p', device_dir])
+                    self.device.shell(['echo', 'foo', '>', device_file])
 
-            # Populate device directory with random files.
-            temp_files = make_random_device_files(
-                self.device, in_dir=remote_dir, num_files=32)
+                    host_dir = tempfile.mkdtemp()
+                    host_file = posixpath.join(host_dir, 'file')
 
-            for temp_file in temp_files:
-                self.device.shell(
-                    ['ln', '-s', '../contents/{}'.format(temp_file.base_name),
-                     posixpath.join(remote_links, temp_file.base_name)])
+                    with open(host_file, "w") as f:
+                        f.write('x' * file_size)
 
-            self.device.pull(remote=remote_symlink, local=host_dir)
+                    self.device._simple_call(['push', '-n', host_file, device_file])
+                    stdout, stderr = self.device.shell(['cat', device_file])
+                    self.assertEqual(stdout.strip(), "foo")
 
-            for temp_file in temp_files:
-                host_path = os.path.join(
-                    host_dir, 'symlink', temp_file.base_name)
-                self._verify_local(temp_file.checksum, host_path)
+                    self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
+                finally:
+                    if host_dir is not None:
+                        shutil.rmtree(host_dir)
 
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        finally:
-            if host_dir is not None:
-                shutil.rmtree(host_dir)
+        def test_unicode_paths(self):
+            """Ensure that we can support non-ASCII paths, even on Windows."""
+            name = u'로보카 폴리'
 
-    def test_pull_empty(self):
-        """Pull a directory containing an empty directory from the device."""
-        try:
-            host_dir = tempfile.mkdtemp()
+            self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
+            remote_path = u'/data/local/tmp/adb-test-{}'.format(name)
 
-            remote_empty_path = posixpath.join(self.DEVICE_TEMP_DIR, 'empty')
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-            self.device.shell(['mkdir', '-p', remote_empty_path])
+            ## push.
+            tf = tempfile.NamedTemporaryFile('wb', suffix=name, delete=False)
+            tf.close()
+            self.device.push(tf.name, remote_path)
+            os.remove(tf.name)
+            self.assertFalse(os.path.exists(tf.name))
 
-            self.device.pull(remote=remote_empty_path, local=host_dir)
-            self.assertTrue(os.path.isdir(os.path.join(host_dir, 'empty')))
-        finally:
-            if host_dir is not None:
-                shutil.rmtree(host_dir)
+            # Verify that the device ended up with the expected UTF-8 path
+            output = self.device.shell(
+                    ['ls', '/data/local/tmp/adb-test-*'])[0].strip()
+            self.assertEqual(remote_path, output)
 
-    def test_multiple_pull(self):
-        """Pull a randomly generated directory of files from the device."""
+            # pull.
+            self.device.pull(remote_path, tf.name)
+            self.assertTrue(os.path.exists(tf.name))
+            os.remove(tf.name)
+            self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
 
-        try:
-            host_dir = tempfile.mkdtemp()
 
-            subdir = posixpath.join(self.DEVICE_TEMP_DIR, 'subdir')
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-            self.device.shell(['mkdir', '-p', subdir])
+class FileOperationsTestUncompressed(FileOperationsTest.Base):
+    compression = "none"
 
-            # Create some random files and a subdirectory containing more files.
-            temp_files = make_random_device_files(
-                self.device, in_dir=self.DEVICE_TEMP_DIR, num_files=4)
 
-            subdir_temp_files = make_random_device_files(
-                self.device, in_dir=subdir, num_files=4, prefix='subdir_')
+class FileOperationsTestBrotli(FileOperationsTest.Base):
+    compression = "brotli"
 
-            paths = [x.full_path for x in temp_files]
-            paths.append(subdir)
-            self.device._simple_call(['pull'] + paths + [host_dir])
 
-            for temp_file in temp_files:
-                local_path = os.path.join(host_dir, temp_file.base_name)
-                self._verify_local(temp_file.checksum, local_path)
-
-            for subdir_temp_file in subdir_temp_files:
-                local_path = os.path.join(host_dir,
-                                          'subdir',
-                                          subdir_temp_file.base_name)
-                self._verify_local(subdir_temp_file.checksum, local_path)
-
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        finally:
-            if host_dir is not None:
-                shutil.rmtree(host_dir)
-
-    def verify_sync(self, device, temp_files, device_dir):
-        """Verifies that a list of temp files was synced to the device."""
-        # Confirm that every file on the device mirrors that on the host.
-        for temp_file in temp_files:
-            device_full_path = posixpath.join(
-                device_dir, temp_file.base_name)
-            dev_md5, _ = device.shell(
-                [get_md5_prog(self.device), device_full_path])[0].split()
-            self.assertEqual(temp_file.checksum, dev_md5)
-
-    def test_sync(self):
-        """Sync a host directory to the data partition."""
-
-        try:
-            base_dir = tempfile.mkdtemp()
-
-            # Create mirror device directory hierarchy within base_dir.
-            full_dir_path = base_dir + self.DEVICE_TEMP_DIR
-            os.makedirs(full_dir_path)
-
-            # Create 32 random files within the host mirror.
-            temp_files = make_random_host_files(
-                in_dir=full_dir_path, num_files=32)
-
-            # Clean up any stale files on the device.
-            device = adb.get_device()  # pylint: disable=no-member
-            device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-
-            old_product_out = os.environ.get('ANDROID_PRODUCT_OUT')
-            os.environ['ANDROID_PRODUCT_OUT'] = base_dir
-            device.sync('data')
-            if old_product_out is None:
-                del os.environ['ANDROID_PRODUCT_OUT']
-            else:
-                os.environ['ANDROID_PRODUCT_OUT'] = old_product_out
-
-            self.verify_sync(device, temp_files, self.DEVICE_TEMP_DIR)
-
-            #self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        finally:
-            if base_dir is not None:
-                shutil.rmtree(base_dir)
-
-    def test_push_sync(self):
-        """Sync a host directory to a specific path."""
-
-        try:
-            temp_dir = tempfile.mkdtemp()
-            temp_files = make_random_host_files(in_dir=temp_dir, num_files=32)
-
-            device_dir = posixpath.join(self.DEVICE_TEMP_DIR, 'sync_src_dst')
-
-            # Clean up any stale files on the device.
-            device = adb.get_device()  # pylint: disable=no-member
-            device.shell(['rm', '-rf', device_dir])
-
-            device.push(temp_dir, device_dir, sync=True)
-
-            self.verify_sync(device, temp_files, device_dir)
-
-            self.device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR])
-        finally:
-            if temp_dir is not None:
-                shutil.rmtree(temp_dir)
-
-    def test_unicode_paths(self):
-        """Ensure that we can support non-ASCII paths, even on Windows."""
-        name = u'로보카 폴리'
-
-        self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
-        remote_path = u'/data/local/tmp/adb-test-{}'.format(name)
-
-        ## push.
-        tf = tempfile.NamedTemporaryFile('wb', suffix=name, delete=False)
-        tf.close()
-        self.device.push(tf.name, remote_path)
-        os.remove(tf.name)
-        self.assertFalse(os.path.exists(tf.name))
-
-        # Verify that the device ended up with the expected UTF-8 path
-        output = self.device.shell(
-                ['ls', '/data/local/tmp/adb-test-*'])[0].strip()
-        self.assertEqual(remote_path, output)
-
-        # pull.
-        self.device.pull(remote_path, tf.name)
-        self.assertTrue(os.path.exists(tf.name))
-        os.remove(tf.name)
-        self.device.shell(['rm', '-f', '/data/local/tmp/adb-test-*'])
+class FileOperationsTestLZ4(FileOperationsTest.Base):
+    compression = "lz4"
 
 
 class DeviceOfflineTest(DeviceTest):
diff --git a/adb/tls/Android.bp b/adb/tls/Android.bp
index f2837e1..e5204f3 100644
--- a/adb/tls/Android.bp
+++ b/adb/tls/Android.bp
@@ -39,6 +39,7 @@
     recovery_available: true,
 
     visibility: [
+        "//bootable/recovery/minadbd:__subpackages__",
         "//system/core/adb:__subpackages__",
     ],
 
diff --git a/adb/transport.cpp b/adb/transport.cpp
index 447a8fe..25ed366 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -29,7 +29,6 @@
 #include <unistd.h>
 
 #include <algorithm>
-#include <deque>
 #include <list>
 #include <memory>
 #include <mutex>
@@ -40,6 +39,7 @@
 #include <adb/crypto/x509_generator.h>
 #include <adb/tls/tls_connection.h>
 #include <android-base/logging.h>
+#include <android-base/no_destructor.h>
 #include <android-base/parsenetaddress.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
@@ -82,6 +82,10 @@
 const char* const kFeatureAbbExec = "abb_exec";
 const char* const kFeatureRemountShell = "remount_shell";
 const char* const kFeatureTrackApp = "track_app";
+const char* const kFeatureSendRecv2 = "sendrecv_v2";
+const char* const kFeatureSendRecv2Brotli = "sendrecv_v2_brotli";
+const char* const kFeatureSendRecv2LZ4 = "sendrecv_v2_lz4";
+const char* const kFeatureSendRecv2DryRunSend = "sendrecv_v2_dry_run_send";
 
 namespace {
 
@@ -498,12 +502,13 @@
     auto x509 = GenerateX509Certificate(evp_pkey.get());
     auto x509_str = X509ToPEMString(x509.get());
     auto evp_str = Key::ToPEMString(evp_pkey.get());
+
+    int osh = cast_handle_to_int(adb_get_os_handle(fd_));
 #if ADB_HOST
-    tls_ = TlsConnection::Create(TlsConnection::Role::Client,
+    tls_ = TlsConnection::Create(TlsConnection::Role::Client, x509_str, evp_str, osh);
 #else
-    tls_ = TlsConnection::Create(TlsConnection::Role::Server,
+    tls_ = TlsConnection::Create(TlsConnection::Role::Server, x509_str, evp_str, osh);
 #endif
-                                 x509_str, evp_str, fd_);
     CHECK(tls_);
 #if ADB_HOST
     // TLS 1.3 gives the client no message if the server rejected the
@@ -1164,24 +1169,29 @@
 }
 
 const FeatureSet& supported_features() {
-    // Local static allocation to avoid global non-POD variables.
-    static const FeatureSet* features = new FeatureSet{
-            kFeatureShell2,
-            kFeatureCmd,
-            kFeatureStat2,
-            kFeatureLs2,
-            kFeatureFixedPushMkdir,
-            kFeatureApex,
-            kFeatureAbb,
-            kFeatureFixedPushSymlinkTimestamp,
-            kFeatureAbbExec,
-            kFeatureRemountShell,
-            kFeatureTrackApp,
-            // Increment ADB_SERVER_VERSION when adding a feature that adbd needs
-            // to know about. Otherwise, the client can be stuck running an old
-            // version of the server even after upgrading their copy of adb.
-            // (http://b/24370690)
-    };
+    static const android::base::NoDestructor<FeatureSet> features([] {
+        return FeatureSet{
+                kFeatureShell2,
+                kFeatureCmd,
+                kFeatureStat2,
+                kFeatureLs2,
+                kFeatureFixedPushMkdir,
+                kFeatureApex,
+                kFeatureAbb,
+                kFeatureFixedPushSymlinkTimestamp,
+                kFeatureAbbExec,
+                kFeatureRemountShell,
+                kFeatureTrackApp,
+                kFeatureSendRecv2,
+                kFeatureSendRecv2Brotli,
+                kFeatureSendRecv2LZ4,
+                kFeatureSendRecv2DryRunSend,
+                // Increment ADB_SERVER_VERSION when adding a feature that adbd needs
+                // to know about. Otherwise, the client can be stuck running an old
+                // version of the server even after upgrading their copy of adb.
+                // (http://b/24370690)
+        };
+    }());
 
     return *features;
 }
@@ -1195,16 +1205,20 @@
         return FeatureSet();
     }
 
-    auto names = android::base::Split(features_string, ",");
-    return FeatureSet(names.begin(), names.end());
+    return android::base::Split(features_string, ",");
+}
+
+template <class Range, class Value>
+static bool contains(const Range& r, const Value& v) {
+    return std::find(std::begin(r), std::end(r), v) != std::end(r);
 }
 
 bool CanUseFeature(const FeatureSet& feature_set, const std::string& feature) {
-    return feature_set.count(feature) > 0 && supported_features().count(feature) > 0;
+    return contains(feature_set, feature) && contains(supported_features(), feature);
 }
 
 bool atransport::has_feature(const std::string& feature) const {
-    return features_.count(feature) > 0;
+    return contains(features_, feature);
 }
 
 void atransport::SetFeatures(const std::string& features_string) {
@@ -1446,6 +1460,7 @@
 
 #endif
 
+#if ADB_HOST
 void register_usb_transport(usb_handle* usb, const char* serial, const char* devpath,
                             unsigned writeable) {
     atransport* t = new atransport(writeable ? kCsOffline : kCsNoPerm);
@@ -1467,6 +1482,7 @@
 
     register_transport(t);
 }
+#endif
 
 #if ADB_HOST
 // This should only be used for transports with connection_state == kCsNoPerm.
diff --git a/adb/transport.h b/adb/transport.h
index a62349e..e93c31c 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -30,7 +30,7 @@
 #include <string>
 #include <string_view>
 #include <thread>
-#include <unordered_set>
+#include <vector>
 
 #include <android-base/macros.h>
 #include <android-base/thread_annotations.h>
@@ -39,9 +39,11 @@
 #include "adb.h"
 #include "adb_unique_fd.h"
 #include "types.h"
-#include "usb.h"
 
-typedef std::unordered_set<std::string> FeatureSet;
+// Even though the feature set is used as a set, we only have a dozen or two
+// of available features at any moment. Vector works much better in terms of
+// both memory usage and performance for these sizes.
+using FeatureSet = std::vector<std::string>;
 
 namespace adb {
 namespace tls {
@@ -81,9 +83,18 @@
 extern const char* const kFeatureAbbExec;
 // adbd properly updates symlink timestamps on push.
 extern const char* const kFeatureFixedPushSymlinkTimestamp;
+// Implement `adb remount` via shelling out to /system/bin/remount.
 extern const char* const kFeatureRemountShell;
 // adbd supports `track-app` service reporting debuggable/profileable apps.
 extern const char* const kFeatureTrackApp;
+// adbd supports version 2 of send/recv.
+extern const char* const kFeatureSendRecv2;
+// adbd supports brotli for send/recv v2.
+extern const char* const kFeatureSendRecv2Brotli;
+// adbd supports LZ4 for send/recv v2.
+extern const char* const kFeatureSendRecv2LZ4;
+// adbd supports dry-run send for send/recv v2.
+extern const char* const kFeatureSendRecv2DryRunSend;
 
 TransportId NextTransportId();
 
@@ -199,20 +210,6 @@
     std::unique_ptr<adb::tls::TlsConnection> tls_;
 };
 
-struct UsbConnection : public BlockingConnection {
-    explicit UsbConnection(usb_handle* handle) : handle_(handle) {}
-    ~UsbConnection();
-
-    bool Read(apacket* packet) override final;
-    bool Write(apacket* packet) override final;
-    bool DoTlsHandshake(RSA* key, std::string* auth_key) override final;
-
-    void Close() override final;
-    virtual void Reset() override final;
-
-    usb_handle* handle_;
-};
-
 // Waits for a transport's connection to be not pending. This is a separate
 // object so that the transport can be destroyed and another thread can be
 // notified of it in a race-free way.
@@ -248,6 +245,10 @@
     Abort,
 };
 
+#if ADB_HOST
+struct usb_handle;
+#endif
+
 class atransport : public enable_weak_from_this<atransport> {
   public:
     // TODO(danalbert): We expose waaaaaaay too much stuff because this was
@@ -288,8 +289,10 @@
         return connection_;
     }
 
+#if ADB_HOST
     void SetUsbHandle(usb_handle* h) { usb_handle_ = h; }
     usb_handle* GetUsbHandle() { return usb_handle_; }
+#endif
 
     const TransportId id;
 
@@ -396,8 +399,10 @@
     // The underlying connection object.
     std::shared_ptr<Connection> connection_ GUARDED_BY(mutex_);
 
+#if ADB_HOST
     // USB handle for the connection, if available.
     usb_handle* usb_handle_ = nullptr;
+#endif
 
     // A callback that will be invoked when the atransport needs to reconnect.
     ReconnectCallback reconnect_;
@@ -438,8 +443,15 @@
 #endif
 
 void register_transport(atransport* transport);
-void register_usb_transport(usb_handle* h, const char* serial,
-                            const char* devpath, unsigned writeable);
+
+#if ADB_HOST
+void init_usb_transport(atransport* t, usb_handle* usb);
+void register_usb_transport(usb_handle* h, const char* serial, const char* devpath,
+                            unsigned writeable);
+
+// This should only be used for transports with connection_state == kCsNoPerm.
+void unregister_usb_transport(usb_handle* usb);
+#endif
 
 /* Connect to a network address and register it as a device */
 void connect_device(const std::string& address, std::string* response);
@@ -449,9 +461,6 @@
                                atransport::ReconnectCallback reconnect, bool use_tls,
                                int* error = nullptr);
 
-// This should only be used for transports with connection_state == kCsNoPerm.
-void unregister_usb_transport(usb_handle* usb);
-
 bool check_header(apacket* p, atransport* t);
 
 void close_usb_devices(bool reset = false);
diff --git a/adb/transport_test.cpp b/adb/transport_test.cpp
index 00beb3a..a9ada4a 100644
--- a/adb/transport_test.cpp
+++ b/adb/transport_test.cpp
@@ -66,7 +66,7 @@
     ASSERT_TRUE(t.has_feature("bar"));
 
     t.SetFeatures(FeatureSetToString(FeatureSet{"foo", "bar", "foo"}));
-    ASSERT_EQ(2U, t.features().size());
+    ASSERT_LE(2U, t.features().size());
     ASSERT_TRUE(t.has_feature("foo"));
     ASSERT_TRUE(t.has_feature("bar"));
 
diff --git a/adb/types.cpp b/adb/types.cpp
index 26b77ab..9cdf32b 100644
--- a/adb/types.cpp
+++ b/adb/types.cpp
@@ -51,7 +51,7 @@
     auto dropped = 0u;
     while (dropped < len) {
         const auto next = chain_[start_index_].size() - begin_offset_;
-        if (dropped + next < len) {
+        if (dropped + next <= len) {
             pop_front_block();
             dropped += next;
         } else {
diff --git a/adb/types.h b/adb/types.h
index c619fff..620aa8e 100644
--- a/adb/types.h
+++ b/adb/types.h
@@ -150,6 +150,22 @@
     IOVector& operator=(const IOVector& copy) = delete;
     IOVector& operator=(IOVector&& move) noexcept;
 
+    const value_type* front_data() const {
+        if (chain_.empty()) {
+            return nullptr;
+        }
+
+        return chain_[start_index_].data() + begin_offset_;
+    }
+
+    size_type front_size() const {
+        if (chain_.empty()) {
+            return 0;
+        }
+
+        return chain_[start_index_].size() - begin_offset_;
+    }
+
     size_type size() const { return chain_length_ - begin_offset_; }
     bool empty() const { return size() == 0; }
 
diff --git a/adb/types_test.cpp b/adb/types_test.cpp
index 2c99f95..41fa1db 100644
--- a/adb/types_test.cpp
+++ b/adb/types_test.cpp
@@ -117,3 +117,20 @@
     ASSERT_EQ(1ULL, bc.size());
     ASSERT_EQ(create_block("x"), bc.coalesce());
 }
+
+TEST(IOVector, drop_front) {
+    IOVector vec;
+
+    vec.append(create_block('x', 2));
+    vec.append(create_block('y', 1000));
+    ASSERT_EQ(2U, vec.front_size());
+    ASSERT_EQ(1002U, vec.size());
+
+    vec.drop_front(1);
+    ASSERT_EQ(1U, vec.front_size());
+    ASSERT_EQ(1001U, vec.size());
+
+    vec.drop_front(1);
+    ASSERT_EQ(1000U, vec.front_size());
+    ASSERT_EQ(1000U, vec.size());
+}
diff --git a/base/Android.bp b/base/Android.bp
index 12de3b2..894ad6c 100644
--- a/base/Android.bp
+++ b/base/Android.bp
@@ -74,10 +74,6 @@
         "test_utils.cpp",
     ],
 
-    static: {
-        cflags: ["-DNO_LIBLOG_DLSYM"],
-    },
-
     cppflags: ["-Wexit-time-destructors"],
     shared_libs: ["liblog"],
     target: {
diff --git a/base/liblog_symbols.cpp b/base/liblog_symbols.cpp
index 8d59179..1f4b69b 100644
--- a/base/liblog_symbols.cpp
+++ b/base/liblog_symbols.cpp
@@ -16,11 +16,9 @@
 
 #include "liblog_symbols.h"
 
-#if defined(__ANDROID__)
-#if !defined(NO_LIBLOG_DLSYM) || defined(__ANDROID_APEX__)
+#if defined(__ANDROID_SDK_VERSION__) && (__ANDROID_SDK_VERSION__ <= 29)
 #define USE_DLSYM
 #endif
-#endif
 
 #ifdef USE_DLSYM
 #include <dlfcn.h>
@@ -48,7 +46,7 @@
   }
 
     DLSYM(__android_log_set_logger)
-    DLSYM(__android_log_write_logger_data)
+    DLSYM(__android_log_write_log_message)
     DLSYM(__android_log_logd_logger)
     DLSYM(__android_log_stderr_logger)
     DLSYM(__android_log_set_aborter)
@@ -71,7 +69,7 @@
   static std::optional<LibLogFunctions> liblog_functions = []() -> std::optional<LibLogFunctions> {
     return LibLogFunctions{
         .__android_log_set_logger = __android_log_set_logger,
-        .__android_log_write_logger_data = __android_log_write_logger_data,
+        .__android_log_write_log_message = __android_log_write_log_message,
         .__android_log_logd_logger = __android_log_logd_logger,
         .__android_log_stderr_logger = __android_log_stderr_logger,
         .__android_log_set_aborter = __android_log_set_aborter,
diff --git a/base/liblog_symbols.h b/base/liblog_symbols.h
index b4ab06a..2e6b47f 100644
--- a/base/liblog_symbols.h
+++ b/base/liblog_symbols.h
@@ -25,13 +25,10 @@
 
 struct LibLogFunctions {
   void (*__android_log_set_logger)(__android_logger_function logger);
-  void (*__android_log_write_logger_data)(struct __android_logger_data* logger_data,
-                                          const char* msg);
+  void (*__android_log_write_log_message)(struct __android_log_message* log_message);
 
-  void (*__android_log_logd_logger)(const struct __android_logger_data* logger_data,
-                                    const char* msg);
-  void (*__android_log_stderr_logger)(const struct __android_logger_data* logger_data,
-                                      const char* message);
+  void (*__android_log_logd_logger)(const struct __android_log_message* log_message);
+  void (*__android_log_stderr_logger)(const struct __android_log_message* log_message);
 
   void (*__android_log_set_aborter)(__android_aborter_function aborter);
   void (*__android_log_call_aborter)(const char* abort_message);
diff --git a/base/logging.cpp b/base/logging.cpp
index 9a6e0fb..cd460eb 100644
--- a/base/logging.cpp
+++ b/base/logging.cpp
@@ -349,9 +349,9 @@
 
   static auto& liblog_functions = GetLibLogFunctions();
   if (liblog_functions) {
-    __android_logger_data logger_data = {sizeof(__android_logger_data),     lg_id, priority, tag,
-                                         static_cast<const char*>(nullptr), 0};
-    liblog_functions->__android_log_logd_logger(&logger_data, message);
+    __android_log_message log_message = {sizeof(__android_log_message),     lg_id, priority, tag,
+                                         static_cast<const char*>(nullptr), 0,     message};
+    liblog_functions->__android_log_logd_logger(&log_message);
   } else {
     __android_log_buf_print(lg_id, priority, tag, "%s", message);
   }
@@ -426,13 +426,13 @@
     // std::function<>, which is the not-thread-safe alternative.
     static std::atomic<LogFunction*> logger_function(nullptr);
     auto* old_logger_function = logger_function.exchange(new LogFunction(logger));
-    liblog_functions->__android_log_set_logger([](const struct __android_logger_data* logger_data,
-                                                  const char* message) {
-      auto log_id = log_id_tToLogId(logger_data->buffer_id);
-      auto severity = PriorityToLogSeverity(logger_data->priority);
+    liblog_functions->__android_log_set_logger([](const struct __android_log_message* log_message) {
+      auto log_id = log_id_tToLogId(log_message->buffer_id);
+      auto severity = PriorityToLogSeverity(log_message->priority);
 
       auto& function = *logger_function.load(std::memory_order_acquire);
-      function(log_id, severity, logger_data->tag, logger_data->file, logger_data->line, message);
+      function(log_id, severity, log_message->tag, log_message->file, log_message->line,
+               log_message->message);
     });
     delete old_logger_function;
   } else {
@@ -576,9 +576,9 @@
   static auto& liblog_functions = GetLibLogFunctions();
   int32_t priority = LogSeverityToPriority(severity);
   if (liblog_functions) {
-    __android_logger_data logger_data = {
-        sizeof(__android_logger_data), LOG_ID_DEFAULT, priority, tag, file, line};
-    liblog_functions->__android_log_write_logger_data(&logger_data, message);
+    __android_log_message log_message = {
+        sizeof(__android_log_message), LOG_ID_DEFAULT, priority, tag, file, line, message};
+    liblog_functions->__android_log_write_log_message(&log_message);
   } else {
     if (tag == nullptr) {
       std::lock_guard<std::recursive_mutex> lock(TagLock());
diff --git a/base/properties.cpp b/base/properties.cpp
index 35e41a8..5c9ec7e 100644
--- a/base/properties.cpp
+++ b/base/properties.cpp
@@ -133,7 +133,6 @@
   ts.tv_nsec = ns.count();
 }
 
-// TODO: boot_clock?
 using AbsTime = std::chrono::time_point<std::chrono::steady_clock>;
 
 static void UpdateTimeSpec(timespec& ts, std::chrono::milliseconds relative_timeout,
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index a9c1676..5d6cee4 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -1238,16 +1238,26 @@
   // Shift last_reboot_reason_property to last_last_reboot_reason_property
   std::string last_boot_reason;
   if (!android::base::ReadFileToString(last_reboot_reason_file, &last_boot_reason)) {
+    PLOG(ERROR) << "Failed to read " << last_reboot_reason_file;
     last_boot_reason = android::base::GetProperty(last_reboot_reason_property, "");
+    LOG(INFO) << "Value of " << last_reboot_reason_property << " : " << last_boot_reason;
+  } else {
+    LOG(INFO) << "Last reboot reason read from " << last_reboot_reason_file << " : "
+              << last_boot_reason << ". Last reboot reason read from "
+              << last_reboot_reason_property << " : "
+              << android::base::GetProperty(last_reboot_reason_property, "");
   }
   if (last_boot_reason.empty() || isKernelRebootReason(system_boot_reason)) {
     last_boot_reason = system_boot_reason;
   } else {
     transformReason(last_boot_reason);
   }
+  LOG(INFO) << "Normalized last reboot reason : " << last_boot_reason;
   android::base::SetProperty(last_last_reboot_reason_property, last_boot_reason);
   android::base::SetProperty(last_reboot_reason_property, "");
-  unlink(last_reboot_reason_file);
+  if (unlink(last_reboot_reason_file) != 0) {
+    PLOG(ERROR) << "Failed to unlink " << last_reboot_reason_file;
+  }
 }
 
 // Gets the boot time offset. This is useful when Android is running in a
diff --git a/cpio/mkbootfs.c b/cpio/mkbootfs.c
index e52762e..58153f3 100644
--- a/cpio/mkbootfs.c
+++ b/cpio/mkbootfs.c
@@ -13,6 +13,7 @@
 #include <fcntl.h>
 
 #include <private/android_filesystem_config.h>
+#include <private/fs_config.h>
 
 /* NOTES
 **
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index f28c778..d67b522 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -103,9 +103,14 @@
     export_include_dirs: ["include"],
 }
 
-// Fallback implementation.
+// Fallback implementation, for use in the Bionic linker only.
 cc_library_static {
     name: "libdebuggerd_handler_fallback",
+    visibility: ["//bionic/linker"],
+    apex_available: [
+        "com.android.runtime",
+        "//apex_available:platform",
+    ],
     defaults: ["debuggerd_defaults"],
     recovery_available: true,
     srcs: [
@@ -118,8 +123,7 @@
         "libasync_safe",
         "libbase",
         "libdebuggerd",
-        "libunwindstack",
-        "libdexfile_support_static",  // libunwindstack dependency
+        "libunwindstack_no_dex",
         "liblzma",
         "libcutils",
     ],
@@ -127,14 +131,6 @@
     header_libs: ["bionic_libc_platform_headers"],
     export_header_lib_headers: ["bionic_libc_platform_headers"],
 
-    target: {
-        recovery: {
-            exclude_static_libs: [
-                "libdexfile_support_static",
-            ],
-        },
-    },
-
     export_include_dirs: ["include"],
 }
 
@@ -188,7 +184,7 @@
     ],
 
     static_libs: [
-        "libdexfile_support_static",  // libunwindstack dependency
+        "libdexfile_support",  // libunwindstack dependency
         "libunwindstack",
         "liblzma",
         "libbase",
@@ -201,7 +197,7 @@
     target: {
         recovery: {
             exclude_static_libs: [
-                "libdexfile_support_static",
+                "libdexfile_support",
             ],
         },
     },
@@ -321,6 +317,10 @@
         "libprocinfo",
         "libunwindstack",
     ],
+
+    apex_available: [
+        "com.android.runtime",
+    ],
 }
 
 cc_binary {
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 6a38145..0cd2350 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -264,15 +264,13 @@
     ssize_t expected_size = 0;
     switch (crash_info->header.version) {
       case 1:
-        expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV1);
-        break;
-
       case 2:
-        expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV2);
+      case 3:
+        expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataStatic);
         break;
 
-      case 3:
-        expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV3);
+      case 4:
+        expected_size = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataDynamic);
         break;
 
       default:
@@ -280,25 +278,32 @@
         break;
     };
 
-    if (rc != expected_size) {
+    if (rc < expected_size) {
       LOG(FATAL) << "read " << rc << " bytes when reading target crash information, expected "
                  << expected_size;
     }
   }
 
   switch (crash_info->header.version) {
-    case 3:
-      process_info->gwp_asan_state = crash_info->data.v3.gwp_asan_state;
-      process_info->gwp_asan_metadata = crash_info->data.v3.gwp_asan_metadata;
-      FALLTHROUGH_INTENDED;
-    case 2:
-      process_info->fdsan_table_address = crash_info->data.v2.fdsan_table_address;
+    case 4:
+      process_info->fdsan_table_address = crash_info->data.d.fdsan_table_address;
+      process_info->gwp_asan_state = crash_info->data.d.gwp_asan_state;
+      process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata;
       FALLTHROUGH_INTENDED;
     case 1:
-      process_info->abort_msg_address = crash_info->data.v1.abort_msg_address;
-      *siginfo = crash_info->data.v1.siginfo;
+    case 2:
+    case 3:
+      process_info->abort_msg_address = crash_info->data.s.abort_msg_address;
+      *siginfo = crash_info->data.s.siginfo;
+      if (signal_has_si_addr(siginfo)) {
+        // Make a copy of the ucontext field because otherwise it is not aligned enough (due to
+        // being in a packed struct) and clang complains about that.
+        ucontext_t ucontext = crash_info->data.s.ucontext;
+        process_info->has_fault_address = true;
+        process_info->fault_address = get_fault_address(siginfo, &ucontext);
+      }
       regs->reset(unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(),
-                                                        &crash_info->data.v1.ucontext));
+                                                        &crash_info->data.s.ucontext));
       break;
 
     default:
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index 3041664..a2b13a3 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -349,7 +349,7 @@
 int main(int argc, char** argv) {
 #if defined(STATIC_CRASHER)
     debuggerd_callbacks_t callbacks = {
-      .get_abort_message = []() {
+      .get_process_info = []() {
         static struct {
           size_t size;
           char msg[32];
@@ -357,7 +357,9 @@
 
         msg.size = strlen("dummy abort message");
         memcpy(msg.msg, "dummy abort message", strlen("dummy abort message"));
-        return reinterpret_cast<abort_msg_t*>(&msg);
+        return debugger_process_info{
+            .abort_msg = reinterpret_cast<void*>(&msg),
+        };
       },
       .post_dump = nullptr
     };
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index 6a8cc56..054f836 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -305,6 +305,32 @@
   ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr 0xdead)");
 }
 
+TEST_F(CrasherTest, tagged_fault_addr) {
+#if !defined(__aarch64__)
+  GTEST_SKIP() << "Requires aarch64";
+#endif
+  int intercept_result;
+  unique_fd output_fd;
+  StartProcess([]() {
+    *reinterpret_cast<volatile char*>(0x100000000000dead) = '1';
+  });
+
+  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);
+
+  // The address can either be tagged (new kernels) or untagged (old kernels).
+  ASSERT_MATCH(
+      result,
+      R"(signal 11 \(SIGSEGV\), code 1 \(SEGV_MAPERR\), fault addr (0x100000000000dead|0xdead))");
+}
+
 TEST_F(CrasherTest, LD_PRELOAD) {
   int intercept_result;
   unique_fd output_fd;
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 8b4b630..121a074 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -83,7 +83,7 @@
 #define CRASH_DUMP_NAME "crash_dump32"
 #endif
 
-#define CRASH_DUMP_PATH "/system/bin/" CRASH_DUMP_NAME
+#define CRASH_DUMP_PATH "/apex/com.android.runtime/bin/" CRASH_DUMP_NAME
 
 // Wrappers that directly invoke the respective syscalls, in case the cached values are invalid.
 #pragma GCC poison getpid gettid
@@ -167,7 +167,7 @@
  * mutex is being held, so we don't want to use any libc functions that
  * could allocate memory or hold a lock.
  */
-static void log_signal_summary(const siginfo_t* info) {
+static void log_signal_summary(const siginfo_t* info, const ucontext_t* ucontext) {
   char thread_name[MAX_TASK_NAME_LEN + 1];  // one more for termination
   if (prctl(PR_GET_NAME, reinterpret_cast<unsigned long>(thread_name), 0, 0, 0) != 0) {
     strcpy(thread_name, "<name unknown>");
@@ -186,7 +186,8 @@
   // Many signals don't have an address or sender.
   char addr_desc[32] = "";  // ", fault addr 0x1234"
   if (signal_has_si_addr(info)) {
-    async_safe_format_buffer(addr_desc, sizeof(addr_desc), ", fault addr %p", info->si_addr);
+    async_safe_format_buffer(addr_desc, sizeof(addr_desc), ", fault addr %p",
+                             reinterpret_cast<void*>(get_fault_address(info, ucontext)));
   }
   pid_t self_pid = __getpid();
   char sender_desc[32] = {};  // " from pid 1234, uid 666"
@@ -297,10 +298,7 @@
   pid_t pseudothread_tid;
   siginfo_t* siginfo;
   void* ucontext;
-  uintptr_t abort_msg;
-  uintptr_t fdsan_table;
-  uintptr_t gwp_asan_state;
-  uintptr_t gwp_asan_metadata;
+  debugger_process_info process_info;
 };
 
 // Logging and contacting debuggerd requires free file descriptors, which we might not have.
@@ -344,25 +342,36 @@
     fatal_errno("failed to create pipe");
   }
 
-  // ucontext_t is absurdly large on AArch64, so piece it together manually with writev.
-  uint32_t version = 3;
-  constexpr size_t expected = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataV3);
+  uint32_t version;
+  ssize_t expected;
 
+  // ucontext_t is absurdly large on AArch64, so piece it together manually with writev.
+  struct iovec iovs[4] = {
+      {.iov_base = &version, .iov_len = sizeof(version)},
+      {.iov_base = thread_info->siginfo, .iov_len = sizeof(siginfo_t)},
+      {.iov_base = thread_info->ucontext, .iov_len = sizeof(ucontext_t)},
+  };
+
+  if (thread_info->process_info.fdsan_table) {
+    // Dynamic executables always use version 4. There is no need to increment the version number if
+    // the format changes, because the sender (linker) and receiver (crash_dump) are version locked.
+    version = 4;
+    expected = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataDynamic);
+
+    iovs[3] = {.iov_base = &thread_info->process_info,
+               .iov_len = sizeof(thread_info->process_info)};
+  } else {
+    // Static executables always use version 1.
+    version = 1;
+    expected = sizeof(CrashInfoHeader) + sizeof(CrashInfoDataStatic);
+
+    iovs[3] = {.iov_base = &thread_info->process_info.abort_msg, .iov_len = sizeof(uintptr_t)};
+  }
   errno = 0;
   if (fcntl(output_write.get(), F_SETPIPE_SZ, expected) < static_cast<int>(expected)) {
     fatal_errno("failed to set pipe buffer size");
   }
 
-  struct iovec iovs[] = {
-      {.iov_base = &version, .iov_len = sizeof(version)},
-      {.iov_base = thread_info->siginfo, .iov_len = sizeof(siginfo_t)},
-      {.iov_base = thread_info->ucontext, .iov_len = sizeof(ucontext_t)},
-      {.iov_base = &thread_info->abort_msg, .iov_len = sizeof(uintptr_t)},
-      {.iov_base = &thread_info->fdsan_table, .iov_len = sizeof(uintptr_t)},
-      {.iov_base = &thread_info->gwp_asan_state, .iov_len = sizeof(uintptr_t)},
-      {.iov_base = &thread_info->gwp_asan_metadata, .iov_len = sizeof(uintptr_t)},
-  };
-
   ssize_t rc = TEMP_FAILURE_RETRY(writev(output_write.get(), iovs, arraysize(iovs)));
   if (rc == -1) {
     fatal_errno("failed to write crash info");
@@ -408,23 +417,28 @@
   // us to fork off a process to read memory from.
   char buf[4];
   rc = TEMP_FAILURE_RETRY(read(input_read.get(), &buf, sizeof(buf)));
-  if (rc == -1) {
-    async_safe_format_log(ANDROID_LOG_FATAL, "libc", "read of IPC pipe failed: %s", strerror(errno));
-    return 1;
-  } else if (rc == 0) {
-    async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper failed to exec");
-    return 1;
-  } else if (rc != 1) {
-    async_safe_format_log(ANDROID_LOG_FATAL, "libc",
-                          "read of IPC pipe returned unexpected value: %zd", rc);
-    return 1;
-  } else if (buf[0] != '\1') {
-    async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper reported failure");
-    return 1;
-  }
 
-  // crash_dump is ptracing us, fork off a copy of our address space for it to use.
-  create_vm_process();
+  bool success = false;
+  if (rc == 1 && buf[0] == '\1') {
+    // crash_dump successfully started, and is ptracing us.
+    // Fork off a copy of our address space for it to use.
+    create_vm_process();
+    success = true;
+  } else {
+    // Something went wrong, log it.
+    if (rc == -1) {
+      async_safe_format_log(ANDROID_LOG_FATAL, "libc", "read of IPC pipe failed: %s",
+                            strerror(errno));
+    } else if (rc == 0) {
+      async_safe_format_log(ANDROID_LOG_FATAL, "libc",
+                            "crash_dump helper failed to exec, or was killed");
+    } else if (rc != 1) {
+      async_safe_format_log(ANDROID_LOG_FATAL, "libc",
+                            "read of IPC pipe returned unexpected value: %zd", rc);
+    } else if (buf[0] != '\1') {
+      async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper reported failure");
+    }
+  }
 
   // Don't leave a zombie child.
   int status;
@@ -435,14 +449,16 @@
     async_safe_format_log(ANDROID_LOG_FATAL, "libc", "crash_dump helper crashed or stopped");
   }
 
-  if (thread_info->siginfo->si_signo != BIONIC_SIGNAL_DEBUGGER) {
-    // For crashes, we don't need to minimize pause latency.
-    // Wait for the dump to complete before having the process exit, to avoid being murdered by
-    // ActivityManager or init.
-    TEMP_FAILURE_RETRY(read(input_read, &buf, sizeof(buf)));
+  if (success) {
+    if (thread_info->siginfo->si_signo != BIONIC_SIGNAL_DEBUGGER) {
+      // For crashes, we don't need to minimize pause latency.
+      // Wait for the dump to complete before having the process exit, to avoid being murdered by
+      // ActivityManager or init.
+      TEMP_FAILURE_RETRY(read(input_read, &buf, sizeof(buf)));
+    }
   }
 
-  return 0;
+  return success ? 0 : 1;
 }
 
 static void resend_signal(siginfo_t* info) {
@@ -468,6 +484,8 @@
   // making a syscall and checking errno.
   ErrnoRestorer restorer;
 
+  auto *ucontext = static_cast<ucontext_t*>(context);
+
   // It's possible somebody cleared the SA_SIGINFO flag, which would mean
   // our "info" arg holds an undefined value.
   if (!have_siginfo(signal_number)) {
@@ -489,29 +507,19 @@
     // check to allow all si_code values in calls coming from inside the house.
   }
 
-  void* abort_message = nullptr;
-  const gwp_asan::AllocatorState* gwp_asan_state = nullptr;
-  const gwp_asan::AllocationMetadata* gwp_asan_metadata = nullptr;
+  debugger_process_info process_info = {};
   uintptr_t si_val = reinterpret_cast<uintptr_t>(info->si_ptr);
   if (signal_number == BIONIC_SIGNAL_DEBUGGER) {
     if (info->si_code == SI_QUEUE && info->si_pid == __getpid()) {
       // Allow for the abort message to be explicitly specified via the sigqueue value.
       // Keep the bottom bit intact for representing whether we want a backtrace or a tombstone.
       if (si_val != kDebuggerdFallbackSivalUintptrRequestDump) {
-        abort_message = reinterpret_cast<void*>(si_val & ~1);
+        process_info.abort_msg = reinterpret_cast<void*>(si_val & ~1);
         info->si_ptr = reinterpret_cast<void*>(si_val & 1);
       }
     }
-  } else {
-    if (g_callbacks.get_abort_message) {
-      abort_message = g_callbacks.get_abort_message();
-    }
-    if (g_callbacks.get_gwp_asan_state) {
-      gwp_asan_state = g_callbacks.get_gwp_asan_state();
-    }
-    if (g_callbacks.get_gwp_asan_metadata) {
-      gwp_asan_metadata = g_callbacks.get_gwp_asan_metadata();
-    }
+  } else if (g_callbacks.get_process_info) {
+    process_info = g_callbacks.get_process_info();
   }
 
   // If sival_int is ~0, it means that the fallback handler has been called
@@ -524,7 +532,7 @@
     // This check might be racy if another thread sets NO_NEW_PRIVS, but this should be unlikely,
     // you can only set NO_NEW_PRIVS to 1, and the effect should be at worst a single missing
     // ANR trace.
-    debuggerd_fallback_handler(info, static_cast<ucontext_t*>(context), abort_message);
+    debuggerd_fallback_handler(info, ucontext, process_info.abort_msg);
     resend_signal(info);
     return;
   }
@@ -536,17 +544,14 @@
     return;
   }
 
-  log_signal_summary(info);
+  log_signal_summary(info, ucontext);
 
   debugger_thread_info thread_info = {
       .crashing_tid = __gettid(),
       .pseudothread_tid = -1,
       .siginfo = info,
       .ucontext = context,
-      .abort_msg = reinterpret_cast<uintptr_t>(abort_message),
-      .fdsan_table = reinterpret_cast<uintptr_t>(android_fdsan_get_fd_table()),
-      .gwp_asan_state = reinterpret_cast<uintptr_t>(gwp_asan_state),
-      .gwp_asan_metadata = reinterpret_cast<uintptr_t>(gwp_asan_metadata),
+      .process_info = process_info,
   };
 
   // Set PR_SET_DUMPABLE to 1, so that crash_dump can ptrace us.
diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h
index 665d24a..6650294 100644
--- a/debuggerd/include/debuggerd/handler.h
+++ b/debuggerd/include/debuggerd/handler.h
@@ -33,13 +33,20 @@
 struct AllocationMetadata;
 };  // namespace gwp_asan
 
+// When updating this data structure, CrashInfoDataDynamic and the code in
+// ReadCrashInfo() must also be updated.
+struct debugger_process_info {
+  void* abort_msg;
+  void* fdsan_table;
+  const gwp_asan::AllocatorState* gwp_asan_state;
+  const gwp_asan::AllocationMetadata* gwp_asan_metadata;
+};
+
 // These callbacks are called in a signal handler, and thus must be async signal safe.
 // If null, the callbacks will not be called.
 typedef struct {
-  struct abort_msg_t* (*get_abort_message)();
+  debugger_process_info (*get_process_info)();
   void (*post_dump)();
-  const struct gwp_asan::AllocatorState* (*get_gwp_asan_state)();
-  const struct gwp_asan::AllocationMetadata* (*get_gwp_asan_metadata)();
 } debuggerd_callbacks_t;
 
 void debuggerd_init(debuggerd_callbacks_t* callbacks);
diff --git a/debuggerd/libdebuggerd/gwp_asan.cpp b/debuggerd/libdebuggerd/gwp_asan.cpp
index fe3a173..f271365 100644
--- a/debuggerd/libdebuggerd/gwp_asan.cpp
+++ b/debuggerd/libdebuggerd/gwp_asan.cpp
@@ -157,63 +157,6 @@
        error_string_, diff, byte_suffix, location_str, alloc_size, alloc_address);
 }
 
-// Build a frame for symbolization using the maps from the provided unwinder.
-// The constructed frame contains just enough information to be used to
-// symbolize a GWP-ASan stack trace.
-static unwindstack::FrameData BuildFrame(unwindstack::Unwinder* unwinder, uintptr_t pc,
-                                         size_t frame_num) {
-  unwindstack::FrameData frame;
-  frame.num = frame_num;
-
-  unwindstack::Maps* maps = unwinder->GetMaps();
-  unwindstack::MapInfo* map_info = maps->Find(pc);
-  if (!map_info) {
-    frame.rel_pc = pc;
-    return frame;
-  }
-
-  unwindstack::Elf* elf =
-      map_info->GetElf(unwinder->GetProcessMemory(), unwindstack::Regs::CurrentArch());
-
-  uint64_t relative_pc = elf->GetRelPc(pc, map_info);
-
-  // Create registers just to get PC adjustment. Doesn't matter what they point
-  // to.
-  unwindstack::Regs* regs = unwindstack::Regs::CreateFromLocal();
-  uint64_t pc_adjustment = regs->GetPcAdjustment(relative_pc, elf);
-  relative_pc -= pc_adjustment;
-  // The debug PC may be different if the PC comes from the JIT.
-  uint64_t debug_pc = relative_pc;
-
-  // If we don't have a valid ELF file, check the JIT.
-  if (!elf->valid()) {
-    unwindstack::JitDebug jit_debug(unwinder->GetProcessMemory());
-    uint64_t jit_pc = pc - pc_adjustment;
-    unwindstack::Elf* jit_elf = jit_debug.GetElf(maps, jit_pc);
-    if (jit_elf != nullptr) {
-      debug_pc = jit_pc;
-      elf = jit_elf;
-    }
-  }
-
-  // Copy all the things we need into the frame for symbolization.
-  frame.rel_pc = relative_pc;
-  frame.pc = pc - pc_adjustment;
-  frame.map_name = map_info->name;
-  frame.map_elf_start_offset = map_info->elf_start_offset;
-  frame.map_exact_offset = map_info->offset;
-  frame.map_start = map_info->start;
-  frame.map_end = map_info->end;
-  frame.map_flags = map_info->flags;
-  frame.map_load_bias = elf->GetLoadBias();
-
-  if (!elf->GetFunctionName(relative_pc, &frame.function_name, &frame.function_offset)) {
-    frame.function_name = "";
-    frame.function_offset = 0;
-  }
-  return frame;
-}
-
 constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect;
 
 bool GwpAsanCrashData::HasDeallocationTrace() const {
@@ -240,7 +183,8 @@
 
   unwinder->SetDisplayBuildID(true);
   for (size_t i = 0; i < num_frames; ++i) {
-    unwindstack::FrameData frame_data = BuildFrame(unwinder, frames.get()[i], i);
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
+    frame_data.num = i;
     _LOG(log, logtype::BACKTRACE, "    %s\n", unwinder->FormatFrame(frame_data).c_str());
   }
 }
@@ -266,7 +210,8 @@
 
   unwinder->SetDisplayBuildID(true);
   for (size_t i = 0; i < num_frames; ++i) {
-    unwindstack::FrameData frame_data = BuildFrame(unwinder, frames.get()[i], i);
+    unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]);
+    frame_data.num = i;
     _LOG(log, logtype::BACKTRACE, "    %s\n", unwinder->FormatFrame(frame_data).c_str());
   }
 }
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
index 4f681c2..35c3fd6 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -41,4 +41,7 @@
   uintptr_t fdsan_table_address = 0;
   uintptr_t gwp_asan_state = 0;
   uintptr_t gwp_asan_metadata = 0;
+
+  bool has_fault_address = false;
+  uintptr_t fault_address = 0;
 };
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index 75bac87..7bfcf5d 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -93,4 +93,6 @@
 const char* get_signame(const siginfo_t*);
 const char* get_sigcode(const siginfo_t*);
 
+uintptr_t get_fault_address(const siginfo_t* siginfo, const ucontext_t* ucontext);
+
 #endif // _DEBUGGERD_UTILITY_H
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index b3f059c..e0168d5 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -154,16 +154,16 @@
 }
 
 static void dump_signal_info(log_t* log, const ThreadInfo& thread_info,
-                             unwindstack::Memory* process_memory) {
+                             const ProcessInfo& process_info, unwindstack::Memory* process_memory) {
   char addr_desc[64];  // ", fault addr 0x1234"
-  if (signal_has_si_addr(thread_info.siginfo)) {
-    void* addr = thread_info.siginfo->si_addr;
+  if (process_info.has_fault_address) {
+    size_t addr = process_info.fault_address;
     if (thread_info.siginfo->si_signo == SIGILL) {
       uint32_t instruction = {};
-      process_memory->Read(reinterpret_cast<uint64_t>(addr), &instruction, sizeof(instruction));
-      snprintf(addr_desc, sizeof(addr_desc), "%p (*pc=%#08x)", addr, instruction);
+      process_memory->Read(addr, &instruction, sizeof(instruction));
+      snprintf(addr_desc, sizeof(addr_desc), "0x%zx (*pc=%#08x)", addr, instruction);
     } else {
-      snprintf(addr_desc, sizeof(addr_desc), "%p", addr);
+      snprintf(addr_desc, sizeof(addr_desc), "0x%zx", addr);
     }
   } else {
     snprintf(addr_desc, sizeof(addr_desc), "--------");
@@ -384,7 +384,7 @@
   dump_thread_info(log, thread_info);
 
   if (thread_info.siginfo) {
-    dump_signal_info(log, thread_info, unwinder->GetProcessMemory().get());
+    dump_signal_info(log, thread_info, process_info, unwinder->GetProcessMemory().get());
   }
 
   std::unique_ptr<GwpAsanCrashData> gwp_asan_crash_data;
@@ -447,8 +447,6 @@
 // that don't match the specified pid, and writes them to the tombstone file.
 //
 // If "tail" is non-zero, log the last "tail" number of lines.
-static EventTagMap* g_eventTagMap = NULL;
-
 static void dump_log_file(log_t* log, pid_t pid, const char* filename, unsigned int tail) {
   bool first = true;
   logger_list* logger_list;
@@ -457,8 +455,8 @@
     return;
   }
 
-  logger_list = android_logger_list_open(
-      android_name_to_log_id(filename), ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, tail, pid);
+  logger_list =
+      android_logger_list_open(android_name_to_log_id(filename), ANDROID_LOG_NONBLOCK, tail, pid);
 
   if (!logger_list) {
     ALOGE("Unable to open %s: %s\n", filename, strerror(errno));
@@ -507,21 +505,6 @@
     ptm = localtime_r(&sec, &tmBuf);
     strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
 
-    if (log_entry.id() == LOG_ID_EVENTS) {
-      if (!g_eventTagMap) {
-        g_eventTagMap = android_openEventTagMap(nullptr);
-      }
-      AndroidLogEntry e;
-      char buf[512];
-      if (android_log_processBinaryLogBuffer(&log_entry.entry, &e, g_eventTagMap, buf,
-                                             sizeof(buf)) == 0) {
-        _LOG(log, logtype::LOGS, "%s.%03d %5d %5d %c %-8.*s: %s\n", timeBuf,
-             log_entry.entry.nsec / 1000000, log_entry.entry.pid, log_entry.entry.tid, 'I',
-             (int)e.tagLen, e.tag, e.message);
-      }
-      continue;
-    }
-
     char* msg = log_entry.msg();
     if (msg == nullptr) {
       continue;
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 0a1d2a4..3bf28b6 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -449,3 +449,40 @@
     _LOG(log, logtype::BACKTRACE, "%s%s\n", prefix, unwinder->FormatFrame(i).c_str());
   }
 }
+
+#if defined(__aarch64__)
+#define FAR_MAGIC 0x46415201
+
+struct far_context {
+  struct _aarch64_ctx head;
+  __u64 far;
+};
+#endif
+
+uintptr_t get_fault_address(const siginfo_t* siginfo, const ucontext_t* ucontext) {
+  (void)ucontext;
+#if defined(__aarch64__)
+  // This relies on a kernel patch:
+  //   https://patchwork.kernel.org/patch/11435077/
+  // that hasn't been accepted into the kernel yet. TODO(pcc): Update this to
+  // use the official interface once it lands.
+  auto* begin = reinterpret_cast<const char*>(ucontext->uc_mcontext.__reserved);
+  auto* end = begin + sizeof(ucontext->uc_mcontext.__reserved);
+  auto* ptr = begin;
+  while (1) {
+    auto* ctx = reinterpret_cast<const _aarch64_ctx*>(ptr);
+    if (ctx->magic == 0) {
+      break;
+    }
+    if (ctx->magic == FAR_MAGIC) {
+      auto* far_ctx = reinterpret_cast<const far_context*>(ctx);
+      return far_ctx->far;
+    }
+    ptr += ctx->size;
+    if (ctx->size % sizeof(void*) != 0 || ptr < begin || ptr >= end) {
+      break;
+    }
+  }
+#endif
+  return reinterpret_cast<uintptr_t>(siginfo->si_addr);
+}
diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h
index bf53864..e85660c 100644
--- a/debuggerd/protocol.h
+++ b/debuggerd/protocol.h
@@ -85,17 +85,14 @@
   uint32_t version;
 };
 
-struct __attribute__((__packed__)) CrashInfoDataV1 {
+struct __attribute__((__packed__)) CrashInfoDataStatic {
   siginfo_t siginfo;
   ucontext_t ucontext;
   uintptr_t abort_msg_address;
 };
 
-struct __attribute__((__packed__)) CrashInfoDataV2 : public CrashInfoDataV1 {
+struct __attribute__((__packed__)) CrashInfoDataDynamic : public CrashInfoDataStatic {
   uintptr_t fdsan_table_address;
-};
-
-struct __attribute__((__packed__)) CrashInfoDataV3 : public CrashInfoDataV2 {
   uintptr_t gwp_asan_state;
   uintptr_t gwp_asan_metadata;
 };
@@ -103,8 +100,7 @@
 struct __attribute__((__packed__)) CrashInfo {
   CrashInfoHeader header;
   union {
-    CrashInfoDataV1 v1;
-    CrashInfoDataV2 v2;
-    CrashInfoDataV3 v3;
+    CrashInfoDataStatic s;
+    CrashInfoDataDynamic d;
   } data;
 };
diff --git a/diagnose_usb/Android.bp b/diagnose_usb/Android.bp
index 6bee28c..93d13bd 100644
--- a/diagnose_usb/Android.bp
+++ b/diagnose_usb/Android.bp
@@ -3,6 +3,11 @@
     cflags: ["-Wall", "-Wextra", "-Werror"],
     host_supported: true,
     recovery_available: true,
+    apex_available: [
+        "com.android.adbd",
+        // TODO(b/151398197) remove the below
+        "//apex_available:platform",
+    ],
     target: {
         windows: {
             enabled: true,
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index a757d56..3a2deb7 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -115,6 +115,7 @@
         "device/fastboot_device.cpp",
         "device/flashing.cpp",
         "device/main.cpp",
+        "device/usb.cpp",
         "device/usb_client.cpp",
         "device/utility.cpp",
         "device/variables.cpp",
@@ -125,7 +126,6 @@
         "android.hardware.boot@1.1",
         "android.hardware.fastboot@1.0",
         "android.hardware.health@2.0",
-        "libadbd",
         "libasyncio",
         "libbase",
         "libbootloader_message",
@@ -145,6 +145,7 @@
     static_libs: [
         "libhealthhalutils",
         "libsnapshot_nobinder",
+        "update_metadata-protos",
     ],
 
     header_libs: [
diff --git a/fastboot/bootimg_utils.cpp b/fastboot/bootimg_utils.cpp
index 46d4bd3..2c0989e 100644
--- a/fastboot/bootimg_utils.cpp
+++ b/fastboot/bootimg_utils.cpp
@@ -34,14 +34,54 @@
 #include <stdlib.h>
 #include <string.h>
 
-void bootimg_set_cmdline(boot_img_hdr_v2* h, const std::string& cmdline) {
+static void bootimg_set_cmdline_v3(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 (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) {
+#define V3_PAGE_SIZE 4096
+    const size_t page_mask = V3_PAGE_SIZE - 1;
+    int64_t kernel_actual = (kernel.size() + page_mask) & (~page_mask);
+    int64_t ramdisk_actual = (ramdisk.size() + page_mask) & (~page_mask);
+
+    int64_t bootimg_size = V3_PAGE_SIZE + kernel_actual + ramdisk_actual;
+    out->resize(bootimg_size);
+
+    boot_img_hdr_v3* hdr = reinterpret_cast<boot_img_hdr_v3*>(out->data());
+
+    memcpy(hdr->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE);
+    hdr->kernel_size = kernel.size();
+    hdr->ramdisk_size = ramdisk.size();
+    hdr->os_version = src.os_version;
+    hdr->header_size = sizeof(boot_img_hdr_v3);
+    hdr->header_version = 3;
+
+    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) {
+        if (!second.empty() || !dtb.empty()) {
+            die("Second stage bootloader and dtb not supported in v3 boot image\n");
+        }
+        return reinterpret_cast<boot_img_hdr_v2*>(mkbootimg_v3(kernel, ramdisk, src, out));
+    }
     const size_t page_mask = src.page_size - 1;
 
     int64_t header_actual = (sizeof(boot_img_hdr_v1) + page_mask) & (~page_mask);
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index ca120c6..b8eee4a 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -31,6 +31,7 @@
 #include <cutils/android_reboot.h>
 #include <ext4_utils/wipe.h>
 #include <fs_mgr.h>
+#include <fs_mgr/roots.h>
 #include <libgsi/libgsi.h>
 #include <liblp/builder.h>
 #include <liblp/liblp.h>
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 31fc359..bb085c5 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -16,18 +16,22 @@
 
 #include "fastboot_device.h"
 
+#include <algorithm>
+
 #include <android-base/logging.h>
 #include <android-base/strings.h>
 #include <android/hardware/boot/1.0/IBootControl.h>
 #include <android/hardware/fastboot/1.0/IFastboot.h>
+#include <fs_mgr.h>
+#include <fs_mgr/roots.h>
 #include <healthhalutils/HealthHalUtils.h>
 
-#include <algorithm>
-
 #include "constants.h"
 #include "flashing.h"
 #include "usb_client.h"
 
+using android::fs_mgr::EnsurePathUnmounted;
+using android::fs_mgr::Fstab;
 using ::android::hardware::hidl_string;
 using ::android::hardware::boot::V1_0::IBootControl;
 using ::android::hardware::boot::V1_0::Slot;
@@ -64,6 +68,13 @@
     if (boot_control_hal_) {
         boot1_1_ = android::hardware::boot::V1_1::IBootControl::castFrom(boot_control_hal_);
     }
+
+    // Make sure cache is unmounted, since recovery will have mounted it for
+    // logging.
+    Fstab fstab;
+    if (ReadDefaultFstab(&fstab)) {
+        EnsurePathUnmounted(&fstab, "/cache");
+    }
 }
 
 FastbootDevice::~FastbootDevice() {
diff --git a/fastboot/device/flashing.cpp b/fastboot/device/flashing.cpp
index 7e7e507..fd6ff8e 100644
--- a/fastboot/device/flashing.cpp
+++ b/fastboot/device/flashing.cpp
@@ -135,7 +135,9 @@
         return -EOVERFLOW;
     }
     WipeOverlayfsForPartition(device, partition_name);
-    return FlashBlockDevice(handle.fd(), data);
+    int result = FlashBlockDevice(handle.fd(), data);
+    sync();
+    return result;
 }
 
 bool UpdateSuper(FastbootDevice* device, const std::string& super_name, bool wipe) {
@@ -165,6 +167,7 @@
             return device->WriteFail("Unable to flash new partition table");
         }
         fs_mgr_overlayfs_teardown();
+        sync();
         return device->WriteOkay("Successfully flashed partition table");
     }
 
@@ -204,5 +207,6 @@
         return device->WriteFail("Unable to write new partition table");
     }
     fs_mgr_overlayfs_teardown();
+    sync();
     return device->WriteOkay("Successfully updated partition table");
 }
diff --git a/adb/daemon/usb_legacy.cpp b/fastboot/device/usb.cpp
similarity index 70%
rename from adb/daemon/usb_legacy.cpp
rename to fastboot/device/usb.cpp
index fe80e7d..4bee7b2 100644
--- a/adb/daemon/usb_legacy.cpp
+++ b/fastboot/device/usb.cpp
@@ -14,9 +14,7 @@
  * limitations under the License.
  */
 
-#define TRACE_TAG USB
-
-#include "sysdeps.h"
+#include "usb.h"
 
 #include <dirent.h>
 #include <errno.h>
@@ -41,12 +39,9 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 
-#include "adb.h"
-#include "adbd/usb.h"
-#include "transport.h"
-
 using namespace std::chrono_literals;
 
+#define D(...)
 #define MAX_PACKET_SIZE_FS 64
 #define MAX_PACKET_SIZE_HS 512
 #define MAX_PACKET_SIZE_SS 1024
@@ -56,8 +51,6 @@
 // Number of buffers needed to fit MAX_PAYLOAD, with an extra for ZLPs.
 #define USB_FFS_NUM_BUFS ((4 * MAX_PAYLOAD / USB_FFS_BULK_SIZE) + 1)
 
-static unique_fd& dummy_fd = *new unique_fd();
-
 static void aio_block_init(aio_block* aiob, unsigned num_bufs) {
     aiob->iocb.resize(num_bufs);
     aiob->iocbs.resize(num_bufs);
@@ -82,46 +75,6 @@
     }
 }
 
-static bool init_functionfs(struct usb_handle* h) {
-    LOG(INFO) << "initializing functionfs";
-    if (!open_functionfs(&h->control, &h->bulk_out, &h->bulk_in)) {
-        return false;
-    }
-
-    h->read_aiob.fd = h->bulk_out.get();
-    h->write_aiob.fd = h->bulk_in.get();
-    h->reads_zero_packets = true;
-    return true;
-}
-
-static void usb_legacy_ffs_open_thread(usb_handle* usb) {
-    adb_thread_setname("usb legacy ffs open");
-
-    while (true) {
-        // wait until the USB device needs opening
-        std::unique_lock<std::mutex> lock(usb->lock);
-        while (!usb->open_new_connection) {
-            usb->notify.wait(lock);
-        }
-        usb->open_new_connection = false;
-        lock.unlock();
-
-        while (true) {
-            if (init_functionfs(usb)) {
-                LOG(INFO) << "functionfs successfully initialized";
-                break;
-            }
-            std::this_thread::sleep_for(1s);
-        }
-
-        LOG(INFO) << "registering usb transport";
-        register_usb_transport(usb, nullptr, nullptr, 1);
-    }
-
-    // never gets here
-    abort();
-}
-
 static int usb_ffs_write(usb_handle* h, const void* data, int len) {
     D("about to write (fd=%d, len=%d)", h->bulk_in.get(), len);
 
@@ -129,7 +82,7 @@
     int orig_len = len;
     while (len > 0) {
         int write_len = std::min(USB_FFS_BULK_SIZE, len);
-        int n = adb_write(h->bulk_in, buf, write_len);
+        int n = write(h->bulk_in, buf, write_len);
         if (n < 0) {
             D("ERROR: fd = %d, n = %d: %s", h->bulk_in.get(), n, strerror(errno));
             return -1;
@@ -150,7 +103,7 @@
     unsigned count = 0;
     while (len > 0) {
         int read_len = std::min(USB_FFS_BULK_SIZE, len);
-        int n = adb_read(h->bulk_out, buf, read_len);
+        int n = read(h->bulk_out, buf, read_len);
         if (n < 0) {
             D("ERROR: fd = %d, n = %d: %s", h->bulk_out.get(), n, strerror(errno));
             return -1;
@@ -232,7 +185,7 @@
     }
 }
 
-static int usb_ffs_aio_read(usb_handle* h, void* data, int len, bool allow_partial) {
+static int usb_ffs_aio_read(usb_handle* h, void* data, int len, bool /* allow_partial */) {
     return usb_ffs_do_aio(h, data, len, true);
 }
 
@@ -240,32 +193,9 @@
     return usb_ffs_do_aio(h, data, len, false);
 }
 
-static void usb_ffs_kick(usb_handle* h) {
-    int err;
-
-    err = ioctl(h->bulk_in.get(), FUNCTIONFS_CLEAR_HALT);
-    if (err < 0) {
-        D("[ kick: source (fd=%d) clear halt failed (%d) ]", h->bulk_in.get(), errno);
-    }
-
-    err = ioctl(h->bulk_out.get(), FUNCTIONFS_CLEAR_HALT);
-    if (err < 0) {
-        D("[ kick: sink (fd=%d) clear halt failed (%d) ]", h->bulk_out.get(), errno);
-    }
-
-    // don't close ep0 here, since we may not need to reinitialize it with
-    // the same descriptors again. if however ep1/ep2 fail to re-open in
-    // init_functionfs, only then would we close and open ep0 again.
-    // Ditto the comment in usb_adb_kick.
-    h->kicked = true;
-    TEMP_FAILURE_RETRY(dup2(dummy_fd.get(), h->bulk_out.get()));
-    TEMP_FAILURE_RETRY(dup2(dummy_fd.get(), h->bulk_in.get()));
-}
-
 static void usb_ffs_close(usb_handle* h) {
     LOG(INFO) << "closing functionfs transport";
 
-    h->kicked = false;
     h->bulk_out.reset();
     h->bulk_in.reset();
 
@@ -291,37 +221,6 @@
         aio_block_init(&h->write_aiob, num_bufs);
     }
     h->io_size = io_size;
-    h->kick = usb_ffs_kick;
     h->close = usb_ffs_close;
     return h;
 }
-
-void usb_init_legacy() {
-    D("[ usb_init - using legacy FunctionFS ]");
-    dummy_fd.reset(adb_open("/dev/null", O_WRONLY | O_CLOEXEC));
-    CHECK_NE(-1, dummy_fd.get());
-
-    std::thread(usb_legacy_ffs_open_thread, create_usb_handle(USB_FFS_NUM_BUFS, USB_FFS_BULK_SIZE))
-            .detach();
-}
-
-int usb_write(usb_handle* h, const void* data, int len) {
-    return h->write(h, data, len);
-}
-
-int usb_read(usb_handle* h, void* data, int len) {
-    return h->read(h, data, len, false /* allow_partial */);
-}
-
-int usb_close(usb_handle* h) {
-    h->close(h);
-    return 0;
-}
-
-void usb_reset(usb_handle* h) {
-    usb_close(h);
-}
-
-void usb_kick(usb_handle* h) {
-    h->kick(h);
-}
diff --git a/adb/daemon/include/adbd/usb.h b/fastboot/device/usb.h
similarity index 84%
rename from adb/daemon/include/adbd/usb.h
rename to fastboot/device/usb.h
index 2204246..6c3f542 100644
--- a/adb/daemon/include/adbd/usb.h
+++ b/fastboot/device/usb.h
@@ -36,17 +36,14 @@
 };
 
 struct usb_handle {
-    usb_handle() : kicked(false) {
-    }
+    usb_handle() {}
 
     std::condition_variable notify;
     std::mutex lock;
-    std::atomic<bool> kicked;
     bool open_new_connection = true;
 
     int (*write)(usb_handle* h, const void* data, int len);
     int (*read)(usb_handle* h, void* data, int len, bool allow_partial);
-    void (*kick)(usb_handle* h);
     void (*close)(usb_handle* h);
 
     // FunctionFS
@@ -63,6 +60,4 @@
     size_t io_size;
 };
 
-usb_handle *create_usb_handle(unsigned num_bufs, unsigned io_size);
-bool open_functionfs(android::base::unique_fd* control, android::base::unique_fd* bulk_out,
-                     android::base::unique_fd* bulk_in);
+usb_handle* create_usb_handle(unsigned num_bufs, unsigned io_size);
diff --git a/fastboot/device/usb_client.h b/fastboot/device/usb_client.h
index e6a1a8b..e702a0d 100644
--- a/fastboot/device/usb_client.h
+++ b/fastboot/device/usb_client.h
@@ -17,7 +17,7 @@
 
 #include <memory>
 
-#include <adbd/usb.h>
+#include "usb.h"
 
 #include "transport.h"
 
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 7fdc28b..7f6e723 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -464,7 +464,7 @@
     }
 
     // Is this actually a boot image?
-    if (kernel_data.size() < sizeof(boot_img_hdr_v2)) {
+    if (kernel_data.size() < sizeof(boot_img_hdr_v3)) {
         die("cannot load '%s': too short", kernel.c_str());
     }
     if (!memcmp(kernel_data.data(), BOOT_MAGIC, BOOT_MAGIC_SIZE)) {
@@ -493,7 +493,7 @@
 
     std::vector<char> dtb_data;
     if (!g_dtb_path.empty()) {
-        if (g_boot_img_hdr.header_version < 2) {
+        if (g_boot_img_hdr.header_version != 2) {
                     die("Argument dtb not supported for boot image header version %d\n",
                         g_boot_img_hdr.header_version);
         }
diff --git a/fastboot/fuzzy_fastboot/Android.bp b/fastboot/fuzzy_fastboot/Android.bp
index bb54fd9..aa449b2 100644
--- a/fastboot/fuzzy_fastboot/Android.bp
+++ b/fastboot/fuzzy_fastboot/Android.bp
@@ -49,6 +49,6 @@
   auto_gen_config: false,
   test_suites: [
     "general-tests",
-    "vts-core",
+    "vts",
   ],
 }
diff --git a/fs_mgr/clean_scratch_files.rc b/fs_mgr/clean_scratch_files.rc
index 738d1aa..25a7e69 100644
--- a/fs_mgr/clean_scratch_files.rc
+++ b/fs_mgr/clean_scratch_files.rc
@@ -1,2 +1,2 @@
 on post-fs-data && property:ro.debuggable=1
-    exec_background - root root -- clean_scratch_files
+    exec_background - root root -- /system/bin/clean_scratch_files
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index c0c0e99..d2daaa1 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -62,6 +62,7 @@
 #include <fs_mgr_overlayfs.h>
 #include <fscrypt/fscrypt.h>
 #include <libdm/dm.h>
+#include <libdm/loop_control.h>
 #include <liblp/metadata_format.h>
 #include <linux/fs.h>
 #include <linux/loop.h>
@@ -96,7 +97,7 @@
 
 using android::base::Basename;
 using android::base::GetBoolProperty;
-using android::base::Readlink;
+using android::base::GetUintProperty;
 using android::base::Realpath;
 using android::base::SetProperty;
 using android::base::StartsWith;
@@ -105,6 +106,7 @@
 using android::dm::DeviceMapper;
 using android::dm::DmDeviceState;
 using android::dm::DmTargetLinear;
+using android::dm::LoopControl;
 
 // Realistically, this file should be part of the android::fs_mgr namespace;
 using namespace android::fs_mgr;
@@ -358,7 +360,7 @@
                        const struct ext4_super_block* sb, int* fs_stat) {
     bool has_quota = (sb->s_feature_ro_compat & cpu_to_le32(EXT4_FEATURE_RO_COMPAT_QUOTA)) != 0;
     bool want_quota = entry.fs_mgr_flags.quota;
-    bool want_projid = android::base::GetBoolProperty("ro.emulated_storage.projid", false);
+    bool want_projid = android::base::GetBoolProperty("external_storage.projid.enabled", false);
 
     if (has_quota == want_quota) {
         return;
@@ -521,7 +523,8 @@
 static void tune_casefold(const std::string& blk_device, const struct ext4_super_block* sb,
                           int* fs_stat) {
     bool has_casefold = (sb->s_feature_incompat & cpu_to_le32(EXT4_FEATURE_INCOMPAT_CASEFOLD)) != 0;
-    bool wants_casefold = android::base::GetBoolProperty("ro.emulated_storage.casefold", false);
+    bool wants_casefold =
+            android::base::GetBoolProperty("external_storage.casefold.enabled", false);
 
     if (!wants_casefold || has_casefold) return;
 
@@ -1545,11 +1548,16 @@
     return ret;
 }
 
-static bool fs_mgr_unmount_all_data_mounts(const std::string& block_device) {
-    LINFO << __FUNCTION__ << "(): about to umount everything on top of " << block_device;
+static std::chrono::milliseconds GetMillisProperty(const std::string& name,
+                                                   std::chrono::milliseconds default_value) {
+    auto value = GetUintProperty(name, static_cast<uint64_t>(default_value.count()));
+    return std::chrono::milliseconds(std::move(value));
+}
+
+static bool fs_mgr_unmount_all_data_mounts(const std::string& data_block_device) {
+    LINFO << __FUNCTION__ << "(): about to umount everything on top of " << data_block_device;
     Timer t;
-    // TODO(b/135984674): should be configured via a read-only property.
-    std::chrono::milliseconds timeout = 5s;
+    auto timeout = GetMillisProperty("init.userspace_reboot.userdata_remount.timeoutmillis", 5s);
     while (true) {
         bool umount_done = true;
         Fstab proc_mounts;
@@ -1559,7 +1567,13 @@
         }
         // Now proceed with other bind mounts on top of /data.
         for (const auto& entry : proc_mounts) {
-            if (entry.blk_device == block_device) {
+            std::string block_device;
+            if (StartsWith(entry.blk_device, "/dev/block") &&
+                !Realpath(entry.blk_device, &block_device)) {
+                PWARNING << __FUNCTION__ << "(): failed to realpath " << entry.blk_device;
+                block_device = entry.blk_device;
+            }
+            if (data_block_device == block_device) {
                 if (umount2(entry.mount_point.c_str(), 0) != 0) {
                     PERROR << __FUNCTION__ << "(): Failed to umount " << entry.mount_point;
                     umount_done = false;
@@ -1571,7 +1585,8 @@
             return true;
         }
         if (t.duration() > timeout) {
-            LERROR << __FUNCTION__ << "(): Timed out unmounting all mounts on " << block_device;
+            LERROR << __FUNCTION__ << "(): Timed out unmounting all mounts on "
+                   << data_block_device;
             Fstab remaining_mounts;
             if (!ReadFstabFromFile("/proc/mounts", &remaining_mounts)) {
                 LERROR << __FUNCTION__ << "(): Can't read /proc/mounts";
@@ -1610,14 +1625,11 @@
     return true;
 }
 
-FstabEntry* fs_mgr_get_mounted_entry_for_userdata(Fstab* fstab, const FstabEntry& mounted_entry) {
-    if (mounted_entry.mount_point != "/data") {
-        LERROR << mounted_entry.mount_point << " is not /data";
-        return nullptr;
-    }
+FstabEntry* fs_mgr_get_mounted_entry_for_userdata(Fstab* fstab,
+                                                  const std::string& data_block_device) {
     std::vector<std::string> dm_stack;
-    if (!UnwindDmDeviceStack(mounted_entry.blk_device, &dm_stack)) {
-        LERROR << "Failed to unwind dm-device stack for " << mounted_entry.blk_device;
+    if (!UnwindDmDeviceStack(data_block_device, &dm_stack)) {
+        LERROR << "Failed to unwind dm-device stack for " << data_block_device;
         return nullptr;
     }
     for (auto& entry : *fstab) {
@@ -1631,15 +1643,15 @@
                 continue;
             }
             block_device = entry.blk_device;
-        } else if (!Readlink(entry.blk_device, &block_device)) {
-            PWARNING << "Failed to read link " << entry.blk_device;
+        } else if (!Realpath(entry.blk_device, &block_device)) {
+            PWARNING << "Failed to realpath " << entry.blk_device;
             block_device = entry.blk_device;
         }
         if (std::find(dm_stack.begin(), dm_stack.end(), block_device) != dm_stack.end()) {
             return &entry;
         }
     }
-    LERROR << "Didn't find entry that was used to mount /data onto " << mounted_entry.blk_device;
+    LERROR << "Didn't find entry that was used to mount /data onto " << data_block_device;
     return nullptr;
 }
 
@@ -1650,14 +1662,17 @@
         LERROR << "Can't read /proc/mounts";
         return -1;
     }
-    std::string block_device;
     auto mounted_entry = GetEntryForMountPoint(&proc_mounts, "/data");
     if (mounted_entry == nullptr) {
         LERROR << "/data is not mounted";
         return -1;
     }
-    block_device = mounted_entry->blk_device;
-    auto fstab_entry = fs_mgr_get_mounted_entry_for_userdata(fstab, *mounted_entry);
+    std::string block_device;
+    if (!Realpath(mounted_entry->blk_device, &block_device)) {
+        PERROR << "Failed to realpath " << mounted_entry->blk_device;
+        return -1;
+    }
+    auto fstab_entry = fs_mgr_get_mounted_entry_for_userdata(fstab, block_device);
     if (fstab_entry == nullptr) {
         LERROR << "Can't find /data in fstab";
         return -1;
@@ -1895,19 +1910,6 @@
         return InstallZramDevice(bdev);
     }
 
-    // Get free loopback
-    unique_fd loop_fd(TEMP_FAILURE_RETRY(open("/dev/loop-control", O_RDWR | O_CLOEXEC)));
-    if (loop_fd.get() == -1) {
-        PERROR << "Cannot open loop-control";
-        return false;
-    }
-
-    int num = ioctl(loop_fd.get(), LOOP_CTL_GET_FREE);
-    if (num == -1) {
-        PERROR << "Cannot get free loop slot";
-        return false;
-    }
-
     // Prepare target path
     unique_fd target_fd(TEMP_FAILURE_RETRY(open(loop.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600)));
     if (target_fd.get() == -1) {
@@ -1919,25 +1921,21 @@
         return false;
     }
 
-    // Connect loopback (device_fd) to target path (target_fd)
-    std::string device = android::base::StringPrintf("/dev/block/loop%d", num);
-    unique_fd device_fd(TEMP_FAILURE_RETRY(open(device.c_str(), O_RDWR | O_CLOEXEC)));
-    if (device_fd.get() == -1) {
-        PERROR << "Cannot open /dev/block/loop" << num;
-        return false;
-    }
-
-    if (ioctl(device_fd.get(), LOOP_SET_FD, target_fd.get())) {
-        PERROR << "Cannot set loopback to target path";
+    // Allocate loop device and attach it to file_path.
+    LoopControl loop_control;
+    std::string device;
+    if (!loop_control.Attach(target_fd.get(), 5s, &device)) {
         return false;
     }
 
     // set block size & direct IO
-    if (ioctl(device_fd.get(), LOOP_SET_BLOCK_SIZE, 4096)) {
-        PWARNING << "Cannot set 4KB blocksize to /dev/block/loop" << num;
+    unique_fd device_fd(TEMP_FAILURE_RETRY(open(device.c_str(), O_RDWR | O_CLOEXEC)));
+    if (device_fd.get() == -1) {
+        PERROR << "Cannot open " << device;
+        return false;
     }
-    if (ioctl(device_fd.get(), LOOP_SET_DIRECT_IO, 1)) {
-        PWARNING << "Cannot set direct_io to /dev/block/loop" << num;
+    if (!LoopControl::EnableDirectIo(device_fd.get())) {
+        return false;
     }
 
     return InstallZramDevice(device);
diff --git a/fs_mgr/fs_mgr_format.cpp b/fs_mgr/fs_mgr_format.cpp
index 7be024f..301c907 100644
--- a/fs_mgr/fs_mgr_format.cpp
+++ b/fs_mgr/fs_mgr_format.cpp
@@ -121,7 +121,7 @@
 }
 
 static int format_f2fs(const std::string& fs_blkdev, uint64_t dev_sz, bool crypt_footer,
-                       bool needs_projid, bool needs_casefold) {
+                       bool needs_projid, bool needs_casefold, bool fs_compress) {
     if (!dev_sz) {
         int rc = get_dev_sz(fs_blkdev, &dev_sz);
         if (rc) {
@@ -147,6 +147,12 @@
         args.push_back("-C");
         args.push_back("utf8");
     }
+    if (fs_compress) {
+        args.push_back("-O");
+        args.push_back("compression");
+        args.push_back("-O");
+        args.push_back("extra_attr");
+    }
     args.push_back(fs_blkdev.c_str());
     args.push_back(size_str.c_str());
 
@@ -160,13 +166,13 @@
     bool needs_projid = false;
 
     if (entry.mount_point == "/data") {
-        needs_casefold = android::base::GetBoolProperty("ro.emulated_storage.casefold", false);
-        needs_projid = android::base::GetBoolProperty("ro.emulated_storage.projid", false);
+        needs_casefold = android::base::GetBoolProperty("external_storage.casefold.enabled", false);
+        needs_projid = android::base::GetBoolProperty("external_storage.projid.enabled", false);
     }
 
     if (entry.fs_type == "f2fs") {
         return format_f2fs(entry.blk_device, entry.length, crypt_footer, needs_projid,
-                           needs_casefold);
+                           needs_casefold, entry.fs_mgr_flags.fs_compress);
     } else if (entry.fs_type == "ext4") {
         return format_ext4(entry.blk_device, entry.mount_point, crypt_footer, needs_projid,
                            entry.fs_mgr_flags.ext_meta_csum);
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index a836d3b..0825a77 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -178,6 +178,7 @@
         CheckFlag("slotselect_other", slot_select_other);
         CheckFlag("fsverity", fs_verity);
         CheckFlag("metadata_csum", ext_meta_csum);
+        CheckFlag("fscompress", fs_compress);
 
 #undef CheckFlag
 
@@ -829,6 +830,20 @@
         return std::set<std::string>(boot_devices.begin(), boot_devices.end());
     }
 
+    std::string cmdline;
+    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)) {
+            if (key == cmdline_key) {
+                boot_devices.emplace(value);
+            }
+        }
+        if (!boot_devices.empty()) {
+            return boot_devices;
+        }
+    }
+
     // Fallback to extract boot devices from fstab.
     Fstab fstab;
     if (!ReadDefaultFstab(&fstab)) {
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 3d556c9..86090c1 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -107,10 +107,9 @@
 // it destroys verity devices from device mapper after the device is unmounted.
 int fs_mgr_umount_all(android::fs_mgr::Fstab* fstab);
 
-// Finds a entry in |fstab| that was used to mount a /data |mounted_entry| from
-// /proc/mounts.
+// Finds a entry in |fstab| that was used to mount a /data on |data_block_device|.
 android::fs_mgr::FstabEntry* fs_mgr_get_mounted_entry_for_userdata(
-        android::fs_mgr::Fstab* fstab, const android::fs_mgr::FstabEntry& mounted_entry);
+        android::fs_mgr::Fstab* fstab, const std::string& data_block_device);
 int fs_mgr_remount_userdata_into_checkpointing(android::fs_mgr::Fstab* fstab);
 
 // Finds the dm_bow device on which this block device is stacked, or returns
diff --git a/fs_mgr/include_fstab/fstab/fstab.h b/fs_mgr/include_fstab/fstab/fstab.h
index 79d9402..7216402 100644
--- a/fs_mgr/include_fstab/fstab/fstab.h
+++ b/fs_mgr/include_fstab/fstab/fstab.h
@@ -84,6 +84,7 @@
         bool slot_select_other : 1;
         bool fs_verity : 1;
         bool ext_meta_csum : 1;
+        bool fs_compress : 1;
     } fs_mgr_flags = {};
 
     bool is_encryptable() const {
diff --git a/fs_mgr/libdm/Android.bp b/fs_mgr/libdm/Android.bp
index c2c2cc4..58241b3 100644
--- a/fs_mgr/libdm/Android.bp
+++ b/fs_mgr/libdm/Android.bp
@@ -58,11 +58,11 @@
     defaults: ["fs_mgr_defaults"],
     static_libs: [
         "libdm",
+        "libext2_uuid",
+        "libfs_mgr",
     ],
     shared_libs: [
         "libbase",
-        "libext2_uuid",
-        "libfs_mgr",
         "liblog",
     ],
     srcs: [":libdm_test_srcs"],
@@ -79,7 +79,7 @@
 cc_test {
     name: "vts_libdm_test",
     defaults: ["libdm_test_defaults"],
-    test_suites: ["vts-core"],
+    test_suites: ["vts"],
     test_min_api_level: 29,
 }
 
diff --git a/fs_mgr/libdm/dm_target.cpp b/fs_mgr/libdm/dm_target.cpp
index 29b1032..a594198 100644
--- a/fs_mgr/libdm/dm_target.cpp
+++ b/fs_mgr/libdm/dm_target.cpp
@@ -243,20 +243,8 @@
     return android::base::Join(argv, " ");
 }
 
-bool DmTargetDefaultKey::IsLegacy(bool* result) {
-    DeviceMapper& dm = DeviceMapper::Instance();
-    DmTargetTypeInfo info;
-    if (!dm.GetTargetByName(kName, &info)) return false;
-    // dm-default-key was modified to be like dm-crypt with version 2
-    *result = !info.IsAtLeast(2, 0, 0);
-    return true;
-}
-
 bool DmTargetDefaultKey::Valid() const {
-    bool real_is_legacy;
-    if (!DmTargetDefaultKey::IsLegacy(&real_is_legacy)) return false;
-    if (real_is_legacy != is_legacy_) return false;
-    if (!is_legacy_ && !set_dun_) return false;
+    if (!use_legacy_options_format_ && !set_dun_) return false;
     return true;
 }
 
@@ -264,13 +252,13 @@
     std::vector<std::string> argv;
     argv.emplace_back(cipher_);
     argv.emplace_back(key_);
-    if (!is_legacy_) {
+    if (!use_legacy_options_format_) {
         argv.emplace_back("0");  // iv_offset
     }
     argv.emplace_back(blockdev_);
     argv.push_back(std::to_string(start_sector_));
     std::vector<std::string> extra_argv;
-    if (is_legacy_) {
+    if (use_legacy_options_format_) {
         if (set_dun_) {  // v2 always sets the DUN.
             extra_argv.emplace_back("set_dun");
         }
diff --git a/fs_mgr/libdm/dm_test.cpp b/fs_mgr/libdm/dm_test.cpp
index 67af59a..41d3145 100644
--- a/fs_mgr/libdm/dm_test.cpp
+++ b/fs_mgr/libdm/dm_test.cpp
@@ -516,32 +516,22 @@
 }
 
 TEST(libdm, DefaultKeyArgs) {
-    DmTargetTypeInfo info;
-
-    DeviceMapper& dm = DeviceMapper::Instance();
-    if (!dm.GetTargetByName("default-key", &info)) {
-        cout << "default-key module not enabled; skipping test" << std::endl;
-        return;
-    }
-    bool is_legacy;
-    ASSERT_TRUE(DmTargetDefaultKey::IsLegacy(&is_legacy));
-    // set_dun only in the non-is_legacy case
-    DmTargetDefaultKey target(0, 4096, "AES-256-XTS", "abcdef0123456789", "/dev/loop0", 0);
-    if (is_legacy) {
-        target.SetIsLegacy();
-    } else {
-        target.SetSetDun();
-    }
+    DmTargetDefaultKey target(0, 4096, "aes-xts-plain64", "abcdef0123456789", "/dev/loop0", 0);
+    target.SetSetDun();
     ASSERT_EQ(target.name(), "default-key");
     ASSERT_TRUE(target.Valid());
-    if (is_legacy) {
-        ASSERT_EQ(target.GetParameterString(), "AES-256-XTS abcdef0123456789 /dev/loop0 0");
-    } else {
-        // TODO: Add case for wrapped key enabled
-        ASSERT_EQ(target.GetParameterString(),
-                  "AES-256-XTS abcdef0123456789 0 /dev/loop0 0 3 allow_discards sector_size:4096 "
-                  "iv_large_sectors");
-    }
+    // TODO: Add case for wrapped key enabled
+    ASSERT_EQ(target.GetParameterString(),
+              "aes-xts-plain64 abcdef0123456789 0 /dev/loop0 0 3 allow_discards sector_size:4096 "
+              "iv_large_sectors");
+}
+
+TEST(libdm, DefaultKeyLegacyArgs) {
+    DmTargetDefaultKey target(0, 4096, "AES-256-XTS", "abcdef0123456789", "/dev/loop0", 0);
+    target.SetUseLegacyOptionsFormat();
+    ASSERT_EQ(target.name(), "default-key");
+    ASSERT_TRUE(target.Valid());
+    ASSERT_EQ(target.GetParameterString(), "AES-256-XTS abcdef0123456789 /dev/loop0 0");
 }
 
 TEST(libdm, DeleteDeviceWithTimeout) {
diff --git a/fs_mgr/libdm/include/libdm/dm_target.h b/fs_mgr/libdm/include/libdm/dm_target.h
index 050d0b6..57096ce 100644
--- a/fs_mgr/libdm/include/libdm/dm_target.h
+++ b/fs_mgr/libdm/include/libdm/dm_target.h
@@ -290,8 +290,7 @@
     std::string name() const override { return kName; }
     bool Valid() const override;
     std::string GetParameterString() const override;
-    static bool IsLegacy(bool* result);
-    void SetIsLegacy() { is_legacy_ = true; }
+    void SetUseLegacyOptionsFormat() { use_legacy_options_format_ = true; }
     void SetSetDun() { set_dun_ = true; }
     void SetWrappedKeyV0() { is_hw_wrapped_ = true; }
 
@@ -302,7 +301,7 @@
     std::string key_;
     std::string blockdev_;
     uint64_t start_sector_;
-    bool is_legacy_ = false;
+    bool use_legacy_options_format_ = false;
     bool set_dun_ = false;
     bool is_hw_wrapped_ = false;
 };
diff --git a/fs_mgr/libfiemap/Android.bp b/fs_mgr/libfiemap/Android.bp
index ac589c7..9d18a44 100644
--- a/fs_mgr/libfiemap/Android.bp
+++ b/fs_mgr/libfiemap/Android.bp
@@ -81,7 +81,7 @@
         "fiemap_writer_test.cpp",
     ],
 
-    test_suites: ["vts-core", "device-tests"],
+    test_suites: ["vts", "device-tests"],
     auto_gen_config: true,
     test_min_api_level: 29,
     require_root: true,
diff --git a/fs_mgr/liblp/Android.bp b/fs_mgr/liblp/Android.bp
index ad19f38..a779a78 100644
--- a/fs_mgr/liblp/Android.bp
+++ b/fs_mgr/liblp/Android.bp
@@ -97,7 +97,7 @@
 cc_test {
     name: "vts_core_liblp_test",
     defaults: ["liblp_test_defaults"],
-    test_suites: ["vts-core"],
+    test_suites: ["vts"],
     auto_gen_config: true,
     test_min_api_level: 29,
     require_root: true,
diff --git a/fs_mgr/liblp/liblp_test.xml b/fs_mgr/liblp/liblp_test.xml
index d9ee12e..98414b1 100644
--- a/fs_mgr/liblp/liblp_test.xml
+++ b/fs_mgr/liblp/liblp_test.xml
@@ -19,7 +19,6 @@
         <option name="cleanup" value="true" />
         <option name="push" value="liblp_test->/data/local/tmp/liblp_test" />
     </target_preparer>
-    <option name="test-suite-tag" value="vts-core" />
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
         <option name="module-name" value="liblp_test" />
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index d670ca0..384595d 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -26,7 +26,6 @@
         "libbase",
         "libcutils",
         "liblog",
-        "liblp",
     ],
     static_libs: [
         "libdm",
@@ -73,6 +72,7 @@
         "device_info.cpp",
         "snapshot.cpp",
         "snapshot_stats.cpp",
+        "snapshot_stub.cpp",
         "snapshot_metadata_updater.cpp",
         "partition_cow_creator.cpp",
         "return.cpp",
@@ -182,7 +182,7 @@
         "libstorage_literals_headers",
     ],
     test_suites: [
-        "vts-core",
+        "vts",
         "device-tests"
     ],
     test_min_api_level: 29,
@@ -195,6 +195,12 @@
     defaults: ["libsnapshot_test_defaults"],
 }
 
+// For VTS 10
+vts_config {
+    name: "VtsLibsnapshotTest",
+    test_config: "VtsLibsnapshotTest.xml"
+}
+
 cc_binary {
     name: "snapshotctl",
     srcs: [
@@ -203,6 +209,7 @@
     static_libs: [
         "libfstab",
         "libsnapshot",
+        "update_metadata-protos",
     ],
     shared_libs: [
         "android.hardware.boot@1.0",
@@ -220,3 +227,20 @@
         "libutils",
     ],
 }
+
+cc_test {
+    name: "snapshot_power_test",
+    srcs: [
+        "power_test.cpp",
+    ],
+    static_libs: [
+        "libsnapshot",
+        "update_metadata-protos",
+    ],
+    shared_libs: [
+        "libbase",
+        "libfs_mgr_binder",
+        "liblog",
+    ],
+    gtest: false,
+}
diff --git a/fs_mgr/libsnapshot/PowerTest.md b/fs_mgr/libsnapshot/PowerTest.md
new file mode 100644
index 0000000..0b0cb5d
--- /dev/null
+++ b/fs_mgr/libsnapshot/PowerTest.md
@@ -0,0 +1,40 @@
+snapshot\_power\_test
+---------------------
+
+snapshot\_power\_test is a standalone test to simulate power failures during a snapshot-merge operation.
+
+### Test Setup
+
+Start by creating two large files that will be used as the pre-merge and post-merge state. You can take two different partition images (for example, a product.img from two separate builds), or just create random data:
+
+	dd if=/dev/urandom of=pre-merge count=1024 bs=1048576
+	dd if=/dev/urandom of=post-merge count=1024 bs=1048576
+
+Next, push these files to an unencrypted directory on the device:
+
+	adb push pre-merge /data/local/unencrypted
+	adb push post-merge /data/local/unencrypted
+
+Next, run the test setup:
+
+	adb sync data
+	adb shell /data/nativetest64/snapshot_power_test/snapshot_power_test \
+		/data/local/unencrypted/pre-merge \
+		/data/local/unencrypted/post-merge
+
+This will create the necessary fiemap-based images.
+
+### Running
+The actual test can be run via `run_power_test.sh`. Its syntax is:
+
+	run_power_test.sh <POST_MERGE_FILE>
+
+`POST_MERGE_FILE` should be the path on the device of the image to validate the merge against. Example:
+
+	run_power_test.sh /data/local/unencrypted/post-merge
+
+The device will begin the merge with a 5% chance of injecting a kernel crash every 10ms. The device should be capable of rebooting normally without user intervention. Once the merge has completed, the test will run a final check command to validate the contents of the snapshot against the post-merge file. It will error if there are any incorrect blocks.
+
+Two environment variables can be passed to `run_power_test.sh`:
+1. `FAIL_RATE` - A fraction between 0 and 100 (inclusive) indicating the probability the device should inject a kernel crash every 10ms.
+2. `DEVICE_SERIAL` - If multiple devices are attached to adb, this argument is passed as the serial to select (to `adb -s`).
diff --git a/fs_mgr/libsnapshot/VtsLibsnapshotTest.xml b/fs_mgr/libsnapshot/VtsLibsnapshotTest.xml
new file mode 100644
index 0000000..b53b51e
--- /dev/null
+++ b/fs_mgr/libsnapshot/VtsLibsnapshotTest.xml
@@ -0,0 +1,29 @@
+<?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="Config for VTS VtsLibsnapshotTest">
+    <option name="config-descriptor:metadata" key="plan" value="vts-kernel"/>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.VtsFilePusher">
+        <option name="abort-on-push-failure" value="false"/>
+        <option name="push-group" value="HostDrivenTest.push"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.VtsMultiDeviceTest">
+        <option name="test-module-name" value="VtsLibsnapshotTest"/>
+        <option name="binary-test-source" value="_32bit::DATA/nativetest/vts_libsnapshot_test/vts_libsnapshot_test"/>
+        <option name="binary-test-source" value="_64bit::DATA/nativetest64/vts_libsnapshot_test/vts_libsnapshot_test"/>
+        <option name="binary-test-type" value="gtest"/>
+        <option name="test-timeout" value="5m"/>
+    </test>
+</configuration>
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
new file mode 100644
index 0000000..ef9d648
--- /dev/null
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_device_info.h
@@ -0,0 +1,37 @@
+// 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 <libsnapshot/snapshot.h>
+
+#include <gmock/gmock.h>
+
+namespace android::snapshot {
+
+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));
+    MOCK_METHOD(std::string, GetSuperDevice, (uint32_t slot), (const, override));
+    MOCK_METHOD(const android::fs_mgr::IPartitionOpener&, GetPartitionOpener, (), (const));
+    MOCK_METHOD(bool, IsOverlayfsSetup, (), (const, override));
+    MOCK_METHOD(bool, SetBootControlMergeStatus, (MergeStatus status), (override));
+    MOCK_METHOD(bool, SetSlotAsUnbootable, (unsigned int slot), (override));
+    MOCK_METHOD(bool, IsRecovery, (), (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
new file mode 100644
index 0000000..758d66c
--- /dev/null
+++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h
@@ -0,0 +1,54 @@
+// 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 <libsnapshot/snapshot.h>
+
+#include <gmock/gmock.h>
+
+namespace android::snapshot {
+
+class MockSnapshotManager : public ISnapshotManager {
+  public:
+    MOCK_METHOD(bool, BeginUpdate, (), (override));
+    MOCK_METHOD(bool, CancelUpdate, (), (override));
+    MOCK_METHOD(bool, FinishedSnapshotWrites, (bool wipe), (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(Return, CreateUpdateSnapshots,
+                (const chromeos_update_engine::DeltaArchiveManifest& manifest), (override));
+    MOCK_METHOD(bool, MapUpdateSnapshot,
+                (const android::fs_mgr::CreateLogicalPartitionParams& params,
+                 std::string* snapshot_path),
+                (override));
+    MOCK_METHOD(bool, UnmapUpdateSnapshot, (const std::string& target_partition_name), (override));
+    MOCK_METHOD(bool, NeedSnapshotsInFirstStageMount, (), (override));
+    MOCK_METHOD(bool, CreateLogicalAndSnapshotPartitions,
+                (const std::string& super_device, const std::chrono::milliseconds& timeout_ms),
+                (override));
+    MOCK_METHOD(bool, HandleImminentDataWipe, (const std::function<void()>& callback), (override));
+    MOCK_METHOD(CreateResult, RecoveryCreateSnapshotDevices, (), (override));
+    MOCK_METHOD(CreateResult, RecoveryCreateSnapshotDevices,
+                (const std::unique_ptr<AutoDevice>& metadata_device), (override));
+    MOCK_METHOD(bool, Dump, (std::ostream & os), (override));
+    MOCK_METHOD(std::unique_ptr<AutoDevice>, EnsureMetadataMounted, (), (override));
+    MOCK_METHOD(ISnapshotMergeStats*, GetSnapshotMergeStatsInstance, (), (override));
+};
+
+}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 957c26c..fff667e 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -70,6 +70,8 @@
 struct AutoDeleteSnapshot;
 struct AutoDeviceList;
 struct PartitionCowCreator;
+class ISnapshotMergeStats;
+class SnapshotMergeStats;
 class SnapshotStatus;
 
 static constexpr const std::string_view kCowGroupName = "cow";
@@ -83,17 +85,7 @@
     NOT_CREATED,
 };
 
-class SnapshotManager final {
-    using CreateLogicalPartitionParams = android::fs_mgr::CreateLogicalPartitionParams;
-    using IPartitionOpener = android::fs_mgr::IPartitionOpener;
-    using LpMetadata = android::fs_mgr::LpMetadata;
-    using MetadataBuilder = android::fs_mgr::MetadataBuilder;
-    using DeltaArchiveManifest = chromeos_update_engine::DeltaArchiveManifest;
-    using MergeStatus = android::hardware::boot::V1_1::MergeStatus;
-    using FiemapStatus = android::fiemap::FiemapStatus;
-
-    friend class SnapshotMergeStats;
-
+class ISnapshotManager {
   public:
     // Dependency injection for testing.
     class IDeviceInfo {
@@ -104,49 +96,35 @@
         virtual std::string GetSlotSuffix() const = 0;
         virtual std::string GetOtherSlotSuffix() const = 0;
         virtual std::string GetSuperDevice(uint32_t slot) const = 0;
-        virtual const IPartitionOpener& GetPartitionOpener() const = 0;
+        virtual const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const = 0;
         virtual bool IsOverlayfsSetup() const = 0;
-        virtual bool SetBootControlMergeStatus(MergeStatus status) = 0;
+        virtual bool SetBootControlMergeStatus(
+                android::hardware::boot::V1_1::MergeStatus status) = 0;
         virtual bool SetSlotAsUnbootable(unsigned int slot) = 0;
         virtual bool IsRecovery() const = 0;
     };
-
-    ~SnapshotManager();
-
-    // Return a new SnapshotManager instance, or null on error. The device
-    // pointer is owned for the lifetime of SnapshotManager. If null, a default
-    // instance will be created.
-    static std::unique_ptr<SnapshotManager> New(IDeviceInfo* device = nullptr);
-
-    // This is similar to New(), except designed specifically for first-stage
-    // init or recovery.
-    static std::unique_ptr<SnapshotManager> NewForFirstStageMount(IDeviceInfo* device = nullptr);
-
-    // Helper function for first-stage init to check whether a SnapshotManager
-    // might be needed to perform first-stage mounts.
-    static bool IsSnapshotManagerNeeded();
-
-    // Helper function for second stage init to restorecon on the rollback indicator.
-    static std::string GetGlobalRollbackIndicatorPath();
+    virtual ~ISnapshotManager() = default;
 
     // Begin an update. This must be called before creating any snapshots. It
     // will fail if GetUpdateState() != None.
-    bool BeginUpdate();
+    virtual bool BeginUpdate() = 0;
 
     // Cancel an update; any snapshots will be deleted. This is allowed if the
     // state == Initiated, None, or Unverified (before rebooting to the new
     // slot).
-    bool CancelUpdate();
+    virtual bool CancelUpdate() = 0;
 
     // Mark snapshot writes as having completed. After this, new snapshots cannot
     // be created, and the device must either cancel the OTA (either before
     // rebooting or after rolling back), or merge the OTA.
     // Before calling this function, all snapshots must be mapped.
-    bool FinishedSnapshotWrites();
+    // If |wipe| is set to true, wipe is scheduled after reboot, and snapshots
+    // may need to be merged before wiping.
+    virtual bool FinishedSnapshotWrites(bool wipe) = 0;
 
     // Initiate a merge on all snapshot devices. This should only be used after an
     // update has been marked successful after booting.
-    bool InitiateMerge();
+    virtual bool InitiateMerge() = 0;
 
     // Perform any necessary post-boot actions. This should be run soon after
     // /data is mounted.
@@ -176,8 +154,8 @@
     //
     // The optional callback allows the caller to periodically check the
     // progress with GetUpdateState().
-    UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
-                                   const std::function<bool()>& before_cancel = {});
+    virtual UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
+                                           const std::function<bool()>& before_cancel = {}) = 0;
 
     // Find the status of the current update, if any.
     //
@@ -185,28 +163,30 @@
     //   Merging: Value in the range [0, 100]
     //   MergeCompleted: 100
     //   Other: 0
-    UpdateState GetUpdateState(double* progress = nullptr);
+    virtual UpdateState GetUpdateState(double* progress = nullptr) = 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.
-    Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest);
+    virtual Return CreateUpdateSnapshots(
+            const chromeos_update_engine::DeltaArchiveManifest& manifest) = 0;
 
     // Map a snapshotted partition for OTA clients to write to. Write-protected regions are
     // determined previously in CreateSnapshots.
-    bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params, std::string* snapshot_path);
+    virtual bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,
+                                   std::string* snapshot_path) = 0;
 
     // Unmap a snapshot device that's previously mapped with MapUpdateSnapshot.
-    bool UnmapUpdateSnapshot(const std::string& target_partition_name);
+    virtual bool UnmapUpdateSnapshot(const std::string& target_partition_name) = 0;
 
     // If this returns true, first-stage mount must call
     // CreateLogicalAndSnapshotPartitions rather than CreateLogicalPartitions.
-    bool NeedSnapshotsInFirstStageMount();
+    virtual bool NeedSnapshotsInFirstStageMount() = 0;
 
     // Perform first-stage mapping of snapshot targets. This replaces init's
     // call to CreateLogicalPartitions when snapshots are present.
-    bool CreateLogicalAndSnapshotPartitions(const std::string& super_device,
-                                            const std::chrono::milliseconds& timeout_ms = {});
+    virtual bool CreateLogicalAndSnapshotPartitions(
+            const std::string& super_device, const std::chrono::milliseconds& timeout_ms = {}) = 0;
 
     // This method should be called preceding any wipe or flash of metadata or
     // userdata. It is only valid in recovery or fastbootd, and it ensures that
@@ -219,7 +199,7 @@
     //
     // Returns true on success (or nothing to do), false on failure. The
     // optional callback fires periodically to query progress via GetUpdateState.
-    bool HandleImminentDataWipe(const std::function<void()>& callback = {});
+    virtual bool HandleImminentDataWipe(const std::function<void()>& callback = {}) = 0;
 
     // This method is only allowed in recovery and is used as a helper to
     // initialize the snapshot devices as a requirement to mount a snapshotted
@@ -230,10 +210,17 @@
     // devices;
     // - CreateResult::ERROR if a fatal error occurred, mounting /system should
     // be aborted.
-    CreateResult RecoveryCreateSnapshotDevices();
+    // This function mounts /metadata when called, and unmounts /metadata upon
+    // return.
+    virtual CreateResult RecoveryCreateSnapshotDevices() = 0;
+
+    // Same as RecoveryCreateSnapshotDevices(), but does not auto mount/umount
+    // /metadata.
+    virtual CreateResult RecoveryCreateSnapshotDevices(
+            const std::unique_ptr<AutoDevice>& metadata_device) = 0;
 
     // Dump debug information.
-    bool Dump(std::ostream& os);
+    virtual bool Dump(std::ostream& os) = 0;
 
     // Ensure metadata directory is mounted in recovery. When the returned
     // AutoDevice is destroyed, the metadata directory is automatically
@@ -249,7 +236,65 @@
     //   auto b = mgr->EnsureMetadataMounted(); // does nothing
     //   b.reset() // unmounts
     //   a.reset() // does nothing
-    std::unique_ptr<AutoDevice> EnsureMetadataMounted();
+    virtual std::unique_ptr<AutoDevice> EnsureMetadataMounted() = 0;
+
+    // Return the associated ISnapshotMergeStats instance. Never null.
+    virtual ISnapshotMergeStats* GetSnapshotMergeStatsInstance() = 0;
+};
+
+class SnapshotManager final : public ISnapshotManager {
+    using CreateLogicalPartitionParams = android::fs_mgr::CreateLogicalPartitionParams;
+    using IPartitionOpener = android::fs_mgr::IPartitionOpener;
+    using LpMetadata = android::fs_mgr::LpMetadata;
+    using MetadataBuilder = android::fs_mgr::MetadataBuilder;
+    using DeltaArchiveManifest = chromeos_update_engine::DeltaArchiveManifest;
+    using MergeStatus = android::hardware::boot::V1_1::MergeStatus;
+    using FiemapStatus = android::fiemap::FiemapStatus;
+
+    friend class SnapshotMergeStats;
+
+  public:
+    ~SnapshotManager();
+
+    // Return a new SnapshotManager instance, or null on error. The device
+    // pointer is owned for the lifetime of SnapshotManager. If null, a default
+    // instance will be created.
+    static std::unique_ptr<SnapshotManager> New(IDeviceInfo* device = nullptr);
+
+    // This is similar to New(), except designed specifically for first-stage
+    // init or recovery.
+    static std::unique_ptr<SnapshotManager> NewForFirstStageMount(IDeviceInfo* device = nullptr);
+
+    // Helper function for first-stage init to check whether a SnapshotManager
+    // might be needed to perform first-stage mounts.
+    static bool IsSnapshotManagerNeeded();
+
+    // Helper function for second stage init to restorecon on the rollback indicator.
+    static std::string GetGlobalRollbackIndicatorPath();
+
+    // ISnapshotManager overrides.
+    bool BeginUpdate() override;
+    bool CancelUpdate() override;
+    bool FinishedSnapshotWrites(bool wipe) override;
+    bool InitiateMerge() override;
+    UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
+                                   const std::function<bool()>& before_cancel = {}) override;
+    UpdateState GetUpdateState(double* progress = nullptr) override;
+    Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) override;
+    bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
+                           std::string* snapshot_path) override;
+    bool UnmapUpdateSnapshot(const std::string& target_partition_name) override;
+    bool NeedSnapshotsInFirstStageMount() override;
+    bool CreateLogicalAndSnapshotPartitions(
+            const std::string& super_device,
+            const std::chrono::milliseconds& timeout_ms = {}) override;
+    bool HandleImminentDataWipe(const std::function<void()>& callback = {}) override;
+    CreateResult RecoveryCreateSnapshotDevices() override;
+    CreateResult RecoveryCreateSnapshotDevices(
+            const std::unique_ptr<AutoDevice>& metadata_device) override;
+    bool Dump(std::ostream& os) override;
+    std::unique_ptr<AutoDevice> EnsureMetadataMounted() override;
+    ISnapshotMergeStats* GetSnapshotMergeStatsInstance() override;
 
   private:
     FRIEND_TEST(SnapshotTest, CleanFirstStageMount);
@@ -446,6 +491,7 @@
 
     std::string GetSnapshotBootIndicatorPath();
     std::string GetRollbackIndicatorPath();
+    std::string GetForwardMergeIndicatorPath();
 
     // Return the name of the device holding the "snapshot" or "snapshot-merge"
     // target. This may not be the final device presented via MapSnapshot(), if
@@ -516,6 +562,17 @@
     bool ShouldDeleteSnapshot(LockedFile* lock, 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.
+    bool UpdateForwardMergeIndicator(bool wipe);
+
+    // Helper for HandleImminentDataWipe.
+    // 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);
+
     std::string gsid_dir_;
     std::string metadata_dir_;
     std::unique_ptr<IDeviceInfo> device_;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
index 91dd34f..4caf632 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h
@@ -23,14 +23,12 @@
 namespace android {
 namespace snapshot {
 
-class SnapshotMergeStats {
+class ISnapshotMergeStats {
   public:
-    // Not thread safe.
-    static SnapshotMergeStats* GetInstance(SnapshotManager& manager);
-
+    virtual ~ISnapshotMergeStats() = default;
     // Called when merge starts or resumes.
-    bool Start();
-    void set_state(android::snapshot::UpdateState state);
+    virtual bool Start() = 0;
+    virtual void set_state(android::snapshot::UpdateState state) = 0;
 
     // Called when merge ends. Properly clean up permanent storage.
     class Result {
@@ -40,7 +38,19 @@
         // Time between successful Start() / Resume() to Finish().
         virtual std::chrono::steady_clock::duration merge_time() const = 0;
     };
-    std::unique_ptr<Result> Finish();
+    // Return nullptr if any failure.
+    virtual std::unique_ptr<Result> Finish() = 0;
+};
+
+class SnapshotMergeStats : public ISnapshotMergeStats {
+  public:
+    // Not thread safe.
+    static SnapshotMergeStats* GetInstance(SnapshotManager& manager);
+
+    // ISnapshotMergeStats overrides
+    bool Start() override;
+    void set_state(android::snapshot::UpdateState state) override;
+    std::unique_ptr<Result> Finish() override;
 
   private:
     bool ReadState();
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
new file mode 100644
index 0000000..9b2590c
--- /dev/null
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#include <libsnapshot/snapshot.h>
+
+namespace android::snapshot {
+
+class SnapshotManagerStub : public ISnapshotManager {
+  public:
+    // Create a stubbed snapshot manager. All calls into the stub fails.
+    static std::unique_ptr<ISnapshotManager> New();
+
+    // ISnapshotManager overrides.
+    bool BeginUpdate() override;
+    bool CancelUpdate() override;
+    bool FinishedSnapshotWrites(bool wipe) override;
+    bool InitiateMerge() override;
+    UpdateState ProcessUpdateState(const std::function<bool()>& callback = {},
+                                   const std::function<bool()>& before_cancel = {}) override;
+    UpdateState GetUpdateState(double* progress = nullptr) override;
+    Return CreateUpdateSnapshots(
+            const chromeos_update_engine::DeltaArchiveManifest& manifest) override;
+    bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params,
+                           std::string* snapshot_path) override;
+    bool UnmapUpdateSnapshot(const std::string& target_partition_name) override;
+    bool NeedSnapshotsInFirstStageMount() override;
+    bool CreateLogicalAndSnapshotPartitions(
+            const std::string& super_device,
+            const std::chrono::milliseconds& timeout_ms = {}) override;
+    bool HandleImminentDataWipe(const std::function<void()>& callback = {}) override;
+    CreateResult RecoveryCreateSnapshotDevices() override;
+    CreateResult RecoveryCreateSnapshotDevices(
+            const std::unique_ptr<AutoDevice>& metadata_device) override;
+    bool Dump(std::ostream& os) override;
+    std::unique_ptr<AutoDevice> EnsureMetadataMounted() override;
+    ISnapshotMergeStats* GetSnapshotMergeStatsInstance() override;
+};
+
+}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/power_test.cpp b/fs_mgr/libsnapshot/power_test.cpp
new file mode 100644
index 0000000..4d2548a
--- /dev/null
+++ b/fs_mgr/libsnapshot/power_test.cpp
@@ -0,0 +1,559 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <iostream>
+#include <random>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/parsedouble.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ext4_utils/ext4_utils.h>
+#include <fstab/fstab.h>
+#include <libdm/dm.h>
+#include <libfiemap/image_manager.h>
+
+using namespace std::chrono_literals;
+using namespace std::string_literals;
+using android::base::borrowed_fd;
+using android::base::unique_fd;
+using android::dm::DeviceMapper;
+using android::dm::DmDeviceState;
+using android::dm::DmTable;
+using android::dm::DmTargetSnapshot;
+using android::dm::SnapshotStorageMode;
+using android::fiemap::ImageManager;
+using android::fs_mgr::Fstab;
+
+namespace android {
+namespace snapshot {
+
+static void usage() {
+    std::cerr << "Usage:\n";
+    std::cerr << "  create <orig-payload> <new-payload>\n";
+    std::cerr << "\n";
+    std::cerr << "  Create a snapshot device containing the contents of\n";
+    std::cerr << "  orig-payload, and then write the contents of new-payload.\n";
+    std::cerr << "  The original files are not modified.\n";
+    std::cerr << "\n";
+    std::cerr << "  merge <fail-rate>\n";
+    std::cerr << "\n";
+    std::cerr << "  Merge the snapshot previously started by create, and wait\n";
+    std::cerr << "  for it to complete. Once done, it is compared to the\n";
+    std::cerr << "  new-payload for consistency. The original files are not \n";
+    std::cerr << "  modified. If a fail-rate is passed (as a fraction between 0\n";
+    std::cerr << "  and 100), every 10ms the device has that percent change of\n";
+    std::cerr << "  injecting a kernel crash.\n";
+    std::cerr << "\n";
+    std::cerr << "  check <new-payload>\n";
+    std::cerr << "  Verify that all artifacts are correct after a merge\n";
+    std::cerr << "  completes.\n";
+    std::cerr << "\n";
+    std::cerr << "  cleanup\n";
+    std::cerr << "  Remove all ImageManager artifacts from create/merge.\n";
+}
+
+class PowerTest final {
+  public:
+    PowerTest();
+    bool Run(int argc, char** argv);
+
+  private:
+    bool OpenImageManager();
+    bool Create(int argc, char** argv);
+    bool Merge(int argc, char** argv);
+    bool Check(int argc, char** argv);
+    bool Cleanup();
+    bool CleanupImage(const std::string& name);
+    bool SetupImages(const std::string& first_file, borrowed_fd second_fd);
+    bool MapImages();
+    bool MapSnapshot(SnapshotStorageMode mode);
+    bool GetMergeStatus(DmTargetSnapshot::Status* status);
+
+    static constexpr char kSnapshotName[] = "snapshot-power-test";
+    static constexpr char kSnapshotImageName[] = "snapshot-power-test-image";
+    static constexpr char kSnapshotCowName[] = "snapshot-power-test-cow";
+
+    DeviceMapper& dm_;
+    std::unique_ptr<ImageManager> images_;
+    std::string image_path_;
+    std::string cow_path_;
+    std::string snapshot_path_;
+};
+
+PowerTest::PowerTest() : dm_(DeviceMapper::Instance()) {}
+
+bool PowerTest::Run([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+    if (!OpenImageManager()) {
+        return false;
+    }
+
+    if (argc < 2) {
+        usage();
+        return false;
+    }
+    if (argv[1] == "create"s) {
+        return Create(argc, argv);
+    } else if (argv[1] == "merge"s) {
+        return Merge(argc, argv);
+    } else if (argv[1] == "check"s) {
+        return Check(argc, argv);
+    } else if (argv[1] == "cleanup"s) {
+        return Cleanup();
+    } else {
+        usage();
+        return false;
+    }
+}
+
+bool PowerTest::OpenImageManager() {
+    std::vector<std::string> dirs = {
+            "/data/gsi/test",
+            "/metadata/gsi/test",
+    };
+    for (const auto& dir : dirs) {
+        if (mkdir(dir.c_str(), 0700) && errno != EEXIST) {
+            std::cerr << "mkdir " << dir << ": " << strerror(errno) << "\n";
+            return false;
+        }
+    }
+
+    images_ = ImageManager::Open("/metadata/gsi/test", "/data/gsi/test");
+    if (!images_) {
+        std::cerr << "Could not open ImageManager\n";
+        return false;
+    }
+    return true;
+}
+
+bool PowerTest::Create(int argc, char** argv) {
+    if (argc < 4) {
+        usage();
+        return false;
+    }
+
+    std::string first = argv[2];
+    std::string second = argv[3];
+
+    unique_fd second_fd(open(second.c_str(), O_RDONLY));
+    if (second_fd < 0) {
+        std::cerr << "open " << second << ": " << strerror(errno) << "\n";
+        return false;
+    }
+
+    if (!Cleanup()) {
+        return false;
+    }
+    if (!SetupImages(first, second_fd)) {
+        return false;
+    }
+    if (!MapSnapshot(SnapshotStorageMode::Persistent)) {
+        return false;
+    }
+
+    struct stat s;
+    if (fstat(second_fd, &s)) {
+        std::cerr << "fstat " << second << ": " << strerror(errno) << "\n";
+        return false;
+    }
+
+    unique_fd snap_fd(open(snapshot_path_.c_str(), O_WRONLY));
+    if (snap_fd < 0) {
+        std::cerr << "open " << snapshot_path_ << ": " << strerror(errno) << "\n";
+        return false;
+    }
+
+    uint8_t chunk[4096];
+    uint64_t written = 0;
+    while (written < s.st_size) {
+        uint64_t remaining = s.st_size - written;
+        size_t bytes = (size_t)std::min((uint64_t)sizeof(chunk), remaining);
+        if (!android::base::ReadFully(second_fd, chunk, bytes)) {
+            std::cerr << "read " << second << ": " << strerror(errno) << "\n";
+            return false;
+        }
+        if (!android::base::WriteFully(snap_fd, chunk, bytes)) {
+            std::cerr << "write " << snapshot_path_ << ": " << strerror(errno) << "\n";
+            return false;
+        }
+        written += bytes;
+    }
+    if (fsync(snap_fd)) {
+        std::cerr << "fsync: " << strerror(errno) << "\n";
+        return false;
+    }
+
+    sync();
+
+    snap_fd = {};
+    if (!dm_.DeleteDeviceIfExists(kSnapshotName)) {
+        std::cerr << "could not delete dm device " << kSnapshotName << "\n";
+        return false;
+    }
+    if (!images_->UnmapImageIfExists(kSnapshotImageName)) {
+        std::cerr << "failed to unmap " << kSnapshotImageName << "\n";
+        return false;
+    }
+    if (!images_->UnmapImageIfExists(kSnapshotCowName)) {
+        std::cerr << "failed to unmap " << kSnapshotImageName << "\n";
+        return false;
+    }
+    return true;
+}
+
+bool PowerTest::Cleanup() {
+    if (!dm_.DeleteDeviceIfExists(kSnapshotName)) {
+        std::cerr << "could not delete dm device " << kSnapshotName << "\n";
+        return false;
+    }
+    if (!CleanupImage(kSnapshotImageName) || !CleanupImage(kSnapshotCowName)) {
+        return false;
+    }
+    return true;
+}
+
+bool PowerTest::CleanupImage(const std::string& name) {
+    if (!images_->UnmapImageIfExists(name)) {
+        std::cerr << "failed to unmap " << name << "\n";
+        return false;
+    }
+    if (images_->BackingImageExists(name) && !images_->DeleteBackingImage(name)) {
+        std::cerr << "failed to delete " << name << "\n";
+        return false;
+    }
+    return true;
+}
+
+bool PowerTest::SetupImages(const std::string& first, borrowed_fd second_fd) {
+    unique_fd first_fd(open(first.c_str(), O_RDONLY));
+    if (first_fd < 0) {
+        std::cerr << "open " << first << ": " << strerror(errno) << "\n";
+        return false;
+    }
+
+    struct stat s1, s2;
+    if (fstat(first_fd.get(), &s1)) {
+        std::cerr << "first stat: " << strerror(errno) << "\n";
+        return false;
+    }
+    if (fstat(second_fd.get(), &s2)) {
+        std::cerr << "second stat: " << strerror(errno) << "\n";
+        return false;
+    }
+
+    // Pick the bigger size of both images, rounding up to the nearest block.
+    uint64_t s1_size = (s1.st_size + 4095) & ~uint64_t(4095);
+    uint64_t s2_size = (s2.st_size + 4095) & ~uint64_t(4095);
+    uint64_t image_size = std::max(s1_size, s2_size) + (1024 * 1024 * 128);
+    if (!images_->CreateBackingImage(kSnapshotImageName, image_size, 0, nullptr)) {
+        std::cerr << "failed to create " << kSnapshotImageName << "\n";
+        return false;
+    }
+    // Use the same size for the cow.
+    if (!images_->CreateBackingImage(kSnapshotCowName, image_size, 0, nullptr)) {
+        std::cerr << "failed to create " << kSnapshotCowName << "\n";
+        return false;
+    }
+    if (!MapImages()) {
+        return false;
+    }
+
+    unique_fd image_fd(open(image_path_.c_str(), O_WRONLY));
+    if (image_fd < 0) {
+        std::cerr << "open: " << image_path_ << ": " << strerror(errno) << "\n";
+        return false;
+    }
+
+    uint8_t chunk[4096];
+    uint64_t written = 0;
+    while (written < s1.st_size) {
+        uint64_t remaining = s1.st_size - written;
+        size_t bytes = (size_t)std::min((uint64_t)sizeof(chunk), remaining);
+        if (!android::base::ReadFully(first_fd, chunk, bytes)) {
+            std::cerr << "read: " << strerror(errno) << "\n";
+            return false;
+        }
+        if (!android::base::WriteFully(image_fd, chunk, bytes)) {
+            std::cerr << "write: " << strerror(errno) << "\n";
+            return false;
+        }
+        written += bytes;
+    }
+    if (fsync(image_fd)) {
+        std::cerr << "fsync: " << strerror(errno) << "\n";
+        return false;
+    }
+
+    // Zero the first block of the COW.
+    unique_fd cow_fd(open(cow_path_.c_str(), O_WRONLY));
+    if (cow_fd < 0) {
+        std::cerr << "open: " << cow_path_ << ": " << strerror(errno) << "\n";
+        return false;
+    }
+
+    memset(chunk, 0, sizeof(chunk));
+    if (!android::base::WriteFully(cow_fd, chunk, sizeof(chunk))) {
+        std::cerr << "read: " << strerror(errno) << "\n";
+        return false;
+    }
+    if (fsync(cow_fd)) {
+        std::cerr << "fsync: " << strerror(errno) << "\n";
+        return false;
+    }
+    return true;
+}
+
+bool PowerTest::MapImages() {
+    if (!images_->MapImageDevice(kSnapshotImageName, 10s, &image_path_)) {
+        std::cerr << "failed to map " << kSnapshotImageName << "\n";
+        return false;
+    }
+    if (!images_->MapImageDevice(kSnapshotCowName, 10s, &cow_path_)) {
+        std::cerr << "failed to map " << kSnapshotCowName << "\n";
+        return false;
+    }
+    return true;
+}
+
+bool PowerTest::MapSnapshot(SnapshotStorageMode mode) {
+    uint64_t sectors;
+    {
+        unique_fd fd(open(image_path_.c_str(), O_RDONLY));
+        if (fd < 0) {
+            std::cerr << "open: " << image_path_ << ": " << strerror(errno) << "\n";
+            return false;
+        }
+        sectors = get_block_device_size(fd) / 512;
+    }
+
+    DmTable table;
+    table.Emplace<DmTargetSnapshot>(0, sectors, image_path_, cow_path_, mode, 8);
+    if (!dm_.CreateDevice(kSnapshotName, table, &snapshot_path_, 10s)) {
+        std::cerr << "failed to create snapshot device\n";
+        return false;
+    }
+    return true;
+}
+
+bool PowerTest::GetMergeStatus(DmTargetSnapshot::Status* status) {
+    std::vector<DeviceMapper::TargetInfo> targets;
+    if (!dm_.GetTableStatus(kSnapshotName, &targets)) {
+        std::cerr << "failed to get merge status\n";
+        return false;
+    }
+    if (targets.size() != 1) {
+        std::cerr << "merge device has wrong number of targets\n";
+        return false;
+    }
+    if (!DmTargetSnapshot::ParseStatusText(targets[0].data, status)) {
+        std::cerr << "could not parse merge target status text\n";
+        return false;
+    }
+    return true;
+}
+
+static std::string GetUserdataBlockDeviceName() {
+    Fstab fstab;
+    if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
+        return {};
+    }
+
+    auto entry = android::fs_mgr::GetEntryForMountPoint(&fstab, "/data");
+    if (!entry) {
+        return {};
+    }
+
+    auto prefix = "/dev/block/"s;
+    if (!android::base::StartsWith(entry->blk_device, prefix)) {
+        return {};
+    }
+    return entry->blk_device.substr(prefix.size());
+}
+
+bool PowerTest::Merge(int argc, char** argv) {
+    // Start an f2fs GC to really stress things. :TODO: figure out data device
+    auto userdata_dev = GetUserdataBlockDeviceName();
+    if (userdata_dev.empty()) {
+        std::cerr << "could not locate userdata block device\n";
+        return false;
+    }
+
+    auto cmd =
+            android::base::StringPrintf("echo 1 > /sys/fs/f2fs/%s/gc_urgent", userdata_dev.c_str());
+    system(cmd.c_str());
+
+    if (dm_.GetState(kSnapshotName) == DmDeviceState::INVALID) {
+        if (!MapImages()) {
+            return false;
+        }
+        if (!MapSnapshot(SnapshotStorageMode::Merge)) {
+            return false;
+        }
+    }
+
+    std::random_device r;
+    std::default_random_engine re(r());
+    std::uniform_real_distribution<double> dist(0.0, 100.0);
+
+    std::optional<double> failure_rate;
+    if (argc >= 3) {
+        double d;
+        if (!android::base::ParseDouble(argv[2], &d)) {
+            std::cerr << "Could not parse failure rate as double: " << argv[2] << "\n";
+            return false;
+        }
+        failure_rate = d;
+    }
+
+    while (true) {
+        DmTargetSnapshot::Status status;
+        if (!GetMergeStatus(&status)) {
+            return false;
+        }
+        if (!status.error.empty()) {
+            std::cerr << "merge reported error: " << status.error << "\n";
+            return false;
+        }
+        if (status.sectors_allocated == status.metadata_sectors) {
+            break;
+        }
+
+        std::cerr << status.sectors_allocated << " / " << status.metadata_sectors << "\n";
+
+        if (failure_rate && *failure_rate >= dist(re)) {
+            system("echo 1 > /proc/sys/kernel/sysrq");
+            system("echo c > /proc/sysrq-trigger");
+        }
+
+        std::this_thread::sleep_for(10ms);
+    }
+
+    std::cout << "Merge completed.\n";
+    return true;
+}
+
+bool PowerTest::Check([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+    if (argc < 3) {
+        std::cerr << "Expected argument: <new-image-path>\n";
+        return false;
+    }
+    std::string md_path, image_path;
+    std::string canonical_path = argv[2];
+
+    if (!dm_.GetDmDevicePathByName(kSnapshotName, &md_path)) {
+        std::cerr << "could not get dm-path for merge device\n";
+        return false;
+    }
+    if (!images_->GetMappedImageDevice(kSnapshotImageName, &image_path)) {
+        std::cerr << "could not get image path\n";
+        return false;
+    }
+
+    unique_fd md_fd(open(md_path.c_str(), O_RDONLY));
+    if (md_fd < 0) {
+        std::cerr << "open: " << md_path << ": " << strerror(errno) << "\n";
+        return false;
+    }
+    unique_fd image_fd(open(image_path.c_str(), O_RDONLY));
+    if (image_fd < 0) {
+        std::cerr << "open: " << image_path << ": " << strerror(errno) << "\n";
+        return false;
+    }
+    unique_fd canonical_fd(open(canonical_path.c_str(), O_RDONLY));
+    if (canonical_fd < 0) {
+        std::cerr << "open: " << canonical_path << ": " << strerror(errno) << "\n";
+        return false;
+    }
+
+    struct stat s;
+    if (fstat(canonical_fd, &s)) {
+        std::cerr << "fstat: " << canonical_path << ": " << strerror(errno) << "\n";
+        return false;
+    }
+    uint64_t canonical_size = s.st_size;
+    uint64_t md_size = get_block_device_size(md_fd);
+    uint64_t image_size = get_block_device_size(image_fd);
+    if (image_size != md_size) {
+        std::cerr << "image size does not match merge device size\n";
+        return false;
+    }
+    if (canonical_size > image_size) {
+        std::cerr << "canonical size " << canonical_size << " is greater than image size "
+                  << image_size << "\n";
+        return false;
+    }
+
+    constexpr size_t kBlockSize = 4096;
+    uint8_t canonical_buffer[kBlockSize];
+    uint8_t image_buffer[kBlockSize];
+    uint8_t md_buffer[kBlockSize];
+
+    uint64_t remaining = canonical_size;
+    uint64_t blockno = 0;
+    while (remaining) {
+        size_t bytes = (size_t)std::min((uint64_t)kBlockSize, remaining);
+        if (!android::base::ReadFully(canonical_fd, canonical_buffer, bytes)) {
+            std::cerr << "read: " << canonical_buffer << ": " << strerror(errno) << "\n";
+            return false;
+        }
+        if (!android::base::ReadFully(image_fd, image_buffer, bytes)) {
+            std::cerr << "read: " << image_buffer << ": " << strerror(errno) << "\n";
+            return false;
+        }
+        if (!android::base::ReadFully(md_fd, md_buffer, bytes)) {
+            std::cerr << "read: " << md_buffer << ": " << strerror(errno) << "\n";
+            return false;
+        }
+        if (memcmp(canonical_buffer, image_buffer, bytes)) {
+            std::cerr << "canonical and image differ at block " << blockno << "\n";
+            return false;
+        }
+        if (memcmp(canonical_buffer, md_buffer, bytes)) {
+            std::cerr << "canonical and image differ at block " << blockno << "\n";
+            return false;
+        }
+
+        remaining -= bytes;
+        blockno++;
+    }
+
+    std::cout << "Images all match.\n";
+    return true;
+}
+
+}  // namespace snapshot
+}  // namespace android
+
+int main(int argc, char** argv) {
+    android::snapshot::PowerTest test;
+
+    if (!test.Run(argc, argv)) {
+        std::cerr << "Unexpected error running test." << std::endl;
+        return 1;
+    }
+    fflush(stdout);
+    return 0;
+}
diff --git a/fs_mgr/libsnapshot/run_power_test.sh b/fs_mgr/libsnapshot/run_power_test.sh
new file mode 100755
index 0000000..dc03dc9
--- /dev/null
+++ b/fs_mgr/libsnapshot/run_power_test.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+set -e
+
+if [ -z "$FAIL_RATE" ]; then
+    FAIL_RATE=5.0
+fi
+if [ ! -z "$ANDROID_SERIAL" ]; then
+    DEVICE_ARGS=-s $ANDROID_SERIAL
+else
+    DEVICE_ARGS=
+fi
+
+TEST_BIN=/data/nativetest64/snapshot_power_test/snapshot_power_test
+
+while :
+do
+    adb $DEVICE_ARGS wait-for-device
+    adb $DEVICE_ARGS root
+    adb $DEVICE_ARGS shell rm $TEST_BIN
+    adb $DEVICE_ARGS sync data
+    set +e
+    output=$(adb $DEVICE_ARGS shell $TEST_BIN merge $FAIL_RATE 2>&1)
+    set -e
+    if [[ "$output" == *"Merge completed"* ]]; then
+        echo "Merge completed."
+        break
+    fi
+    if [[ "$output" == *"Unexpected error"* ]]; then
+        echo "Unexpected error."
+        exit 1
+    fi
+done
+
+adb $DEVICE_ARGS shell $TEST_BIN check $1
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index ed052b1..eafeea2 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -228,10 +228,17 @@
         return false;
     }
 
-    // It's okay if these fail - first-stage init performs a deeper check after
+    // It's okay if these fail:
+    // - For SnapshotBoot and Rollback, first-stage init performs a deeper check after
     // reading the indicator file, so it's not a problem if it still exists
     // after the update completes.
-    std::vector<std::string> files = {GetSnapshotBootIndicatorPath(), GetRollbackIndicatorPath()};
+    // - For ForwardMerge, FinishedSnapshotWrites asserts that the existence of the indicator
+    // matches the incoming update.
+    std::vector<std::string> files = {
+            GetSnapshotBootIndicatorPath(),
+            GetRollbackIndicatorPath(),
+            GetForwardMergeIndicatorPath(),
+    };
     for (const auto& file : files) {
         RemoveFileIfExists(file);
     }
@@ -241,7 +248,7 @@
     return WriteUpdateState(lock, UpdateState::None);
 }
 
-bool SnapshotManager::FinishedSnapshotWrites() {
+bool SnapshotManager::FinishedSnapshotWrites(bool wipe) {
     auto lock = LockExclusive();
     if (!lock) return false;
 
@@ -261,6 +268,10 @@
         return false;
     }
 
+    if (!UpdateForwardMergeIndicator(wipe)) {
+        return false;
+    }
+
     // This file is written on boot to detect whether a rollback occurred. It
     // MUST NOT exist before rebooting, otherwise, we're at risk of deleting
     // snapshots too early.
@@ -992,6 +1003,10 @@
     return metadata_dir_ + "/" + android::base::Basename(kRollbackIndicatorPath);
 }
 
+std::string SnapshotManager::GetForwardMergeIndicatorPath() {
+    return metadata_dir_ + "/allow-forward-merge";
+}
+
 void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) {
     RemoveAllUpdateState(lock);
 }
@@ -2438,6 +2453,9 @@
     ss << "Rollback indicator: "
        << (access(GetRollbackIndicatorPath().c_str(), F_OK) == 0 ? "exists" : strerror(errno))
        << std::endl;
+    ss << "Forward merge indicator: "
+       << (access(GetForwardMergeIndicatorPath().c_str(), F_OK) == 0 ? "exists" : strerror(errno))
+       << std::endl;
 
     bool ok = true;
     std::vector<std::string> snapshots;
@@ -2504,17 +2522,39 @@
         return false;
     }
 
-    UpdateState state = ProcessUpdateState([&]() -> bool {
-        callback();
+    auto process_callback = [&]() -> bool {
+        if (callback) {
+            callback();
+        }
         return true;
-    });
+    };
+    if (!ProcessUpdateStateOnDataWipe(true /* allow_forward_merge */, process_callback)) {
+        return false;
+    }
+
+    // Nothing should be depending on partitions now, so unmap them all.
+    if (!UnmapAllPartitions()) {
+        LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash.";
+    }
+    return true;
+}
+
+bool 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;
         case UpdateState::Unverified: {
-            // If an OTA was just applied but has not yet started merging, we
+            // If an OTA was just applied but has not yet started merging:
+            //
+            // - if forward merge is allowed, initiate merge and call
+            // ProcessUpdateState again.
+            //
+            // - if forward merge is not allowed, we
             // have no choice but to revert slots, because the current slot will
             // immediately become unbootable. Rather than wait for the device
             // to reboot N times until a rollback, we proactively disable the
@@ -2524,8 +2564,17 @@
             // as an error here.
             auto slot = GetCurrentSlot();
             if (slot == Slot::Target) {
+                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);
+                }
+
                 LOG(ERROR) << "Reverting to old slot since update will be deleted.";
                 device_->SetSlotAsUnbootable(slot_number);
+            } else {
+                LOG(INFO) << "Booting from " << slot << " slot, no action is taken.";
             }
             break;
         }
@@ -2537,11 +2586,6 @@
         default:
             break;
     }
-
-    // Nothing should be depending on partitions now, so unmap them all.
-    if (!UnmapAllPartitions()) {
-        LOG(ERROR) << "Unable to unmap all partitions; fastboot may fail to flash.";
-    }
     return true;
 }
 
@@ -2587,6 +2631,20 @@
         LOG(ERROR) << "Couldn't mount Metadata.";
         return CreateResult::NOT_CREATED;
     }
+    return RecoveryCreateSnapshotDevices(mount);
+}
+
+CreateResult SnapshotManager::RecoveryCreateSnapshotDevices(
+        const std::unique_ptr<AutoDevice>& metadata_device) {
+    if (!device_->IsRecovery()) {
+        LOG(ERROR) << __func__ << " is only allowed in recovery.";
+        return CreateResult::NOT_CREATED;
+    }
+
+    if (metadata_device == nullptr || !metadata_device->HasDevice()) {
+        LOG(ERROR) << "Metadata not mounted.";
+        return CreateResult::NOT_CREATED;
+    }
 
     auto state_file = GetStateFilePath();
     if (access(state_file.c_str(), F_OK) != 0 && errno == ENOENT) {
@@ -2608,5 +2666,28 @@
     return CreateResult::CREATED;
 }
 
+bool SnapshotManager::UpdateForwardMergeIndicator(bool wipe) {
+    auto path = GetForwardMergeIndicatorPath();
+
+    if (!wipe) {
+        LOG(INFO) << "Wipe is not scheduled. Deleting forward merge indicator.";
+        return RemoveFileIfExists(path);
+    }
+
+    // TODO(b/152094219): Don't forward merge if no CoW file is allocated.
+
+    LOG(INFO) << "Wipe will be scheduled. Allowing forward merge of snapshots.";
+    if (!android::base::WriteStringToFile("1", path)) {
+        PLOG(ERROR) << "Unable to write forward merge indicator: " << path;
+        return false;
+    }
+
+    return true;
+}
+
+ISnapshotMergeStats* SnapshotManager::GetSnapshotMergeStatsInstance() {
+    return SnapshotMergeStats::GetInstance(*this);
+}
+
 }  // namespace snapshot
 }  // namespace android
diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp
new file mode 100644
index 0000000..2747b03
--- /dev/null
+++ b/fs_mgr/libsnapshot/snapshot_stub.cpp
@@ -0,0 +1,125 @@
+// 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 <libsnapshot/snapshot_stub.h>
+
+#include <android-base/logging.h>
+
+#include <libsnapshot/snapshot_stats.h>
+
+using android::fs_mgr::CreateLogicalPartitionParams;
+using chromeos_update_engine::DeltaArchiveManifest;
+
+namespace android::snapshot {
+
+std::unique_ptr<ISnapshotManager> SnapshotManagerStub::New() {
+    return std::make_unique<SnapshotManagerStub>();
+}
+
+bool SnapshotManagerStub::BeginUpdate() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
+bool SnapshotManagerStub::CancelUpdate() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
+bool SnapshotManagerStub::FinishedSnapshotWrites(bool) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
+bool SnapshotManagerStub::InitiateMerge() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
+UpdateState SnapshotManagerStub::ProcessUpdateState(const std::function<bool()>&,
+                                                    const std::function<bool()>&) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return UpdateState::None;
+}
+
+UpdateState SnapshotManagerStub::GetUpdateState(double*) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return UpdateState::None;
+}
+
+Return SnapshotManagerStub::CreateUpdateSnapshots(const DeltaArchiveManifest&) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return Return::Error();
+}
+
+bool SnapshotManagerStub::MapUpdateSnapshot(const CreateLogicalPartitionParams&, std::string*) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
+bool SnapshotManagerStub::UnmapUpdateSnapshot(const std::string&) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
+bool SnapshotManagerStub::NeedSnapshotsInFirstStageMount() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
+bool SnapshotManagerStub::CreateLogicalAndSnapshotPartitions(const std::string&,
+                                                             const std::chrono::milliseconds&) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
+bool SnapshotManagerStub::HandleImminentDataWipe(const std::function<void()>&) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
+CreateResult SnapshotManagerStub::RecoveryCreateSnapshotDevices() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return CreateResult::ERROR;
+}
+
+CreateResult SnapshotManagerStub::RecoveryCreateSnapshotDevices(
+        const std::unique_ptr<AutoDevice>&) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return CreateResult::ERROR;
+}
+
+bool SnapshotManagerStub::Dump(std::ostream&) {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return false;
+}
+
+std::unique_ptr<AutoDevice> SnapshotManagerStub::EnsureMetadataMounted() {
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return nullptr;
+}
+
+class SnapshotMergeStatsStub : public ISnapshotMergeStats {
+    bool Start() override { return false; }
+    void set_state(android::snapshot::UpdateState) override {}
+    std::unique_ptr<Result> Finish() override { return nullptr; }
+};
+
+ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() {
+    static SnapshotMergeStatsStub snapshot_merge_stats;
+    LOG(ERROR) << __FUNCTION__ << " should never be called.";
+    return &snapshot_merge_stats;
+}
+
+}  // namespace android::snapshot
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 855451d..53a37be 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -41,6 +41,11 @@
 #include <libsnapshot/test_helpers.h>
 #include "utility.h"
 
+// Mock classes are not used. Header included to ensure mocked class definition aligns with the
+// class itself.
+#include <libsnapshot/mock_device_info.h>
+#include <libsnapshot/mock_snapshot.h>
+
 namespace android {
 namespace snapshot {
 
@@ -320,7 +325,7 @@
     // Simulate a reboot into the new slot.
     AssertionResult SimulateReboot() {
         lock_ = nullptr;
-        if (!sm->FinishedSnapshotWrites()) {
+        if (!sm->FinishedSnapshotWrites(false)) {
             return AssertionFailure();
         }
         if (!dm_.DeleteDevice("test_partition_b")) {
@@ -424,7 +429,7 @@
 }
 
 TEST_F(SnapshotTest, NoMergeBeforeReboot) {
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // Merge should fail, since the slot hasn't changed.
     ASSERT_FALSE(sm->InitiateMerge());
@@ -440,7 +445,7 @@
 }
 
 TEST_F(SnapshotTest, FirstStageMountAfterRollback) {
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // We didn't change the slot, so we shouldn't need snapshots.
     TestDeviceInfo* info = new TestDeviceInfo(fake_super);
@@ -476,7 +481,7 @@
     lock_ = nullptr;
 
     // Done updating.
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     test_device->set_slot_suffix("_b");
     ASSERT_TRUE(sm->InitiateMerge());
@@ -1007,7 +1012,7 @@
         ASSERT_TRUE(IsPartitionUnchanged(name));
     }
 
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // Simulate shutting down the device.
     ASSERT_TRUE(UnmapAll());
@@ -1139,7 +1144,7 @@
         ASSERT_TRUE(IsPartitionUnchanged(name));
     }
 
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // Simulate shutting down the device.
     ASSERT_TRUE(UnmapAll());
@@ -1171,7 +1176,7 @@
 // Test that if an update is applied but not booted into, it can be canceled.
 TEST_F(SnapshotUpdateTest, CancelAfterApply) {
     ASSERT_TRUE(sm->BeginUpdate());
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
     ASSERT_TRUE(sm->CancelUpdate());
 }
 
@@ -1188,7 +1193,7 @@
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
     ASSERT_TRUE(MapUpdateSnapshots());
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // Simulate shutting down the device.
     ASSERT_TRUE(UnmapAll());
@@ -1295,7 +1300,7 @@
         ASSERT_TRUE(IsPartitionUnchanged(name));
     }
 
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 }
 
 TEST_F(SnapshotUpdateTest, MergeCannotRemoveCow) {
@@ -1324,7 +1329,7 @@
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
     ASSERT_TRUE(MapUpdateSnapshots());
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // Simulate shutting down the device.
     ASSERT_TRUE(UnmapAll());
@@ -1428,7 +1433,7 @@
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
     ASSERT_TRUE(MapUpdateSnapshots());
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // Simulate shutting down the device.
     ASSERT_TRUE(UnmapAll());
@@ -1460,7 +1465,7 @@
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
     ASSERT_TRUE(MapUpdateSnapshots());
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // Simulate shutting down the device.
     ASSERT_TRUE(UnmapAll());
@@ -1485,7 +1490,7 @@
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
     ASSERT_TRUE(MapUpdateSnapshots());
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // Simulate shutting down the device.
     ASSERT_TRUE(UnmapAll());
@@ -1498,7 +1503,46 @@
     ASSERT_TRUE(new_sm->HandleImminentDataWipe());
     EXPECT_EQ(new_sm->GetUpdateState(), UpdateState::None);
     EXPECT_FALSE(test_device->IsSlotUnbootable(0));
-    EXPECT_FALSE(test_device->IsSlotUnbootable(0));
+    EXPECT_FALSE(test_device->IsSlotUnbootable(1));
+}
+
+// Test update package that requests data wipe.
+TEST_F(SnapshotUpdateTest, DataWipeRequiredInPackage) {
+    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;
+    }
+
+    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 = SnapshotManager::NewForFirstStageMount(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 = SnapshotManager::NewForFirstStageMount(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_F(SnapshotUpdateTest, Hashtree) {
@@ -1533,7 +1577,7 @@
     ASSERT_TRUE(WriteSnapshotAndHash("sys_b", partition_size));
 
     // Finish update.
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // Simulate shutting down the device.
     ASSERT_TRUE(UnmapAll());
@@ -1569,7 +1613,7 @@
     ASSERT_EQ(1u, table.size());
     EXPECT_TRUE(table[0].IsOverflowSnapshot());
 
-    ASSERT_FALSE(sm->FinishedSnapshotWrites())
+    ASSERT_FALSE(sm->FinishedSnapshotWrites(false))
             << "FinishedSnapshotWrites should detect overflow of CoW device.";
 }
 
@@ -1623,7 +1667,7 @@
     ASSERT_TRUE(sm->BeginUpdate());
     ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
     ASSERT_TRUE(MapUpdateSnapshots());
-    ASSERT_TRUE(sm->FinishedSnapshotWrites());
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
 
     // Simulate shutting down the device.
     ASSERT_TRUE(UnmapAll());
@@ -1714,7 +1758,6 @@
   protected:
     void SetUp() override {
         if (!is_virtual_ab_) GTEST_SKIP() << "Test for Virtual A/B devices only";
-        GTEST_SKIP() << "WIP failure b/149738928";
 
         SnapshotTest::SetUp();
         userdata_ = std::make_unique<LowSpaceUserdata>();
diff --git a/fs_mgr/tests/Android.bp b/fs_mgr/tests/Android.bp
index 4f6ec5a..f68ab87 100644
--- a/fs_mgr/tests/Android.bp
+++ b/fs_mgr/tests/Android.bp
@@ -17,7 +17,7 @@
     test_suites: [
         "cts",
         "device-tests",
-        "vts",
+        "vts10",
     ],
     compile_multilib: "both",
     multilib: {
diff --git a/fs_mgr/tests/AndroidTest.xml b/fs_mgr/tests/AndroidTest.xml
index 0ff8995..de835b3 100644
--- a/fs_mgr/tests/AndroidTest.xml
+++ b/fs_mgr/tests/AndroidTest.xml
@@ -21,6 +21,9 @@
         <option name="push" value="CtsFsMgrTestCases->/data/local/tmp/CtsFsMgrTestCases" />
         <option name="append-bitness" value="true" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="throw-on-error" value="false" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
         <option name="module-name" value="CtsFsMgrTestCases" />
diff --git a/fs_mgr/tests/adb-remount-test.sh b/fs_mgr/tests/adb-remount-test.sh
index cf324fe..82c4262 100755
--- a/fs_mgr/tests/adb-remount-test.sh
+++ b/fs_mgr/tests/adb-remount-test.sh
@@ -732,6 +732,7 @@
   grep -v \
     -e "^\(overlay\|tmpfs\|none\|sysfs\|proc\|selinuxfs\|debugfs\|bpf\) " \
     -e "^\(binfmt_misc\|cg2_bpf\|pstore\|tracefs\|adb\|mtp\|ptp\|devpts\) " \
+    -e " functionfs " \
     -e "^\(/data/media\|/dev/block/loop[0-9]*\) " \
     -e "^rootfs / rootfs rw," \
     -e " /\(cache\|mnt/scratch\|mnt/vendor/persist\|persist\|metadata\) "
diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp
index 3fec608..27c8aae 100644
--- a/fs_mgr/tests/fs_mgr_test.cpp
+++ b/fs_mgr/tests/fs_mgr_test.cpp
@@ -1020,6 +1020,8 @@
     ASSERT_TRUE(ReadFstabFromFile("/proc/mounts", &proc_mounts)) << "Failed to read /proc/mounts";
     auto mounted_entry = GetEntryForMountPoint(&proc_mounts, "/data");
     ASSERT_NE(mounted_entry, nullptr) << "/data is not mounted";
-    ASSERT_NE(nullptr, fs_mgr_get_mounted_entry_for_userdata(&fstab, *mounted_entry))
+    std::string block_device;
+    ASSERT_TRUE(android::base::Realpath(mounted_entry->blk_device, &block_device));
+    ASSERT_NE(nullptr, fs_mgr_get_mounted_entry_for_userdata(&fstab, block_device))
             << "/data wasn't mounted from default fstab";
 }
diff --git a/healthd/BatteryMonitor.cpp b/healthd/BatteryMonitor.cpp
index 8e9e074..599f500 100644
--- a/healthd/BatteryMonitor.cpp
+++ b/healthd/BatteryMonitor.cpp
@@ -233,6 +233,15 @@
     return value;
 }
 
+bool BatteryMonitor::isScopedPowerSupply(const char* name) {
+    constexpr char kScopeDevice[] = "Device";
+
+    String8 path;
+    path.appendFormat("%s/%s/scope", POWER_SUPPLY_SYSFS_PATH, name);
+    std::string scope;
+    return (readFromFile(path, &scope) > 0 && scope == kScopeDevice);
+}
+
 void BatteryMonitor::updateValues(void) {
     initHealthInfo(mHealthInfo.get());
 
@@ -249,7 +258,7 @@
     props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
 
     if (!mHealthdConfig->batteryCurrentNowPath.isEmpty())
-        props.batteryCurrent = getIntField(mHealthdConfig->batteryCurrentNowPath) / 1000;
+        props.batteryCurrent = getIntField(mHealthdConfig->batteryCurrentNowPath);
 
     if (!mHealthdConfig->batteryFullChargePath.isEmpty())
         props.batteryFullCharge = getIntField(mHealthdConfig->batteryFullChargePath);
@@ -547,6 +556,11 @@
                 break;
 
             case ANDROID_POWER_SUPPLY_TYPE_BATTERY:
+                // Some devices expose the battery status of sub-component like
+                // stylus. Such a device-scoped battery info needs to be skipped
+                // in BatteryMonitor, which is intended to report the status of
+                // the battery supplying the power to the whole system.
+                if (isScopedPowerSupply(name)) continue;
                 mBatteryDevicePresent = true;
 
                 if (mHealthdConfig->batteryStatusPath.isEmpty()) {
diff --git a/healthd/include/healthd/BatteryMonitor.h b/healthd/include/healthd/BatteryMonitor.h
index d41a374..fadb5a5 100644
--- a/healthd/include/healthd/BatteryMonitor.h
+++ b/healthd/include/healthd/BatteryMonitor.h
@@ -78,6 +78,7 @@
     PowerSupplyType readPowerSupplyType(const String8& path);
     bool getBooleanField(const String8& path);
     int getIntField(const String8& path);
+    bool isScopedPowerSupply(const char* name);
 };
 
 }; // namespace android
diff --git a/init/Android.bp b/init/Android.bp
index 72a7bfe..edf9099 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -36,10 +36,12 @@
     "util.cpp",
 ]
 init_device_sources = [
+    "block_dev_initializer.cpp",
     "bootchart.cpp",
     "builtins.cpp",
     "devices.cpp",
     "firmware_handler.cpp",
+    "first_stage_console.cpp",
     "first_stage_init.cpp",
     "first_stage_mount.cpp",
     "fscrypt_init_extensions.cpp",
@@ -129,6 +131,7 @@
         "libpropertyinfoparser",
         "libsnapshot_init",
         "lib_apex_manifest_proto_lite",
+        "update_metadata-protos",
     ],
     shared_libs: [
         "libbacktrace",
@@ -240,6 +243,7 @@
         "firmware_handler_test.cpp",
         "init_test.cpp",
         "keychords_test.cpp",
+        "oneshot_on_test.cpp",
         "persistent_properties_test.cpp",
         "property_service_test.cpp",
         "property_type_test.cpp",
@@ -256,7 +260,7 @@
     test_suites: [
         "cts",
         "device-tests",
-        "vts",
+        "vts10",
     ],
 }
 
diff --git a/init/Android.mk b/init/Android.mk
index 07b0f95..da94daf 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -48,7 +48,9 @@
 include $(CLEAR_VARS)
 LOCAL_CPPFLAGS := $(init_cflags)
 LOCAL_SRC_FILES := \
+    block_dev_initializer.cpp \
     devices.cpp \
+    first_stage_console.cpp \
     first_stage_init.cpp \
     first_stage_main.cpp \
     first_stage_mount.cpp \
@@ -105,13 +107,13 @@
     libgsi \
     libcom.android.sysprop.apex \
     liblzma \
-    libdexfile_support_static \
-    libunwindstack \
-    libbacktrace \
+    libunwindstack_no_dex \
+    libbacktrace_no_dex \
     libmodprobe \
     libext2_uuid \
     libprotobuf-cpp-lite \
     libsnapshot_init \
+    update_metadata-protos \
 
 LOCAL_SANITIZE := signed-integer-overflow
 # First stage init is weird: it may start without stdout/stderr, and no /proc.
diff --git a/init/AndroidTest.xml b/init/AndroidTest.xml
index 920dc6c..17f509a 100644
--- a/init/AndroidTest.xml
+++ b/init/AndroidTest.xml
@@ -24,6 +24,9 @@
         <option name="push" value="CtsInitTestCases->/data/local/tmp/CtsInitTestCases" />
         <option name="append-bitness" value="true" />
     </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="throw-on-error" value="false" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
         <option name="module-name" value="CtsInitTestCases" />
diff --git a/init/README.md b/init/README.md
index 4f0a7ec..13f1bac 100644
--- a/init/README.md
+++ b/init/README.md
@@ -720,23 +720,35 @@
   characteristics in a device agnostic manner.
 
 Init responds to properties that begin with `ctl.`.  These properties take the format of
-`ctl.<command>` and the _value_ of the system property is used as a parameter, for example:
-`SetProperty("ctl.start", "logd")` will run the `start` command on `logd`.  Note that these
+`ctl.[<target>_]<command>` and the _value_ of the system property is used as a parameter.  The
+_target_ is optional and specifies the service option that _value_ is meant to match with.  There is
+only one option for _target_, `interface` which indicates that _value_ will refer to an interface
+that a service provides and not the service name itself.
+
+For example:
+
+`SetProperty("ctl.start", "logd")` will run the `start` command on `logd`.
+
+`SetProperty("ctl.interface_start", "aidl/aidl_lazy_test_1")` will run the `start` command on the
+service that exposes the `aidl aidl_lazy_test_1` interface.
+
+Note that these
 properties are only settable; they will have no value when read.
 
-`ctl.start` \
-`ctl.restart` \
-`ctl.stop`
-> These are equivalent to using the `start`, `restart`, and `stop` commands on the service specified
+The _commands_ are listed below.
+
+`start` \
+`restart` \
+`stop` \
+These are equivalent to using the `start`, `restart`, and `stop` commands on the service specified
 by the _value_ of the property.
 
-`ctl.interface_start` \
-`ctl.interface_restart` \
-`ctl.interface_stop`
-> These are equivalent to using the `interface_start`, `interface_restart`, and `interface_stop`
-commands on the interface specified by the _value_ of the property.
+`oneshot_one` and `oneshot_off` will turn on or off the _oneshot_
+flag for the service specified by the _value_ of the property.  This is
+particularly intended for services that are conditionally lazy HALs.  When
+they are lazy HALs, oneshot must be on, otherwise oneshot should be off.
 
-`ctl.sigstop_on` and `ctl.sigstop_off` will turn on or off the _sigstop_ feature for the service
+`sigstop_on` and `sigstop_off` will turn on or off the _sigstop_ feature for the service
 specified by the _value_ of the property.  See the _Debugging init_ section below for more details
 about this feature.
 
diff --git a/init/block_dev_initializer.cpp b/init/block_dev_initializer.cpp
new file mode 100644
index 0000000..b423f86
--- /dev/null
+++ b/init/block_dev_initializer.cpp
@@ -0,0 +1,148 @@
+// 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 <chrono>
+#include <string_view>
+#include <vector>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <fs_mgr.h>
+
+#include "block_dev_initializer.h"
+
+namespace android {
+namespace init {
+
+using android::base::Timer;
+using namespace std::chrono_literals;
+
+BlockDevInitializer::BlockDevInitializer() : uevent_listener_(16 * 1024 * 1024) {
+    auto boot_devices = android::fs_mgr::GetBootDevices();
+    device_handler_ = std::make_unique<DeviceHandler>(
+            std::vector<Permissions>{}, std::vector<SysfsPermissions>{}, std::vector<Subsystem>{},
+            std::move(boot_devices), false);
+}
+
+bool BlockDevInitializer::InitDeviceMapper() {
+    const std::string dm_path = "/devices/virtual/misc/device-mapper";
+    bool found = false;
+    auto dm_callback = [this, &dm_path, &found](const Uevent& uevent) {
+        if (uevent.path == dm_path) {
+            device_handler_->HandleUevent(uevent);
+            found = true;
+            return ListenerAction::kStop;
+        }
+        return ListenerAction::kContinue;
+    };
+    uevent_listener_.RegenerateUeventsForPath("/sys" + dm_path, dm_callback);
+    if (!found) {
+        LOG(INFO) << "device-mapper device not found in /sys, waiting for its uevent";
+        Timer t;
+        uevent_listener_.Poll(dm_callback, 10s);
+        LOG(INFO) << "Wait for device-mapper returned after " << t;
+    }
+    if (!found) {
+        LOG(ERROR) << "device-mapper device not found after polling timeout";
+        return false;
+    }
+    return true;
+}
+
+ListenerAction BlockDevInitializer::HandleUevent(const Uevent& uevent,
+                                                 std::set<std::string>* devices) {
+    // Ignore everything that is not a block device.
+    if (uevent.subsystem != "block") {
+        return ListenerAction::kContinue;
+    }
+
+    auto name = uevent.partition_name;
+    if (name.empty()) {
+        size_t base_idx = uevent.path.rfind('/');
+        if (base_idx == std::string::npos) {
+            return ListenerAction::kContinue;
+        }
+        name = uevent.path.substr(base_idx + 1);
+    }
+
+    auto iter = devices->find(name);
+    if (iter == devices->end()) {
+        return ListenerAction::kContinue;
+    }
+
+    LOG(VERBOSE) << __PRETTY_FUNCTION__ << ": found partition: " << name;
+
+    devices->erase(iter);
+    device_handler_->HandleUevent(uevent);
+    return devices->empty() ? ListenerAction::kStop : ListenerAction::kContinue;
+}
+
+bool BlockDevInitializer::InitDevices(std::set<std::string> devices) {
+    auto uevent_callback = [&, this](const Uevent& uevent) -> ListenerAction {
+        return HandleUevent(uevent, &devices);
+    };
+    uevent_listener_.RegenerateUevents(uevent_callback);
+
+    // UeventCallback() will remove found partitions from |devices|. So if it
+    // isn't empty here, it means some partitions are not found.
+    if (!devices.empty()) {
+        LOG(INFO) << __PRETTY_FUNCTION__
+                  << ": partition(s) not found in /sys, waiting for their uevent(s): "
+                  << android::base::Join(devices, ", ");
+        Timer t;
+        uevent_listener_.Poll(uevent_callback, 10s);
+        LOG(INFO) << "Wait for partitions returned after " << t;
+    }
+
+    if (!devices.empty()) {
+        LOG(ERROR) << __PRETTY_FUNCTION__ << ": partition(s) not found after polling timeout: "
+                   << android::base::Join(devices, ", ");
+        return false;
+    }
+    return true;
+}
+
+// Creates "/dev/block/dm-XX" for dm nodes by running coldboot on /sys/block/dm-XX.
+bool BlockDevInitializer::InitDmDevice(const std::string& device) {
+    const std::string device_name(basename(device.c_str()));
+    const std::string syspath = "/sys/block/" + device_name;
+    bool found = false;
+
+    auto uevent_callback = [&device_name, &device, this, &found](const Uevent& uevent) {
+        if (uevent.device_name == device_name) {
+            LOG(VERBOSE) << "Creating device-mapper device : " << device;
+            device_handler_->HandleUevent(uevent);
+            found = true;
+            return ListenerAction::kStop;
+        }
+        return ListenerAction::kContinue;
+    };
+
+    uevent_listener_.RegenerateUeventsForPath(syspath, uevent_callback);
+    if (!found) {
+        LOG(INFO) << "dm device '" << device << "' not found in /sys, waiting for its uevent";
+        Timer t;
+        uevent_listener_.Poll(uevent_callback, 10s);
+        LOG(INFO) << "wait for dm device '" << device << "' returned after " << t;
+    }
+    if (!found) {
+        LOG(ERROR) << "dm device '" << device << "' not found after polling timeout";
+        return false;
+    }
+    return true;
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/block_dev_initializer.h b/init/block_dev_initializer.h
new file mode 100644
index 0000000..0d4c6e9
--- /dev/null
+++ b/init/block_dev_initializer.h
@@ -0,0 +1,41 @@
+// 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 <memory>
+#include <set>
+#include <string>
+
+#include "devices.h"
+#include "uevent_listener.h"
+
+namespace android {
+namespace init {
+
+class BlockDevInitializer final {
+  public:
+    BlockDevInitializer();
+
+    bool InitDeviceMapper();
+    bool InitDevices(std::set<std::string> devices);
+    bool InitDmDevice(const std::string& device);
+
+  private:
+    ListenerAction HandleUevent(const Uevent& uevent, std::set<std::string>* devices);
+
+    std::unique_ptr<DeviceHandler> device_handler_;
+    UeventListener uevent_listener_;
+};
+
+}  // namespace init
+}  // namespace android
diff --git a/init/first_stage_console.cpp b/init/first_stage_console.cpp
new file mode 100644
index 0000000..cae53f4
--- /dev/null
+++ b/init/first_stage_console.cpp
@@ -0,0 +1,95 @@
+/*
+ * 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 "first_stage_console.h"
+
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+
+#include <string>
+#include <thread>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+static void RunScript() {
+    LOG(INFO) << "Attempting to run /first_stage.sh...";
+    pid_t pid = fork();
+    if (pid != 0) {
+        int status;
+        waitpid(pid, &status, 0);
+        LOG(INFO) << "/first_stage.sh exited with status " << status;
+        return;
+    }
+    const char* path = "/system/bin/sh";
+    const char* args[] = {path, "/first_stage.sh", nullptr};
+    int rv = execv(path, const_cast<char**>(args));
+    LOG(ERROR) << "unable to execv /first_stage.sh, returned " << rv << " errno " << errno;
+}
+
+namespace android {
+namespace init {
+
+void StartConsole() {
+    if (mknod("/dev/console", S_IFCHR | 0600, makedev(5, 1))) {
+        PLOG(ERROR) << "unable to create /dev/console";
+        return;
+    }
+    pid_t pid = fork();
+    if (pid != 0) {
+        int status;
+        waitpid(pid, &status, 0);
+        LOG(ERROR) << "console shell exited with status " << status;
+        return;
+    }
+    int fd = -1;
+    int tries = 50; // should timeout after 5s
+    // The device driver for console may not be ready yet so retry for a while in case of failure.
+    while (tries--) {
+        fd = open("/dev/console", O_RDWR);
+        if (fd != -1) {
+            break;
+        }
+        std::this_thread::sleep_for(100ms);
+    }
+    if (fd == -1) {
+        LOG(ERROR) << "Could not open /dev/console, errno = " << errno;
+        _exit(127);
+    }
+    ioctl(fd, TIOCSCTTY, 0);
+    dup2(fd, STDIN_FILENO);
+    dup2(fd, STDOUT_FILENO);
+    dup2(fd, STDERR_FILENO);
+    close(fd);
+
+    RunScript();
+    const char* path = "/system/bin/sh";
+    const char* args[] = {path, nullptr};
+    int rv = execv(path, const_cast<char**>(args));
+    LOG(ERROR) << "unable to execv, returned " << rv << " errno " << errno;
+    _exit(127);
+}
+
+bool FirstStageConsole(const std::string& cmdline) {
+    return cmdline.find("androidboot.first_stage_console=1") != std::string::npos;
+}
+
+}  // namespace init
+}  // namespace android
diff --git a/init/first_stage_console.h b/init/first_stage_console.h
new file mode 100644
index 0000000..7485339
--- /dev/null
+++ b/init/first_stage_console.h
@@ -0,0 +1,28 @@
+/*
+ * 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 <string>
+
+namespace android {
+namespace init {
+
+void StartConsole();
+bool FirstStageConsole(const std::string& cmdline);
+
+}  // namespace init
+}  // namespace android
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index bd71cb5..5eca644 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -24,12 +24,10 @@
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <sys/types.h>
-#include <sys/wait.h>
 #include <unistd.h>
 
 #include <filesystem>
 #include <string>
-#include <thread>
 #include <vector>
 
 #include <android-base/chrono_utils.h>
@@ -39,6 +37,7 @@
 #include <private/android_filesystem_config.h>
 
 #include "debug_ramdisk.h"
+#include "first_stage_console.h"
 #include "first_stage_mount.h"
 #include "reboot_utils.h"
 #include "switch_root.h"
@@ -94,49 +93,6 @@
     }
 }
 
-void StartConsole() {
-    if (mknod("/dev/console", S_IFCHR | 0600, makedev(5, 1))) {
-        PLOG(ERROR) << "unable to create /dev/console";
-        return;
-    }
-    pid_t pid = fork();
-    if (pid != 0) {
-        int status;
-        waitpid(pid, &status, 0);
-        LOG(ERROR) << "console shell exited with status " << status;
-        return;
-    }
-    int fd = -1;
-    int tries = 10;
-    // The device driver for console may not be ready yet so retry for a while in case of failure.
-    while (tries--) {
-        fd = open("/dev/console", O_RDWR);
-        if (fd != -1) {
-            break;
-        }
-        std::this_thread::sleep_for(100ms);
-    }
-    if (fd == -1) {
-        LOG(ERROR) << "Could not open /dev/console, errno = " << errno;
-        _exit(127);
-    }
-    ioctl(fd, TIOCSCTTY, 0);
-    dup2(fd, STDIN_FILENO);
-    dup2(fd, STDOUT_FILENO);
-    dup2(fd, STDERR_FILENO);
-    close(fd);
-
-    const char* path = "/system/bin/sh";
-    const char* args[] = {path, nullptr};
-    int rv = execv(path, const_cast<char**>(args));
-    LOG(ERROR) << "unable to execv, returned " << rv << " errno " << errno;
-    _exit(127);
-}
-
-bool FirstStageConsole(const std::string& cmdline) {
-    return cmdline.find("androidboot.first_stage_console=1") != std::string::npos;
-}
-
 bool ForceNormalBoot(const std::string& cmdline) {
     return cmdline.find("androidboot.force_normal_boot=1") != std::string::npos;
 }
@@ -234,7 +190,16 @@
         old_root_dir.reset();
     }
 
-    Modprobe m({"/lib/modules"});
+    std::string module_load_file = "modules.load";
+    if (IsRecoveryMode() && !ForceNormalBoot(cmdline)) {
+        struct stat fileStat;
+        std::string recovery_load_path = "/lib/modules/modules.load.recovery";
+        if (!stat(recovery_load_path.c_str(), &fileStat)) {
+            module_load_file = "modules.load.recovery";
+        }
+    }
+
+    Modprobe m({"/lib/modules"}, module_load_file);
     auto want_console = ALLOW_FIRST_STAGE_CONSOLE && FirstStageConsole(cmdline);
     if (!m.LoadListedModules(!want_console)) {
         if (want_console) {
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index 622e457..8eb2f97 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -42,6 +42,7 @@
 #include <liblp/liblp.h>
 #include <libsnapshot/snapshot.h>
 
+#include "block_dev_initializer.h"
 #include "devices.h"
 #include "switch_root.h"
 #include "uevent.h"
@@ -84,11 +85,7 @@
     bool InitDevices();
 
   protected:
-    ListenerAction HandleBlockDevice(const std::string& name, const Uevent&,
-                                     std::set<std::string>* required_devices);
     bool InitRequiredDevices(std::set<std::string> devices);
-    bool InitMappedDevice(const std::string& verity_device);
-    bool InitDeviceMapper();
     bool CreateLogicalPartitions();
     bool MountPartition(const Fstab::iterator& begin, bool erase_same_mounts,
                         Fstab::iterator* end = nullptr);
@@ -97,7 +94,7 @@
     bool TrySwitchSystemAsRoot();
     bool TrySkipMountingPartitions();
     bool IsDmLinearEnabled();
-    void GetDmLinearMetadataDevice(std::set<std::string>* devices);
+    void GetSuperDeviceName(std::set<std::string>* devices);
     bool InitDmLinearBackingDevices(const android::fs_mgr::LpMetadata& metadata);
     void UseDsuIfPresent();
     // Reads all fstab.avb_keys from the ramdisk for first-stage mount.
@@ -106,8 +103,6 @@
     // revocation check by DSU installation service.
     void CopyDsuAvbKeys();
 
-    ListenerAction UeventCallback(const Uevent& uevent, std::set<std::string>* required_devices);
-
     // Pure virtual functions.
     virtual bool GetDmVerityDevices(std::set<std::string>* devices) = 0;
     virtual bool SetUpDmVerity(FstabEntry* fstab_entry) = 0;
@@ -116,10 +111,10 @@
     bool dsu_not_on_userdata_ = false;
 
     Fstab fstab_;
-    std::string lp_metadata_partition_;
+    // The super path is only set after InitDevices, and is invalid before.
+    std::string super_path_;
     std::string super_partition_name_;
-    std::unique_ptr<DeviceHandler> device_handler_;
-    UeventListener uevent_listener_;
+    BlockDevInitializer block_dev_init_;
     // Reads all AVB keys before chroot into /system, as they might be used
     // later when mounting other partitions, e.g., /vendor and /product.
     std::map<std::string, std::vector<std::string>> preload_avb_key_blobs_;
@@ -233,13 +228,7 @@
 
 // Class Definitions
 // -----------------
-FirstStageMount::FirstStageMount(Fstab fstab)
-    : need_dm_verity_(false), fstab_(std::move(fstab)), uevent_listener_(16 * 1024 * 1024) {
-    auto boot_devices = android::fs_mgr::GetBootDevices();
-    device_handler_ = std::make_unique<DeviceHandler>(
-            std::vector<Permissions>{}, std::vector<SysfsPermissions>{}, std::vector<Subsystem>{},
-            std::move(boot_devices), false);
-
+FirstStageMount::FirstStageMount(Fstab fstab) : need_dm_verity_(false), fstab_(std::move(fstab)) {
     super_partition_name_ = fs_mgr_get_super_partition_name();
 }
 
@@ -268,12 +257,23 @@
 
 bool FirstStageMount::InitDevices() {
     std::set<std::string> devices;
-    GetDmLinearMetadataDevice(&devices);
+    GetSuperDeviceName(&devices);
 
     if (!GetDmVerityDevices(&devices)) {
         return false;
     }
-    return InitRequiredDevices(std::move(devices));
+    if (!InitRequiredDevices(std::move(devices))) {
+        return false;
+    }
+
+    if (IsDmLinearEnabled()) {
+        auto super_symlink = "/dev/block/by-name/"s + super_partition_name_;
+        if (!android::base::Realpath(super_symlink, &super_path_)) {
+            PLOG(ERROR) << "realpath failed: " << super_symlink;
+            return false;
+        }
+    }
+    return true;
 }
 
 bool FirstStageMount::IsDmLinearEnabled() {
@@ -283,7 +283,7 @@
     return false;
 }
 
-void FirstStageMount::GetDmLinearMetadataDevice(std::set<std::string>* devices) {
+void FirstStageMount::GetSuperDeviceName(std::set<std::string>* devices) {
     // Add any additional devices required for dm-linear mappings.
     if (!IsDmLinearEnabled()) {
         return;
@@ -296,62 +296,13 @@
 // Found partitions will then be removed from it for the subsequent member
 // function to check which devices are NOT created.
 bool FirstStageMount::InitRequiredDevices(std::set<std::string> devices) {
-    if (!InitDeviceMapper()) {
+    if (!block_dev_init_.InitDeviceMapper()) {
         return false;
     }
-
     if (devices.empty()) {
         return true;
     }
-
-    auto uevent_callback = [&, this](const Uevent& uevent) {
-        return UeventCallback(uevent, &devices);
-    };
-    uevent_listener_.RegenerateUevents(uevent_callback);
-
-    // UeventCallback() will remove found partitions from |devices|. So if it
-    // isn't empty here, it means some partitions are not found.
-    if (!devices.empty()) {
-        LOG(INFO) << __PRETTY_FUNCTION__
-                  << ": partition(s) not found in /sys, waiting for their uevent(s): "
-                  << android::base::Join(devices, ", ");
-        Timer t;
-        uevent_listener_.Poll(uevent_callback, 10s);
-        LOG(INFO) << "Wait for partitions returned after " << t;
-    }
-
-    if (!devices.empty()) {
-        LOG(ERROR) << __PRETTY_FUNCTION__ << ": partition(s) not found after polling timeout: "
-                   << android::base::Join(devices, ", ");
-        return false;
-    }
-
-    return true;
-}
-
-bool FirstStageMount::InitDeviceMapper() {
-    const std::string dm_path = "/devices/virtual/misc/device-mapper";
-    bool found = false;
-    auto dm_callback = [this, &dm_path, &found](const Uevent& uevent) {
-        if (uevent.path == dm_path) {
-            device_handler_->HandleUevent(uevent);
-            found = true;
-            return ListenerAction::kStop;
-        }
-        return ListenerAction::kContinue;
-    };
-    uevent_listener_.RegenerateUeventsForPath("/sys" + dm_path, dm_callback);
-    if (!found) {
-        LOG(INFO) << "device-mapper device not found in /sys, waiting for its uevent";
-        Timer t;
-        uevent_listener_.Poll(dm_callback, 10s);
-        LOG(INFO) << "Wait for device-mapper returned after " << t;
-    }
-    if (!found) {
-        LOG(ERROR) << "device-mapper device not found after polling timeout";
-        return false;
-    }
-    return true;
+    return block_dev_init_.InitDevices(std::move(devices));
 }
 
 bool FirstStageMount::InitDmLinearBackingDevices(const android::fs_mgr::LpMetadata& metadata) {
@@ -375,7 +326,7 @@
     if (!IsDmLinearEnabled()) {
         return true;
     }
-    if (lp_metadata_partition_.empty()) {
+    if (super_path_.empty()) {
         LOG(ERROR) << "Could not locate logical partition tables in partition "
                    << super_partition_name_;
         return false;
@@ -392,92 +343,19 @@
             if (!InitRequiredDevices({"userdata"})) {
                 return false;
             }
-            return sm->CreateLogicalAndSnapshotPartitions(lp_metadata_partition_);
+            return sm->CreateLogicalAndSnapshotPartitions(super_path_);
         }
     }
 
-    auto metadata = android::fs_mgr::ReadCurrentMetadata(lp_metadata_partition_);
+    auto metadata = android::fs_mgr::ReadCurrentMetadata(super_path_);
     if (!metadata) {
-        LOG(ERROR) << "Could not read logical partition metadata from " << lp_metadata_partition_;
+        LOG(ERROR) << "Could not read logical partition metadata from " << super_path_;
         return false;
     }
     if (!InitDmLinearBackingDevices(*metadata.get())) {
         return false;
     }
-    return android::fs_mgr::CreateLogicalPartitions(*metadata.get(), lp_metadata_partition_);
-}
-
-ListenerAction FirstStageMount::HandleBlockDevice(const std::string& name, const Uevent& uevent,
-                                                  std::set<std::string>* required_devices) {
-    // Matches partition name to create device nodes.
-    // Both required_devices_partition_names_ and uevent->partition_name have A/B
-    // suffix when A/B is used.
-    auto iter = required_devices->find(name);
-    if (iter != required_devices->end()) {
-        LOG(VERBOSE) << __PRETTY_FUNCTION__ << ": found partition: " << *iter;
-        if (IsDmLinearEnabled() && name == super_partition_name_) {
-            std::vector<std::string> links = device_handler_->GetBlockDeviceSymlinks(uevent);
-            lp_metadata_partition_ = links[0];
-        }
-        required_devices->erase(iter);
-        device_handler_->HandleUevent(uevent);
-        if (required_devices->empty()) {
-            return ListenerAction::kStop;
-        } else {
-            return ListenerAction::kContinue;
-        }
-    }
-    return ListenerAction::kContinue;
-}
-
-ListenerAction FirstStageMount::UeventCallback(const Uevent& uevent,
-                                               std::set<std::string>* required_devices) {
-    // Ignores everything that is not a block device.
-    if (uevent.subsystem != "block") {
-        return ListenerAction::kContinue;
-    }
-
-    if (!uevent.partition_name.empty()) {
-        return HandleBlockDevice(uevent.partition_name, uevent, required_devices);
-    } else {
-        size_t base_idx = uevent.path.rfind('/');
-        if (base_idx != std::string::npos) {
-            return HandleBlockDevice(uevent.path.substr(base_idx + 1), uevent, required_devices);
-        }
-    }
-    // Not found a partition or find an unneeded partition, continue to find others.
-    return ListenerAction::kContinue;
-}
-
-// Creates "/dev/block/dm-XX" for dm-verity by running coldboot on /sys/block/dm-XX.
-bool FirstStageMount::InitMappedDevice(const std::string& dm_device) {
-    const std::string device_name(basename(dm_device.c_str()));
-    const std::string syspath = "/sys/block/" + device_name;
-    bool found = false;
-
-    auto verity_callback = [&device_name, &dm_device, this, &found](const Uevent& uevent) {
-        if (uevent.device_name == device_name) {
-            LOG(VERBOSE) << "Creating device-mapper device : " << dm_device;
-            device_handler_->HandleUevent(uevent);
-            found = true;
-            return ListenerAction::kStop;
-        }
-        return ListenerAction::kContinue;
-    };
-
-    uevent_listener_.RegenerateUeventsForPath(syspath, verity_callback);
-    if (!found) {
-        LOG(INFO) << "dm device '" << dm_device << "' not found in /sys, waiting for its uevent";
-        Timer t;
-        uevent_listener_.Poll(verity_callback, 10s);
-        LOG(INFO) << "wait for dm device '" << dm_device << "' returned after " << t;
-    }
-    if (!found) {
-        LOG(ERROR) << "dm device '" << dm_device << "' not found after polling timeout";
-        return false;
-    }
-
-    return true;
+    return android::fs_mgr::CreateLogicalPartitions(*metadata.get(), super_path_);
 }
 
 bool FirstStageMount::MountPartition(const Fstab::iterator& begin, bool erase_same_mounts,
@@ -491,7 +369,7 @@
         if (!fs_mgr_update_logical_partition(&(*begin))) {
             return false;
         }
-        if (!InitMappedDevice(begin->blk_device)) {
+        if (!block_dev_init_.InitDmDevice(begin->blk_device)) {
             return false;
         }
     }
@@ -658,7 +536,9 @@
     auto init_devices = [this](std::set<std::string> devices) -> bool {
         for (auto iter = devices.begin(); iter != devices.end();) {
             if (android::base::StartsWith(*iter, "/dev/block/dm-")) {
-                if (!InitMappedDevice(*iter)) return false;
+                if (!block_dev_init_.InitDmDevice(*iter)) {
+                    return false;
+                }
                 iter = devices.erase(iter);
             } else {
                 iter++;
@@ -774,7 +654,7 @@
                 // The exact block device name (fstab_rec->blk_device) is changed to
                 // "/dev/block/dm-XX". Needs to create it because ueventd isn't started in init
                 // first stage.
-                return InitMappedDevice(fstab_entry->blk_device);
+                return block_dev_init_.InitDmDevice(fstab_entry->blk_device);
             default:
                 return false;
         }
@@ -894,7 +774,7 @@
             // The exact block device name (fstab_rec->blk_device) is changed to
             // "/dev/block/dm-XX". Needs to create it because ueventd isn't started in init
             // first stage.
-            return InitMappedDevice(fstab_entry->blk_device);
+            return block_dev_init_.InitDmDevice(fstab_entry->blk_device);
         default:
             return false;
     }
diff --git a/init/host_init_verifier.cpp b/init/host_init_verifier.cpp
index 0bd4df4..ef9a451 100644
--- a/init/host_init_verifier.cpp
+++ b/init/host_init_verifier.cpp
@@ -32,6 +32,7 @@
 #include <android-base/logging.h>
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
+#include <generated_android_ids.h>
 #include <hidl/metadata.h>
 #include <property_info_serializer/property_info_serializer.h>
 
@@ -48,9 +49,6 @@
 #include "service_list.h"
 #include "service_parser.h"
 
-#define EXCLUDE_FS_CONFIG_STRUCTURES
-#include "generated_android_ids.h"
-
 using namespace std::literals;
 
 using android::base::ParseInt;
diff --git a/init/init.cpp b/init/init.cpp
index 4289dcf..a9d6301 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -22,6 +22,7 @@
 #include <signal.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/eventfd.h>
 #include <sys/mount.h>
 #include <sys/signalfd.h>
 #include <sys/types.h>
@@ -45,6 +46,7 @@
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
+#include <backtrace/Backtrace.h>
 #include <fs_avb/fs_avb.h>
 #include <fs_mgr_vendor_overlay.h>
 #include <keyutils.h>
@@ -81,6 +83,7 @@
 using namespace std::string_literals;
 
 using android::base::boot_clock;
+using android::base::ConsumePrefix;
 using android::base::GetProperty;
 using android::base::ReadFileToString;
 using android::base::SetProperty;
@@ -113,30 +116,26 @@
 // to fill that socket and deadlock the system.  Now we use locks to handle the property changes
 // directly in the property thread, however we still must wake the epoll to inform init that there
 // is a change to process, so we use this FD.  It is non-blocking, since we do not care how many
-// times WakeEpoll() is called, only that the epoll will wake.
-static int wake_epoll_fd = -1;
+// times WakeMainInitThread() is called, only that the epoll will wake.
+static int wake_main_thread_fd = -1;
 static void InstallInitNotifier(Epoll* epoll) {
-    int sockets[2];
-    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sockets) != 0) {
-        PLOG(FATAL) << "Failed to socketpair() between property_service and init";
+    wake_main_thread_fd = eventfd(0, EFD_CLOEXEC);
+    if (wake_main_thread_fd == -1) {
+        PLOG(FATAL) << "Failed to create eventfd for waking init";
     }
-    int epoll_fd = sockets[0];
-    wake_epoll_fd = sockets[1];
-
-    auto drain_socket = [epoll_fd] {
-        char buf[512];
-        while (read(epoll_fd, buf, sizeof(buf)) > 0) {
-        }
+    auto clear_eventfd = [] {
+        uint64_t counter;
+        TEMP_FAILURE_RETRY(read(wake_main_thread_fd, &counter, sizeof(counter)));
     };
 
-    if (auto result = epoll->RegisterHandler(epoll_fd, drain_socket); !result.ok()) {
+    if (auto result = epoll->RegisterHandler(wake_main_thread_fd, clear_eventfd); !result.ok()) {
         LOG(FATAL) << result.error();
     }
 }
 
-static void WakeEpoll() {
-    constexpr char value[] = "1";
-    write(wake_epoll_fd, value, sizeof(value));
+static void WakeMainInitThread() {
+    uint64_t counter = 1;
+    TEMP_FAILURE_RETRY(write(wake_main_thread_fd, &counter, sizeof(counter)));
 }
 
 static class PropWaiterState {
@@ -180,7 +179,7 @@
                 LOG(INFO) << "Wait for property '" << wait_prop_name_ << "=" << wait_prop_value_
                           << "' took " << *waiting_for_prop_;
                 ResetWaitForPropLocked();
-                WakeEpoll();
+                WakeMainInitThread();
             }
         }
     }
@@ -217,6 +216,16 @@
     prop_waiter_state.ResetWaitForProp();
 }
 
+static void UnwindMainThreadStack() {
+    std::unique_ptr<Backtrace> backtrace(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, 1));
+    if (!backtrace->Unwind(0)) {
+        LOG(ERROR) << __FUNCTION__ << ": Failed to unwind callstack.";
+    }
+    for (size_t i = 0; i < backtrace->NumFrames(); i++) {
+        LOG(ERROR) << backtrace->FormatFrameData(i);
+    }
+}
+
 static class ShutdownState {
   public:
     void TriggerShutdown(const std::string& command) {
@@ -228,7 +237,7 @@
         auto lock = std::lock_guard{shutdown_command_lock_};
         shutdown_command_ = command;
         do_shutdown_ = true;
-        WakeEpoll();
+        WakeMainInitThread();
     }
 
     std::optional<std::string> CheckShutdown() {
@@ -240,12 +249,27 @@
         return {};
     }
 
+    bool do_shutdown() const { return do_shutdown_; }
+
   private:
     std::mutex shutdown_command_lock_;
     std::string shutdown_command_;
     bool do_shutdown_ = false;
 } shutdown_state;
 
+void DebugRebootLogging() {
+    LOG(INFO) << "do_shutdown: " << shutdown_state.do_shutdown()
+              << " IsShuttingDown: " << IsShuttingDown();
+    if (shutdown_state.do_shutdown()) {
+        LOG(ERROR) << "sys.powerctl set while a previous shutdown command has not been handled";
+        UnwindMainThreadStack();
+    }
+    if (IsShuttingDown()) {
+        LOG(ERROR) << "sys.powerctl set while init is already shutting down";
+        UnwindMainThreadStack();
+    }
+}
+
 void DumpState() {
     ServiceList::GetInstance().DumpState();
     ActionManager::GetInstance().DumpState();
@@ -312,7 +336,7 @@
 
     if (property_triggers_enabled) {
         ActionManager::GetInstance().QueuePropertyChange(name, value);
-        WakeEpoll();
+        WakeMainInitThread();
     }
 
     prop_waiter_state.CheckAndResetWait(name, value);
@@ -367,40 +391,27 @@
     INTERFACE,  // action gets called for every service that holds this interface
 };
 
-struct ControlMessageFunction {
-    ControlTarget target;
-    std::function<Result<void>(Service*)> action;
-};
+using ControlMessageFunction = std::function<Result<void>(Service*)>;
 
-static const std::map<std::string, ControlMessageFunction>& get_control_message_map() {
+static const std::map<std::string, ControlMessageFunction, std::less<>>& GetControlMessageMap() {
     // clang-format off
-    static const std::map<std::string, ControlMessageFunction> control_message_functions = {
-        {"sigstop_on",        {ControlTarget::SERVICE,
-                               [](auto* service) { service->set_sigstop(true); return Result<void>{}; }}},
-        {"sigstop_off",       {ControlTarget::SERVICE,
-                               [](auto* service) { service->set_sigstop(false); return Result<void>{}; }}},
-        {"start",             {ControlTarget::SERVICE,   DoControlStart}},
-        {"stop",              {ControlTarget::SERVICE,   DoControlStop}},
-        {"restart",           {ControlTarget::SERVICE,   DoControlRestart}},
-        {"interface_start",   {ControlTarget::INTERFACE, DoControlStart}},
-        {"interface_stop",    {ControlTarget::INTERFACE, DoControlStop}},
-        {"interface_restart", {ControlTarget::INTERFACE, DoControlRestart}},
+    static const std::map<std::string, ControlMessageFunction, std::less<>> control_message_functions = {
+        {"sigstop_on",        [](auto* service) { service->set_sigstop(true); return Result<void>{}; }},
+        {"sigstop_off",       [](auto* service) { service->set_sigstop(false); return Result<void>{}; }},
+        {"oneshot_on",        [](auto* service) { service->set_oneshot(true); return Result<void>{}; }},
+        {"oneshot_off",       [](auto* service) { service->set_oneshot(false); return Result<void>{}; }},
+        {"start",             DoControlStart},
+        {"stop",              DoControlStop},
+        {"restart",           DoControlRestart},
     };
     // clang-format on
 
     return control_message_functions;
 }
 
-bool HandleControlMessage(const std::string& msg, const std::string& name, pid_t pid) {
-    const auto& map = get_control_message_map();
-    const auto it = map.find(msg);
-
-    if (it == map.end()) {
-        LOG(ERROR) << "Unknown control msg '" << msg << "'";
-        return false;
-    }
-
-    std::string cmdline_path = StringPrintf("proc/%d/cmdline", pid);
+static bool HandleControlMessage(std::string_view message, const std::string& name,
+                                 pid_t from_pid) {
+    std::string cmdline_path = StringPrintf("proc/%d/cmdline", from_pid);
     std::string process_cmdline;
     if (ReadFileToString(cmdline_path, &process_cmdline)) {
         std::replace(process_cmdline.begin(), process_cmdline.end(), '\0', ' ');
@@ -409,37 +420,37 @@
         process_cmdline = "unknown process";
     }
 
-    const ControlMessageFunction& function = it->second;
-
-    Service* svc = nullptr;
-
-    switch (function.target) {
-        case ControlTarget::SERVICE:
-            svc = ServiceList::GetInstance().FindService(name);
-            break;
-        case ControlTarget::INTERFACE:
-            svc = ServiceList::GetInstance().FindInterface(name);
-            break;
-        default:
-            LOG(ERROR) << "Invalid function target from static map key ctl." << msg << ": "
-                       << static_cast<std::underlying_type<ControlTarget>::type>(function.target);
-            return false;
+    Service* service = nullptr;
+    auto action = message;
+    if (ConsumePrefix(&action, "interface_")) {
+        service = ServiceList::GetInstance().FindInterface(name);
+    } else {
+        service = ServiceList::GetInstance().FindService(name);
     }
 
-    if (svc == nullptr) {
-        LOG(ERROR) << "Control message: Could not find '" << name << "' for ctl." << msg
-                   << " from pid: " << pid << " (" << process_cmdline << ")";
+    if (service == nullptr) {
+        LOG(ERROR) << "Control message: Could not find '" << name << "' for ctl." << message
+                   << " from pid: " << from_pid << " (" << process_cmdline << ")";
         return false;
     }
 
-    if (auto result = function.action(svc); !result.ok()) {
-        LOG(ERROR) << "Control message: Could not ctl." << msg << " for '" << name
-                   << "' from pid: " << pid << " (" << process_cmdline << "): " << result.error();
+    const auto& map = GetControlMessageMap();
+    const auto it = map.find(action);
+    if (it == map.end()) {
+        LOG(ERROR) << "Unknown control msg '" << message << "'";
+        return false;
+    }
+    const auto& function = it->second;
+
+    if (auto result = function(service); !result.ok()) {
+        LOG(ERROR) << "Control message: Could not ctl." << message << " for '" << name
+                   << "' from pid: " << from_pid << " (" << process_cmdline
+                   << "): " << result.error();
         return false;
     }
 
-    LOG(INFO) << "Control message: Processed ctl." << msg << " for '" << name
-              << "' from pid: " << pid << " (" << process_cmdline << ")";
+    LOG(INFO) << "Control message: Processed ctl." << message << " for '" << name
+              << "' from pid: " << from_pid << " (" << process_cmdline << ")";
     return true;
 }
 
@@ -451,7 +462,7 @@
         return false;
     }
     pending_control_messages.push({message, name, pid, fd});
-    WakeEpoll();
+    WakeMainInitThread();
     return true;
 }
 
@@ -477,7 +488,7 @@
     }
     // If we still have items to process, make sure we wake back up to do so.
     if (!pending_control_messages.empty()) {
-        WakeEpoll();
+        WakeMainInitThread();
     }
 }
 
@@ -860,6 +871,8 @@
 
         auto shutdown_command = shutdown_state.CheckShutdown();
         if (shutdown_command) {
+            LOG(INFO) << "Got shutdown_command '" << *shutdown_command
+                      << "' Calling HandlePowerctlMessage()";
             HandlePowerctlMessage(*shutdown_command);
         }
 
@@ -894,7 +907,9 @@
                 (*function)();
             }
         }
-        HandleControlMessages();
+        if (!IsShuttingDown()) {
+            HandleControlMessages();
+        }
     }
 
     return 0;
diff --git a/init/init.h b/init/init.h
index 27f64e2..4f686cb 100644
--- a/init/init.h
+++ b/init/init.h
@@ -42,6 +42,8 @@
 void PropertyChanged(const std::string& name, const std::string& value);
 bool QueueControlMessage(const std::string& message, const std::string& name, pid_t pid, int fd);
 
+void DebugRebootLogging();
+
 int SecondStageMain(int argc, char** argv);
 
 }  // namespace init
diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp
index 0749fe3..f3b584c 100644
--- a/init/mount_namespace.cpp
+++ b/init/mount_namespace.cpp
@@ -240,14 +240,20 @@
     // slave to the /mnt/user mount, and at the same time /mnt/installer in the
     // bootstrap namespace shares a peer group with /mnt/installer in the
     // default namespace.
+    // /mnt/androidwritable is similar to /mnt/installer but serves for
+    // MOUNT_EXTERNAL_ANDROID_WRITABLE apps.
     if (!mkdir_recursive("/mnt/user", 0755)) return false;
     if (!mkdir_recursive("/mnt/installer", 0755)) return false;
+    if (!mkdir_recursive("/mnt/androidwritable", 0755)) return false;
     if (!(BindMount("/mnt/user", "/mnt/installer", true))) return false;
-    // First, make /mnt/installer a slave bind mount
+    if (!(BindMount("/mnt/user", "/mnt/androidwritable", true))) return false;
+    // First, make /mnt/installer and /mnt/androidwritable a slave bind mount
     if (!(MakeSlave("/mnt/installer"))) return false;
+    if (!(MakeSlave("/mnt/androidwritable"))) return false;
     // Then, make it shared again - effectively creating a new peer group, that
     // will be inherited by new mount namespaces.
     if (!(MakeShared("/mnt/installer"))) return false;
+    if (!(MakeShared("/mnt/androidwritable"))) return false;
 
     bootstrap_ns_fd.reset(OpenMountNamespace());
     bootstrap_ns_id = GetMountNamespaceId();
diff --git a/init/oneshot_on_test.cpp b/init/oneshot_on_test.cpp
new file mode 100644
index 0000000..650f065
--- /dev/null
+++ b/init/oneshot_on_test.cpp
@@ -0,0 +1,49 @@
+/*
+ * 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 <chrono>
+
+#include <android-base/properties.h>
+
+using android::base::GetProperty;
+using android::base::SetProperty;
+using android::base::WaitForProperty;
+using namespace std::literals;
+
+TEST(init, oneshot_on) {
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Skipping test, must be run as root.";
+        return;
+    }
+
+    // Bootanim shouldn't be running once the device has booted.
+    ASSERT_EQ("stopped", GetProperty("init.svc.bootanim", ""));
+
+    SetProperty("ctl.oneshot_off", "bootanim");
+    SetProperty("ctl.start", "bootanim");
+
+    // Bootanim exits quickly when the device is fully booted, so check that it goes back to the
+    // 'restarting' state that non-oneshot services enter once they've restarted.
+    EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "restarting", 10s));
+
+    SetProperty("ctl.oneshot_on", "bootanim");
+    SetProperty("ctl.start", "bootanim");
+
+    // Now that oneshot is enabled again, bootanim should transition into the 'stopped' state.
+    EXPECT_TRUE(WaitForProperty("init.svc.bootanim", "stopped", 10s));
+}
diff --git a/init/parser.cpp b/init/parser.cpp
index 507ee4a..5c18551 100644
--- a/init/parser.cpp
+++ b/init/parser.cpp
@@ -41,7 +41,7 @@
 }
 
 void Parser::ParseData(const std::string& filename, std::string* data) {
-    data->push_back('\n');  // TODO: fix tokenizer
+    data->push_back('\n');
     data->push_back('\0');
 
     parse_state state;
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 8206522..842b2e5 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -47,6 +47,7 @@
 #include <thread>
 #include <vector>
 
+#include <InitProperties.sysprop.h>
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
@@ -85,6 +86,7 @@
 using android::properties::ParsePropertyInfoFile;
 using android::properties::PropertyInfoAreaFile;
 using android::properties::PropertyInfoEntry;
+using android::sysprop::InitProperties::is_userspace_reboot_supported;
 
 namespace android {
 namespace init {
@@ -125,7 +127,7 @@
 
 void StopSendingMessages() {
     auto lock = std::lock_guard{accept_messages_lock};
-    accept_messages = true;
+    accept_messages = false;
 }
 
 bool CanReadProperty(const std::string& source_context, const std::string& name) {
@@ -489,6 +491,13 @@
         }
         LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                   << process_log_string;
+        if (!value.empty()) {
+            DebugRebootLogging();
+        }
+        if (value == "reboot,userspace" && !is_userspace_reboot_supported().value_or(false)) {
+            *error = "Userspace reboot is not supported by this device";
+            return PROP_ERROR_INVALID_VALUE;
+        }
     }
 
     // If a process other than init is writing a non-empty value, it means that process is
diff --git a/init/property_service_test.cpp b/init/property_service_test.cpp
index 0f4cd0d..c6dcfa2 100644
--- a/init/property_service_test.cpp
+++ b/init/property_service_test.cpp
@@ -22,8 +22,10 @@
 #include <sys/_system_properties.h>
 
 #include <android-base/properties.h>
+#include <android-base/scopeguard.h>
 #include <gtest/gtest.h>
 
+using android::base::GetProperty;
 using android::base::SetProperty;
 
 namespace android {
@@ -74,5 +76,19 @@
     EXPECT_TRUE(SetProperty("property_service_utf8_test", "\xF0\x90\x80\x80"));
 }
 
+TEST(property_service, userspace_reboot_not_supported) {
+    if (getuid() != 0) {
+        GTEST_SKIP() << "Skipping test, must be run as root.";
+        return;
+    }
+    const std::string original_value = GetProperty("init.userspace_reboot.is_supported", "");
+    auto guard = android::base::make_scope_guard([&original_value]() {
+        SetProperty("init.userspace_reboot.is_supported", original_value);
+    });
+
+    ASSERT_TRUE(SetProperty("init.userspace_reboot.is_supported", "false"));
+    EXPECT_FALSE(SetProperty("sys.powerctl", "reboot,userspace"));
+}
+
 }  // namespace init
 }  // namespace android
diff --git a/init/reboot.cpp b/init/reboot.cpp
index f006df3..72f0450 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -72,6 +72,7 @@
 
 using android::base::boot_clock;
 using android::base::GetBoolProperty;
+using android::base::GetUintProperty;
 using android::base::SetProperty;
 using android::base::Split;
 using android::base::Timer;
@@ -101,7 +102,15 @@
     if (write_to_property) {
         SetProperty(LAST_REBOOT_REASON_PROPERTY, reason);
     }
-    WriteStringToFile(reason, LAST_REBOOT_REASON_FILE);
+    auto fd = unique_fd(TEMP_FAILURE_RETRY(open(
+            LAST_REBOOT_REASON_FILE, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY, 0666)));
+    if (!fd.ok()) {
+        PLOG(ERROR) << "Could not open '" << LAST_REBOOT_REASON_FILE
+                    << "' to persist reboot reason";
+        return;
+    }
+    WriteStringToFd(reason, fd);
+    fsync(fd.get());
 }
 
 // represents umount status during reboot / shutdown.
@@ -194,6 +203,7 @@
 }
 
 static Result<void> CallVdc(const std::string& system, const std::string& cmd) {
+    LOG(INFO) << "Calling /system/bin/vdc " << system << " " << cmd;
     const char* vdc_argv[] = {"/system/bin/vdc", system.c_str(), cmd.c_str()};
     int status;
     if (logwrap_fork_execvp(arraysize(vdc_argv), vdc_argv, &status, false, LOG_KLOG, true,
@@ -316,9 +326,9 @@
                          bool* reboot_monitor_run) {
     unsigned int remaining_shutdown_time = 0;
 
-    // 30 seconds more than the timeout passed to the thread as there is a final Umount pass
+    // 300 seconds more than the timeout passed to the thread as there is a final Umount pass
     // after the timeout is reached.
-    constexpr unsigned int shutdown_watchdog_timeout_default = 30;
+    constexpr unsigned int shutdown_watchdog_timeout_default = 300;
     auto shutdown_watchdog_timeout = android::base::GetUintProperty(
             "ro.build.shutdown.watchdog.timeout", shutdown_watchdog_timeout_default);
     remaining_shutdown_time = shutdown_watchdog_timeout + shutdown_timeout.count() / 1000;
@@ -447,10 +457,14 @@
 #define ZRAM_RESET    "/sys/block/zram0/reset"
 #define ZRAM_BACK_DEV "/sys/block/zram0/backing_dev"
 static Result<void> KillZramBackingDevice() {
+    if (access(ZRAM_BACK_DEV, F_OK) != 0 && errno == ENOENT) {
+        LOG(INFO) << "No zram backing device configured";
+        return {};
+    }
     std::string backing_dev;
-    if (!android::base::ReadFileToString(ZRAM_BACK_DEV, &backing_dev)) return {};
-
-    if (!android::base::StartsWith(backing_dev, "/dev/block/loop")) return {};
+    if (!android::base::ReadFileToString(ZRAM_BACK_DEV, &backing_dev)) {
+        return ErrnoError() << "Failed to read " << ZRAM_BACK_DEV;
+    }
 
     // cut the last "\n"
     backing_dev.erase(backing_dev.length() - 1);
@@ -469,6 +483,11 @@
                        << " failed";
     }
 
+    if (!android::base::StartsWith(backing_dev, "/dev/block/loop")) {
+        LOG(INFO) << backing_dev << " is not a loop device. Exiting early";
+        return {};
+    }
+
     // clear loopback device
     unique_fd loop(TEMP_FAILURE_RETRY(open(backing_dev.c_str(), O_RDWR | O_CLOEXEC)));
     if (loop.get() < 0) {
@@ -538,26 +557,6 @@
     Timer t;
     LOG(INFO) << "Reboot start, reason: " << reason << ", reboot_target: " << reboot_target;
 
-    // Ensure last reboot reason is reduced to canonical
-    // alias reported in bootloader or system boot reason.
-    size_t skip = 0;
-    std::vector<std::string> reasons = Split(reason, ",");
-    if (reasons.size() >= 2 && reasons[0] == "reboot" &&
-        (reasons[1] == "recovery" || reasons[1] == "bootloader" || reasons[1] == "cold" ||
-         reasons[1] == "hard" || reasons[1] == "warm")) {
-        skip = strlen("reboot,");
-    }
-    PersistRebootReason(reason.c_str() + skip, true);
-    sync();
-
-    // If /data isn't mounted then we can skip the extra reboot steps below, since we don't need to
-    // worry about unmounting it.
-    if (!IsDataMounted()) {
-        sync();
-        RebootSystem(cmd, reboot_target);
-        abort();
-    }
-
     bool is_thermal_shutdown = cmd == ANDROID_RB_THERMOFF;
 
     auto shutdown_timeout = 0ms;
@@ -590,6 +589,25 @@
     // Start reboot monitor thread
     sem_post(&reboot_semaphore);
 
+    // Ensure last reboot reason is reduced to canonical
+    // alias reported in bootloader or system boot reason.
+    size_t skip = 0;
+    std::vector<std::string> reasons = Split(reason, ",");
+    if (reasons.size() >= 2 && reasons[0] == "reboot" &&
+        (reasons[1] == "recovery" || reasons[1] == "bootloader" || reasons[1] == "cold" ||
+         reasons[1] == "hard" || reasons[1] == "warm")) {
+        skip = strlen("reboot,");
+    }
+    PersistRebootReason(reason.c_str() + skip, true);
+
+    // If /data isn't mounted then we can skip the extra reboot steps below, since we don't need to
+    // worry about unmounting it.
+    if (!IsDataMounted()) {
+        sync();
+        RebootSystem(cmd, reboot_target);
+        abort();
+    }
+
     // 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;
@@ -732,6 +750,12 @@
     return Error() << "'/system/bin/apexd --unmount-all' failed : " << status;
 }
 
+static std::chrono::milliseconds GetMillisProperty(const std::string& name,
+                                                   std::chrono::milliseconds default_value) {
+    auto value = GetUintProperty(name, static_cast<uint64_t>(default_value.count()));
+    return std::chrono::milliseconds(std::move(value));
+}
+
 static Result<void> DoUserspaceReboot() {
     LOG(INFO) << "Userspace reboot initiated";
     auto guard = android::base::make_scope_guard([] {
@@ -769,10 +793,13 @@
         sync();
         LOG(INFO) << "sync() took " << sync_timer;
     }
-    // TODO(b/135984674): do we need shutdown animation for userspace reboot?
-    // TODO(b/135984674): control userspace timeout via read-only property?
-    StopServicesAndLogViolations(stop_first, 10s, true /* SIGTERM */);
-    if (int r = StopServicesAndLogViolations(stop_first, 20s, false /* SIGKILL */); r > 0) {
+    auto sigterm_timeout = GetMillisProperty("init.userspace_reboot.sigterm.timeoutmillis", 5s);
+    auto sigkill_timeout = GetMillisProperty("init.userspace_reboot.sigkill.timeoutmillis", 10s);
+    LOG(INFO) << "Timeout to terminate services: " << sigterm_timeout.count() << "ms "
+              << "Timeout to kill services: " << sigkill_timeout.count() << "ms";
+    StopServicesAndLogViolations(stop_first, sigterm_timeout, true /* SIGTERM */);
+    if (int r = StopServicesAndLogViolations(stop_first, sigkill_timeout, false /* SIGKILL */);
+        r > 0) {
         // TODO(b/135984674): store information about offending services for debugging.
         return Error() << r << " post-data services are still running";
     }
@@ -782,8 +809,8 @@
     if (auto result = CallVdc("volume", "reset"); !result.ok()) {
         return result;
     }
-    if (int r = StopServicesAndLogViolations(GetDebuggingServices(true /* only_post_data */), 5s,
-                                             false /* SIGKILL */);
+    if (int r = StopServicesAndLogViolations(GetDebuggingServices(true /* only_post_data */),
+                                             sigkill_timeout, false /* SIGKILL */);
         r > 0) {
         // TODO(b/135984674): store information about offending services for debugging.
         return Error() << r << " debugging services are still running";
@@ -821,20 +848,25 @@
 }
 
 static void UserspaceRebootWatchdogThread() {
-    if (!WaitForProperty("sys.init.userspace_reboot.in_progress", "1", 20s)) {
-        // TODO(b/135984674): should we reboot instead?
-        LOG(WARNING) << "Userspace reboot didn't start in 20 seconds. Stopping watchdog";
-        return;
+    auto started_timeout = GetMillisProperty("init.userspace_reboot.started.timeoutmillis", 10s);
+    if (!WaitForProperty("sys.init.userspace_reboot.in_progress", "1", started_timeout)) {
+        LOG(ERROR) << "Userspace reboot didn't start in " << started_timeout.count()
+                   << "ms. Switching to full reboot";
+        // Init might be wedged, don't try to write reboot reason into a persistent property and do
+        // a dirty reboot.
+        PersistRebootReason("userspace_failed,watchdog_triggered,failed_to_start", false);
+        RebootSystem(ANDROID_RB_RESTART2, "userspace_failed,watchdog_triggered,failed_to_start");
     }
     LOG(INFO) << "Starting userspace reboot watchdog";
-    // TODO(b/135984674): this should be configured via a read-only sysprop.
-    std::chrono::milliseconds timeout = 60s;
-    if (!WaitForProperty("sys.boot_completed", "1", timeout)) {
-        LOG(ERROR) << "Failed to boot in " << timeout.count() << "ms. Switching to full reboot";
+    auto watchdog_timeout = GetMillisProperty("init.userspace_reboot.watchdog.timeoutmillis", 5min);
+    LOG(INFO) << "UserspaceRebootWatchdog timeout: " << watchdog_timeout.count() << "ms";
+    if (!WaitForProperty("sys.boot_completed", "1", watchdog_timeout)) {
+        LOG(ERROR) << "Failed to boot in " << watchdog_timeout.count()
+                   << "ms. Switching to full reboot";
         // In this case device is in a boot loop. Only way to recover is to do dirty reboot.
         // Since init might be wedged, don't try to write reboot reason into a persistent property.
-        PersistRebootReason("userspace_failed,watchdog_triggered", false);
-        RebootSystem(ANDROID_RB_RESTART2, "userspace_failed,watchdog_triggered");
+        PersistRebootReason("userspace_failed,watchdog_triggered,failed_to_boot", false);
+        RebootSystem(ANDROID_RB_RESTART2, "userspace_failed,watchdog_triggered,failed_to_boot");
     }
     LOG(INFO) << "Device booted, stopping userspace reboot watchdog";
 }
diff --git a/init/selinux.cpp b/init/selinux.cpp
index 808cb7f..5a0255a 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -63,12 +63,15 @@
 #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 <fs_avb/fs_avb.h>
+#include <fs_mgr.h>
 #include <libgsi/libgsi.h>
 #include <libsnapshot/snapshot.h>
 #include <selinux/android.h>
 
+#include "block_dev_initializer.h"
 #include "debug_ramdisk.h"
 #include "reboot_utils.h"
 #include "util.h"
@@ -598,6 +601,74 @@
     return vendor_android_version;
 }
 
+// This is for R system.img/system_ext.img to work on old vendor.img as system_ext.img
+// is introduced in R. We mount system_ext in second stage init because the first-stage
+// init in boot.img won't be updated in the system-only OTA scenario.
+void MountMissingSystemPartitions() {
+    android::fs_mgr::Fstab fstab;
+    if (!ReadDefaultFstab(&fstab)) {
+        LOG(ERROR) << "Could not read default fstab";
+    }
+
+    android::fs_mgr::Fstab mounts;
+    if (!ReadFstabFromFile("/proc/mounts", &mounts)) {
+        LOG(ERROR) << "Could not read /proc/mounts";
+    }
+
+    static const std::vector<std::string> kPartitionNames = {"system_ext", "product"};
+
+    android::fs_mgr::Fstab extra_fstab;
+    for (const auto& name : kPartitionNames) {
+        if (GetEntryForMountPoint(&mounts, "/"s + name)) {
+            // The partition is already mounted.
+            continue;
+        }
+
+        auto system_entry = GetEntryForMountPoint(&fstab, "/system");
+        if (!system_entry) {
+            LOG(ERROR) << "Could not find mount entry for /system";
+            break;
+        }
+        if (!system_entry->fs_mgr_flags.logical) {
+            LOG(INFO) << "Skipping mount of " << name << ", system is not dynamic.";
+            break;
+        }
+
+        auto entry = *system_entry;
+        auto partition_name = name + fs_mgr_get_slot_suffix();
+        auto replace_name = "system"s + fs_mgr_get_slot_suffix();
+
+        entry.mount_point = "/"s + name;
+        entry.blk_device =
+                android::base::StringReplace(entry.blk_device, replace_name, partition_name, false);
+        if (!fs_mgr_update_logical_partition(&entry)) {
+            LOG(ERROR) << "Could not update logical partition";
+            continue;
+        }
+
+        extra_fstab.emplace_back(std::move(entry));
+    }
+
+    SkipMountingPartitions(&extra_fstab);
+    if (extra_fstab.empty()) {
+        return;
+    }
+
+    BlockDevInitializer block_dev_init;
+    for (auto& entry : extra_fstab) {
+        if (access(entry.blk_device.c_str(), F_OK) != 0) {
+            auto block_dev = android::base::Basename(entry.blk_device);
+            if (!block_dev_init.InitDmDevice(block_dev)) {
+                LOG(ERROR) << "Failed to find device-mapper node: " << block_dev;
+                continue;
+            }
+        }
+        if (fs_mgr_do_mount_one(entry)) {
+            LOG(ERROR) << "Could not mount " << entry.mount_point;
+        }
+    }
+}
+
 int SetupSelinux(char** argv) {
     SetStdioToDevNull(argv);
     InitKernelLogging(argv);
@@ -608,6 +679,8 @@
 
     boot_clock::time_point start_time = boot_clock::now();
 
+    MountMissingSystemPartitions();
+
     // Set up SELinux, loading the SELinux policy.
     SelinuxSetupKernelLogging();
     SelinuxInitialize();
diff --git a/init/service.cpp b/init/service.cpp
index b12d11a..20400a0 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -90,7 +90,9 @@
                        << "\") has incorrect label or no domain transition from " << mycon.get()
                        << " to another SELinux domain defined. Have you configured your "
                           "service correctly? https://source.android.com/security/selinux/"
-                          "device-policy#label_new_services_and_address_denials";
+                          "device-policy#label_new_services_and_address_denials. Note: this "
+                          "error shows up even in permissive mode in order to make auditing "
+                          "denials possible.";
     }
     if (rc < 0) {
         return Error() << "Could not get process context";
diff --git a/init/service.h b/init/service.h
index cf3f0c2..9f1d697 100644
--- a/init/service.h
+++ b/init/service.h
@@ -130,6 +130,13 @@
     bool is_updatable() const { return updatable_; }
     bool is_post_data() const { return post_data_; }
     bool is_from_apex() const { return from_apex_; }
+    void set_oneshot(bool value) {
+        if (value) {
+            flags_ |= SVC_ONESHOT;
+        } else {
+            flags_ &= ~SVC_ONESHOT;
+        }
+    }
 
   private:
     void NotifyStateChange(const std::string& new_state) const;
diff --git a/init/sysprop/InitProperties.sysprop b/init/sysprop/InitProperties.sysprop
index b876dc0..24c2434 100644
--- a/init/sysprop/InitProperties.sysprop
+++ b/init/sysprop/InitProperties.sysprop
@@ -31,6 +31,6 @@
     type: Boolean
     scope: Public
     access: Readonly
-    prop_name: "ro.init.userspace_reboot.is_supported"
+    prop_name: "init.userspace_reboot.is_supported"
     integer_as_bool: true
 }
diff --git a/init/sysprop/api/com.android.sysprop.init-current.txt b/init/sysprop/api/com.android.sysprop.init-current.txt
index b8bcef9..01f4e9a 100644
--- a/init/sysprop/api/com.android.sysprop.init-current.txt
+++ b/init/sysprop/api/com.android.sysprop.init-current.txt
@@ -2,7 +2,7 @@
   module: "android.sysprop.InitProperties"
   prop {
     api_name: "is_userspace_reboot_supported"
-    prop_name: "ro.init.userspace_reboot.is_supported"
+    prop_name: "init.userspace_reboot.is_supported"
     integer_as_bool: true
   }
   prop {
diff --git a/init/test_kill_services/Android.bp b/init/test_kill_services/Android.bp
index f6e85e2..d59e548 100644
--- a/init/test_kill_services/Android.bp
+++ b/init/test_kill_services/Android.bp
@@ -3,5 +3,9 @@
     srcs: ["init_kill_services_test.cpp"],
     shared_libs: ["libbase"],
     test_suites: ["general-tests"],
-    require_root: true,
+
+    // TODO(b/153565474): switch back to auto-generation
+    // and add back:
+    //     require_root: true,
+    auto_gen_config: false,
 }
diff --git a/init/test_kill_services/AndroidTest.xml b/init/test_kill_services/AndroidTest.xml
new file mode 100644
index 0000000..c1dcd59
--- /dev/null
+++ b/init/test_kill_services/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?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 init_kill_services_test.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native" />
+
+    <!-- cannot be autogenerated: b/153565474 -->
+    <target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="init_kill_services_test->/data/local/tmp/init_kill_services_test" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="init_kill_services_test" />
+    </test>
+</configuration>
diff --git a/init/tokenizer_test.cpp b/init/tokenizer_test.cpp
index 6b31683..0122884 100644
--- a/init/tokenizer_test.cpp
+++ b/init/tokenizer_test.cpp
@@ -28,7 +28,7 @@
 
 void RunTest(const std::string& data, const std::vector<std::vector<std::string>>& expected_tokens) {
     auto data_copy = std::string{data};
-    data_copy.push_back('\n');  // TODO: fix tokenizer
+    data_copy.push_back('\n');
     data_copy.push_back('\0');
 
     parse_state state;
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index d2b503b..7514b61 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -285,7 +285,6 @@
 
     // Keep the current product name base configuration so we remain backwards compatible and
     // allow it to override everything.
-    // TODO: cleanup platform ueventd.rc to remove vendor specific device node entries (b/34968103)
     auto hardware = android::base::GetProperty("ro.hardware", "");
 
     auto ueventd_configuration = ParseConfig({"/system/etc/ueventd.rc", "/vendor/ueventd.rc",
diff --git a/init/ueventd_test.cpp b/init/ueventd_test.cpp
index 2d7d2f8..fc3cdfb 100644
--- a/init/ueventd_test.cpp
+++ b/init/ueventd_test.cpp
@@ -53,11 +53,7 @@
     };
 
     std::vector<std::thread> threads;
-    // TODO(b/63712782): Structured bindings + templated containers are broken in clang :(
-    // for (const auto& [file, parameter] : files_and_parameters) {
-    for (const auto& pair : files_and_parameters) {
-        const auto& file = pair.first;
-        const auto& parameter = pair.second;
+    for (const auto& [file, parameter] : files_and_parameters) {
         threads.emplace_back(std::thread(make_thread_function(file, parameter)));
     }
 
diff --git a/libasyncio/Android.bp b/libasyncio/Android.bp
index 4ab439d..44e7933 100644
--- a/libasyncio/Android.bp
+++ b/libasyncio/Android.bp
@@ -28,6 +28,10 @@
     defaults: ["libasyncio_defaults"],
     vendor_available: true,
     recovery_available: true,
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.adbd",
+    ],
     host_supported: true,
     srcs: [
         "AsyncIO.cpp",
diff --git a/libbacktrace/Android.bp b/libbacktrace/Android.bp
index 76caadc..dc989a0 100644
--- a/libbacktrace/Android.bp
+++ b/libbacktrace/Android.bp
@@ -50,20 +50,9 @@
     ],
 }
 
-cc_library {
-    name: "libbacktrace",
-    vendor_available: false,
-    recovery_available: true,
-    apex_available: [
-        "//apex_available:platform",
-        "//apex_available:anyapex",
-    ],
-    vndk: {
-        enabled: true,
-        support_system_process: true,
-    },
+cc_defaults {
+    name: "libbacktrace_defaults",
     defaults: ["libbacktrace_common"],
-    host_supported: true,
 
     cflags: [
         "-Wexit-time-destructors",
@@ -88,7 +77,6 @@
             shared_libs: [
                 "libbase",
                 "liblog",
-                "libunwindstack",
             ],
 
             static_libs: [
@@ -101,6 +89,30 @@
                 whole_static_libs: ["libasync_safe"],
             },
         },
+    },
+}
+
+cc_library {
+    name: "libbacktrace",
+    vendor_available: false,
+    recovery_available: true,
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
+    vndk: {
+        enabled: true,
+        support_system_process: true,
+    },
+    host_supported: true,
+    defaults: ["libbacktrace_defaults"],
+
+    target: {
+        linux: {
+            shared_libs: [
+                "libunwindstack",
+            ],
+        },
         vendor: {
             cflags: ["-DNO_LIBDEXFILE_SUPPORT"],
         },
@@ -110,6 +122,21 @@
     },
 }
 
+// Static library without DEX support to avoid dependencies on the ART APEX.
+cc_library_static {
+    name: "libbacktrace_no_dex",
+    visibility: ["//system/core/debuggerd"],
+    defaults: ["libbacktrace_defaults"],
+    cflags: ["-DNO_LIBDEXFILE_SUPPORT"],
+    target: {
+        linux: {
+            static_libs: [
+                "libunwindstack_no_dex",
+            ],
+        },
+    },
+}
+
 cc_test_library {
     name: "libbacktrace_test",
     defaults: ["libbacktrace_common"],
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index dccf588..175b2b7 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -346,7 +346,7 @@
 
 cc_test {
     name: "KernelLibcutilsTest",
-    test_suites: ["general-tests", "vts-core"],
+    test_suites: ["general-tests", "vts"],
     defaults: ["libcutils_test_static_defaults"],
     test_config: "KernelLibcutilsTest.xml",
 }
diff --git a/libcutils/arch-x86/android_memset16.S b/libcutils/arch-x86/android_memset16.S
index cb2ff14..f4d497e 100755
--- a/libcutils/arch-x86/android_memset16.S
+++ b/libcutils/arch-x86/android_memset16.S
@@ -105,14 +105,16 @@
     /* We loaded the jump table and adjuested EDX. Go.  */	\
     jmp		*%ebx
 
-	.section	.gnu.linkonce.t.__x86.get_pc_thunk.bx,"ax",@progbits
+	.section	.text.__x86.get_pc_thunk.bx,"axG",@progbits,__x86.get_pc_thunk.bx,comdat
 	.globl	__x86.get_pc_thunk.bx
 	.hidden	__x86.get_pc_thunk.bx
 	ALIGN (4)
 	.type	__x86.get_pc_thunk.bx,@function
 __x86.get_pc_thunk.bx:
+	cfi_startproc
 	movl	(%esp), %ebx
 	ret
+	cfi_endproc
 #else
 # define ENTRANCE
 # define RETURN_END	ret
diff --git a/libcutils/arch-x86/android_memset32.S b/libcutils/arch-x86/android_memset32.S
index f4326dc..b928f6b 100755
--- a/libcutils/arch-x86/android_memset32.S
+++ b/libcutils/arch-x86/android_memset32.S
@@ -105,14 +105,16 @@
     /* We loaded the jump table and adjuested EDX. Go.  */	\
     jmp		*%ebx
 
-	.section	.gnu.linkonce.t.__x86.get_pc_thunk.bx,"ax",@progbits
+	.section	.text.__x86.get_pc_thunk.bx,"axG",@progbits,__x86.get_pc_thunk.bx,comdat
 	.globl	__x86.get_pc_thunk.bx
 	.hidden	__x86.get_pc_thunk.bx
 	ALIGN (4)
 	.type	__x86.get_pc_thunk.bx,@function
 __x86.get_pc_thunk.bx:
+	cfi_startproc
 	movl	(%esp), %ebx
 	ret
+	cfi_endproc
 #else
 # define ENTRANCE
 # define RETURN_END	ret
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index b73a29b..e4f45a8 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -34,14 +34,7 @@
  * partition, from which the system reads passwd and group files.
  */
 
-#ifndef _ANDROID_FILESYSTEM_CONFIG_H_
-#define _ANDROID_FILESYSTEM_CONFIG_H_
-
-#include <sys/types.h>
-
-#if !defined(__ANDROID_VNDK__) && !defined(EXCLUDE_FS_CONFIG_STRUCTURES)
-#include <private/fs_config.h>
-#endif
+#pragma once
 
 /* This is the master Users and Groups config for the platform.
  * DO NOT EVER RENUMBER
@@ -224,5 +217,3 @@
  * documented at the top of this header file.
  * Also see build/tools/fs_config for more details.
  */
-
-#endif
diff --git a/libcutils/include/private/canned_fs_config.h b/libcutils/include/private/canned_fs_config.h
index 135b91c..ad4de4c 100644
--- a/libcutils/include/private/canned_fs_config.h
+++ b/libcutils/include/private/canned_fs_config.h
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef _CANNED_FS_CONFIG_H
-#define _CANNED_FS_CONFIG_H
+#pragma once
 
 #include <inttypes.h>
+#include <sys/cdefs.h>
 
 __BEGIN_DECLS
 
@@ -26,5 +26,3 @@
                       unsigned* gid, unsigned* mode, uint64_t* capabilities);
 
 __END_DECLS
-
-#endif
diff --git a/liblog/README.md b/liblog/README.md
index 871399a..f64f376 100644
--- a/liblog/README.md
+++ b/liblog/README.md
@@ -118,10 +118,9 @@
 finally a call closing the logs.  A single log can be opened with `android_logger_list_open()`; or
 multiple logs can be opened with `android_logger_list_alloc()`, calling in turn the
 `android_logger_open()` for each log id.  Each entry can be retrieved with
-`android_logger_list_read()`.  The log(s) can be closed with `android_logger_list_free()`.  The logs
-should be opened with an `ANDROID_LOG_RDONLY` mode.  `ANDROID_LOG_NONBLOCK` mode will report when
-the log reading is done with an `EAGAIN` error return code, otherwise the
-`android_logger_list_read()` call will block for new entries.
+`android_logger_list_read()`.  The log(s) can be closed with `android_logger_list_free()`.
+`ANDROID_LOG_NONBLOCK` mode will report when the log reading is done with an `EAGAIN` error return 
+code, otherwise the `android_logger_list_read()` call will block for new entries.
 
 The `ANDROID_LOG_WRAP` mode flag to the `android_logger_list_alloc_time()` signals logd to quiesce
 the reader until the buffer is about to prune at the start time then proceed to dumping content.
@@ -130,14 +129,12 @@
 logs to the persistent logs from before the last reboot.
 
 The value returned by `android_logger_open()` can be used as a parameter to the
-`android_logger_clear()` function to empty the sub-log.  It is recommended to only open log
-`ANDROID_LOG_WRONLY` in that case.
+`android_logger_clear()` function to empty the sub-log.
 
 The value returned by `android_logger_open()` can be used as a parameter to the
 `android_logger_get_log_(size|readable_size|version)` to retrieve the sub-log maximum size, readable
 size and log buffer format protocol version respectively.  `android_logger_get_id()` returns the id
-that was used when opening the sub-log.  It is recommended to open the log `ANDROID_LOG_RDONLY` in
-these cases.
+that was used when opening the sub-log.
 
 Errors
 ------
diff --git a/liblog/README.protocol.md b/liblog/README.protocol.md
index fef29c9..f247b28 100644
--- a/liblog/README.protocol.md
+++ b/liblog/README.protocol.md
@@ -17,6 +17,49 @@
         };
     };
 
+where the embedded structs are defined as:
+
+    struct android_log_header_t {
+        uint8_t id;
+        uint16_t tid;
+        log_time realtime;
+    };
+
+    struct log_time {
+        uint32_t tv_sec = 0;
+        uint32_t tv_nsec = 0;
+    }
+
+    struct android_event_header_t {
+        int32_t tag;
+    };
+
+    struct android_event_list_t {
+        int8_t type;  // EVENT_TYPE_LIST
+        int8_t element_count;
+    };
+
+    struct android_event_float_t {
+        int8_t type;  // EVENT_TYPE_FLOAT
+        float data;
+    };
+
+    struct android_event_int_t {
+        int8_t type;   // EVENT_TYPE_INT
+        int32_t data;
+    } android_event_int_t;
+
+    struct android_event_long_t {
+        int8_t type;   // EVENT_TYPE_LONG
+        int64_t data;
+    };
+
+    struct android_event_string_t {
+        int8_t type;     // EVENT_TYPE_STRING;
+        int32_t length;
+        char data[];
+    };
+
 The payload, excluding the header, has a max size of LOGGER_ENTRY_MAX_PAYLOAD.
 
 ## header
diff --git a/liblog/include/android/log.h b/liblog/include/android/log.h
index 43a91ab..512c7cd 100644
--- a/liblog/include/android/log.h
+++ b/liblog/include/android/log.h
@@ -184,20 +184,21 @@
  * Logger data struct used for writing log messages to liblog via __android_log_write_logger_data()
  * and sending log messages to user defined loggers specified in __android_log_set_logger().
  */
-struct __android_logger_data {
-  size_t struct_size; /* Must be set to sizeof(__android_logger_data) and is used for versioning. */
-  int32_t buffer_id;  /* log_id_t or -1 to represent 'default'. */
-  int32_t priority;   /* android_LogPriority values. */
-  const char* tag;
-  const char* file;  /* Optional file name, may be set to nullptr. */
-  uint32_t line;     /* Optional line number, ignore if file is nullptr. */
+struct __android_log_message {
+  size_t
+      struct_size;   /** Must be set to sizeof(__android_log_message) and is used for versioning. */
+  int32_t buffer_id; /** {@link log_id_t} values. */
+  int32_t priority;  /** {@link android_LogPriority} values. */
+  const char* tag;   /** The tag for the log message. */
+  const char* file;  /** Optional file name, may be set to nullptr. */
+  uint32_t line;     /** Optional line number, ignore if file is nullptr. */
+  const char* message; /** The log message itself. */
 };
 
 /**
  * Prototype for the 'logger' function that is called for every log message.
  */
-typedef void (*__android_logger_function)(const struct __android_logger_data* logger_data,
-                                          const char* message);
+typedef void (*__android_logger_function)(const struct __android_log_message* log_message);
 /**
  * Prototype for the 'abort' function that is called when liblog will abort due to
  * __android_log_assert() failures.
@@ -206,52 +207,85 @@
 
 #if !defined(__ANDROID__) || __ANDROID_API__ >= 30
 /**
- * Writes the log message specified with logger_data and msg to the log.  logger_data includes
- * additional file name and line number information that a logger may use.  logger_data is versioned
- * for backwards compatibility.
+ * Writes the log message specified by log_message.  log_message includes additional file name and
+ * line number information that a logger may use.  log_message is versioned for backwards
+ * compatibility.
  * This assumes that loggability has already been checked through __android_log_is_loggable().
  * Higher level logging libraries, such as libbase, first check loggability, then format their
  * buffers, then pass the message to liblog via this function, and therefore we do not want to
  * duplicate the loggability check here.
+ *
+ * @param log_message the log message itself, see {@link __android_log_message}.
+ *
+ * Available since API level 30.
  */
-void __android_log_write_logger_data(struct __android_logger_data* logger_data, const char* msg)
-    __INTRODUCED_IN(30);
+void __android_log_write_log_message(struct __android_log_message* log_message) __INTRODUCED_IN(30);
 
 /**
  * Sets a user defined logger function.  All log messages sent to liblog will be set to the
- * function pointer specified by logger for processing.
+ * function pointer specified by logger for processing.  It is not expected that log messages are
+ * already terminated with a new line.  This function should add new lines if required for line
+ * separation.
+ *
+ * @param logger the new function that will handle log messages.
+ *
+ * Available since API level 30.
  */
 void __android_log_set_logger(__android_logger_function logger) __INTRODUCED_IN(30);
 
 /**
  * Writes the log message to logd.  This is an __android_logger_function and can be provided to
  * __android_log_set_logger().  It is the default logger when running liblog on a device.
+ *
+ * @param log_message the log message to write, see {@link __android_log_message}.
+ *
+ * Available since API level 30.
  */
-void __android_log_logd_logger(const struct __android_logger_data* logger_data, const char* msg)
-    __INTRODUCED_IN(30);
+void __android_log_logd_logger(const struct __android_log_message* log_message) __INTRODUCED_IN(30);
 
 /**
  * Writes the log message to stderr.  This is an __android_logger_function and can be provided to
  * __android_log_set_logger().  It is the default logger when running liblog on host.
+ *
+ * @param log_message the log message to write, see {@link __android_log_message}.
+ *
+ * Available since API level 30.
  */
-void __android_log_stderr_logger(const struct __android_logger_data* logger_data,
-                                 const char* message) __INTRODUCED_IN(30);
+void __android_log_stderr_logger(const struct __android_log_message* log_message)
+    __INTRODUCED_IN(30);
 
 /**
- * Sets a user defined aborter function that is called for __android_log_assert() failures.
+ * Sets a user defined aborter function that is called for __android_log_assert() failures.  This
+ * user defined aborter function is highly recommended to abort and be noreturn, but is not strictly
+ * required to.
+ *
+ * @param aborter the new aborter function, see {@link __android_aborter_function}.
+ *
+ * Available since API level 30.
  */
 void __android_log_set_aborter(__android_aborter_function aborter) __INTRODUCED_IN(30);
 
 /**
  * Calls the stored aborter function.  This allows for other logging libraries to use the same
  * aborter function by calling this function in liblog.
+ *
+ * @param abort_message an additional message supplied when aborting, for example this is used to
+ *                      call android_set_abort_message() in __android_log_default_aborter().
+ *
+ * Available since API level 30.
  */
 void __android_log_call_aborter(const char* abort_message) __INTRODUCED_IN(30);
 
 /**
  * Sets android_set_abort_message() on device then aborts().  This is the default aborter.
+ *
+ * @param abort_message an additional message supplied when aborting.  This functions calls
+ *                      android_set_abort_message() with its contents.
+ *
+ * Available since API level 30.
  */
-void __android_log_default_aborter(const char* abort_message) __INTRODUCED_IN(30);
+void __android_log_default_aborter(const char* abort_message) __attribute__((noreturn))
+__INTRODUCED_IN(30);
 
 /**
  * Use the per-tag properties "log.tag.<tagname>" along with the minimum priority from
@@ -263,7 +297,13 @@
  * minimum priority needed to log.  If only one is set, then that value is used to determine the
  * minimum priority needed.  If none are set, then default_priority is used.
  *
- * prio is ANDROID_LOG_VERBOSE to ANDROID_LOG_FATAL.
+ * @param prio         the priority to test, takes {@link android_LogPriority} values.
+ * @param tag          the tag to test.
+ * @param len          the length of the tag.
+ * @param default_prio the default priority to use if no properties or minimum priority are set.
+ * @return an integer where 1 indicates that the message is loggable and 0 indicates that it is not.
+ *
+ * Available since API level 30.
  */
 int __android_log_is_loggable(int prio, const char* tag, int default_prio) __INTRODUCED_IN(30);
 int __android_log_is_loggable_len(int prio, const char* tag, size_t len, int default_prio)
@@ -272,13 +312,22 @@
 /**
  * Sets the minimum priority that will be logged for this process.
  *
- * This returns the previous set minimum priority, or ANDROID_LOG_DEFAULT if none was set.
+ * @param priority the new minimum priority to set, takes @{link android_LogPriority} values.
+ * @return the previous set minimum priority as @{link android_LogPriority} values, or
+ *         ANDROID_LOG_DEFAULT if none was set.
+ *
+ * Available since API level 30.
  */
 int32_t __android_log_set_minimum_priority(int32_t priority) __INTRODUCED_IN(30);
 
 /**
  * Gets the minimum priority that will be logged for this process.  If none has been set by a
  * previous __android_log_set_minimum_priority() call, this returns ANDROID_LOG_DEFAULT.
+ *
+ * @return the current minimum priority as @{link android_LogPriority} values, or
+ *         ANDROID_LOG_DEFAULT if none is set.
+ *
+ * Available since API level 30.
  */
 int32_t __android_log_get_minimum_priority(void) __INTRODUCED_IN(30);
 
@@ -286,6 +335,10 @@
  * Sets the default tag if no tag is provided when writing a log message.  Defaults to
  * getprogname().  This truncates tag to the maximum log message size, though appropriate tags
  * should be much smaller.
+ *
+ * @param tag the new log tag.
+ *
+ * Available since API level 30.
  */
 void __android_log_set_default_tag(const char* tag) __INTRODUCED_IN(30);
 #endif
diff --git a/liblog/include/log/log.h b/liblog/include/log/log.h
index 90d1e76..19edb83 100644
--- a/liblog/include/log/log.h
+++ b/liblog/include/log/log.h
@@ -87,8 +87,6 @@
 /*
  * Event log entry types.
  */
-#ifndef __AndroidEventLogType_defined
-#define __AndroidEventLogType_defined
 typedef enum {
   /* Special markers for android_log_list_element type */
   EVENT_TYPE_LIST_STOP = '\n', /* declare end of list  */
@@ -101,9 +99,6 @@
   EVENT_TYPE_LIST = 3,
   EVENT_TYPE_FLOAT = 4,
 } AndroidEventLogType;
-#endif
-#define sizeof_AndroidEventLogType sizeof(typeof_AndroidEventLogType)
-#define typeof_AndroidEventLogType unsigned char
 
 #ifndef LOG_EVENT_INT
 #define LOG_EVENT_INT(_tag, _value)                                          \
diff --git a/liblog/include/log/log_read.h b/liblog/include/log/log_read.h
index 18c1c33..e2bc297 100644
--- a/liblog/include/log/log_read.h
+++ b/liblog/include/log/log_read.h
@@ -16,22 +16,8 @@
 
 #pragma once
 
-#include <sys/types.h>
-
-/* deal with possible sys/cdefs.h conflict with fcntl.h */
-#ifdef __unused
-#define __unused_defined __unused
-#undef __unused
-#endif
-
-#include <fcntl.h> /* Pick up O_* macros */
-
-/* restore definitions from above */
-#ifdef __unused_defined
-#define __unused __attribute__((__unused__))
-#endif
-
 #include <stdint.h>
+#include <sys/types.h>
 
 #include <log/log_id.h>
 #include <log/log_time.h>
@@ -40,6 +26,8 @@
 extern "C" {
 #endif
 
+#define ANDROID_LOG_WRAP_DEFAULT_TIMEOUT 7200 /* 2 hour default */
+
 /*
  * Native log reading interface section. See logcat for sample code.
  *
@@ -48,8 +36,6 @@
  * access to raw information, or parsing is an issue.
  */
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wzero-length-array"
 struct logger_entry {
   uint16_t len;      /* length of the payload */
   uint16_t hdr_size; /* sizeof(struct logger_entry) */
@@ -59,9 +45,7 @@
   uint32_t nsec;     /* nanoseconds */
   uint32_t lid;      /* log id of the payload, bottom 4 bits currently */
   uint32_t uid;      /* generating process's uid */
-  char msg[0]; /* the entry's payload */
 };
-#pragma clang diagnostic pop
 
 /*
  * The maximum size of the log entry payload that can be
@@ -83,32 +67,9 @@
     struct logger_entry entry;
   } __attribute__((aligned(4)));
 #ifdef __cplusplus
-  /* Matching log_time operators */
-  bool operator==(const log_msg& T) const {
-    return (entry.sec == T.entry.sec) && (entry.nsec == T.entry.nsec);
-  }
-  bool operator!=(const log_msg& T) const {
-    return !(*this == T);
-  }
-  bool operator<(const log_msg& T) const {
-    return (entry.sec < T.entry.sec) ||
-           ((entry.sec == T.entry.sec) && (entry.nsec < T.entry.nsec));
-  }
-  bool operator>=(const log_msg& T) const {
-    return !(*this < T);
-  }
-  bool operator>(const log_msg& T) const {
-    return (entry.sec > T.entry.sec) ||
-           ((entry.sec == T.entry.sec) && (entry.nsec > T.entry.nsec));
-  }
-  bool operator<=(const log_msg& T) const {
-    return !(*this > T);
-  }
   uint64_t nsec() const {
     return static_cast<uint64_t>(entry.sec) * NS_PER_SEC + entry.nsec;
   }
-
-  /* packet methods */
   log_id_t id() {
     return static_cast<log_id_t>(entry.lid);
   }
@@ -141,17 +102,10 @@
                                       char* buf, size_t len);
 int android_logger_set_prune_list(struct logger_list* logger_list, const char* buf, size_t len);
 
-#define ANDROID_LOG_RDONLY O_RDONLY
-#define ANDROID_LOG_WRONLY O_WRONLY
-#define ANDROID_LOG_RDWR O_RDWR
-#define ANDROID_LOG_ACCMODE O_ACCMODE
-#ifndef O_NONBLOCK
+/* The below values are used for the `mode` argument of the below functions. */
+/* Note that 0x00000003 were previously used and should be considered reserved. */
 #define ANDROID_LOG_NONBLOCK 0x00000800
-#else
-#define ANDROID_LOG_NONBLOCK O_NONBLOCK
-#endif
 #define ANDROID_LOG_WRAP 0x40000000 /* Block until buffer about to wrap */
-#define ANDROID_LOG_WRAP_DEFAULT_TIMEOUT 7200 /* 2 hour default */
 #define ANDROID_LOG_PSTORE 0x80000000
 
 struct logger_list* android_logger_list_alloc(int mode, unsigned int tail,
diff --git a/liblog/include_vndk/log/log.h b/liblog/include_vndk/log/log.h
index a79beec..ab4adc4 100644
--- a/liblog/include_vndk/log/log.h
+++ b/liblog/include_vndk/log/log.h
@@ -3,6 +3,9 @@
 #ifndef _LIBS_LOG_LOG_H
 #define _LIBS_LOG_LOG_H
 
+/* Historically vendors have depended on this header being included. */
+#include <fcntl.h>
+
 #include <android/log.h>
 #include <log/log_id.h>
 #include <log/log_main.h>
diff --git a/liblog/liblog.map.txt b/liblog/liblog.map.txt
index 198cdae..161fcf1 100644
--- a/liblog/liblog.map.txt
+++ b/liblog/liblog.map.txt
@@ -1,6 +1,6 @@
 LIBLOG {
   global:
-    android_name_to_log_id; # llndk
+    android_name_to_log_id; # apex llndk
     android_log_id_to_name; # llndk
     __android_log_assert;
     __android_log_buf_print;
@@ -22,7 +22,7 @@
     android_logger_list_alloc; # apex llndk
     android_logger_list_alloc_time; # apex llndk
     android_logger_list_free; # apex llndk
-    android_logger_list_open; # llndk
+    android_logger_list_open; # apex llndk
     android_logger_list_read; # apex llndk
     android_logger_open; # apex llndk
     android_logger_set_log_size; # llndk
@@ -65,7 +65,7 @@
     android_log_parser_reset; # llndk
 };
 
-LIGLOG_R { # introduced=30
+LIBLOG_R { # introduced=30
   global:
     __android_log_call_aborter;
     __android_log_default_aborter;
@@ -77,7 +77,7 @@
     __android_log_set_logger;
     __android_log_set_minimum_priority;
     __android_log_stderr_logger;
-    __android_log_write_logger_data;
+    __android_log_write_log_message;
 };
 
 LIBLOG_PRIVATE {
diff --git a/liblog/logger_write.cpp b/liblog/logger_write.cpp
index a8620a0..d15b367 100644
--- a/liblog/logger_write.cpp
+++ b/liblog/logger_write.cpp
@@ -250,8 +250,7 @@
 #endif
 }
 
-void __android_log_stderr_logger(const struct __android_logger_data* logger_data,
-                                 const char* message) {
+void __android_log_stderr_logger(const struct __android_log_message* log_message) {
   struct tm now;
   time_t t = time(nullptr);
 
@@ -268,33 +267,32 @@
   static_assert(arraysize(log_characters) - 1 == ANDROID_LOG_SILENT,
                 "Mismatch in size of log_characters and values in android_LogPriority");
   int32_t priority =
-      logger_data->priority > ANDROID_LOG_SILENT ? ANDROID_LOG_FATAL : logger_data->priority;
+      log_message->priority > ANDROID_LOG_SILENT ? ANDROID_LOG_FATAL : log_message->priority;
   char priority_char = log_characters[priority];
   uint64_t tid = GetThreadId();
 
-  if (logger_data->file != nullptr) {
+  if (log_message->file != nullptr) {
     fprintf(stderr, "%s %c %s %5d %5" PRIu64 " %s:%u] %s\n",
-            logger_data->tag ? logger_data->tag : "nullptr", priority_char, timestamp, getpid(),
-            tid, logger_data->file, logger_data->line, message);
+            log_message->tag ? log_message->tag : "nullptr", priority_char, timestamp, getpid(),
+            tid, log_message->file, log_message->line, log_message->message);
   } else {
     fprintf(stderr, "%s %c %s %5d %5" PRIu64 " %s\n",
-            logger_data->tag ? logger_data->tag : "nullptr", priority_char, timestamp, getpid(),
-            tid, message);
+            log_message->tag ? log_message->tag : "nullptr", priority_char, timestamp, getpid(),
+            tid, log_message->message);
   }
 }
 
-void __android_log_logd_logger(const struct __android_logger_data* logger_data,
-                               const char* message) {
-  int buffer_id = logger_data->buffer_id == LOG_ID_DEFAULT ? LOG_ID_MAIN : logger_data->buffer_id;
+void __android_log_logd_logger(const struct __android_log_message* log_message) {
+  int buffer_id = log_message->buffer_id == LOG_ID_DEFAULT ? LOG_ID_MAIN : log_message->buffer_id;
 
   struct iovec vec[3];
   vec[0].iov_base =
-      const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(&logger_data->priority));
+      const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(&log_message->priority));
   vec[0].iov_len = 1;
-  vec[1].iov_base = const_cast<void*>(static_cast<const void*>(logger_data->tag));
-  vec[1].iov_len = strlen(logger_data->tag) + 1;
-  vec[2].iov_base = const_cast<void*>(static_cast<const void*>(message));
-  vec[2].iov_len = strlen(message) + 1;
+  vec[1].iov_base = const_cast<void*>(static_cast<const void*>(log_message->tag));
+  vec[1].iov_len = strlen(log_message->tag) + 1;
+  vec[2].iov_base = const_cast<void*>(static_cast<const void*>(log_message->message));
+  vec[2].iov_len = strlen(log_message->message) + 1;
 
   write_to_log(static_cast<log_id_t>(buffer_id), vec, 3);
 }
@@ -303,40 +301,41 @@
   return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
 }
 
-void __android_log_write_logger_data(__android_logger_data* logger_data, const char* msg) {
+void __android_log_write_log_message(__android_log_message* log_message) {
   ErrnoRestorer errno_restorer;
 
-  if (logger_data->buffer_id != LOG_ID_DEFAULT && logger_data->buffer_id != LOG_ID_MAIN &&
-      logger_data->buffer_id != LOG_ID_SYSTEM && logger_data->buffer_id != LOG_ID_RADIO &&
-      logger_data->buffer_id != LOG_ID_CRASH) {
+  if (log_message->buffer_id != LOG_ID_DEFAULT && log_message->buffer_id != LOG_ID_MAIN &&
+      log_message->buffer_id != LOG_ID_SYSTEM && log_message->buffer_id != LOG_ID_RADIO &&
+      log_message->buffer_id != LOG_ID_CRASH) {
     return;
   }
 
   auto tag_lock = std::shared_lock{default_tag_lock, std::defer_lock};
-  if (logger_data->tag == nullptr) {
+  if (log_message->tag == nullptr) {
     tag_lock.lock();
-    logger_data->tag = GetDefaultTag().c_str();
+    log_message->tag = GetDefaultTag().c_str();
   }
 
 #if __BIONIC__
-  if (logger_data->priority == ANDROID_LOG_FATAL) {
-    android_set_abort_message(msg);
+  if (log_message->priority == ANDROID_LOG_FATAL) {
+    android_set_abort_message(log_message->message);
   }
 #endif
 
   auto lock = std::shared_lock{logger_function_lock};
-  logger_function(logger_data, msg);
+  logger_function(log_message);
 }
 
 int __android_log_buf_write(int bufID, int prio, const char* tag, const char* msg) {
   ErrnoRestorer errno_restorer;
 
   if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
-    return 0;
+    return -EPERM;
   }
 
-  __android_logger_data logger_data = {sizeof(__android_logger_data), bufID, prio, tag, nullptr, 0};
-  __android_log_write_logger_data(&logger_data, msg);
+  __android_log_message log_message = {
+      sizeof(__android_log_message), bufID, prio, tag, nullptr, 0, msg};
+  __android_log_write_log_message(&log_message);
   return 1;
 }
 
@@ -344,16 +343,16 @@
   ErrnoRestorer errno_restorer;
 
   if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
-    return 0;
+    return -EPERM;
   }
 
   __attribute__((uninitialized)) char buf[LOG_BUF_SIZE];
 
   vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
 
-  __android_logger_data logger_data = {
-      sizeof(__android_logger_data), LOG_ID_MAIN, prio, tag, nullptr, 0};
-  __android_log_write_logger_data(&logger_data, buf);
+  __android_log_message log_message = {
+      sizeof(__android_log_message), LOG_ID_MAIN, prio, tag, nullptr, 0, buf};
+  __android_log_write_log_message(&log_message);
   return 1;
 }
 
@@ -361,7 +360,7 @@
   ErrnoRestorer errno_restorer;
 
   if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
-    return 0;
+    return -EPERM;
   }
 
   va_list ap;
@@ -371,9 +370,9 @@
   vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
   va_end(ap);
 
-  __android_logger_data logger_data = {
-      sizeof(__android_logger_data), LOG_ID_MAIN, prio, tag, nullptr, 0};
-  __android_log_write_logger_data(&logger_data, buf);
+  __android_log_message log_message = {
+      sizeof(__android_log_message), LOG_ID_MAIN, prio, tag, nullptr, 0, buf};
+  __android_log_write_log_message(&log_message);
   return 1;
 }
 
@@ -381,7 +380,7 @@
   ErrnoRestorer errno_restorer;
 
   if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
-    return 0;
+    return -EPERM;
   }
 
   va_list ap;
@@ -391,8 +390,9 @@
   vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
   va_end(ap);
 
-  __android_logger_data logger_data = {sizeof(__android_logger_data), bufID, prio, tag, nullptr, 0};
-  __android_log_write_logger_data(&logger_data, buf);
+  __android_log_message log_message = {
+      sizeof(__android_log_message), bufID, prio, tag, nullptr, 0, buf};
+  __android_log_write_log_message(&log_message);
   return 1;
 }
 
diff --git a/liblog/logprint.cpp b/liblog/logprint.cpp
index e32878a..5c69bf8 100644
--- a/liblog/logprint.cpp
+++ b/liblog/logprint.cpp
@@ -509,12 +509,12 @@
    * format: <priority:1><tag:N>\0<message:N>\0
    *
    * tag str
-   *   starts at buf->msg+1
+   *   starts at buf + buf->hdr_size + 1
    * msg
-   *   starts at buf->msg+1+len(tag)+1
+   *   starts at buf + buf->hdr_size + 1 + len(tag) + 1
    *
-   * The message may have been truncated by the kernel log driver.
-   * When that happens, we must null-terminate the message ourselves.
+   * The message may have been truncated.  When that happens, we must null-terminate the message
+   * ourselves.
    */
   if (buf->len < 3) {
     /*
@@ -529,11 +529,11 @@
   int msgEnd = -1;
 
   int i;
-  char* msg = buf->msg;
-  if (buf->hdr_size != sizeof(struct logger_entry)) {
-    fprintf(stderr, "+++ LOG: entry illegal hdr_size\n");
+  if (buf->hdr_size < sizeof(logger_entry)) {
+    fprintf(stderr, "+++ LOG: hdr_size must be at least as big as struct logger_entry\n");
     return -1;
   }
+  char* msg = reinterpret_cast<char*>(buf) + buf->hdr_size;
   entry->uid = buf->uid;
 
   for (i = 1; i < buf->len; i++) {
@@ -985,11 +985,11 @@
   entry->pid = buf->pid;
   entry->tid = buf->tid;
 
-  eventData = (const unsigned char*)buf->msg;
-  if (buf->hdr_size != sizeof(struct logger_entry)) {
-    fprintf(stderr, "+++ LOG: entry illegal hdr_size\n");
+  if (buf->hdr_size < sizeof(logger_entry)) {
+    fprintf(stderr, "+++ LOG: hdr_size must be at least as big as struct logger_entry\n");
     return -1;
   }
+  eventData = reinterpret_cast<unsigned char*>(buf) + buf->hdr_size;
   if (buf->lid == LOG_ID_SECURITY) {
     entry->priority = ANDROID_LOG_WARN;
   }
@@ -1048,7 +1048,7 @@
   }
   if ((result == 1) && fmtStr) {
     /* We overflowed :-(, let's repaint the line w/o format dressings */
-    eventData = (const unsigned char*)buf->msg;
+    eventData = reinterpret_cast<unsigned char*>(buf) + buf->hdr_size;
     eventData += 4;
     outBuf = messageBuf;
     outRemaining = messageBufLen - 1;
diff --git a/liblog/pmsg_reader.cpp b/liblog/pmsg_reader.cpp
index 64a92b7..0e39aab 100644
--- a/liblog/pmsg_reader.cpp
+++ b/liblog/pmsg_reader.cpp
@@ -96,7 +96,7 @@
           ((logger_list->start.tv_sec != buf.l.realtime.tv_sec) ||
            (logger_list->start.tv_nsec <= buf.l.realtime.tv_nsec)))) &&
         (!logger_list->pid || (logger_list->pid == buf.p.pid))) {
-      char* msg = log_msg->entry.msg;
+      char* msg = reinterpret_cast<char*>(&log_msg->entry) + log_msg->entry.hdr_size;
       *msg = buf.prio;
       fd = atomic_load(&logger_list->fd);
       if (fd <= 0) {
@@ -185,7 +185,7 @@
   /* Add just enough clues in logger_list and transp to make API function */
   memset(&logger_list, 0, sizeof(logger_list));
 
-  logger_list.mode = ANDROID_LOG_PSTORE | ANDROID_LOG_NONBLOCK | ANDROID_LOG_RDONLY;
+  logger_list.mode = ANDROID_LOG_PSTORE | ANDROID_LOG_NONBLOCK;
   logger_list.log_mask = (unsigned)-1;
   if (logId != LOG_ID_ANY) {
     logger_list.log_mask = (1 << logId);
diff --git a/liblog/tests/Android.bp b/liblog/tests/Android.bp
index b4bb77f..385b079 100644
--- a/liblog/tests/Android.bp
+++ b/liblog/tests/Android.bp
@@ -96,7 +96,7 @@
     cflags: ["-DNO_PSTORE"],
     test_suites: [
         "cts",
-        "vts",
+        "vts10",
     ],
 }
 
diff --git a/liblog/tests/libc_test.cpp b/liblog/tests/libc_test.cpp
index 3534eb8..1f26263 100644
--- a/liblog/tests/libc_test.cpp
+++ b/liblog/tests/libc_test.cpp
@@ -22,6 +22,10 @@
 TEST(libc, __pstore_append) {
 #ifdef __ANDROID__
 #ifndef NO_PSTORE
+  if (access("/dev/pmsg0", W_OK) != 0) {
+    GTEST_SKIP() << "pmsg0 not found, skipping test";
+  }
+
   FILE* fp;
   ASSERT_TRUE(NULL != (fp = fopen("/dev/pmsg0", "ae")));
   static const char message[] = "libc.__pstore_append\n";
diff --git a/liblog/tests/liblog_benchmark.cpp b/liblog/tests/liblog_benchmark.cpp
index 4366f3d..3a6ed90 100644
--- a/liblog/tests/liblog_benchmark.cpp
+++ b/liblog/tests/liblog_benchmark.cpp
@@ -648,8 +648,7 @@
 static void BM_log_latency(benchmark::State& state) {
   pid_t pid = getpid();
 
-  struct logger_list* logger_list =
-      android_logger_list_open(LOG_ID_EVENTS, ANDROID_LOG_RDONLY, 0, pid);
+  struct logger_list* logger_list = android_logger_list_open(LOG_ID_EVENTS, 0, 0, pid);
 
   if (!logger_list) {
     fprintf(stderr, "Unable to open events log: %s\n", strerror(errno));
@@ -723,8 +722,7 @@
 static void BM_log_delay(benchmark::State& state) {
   pid_t pid = getpid();
 
-  struct logger_list* logger_list =
-      android_logger_list_open(LOG_ID_EVENTS, ANDROID_LOG_RDONLY, 0, pid);
+  struct logger_list* logger_list = android_logger_list_open(LOG_ID_EVENTS, 0, 0, pid);
 
   if (!logger_list) {
     fprintf(stderr, "Unable to open events log: %s\n", strerror(errno));
diff --git a/liblog/tests/liblog_global_state.cpp b/liblog/tests/liblog_global_state.cpp
index 9a181ef..3508818 100644
--- a/liblog/tests/liblog_global_state.cpp
+++ b/liblog/tests/liblog_global_state.cpp
@@ -59,16 +59,15 @@
   static unsigned int expected_line;
   static std::string expected_message = "libbase test message";
 
-  auto liblog_logger_function = [](const struct __android_logger_data* logger_data,
-                                   const char* message) {
+  auto liblog_logger_function = [](const struct __android_log_message* log_message) {
     message_seen = true;
-    EXPECT_EQ(sizeof(__android_logger_data), logger_data->struct_size);
-    EXPECT_EQ(LOG_ID_DEFAULT, logger_data->buffer_id);
-    EXPECT_EQ(ANDROID_LOG_WARN, logger_data->priority);
-    EXPECT_STREQ(LOG_TAG, logger_data->tag);
-    EXPECT_EQ(expected_file, logger_data->file);
-    EXPECT_EQ(expected_line, logger_data->line);
-    EXPECT_EQ(expected_message, message);
+    EXPECT_EQ(sizeof(__android_log_message), log_message->struct_size);
+    EXPECT_EQ(LOG_ID_DEFAULT, log_message->buffer_id);
+    EXPECT_EQ(ANDROID_LOG_WARN, log_message->priority);
+    EXPECT_STREQ(LOG_TAG, log_message->tag);
+    EXPECT_EQ(expected_file, log_message->file);
+    EXPECT_EQ(expected_line, log_message->line);
+    EXPECT_EQ(expected_message, log_message->message);
   };
 
   __android_log_set_logger(liblog_logger_function);
@@ -111,16 +110,15 @@
   static int expected_priority = ANDROID_LOG_WARN;
   static std::string expected_message = "libbase test message";
 
-  auto liblog_logger_function = [](const struct __android_logger_data* logger_data,
-                                   const char* message) {
+  auto liblog_logger_function = [](const struct __android_log_message* log_message) {
     message_seen = true;
-    EXPECT_EQ(sizeof(__android_logger_data), logger_data->struct_size);
-    EXPECT_EQ(expected_buffer_id, logger_data->buffer_id);
-    EXPECT_EQ(expected_priority, logger_data->priority);
-    EXPECT_STREQ(LOG_TAG, logger_data->tag);
-    EXPECT_STREQ(nullptr, logger_data->file);
-    EXPECT_EQ(0U, logger_data->line);
-    EXPECT_EQ(expected_message, message);
+    EXPECT_EQ(sizeof(__android_log_message), log_message->struct_size);
+    EXPECT_EQ(expected_buffer_id, log_message->buffer_id);
+    EXPECT_EQ(expected_priority, log_message->priority);
+    EXPECT_STREQ(LOG_TAG, log_message->tag);
+    EXPECT_STREQ(nullptr, log_message->file);
+    EXPECT_EQ(0U, log_message->line);
+    EXPECT_EQ(expected_message, log_message->message);
   };
 
   __android_log_set_logger(liblog_logger_function);
diff --git a/liblog/tests/liblog_test.cpp b/liblog/tests/liblog_test.cpp
index a60d2df..048bf61 100644
--- a/liblog/tests/liblog_test.cpp
+++ b/liblog/tests/liblog_test.cpp
@@ -82,7 +82,7 @@
   pid_t pid = getpid();
 
   auto logger_list = std::unique_ptr<struct logger_list, ListCloser>{
-      android_logger_list_open(log_buffer, ANDROID_LOG_RDONLY, 1000, pid)};
+      android_logger_list_open(log_buffer, 0, 1000, pid)};
   ASSERT_TRUE(logger_list);
 
   write_messages();
@@ -97,16 +97,13 @@
     ASSERT_EQ(log_buffer, log_msg.id());
     ASSERT_EQ(pid, log_msg.entry.pid);
 
-    // TODO: Should this be an assert?
-    if (log_msg.msg() == nullptr) {
-      continue;
-    }
+    ASSERT_NE(nullptr, log_msg.msg());
 
     check_message(log_msg, &found);
   }
 
   auto logger_list_non_block = std::unique_ptr<struct logger_list, ListCloser>{
-      android_logger_list_open(log_buffer, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 1000, pid)};
+      android_logger_list_open(log_buffer, ANDROID_LOG_NONBLOCK, 1000, pid)};
   ASSERT_TRUE(logger_list_non_block);
 
   size_t count = 0;
@@ -121,10 +118,7 @@
     ASSERT_EQ(log_buffer, log_msg.id());
     ASSERT_EQ(pid, log_msg.entry.pid);
 
-    // TODO: Should this be an assert?
-    if (log_msg.msg() == nullptr) {
-      continue;
-    }
+    ASSERT_NE(nullptr, log_msg.msg());
 
     found = false;
     check_message(log_msg, &found);
@@ -160,7 +154,6 @@
   return ret;
 }
 
-#ifndef NO_PSTORE
 static bool isPmsgActive() {
   pid_t pid = getpid();
 
@@ -170,7 +163,6 @@
 
   return std::string::npos != myPidFds.find(" -> /dev/pmsg0");
 }
-#endif /* NO_PSTORE */
 
 static bool isLogdwActive() {
   std::string logdwSignature =
@@ -222,16 +214,18 @@
   log_time ts(CLOCK_MONOTONIC);
   log_time ts1(ts);
 
+  bool has_pstore = access("/dev/pmsg0", W_OK) == 0;
+
   auto write_function = [&] {
     EXPECT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts, sizeof(ts)));
     // Check that we can close and reopen the logger
     bool logdwActiveAfter__android_log_btwrite;
     if (getuid() == AID_ROOT) {
       tested__android_log_close = true;
-#ifndef NO_PSTORE
-      bool pmsgActiveAfter__android_log_btwrite = isPmsgActive();
-      EXPECT_TRUE(pmsgActiveAfter__android_log_btwrite);
-#endif /* NO_PSTORE */
+      if (has_pstore) {
+        bool pmsgActiveAfter__android_log_btwrite = isPmsgActive();
+        EXPECT_TRUE(pmsgActiveAfter__android_log_btwrite);
+      }
       logdwActiveAfter__android_log_btwrite = isLogdwActive();
       EXPECT_TRUE(logdwActiveAfter__android_log_btwrite);
     } else if (!tested__android_log_close) {
@@ -239,10 +233,10 @@
     }
     __android_log_close();
     if (getuid() == AID_ROOT) {
-#ifndef NO_PSTORE
-      bool pmsgActiveAfter__android_log_close = isPmsgActive();
-      EXPECT_FALSE(pmsgActiveAfter__android_log_close);
-#endif /* NO_PSTORE */
+      if (has_pstore) {
+        bool pmsgActiveAfter__android_log_close = isPmsgActive();
+        EXPECT_FALSE(pmsgActiveAfter__android_log_close);
+      }
       bool logdwActiveAfter__android_log_close = isLogdwActive();
       EXPECT_FALSE(logdwActiveAfter__android_log_close);
     }
@@ -250,10 +244,10 @@
     ts1 = log_time(CLOCK_MONOTONIC);
     EXPECT_LT(0, __android_log_btwrite(0, EVENT_TYPE_LONG, &ts1, sizeof(ts1)));
     if (getuid() == AID_ROOT) {
-#ifndef NO_PSTORE
-      bool pmsgActiveAfter__android_log_btwrite = isPmsgActive();
-      EXPECT_TRUE(pmsgActiveAfter__android_log_btwrite);
-#endif /* NO_PSTORE */
+      if (has_pstore) {
+        bool pmsgActiveAfter__android_log_btwrite = isPmsgActive();
+        EXPECT_TRUE(pmsgActiveAfter__android_log_btwrite);
+      }
       logdwActiveAfter__android_log_btwrite = isLogdwActive();
       EXPECT_TRUE(logdwActiveAfter__android_log_btwrite);
     }
@@ -572,8 +566,7 @@
 
   v += pid & 0xFFFF;
 
-  ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(
-                           LOG_ID_EVENTS, ANDROID_LOG_RDONLY, 1000, pid)));
+  ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(LOG_ID_EVENTS, 0, 1000, pid)));
 
   int count = 0;
 
@@ -728,8 +721,7 @@
 
   v += pid & 0xFFFF;
 
-  ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(
-                           LOG_ID_EVENTS, ANDROID_LOG_RDONLY, 1000, pid)));
+  ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(LOG_ID_EVENTS, 0, 1000, pid)));
 
   int count = 0;
 
@@ -1033,7 +1025,7 @@
 #endif
 }
 
-// TODO: This test is tautological. android_logger_list_read() calls recv() with
+// Note: This test is tautological. android_logger_list_read() calls recv() with
 // LOGGER_ENTRY_MAX_PAYLOAD as its size argument, so it's not possible for this test to read a
 // payload larger than that size.
 TEST(liblog, too_big_payload) {
@@ -1093,11 +1085,11 @@
   pid_t pid = getpid();
 
   auto logger_list1 = std::unique_ptr<struct logger_list, ListCloser>{
-      android_logger_list_open(LOG_ID_MAIN, ANDROID_LOG_RDONLY, expected_count1, pid)};
+      android_logger_list_open(LOG_ID_MAIN, 0, expected_count1, pid)};
   ASSERT_TRUE(logger_list1);
 
   auto logger_list2 = std::unique_ptr<struct logger_list, ListCloser>{
-      android_logger_list_open(LOG_ID_MAIN, ANDROID_LOG_RDONLY, expected_count2, pid)};
+      android_logger_list_open(LOG_ID_MAIN, 0, expected_count2, pid)};
   ASSERT_TRUE(logger_list2);
 
   for (int i = 25; i > 0; --i) {
@@ -1128,14 +1120,12 @@
   }
 
   // Test again with the nonblocking reader.
-  auto logger_list_non_block1 =
-      std::unique_ptr<struct logger_list, ListCloser>{android_logger_list_open(
-          LOG_ID_MAIN, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, expected_count1, pid)};
+  auto logger_list_non_block1 = std::unique_ptr<struct logger_list, ListCloser>{
+      android_logger_list_open(LOG_ID_MAIN, ANDROID_LOG_NONBLOCK, expected_count1, pid)};
   ASSERT_TRUE(logger_list_non_block1);
 
-  auto logger_list_non_block2 =
-      std::unique_ptr<struct logger_list, ListCloser>{android_logger_list_open(
-          LOG_ID_MAIN, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, expected_count2, pid)};
+  auto logger_list_non_block2 = std::unique_ptr<struct logger_list, ListCloser>{
+      android_logger_list_open(LOG_ID_MAIN, ANDROID_LOG_NONBLOCK, expected_count2, pid)};
   ASSERT_TRUE(logger_list_non_block2);
   count1 = 0;
   count2 = 0;
@@ -1542,8 +1532,8 @@
 
   pid_t pid = getpid();
 
-  struct logger_list* logger_list = android_logger_list_open(
-      LOG_ID_EVENTS, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 1000, pid);
+  struct logger_list* logger_list =
+      android_logger_list_open(LOG_ID_EVENTS, ANDROID_LOG_NONBLOCK, 1000, pid);
 
   int count = 0;
   if (logger_list == NULL) return count;
@@ -1832,10 +1822,8 @@
   gid = getgid();
   pid_t pid = getpid();
 
-  ASSERT_TRUE(NULL !=
-              (logger_list = android_logger_list_open(
-                   LOG_ID_SECURITY, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK,
-                   1000, pid)));
+  ASSERT_TRUE(NULL != (logger_list = android_logger_list_open(LOG_ID_SECURITY, ANDROID_LOG_NONBLOCK,
+                                                              1000, pid)));
 
   log_time ts(CLOCK_MONOTONIC);
 
@@ -1988,7 +1976,6 @@
 #endif
 }
 
-// TODO: Do we need to check that we didn't actually write anything if we return a failure here?
 TEST(liblog,
      android_errorWriteWithInfoLog__android_logger_list_read__null_data) {
 #ifdef __ANDROID__
diff --git a/liblog/tests/log_read_test.cpp b/liblog/tests/log_read_test.cpp
index 1be99aa..3e09617 100644
--- a/liblog/tests/log_read_test.cpp
+++ b/liblog/tests/log_read_test.cpp
@@ -34,8 +34,7 @@
   // This test assumes the log buffers are filled with noise from
   // normal operations. It will fail if done immediately after a
   // logcat -c.
-  struct logger_list* logger_list =
-      android_logger_list_alloc(ANDROID_LOG_WRONLY, 0, 0);
+  struct logger_list* logger_list = android_logger_list_alloc(0, 0, 0);
 
   for (int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
     log_id_t id = static_cast<log_id_t>(i);
diff --git a/liblog/tests/log_wrap_test.cpp b/liblog/tests/log_wrap_test.cpp
index e06964f..755898a 100644
--- a/liblog/tests/log_wrap_test.cpp
+++ b/liblog/tests/log_wrap_test.cpp
@@ -32,7 +32,7 @@
 static void read_with_wrap() {
   // Read the last line in the log to get a starting timestamp. We're assuming
   // the log is not empty.
-  const int mode = ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK;
+  const int mode = ANDROID_LOG_NONBLOCK;
   struct logger_list* logger_list =
       android_logger_list_open(LOG_ID_MAIN, mode, 1000, 0);
 
diff --git a/liblog/tests/logprint_test.cpp b/liblog/tests/logprint_test.cpp
index 7ca02ac..72e53f9 100644
--- a/liblog/tests/logprint_test.cpp
+++ b/liblog/tests/logprint_test.cpp
@@ -14,8 +14,14 @@
  * limitations under the License.
  */
 
+#include <log/logprint.h>
+
+#include <string>
+
 #include <gtest/gtest.h>
 
+#include <log/log_read.h>
+
 size_t convertPrintable(char* p, const char* message, size_t messageLen);
 
 TEST(liblog, convertPrintable_ascii) {
@@ -85,3 +91,63 @@
   EXPECT_EQ(output_size, strlen(expected_output));
   EXPECT_STREQ(expected_output, output);
 }
+
+TEST(liblog, log_print_different_header_size) {
+  constexpr int32_t kPid = 123;
+  constexpr uint32_t kTid = 456;
+  constexpr uint32_t kSec = 1000;
+  constexpr uint32_t kNsec = 999;
+  constexpr uint32_t kLid = LOG_ID_MAIN;
+  constexpr uint32_t kUid = 987;
+  constexpr char kPriority = ANDROID_LOG_ERROR;
+
+  auto create_buf = [](char* buf, size_t len, uint16_t hdr_size) {
+    memset(buf, 0, len);
+    logger_entry* header = reinterpret_cast<logger_entry*>(buf);
+    header->hdr_size = hdr_size;
+    header->pid = kPid;
+    header->tid = kTid;
+    header->sec = kSec;
+    header->nsec = kNsec;
+    header->lid = kLid;
+    header->uid = kUid;
+    char* message = buf + header->hdr_size;
+    uint16_t message_len = 0;
+    message[message_len++] = kPriority;
+    message[message_len++] = 'T';
+    message[message_len++] = 'a';
+    message[message_len++] = 'g';
+    message[message_len++] = '\0';
+    message[message_len++] = 'm';
+    message[message_len++] = 's';
+    message[message_len++] = 'g';
+    message[message_len++] = '!';
+    message[message_len++] = '\0';
+    header->len = message_len;
+  };
+
+  auto check_entry = [&](const AndroidLogEntry& entry) {
+    EXPECT_EQ(kSec, static_cast<uint32_t>(entry.tv_sec));
+    EXPECT_EQ(kNsec, static_cast<uint32_t>(entry.tv_nsec));
+    EXPECT_EQ(kPriority, entry.priority);
+    EXPECT_EQ(kUid, static_cast<uint32_t>(entry.uid));
+    EXPECT_EQ(kPid, entry.pid);
+    EXPECT_EQ(kTid, static_cast<uint32_t>(entry.tid));
+    EXPECT_STREQ("Tag", entry.tag);
+    EXPECT_EQ(4U, entry.tagLen);  // Apparently taglen includes the nullptr?
+    EXPECT_EQ(4U, entry.messageLen);
+    EXPECT_STREQ("msg!", entry.message);
+  };
+  alignas(logger_entry) char buf[LOGGER_ENTRY_MAX_LEN];
+  create_buf(buf, sizeof(buf), sizeof(logger_entry));
+
+  AndroidLogEntry entry_normal_size;
+  ASSERT_EQ(0,
+            android_log_processLogBuffer(reinterpret_cast<logger_entry*>(buf), &entry_normal_size));
+  check_entry(entry_normal_size);
+
+  create_buf(buf, sizeof(buf), sizeof(logger_entry) + 3);
+  AndroidLogEntry entry_odd_size;
+  ASSERT_EQ(0, android_log_processLogBuffer(reinterpret_cast<logger_entry*>(buf), &entry_odd_size));
+  check_entry(entry_odd_size);
+}
\ No newline at end of file
diff --git a/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index ee6ae7a..297036e 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -24,7 +24,7 @@
 
 class Modprobe {
   public:
-    Modprobe(const std::vector<std::string>&);
+    Modprobe(const std::vector<std::string>&, const std::string load_file = "modules.load");
 
     bool LoadListedModules(bool strict = true);
     bool LoadWithAliases(const std::string& module_name, bool strict,
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index f22bbf1..d193796 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -312,7 +312,7 @@
     }
 }
 
-Modprobe::Modprobe(const std::vector<std::string>& base_paths) {
+Modprobe::Modprobe(const std::vector<std::string>& base_paths, const std::string load_file) {
     using namespace std::placeholders;
 
     for (const auto& base_path : base_paths) {
@@ -326,7 +326,7 @@
         ParseCfg(base_path + "/modules.softdep", softdep_callback);
 
         auto load_callback = std::bind(&Modprobe::ParseLoadCallback, this, _1);
-        ParseCfg(base_path + "/modules.load", load_callback);
+        ParseCfg(base_path + "/" + load_file, load_callback);
 
         auto options_callback = std::bind(&Modprobe::ParseOptionsCallback, this, _1);
         ParseCfg(base_path + "/modules.options", options_callback);
diff --git a/libprocessgroup/Android.bp b/libprocessgroup/Android.bp
index 618a5c5..2c1b255 100644
--- a/libprocessgroup/Android.bp
+++ b/libprocessgroup/Android.bp
@@ -13,6 +13,10 @@
             enabled: true,
         },
     },
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
 }
 
 cc_library {
@@ -52,4 +56,8 @@
         "-Werror",
         "-Wexit-time-destructors",
     ],
+    apex_available: [
+        "//apex_available:platform",
+        "//apex_available:anyapex",
+    ],
 }
diff --git a/libstats/push_compat/Android.bp b/libstats/push_compat/Android.bp
index fcd8c83..caea048 100644
--- a/libstats/push_compat/Android.bp
+++ b/libstats/push_compat/Android.bp
@@ -47,6 +47,7 @@
     defaults: ["libstatspush_compat_defaults"],
     export_include_dirs: ["include"],
     static_libs: ["libgtest_prod"],
+    apex_available: ["com.android.resolv"],
 }
 
 cc_test {
diff --git a/libstats/socket/Android.bp b/libstats/socket/Android.bp
index 9fd9fbc..690dc94 100644
--- a/libstats/socket/Android.bp
+++ b/libstats/socket/Android.bp
@@ -54,6 +54,7 @@
     name: "libstatssocket_headers",
     export_include_dirs: ["include"],
     host_supported: true,
+    apex_available: ["com.android.resolv"],
 }
 
 cc_benchmark {
diff --git a/libsysutils/src/NetlinkEvent.cpp b/libsysutils/src/NetlinkEvent.cpp
index 2351afa..5efe03f 100644
--- a/libsysutils/src/NetlinkEvent.cpp
+++ b/libsysutils/src/NetlinkEvent.cpp
@@ -529,6 +529,10 @@
         free(buf);
     } else if (opthdr->nd_opt_type == ND_OPT_DNSSL) {
         // TODO: support DNSSL.
+    } else if (opthdr->nd_opt_type == ND_OPT_CAPTIVE_PORTAL) {
+        // TODO: support CAPTIVE PORTAL.
+    } else if (opthdr->nd_opt_type == ND_OPT_PREF64) {
+        // TODO: support PREF64.
     } else {
         SLOGD("Unknown ND option type %d\n", opthdr->nd_opt_type);
         return false;
diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp
index 36449eb..bf7d69e 100644
--- a/libunwindstack/Android.bp
+++ b/libunwindstack/Android.bp
@@ -35,20 +35,13 @@
     },
 }
 
-cc_library {
-    name: "libunwindstack",
-    vendor_available: true,
-    recovery_available: true,
-    vndk: {
-        enabled: true,
-        support_system_process: true,
-    },
+cc_defaults {
+    name: "libunwindstack_defaults",
     defaults: ["libunwindstack_flags"],
     export_include_dirs: ["include"],
 
     srcs: [
         "ArmExidx.cpp",
-        "DexFile.cpp",
         "DexFiles.cpp",
         "DwarfCfa.cpp",
         "DwarfEhFrameWithHdr.cpp",
@@ -64,6 +57,7 @@
         "MapInfo.cpp",
         "Maps.cpp",
         "Memory.cpp",
+        "MemoryMte.cpp",
         "LocalUnwinder.cpp",
         "Regs.cpp",
         "RegsArm.cpp",
@@ -77,7 +71,6 @@
     ],
 
     cflags: [
-        "-DDEXFILE_SUPPORT",
         "-Wexit-time-destructors",
     ],
 
@@ -89,24 +82,6 @@
                 "-g",
             ],
         },
-        vendor: {
-            cflags: ["-UDEXFILE_SUPPORT"],
-            exclude_srcs: [
-                "DexFile.cpp",
-            ],
-            exclude_shared_libs: [
-                "libdexfile_support",
-            ],
-        },
-        recovery: {
-            cflags: ["-UDEXFILE_SUPPORT"],
-            exclude_srcs: [
-                "DexFile.cpp",
-            ],
-            exclude_shared_libs: [
-                "libdexfile_support",
-            ],
-        },
     },
 
     arch: {
@@ -124,10 +99,64 @@
 
     shared_libs: [
         "libbase",
-        "libdexfile_support",
         "liblog",
         "liblzma",
     ],
+
+    header_libs: [
+        "bionic_libc_platform_headers",
+    ],
+
+    product_variables: {
+        experimental_mte: {
+            cflags: ["-DANDROID_EXPERIMENTAL_MTE"],
+        },
+    },
+}
+
+cc_library {
+    name: "libunwindstack",
+    vendor_available: true,
+    recovery_available: true,
+    vndk: {
+        enabled: true,
+        support_system_process: true,
+    },
+    defaults: ["libunwindstack_defaults"],
+
+    srcs: ["DexFile.cpp"],
+    cflags: ["-DDEXFILE_SUPPORT"],
+    shared_libs: ["libdexfile_support"],
+
+    target: {
+        vendor: {
+            cflags: ["-UDEXFILE_SUPPORT"],
+            exclude_srcs: ["DexFile.cpp"],
+            exclude_shared_libs: ["libdexfile_support"],
+        },
+        recovery: {
+            cflags: ["-UDEXFILE_SUPPORT"],
+            exclude_srcs: ["DexFile.cpp"],
+            exclude_shared_libs: ["libdexfile_support"],
+        },
+    },
+}
+
+// Static library without DEX support to avoid dependencies on the ART APEX.
+cc_library_static {
+    name: "libunwindstack_no_dex",
+    recovery_available: true,
+    defaults: ["libunwindstack_defaults"],
+
+    visibility: [
+        "//system/core/debuggerd",
+        "//system/core/init",
+        "//system/core/libbacktrace",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.runtime",
+    ],
 }
 
 //-------------------------------------------------------------------------
@@ -195,6 +224,7 @@
         "tests/MemoryRangesTest.cpp",
         "tests/MemoryRemoteTest.cpp",
         "tests/MemoryTest.cpp",
+        "tests/MemoryMteTest.cpp",
         "tests/RegsInfoTest.cpp",
         "tests/RegsIterateTest.cpp",
         "tests/RegsStepIfSignalHandlerTest.cpp",
@@ -250,6 +280,16 @@
         "tests/files/offline/straddle_arm/*",
         "tests/files/offline/straddle_arm64/*",
     ],
+
+    header_libs: [
+        "bionic_libc_platform_headers",
+    ],
+
+    product_variables: {
+        experimental_mte: {
+            cflags: ["-DANDROID_EXPERIMENTAL_MTE"],
+        },
+    },
 }
 
 cc_test {
@@ -355,12 +395,28 @@
 
     srcs: [
         "benchmarks/unwind_benchmarks.cpp",
+        "benchmarks/ElfBenchmark.cpp",
+        "benchmarks/SymbolBenchmark.cpp",
+        "benchmarks/Utils.cpp",
+    ],
+
+    data: [
+        "benchmarks/files/*",
     ],
 
     shared_libs: [
         "libbase",
         "libunwindstack",
     ],
+
+    target: {
+        android: {
+            static_libs: [
+                "libmeminfo",
+                "libprocinfo",
+            ],
+        },
+    },
 }
 
 // Generates the elf data for use in the tests for .gnu_debugdata frames.
diff --git a/libunwindstack/DexFile.cpp b/libunwindstack/DexFile.cpp
index dff7a8b..8fc3d23 100644
--- a/libunwindstack/DexFile.cpp
+++ b/libunwindstack/DexFile.cpp
@@ -50,6 +50,22 @@
 
 std::unique_ptr<DexFile> DexFile::Create(uint64_t dex_file_offset_in_memory, Memory* memory,
                                          MapInfo* info) {
+  if (UNLIKELY(!HasDexSupport())) {
+    return nullptr;
+  }
+
+  size_t max_size = info->end - dex_file_offset_in_memory;
+  if (memory->IsLocal()) {
+    size_t size = max_size;
+
+    std::string err_msg;
+    std::unique_ptr<art_api::dex::DexFile> art_dex_file = DexFile::OpenFromMemory(
+        reinterpret_cast<void const*>(dex_file_offset_in_memory), &size, info->name, &err_msg);
+    if (art_dex_file != nullptr && size <= max_size) {
+      return std::unique_ptr<DexFile>(new DexFile(art_dex_file));
+    }
+  }
+
   if (!info->name.empty()) {
     std::unique_ptr<DexFile> dex_file =
         DexFileFromFile::Create(dex_file_offset_in_memory - info->start + info->offset, info->name);
@@ -57,7 +73,7 @@
       return dex_file;
     }
   }
-  return DexFileFromMemory::Create(dex_file_offset_in_memory, memory, info->name);
+  return DexFileFromMemory::Create(dex_file_offset_in_memory, memory, info->name, max_size);
 }
 
 bool DexFile::GetMethodInformation(uint64_t dex_offset, std::string* method_name,
@@ -89,12 +105,13 @@
     return nullptr;
   }
 
-  return std::unique_ptr<DexFileFromFile>(new DexFileFromFile(std::move(*art_dex_file.release())));
+  return std::unique_ptr<DexFileFromFile>(new DexFileFromFile(art_dex_file));
 }
 
 std::unique_ptr<DexFileFromMemory> DexFileFromMemory::Create(uint64_t dex_file_offset_in_memory,
                                                              Memory* memory,
-                                                             const std::string& name) {
+                                                             const std::string& name,
+                                                             size_t max_size) {
   if (UNLIKELY(!HasDexSupport())) {
     return nullptr;
   }
@@ -105,10 +122,13 @@
     std::string error_msg;
     std::unique_ptr<art_api::dex::DexFile> art_dex_file =
         OpenFromMemory(backing_memory.data(), &size, name, &error_msg);
+    if (size > max_size) {
+      return nullptr;
+    }
 
     if (art_dex_file != nullptr) {
       return std::unique_ptr<DexFileFromMemory>(
-          new DexFileFromMemory(std::move(*art_dex_file.release()), std::move(backing_memory)));
+          new DexFileFromMemory(art_dex_file, std::move(backing_memory)));
     }
 
     if (!error_msg.empty()) {
diff --git a/libunwindstack/DexFile.h b/libunwindstack/DexFile.h
index ca658e6..fe185da 100644
--- a/libunwindstack/DexFile.h
+++ b/libunwindstack/DexFile.h
@@ -39,7 +39,8 @@
                                          MapInfo* info);
 
  protected:
-  DexFile(art_api::dex::DexFile&& art_dex_file) : art_api::dex::DexFile(std::move(art_dex_file)) {}
+  DexFile(std::unique_ptr<art_api::dex::DexFile>& art_dex_file)
+      : art_api::dex::DexFile(art_dex_file) {}
 };
 
 class DexFileFromFile : public DexFile {
@@ -48,17 +49,19 @@
                                                  const std::string& file);
 
  private:
-  DexFileFromFile(art_api::dex::DexFile&& art_dex_file) : DexFile(std::move(art_dex_file)) {}
+  DexFileFromFile(std::unique_ptr<art_api::dex::DexFile>& art_dex_file) : DexFile(art_dex_file) {}
 };
 
 class DexFileFromMemory : public DexFile {
  public:
   static std::unique_ptr<DexFileFromMemory> Create(uint64_t dex_file_offset_in_memory,
-                                                   Memory* memory, const std::string& name);
+                                                   Memory* memory, const std::string& name,
+                                                   size_t max_size);
 
  private:
-  DexFileFromMemory(art_api::dex::DexFile&& art_dex_file, std::vector<uint8_t>&& memory)
-      : DexFile(std::move(art_dex_file)), memory_(std::move(memory)) {}
+  DexFileFromMemory(std::unique_ptr<art_api::dex::DexFile>& art_dex_file,
+                    std::vector<uint8_t>&& memory)
+      : DexFile(art_dex_file), memory_(std::move(memory)) {}
 
   std::vector<uint8_t> memory_;
 };
diff --git a/libunwindstack/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp
index 341275d..17470fd 100644
--- a/libunwindstack/ElfInterface.cpp
+++ b/libunwindstack/ElfInterface.cpp
@@ -371,7 +371,7 @@
       // Look for the .debug_frame and .gnu_debugdata.
       if (shdr.sh_name < sec_size) {
         std::string name;
-        if (memory_->ReadString(sec_offset + shdr.sh_name, &name)) {
+        if (memory_->ReadString(sec_offset + shdr.sh_name, &name, sec_size - shdr.sh_name)) {
           if (name == ".debug_frame") {
             debug_frame_offset_ = shdr.sh_offset;
             debug_frame_size_ = shdr.sh_size;
@@ -405,7 +405,7 @@
     } else if (shdr.sh_type == SHT_NOTE) {
       if (shdr.sh_name < sec_size) {
         std::string name;
-        if (memory_->ReadString(sec_offset + shdr.sh_name, &name) &&
+        if (memory_->ReadString(sec_offset + shdr.sh_name, &name, sec_size - shdr.sh_name) &&
             name == ".note.gnu.build-id") {
           gnu_build_id_offset_ = shdr.sh_offset;
           gnu_build_id_size_ = shdr.sh_size;
@@ -456,10 +456,11 @@
   for (const auto& entry : strtabs_) {
     if (entry.first == strtab_addr) {
       soname_offset = entry.second + soname_offset;
-      if (soname_offset >= entry.second + strtab_size) {
+      uint64_t soname_max = entry.second + strtab_size;
+      if (soname_offset >= soname_max) {
         return "";
       }
-      if (!memory_->ReadString(soname_offset, &soname_)) {
+      if (!memory_->ReadString(soname_offset, &soname_, soname_max - soname_offset)) {
         return "";
       }
       soname_type_ = SONAME_VALID;
@@ -608,7 +609,8 @@
     }
     std::string name;
     if (shdr.sh_type == SHT_NOTE && shdr.sh_name < sec_size &&
-        memory->ReadString(sec_offset + shdr.sh_name, &name) && name == ".note.gnu.build-id") {
+        memory->ReadString(sec_offset + shdr.sh_name, &name, sec_size - shdr.sh_name) &&
+        name == ".note.gnu.build-id") {
       *build_id_offset = shdr.sh_offset;
       *build_id_size = shdr.sh_size;
       return true;
@@ -662,7 +664,7 @@
         if (note_size - offset < hdr.n_descsz || hdr.n_descsz == 0) {
           return "";
         }
-        std::string build_id(hdr.n_descsz - 1, '\0');
+        std::string build_id(hdr.n_descsz, '\0');
         if (memory->ReadFully(note_offset + offset, &build_id[0], hdr.n_descsz)) {
           return build_id;
         }
diff --git a/libunwindstack/LocalUnwinder.cpp b/libunwindstack/LocalUnwinder.cpp
index 5d81200..05650fb 100644
--- a/libunwindstack/LocalUnwinder.cpp
+++ b/libunwindstack/LocalUnwinder.cpp
@@ -106,7 +106,7 @@
     uint64_t step_pc = rel_pc;
     uint64_t pc_adjustment;
     if (adjust_pc) {
-      pc_adjustment = regs->GetPcAdjustment(rel_pc, elf);
+      pc_adjustment = GetPcAdjustment(rel_pc, elf, arch);
     } else {
       pc_adjustment = 0;
     }
diff --git a/libunwindstack/Memory.cpp b/libunwindstack/Memory.cpp
index 8de3d98..b4623fa 100644
--- a/libunwindstack/Memory.cpp
+++ b/libunwindstack/Memory.cpp
@@ -158,20 +158,30 @@
   return rc == size;
 }
 
-bool Memory::ReadString(uint64_t addr, std::string* string, uint64_t max_read) {
-  string->clear();
-  uint64_t bytes_read = 0;
-  while (bytes_read < max_read) {
-    uint8_t value;
-    if (!ReadFully(addr, &value, sizeof(value))) {
-      return false;
+bool Memory::ReadString(uint64_t addr, std::string* dst, size_t max_read) {
+  char buffer[256];  // Large enough for 99% of symbol names.
+  size_t size = 0;   // Number of bytes which were read into the buffer.
+  for (size_t offset = 0; offset < max_read; offset += size) {
+    // Look for null-terminator first, so we can allocate string of exact size.
+    // If we know the end of valid memory range, do the reads in larger blocks.
+    size_t read = std::min(sizeof(buffer), max_read - offset);
+    size = Read(addr + offset, buffer, read);
+    if (size == 0) {
+      return false;  // We have not found end of string yet and we can not read more data.
     }
-    if (value == '\0') {
-      return true;
+    size_t length = strnlen(buffer, size);  // Index of the null-terminator.
+    if (length < size) {
+      // We found the null-terminator. Allocate the string and set its content.
+      if (offset == 0) {
+        // We did just single read, so the buffer already contains the whole string.
+        dst->assign(buffer, length);
+        return true;
+      } else {
+        // The buffer contains only the last block. Read the whole string again.
+        dst->assign(offset + length, '\0');
+        return ReadFully(addr, dst->data(), dst->size());
+      }
     }
-    string->push_back(value);
-    addr++;
-    bytes_read++;
   }
   return false;
 }
@@ -324,6 +334,16 @@
   return ProcessVmRead(getpid(), addr, dst, size);
 }
 
+#if !defined(ANDROID_EXPERIMENTAL_MTE)
+long MemoryRemote::ReadTag(uint64_t) {
+  return -1;
+}
+
+long MemoryLocal::ReadTag(uint64_t) {
+  return -1;
+}
+#endif
+
 MemoryRange::MemoryRange(const std::shared_ptr<Memory>& memory, uint64_t begin, uint64_t length,
                          uint64_t offset)
     : memory_(memory), begin_(begin), length_(length), offset_(offset) {}
diff --git a/libunwindstack/MemoryCache.h b/libunwindstack/MemoryCache.h
index 769d907..d97640d 100644
--- a/libunwindstack/MemoryCache.h
+++ b/libunwindstack/MemoryCache.h
@@ -33,6 +33,7 @@
   virtual ~MemoryCache() = default;
 
   size_t Read(uint64_t addr, void* dst, size_t size) override;
+  long ReadTag(uint64_t addr) override { return impl_->ReadTag(addr); }
 
   void Clear() override { cache_.clear(); }
 
diff --git a/libunwindstack/MemoryLocal.h b/libunwindstack/MemoryLocal.h
index 29aaf12..741f107 100644
--- a/libunwindstack/MemoryLocal.h
+++ b/libunwindstack/MemoryLocal.h
@@ -28,7 +28,10 @@
   MemoryLocal() = default;
   virtual ~MemoryLocal() = default;
 
+  bool IsLocal() const override { return true; }
+
   size_t Read(uint64_t addr, void* dst, size_t size) override;
+  long ReadTag(uint64_t addr) override;
 };
 
 }  // namespace unwindstack
diff --git a/libunwindstack/MemoryMte.cpp b/libunwindstack/MemoryMte.cpp
new file mode 100644
index 0000000..d1d0ebc
--- /dev/null
+++ b/libunwindstack/MemoryMte.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#if defined(ANDROID_EXPERIMENTAL_MTE)
+
+#include <sys/ptrace.h>
+
+#include <bionic/mte.h>
+#include <bionic/mte_kernel.h>
+
+#include "MemoryLocal.h"
+#include "MemoryRemote.h"
+
+namespace unwindstack {
+
+long MemoryRemote::ReadTag(uint64_t addr) {
+#if defined(__aarch64__)
+  return ptrace(PTRACE_PEEKTAG, pid_, (void*)addr, nullptr);
+#else
+  (void)addr;
+  return -1;
+#endif
+}
+
+long MemoryLocal::ReadTag(uint64_t addr) {
+#if defined(__aarch64__)
+  // Check that the memory is readable first. This is racy with the ldg but there's not much
+  // we can do about it.
+  char data;
+  if (!mte_supported() || !Read(addr, &data, 1)) {
+    return -1;
+  }
+
+  __asm__ __volatile__(".arch_extension mte; ldg %0, [%0]" : "+r"(addr) : : "memory");
+  return (addr >> 56) & 0xf;
+#else
+  (void)addr;
+  return -1;
+#endif
+}
+
+}  // namespace unwindstack
+
+#endif
diff --git a/libunwindstack/MemoryRemote.h b/libunwindstack/MemoryRemote.h
index db367d6..dd09c88 100644
--- a/libunwindstack/MemoryRemote.h
+++ b/libunwindstack/MemoryRemote.h
@@ -32,6 +32,7 @@
   virtual ~MemoryRemote() = default;
 
   size_t Read(uint64_t addr, void* dst, size_t size) override;
+  long ReadTag(uint64_t addr) override;
 
   pid_t pid() { return pid_; }
 
diff --git a/libunwindstack/Regs.cpp b/libunwindstack/Regs.cpp
index e0a785b..03aa6c2 100644
--- a/libunwindstack/Regs.cpp
+++ b/libunwindstack/Regs.cpp
@@ -121,4 +121,62 @@
   return regs;
 }
 
+uint64_t GetPcAdjustment(uint64_t rel_pc, Elf* elf, ArchEnum arch) {
+  switch (arch) {
+    case ARCH_ARM: {
+      if (!elf->valid()) {
+        return 2;
+      }
+
+      uint64_t load_bias = elf->GetLoadBias();
+      if (rel_pc < load_bias) {
+        if (rel_pc < 2) {
+          return 0;
+        }
+        return 2;
+      }
+      uint64_t adjusted_rel_pc = rel_pc - load_bias;
+      if (adjusted_rel_pc < 5) {
+        if (adjusted_rel_pc < 2) {
+          return 0;
+        }
+        return 2;
+      }
+
+      if (adjusted_rel_pc & 1) {
+        // This is a thumb instruction, it could be 2 or 4 bytes.
+        uint32_t value;
+        if (!elf->memory()->ReadFully(adjusted_rel_pc - 5, &value, sizeof(value)) ||
+            (value & 0xe000f000) != 0xe000f000) {
+          return 2;
+        }
+      }
+      return 4;
+    }
+  case ARCH_ARM64: {
+    if (rel_pc < 4) {
+      return 0;
+    }
+    return 4;
+  }
+  case ARCH_MIPS:
+  case ARCH_MIPS64: {
+    if (rel_pc < 8) {
+      return 0;
+    }
+    // For now, just assume no compact branches
+    return 8;
+  }
+  case ARCH_X86:
+  case ARCH_X86_64: {
+    if (rel_pc == 0) {
+      return 0;
+    }
+    return 1;
+  }
+  case ARCH_UNKNOWN:
+    return 0;
+  }
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/RegsArm.cpp b/libunwindstack/RegsArm.cpp
index 1b1f7eb..1aaa08f 100644
--- a/libunwindstack/RegsArm.cpp
+++ b/libunwindstack/RegsArm.cpp
@@ -51,37 +51,6 @@
   regs_[ARM_REG_SP] = sp;
 }
 
-uint64_t RegsArm::GetPcAdjustment(uint64_t rel_pc, Elf* elf) {
-  if (!elf->valid()) {
-    return 2;
-  }
-
-  uint64_t load_bias = elf->GetLoadBias();
-  if (rel_pc < load_bias) {
-    if (rel_pc < 2) {
-      return 0;
-    }
-    return 2;
-  }
-  uint64_t adjusted_rel_pc = rel_pc - load_bias;
-  if (adjusted_rel_pc < 5) {
-    if (adjusted_rel_pc < 2) {
-      return 0;
-    }
-    return 2;
-  }
-
-  if (adjusted_rel_pc & 1) {
-    // This is a thumb instruction, it could be 2 or 4 bytes.
-    uint32_t value;
-    if (!elf->memory()->ReadFully(adjusted_rel_pc - 5, &value, sizeof(value)) ||
-        (value & 0xe000f000) != 0xe000f000) {
-      return 2;
-    }
-  }
-  return 4;
-}
-
 bool RegsArm::SetPcFromReturnAddress(Memory*) {
   uint32_t lr = regs_[ARM_REG_LR];
   if (regs_[ARM_REG_PC] == lr) {
diff --git a/libunwindstack/RegsArm64.cpp b/libunwindstack/RegsArm64.cpp
index 00b3367..5b7431a 100644
--- a/libunwindstack/RegsArm64.cpp
+++ b/libunwindstack/RegsArm64.cpp
@@ -52,13 +52,6 @@
   regs_[ARM64_REG_SP] = sp;
 }
 
-uint64_t RegsArm64::GetPcAdjustment(uint64_t rel_pc, Elf*) {
-  if (rel_pc < 4) {
-    return 0;
-  }
-  return 4;
-}
-
 bool RegsArm64::SetPcFromReturnAddress(Memory*) {
   uint64_t lr = regs_[ARM64_REG_LR];
   if (regs_[ARM64_REG_PC] == lr) {
diff --git a/libunwindstack/RegsMips.cpp b/libunwindstack/RegsMips.cpp
index ebefe42..ab84691 100644
--- a/libunwindstack/RegsMips.cpp
+++ b/libunwindstack/RegsMips.cpp
@@ -52,14 +52,6 @@
   regs_[MIPS_REG_SP] = static_cast<uint32_t>(sp);
 }
 
-uint64_t RegsMips::GetPcAdjustment(uint64_t rel_pc, Elf*) {
-  if (rel_pc < 8) {
-    return 0;
-  }
-  // For now, just assume no compact branches
-  return 8;
-}
-
 bool RegsMips::SetPcFromReturnAddress(Memory*) {
   uint32_t ra = regs_[MIPS_REG_RA];
   if (regs_[MIPS_REG_PC] == ra) {
diff --git a/libunwindstack/RegsMips64.cpp b/libunwindstack/RegsMips64.cpp
index be2fd22..7f600d3 100644
--- a/libunwindstack/RegsMips64.cpp
+++ b/libunwindstack/RegsMips64.cpp
@@ -52,14 +52,6 @@
   regs_[MIPS64_REG_SP] = sp;
 }
 
-uint64_t RegsMips64::GetPcAdjustment(uint64_t rel_pc, Elf*) {
-  if (rel_pc < 8) {
-    return 0;
-  }
-  // For now, just assume no compact branches
-  return 8;
-}
-
 bool RegsMips64::SetPcFromReturnAddress(Memory*) {
   uint64_t ra = regs_[MIPS64_REG_RA];
   if (regs_[MIPS64_REG_PC] == ra) {
diff --git a/libunwindstack/RegsX86.cpp b/libunwindstack/RegsX86.cpp
index 5538fc0..4d3c246 100644
--- a/libunwindstack/RegsX86.cpp
+++ b/libunwindstack/RegsX86.cpp
@@ -50,13 +50,6 @@
   regs_[X86_REG_SP] = static_cast<uint32_t>(sp);
 }
 
-uint64_t RegsX86::GetPcAdjustment(uint64_t rel_pc, Elf*) {
-  if (rel_pc == 0) {
-    return 0;
-  }
-  return 1;
-}
-
 bool RegsX86::SetPcFromReturnAddress(Memory* process_memory) {
   // Attempt to get the return address from the top of the stack.
   uint32_t new_pc;
diff --git a/libunwindstack/RegsX86_64.cpp b/libunwindstack/RegsX86_64.cpp
index 5b9aa58..c9e245d 100644
--- a/libunwindstack/RegsX86_64.cpp
+++ b/libunwindstack/RegsX86_64.cpp
@@ -51,13 +51,6 @@
   regs_[X86_64_REG_SP] = sp;
 }
 
-uint64_t RegsX86_64::GetPcAdjustment(uint64_t rel_pc, Elf*) {
-  if (rel_pc == 0) {
-    return 0;
-  }
-  return 1;
-}
-
 bool RegsX86_64::SetPcFromReturnAddress(Memory* process_memory) {
   // Attempt to get the return address from the top of the stack.
   uint64_t new_pc;
diff --git a/libunwindstack/Symbols.cpp b/libunwindstack/Symbols.cpp
index e3c15a2..2117ebd 100644
--- a/libunwindstack/Symbols.cpp
+++ b/libunwindstack/Symbols.cpp
@@ -19,6 +19,7 @@
 
 #include <algorithm>
 #include <string>
+#include <vector>
 
 #include <unwindstack/Memory.h>
 
@@ -29,23 +30,55 @@
 
 Symbols::Symbols(uint64_t offset, uint64_t size, uint64_t entry_size, uint64_t str_offset,
                  uint64_t str_size)
-    : cur_offset_(offset),
-      offset_(offset),
-      end_(offset + size),
+    : offset_(offset),
+      count_(entry_size != 0 ? size / entry_size : 0),
       entry_size_(entry_size),
       str_offset_(str_offset),
       str_end_(str_offset_ + str_size) {}
 
-const Symbols::Info* Symbols::GetInfoFromCache(uint64_t addr) {
-  // Binary search the table.
+template <typename SymType>
+static bool IsFunc(const SymType* entry) {
+  return entry->st_shndx != SHN_UNDEF && ELF32_ST_TYPE(entry->st_info) == STT_FUNC;
+}
+
+// Read symbol entry from memory and cache it so we don't have to read it again.
+template <typename SymType>
+inline __attribute__((__always_inline__)) const Symbols::Info* Symbols::ReadFuncInfo(
+    uint32_t symbol_index, Memory* elf_memory) {
+  auto it = symbols_.find(symbol_index);
+  if (it != symbols_.end()) {
+    return &it->second;
+  }
+  SymType sym;
+  if (!elf_memory->ReadFully(offset_ + symbol_index * entry_size_, &sym, sizeof(sym))) {
+    return nullptr;
+  }
+  if (!IsFunc(&sym)) {
+    // We need the address for binary search, but we don't want it to be matched.
+    sym.st_size = 0;
+  }
+  Info info{.addr = sym.st_value, .size = static_cast<uint32_t>(sym.st_size), .name = sym.st_name};
+  return &symbols_.emplace(symbol_index, info).first->second;
+}
+
+// Binary search the symbol table to find function containing the given address.
+// Without remap, the symbol table is assumed to be sorted and accessed directly.
+// If the symbol table is not sorted this method might fail but should not crash.
+// When the indices are remapped, they are guaranteed to be sorted by address.
+template <typename SymType, bool RemapIndices>
+const Symbols::Info* Symbols::BinarySearch(uint64_t addr, Memory* elf_memory) {
   size_t first = 0;
-  size_t last = symbols_.size();
+  size_t last = RemapIndices ? remap_->size() : count_;
   while (first < last) {
     size_t current = first + (last - first) / 2;
-    const Info* info = &symbols_[current];
-    if (addr < info->start_offset) {
+    size_t symbol_index = RemapIndices ? remap_.value()[current] : current;
+    const Info* info = ReadFuncInfo<SymType>(symbol_index, elf_memory);
+    if (info == nullptr) {
+      return nullptr;
+    }
+    if (addr < info->addr) {
       last = current;
-    } else if (addr < info->end_offset) {
+    } else if (addr < info->addr + info->size) {
       return info;
     } else {
       first = current + 1;
@@ -54,64 +87,72 @@
   return nullptr;
 }
 
+// Create remapping table which allows us to access symbols as if they were sorted by address.
 template <typename SymType>
-bool Symbols::GetName(uint64_t addr, Memory* elf_memory, std::string* name, uint64_t* func_offset) {
-  if (symbols_.size() != 0) {
-    const Info* info = GetInfoFromCache(addr);
-    if (info) {
-      CHECK(addr >= info->start_offset && addr <= info->end_offset);
-      *func_offset = addr - info->start_offset;
-      return elf_memory->ReadString(info->str_offset, name, str_end_ - info->str_offset);
+void Symbols::BuildRemapTable(Memory* elf_memory) {
+  std::vector<uint64_t> addrs;  // Addresses of all symbols (addrs[i] == symbols[i].st_value).
+  addrs.reserve(count_);
+  remap_.emplace();  // Construct the optional remap table.
+  remap_->reserve(count_);
+  for (size_t symbol_idx = 0; symbol_idx < count_;) {
+    // Read symbols from memory.  We intentionally bypass the cache to save memory.
+    // Do the reads in batches so that we minimize the number of memory read calls.
+    uint8_t buffer[1024];
+    size_t read = std::min<size_t>(sizeof(buffer), (count_ - symbol_idx) * entry_size_);
+    size_t size = elf_memory->Read(offset_ + symbol_idx * entry_size_, buffer, read);
+    if (size < sizeof(SymType)) {
+      break;  // Stop processing, something looks like it is corrupted.
     }
-  }
-
-  bool symbol_added = false;
-  bool return_value = false;
-  while (cur_offset_ + entry_size_ <= end_) {
-    SymType entry;
-    if (!elf_memory->ReadFully(cur_offset_, &entry, sizeof(entry))) {
-      // Stop all processing, something looks like it is corrupted.
-      cur_offset_ = UINT64_MAX;
-      return false;
-    }
-    cur_offset_ += entry_size_;
-
-    if (entry.st_shndx != SHN_UNDEF && ELF32_ST_TYPE(entry.st_info) == STT_FUNC) {
-      // Treat st_value as virtual address.
-      uint64_t start_offset = entry.st_value;
-      uint64_t end_offset = start_offset + entry.st_size;
-
-      // Cache the value.
-      symbols_.emplace_back(start_offset, end_offset, str_offset_ + entry.st_name);
-      symbol_added = true;
-
-      if (addr >= start_offset && addr < end_offset) {
-        *func_offset = addr - start_offset;
-        uint64_t offset = str_offset_ + entry.st_name;
-        if (offset < str_end_) {
-          return_value = elf_memory->ReadString(offset, name, str_end_ - offset);
-        }
-        break;
+    for (size_t offset = 0; offset + sizeof(SymType) <= size; offset += entry_size_, symbol_idx++) {
+      SymType sym;
+      memcpy(&sym, &buffer[offset], sizeof(SymType));  // Copy to ensure alignment.
+      addrs.push_back(sym.st_value);  // Always insert so it is indexable by symbol index.
+      if (IsFunc(&sym)) {
+        remap_->push_back(symbol_idx);  // Indices of function symbols only.
       }
     }
   }
+  // Sort by address to make the remap list binary searchable (stable due to the a<b tie break).
+  auto comp = [&addrs](auto a, auto b) { return std::tie(addrs[a], a) < std::tie(addrs[b], b); };
+  std::sort(remap_->begin(), remap_->end(), comp);
+  // Remove duplicate entries (methods de-duplicated by the linker).
+  auto pred = [&addrs](auto a, auto b) { return addrs[a] == addrs[b]; };
+  remap_->erase(std::unique(remap_->begin(), remap_->end(), pred), remap_->end());
+  remap_->shrink_to_fit();
+}
 
-  if (symbol_added) {
-    std::sort(symbols_.begin(), symbols_.end(),
-              [](const Info& a, const Info& b) { return a.start_offset < b.start_offset; });
+template <typename SymType>
+bool Symbols::GetName(uint64_t addr, Memory* elf_memory, std::string* name, uint64_t* func_offset) {
+  const Info* info;
+  if (!remap_.has_value()) {
+    // Assume the symbol table is sorted. If it is not, this will gracefully fail.
+    info = BinarySearch<SymType, false>(addr, elf_memory);
+    if (info == nullptr) {
+      // Create the remapping table and retry the search.
+      BuildRemapTable<SymType>(elf_memory);
+      symbols_.clear();  // Remove cached symbols since the access pattern will be different.
+      info = BinarySearch<SymType, true>(addr, elf_memory);
+    }
+  } else {
+    // Fast search using the previously created remap table.
+    info = BinarySearch<SymType, true>(addr, elf_memory);
   }
-  return return_value;
+  if (info == nullptr) {
+    return false;
+  }
+  // Read the function name from the string table.
+  *func_offset = addr - info->addr;
+  uint64_t str = str_offset_ + info->name;
+  return str < str_end_ && elf_memory->ReadString(str, name, str_end_ - str);
 }
 
 template <typename SymType>
 bool Symbols::GetGlobal(Memory* elf_memory, const std::string& name, uint64_t* memory_address) {
-  uint64_t cur_offset = offset_;
-  while (cur_offset + entry_size_ <= end_) {
+  for (uint32_t i = 0; i < count_; i++) {
     SymType entry;
-    if (!elf_memory->ReadFully(cur_offset, &entry, sizeof(entry))) {
+    if (!elf_memory->ReadFully(offset_ + i * entry_size_, &entry, sizeof(entry))) {
       return false;
     }
-    cur_offset += entry_size_;
 
     if (entry.st_shndx != SHN_UNDEF && ELF32_ST_TYPE(entry.st_info) == STT_OBJECT &&
         ELF32_ST_BIND(entry.st_info) == STB_GLOBAL) {
diff --git a/libunwindstack/Symbols.h b/libunwindstack/Symbols.h
index 7fcd067..3b3f20b 100644
--- a/libunwindstack/Symbols.h
+++ b/libunwindstack/Symbols.h
@@ -19,8 +19,9 @@
 
 #include <stdint.h>
 
+#include <optional>
 #include <string>
-#include <vector>
+#include <unordered_map>
 
 namespace unwindstack {
 
@@ -29,11 +30,9 @@
 
 class Symbols {
   struct Info {
-    Info(uint64_t start_offset, uint64_t end_offset, uint64_t str_offset)
-        : start_offset(start_offset), end_offset(end_offset), str_offset(str_offset) {}
-    uint64_t start_offset;
-    uint64_t end_offset;
-    uint64_t str_offset;
+    uint64_t addr;  // Symbol address.
+    uint32_t size;  // Symbol size in bytes. Zero if not a function.
+    uint32_t name;  // Offset in .strtab.
   };
 
  public:
@@ -41,8 +40,6 @@
           uint64_t str_size);
   virtual ~Symbols() = default;
 
-  const Info* GetInfoFromCache(uint64_t addr);
-
   template <typename SymType>
   bool GetName(uint64_t addr, Memory* elf_memory, std::string* name, uint64_t* func_offset);
 
@@ -51,18 +48,27 @@
 
   void ClearCache() {
     symbols_.clear();
-    cur_offset_ = offset_;
+    remap_.reset();
   }
 
  private:
-  uint64_t cur_offset_;
-  uint64_t offset_;
-  uint64_t end_;
-  uint64_t entry_size_;
-  uint64_t str_offset_;
-  uint64_t str_end_;
+  template <typename SymType>
+  const Info* ReadFuncInfo(uint32_t symbol_index, Memory* elf_memory);
 
-  std::vector<Info> symbols_;
+  template <typename SymType, bool RemapIndices>
+  const Info* BinarySearch(uint64_t addr, Memory* elf_memory);
+
+  template <typename SymType>
+  void BuildRemapTable(Memory* elf_memory);
+
+  const uint64_t offset_;
+  const uint64_t count_;
+  const uint64_t entry_size_;
+  const uint64_t str_offset_;
+  const uint64_t str_end_;
+
+  std::unordered_map<uint32_t, Info> symbols_;  // Cache of read symbols (keyed by symbol index).
+  std::optional<std::vector<uint32_t>> remap_;  // Indices of function symbols sorted by address.
 };
 
 }  // namespace unwindstack
diff --git a/libunwindstack/Unwinder.cpp b/libunwindstack/Unwinder.cpp
index 1bb0319..2d867cd 100644
--- a/libunwindstack/Unwinder.cpp
+++ b/libunwindstack/Unwinder.cpp
@@ -181,7 +181,7 @@
         step_pc = rel_pc;
       }
       if (adjust_pc) {
-        pc_adjustment = regs_->GetPcAdjustment(rel_pc, elf);
+        pc_adjustment = GetPcAdjustment(rel_pc, elf, arch);
       } else {
         pc_adjustment = 0;
       }
@@ -395,4 +395,54 @@
   return true;
 }
 
+FrameData Unwinder::BuildFrameFromPcOnly(uint64_t pc) {
+  FrameData frame;
+
+  Maps* maps = GetMaps();
+  MapInfo* map_info = maps->Find(pc);
+  if (!map_info) {
+    frame.rel_pc = pc;
+    return frame;
+  }
+
+  ArchEnum arch = Regs::CurrentArch();
+  Elf* elf = map_info->GetElf(GetProcessMemory(), arch);
+
+  uint64_t relative_pc = elf->GetRelPc(pc, map_info);
+
+  uint64_t pc_adjustment = GetPcAdjustment(relative_pc, elf, arch);
+  relative_pc -= pc_adjustment;
+  // The debug PC may be different if the PC comes from the JIT.
+  uint64_t debug_pc = relative_pc;
+
+  // If we don't have a valid ELF file, check the JIT.
+  if (!elf->valid()) {
+    JitDebug jit_debug(GetProcessMemory());
+    uint64_t jit_pc = pc - pc_adjustment;
+    Elf* jit_elf = jit_debug.GetElf(maps, jit_pc);
+    if (jit_elf != nullptr) {
+      debug_pc = jit_pc;
+      elf = jit_elf;
+    }
+  }
+
+  // Copy all the things we need into the frame for symbolization.
+  frame.rel_pc = relative_pc;
+  frame.pc = pc - pc_adjustment;
+  frame.map_name = map_info->name;
+  frame.map_elf_start_offset = map_info->elf_start_offset;
+  frame.map_exact_offset = map_info->offset;
+  frame.map_start = map_info->start;
+  frame.map_end = map_info->end;
+  frame.map_flags = map_info->flags;
+  frame.map_load_bias = elf->GetLoadBias();
+
+  if (!resolve_names_ ||
+      !elf->GetFunctionName(relative_pc, &frame.function_name, &frame.function_offset)) {
+    frame.function_name = "";
+    frame.function_offset = 0;
+  }
+  return frame;
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/benchmarks/ElfBenchmark.cpp b/libunwindstack/benchmarks/ElfBenchmark.cpp
new file mode 100644
index 0000000..c108a2a
--- /dev/null
+++ b/libunwindstack/benchmarks/ElfBenchmark.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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 <malloc.h>
+#include <stdint.h>
+
+#include <string>
+
+#include <benchmark/benchmark.h>
+
+#include <unwindstack/Elf.h>
+#include <unwindstack/Memory.h>
+
+#include "Utils.h"
+
+static void BenchmarkElfCreate(benchmark::State& state, const std::string& elf_file) {
+#if defined(__BIONIC__)
+  uint64_t rss_bytes = 0;
+#endif
+  uint64_t alloc_bytes = 0;
+  for (auto _ : state) {
+    state.PauseTiming();
+#if defined(__BIONIC__)
+    mallopt(M_PURGE, 0);
+    uint64_t rss_bytes_before = 0;
+    GatherRss(&rss_bytes_before);
+#endif
+    uint64_t alloc_bytes_before = mallinfo().uordblks;
+    auto file_memory = unwindstack::Memory::CreateFileMemory(elf_file, 0);
+    state.ResumeTiming();
+
+    unwindstack::Elf elf(file_memory.release());
+    if (!elf.Init() || !elf.valid()) {
+      errx(1, "Internal Error: Cannot open elf.");
+    }
+
+    state.PauseTiming();
+#if defined(__BIONIC__)
+    mallopt(M_PURGE, 0);
+#endif
+    alloc_bytes += mallinfo().uordblks - alloc_bytes_before;
+#if defined(__BIONIC__)
+    GatherRss(&rss_bytes);
+    rss_bytes -= rss_bytes_before;
+#endif
+    state.ResumeTiming();
+  }
+
+#if defined(__BIONIC__)
+  state.counters["RSS_BYTES"] = rss_bytes / static_cast<double>(state.iterations());
+#endif
+  state.counters["ALLOCATED_BYTES"] = alloc_bytes / static_cast<double>(state.iterations());
+}
+
+void BM_elf_create(benchmark::State& state) {
+  BenchmarkElfCreate(state, GetElfFile());
+}
+BENCHMARK(BM_elf_create);
+
+void BM_elf_create_compressed(benchmark::State& state) {
+  BenchmarkElfCreate(state, GetCompressedElfFile());
+}
+BENCHMARK(BM_elf_create_compressed);
diff --git a/libunwindstack/benchmarks/SymbolBenchmark.cpp b/libunwindstack/benchmarks/SymbolBenchmark.cpp
new file mode 100644
index 0000000..73088da
--- /dev/null
+++ b/libunwindstack/benchmarks/SymbolBenchmark.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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 <inttypes.h>
+#include <malloc.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <benchmark/benchmark.h>
+
+#include <unwindstack/Elf.h>
+#include <unwindstack/Memory.h>
+
+#include "Utils.h"
+
+static void BenchmarkSymbolLookup(benchmark::State& state, std::vector<uint64_t> offsets,
+                                  std::string elf_file, bool expect_found) {
+#if defined(__BIONIC__)
+  uint64_t rss_bytes = 0;
+#endif
+  uint64_t alloc_bytes = 0;
+  for (auto _ : state) {
+    state.PauseTiming();
+    unwindstack::Elf elf(unwindstack::Memory::CreateFileMemory(elf_file, 0).release());
+    if (!elf.Init() || !elf.valid()) {
+      errx(1, "Internal Error: Cannot open elf.");
+    }
+
+#if defined(__BIONIC__)
+    mallopt(M_PURGE, 0);
+    uint64_t rss_bytes_before = 0;
+    GatherRss(&rss_bytes_before);
+#endif
+    uint64_t alloc_bytes_before = mallinfo().uordblks;
+    state.ResumeTiming();
+
+    for (auto pc : offsets) {
+      std::string name;
+      uint64_t offset;
+      bool found = elf.GetFunctionName(pc, &name, &offset);
+      if (expect_found && !found) {
+        errx(1, "expected pc 0x%" PRIx64 " present, but not found.", pc);
+      } else if (!expect_found && found) {
+        errx(1, "expected pc 0x%" PRIx64 " not present, but found.", pc);
+      }
+    }
+
+    state.PauseTiming();
+#if defined(__BIONIC__)
+    mallopt(M_PURGE, 0);
+#endif
+    alloc_bytes += mallinfo().uordblks - alloc_bytes_before;
+#if defined(__BIONIC__)
+    GatherRss(&rss_bytes);
+    rss_bytes -= rss_bytes_before;
+#endif
+    state.ResumeTiming();
+  }
+
+#if defined(__BIONIC__)
+  state.counters["RSS_BYTES"] = rss_bytes / static_cast<double>(state.iterations());
+#endif
+  state.counters["ALLOCATED_BYTES"] = alloc_bytes / static_cast<double>(state.iterations());
+}
+
+static void BenchmarkSymbolLookup(benchmark::State& state, uint64_t pc, std::string elf_file,
+                                  bool expect_found) {
+  BenchmarkSymbolLookup(state, std::vector<uint64_t>{pc}, elf_file, expect_found);
+}
+
+void BM_symbol_not_present(benchmark::State& state) {
+  BenchmarkSymbolLookup(state, 0, GetElfFile(), false);
+}
+BENCHMARK(BM_symbol_not_present);
+
+void BM_symbol_find_single(benchmark::State& state) {
+  BenchmarkSymbolLookup(state, 0x22b2bc, GetElfFile(), true);
+}
+BENCHMARK(BM_symbol_find_single);
+
+void BM_symbol_find_single_many_times(benchmark::State& state) {
+  BenchmarkSymbolLookup(state, std::vector<uint64_t>(15, 0x22b2bc), GetElfFile(), true);
+}
+BENCHMARK(BM_symbol_find_single_many_times);
+
+void BM_symbol_find_multiple(benchmark::State& state) {
+  BenchmarkSymbolLookup(state,
+                        std::vector<uint64_t>{0x22b2bc, 0xd5d30, 0x1312e8, 0x13582e, 0x1389c8},
+                        GetElfFile(), true);
+}
+BENCHMARK(BM_symbol_find_multiple);
+
+void BM_symbol_not_present_from_sorted(benchmark::State& state) {
+  BenchmarkSymbolLookup(state, 0, GetSymbolSortedElfFile(), false);
+}
+BENCHMARK(BM_symbol_not_present_from_sorted);
+
+void BM_symbol_find_single_from_sorted(benchmark::State& state) {
+  BenchmarkSymbolLookup(state, 0x138638, GetSymbolSortedElfFile(), true);
+}
+BENCHMARK(BM_symbol_find_single_from_sorted);
+
+void BM_symbol_find_single_many_times_from_sorted(benchmark::State& state) {
+  BenchmarkSymbolLookup(state, std::vector<uint64_t>(15, 0x138638), GetSymbolSortedElfFile(), true);
+}
+BENCHMARK(BM_symbol_find_single_many_times_from_sorted);
+
+void BM_symbol_find_multiple_from_sorted(benchmark::State& state) {
+  BenchmarkSymbolLookup(state,
+                        std::vector<uint64_t>{0x138638, 0x84350, 0x14df18, 0x1f3a38, 0x1f3ca8},
+                        GetSymbolSortedElfFile(), true);
+}
+BENCHMARK(BM_symbol_find_multiple_from_sorted);
diff --git a/libunwindstack/benchmarks/Utils.cpp b/libunwindstack/benchmarks/Utils.cpp
new file mode 100644
index 0000000..c92f109
--- /dev/null
+++ b/libunwindstack/benchmarks/Utils.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 <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
+#include <benchmark/benchmark.h>
+
+#include <unwindstack/Elf.h>
+#include <unwindstack/Memory.h>
+
+std::string GetElfFile() {
+  return android::base::GetExecutableDirectory() + "/benchmarks/files/libart_arm.so";
+}
+
+std::string GetSymbolSortedElfFile() {
+  return android::base::GetExecutableDirectory() + "/benchmarks/files/boot_arm.oat";
+}
+
+std::string GetCompressedElfFile() {
+  // Both are the same right now.
+  return GetSymbolSortedElfFile();
+}
+
+#if defined(__BIONIC__)
+
+#include <meminfo/procmeminfo.h>
+#include <procinfo/process_map.h>
+
+void GatherRss(uint64_t* rss_bytes) {
+  android::meminfo::ProcMemInfo proc_mem(getpid());
+  const std::vector<android::meminfo::Vma>& maps = proc_mem.MapsWithoutUsageStats();
+  for (auto& vma : maps) {
+    if (vma.name == "[anon:libc_malloc]" || android::base::StartsWith(vma.name, "[anon:scudo:") ||
+        android::base::StartsWith(vma.name, "[anon:GWP-ASan")) {
+      android::meminfo::Vma update_vma(vma);
+      if (!proc_mem.FillInVmaStats(update_vma)) {
+        err(1, "FillInVmaStats failed\n");
+      }
+      *rss_bytes += update_vma.usage.rss;
+    }
+  }
+}
+#endif
diff --git a/libunwindstack/benchmarks/Utils.h b/libunwindstack/benchmarks/Utils.h
new file mode 100644
index 0000000..bee6efc
--- /dev/null
+++ b/libunwindstack/benchmarks/Utils.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#ifndef _LIBUNWINDSTACK_UTILS_H
+#define _LIBUNWINDSTACK_UTILS_H
+
+#include <stdint.h>
+
+#include <string>
+
+std::string GetElfFile();
+
+std::string GetSymbolSortedElfFile();
+
+std::string GetCompressedElfFile();
+
+#if defined(__BIONIC__)
+
+#include <meminfo/procmeminfo.h>
+#include <procinfo/process_map.h>
+
+void GatherRss(uint64_t* rss_bytes);
+
+#endif
+
+#endif  // _LIBUNWINDSTACK_UTILS_h
diff --git a/libunwindstack/benchmarks/files/boot_arm.oat b/libunwindstack/benchmarks/files/boot_arm.oat
new file mode 100644
index 0000000..51188eb
--- /dev/null
+++ b/libunwindstack/benchmarks/files/boot_arm.oat
Binary files differ
diff --git a/libunwindstack/benchmarks/files/libart_arm.so b/libunwindstack/benchmarks/files/libart_arm.so
new file mode 100644
index 0000000..2201faf
--- /dev/null
+++ b/libunwindstack/benchmarks/files/libart_arm.so
Binary files differ
diff --git a/libunwindstack/include/unwindstack/Memory.h b/libunwindstack/include/unwindstack/Memory.h
index 3106564..3d81878 100644
--- a/libunwindstack/include/unwindstack/Memory.h
+++ b/libunwindstack/include/unwindstack/Memory.h
@@ -37,11 +37,14 @@
                                                      uint64_t end);
   static std::unique_ptr<Memory> CreateFileMemory(const std::string& path, uint64_t offset);
 
-  virtual bool ReadString(uint64_t addr, std::string* string, uint64_t max_read = UINT64_MAX);
+  virtual bool ReadString(uint64_t addr, std::string* dst, size_t max_read);
 
   virtual void Clear() {}
 
+  virtual bool IsLocal() const { return false; }
+
   virtual size_t Read(uint64_t addr, void* dst, size_t size) = 0;
+  virtual long ReadTag(uint64_t) { return -1; }
 
   bool ReadFully(uint64_t addr, void* dst, size_t size);
 
diff --git a/libunwindstack/include/unwindstack/Regs.h b/libunwindstack/include/unwindstack/Regs.h
index 4f761b4..a367e6c 100644
--- a/libunwindstack/include/unwindstack/Regs.h
+++ b/libunwindstack/include/unwindstack/Regs.h
@@ -64,8 +64,6 @@
   uint64_t dex_pc() { return dex_pc_; }
   void set_dex_pc(uint64_t dex_pc) { dex_pc_ = dex_pc; }
 
-  virtual uint64_t GetPcAdjustment(uint64_t rel_pc, Elf* elf) = 0;
-
   virtual bool StepIfSignalHandler(uint64_t elf_offset, Elf* elf, Memory* process_memory) = 0;
 
   virtual bool SetPcFromReturnAddress(Memory* process_memory) = 0;
@@ -110,6 +108,8 @@
   std::vector<AddressType> regs_;
 };
 
+uint64_t GetPcAdjustment(uint64_t rel_pc, Elf* elf, ArchEnum arch);
+
 }  // namespace unwindstack
 
 #endif  // _LIBUNWINDSTACK_REGS_H
diff --git a/libunwindstack/include/unwindstack/RegsArm.h b/libunwindstack/include/unwindstack/RegsArm.h
index aa029be..fbb34e7 100644
--- a/libunwindstack/include/unwindstack/RegsArm.h
+++ b/libunwindstack/include/unwindstack/RegsArm.h
@@ -36,8 +36,6 @@
 
   ArchEnum Arch() override final;
 
-  uint64_t GetPcAdjustment(uint64_t rel_pc, Elf* elf) override;
-
   bool SetPcFromReturnAddress(Memory* process_memory) override;
 
   bool StepIfSignalHandler(uint64_t elf_offset, Elf* elf, Memory* process_memory) override;
diff --git a/libunwindstack/include/unwindstack/RegsArm64.h b/libunwindstack/include/unwindstack/RegsArm64.h
index 5cd7e5b..2b3ddeb 100644
--- a/libunwindstack/include/unwindstack/RegsArm64.h
+++ b/libunwindstack/include/unwindstack/RegsArm64.h
@@ -36,8 +36,6 @@
 
   ArchEnum Arch() override final;
 
-  uint64_t GetPcAdjustment(uint64_t rel_pc, Elf* elf) override;
-
   bool SetPcFromReturnAddress(Memory* process_memory) override;
 
   bool StepIfSignalHandler(uint64_t elf_offset, Elf* elf, Memory* process_memory) override;
diff --git a/libunwindstack/include/unwindstack/RegsMips.h b/libunwindstack/include/unwindstack/RegsMips.h
index 8164a15..dc09b83 100644
--- a/libunwindstack/include/unwindstack/RegsMips.h
+++ b/libunwindstack/include/unwindstack/RegsMips.h
@@ -36,8 +36,6 @@
 
   ArchEnum Arch() override final;
 
-  uint64_t GetPcAdjustment(uint64_t rel_pc, Elf* elf) override;
-
   bool SetPcFromReturnAddress(Memory* process_memory) override;
 
   bool StepIfSignalHandler(uint64_t elf_offset, Elf* elf, Memory* process_memory) override;
diff --git a/libunwindstack/include/unwindstack/RegsMips64.h b/libunwindstack/include/unwindstack/RegsMips64.h
index c982542..64a80dc 100644
--- a/libunwindstack/include/unwindstack/RegsMips64.h
+++ b/libunwindstack/include/unwindstack/RegsMips64.h
@@ -36,8 +36,6 @@
 
   ArchEnum Arch() override final;
 
-  uint64_t GetPcAdjustment(uint64_t rel_pc, Elf* elf) override;
-
   bool SetPcFromReturnAddress(Memory* process_memory) override;
 
   bool StepIfSignalHandler(uint64_t elf_offset, Elf* elf, Memory* process_memory) override;
diff --git a/libunwindstack/include/unwindstack/RegsX86.h b/libunwindstack/include/unwindstack/RegsX86.h
index 2323a4f..cfbdda6 100644
--- a/libunwindstack/include/unwindstack/RegsX86.h
+++ b/libunwindstack/include/unwindstack/RegsX86.h
@@ -37,8 +37,6 @@
 
   ArchEnum Arch() override final;
 
-  uint64_t GetPcAdjustment(uint64_t rel_pc, Elf* elf) override;
-
   bool SetPcFromReturnAddress(Memory* process_memory) override;
 
   bool StepIfSignalHandler(uint64_t elf_offset, Elf* elf, Memory* process_memory) override;
diff --git a/libunwindstack/include/unwindstack/RegsX86_64.h b/libunwindstack/include/unwindstack/RegsX86_64.h
index 3e919a4..a11aef0 100644
--- a/libunwindstack/include/unwindstack/RegsX86_64.h
+++ b/libunwindstack/include/unwindstack/RegsX86_64.h
@@ -37,8 +37,6 @@
 
   ArchEnum Arch() override final;
 
-  uint64_t GetPcAdjustment(uint64_t rel_pc, Elf* elf) override;
-
   bool SetPcFromReturnAddress(Memory* process_memory) override;
 
   bool StepIfSignalHandler(uint64_t elf_offset, Elf* elf, Memory* process_memory) override;
diff --git a/libunwindstack/include/unwindstack/Unwinder.h b/libunwindstack/include/unwindstack/Unwinder.h
index 67762c0..4d49f23 100644
--- a/libunwindstack/include/unwindstack/Unwinder.h
+++ b/libunwindstack/include/unwindstack/Unwinder.h
@@ -114,6 +114,13 @@
   ErrorCode LastErrorCode() { return last_error_.code; }
   uint64_t LastErrorAddress() { return last_error_.address; }
 
+  // Builds a frame for symbolization using the maps from this unwinder. The
+  // constructed frame contains just enough information to be used to symbolize
+  // frames collected by frame-pointer unwinding that's done outside of
+  // libunwindstack. This is used by tombstoned to symbolize frame pointer-based
+  // stack traces that are collected by tools such as GWP-ASan and MTE.
+  FrameData BuildFrameFromPcOnly(uint64_t pc);
+
  protected:
   Unwinder(size_t max_frames) : max_frames_(max_frames) { frames_.reserve(max_frames); }
 
diff --git a/libunwindstack/tests/DexFileTest.cpp b/libunwindstack/tests/DexFileTest.cpp
index 1b54da6..1deba01 100644
--- a/libunwindstack/tests/DexFileTest.cpp
+++ b/libunwindstack/tests/DexFileTest.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+#include <malloc.h>
 #include <stdint.h>
 #include <sys/types.h>
 #include <unistd.h>
 
 #include <unordered_map>
 
+#include <MemoryLocal.h>
 #include <android-base/file.h>
 #include <gtest/gtest.h>
 #include <unwindstack/MapInfo.h>
@@ -72,12 +74,43 @@
   EXPECT_TRUE(DexFileFromFile::Create(0x100, tf.path) != nullptr);
 }
 
+static constexpr size_t kNumLeakLoops = 5000;
+static constexpr size_t kMaxAllowedLeakBytes = 1024;
+
+static void CheckForLeak(size_t loop, size_t* first_allocated_bytes, size_t* last_allocated_bytes) {
+  size_t allocated_bytes = mallinfo().uordblks;
+  if (*first_allocated_bytes == 0) {
+    *first_allocated_bytes = allocated_bytes;
+  } else if (*last_allocated_bytes > *first_allocated_bytes) {
+    // Check that the total memory did not increase too much over the first loop.
+    ASSERT_LE(*last_allocated_bytes - *first_allocated_bytes, kMaxAllowedLeakBytes)
+        << "Failed in loop " << loop << " first_allocated_bytes " << *first_allocated_bytes
+        << " last_allocated_bytes " << *last_allocated_bytes;
+  }
+  *last_allocated_bytes = allocated_bytes;
+}
+
+TEST(DexFileTest, from_file_no_leak) {
+  TemporaryFile tf;
+  ASSERT_TRUE(tf.fd != -1);
+
+  ASSERT_EQ(sizeof(kDexData),
+            static_cast<size_t>(TEMP_FAILURE_RETRY(write(tf.fd, kDexData, sizeof(kDexData)))));
+
+  size_t first_allocated_bytes = 0;
+  size_t last_allocated_bytes = 0;
+  for (size_t i = 0; i < kNumLeakLoops; i++) {
+    EXPECT_TRUE(DexFileFromFile::Create(0, tf.path) != nullptr);
+    ASSERT_NO_FATAL_FAILURE(CheckForLeak(i, &first_allocated_bytes, &last_allocated_bytes));
+  }
+}
+
 TEST(DexFileTest, from_memory_fail_too_small_for_header) {
   MemoryFake memory;
 
   memory.SetMemory(0x1000, kDexData, 10);
 
-  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "") == nullptr);
+  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "", sizeof(kDexData)) == nullptr);
 }
 
 TEST(DexFileTest, from_memory_fail_too_small_for_data) {
@@ -85,7 +118,7 @@
 
   memory.SetMemory(0x1000, kDexData, sizeof(kDexData) - 2);
 
-  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "") == nullptr);
+  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "", sizeof(kDexData)) == nullptr);
 }
 
 TEST(DexFileTest, from_memory_open) {
@@ -93,7 +126,20 @@
 
   memory.SetMemory(0x1000, kDexData, sizeof(kDexData));
 
-  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "") != nullptr);
+  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "", sizeof(kDexData)) != nullptr);
+}
+
+TEST(DexFileTest, from_memory_no_leak) {
+  MemoryFake memory;
+
+  memory.SetMemory(0x1000, kDexData, sizeof(kDexData));
+
+  size_t first_allocated_bytes = 0;
+  size_t last_allocated_bytes = 0;
+  for (size_t i = 0; i < kNumLeakLoops; i++) {
+    EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "", sizeof(kDexData)) != nullptr);
+    ASSERT_NO_FATAL_FAILURE(CheckForLeak(i, &first_allocated_bytes, &last_allocated_bytes));
+  }
 }
 
 TEST(DexFileTest, create_using_file) {
@@ -168,6 +214,43 @@
   EXPECT_TRUE(dex_file == nullptr);
 }
 
+TEST(DexFileTest, create_using_memory_size_too_small) {
+  MemoryFake memory;
+  memory.SetMemory(0x4000, kDexData, sizeof(kDexData));
+  MapInfo info(nullptr, nullptr, 0x100, sizeof(kDexData) - 2, 0x200, 0x5, "/does/not/exist");
+  EXPECT_TRUE(DexFile::Create(0x4000, &memory, &info) != nullptr);
+}
+
+class MemoryLocalFake : public MemoryLocal {
+ public:
+  MemoryLocalFake(size_t memory_size) : backing_(memory_size) {}
+  virtual ~MemoryLocalFake() = default;
+
+  void* Data() { return backing_.data(); }
+
+ private:
+  std::vector<void*> backing_;
+};
+
+TEST(DexFileTest, create_using_local_memory) {
+  MemoryLocalFake memory(sizeof(kDexData));
+
+  memcpy(memory.Data(), kDexData, sizeof(kDexData));
+  uint64_t start = reinterpret_cast<uint64_t>(memory.Data());
+  MapInfo info(nullptr, nullptr, start, start + 0x1000, 0x200, 0x5, "/does/not/exist");
+  EXPECT_TRUE(DexFile::Create(start, &memory, &info) != nullptr);
+}
+
+TEST(DexFileTest, create_using_local_memory_size_too_small) {
+  MemoryLocalFake memory(sizeof(kDexData));
+
+  memcpy(memory.Data(), kDexData, sizeof(kDexData));
+  uint64_t start = reinterpret_cast<uint64_t>(memory.Data());
+  MapInfo info(nullptr, nullptr, start, start + sizeof(kDexData) - 2, 0x200, 0x5,
+               "/does/not/exist");
+  EXPECT_TRUE(DexFile::Create(start, &memory, &info) == nullptr);
+}
+
 TEST(DexFileTest, get_method) {
   MemoryFake memory;
   memory.SetMemory(0x4000, kDexData, sizeof(kDexData));
diff --git a/libunwindstack/tests/MapInfoGetBuildIDTest.cpp b/libunwindstack/tests/MapInfoGetBuildIDTest.cpp
index 6953e26..70e136b 100644
--- a/libunwindstack/tests/MapInfoGetBuildIDTest.cpp
+++ b/libunwindstack/tests/MapInfoGetBuildIDTest.cpp
@@ -142,15 +142,14 @@
 
   char note_section[128];
   Elf32_Nhdr note_header = {};
-  note_header.n_namesz = 4;   // "GNU"
-  note_header.n_descsz = 12;  // "ELF_BUILDID"
+  note_header.n_namesz = sizeof("GNU");
+  note_header.n_descsz = sizeof("ELF_BUILDID") - 1;
   note_header.n_type = NT_GNU_BUILD_ID;
   memcpy(&note_section, &note_header, sizeof(note_header));
   size_t note_offset = sizeof(note_header);
-  memcpy(&note_section[note_offset], "GNU", sizeof("GNU"));
-  note_offset += sizeof("GNU");
-  memcpy(&note_section[note_offset], "ELF_BUILDID", sizeof("ELF_BUILDID"));
-  note_offset += sizeof("ELF_BUILDID");
+  memcpy(&note_section[note_offset], "GNU", note_header.n_namesz);
+  note_offset += note_header.n_namesz;
+  memcpy(&note_section[note_offset], "ELF_BUILDID", note_header.n_descsz);
 
   Elf32_Shdr shdr = {};
   shdr.sh_type = SHT_NOTE;
@@ -195,4 +194,10 @@
   MultipleThreadTest("ELF_BUILDID");
 }
 
+TEST_F(MapInfoGetBuildIDTest, real_elf) {
+  MapInfo map_info(nullptr, nullptr, 0x1000, 0x20000, 0, PROT_READ | PROT_WRITE,
+                   TestGetFileDirectory() + "offline/empty_arm64/libc.so");
+  EXPECT_EQ("6df0590c4920f4c7b9f34fe833f37d54", map_info.GetPrintableBuildID());
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/MemoryMteTest.cpp b/libunwindstack/tests/MemoryMteTest.cpp
new file mode 100644
index 0000000..3ae322e
--- /dev/null
+++ b/libunwindstack/tests/MemoryMteTest.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#if defined(ANDROID_EXPERIMENTAL_MTE)
+
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include <gtest/gtest.h>
+
+#include <bionic/mte.h>
+
+#include "MemoryLocal.h"
+#include "MemoryRemote.h"
+#include "TestUtils.h"
+
+namespace unwindstack {
+
+static uintptr_t CreateTagMapping() {
+  uintptr_t mapping =
+      reinterpret_cast<uintptr_t>(mmap(nullptr, getpagesize(), PROT_READ | PROT_WRITE | PROT_MTE,
+                                       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
+  if (reinterpret_cast<void*>(mapping) == MAP_FAILED) {
+    return 0;
+  }
+#if defined(__aarch64__)
+  __asm__ __volatile__(".arch_extension mte; stg %0, [%0]"
+                       :
+                       : "r"(mapping + (1ULL << 56))
+                       : "memory");
+#endif
+  return mapping;
+}
+
+TEST(MemoryMteTest, remote_read_tag) {
+#if !defined(__aarch64__)
+  GTEST_SKIP() << "Requires aarch64";
+#else
+  if (!mte_supported()) {
+    GTEST_SKIP() << "Requires MTE";
+  }
+#endif
+
+  uintptr_t mapping = CreateTagMapping();
+  ASSERT_NE(0U, mapping);
+
+  pid_t pid;
+  if ((pid = fork()) == 0) {
+    while (true)
+      ;
+    exit(1);
+  }
+  ASSERT_LT(0, pid);
+  TestScopedPidReaper reap(pid);
+
+  ASSERT_TRUE(TestAttach(pid));
+
+  MemoryRemote remote(pid);
+
+  EXPECT_EQ(1, remote.ReadTag(mapping));
+  EXPECT_EQ(0, remote.ReadTag(mapping + 16));
+
+  ASSERT_TRUE(TestDetach(pid));
+}
+
+TEST(MemoryMteTest, local_read_tag) {
+#if !defined(__aarch64__)
+  GTEST_SKIP() << "Requires aarch64";
+#else
+  if (!mte_supported()) {
+    GTEST_SKIP() << "Requires MTE";
+  }
+#endif
+
+  uintptr_t mapping = CreateTagMapping();
+  ASSERT_NE(0U, mapping);
+
+  MemoryLocal local;
+
+  EXPECT_EQ(1, local.ReadTag(mapping));
+  EXPECT_EQ(0, local.ReadTag(mapping + 16));
+}
+
+}  // namespace unwindstack
+
+#endif
diff --git a/libunwindstack/tests/MemoryRemoteTest.cpp b/libunwindstack/tests/MemoryRemoteTest.cpp
index c90dedc..385078d 100644
--- a/libunwindstack/tests/MemoryRemoteTest.cpp
+++ b/libunwindstack/tests/MemoryRemoteTest.cpp
@@ -26,8 +26,10 @@
 
 #include <vector>
 
-#include <android-base/test_utils.h>
 #include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <bionic/mte.h>
+#include <bionic/mte_kernel.h>
 #include <gtest/gtest.h>
 
 #include "MemoryRemote.h"
@@ -37,24 +39,7 @@
 
 namespace unwindstack {
 
-class MemoryRemoteTest : public ::testing::Test {
- protected:
-  static bool Attach(pid_t pid) {
-    if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) {
-      return false;
-    }
-
-    return TestQuiescePid(pid);
-  }
-
-  static bool Detach(pid_t pid) {
-    return ptrace(PTRACE_DETACH, pid, 0, 0) == 0;
-  }
-
-  static constexpr size_t NS_PER_SEC = 1000000000ULL;
-};
-
-TEST_F(MemoryRemoteTest, read) {
+TEST(MemoryRemoteTest, read) {
   std::vector<uint8_t> src(1024);
   memset(src.data(), 0x4c, 1024);
 
@@ -66,7 +51,7 @@
   ASSERT_LT(0, pid);
   TestScopedPidReaper reap(pid);
 
-  ASSERT_TRUE(Attach(pid));
+  ASSERT_TRUE(TestAttach(pid));
 
   MemoryRemote remote(pid);
 
@@ -76,10 +61,10 @@
     ASSERT_EQ(0x4cU, dst[i]) << "Failed at byte " << i;
   }
 
-  ASSERT_TRUE(Detach(pid));
+  ASSERT_TRUE(TestDetach(pid));
 }
 
-TEST_F(MemoryRemoteTest, read_large) {
+TEST(MemoryRemoteTest, read_large) {
   static constexpr size_t kTotalPages = 245;
   std::vector<uint8_t> src(kTotalPages * getpagesize());
   for (size_t i = 0; i < kTotalPages; i++) {
@@ -95,7 +80,7 @@
   ASSERT_LT(0, pid);
   TestScopedPidReaper reap(pid);
 
-  ASSERT_TRUE(Attach(pid));
+  ASSERT_TRUE(TestAttach(pid));
 
   MemoryRemote remote(pid);
 
@@ -105,10 +90,10 @@
     ASSERT_EQ(i / getpagesize(), dst[i]) << "Failed at byte " << i;
   }
 
-  ASSERT_TRUE(Detach(pid));
+  ASSERT_TRUE(TestDetach(pid));
 }
 
-TEST_F(MemoryRemoteTest, read_partial) {
+TEST(MemoryRemoteTest, read_partial) {
   char* mapping = static_cast<char*>(
       mmap(nullptr, 4 * getpagesize(), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
   ASSERT_NE(MAP_FAILED, mapping);
@@ -128,7 +113,7 @@
   // Unmap from our process.
   ASSERT_EQ(0, munmap(mapping, 3 * getpagesize()));
 
-  ASSERT_TRUE(Attach(pid));
+  ASSERT_TRUE(TestAttach(pid));
 
   MemoryRemote remote(pid);
 
@@ -149,10 +134,10 @@
     ASSERT_EQ(0x4cU, dst[i]) << "Failed at byte " << i;
   }
 
-  ASSERT_TRUE(Detach(pid));
+  ASSERT_TRUE(TestDetach(pid));
 }
 
-TEST_F(MemoryRemoteTest, read_fail) {
+TEST(MemoryRemoteTest, read_fail) {
   int pagesize = getpagesize();
   void* src = mmap(nullptr, pagesize * 2, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE,-1, 0);
   memset(src, 0x4c, pagesize * 2);
@@ -169,7 +154,7 @@
   ASSERT_LT(0, pid);
   TestScopedPidReaper reap(pid);
 
-  ASSERT_TRUE(Attach(pid));
+  ASSERT_TRUE(TestAttach(pid));
 
   MemoryRemote remote(pid);
 
@@ -188,10 +173,10 @@
 
   ASSERT_EQ(0, munmap(src, pagesize));
 
-  ASSERT_TRUE(Detach(pid));
+  ASSERT_TRUE(TestDetach(pid));
 }
 
-TEST_F(MemoryRemoteTest, read_overflow) {
+TEST(MemoryRemoteTest, read_overflow) {
   pid_t pid;
   if ((pid = fork()) == 0) {
     while (true)
@@ -201,7 +186,7 @@
   ASSERT_LT(0, pid);
   TestScopedPidReaper reap(pid);
 
-  ASSERT_TRUE(Attach(pid));
+  ASSERT_TRUE(TestAttach(pid));
 
   MemoryRemote remote(pid);
 
@@ -209,10 +194,10 @@
   std::vector<uint8_t> dst(200);
   ASSERT_FALSE(remote.ReadFully(UINT64_MAX - 100, dst.data(), 200));
 
-  ASSERT_TRUE(Detach(pid));
+  ASSERT_TRUE(TestDetach(pid));
 }
 
-TEST_F(MemoryRemoteTest, read_illegal) {
+TEST(MemoryRemoteTest, read_illegal) {
   pid_t pid;
   if ((pid = fork()) == 0) {
     while (true);
@@ -221,7 +206,7 @@
   ASSERT_LT(0, pid);
   TestScopedPidReaper reap(pid);
 
-  ASSERT_TRUE(Attach(pid));
+  ASSERT_TRUE(TestAttach(pid));
 
   MemoryRemote remote(pid);
 
@@ -229,10 +214,10 @@
   ASSERT_FALSE(remote.ReadFully(0, dst.data(), 1));
   ASSERT_FALSE(remote.ReadFully(0, dst.data(), 100));
 
-  ASSERT_TRUE(Detach(pid));
+  ASSERT_TRUE(TestDetach(pid));
 }
 
-TEST_F(MemoryRemoteTest, read_mprotect_hole) {
+TEST(MemoryRemoteTest, read_mprotect_hole) {
   size_t page_size = getpagesize();
   void* mapping =
       mmap(nullptr, 3 * getpagesize(), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
@@ -250,7 +235,7 @@
 
   ASSERT_EQ(0, munmap(mapping, 3 * page_size));
 
-  ASSERT_TRUE(Attach(pid));
+  ASSERT_TRUE(TestAttach(pid));
 
   MemoryRemote remote(pid);
   std::vector<uint8_t> dst(getpagesize() * 4, 0xCC);
@@ -263,9 +248,11 @@
   for (size_t i = read_size; i < dst.size(); ++i) {
     ASSERT_EQ(0xCC, dst[i]);
   }
+
+  ASSERT_TRUE(TestDetach(pid));
 }
 
-TEST_F(MemoryRemoteTest, read_munmap_hole) {
+TEST(MemoryRemoteTest, read_munmap_hole) {
   size_t page_size = getpagesize();
   void* mapping =
       mmap(nullptr, 3 * getpagesize(), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
@@ -285,7 +272,7 @@
   ASSERT_EQ(0, munmap(mapping, page_size));
   ASSERT_EQ(0, munmap(static_cast<char*>(mapping) + 2 * page_size, page_size));
 
-  ASSERT_TRUE(Attach(pid));
+  ASSERT_TRUE(TestAttach(pid));
 
   MemoryRemote remote(pid);
   std::vector<uint8_t> dst(getpagesize() * 4, 0xCC);
@@ -297,11 +284,13 @@
   for (size_t i = read_size; i < dst.size(); ++i) {
     ASSERT_EQ(0xCC, dst[i]);
   }
+
+  ASSERT_TRUE(TestDetach(pid));
 }
 
 // Verify that the memory remote object chooses a memory read function
 // properly. Either process_vm_readv or ptrace.
-TEST_F(MemoryRemoteTest, read_choose_correctly) {
+TEST(MemoryRemoteTest, read_choose_correctly) {
   size_t page_size = getpagesize();
   void* mapping =
       mmap(nullptr, 2 * getpagesize(), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
@@ -320,7 +309,7 @@
 
   ASSERT_EQ(0, munmap(mapping, 2 * page_size));
 
-  ASSERT_TRUE(Attach(pid));
+  ASSERT_TRUE(TestAttach(pid));
 
   // We know that process_vm_readv of a mprotect'd PROT_NONE region will fail.
   // Read from the PROT_NONE area first to force the choice of ptrace.
@@ -348,6 +337,8 @@
   bytes = remote_readv.Read(reinterpret_cast<uint64_t>(mapping) + page_size, &value, sizeof(value));
   ASSERT_EQ(sizeof(value), bytes);
   ASSERT_EQ(0xfcfcfcfcU, value);
+
+  ASSERT_TRUE(TestDetach(pid));
 }
 
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/MemoryTest.cpp b/libunwindstack/tests/MemoryTest.cpp
index 3655984..8a8eb24 100644
--- a/libunwindstack/tests/MemoryTest.cpp
+++ b/libunwindstack/tests/MemoryTest.cpp
@@ -59,10 +59,10 @@
   memory.SetMemory(100, name.c_str(), name.size() + 1);
 
   std::string dst_name;
-  ASSERT_TRUE(memory.ReadString(100, &dst_name));
+  ASSERT_TRUE(memory.ReadString(100, &dst_name, 100));
   ASSERT_EQ("string_in_memory", dst_name);
 
-  ASSERT_TRUE(memory.ReadString(107, &dst_name));
+  ASSERT_TRUE(memory.ReadString(107, &dst_name, 100));
   ASSERT_EQ("in_memory", dst_name);
 
   // Set size greater than string.
@@ -82,15 +82,56 @@
 
   std::string dst_name;
   // Read from a non-existant address.
-  ASSERT_FALSE(memory.ReadString(100, &dst_name));
+  ASSERT_FALSE(memory.ReadString(100, &dst_name, 100));
 
   // This should fail because there is no terminating '\0'.
-  ASSERT_FALSE(memory.ReadString(0, &dst_name));
+  ASSERT_FALSE(memory.ReadString(0, &dst_name, 100));
 
   // This should pass because there is a terminating '\0'.
   memory.SetData8(name.size(), '\0');
-  ASSERT_TRUE(memory.ReadString(0, &dst_name));
+  ASSERT_TRUE(memory.ReadString(0, &dst_name, 100));
   ASSERT_EQ("short", dst_name);
 }
 
+TEST(MemoryTest, read_string_long) {
+  // This string should be greater than 768 characters long (greater than 3 times
+  // the buffer in the ReadString function) to read multiple blocks.
+  static constexpr char kLongString[] =
+      "one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen "
+      "sixteen seventeen eightteen nineteen twenty twenty-one twenty-two twenty-three twenty-four "
+      "twenty-five twenty-six twenty-seven twenty-eight twenty-nine thirty thirty-one thirty-two "
+      "thirty-three thirty-four thirty-five thirty-six thirty-seven thirty-eight thirty-nine forty "
+      "forty-one forty-two forty-three forty-four forty-five forty-size forty-seven forty-eight "
+      "forty-nine fifty fifty-one fifty-two fifty-three fifty-four fifty-five fifty-six "
+      "fifty-seven fifty-eight fifty-nine sixty sixty-one sixty-two sixty-three sixty-four "
+      "sixty-five sixty-six sixty-seven sixty-eight sixty-nine seventy seventy-one seventy-two "
+      "seventy-three seventy-four seventy-five seventy-six seventy-seven seventy-eight "
+      "seventy-nine eighty";
+
+  MemoryFake memory;
+
+  memory.SetMemory(100, kLongString, sizeof(kLongString));
+
+  std::string dst_name;
+  ASSERT_TRUE(memory.ReadString(100, &dst_name, sizeof(kLongString)));
+  ASSERT_EQ(kLongString, dst_name);
+
+  std::string expected_str(kLongString, 255);
+  memory.SetMemory(100, expected_str.data(), expected_str.length() + 1);
+  ASSERT_TRUE(memory.ReadString(100, &dst_name, 256));
+  ASSERT_EQ(expected_str, dst_name);
+  ASSERT_FALSE(memory.ReadString(100, &dst_name, 255));
+
+  expected_str = std::string(kLongString, 256);
+  memory.SetMemory(100, expected_str.data(), expected_str.length() + 1);
+  ASSERT_TRUE(memory.ReadString(100, &dst_name, 257));
+  ASSERT_EQ(expected_str, dst_name);
+  ASSERT_FALSE(memory.ReadString(100, &dst_name, 256));
+
+  expected_str = std::string(kLongString, 299);
+  memory.SetMemory(100, expected_str.data(), expected_str.length() + 1);
+  ASSERT_TRUE(memory.ReadString(100, &dst_name, 300));
+  ASSERT_EQ(expected_str, dst_name);
+}
+
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/RegsFake.h b/libunwindstack/tests/RegsFake.h
index 207d46e..75fc9d0 100644
--- a/libunwindstack/tests/RegsFake.h
+++ b/libunwindstack/tests/RegsFake.h
@@ -54,8 +54,6 @@
     return fake_arch_ == ARCH_ARM || fake_arch_ == ARCH_X86 || fake_arch_ == ARCH_MIPS;
   }
 
-  uint64_t GetPcAdjustment(uint64_t, Elf*) override { return 2; }
-
   bool StepIfSignalHandler(uint64_t, Elf*, Memory*) override { return false; }
 
   void FakeSetArch(ArchEnum arch) { fake_arch_ = arch; }
@@ -86,7 +84,6 @@
   void set_pc(uint64_t pc) override { fake_pc_ = pc; }
   void set_sp(uint64_t sp) override { fake_sp_ = sp; }
 
-  uint64_t GetPcAdjustment(uint64_t, Elf*) override { return 0; }
   bool SetPcFromReturnAddress(Memory*) override { return false; }
   bool StepIfSignalHandler(uint64_t, Elf*, Memory*) override { return false; }
 
diff --git a/libunwindstack/tests/RegsTest.cpp b/libunwindstack/tests/RegsTest.cpp
index 0a33e2f..e4fc6f0 100644
--- a/libunwindstack/tests/RegsTest.cpp
+++ b/libunwindstack/tests/RegsTest.cpp
@@ -93,123 +93,104 @@
 }
 
 TEST_F(RegsTest, rel_pc) {
-  RegsArm64 arm64;
-  EXPECT_EQ(4U, arm64.GetPcAdjustment(0x10, elf_.get()));
-  EXPECT_EQ(4U, arm64.GetPcAdjustment(0x4, elf_.get()));
-  EXPECT_EQ(0U, arm64.GetPcAdjustment(0x3, elf_.get()));
-  EXPECT_EQ(0U, arm64.GetPcAdjustment(0x2, elf_.get()));
-  EXPECT_EQ(0U, arm64.GetPcAdjustment(0x1, elf_.get()));
-  EXPECT_EQ(0U, arm64.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(4U, GetPcAdjustment(0x10, elf_.get(), ARCH_ARM64));
+  EXPECT_EQ(4U, GetPcAdjustment(0x4, elf_.get(), ARCH_ARM64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x3, elf_.get(), ARCH_ARM64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x2, elf_.get(), ARCH_ARM64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x1, elf_.get(), ARCH_ARM64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x0, elf_.get(), ARCH_ARM64));
 
-  RegsX86 x86;
-  EXPECT_EQ(1U, x86.GetPcAdjustment(0x100, elf_.get()));
-  EXPECT_EQ(1U, x86.GetPcAdjustment(0x2, elf_.get()));
-  EXPECT_EQ(1U, x86.GetPcAdjustment(0x1, elf_.get()));
-  EXPECT_EQ(0U, x86.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(1U, GetPcAdjustment(0x100, elf_.get(), ARCH_X86));
+  EXPECT_EQ(1U, GetPcAdjustment(0x2, elf_.get(), ARCH_X86));
+  EXPECT_EQ(1U, GetPcAdjustment(0x1, elf_.get(), ARCH_X86));
+  EXPECT_EQ(0U, GetPcAdjustment(0x0, elf_.get(), ARCH_X86));
 
-  RegsX86_64 x86_64;
-  EXPECT_EQ(1U, x86_64.GetPcAdjustment(0x100, elf_.get()));
-  EXPECT_EQ(1U, x86_64.GetPcAdjustment(0x2, elf_.get()));
-  EXPECT_EQ(1U, x86_64.GetPcAdjustment(0x1, elf_.get()));
-  EXPECT_EQ(0U, x86_64.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(1U, GetPcAdjustment(0x100, elf_.get(), ARCH_X86_64));
+  EXPECT_EQ(1U, GetPcAdjustment(0x2, elf_.get(), ARCH_X86_64));
+  EXPECT_EQ(1U, GetPcAdjustment(0x1, elf_.get(), ARCH_X86_64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x0, elf_.get(), ARCH_X86_64));
 
-  RegsMips mips;
-  EXPECT_EQ(8U, mips.GetPcAdjustment(0x10, elf_.get()));
-  EXPECT_EQ(8U, mips.GetPcAdjustment(0x8, elf_.get()));
-  EXPECT_EQ(0U, mips.GetPcAdjustment(0x7, elf_.get()));
-  EXPECT_EQ(0U, mips.GetPcAdjustment(0x6, elf_.get()));
-  EXPECT_EQ(0U, mips.GetPcAdjustment(0x5, elf_.get()));
-  EXPECT_EQ(0U, mips.GetPcAdjustment(0x4, elf_.get()));
-  EXPECT_EQ(0U, mips.GetPcAdjustment(0x3, elf_.get()));
-  EXPECT_EQ(0U, mips.GetPcAdjustment(0x2, elf_.get()));
-  EXPECT_EQ(0U, mips.GetPcAdjustment(0x1, elf_.get()));
-  EXPECT_EQ(0U, mips.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(8U, GetPcAdjustment(0x10, elf_.get(), ARCH_MIPS));
+  EXPECT_EQ(8U, GetPcAdjustment(0x8, elf_.get(), ARCH_MIPS));
+  EXPECT_EQ(0U, GetPcAdjustment(0x7, elf_.get(), ARCH_MIPS));
+  EXPECT_EQ(0U, GetPcAdjustment(0x6, elf_.get(), ARCH_MIPS));
+  EXPECT_EQ(0U, GetPcAdjustment(0x5, elf_.get(), ARCH_MIPS));
+  EXPECT_EQ(0U, GetPcAdjustment(0x4, elf_.get(), ARCH_MIPS));
+  EXPECT_EQ(0U, GetPcAdjustment(0x3, elf_.get(), ARCH_MIPS));
+  EXPECT_EQ(0U, GetPcAdjustment(0x2, elf_.get(), ARCH_MIPS));
+  EXPECT_EQ(0U, GetPcAdjustment(0x1, elf_.get(), ARCH_MIPS));
+  EXPECT_EQ(0U, GetPcAdjustment(0x0, elf_.get(), ARCH_MIPS));
 
-  RegsMips64 mips64;
-  EXPECT_EQ(8U, mips64.GetPcAdjustment(0x10, elf_.get()));
-  EXPECT_EQ(8U, mips64.GetPcAdjustment(0x8, elf_.get()));
-  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x7, elf_.get()));
-  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x6, elf_.get()));
-  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x5, elf_.get()));
-  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x4, elf_.get()));
-  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x3, elf_.get()));
-  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x2, elf_.get()));
-  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x1, elf_.get()));
-  EXPECT_EQ(0U, mips64.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(8U, GetPcAdjustment(0x10, elf_.get(), ARCH_MIPS64));
+  EXPECT_EQ(8U, GetPcAdjustment(0x8, elf_.get(), ARCH_MIPS64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x7, elf_.get(), ARCH_MIPS64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x6, elf_.get(), ARCH_MIPS64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x5, elf_.get(), ARCH_MIPS64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x4, elf_.get(), ARCH_MIPS64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x3, elf_.get(), ARCH_MIPS64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x2, elf_.get(), ARCH_MIPS64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x1, elf_.get(), ARCH_MIPS64));
+  EXPECT_EQ(0U, GetPcAdjustment(0x0, elf_.get(), ARCH_MIPS64));
 }
 
 TEST_F(RegsTest, rel_pc_arm) {
-  RegsArm arm;
-
   // Check fence posts.
   elf_->FakeSetLoadBias(0);
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0x5, elf_.get()));
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0x4, elf_.get()));
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0x3, elf_.get()));
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0x2, elf_.get()));
-  EXPECT_EQ(0U, arm.GetPcAdjustment(0x1, elf_.get()));
-  EXPECT_EQ(0U, arm.GetPcAdjustment(0x0, elf_.get()));
+  EXPECT_EQ(2U, GetPcAdjustment(0x5, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(2U, GetPcAdjustment(0x4, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(2U, GetPcAdjustment(0x3, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(2U, GetPcAdjustment(0x2, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(0U, GetPcAdjustment(0x1, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(0U, GetPcAdjustment(0x0, elf_.get(), ARCH_ARM));
 
   elf_->FakeSetLoadBias(0x100);
-  EXPECT_EQ(0U, arm.GetPcAdjustment(0x1, elf_.get()));
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0x2, elf_.get()));
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0xff, elf_.get()));
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0x105, elf_.get()));
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0x104, elf_.get()));
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0x103, elf_.get()));
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0x102, elf_.get()));
-  EXPECT_EQ(0U, arm.GetPcAdjustment(0x101, elf_.get()));
-  EXPECT_EQ(0U, arm.GetPcAdjustment(0x100, elf_.get()));
+  EXPECT_EQ(0U, GetPcAdjustment(0x1, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(2U, GetPcAdjustment(0x2, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(2U, GetPcAdjustment(0xff, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(2U, GetPcAdjustment(0x105, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(2U, GetPcAdjustment(0x104, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(2U, GetPcAdjustment(0x103, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(2U, GetPcAdjustment(0x102, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(0U, GetPcAdjustment(0x101, elf_.get(), ARCH_ARM));
+  EXPECT_EQ(0U, GetPcAdjustment(0x100, elf_.get(), ARCH_ARM));
 
   // Check thumb instructions handling.
   elf_->FakeSetLoadBias(0);
   memory_->SetData32(0x2000, 0);
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0x2005, elf_.get()));
+  EXPECT_EQ(2U, GetPcAdjustment(0x2005, elf_.get(), ARCH_ARM));
   memory_->SetData32(0x2000, 0xe000f000);
-  EXPECT_EQ(4U, arm.GetPcAdjustment(0x2005, elf_.get()));
+  EXPECT_EQ(4U, GetPcAdjustment(0x2005, elf_.get(), ARCH_ARM));
 
   elf_->FakeSetLoadBias(0x400);
   memory_->SetData32(0x2100, 0);
-  EXPECT_EQ(2U, arm.GetPcAdjustment(0x2505, elf_.get()));
+  EXPECT_EQ(2U, GetPcAdjustment(0x2505, elf_.get(), ARCH_ARM));
   memory_->SetData32(0x2100, 0xf111f111);
-  EXPECT_EQ(4U, arm.GetPcAdjustment(0x2505, elf_.get()));
+  EXPECT_EQ(4U, GetPcAdjustment(0x2505, elf_.get(), ARCH_ARM));
 }
 
 TEST_F(RegsTest, elf_invalid) {
-  RegsArm regs_arm;
-  RegsArm64 regs_arm64;
-  RegsX86 regs_x86;
-  RegsX86_64 regs_x86_64;
-  RegsMips regs_mips;
-  RegsMips64 regs_mips64;
   MapInfo map_info(nullptr, nullptr, 0x1000, 0x2000, 0, 0, "");
   Elf* invalid_elf = new Elf(nullptr);
   map_info.elf.reset(invalid_elf);
 
-  regs_arm.set_pc(0x1500);
-  EXPECT_EQ(0x500U, invalid_elf->GetRelPc(regs_arm.pc(), &map_info));
-  EXPECT_EQ(2U, regs_arm.GetPcAdjustment(0x500U, invalid_elf));
-  EXPECT_EQ(2U, regs_arm.GetPcAdjustment(0x511U, invalid_elf));
+  EXPECT_EQ(0x500U, invalid_elf->GetRelPc(0x1500, &map_info));
+  EXPECT_EQ(2U, GetPcAdjustment(0x500U, invalid_elf, ARCH_ARM));
+  EXPECT_EQ(2U, GetPcAdjustment(0x511U, invalid_elf, ARCH_ARM));
 
-  regs_arm64.set_pc(0x1600);
-  EXPECT_EQ(0x600U, invalid_elf->GetRelPc(regs_arm64.pc(), &map_info));
-  EXPECT_EQ(4U, regs_arm64.GetPcAdjustment(0x600U, invalid_elf));
+  EXPECT_EQ(0x600U, invalid_elf->GetRelPc(0x1600, &map_info));
+  EXPECT_EQ(4U, GetPcAdjustment(0x600U, invalid_elf, ARCH_ARM64));
 
-  regs_x86.set_pc(0x1700);
-  EXPECT_EQ(0x700U, invalid_elf->GetRelPc(regs_x86.pc(), &map_info));
-  EXPECT_EQ(1U, regs_x86.GetPcAdjustment(0x700U, invalid_elf));
+  EXPECT_EQ(0x700U, invalid_elf->GetRelPc(0x1700, &map_info));
+  EXPECT_EQ(1U, GetPcAdjustment(0x700U, invalid_elf, ARCH_X86));
 
-  regs_x86_64.set_pc(0x1800);
-  EXPECT_EQ(0x800U, invalid_elf->GetRelPc(regs_x86_64.pc(), &map_info));
-  EXPECT_EQ(1U, regs_x86_64.GetPcAdjustment(0x800U, invalid_elf));
+  EXPECT_EQ(0x800U, invalid_elf->GetRelPc(0x1800, &map_info));
+  EXPECT_EQ(1U, GetPcAdjustment(0x800U, invalid_elf, ARCH_X86_64));
 
-  regs_mips.set_pc(0x1900);
-  EXPECT_EQ(0x900U, invalid_elf->GetRelPc(regs_mips.pc(), &map_info));
-  EXPECT_EQ(8U, regs_mips.GetPcAdjustment(0x900U, invalid_elf));
+  EXPECT_EQ(0x900U, invalid_elf->GetRelPc(0x1900, &map_info));
+  EXPECT_EQ(8U, GetPcAdjustment(0x900U, invalid_elf, ARCH_MIPS));
 
-  regs_mips64.set_pc(0x1a00);
-  EXPECT_EQ(0xa00U, invalid_elf->GetRelPc(regs_mips64.pc(), &map_info));
-  EXPECT_EQ(8U, regs_mips64.GetPcAdjustment(0xa00U, invalid_elf));
+  EXPECT_EQ(0xa00U, invalid_elf->GetRelPc(0x1a00, &map_info));
+  EXPECT_EQ(8U, GetPcAdjustment(0xa00U, invalid_elf, ARCH_MIPS64));
 }
 
 TEST_F(RegsTest, arm_verify_sp_pc) {
diff --git a/libunwindstack/tests/SymbolsTest.cpp b/libunwindstack/tests/SymbolsTest.cpp
index c58aeff..9afbeec 100644
--- a/libunwindstack/tests/SymbolsTest.cpp
+++ b/libunwindstack/tests/SymbolsTest.cpp
@@ -185,18 +185,21 @@
   std::string fake_name;
 
   this->InitSym(&sym, 0x5000, 0x10, 0x40);
+  this->memory_.SetMemoryBlock(offset, entry_size, 0);
   this->memory_.SetMemory(offset, &sym, sizeof(sym));
   fake_name = "function_one";
   this->memory_.SetMemory(0x2040, fake_name.c_str(), fake_name.size() + 1);
   offset += entry_size;
 
   this->InitSym(&sym, 0x3004, 0x200, 0x100);
+  this->memory_.SetMemoryBlock(offset, entry_size, 0);
   this->memory_.SetMemory(offset, &sym, sizeof(sym));
   fake_name = "function_two";
   this->memory_.SetMemory(0x2100, fake_name.c_str(), fake_name.size() + 1);
   offset += entry_size;
 
   this->InitSym(&sym, 0xa010, 0x20, 0x230);
+  this->memory_.SetMemoryBlock(offset, entry_size, 0);
   this->memory_.SetMemory(offset, &sym, sizeof(sym));
   fake_name = "function_three";
   this->memory_.SetMemory(0x2230, fake_name.c_str(), fake_name.size() + 1);
@@ -274,7 +277,9 @@
   // Do call that should cache all of the entries (except the string data).
   std::string name;
   uint64_t func_offset;
-  ASSERT_FALSE(symbols.GetName<TypeParam>(0x6000, &this->memory_, &name, &func_offset));
+  ASSERT_FALSE(symbols.GetName<TypeParam>(0x5000, &this->memory_, &name, &func_offset));
+  ASSERT_FALSE(symbols.GetName<TypeParam>(0x2000, &this->memory_, &name, &func_offset));
+  ASSERT_FALSE(symbols.GetName<TypeParam>(0x1000, &this->memory_, &name, &func_offset));
   this->memory_.Clear();
   ASSERT_FALSE(symbols.GetName<TypeParam>(0x6000, &this->memory_, &name, &func_offset));
 
diff --git a/libunwindstack/tests/TestUtils.h b/libunwindstack/tests/TestUtils.h
index a4d7b9b..0685006 100644
--- a/libunwindstack/tests/TestUtils.h
+++ b/libunwindstack/tests/TestUtils.h
@@ -21,6 +21,7 @@
 #include <sys/ptrace.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <unistd.h>
 
 namespace unwindstack {
 
@@ -50,6 +51,18 @@
   return ready;
 }
 
+inline bool TestAttach(pid_t pid) {
+  if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) {
+    return false;
+  }
+
+  return TestQuiescePid(pid);
+}
+
+inline bool TestDetach(pid_t pid) {
+  return ptrace(PTRACE_DETACH, pid, 0, 0) == 0;
+}
+
 void TestCheckForLeaks(void (*unwind_func)(void*), void* data);
 
 }  // namespace unwindstack
diff --git a/libunwindstack/tests/UnwinderTest.cpp b/libunwindstack/tests/UnwinderTest.cpp
index ef1950c..dd33aa9 100644
--- a/libunwindstack/tests/UnwinderTest.cpp
+++ b/libunwindstack/tests/UnwinderTest.cpp
@@ -161,8 +161,8 @@
 
   regs_.set_pc(0x1000);
   regs_.set_sp(0x10000);
-  ElfInterfaceFake::FakePushStepData(StepData(0x1102, 0x10010, false));
-  ElfInterfaceFake::FakePushStepData(StepData(0x1202, 0x10020, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x1104, 0x10010, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x1204, 0x10020, false));
   ElfInterfaceFake::FakePushStepData(StepData(0, 0, true));
 
   Unwinder unwinder(64, maps_.get(), &regs_, process_memory_);
@@ -225,8 +225,8 @@
 
   regs_.set_pc(0x1000);
   regs_.set_sp(0x10000);
-  ElfInterfaceFake::FakePushStepData(StepData(0x1102, 0x10010, false));
-  ElfInterfaceFake::FakePushStepData(StepData(0x1202, 0x10020, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x1104, 0x10010, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x1204, 0x10020, false));
   ElfInterfaceFake::FakePushStepData(StepData(0, 0, true));
 
   Unwinder unwinder(64, maps_.get(), &regs_, process_memory_);
@@ -445,7 +445,7 @@
 TEST_F(UnwinderTest, max_frames) {
   for (size_t i = 0; i < 30; i++) {
     ElfInterfaceFake::FakePushFunctionData(FunctionData("Frame" + std::to_string(i), i));
-    ElfInterfaceFake::FakePushStepData(StepData(0x1102 + i * 0x100, 0x10010 + i * 0x10, false));
+    ElfInterfaceFake::FakePushStepData(StepData(0x1104 + i * 0x100, 0x10010 + i * 0x10, false));
   }
 
   regs_.set_pc(0x1000);
@@ -484,12 +484,12 @@
 
   regs_.set_pc(0x20000);
   regs_.set_sp(0x10000);
-  ElfInterfaceFake::FakePushStepData(StepData(0x23002, 0x10010, false));
-  ElfInterfaceFake::FakePushStepData(StepData(0x23102, 0x10020, false));
-  ElfInterfaceFake::FakePushStepData(StepData(0x20002, 0x10030, false));
-  ElfInterfaceFake::FakePushStepData(StepData(0x21002, 0x10040, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x23004, 0x10010, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x23104, 0x10020, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x20004, 0x10030, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x21004, 0x10040, false));
   ElfInterfaceFake::FakePushStepData(StepData(0x1002, 0x10050, false));
-  ElfInterfaceFake::FakePushStepData(StepData(0x21002, 0x10060, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x21004, 0x10060, false));
   ElfInterfaceFake::FakePushStepData(StepData(0x23002, 0x10070, false));
   ElfInterfaceFake::FakePushStepData(StepData(0, 0, true));
 
@@ -553,7 +553,7 @@
 
   regs_.set_pc(0x1000);
   regs_.set_sp(0x63000);
-  ElfInterfaceFake::FakePushStepData(StepData(0x21002, 0x50020, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x21004, 0x50020, false));
   ElfInterfaceFake::FakePushStepData(StepData(0, 0, true));
 
   Unwinder unwinder(64, maps_.get(), &regs_, process_memory_);
@@ -670,10 +670,10 @@
   // Fake as if code called a nullptr function.
   regs_.set_pc(0);
   regs_.set_sp(0x10000);
-  regs_.FakeSetReturnAddress(0x1202);
+  regs_.FakeSetReturnAddress(0x1204);
   regs_.FakeSetReturnAddressValid(true);
 
-  ElfInterfaceFake::FakePushStepData(StepData(0x23102, 0x10020, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x23104, 0x10020, false));
   ElfInterfaceFake::FakePushStepData(StepData(0, 0, true));
 
   Unwinder unwinder(64, maps_.get(), &regs_, process_memory_);
@@ -789,7 +789,7 @@
   // Fake as if code called a nullptr function.
   regs_.set_pc(0);
   regs_.set_sp(0x10000);
-  regs_.FakeSetReturnAddress(0x1202);
+  regs_.FakeSetReturnAddress(0x1204);
   regs_.FakeSetReturnAddressValid(true);
 
   Unwinder unwinder(64, maps_.get(), &regs_, process_memory_);
@@ -858,8 +858,8 @@
   // Fake as if code called a nullptr function.
   regs_.set_pc(0x1000);
   regs_.set_sp(0x10000);
-  ElfInterfaceFake::FakePushStepData(StepData(0x43402, 0x10010, false));
-  ElfInterfaceFake::FakePushStepData(StepData(0x53502, 0x10020, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x43404, 0x10010, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x53504, 0x10020, false));
   ElfInterfaceFake::FakePushStepData(StepData(0, 0, true));
 
   Unwinder unwinder(64, maps_.get(), &regs_, process_memory_);
@@ -915,11 +915,11 @@
 
   regs_.set_pc(0x1000);
   regs_.set_sp(0x10000);
-  ElfInterfaceFake::FakePushStepData(StepData(0x33402, 0x10010, false));
-  ElfInterfaceFake::FakePushStepData(StepData(0x33502, 0x10020, false));
-  ElfInterfaceFake::FakePushStepData(StepData(0x33502, 0x10020, false));
-  ElfInterfaceFake::FakePushStepData(StepData(0x33502, 0x10020, false));
-  ElfInterfaceFake::FakePushStepData(StepData(0x33502, 0x10020, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x33404, 0x10010, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x33504, 0x10020, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x33504, 0x10020, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x33504, 0x10020, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x33504, 0x10020, false));
   ElfInterfaceFake::FakePushStepData(StepData(0, 0, true));
 
   Unwinder unwinder(64, maps_.get(), &regs_, process_memory_);
@@ -1113,7 +1113,7 @@
   regs_.set_pc(0x1000);
   regs_.set_sp(0x10000);
   regs_.FakeSetDexPc(0xa3400);
-  ElfInterfaceFake::FakePushStepData(StepData(0x33402, 0x10010, false));
+  ElfInterfaceFake::FakePushStepData(StepData(0x33404, 0x10010, false));
   ElfInterfaceFake::FakePushStepData(StepData(0, 0, true));
 
   Unwinder unwinder(64, maps_.get(), &regs_, process_memory_);
diff --git a/libziparchive/Android.bp b/libziparchive/Android.bp
index 3d4e86e..786e7b3 100644
--- a/libziparchive/Android.bp
+++ b/libziparchive/Android.bp
@@ -61,6 +61,7 @@
         "zip_archive.cc",
         "zip_archive_stream_entry.cc",
         "zip_cd_entry_map.cc",
+        "zip_error.cpp",
         "zip_writer.cc",
     ],
 
@@ -211,3 +212,20 @@
     data: ["cli-tests/**/*"],
     target_required: ["cli-test", "ziptool"],
 }
+
+python_test_host {
+    name: "ziparchive_tests_large",
+    srcs: ["test_ziparchive_large.py"],
+    main: "test_ziparchive_large.py",
+    version: {
+        py2: {
+            enabled: true,
+            embedded_launcher: false,
+        },
+        py3: {
+            enabled: false,
+            embedded_launcher: false,
+        },
+    },
+    test_suites: ["general-tests"],
+}
diff --git a/libziparchive/include/ziparchive/zip_archive.h b/libziparchive/include/ziparchive/zip_archive.h
index 047af90..005d697 100644
--- a/libziparchive/include/ziparchive/zip_archive.h
+++ b/libziparchive/include/ziparchive/zip_archive.h
@@ -25,6 +25,7 @@
 #include <sys/cdefs.h>
 #include <sys/types.h>
 
+#include <functional>
 #include <string>
 #include <string_view>
 
@@ -36,10 +37,10 @@
   kCompressDeflated = 8,  // standard deflate
 };
 
-/*
- * Represents information about a zip entry in a zip file.
- */
-struct ZipEntry {
+// This struct holds the common information of a zip entry other than the
+// the entry size. The compressed and uncompressed length will be handled
+// separately in the derived class.
+struct ZipEntryCommon {
   // Compression method. One of kCompressStored or kCompressDeflated.
   // See also `gpbf` for deflate subtypes.
   uint16_t method;
@@ -47,9 +48,10 @@
   // Modification time. The zipfile format specifies
   // that the first two little endian bytes contain the time
   // and the last two little endian bytes contain the date.
-  // See `GetModificationTime`.
+  // See `GetModificationTime`. Use signed integer to avoid the
+  // sub-overflow.
   // TODO: should be overridden by extra time field, if present.
-  uint32_t mod_time;
+  int32_t mod_time;
 
   // Returns `mod_time` as a broken-down struct tm.
   struct tm GetModificationTime() const;
@@ -67,15 +69,9 @@
   // Data descriptor footer at the end of the file entry.
   uint32_t crc32;
 
-  // Compressed length of this ZipEntry. Might be present
-  // either in the local file header or in the data descriptor
-  // footer.
-  uint32_t compressed_length;
-
-  // Uncompressed length of this ZipEntry. Might be present
-  // either in the local file header or in the data descriptor
-  // footer.
-  uint32_t uncompressed_length;
+  // If the value of uncompressed length and compressed length are stored in
+  // the zip64 extended info of the extra field.
+  bool zip64_format_size{false};
 
   // The offset to the start of data for this ZipEntry.
   off64_t offset;
@@ -93,6 +89,52 @@
   bool is_text;
 };
 
+struct ZipEntry64;
+// Many users of the library assume the entry size is capped at UNIT32_MAX. So we keep
+// the interface for the old ZipEntry here; and we could switch them over to the new
+// ZipEntry64 later.
+struct ZipEntry : public ZipEntryCommon {
+  // Compressed length of this ZipEntry. The maximum value is UNIT32_MAX.
+  // Might be present either in the local file header or in the data
+  // descriptor footer.
+  uint32_t compressed_length{0};
+
+  // Uncompressed length of this ZipEntry. The maximum value is UNIT32_MAX.
+  // Might be present either in the local file header or in the data
+  // descriptor footer.
+  uint32_t uncompressed_length{0};
+
+  // Copies the contents of a ZipEntry64 object to a 32 bits ZipEntry. Returns 0 if the
+  // size of the entry fits into uint32_t, returns a negative error code
+  // (kUnsupportedEntrySize) otherwise.
+  static int32_t CopyFromZipEntry64(ZipEntry* dst, const ZipEntry64* src);
+
+ private:
+  ZipEntry& operator=(const ZipEntryCommon& other) {
+    ZipEntryCommon::operator=(other);
+    return *this;
+  }
+};
+
+// Represents information about a zip entry in a zip file.
+struct ZipEntry64 : public ZipEntryCommon {
+  // Compressed length of this ZipEntry. The maximum value is UNIT64_MAX.
+  // Might be present either in the local file header, the zip64 extended field,
+  // or in the data descriptor footer.
+  uint64_t compressed_length{0};
+
+  // Uncompressed length of this ZipEntry. The maximum value is UNIT64_MAX.
+  // Might be present either in the local file header, the zip64 extended field,
+  // or in the data descriptor footer.
+  uint64_t uncompressed_length{0};
+
+  explicit ZipEntry64() = default;
+  explicit ZipEntry64(const ZipEntry& zip_entry) : ZipEntryCommon(zip_entry) {
+    compressed_length = zip_entry.compressed_length;
+    uncompressed_length = zip_entry.uncompressed_length;
+  }
+};
+
 struct ZipArchive;
 typedef ZipArchive* ZipArchiveHandle;
 
@@ -126,6 +168,9 @@
 int32_t OpenArchiveFd(const int fd, const char* debugFileName, ZipArchiveHandle* handle,
                       bool assume_ownership = true);
 
+int32_t OpenArchiveFdRange(const int fd, const char* debugFileName, ZipArchiveHandle* handle,
+                           off64_t length, off64_t offset, bool assume_ownership = true);
+
 int32_t OpenArchiveFromMemory(const void* address, size_t length, const char* debugFileName,
                               ZipArchiveHandle* handle);
 /*
@@ -142,7 +187,7 @@
   /** The size in bytes of the archive itself. Used by zipinfo. */
   off64_t archive_size;
   /** The number of entries in the archive. */
-  size_t entry_count;
+  uint64_t entry_count;
 };
 
 /**
@@ -165,7 +210,8 @@
  * On non-Windows platforms this method does not modify internal state and
  * can be called concurrently.
  */
-int32_t FindEntry(const ZipArchiveHandle archive, const std::string_view entryName, ZipEntry* data);
+int32_t FindEntry(const ZipArchiveHandle archive, const std::string_view entryName,
+                  ZipEntry64* data);
 
 /*
  * Start iterating over all entries of a zip file. The order of iteration
@@ -185,13 +231,22 @@
                        const std::string_view optional_suffix = "");
 
 /*
+ * Start iterating over all entries of a zip file. Use the matcher functor to
+ * restrict iteration to entry names that make the functor return true.
+ *
+ * Returns 0 on success and negative values on failure.
+ */
+int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr,
+                       std::function<bool(std::string_view entry_name)> matcher);
+
+/*
  * Advance to the next element in the zipfile in iteration order.
  *
  * Returns 0 on success, -1 if there are no more elements in this
  * archive and lower negative values on failure.
  */
-int32_t Next(void* cookie, ZipEntry* data, std::string* name);
-int32_t Next(void* cookie, ZipEntry* data, std::string_view* name);
+int32_t Next(void* cookie, ZipEntry64* data, std::string_view* name);
+int32_t Next(void* cookie, ZipEntry64* data, std::string* name);
 
 /*
  * End iteration over all entries of a zip file and frees the memory allocated
@@ -208,7 +263,7 @@
  *
  * Returns 0 on success and negative values on failure.
  */
-int32_t ExtractEntryToFile(ZipArchiveHandle archive, ZipEntry* entry, int fd);
+int32_t ExtractEntryToFile(ZipArchiveHandle archive, const ZipEntry64* entry, int fd);
 
 /**
  * Uncompress a given zip entry to the memory region at |begin| and of
@@ -218,12 +273,29 @@
  *
  * Returns 0 on success and negative values on failure.
  */
-int32_t ExtractToMemory(ZipArchiveHandle archive, ZipEntry* entry, uint8_t* begin, uint32_t size);
+int32_t ExtractToMemory(ZipArchiveHandle archive, const ZipEntry64* entry, uint8_t* begin,
+                        size_t size);
 
 int GetFileDescriptor(const ZipArchiveHandle archive);
 
+/**
+ * Returns the offset of the zip archive in the backing file descriptor, or 0 if the zip archive is
+ * not backed by a file descriptor.
+ */
+off64_t GetFileDescriptorOffset(const ZipArchiveHandle archive);
+
 const char* ErrorCodeString(int32_t error_code);
 
+// Many users of libziparchive assume the entry size to be 32 bits long. So we keep these
+// interfaces that use 32 bit ZipEntry to make old code work. TODO(xunchang) Remove the 32 bit
+// wrapper functions once we switch all users to recognize ZipEntry64.
+int32_t FindEntry(const ZipArchiveHandle archive, const std::string_view entryName, ZipEntry* data);
+int32_t Next(void* cookie, ZipEntry* data, std::string* name);
+int32_t Next(void* cookie, ZipEntry* data, std::string_view* name);
+int32_t ExtractEntryToFile(ZipArchiveHandle archive, const ZipEntry* entry, int fd);
+int32_t ExtractToMemory(ZipArchiveHandle archive, const ZipEntry* entry, uint8_t* begin,
+                        size_t size);
+
 #if !defined(_WIN32)
 typedef bool (*ProcessZipEntryFunction)(const uint8_t* buf, size_t buf_size, void* cookie);
 
@@ -231,7 +303,9 @@
  * Stream the uncompressed data through the supplied function,
  * passing cookie to it each time it gets called.
  */
-int32_t ProcessZipEntryContents(ZipArchiveHandle archive, ZipEntry* entry,
+int32_t ProcessZipEntryContents(ZipArchiveHandle archive, const ZipEntry* entry,
+                                ProcessZipEntryFunction func, void* cookie);
+int32_t ProcessZipEntryContents(ZipArchiveHandle archive, const ZipEntry64* entry,
                                 ProcessZipEntryFunction func, void* cookie);
 #endif
 
@@ -252,7 +326,7 @@
 
 class Reader {
  public:
-  virtual bool ReadAtOffset(uint8_t* buf, size_t len, uint32_t offset) const = 0;
+  virtual bool ReadAtOffset(uint8_t* buf, size_t len, off64_t offset) const = 0;
   virtual ~Reader();
 
  protected:
@@ -274,6 +348,6 @@
  * If |crc_out| is not nullptr, it is set to the crc32 checksum of the
  * uncompressed data.
  */
-int32_t Inflate(const Reader& reader, const uint32_t compressed_length,
-                const uint32_t uncompressed_length, Writer* writer, uint64_t* crc_out);
+int32_t Inflate(const Reader& reader, const uint64_t compressed_length,
+                const uint64_t uncompressed_length, Writer* writer, uint64_t* crc_out);
 }  // namespace zip_archive
diff --git a/libziparchive/test_ziparchive_large.py b/libziparchive/test_ziparchive_large.py
new file mode 100644
index 0000000..46d02aa
--- /dev/null
+++ b/libziparchive/test_ziparchive_large.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+"""Unittests for parsing files in zip64 format"""
+
+import os
+import subprocess
+import tempfile
+import unittest
+import zipfile
+import time
+
+class Zip64Test(unittest.TestCase):
+  @staticmethod
+  def _WriteFile(path, size_in_kib):
+    contents = os.path.basename(path)[0] * 1024
+    with open(path, 'w') as f:
+      for it in range(0, size_in_kib):
+        f.write(contents)
+
+  @staticmethod
+  def _AddEntriesToZip(output_zip, entries_dict=None):
+    for name, size in entries_dict.items():
+      file_path = tempfile.NamedTemporaryFile()
+      Zip64Test._WriteFile(file_path.name, size)
+      output_zip.write(file_path.name, arcname = name)
+
+  def _getEntryNames(self, zip_name):
+    cmd = ['ziptool', 'zipinfo', '-1', zip_name]
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    output, _ = proc.communicate()
+    self.assertEquals(0, proc.returncode)
+    self.assertNotEqual(None, output)
+    return output.split()
+
+  def _ExtractEntries(self, zip_name):
+    temp_dir = tempfile.mkdtemp()
+    cmd = ['ziptool', 'unzip', '-d', temp_dir, zip_name]
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    proc.communicate()
+    self.assertEquals(0, proc.returncode)
+
+  def test_entriesSmallerThan2G(self):
+    zip_path = tempfile.NamedTemporaryFile(suffix='.zip')
+    # Add a few entries with each of them smaller than 2GiB. But the entire zip file is larger
+    # than 4GiB in size.
+    with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip:
+      entry_dict = {'a.txt': 1025 * 1024, 'b.txt': 1025 * 1024, 'c.txt': 1025 * 1024,
+                    'd.txt': 1025 * 1024, 'e.txt': 1024}
+      self._AddEntriesToZip(output_zip, entry_dict)
+
+    read_names = self._getEntryNames(zip_path.name)
+    self.assertEquals(sorted(entry_dict.keys()), sorted(read_names))
+    self._ExtractEntries(zip_path.name)
+
+
+  def test_largeNumberOfEntries(self):
+    zip_path = tempfile.NamedTemporaryFile(suffix='.zip')
+    entry_dict = {}
+    # Add 100k entries (more than 65535|UINT16_MAX).
+    for num in range(0, 100 * 1024):
+      entry_dict[str(num)] = 50
+
+    with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip:
+      self._AddEntriesToZip(output_zip, entry_dict)
+
+    read_names = self._getEntryNames(zip_path.name)
+    self.assertEquals(sorted(entry_dict.keys()), sorted(read_names))
+    self._ExtractEntries(zip_path.name)
+
+
+  def test_largeCompressedEntriesSmallerThan4G(self):
+    zip_path = tempfile.NamedTemporaryFile(suffix='.zip')
+    with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED,
+                         allowZip64=True) as output_zip:
+      # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed
+      # sizes in the extra field. Test if our ziptool should be able to parse it.
+      entry_dict = {'e.txt': 4095 * 1024, 'f.txt': 4095 * 1024}
+      self._AddEntriesToZip(output_zip, entry_dict)
+
+    read_names = self._getEntryNames(zip_path.name)
+    self.assertEquals(sorted(entry_dict.keys()), sorted(read_names))
+    self._ExtractEntries(zip_path.name)
+
+
+  def test_forceDataDescriptor(self):
+    file_path = tempfile.NamedTemporaryFile(suffix='.txt')
+    self._WriteFile(file_path.name, 5000 * 1024)
+
+    zip_path = tempfile.NamedTemporaryFile(suffix='.zip')
+    with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip:
+      pass
+    # The fd option force writes a data descriptor
+    cmd = ['zip', '-fd', zip_path.name, file_path.name]
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    proc.communicate()
+    read_names = self._getEntryNames(zip_path.name)
+    self.assertEquals([file_path.name[1:]], read_names)
+    self._ExtractEntries(zip_path.name)
+
+
+  def test_largeUncompressedEntriesLargerThan4G(self):
+    zip_path = tempfile.NamedTemporaryFile(suffix='.zip')
+    with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_STORED,
+                         allowZip64=True) as output_zip:
+      # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed
+      # sizes in the extra field. Test if our ziptool should be able to parse it.
+      entry_dict = {'g.txt': 5000 * 1024, 'h.txt': 6000 * 1024}
+      self._AddEntriesToZip(output_zip, entry_dict)
+
+    read_names = self._getEntryNames(zip_path.name)
+    self.assertEquals(sorted(entry_dict.keys()), sorted(read_names))
+    self._ExtractEntries(zip_path.name)
+
+
+  def test_largeCompressedEntriesLargerThan4G(self):
+    zip_path = tempfile.NamedTemporaryFile(suffix='.zip')
+    with zipfile.ZipFile(zip_path, 'w', compression=zipfile.ZIP_DEFLATED,
+                         allowZip64=True) as output_zip:
+      # Add entries close to 4GiB in size. Somehow the python library will put the (un)compressed
+      # sizes in the extra field. Test if our ziptool should be able to parse it.
+      entry_dict = {'i.txt': 4096 * 1024, 'j.txt': 7000 * 1024}
+      self._AddEntriesToZip(output_zip, entry_dict)
+
+    read_names = self._getEntryNames(zip_path.name)
+    self.assertEquals(sorted(entry_dict.keys()), sorted(read_names))
+    self._ExtractEntries(zip_path.name)
+
+
+if __name__ == '__main__':
+  testsuite = unittest.TestLoader().discover(
+      os.path.dirname(os.path.realpath(__file__)))
+  unittest.TextTestRunner(verbosity=2).run(testsuite)
diff --git a/libziparchive/testdata/zip64.zip b/libziparchive/testdata/zip64.zip
new file mode 100644
index 0000000..3f25a4c
--- /dev/null
+++ b/libziparchive/testdata/zip64.zip
Binary files differ
diff --git a/libziparchive/zip_archive.cc b/libziparchive/zip_archive.cc
index 2648c59..014f881 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -32,6 +32,7 @@
 #include <unistd.h>
 
 #include <memory>
+#include <optional>
 #include <vector>
 
 #if defined(__APPLE__)
@@ -56,15 +57,17 @@
 #include "zip_archive_common.h"
 #include "zip_archive_private.h"
 
-using android::base::get_unaligned;
-
 // Used to turn on crc checks - verify that the content CRC matches the values
 // specified in the local file header and the central directory.
-static const bool kCrcChecksEnabled = false;
+static constexpr bool kCrcChecksEnabled = false;
 
 // The maximum number of bytes to scan backwards for the EOCD start.
 static const uint32_t kMaxEOCDSearch = kMaxCommentLen + sizeof(EocdRecord);
 
+// Set a reasonable cap (256 GiB) for the zip file size. So the data is always valid when
+// we parse the fields in cd or local headers as 64 bits signed integers.
+static constexpr uint64_t kMaxFileLength = 256 * static_cast<uint64_t>(1u << 30u);
+
 /*
  * A Read-only Zip archive.
  *
@@ -92,8 +95,8 @@
 }
 #endif
 
-ZipArchive::ZipArchive(const int fd, bool assume_ownership)
-    : mapped_zip(fd),
+ZipArchive::ZipArchive(MappedZipFile&& map, bool assume_ownership)
+    : mapped_zip(map),
       close_file(assume_ownership),
       directory_offset(0),
       central_directory(),
@@ -101,7 +104,8 @@
       num_entries(0) {
 #if defined(__BIONIC__)
   if (assume_ownership) {
-    android_fdsan_exchange_owner_tag(fd, 0, GetOwnerTag(this));
+    CHECK(mapped_zip.HasFd());
+    android_fdsan_exchange_owner_tag(mapped_zip.GetFileDescriptor(), 0, GetOwnerTag(this));
   }
 #endif
 }
@@ -124,12 +128,94 @@
   }
 }
 
-static int32_t MapCentralDirectory0(const char* debug_file_name, ZipArchive* archive,
-                                    off64_t file_length, uint32_t read_amount,
-                                    uint8_t* scan_buffer) {
+struct CentralDirectoryInfo {
+  uint64_t num_records;
+  // The size of the central directory (in bytes).
+  uint64_t cd_size;
+  // The offset of the start of the central directory, relative
+  // to the start of the file.
+  uint64_t cd_start_offset;
+};
+
+// Reads |T| at |readPtr| and increments |readPtr|. Returns std::nullopt if the boundary check
+// fails.
+template <typename T>
+static std::optional<T> TryConsumeUnaligned(uint8_t** readPtr, const uint8_t* bufStart,
+                                            size_t bufSize) {
+  if (bufSize < sizeof(T) || *readPtr - bufStart > bufSize - sizeof(T)) {
+    ALOGW("Zip: %zu byte read exceeds the boundary of allocated buf, offset %zu, bufSize %zu",
+          sizeof(T), *readPtr - bufStart, bufSize);
+    return std::nullopt;
+  }
+  return ConsumeUnaligned<T>(readPtr);
+}
+
+static ZipError FindCentralDirectoryInfoForZip64(const char* debugFileName, ZipArchive* archive,
+                                                 off64_t eocdOffset, CentralDirectoryInfo* cdInfo) {
+  if (eocdOffset <= sizeof(Zip64EocdLocator)) {
+    ALOGW("Zip: %s: Not enough space for zip64 eocd locator", debugFileName);
+    return kInvalidFile;
+  }
+  // We expect to find the zip64 eocd locator immediately before the zip eocd.
+  const int64_t locatorOffset = eocdOffset - sizeof(Zip64EocdLocator);
+  Zip64EocdLocator zip64EocdLocator{};
+  if (!archive->mapped_zip.ReadAtOffset(reinterpret_cast<uint8_t*>((&zip64EocdLocator)),
+                                        sizeof(Zip64EocdLocator), locatorOffset)) {
+    ALOGW("Zip: %s: Read %zu from offset %" PRId64 " failed %s", debugFileName,
+          sizeof(Zip64EocdLocator), locatorOffset, debugFileName);
+    return kIoError;
+  }
+
+  if (zip64EocdLocator.locator_signature != Zip64EocdLocator::kSignature) {
+    ALOGW("Zip: %s: Zip64 eocd locator signature not found at offset %" PRId64, debugFileName,
+          locatorOffset);
+    return kInvalidFile;
+  }
+
+  const int64_t zip64EocdOffset = zip64EocdLocator.zip64_eocd_offset;
+  if (locatorOffset <= sizeof(Zip64EocdRecord) ||
+      zip64EocdOffset > locatorOffset - sizeof(Zip64EocdRecord)) {
+    ALOGW("Zip: %s: Bad zip64 eocd offset %" PRId64 ", eocd locator offset %" PRId64, debugFileName,
+          zip64EocdOffset, locatorOffset);
+    return kInvalidOffset;
+  }
+
+  Zip64EocdRecord zip64EocdRecord{};
+  if (!archive->mapped_zip.ReadAtOffset(reinterpret_cast<uint8_t*>(&zip64EocdRecord),
+                                        sizeof(Zip64EocdRecord), zip64EocdOffset)) {
+    ALOGW("Zip: %s: read %zu from offset %" PRId64 " failed %s", debugFileName,
+          sizeof(Zip64EocdLocator), zip64EocdOffset, debugFileName);
+    return kIoError;
+  }
+
+  if (zip64EocdRecord.record_signature != Zip64EocdRecord::kSignature) {
+    ALOGW("Zip: %s: Zip64 eocd record signature not found at offset %" PRId64, debugFileName,
+          zip64EocdOffset);
+    return kInvalidFile;
+  }
+
+  if (zip64EocdOffset <= zip64EocdRecord.cd_size ||
+      zip64EocdRecord.cd_start_offset > zip64EocdOffset - zip64EocdRecord.cd_size) {
+    ALOGW("Zip: %s: Bad offset for zip64 central directory. cd offset %" PRIu64 ", cd size %" PRIu64
+          ", zip64 eocd offset %" PRIu64,
+          debugFileName, zip64EocdRecord.cd_start_offset, zip64EocdRecord.cd_size, zip64EocdOffset);
+    return kInvalidOffset;
+  }
+
+  *cdInfo = {.num_records = zip64EocdRecord.num_records,
+             .cd_size = zip64EocdRecord.cd_size,
+             .cd_start_offset = zip64EocdRecord.cd_start_offset};
+
+  return kSuccess;
+}
+
+static ZipError FindCentralDirectoryInfo(const char* debug_file_name, ZipArchive* archive,
+                                         off64_t file_length, uint32_t read_amount,
+                                         CentralDirectoryInfo* cdInfo) {
+  std::vector<uint8_t> scan_buffer(read_amount);
   const off64_t search_start = file_length - read_amount;
 
-  if (!archive->mapped_zip.ReadAtOffset(scan_buffer, read_amount, search_start)) {
+  if (!archive->mapped_zip.ReadAtOffset(scan_buffer.data(), read_amount, search_start)) {
     ALOGE("Zip: read %" PRId64 " from offset %" PRId64 " failed", static_cast<int64_t>(read_amount),
           static_cast<int64_t>(search_start));
     return kIoError;
@@ -146,7 +232,7 @@
   for (; i >= 0; i--) {
     if (scan_buffer[i] == 0x50) {
       uint32_t* sig_addr = reinterpret_cast<uint32_t*>(&scan_buffer[i]);
-      if (get_unaligned<uint32_t>(sig_addr) == EocdRecord::kSignature) {
+      if (android::base::get_unaligned<uint32_t>(sig_addr) == EocdRecord::kSignature) {
         ALOGV("+++ Found EOCD at buf+%d", i);
         break;
       }
@@ -158,7 +244,7 @@
   }
 
   const off64_t eocd_offset = search_start + i;
-  const EocdRecord* eocd = reinterpret_cast<const EocdRecord*>(scan_buffer + i);
+  auto eocd = reinterpret_cast<const EocdRecord*>(scan_buffer.data() + i);
   /*
    * Verify that there's no trailing space at the end of the central directory
    * and its comment.
@@ -170,6 +256,13 @@
     return kInvalidFile;
   }
 
+  // One of the field is 0xFFFFFFFF, look for the zip64 EOCD instead.
+  if (eocd->cd_size == UINT32_MAX || eocd->cd_start_offset == UINT32_MAX) {
+    ALOGV("Looking for the zip64 EOCD, cd_size: %" PRIu32 "cd_start_offset: %" PRId32,
+          eocd->cd_size, eocd->cd_start_offset);
+    return FindCentralDirectoryInfoForZip64(debug_file_name, archive, eocd_offset, cdInfo);
+  }
+
   /*
    * Grab the CD offset and size, and the number of entries in the
    * archive and verify that they look reasonable.
@@ -179,47 +272,29 @@
           eocd->cd_start_offset, eocd->cd_size, static_cast<int64_t>(eocd_offset));
     return kInvalidOffset;
   }
-  if (eocd->num_records == 0) {
-#if defined(__ANDROID__)
-    ALOGW("Zip: empty archive?");
-#endif
-    return kEmptyArchive;
-  }
 
-  ALOGV("+++ num_entries=%" PRIu32 " dir_size=%" PRIu32 " dir_offset=%" PRIu32, eocd->num_records,
-        eocd->cd_size, eocd->cd_start_offset);
-
-  // It all looks good.  Create a mapping for the CD, and set the fields
-  // in archive.
-  if (!archive->InitializeCentralDirectory(static_cast<off64_t>(eocd->cd_start_offset),
-                                           static_cast<size_t>(eocd->cd_size))) {
-    return kMmapFailed;
-  }
-
-  archive->num_entries = eocd->num_records;
-  archive->directory_offset = eocd->cd_start_offset;
-
-  return 0;
+  *cdInfo = {.num_records = eocd->num_records,
+             .cd_size = eocd->cd_size,
+             .cd_start_offset = eocd->cd_start_offset};
+  return kSuccess;
 }
 
 /*
  * Find the zip Central Directory and memory-map it.
  *
- * On success, returns 0 after populating fields from the EOCD area:
+ * On success, returns kSuccess after populating fields from the EOCD area:
  *   directory_offset
  *   directory_ptr
  *   num_entries
  */
-static int32_t MapCentralDirectory(const char* debug_file_name, ZipArchive* archive) {
-  // Test file length. We use lseek64 to make sure the file
-  // is small enough to be a zip file (Its size must be less than
-  // 0xffffffff bytes).
+static ZipError MapCentralDirectory(const char* debug_file_name, ZipArchive* archive) {
+  // Test file length. We use lseek64 to make sure the file is small enough to be a zip file.
   off64_t file_length = archive->mapped_zip.GetFileLength();
   if (file_length == -1) {
     return kInvalidFile;
   }
 
-  if (file_length > static_cast<off64_t>(0xffffffff)) {
+  if (file_length > kMaxFileLength) {
     ALOGV("Zip: zip file too long %" PRId64, static_cast<int64_t>(file_length));
     return kInvalidFile;
   }
@@ -246,10 +321,118 @@
     read_amount = static_cast<uint32_t>(file_length);
   }
 
-  std::vector<uint8_t> scan_buffer(read_amount);
-  int32_t result =
-      MapCentralDirectory0(debug_file_name, archive, file_length, read_amount, scan_buffer.data());
-  return result;
+  CentralDirectoryInfo cdInfo = {};
+  if (auto result =
+          FindCentralDirectoryInfo(debug_file_name, archive, file_length, read_amount, &cdInfo);
+      result != kSuccess) {
+    return result;
+  }
+
+  if (cdInfo.num_records == 0) {
+#if defined(__ANDROID__)
+    ALOGW("Zip: empty archive?");
+#endif
+    return kEmptyArchive;
+  }
+
+  if (cdInfo.cd_size >= SIZE_MAX) {
+    ALOGW("Zip: The size of central directory doesn't fit in range of size_t: %" PRIu64,
+          cdInfo.cd_size);
+    return kInvalidFile;
+  }
+
+  ALOGV("+++ num_entries=%" PRIu64 " dir_size=%" PRIu64 " dir_offset=%" PRIu64, cdInfo.num_records,
+        cdInfo.cd_size, cdInfo.cd_start_offset);
+
+  // It all looks good.  Create a mapping for the CD, and set the fields in archive.
+  if (!archive->InitializeCentralDirectory(static_cast<off64_t>(cdInfo.cd_start_offset),
+                                           static_cast<size_t>(cdInfo.cd_size))) {
+    return kMmapFailed;
+  }
+
+  archive->num_entries = cdInfo.num_records;
+  archive->directory_offset = cdInfo.cd_start_offset;
+
+  return kSuccess;
+}
+
+static ZipError ParseZip64ExtendedInfoInExtraField(
+    const uint8_t* extraFieldStart, uint16_t extraFieldLength, uint32_t zip32UncompressedSize,
+    uint32_t zip32CompressedSize, std::optional<uint32_t> zip32LocalFileHeaderOffset,
+    Zip64ExtendedInfo* zip64Info) {
+  if (extraFieldLength <= 4) {
+    ALOGW("Zip: Extra field isn't large enough to hold zip64 info, size %" PRIu16,
+          extraFieldLength);
+    return kInvalidFile;
+  }
+
+  // Each header MUST consist of:
+  // Header ID - 2 bytes
+  // Data Size - 2 bytes
+  uint16_t offset = 0;
+  while (offset < extraFieldLength - 4) {
+    auto readPtr = const_cast<uint8_t*>(extraFieldStart + offset);
+    auto headerId = ConsumeUnaligned<uint16_t>(&readPtr);
+    auto dataSize = ConsumeUnaligned<uint16_t>(&readPtr);
+
+    offset += 4;
+    if (dataSize > extraFieldLength - offset) {
+      ALOGW("Zip: Data size exceeds the boundary of extra field, data size %" PRIu16, dataSize);
+      return kInvalidOffset;
+    }
+
+    // Skip the other types of extensible data fields. Details in
+    // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT section 4.5
+    if (headerId != Zip64ExtendedInfo::kHeaderId) {
+      offset += dataSize;
+      continue;
+    }
+
+    std::optional<uint64_t> uncompressedFileSize;
+    std::optional<uint64_t> compressedFileSize;
+    std::optional<uint64_t> localHeaderOffset;
+    if (zip32UncompressedSize == UINT32_MAX) {
+      uncompressedFileSize =
+          TryConsumeUnaligned<uint64_t>(&readPtr, extraFieldStart, extraFieldLength);
+      if (!uncompressedFileSize.has_value()) return kInvalidOffset;
+    }
+    if (zip32CompressedSize == UINT32_MAX) {
+      compressedFileSize =
+          TryConsumeUnaligned<uint64_t>(&readPtr, extraFieldStart, extraFieldLength);
+      if (!compressedFileSize.has_value()) return kInvalidOffset;
+    }
+    if (zip32LocalFileHeaderOffset == UINT32_MAX) {
+      localHeaderOffset =
+          TryConsumeUnaligned<uint64_t>(&readPtr, extraFieldStart, extraFieldLength);
+      if (!localHeaderOffset.has_value()) return kInvalidOffset;
+    }
+
+    // calculate how many bytes we read after the data size field.
+    size_t bytesRead = readPtr - (extraFieldStart + offset);
+    if (bytesRead == 0) {
+      ALOGW("Zip: Data size should not be 0 in zip64 extended field");
+      return kInvalidFile;
+    }
+
+    if (dataSize != bytesRead) {
+      auto localOffsetString = zip32LocalFileHeaderOffset.has_value()
+                                   ? std::to_string(zip32LocalFileHeaderOffset.value())
+                                   : "missing";
+      ALOGW("Zip: Invalid data size in zip64 extended field, expect %zu , get %" PRIu16
+            ", uncompressed size %" PRIu32 ", compressed size %" PRIu32 ", local header offset %s",
+            bytesRead, dataSize, zip32UncompressedSize, zip32CompressedSize,
+            localOffsetString.c_str());
+      return kInvalidFile;
+    }
+
+    zip64Info->uncompressed_file_size = uncompressedFileSize;
+    zip64Info->compressed_file_size = compressedFileSize;
+    zip64Info->local_header_offset = localHeaderOffset;
+    return kSuccess;
+  }
+
+  ALOGW("Zip: zip64 extended info isn't found in the extra field.");
+  return kInvalidFile;
 }
 
 /*
@@ -258,16 +441,15 @@
  *
  * Returns 0 on success.
  */
-static int32_t ParseZipArchive(ZipArchive* archive) {
+static ZipError ParseZipArchive(ZipArchive* archive) {
   const uint8_t* const cd_ptr = archive->central_directory.GetBasePtr();
   const size_t cd_length = archive->central_directory.GetMapLength();
-  const uint16_t num_entries = archive->num_entries;
+  const uint64_t num_entries = archive->num_entries;
 
-  // TODO(xunchang) parse the zip64 Eocd
-  if (num_entries > UINT16_MAX) {
-    archive->cd_entry_map = CdEntryMapZip64::Create();
+  if (num_entries <= UINT16_MAX) {
+    archive->cd_entry_map = CdEntryMapZip32::Create(static_cast<uint16_t>(num_entries));
   } else {
-    archive->cd_entry_map = CdEntryMapZip32::Create(num_entries);
+    archive->cd_entry_map = CdEntryMapZip64::Create();
   }
   if (archive->cd_entry_map == nullptr) {
     return kAllocationFailed;
@@ -279,9 +461,9 @@
    */
   const uint8_t* const cd_end = cd_ptr + cd_length;
   const uint8_t* ptr = cd_ptr;
-  for (uint16_t i = 0; i < num_entries; i++) {
+  for (uint64_t i = 0; i < num_entries; i++) {
     if (ptr > cd_end - sizeof(CentralDirectoryRecord)) {
-      ALOGW("Zip: ran off the end (item #%" PRIu16 ", %zu bytes of central directory)", i,
+      ALOGW("Zip: ran off the end (item #%" PRIu64 ", %zu bytes of central directory)", i,
             cd_length);
 #if defined(__ANDROID__)
       android_errorWriteLog(0x534e4554, "36392138");
@@ -289,16 +471,9 @@
       return kInvalidFile;
     }
 
-    const CentralDirectoryRecord* cdr = reinterpret_cast<const CentralDirectoryRecord*>(ptr);
+    auto cdr = reinterpret_cast<const CentralDirectoryRecord*>(ptr);
     if (cdr->record_signature != CentralDirectoryRecord::kSignature) {
-      ALOGW("Zip: missed a central dir sig (at %" PRIu16 ")", i);
-      return kInvalidFile;
-    }
-
-    const off64_t local_header_offset = cdr->local_file_header_offset;
-    if (local_header_offset >= archive->directory_offset) {
-      ALOGW("Zip: bad LFH offset %" PRId64 " at entry %" PRIu16,
-            static_cast<int64_t>(local_header_offset), i);
+      ALOGW("Zip: missed a central dir sig (at %" PRIu64 ")", i);
       return kInvalidFile;
     }
 
@@ -307,15 +482,43 @@
     const uint16_t comment_length = cdr->comment_length;
     const uint8_t* file_name = ptr + sizeof(CentralDirectoryRecord);
 
-    if (file_name + file_name_length > cd_end) {
-      ALOGW("Zip: file name for entry %" PRIu16
+    if (file_name_length >= cd_length || file_name > cd_end - file_name_length) {
+      ALOGW("Zip: file name for entry %" PRIu64
             " exceeds the central directory range, file_name_length: %" PRIu16 ", cd_length: %zu",
             i, file_name_length, cd_length);
       return kInvalidEntryName;
     }
+
+    const uint8_t* extra_field = file_name + file_name_length;
+    if (extra_length >= cd_length || extra_field > cd_end - extra_length) {
+      ALOGW("Zip: extra field for entry %" PRIu64
+            " exceeds the central directory range, file_name_length: %" PRIu16 ", cd_length: %zu",
+            i, extra_length, cd_length);
+      return kInvalidFile;
+    }
+
+    off64_t local_header_offset = cdr->local_file_header_offset;
+    if (local_header_offset == UINT32_MAX) {
+      Zip64ExtendedInfo zip64_info{};
+      if (auto status = ParseZip64ExtendedInfoInExtraField(
+              extra_field, extra_length, cdr->uncompressed_size, cdr->compressed_size,
+              cdr->local_file_header_offset, &zip64_info);
+          status != kSuccess) {
+        return status;
+      }
+      CHECK(zip64_info.local_header_offset.has_value());
+      local_header_offset = zip64_info.local_header_offset.value();
+    }
+
+    if (local_header_offset >= archive->directory_offset) {
+      ALOGW("Zip: bad LFH offset %" PRId64 " at entry %" PRIu64,
+            static_cast<int64_t>(local_header_offset), i);
+      return kInvalidFile;
+    }
+
     // Check that file name is valid UTF-8 and doesn't contain NUL (U+0000) characters.
     if (!IsValidEntryName(file_name, file_name_length)) {
-      ALOGW("Zip: invalid file name at entry %" PRIu16, i);
+      ALOGW("Zip: invalid file name at entry %" PRIu64, i);
       return kInvalidEntryName;
     }
 
@@ -330,7 +533,7 @@
 
     ptr += sizeof(CentralDirectoryRecord) + file_name_length + extra_length + comment_length;
     if ((ptr - cd_ptr) > static_cast<int64_t>(cd_length)) {
-      ALOGW("Zip: bad CD advance (%tu vs %zu) at entry %" PRIu16, ptr - cd_ptr, cd_length, i);
+      ALOGW("Zip: bad CD advance (%tu vs %zu) at entry %" PRIu64, ptr - cd_ptr, cd_length, i);
       return kInvalidFile;
     }
   }
@@ -350,26 +553,44 @@
     return kInvalidFile;
   }
 
-  ALOGV("+++ zip good scan %" PRIu16 " entries", num_entries);
+  ALOGV("+++ zip good scan %" PRIu64 " entries", num_entries);
 
-  return 0;
+  return kSuccess;
 }
 
 static int32_t OpenArchiveInternal(ZipArchive* archive, const char* debug_file_name) {
   int32_t result = MapCentralDirectory(debug_file_name, archive);
-  return result != 0 ? result : ParseZipArchive(archive);
+  return result != kSuccess ? result : ParseZipArchive(archive);
 }
 
 int32_t OpenArchiveFd(int fd, const char* debug_file_name, ZipArchiveHandle* handle,
                       bool assume_ownership) {
-  ZipArchive* archive = new ZipArchive(fd, assume_ownership);
+  ZipArchive* archive = new ZipArchive(MappedZipFile(fd), assume_ownership);
   *handle = archive;
   return OpenArchiveInternal(archive, debug_file_name);
 }
 
+int32_t OpenArchiveFdRange(int fd, const char* debug_file_name, ZipArchiveHandle* handle,
+                           off64_t length, off64_t offset, bool assume_ownership) {
+  ZipArchive* archive = new ZipArchive(MappedZipFile(fd, length, offset), assume_ownership);
+  *handle = archive;
+
+  if (length < 0) {
+    ALOGW("Invalid zip length %" PRId64, length);
+    return kIoError;
+  }
+
+  if (offset < 0) {
+    ALOGW("Invalid zip offset %" PRId64, offset);
+    return kIoError;
+  }
+
+  return OpenArchiveInternal(archive, debug_file_name);
+}
+
 int32_t OpenArchive(const char* fileName, ZipArchiveHandle* handle) {
   const int fd = ::android::base::utf8::open(fileName, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
-  ZipArchive* archive = new ZipArchive(fd, true);
+  ZipArchive* archive = new ZipArchive(MappedZipFile(fd), true);
   *handle = archive;
 
   if (fd < 0) {
@@ -402,8 +623,13 @@
   delete archive;
 }
 
-static int32_t ValidateDataDescriptor(MappedZipFile& mapped_zip, ZipEntry* entry) {
-  uint8_t ddBuf[sizeof(DataDescriptor) + sizeof(DataDescriptor::kOptSignature)];
+static int32_t ValidateDataDescriptor(MappedZipFile& mapped_zip, const ZipEntry64* entry) {
+  // Maximum possible size for data descriptor: 2 * 4 + 2 * 8 = 24 bytes
+  // The zip format doesn't specify the size of data descriptor. But we won't read OOB here even
+  // if the descriptor isn't present. Because the size cd + eocd in the end of the zipfile is
+  // larger than 24 bytes. And if the descriptor contains invalid data, we'll abort due to
+  // kInconsistentInformation.
+  uint8_t ddBuf[24];
   off64_t offset = entry->offset;
   if (entry->method != kCompressStored) {
     offset += entry->compressed_length;
@@ -416,18 +642,26 @@
   }
 
   const uint32_t ddSignature = *(reinterpret_cast<const uint32_t*>(ddBuf));
-  const uint16_t ddOffset = (ddSignature == DataDescriptor::kOptSignature) ? 4 : 0;
-  const DataDescriptor* descriptor = reinterpret_cast<const DataDescriptor*>(ddBuf + ddOffset);
+  uint8_t* ddReadPtr = (ddSignature == DataDescriptor::kOptSignature) ? ddBuf + 4 : ddBuf;
+  DataDescriptor descriptor{};
+  descriptor.crc32 = ConsumeUnaligned<uint32_t>(&ddReadPtr);
+  if (entry->zip64_format_size) {
+    descriptor.compressed_size = ConsumeUnaligned<uint64_t>(&ddReadPtr);
+    descriptor.uncompressed_size = ConsumeUnaligned<uint64_t>(&ddReadPtr);
+  } else {
+    descriptor.compressed_size = ConsumeUnaligned<uint32_t>(&ddReadPtr);
+    descriptor.uncompressed_size = ConsumeUnaligned<uint32_t>(&ddReadPtr);
+  }
 
   // Validate that the values in the data descriptor match those in the central
   // directory.
-  if (entry->compressed_length != descriptor->compressed_size ||
-      entry->uncompressed_length != descriptor->uncompressed_size ||
-      entry->crc32 != descriptor->crc32) {
-    ALOGW("Zip: size/crc32 mismatch. expected {%" PRIu32 ", %" PRIu32 ", %" PRIx32
-          "}, was {%" PRIu32 ", %" PRIu32 ", %" PRIx32 "}",
+  if (entry->compressed_length != descriptor.compressed_size ||
+      entry->uncompressed_length != descriptor.uncompressed_size ||
+      entry->crc32 != descriptor.crc32) {
+    ALOGW("Zip: size/crc32 mismatch. expected {%" PRIu64 ", %" PRIu64 ", %" PRIx32
+          "}, was {%" PRIu64 ", %" PRIu64 ", %" PRIx32 "}",
           entry->compressed_length, entry->uncompressed_length, entry->crc32,
-          descriptor->compressed_size, descriptor->uncompressed_size, descriptor->crc32);
+          descriptor.compressed_size, descriptor.uncompressed_size, descriptor.crc32);
     return kInconsistentInformation;
   }
 
@@ -435,7 +669,7 @@
 }
 
 static int32_t FindEntry(const ZipArchive* archive, std::string_view entryName,
-                         const uint64_t nameOffset, ZipEntry* data) {
+                         const uint64_t nameOffset, ZipEntry64* data) {
   // Recover the start of the central directory entry from the filename
   // pointer.  The filename is the first entry past the fixed-size data,
   // so we can just subtract back from that.
@@ -451,7 +685,7 @@
     return kInvalidOffset;
   }
 
-  const CentralDirectoryRecord* cdr = reinterpret_cast<const CentralDirectoryRecord*>(ptr);
+  auto cdr = reinterpret_cast<const CentralDirectoryRecord*>(ptr);
 
   // The offset of the start of the central directory in the zipfile.
   // We keep this lying around so that we can sanity check all our lengths
@@ -470,7 +704,27 @@
   // Figure out the local header offset from the central directory. The
   // actual file data will begin after the local header and the name /
   // extra comments.
-  const off64_t local_header_offset = cdr->local_file_header_offset;
+  off64_t local_header_offset = cdr->local_file_header_offset;
+  // One of the info field is UINT32_MAX, try to parse the real value in the zip64 extended info in
+  // the extra field.
+  if (cdr->uncompressed_size == UINT32_MAX || cdr->compressed_size == UINT32_MAX ||
+      cdr->local_file_header_offset == UINT32_MAX) {
+    const uint8_t* extra_field = ptr + sizeof(CentralDirectoryRecord) + cdr->file_name_length;
+    Zip64ExtendedInfo zip64_info{};
+    if (auto status = ParseZip64ExtendedInfoInExtraField(
+            extra_field, cdr->extra_field_length, cdr->uncompressed_size, cdr->compressed_size,
+            cdr->local_file_header_offset, &zip64_info);
+        status != kSuccess) {
+      return status;
+    }
+
+    data->uncompressed_length = zip64_info.uncompressed_file_size.value_or(cdr->uncompressed_size);
+    data->compressed_length = zip64_info.compressed_file_size.value_or(cdr->compressed_size);
+    local_header_offset = zip64_info.local_header_offset.value_or(local_header_offset);
+    data->zip64_format_size =
+        cdr->uncompressed_size == UINT32_MAX || cdr->compressed_size == UINT32_MAX;
+  }
+
   if (local_header_offset + static_cast<off64_t>(sizeof(LocalFileHeader)) >= cd_offset) {
     ALOGW("Zip: bad local hdr offset in zip");
     return kInvalidOffset;
@@ -483,14 +737,75 @@
     return kIoError;
   }
 
-  const LocalFileHeader* lfh = reinterpret_cast<const LocalFileHeader*>(lfh_buf);
-
+  auto lfh = reinterpret_cast<const LocalFileHeader*>(lfh_buf);
   if (lfh->lfh_signature != LocalFileHeader::kSignature) {
     ALOGW("Zip: didn't find signature at start of lfh, offset=%" PRId64,
           static_cast<int64_t>(local_header_offset));
     return kInvalidOffset;
   }
 
+  // Check that the local file header name matches the declared name in the central directory.
+  CHECK_LE(entryName.size(), UINT16_MAX);
+  auto nameLen = static_cast<uint16_t>(entryName.size());
+  if (lfh->file_name_length != nameLen) {
+    ALOGW("Zip: lfh name length did not match central directory for %s: %" PRIu16 " %" PRIu16,
+          std::string(entryName).c_str(), lfh->file_name_length, nameLen);
+    return kInconsistentInformation;
+  }
+  const off64_t name_offset = local_header_offset + sizeof(LocalFileHeader);
+  if (name_offset > cd_offset - lfh->file_name_length) {
+    ALOGW("Zip: lfh name has invalid declared length");
+    return kInvalidOffset;
+  }
+
+  std::vector<uint8_t> name_buf(nameLen);
+  if (!archive->mapped_zip.ReadAtOffset(name_buf.data(), nameLen, name_offset)) {
+    ALOGW("Zip: failed reading lfh name from offset %" PRId64, static_cast<int64_t>(name_offset));
+    return kIoError;
+  }
+  if (memcmp(entryName.data(), name_buf.data(), nameLen) != 0) {
+    ALOGW("Zip: lfh name did not match central directory");
+    return kInconsistentInformation;
+  }
+
+  uint64_t lfh_uncompressed_size = lfh->uncompressed_size;
+  uint64_t lfh_compressed_size = lfh->compressed_size;
+  if (lfh_uncompressed_size == UINT32_MAX || lfh_compressed_size == UINT32_MAX) {
+    if (lfh_uncompressed_size != UINT32_MAX || lfh_compressed_size != UINT32_MAX) {
+      ALOGW(
+          "Zip: The zip64 extended field in the local header MUST include BOTH original and "
+          "compressed file size fields.");
+      return kInvalidFile;
+    }
+
+    const off64_t lfh_extra_field_offset = name_offset + lfh->file_name_length;
+    const uint16_t lfh_extra_field_size = lfh->extra_field_length;
+    if (lfh_extra_field_offset > cd_offset - lfh_extra_field_size) {
+      ALOGW("Zip: extra field has a bad size for entry %s", std::string(entryName).c_str());
+      return kInvalidOffset;
+    }
+
+    std::vector<uint8_t> local_extra_field(lfh_extra_field_size);
+    if (!archive->mapped_zip.ReadAtOffset(local_extra_field.data(), lfh_extra_field_size,
+                                          lfh_extra_field_offset)) {
+      ALOGW("Zip: failed reading lfh extra field from offset %" PRId64, lfh_extra_field_offset);
+      return kIoError;
+    }
+
+    Zip64ExtendedInfo zip64_info{};
+    if (auto status = ParseZip64ExtendedInfoInExtraField(
+            local_extra_field.data(), lfh_extra_field_size, lfh->uncompressed_size,
+            lfh->compressed_size, std::nullopt, &zip64_info);
+        status != kSuccess) {
+      return status;
+    }
+
+    CHECK(zip64_info.uncompressed_file_size.has_value());
+    CHECK(zip64_info.compressed_file_size.has_value());
+    lfh_uncompressed_size = zip64_info.uncompressed_file_size.value();
+    lfh_compressed_size = zip64_info.compressed_file_size.value();
+  }
+
   // Paranoia: Match the values specified in the local file header
   // to those specified in the central directory.
 
@@ -516,12 +831,12 @@
   // header agree on the crc, compressed, and uncompressed sizes of the entry.
   if ((lfh->gpb_flags & kGPBDDFlagMask) == 0) {
     data->has_data_descriptor = 0;
-    if (data->compressed_length != lfh->compressed_size ||
-        data->uncompressed_length != lfh->uncompressed_size || data->crc32 != lfh->crc32) {
-      ALOGW("Zip: size/crc32 mismatch. expected {%" PRIu32 ", %" PRIu32 ", %" PRIx32
-            "}, was {%" PRIu32 ", %" PRIu32 ", %" PRIx32 "}",
-            data->compressed_length, data->uncompressed_length, data->crc32, lfh->compressed_size,
-            lfh->uncompressed_size, lfh->crc32);
+    if (data->compressed_length != lfh_compressed_size ||
+        data->uncompressed_length != lfh_uncompressed_size || data->crc32 != lfh->crc32) {
+      ALOGW("Zip: size/crc32 mismatch. expected {%" PRIu64 ", %" PRIu64 ", %" PRIx32
+            "}, was {%" PRIu64 ", %" PRIu64 ", %" PRIx32 "}",
+            data->compressed_length, data->uncompressed_length, data->crc32, lfh_compressed_size,
+            lfh_uncompressed_size, lfh->crc32);
       return kInconsistentInformation;
     }
   } else {
@@ -544,30 +859,6 @@
   // Currently only needed to implement zipinfo.
   data->is_text = (cdr->internal_file_attributes & 1);
 
-  // Check that the local file header name matches the declared
-  // name in the central directory.
-  CHECK_LE(entryName.size(), UINT16_MAX);
-  auto nameLen = static_cast<uint16_t>(entryName.size());
-  if (lfh->file_name_length != nameLen) {
-    ALOGW("Zip: lfh name length did not match central directory for %s: %" PRIu16 " %" PRIu16,
-          std::string(entryName).c_str(), lfh->file_name_length, nameLen);
-    return kInconsistentInformation;
-  }
-  const off64_t name_offset = local_header_offset + sizeof(LocalFileHeader);
-  if (name_offset + lfh->file_name_length > cd_offset) {
-    ALOGW("Zip: lfh name has invalid declared length");
-    return kInvalidOffset;
-  }
-  std::vector<uint8_t> name_buf(nameLen);
-  if (!archive->mapped_zip.ReadAtOffset(name_buf.data(), nameLen, name_offset)) {
-    ALOGW("Zip: failed reading lfh name from offset %" PRId64, static_cast<int64_t>(name_offset));
-    return kIoError;
-  }
-  if (memcmp(entryName.data(), name_buf.data(), nameLen) != 0) {
-    ALOGW("Zip: lfh name did not match central directory");
-    return kInconsistentInformation;
-  }
-
   const off64_t data_offset = local_header_offset + sizeof(LocalFileHeader) +
                               lfh->file_name_length + lfh->extra_field_length;
   if (data_offset > cd_offset) {
@@ -575,16 +866,15 @@
     return kInvalidOffset;
   }
 
-  if (static_cast<off64_t>(data_offset + data->compressed_length) > cd_offset) {
-    ALOGW("Zip: bad compressed length in zip (%" PRId64 " + %" PRIu32 " > %" PRId64 ")",
+  if (data->compressed_length > cd_offset - data_offset) {
+    ALOGW("Zip: bad compressed length in zip (%" PRId64 " + %" PRIu64 " > %" PRId64 ")",
           static_cast<int64_t>(data_offset), data->compressed_length,
           static_cast<int64_t>(cd_offset));
     return kInvalidOffset;
   }
 
-  if (data->method == kCompressStored &&
-      static_cast<off64_t>(data_offset + data->uncompressed_length) > cd_offset) {
-    ALOGW("Zip: bad uncompressed length in zip (%" PRId64 " + %" PRIu32 " > %" PRId64 ")",
+  if (data->method == kCompressStored && data->uncompressed_length > cd_offset - data_offset) {
+    ALOGW("Zip: bad uncompressed length in zip (%" PRId64 " + %" PRIu64 " > %" PRId64 ")",
           static_cast<int64_t>(data_offset), data->uncompressed_length,
           static_cast<int64_t>(cd_offset));
     return kInvalidOffset;
@@ -597,31 +887,40 @@
 struct IterationHandle {
   ZipArchive* archive;
 
-  std::string prefix;
-  std::string suffix;
+  std::function<bool(std::string_view)> matcher;
 
   uint32_t position = 0;
 
-  IterationHandle(ZipArchive* archive, std::string_view in_prefix, std::string_view in_suffix)
-      : archive(archive), prefix(in_prefix), suffix(in_suffix) {}
+  IterationHandle(ZipArchive* archive, std::function<bool(std::string_view)> in_matcher)
+      : archive(archive), matcher(std::move(in_matcher)) {}
+
+  bool Match(std::string_view entry_name) const { return matcher(entry_name); }
 };
 
 int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr,
                        const std::string_view optional_prefix,
                        const std::string_view optional_suffix) {
-  if (archive == nullptr || archive->cd_entry_map == nullptr) {
-    ALOGW("Zip: Invalid ZipArchiveHandle");
-    return kInvalidHandle;
-  }
-
   if (optional_prefix.size() > static_cast<size_t>(UINT16_MAX) ||
       optional_suffix.size() > static_cast<size_t>(UINT16_MAX)) {
     ALOGW("Zip: prefix/suffix too long");
     return kInvalidEntryName;
   }
+  auto matcher = [prefix = std::string(optional_prefix),
+                  suffix = std::string(optional_suffix)](std::string_view name) mutable {
+    return android::base::StartsWith(name, prefix) && android::base::EndsWith(name, suffix);
+  };
+  return StartIteration(archive, cookie_ptr, std::move(matcher));
+}
+
+int32_t StartIteration(ZipArchiveHandle archive, void** cookie_ptr,
+                       std::function<bool(std::string_view)> matcher) {
+  if (archive == nullptr || archive->cd_entry_map == nullptr) {
+    ALOGW("Zip: Invalid ZipArchiveHandle");
+    return kInvalidHandle;
+  }
 
   archive->cd_entry_map->ResetIteration();
-  *cookie_ptr = new IterationHandle(archive, optional_prefix, optional_suffix);
+  *cookie_ptr = new IterationHandle(archive, matcher);
   return 0;
 }
 
@@ -629,8 +928,33 @@
   delete reinterpret_cast<IterationHandle*>(cookie);
 }
 
+int32_t ZipEntry::CopyFromZipEntry64(ZipEntry* dst, const ZipEntry64* src) {
+  if (src->compressed_length > UINT32_MAX || src->uncompressed_length > UINT32_MAX) {
+    ALOGW(
+        "Zip: the entry size is too large to fit into the 32 bits ZipEntry, uncompressed "
+        "length %" PRIu64 ", compressed length %" PRIu64,
+        src->uncompressed_length, src->compressed_length);
+    return kUnsupportedEntrySize;
+  }
+
+  *dst = *src;
+  dst->uncompressed_length = static_cast<uint32_t>(src->uncompressed_length);
+  dst->compressed_length = static_cast<uint32_t>(src->compressed_length);
+  return kSuccess;
+}
+
 int32_t FindEntry(const ZipArchiveHandle archive, const std::string_view entryName,
                   ZipEntry* data) {
+  ZipEntry64 entry64;
+  if (auto status = FindEntry(archive, entryName, &entry64); status != kSuccess) {
+    return status;
+  }
+
+  return ZipEntry::CopyFromZipEntry64(data, &entry64);
+}
+
+int32_t FindEntry(const ZipArchiveHandle archive, const std::string_view entryName,
+                  ZipEntry64* data) {
   if (entryName.empty() || entryName.size() > static_cast<size_t>(UINT16_MAX)) {
     ALOGW("Zip: Invalid filename of length %zu", entryName.size());
     return kInvalidEntryName;
@@ -647,6 +971,24 @@
 }
 
 int32_t Next(void* cookie, ZipEntry* data, std::string* name) {
+  ZipEntry64 entry64;
+  if (auto status = Next(cookie, &entry64, name); status != kSuccess) {
+    return status;
+  }
+
+  return ZipEntry::CopyFromZipEntry64(data, &entry64);
+}
+
+int32_t Next(void* cookie, ZipEntry* data, std::string_view* name) {
+  ZipEntry64 entry64;
+  if (auto status = Next(cookie, &entry64, name); status != kSuccess) {
+    return status;
+  }
+
+  return ZipEntry::CopyFromZipEntry64(data, &entry64);
+}
+
+int32_t Next(void* cookie, ZipEntry64* data, std::string* name) {
   std::string_view sv;
   int32_t result = Next(cookie, data, &sv);
   if (result == 0 && name) {
@@ -655,7 +997,7 @@
   return result;
 }
 
-int32_t Next(void* cookie, ZipEntry* data, std::string_view* name) {
+int32_t Next(void* cookie, ZipEntry64* data, std::string_view* name) {
   IterationHandle* handle = reinterpret_cast<IterationHandle*>(cookie);
   if (handle == nullptr) {
     ALOGW("Zip: Null ZipArchiveHandle");
@@ -671,8 +1013,7 @@
   auto entry = archive->cd_entry_map->Next(archive->central_directory.GetBasePtr());
   while (entry != std::pair<std::string_view, uint64_t>()) {
     const auto [entry_name, offset] = entry;
-    if (android::base::StartsWith(entry_name, handle->prefix) &&
-        android::base::EndsWith(entry_name, handle->suffix)) {
+    if (handle->Match(entry_name)) {
       const int error = FindEntry(archive, entry_name, offset, data);
       if (!error && name) {
         *name = entry_name;
@@ -691,10 +1032,19 @@
 // the data appended to it.
 class MemoryWriter : public zip_archive::Writer {
  public:
-  MemoryWriter(uint8_t* buf, size_t size) : Writer(), buf_(buf), size_(size), bytes_written_(0) {}
+  static std::unique_ptr<MemoryWriter> Create(uint8_t* buf, size_t size, const ZipEntry64* entry) {
+    const uint64_t declared_length = entry->uncompressed_length;
+    if (declared_length > size) {
+      ALOGW("Zip: file size %" PRIu64 " is larger than the buffer size %zu.", declared_length,
+            size);
+      return nullptr;
+    }
+
+    return std::unique_ptr<MemoryWriter>(new MemoryWriter(buf, size));
+  }
 
   virtual bool Append(uint8_t* buf, size_t buf_size) override {
-    if (bytes_written_ + buf_size > size_) {
+    if (size_ < buf_size || bytes_written_ > size_ - buf_size) {
       ALOGW("Zip: Unexpected size %zu (declared) vs %zu (actual)", size_,
             bytes_written_ + buf_size);
       return false;
@@ -706,7 +1056,9 @@
   }
 
  private:
-  uint8_t* const buf_;
+  MemoryWriter(uint8_t* buf, size_t size) : Writer(), buf_(buf), size_(size), bytes_written_(0) {}
+
+  uint8_t* const buf_{nullptr};
   const size_t size_;
   size_t bytes_written_;
 };
@@ -722,12 +1074,17 @@
   // block device).
   //
   // Returns a valid FileWriter on success, |nullptr| if an error occurred.
-  static FileWriter Create(int fd, const ZipEntry* entry) {
-    const uint32_t declared_length = entry->uncompressed_length;
+  static std::unique_ptr<FileWriter> Create(int fd, const ZipEntry64* entry) {
+    const uint64_t declared_length = entry->uncompressed_length;
     const off64_t current_offset = lseek64(fd, 0, SEEK_CUR);
     if (current_offset == -1) {
       ALOGW("Zip: unable to seek to current location on fd %d: %s", fd, strerror(errno));
-      return FileWriter{};
+      return nullptr;
+    }
+
+    if (declared_length > SIZE_MAX || declared_length > INT64_MAX) {
+      ALOGW("Zip: file size %" PRIu64 " is too large to extract.", declared_length);
+      return nullptr;
     }
 
 #if defined(__linux__)
@@ -743,10 +1100,9 @@
       // disk does not have enough space.
       long result = TEMP_FAILURE_RETRY(fallocate(fd, 0, current_offset, declared_length));
       if (result == -1 && errno == ENOSPC) {
-        ALOGW("Zip: unable to allocate %" PRId64 " bytes at offset %" PRId64 ": %s",
-              static_cast<int64_t>(declared_length), static_cast<int64_t>(current_offset),
-              strerror(errno));
-        return FileWriter{};
+        ALOGW("Zip: unable to allocate %" PRIu64 " bytes at offset %" PRId64 ": %s",
+              declared_length, static_cast<int64_t>(current_offset), strerror(errno));
+        return nullptr;
       }
     }
 #endif  // __linux__
@@ -754,7 +1110,7 @@
     struct stat sb;
     if (fstat(fd, &sb) == -1) {
       ALOGW("Zip: unable to fstat file: %s", strerror(errno));
-      return FileWriter{};
+      return nullptr;
     }
 
     // Block device doesn't support ftruncate(2).
@@ -763,11 +1119,11 @@
       if (result == -1) {
         ALOGW("Zip: unable to truncate file to %" PRId64 ": %s",
               static_cast<int64_t>(declared_length + current_offset), strerror(errno));
-        return FileWriter{};
+        return nullptr;
       }
     }
 
-    return FileWriter(fd, declared_length);
+    return std::unique_ptr<FileWriter>(new FileWriter(fd, declared_length));
   }
 
   FileWriter(FileWriter&& other) noexcept
@@ -777,11 +1133,9 @@
     other.fd_ = -1;
   }
 
-  bool IsValid() const { return fd_ != -1; }
-
   virtual bool Append(uint8_t* buf, size_t buf_size) override {
-    if (total_bytes_written_ + buf_size > declared_length_) {
-      ALOGW("Zip: Unexpected size %zu (declared) vs %zu (actual)", declared_length_,
+    if (declared_length_ < buf_size || total_bytes_written_ > declared_length_ - buf_size) {
+      ALOGW("Zip: Unexpected size %zu  (declared) vs %zu (actual)", declared_length_,
             total_bytes_written_ + buf_size);
       return false;
     }
@@ -797,8 +1151,13 @@
   }
 
  private:
-  explicit FileWriter(const int fd = -1, const size_t declared_length = 0)
-      : Writer(), fd_(fd), declared_length_(declared_length), total_bytes_written_(0) {}
+  explicit FileWriter(const int fd = -1, const uint64_t declared_length = 0)
+      : Writer(),
+        fd_(fd),
+        declared_length_(static_cast<size_t>(declared_length)),
+        total_bytes_written_(0) {
+    CHECK_LE(declared_length, SIZE_MAX);
+  }
 
   int fd_;
   const size_t declared_length_;
@@ -807,10 +1166,10 @@
 
 class EntryReader : public zip_archive::Reader {
  public:
-  EntryReader(const MappedZipFile& zip_file, const ZipEntry* entry)
+  EntryReader(const MappedZipFile& zip_file, const ZipEntry64* entry)
       : Reader(), zip_file_(zip_file), entry_(entry) {}
 
-  virtual bool ReadAtOffset(uint8_t* buf, size_t len, uint32_t offset) const {
+  virtual bool ReadAtOffset(uint8_t* buf, size_t len, off64_t offset) const {
     return zip_file_.ReadAtOffset(buf, len, entry_->offset + offset);
   }
 
@@ -818,7 +1177,7 @@
 
  private:
   const MappedZipFile& zip_file_;
-  const ZipEntry* entry_;
+  const ZipEntry64* entry_;
 };
 
 // This method is using libz macros with old-style-casts
@@ -835,8 +1194,8 @@
 Reader::~Reader() {}
 Writer::~Writer() {}
 
-int32_t Inflate(const Reader& reader, const uint32_t compressed_length,
-                const uint32_t uncompressed_length, Writer* writer, uint64_t* crc_out) {
+int32_t Inflate(const Reader& reader, const uint64_t compressed_length,
+                const uint64_t uncompressed_length, Writer* writer, uint64_t* crc_out) {
   const size_t kBufSize = 32768;
   std::vector<uint8_t> read_buf(kBufSize);
   std::vector<uint8_t> write_buf(kBufSize);
@@ -879,12 +1238,14 @@
 
   const bool compute_crc = (crc_out != nullptr);
   uLong crc = 0;
-  uint32_t remaining_bytes = compressed_length;
+  uint64_t remaining_bytes = compressed_length;
+  uint64_t total_output = 0;
   do {
     /* read as much as we can */
     if (zstream.avail_in == 0) {
-      const uint32_t read_size = (remaining_bytes > kBufSize) ? kBufSize : remaining_bytes;
-      const uint32_t offset = (compressed_length - remaining_bytes);
+      const uint32_t read_size =
+          (remaining_bytes > kBufSize) ? kBufSize : static_cast<uint32_t>(remaining_bytes);
+      const off64_t offset = (compressed_length - remaining_bytes);
       // Make sure to read at offset to ensure concurrent access to the fd.
       if (!reader.ReadAtOffset(read_buf.data(), read_size, offset)) {
         ALOGW("Zip: inflate read failed, getSize = %u: %s", read_size, strerror(errno));
@@ -915,6 +1276,7 @@
         crc = crc32(crc, &write_buf[0], static_cast<uint32_t>(write_size));
       }
 
+      total_output += kBufSize - zstream.avail_out;
       zstream.next_out = &write_buf[0];
       zstream.avail_out = kBufSize;
     }
@@ -931,9 +1293,8 @@
   if (compute_crc) {
     *crc_out = crc;
   }
-
-  if (zstream.total_out != uncompressed_length || remaining_bytes != 0) {
-    ALOGW("Zip: size mismatch on inflated file (%lu vs %" PRIu32 ")", zstream.total_out,
+  if (total_output != uncompressed_length || remaining_bytes != 0) {
+    ALOGW("Zip: size mismatch on inflated file (%lu vs %" PRIu64 ")", zstream.total_out,
           uncompressed_length);
     return kInconsistentInformation;
   }
@@ -942,7 +1303,7 @@
 }
 }  // namespace zip_archive
 
-static int32_t InflateEntryToWriter(MappedZipFile& mapped_zip, const ZipEntry* entry,
+static int32_t InflateEntryToWriter(MappedZipFile& mapped_zip, const ZipEntry64* entry,
                                     zip_archive::Writer* writer, uint64_t* crc_out) {
   const EntryReader reader(mapped_zip, entry);
 
@@ -950,20 +1311,21 @@
                               crc_out);
 }
 
-static int32_t CopyEntryToWriter(MappedZipFile& mapped_zip, const ZipEntry* entry,
+static int32_t CopyEntryToWriter(MappedZipFile& mapped_zip, const ZipEntry64* entry,
                                  zip_archive::Writer* writer, uint64_t* crc_out) {
   static const uint32_t kBufSize = 32768;
   std::vector<uint8_t> buf(kBufSize);
 
-  const uint32_t length = entry->uncompressed_length;
-  uint32_t count = 0;
+  const uint64_t length = entry->uncompressed_length;
+  uint64_t count = 0;
   uLong crc = 0;
   while (count < length) {
-    uint32_t remaining = length - count;
+    uint64_t remaining = length - count;
     off64_t offset = entry->offset + count;
 
     // Safe conversion because kBufSize is narrow enough for a 32 bit signed value.
-    const uint32_t block_size = (remaining > kBufSize) ? kBufSize : remaining;
+    const uint32_t block_size =
+        (remaining > kBufSize) ? kBufSize : static_cast<uint32_t>(remaining);
 
     // Make sure to read at offset to ensure concurrent access to the fd.
     if (!mapped_zip.ReadAtOffset(buf.data(), block_size, offset)) {
@@ -975,29 +1337,36 @@
     if (!writer->Append(&buf[0], block_size)) {
       return kIoError;
     }
-    crc = crc32(crc, &buf[0], block_size);
+    if (crc_out) {
+      crc = crc32(crc, &buf[0], block_size);
+    }
     count += block_size;
   }
 
-  *crc_out = crc;
+  if (crc_out) {
+    *crc_out = crc;
+  }
 
   return 0;
 }
 
-int32_t ExtractToWriter(ZipArchiveHandle archive, ZipEntry* entry, zip_archive::Writer* writer) {
+int32_t ExtractToWriter(ZipArchiveHandle handle, const ZipEntry64* entry,
+                        zip_archive::Writer* writer) {
   const uint16_t method = entry->method;
 
   // this should default to kUnknownCompressionMethod.
   int32_t return_value = -1;
   uint64_t crc = 0;
   if (method == kCompressStored) {
-    return_value = CopyEntryToWriter(archive->mapped_zip, entry, writer, &crc);
+    return_value =
+        CopyEntryToWriter(handle->mapped_zip, entry, writer, kCrcChecksEnabled ? &crc : nullptr);
   } else if (method == kCompressDeflated) {
-    return_value = InflateEntryToWriter(archive->mapped_zip, entry, writer, &crc);
+    return_value =
+        InflateEntryToWriter(handle->mapped_zip, entry, writer, kCrcChecksEnabled ? &crc : nullptr);
   }
 
   if (!return_value && entry->has_data_descriptor) {
-    return_value = ValidateDataDescriptor(archive->mapped_zip, entry);
+    return_value = ValidateDataDescriptor(handle->mapped_zip, entry);
     if (return_value) {
       return return_value;
     }
@@ -1012,38 +1381,44 @@
   return return_value;
 }
 
-int32_t ExtractToMemory(ZipArchiveHandle archive, ZipEntry* entry, uint8_t* begin, uint32_t size) {
-  MemoryWriter writer(begin, size);
-  return ExtractToWriter(archive, entry, &writer);
+int32_t ExtractToMemory(ZipArchiveHandle archive, const ZipEntry* entry, uint8_t* begin,
+                        size_t size) {
+  ZipEntry64 entry64(*entry);
+  return ExtractToMemory(archive, &entry64, begin, size);
 }
 
-int32_t ExtractEntryToFile(ZipArchiveHandle archive, ZipEntry* entry, int fd) {
-  auto writer = FileWriter::Create(fd, entry);
-  if (!writer.IsValid()) {
+int32_t ExtractToMemory(ZipArchiveHandle archive, const ZipEntry64* entry, uint8_t* begin,
+                        size_t size) {
+  auto writer = MemoryWriter::Create(begin, size, entry);
+  if (!writer) {
     return kIoError;
   }
 
-  return ExtractToWriter(archive, entry, &writer);
+  return ExtractToWriter(archive, entry, writer.get());
 }
 
-const char* ErrorCodeString(int32_t error_code) {
-  // Make sure that the number of entries in kErrorMessages and ErrorCodes
-  // match.
-  static_assert((-kLastErrorCode + 1) == arraysize(kErrorMessages),
-                "(-kLastErrorCode + 1) != arraysize(kErrorMessages)");
+int32_t ExtractEntryToFile(ZipArchiveHandle archive, const ZipEntry* entry, int fd) {
+  ZipEntry64 entry64(*entry);
+  return ExtractEntryToFile(archive, &entry64, fd);
+}
 
-  const uint32_t idx = -error_code;
-  if (idx < arraysize(kErrorMessages)) {
-    return kErrorMessages[idx];
+int32_t ExtractEntryToFile(ZipArchiveHandle archive, const ZipEntry64* entry, int fd) {
+  auto writer = FileWriter::Create(fd, entry);
+  if (!writer) {
+    return kIoError;
   }
 
-  return "Unknown return code";
+  return ExtractToWriter(archive, entry, writer.get());
 }
 
 int GetFileDescriptor(const ZipArchiveHandle archive) {
   return archive->mapped_zip.GetFileDescriptor();
 }
 
+off64_t GetFileDescriptorOffset(const ZipArchiveHandle archive) {
+  return archive->mapped_zip.GetFileOffset();
+}
+
 #if !defined(_WIN32)
 class ProcessWriter : public zip_archive::Writer {
  public:
@@ -1059,7 +1434,13 @@
   void* cookie_;
 };
 
-int32_t ProcessZipEntryContents(ZipArchiveHandle archive, ZipEntry* entry,
+int32_t ProcessZipEntryContents(ZipArchiveHandle archive, const ZipEntry* entry,
+                                ProcessZipEntryFunction func, void* cookie) {
+  ZipEntry64 entry64(*entry);
+  return ProcessZipEntryContents(archive, &entry64, func, cookie);
+}
+
+int32_t ProcessZipEntryContents(ZipArchiveHandle archive, const ZipEntry64* entry,
                                 ProcessZipEntryFunction func, void* cookie) {
   ProcessWriter writer(func, cookie);
   return ExtractToWriter(archive, entry, &writer);
@@ -1083,32 +1464,67 @@
   return base_ptr_;
 }
 
+off64_t MappedZipFile::GetFileOffset() const {
+  return fd_offset_;
+}
+
 off64_t MappedZipFile::GetFileLength() const {
   if (has_fd_) {
-    off64_t result = lseek64(fd_, 0, SEEK_END);
-    if (result == -1) {
+    if (data_length_ != -1) {
+      return data_length_;
+    }
+    data_length_ = lseek64(fd_, 0, SEEK_END);
+    if (data_length_ == -1) {
       ALOGE("Zip: lseek on fd %d failed: %s", fd_, strerror(errno));
     }
-    return result;
+    return data_length_;
   } else {
     if (base_ptr_ == nullptr) {
       ALOGE("Zip: invalid file map");
       return -1;
     }
-    return static_cast<off64_t>(data_length_);
+    return data_length_;
   }
 }
 
 // Attempts to read |len| bytes into |buf| at offset |off|.
 bool MappedZipFile::ReadAtOffset(uint8_t* buf, size_t len, off64_t off) const {
   if (has_fd_) {
-    if (!android::base::ReadFullyAtOffset(fd_, buf, len, off)) {
+    if (off < 0) {
+      ALOGE("Zip: invalid offset %" PRId64, off);
+      return false;
+    }
+
+    off64_t read_offset;
+    if (__builtin_add_overflow(fd_offset_, off, &read_offset)) {
+      ALOGE("Zip: invalid read offset %" PRId64 " overflows, fd offset %" PRId64, off, fd_offset_);
+      return false;
+    }
+
+    if (data_length_ != -1) {
+      off64_t read_end;
+      if (len > std::numeric_limits<off64_t>::max() ||
+          __builtin_add_overflow(off, static_cast<off64_t>(len), &read_end)) {
+        ALOGE("Zip: invalid read length %" PRId64 " overflows, offset %" PRId64,
+              static_cast<off64_t>(len), off);
+        return false;
+      }
+
+      if (read_end > data_length_) {
+        ALOGE("Zip: invalid read length %" PRId64 " exceeds data length %" PRId64 ", offset %"
+              PRId64, static_cast<off64_t>(len), data_length_, off);
+        return false;
+      }
+    }
+
+    if (!android::base::ReadFullyAtOffset(fd_, buf, len, read_offset)) {
       ALOGE("Zip: failed to read at offset %" PRId64, off);
       return false;
     }
   } else {
-    if (off < 0 || off > static_cast<off64_t>(data_length_)) {
-      ALOGE("Zip: invalid offset: %" PRId64 ", data length: %" PRId64, off, data_length_);
+    if (off < 0 || data_length_ < len || off > data_length_ - len) {
+      ALOGE("Zip: invalid offset: %" PRId64 ", read length: %zu, data length: %" PRId64, off, len,
+            data_length_);
       return false;
     }
     memcpy(buf, static_cast<const uint8_t*>(base_ptr_) + off, len);
@@ -1125,7 +1541,8 @@
 bool ZipArchive::InitializeCentralDirectory(off64_t cd_start_offset, size_t cd_size) {
   if (mapped_zip.HasFd()) {
     directory_map = android::base::MappedFile::FromFd(mapped_zip.GetFileDescriptor(),
-                                                      cd_start_offset, cd_size, PROT_READ);
+                                                      mapped_zip.GetFileOffset() + cd_start_offset,
+                                                      cd_size, PROT_READ);
     if (!directory_map) {
       ALOGE("Zip: failed to map central directory (offset %" PRId64 ", size %zu): %s",
             cd_start_offset, cd_size, strerror(errno));
@@ -1153,7 +1570,8 @@
   return true;
 }
 
-tm ZipEntry::GetModificationTime() const {
+// This function returns the embedded timestamp as is; and doesn't perform validations.
+tm ZipEntryCommon::GetModificationTime() const {
   tm t = {};
 
   t.tm_hour = (mod_time >> 11) & 0x1f;
diff --git a/libziparchive/zip_archive_benchmark.cpp b/libziparchive/zip_archive_benchmark.cpp
index 09d3b8a..cfa5912 100644
--- a/libziparchive/zip_archive_benchmark.cpp
+++ b/libziparchive/zip_archive_benchmark.cpp
@@ -17,7 +17,6 @@
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
-#include <iostream>
 #include <string>
 #include <tuple>
 #include <vector>
@@ -28,17 +27,20 @@
 #include <ziparchive/zip_archive_stream_entry.h>
 #include <ziparchive/zip_writer.h>
 
-static TemporaryFile* CreateZip() {
-  TemporaryFile* result = new TemporaryFile;
+static std::unique_ptr<TemporaryFile> CreateZip(int size = 4, int count = 1000) {
+  auto result = std::make_unique<TemporaryFile>();
   FILE* fp = fdopen(result->fd, "w");
 
   ZipWriter writer(fp);
   std::string lastName = "file";
-  for (size_t i = 0; i < 1000; i++) {
+  for (size_t i = 0; i < count; i++) {
     // Make file names longer and longer.
     lastName = lastName + std::to_string(i);
     writer.StartEntry(lastName.c_str(), ZipWriter::kCompress);
-    writer.WriteBytes("helo", 4);
+    while (size > 0) {
+      writer.WriteBytes("helo", 4);
+      size -= 4;
+    }
     writer.FinishEntry();
   }
   writer.Finish();
@@ -106,5 +108,28 @@
 }
 BENCHMARK(StartAlignedEntry)->Arg(2)->Arg(16)->Arg(1024)->Arg(4096);
 
+static void ExtractEntry(benchmark::State& state) {
+  std::unique_ptr<TemporaryFile> temp_file(CreateZip(1024 * 1024, 1));
+
+  ZipArchiveHandle handle;
+  ZipEntry data;
+  if (OpenArchive(temp_file->path, &handle)) {
+    state.SkipWithError("Failed to open archive");
+  }
+  if (FindEntry(handle, "file0", &data)) {
+    state.SkipWithError("Failed to find archive entry");
+  }
+
+  std::vector<uint8_t> buffer(1024 * 1024);
+  for (auto _ : state) {
+    if (ExtractToMemory(handle, &data, buffer.data(), uint32_t(buffer.size()))) {
+      state.SkipWithError("Failed to extract archive entry");
+      break;
+    }
+  }
+  CloseArchive(handle);
+}
+
+BENCHMARK(ExtractEntry)->Arg(2)->Arg(16)->Arg(1024);
 
 BENCHMARK_MAIN();
diff --git a/libziparchive/zip_archive_common.h b/libziparchive/zip_archive_common.h
index 8b99bde..d461856 100644
--- a/libziparchive/zip_archive_common.h
+++ b/libziparchive/zip_archive_common.h
@@ -21,6 +21,8 @@
 
 #include <inttypes.h>
 
+#include <optional>
+
 // The "end of central directory" (EOCD) record. Each archive
 // contains exactly once such record which appears at the end of
 // the archive. It contains archive wide information like the
@@ -163,16 +165,108 @@
 
   // CRC-32 checksum of the entry.
   uint32_t crc32;
-  // Compressed size of the entry.
-  uint32_t compressed_size;
-  // Uncompressed size of the entry.
-  uint32_t uncompressed_size;
+
+  // For ZIP64 format archives, the compressed and uncompressed sizes are 8
+  // bytes each. Also, the ZIP64 format MAY be used regardless of the size
+  // of a file.  When extracting, if the zip64 extended information extra field
+  // is present for the file the compressed and uncompressed sizes will be 8
+  // byte values.
+
+  // Compressed size of the entry, the field can be either 4 bytes or 8 bytes
+  // in the zip file.
+  uint64_t compressed_size;
+  // Uncompressed size of the entry, the field can be either 4 bytes or 8 bytes
+  // in the zip file.
+  uint64_t uncompressed_size;
 
  private:
   DataDescriptor() = default;
   DISALLOW_COPY_AND_ASSIGN(DataDescriptor);
+};
+
+// The zip64 end of central directory locator helps to find the zip64 EOCD.
+struct Zip64EocdLocator {
+  static constexpr uint32_t kSignature = 0x07064b50;
+
+  // The signature of zip64 eocd locator, must be |kSignature|
+  uint32_t locator_signature;
+  // The start disk of the zip64 eocd. This implementation assumes that each
+  // archive spans a single disk only.
+  uint32_t eocd_start_disk;
+  // The offset offset of the zip64 end of central directory record.
+  uint64_t zip64_eocd_offset;
+  // The total number of disks. This implementation assumes that each archive
+  // spans a single disk only.
+  uint32_t num_of_disks;
+
+ private:
+  Zip64EocdLocator() = default;
+  DISALLOW_COPY_AND_ASSIGN(Zip64EocdLocator);
 } __attribute__((packed));
 
+// The optional zip64 EOCD. If one of the fields in the end of central directory
+// record is too small to hold required data, the field SHOULD be  set to -1
+// (0xFFFF or 0xFFFFFFFF) and the ZIP64 format record SHOULD be created.
+struct Zip64EocdRecord {
+  static constexpr uint32_t kSignature = 0x06064b50;
+
+  // The signature of zip64 eocd record, must be |kSignature|
+  uint32_t record_signature;
+  // Size of zip64 end of central directory record. It SHOULD be the size of the
+  // remaining record and SHOULD NOT include the leading 12 bytes.
+  uint64_t record_size;
+  // The version of the tool that make this archive.
+  uint16_t version_made_by;
+  // Tool version needed to extract this archive.
+  uint16_t version_needed;
+  // Number of this disk.
+  uint32_t disk_num;
+  // Number of the disk with the start of the central directory.
+  uint32_t cd_start_disk;
+  // Total number of entries in the central directory on this disk.
+  // This implementation assumes that each archive spans a single
+  // disk only. i.e, that num_records_on_disk == num_records.
+  uint64_t num_records_on_disk;
+  // The total number of central directory records.
+  uint64_t num_records;
+  // The size of the central directory in bytes.
+  uint64_t cd_size;
+  // The offset of the start of the central directory, relative to the start of
+  // the file.
+  uint64_t cd_start_offset;
+
+ private:
+  Zip64EocdRecord() = default;
+  DISALLOW_COPY_AND_ASSIGN(Zip64EocdRecord);
+} __attribute__((packed));
+
+// The possible contents of the Zip64 Extended Information Extra Field. It may appear in
+// the 'extra' field of a central directory record or local file header. The order of
+// the fields in the zip64 extended information record is fixed, but the fields MUST
+// only appear if the corresponding local or central directory record field is set to
+// 0xFFFF or 0xFFFFFFFF. And this entry in the Local header MUST include BOTH original
+// and compressed file size fields.
+struct Zip64ExtendedInfo {
+  static constexpr uint16_t kHeaderId = 0x0001;
+  // The header tag for this 'extra' block, should be |kHeaderId|.
+  uint16_t header_id;
+  // The size in bytes of the remaining data (excluding the top 4 bytes).
+  uint16_t data_size;
+  // Size in bytes of the uncompressed file.
+  std::optional<uint64_t> uncompressed_file_size;
+  // Size in bytes of the compressed file.
+  std::optional<uint64_t> compressed_file_size;
+  // Local file header offset relative to the start of the zip file.
+  std::optional<uint64_t> local_header_offset;
+
+  // This implementation assumes that each archive spans a single disk only. So
+  // the disk_number is not used.
+  // uint32_t disk_num;
+ private:
+  Zip64ExtendedInfo() = default;
+  DISALLOW_COPY_AND_ASSIGN(Zip64ExtendedInfo);
+};
+
 // mask value that signifies that the entry has a DD
 static const uint32_t kGPBDDFlagMask = 0x0008;
 
diff --git a/libziparchive/zip_archive_private.h b/libziparchive/zip_archive_private.h
index 536894c..4b43cba 100644
--- a/libziparchive/zip_archive_private.h
+++ b/libziparchive/zip_archive_private.h
@@ -28,16 +28,21 @@
 
 #include "android-base/macros.h"
 #include "android-base/mapped_file.h"
+#include "android-base/memory.h"
 #include "zip_cd_entry_map.h"
 #include "zip_error.h"
 
 class MappedZipFile {
  public:
   explicit MappedZipFile(const int fd)
-      : has_fd_(true), fd_(fd), base_ptr_(nullptr), data_length_(0) {}
+      : has_fd_(true), fd_(fd), fd_offset_(0), base_ptr_(nullptr), data_length_(-1) {}
+
+  explicit MappedZipFile(const int fd, off64_t length, off64_t offset)
+      : has_fd_(true), fd_(fd), fd_offset_(offset), base_ptr_(nullptr), data_length_(length) {}
 
   explicit MappedZipFile(const void* address, size_t length)
-      : has_fd_(false), fd_(-1), base_ptr_(address), data_length_(static_cast<off64_t>(length)) {}
+      : has_fd_(false), fd_(-1), fd_offset_(0), base_ptr_(address),
+        data_length_(static_cast<off64_t>(length)) {}
 
   bool HasFd() const { return has_fd_; }
 
@@ -45,6 +50,8 @@
 
   const void* GetBasePtr() const;
 
+  off64_t GetFileOffset() const;
+
   off64_t GetFileLength() const;
 
   bool ReadAtOffset(uint8_t* buf, size_t len, off64_t off) const;
@@ -57,9 +64,10 @@
   const bool has_fd_;
 
   const int fd_;
+  const off64_t fd_offset_;
 
   const void* const base_ptr_;
-  const off64_t data_length_;
+  mutable off64_t data_length_;
 };
 
 class CentralDirectory {
@@ -88,12 +96,30 @@
   std::unique_ptr<android::base::MappedFile> directory_map;
 
   // number of entries in the Zip archive
-  uint16_t num_entries;
+  uint64_t num_entries;
   std::unique_ptr<CdEntryMapInterface> cd_entry_map;
 
-  ZipArchive(const int fd, bool assume_ownership);
+  ZipArchive(MappedZipFile&& map, bool assume_ownership);
   ZipArchive(const void* address, size_t length);
   ~ZipArchive();
 
   bool InitializeCentralDirectory(off64_t cd_start_offset, size_t cd_size);
 };
+
+int32_t ExtractToWriter(ZipArchiveHandle handle, const ZipEntry64* entry,
+                        zip_archive::Writer* writer);
+
+// Reads the unaligned data of type |T| and auto increment the offset.
+template <typename T>
+static T ConsumeUnaligned(uint8_t** address) {
+  auto ret = android::base::get_unaligned<T>(*address);
+  *address += sizeof(T);
+  return ret;
+}
+
+// Writes the unaligned data of type |T| and auto increment the offset.
+template <typename T>
+void EmitUnaligned(uint8_t** address, T data) {
+  android::base::put_unaligned<T>(*address, data);
+  *address += sizeof(T);
+}
diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc
index 523d4c5..f5429be 100644
--- a/libziparchive/zip_archive_test.cc
+++ b/libziparchive/zip_archive_test.cc
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#include "zip_archive_private.h"
-
 #include <errno.h>
 #include <fcntl.h>
 #include <getopt.h>
@@ -23,6 +21,7 @@
 #include <string.h>
 #include <unistd.h>
 
+#include <map>
 #include <memory>
 #include <set>
 #include <string_view>
@@ -31,12 +30,16 @@
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/mapped_file.h>
+#include <android-base/memory.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
 #include <ziparchive/zip_archive.h>
 #include <ziparchive/zip_archive_stream_entry.h>
 
+#include "zip_archive_common.h"
+#include "zip_archive_private.h"
+
 static std::string test_data_dir = android::base::GetExecutableDirectory() + "/testdata";
 
 static const std::string kValidZip = "valid.zip";
@@ -181,6 +184,32 @@
   close(fd);
 }
 
+TEST(ziparchive, OpenAssumeFdRangeOwnership) {
+  int fd = open((test_data_dir + "/" + kValidZip).c_str(), O_RDONLY | O_BINARY);
+  ASSERT_NE(-1, fd);
+  const off64_t length = lseek64(fd, 0, SEEK_END);
+  ASSERT_NE(-1, length);
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFdRange(fd, "OpenWithAssumeFdOwnership", &handle,
+                                  static_cast<size_t>(length), 0));
+  CloseArchive(handle);
+  ASSERT_EQ(-1, lseek(fd, 0, SEEK_SET));
+  ASSERT_EQ(EBADF, errno);
+}
+
+TEST(ziparchive, OpenDoNotAssumeFdRangeOwnership) {
+  int fd = open((test_data_dir + "/" + kValidZip).c_str(), O_RDONLY | O_BINARY);
+  ASSERT_NE(-1, fd);
+  const off64_t length = lseek(fd, 0, SEEK_END);
+  ASSERT_NE(-1, length);
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveFdRange(fd, "OpenWithAssumeFdOwnership", &handle,
+                                  static_cast<size_t>(length), 0, false));
+  CloseArchive(handle);
+  ASSERT_EQ(0, lseek(fd, 0, SEEK_SET));
+  close(fd);
+}
+
 TEST(ziparchive, Iteration_std_string_view) {
   ZipArchiveHandle handle;
   ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
@@ -188,7 +217,7 @@
   void* iteration_cookie;
   ASSERT_EQ(0, StartIteration(handle, &iteration_cookie));
 
-  ZipEntry data;
+  ZipEntry64 data;
   std::vector<std::string_view> names;
   std::string_view name;
   while (Next(iteration_cookie, &data, &name) == 0) names.push_back(name);
@@ -201,6 +230,22 @@
   CloseArchive(handle);
 }
 
+static void AssertIterationNames(void* iteration_cookie,
+                                 const std::vector<std::string>& expected_names_sorted) {
+  ZipEntry64 data;
+  std::vector<std::string> names;
+  std::string_view name;
+  for (size_t i = 0; i < expected_names_sorted.size(); ++i) {
+    ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
+    names.push_back(std::string(name));
+  }
+  // End of iteration.
+  ASSERT_EQ(-1, Next(iteration_cookie, &data, &name));
+  // Assert that the names are as expected.
+  std::sort(names.begin(), names.end());
+  ASSERT_EQ(expected_names_sorted, names);
+}
+
 static void AssertIterationOrder(const std::string_view prefix, const std::string_view suffix,
                                  const std::vector<std::string>& expected_names_sorted) {
   ZipArchiveHandle handle;
@@ -208,23 +253,19 @@
 
   void* iteration_cookie;
   ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, prefix, suffix));
-
-  ZipEntry data;
-  std::vector<std::string> names;
-
-  std::string name;
-  for (size_t i = 0; i < expected_names_sorted.size(); ++i) {
-    ASSERT_EQ(0, Next(iteration_cookie, &data, &name));
-    names.push_back(name);
-  }
-
-  // End of iteration.
-  ASSERT_EQ(-1, Next(iteration_cookie, &data, &name));
+  AssertIterationNames(iteration_cookie, expected_names_sorted);
   CloseArchive(handle);
+}
 
-  // Assert that the names are as expected.
-  std::sort(names.begin(), names.end());
-  ASSERT_EQ(expected_names_sorted, names);
+static void AssertIterationOrderWithMatcher(std::function<bool(std::string_view)> matcher,
+                                            const std::vector<std::string>& expected_names_sorted) {
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
+
+  void* iteration_cookie;
+  ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, matcher));
+  AssertIterationNames(iteration_cookie, expected_names_sorted);
+  CloseArchive(handle);
 }
 
 TEST(ziparchive, Iteration) {
@@ -253,6 +294,30 @@
   AssertIterationOrder("b", ".txt", kExpectedMatchesSorted);
 }
 
+TEST(ziparchive, IterationWithAdditionalMatchesExactly) {
+  static const std::vector<std::string> kExpectedMatchesSorted = {"a.txt"};
+  auto matcher = [](std::string_view name) { return name == "a.txt"; };
+  AssertIterationOrderWithMatcher(matcher, kExpectedMatchesSorted);
+}
+
+TEST(ziparchive, IterationWithAdditionalMatchesWithSuffix) {
+  static const std::vector<std::string> kExpectedMatchesSorted = {"a.txt", "b.txt", "b/c.txt",
+                                                                  "b/d.txt"};
+  auto matcher = [](std::string_view name) {
+    return name == "a.txt" || android::base::EndsWith(name, ".txt");
+  };
+  AssertIterationOrderWithMatcher(matcher, kExpectedMatchesSorted);
+}
+
+TEST(ziparchive, IterationWithAdditionalMatchesWithPrefixAndSuffix) {
+  static const std::vector<std::string> kExpectedMatchesSorted = {"a.txt", "b/c.txt", "b/d.txt"};
+  auto matcher = [](std::string_view name) {
+    return name == "a.txt" ||
+           (android::base::EndsWith(name, ".txt") && android::base::StartsWith(name, "b/"));
+  };
+  AssertIterationOrderWithMatcher(matcher, kExpectedMatchesSorted);
+}
+
 TEST(ziparchive, IterationWithBadPrefixAndSuffix) {
   ZipArchiveHandle handle;
   ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
@@ -260,8 +325,8 @@
   void* iteration_cookie;
   ASSERT_EQ(0, StartIteration(handle, &iteration_cookie, "x", "y"));
 
-  ZipEntry data;
-  std::string name;
+  ZipEntry64 data;
+  std::string_view name;
 
   // End of iteration.
   ASSERT_EQ(-1, Next(iteration_cookie, &data, &name));
@@ -273,14 +338,14 @@
   ZipArchiveHandle handle;
   ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
 
-  ZipEntry data;
+  ZipEntry64 data;
   ASSERT_EQ(0, FindEntry(handle, "a.txt", &data));
 
   // Known facts about a.txt, from zipinfo -v.
   ASSERT_EQ(63, data.offset);
   ASSERT_EQ(kCompressDeflated, data.method);
-  ASSERT_EQ(static_cast<uint32_t>(17), data.uncompressed_length);
-  ASSERT_EQ(static_cast<uint32_t>(13), data.compressed_length);
+  ASSERT_EQ(17u, data.uncompressed_length);
+  ASSERT_EQ(13u, data.compressed_length);
   ASSERT_EQ(0x950821c5, data.crc32);
   ASSERT_EQ(static_cast<uint32_t>(0x438a8005), data.mod_time);
 
@@ -294,7 +359,7 @@
   ZipArchiveHandle handle;
   ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
 
-  ZipEntry data;
+  ZipEntry64 data;
   ASSERT_EQ(kInvalidEntryName, FindEntry(handle, "", &data));
 
   CloseArchive(handle);
@@ -305,7 +370,7 @@
   ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
 
   std::string very_long_name(65536, 'x');
-  ZipEntry data;
+  ZipEntry64 data;
   ASSERT_EQ(kInvalidEntryName, FindEntry(handle, very_long_name, &data));
 
   CloseArchive(handle);
@@ -318,8 +383,8 @@
   void* iteration_cookie;
   ASSERT_EQ(0, StartIteration(handle, &iteration_cookie));
 
-  std::string name;
-  ZipEntry data;
+  std::string_view name;
+  ZipEntry64 data;
 
   ASSERT_EQ(Next(iteration_cookie, &data, &name), 0);
   ASSERT_EQ(Next(iteration_cookie, &data, &name), 0);
@@ -327,14 +392,56 @@
   CloseArchive(handle);
 }
 
+TEST(ziparchive, OpenArchiveFdRange) {
+  TemporaryFile tmp_file;
+  ASSERT_NE(-1, tmp_file.fd);
+
+  const std::string leading_garbage(21, 'x');
+  ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, leading_garbage.c_str(),
+                                        leading_garbage.size()));
+
+  std::string valid_content;
+  ASSERT_TRUE(android::base::ReadFileToString(test_data_dir + "/" + kValidZip, &valid_content));
+  ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, valid_content.c_str(), valid_content.size()));
+
+  const std::string ending_garbage(42, 'x');
+  ASSERT_TRUE(android::base::WriteFully(tmp_file.fd, ending_garbage.c_str(),
+                                        ending_garbage.size()));
+
+  ZipArchiveHandle handle;
+  ASSERT_EQ(0, lseek(tmp_file.fd, 0, SEEK_SET));
+  ASSERT_EQ(0, OpenArchiveFdRange(tmp_file.fd, "OpenArchiveFdRange", &handle,
+                                  valid_content.size(),
+                                  static_cast<off64_t>(leading_garbage.size())));
+
+  // An entry that's deflated.
+  ZipEntry64 data;
+  ASSERT_EQ(0, FindEntry(handle, "a.txt", &data));
+  const auto a_size = static_cast<size_t>(data.uncompressed_length);
+  ASSERT_EQ(a_size, kATxtContents.size());
+  auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[a_size]);
+  ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer.get(), a_size));
+  ASSERT_EQ(0, memcmp(buffer.get(), kATxtContents.data(), a_size));
+
+  // An entry that's stored.
+  ASSERT_EQ(0, FindEntry(handle, "b.txt", &data));
+  const auto b_size = static_cast<size_t>(data.uncompressed_length);
+  ASSERT_EQ(b_size, kBTxtContents.size());
+  buffer = std::unique_ptr<uint8_t[]>(new uint8_t[b_size]);
+  ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer.get(), b_size));
+  ASSERT_EQ(0, memcmp(buffer.get(), kBTxtContents.data(), b_size));
+
+  CloseArchive(handle);
+}
+
 TEST(ziparchive, ExtractToMemory) {
   ZipArchiveHandle handle;
   ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
 
   // An entry that's deflated.
-  ZipEntry data;
+  ZipEntry64 data;
   ASSERT_EQ(0, FindEntry(handle, "a.txt", &data));
-  const uint32_t a_size = data.uncompressed_length;
+  const auto a_size = static_cast<size_t>(data.uncompressed_length);
   ASSERT_EQ(a_size, kATxtContents.size());
   uint8_t* buffer = new uint8_t[a_size];
   ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer, a_size));
@@ -343,7 +450,7 @@
 
   // An entry that's stored.
   ASSERT_EQ(0, FindEntry(handle, "b.txt", &data));
-  const uint32_t b_size = data.uncompressed_length;
+  const auto b_size = static_cast<size_t>(data.uncompressed_length);
   ASSERT_EQ(b_size, kBTxtContents.size());
   buffer = new uint8_t[b_size];
   ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer, b_size));
@@ -396,11 +503,14 @@
   ZipArchiveHandle handle;
   ASSERT_EQ(0, OpenArchiveFd(tmp_file.fd, "EmptyEntriesTest", &handle, false));
 
-  ZipEntry entry;
+  ZipEntry64 entry;
   ASSERT_EQ(0, FindEntry(handle, "empty.txt", &entry));
-  ASSERT_EQ(static_cast<uint32_t>(0), entry.uncompressed_length);
+  ASSERT_EQ(0u, entry.uncompressed_length);
+  // Extraction to a 1 byte buffer should succeed.
   uint8_t buffer[1];
   ASSERT_EQ(0, ExtractToMemory(handle, &entry, buffer, 1));
+  // Extraction to an empty buffer should succeed.
+  ASSERT_EQ(0, ExtractToMemory(handle, &entry, nullptr, 0));
 
   TemporaryFile tmp_output_file;
   ASSERT_NE(-1, tmp_output_file.fd);
@@ -419,7 +529,7 @@
   ZipArchiveHandle handle;
   ASSERT_EQ(0, OpenArchiveFd(tmp_file.fd, "EntryLargerThan32KTest", &handle, false));
 
-  ZipEntry entry;
+  ZipEntry64 entry;
   ASSERT_EQ(0, FindEntry(handle, kAbTxtName, &entry));
   ASSERT_EQ(kAbUncompressedSize, entry.uncompressed_length);
 
@@ -476,7 +586,7 @@
   ZipArchiveHandle handle;
   ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
 
-  ZipEntry entry;
+  ZipEntry64 entry;
   ASSERT_EQ(0, FindEntry(handle, "a.txt", &entry));
   ASSERT_EQ(0, ExtractEntryToFile(handle, &entry, tmp_file.fd));
 
@@ -487,9 +597,9 @@
   ASSERT_EQ(0, memcmp(read_buffer, data, data_size));
 
   // Assert that the remainder of the file contains the incompressed data.
-  std::vector<uint8_t> uncompressed_data(entry.uncompressed_length);
-  ASSERT_TRUE(
-      android::base::ReadFully(tmp_file.fd, uncompressed_data.data(), entry.uncompressed_length));
+  std::vector<uint8_t> uncompressed_data(static_cast<size_t>(entry.uncompressed_length));
+  ASSERT_TRUE(android::base::ReadFully(tmp_file.fd, uncompressed_data.data(),
+                                       static_cast<size_t>(entry.uncompressed_length)));
   ASSERT_EQ(0, memcmp(&uncompressed_data[0], kATxtContents.data(), kATxtContents.size()));
 
   // Assert that the total length of the file is sane
@@ -513,7 +623,7 @@
             OpenArchiveFromMemory(file_map->data(), file_map->size(), zip_path.c_str(), &handle));
 
   // Assert one entry can be found and extracted correctly.
-  ZipEntry binary_entry;
+  ZipEntry64 binary_entry;
   ASSERT_EQ(0, FindEntry(handle, "META-INF/com/google/android/update-binary", &binary_entry));
   TemporaryFile tmp_binary;
   ASSERT_NE(-1, tmp_binary.fd);
@@ -528,13 +638,13 @@
   if (raw) {
     stream.reset(ZipArchiveStreamEntry::CreateRaw(handle, *entry));
     if (entry->method == kCompressStored) {
-      read_data->resize(entry->uncompressed_length);
+      read_data->resize(static_cast<size_t>(entry->uncompressed_length));
     } else {
-      read_data->resize(entry->compressed_length);
+      read_data->resize(static_cast<size_t>(entry->compressed_length));
     }
   } else {
     stream.reset(ZipArchiveStreamEntry::Create(handle, *entry));
-    read_data->resize(entry->uncompressed_length);
+    read_data->resize(static_cast<size_t>(entry->uncompressed_length));
   }
   uint8_t* read_data_ptr = read_data->data();
   ASSERT_TRUE(stream.get() != nullptr);
@@ -574,7 +684,7 @@
   std::vector<uint8_t> read_data;
   ZipArchiveStreamTest(handle, entry_name, false, true, &entry, &read_data);
 
-  std::vector<uint8_t> cmp_data(entry.uncompressed_length);
+  std::vector<uint8_t> cmp_data(static_cast<size_t>(entry.uncompressed_length));
   ASSERT_EQ(entry.uncompressed_length, read_data.size());
   ASSERT_EQ(
       0, ExtractToMemory(handle, &entry, cmp_data.data(), static_cast<uint32_t>(cmp_data.size())));
@@ -634,8 +744,8 @@
 //       FileOutputStream fos = new
 //       FileOutputStream("/tmp/data_descriptor.zip");
 //       ZipOutputStream zos = new ZipOutputStream(fos);
-//       ZipEntry ze = new ZipEntry("name");
-//       ze.setMethod(ZipEntry.DEFLATED);
+//       ZipEntry64 ze = new ZipEntry64("name");
+//       ze.setMethod(ZipEntry64.DEFLATED);
 //       zos.putNextEntry(ze);
 //       zos.write("abdcdefghijk".getBytes());
 //       zos.closeEntry();
@@ -673,9 +783,9 @@
   // This function expects a variant of kDataDescriptorZipFile, for look for
   // an entry whose name is "name" and whose size is 12 (contents =
   // "abdcdefghijk").
-  ZipEntry entry;
+  ZipEntry64 entry;
   ASSERT_EQ(0, FindEntry(handle, "name", &entry));
-  ASSERT_EQ(static_cast<uint32_t>(12), entry.uncompressed_length);
+  ASSERT_EQ(12u, entry.uncompressed_length);
 
   entry_out->resize(12);
   (*error_code_out) = ExtractToMemory(handle, &entry, &((*entry_out)[0]), 12);
@@ -780,12 +890,12 @@
  public:
   VectorReader(const std::vector<uint8_t>& input) : Reader(), input_(input) {}
 
-  bool ReadAtOffset(uint8_t* buf, size_t len, uint32_t offset) const {
+  bool ReadAtOffset(uint8_t* buf, size_t len, off64_t offset) const {
     if ((offset + len) < input_.size()) {
       return false;
     }
 
-    memcpy(buf, &input_[offset], len);
+    memcpy(buf, &input_[static_cast<size_t>(offset)], len);
     return true;
   }
 
@@ -812,7 +922,7 @@
  public:
   BadReader() : Reader() {}
 
-  bool ReadAtOffset(uint8_t*, size_t, uint32_t) const { return false; }
+  bool ReadAtOffset(uint8_t*, size_t, off64_t) const { return false; }
 };
 
 class BadWriter : public zip_archive::Writer {
@@ -862,3 +972,356 @@
     ASSERT_EQ(0u, writer.GetOutput().size());
   }
 }
+
+// The class constructs a zipfile with zip64 format, and test the parsing logic.
+class Zip64ParseTest : public ::testing::Test {
+ protected:
+  struct LocalFileEntry {
+    std::vector<uint8_t> local_file_header;
+    std::string file_name;
+    std::vector<uint8_t> extended_field;
+    // Fake data to mimic the compressed bytes in the zipfile.
+    std::vector<uint8_t> compressed_bytes;
+    std::vector<uint8_t> data_descriptor;
+
+    size_t GetSize() const {
+      return local_file_header.size() + file_name.size() + extended_field.size() +
+             compressed_bytes.size() + data_descriptor.size();
+    }
+
+    void CopyToOutput(std::vector<uint8_t>* output) const {
+      std::copy(local_file_header.begin(), local_file_header.end(), std::back_inserter(*output));
+      std::copy(file_name.begin(), file_name.end(), std::back_inserter(*output));
+      std::copy(extended_field.begin(), extended_field.end(), std::back_inserter(*output));
+      std::copy(compressed_bytes.begin(), compressed_bytes.end(), std::back_inserter(*output));
+      std::copy(data_descriptor.begin(), data_descriptor.end(), std::back_inserter(*output));
+    }
+  };
+
+  struct CdRecordEntry {
+    std::vector<uint8_t> central_directory_record;
+    std::string file_name;
+    std::vector<uint8_t> extended_field;
+
+    size_t GetSize() const {
+      return central_directory_record.size() + file_name.size() + extended_field.size();
+    }
+
+    void CopyToOutput(std::vector<uint8_t>* output) const {
+      std::copy(central_directory_record.begin(), central_directory_record.end(),
+                std::back_inserter(*output));
+      std::copy(file_name.begin(), file_name.end(), std::back_inserter(*output));
+      std::copy(extended_field.begin(), extended_field.end(), std::back_inserter(*output));
+    }
+  };
+
+  static void ConstructLocalFileHeader(const std::string& name, std::vector<uint8_t>* output,
+                                       uint32_t uncompressed_size, uint32_t compressed_size) {
+    LocalFileHeader lfh = {};
+    lfh.lfh_signature = LocalFileHeader::kSignature;
+    lfh.compressed_size = compressed_size;
+    lfh.uncompressed_size = uncompressed_size;
+    lfh.file_name_length = static_cast<uint16_t>(name.size());
+    lfh.extra_field_length = 20;
+    *output = std::vector<uint8_t>(reinterpret_cast<uint8_t*>(&lfh),
+                                   reinterpret_cast<uint8_t*>(&lfh) + sizeof(LocalFileHeader));
+  }
+
+  // Put one zip64 extended info in the extended field.
+  static void ConstructExtendedField(const std::vector<uint64_t>& zip64_fields,
+                                     std::vector<uint8_t>* output) {
+    ASSERT_FALSE(zip64_fields.empty());
+    uint16_t data_size = 8 * static_cast<uint16_t>(zip64_fields.size());
+    std::vector<uint8_t> extended_field(data_size + 4);
+    android::base::put_unaligned(extended_field.data(), Zip64ExtendedInfo::kHeaderId);
+    android::base::put_unaligned(extended_field.data() + 2, data_size);
+    size_t offset = 4;
+    for (const auto& field : zip64_fields) {
+      android::base::put_unaligned(extended_field.data() + offset, field);
+      offset += 8;
+    }
+
+    *output = std::move(extended_field);
+  }
+
+  static void ConstructCentralDirectoryRecord(const std::string& name, uint32_t uncompressed_size,
+                                              uint32_t compressed_size, uint32_t local_offset,
+                                              std::vector<uint8_t>* output) {
+    CentralDirectoryRecord cdr = {};
+    cdr.record_signature = CentralDirectoryRecord::kSignature;
+    cdr.compressed_size = uncompressed_size;
+    cdr.uncompressed_size = compressed_size;
+    cdr.file_name_length = static_cast<uint16_t>(name.size());
+    cdr.extra_field_length = local_offset == UINT32_MAX ? 28 : 20;
+    cdr.local_file_header_offset = local_offset;
+    *output =
+        std::vector<uint8_t>(reinterpret_cast<uint8_t*>(&cdr),
+                             reinterpret_cast<uint8_t*>(&cdr) + sizeof(CentralDirectoryRecord));
+  }
+
+  // Add an entry to the zipfile, construct the corresponding local header and cd entry.
+  void AddEntry(const std::string& name, const std::vector<uint8_t>& content,
+                bool uncompressed_size_in_extended, bool compressed_size_in_extended,
+                bool local_offset_in_extended, bool include_data_descriptor = false) {
+    auto uncompressed_size = static_cast<uint32_t>(content.size());
+    auto compressed_size = static_cast<uint32_t>(content.size());
+    uint32_t local_file_header_offset = 0;
+    std::for_each(file_entries_.begin(), file_entries_.end(),
+                  [&local_file_header_offset](const LocalFileEntry& file_entry) {
+                    local_file_header_offset += file_entry.GetSize();
+                  });
+
+    std::vector<uint64_t> zip64_fields;
+    if (uncompressed_size_in_extended) {
+      zip64_fields.push_back(uncompressed_size);
+      uncompressed_size = UINT32_MAX;
+    }
+    if (compressed_size_in_extended) {
+      zip64_fields.push_back(compressed_size);
+      compressed_size = UINT32_MAX;
+    }
+    LocalFileEntry local_entry = {
+        .local_file_header = {},
+        .file_name = name,
+        .extended_field = {},
+        .compressed_bytes = content,
+    };
+    ConstructLocalFileHeader(name, &local_entry.local_file_header, uncompressed_size,
+                             compressed_size);
+    ConstructExtendedField(zip64_fields, &local_entry.extended_field);
+    if (include_data_descriptor) {
+      size_t descriptor_size = compressed_size_in_extended ? 24 : 16;
+      local_entry.data_descriptor.resize(descriptor_size);
+      uint8_t* write_ptr = local_entry.data_descriptor.data();
+      EmitUnaligned<uint32_t>(&write_ptr, DataDescriptor::kOptSignature);
+      EmitUnaligned<uint32_t>(&write_ptr, 0 /* crc */);
+      if (compressed_size_in_extended) {
+        EmitUnaligned<uint64_t>(&write_ptr, compressed_size_in_extended);
+        EmitUnaligned<uint64_t>(&write_ptr, uncompressed_size_in_extended);
+      } else {
+        EmitUnaligned<uint32_t>(&write_ptr, compressed_size_in_extended);
+        EmitUnaligned<uint32_t>(&write_ptr, uncompressed_size_in_extended);
+      }
+    }
+
+    file_entries_.push_back(std::move(local_entry));
+
+    if (local_offset_in_extended) {
+      zip64_fields.push_back(local_file_header_offset);
+      local_file_header_offset = UINT32_MAX;
+    }
+    CdRecordEntry cd_entry = {
+        .central_directory_record = {},
+        .file_name = name,
+        .extended_field = {},
+    };
+    ConstructCentralDirectoryRecord(name, uncompressed_size, compressed_size,
+                                    local_file_header_offset, &cd_entry.central_directory_record);
+    ConstructExtendedField(zip64_fields, &cd_entry.extended_field);
+    cd_entries_.push_back(std::move(cd_entry));
+  }
+
+  void ConstructEocd() {
+    ASSERT_EQ(file_entries_.size(), cd_entries_.size());
+    Zip64EocdRecord zip64_eocd = {};
+    zip64_eocd.record_signature = Zip64EocdRecord::kSignature;
+    zip64_eocd.num_records = file_entries_.size();
+    zip64_eocd.cd_size = 0;
+    std::for_each(
+        cd_entries_.begin(), cd_entries_.end(),
+        [&zip64_eocd](const CdRecordEntry& cd_entry) { zip64_eocd.cd_size += cd_entry.GetSize(); });
+    zip64_eocd.cd_start_offset = 0;
+    std::for_each(file_entries_.begin(), file_entries_.end(),
+                  [&zip64_eocd](const LocalFileEntry& file_entry) {
+                    zip64_eocd.cd_start_offset += file_entry.GetSize();
+                  });
+    zip64_eocd_record_ =
+        std::vector<uint8_t>(reinterpret_cast<uint8_t*>(&zip64_eocd),
+                             reinterpret_cast<uint8_t*>(&zip64_eocd) + sizeof(Zip64EocdRecord));
+
+    Zip64EocdLocator zip64_locator = {};
+    zip64_locator.locator_signature = Zip64EocdLocator::kSignature;
+    zip64_locator.zip64_eocd_offset = zip64_eocd.cd_start_offset + zip64_eocd.cd_size;
+    zip64_eocd_locator_ =
+        std::vector<uint8_t>(reinterpret_cast<uint8_t*>(&zip64_locator),
+                             reinterpret_cast<uint8_t*>(&zip64_locator) + sizeof(Zip64EocdLocator));
+
+    EocdRecord eocd = {};
+    eocd.eocd_signature = EocdRecord::kSignature,
+    eocd.num_records = file_entries_.size() > UINT16_MAX
+                           ? UINT16_MAX
+                           : static_cast<uint16_t>(file_entries_.size());
+    eocd.cd_size = UINT32_MAX;
+    eocd.cd_start_offset = UINT32_MAX;
+    eocd_record_ = std::vector<uint8_t>(reinterpret_cast<uint8_t*>(&eocd),
+                                        reinterpret_cast<uint8_t*>(&eocd) + sizeof(EocdRecord));
+  }
+
+  // Concatenate all the local file entries, cd entries, and eocd metadata.
+  void ConstructZipFile() {
+    for (const auto& file_entry : file_entries_) {
+      file_entry.CopyToOutput(&zip_content_);
+    }
+    for (const auto& cd_entry : cd_entries_) {
+      cd_entry.CopyToOutput(&zip_content_);
+    }
+    std::copy(zip64_eocd_record_.begin(), zip64_eocd_record_.end(),
+              std::back_inserter(zip_content_));
+    std::copy(zip64_eocd_locator_.begin(), zip64_eocd_locator_.end(),
+              std::back_inserter(zip_content_));
+    std::copy(eocd_record_.begin(), eocd_record_.end(), std::back_inserter(zip_content_));
+  }
+
+  std::vector<uint8_t> zip_content_;
+
+  std::vector<LocalFileEntry> file_entries_;
+  std::vector<CdRecordEntry> cd_entries_;
+  std::vector<uint8_t> zip64_eocd_record_;
+  std::vector<uint8_t> zip64_eocd_locator_;
+  std::vector<uint8_t> eocd_record_;
+};
+
+TEST_F(Zip64ParseTest, openFile) {
+  AddEntry("a.txt", std::vector<uint8_t>(100, 'a'), true, true, false);
+  ConstructEocd();
+  ConstructZipFile();
+
+  ZipArchiveHandle handle;
+  ASSERT_EQ(
+      0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle));
+  CloseArchive(handle);
+}
+
+TEST_F(Zip64ParseTest, openFilelocalOffsetInExtendedField) {
+  AddEntry("a.txt", std::vector<uint8_t>(100, 'a'), true, true, true);
+  AddEntry("b.txt", std::vector<uint8_t>(200, 'b'), true, true, true);
+  ConstructEocd();
+  ConstructZipFile();
+
+  ZipArchiveHandle handle;
+  ASSERT_EQ(
+      0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle));
+  CloseArchive(handle);
+}
+
+TEST_F(Zip64ParseTest, openFileCompressedNotInExtendedField) {
+  AddEntry("a.txt", std::vector<uint8_t>(100, 'a'), true, false, false);
+  ConstructEocd();
+  ConstructZipFile();
+
+  ZipArchiveHandle handle;
+  // Zip64 extended fields must include both uncompressed and compressed size.
+  ASSERT_NE(
+      0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle));
+  CloseArchive(handle);
+}
+
+TEST_F(Zip64ParseTest, findEntry) {
+  AddEntry("a.txt", std::vector<uint8_t>(200, 'a'), true, true, true);
+  AddEntry("b.txt", std::vector<uint8_t>(300, 'b'), true, true, false);
+  ConstructEocd();
+  ConstructZipFile();
+
+  ZipArchiveHandle handle;
+  ASSERT_EQ(
+      0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle));
+  ZipEntry64 entry;
+  ASSERT_EQ(0, FindEntry(handle, "a.txt", &entry));
+  ASSERT_EQ(200, entry.uncompressed_length);
+  ASSERT_EQ(200, entry.compressed_length);
+
+  ASSERT_EQ(0, FindEntry(handle, "b.txt", &entry));
+  ASSERT_EQ(300, entry.uncompressed_length);
+  ASSERT_EQ(300, entry.compressed_length);
+  CloseArchive(handle);
+}
+
+TEST_F(Zip64ParseTest, openFileIncorrectDataSizeInLocalExtendedField) {
+  AddEntry("a.txt", std::vector<uint8_t>(100, 'a'), true, true, false);
+  ASSERT_EQ(1, file_entries_.size());
+  auto& extended_field = file_entries_[0].extended_field;
+  // data size exceeds the extended field size in local header.
+  android::base::put_unaligned<uint16_t>(extended_field.data() + 2, 30);
+  ConstructEocd();
+  ConstructZipFile();
+
+  ZipArchiveHandle handle;
+  ASSERT_EQ(
+      0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle));
+  ZipEntry64 entry;
+  ASSERT_NE(0, FindEntry(handle, "a.txt", &entry));
+
+  CloseArchive(handle);
+}
+
+TEST_F(Zip64ParseTest, iterates) {
+  std::set<std::string_view> names{"a.txt", "b.txt", "c.txt", "d.txt", "e.txt"};
+  for (const auto& name : names) {
+    AddEntry(std::string(name), std::vector<uint8_t>(100, name[0]), true, true, true);
+  }
+  ConstructEocd();
+  ConstructZipFile();
+
+  ZipArchiveHandle handle;
+  ASSERT_EQ(
+      0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle));
+
+  void* iteration_cookie;
+  ASSERT_EQ(0, StartIteration(handle, &iteration_cookie));
+  std::set<std::string_view> result;
+  std::string_view name;
+  ZipEntry64 entry;
+  while (Next(iteration_cookie, &entry, &name) == 0) result.emplace(name);
+  ASSERT_EQ(names, result);
+
+  CloseArchive(handle);
+}
+
+TEST_F(Zip64ParseTest, zip64EocdWrongLocatorOffset) {
+  AddEntry("a.txt", std::vector<uint8_t>(1, 'a'), true, true, true);
+  ConstructEocd();
+  zip_content_.resize(20, 'a');
+  std::copy(zip64_eocd_locator_.begin(), zip64_eocd_locator_.end(),
+            std::back_inserter(zip_content_));
+  std::copy(eocd_record_.begin(), eocd_record_.end(), std::back_inserter(zip_content_));
+
+  ZipArchiveHandle handle;
+  ASSERT_NE(
+      0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle));
+  CloseArchive(handle);
+}
+
+TEST_F(Zip64ParseTest, extract) {
+  std::vector<uint8_t> content(200, 'a');
+  AddEntry("a.txt", content, true, true, true);
+  ConstructEocd();
+  ConstructZipFile();
+
+  ZipArchiveHandle handle;
+  ASSERT_EQ(
+      0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle));
+  ZipEntry64 entry;
+  ASSERT_EQ(0, FindEntry(handle, "a.txt", &entry));
+
+  VectorWriter writer;
+  ASSERT_EQ(0, ExtractToWriter(handle, &entry, &writer));
+  ASSERT_EQ(content, writer.GetOutput());
+}
+
+TEST_F(Zip64ParseTest, extractWithDataDescriptor) {
+  std::vector<uint8_t> content(300, 'b');
+  AddEntry("a.txt", std::vector<uint8_t>(200, 'a'), true, true, true);
+  AddEntry("b.txt", content, true, true, true, true /* data descriptor */);
+  ConstructEocd();
+  ConstructZipFile();
+
+  ZipArchiveHandle handle;
+  ASSERT_EQ(
+      0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle));
+  ZipEntry64 entry;
+  ASSERT_EQ(0, FindEntry(handle, "b.txt", &entry));
+
+  VectorWriter writer;
+  ASSERT_EQ(0, ExtractToWriter(handle, &entry, &writer));
+  ASSERT_EQ(content, writer.GetOutput());
+}
diff --git a/libziparchive/zip_error.cpp b/libziparchive/zip_error.cpp
new file mode 100644
index 0000000..14e49bb
--- /dev/null
+++ b/libziparchive/zip_error.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#include "zip_error.h"
+
+#include <android-base/macros.h>
+
+static const char* kErrorMessages[] = {
+    "Success",
+    "Iteration ended",
+    "Zlib error",
+    "Invalid file",
+    "Invalid handle",
+    "Duplicate entries in archive",
+    "Empty archive",
+    "Entry not found",
+    "Invalid offset",
+    "Inconsistent information",
+    "Invalid entry name",
+    "I/O error",
+    "File mapping failed",
+    "Allocation failed",
+    "Unsupported zip entry size",
+};
+
+const char* ErrorCodeString(int32_t error_code) {
+  // Make sure that the number of entries in kErrorMessages and the ZipError
+  // enum match.
+  static_assert((-kLastErrorCode + 1) == arraysize(kErrorMessages),
+                "(-kLastErrorCode + 1) != arraysize(kErrorMessages)");
+
+  const uint32_t idx = -error_code;
+  if (idx < arraysize(kErrorMessages)) {
+    return kErrorMessages[idx];
+  }
+
+  return "Unknown return code";
+}
diff --git a/libziparchive/zip_error.h b/libziparchive/zip_error.h
index 44d7221..3d7285d 100644
--- a/libziparchive/zip_error.h
+++ b/libziparchive/zip_error.h
@@ -18,23 +18,6 @@
 
 #include <stdint.h>
 
-static const char* kErrorMessages[] = {
-    "Success",
-    "Iteration ended",
-    "Zlib error",
-    "Invalid file",
-    "Invalid handle",
-    "Duplicate entries in archive",
-    "Empty archive",
-    "Entry not found",
-    "Invalid offset",
-    "Inconsistent information",
-    "Invalid entry name",
-    "I/O error",
-    "File mapping failed",
-    "Allocation failed",
-};
-
 enum ZipError : int32_t {
   kSuccess = 0,
 
@@ -83,5 +66,9 @@
   // An allocation failed.
   kAllocationFailed = -13,
 
-  kLastErrorCode = kAllocationFailed,
+  // The compressed or uncompressed size is larger than UINT32_MAX and
+  // doesn't fit into the 32 bits zip entry.
+  kUnsupportedEntrySize = -14,
+
+  kLastErrorCode = kUnsupportedEntrySize,
 };
diff --git a/libziparchive/zip_writer.cc b/libziparchive/zip_writer.cc
index 67279a6..25b1da4 100644
--- a/libziparchive/zip_writer.cc
+++ b/libziparchive/zip_writer.cc
@@ -475,19 +475,16 @@
   if (ShouldUseDataDescriptor()) {
     // Some versions of ZIP don't allow STORED data to have a trailing DataDescriptor.
     // If this file is not seekable, or if the data is compressed, write a DataDescriptor.
-    const uint32_t sig = DataDescriptor::kOptSignature;
-    if (fwrite(&sig, sizeof(sig), 1, file_) != 1) {
+    // We haven't supported zip64 format yet. Write both uncompressed size and compressed
+    // size as uint32_t.
+    std::vector<uint32_t> dataDescriptor = {
+        DataDescriptor::kOptSignature, current_file_entry_.crc32,
+        current_file_entry_.compressed_size, current_file_entry_.uncompressed_size};
+    if (fwrite(dataDescriptor.data(), dataDescriptor.size() * sizeof(uint32_t), 1, file_) != 1) {
       return HandleError(kIoError);
     }
 
-    DataDescriptor dd = {};
-    dd.crc32 = current_file_entry_.crc32;
-    dd.compressed_size = current_file_entry_.compressed_size;
-    dd.uncompressed_size = current_file_entry_.uncompressed_size;
-    if (fwrite(&dd, sizeof(dd), 1, file_) != 1) {
-      return HandleError(kIoError);
-    }
-    current_offset_ += sizeof(DataDescriptor::kOptSignature) + sizeof(dd);
+    current_offset_ += sizeof(uint32_t) * dataDescriptor.size();
   } else {
     // Seek back to the header and rewrite to include the size.
     if (fseeko(file_, current_file_entry_.local_file_header_offset, SEEK_SET) != 0) {
diff --git a/libziparchive/ziptool.cpp b/libziparchive/ziptool.cpp
index dd42e90..a261535 100644
--- a/libziparchive/ziptool.cpp
+++ b/libziparchive/ziptool.cpp
@@ -133,8 +133,8 @@
     if (!flag_1 && includes.empty() && excludes.empty()) {
       ZipArchiveInfo info{GetArchiveInfo(zah)};
       printf("Archive:  %s\n", archive_name);
-      printf("Zip file size: %" PRId64 " bytes, number of entries: %zu\n", info.archive_size,
-             info.entry_count);
+      printf("Zip file size: %" PRId64 " bytes, number of entries: %" PRIu64 "\n",
+             info.archive_size, info.entry_count);
     }
   }
 }
@@ -193,21 +193,25 @@
   }
 }
 
-static void ExtractToPipe(ZipArchiveHandle zah, ZipEntry& entry, const std::string& name) {
+static void ExtractToPipe(ZipArchiveHandle zah, const ZipEntry64& entry, const std::string& name) {
   // We need to extract to memory because ExtractEntryToFile insists on
   // being able to seek and truncate, and you can't do that with stdout.
-  uint8_t* buffer = new uint8_t[entry.uncompressed_length];
-  int err = ExtractToMemory(zah, &entry, buffer, entry.uncompressed_length);
+  if (entry.uncompressed_length > SIZE_MAX) {
+    die(0, "entry size %" PRIu64 " is too large to extract.", entry.uncompressed_length);
+  }
+  auto uncompressed_length = static_cast<size_t>(entry.uncompressed_length);
+  uint8_t* buffer = new uint8_t[uncompressed_length];
+  int err = ExtractToMemory(zah, &entry, buffer, uncompressed_length);
   if (err < 0) {
     die(0, "failed to extract %s: %s", name.c_str(), ErrorCodeString(err));
   }
-  if (!android::base::WriteFully(1, buffer, entry.uncompressed_length)) {
+  if (!android::base::WriteFully(1, buffer, uncompressed_length)) {
     die(errno, "failed to write %s to stdout", name.c_str());
   }
   delete[] buffer;
 }
 
-static void ExtractOne(ZipArchiveHandle zah, ZipEntry& entry, const std::string& name) {
+static void ExtractOne(ZipArchiveHandle zah, const ZipEntry64& entry, const std::string& name) {
   // Bad filename?
   if (StartsWith(name, "/") || StartsWith(name, "../") || name.find("/../") != std::string::npos) {
     die(0, "bad filename %s", name.c_str());
@@ -253,22 +257,22 @@
   close(fd);
 }
 
-static void ListOne(const ZipEntry& entry, const std::string& name) {
+static void ListOne(const ZipEntry64& entry, const std::string& name) {
   tm t = entry.GetModificationTime();
   char time[32];
   snprintf(time, sizeof(time), "%04d-%02d-%02d %02d:%02d", t.tm_year + 1900, t.tm_mon + 1,
            t.tm_mday, t.tm_hour, t.tm_min);
   if (flag_v) {
-    printf("%8d  %s  %7d %3.0f%% %s %08x  %s\n", entry.uncompressed_length,
+    printf("%8" PRIu64 "  %s %8" PRIu64 " %3.0f%% %s %08x  %s\n", entry.uncompressed_length,
            (entry.method == kCompressStored) ? "Stored" : "Defl:N", entry.compressed_length,
            CompressionRatio(entry.uncompressed_length, entry.compressed_length), time, entry.crc32,
            name.c_str());
   } else {
-    printf("%9d  %s   %s\n", entry.uncompressed_length, time, name.c_str());
+    printf("%9" PRIu64 "  %s   %s\n", entry.uncompressed_length, time, name.c_str());
   }
 }
 
-static void InfoOne(const ZipEntry& entry, const std::string& name) {
+static void InfoOne(const ZipEntry64& entry, const std::string& name) {
   if (flag_1) {
     // "android-ndk-r19b/sources/android/NOTICE"
     printf("%s\n", name.c_str());
@@ -323,12 +327,12 @@
            t.tm_mday, t.tm_hour, t.tm_min);
 
   // "-rw-r--r--  3.0 unx      577 t- defX 19-Feb-12 16:09 android-ndk-r19b/sources/android/NOTICE"
-  printf("%s %2d.%d %s %8d %c%c %s %s %s\n", mode, version / 10, version % 10, src_fs,
+  printf("%s %2d.%d %s %8" PRIu64 " %c%c %s %s %s\n", mode, version / 10, version % 10, src_fs,
          entry.uncompressed_length, entry.is_text ? 't' : 'b',
          entry.has_data_descriptor ? 'X' : 'x', method, time, name.c_str());
 }
 
-static void ProcessOne(ZipArchiveHandle zah, ZipEntry& entry, const std::string& name) {
+static void ProcessOne(ZipArchiveHandle zah, const ZipEntry64& entry, const std::string& name) {
   if (role == kUnzip) {
     if (flag_l || flag_v) {
       // -l or -lv or -lq or -v.
@@ -361,7 +365,7 @@
     die(0, "couldn't iterate %s: %s", archive_name, ErrorCodeString(err));
   }
 
-  ZipEntry entry;
+  ZipEntry64 entry;
   std::string name;
   while ((err = Next(cookie, &entry, &name)) >= 0) {
     if (ShouldInclude(name)) ProcessOne(zah, entry, name);
diff --git a/logcat/event.logtags b/logcat/event.logtags
index 3a1d36f..56a670a 100644
--- a/logcat/event.logtags
+++ b/logcat/event.logtags
@@ -145,6 +145,8 @@
 # libcore failure logging
 90100 exp_det_cert_pin_failure (certs|4)
 
+# 150000 - 160000 reserved for Android Automotive builds
+
 1397638484 snet_event_log (subtag|3) (uid|1) (message|3)
 
 # for events that go to stats log buffer
diff --git a/logcat/logcat.cpp b/logcat/logcat.cpp
index 76a970f..b065855 100644
--- a/logcat/logcat.cpp
+++ b/logcat/logcat.cpp
@@ -513,7 +513,7 @@
     unsigned long setLogSize = 0;
     const char* setPruneList = nullptr;
     const char* setId = nullptr;
-    int mode = ANDROID_LOG_RDONLY;
+    int mode = 0;
     std::string forceFilters;
     size_t tail_lines = 0;
     log_time tail_time(log_time::EPOCH);
@@ -591,8 +591,7 @@
                     break;
                 }
                 if (long_options[option_index].name == wrap_str) {
-                    mode |= ANDROID_LOG_WRAP | ANDROID_LOG_RDONLY |
-                            ANDROID_LOG_NONBLOCK;
+                    mode |= ANDROID_LOG_WRAP | ANDROID_LOG_NONBLOCK;
                     // ToDo: implement API that supports setting a wrap timeout
                     size_t dummy = ANDROID_LOG_WRAP_DEFAULT_TIMEOUT;
                     if (optarg && (!ParseUint(optarg, &dummy) || dummy < 1)) {
@@ -626,21 +625,19 @@
 
             case 'c':
                 clearLog = true;
-                mode |= ANDROID_LOG_WRONLY;
                 break;
 
             case 'L':
-                mode |= ANDROID_LOG_RDONLY | ANDROID_LOG_PSTORE |
-                        ANDROID_LOG_NONBLOCK;
+                mode |= ANDROID_LOG_PSTORE | ANDROID_LOG_NONBLOCK;
                 break;
 
             case 'd':
-                mode |= ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK;
+                mode |= ANDROID_LOG_NONBLOCK;
                 break;
 
             case 't':
                 got_t = true;
-                mode |= ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK;
+                mode |= ANDROID_LOG_NONBLOCK;
                 FALLTHROUGH_INTENDED;
             case 'T':
                 if (strspn(optarg, "0123456789") != strlen(optarg)) {
diff --git a/logd/FlushCommand.cpp b/logd/FlushCommand.cpp
index bd17555..0845504 100644
--- a/logd/FlushCommand.cpp
+++ b/logd/FlushCommand.cpp
@@ -49,18 +49,8 @@
                 return;
             }
             if (entry->mTimeout.tv_sec || entry->mTimeout.tv_nsec) {
-                if (mReader.logbuf().isMonotonic()) {
-                    LogTimeEntry::unlock();
-                    return;
-                }
-                // If the user changes the time in a gross manner that
-                // invalidates the timeout, fall through and trigger.
-                log_time now(CLOCK_REALTIME);
-                if (((entry->mEnd + entry->mTimeout) > now) &&
-                    (now > entry->mEnd)) {
-                    LogTimeEntry::unlock();
-                    return;
-                }
+                LogTimeEntry::unlock();
+                return;
             }
             entry->triggerReader_Locked();
             LogTimeEntry::unlock();
diff --git a/logd/FlushCommand.h b/logd/FlushCommand.h
index ceaf393..a69d439 100644
--- a/logd/FlushCommand.h
+++ b/logd/FlushCommand.h
@@ -16,7 +16,7 @@
 #ifndef _FLUSH_COMMAND_H
 #define _FLUSH_COMMAND_H
 
-#include <private/android_logger.h>
+#include <android/log.h>
 #include <sysutils/SocketClientCommand.h>
 
 class LogBufferElement;
diff --git a/logd/LogBuffer.cpp b/logd/LogBuffer.cpp
index 1cf2061..1f8ad05 100644
--- a/logd/LogBuffer.cpp
+++ b/logd/LogBuffer.cpp
@@ -27,6 +27,7 @@
 #include <unistd.h>
 
 #include <unordered_map>
+#include <utility>
 
 #include <cutils/properties.h>
 #include <private/android_logger.h>
@@ -43,8 +44,6 @@
 // Default
 #define log_buffer_size(id) mMaxSize[id]
 
-const log_time LogBuffer::pruneMargin(3, 0);
-
 void LogBuffer::init() {
     log_id_for_each(i) {
         mLastSet[i] = false;
@@ -390,59 +389,7 @@
 
 // assumes LogBuffer::wrlock() held, owns elem, look after garbage collection
 void LogBuffer::log(LogBufferElement* elem) {
-    // cap on how far back we will sort in-place, otherwise append
-    static uint32_t too_far_back = 5;  // five seconds
-    // Insert elements in time sorted order if possible
-    //  NB: if end is region locked, place element at end of list
-    LogBufferElementCollection::iterator it = mLogElements.end();
-    LogBufferElementCollection::iterator last = it;
-    if (__predict_true(it != mLogElements.begin())) --it;
-    if (__predict_false(it == mLogElements.begin()) ||
-        __predict_true((*it)->getRealTime() <= elem->getRealTime()) ||
-        __predict_false((((*it)->getRealTime().tv_sec - too_far_back) >
-                         elem->getRealTime().tv_sec) &&
-                        (elem->getLogId() != LOG_ID_KERNEL) &&
-                        ((*it)->getLogId() != LOG_ID_KERNEL))) {
-        mLogElements.push_back(elem);
-    } else {
-        log_time end(log_time::EPOCH);
-        bool end_set = false;
-        bool end_always = false;
-
-        LogTimeEntry::rdlock();
-
-        LastLogTimes::iterator times = mTimes.begin();
-        while (times != mTimes.end()) {
-            LogTimeEntry* entry = times->get();
-            if (!entry->mNonBlock) {
-                end_always = true;
-                break;
-            }
-            // it passing mEnd is blocked by the following checks.
-            if (!end_set || (end <= entry->mEnd)) {
-                end = entry->mEnd;
-                end_set = true;
-            }
-            times++;
-        }
-
-        if (end_always || (end_set && (end > (*it)->getRealTime()))) {
-            mLogElements.push_back(elem);
-        } else {
-            // should be short as timestamps are localized near end()
-            do {
-                last = it;
-                if (__predict_false(it == mLogElements.begin())) {
-                    break;
-                }
-                --it;
-            } while (((*it)->getRealTime() > elem->getRealTime()) &&
-                     (!end_set || (end <= (*it)->getRealTime())));
-            mLogElements.insert(last, elem);
-        }
-        LogTimeEntry::unlock();
-    }
-
+    mLogElements.push_back(elem);
     stats.add(elem);
     maybePrune(elem->getLogId());
 }
@@ -614,12 +561,11 @@
     }
 
     void clear(LogBufferElement* element) {
-        log_time current =
-            element->getRealTime() - log_time(EXPIRE_RATELIMIT, 0);
+        uint64_t current = element->getRealTime().nsec() - (EXPIRE_RATELIMIT * NS_PER_SEC);
         for (LogBufferElementMap::iterator it = map.begin(); it != map.end();) {
             LogBufferElement* mapElement = it->second;
-            if ((mapElement->getDropped() >= EXPIRE_THRESHOLD) &&
-                (current > mapElement->getRealTime())) {
+            if (mapElement->getDropped() >= EXPIRE_THRESHOLD &&
+                current > mapElement->getRealTime().nsec()) {
                 it = map.erase(it);
             } else {
                 ++it;
@@ -628,16 +574,6 @@
     }
 };
 
-// Determine if watermark is within pruneMargin + 1s from the end of the list,
-// the caller will use this result to set an internal busy flag indicating
-// the prune operation could not be completed because a reader is blocking
-// the request.
-bool LogBuffer::isBusy(log_time watermark) {
-    LogBufferElementCollection::iterator ei = mLogElements.end();
-    --ei;
-    return watermark < ((*ei)->getRealTime() - pruneMargin - log_time(1, 0));
-}
-
 // If the selected reader is blocking our pruning progress, decide on
 // what kind of mitigation is necessary to unblock the situation.
 void LogBuffer::kickMe(LogTimeEntry* me, log_id_t id, unsigned long pruneRows) {
@@ -726,8 +662,6 @@
         }
         times++;
     }
-    log_time watermark(log_time::tv_sec_max, log_time::tv_nsec_max);
-    if (oldest) watermark = oldest->mStart - pruneMargin;
 
     LogBufferElementCollection::iterator it;
 
@@ -749,9 +683,9 @@
                 mLastSet[id] = true;
             }
 
-            if (oldest && (watermark <= element->getRealTime())) {
-                busy = isBusy(watermark);
-                if (busy) kickMe(oldest, id, pruneRows);
+            if (oldest && oldest->mStart <= element->getSequence()) {
+                busy = true;
+                kickMe(oldest, id, pruneRows);
                 break;
             }
 
@@ -837,8 +771,8 @@
         while (it != mLogElements.end()) {
             LogBufferElement* element = *it;
 
-            if (oldest && (watermark <= element->getRealTime())) {
-                busy = isBusy(watermark);
+            if (oldest && oldest->mStart <= element->getSequence()) {
+                busy = true;
                 // Do not let chatty eliding trigger any reader mitigation
                 break;
             }
@@ -989,9 +923,9 @@
             mLastSet[id] = true;
         }
 
-        if (oldest && (watermark <= element->getRealTime())) {
-            busy = isBusy(watermark);
-            if (!whitelist && busy) kickMe(oldest, id, pruneRows);
+        if (oldest && oldest->mStart <= element->getSequence()) {
+            busy = true;
+            if (!whitelist) kickMe(oldest, id, pruneRows);
             break;
         }
 
@@ -1022,9 +956,9 @@
                 mLastSet[id] = true;
             }
 
-            if (oldest && (watermark <= element->getRealTime())) {
-                busy = isBusy(watermark);
-                if (busy) kickMe(oldest, id, pruneRows);
+            if (oldest && oldest->mStart <= element->getSequence()) {
+                busy = true;
+                kickMe(oldest, id, pruneRows);
                 break;
             }
 
@@ -1111,60 +1045,36 @@
     return retval;
 }
 
-log_time LogBuffer::flushTo(SocketClient* reader, const log_time& start,
-                            pid_t* lastTid, bool privileged, bool security,
-                            int (*filter)(const LogBufferElement* element,
-                                          void* arg),
-                            void* arg) {
+uint64_t LogBuffer::flushTo(SocketClient* reader, uint64_t start, pid_t* lastTid, bool privileged,
+                            bool security,
+                            int (*filter)(const LogBufferElement* element, void* arg), void* arg) {
     LogBufferElementCollection::iterator it;
     uid_t uid = reader->getUid();
 
     rdlock();
 
-    if (start == log_time::EPOCH) {
+    if (start <= 1) {
         // client wants to start from the beginning
         it = mLogElements.begin();
     } else {
-        // Cap to 300 iterations we look back for out-of-order entries.
-        size_t count = 300;
-
         // Client wants to start from some specified time. Chances are
         // we are better off starting from the end of the time sorted list.
-        LogBufferElementCollection::iterator last;
-        for (last = it = mLogElements.end(); it != mLogElements.begin();
+        for (it = mLogElements.end(); it != mLogElements.begin();
              /* do nothing */) {
             --it;
             LogBufferElement* element = *it;
-            if (element->getRealTime() > start) {
-                last = it;
-            } else if (element->getRealTime() == start) {
-                last = ++it;
-                break;
-            } else if (!--count) {
+            if (element->getSequence() <= start) {
+                it++;
                 break;
             }
         }
-        it = last;
     }
 
-    log_time curr = start;
+    uint64_t curr = start;
 
-    LogBufferElement* lastElement = nullptr;  // iterator corruption paranoia
-    static const size_t maxSkip = 4194304;    // maximum entries to skip
-    size_t skip = maxSkip;
     for (; it != mLogElements.end(); ++it) {
         LogBufferElement* element = *it;
 
-        if (!--skip) {
-            android::prdebug("reader.per: too many elements skipped");
-            break;
-        }
-        if (element == lastElement) {
-            android::prdebug("reader.per: identical elements");
-            break;
-        }
-        lastElement = element;
-
         if (!privileged && (element->getUid() != uid)) {
             continue;
         }
@@ -1205,7 +1115,6 @@
             return curr;
         }
 
-        skip = maxSkip;
         rdlock();
     }
     unlock();
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
index c2d5b97..16225a5 100644
--- a/logd/LogBuffer.h
+++ b/logd/LogBuffer.h
@@ -118,11 +118,10 @@
     // lastTid is an optional context to help detect if the last previous
     // valid message was from the same source so we can differentiate chatty
     // filter types (identical or expired)
-    log_time flushTo(SocketClient* writer, const log_time& start,
+    uint64_t flushTo(SocketClient* writer, uint64_t start,
                      pid_t* lastTid,  // &lastTid[LOG_ID_MAX] or nullptr
                      bool privileged, bool security,
-                     int (*filter)(const LogBufferElement* element,
-                                   void* arg) = nullptr,
+                     int (*filter)(const LogBufferElement* element, void* arg) = nullptr,
                      void* arg = nullptr);
 
     bool clear(log_id_t id, uid_t uid = AID_ROOT);
@@ -175,10 +174,8 @@
    private:
     static constexpr size_t minPrune = 4;
     static constexpr size_t maxPrune = 256;
-    static const log_time pruneMargin;
 
     void maybePrune(log_id_t id);
-    bool isBusy(log_time watermark);
     void kickMe(LogTimeEntry* me, log_id_t id, unsigned long pruneRows);
 
     bool prune(log_id_t id, unsigned long pruneRows, uid_t uid = AID_ROOT);
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
index ec81933..3714800 100644
--- a/logd/LogBufferElement.cpp
+++ b/logd/LogBufferElement.cpp
@@ -30,15 +30,15 @@
 #include "LogReader.h"
 #include "LogUtils.h"
 
-const log_time LogBufferElement::FLUSH_ERROR((uint32_t)-1, (uint32_t)-1);
+const uint64_t LogBufferElement::FLUSH_ERROR(0);
 atomic_int_fast64_t LogBufferElement::sequence(1);
 
-LogBufferElement::LogBufferElement(log_id_t log_id, log_time realtime,
-                                   uid_t uid, pid_t pid, pid_t tid,
-                                   const char* msg, uint16_t len)
+LogBufferElement::LogBufferElement(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid,
+                                   pid_t tid, const char* msg, uint16_t len)
     : mUid(uid),
       mPid(pid),
       mTid(tid),
+      mSequence(sequence.fetch_add(1, memory_order_relaxed)),
       mRealTime(realtime),
       mMsgLen(len),
       mLogId(log_id),
@@ -51,6 +51,7 @@
     : mUid(elem.mUid),
       mPid(elem.mPid),
       mTid(elem.mTid),
+      mSequence(elem.mSequence),
       mRealTime(elem.mRealTime),
       mMsgLen(elem.mMsgLen),
       mLogId(elem.mLogId),
@@ -244,7 +245,7 @@
     return retval;
 }
 
-log_time LogBufferElement::flushTo(SocketClient* reader, LogBuffer* parent, bool lastSame) {
+uint64_t LogBufferElement::flushTo(SocketClient* reader, LogBuffer* parent, bool lastSame) {
     struct logger_entry entry = {};
 
     entry.hdr_size = sizeof(struct logger_entry);
@@ -263,7 +264,7 @@
 
     if (mDropped) {
         entry.len = populateDroppedMessage(buffer, parent, lastSame);
-        if (!entry.len) return mRealTime;
+        if (!entry.len) return mSequence;
         iovec[1].iov_base = buffer;
     } else {
         entry.len = mMsgLen;
@@ -271,9 +272,7 @@
     }
     iovec[1].iov_len = entry.len;
 
-    log_time retval = reader->sendDatav(iovec, 1 + (entry.len != 0))
-                          ? FLUSH_ERROR
-                          : mRealTime;
+    uint64_t retval = reader->sendDatav(iovec, 1 + (entry.len != 0)) ? FLUSH_ERROR : mSequence;
 
     if (buffer) free(buffer);
 
diff --git a/logd/LogBufferElement.h b/logd/LogBufferElement.h
index fd790e4..434b7db 100644
--- a/logd/LogBufferElement.h
+++ b/logd/LogBufferElement.h
@@ -39,6 +39,7 @@
     const uint32_t mUid;
     const uint32_t mPid;
     const uint32_t mTid;
+    uint64_t mSequence;
     log_time mRealTime;
     union {
         char* mMsg;    // mDropped == false
@@ -90,10 +91,12 @@
     const char* getMsg() const {
         return mDropped ? nullptr : mMsg;
     }
+    uint64_t getSequence() const { return mSequence; }
+    static uint64_t getCurrentSequence() { return sequence.load(memory_order_relaxed); }
     log_time getRealTime(void) const {
         return mRealTime;
     }
 
-    static const log_time FLUSH_ERROR;
-    log_time flushTo(SocketClient* writer, LogBuffer* parent, bool lastSame);
+    static const uint64_t FLUSH_ERROR;
+    uint64_t flushTo(SocketClient* writer, LogBuffer* parent, bool lastSame);
 };
diff --git a/logd/LogReader.cpp b/logd/LogReader.cpp
index 9db8c00..f79d39c 100644
--- a/logd/LogReader.cpp
+++ b/logd/LogReader.cpp
@@ -89,8 +89,7 @@
     static const char _timeout[] = " timeout=";
     cp = strstr(buffer, _timeout);
     if (cp) {
-        timeout = atol(cp + sizeof(_timeout) - 1) * NS_PER_SEC +
-                  log_time(CLOCK_REALTIME).nsec();
+        timeout = atol(cp + sizeof(_timeout) - 1) * NS_PER_SEC + log_time(CLOCK_MONOTONIC).nsec();
     }
 
     unsigned int logMask = -1;
@@ -130,70 +129,53 @@
         nonBlock = true;
     }
 
-    log_time sequence = start;
-    //
-    // This somewhat expensive data validation operation is required
-    // for non-blocking, with timeout.  The incoming timestamp must be
-    // in range of the list, if not, return immediately.  This is
-    // used to prevent us from from getting stuck in timeout processing
-    // with an invalid time.
-    //
-    // Find if time is really present in the logs, monotonic or real, implicit
-    // conversion from monotonic or real as necessary to perform the check.
-    // Exit in the check loop ASAP as you find a transition from older to
-    // newer, but use the last entry found to ensure overlap.
-    //
-    if (nonBlock && (sequence != log_time::EPOCH) && timeout) {
-        class LogFindStart {  // A lambda by another name
-           private:
+    uint64_t sequence = 1;
+    // Convert realtime to sequence number
+    if (start != log_time::EPOCH) {
+        class LogFindStart {
             const pid_t mPid;
             const unsigned mLogMask;
-            bool mStartTimeSet;
-            log_time mStart;
-            log_time& mSequence;
-            log_time mLast;
-            bool mIsMonotonic;
+            bool startTimeSet;
+            const log_time start;
+            uint64_t& sequence;
+            uint64_t last;
+            bool isMonotonic;
 
-           public:
-            LogFindStart(pid_t pid, unsigned logMask, log_time& sequence,
+          public:
+            LogFindStart(unsigned logMask, pid_t pid, log_time start, uint64_t& sequence,
                          bool isMonotonic)
                 : mPid(pid),
                   mLogMask(logMask),
-                  mStartTimeSet(false),
-                  mStart(sequence),
-                  mSequence(sequence),
-                  mLast(sequence),
-                  mIsMonotonic(isMonotonic) {
-            }
+                  startTimeSet(false),
+                  start(start),
+                  sequence(sequence),
+                  last(sequence),
+                  isMonotonic(isMonotonic) {}
 
             static int callback(const LogBufferElement* element, void* obj) {
                 LogFindStart* me = reinterpret_cast<LogFindStart*>(obj);
                 if ((!me->mPid || (me->mPid == element->getPid())) &&
                     (me->mLogMask & (1 << element->getLogId()))) {
-                    log_time real = element->getRealTime();
-                    if (me->mStart == real) {
-                        me->mSequence = real;
-                        me->mStartTimeSet = true;
+                    if (me->start == element->getRealTime()) {
+                        me->sequence = element->getSequence();
+                        me->startTimeSet = true;
                         return -1;
-                    } else if (!me->mIsMonotonic || android::isMonotonic(real)) {
-                        if (me->mStart < real) {
-                            me->mSequence = me->mLast;
-                            me->mStartTimeSet = true;
+                    } else if (!me->isMonotonic || android::isMonotonic(element->getRealTime())) {
+                        if (me->start < element->getRealTime()) {
+                            me->sequence = me->last;
+                            me->startTimeSet = true;
                             return -1;
                         }
-                        me->mLast = real;
+                        me->last = element->getSequence();
                     } else {
-                        me->mLast = real;
+                        me->last = element->getSequence();
                     }
                 }
                 return false;
             }
 
-            bool found() {
-                return mStartTimeSet;
-            }
-
-        } logFindStart(pid, logMask, sequence,
+            bool found() { return startTimeSet; }
+        } logFindStart(logMask, pid, start, sequence,
                        logbuf().isMonotonic() && android::isMonotonic(start));
 
         logbuf().flushTo(cli, sequence, nullptr, FlushCommand::hasReadLogs(cli),
@@ -201,24 +183,27 @@
                          logFindStart.callback, &logFindStart);
 
         if (!logFindStart.found()) {
-            doSocketDelete(cli);
-            return false;
+            if (nonBlock) {
+                doSocketDelete(cli);
+                return false;
+            }
+            sequence = LogBufferElement::getCurrentSequence();
         }
     }
 
     android::prdebug(
-        "logdr: UID=%d GID=%d PID=%d %c tail=%lu logMask=%x pid=%d "
-        "start=%" PRIu64 "ns timeout=%" PRIu64 "ns\n",
-        cli->getUid(), cli->getGid(), cli->getPid(), nonBlock ? 'n' : 'b', tail,
-        logMask, (int)pid, sequence.nsec(), timeout);
+            "logdr: UID=%d GID=%d PID=%d %c tail=%lu logMask=%x pid=%d "
+            "start=%" PRIu64 "ns timeout=%" PRIu64 "ns\n",
+            cli->getUid(), cli->getGid(), cli->getPid(), nonBlock ? 'n' : 'b', tail, logMask,
+            (int)pid, start.nsec(), timeout);
 
-    if (sequence == log_time::EPOCH) {
+    if (start == log_time::EPOCH) {
         timeout = 0;
     }
 
     LogTimeEntry::wrlock();
-    auto entry = std::make_unique<LogTimeEntry>(
-        *this, cli, nonBlock, tail, logMask, pid, sequence, timeout);
+    auto entry = std::make_unique<LogTimeEntry>(*this, cli, nonBlock, tail, logMask, pid, start,
+                                                sequence, timeout);
     if (!entry->startReader_Locked()) {
         LogTimeEntry::unlock();
         return false;
diff --git a/logd/LogTags.cpp b/logd/LogTags.cpp
index 0cc7886..8299e66 100644
--- a/logd/LogTags.cpp
+++ b/logd/LogTags.cpp
@@ -289,9 +289,8 @@
 // special pmsg event for log tags, and build up our internal
 // database with any found.
 void LogTags::ReadPersistEventLogTags() {
-    struct logger_list* logger_list = android_logger_list_alloc(
-        ANDROID_LOG_RDONLY | ANDROID_LOG_PSTORE | ANDROID_LOG_NONBLOCK, 0,
-        (pid_t)0);
+    struct logger_list* logger_list =
+            android_logger_list_alloc(ANDROID_LOG_PSTORE | ANDROID_LOG_NONBLOCK, 0, (pid_t)0);
     if (!logger_list) return;
 
     struct logger* e = android_logger_open(logger_list, LOG_ID_EVENTS);
diff --git a/logd/LogTimes.cpp b/logd/LogTimes.cpp
index 208d67f..ed8d2f5 100644
--- a/logd/LogTimes.cpp
+++ b/logd/LogTimes.cpp
@@ -18,8 +18,6 @@
 #include <string.h>
 #include <sys/prctl.h>
 
-#include <private/android_logger.h>
-
 #include "FlushCommand.h"
 #include "LogBuffer.h"
 #include "LogReader.h"
@@ -27,9 +25,9 @@
 
 pthread_mutex_t LogTimeEntry::timesLock = PTHREAD_MUTEX_INITIALIZER;
 
-LogTimeEntry::LogTimeEntry(LogReader& reader, SocketClient* client,
-                           bool nonBlock, unsigned long tail, log_mask_t logMask,
-                           pid_t pid, log_time start, uint64_t timeout)
+LogTimeEntry::LogTimeEntry(LogReader& reader, SocketClient* client, bool nonBlock,
+                           unsigned long tail, log_mask_t logMask, pid_t pid, log_time start_time,
+                           uint64_t start, uint64_t timeout)
     : leadingDropped(false),
       mReader(reader),
       mLogMask(logMask),
@@ -38,9 +36,9 @@
       mTail(tail),
       mIndex(0),
       mClient(client),
+      mStartTime(start_time),
       mStart(start),
-      mNonBlock(nonBlock),
-      mEnd(log_time(android_log_clockid())) {
+      mNonBlock(nonBlock) {
     mTimeout.tv_sec = timeout / NS_PER_SEC;
     mTimeout.tv_nsec = timeout % NS_PER_SEC;
     memset(mLastTid, 0, sizeof(mLastTid));
@@ -81,12 +79,12 @@
 
     wrlock();
 
-    log_time start = me->mStart;
+    uint64_t start = me->mStart;
 
     while (!me->mRelease) {
         if (me->mTimeout.tv_sec || me->mTimeout.tv_nsec) {
-            if (pthread_cond_timedwait(&me->threadTriggeredCondition,
-                                       &timesLock, &me->mTimeout) == ETIMEDOUT) {
+            if (pthread_cond_clockwait(&me->threadTriggeredCondition, &timesLock, CLOCK_MONOTONIC,
+                                       &me->mTimeout) == ETIMEDOUT) {
                 me->mTimeout.tv_sec = 0;
                 me->mTimeout.tv_nsec = 0;
             }
@@ -105,13 +103,22 @@
         start = logbuf.flushTo(client, start, me->mLastTid, privileged,
                                security, FilterSecondPass, me);
 
+        // We only ignore entries before the original start time for the first flushTo(), if we
+        // get entries after this first flush before the original start time, then the client
+        // wouldn't have seen them.
+        // Note: this is still racy and may skip out of order events that came in since the last
+        // time the client disconnected and then reconnected with the new start time.  The long term
+        // solution here is that clients must request events since a specific sequence number.
+        me->mStartTime.tv_sec = 0;
+        me->mStartTime.tv_nsec = 0;
+
         wrlock();
 
         if (start == LogBufferElement::FLUSH_ERROR) {
             break;
         }
 
-        me->mStart = start + log_time(0, 1);
+        me->mStart = start + 1;
 
         if (me->mNonBlock || me->mRelease) {
             break;
@@ -158,11 +165,11 @@
     }
 
     if (me->mCount == 0) {
-        me->mStart = element->getRealTime();
+        me->mStart = element->getSequence();
     }
 
-    if ((!me->mPid || (me->mPid == element->getPid())) &&
-        (me->isWatching(element->getLogId()))) {
+    if ((!me->mPid || me->mPid == element->getPid()) && me->isWatching(element->getLogId()) &&
+        (me->mStartTime == log_time::EPOCH || me->mStartTime <= element->getRealTime())) {
         ++me->mCount;
     }
 
@@ -177,7 +184,7 @@
 
     LogTimeEntry::wrlock();
 
-    me->mStart = element->getRealTime();
+    me->mStart = element->getSequence();
 
     if (me->skipAhead[element->getLogId()]) {
         me->skipAhead[element->getLogId()]--;
@@ -204,6 +211,10 @@
         goto skip;
     }
 
+    if (me->mStartTime != log_time::EPOCH && element->getRealTime() <= me->mStartTime) {
+        goto skip;
+    }
+
     if (me->mRelease) {
         goto stop;
     }
diff --git a/logd/LogTimes.h b/logd/LogTimes.h
index 9f6f203..a99c73b 100644
--- a/logd/LogTimes.h
+++ b/logd/LogTimes.h
@@ -49,16 +49,16 @@
     unsigned long mTail;
     unsigned long mIndex;
 
-   public:
-    LogTimeEntry(LogReader& reader, SocketClient* client, bool nonBlock,
-                 unsigned long tail, log_mask_t logMask, pid_t pid,
-                 log_time start, uint64_t timeout);
+  public:
+    LogTimeEntry(LogReader& reader, SocketClient* client, bool nonBlock, unsigned long tail,
+                 log_mask_t logMask, pid_t pid, log_time start_time, uint64_t sequence,
+                 uint64_t timeout);
 
     SocketClient* mClient;
-    log_time mStart;
-    struct timespec mTimeout;
+    log_time mStartTime;
+    uint64_t mStart;
+    struct timespec mTimeout;  // CLOCK_MONOTONIC based timeout used for log wrapping.
     const bool mNonBlock;
-    const log_time mEnd;  // only relevant if mNonBlock
 
     // Protect List manipulations
     static void wrlock(void) {
diff --git a/logd/tests/Android.bp b/logd/tests/Android.bp
index d39da8a..9a5defa 100644
--- a/logd/tests/Android.bp
+++ b/logd/tests/Android.bp
@@ -63,6 +63,6 @@
     },
     test_suites: [
         "cts",
-        "vts",
+        "vts10",
     ],
 }
diff --git a/logd/tests/logd_test.cpp b/logd/tests/logd_test.cpp
index f47bee1..d57b79e 100644
--- a/logd/tests/logd_test.cpp
+++ b/logd/tests/logd_test.cpp
@@ -246,7 +246,7 @@
     std::cerr << std::flush;
     fflush(stdout);
     fflush(stderr);
-    EXPECT_EQ(sizeof(logger_entry), msg->entry.hdr_size);
+    EXPECT_GE(msg->entry.hdr_size, sizeof(logger_entry));
 
     fprintf(stderr, "%s: [%u] ", prefix, msg->len());
     fprintf(stderr, "hdr_size=%u ", msg->entry.hdr_size);
@@ -870,10 +870,8 @@
     ASSERT_EQ(0, info.si_status);
 
     struct logger_list* logger_list;
-    ASSERT_TRUE(nullptr !=
-                (logger_list = android_logger_list_open(
-                     LOG_ID_EVENTS, ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK,
-                     0, pid)));
+    ASSERT_TRUE(nullptr != (logger_list = android_logger_list_open(LOG_ID_EVENTS,
+                                                                   ANDROID_LOG_NONBLOCK, 0, pid)));
 
     int expected_count = (count < 2) ? count : 2;
     int expected_chatty_count = (count <= 2) ? 0 : 1;
diff --git a/qemu_pipe/Android.bp b/qemu_pipe/Android.bp
index c6bda4a..ad86a4e 100644
--- a/qemu_pipe/Android.bp
+++ b/qemu_pipe/Android.bp
@@ -4,6 +4,11 @@
     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"],
     },
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 5ebffab..6564e8f 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -825,6 +825,11 @@
     start zygote
     start zygote_secondary
 
+on boot && property:ro.config.low_ram=true
+    # Tweak background writeout
+    write /proc/sys/vm/dirty_expire_centisecs 200
+    write /proc/sys/vm/dirty_background_ratio  5
+
 on boot
     # basic network init
     ifup lo
@@ -846,11 +851,7 @@
     chown root system /sys/block/zram0/writeback
     chmod 0664 /sys/block/zram0/writeback
 
-    # Tweak background writeout
-    write /proc/sys/vm/dirty_expire_centisecs 200
-    write /proc/sys/vm/dirty_background_ratio  5
-
-    # F2FS tuning. Set cp_interval larger than dirty_expire_centisecs
+    # F2FS tuning. Set cp_interval larger than dirty_expire_centisecs, 30 secs,
     # to avoid power consumption when system becomes mostly idle. Be careful
     # to make it too large, since it may bring userdata loss, if they
     # are not aware of using fsync()/sync() to prepare sudden power-cut.
@@ -1062,6 +1063,12 @@
   start vold
   exec - system system -- /system/bin/vdc checkpoint resetCheckpoint
   exec - system system -- /system/bin/vdc checkpoint markBootAttempt
+  # Unmount /data_mirror mounts in the reverse order of corresponding mounts.
+  umount /data_mirror/data_ce/null/0
+  umount /data_mirror/data_ce/null
+  umount /data_mirror/data_de/null
+  umount /data_mirror/cur_profiles
+  umount /data_mirror
   remount_userdata
   start bootanim
 
diff --git a/storaged/main.cpp b/storaged/main.cpp
index a7bda14..bbed210 100644
--- a/storaged/main.cpp
+++ b/storaged/main.cpp
@@ -71,6 +71,7 @@
     bool flag_dump_perf = false;
     int opt;
 
+    signal(SIGPIPE, SIG_IGN);
     android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
 
     for (;;) {
diff --git a/trusty/keymaster/4.0/android.hardware.keymaster@4.0-service.trusty.xml b/trusty/keymaster/4.0/android.hardware.keymaster@4.0-service.trusty.xml
index aa30707..65eb854 100644
--- a/trusty/keymaster/4.0/android.hardware.keymaster@4.0-service.trusty.xml
+++ b/trusty/keymaster/4.0/android.hardware.keymaster@4.0-service.trusty.xml
@@ -2,10 +2,6 @@
     <hal format="hidl">
         <name>android.hardware.keymaster</name>
         <transport>hwbinder</transport>
-        <version>4.0</version>
-        <interface>
-        <name>IKeymasterDevice</name>
-            <instance>default</instance>
-        </interface>
+        <fqname>@4.0::IKeymasterDevice/default</fqname>
     </hal>
 </manifest>