Merge "init_kill_services_test: reboot device first."
diff --git a/adb/Android.bp b/adb/Android.bp
index 12d9a14..6386a78 100644
--- a/adb/Android.bp
+++ b/adb/Android.bp
@@ -470,6 +470,7 @@
         "libadbd_core",
         "libbrotli",
         "libdiagnose_usb",
+        "liblz4",
     ],
 
     shared_libs: [
@@ -571,6 +572,7 @@
         "libbrotli",
         "libcutils_sockets",
         "libdiagnose_usb",
+        "liblz4",
         "libmdnssd",
     ],
 
@@ -605,6 +607,7 @@
         "libadbd_services",
         "libasyncio",
         "libcap",
+        "liblz4",
         "libminijail",
         "libssl",
     ],
diff --git a/adb/adb.cpp b/adb/adb.cpp
index 6c03f74..0518e9d 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -1071,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 7bc60fc..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>
@@ -237,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.
@@ -250,6 +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();
-#endif
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 092a866..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);
     }
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 2ed58b2..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"
@@ -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,7 +668,7 @@
 
             while (true) {
                 Block output;
-                EncodeResult result = encoder.Encode(&output);
+                EncodeResult result = encoder->Encode(&output);
                 if (result == EncodeResult::Error) {
                     Error("compressing '%s' locally failed", lpath.c_str());
                     return false;
@@ -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,33 +1159,32 @@
         }
 
         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;
-            DecodeResult result = decoder.Decode(&output);
+            DecodeResult result = decoder->Decode(&output);
 
             if (result == DecodeResult::Error) {
                 sc.Error("decompress failed");
@@ -1093,8 +1201,7 @@
             }
 
             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 == DecodeResult::NeedInput) {
@@ -1102,33 +1209,19 @@
             } else if (result == DecodeResult::MoreOutput) {
                 continue;
             } else if (result == DecodeResult::Done) {
-                reading = false;
-                break;
+                sc.RecordFilesTransferred(1);
+                return true;
             } else {
                 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 4a9eadc..05e210f 100644
--- a/adb/client/main.cpp
+++ b/adb/client/main.cpp
@@ -105,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/compression_utils.h b/adb/compression_utils.h
index c445095..a0c48a2 100644
--- a/adb/compression_utils.h
+++ b/adb/compression_utils.h
@@ -16,10 +16,15 @@
 
 #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"
 
@@ -37,15 +42,103 @@
     MoreOutput,
 };
 
-struct BrotliDecoder {
+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)
-        : output_buffer_(output_buffer),
+        : Decoder(output_buffer),
           decoder_(BrotliDecoderCreateInstance(nullptr, nullptr, nullptr),
                    BrotliDecoderDestroyInstance) {}
 
-    void Append(Block&& block) { input_buffer_.append(std::move(block)); }
-
-    DecodeResult Decode(std::span<char>* output) {
+    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());
 
@@ -63,7 +156,8 @@
 
         switch (r) {
             case BROTLI_DECODER_RESULT_SUCCESS:
-                return DecodeResult::Done;
+                // 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:
@@ -77,33 +171,29 @@
     }
 
   private:
-    IOVector input_buffer_;
-    std::span<char> output_buffer_;
     std::unique_ptr<BrotliDecoderState, void (*)(BrotliDecoderState*)> decoder_;
 };
 
-template <size_t OutputBlockSize>
-struct BrotliEncoder {
-    explicit BrotliEncoder()
-        : output_block_(OutputBlockSize),
-          output_bytes_left_(OutputBlockSize),
+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);
     }
 
