Merge "first_stage_init: load modules in modules.load.recovery in recovery"
diff --git a/adb/Android.bp b/adb/Android.bp
index 81d20c1..6386a78 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",
 
@@ -310,8 +317,8 @@
     static_libs: [
         "libadb_crypto",
         "libadb_host",
-	"libadb_pairing_auth",
-	"libadb_pairing_connection",
+        "libadb_pairing_auth",
+        "libadb_pairing_connection",
         "libadb_protos",
         "libadb_tls_connection",
         "libandroidfw",
@@ -382,10 +389,6 @@
         "daemon/adb_wifi.cpp",
     ],
 
-    local_include_dirs: [
-        "daemon/include",
-    ],
-
     generated_headers: ["platform_tools_version"],
 
     static_libs: [
@@ -404,6 +407,7 @@
         "libbase",
         "libcrypto",
         "libcrypto_utils",
+        "libcutils_sockets",
         "liblog",
     ],
 
@@ -422,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: {
@@ -472,6 +470,7 @@
         "libadbd_core",
         "libbrotli",
         "libdiagnose_usb",
+        "liblz4",
     ],
 
     shared_libs: [
@@ -479,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: {
@@ -537,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",
@@ -548,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: {
@@ -569,20 +568,23 @@
     },
 
     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"],
@@ -601,33 +603,18 @@
     },
 
     static_libs: [
-        "libadb_crypto",
-        "libadb_tls_connection",
-        "libadbconnection_server",
         "libadbd",
         "libadbd_services",
-        "libapp_processes_protos_lite",
         "libasyncio",
-        "libbase",
-        "libbrotli",
         "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: {
@@ -699,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 + [
@@ -711,21 +697,16 @@
         "shell_service_protocol_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 b514368..0518e9d 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(
@@ -1067,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 << ")";
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/brotli_utils.h b/adb/brotli_utils.h
deleted file mode 100644
index c5be73d..0000000
--- a/adb/brotli_utils.h
+++ /dev/null
@@ -1,144 +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 <span>
-
-#include <brotli/decode.h>
-#include <brotli/encode.h>
-
-#include "types.h"
-
-enum class BrotliDecodeResult {
-    Error,
-    Done,
-    NeedInput,
-    MoreOutput,
-};
-
-struct BrotliDecoder {
-    explicit BrotliDecoder(std::span<char> output_buffer)
-        : output_buffer_(output_buffer),
-          decoder_(BrotliDecoderCreateInstance(nullptr, nullptr, nullptr),
-                   BrotliDecoderDestroyInstance) {}
-
-    void Append(Block&& block) { input_buffer_.append(std::move(block)); }
-
-    BrotliDecodeResult Decode(std::span<char>* output) {
-        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:
-                return BrotliDecodeResult::Done;
-            case BROTLI_DECODER_RESULT_ERROR:
-                return BrotliDecodeResult::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 BrotliDecodeResult::NeedInput;
-            case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
-                return BrotliDecodeResult::MoreOutput;
-        }
-    }
-
-  private:
-    IOVector input_buffer_;
-    std::span<char> output_buffer_;
-    std::unique_ptr<BrotliDecoderState, void (*)(BrotliDecoderState*)> decoder_;
-};
-
-enum class BrotliEncodeResult {
-    Error,
-    Done,
-    NeedInput,
-    MoreOutput,
-};
-
-template <size_t OutputBlockSize>
-struct BrotliEncoder {
-    explicit BrotliEncoder()
-        : output_block_(OutputBlockSize),
-          output_bytes_left_(OutputBlockSize),
-          encoder_(BrotliEncoderCreateInstance(nullptr, nullptr, nullptr),
-                   BrotliEncoderDestroyInstance) {
-        BrotliEncoderSetParameter(encoder_.get(), BROTLI_PARAM_QUALITY, 1);
-    }
-
-    void Append(Block input) { input_buffer_.append(std::move(input)); }
-    void Finish() { finished_ = true; }
-
-    BrotliEncodeResult Encode(Block* output) {
-        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() +
-                                                           (OutputBlockSize - 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 BrotliEncodeResult::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(OutputBlockSize - output_bytes_left_);
-                *output = std::move(output_block_);
-                return BrotliEncodeResult::Done;
-            } else if (output_bytes_left_ == 0) {
-                *output = std::move(output_block_);
-                output_block_.resize(OutputBlockSize);
-                output_bytes_left_ = OutputBlockSize;
-                return BrotliEncodeResult::MoreOutput;
-            } else if (input_buffer_.empty()) {
-                return BrotliEncodeResult::NeedInput;
-            }
-        }
-    }
-
-  private:
-    bool finished_ = false;
-    IOVector input_buffer_;
-    Block output_block_;
-    size_t output_bytes_left_;
-    std::unique_ptr<BrotliEncoderState, void (*)(BrotliEncoderState*)> encoder_;
-};
diff --git a/adb/client/adb_client.cpp b/adb/client/adb_client.cpp
index f724cb5..c859d75 100644
--- a/adb/client/adb_client.cpp
+++ b/adb/client/adb_client.cpp
@@ -204,9 +204,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());
diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp
index e4d010c..da3154e 100644
--- a/adb/client/adb_install.cpp
+++ b/adb/client/adb_install.cpp
@@ -290,7 +290,7 @@
         }
     }
 
-    if (do_sync_push(apk_file, apk_dest.c_str(), false, true)) {
+    if (do_sync_push(apk_file, apk_dest.c_str(), false, CompressionType::Any, false)) {
         result = pm_command(argc, argv);
         delete_device_file(apk_dest);
     }
@@ -325,7 +325,12 @@
         }
     }
 
