/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "sysdeps.h" #include "adb.h" #include "adb_client.h" #include "adb_io.h" #include "adb_utils.h" #include "file_sync_service.h" #include #include 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); 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(buf); req->id = id; req->path_length = path_length; char* data = reinterpret_cast(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())) goto fail; 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 { goto fail; } syncmsg msg; msg.data.id = ID_DONE; msg.data.size = mtime; if (!WriteFdExactly(sc.fd, &msg.data, sizeof(msg.data))) goto fail; if (!ReadFdExactly(sc.fd, &msg.status, sizeof(msg.status))) goto fail; 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)) goto fail; 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; fail: fprintf(stderr, "protocol failure\n"); return false; } 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(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(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(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(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); }