/* Copy a directory from one libvirt guest to another.
 *
 * This is a more substantial example of using the libguestfs API,
 * demonstrating amongst other things:
 *
 * - using multiple handles with threads
 * - upload and downloading (using a pipe between handles)
 * - inspection
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>

#include <pthread.h>

#include <guestfs.h>

struct threaddata {
  const char *src;
  const char *srcdir;
  int fd;
  pthread_t mainthread;
};

static void *start_srcthread (void *);
static int open_guest (guestfs_h *g, const char *dom, int readonly);
static int64_t timeval_diff (const struct timeval *x, const struct timeval *y);
static int compare_keys_len (const void *p1, const void *p2);
static size_t count_strings (char *const *argv);

static void
usage (void)
{
  fprintf (stderr,
	   "Usage: copy-over source srcdir dest destdir\n"
	   "\n"
	   "  source  : the source domain (a libvirt guest name)\n"
	   "  srcdir  : the directory to copy from the source guest\n"
	   "  dest    : the destination domain (a libvirt guest name)\n"
	   "  destdir : the destination directory (must exist at destination)\n"
	   "\n"
	   "eg: copy-over Src /home/rjones Dest /tmp/dir\n"
	   "would copy /home/rjones from Src to /tmp/dir on Dest\n"
	   "\n"
	   "The destination guest cannot be running.\n");
}

int
main (int argc, char *argv[])
{
  const char *src, *srcdir, *dest, *destdir;
  guestfs_h *destg;
  int fd[2];
  pthread_t srcthread;
  struct threaddata threaddata;
  int err;
  char fdname[128];
  struct timeval start_t, end_t;
  int64_t ms;

  if (argc != 5) {
    usage ();
    exit (EXIT_FAILURE);
  }

  src = argv[1];
  srcdir = argv[2];
  dest = argv[3];
  destdir = argv[4];

  /* Instead of downloading to local disk and uploading, we are going
   * to connect the source download and destination upload using a
   * pipe.  Create that pipe.
   */
  if (pipe (fd) == -1) {
    perror ("pipe");
    exit (EXIT_FAILURE);
  }

  /* We don't want the pipe to be passed to subprocesses. */
  if (fcntl (fd[0], F_SETFD, FD_CLOEXEC) == -1 ||
      fcntl (fd[1], F_SETFD, FD_CLOEXEC) == -1) {
    perror ("fcntl");
    exit (EXIT_FAILURE);
  }

  /* The libguestfs API is synchronous, so if we want to use two
   * handles concurrently, then we have to have two threads.  In this
   * case the main thread (this one) is handling the destination
   * domain (uploading), and we create one more thread to handle the
   * source domain (downloading).
   */
  threaddata.src = src;
  threaddata.srcdir = srcdir;
  threaddata.fd = fd[1];
  threaddata.mainthread = pthread_self ();
  err = pthread_create (&srcthread, NULL, start_srcthread, &threaddata);
  if (err != 0) {
    fprintf (stderr, "pthread_create: %s\n", strerror (err));
    exit (EXIT_FAILURE);
  }

  /* Open the destination domain. */
  destg = guestfs_create ();
  if (!destg) {
    perror ("failed to create libguestfs handle");
    pthread_cancel (srcthread);
    exit (EXIT_FAILURE);
  }
  if (open_guest (destg, dest, 0) == -1) {
    pthread_cancel (srcthread);
    exit (EXIT_FAILURE);
  }

  gettimeofday (&start_t, NULL);

  /* Begin the upload. */
  snprintf (fdname, sizeof fdname, "/dev/fd/%d", fd[0]);
  if (guestfs_tar_in (destg, fdname, destdir) == -1) {
    pthread_cancel (srcthread);
    exit (EXIT_FAILURE);
  }

  /* Close our end of the pipe.  The other thread will close the
   * other side of the pipe.
   */
  close (fd[0]);

  /* Wait for the other thread to finish. */
  err = pthread_join (srcthread, NULL);
  if (err != 0) {
    fprintf (stderr, "pthread_join: %s\n", strerror (err));
    exit (EXIT_FAILURE);
  }

  /* Clean up. */
  if (guestfs_shutdown (destg) == -1)
    exit (EXIT_FAILURE);
  guestfs_close (destg);

  gettimeofday (&end_t, NULL);

  /* Print the elapsed time. */
  ms = timeval_diff (&start_t, &end_t);
  printf ("copy finished, elapsed time (excluding launch) was "
          "%" PRIi64 ".%03" PRIi64 " s\n",
          ms / 1000, ms % 1000);

  exit (EXIT_SUCCESS);
}

