| /* |
| * Copyright (C) 2007 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. |
| */ |
| |
| #include <dirent.h> |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <utime.h> |
| |
| #include <memory> |
| |
| #include "sysdeps.h" |
| |
| #include "adb.h" |
| #include "adb_client.h" |
| #include "adb_io.h" |
| #include "adb_utils.h" |
| #include "file_sync_service.h" |
| |
| #include <base/strings.h> |
| #include <base/stringprintf.h> |
| |
| struct syncsendbuf { |
| unsigned id; |
| unsigned size; |
| char data[SYNC_DATA_MAX]; |
| }; |
| |
| static syncsendbuf send_buffer; |
| |
| static long long NOW() { |
| struct timeval tv; |
| gettimeofday(&tv, 0); |
| return ((long long) tv.tv_usec) + 1000000LL * ((long long) tv.tv_sec); |
| } |
| |
| static void print_transfer_progress(uint64_t bytes_current, |
| uint64_t bytes_total) { |
| if (bytes_total == 0) return; |
| |
| fprintf(stderr, "\rTransferring: %" PRIu64 "/%" PRIu64 " (%d%%)", |
| bytes_current, bytes_total, |
| (int) (bytes_current * 100 / bytes_total)); |
| |
| if (bytes_current == bytes_total) { |
| fputc('\n', stderr); |
| } |
| |
| fflush(stderr); |
| } |
| |
| static bool SendRequest(int fd, int id, const char* path) { |
| size_t path_length = strlen(path); |
| if (path_length > 1024) { |
| fprintf(stderr, "SendRequest failed: path too long: %zu", path_length); |
| errno = ENAMETOOLONG; |
| return false; |
| } |
| |
| // Sending header and payload in a single write makes a noticeable |
| // difference to "adb sync" performance. |
| char buf[sizeof(SyncRequest) + path_length] __attribute__((aligned(8))); |
| SyncRequest* req = reinterpret_cast<SyncRequest*>(buf); |
| req->id = id; |
| req->path_length = path_length; |
| char* data = reinterpret_cast<char*>(req + 1); |
| memcpy(data, path, path_length); |
| |
| return WriteFdExactly(fd, buf, sizeof(buf)); |
| } |
| |
| class SyncConnection { |
| public: |
| SyncConnection() : total_bytes(0), start_time_(NOW()) { |
| max = SYNC_DATA_MAX; // TODO: decide at runtime. |
| |
| std::string error; |
| fd = adb_connect("sync:", &error); |
| if (fd < 0) { |
| fprintf(stderr, "error: %s\n", error.c_str()); |
| } |
| } |
| |
| ~SyncConnection() { |
| if (!IsValid()) return; |
| |
| SendQuit(); |
| ShowTransferRate(); |
| adb_close(fd); |
| } |
| |
| bool IsValid() { return fd >= 0; } |
| |
| uint64_t total_bytes; |
| |
| // TODO: add a char[max] buffer here, to replace syncsendbuf... |
| int fd; |
| size_t max; |
| |
| private: |
| uint64_t start_time_; |
| |
| void SendQuit() { |
| SendRequest(fd, ID_QUIT, ""); // TODO: add a SendResponse? |
| } |
| |
| void ShowTransferRate() { |
| uint64_t t = NOW() - start_time_; |
| if (total_bytes == 0 || t == 0) return; |
| |
| fprintf(stderr, "%lld KB/s (%" PRId64 " bytes in %lld.%03llds)\n", |
| ((total_bytes * 1000000LL) / t) / 1024LL, |
| total_bytes, (t / 1000000LL), (t % 1000000LL) / 1000LL); |
| } |
| }; |
| |
| typedef void (*sync_ls_cb)(unsigned mode, unsigned size, unsigned time, const char* name, void* cookie); |
| |
| static bool sync_ls(int fd, const char* path, sync_ls_cb func, void* cookie) { |
| if (!SendRequest(fd, ID_LIST, path)) return false; |
| |
| while (true) { |
| syncmsg msg; |
| if (!ReadFdExactly(fd, &msg.dent, sizeof(msg.dent))) return false; |
| |
| if (msg.dent.id == ID_DONE) return true; |
| if (msg.dent.id != ID_DENT) return false; |
| |
| size_t len = msg.dent.namelen; |
| if (len > 256) return false; // TODO: resize buffer? continue? |
| |
| char buf[257]; |
| if (!ReadFdExactly(fd, buf, len)) return false; |
| buf[len] = 0; |
| |
| func(msg.dent.mode, msg.dent.size, msg.dent.time, buf, cookie); |
| } |
| } |
| |
| static bool sync_start_stat(SyncConnection& sc, const char* path) { |
| return SendRequest(sc.fd, ID_STAT, path); |
| } |
| |
| static bool sync_finish_stat(SyncConnection& sc, unsigned int* timestamp, |
| unsigned int* mode, unsigned int* size) { |
| syncmsg msg; |
| if (!ReadFdExactly(sc.fd, &msg.stat, sizeof(msg.stat)) || msg.stat.id != ID_STAT) { |
| return false; |
| } |
| |
| if (timestamp) *timestamp = msg.stat.time; |
| if (mode) *mode = msg.stat.mode; |
| if (size) *size = msg.stat.size; |
| |
| return true; |
| } |
| |
| static bool sync_stat(SyncConnection& sc, const char* path, |
| unsigned int* timestamp, unsigned int* mode, unsigned int* size) { |
| return sync_start_stat(sc, path) && sync_finish_stat(sc, timestamp, mode, size); |
| } |
| |
| static bool write_data_file(SyncConnection& sc, const char* path, syncsendbuf* sbuf, bool show_progress) { |
| unsigned long long size = 0; |
| if (show_progress) { |
| // Determine local file size. |
| struct stat st; |
| if (stat(path, &st) == -1) { |
| fprintf(stderr, "cannot stat '%s': %s\n", path, strerror(errno)); |
| return false; |
| } |
| |
| size = st.st_size; |
| } |
| |
| int lfd = adb_open(path, O_RDONLY); |
| if (lfd < 0) { |
| fprintf(stderr, "cannot open '%s': %s\n", path, strerror(errno)); |
| return false; |
| } |
| |
| sbuf->id = ID_DATA; |
| while (true) { |
| int ret = adb_read(lfd, sbuf->data, sc.max); |
| if (ret <= 0) { |
| if (ret < 0) { |
| fprintf(stderr, "cannot read '%s': %s\n", path, strerror(errno)); |
| adb_close(lfd); |
| return false; |
| } |
| break; |
| } |
| |
| sbuf->size = ret; |
| if (!WriteFdExactly(sc.fd, sbuf, sizeof(unsigned) * 2 + ret)) { |
| adb_close(lfd); |
| return false; |
| } |
| sc.total_bytes += ret; |
| |
| if (show_progress) { |
| print_transfer_progress(sc.total_bytes, size); |
| } |
| } |
| |
| adb_close(lfd); |
| return true; |
| } |
| |
| #if defined(_WIN32) |
| extern bool write_data_link(SyncConnection& sc, const char* path, syncsendbuf* sbuf) __attribute__((error("no symlinks on Windows"))); |
| #else |
| static bool write_data_link(SyncConnection& sc, const char* path, syncsendbuf* sbuf) { |
| ssize_t len = readlink(path, sbuf->data, sc.max - 1); |
| if (len < 0) { |
| fprintf(stderr, "error reading link '%s': %s\n", path, strerror(errno)); |
| return false; |
| } |
| sbuf->data[len] = '\0'; |
| |
| sbuf->size = len + 1; |
| sbuf->id = ID_DATA; |
| |
| if (!WriteFdExactly(sc.fd, sbuf, sizeof(unsigned) * 2 + len + 1)) { |
| return false; |
| } |
| |
| sc.total_bytes += len + 1; |
| |
| return true; |
| } |
| #endif |
| |
| static bool sync_send(SyncConnection& sc, const char *lpath, const char *rpath, |
| unsigned mtime, mode_t mode, bool show_progress) |
| { |
| syncsendbuf* sbuf = &send_buffer; |
| |
| std::string path_and_mode = android::base::StringPrintf("%s,%d", rpath, mode); |
| if (!SendRequest(sc.fd, ID_SEND, path_and_mode.c_str())) { |
| fprintf(stderr, "failed to send ID_SEND message '%s': %s\n", |
| path_and_mode.c_str(), strerror(errno)); |
| return false; |
| } |
| |
| if (S_ISREG(mode)) { |
| if (!write_data_file(sc, lpath, sbuf, show_progress)) return false; |
| } else if (S_ISLNK(mode)) { |
| if (!write_data_link(sc, lpath, sbuf)) return false; |
| } else { |
| fprintf(stderr, "local file '%s' has unsupported mode: 0o%o\n", lpath, mode); |
| return false; |
| } |
| |
| syncmsg msg; |
| msg.data.id = ID_DONE; |
| msg.data.size = mtime; |
| if (!WriteFdExactly(sc.fd, &msg.data, sizeof(msg.data))) { |
| fprintf(stderr, "failed to send ID_DONE message for '%s': %s\n", lpath, strerror(errno)); |
| return false; |
| } |
| |
| if (!ReadFdExactly(sc.fd, &msg.status, sizeof(msg.status))) { |
| fprintf(stderr, "failed to read ID_DONE response for '%s': %s\n", lpath, strerror(errno)); |
| return false; |
| } |
| if (msg.status.id != ID_OKAY) { |
| if (msg.status.id == ID_FAIL) { |
| size_t len = msg.status.msglen; |
| if (len > 256) len = 256; |
| if (!ReadFdExactly(sc.fd, sbuf->data, len)) { |
| fprintf(stderr, "failed to read failure reason (!): %s\n", strerror(errno)); |
| return false; |
| } |
| sbuf->data[len] = 0; |
| } else { |
| strcpy(sbuf->data, "unknown reason"); |
| } |
| fprintf(stderr, "failed to copy '%s' to '%s': %s\n", lpath, rpath, sbuf->data); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int sync_recv(SyncConnection& sc, const char* rpath, const char* lpath, bool show_progress) { |
| syncmsg msg; |
| int lfd = -1; |
| char *buffer = send_buffer.data; |
| unsigned id; |
| |
| size_t len = strlen(rpath); |
| if (len > 1024) return -1; |
| |
| unsigned size = 0; |
| if (show_progress) { |
| if (!sync_stat(sc, rpath, nullptr, nullptr, &size)) return -1; |
| } |
| |
| if (!SendRequest(sc.fd, ID_RECV, rpath)) return -1; |
| if (!ReadFdExactly(sc.fd, &msg.data, sizeof(msg.data))) return -1; |
| |
| id = msg.data.id; |
| |
| if (id == ID_DATA || id == ID_DONE) { |
| adb_unlink(lpath); |
| mkdirs(lpath); |
| lfd = adb_creat(lpath, 0644); |
| if(lfd < 0) { |
| fprintf(stderr, "cannot create '%s': %s\n", lpath, strerror(errno)); |
| return -1; |
| } |
| goto handle_data; |
| } else { |
| goto remote_error; |
| } |
| |
| while (true) { |
| if(!ReadFdExactly(sc.fd, &msg.data, sizeof(msg.data))) { |
| adb_close(lfd); |
| return -1; |
| } |
| id = msg.data.id; |
| |
| handle_data: |
| len = msg.data.size; |
| if (id == ID_DONE) break; |
| if (id != ID_DATA) goto remote_error; |
| if (len > sc.max) { |
| fprintf(stderr, "msg.data.size too large: %zu (max %zu)\n", len, sc.max); |
| adb_close(lfd); |
| return -1; |
| } |
| |
| if(!ReadFdExactly(sc.fd, buffer, len)) { |
| adb_close(lfd); |
| return -1; |
| } |
| |
| if(!WriteFdExactly(lfd, buffer, len)) { |
| fprintf(stderr, "cannot write '%s': %s\n", rpath, strerror(errno)); |
| adb_close(lfd); |
| return -1; |
| } |
| |
| sc.total_bytes += len; |
| |
| if (show_progress) { |
| print_transfer_progress(sc.total_bytes, size); |
| } |
| } |
| |
| adb_close(lfd); |
| return 0; |
| |
| remote_error: |
| adb_close(lfd); |
| adb_unlink(lpath); |
| |
| if(id == ID_FAIL) { |
| len = msg.data.size; |
| if(len > 256) len = 256; |
| if(!ReadFdExactly(sc.fd, buffer, len)) { |
| return -1; |
| } |
| buffer[len] = 0; |
| } else { |
| memcpy(buffer, &id, 4); |
| buffer[4] = 0; |
| } |
| fprintf(stderr, "failed to copy '%s' to '%s': %s\n", rpath, lpath, buffer); |
| return 0; |
| } |
| |
| static void do_sync_ls_cb(unsigned mode, unsigned size, unsigned time, |
| const char* name, void* /*cookie*/) { |
| printf("%08x %08x %08x %s\n", mode, size, time, name); |
| } |
| |
| bool do_sync_ls(const char* path) { |
| SyncConnection sc; |
| if (!sc.IsValid()) return false; |
| |
| return sync_ls(sc.fd, path, do_sync_ls_cb, 0); |
| } |
| |
| struct copyinfo |
| { |
| copyinfo *next; |
| const char *src; |
| const char *dst; |
| unsigned int time; |
| unsigned int mode; |
| unsigned int size; |
| int flag; |
| }; |
| |
| static copyinfo* mkcopyinfo(const char* spath, const char* dpath, const char* name, int isdir) { |
| int slen = strlen(spath); |
| int dlen = strlen(dpath); |
| int nlen = strlen(name); |
| int ssize = slen + nlen + 2; |
| int dsize = dlen + nlen + 2; |
| |
| copyinfo *ci = reinterpret_cast<copyinfo*>(malloc(sizeof(copyinfo) + ssize + dsize)); |
| if(ci == 0) { |
| fprintf(stderr, "out of memory\n"); |
| abort(); |
| } |
| |
| ci->next = 0; |
| ci->time = 0; |
| ci->mode = 0; |
| ci->size = 0; |
| ci->flag = 0; |
| ci->src = (const char*)(ci + 1); |
| ci->dst = ci->src + ssize; |
| snprintf((char*) ci->src, ssize, isdir ? "%s%s/" : "%s%s", spath, name); |
| snprintf((char*) ci->dst, dsize, isdir ? "%s%s/" : "%s%s", dpath, name); |
| |
| return ci; |
| } |
| |
| static bool IsDotOrDotDot(const char* name) { |
| return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); |
| } |
| |
| static int local_build_list(copyinfo** filelist, const char* lpath, const char* rpath) { |
| copyinfo *dirlist = 0; |
| copyinfo *ci, *next; |
| |
| std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(lpath), closedir); |
| if (!dir) { |
| fprintf(stderr, "cannot open '%s': %s\n", lpath, strerror(errno)); |
| return -1; |
| } |
| |
| dirent *de; |
| while ((de = readdir(dir.get()))) { |
| if (IsDotOrDotDot(de->d_name)) continue; |
| |
| char stat_path[PATH_MAX]; |
| if (strlen(lpath) + strlen(de->d_name) + 1 > sizeof(stat_path)) { |
| fprintf(stderr, "skipping long path '%s%s'\n", lpath, de->d_name); |
| continue; |
| } |
| strcpy(stat_path, lpath); |
| strcat(stat_path, de->d_name); |
| |
| struct stat st; |
| if (!lstat(stat_path, &st)) { |
| if (S_ISDIR(st.st_mode)) { |
| ci = mkcopyinfo(lpath, rpath, de->d_name, 1); |
| ci->next = dirlist; |
| dirlist = ci; |
| } else { |
| ci = mkcopyinfo(lpath, rpath, de->d_name, 0); |
| if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode)) { |
| fprintf(stderr, "skipping special file '%s'\n", ci->src); |
| free(ci); |
| } else { |
| ci->time = st.st_mtime; |
| ci->mode = st.st_mode; |
| ci->size = st.st_size; |
| ci->next = *filelist; |
| *filelist = ci; |
| } |
| } |
| } else { |
| fprintf(stderr, "cannot lstat '%s': %s\n",stat_path , strerror(errno)); |
| } |
| } |
| |
| // Close this directory and recurse. |
| dir.reset(); |
| for (ci = dirlist; ci != 0; ci = next) { |
| next = ci->next; |
| local_build_list(filelist, ci->src, ci->dst); |
| free(ci); |
| } |
| |
| return 0; |
| } |
| |
| static bool copy_local_dir_remote(SyncConnection& sc, const char* lpath, const char* rpath, |
| bool check_timestamps, bool list_only) { |
| copyinfo *filelist = 0; |
| copyinfo *ci, *next; |
| int pushed = 0; |
| int skipped = 0; |
| |
| if ((lpath[0] == 0) || (rpath[0] == 0)) return false; |
| if (lpath[strlen(lpath) - 1] != '/') { |
| int tmplen = strlen(lpath)+2; |
| char *tmp = reinterpret_cast<char*>(malloc(tmplen)); |
| if(tmp == 0) return false; |
| snprintf(tmp, tmplen, "%s/",lpath); |
| lpath = tmp; |
| } |
| if (rpath[strlen(rpath) - 1] != '/') { |
| int tmplen = strlen(rpath)+2; |
| char *tmp = reinterpret_cast<char*>(malloc(tmplen)); |
| if(tmp == 0) return false; |
| snprintf(tmp, tmplen, "%s/",rpath); |
| rpath = tmp; |
| } |
| |
| if (local_build_list(&filelist, lpath, rpath)) { |
| return false; |
| } |
| |
| if (check_timestamps) { |
| for (ci = filelist; ci != 0; ci = ci->next) { |
| if (!sync_start_stat(sc, ci->dst)) return false; |
| } |
| for(ci = filelist; ci != 0; ci = ci->next) { |
| unsigned int timestamp, mode, size; |
| if (!sync_finish_stat(sc, ×tamp, &mode, &size)) return false; |
| if (size == ci->size) { |
| /* for links, we cannot update the atime/mtime */ |
| if ((S_ISREG(ci->mode & mode) && timestamp == ci->time) || |
| (S_ISLNK(ci->mode & mode) && timestamp >= ci->time)) { |
| ci->flag = 1; |
| } |
| } |
| } |
| } |
| for (ci = filelist; ci != 0; ci = next) { |
| next = ci->next; |
| if (ci->flag == 0) { |
| fprintf(stderr, "%spush: %s -> %s\n", list_only ? "would " : "", ci->src, ci->dst); |
| if (!list_only && !sync_send(sc, ci->src, ci->dst, ci->time, ci->mode, false)) { |
| return false; |
| } |
| pushed++; |
| } else { |
| skipped++; |
| } |
| free(ci); |
| } |
| |
| fprintf(stderr, "%d file%s pushed. %d file%s skipped.\n", |
| pushed, (pushed == 1) ? "" : "s", |
| skipped, (skipped == 1) ? "" : "s"); |
| |
| return true; |
| } |
| |
| bool do_sync_push(const char* lpath, const char* rpath, bool show_progress) { |
| SyncConnection sc; |
| if (!sc.IsValid()) return false; |
| |
| struct stat st; |
| if (stat(lpath, &st)) { |
| fprintf(stderr, "cannot stat '%s': %s\n", lpath, strerror(errno)); |
| return false; |
| } |
| |
| if (S_ISDIR(st.st_mode)) { |
| return copy_local_dir_remote(sc, lpath, rpath, false, false); |
| } |
| |
| unsigned mode; |
| if (!sync_stat(sc, rpath, nullptr, &mode, nullptr)) return false; |
| std::string path_holder; |
| if (mode != 0 && S_ISDIR(mode)) { |
| // If we're copying a local file to a remote directory, |
| // we really want to copy to remote_dir + "/" + local_filename. |
| path_holder = android::base::StringPrintf("%s/%s", rpath, adb_basename(lpath).c_str()); |
| rpath = path_holder.c_str(); |
| } |
| return sync_send(sc, lpath, rpath, st.st_mtime, st.st_mode, show_progress); |
| } |
| |
| |
| struct sync_ls_build_list_cb_args { |
| copyinfo **filelist; |
| copyinfo **dirlist; |
| const char *rpath; |
| const char *lpath; |
| }; |
| |
| static void sync_ls_build_list_cb(unsigned mode, unsigned size, unsigned time, |
| const char* name, void* cookie) |
| { |
| sync_ls_build_list_cb_args *args = (sync_ls_build_list_cb_args *)cookie; |
| copyinfo *ci; |
| |
| if (S_ISDIR(mode)) { |
| copyinfo **dirlist = args->dirlist; |
| |
| // Don't try recursing down "." or "..". |
| if (IsDotOrDotDot(name)) return; |
| |
| ci = mkcopyinfo(args->rpath, args->lpath, name, 1); |
| ci->next = *dirlist; |
| *dirlist = ci; |
| } else if (S_ISREG(mode) || S_ISLNK(mode)) { |
| copyinfo **filelist = args->filelist; |
| |
| ci = mkcopyinfo(args->rpath, args->lpath, name, 0); |
| ci->time = time; |
| ci->mode = mode; |
| ci->size = size; |
| ci->next = *filelist; |
| *filelist = ci; |
| } else { |
| fprintf(stderr, "skipping special file '%s'\n", name); |
| } |
| } |
| |
| static bool remote_build_list(int syncfd, copyinfo **filelist, |
| const char *rpath, const char *lpath) { |
| copyinfo *dirlist = NULL; |
| sync_ls_build_list_cb_args args; |
| |
| args.filelist = filelist; |
| args.dirlist = &dirlist; |
| args.rpath = rpath; |
| args.lpath = lpath; |
| |
| // Put the files/dirs in rpath on the lists. |
| if (!sync_ls(syncfd, rpath, sync_ls_build_list_cb, (void *)&args)) { |
| return false; |
| } |
| |
| // Recurse into each directory we found. |
| while (dirlist != NULL) { |
| copyinfo *next = dirlist->next; |
| if (!remote_build_list(syncfd, filelist, dirlist->src, dirlist->dst)) { |
| return false; |
| } |
| free(dirlist); |
| dirlist = next; |
| } |
| |
| return true; |
| } |
| |
| static int set_time_and_mode(const char *lpath, time_t time, unsigned int mode) |
| { |
| struct utimbuf times = { time, time }; |
| int r1 = utime(lpath, ×); |
| |
| /* use umask for permissions */ |
| mode_t mask=umask(0000); |
| umask(mask); |
| int r2 = chmod(lpath, mode & ~mask); |
| |
| return r1 ? : r2; |
| } |
| |
| static bool copy_remote_dir_local(SyncConnection& sc, const char* rpath, const char* lpath, |
| int copy_attrs) { |
| // Make sure that both directory paths end in a slash. |
| std::string rpath_clean(rpath); |
| std::string lpath_clean(lpath); |
| if (rpath_clean.empty() || lpath_clean.empty()) return false; |
| if (rpath_clean.back() != '/') rpath_clean.push_back('/'); |
| if (lpath_clean.back() != '/') lpath_clean.push_back('/'); |
| |
| // Recursively build the list of files to copy. |
| fprintf(stderr, "pull: building file list...\n"); |
| copyinfo* filelist = nullptr; |
| if (!remote_build_list(sc.fd, &filelist, rpath_clean.c_str(), lpath_clean.c_str())) return false; |
| |
| int pulled = 0; |
| int skipped = 0; |
| copyinfo* ci = filelist; |
| while (ci) { |
| copyinfo* next = ci->next; |
| if (ci->flag == 0) { |
| fprintf(stderr, "pull: %s -> %s\n", ci->src, ci->dst); |
| if (sync_recv(sc, ci->src, ci->dst, false)) { |
| return false; |
| } |
| |
| if (copy_attrs && set_time_and_mode(ci->dst, ci->time, ci->mode)) { |
| return false; |
| } |
| pulled++; |
| } else { |
| skipped++; |
| } |
| free(ci); |
| ci = next; |
| } |
| |
| fprintf(stderr, "%d file%s pulled. %d file%s skipped.\n", |
| pulled, (pulled == 1) ? "" : "s", |
| skipped, (skipped == 1) ? "" : "s"); |
| return true; |
| } |
| |
| bool do_sync_pull(const char* rpath, const char* lpath, bool show_progress, int copy_attrs) { |
| SyncConnection sc; |
| if (!sc.IsValid()) return false; |
| |
| unsigned mode, time; |
| if (!sync_stat(sc, rpath, &time, &mode, nullptr)) return false; |
| if (mode == 0) { |
| fprintf(stderr, "remote object '%s' does not exist\n", rpath); |
| return false; |
| } |
| |
| if (S_ISREG(mode) || S_ISLNK(mode) || S_ISCHR(mode) || S_ISBLK(mode)) { |
| std::string path_holder; |
| struct stat st; |
| if (stat(lpath, &st) == 0) { |
| if (S_ISDIR(st.st_mode)) { |
| // If we're copying a remote file to a local directory, |
| // we really want to copy to local_dir + "/" + basename(remote). |
| path_holder = android::base::StringPrintf("%s/%s", lpath, adb_basename(rpath).c_str()); |
| lpath = path_holder.c_str(); |
| } |
| } |
| if (sync_recv(sc, rpath, lpath, show_progress)) { |
| return false; |
| } else { |
| if (copy_attrs && set_time_and_mode(lpath, time, mode)) { |
| return false; |
| } |
| } |
| return true; |
| } else if (S_ISDIR(mode)) { |
| return copy_remote_dir_local(sc, rpath, lpath, copy_attrs); |
| } |
| |
| fprintf(stderr, "remote object '%s' not a file or directory\n", rpath); |
| return false; |
| } |
| |
| bool do_sync_sync(const std::string& lpath, const std::string& rpath, bool list_only) { |
| fprintf(stderr, "syncing %s...\n", rpath.c_str()); |
| |
| SyncConnection sc; |
| if (!sc.IsValid()) return false; |
| |
| return copy_local_dir_remote(sc, lpath.c_str(), rpath.c_str(), true, list_only); |
| } |