adb: make pulling symlinks and devices work.

Bug: http://b/25972989
Bug: http://b/26085751
Change-Id: I43842871522ea5f67a8c258dcb6ddafa8dd744c8
This commit is contained in:
Josh Gao 2015-12-09 14:03:30 -08:00
parent ce8f2cdcd0
commit f264224191
2 changed files with 115 additions and 72 deletions

View file

@ -503,16 +503,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) {
if (!adb_is_separator(lpath.back())) {
lpath.push_back(OS_PATH_SEPARATOR);
@ -522,25 +512,26 @@ static void ensure_trailing_separator(std::string& lpath, std::string& rpath) {
}
}
static copyinfo mkcopyinfo(std::string lpath, std::string rpath,
const std::string& name, unsigned int mode) {
copyinfo result;
result.lpath = std::move(lpath);
result.rpath = std::move(rpath);
ensure_trailing_separator(result.lpath, result.rpath);
result.lpath.append(name);
result.rpath.append(name);
struct copyinfo {
std::string lpath;
std::string rpath;
unsigned int time = 0;
unsigned int mode;
uint64_t size = 0;
bool skip = false;
if (S_ISDIR(mode)) {
ensure_trailing_separator(result.lpath, result.rpath);
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)) {
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) {
return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
@ -573,7 +564,7 @@ static bool local_build_list(SyncConnection& sc, std::vector<copyinfo>* filelist
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)) {
dirlist.push_back(ci);
} else {
@ -597,8 +588,7 @@ static bool local_build_list(SyncConnection& sc, std::vector<copyinfo>* filelist
// TODO(b/25566053): Make pushing empty directories work.
// TODO(b/25457350): We don't preserve permissions on directories.
sc.Warning("skipping empty directory '%s'", lpath.c_str());
copyinfo ci = mkcopyinfo(adb_dirname(lpath), adb_dirname(rpath),
adb_basename(lpath), S_IFDIR);
copyinfo ci(adb_dirname(lpath), adb_dirname(rpath), adb_basename(lpath), S_IFDIR);
ci.skip = true;
filelist->push_back(ci);
return true;
@ -743,11 +733,23 @@ bool do_sync_push(const std::vector<const char*>& srcs, const char* dst) {
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,
std::vector<copyinfo>* filelist,
const std::string& rpath,
const std::string& lpath) {
std::vector<copyinfo> dirlist;
std::vector<copyinfo> linklist;
bool empty_dir = true;
// Put the files/dirs in rpath on the lists.
@ -759,20 +761,14 @@ static bool remote_build_list(SyncConnection& sc,
// We found a child that isn't '.' or '..'.
empty_dir = false;
copyinfo ci = mkcopyinfo(lpath, rpath, name, mode);
copyinfo ci(lpath, rpath, name, mode);
if (S_ISDIR(mode)) {
dirlist.push_back(ci);
} else if (S_ISLNK(mode)) {
linklist.push_back(ci);
} else {
if (S_ISREG(mode)) {
ci.time = time;
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;
}
ci.time = time;
ci.size = size;
filelist->push_back(ci);
}
};
@ -781,14 +777,22 @@ static bool remote_build_list(SyncConnection& sc,
return false;
}
// Add the current directory to the list if it was empty, to ensure that
// it gets created.
// Add the current directory to the list if it was empty, to ensure that it gets created.
if (empty_dir) {
filelist->push_back(mkcopyinfo(adb_dirname(lpath), adb_dirname(rpath),
adb_basename(rpath), S_IFDIR));
copyinfo ci(adb_dirname(lpath), adb_dirname(rpath), adb_basename(rpath), S_IFDIR);
filelist->push_back(ci);
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.
while (!dirlist.empty()) {
copyinfo current = dirlist.back();
@ -912,6 +916,7 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
const char* dst_path = dst;
unsigned src_mode, src_time;
if (!sync_stat(sc, src_path, &src_time, &src_mode, nullptr)) {
sc.Error("failed to stat remote object '%s'", src_path);
return false;
}
if (src_mode == 0) {
@ -920,28 +925,17 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
continue;
}
if (S_ISREG(src_mode)) {
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;
continue;
} else {
if (copy_attrs &&
set_time_and_mode(dst_path, src_time, src_mode) != 0) {
success = false;
continue;
}
}
} else if (S_ISDIR(src_mode)) {
bool src_isdir = S_ISDIR(src_mode);
if (S_ISLNK(src_mode)) {
src_isdir = remote_symlink_isdir(sc, src_path);
}
if ((src_mode & (S_IFREG | S_IFDIR | S_IFBLK | S_IFCHR)) == 0) {
sc.Error("skipping remote object '%s' (mode = 0o%o)", src_path, src_mode);
continue;
}
if (src_isdir) {
std::string dst_dir = dst;
// If the destination path existed originally, the source directory
@ -957,13 +951,28 @@ bool do_sync_pull(const std::vector<const char*>& srcs, const char* dst,
dst_dir.append(adb_basename(src_path));
}
success &= copy_remote_dir_local(sc, src_path, dst_dir.c_str(),
copy_attrs);
success &= copy_remote_dir_local(sc, src_path, dst_dir.c_str(), copy_attrs);
continue;
} else {
sc.Error("remote object '%s' not a file or directory", src_path);
success = false;
continue;
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;
continue;
}
if (copy_attrs && set_time_and_mode(dst_path, src_time, src_mode) != 0) {
success = false;
continue;
}
}
}

View file

@ -829,6 +829,40 @@ class FileOperationsTest(DeviceTest):
if host_dir is not None:
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):
"""Pull a directory containing an empty directory from the device."""
try: