/* libguestfs-test-tool
 * Copyright (C) 2009 Red Hat Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <locale.h>
#include <limits.h>

#include <guestfs.h>

#ifdef HAVE_GETTEXT
#include "gettext.h"
#define _(str) dgettext(PACKAGE, (str))
#define N_(str) dgettext(PACKAGE, (str))
#else
#define _(str) str
#define N_(str) str
#endif

#if !ENABLE_NLS
#undef textdomain
#define textdomain(Domainname) /* empty */
#undef bindtextdomain
#define bindtextdomain(Domainname, Dirname) /* empty */
#endif

#define STREQ(a,b) (strcmp((a),(b)) == 0)
#define STRCASEEQ(a,b) (strcasecmp((a),(b)) == 0)
#define STRNEQ(a,b) (strcmp((a),(b)) != 0)
#define STRCASENEQ(a,b) (strcasecmp((a),(b)) != 0)
#define STREQLEN(a,b,n) (strncmp((a),(b),(n)) == 0)
#define STRCASEEQLEN(a,b,n) (strncasecmp((a),(b),(n)) == 0)
#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0)
#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0)
#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)

#define DEFAULT_TIMEOUT 120

static const char *helper = DEFAULT_HELPER;
static int timeout = DEFAULT_TIMEOUT;
static char tmpf[] = "/tmp/libguestfs-test-tool-sda-XXXXXX";
static char isof[] = "/tmp/libguestfs-test-tool-iso-XXXXXX";
static guestfs_h *g;

static void preruncheck (void);
static void make_files (void);
static void set_qemu (const char *path, int use_wrapper);

static void
usage (void)
{
  printf (_("libguestfs-test-tool: interactive test tool\n"
            "Copyright (C) 2009 Red Hat Inc.\n"
            "Usage:\n"
            "  libguestfs-test-tool [--options]\n"
            "Options:\n"
            "  --help         Display usage\n"
            "  --helper libguestfs-test-tool-helper\n"
            "                 Helper program (default: %s)\n"
            "  --qemudir dir  Specify QEMU source directory\n"
            "  --qemu qemu    Specify QEMU binary\n"
            "  --timeout n\n"
            "  -t n           Set launch timeout (default: %d seconds)\n"
            ),
          DEFAULT_HELPER, DEFAULT_TIMEOUT);
}

