| /* | 
 |  * 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 <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #include <errno.h> | 
 | #include <sys/stat.h> | 
 | #include <sys/time.h> | 
 | #include <time.h> | 
 | #include <dirent.h> | 
 | #include <limits.h> | 
 | #include <sys/types.h> | 
 | #include <zipfile/zipfile.h> | 
 |  | 
 | #include "sysdeps.h" | 
 | #include "adb.h" | 
 | #include "adb_client.h" | 
 | #include "file_sync_service.h" | 
 |  | 
 |  | 
 | static unsigned total_bytes; | 
 | static long long start_time; | 
 |  | 
 | static long long NOW() | 
 | { | 
 |     struct timeval tv; | 
 |     gettimeofday(&tv, 0); | 
 |     return ((long long) tv.tv_usec) + | 
 |         1000000LL * ((long long) tv.tv_sec); | 
 | } | 
 |  | 
 | static void BEGIN() | 
 | { | 
 |     total_bytes = 0; | 
 |     start_time = NOW(); | 
 | } | 
 |  | 
 | static void END() | 
 | { | 
 |     long long t = NOW() - start_time; | 
 |     if(total_bytes == 0) return; | 
 |  | 
 |     if (t == 0)  /* prevent division by 0 :-) */ | 
 |         t = 1000000; | 
 |  | 
 |     fprintf(stderr,"%lld KB/s (%lld bytes in %lld.%03llds)\n", | 
 |             ((((long long) total_bytes) * 1000000LL) / t) / 1024LL, | 
 |             (long long) total_bytes, (t / 1000000LL), (t % 1000000LL) / 1000LL); | 
 | } | 
 |  | 
 | void sync_quit(int fd) | 
 | { | 
 |     syncmsg msg; | 
 |  | 
 |     msg.req.id = ID_QUIT; | 
 |     msg.req.namelen = 0; | 
 |  | 
 |     writex(fd, &msg.req, sizeof(msg.req)); | 
 | } | 
 |  | 
 | typedef void (*sync_ls_cb)(unsigned mode, unsigned size, unsigned time, const char *name, void *cookie); | 
 |  | 
 | int sync_ls(int fd, const char *path, sync_ls_cb func, void *cookie) | 
 | { | 
 |     syncmsg msg; | 
 |     char buf[257]; | 
 |     int len; | 
 |  | 
 |     len = strlen(path); | 
 |     if(len > 1024) goto fail; | 
 |  | 
 |     msg.req.id = ID_LIST; | 
 |     msg.req.namelen = htoll(len); | 
 |  | 
 |     if(writex(fd, &msg.req, sizeof(msg.req)) || | 
 |        writex(fd, path, len)) { | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     for(;;) { | 
 |         if(readx(fd, &msg.dent, sizeof(msg.dent))) break; | 
 |         if(msg.dent.id == ID_DONE) return 0; | 
 |         if(msg.dent.id != ID_DENT) break; | 
 |  | 
 |         len = ltohl(msg.dent.namelen); | 
 |         if(len > 256) break; | 
 |  | 
 |         if(readx(fd, buf, len)) break; | 
 |         buf[len] = 0; | 
 |  | 
 |         func(ltohl(msg.dent.mode), | 
 |              ltohl(msg.dent.size), | 
 |              ltohl(msg.dent.time), | 
 |              buf, cookie); | 
 |     } | 
 |  | 
 | fail: | 
 |     adb_close(fd); | 
 |     return -1; | 
 | } | 
 |  | 
 | typedef struct syncsendbuf syncsendbuf; | 
 |  | 
 | struct syncsendbuf { | 
 |     unsigned id; | 
 |     unsigned size; | 
 |     char data[SYNC_DATA_MAX]; | 
 | }; | 
 |  | 
 | static syncsendbuf send_buffer; | 
 |  | 
 | int sync_readtime(int fd, const char *path, unsigned *timestamp) | 
 | { | 
 |     syncmsg msg; | 
 |     int len = strlen(path); | 
 |  | 
 |     msg.req.id = ID_STAT; | 
 |     msg.req.namelen = htoll(len); | 
 |  | 
 |     if(writex(fd, &msg.req, sizeof(msg.req)) || | 
 |        writex(fd, path, len)) { | 
 |         return -1; | 
 |     } | 
 |  | 
 |     if(readx(fd, &msg.stat, sizeof(msg.stat))) { | 
 |         return -1; | 
 |     } | 
 |  | 
 |     if(msg.stat.id != ID_STAT) { | 
 |         return -1; | 
 |     } | 
 |  | 
 |     *timestamp = ltohl(msg.stat.time); | 
 |     return 0; | 
 | } | 
 |  | 
 | static int sync_start_readtime(int fd, const char *path) | 
 | { | 
 |     syncmsg msg; | 
 |     int len = strlen(path); | 
 |  | 
 |     msg.req.id = ID_STAT; | 
 |     msg.req.namelen = htoll(len); | 
 |  | 
 |     if(writex(fd, &msg.req, sizeof(msg.req)) || | 
 |        writex(fd, path, len)) { | 
 |         return -1; | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static int sync_finish_readtime(int fd, unsigned int *timestamp, | 
 |                                 unsigned int *mode, unsigned int *size) | 
 | { | 
 |     syncmsg msg; | 
 |  | 
 |     if(readx(fd, &msg.stat, sizeof(msg.stat))) | 
 |         return -1; | 
 |  | 
 |     if(msg.stat.id != ID_STAT) | 
 |         return -1; | 
 |  | 
 |     *timestamp = ltohl(msg.stat.time); | 
 |     *mode = ltohl(msg.stat.mode); | 
 |     *size = ltohl(msg.stat.size); | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | int sync_readmode(int fd, const char *path, unsigned *mode) | 
 | { | 
 |     syncmsg msg; | 
 |     int len = strlen(path); | 
 |  | 
 |     msg.req.id = ID_STAT; | 
 |     msg.req.namelen = htoll(len); | 
 |  | 
 |     if(writex(fd, &msg.req, sizeof(msg.req)) || | 
 |        writex(fd, path, len)) { | 
 |         return -1; | 
 |     } | 
 |  | 
 |     if(readx(fd, &msg.stat, sizeof(msg.stat))) { | 
 |         return -1; | 
 |     } | 
 |  | 
 |     if(msg.stat.id != ID_STAT) { | 
 |         return -1; | 
 |     } | 
 |  | 
 |     *mode = ltohl(msg.stat.mode); | 
 |     return 0; | 
 | } | 
 |  | 
 | static int write_data_file(int fd, const char *path, syncsendbuf *sbuf) | 
 | { | 
 |     int lfd, err = 0; | 
 |  | 
 |     lfd = adb_open(path, O_RDONLY); | 
 |     if(lfd < 0) { | 
 |         fprintf(stderr,"cannot open '%s': %s\n", path, strerror(errno)); | 
 |         return -1; | 
 |     } | 
 |  | 
 |     sbuf->id = ID_DATA; | 
 |     for(;;) { | 
 |         int ret; | 
 |  | 
 |         ret = adb_read(lfd, sbuf->data, SYNC_DATA_MAX); | 
 |         if(!ret) | 
 |             break; | 
 |  | 
 |         if(ret < 0) { | 
 |             if(errno == EINTR) | 
 |                 continue; | 
 |             fprintf(stderr,"cannot read '%s': %s\n", path, strerror(errno)); | 
 |             break; | 
 |         } | 
 |  | 
 |         sbuf->size = htoll(ret); | 
 |         if(writex(fd, sbuf, sizeof(unsigned) * 2 + ret)){ | 
 |             err = -1; | 
 |             break; | 
 |         } | 
 |         total_bytes += ret; | 
 |     } | 
 |  | 
 |     adb_close(lfd); | 
 |     return err; | 
 | } | 
 |  | 
 | static int write_data_buffer(int fd, char* file_buffer, int size, syncsendbuf *sbuf) | 
 | { | 
 |     int err = 0; | 
 |     int total = 0; | 
 |  | 
 |     sbuf->id = ID_DATA; | 
 |     while (total < size) { | 
 |         int count = size - total; | 
 |         if (count > SYNC_DATA_MAX) { | 
 |             count = SYNC_DATA_MAX; | 
 |         } | 
 |  | 
 |         memcpy(sbuf->data, &file_buffer[total], count); | 
 |         sbuf->size = htoll(count); | 
 |         if(writex(fd, sbuf, sizeof(unsigned) * 2 + count)){ | 
 |             err = -1; | 
 |             break; | 
 |         } | 
 |         total += count; | 
 |         total_bytes += count; | 
 |     } | 
 |  | 
 |     return err; | 
 | } | 
 |  | 
 | #ifdef HAVE_SYMLINKS | 
 | static int write_data_link(int fd, const char *path, syncsendbuf *sbuf) | 
 | { | 
 |     int len, ret; | 
 |  | 
 |     len = readlink(path, sbuf->data, SYNC_DATA_MAX-1); | 
 |     if(len < 0) { | 
 |         fprintf(stderr, "error reading link '%s': %s\n", path, strerror(errno)); | 
 |         return -1; | 
 |     } | 
 |     sbuf->data[len] = '\0'; | 
 |  | 
 |     sbuf->size = htoll(len + 1); | 
 |     sbuf->id = ID_DATA; | 
 |  | 
 |     ret = writex(fd, sbuf, sizeof(unsigned) * 2 + len + 1); | 
 |     if(ret) | 
 |         return -1; | 
 |  | 
 |     total_bytes += len + 1; | 
 |  | 
 |     return 0; | 
 | } | 
 | #endif | 
 |  | 
 | static int sync_send(int fd, const char *lpath, const char *rpath, | 
 |                      unsigned mtime, mode_t mode, int verifyApk) | 
 | { | 
 |     syncmsg msg; | 
 |     int len, r; | 
 |     syncsendbuf *sbuf = &send_buffer; | 
 |     char* file_buffer = NULL; | 
 |     int size = 0; | 
 |     char tmp[64]; | 
 |  | 
 |     len = strlen(rpath); | 
 |     if(len > 1024) goto fail; | 
 |  | 
 |     snprintf(tmp, sizeof(tmp), ",%d", mode); | 
 |     r = strlen(tmp); | 
 |  | 
 |     if (verifyApk) { | 
 |         int lfd; | 
 |         zipfile_t zip; | 
 |         zipentry_t entry; | 
 |         int amt; | 
 |  | 
 |         // if we are transferring an APK file, then sanity check to make sure | 
 |         // we have a real zip file that contains an AndroidManifest.xml | 
 |         // this requires that we read the entire file into memory. | 
 |         lfd = adb_open(lpath, O_RDONLY); | 
 |         if(lfd < 0) { | 
 |             fprintf(stderr,"cannot open '%s': %s\n", lpath, strerror(errno)); | 
 |             return -1; | 
 |         } | 
 |  | 
 |         size = adb_lseek(lfd, 0, SEEK_END); | 
 |         if (size == -1 || -1 == adb_lseek(lfd, 0, SEEK_SET)) { | 
 |             fprintf(stderr, "error seeking in file '%s'\n", lpath); | 
 |             adb_close(lfd); | 
 |             return 1; | 
 |         } | 
 |  | 
 |         file_buffer = (char *)malloc(size); | 
 |         if (file_buffer == NULL) { | 
 |             fprintf(stderr, "could not allocate buffer for '%s'\n", | 
 |                     lpath); | 
 |             adb_close(lfd); | 
 |             return 1; | 
 |         } | 
 |         amt = adb_read(lfd, file_buffer, size); | 
 |         if (amt != size) { | 
 |             fprintf(stderr, "error reading from file: '%s'\n", lpath); | 
 |             adb_close(lfd); | 
 |             free(file_buffer); | 
 |             return 1; | 
 |         } | 
 |  | 
 |         adb_close(lfd); | 
 |  | 
 |         zip = init_zipfile(file_buffer, size); | 
 |         if (zip == NULL) { | 
 |             fprintf(stderr, "file '%s' is not a valid zip file\n", | 
 |                     lpath); | 
 |             free(file_buffer); | 
 |             return 1; | 
 |         } | 
 |  | 
 |         entry = lookup_zipentry(zip, "AndroidManifest.xml"); | 
 |         release_zipfile(zip); | 
 |         if (entry == NULL) { | 
 |             fprintf(stderr, "file '%s' does not contain AndroidManifest.xml\n", | 
 |                     lpath); | 
 |             free(file_buffer); | 
 |             return 1; | 
 |         } | 
 |     } | 
 |  | 
 |     msg.req.id = ID_SEND; | 
 |     msg.req.namelen = htoll(len + r); | 
 |  | 
 |     if(writex(fd, &msg.req, sizeof(msg.req)) || | 
 |        writex(fd, rpath, len) || writex(fd, tmp, r)) { | 
 |         free(file_buffer); | 
 |         goto fail; | 
 |     } | 
 |  | 
 |     if (file_buffer) { | 
 |         write_data_buffer(fd, file_buffer, size, sbuf); | 
 |         free(file_buffer); | 
 |     } else if (S_ISREG(mode)) | 
 |         write_data_file(fd, lpath, sbuf); | 
 | #ifdef HAVE_SYMLINKS | 
 |     else if (S_ISLNK(mode)) | 
 |         write_data_link(fd, lpath, sbuf); | 
 | #endif | 
 |     else | 
 |         goto fail; | 
 |  | 
 |     msg.data.id = ID_DONE; | 
 |     msg.data.size = htoll(mtime); | 
 |     if(writex(fd, &msg.data, sizeof(msg.data))) | 
 |         goto fail; | 
 |  | 
 |     if(readx(fd, &msg.status, sizeof(msg.status))) | 
 |         return -1; | 
 |  | 
 |     if(msg.status.id != ID_OKAY) { | 
 |         if(msg.status.id == ID_FAIL) { | 
 |             len = ltohl(msg.status.msglen); | 
 |             if(len > 256) len = 256; | 
 |             if(readx(fd, sbuf->data, len)) { | 
 |                 return -1; | 
 |             } | 
 |             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 -1; | 
 |     } | 
 |  | 
 |     return 0; | 
 |  | 
 | fail: | 
 |     fprintf(stderr,"protocol failure\n"); | 
 |     adb_close(fd); | 
 |     return -1; | 
 | } | 
 |  | 
 | static int mkdirs(char *name) | 
 | { | 
 |     int ret; | 
 |     char *x = name + 1; | 
 |  | 
 |     for(;;) { | 
 |         x = adb_dirstart(x); | 
 |         if(x == 0) return 0; | 
 |         *x = 0; | 
 |         ret = adb_mkdir(name, 0775); | 
 |         *x = OS_PATH_SEPARATOR; | 
 |         if((ret < 0) && (errno != EEXIST)) { | 
 |             return ret; | 
 |         } | 
 |         x++; | 
 |     } | 
 |     return 0; | 
 | } | 
 |  | 
 | int sync_recv(int fd, const char *rpath, const char *lpath) | 
 | { | 
 |     syncmsg msg; | 
 |     int len; | 
 |     int lfd = -1; | 
 |     char *buffer = send_buffer.data; | 
 |     unsigned id; | 
 |  | 
 |     len = strlen(rpath); | 
 |     if(len > 1024) return -1; | 
 |  | 
 |     msg.req.id = ID_RECV; | 
 |     msg.req.namelen = htoll(len); | 
 |     if(writex(fd, &msg.req, sizeof(msg.req)) || | 
 |        writex(fd, rpath, len)) { | 
 |         return -1; | 
 |     } | 
 |  | 
 |     if(readx(fd, &msg.data, sizeof(msg.data))) { | 
 |         return -1; | 
 |     } | 
 |     id = msg.data.id; | 
 |  | 
 |     if((id == ID_DATA) || (id == ID_DONE)) { | 
 |         adb_unlink(lpath); | 
 |         mkdirs((char *)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; | 
 |     } | 
 |  | 
 |     for(;;) { | 
 |         if(readx(fd, &msg.data, sizeof(msg.data))) { | 
 |             return -1; | 
 |         } | 
 |         id = msg.data.id; | 
 |  | 
 |     handle_data: | 
 |         len = ltohl(msg.data.size); | 
 |         if(id == ID_DONE) break; | 
 |         if(id != ID_DATA) goto remote_error; | 
 |         if(len > SYNC_DATA_MAX) { | 
 |             fprintf(stderr,"data overrun\n"); | 
 |             adb_close(lfd); | 
 |             return -1; | 
 |         } | 
 |  | 
 |         if(readx(fd, buffer, len)) { | 
 |             adb_close(lfd); | 
 |             return -1; | 
 |         } | 
 |  | 
 |         if(writex(lfd, buffer, len)) { | 
 |             fprintf(stderr,"cannot write '%s': %s\n", rpath, strerror(errno)); | 
 |             adb_close(lfd); | 
 |             return -1; | 
 |         } | 
 |  | 
 |         total_bytes += len; | 
 |     } | 
 |  | 
 |     adb_close(lfd); | 
 |     return 0; | 
 |  | 
 | remote_error: | 
 |     adb_close(lfd); | 
 |     adb_unlink(lpath); | 
 |  | 
 |     if(id == ID_FAIL) { | 
 |         len = ltohl(msg.data.size); | 
 |         if(len > 256) len = 256; | 
 |         if(readx(fd, buffer, len)) { | 
 |             return -1; | 
 |         } | 
 |         buffer[len] = 0; | 
 |     } else { | 
 |         memcpy(buffer, &id, 4); | 
 |         buffer[4] = 0; | 
 | //        strcpy(buffer,"unknown reason"); | 
 |     } | 
 |     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); | 
 | } | 
 |  | 
 | int do_sync_ls(const char *path) | 
 | { | 
 |     int fd = adb_connect("sync:"); | 
 |     if(fd < 0) { | 
 |         fprintf(stderr,"error: %s\n", adb_error()); | 
 |         return 1; | 
 |     } | 
 |  | 
 |     if(sync_ls(fd, path, do_sync_ls_cb, 0)) { | 
 |         return 1; | 
 |     } else { | 
 |         sync_quit(fd); | 
 |         return 0; | 
 |     } | 
 | } | 
 |  | 
 | typedef struct copyinfo copyinfo; | 
 |  | 
 | struct copyinfo | 
 | { | 
 |     copyinfo *next; | 
 |     const char *src; | 
 |     const char *dst; | 
 |     unsigned int time; | 
 |     unsigned int mode; | 
 |     unsigned int size; | 
 |     int flag; | 
 |     //char data[0]; | 
 | }; | 
 |  | 
 | 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 = 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); | 
 |  | 
 | //    fprintf(stderr,"mkcopyinfo('%s','%s')\n", ci->src, ci->dst); | 
 |     return ci; | 
 | } | 
 |  | 
 |  | 
 | static int local_build_list(copyinfo **filelist, | 
 |                             const char *lpath, const char *rpath) | 
 | { | 
 |     DIR *d; | 
 |     struct dirent *de; | 
 |     struct stat st; | 
 |     copyinfo *dirlist = 0; | 
 |     copyinfo *ci, *next; | 
 |  | 
 | //    fprintf(stderr,"local_build_list('%s','%s')\n", lpath, rpath); | 
 |  | 
 |     d = opendir(lpath); | 
 |     if(d == 0) { | 
 |         fprintf(stderr,"cannot open '%s': %s\n", lpath, strerror(errno)); | 
 |         return -1; | 
 |     } | 
 |  | 
 |     while((de = readdir(d))) { | 
 |         char stat_path[PATH_MAX]; | 
 |         char *name = de->d_name; | 
 |  | 
 |         if(name[0] == '.') { | 
 |             if(name[1] == 0) continue; | 
 |             if((name[1] == '.') && (name[2] == 0)) continue; | 
 |         } | 
 |  | 
 |         /* | 
 |          * We could use d_type if HAVE_DIRENT_D_TYPE is defined, but reiserfs | 
 |          * always returns DT_UNKNOWN, so we just use stat() for all cases. | 
 |          */ | 
 |         if (strlen(lpath) + strlen(de->d_name) + 1 > sizeof(stat_path)) | 
 |             continue; | 
 |         strcpy(stat_path, lpath); | 
 |         strcat(stat_path, de->d_name); | 
 |         stat(stat_path, &st); | 
 |  | 
 |         if (S_ISDIR(st.st_mode)) { | 
 |             ci = mkcopyinfo(lpath, rpath, name, 1); | 
 |             ci->next = dirlist; | 
 |             dirlist = ci; | 
 |         } else { | 
 |             ci = mkcopyinfo(lpath, rpath, name, 0); | 
 |             if(lstat(ci->src, &st)) { | 
 |                 fprintf(stderr,"cannot stat '%s': %s\n", ci->src, strerror(errno)); | 
 |                 closedir(d); | 
 |  | 
 |                 return -1; | 
 |             } | 
 |             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; | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     closedir(d); | 
 |  | 
 |     for(ci = dirlist; ci != 0; ci = next) { | 
 |         next = ci->next; | 
 |         local_build_list(filelist, ci->src, ci->dst); | 
 |         free(ci); | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 |  | 
 | static int copy_local_dir_remote(int fd, const char *lpath, const char *rpath, int checktimestamps, int listonly) | 
 | { | 
 |     copyinfo *filelist = 0; | 
 |     copyinfo *ci, *next; | 
 |     int pushed = 0; | 
 |     int skipped = 0; | 
 |  | 
 |     if((lpath[0] == 0) || (rpath[0] == 0)) return -1; | 
 |     if(lpath[strlen(lpath) - 1] != '/') { | 
 |         int  tmplen = strlen(lpath)+2; | 
 |         char *tmp = malloc(tmplen); | 
 |         if(tmp == 0) return -1; | 
 |         snprintf(tmp, tmplen, "%s/",lpath); | 
 |         lpath = tmp; | 
 |     } | 
 |     if(rpath[strlen(rpath) - 1] != '/') { | 
 |         int tmplen = strlen(rpath)+2; | 
 |         char *tmp = malloc(tmplen); | 
 |         if(tmp == 0) return -1; | 
 |         snprintf(tmp, tmplen, "%s/",rpath); | 
 |         rpath = tmp; | 
 |     } | 
 |  | 
 |     if(local_build_list(&filelist, lpath, rpath)) { | 
 |         return -1; | 
 |     } | 
 |  | 
 |     if(checktimestamps){ | 
 |         for(ci = filelist; ci != 0; ci = ci->next) { | 
 |             if(sync_start_readtime(fd, ci->dst)) { | 
 |                 return 1; | 
 |             } | 
 |         } | 
 |         for(ci = filelist; ci != 0; ci = ci->next) { | 
 |             unsigned int timestamp, mode, size; | 
 |             if(sync_finish_readtime(fd, ×tamp, &mode, &size)) | 
 |                 return 1; | 
 |             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", listonly ? "would " : "", ci->src, ci->dst); | 
 |             if(!listonly && | 
 |                sync_send(fd, ci->src, ci->dst, ci->time, ci->mode, 0 /* no verify APK */)){ | 
 |                 return 1; | 
 |             } | 
 |             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 0; | 
 | } | 
 |  | 
 |  | 
 | int do_sync_push(const char *lpath, const char *rpath, int verifyApk) | 
 | { | 
 |     struct stat st; | 
 |     unsigned mode; | 
 |     int fd; | 
 |  | 
 |     fd = adb_connect("sync:"); | 
 |     if(fd < 0) { | 
 |         fprintf(stderr,"error: %s\n", adb_error()); | 
 |         return 1; | 
 |     } | 
 |  | 
 |     if(stat(lpath, &st)) { | 
 |         fprintf(stderr,"cannot stat '%s': %s\n", lpath, strerror(errno)); | 
 |         sync_quit(fd); | 
 |         return 1; | 
 |     } | 
 |  | 
 |     if(S_ISDIR(st.st_mode)) { | 
 |         BEGIN(); | 
 |         if(copy_local_dir_remote(fd, lpath, rpath, 0, 0)) { | 
 |             return 1; | 
 |         } else { | 
 |             END(); | 
 |             sync_quit(fd); | 
 |         } | 
 |     } else { | 
 |         if(sync_readmode(fd, rpath, &mode)) { | 
 |             return 1; | 
 |         } | 
 |         if((mode != 0) && S_ISDIR(mode)) { | 
 |                 /* if we're copying a local file to a remote directory, | 
 |                 ** we *really* want to copy to remotedir + "/" + localfilename | 
 |                 */ | 
 |             const char *name = adb_dirstop(lpath); | 
 |             if(name == 0) { | 
 |                 name = lpath; | 
 |             } else { | 
 |                 name++; | 
 |             } | 
 |             int  tmplen = strlen(name) + strlen(rpath) + 2; | 
 |             char *tmp = malloc(strlen(name) + strlen(rpath) + 2); | 
 |             if(tmp == 0) return 1; | 
 |             snprintf(tmp, tmplen, "%s/%s", rpath, name); | 
 |             rpath = tmp; | 
 |         } | 
 |         BEGIN(); | 
 |         if(sync_send(fd, lpath, rpath, st.st_mtime, st.st_mode, verifyApk)) { | 
 |             return 1; | 
 |         } else { | 
 |             END(); | 
 |             sync_quit(fd); | 
 |             return 0; | 
 |         } | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 |  | 
 | typedef struct { | 
 |     copyinfo **filelist; | 
 |     copyinfo **dirlist; | 
 |     const char *rpath; | 
 |     const char *lpath; | 
 | } sync_ls_build_list_cb_args; | 
 |  | 
 | 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 (name[0] == '.') { | 
 |             if (name[1] == '\0') return; | 
 |             if ((name[1] == '.') && (name[2] == '\0')) 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 int 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 1; | 
 |     } | 
 |  | 
 |     /* Recurse into each directory we found. */ | 
 |     while (dirlist != NULL) { | 
 |         copyinfo *next = dirlist->next; | 
 |         if (remote_build_list(syncfd, filelist, dirlist->src, dirlist->dst)) { | 
 |             return 1; | 
 |         } | 
 |         free(dirlist); | 
 |         dirlist = next; | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static int copy_remote_dir_local(int fd, const char *rpath, const char *lpath, | 
 |                                  int checktimestamps) | 
 | { | 
 |     copyinfo *filelist = 0; | 
 |     copyinfo *ci, *next; | 
 |     int pulled = 0; | 
 |     int skipped = 0; | 
 |  | 
 |     /* Make sure that both directory paths end in a slash. */ | 
 |     if (rpath[0] == 0 || lpath[0] == 0) return -1; | 
 |     if (rpath[strlen(rpath) - 1] != '/') { | 
 |         int  tmplen = strlen(rpath) + 2; | 
 |         char *tmp = malloc(tmplen); | 
 |         if (tmp == 0) return -1; | 
 |         snprintf(tmp, tmplen, "%s/", rpath); | 
 |         rpath = tmp; | 
 |     } | 
 |     if (lpath[strlen(lpath) - 1] != '/') { | 
 |         int  tmplen = strlen(lpath) + 2; | 
 |         char *tmp = malloc(tmplen); | 
 |         if (tmp == 0) return -1; | 
 |         snprintf(tmp, tmplen, "%s/", lpath); | 
 |         lpath = tmp; | 
 |     } | 
 |  | 
 |     fprintf(stderr, "pull: building file list...\n"); | 
 |     /* Recursively build the list of files to copy. */ | 
 |     if (remote_build_list(fd, &filelist, rpath, lpath)) { | 
 |         return -1; | 
 |     } | 
 |  | 
 | #if 0 | 
 |     if (checktimestamps) { | 
 |         for (ci = filelist; ci != 0; ci = ci->next) { | 
 |             if (sync_start_readtime(fd, ci->dst)) { | 
 |                 return 1; | 
 |             } | 
 |         } | 
 |         for (ci = filelist; ci != 0; ci = ci->next) { | 
 |             unsigned int timestamp, mode, size; | 
 |             if (sync_finish_readtime(fd, ×tamp, &mode, &size)) | 
 |                 return 1; | 
 |             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; | 
 |             } | 
 |         } | 
 |     } | 
 | #endif | 
 |     for (ci = filelist; ci != 0; ci = next) { | 
 |         next = ci->next; | 
 |         if (ci->flag == 0) { | 
 |             fprintf(stderr, "pull: %s -> %s\n", ci->src, ci->dst); | 
 |             if (sync_recv(fd, ci->src, ci->dst)) { | 
 |                 return 1; | 
 |             } | 
 |             pulled++; | 
 |         } else { | 
 |             skipped++; | 
 |         } | 
 |         free(ci); | 
 |     } | 
 |  | 
 |     fprintf(stderr, "%d file%s pulled. %d file%s skipped.\n", | 
 |             pulled, (pulled == 1) ? "" : "s", | 
 |             skipped, (skipped == 1) ? "" : "s"); | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | int do_sync_pull(const char *rpath, const char *lpath) | 
 | { | 
 |     unsigned mode; | 
 |     struct stat st; | 
 |  | 
 |     int fd; | 
 |  | 
 |     fd = adb_connect("sync:"); | 
 |     if(fd < 0) { | 
 |         fprintf(stderr,"error: %s\n", adb_error()); | 
 |         return 1; | 
 |     } | 
 |  | 
 |     if(sync_readmode(fd, rpath, &mode)) { | 
 |         return 1; | 
 |     } | 
 |     if(mode == 0) { | 
 |         fprintf(stderr,"remote object '%s' does not exist\n", rpath); | 
 |         return 1; | 
 |     } | 
 |  | 
 |     if(S_ISREG(mode) || S_ISLNK(mode) || S_ISCHR(mode) || S_ISBLK(mode)) { | 
 |         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 localdir + "/" + remotefilename | 
 |                     */ | 
 |                 const char *name = adb_dirstop(rpath); | 
 |                 if(name == 0) { | 
 |                     name = rpath; | 
 |                 } else { | 
 |                     name++; | 
 |                 } | 
 |                 int  tmplen = strlen(name) + strlen(lpath) + 2; | 
 |                 char *tmp = malloc(tmplen); | 
 |                 if(tmp == 0) return 1; | 
 |                 snprintf(tmp, tmplen, "%s/%s", lpath, name); | 
 |                 lpath = tmp; | 
 |             } | 
 |         } | 
 |         BEGIN(); | 
 |         if(sync_recv(fd, rpath, lpath)) { | 
 |             return 1; | 
 |         } else { | 
 |             END(); | 
 |             sync_quit(fd); | 
 |             return 0; | 
 |         } | 
 |     } else if(S_ISDIR(mode)) { | 
 |         BEGIN(); | 
 |         if (copy_remote_dir_local(fd, rpath, lpath, 0)) { | 
 |             return 1; | 
 |         } else { | 
 |             END(); | 
 |             sync_quit(fd); | 
 |             return 0; | 
 |         } | 
 |     } else { | 
 |         fprintf(stderr,"remote object '%s' not a file or directory\n", rpath); | 
 |         return 1; | 
 |     } | 
 | } | 
 |  | 
 | int do_sync_sync(const char *lpath, const char *rpath, int listonly) | 
 | { | 
 |     fprintf(stderr,"syncing %s...\n",rpath); | 
 |  | 
 |     int fd = adb_connect("sync:"); | 
 |     if(fd < 0) { | 
 |         fprintf(stderr,"error: %s\n", adb_error()); | 
 |         return 1; | 
 |     } | 
 |  | 
 |     BEGIN(); | 
 |     if(copy_local_dir_remote(fd, lpath, rpath, 1, listonly)){ | 
 |         return 1; | 
 |     } else { | 
 |         END(); | 
 |         sync_quit(fd); | 
 |         return 0; | 
 |     } | 
 | } |