-    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;
+    }
 
     auto files = incremental::Files{argv + first_apk, argv + last_apk + 1};
     if (silent) {
diff --git a/adb/client/bugreport.cpp b/adb/client/bugreport.cpp
index ab93f7d..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, false, 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 04b250d..ceb21d5 100644
--- a/adb/client/commandline.cpp
+++ b/adb/client/commandline.cpp
@@ -129,20 +129,22 @@
         " reverse --remove-all     remove all reverse socket connections from device\n"
         "\n"
         "file transfer:\n"
-        " push [--sync] [-zZ] 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"
-        "     -z: enable compression\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 [-azZ] REMOTE... LOCAL\n"
+        " pull [-a] [-z ALGORITHM] [-Z] REMOTE... LOCAL\n"
         "     copy files/dirs from device\n"
         "     -a: preserve file timestamp and mode\n"
-        "     -z: enable compression\n"
+        "     -z: enable compression with a specified algorithm (any, none, brotli)\n"
         "     -Z: disable compression\n"
-        " sync [-lzZ] [all|data|odm|oem|product|system|system_ext|vendor]\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\n"
+        "     -z: enable compression with a specified algorithm (any, none, brotli)\n"
         "     -Z: disable compression\n"
         "\n"
         "shell:\n"
@@ -1314,12 +1316,36 @@
     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, bool* compressed) {
+                                 const char** dst, bool* copy_attrs, bool* sync,
+                                 CompressionType* compression, bool* dry_run) {
     *copy_attrs = false;
-    const char* adb_compression = getenv("ADB_COMPRESSION");
-    if (adb_compression && strcmp(adb_compression, "0") == 0) {
-        *compressed = false;
+    if (const char* adb_compression = getenv("ADB_COMPRESSION")) {
+        *compression = parse_compression_type(adb_compression, true);
     }
 
     srcs->clear();
@@ -1333,13 +1359,15 @@
             } else if (!strcmp(*arg, "-a")) {
                 *copy_attrs = true;
             } else if (!strcmp(*arg, "-z")) {
-                if (compressed != nullptr) {
-                    *compressed = true;
+                if (narg < 2) {
+                    error_exit("-z requires an argument");
                 }
+                *compression = parse_compression_type(*++arg, false);
+                --narg;
             } else if (!strcmp(*arg, "-Z")) {
-                if (compressed != nullptr) {
-                    *compressed = false;
-                }
+                *compression = CompressionType::None;
+            } else if (dry_run && !strcmp(*arg, "-n")) {
+                *dry_run = true;
             } else if (!strcmp(*arg, "--sync")) {
                 if (sync != nullptr) {
                     *sync = true;
@@ -1894,22 +1922,25 @@
     } else if (!strcmp(argv[0], "push")) {
         bool copy_attrs = false;
         bool sync = false;
-        bool compressed = true;
+        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, &compressed);
+        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, compressed) ? 0 : 1;
+        return do_sync_push(srcs, dst, sync, compression, dry_run) ? 0 : 1;
     } else if (!strcmp(argv[0], "pull")) {
         bool copy_attrs = false;
-        bool compressed = true;
+        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, &compressed);
+        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, compressed) ? 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);
@@ -1925,27 +1956,30 @@
     } else if (!strcmp(argv[0], "sync")) {
         std::string src;
         bool list_only = false;
-        bool compressed = true;
+        bool dry_run = false;
+        CompressionType compression = CompressionType::Any;
 
-        const char* adb_compression = getenv("ADB_COMPRESSION");
-        if (adb_compression && strcmp(adb_compression, "0") == 0) {
-            compressed = false;
+        if (const char* adb_compression = getenv("ADB_COMPRESSION"); adb_compression) {
+            compression = parse_compression_type(adb_compression, true);
         }
 
         int opt;
-        while ((opt = getopt(argc, const_cast<char**>(argv), "lzZ")) != -1) {
+        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':
-                    compressed = true;
+                    compression = parse_compression_type(optarg, false);
                     break;
                 case 'Z':
-                    compressed = false;
+                    compression = CompressionType::None;
                     break;
                 default:
-                    error_exit("usage: adb sync [-lzZ] [PARTITION]");
+                    error_exit("usage: adb sync [-l] [-n]  [-z ALGORITHM] [-Z] [PARTITION]");
             }
         }
 
@@ -1954,7 +1988,7 @@
         } else if (optind + 1 == argc) {
             src = argv[optind];
         } else {
-            error_exit("usage: adb sync [-lzZ] [PARTITION]");
+            error_exit("usage: adb sync [-l] [-n] [-z ALGORITHM] [-Z] [PARTITION]");
         }
 
         std::vector<std::string> partitions{"data",   "odm",        "oem",   "product",
@@ -1965,7 +1999,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, compressed)) 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());
diff --git a/adb/client/fastdeploy.cpp b/adb/client/fastdeploy.cpp
index de82e14..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, true)) {
+    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 0844428..6816734 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,7 +43,7 @@
 #include "adb_client.h"
 #include "adb_io.h"
 #include "adb_utils.h"
-#include "brotli_utils.h"
+#include "compression_utils.h"
 #include "file_sync_protocol.h"
 #include "line_printer.h"
 #include "sysdeps/errno.h"
@@ -236,6 +237,8 @@
             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);
             fd.reset(adb_connect("sync:", &error));
             if (fd < 0) {
                 Error("connect failed: %s", error.c_str());
@@ -261,6 +264,22 @@
 
     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_; }
 
@@ -323,7 +342,7 @@
         return WriteFdExactly(fd, buf.data(), buf.size());
     }
 
-    bool SendSend2(std::string_view path, mode_t mode, bool compressed) {
+    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;
@@ -339,7 +358,26 @@
         syncmsg msg;
         msg.send_v2_setup.id = ID_SEND_V2;
         msg.send_v2_setup.mode = mode;
-        msg.send_v2_setup.flags = compressed ? kSyncFlagBrotli : kSyncFlagNone;
+        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));
 
@@ -352,7 +390,7 @@
         return WriteFdExactly(fd, buf.data(), buf.size());
     }
 
-    bool SendRecv2(const std::string& path) {
+    bool SendRecv2(const std::string& path, CompressionType compression) {
         if (path.length() > 1024) {
             Error("SendRequest failed: path too long: %zu", path.length());
             errno = ENAMETOOLONG;
@@ -367,7 +405,22 @@
 
         syncmsg msg;
         msg.recv_v2_setup.id = ID_RECV_V2;
-        msg.recv_v2_setup.flags = kSyncFlagBrotli;
+        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));
 
@@ -494,7 +547,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());
@@ -533,9 +591,21 @@
         return true;
     }
 