int
main (int argc, char *argv[])
{
  setlocale (LC_ALL, "");
  bindtextdomain (PACKAGE, LOCALEBASEDIR);
  textdomain (PACKAGE);

  static const char *options = "t:?";
  static const struct option long_options[] = {
    { "help", 0, 0, '?' },
    { "helper", 1, 0, 0 },
    { "qemu", 1, 0, 0 },
    { "qemudir", 1, 0, 0 },
    { "timeout", 1, 0, 't' },
    { 0, 0, 0, 0 }
  };
  int c;
  int option_index;
  extern char **environ;
  int i;
  struct guestfs_version *vers;
  char *sfdisk_lines[] = { ",", NULL };
  char *str;
  /* XXX This is wrong if the user renames the helper. */
  char *helper_args[] = { "/iso/libguestfs-test-tool-helper", NULL };

  for (;;) {
    c = getopt_long (argc, argv, options, long_options, &option_index);
    if (c == -1) break;

    switch (c) {
    case 0:			/* options which are long only */
      if (STREQ (long_options[option_index].name, "helper"))
        helper = optarg;
      else if (STREQ (long_options[option_index].name, "qemu"))
        set_qemu (optarg, 0);
      else if (STREQ (long_options[option_index].name, "qemudir"))
        set_qemu (optarg, 1);
      else {
        fprintf (stderr,
                 _("libguestfs-test-tool: unknown long option: %s (%d)\n"),
                 long_options[option_index].name, option_index);
        exit (EXIT_FAILURE);
      }
      break;

    case 't':
      if (sscanf (optarg, "%d", &timeout) != 1 || timeout < 0) {
        fprintf (stderr,
                 _("libguestfs-test-tool: invalid timeout: %s\n"),
                 optarg);
        exit (EXIT_FAILURE);
      }
      break;

    case '?':
      usage ();
      exit (EXIT_SUCCESS);

    default:
      fprintf (stderr,
               _("libguestfs-test-tool: unexpected command line option 0x%x\n"),
               c);
      exit (EXIT_FAILURE);
    }
  }

  preruncheck ();
  make_files ();

  printf ("===== Test starts here =====\n");

  /* Must set LIBGUESTFS_DEBUG=1 */
  setenv ("LIBGUESTFS_DEBUG", "1", 1);

  /* Print out any environment variables which may relate to this test. */
  for (i = 0; environ[i] != NULL; ++i)
    if (STREQLEN (environ[i], "LIBGUESTFS_", 11))
      printf ("%s\n", environ[i]);

  /* Create the handle and configure it. */
  g = guestfs_create ();
  if (g == NULL) {
    fprintf (stderr,
             _("libguestfs-test-tool: failed to create libguestfs handle\n"));
    exit (EXIT_FAILURE);
  }
  if (guestfs_add_drive (g, tmpf) == -1) {
    fprintf (stderr,
             _("libguestfs-test-tool: failed to add drive '%s'\n"),
             tmpf);
    exit (EXIT_FAILURE);
  }
  if (guestfs_add_drive (g, isof) == -1) {
    fprintf (stderr,
             _("libguestfs-test-tool: failed to add drive '%s'\n"),
             isof);
    exit (EXIT_FAILURE);
  }

  /* Print any version info etc. */
  vers = guestfs_version (g);
  if (vers == NULL) {
    fprintf (stderr, _("libguestfs-test-tool: guestfs_version failed\n"));
    exit (EXIT_FAILURE);
  }
  printf ("library version: %"PRIi64".%"PRIi64".%"PRIi64"%s\n",
          vers->major, vers->minor, vers->release, vers->extra);
  guestfs_free_version (vers);

  printf ("guestfs_get_append: %s\n", guestfs_get_append (g) ? : "(null)");
  printf ("guestfs_get_autosync: %d\n", guestfs_get_autosync (g));
  printf ("guestfs_get_memsize: %d\n", guestfs_get_memsize (g));
  printf ("guestfs_get_path: %s\n", guestfs_get_path (g));
  printf ("guestfs_get_qemu: %s\n", guestfs_get_qemu (g));
  printf ("guestfs_get_verbose: %d\n", guestfs_get_verbose (g));

  /* Launch the guest handle. */
  printf ("Launching appliance, timeout set to %d seconds.\n", timeout);
  fflush (stdout);

  alarm (timeout);

  if (guestfs_launch (g) == -1) {
    fprintf (stderr,
             _("libguestfs-test-tool: failed to launch appliance\n"));
    exit (EXIT_FAILURE);
  }

  alarm (0);

  printf ("Guest launched OK.\n");
  fflush (stdout);

  /* Create the filesystem and mount everything. */
  if (guestfs_sfdiskM (g, "/dev/sda", sfdisk_lines) == -1) {
    fprintf (stderr,
             _("libguestfs-test-tool: failed to run sfdisk\n"));
    exit (EXIT_FAILURE);
  }

  if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1) {
    fprintf (stderr,
             _("libguestfs-test-tool: failed to mkfs.ext2\n"));
    exit (EXIT_FAILURE);
  }

  if (guestfs_mount_options (g, "", "/dev/sda1", "/") == -1) {
    fprintf (stderr,
             _("libguestfs-test-tool: failed to mount /dev/sda1 on /\n"));
    exit (EXIT_FAILURE);
  }

  if (guestfs_mkdir (g, "/iso") == -1) {
    fprintf (stderr,
             _("libguestfs-test-tool: failed to mkdir /iso\n"));
    exit (EXIT_FAILURE);
  }

  if (guestfs_mount (g, "/dev/sdb", "/iso") == -1) {
    fprintf (stderr,
             _("libguestfs-test-tool: failed to mount /dev/sdb on /iso\n"));
    exit (EXIT_FAILURE);
  }

  /* Let's now run some simple tests using the helper program. */
  str = guestfs_command (g, helper_args);
  if (str == NULL) {
    fprintf (stderr,
             _("libguestfs-test-tool: could not run helper program, or helper failed\n"));
    exit (EXIT_FAILURE);
  }
  free (str);

  printf ("===== TEST FINISHED OK =====\n");
  exit (EXIT_SUCCESS);
}

static char qemuwrapper[] = "/tmp/libguestfs-test-tool-wrapper-XXXXXX";

static void
cleanup_wrapper (void)
{
  unlink (qemuwrapper);
}

/* Handle the --qemu and --qemudir parameters.  use_wrapper is true
 * in the --qemudir (source directory) case, where we have to create
 * a wrapper shell script.
 */