-    void Append(Block input) { input_buffer_.append(std::move(input)); }
-    void Finish() { finished_ = true; }
-
-    EncodeResult Encode(Block* output) {
+    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() +
-                                                           (OutputBlockSize - 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_) {
@@ -121,13 +211,13 @@
             output_bytes_left_ = available_out;
 
             if (BrotliEncoderIsFinished(encoder_.get())) {
-                output_block_.resize(OutputBlockSize - output_bytes_left_);
+                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(OutputBlockSize);
-                output_bytes_left_ = OutputBlockSize;
+                output_block_.resize(output_block_size_);
+                output_bytes_left_ = output_block_size_;
                 return EncodeResult::MoreOutput;
             } else if (input_buffer_.empty()) {
                 return EncodeResult::NeedInput;
@@ -136,9 +226,158 @@
     }
 
   private:
-    bool finished_ = false;
-    IOVector input_buffer_;
     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/daemon/file_sync_service.cpp b/adb/daemon/file_sync_service.cpp
index 5ccddea..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>
@@ -266,37 +267,60 @@
     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;
-            DecodeResult result = decoder.Decode(&output);
+            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 == DecodeResult::NeedInput) {
@@ -304,7 +328,7 @@
             } else if (result == DecodeResult::MoreOutput) {
                 continue;
             } else if (result == DecodeResult::Done) {
-                break;
+                return true;
             } else {
                 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,45 +568,80 @@
         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;
-    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;
+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);
 
-        if (!WriteFdExactly(s, &msg.data, sizeof(msg.data)) || !WriteFdExactly(s, &buffer[0], r)) {
-            return false;
-        }
+    unique_fd fd(adb_open(path, O_RDONLY | O_CLOEXEC));
+    if (fd < 0) {
+        SendSyncFailErrno(s, "open failed");
+        return false;
     }
 
-    return true;
-}
+    int rc = posix_fadvise(fd.get(), 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
+    if (rc != 0) {
+        D("[ Failed to fadvise: %s ]", strerror(rc));
+    }
 
-static bool recv_compressed(borrowed_fd s, unique_fd fd) {
     syncmsg msg;
     msg.data.id = ID_DATA;
 
-    BrotliEncoder<SYNC_DATA_MAX> encoder;
+    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) {
@@ -624,15 +653,15 @@
         }
 
         if (r == 0) {
-            encoder.Finish();
+            encoder->Finish();
         } else {
             input.resize(r);
-            encoder.Append(std::move(input));
+            encoder->Append(std::move(input));
         }
 
         while (true) {
             Block output;
-            EncodeResult result = encoder.Encode(&output);
+            EncodeResult result = encoder->Encode(&output);
             if (result == EncodeResult::Error) {
                 SendSyncFailErrno(s, "compress failed");
                 return false;
@@ -657,42 +686,13 @@
         }
     }
 
-    return true;
-}
-
-static bool recv_impl(borrowed_fd s, const char* path, bool compressed, std::vector<char>& buffer) {
-    __android_log_security_bswrite(SEC_TAG_ADB_RECV_FILE, path);
-
-    unique_fd fd(adb_open(path, O_RDONLY | O_CLOEXEC));
-    if (fd < 0) {
-        SendSyncFailErrno(s, "open failed");
-        return false;
-    }
-
-    int rc = posix_fadvise(fd.get(), 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE);
-    if (rc != 0) {
-        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_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) {
@@ -706,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_ffs.cpp b/adb/daemon/usb_ffs.cpp
index 7bd611b..e538ca8 100644
--- a/adb/daemon/usb_ffs.cpp
+++ b/adb/daemon/usb_ffs.cpp
@@ -300,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));
@@ -315,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/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/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/transport.cpp b/adb/transport.cpp
index e06dbe3..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.
diff --git a/adb/transport.h b/adb/transport.h
index b1984db..4b2e000 100644
--- a/adb/transport.h
+++ b/adb/transport.h
@@ -88,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();
 
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/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/first_stage_init.cpp b/init/first_stage_init.cpp
index bd71cb5..ef8ffbe 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -234,7 +234,16 @@
         old_root_dir.reset();
     }
 