-    bool SendLargeFileCompressed(const std::string& path, mode_t mode, const std::string& lpath,
-                                 const std::string& rpath, unsigned mtime) {
-        if (!SendSend2(path, mode, true)) {
+    bool SendLargeFile(const std::string& path, mode_t mode, const std::string& lpath,
+                       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;
         }
@@ -558,7 +628,25 @@
         syncsendbuf sbuf;
         sbuf.id = ID_DATA;
 
-        BrotliEncoder<SYNC_DATA_MAX> encoder;
+        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);
@@ -569,10 +657,10 @@
             }
 
             if (r == 0) {
-                encoder.Finish();
+                encoder->Finish();
             } else {
                 input.resize(r);
-                encoder.Append(std::move(input));
+                encoder->Append(std::move(input));
                 RecordBytesTransferred(r);
                 bytes_copied += r;
                 ReportProgress(rpath, bytes_copied, total_size);
@@ -580,8 +668,8 @@
 
             while (true) {
                 Block output;
-                BrotliEncodeResult result = encoder.Encode(&output);
-                if (result == BrotliEncodeResult::Error) {
+                EncodeResult result = encoder->Encode(&output);
+                if (result == EncodeResult::Error) {
                     Error("compressing '%s' locally failed", lpath.c_str());
                     return false;
                 }
@@ -592,12 +680,12 @@
                     WriteOrDie(lpath, rpath, &sbuf, sizeof(SyncRequest) + output.size());
                 }
 
-                if (result == BrotliEncodeResult::Done) {
+                if (result == EncodeResult::Done) {
                     sending = false;
                     break;
-                } else if (result == BrotliEncodeResult::NeedInput) {
+                } else if (result == EncodeResult::NeedInput) {
                     break;
-                } else if (result == BrotliEncodeResult::MoreOutput) {
+                } else if (result == EncodeResult::MoreOutput) {
                     continue;
                 }
             }
@@ -610,12 +698,8 @@
         return WriteOrDie(lpath, rpath, &msg.data, sizeof(msg.data));
     }
 
-    bool SendLargeFile(const std::string& path, mode_t mode, const std::string& lpath,
-                       const std::string& rpath, unsigned mtime, bool compressed) {
-        if (compressed && HaveSendRecv2Brotli()) {
-            return SendLargeFileCompressed(path, mode, lpath, rpath, mtime);
-        }
-
+    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_V1, path_and_mode)) {
             Error("failed to send ID_SEND_V1 message '%s': %s", path_and_mode.c_str(),
@@ -840,6 +924,8 @@
     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_;
@@ -921,7 +1007,8 @@
 }
 
 static bool sync_send(SyncConnection& sc, const std::string& lpath, const std::string& rpath,
-                      unsigned mtime, mode_t mode, bool sync, bool compressed) {
+                      unsigned mtime, mode_t mode, bool sync, CompressionType compression,
+                      bool dry_run) {
     if (sync) {
         struct stat st;
         if (sync_lstat(sc, rpath, &st)) {
@@ -942,7 +1029,7 @@
         }
         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 sc.ReadAcknowledgements();
@@ -960,11 +1047,12 @@
             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, compressed)) {
+        if (!sc.SendLargeFile(rpath, mode, lpath, rpath, mtime, compression, dry_run)) {
             return false;
         }
     }
@@ -1027,8 +1115,10 @@
 }
 
 static bool sync_recv_v2(SyncConnection& sc, const char* rpath, const char* lpath, const char* name,
-                         uint64_t expected_size) {
-    if (!sc.SendRecv2(rpath)) return false;
+                         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));
@@ -1040,9 +1130,28 @@
     uint64_t bytes_copied = 0;
 
     Block buffer(SYNC_DATA_MAX);
-    BrotliDecoder decoder(std::span(buffer.data(), buffer.size()));
-    bool reading = true;
-    while (reading) {
+    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);
@@ -1050,35 +1159,34 @@
         }
 
         if (msg.data.id == ID_DONE) {
-            adb_unlink(lpath);
-            sc.Error("unexpected ID_DONE");
-            return false;
-        }
-
-        if (msg.data.id != ID_DATA) {
+            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;
+            }
 
-        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));
         }
 
-        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;
-            BrotliDecodeResult result = decoder.Decode(&output);
+            DecodeResult result = decoder->Decode(&output);
 
-            if (result == BrotliDecodeResult::Error) {
+            if (result == DecodeResult::Error) {
                 sc.Error("decompress failed");
                 adb_unlink(lpath);
                 return false;
@@ -1093,42 +1201,27 @@
             }
 
             bytes_copied += output.size();
-
-            sc.RecordBytesTransferred(msg.data.size);
+            sc.RecordBytesTransferred(output.size());
             sc.ReportProgress(name != nullptr ? name : rpath, bytes_copied, expected_size);
 
-            if (result == BrotliDecodeResult::NeedInput) {
+            if (result == DecodeResult::NeedInput) {
                 break;
-            } else if (result == BrotliDecodeResult::MoreOutput) {
+            } else if (result == DecodeResult::MoreOutput) {
                 continue;
-            } else if (result == BrotliDecodeResult::Done) {
-                reading = false;
-                break;
+            } else if (result == DecodeResult::Done) {
+                sc.RecordFilesTransferred(1);
+                return true;
             } else {
-                LOG(FATAL) << "invalid BrotliDecodeResult: " << static_cast<int>(result);
+                LOG(FATAL) << "invalid DecodeResult: " << static_cast<int>(result);
             }
         }
     }
-
-    syncmsg msg;
-    if (!ReadFdExactly(sc.fd, &msg.data, sizeof(msg.data))) {
-        sc.Error("failed to read ID_DONE");
-        return false;
-    }
-
-    if (msg.data.id != ID_DONE) {
-        sc.Error("unexpected message after transfer: id = %d (expected ID_DONE)", msg.data.id);
-        return false;
-    }
-
-    sc.RecordFilesTransferred(1);
-    return true;
 }
 
 static bool sync_recv(SyncConnection& sc, const char* rpath, const char* lpath, const char* name,
-                      uint64_t expected_size, bool compressed) {
-    if (sc.HaveSendRecv2() && compressed) {
-        return sync_recv_v2(sc, rpath, lpath, name, expected_size);
+                      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);
     }
@@ -1210,7 +1303,8 @@
 }
 
 static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath, std::string rpath,
-                                  bool check_timestamps, bool list_only, bool compressed) {
+                                  bool check_timestamps, bool list_only,
+                                  CompressionType compression, bool dry_run) {
     sc.NewTransfer();
 
     // Make sure that both directory paths end in a slash.
@@ -1292,7 +1386,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, compressed)) {
+                if (!sync_send(sc, ci.lpath, ci.rpath, ci.time, ci.mode, false, compression,
+                               dry_run)) {
                     return false;
                 }
             }
