Loading adb/file_sync_client.cpp +81 −72 Original line number Original line Diff line number Diff line Loading @@ -504,16 +504,6 @@ bool do_sync_ls(const char* path) { }); }); } } struct copyinfo { std::string lpath; std::string rpath; unsigned int time; unsigned int mode; uint64_t size; bool skip; }; static void ensure_trailing_separator(std::string& lpath, std::string& rpath) { static void ensure_trailing_separator(std::string& lpath, std::string& rpath) { if (!adb_is_separator(lpath.back())) { if (!adb_is_separator(lpath.back())) { lpath.push_back(OS_PATH_SEPARATOR); lpath.push_back(OS_PATH_SEPARATOR); Loading @@ -523,25 +513,26 @@ static void ensure_trailing_separator(std::string& lpath, std::string& rpath) { } } } } static copyinfo mkcopyinfo(std::string lpath, std::string rpath, struct copyinfo { const std::string& name, unsigned int mode) { std::string lpath; copyinfo result; std::string rpath; result.lpath = std::move(lpath); unsigned int time = 0; result.rpath = std::move(rpath); unsigned int mode; ensure_trailing_separator(result.lpath, result.rpath); uint64_t size = 0; result.lpath.append(name); bool skip = false; result.rpath.append(name); copyinfo(const std::string& lpath, const std::string& rpath, const std::string& name, unsigned int mode) : lpath(lpath), rpath(rpath), mode(mode) { ensure_trailing_separator(this->lpath, this->rpath); this->lpath.append(name); this->rpath.append(name); if (S_ISDIR(mode)) { if (S_ISDIR(mode)) { ensure_trailing_separator(result.lpath, result.rpath); ensure_trailing_separator(this->lpath, this->rpath); } } result.time = 0; result.mode = mode; result.size = 0; result.skip = false; return result; } } }; static bool IsDotOrDotDot(const char* name) { static bool IsDotOrDotDot(const char* name) { return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); Loading Loading @@ -574,7 +565,7 @@ static bool local_build_list(SyncConnection& sc, std::vector<copyinfo>* filelist continue; continue; } } copyinfo ci = mkcopyinfo(lpath, rpath, de->d_name, st.st_mode); copyinfo ci(lpath, rpath, de->d_name, st.st_mode); if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) { dirlist.push_back(ci); dirlist.push_back(ci); } else { } else { Loading @@ -598,8 +589,7 @@ static bool local_build_list(SyncConnection& sc, std::vector<copyinfo>* filelist // TODO(b/25566053): Make pushing empty directories work. // TODO(b/25566053): Make pushing empty directories work. // TODO(b/25457350): We don't preserve permissions on directories. // TODO(b/25457350): We don't preserve permissions on directories. sc.Warning("skipping empty directory '%s'", lpath.c_str()); sc.Warning("skipping empty directory '%s'", lpath.c_str()); copyinfo ci = mkcopyinfo(adb_dirname(lpath), adb_dirname(rpath), copyinfo ci(adb_dirname(lpath), adb_dirname(rpath), adb_basename(lpath), S_IFDIR); adb_basename(lpath), S_IFDIR); ci.skip = true; ci.skip = true; filelist->push_back(ci); filelist->push_back(ci); return true; return true; Loading Loading @@ -743,11 +733,23 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst) { return success; return success; } } static bool remote_symlink_isdir(SyncConnection& sc, const std::string& rpath) { unsigned mode; std::string dir_path = rpath; dir_path.push_back('/'); if (!sync_stat(sc, dir_path.c_str(), nullptr, &mode, nullptr)) { sc.Error("failed to stat remote symlink '%s'", dir_path.c_str()); return false; } return S_ISDIR(mode); } static bool remote_build_list(SyncConnection& sc, static bool remote_build_list(SyncConnection& sc, std::vector<copyinfo>* filelist, std::vector<copyinfo>* filelist, const std::string& rpath, const std::string& rpath, const std::string& lpath) { const std::string& lpath) { std::vector<copyinfo> dirlist; std::vector<copyinfo> dirlist; std::vector<copyinfo> linklist; bool empty_dir = true; bool empty_dir = true; // Put the files/dirs in rpath on the lists. // Put the files/dirs in rpath on the lists. Loading @@ -759,20 +761,14 @@ static bool remote_build_list(SyncConnection& sc, // We found a child that isn't '.' or '..'. // We found a child that isn't '.' or '..'. empty_dir = false; empty_dir = false; copyinfo ci = mkcopyinfo(lpath, rpath, name, mode); copyinfo ci(lpath, rpath, name, mode); if (S_ISDIR(mode)) { if (S_ISDIR(mode)) { dirlist.push_back(ci); dirlist.push_back(ci); } else if (S_ISLNK(mode)) { linklist.push_back(ci); } else { } else { if (S_ISREG(mode)) { ci.time = time; ci.time = time; ci.size = size; ci.size = size; } else if (S_ISLNK(mode)) { sc.Warning("skipping symlink '%s'", name); ci.skip = true; } else { sc.Warning("skipping special file '%s'", name); ci.skip = true; } filelist->push_back(ci); filelist->push_back(ci); } } }; }; Loading @@ -781,14 +777,22 @@ static bool remote_build_list(SyncConnection& sc, return false; return false; } } // Add the current directory to the list if it was empty, to ensure that // Add the current directory to the list if it was empty, to ensure that it gets created. // it gets created. if (empty_dir) { if (empty_dir) { filelist->push_back(mkcopyinfo(adb_dirname(lpath), adb_dirname(rpath), copyinfo ci(adb_dirname(lpath), adb_dirname(rpath), adb_basename(rpath), S_IFDIR); adb_basename(rpath), S_IFDIR)); filelist->push_back(ci); return true; return true; } } // Check each symlink we found to see whether it's a file or directory. for (copyinfo& link_ci : linklist) { if (remote_symlink_isdir(sc, link_ci.rpath)) { dirlist.emplace_back(std::move(link_ci)); } else { filelist->emplace_back(std::move(link_ci)); } } // Recurse into each directory we found. // Recurse into each directory we found. while (!dirlist.empty()) { while (!dirlist.empty()) { copyinfo current = dirlist.back(); copyinfo current = dirlist.back(); Loading Loading @@ -912,6 +916,7 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, const char* dst_path = dst; const char* dst_path = dst; unsigned src_mode, src_time; unsigned src_mode, src_time; if (!sync_stat(sc, src_path, &src_time, &src_mode, nullptr)) { if (!sync_stat(sc, src_path, &src_time, &src_mode, nullptr)) { sc.Error("failed to stat remote object '%s'", src_path); return false; return false; } } if (src_mode == 0) { if (src_mode == 0) { Loading @@ -920,28 +925,17 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, continue; continue; } } if (S_ISREG(src_mode)) { bool src_isdir = S_ISDIR(src_mode); std::string path_holder; if (S_ISLNK(src_mode)) { if (dst_isdir) { src_isdir = remote_symlink_isdir(sc, src_path); // If we're copying a remote file to a local directory, we // really want to copy to local_dir + OS_PATH_SEPARATOR + // basename(remote). path_holder = android::base::StringPrintf( "%s%c%s", dst_path, OS_PATH_SEPARATOR, adb_basename(src_path).c_str()); dst_path = path_holder.c_str(); } } if (!sync_recv(sc, src_path, dst_path)) { success = false; if ((src_mode & (S_IFREG | S_IFDIR | S_IFBLK | S_IFCHR)) == 0) { continue; sc.Error("skipping remote object '%s' (mode = 0o%o)", src_path, src_mode); } else { if (copy_attrs && set_time_and_mode(dst_path, src_time, src_mode) != 0) { success = false; continue; continue; } } } } else if (S_ISDIR(src_mode)) { if (src_isdir) { std::string dst_dir = dst; std::string dst_dir = dst; // If the destination path existed originally, the source directory // If the destination path existed originally, the source directory Loading @@ -957,14 +951,29 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, dst_dir.append(adb_basename(src_path)); dst_dir.append(adb_basename(src_path)); } } success &= copy_remote_dir_local(sc, src_path, dst_dir.c_str(), success &= copy_remote_dir_local(sc, src_path, dst_dir.c_str(), copy_attrs); copy_attrs); continue; continue; } else { } else { sc.Error("remote object '%s' not a file or directory", src_path); std::string path_holder; if (dst_isdir) { // If we're copying a remote file to a local directory, we // really want to copy to local_dir + OS_PATH_SEPARATOR + // basename(remote). path_holder = android::base::StringPrintf("%s%c%s", dst_path, OS_PATH_SEPARATOR, adb_basename(src_path).c_str()); dst_path = path_holder.c_str(); } if (!sync_recv(sc, src_path, dst_path)) { success = false; success = false; continue; continue; } } if (copy_attrs && set_time_and_mode(dst_path, src_time, src_mode) != 0) { success = false; continue; } } } } return success; return success; Loading adb/test_device.py +34 −0 Original line number Original line Diff line number Diff line Loading @@ -829,6 +829,40 @@ class FileOperationsTest(DeviceTest): if host_dir is not None: if host_dir is not None: shutil.rmtree(host_dir) shutil.rmtree(host_dir) def 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): def test_pull_empty(self): """Pull a directory containing an empty directory from the device.""" """Pull a directory containing an empty directory from the device.""" try: try: Loading Loading
adb/file_sync_client.cpp +81 −72 Original line number Original line Diff line number Diff line Loading @@ -504,16 +504,6 @@ bool do_sync_ls(const char* path) { }); }); } } struct copyinfo { std::string lpath; std::string rpath; unsigned int time; unsigned int mode; uint64_t size; bool skip; }; static void ensure_trailing_separator(std::string& lpath, std::string& rpath) { static void ensure_trailing_separator(std::string& lpath, std::string& rpath) { if (!adb_is_separator(lpath.back())) { if (!adb_is_separator(lpath.back())) { lpath.push_back(OS_PATH_SEPARATOR); lpath.push_back(OS_PATH_SEPARATOR); Loading @@ -523,25 +513,26 @@ static void ensure_trailing_separator(std::string& lpath, std::string& rpath) { } } } } static copyinfo mkcopyinfo(std::string lpath, std::string rpath, struct copyinfo { const std::string& name, unsigned int mode) { std::string lpath; copyinfo result; std::string rpath; result.lpath = std::move(lpath); unsigned int time = 0; result.rpath = std::move(rpath); unsigned int mode; ensure_trailing_separator(result.lpath, result.rpath); uint64_t size = 0; result.lpath.append(name); bool skip = false; result.rpath.append(name); copyinfo(const std::string& lpath, const std::string& rpath, const std::string& name, unsigned int mode) : lpath(lpath), rpath(rpath), mode(mode) { ensure_trailing_separator(this->lpath, this->rpath); this->lpath.append(name); this->rpath.append(name); if (S_ISDIR(mode)) { if (S_ISDIR(mode)) { ensure_trailing_separator(result.lpath, result.rpath); ensure_trailing_separator(this->lpath, this->rpath); } } result.time = 0; result.mode = mode; result.size = 0; result.skip = false; return result; } } }; static bool IsDotOrDotDot(const char* name) { static bool IsDotOrDotDot(const char* name) { return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); Loading Loading @@ -574,7 +565,7 @@ static bool local_build_list(SyncConnection& sc, std::vector<copyinfo>* filelist continue; continue; } } copyinfo ci = mkcopyinfo(lpath, rpath, de->d_name, st.st_mode); copyinfo ci(lpath, rpath, de->d_name, st.st_mode); if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) { dirlist.push_back(ci); dirlist.push_back(ci); } else { } else { Loading @@ -598,8 +589,7 @@ static bool local_build_list(SyncConnection& sc, std::vector<copyinfo>* filelist // TODO(b/25566053): Make pushing empty directories work. // TODO(b/25566053): Make pushing empty directories work. // TODO(b/25457350): We don't preserve permissions on directories. // TODO(b/25457350): We don't preserve permissions on directories. sc.Warning("skipping empty directory '%s'", lpath.c_str()); sc.Warning("skipping empty directory '%s'", lpath.c_str()); copyinfo ci = mkcopyinfo(adb_dirname(lpath), adb_dirname(rpath), copyinfo ci(adb_dirname(lpath), adb_dirname(rpath), adb_basename(lpath), S_IFDIR); adb_basename(lpath), S_IFDIR); ci.skip = true; ci.skip = true; filelist->push_back(ci); filelist->push_back(ci); return true; return true; Loading Loading @@ -743,11 +733,23 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst) { return success; return success; } } static bool remote_symlink_isdir(SyncConnection& sc, const std::string& rpath) { unsigned mode; std::string dir_path = rpath; dir_path.push_back('/'); if (!sync_stat(sc, dir_path.c_str(), nullptr, &mode, nullptr)) { sc.Error("failed to stat remote symlink '%s'", dir_path.c_str()); return false; } return S_ISDIR(mode); } static bool remote_build_list(SyncConnection& sc, static bool remote_build_list(SyncConnection& sc, std::vector<copyinfo>* filelist, std::vector<copyinfo>* filelist, const std::string& rpath, const std::string& rpath, const std::string& lpath) { const std::string& lpath) { std::vector<copyinfo> dirlist; std::vector<copyinfo> dirlist; std::vector<copyinfo> linklist; bool empty_dir = true; bool empty_dir = true; // Put the files/dirs in rpath on the lists. // Put the files/dirs in rpath on the lists. Loading @@ -759,20 +761,14 @@ static bool remote_build_list(SyncConnection& sc, // We found a child that isn't '.' or '..'. // We found a child that isn't '.' or '..'. empty_dir = false; empty_dir = false; copyinfo ci = mkcopyinfo(lpath, rpath, name, mode); copyinfo ci(lpath, rpath, name, mode); if (S_ISDIR(mode)) { if (S_ISDIR(mode)) { dirlist.push_back(ci); dirlist.push_back(ci); } else if (S_ISLNK(mode)) { linklist.push_back(ci); } else { } else { if (S_ISREG(mode)) { ci.time = time; ci.time = time; ci.size = size; ci.size = size; } else if (S_ISLNK(mode)) { sc.Warning("skipping symlink '%s'", name); ci.skip = true; } else { sc.Warning("skipping special file '%s'", name); ci.skip = true; } filelist->push_back(ci); filelist->push_back(ci); } } }; }; Loading @@ -781,14 +777,22 @@ static bool remote_build_list(SyncConnection& sc, return false; return false; } } // Add the current directory to the list if it was empty, to ensure that // Add the current directory to the list if it was empty, to ensure that it gets created. // it gets created. if (empty_dir) { if (empty_dir) { filelist->push_back(mkcopyinfo(adb_dirname(lpath), adb_dirname(rpath), copyinfo ci(adb_dirname(lpath), adb_dirname(rpath), adb_basename(rpath), S_IFDIR); adb_basename(rpath), S_IFDIR)); filelist->push_back(ci); return true; return true; } } // Check each symlink we found to see whether it's a file or directory. for (copyinfo& link_ci : linklist) { if (remote_symlink_isdir(sc, link_ci.rpath)) { dirlist.emplace_back(std::move(link_ci)); } else { filelist->emplace_back(std::move(link_ci)); } } // Recurse into each directory we found. // Recurse into each directory we found. while (!dirlist.empty()) { while (!dirlist.empty()) { copyinfo current = dirlist.back(); copyinfo current = dirlist.back(); Loading Loading @@ -912,6 +916,7 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, const char* dst_path = dst; const char* dst_path = dst; unsigned src_mode, src_time; unsigned src_mode, src_time; if (!sync_stat(sc, src_path, &src_time, &src_mode, nullptr)) { if (!sync_stat(sc, src_path, &src_time, &src_mode, nullptr)) { sc.Error("failed to stat remote object '%s'", src_path); return false; return false; } } if (src_mode == 0) { if (src_mode == 0) { Loading @@ -920,28 +925,17 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, continue; continue; } } if (S_ISREG(src_mode)) { bool src_isdir = S_ISDIR(src_mode); std::string path_holder; if (S_ISLNK(src_mode)) { if (dst_isdir) { src_isdir = remote_symlink_isdir(sc, src_path); // If we're copying a remote file to a local directory, we // really want to copy to local_dir + OS_PATH_SEPARATOR + // basename(remote). path_holder = android::base::StringPrintf( "%s%c%s", dst_path, OS_PATH_SEPARATOR, adb_basename(src_path).c_str()); dst_path = path_holder.c_str(); } } if (!sync_recv(sc, src_path, dst_path)) { success = false; if ((src_mode & (S_IFREG | S_IFDIR | S_IFBLK | S_IFCHR)) == 0) { continue; sc.Error("skipping remote object '%s' (mode = 0o%o)", src_path, src_mode); } else { if (copy_attrs && set_time_and_mode(dst_path, src_time, src_mode) != 0) { success = false; continue; continue; } } } } else if (S_ISDIR(src_mode)) { if (src_isdir) { std::string dst_dir = dst; std::string dst_dir = dst; // If the destination path existed originally, the source directory // If the destination path existed originally, the source directory Loading @@ -957,14 +951,29 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst, dst_dir.append(adb_basename(src_path)); dst_dir.append(adb_basename(src_path)); } } success &= copy_remote_dir_local(sc, src_path, dst_dir.c_str(), success &= copy_remote_dir_local(sc, src_path, dst_dir.c_str(), copy_attrs); copy_attrs); continue; continue; } else { } else { sc.Error("remote object '%s' not a file or directory", src_path); std::string path_holder; if (dst_isdir) { // If we're copying a remote file to a local directory, we // really want to copy to local_dir + OS_PATH_SEPARATOR + // basename(remote). path_holder = android::base::StringPrintf("%s%c%s", dst_path, OS_PATH_SEPARATOR, adb_basename(src_path).c_str()); dst_path = path_holder.c_str(); } if (!sync_recv(sc, src_path, dst_path)) { success = false; success = false; continue; continue; } } if (copy_attrs && set_time_and_mode(dst_path, src_time, src_mode) != 0) { success = false; continue; } } } } return success; return success; Loading
adb/test_device.py +34 −0 Original line number Original line Diff line number Diff line Loading @@ -829,6 +829,40 @@ class FileOperationsTest(DeviceTest): if host_dir is not None: if host_dir is not None: shutil.rmtree(host_dir) shutil.rmtree(host_dir) def 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): def test_pull_empty(self): """Pull a directory containing an empty directory from the device.""" """Pull a directory containing an empty directory from the device.""" try: try: Loading