static void *
start_srcthread (void *arg)
{
  struct threaddata *threaddata = arg;
  guestfs_h *srcg;
  char fdname[128];

  /* Open the source domain. */
  srcg = guestfs_create ();
  if (!srcg) {
    perror ("failed to create libguestfs handle");
    pthread_cancel (threaddata->mainthread);
    exit (EXIT_FAILURE);
  }
  if (open_guest (srcg, threaddata->src, 1) == -1) {
    pthread_cancel (threaddata->mainthread);
    exit (EXIT_FAILURE);
  }

  /* Begin the download. */
  snprintf (fdname, sizeof fdname, "/dev/fd/%d", threaddata->fd);
  if (guestfs_tar_out (srcg, threaddata->srcdir, fdname) == -1) {
    pthread_cancel (threaddata->mainthread);
    exit (EXIT_FAILURE);
  }

  /* Close the pipe; this will cause the receiver to finish the upload. */
  if (close (threaddata->fd) == -1) {
    pthread_cancel (threaddata->mainthread);
    exit (EXIT_FAILURE);
  }

  /* Clean up. */
  guestfs_close (srcg);

  return NULL;
}

/* This function deals with the complexity of adding the domain,
 * launching the handle, and mounting up filesystems.  See
 * 'examples/inspect-vm.c' to understand how this works.
 */
static int
open_guest (guestfs_h *g, const char *dom, int readonly)
{
  char **roots, *root, **mountpoints;
  size_t i;

  /* Use libvirt to find the guest disks and add them to the handle. */
  if (guestfs_add_domain (g, dom,
                          GUESTFS_ADD_DOMAIN_READONLY, readonly,
                          -1) == -1)
    return -1;

  if (guestfs_launch (g) == -1)
    return -1;

  /* Inspect the guest, looking for operating systems. */
  roots = guestfs_inspect_os (g);
  if (roots == NULL)
    return -1;

  if (roots[0] == NULL || roots[1] != NULL) {
    fprintf (stderr, "copy-over: %s: no operating systems or multiple operating systems found\n", dom);
    return -1;
  }

  root = roots[0];

  /* Mount up the filesystems (like 'guestfish -i'). */
  mountpoints = guestfs_inspect_get_mountpoints (g, root);
  if (mountpoints == NULL)
    return -1;

  qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
         compare_keys_len);
  for (i = 0; mountpoints[i] != NULL; i += 2) {
    /* Ignore failures from this call, since bogus entries can
     * appear in the guest's /etc/fstab.
     */
    (readonly ? guestfs_mount_ro : guestfs_mount)
      (g, mountpoints[i+1], mountpoints[i]);
    free (mountpoints[i]);
    free (mountpoints[i+1]);
  }

  free (mountpoints);

  free (root);
  free (roots);

  /* Everything ready, no error. */
  return 0;
}

/* Compute Y - X and return the result in milliseconds.
 * Approximately the same as this code:
 * http://www.mpp.mpg.de/~huber/util/timevaldiff.c
 */
static int64_t
timeval_diff (const struct timeval *x, const struct timeval *y)
{
  int64_t msec;

  msec = (y->tv_sec - x->tv_sec) * 1000;
  msec += (y->tv_usec - x->tv_usec) / 1000;
  return msec;
}

static int
compare_keys_len (const void *p1, const void *p2)
{
  const char *key1 = * (char * const *) p1;
  const char *key2 = * (char * const *) p2;
  return strlen (key1) - strlen (key2);
}

static size_t
count_strings (char *const *argv)
{
  size_t c;

  for (c = 0; argv[c]; ++c)
    ;
  return c;
}