@@ -1308,7 +1403,7 @@
 }
 
 bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync,
-                  bool compressed) {
+                  CompressionType compression, bool dry_run) {
     SyncConnection sc;
     if (!sc.IsValid()) return false;
 
@@ -1373,7 +1468,8 @@
                 dst_dir.append(android::base::Basename(src_path));
             }
 
-            success &= copy_local_dir_remote(sc, src_path, dst_dir, sync, false, compressed);
+            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);
@@ -1394,7 +1490,8 @@
 
         sc.NewTransfer();
         sc.SetExpectedTotalBytes(st.st_size);
-        success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync, compressed);
+        success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync, compression,
+                             dry_run);
         sc.ReportTransferRate(src_path, TransferDirection::push);
     }
 
@@ -1480,7 +1577,7 @@
 }
 
 static bool copy_remote_dir_local(SyncConnection& sc, std::string rpath, std::string lpath,
-                                  bool copy_attrs, bool compressed) {
+                                  bool copy_attrs, CompressionType compression) {
     sc.NewTransfer();
 
     // Make sure that both directory paths end in a slash.
@@ -1510,7 +1607,7 @@
                 continue;
             }
 
-            if (!sync_recv(sc, ci.rpath.c_str(), ci.lpath.c_str(), nullptr, ci.size, compressed)) {
+            if (!sync_recv(sc, ci.rpath.c_str(), ci.lpath.c_str(), nullptr, ci.size, compression)) {
                 return false;
             }
 
@@ -1528,7 +1625,7 @@
 }
 
 bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
-                  bool compressed, const char* name) {
+                  CompressionType compression, const char* name) {
     SyncConnection sc;
     if (!sc.IsValid()) return false;
 
@@ -1602,7 +1699,7 @@
                 dst_dir.append(android::base::Basename(src_path));
             }
 
-            success &= copy_remote_dir_local(sc, src_path, dst_dir, copy_attrs, compressed);
+            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);
@@ -1621,7 +1718,7 @@
 
         sc.NewTransfer();
         sc.SetExpectedTotalBytes(src_st.st_size);
-        if (!sync_recv(sc, src_path, dst_path, name, src_st.st_size, compressed)) {
+        if (!sync_recv(sc, src_path, dst_path, name, src_st.st_size, compression)) {
             success = false;
             continue;
         }
@@ -1638,11 +1735,11 @@
 }
 
 bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only,
-                  bool compressed) {
+                  CompressionType compression, bool dry_run) {
     SyncConnection sc;
     if (!sc.IsValid()) return false;
 
-    bool success = copy_local_dir_remote(sc, lpath, rpath, true, list_only, compressed);
+    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 de3f192..cb8ca93 100644
--- a/adb/client/file_sync_client.h
+++ b/adb/client/file_sync_client.h
@@ -19,11 +19,13 @@
 #include <string>
 #include <vector>
 
+#include "file_sync_protocol.h"
+
 bool do_sync_ls(const char* path);
 bool do_sync_push(const std::vector<const char*>& srcs, const char* dst, bool sync,
-                  bool compressed);
+                  CompressionType compression, bool dry_run);
 bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, bool copy_attrs,
-                  bool compressed, const char* name = nullptr);
+                  CompressionType compression, const char* name = nullptr);
 
 bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only,
-                  bool compressed);
+                  CompressionType compression, bool dry_run);
diff --git a/adb/client/main.cpp b/adb/client/main.cpp
index 33e0716..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();
     }
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/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 07f6e65..d58131e 100644
--- a/adb/daemon/file_sync_service.cpp
+++ b/adb/daemon/file_sync_service.cpp
@@ -35,6 +35,7 @@
 #include <optional>
 #include <span>
 #include <string>
+#include <variant>
 #include <vector>
 
 #include <android-base/file.h>
@@ -57,7 +58,7 @@
 #include "adb_io.h"
 #include "adb_trace.h"
 #include "adb_utils.h"
-#include "brotli_utils.h"
+#include "compression_utils.h"
 #include "file_sync_protocol.h"
 #include "security_log_tags.h"
 #include "sysdeps/errno.h"
@@ -266,47 +267,70 @@
     return SendSyncFail(fd, StringPrintf("%s: %s", reason.c_str(), strerror(errno)));
 }
 
-static bool handle_send_file_compressed(borrowed_fd s, unique_fd fd, uint32_t* timestamp) {
+static bool handle_send_file_data(borrowed_fd s, unique_fd fd, uint32_t* timestamp,
+                                  CompressionType compression) {
     syncmsg msg;
-    Block decode_buffer(SYNC_DATA_MAX);
-    BrotliDecoder decoder(std::span(decode_buffer.data(), decode_buffer.size()));
+    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;
+
+    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) {
         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;
-                return true;
-            }
+        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");
             return false;
         }
 
-        Block block(msg.data.size);
-        if (!ReadFdExactly(s, block.data(), msg.data.size)) return false;
-        decoder.Append(std::move(block));
-
         while (true) {
             std::span<char> output;
-            BrotliDecodeResult result = decoder.Decode(&output);
-            if (result == BrotliDecodeResult::Error) {
+            DecodeResult result = decoder->Decode(&output);
+            if (result == DecodeResult::Error) {
                 SendSyncFailErrno(s, "decompress failed");
                 return false;
             }
 
-            if (!WriteFdExactly(fd, output.data(), output.size())) {
-                SendSyncFailErrno(s, "write 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 == BrotliDecodeResult::NeedInput) {
+            if (result == DecodeResult::NeedInput) {
                 break;
-            } else if (result == BrotliDecodeResult::MoreOutput) {
+            } else if (result == DecodeResult::MoreOutput) {
                 continue;
-            } else if (result == BrotliDecodeResult::Done) {
-                break;
+            } else if (result == DecodeResult::Done) {
+                return true;
             } else {
-                LOG(FATAL) << "invalid BrotliDecodeResult: " << static_cast<int>(result);
+                LOG(FATAL) << "invalid DecodeResult: " << static_cast<int>(result);
             }
         }
     }
@@ -314,102 +338,67 @@
     __builtin_unreachable();
 }
 
-static bool handle_send_file_uncompressed(borrowed_fd s, unique_fd fd, uint32_t* timestamp,
-                                          std::vector<char>& buffer) {
-    syncmsg msg;
-
-    while (true) {
-        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;
-                return true;
-            }
-            SendSyncFail(s, "invalid data message");
-            return false;
-        }
-
-        if (msg.data.size > buffer.size()) {  // TODO: resize buffer?
-            SendSyncFail(s, "oversize data message");
-            return false;
-        }
-        if (!ReadFdExactly(s, &buffer[0], msg.data.size)) return false;
-        if (!WriteFdExactly(fd, &buffer[0], msg.data.size)) {
-            SendSyncFailErrno(s, "write failed");
-            return false;
-        }
-    }
-}
-
 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, bool compressed,