-    Modprobe m({"/lib/modules"});
+    std::string module_load_file = "modules.load";
+    if (IsRecoveryMode() && !ForceNormalBoot(cmdline)) {
+        struct stat fileStat;
+        std::string recovery_load_path = "/lib/modules/modules.load.recovery";
+        if (!stat(recovery_load_path.c_str(), &fileStat)) {
+            module_load_file = "modules.load.recovery";
+        }
+    }
+
+    Modprobe m({"/lib/modules"}, module_load_file);
     auto want_console = ALLOW_FIRST_STAGE_CONSOLE && FirstStageConsole(cmdline);
     if (!m.LoadListedModules(!want_console)) {
         if (want_console) {
diff --git a/init/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 655b8de..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 {
@@ -492,6 +494,10 @@
         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/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/libmodprobe/include/modprobe/modprobe.h b/libmodprobe/include/modprobe/modprobe.h
index ee6ae7a..297036e 100644
--- a/libmodprobe/include/modprobe/modprobe.h
+++ b/libmodprobe/include/modprobe/modprobe.h
@@ -24,7 +24,7 @@
 
 class Modprobe {
   public:
-    Modprobe(const std::vector<std::string>&);
+    Modprobe(const std::vector<std::string>&, const std::string load_file = "modules.load");
 
     bool LoadListedModules(bool strict = true);
     bool LoadWithAliases(const std::string& module_name, bool strict,
diff --git a/libmodprobe/libmodprobe.cpp b/libmodprobe/libmodprobe.cpp
index f22bbf1..d193796 100644
--- a/libmodprobe/libmodprobe.cpp
+++ b/libmodprobe/libmodprobe.cpp
@@ -312,7 +312,7 @@
     }
 }
 
-Modprobe::Modprobe(const std::vector<std::string>& base_paths) {
+Modprobe::Modprobe(const std::vector<std::string>& base_paths, const std::string load_file) {
     using namespace std::placeholders;
 
     for (const auto& base_path : base_paths) {
@@ -326,7 +326,7 @@
         ParseCfg(base_path + "/modules.softdep", softdep_callback);
 
         auto load_callback = std::bind(&Modprobe::ParseLoadCallback, this, _1);
-        ParseCfg(base_path + "/modules.load", load_callback);
+        ParseCfg(base_path + "/" + load_file, load_callback);
 
         auto options_callback = std::bind(&Modprobe::ParseOptionsCallback, this, _1);
         ParseCfg(base_path + "/modules.options", options_callback);
diff --git a/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/DexFile.cpp b/libunwindstack/DexFile.cpp
index bf63abf..8fc3d23 100644
--- a/libunwindstack/DexFile.cpp
+++ b/libunwindstack/DexFile.cpp
@@ -50,6 +50,22 @@
 
 std::unique_ptr<DexFile> DexFile::Create(uint64_t dex_file_offset_in_memory, Memory* memory,
                                          MapInfo* info) {
+  if (UNLIKELY(!HasDexSupport())) {
+    return nullptr;
+  }
+
+  size_t max_size = info->end - dex_file_offset_in_memory;
+  if (memory->IsLocal()) {
+    size_t size = max_size;
+
+    std::string err_msg;
+    std::unique_ptr<art_api::dex::DexFile> art_dex_file = DexFile::OpenFromMemory(
+        reinterpret_cast<void const*>(dex_file_offset_in_memory), &size, info->name, &err_msg);
+    if (art_dex_file != nullptr && size <= max_size) {
+      return std::unique_ptr<DexFile>(new DexFile(art_dex_file));
+    }
+  }
+
   if (!info->name.empty()) {
     std::unique_ptr<DexFile> dex_file =
         DexFileFromFile::Create(dex_file_offset_in_memory - info->start + info->offset, info->name);
@@ -57,7 +73,7 @@
       return dex_file;
     }
   }
-  return DexFileFromMemory::Create(dex_file_offset_in_memory, memory, info->name);
+  return DexFileFromMemory::Create(dex_file_offset_in_memory, memory, info->name, max_size);
 }
 
 bool DexFile::GetMethodInformation(uint64_t dex_offset, std::string* method_name,
@@ -94,7 +110,8 @@
 
 std::unique_ptr<DexFileFromMemory> DexFileFromMemory::Create(uint64_t dex_file_offset_in_memory,
                                                              Memory* memory,
-                                                             const std::string& name) {
+                                                             const std::string& name,
+                                                             size_t max_size) {
   if (UNLIKELY(!HasDexSupport())) {
     return nullptr;
   }
@@ -105,6 +122,9 @@
     std::string error_msg;
     std::unique_ptr<art_api::dex::DexFile> art_dex_file =
         OpenFromMemory(backing_memory.data(), &size, name, &error_msg);
+    if (size > max_size) {
+      return nullptr;
+    }
 
     if (art_dex_file != nullptr) {
       return std::unique_ptr<DexFileFromMemory>(
diff --git a/libunwindstack/DexFile.h b/libunwindstack/DexFile.h
index 4e8369f..fe185da 100644
--- a/libunwindstack/DexFile.h
+++ b/libunwindstack/DexFile.h
@@ -55,7 +55,8 @@
 class DexFileFromMemory : public DexFile {
  public:
   static std::unique_ptr<DexFileFromMemory> Create(uint64_t dex_file_offset_in_memory,
-                                                   Memory* memory, const std::string& name);
+                                                   Memory* memory, const std::string& name,
+                                                   size_t max_size);
 
  private:
   DexFileFromMemory(std::unique_ptr<art_api::dex::DexFile>& art_dex_file,
diff --git a/libunwindstack/MemoryLocal.h b/libunwindstack/MemoryLocal.h
index 29aaf12..7e027cf 100644
--- a/libunwindstack/MemoryLocal.h
+++ b/libunwindstack/MemoryLocal.h
@@ -28,6 +28,8 @@
   MemoryLocal() = default;
   virtual ~MemoryLocal() = default;
 
+  bool IsLocal() const override { return true; }
+
   size_t Read(uint64_t addr, void* dst, size_t size) override;
 };
 
diff --git a/libunwindstack/include/unwindstack/Memory.h b/libunwindstack/include/unwindstack/Memory.h
index 3106564..ecd908a 100644
--- a/libunwindstack/include/unwindstack/Memory.h
+++ b/libunwindstack/include/unwindstack/Memory.h
@@ -41,6 +41,8 @@
 
   virtual void Clear() {}
 
+  virtual bool IsLocal() const { return false; }
+
   virtual size_t Read(uint64_t addr, void* dst, size_t size) = 0;
 
   bool ReadFully(uint64_t addr, void* dst, size_t size);
diff --git a/libunwindstack/tests/DexFileTest.cpp b/libunwindstack/tests/DexFileTest.cpp
index dc935a3..1deba01 100644
--- a/libunwindstack/tests/DexFileTest.cpp
+++ b/libunwindstack/tests/DexFileTest.cpp
@@ -21,6 +21,7 @@
 
 #include <unordered_map>
 
+#include <MemoryLocal.h>
 #include <android-base/file.h>
 #include <gtest/gtest.h>
 #include <unwindstack/MapInfo.h>
@@ -109,7 +110,7 @@
 
   memory.SetMemory(0x1000, kDexData, 10);
 
-  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "") == nullptr);
+  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "", sizeof(kDexData)) == nullptr);
 }
 
 TEST(DexFileTest, from_memory_fail_too_small_for_data) {
@@ -117,7 +118,7 @@
 
   memory.SetMemory(0x1000, kDexData, sizeof(kDexData) - 2);
 
-  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "") == nullptr);
+  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "", sizeof(kDexData)) == nullptr);
 }
 
 TEST(DexFileTest, from_memory_open) {
@@ -125,7 +126,7 @@
 
   memory.SetMemory(0x1000, kDexData, sizeof(kDexData));
 
-  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "") != nullptr);
+  EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "", sizeof(kDexData)) != nullptr);
 }
 
 TEST(DexFileTest, from_memory_no_leak) {
@@ -136,7 +137,7 @@
   size_t first_allocated_bytes = 0;
   size_t last_allocated_bytes = 0;
   for (size_t i = 0; i < kNumLeakLoops; i++) {
-    EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "") != nullptr);
+    EXPECT_TRUE(DexFileFromMemory::Create(0x1000, &memory, "", sizeof(kDexData)) != nullptr);
     ASSERT_NO_FATAL_FAILURE(CheckForLeak(i, &first_allocated_bytes, &last_allocated_bytes));
   }
 }
