diff --git a/adb/commandline.cpp b/adb/commandline.cpp index f49c69daf..68ae4affe 100644 --- a/adb/commandline.cpp +++ b/adb/commandline.cpp @@ -126,8 +126,9 @@ static void help() { " reverse --remove-all remove all reverse socket connections from device\n" "\n" "file transfer:\n" - " push LOCAL... REMOTE\n" + " push [--sync] LOCAL... REMOTE\n" " copy local files/directories to device\n" + " --sync: only push files that are newer on the host than the device\n" " pull [-a] REMOTE... LOCAL\n" " copy files/dirs from device\n" " -a: preserve file timestamp and mode\n" @@ -1233,9 +1234,8 @@ static int restore(int argc, const char** argv) { return 0; } -static void parse_push_pull_args(const char** arg, int narg, - std::vector* srcs, - const char** dst, bool* copy_attrs) { +static void parse_push_pull_args(const char** arg, int narg, std::vector* srcs, + const char** dst, bool* copy_attrs, bool* sync) { *copy_attrs = false; srcs->clear(); @@ -1248,6 +1248,10 @@ static void parse_push_pull_args(const char** arg, int narg, // Silently ignore for backwards compatibility. } else if (!strcmp(*arg, "-a")) { *copy_attrs = true; + } else if (!strcmp(*arg, "--sync")) { + if (sync != nullptr) { + *sync = true; + } } else if (!strcmp(*arg, "--")) { ignore_flags = true; } else { @@ -1654,19 +1658,20 @@ int adb_commandline(int argc, const char** argv) { } else if (!strcmp(argv[0], "push")) { bool copy_attrs = false; + bool sync = false; std::vector srcs; const char* dst = nullptr; - parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, ©_attrs); + parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, ©_attrs, &sync); if (srcs.empty() || !dst) return syntax_error("push requires an argument"); - return do_sync_push(srcs, dst) ? 0 : 1; + return do_sync_push(srcs, dst, sync) ? 0 : 1; } else if (!strcmp(argv[0], "pull")) { bool copy_attrs = false; std::vector srcs; const char* dst = "."; - parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, ©_attrs); + parse_push_pull_args(&argv[1], argc - 1, &srcs, &dst, ©_attrs, nullptr); if (srcs.empty()) return syntax_error("pull requires an argument"); return do_sync_pull(srcs, dst, copy_attrs) ? 0 : 1; } @@ -2086,7 +2091,7 @@ static int install_app_legacy(TransportType transport, const char* serial, int a std::vector apk_file = {argv[last_apk]}; std::string apk_dest = android::base::StringPrintf( where, android::base::Basename(argv[last_apk]).c_str()); - if (!do_sync_push(apk_file, apk_dest.c_str())) goto cleanup_apk; + if (!do_sync_push(apk_file, apk_dest.c_str(), false)) goto cleanup_apk; argv[last_apk] = apk_dest.c_str(); /* destination name, not source location */ result = pm_command(transport, serial, argc, argv); diff --git a/adb/file_sync_client.cpp b/adb/file_sync_client.cpp index 22bd2f297..2576fb15b 100644 --- a/adb/file_sync_client.cpp +++ b/adb/file_sync_client.cpp @@ -674,11 +674,22 @@ static bool sync_stat_fallback(SyncConnection& sc, const char* path, struct stat return true; } -static bool sync_send(SyncConnection& sc, const char* lpath, const char* rpath, - unsigned mtime, mode_t mode) -{ +static bool sync_send(SyncConnection& sc, const char* lpath, const char* rpath, unsigned mtime, + mode_t mode, bool sync) { std::string path_and_mode = android::base::StringPrintf("%s,%d", rpath, mode); + if (sync) { + struct stat st; + if (sync_lstat(sc, rpath, &st)) { + // For links, we cannot update the atime/mtime. + if ((S_ISREG(mode & st.st_mode) && st.st_mtime == static_cast(mtime)) || + (S_ISLNK(mode & st.st_mode) && st.st_mtime >= static_cast(mtime))) { + sc.RecordFilesSkipped(1); + return true; + } + } + } + if (S_ISLNK(mode)) { #if !defined(_WIN32) char buf[PATH_MAX]; @@ -902,7 +913,7 @@ static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath, if (list_only) { sc.Println("would push: %s -> %s", ci.lpath.c_str(), ci.rpath.c_str()); } else { - if (!sync_send(sc, ci.lpath.c_str(), ci.rpath.c_str(), ci.time, ci.mode)) { + if (!sync_send(sc, ci.lpath.c_str(), ci.rpath.c_str(), ci.time, ci.mode, false)) { return false; } } @@ -916,7 +927,7 @@ static bool copy_local_dir_remote(SyncConnection& sc, std::string lpath, return true; } -bool do_sync_push(const std::vector& srcs, const char* dst) { +bool do_sync_push(const std::vector& srcs, const char* dst, bool sync) { SyncConnection sc; if (!sc.IsValid()) return false; @@ -981,7 +992,7 @@ bool do_sync_push(const std::vector& srcs, const char* dst) { dst_dir.append(android::base::Basename(src_path)); } - success &= copy_local_dir_remote(sc, src_path, dst_dir.c_str(), false, false); + success &= copy_local_dir_remote(sc, src_path, dst_dir.c_str(), sync, false); continue; } else if (!should_push_file(st.st_mode)) { sc.Warning("skipping special file '%s' (mode = 0o%o)", src_path, st.st_mode); @@ -1002,7 +1013,7 @@ bool do_sync_push(const std::vector& srcs, const char* dst) { sc.NewTransfer(); sc.SetExpectedTotalBytes(st.st_size); - success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode); + success &= sync_send(sc, src_path, dst_path, st.st_mtime, st.st_mode, sync); sc.ReportTransferRate(src_path, TransferDirection::push); } diff --git a/adb/file_sync_service.h b/adb/file_sync_service.h index 90f1965df..6606efdfb 100644 --- a/adb/file_sync_service.h +++ b/adb/file_sync_service.h @@ -81,7 +81,7 @@ union syncmsg { void file_sync_service(int fd, void* cookie); bool do_sync_ls(const char* path); -bool do_sync_push(const std::vector& srcs, const char* dst); +bool do_sync_push(const std::vector& srcs, const char* dst, bool sync); bool do_sync_pull(const std::vector& srcs, const char* dst, bool copy_attrs, const char* name=nullptr); diff --git a/adb/test_device.py b/adb/test_device.py index 9ef467680..737d0c247 100644 --- a/adb/test_device.py +++ b/adb/test_device.py @@ -1130,8 +1130,18 @@ class FileOperationsTest(DeviceTest): 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 randomly generated directory of files to specified device.""" + """Sync a host directory to the data partition.""" try: base_dir = tempfile.mkdtemp() @@ -1141,9 +1151,10 @@ class FileOperationsTest(DeviceTest): 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) + temp_files = make_random_host_files( + in_dir=full_dir_path, num_files=32) - # Clean up any trash on the device. + # Clean up any stale files on the device. device = adb.get_device() # pylint: disable=no-member device.shell(['rm', '-rf', self.DEVICE_TEMP_DIR]) @@ -1155,19 +1166,35 @@ class FileOperationsTest(DeviceTest): else: os.environ['ANDROID_PRODUCT_OUT'] = old_product_out - # Confirm that every file on the device mirrors that on the host. - for temp_file in temp_files: - device_full_path = posixpath.join(self.DEVICE_TEMP_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.verify_sync(device, temp_files, self.DEVICE_TEMP_DIR) - self.device.shell(['rm', '-rf', 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'로보카 폴리'