-                             std::vector<char>& buffer, bool do_unlink) {
-    int rc;
+                             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;
 
-    __android_log_security_bswrite(SEC_TAG_ADB_SEND_FILE, path);
-
-    unique_fd fd(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;
-        }
+    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 == 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 (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);
+            // 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);
-    }
+            // 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);
+        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));
         }
-
-        bool result;
-        if (compressed) {
-            result = handle_send_file_compressed(s, std::move(fd), timestamp);
-        } else {
-            result = handle_send_file_uncompressed(s, std::move(fd), timestamp, buffer);
-        }
-
-        if (!result) {
-            goto fail;
-        }
-
-        if (!update_capabilities(path, capabilities)) {
-            SendSyncFailErrno(s, "update_capabilities failed");
-            goto fail;
-        }
-
-        msg.status.id = ID_OKAY;
-        msg.status.msglen = 0;
-        return WriteFdExactly(s, &msg.status, sizeof(msg.status));
     }
 
+    if (!handle_send_file_data(s, std::move(fd), timestamp, compression)) {
+        goto fail;
+    }
+
+    if (!update_capabilities(path, capabilities)) {
+        SendSyncFailErrno(s, "update_capabilities failed");
+        goto fail;
+    }
+
+    msg.status.id = ID_OKAY;
+    msg.status.msglen = 0;
+    return WriteFdExactly(s, &msg.status, sizeof(msg.status));
+
 fail:
     // If there's a problem on the device, we'll send an ID_FAIL message and
     // close the socket. Unfortunately the kernel will sometimes throw that
@@ -448,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;
 
@@ -467,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;
         }
     }
 
@@ -499,12 +490,15 @@
 }
 #endif
 
-static bool send_impl(int s, const std::string& path, mode_t mode, bool compressed,
-                      std::vector<char>& buffer) {
+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());
     }
@@ -512,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;
@@ -522,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,
-                                  compressed, buffer, do_unlink);
+                                  compression, dry_run, buffer, do_unlink);
     }
 
     if (!result) {
@@ -560,7 +554,7 @@
         return false;
     }
 
-    return send_impl(s, path, mode, false, buffer);
+    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) {
@@ -574,94 +568,45 @@
         PLOG(ERROR) << "failed to read send_v2 setup packet";
     }
 
-    bool compressed = false;
+    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;
-        compressed = true;
+        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, compressed, buffer);
+    return send_impl(s, path, msg.send_v2_setup.mode, compression.value_or(CompressionType::None),
+                     dry_run, buffer);
 }
 
-static bool recv_uncompressed(borrowed_fd s, unique_fd fd, std::vector<char>& buffer) {
-    syncmsg msg;
-    msg.data.id = ID_DATA;
-    std::optional<BrotliEncoder<SYNC_DATA_MAX>> encoder;
-    while (true) {
-        int r = adb_read(fd.get(), &buffer[0], buffer.size() - sizeof(msg.data));
-        if (r <= 0) {
-            if (r == 0) break;
-            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;
-        }
-    }
-
-    return true;
-}
-
-static bool recv_compressed(borrowed_fd s, unique_fd fd) {
-    syncmsg msg;
-    msg.data.id = ID_DATA;
-
-    BrotliEncoder<SYNC_DATA_MAX> encoder;
-
-    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;
-        }
-
-        if (r == 0) {
-            encoder.Finish();
-        } else {
-            input.resize(r);
-            encoder.Append(std::move(input));
-        }
-
-        while (true) {
-            Block output;
-            BrotliEncodeResult result = encoder.Encode(&output);
-            if (result == BrotliEncodeResult::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 == BrotliEncodeResult::Done) {
-                sending = false;
-                break;
-            } else if (result == BrotliEncodeResult::NeedInput) {
-                break;
-            } else if (result == BrotliEncodeResult::MoreOutput) {
-                continue;
-            }
-        }
-    }
-
-    return true;
-}
-
-static bool recv_impl(borrowed_fd s, const char* path, bool compressed, std::vector<char>& 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));
@@ -675,25 +620,79 @@
         D("[ Failed to fadvise: %s ]", strerror(rc));
     }
 
-    bool result;
-    if (compressed) {
-        result = recv_compressed(s, std::move(fd));
-    } else {
-        result = recv_uncompressed(s, std::move(fd), buffer);
-    }
-
-    if (!result) {
-        return false;
-    }
-
     syncmsg msg;
+    msg.data.id = ID_DATA;
+
+    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;
+        }
+
+        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;
+            }
+        }
+    }
+
     msg.data.id = ID_DONE;
     msg.data.size = 0;
     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, false, buffer);
+    return recv_impl(s, path, CompressionType::None, buffer);
 }
 
 static bool do_recv_v2(borrowed_fd s, const char* path, std::vector<char>& buffer) {
@@ -707,17 +706,33 @@
         PLOG(ERROR) << "failed to read recv_v2 setup packet";
     }
 
-    bool compressed = false;
+    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;
-        compressed = true;
+        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, compressed, buffer);
+    return recv_impl(s, path, compression.value_or(CompressionType::None), buffer);
 }
 
 static const char* sync_id_to_name(uint32_t id) {
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/file_sync_protocol.h b/adb/file_sync_protocol.h
index fd9a516..8f8f85f 100644
--- a/adb/file_sync_protocol.h
+++ b/adb/file_sync_protocol.h
@@ -92,6 +92,15 @@
 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.
diff --git a/adb/pairing_connection/Android.bp b/adb/pairing_connection/Android.bp
index bcde7b1..707161b 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",
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 9a879b5..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] == '\\';
 }
 
@@ -386,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);
@@ -410,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));
@@ -426,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));
 }
 