static void
set_qemu (const char *path, int use_wrapper)
{
  char buffer[PATH_MAX];
  struct stat statbuf;
  int fd;
  FILE *fp;

  if (getenv ("LIBGUESTFS_QEMU")) {
    fprintf (stderr,
    _("LIBGUESTFS_QEMU environment variable is already set, so\n"
      "--qemu/--qemudir options cannot be used.\n"));
    exit (EXIT_FAILURE);
  }

  if (!use_wrapper) {
    if (access (path, X_OK) == -1) {
      fprintf (stderr,
               _("Binary '%s' does not exist or is not executable\n"),
               path);
      exit (EXIT_FAILURE);
    }

    setenv ("LIBGUESTFS_QEMU", path, 1);
    return;
  }

  /* This should be a source directory, so check it. */
  snprintf (buffer, sizeof buffer, "%s/pc-bios", path);
  if (stat (buffer, &statbuf) == -1 ||
      !S_ISDIR (statbuf.st_mode)) {
    fprintf (stderr,
             _("%s: does not look like a qemu source directory\n"),
             path);
    exit (EXIT_FAILURE);
  }

  /* Make a wrapper script. */
  fd = mkstemp (qemuwrapper);
  if (fd == -1) {
    perror (qemuwrapper);
    exit (EXIT_FAILURE);
  }

  fchmod (fd, 0700);

  fp = fdopen (fd, "w");
  fprintf (fp,
           "#!/bin/sh -\n"
           "qemudir='%s'\n"
           "\"$qemudir\"/",
           path);

  /* Select the right qemu binary for the wrapper script. */
#ifdef __i386__
  fprintf (fp, "i386-softmmu/qemu");
#else
  fprintf (fp, host_cpu "-softmmu/qemu-system-" host_cpu);
#endif

  fprintf (fp, " -L \"$qemudir\"/pc-bios \"$@\"\n");

  fclose (fp);

  setenv ("LIBGUESTFS_QEMU", qemuwrapper, 1);
  atexit (cleanup_wrapper);
}

/* After getting the command line args, but before running
 * anything, we check everything is in place to do the tests.
 */
static void
preruncheck (void)
{
  int r;
  FILE *fp;
  char cmd[256];
  char buffer[1024];

  if (access (helper, R_OK) == -1) {
    fprintf (stderr,
    _("Test tool helper program 'libguestfs-test-tool-helper' is not\n"
      "available.  Expected to find it in '%s'\n"
      "\n"
      "Use the --helper option to specify the location of this program.\n"),
             helper);
    exit (EXIT_FAILURE);
  }

  snprintf (cmd, sizeof cmd, "file '%s'", helper);
  fp = popen (cmd, "r");
  if (fp == NULL) {
    perror (cmd);
    exit (EXIT_FAILURE);
  }
  r = fread (buffer, 1, sizeof buffer - 1, fp);
  if (r == 0) {
    fprintf (stderr, _("command failed: %s"), cmd);
    exit (EXIT_FAILURE);
  }
  pclose (fp);
  buffer[r] = '\0';

  if (strstr (buffer, "statically linked") == NULL) {
    fprintf (stderr,
    _("Test tool helper program %s\n"
      "is not statically linked.  This is a build error when this test tool\n"
      "was built.\n"),
             helper);
    exit (EXIT_FAILURE);
  }
}

static void
cleanup_tmpfiles (void)
{
  unlink (tmpf);
  unlink (isof);
}

static void
make_files (void)
{
  int fd, r;
  char cmd[256];

  /* Make the ISO which will contain the helper program. */
  fd = mkstemp (isof);
  if (fd == -1) {
    perror (isof);
    exit (EXIT_FAILURE);
  }
  close (fd);

  snprintf (cmd, sizeof cmd, "mkisofs -quiet -rJT -o '%s' '%s'",
            isof, helper);
  r = system (cmd);
  if (r == -1 || WEXITSTATUS(r) != 0) {
    fprintf (stderr,
             _("mkisofs command failed: %s\n"), cmd);
    exit (EXIT_FAILURE);
  }

  /* Allocate the sparse file for /dev/sda. */
  fd = mkstemp (tmpf);
  if (fd == -1) {
    perror (tmpf);
    unlink (isof);
    exit (EXIT_FAILURE);
  }

  if (lseek (fd, 100 * 1024 * 1024 - 1, SEEK_SET) == -1) {
    perror ("lseek");
    close (fd);
    unlink (tmpf);
    unlink (isof);
    exit (EXIT_FAILURE);
  }

  if (write (fd, "\0", 1) == -1) {
    perror ("write");
    close (fd);
    unlink (tmpf);
    unlink (isof);
    exit (EXIT_FAILURE);
  }

  close (fd);

  atexit (cleanup_tmpfiles);	/* Removes tmpf and isof. */
}