@@ -213,6 +214,43 @@
   EXPECT_TRUE(dex_file == nullptr);
 }
 
+TEST(DexFileTest, create_using_memory_size_too_small) {
+  MemoryFake memory;
+  memory.SetMemory(0x4000, kDexData, sizeof(kDexData));
+  MapInfo info(nullptr, nullptr, 0x100, sizeof(kDexData) - 2, 0x200, 0x5, "/does/not/exist");
+  EXPECT_TRUE(DexFile::Create(0x4000, &memory, &info) != nullptr);
+}
+
+class MemoryLocalFake : public MemoryLocal {
+ public:
+  MemoryLocalFake(size_t memory_size) : backing_(memory_size) {}
+  virtual ~MemoryLocalFake() = default;
+
+  void* Data() { return backing_.data(); }
+
+ private:
+  std::vector<void*> backing_;
+};
+
+TEST(DexFileTest, create_using_local_memory) {
+  MemoryLocalFake memory(sizeof(kDexData));
+
+  memcpy(memory.Data(), kDexData, sizeof(kDexData));
+  uint64_t start = reinterpret_cast<uint64_t>(memory.Data());
+  MapInfo info(nullptr, nullptr, start, start + 0x1000, 0x200, 0x5, "/does/not/exist");
+  EXPECT_TRUE(DexFile::Create(start, &memory, &info) != nullptr);
+}
+
+TEST(DexFileTest, create_using_local_memory_size_too_small) {
+  MemoryLocalFake memory(sizeof(kDexData));
+
+  memcpy(memory.Data(), kDexData, sizeof(kDexData));
+  uint64_t start = reinterpret_cast<uint64_t>(memory.Data());
+  MapInfo info(nullptr, nullptr, start, start + sizeof(kDexData) - 2, 0x200, 0x5,
+               "/does/not/exist");
+  EXPECT_TRUE(DexFile::Create(start, &memory, &info) == nullptr);
+}
+
 TEST(DexFileTest, get_method) {
   MemoryFake memory;
   memory.SetMemory(0x4000, kDexData, sizeof(kDexData));
diff --git a/libziparchive/include/ziparchive/zip_archive.h b/libziparchive/include/ziparchive/zip_archive.h
index 4697bb7..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,16 +68,6 @@
   // 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};
@@ -97,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;
 
@@ -172,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
@@ -206,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
@@ -224,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
@@ -234,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);
 