@@ -437,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);
@@ -446,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);
 }
 
@@ -456,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
@@ -465,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
@@ -490,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);
 }
 
@@ -499,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
@@ -516,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
@@ -526,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;
@@ -543,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
@@ -570,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));
@@ -600,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
@@ -613,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);
@@ -640,32 +646,32 @@
 #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) {
+static inline int cast_handle_to_int(int fd) {
     return fd;
 }
 
@@ -715,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 6dccb7f..cc2e0e3 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -84,6 +84,8 @@
 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 {
 
@@ -1183,6 +1185,8 @@
             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.
@@ -1452,6 +1456,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);
@@ -1473,6 +1478,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 3a77cf6..4b2e000 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -39,7 +39,6 @@
 #include "adb.h"
 #include "adb_unique_fd.h"
 #include "types.h"
-#include "usb.h"
 
 typedef std::unordered_set<std::string> FeatureSet;
 
@@ -89,6 +88,10 @@
 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();
 
@@ -204,20 +207,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.
@@ -253,6 +242,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
@@ -293,8 +286,10 @@
         return connection_;
     }
 
+#if ADB_HOST
     void SetUsbHandle(usb_handle* h) { usb_handle_ = h; }
     usb_handle* GetUsbHandle() { return usb_handle_; }
+#endif
 
     const TransportId id;
 
@@ -401,8 +396,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_;
@@ -443,8 +440,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);
@@ -454,9 +458,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/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 deca7ea..620aa8e 100644
--- a/adb/types.h
+++ b/adb/types.h
@@ -155,7 +155,7 @@
             return nullptr;
         }
 
-        return chain_.front().data() + begin_offset_;
+        return chain_[start_index_].data() + begin_offset_;
     }
 
     size_type front_size() const {
@@ -163,7 +163,7 @@
             return 0;
         }
 
-        return chain_.front().size() - begin_offset_;
+        return chain_[start_index_].size() - begin_offset_;
     }
 
     size_type size() const { return chain_length_ - begin_offset_; }
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/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/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 9e3a649..ac28fe9 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -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");
@@ -468,6 +477,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 +500,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 +525,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 +537,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/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 87bb8c5..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;
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/fastboot/Android.bp b/fastboot/Android.bp
index a757d56..884856d 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",
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/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/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 5475cae..1486e87 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -97,7 +97,6 @@
 using android::base::Basename;
 using android::base::GetBoolProperty;
 using android::base::GetUintProperty;
-using android::base::Readlink;
 using android::base::Realpath;
 using android::base::SetProperty;
 using android::base::StartsWith;
@@ -1552,8 +1551,8 @@
     return std::chrono::milliseconds(std::move(value));
 }
 
-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 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;
     auto timeout = GetMillisProperty("init.userspace_reboot.userdata_remount.timeoutmillis", 5s);
     while (true) {
@@ -1565,7 +1564,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;
@@ -1577,7 +1582,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";
@@ -1616,14 +1622,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) {
@@ -1637,15 +1640,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;
 }
 
@@ -1656,14 +1659,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;
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index a836d3b..b218f21 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -829,6 +829,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/libdm/Android.bp b/fs_mgr/libdm/Android.bp
index 0499e8d..58241b3 100644
--- a/fs_mgr/libdm/Android.bp
+++ b/fs_mgr/libdm/Android.bp
@@ -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..a209ea6 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -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: [
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/tests/Android.bp b/fs_mgr/tests/Android.bp
index 28dee88..f68ab87 100644
--- a/fs_mgr/tests/Android.bp
+++ b/fs_mgr/tests/Android.bp
@@ -17,7 +17,6 @@
     test_suites: [
         "cts",
         "device-tests",
-        "vts",
         "vts10",
     ],
     compile_multilib: "both",
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/init/Android.bp b/init/Android.bp
index d512a4e..1b3aa18 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -258,7 +258,6 @@
     test_suites: [
         "cts",
         "device-tests",
-        "vts",
         "vts10",
     ],
 }
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/property_service.cpp b/init/property_service.cpp
index 1e4e127..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 {
@@ -489,7 +491,13 @@
         }
         LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                   << process_log_string;
-        DebugRebootLogging();
+        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 081f695..d2dc6d3 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -102,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.
@@ -317,9 +325,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;
@@ -539,26 +547,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;
@@ -591,6 +579,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;
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/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/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.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/tests/Android.bp b/liblog/tests/Android.bp
index fffb809..385b079 100644
--- a/liblog/tests/Android.bp
+++ b/liblog/tests/Android.bp
@@ -96,7 +96,6 @@
     cflags: ["-DNO_PSTORE"],
     test_suites: [
         "cts",
-        "vts",
         "vts10",
     ],
 }
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/ElfInterface.cpp b/libunwindstack/ElfInterface.cpp
index 341275d..821e042 100644
--- a/libunwindstack/ElfInterface.cpp
+++ b/libunwindstack/ElfInterface.cpp
@@ -662,7 +662,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/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/libziparchive/include/ziparchive/zip_archive.h b/libziparchive/include/ziparchive/zip_archive.h
index 435bfb6..3d51de9 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;
@@ -67,15 +68,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 +88,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;
 
@@ -168,7 +209,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
@@ -202,8 +244,8 @@
  * 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
@@ -220,7 +262,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
@@ -230,7 +272,8 @@
  *
  * 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);
 
@@ -242,6 +285,16 @@
 
 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);
 
@@ -249,7 +302,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
 
@@ -270,7 +325,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:
@@ -292,6 +347,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
index c29c37e..46d02aa 100644
--- a/libziparchive/test_ziparchive_large.py
+++ b/libziparchive/test_ziparchive_large.py
@@ -26,13 +26,17 @@
 
 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():
-      contents = name[0] * 1024
       file_path = tempfile.NamedTemporaryFile()
-      with open(file_path.name, 'w') as f:
-        for it in range(0, size):
-          f.write(contents)
+      Zip64Test._WriteFile(file_path.name, size)
       output_zip.write(file_path.name, arcname = name)
 
   def _getEntryNames(self, zip_name):
@@ -79,7 +83,7 @@
     self._ExtractEntries(zip_path.name)
 
 
-  def test_largeCompressedEntries(self):
+  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:
@@ -93,6 +97,50 @@
     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__)))
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 849b68c..7658d5c 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -57,8 +57,6 @@
 #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;
