From 5949fccc4251cba1e1ad83a2af22023c9e15870e Mon Sep 17 00:00:00 2001 From: Josh Gao Date: Mon, 30 Mar 2020 23:25:16 -0700 Subject: [PATCH] adb: add dry-run option to push/sync. Make it easier to benchmark file sync performance by ignoring the file system. Bug: https://issuetracker.google.com/150827486 Test: test_device.py Change-Id: Icfa4b28eb5206f1914c0c163833d070a3748c3ea --- adb/client/adb_install.cpp | 2 +- adb/client/commandline.cpp | 29 ++++-- adb/client/fastdeploy.cpp | 2 +- adb/client/file_sync_client.cpp | 53 +++++++---- adb/client/file_sync_client.h | 4 +- adb/daemon/file_sync_service.cpp | 148 +++++++++++++++++-------------- adb/file_sync_protocol.h | 1 + adb/test_device.py | 53 +++++++++++ adb/transport.cpp | 2 + adb/transport.h | 2 + 10 files changed, 201 insertions(+), 95 deletions(-) diff --git a/adb/client/adb_install.cpp b/adb/client/adb_install.cpp index f022b8bcc..da3154e4a 100644 --- a/adb/client/adb_install.cpp +++ b/adb/client/adb_install.cpp @@ -290,7 +290,7 @@ static int install_app_legacy(int argc, const char** argv, bool use_fastdeploy) } } - if (do_sync_push(apk_file, apk_dest.c_str(), false, CompressionType::Any)) { + 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/commandline.cpp b/adb/client/commandline.cpp index 02f6e9c1d..ceb21d595 100644 --- a/adb/client/commandline.cpp +++ b/adb/client/commandline.cpp @@ -132,6 +132,7 @@ static void help() { " 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" + " -n: dry run: push files to device without storing to the filesystem\n" " -z: enable compression with a specified algorithm (any, none, brotli)\n" " -Z: disable compression\n" " pull [-a] [-z ALGORITHM] [-Z] REMOTE... LOCAL\n" @@ -141,6 +142,7 @@ static void help() { " -Z: disable compression\n" " sync [-l] [-z ALGORITHM] [-Z] [all|data|odm|oem|product|system|system_ext|vendor]\n" " sync a local build from $ANDROID_PRODUCT_OUT to the device (default all)\n" + " -n: dry run: push files to device without storing to the filesystem\n" " -l: list files that would be copied, but don't copy them\n" " -z: enable compression with a specified algorithm (any, none, brotli)\n" " -Z: disable compression\n" @@ -1340,7 +1342,7 @@ static CompressionType parse_compression_type(const std::string& str, bool allow static void parse_push_pull_args(const char** arg, int narg, std::vector* srcs, const char** dst, bool* copy_attrs, bool* sync, - CompressionType* compression) { + CompressionType* compression, bool* dry_run) { *copy_attrs = false; if (const char* adb_compression = getenv("ADB_COMPRESSION")) { *compression = parse_compression_type(adb_compression, true); @@ -1364,6 +1366,8 @@ static void parse_push_pull_args(const char** arg, int narg, std::vector srcs; const char* dst = nullptr; - parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, ©_attrs, &sync, &compression); + parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, ©_attrs, &sync, &compression, + &dry_run); if (srcs.empty() || !dst) error_exit("push requires an argument"); - return do_sync_push(srcs, dst, sync, compression) ? 0 : 1; + return do_sync_push(srcs, dst, sync, compression, dry_run) ? 0 : 1; } else if (!strcmp(argv[0], "pull")) { bool copy_attrs = false; CompressionType compression = CompressionType::Any; std::vector srcs; const char* dst = "."; - parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, ©_attrs, nullptr, &compression); + parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, ©_attrs, nullptr, &compression, + nullptr); if (srcs.empty()) error_exit("pull requires an argument"); return do_sync_pull(srcs, dst, copy_attrs, compression) ? 0 : 1; } else if (!strcmp(argv[0], "install")) { @@ -1949,6 +1956,7 @@ int adb_commandline(int argc, const char** argv) { } else if (!strcmp(argv[0], "sync")) { std::string src; bool list_only = false; + bool dry_run = false; CompressionType compression = CompressionType::Any; if (const char* adb_compression = getenv("ADB_COMPRESSION"); adb_compression) { @@ -1956,11 +1964,14 @@ int adb_commandline(int argc, const char** argv) { } int opt; - while ((opt = getopt(argc, const_cast(argv), "lz:Z")) != -1) { + while ((opt = getopt(argc, const_cast(argv), "lnz:Z")) != -1) { switch (opt) { case 'l': list_only = true; break; + case 'n': + dry_run = true; + break; case 'z': compression = parse_compression_type(optarg, false); break; @@ -1968,7 +1979,7 @@ int adb_commandline(int argc, const char** argv) { compression = CompressionType::None; break; default: - error_exit("usage: adb sync [-l] [-z ALGORITHM] [-Z] [PARTITION]"); + error_exit("usage: adb sync [-l] [-n] [-z ALGORITHM] [-Z] [PARTITION]"); } } @@ -1977,7 +1988,7 @@ int adb_commandline(int argc, const char** argv) { } else if (optind + 1 == argc) { src = argv[optind]; } else { - error_exit("usage: adb sync [-l] [-z ALGORITHM] [-Z] [PARTITION]"); + error_exit("usage: adb sync [-l] [-n] [-z ALGORITHM] [-Z] [PARTITION]"); } std::vector partitions{"data", "odm", "oem", "product", @@ -1988,7 +1999,9 @@ int adb_commandline(int argc, const char** argv) { std::string src_dir{product_file(partition)}; if (!directory_exists(src_dir)) continue; found = true; - if (!do_sync_sync(src_dir, "/" + partition, list_only, compression)) 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 37f1a90e5..bc4b91bb9 100644 --- a/adb/client/fastdeploy.cpp +++ b/adb/client/fastdeploy.cpp @@ -112,7 +112,7 @@ static void push_to_device(const void* data, size_t byte_count, const char* dst, // but can't be removed until after the push. unix_close(tf.release()); - if (!do_sync_push(srcs, dst, sync, CompressionType::Any)) { + 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 75334d74a..681673449 100644 --- a/adb/client/file_sync_client.cpp +++ b/adb/client/file_sync_client.cpp @@ -238,6 +238,7 @@ class SyncConnection { 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()); @@ -264,6 +265,7 @@ class SyncConnection { 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. @@ -340,7 +342,7 @@ class SyncConnection { return WriteFdExactly(fd, buf.data(), buf.size()); } - bool SendSend2(std::string_view path, mode_t mode, CompressionType compression) { + 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; @@ -373,6 +375,10 @@ class SyncConnection { LOG(FATAL) << "unexpected CompressionType::Any"; } + if (dry_run) { + msg.send_v2_setup.flags |= kSyncFlagDryRun; + } + buf.resize(sizeof(SyncRequest) + path.length() + sizeof(msg.send_v2_setup)); void* p = buf.data(); @@ -541,7 +547,12 @@ class SyncConnection { // 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()); @@ -581,14 +592,20 @@ class SyncConnection { } bool SendLargeFile(const std::string& path, mode_t mode, const std::string& lpath, - const std::string& rpath, unsigned mtime, CompressionType compression) { + 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)) { + if (!SendSend2(path, mode, compression, dry_run)) { Error("failed to send ID_SEND_V2 message '%s': %s", path.c_str(), strerror(errno)); return false; } @@ -908,6 +925,7 @@ class SyncConnection { 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_; @@ -989,7 +1007,8 @@ static bool sync_stat_fallback(SyncConnection& sc, const std::string& path, stru } static bool sync_send(SyncConnection& sc, const std::string& lpath, const std::string& rpath, - unsigned mtime, mode_t mode, bool sync, CompressionType compression) { + unsigned mtime, mode_t mode, bool sync, CompressionType compression, + bool dry_run) { if (sync) { struct stat st; if (sync_lstat(sc, rpath, &st)) { @@ -1010,7 +1029,7 @@ static bool sync_send(SyncConnection& sc, const std::string& lpath, const std::s } 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(); @@ -1028,11 +1047,12 @@ static bool sync_send(SyncConnection& sc, const std::string& lpath, const std::s 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, compression)) { + if (!sc.SendLargeFile(rpath, mode, lpath, rpath, mtime, compression, dry_run)) { return false; } } @@ -1284,7 +1304,7 @@ static bool is_root_dir(std::string_view path) { static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath, std::string rpath, bool check_timestamps, bool list_only, - CompressionType compression) { + CompressionType compression, bool dry_run) { sc.NewTransfer(); // Make sure that both directory paths end in a slash. @@ -1366,7 +1386,8 @@ static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath, std::st 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, compression)) { + if (!sync_send(sc, ci.lpath, ci.rpath, ci.time, ci.mode, false, compression, + dry_run)) { return false; } } @@ -1382,7 +1403,7 @@ static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath, std::st } bool do_sync_push(const std::vector& srcs, const char* dst, bool sync, - CompressionType compression) { + CompressionType compression, bool dry_run) { SyncConnection sc; if (!sc.IsValid()) return false; @@ -1447,7 +1468,8 @@ bool do_sync_push(const std::vector& srcs, const char* dst, bool sy dst_dir.append(android::base::Basename(src_path)); } - success &= copy_local_dir_remote(sc, src_path, dst_dir, sync, false, compression); + 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); @@ -1468,7 +1490,8 @@ bool do_sync_push(const std::vector& srcs, const char* dst, bool sy sc.NewTransfer(); sc.SetExpectedTotalBytes(st.st_size); - success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync, compression); + success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync, compression, + dry_run); sc.ReportTransferRate(src_path, TransferDirection::push); } @@ -1712,11 +1735,11 @@ bool do_sync_pull(const std::vector& srcs, const char* dst, bool co } bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only, - CompressionType compression) { + CompressionType compression, bool dry_run) { SyncConnection sc; if (!sc.IsValid()) return false; - bool success = copy_local_dir_remote(sc, lpath, rpath, true, list_only, compression); + 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 aab2e3f86..cb8ca9323 100644 --- a/adb/client/file_sync_client.h +++ b/adb/client/file_sync_client.h @@ -23,9 +23,9 @@ bool do_sync_ls(const char* path); bool do_sync_push(const std::vector& srcs, const char* dst, bool sync, - CompressionType compression); + CompressionType compression, bool dry_run); bool do_sync_pull(const std::vector& srcs, const char* dst, bool copy_attrs, CompressionType compression, const char* name = nullptr); bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only, - CompressionType compression); + CompressionType compression, bool dry_run); diff --git a/adb/daemon/file_sync_service.cpp b/adb/daemon/file_sync_service.cpp index 3436e32c0..d58131e5b 100644 --- a/adb/daemon/file_sync_service.cpp +++ b/adb/daemon/file_sync_service.cpp @@ -315,9 +315,12 @@ static bool handle_send_file_data(borrowed_fd s, unique_fd fd, uint32_t* timesta 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) { @@ -337,66 +340,65 @@ static bool handle_send_file_data(borrowed_fd s, unique_fd fd, uint32_t* timesta static bool handle_send_file(borrowed_fd s, const char* path, uint32_t* timestamp, uid_t uid, gid_t gid, uint64_t capabilities, mode_t mode, - CompressionType compression, std::vector& buffer, + CompressionType compression, bool dry_run, std::vector& buffer, bool do_unlink) { - int rc; 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)); } - - 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)); } + 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 @@ -435,7 +437,7 @@ extern bool handle_send_link(int s, const std::string& path, uint32_t* timestamp, std::vector& 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& buffer) { syncmsg msg; @@ -454,19 +456,21 @@ static bool handle_send_link(int s, const std::string& path, uint32_t* timestamp 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; } } @@ -487,11 +491,14 @@ static bool handle_send_link(int s, const std::string& path, uint32_t* timestamp #endif static bool send_impl(int s, const std::string& path, mode_t mode, CompressionType compression, - std::vector& buffer) { + bool dry_run, std::vector& 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()); } @@ -499,7 +506,7 @@ static bool send_impl(int s, const std::string& path, mode_t mode, CompressionTy bool result; uint32_t timestamp; if (S_ISLNK(mode)) { - result = handle_send_link(s, path, ×tamp, buffer); + result = handle_send_link(s, path, ×tamp, dry_run, buffer); } else { // Copy user permission bits to "group" and "other" permissions. mode &= 0777; @@ -509,12 +516,12 @@ static bool send_impl(int s, const std::string& path, mode_t mode, CompressionTy 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(), ×tamp, uid, gid, capabilities, mode, - compression, buffer, do_unlink); + compression, dry_run, buffer, do_unlink); } if (!result) { @@ -547,7 +554,7 @@ static bool do_send_v1(int s, const std::string& spec, std::vector& buffer return false; } - return send_impl(s, path, mode, CompressionType::None, buffer); + return send_impl(s, path, mode, CompressionType::None, false, buffer); } static bool do_send_v2(int s, const std::string& path, std::vector& buffer) { @@ -561,6 +568,7 @@ static bool do_send_v2(int s, const std::string& path, std::vector& buffer PLOG(ERROR) << "failed to read send_v2 setup packet"; } + bool dry_run = false; std::optional compression; uint32_t orig_flags = msg.send_v2_setup.flags; @@ -582,6 +590,10 @@ static bool do_send_v2(int s, const std::string& path, std::vector& buffer } 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)); @@ -590,7 +602,7 @@ static bool do_send_v2(int s, const std::string& path, std::vector& buffer errno = 0; return send_impl(s, path, msg.send_v2_setup.mode, compression.value_or(CompressionType::None), - buffer); + dry_run, buffer); } static bool recv_impl(borrowed_fd s, const char* path, CompressionType compression, diff --git a/adb/file_sync_protocol.h b/adb/file_sync_protocol.h index 90bd76393..8f8f85fa5 100644 --- a/adb/file_sync_protocol.h +++ b/adb/file_sync_protocol.h @@ -93,6 +93,7 @@ enum SyncFlag : uint32_t { kSyncFlagNone = 0, kSyncFlagBrotli = 1, kSyncFlagLZ4 = 2, + kSyncFlagDryRun = 0x8000'0000U, }; enum class CompressionType { diff --git a/adb/test_device.py b/adb/test_device.py index 3be7c9aa4..f5e4cbb6e 100755 --- a/adb/test_device.py +++ b/adb/test_device.py @@ -1268,6 +1268,59 @@ class FileOperationsTest: if temp_dir is not None: shutil.rmtree(temp_dir) + def test_push_dry_run_nonexistent_file(self): + """Push with dry run.""" + + 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', device_dir]) + + host_dir = tempfile.mkdtemp() + host_file = posixpath.join(host_dir, 'file') + + with open(host_file, "w") as f: + f.write('x' * file_size) + + 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) + + def test_push_dry_run_existent_file(self): + """Push with dry run.""" + + 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', device_dir]) + self.device.shell(['echo', 'foo', '>', device_file]) + + host_dir = tempfile.mkdtemp() + host_file = posixpath.join(host_dir, 'file') + + with open(host_file, "w") as f: + f.write('x' * file_size) + + self.device._simple_call(['push', '-n', host_file, device_file]) + stdout, stderr = self.device.shell(['cat', device_file]) + self.assertEqual(stdout.strip(), "foo") + + 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'로보카 폴리' diff --git a/adb/transport.cpp b/adb/transport.cpp index cef3850fa..cc2e0e3b1 100644 --- a/adb/transport.cpp +++ b/adb/transport.cpp @@ -85,6 +85,7 @@ 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 { @@ -1185,6 +1186,7 @@ const FeatureSet& supported_features() { 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 12803b5c3..4b2e00043 100644 --- a/adb/transport.h +++ b/adb/transport.h @@ -90,6 +90,8 @@ extern const char* const kFeatureSendRecv2; 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();