@@ -246,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);
 
@@ -253,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
 
@@ -274,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:
@@ -296,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 6b82f63..46d02aa 100644
--- a/libziparchive/test_ziparchive_large.py
+++ b/libziparchive/test_ziparchive_large.py
@@ -83,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:
@@ -99,8 +99,7 @@
 
   def test_forceDataDescriptor(self):
     file_path = tempfile.NamedTemporaryFile(suffix='.txt')
-    # TODO create the entry > 4GiB.
-    self._WriteFile(file_path.name, 1024)
+    self._WriteFile(file_path.name, 5000 * 1024)
 
     zip_path = tempfile.NamedTemporaryFile(suffix='.zip')
     with zipfile.ZipFile(zip_path, 'w', allowZip64=True) as output_zip:
@@ -113,6 +112,35 @@
     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/zip_archive.cc b/libziparchive/zip_archive.cc
index 031d43a..7658d5c 100644
--- a/libziparchive/zip_archive.cc
+++ b/libziparchive/zip_archive.cc
@@ -406,15 +406,6 @@
       return kInvalidFile;
     }
 
-    // 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.has_value() && uncompressedFileSize.value() > UINT32_MAX) ||
-        (compressedFileSize.has_value() && compressedFileSize.value() > UINT32_MAX)) {
-      ALOGW("Zip: File size larger than UINT32_MAX isn't supported yet");
-      return kInvalidFile;
-    }
-
     zip64Info->uncompressed_file_size = uncompressedFileSize;
     zip64Info->compressed_file_size = compressedFileSize;
     zip64Info->local_header_offset = localHeaderOffset;
@@ -613,7 +604,7 @@
   delete archive;
 }
 
-static int32_t ValidateDataDescriptor(MappedZipFile& mapped_zip, ZipEntry* entry) {
+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;
@@ -644,7 +635,7 @@
   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
+    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);
@@ -655,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.
@@ -704,11 +695,8 @@
       return status;
     }
 
-    // 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_or(cdr->uncompressed_size));
-    data->compressed_length =
-        static_cast<uint32_t>(zip64_info.compressed_file_size.value_or(cdr->compressed_size));
+    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;
@@ -822,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);
@@ -855,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;
@@ -918,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;
@@ -936,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) {
@@ -944,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");
@@ -979,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;
@@ -994,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_;
 };
@@ -1010,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__)
@@ -1031,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__
@@ -1042,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).
@@ -1051,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
@@ -1065,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;
     }
@@ -1085,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_;
@@ -1095,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);
   }
 
@@ -1106,7 +1154,7 @@
 
  private:
   const MappedZipFile& zip_file_;
-  const ZipEntry* entry_;
+  const ZipEntry64* entry_;
 };
 
 // This method is using libz macros with old-style-casts
@@ -1123,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);
@@ -1167,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));
@@ -1203,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;
     }
@@ -1219,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;
   }
@@ -1230,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);
 
@@ -1238,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)) {
@@ -1272,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;
     }
@@ -1300,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) {
@@ -1337,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);
@@ -1466,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_private.h b/libziparchive/zip_archive_private.h
index 4ed07aa..4b43cba 100644
--- a/libziparchive/zip_archive_private.h
+++ b/libziparchive/zip_archive_private.h
@@ -106,7 +106,8 @@
   bool InitializeCentralDirectory(off64_t cd_start_offset, size_t cd_size);
 };
 
-int32_t ExtractToWriter(ZipArchiveHandle handle, ZipEntry* entry, zip_archive::Writer* writer);
+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>
diff --git a/libziparchive/zip_archive_test.cc b/libziparchive/zip_archive_test.cc
index 3563340..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 {
@@ -1222,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);
@@ -1245,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);
@@ -1267,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);
 
@@ -1297,7 +1300,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));
 
   VectorWriter writer;
@@ -1315,7 +1318,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, "b.txt", &entry));
 
   VectorWriter writer;
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/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 adfdb7b..6564e8f 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -1063,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