@@ -221,7 +219,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;
       }
@@ -360,8 +358,9 @@
   // Data Size - 2 bytes
   uint16_t offset = 0;
   while (offset < extraFieldLength - 4) {
-    auto headerId = get_unaligned<uint16_t>(extraFieldStart + offset);
-    auto dataSize = get_unaligned<uint16_t>(extraFieldStart + offset + 2);
+    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) {
@@ -376,56 +375,37 @@
       continue;
     }
 
-    uint16_t expectedDataSize = 0;
-    // We expect the extended field to include both uncompressed and compressed size.
-    if (zip32UncompressedSize == UINT32_MAX || zip32CompressedSize == UINT32_MAX) {
-      expectedDataSize += 16;
+    std::optional<uint64_t> uncompressedFileSize;
+    std::optional<uint64_t> compressedFileSize;
+    std::optional<uint64_t> localHeaderOffset;
+    if (zip32UncompressedSize == UINT32_MAX) {
+      uncompressedFileSize = ConsumeUnaligned<uint64_t>(&readPtr);
+    }
+    if (zip32CompressedSize == UINT32_MAX) {
+      compressedFileSize = ConsumeUnaligned<uint64_t>(&readPtr);
     }
     if (zip32LocalFileHeaderOffset == UINT32_MAX) {
-      expectedDataSize += 8;
+      localHeaderOffset = ConsumeUnaligned<uint64_t>(&readPtr);
     }
 
-    if (expectedDataSize == 0) {
+    // 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 != expectedDataSize) {
+    if (dataSize != bytesRead) {
       auto localOffsetString = zip32LocalFileHeaderOffset.has_value()
                                    ? std::to_string(zip32LocalFileHeaderOffset.value())
                                    : "missing";
-      ALOGW("Zip: Invalid data size in zip64 extended field, expect %" PRIu16 ", get %" PRIu16
+      ALOGW("Zip: Invalid data size in zip64 extended field, expect %zu , get %" PRIu16
             ", uncompressed size %" PRIu32 ", compressed size %" PRIu32 ", local header offset %s",
-            expectedDataSize, dataSize, zip32UncompressedSize, zip32CompressedSize,
+            bytesRead, dataSize, zip32UncompressedSize, zip32CompressedSize,
             localOffsetString.c_str());
       return kInvalidFile;
     }
 
-    std::optional<uint64_t> uncompressedFileSize;
-    std::optional<uint64_t> compressedFileSize;
-    std::optional<uint64_t> localHeaderOffset;
-    if (zip32UncompressedSize == UINT32_MAX || zip32CompressedSize == UINT32_MAX) {
-      uncompressedFileSize = get_unaligned<uint64_t>(extraFieldStart + offset);
-      compressedFileSize = get_unaligned<uint64_t>(extraFieldStart + offset + 8);
-      offset += 16;
-
-      // TODO(xunchang) Support handling file large than UINT32_MAX. It's theoretically possible
-      // for libz to (de)compressing file larger than UINT32_MAX. But we should use our own
-      // bytes counter to replace stream.total_out.
-      if (uncompressedFileSize.value() >= UINT32_MAX || compressedFileSize.value() >= UINT32_MAX) {
-        ALOGW(
-            "Zip: File size larger than UINT32_MAX isn't supported yet. uncompressed size %" PRIu64
-            ", compressed size %" PRIu64,
-            uncompressedFileSize.value(), compressedFileSize.value());
-        return kInvalidFile;
-      }
-    }
-
-    if (zip32LocalFileHeaderOffset == UINT32_MAX) {
-      localHeaderOffset = get_unaligned<uint64_t>(extraFieldStart + offset);
-      offset += 8;
-    }
-
     zip64Info->uncompressed_file_size = uncompressedFileSize;
     zip64Info->compressed_file_size = compressedFileSize;
     zip64Info->local_header_offset = localHeaderOffset;
@@ -624,8 +604,9 @@
   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
+  uint8_t ddBuf[24];
   off64_t offset = entry->offset;
   if (entry->method != kCompressStored) {
     offset += entry->compressed_length;
@@ -638,18 +619,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;
   }
 
@@ -657,7 +646,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.
@@ -706,18 +695,11 @@
       return status;
     }
 
-    if (cdr->uncompressed_size == UINT32_MAX || cdr->compressed_size == UINT32_MAX) {
-      CHECK(zip64_info.uncompressed_file_size.has_value());
-      CHECK(zip64_info.compressed_file_size.has_value());
-      // TODO(xunchang) remove the size limit and support entry length > UINT32_MAX.
-      data->uncompressed_length = static_cast<uint32_t>(zip64_info.uncompressed_file_size.value());
-      data->compressed_length = static_cast<uint32_t>(zip64_info.compressed_file_size.value());
-    }
-
-    if (local_header_offset == UINT32_MAX) {
-      CHECK(zip64_info.local_header_offset.has_value());
-      local_header_offset = zip64_info.local_header_offset.value();
-    }
+    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) {
@@ -766,6 +748,13 @@
   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) {
@@ -821,7 +810,7 @@
     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
+      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);
@@ -854,16 +843,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;
@@ -917,8 +905,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;
@@ -935,6 +948,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) {
@@ -943,7 +974,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");
@@ -978,10 +1009,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;
@@ -993,7 +1033,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_;
 };
@@ -1009,12 +1051,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__)
@@ -1030,10 +1077,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__
@@ -1041,7 +1087,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).
@@ -1050,11 +1096,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
@@ -1064,11 +1110,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;
     }
@@ -1084,8 +1128,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_;
@@ -1094,10 +1143,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);
   }
 
@@ -1105,7 +1154,7 @@
 
  private:
   const MappedZipFile& zip_file_;
-  const ZipEntry* entry_;
+  const ZipEntry64* entry_;
 };
 
 // This method is using libz macros with old-style-casts
@@ -1122,8 +1171,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);
@@ -1166,12 +1215,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));
@@ -1202,6 +1253,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;
     }
@@ -1218,9 +1270,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;
   }
@@ -1229,7 +1280,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);
 
@@ -1237,20 +1288,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)) {
@@ -1271,20 +1323,21 @@
   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, &crc);
   } else if (method == kCompressDeflated) {
-    return_value = InflateEntryToWriter(archive->mapped_zip, entry, writer, &crc);
+    return_value = InflateEntryToWriter(handle->mapped_zip, entry, writer, &crc);
   }
 
   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;
     }
