text: /*
   * 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 long long 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",
              ((total_bytes * 1000000LL) / t) / 1024LL,
              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, &timestamp, &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, &timestamp, &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;
      }
  }
  