@@ -1299,18 +1352,34 @@
   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());
+}
+
+int32_t ExtractEntryToFile(ZipArchiveHandle archive, const ZipEntry* entry, int fd) {
+  ZipEntry64 entry64(*entry);
+  return ExtractEntryToFile(archive, &entry64, fd);
+}
+
+int32_t ExtractEntryToFile(ZipArchiveHandle archive, const ZipEntry64* entry, int fd) {
+  auto writer = FileWriter::Create(fd, entry);
+  if (!writer) {
+    return kIoError;
+  }
+
+  return ExtractToWriter(archive, entry, writer.get());
 }
 
 int GetFileDescriptor(const ZipArchiveHandle archive) {
@@ -1336,7 +1405,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);
@@ -1465,7 +1540,7 @@
   return true;
 }
 
-tm ZipEntry::GetModificationTime() const {
+tm ZipEntryCommon::GetModificationTime() const {
   tm t = {};
 
   t.tm_hour = (mod_time >> 11) & 0x1f;
diff --git a/libziparchive/zip_archive_common.h b/libziparchive/zip_archive_common.h
index a92d4d2..d461856 100644
--- a/libziparchive/zip_archive_common.h
+++ b/libziparchive/zip_archive_common.h
@@ -165,15 +165,24 @@
 
   // 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);
-} __attribute__((packed));
+};
 
 // The zip64 end of central directory locator helps to find the zip64 EOCD.
 struct Zip64EocdLocator {
diff --git a/libziparchive/zip_archive_private.h b/libziparchive/zip_archive_private.h
index 5f5232f..4b43cba 100644
--- a/libziparchive/zip_archive_private.h
+++ b/libziparchive/zip_archive_private.h
@@ -28,6 +28,7 @@
 
 #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"
 
@@ -104,3 +105,21 @@
 
   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 69be3df..f5429be 100644
--- a/libziparchive/zip_archive_test.cc
+++ b/libziparchive/zip_archive_test.cc
@@ -217,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);
@@ -232,12 +232,12 @@
 
 static void AssertIterationNames(void* iteration_cookie,
                                  const std::vector<std::string>& expected_names_sorted) {
-  ZipEntry data;
+  ZipEntry64 data;
   std::vector<std::string> names;
-  std::string name;
+  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(name);
+    names.push_back(std::string(name));
   }
   // End of iteration.
   ASSERT_EQ(-1, Next(iteration_cookie, &data, &name));
@@ -325,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));
@@ -338,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);
 
@@ -359,7 +359,7 @@
   ZipArchiveHandle handle;
   ASSERT_EQ(0, OpenArchiveWrapper(kValidZip, &handle));
 
-  ZipEntry data;
+  ZipEntry64 data;
   ASSERT_EQ(kInvalidEntryName, FindEntry(handle, "", &data));
 
   CloseArchive(handle);
@@ -370,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);
@@ -383,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);
@@ -415,9 +415,9 @@
                                   static_cast<off64_t>(leading_garbage.size())));
 
   // 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());
   auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[a_size]);
   ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer.get(), a_size));
@@ -425,7 +425,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 = std::unique_ptr<uint8_t[]>(new uint8_t[b_size]);
   ASSERT_EQ(0, ExtractToMemory(handle, &data, buffer.get(), b_size));
@@ -439,9 +439,9 @@
   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));
@@ -450,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));
@@ -503,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);
@@ -526,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);
 
@@ -583,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));
 
@@ -594,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
@@ -620,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);
@@ -635,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);
@@ -681,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())));
@@ -741,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();
@@ -780,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);
@@ -887,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;
   }
 
@@ -919,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 {
@@ -979,10 +982,11 @@
     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();
+             compressed_bytes.size() + data_descriptor.size();
     }
 
     void CopyToOutput(std::vector<uint8_t>* output) const {
@@ -990,6 +994,7 @@
       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));
     }
   };
 
@@ -1057,7 +1062,7 @@
   // 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 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;
@@ -1084,6 +1089,21 @@
     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) {
@@ -1205,7 +1225,7 @@
   ZipArchiveHandle handle;
   ASSERT_EQ(
       0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle));
-  ZipEntry entry;
+  ZipEntry64 entry;
   ASSERT_EQ(0, FindEntry(handle, "a.txt", &entry));
   ASSERT_EQ(200, entry.uncompressed_length);
   ASSERT_EQ(200, entry.compressed_length);
@@ -1228,7 +1248,7 @@
   ZipArchiveHandle handle;
   ASSERT_EQ(
       0, OpenArchiveFromMemory(zip_content_.data(), zip_content_.size(), "debug_zip64", &handle));
-  ZipEntry entry;
+  ZipEntry64 entry;
   ASSERT_NE(0, FindEntry(handle, "a.txt", &entry));
 
   CloseArchive(handle);
@@ -1250,7 +1270,7 @@
   ASSERT_EQ(0, StartIteration(handle, &iteration_cookie));
   std::set<std::string_view> result;
   std::string_view name;
-  ZipEntry entry;
+  ZipEntry64 entry;
   while (Next(iteration_cookie, &entry, &name) == 0) result.emplace(name);
   ASSERT_EQ(names, result);
 
@@ -1270,3 +1290,38 @@
       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
index 107ec47..14e49bb 100644
--- a/libziparchive/zip_error.cpp
+++ b/libziparchive/zip_error.cpp
@@ -33,6 +33,7 @@
     "I/O error",
     "File mapping failed",
     "Allocation failed",
+    "Unsupported zip entry size",
 };
 
 const char* ErrorCodeString(int32_t error_code) {
diff --git a/libziparchive/zip_error.h b/libziparchive/zip_error.h
index 37fd55f..3d7285d 100644
--- a/libziparchive/zip_error.h
+++ b/libziparchive/zip_error.h
@@ -66,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 f345ffc..17d4833 100644
--- a/libziparchive/ziptool.cpp
+++ b/libziparchive/ziptool.cpp
@@ -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/logd/tests/Android.bp b/logd/tests/Android.bp
index 2519a84..9a5defa 100644
--- a/logd/tests/Android.bp
+++ b/logd/tests/Android.bp
@@ -63,7 +63,6 @@
     },
     test_suites: [
         "cts",
-        "vts",
         "vts10",
     ],
 }
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