--- /dev/null
+=encoding utf8
+
+=head1 NAME
+
+libguestfs-test-tool - End user tests for libguestfs
+
+=head1 SYNOPSIS
+
+ libguestfs-test-tool [--options]
+
+=head1 DESCRIPTION
+
+libguestfs-test-tool is a test program shipped with libguestfs to end
+users and developers, to allow them to check basic libguestfs
+functionality is working. This is needed because libguestfs
+occasionally breaks for reasons beyond our control: usually because of
+changes in the underlying qemu or kernel packages, or the host
+environment.
+
+If you suspect a problem in libguestfs, then just run:
+
+ libguestfs-test-tool
+
+It will print lots of diagnostic messages.
+
+If it runs to completion successfully, you will see this near the end:
+
+ ===== TEST FINISHED OK =====
+
+and the test tool will exit with code 0.
+
+If it fails (and/or exits with non-zero error code), please paste the
+B<complete, unedited> output of the test tool into a bug report. More
+information about reporting bugs can be found on the
+L<http://libguestfs.org/> website.
+
+=head1 OPTIONS
+
+=over 4
+
+=item I<--help>
+
+Display short usage information and exit.
+
+=item I<--helper /path/to/libguestfs-test-tool-helper>
+
+Pass an alternate name for the helper program. libguestfs-test-tool
+will normally look in the C<$libexec> directory that was configured
+when the tool was built.
+
+=item I<--qemu qemu_binary>
+
+If you have downloaded another qemu binary, point this option at the
+full path of the binary to try it.
+
+=item I<--qemudir qemu_source_dir>
+
+If you have compiled qemu from source, point this option at the source
+directory to try it.
+
+=item I<--timeout N>
+
+Set the launch timeout to C<N> seconds. The default is 120 seconds
+which does not usually need to be adjusted unless your machine is very
+slow.
+
+=back
+
+=head1 TRYING OUT A DIFFERENT VERSION OF QEMU
+
+If you have compiled another version of qemu from source and would
+like to try that, then you can use the I<--qemudir> option to point to
+the qemu source directory.
+
+If you have downloaded a qemu binary from somewhere, use the I<--qemu>
+option to point to the binary.
+
+When using an alternate qemu with libguestfs, usually you would need
+to write a qemu wrapper script (see section I<QEMU WRAPPERS> in
+L<guestfs(3)>). libguestfs-test-tool writes a temporary qemu wrapper
+script when you use either of the I<--qemudir> or I<--qemu> options.
+
+=head1 EXIT CODE
+
+libguestfs-test-tool returns I<0> if the tests completed without
+error, or I<1> if there was an error.
+
+=head1 FILES
+
+=over 4
+
+=item /usr/libexec/libguestfs-test-tool-helper
+
+This helper program is run inside the appliance and provides
+additional tests.
+
+=item /usr/bin/mkisofs
+
+The C<mkisofs> command is required in order to construct a CD-ROM ISO
+file which is used as part of the tests.
+
+=back
+
+=head1 ENVIRONMENT VARIABLES
+
+For the full list of environment variables which may affect
+libguestfs, please see the L<guestfs(3)> manual page.
+
+=head1 SEE ALSO
+
+L<guestfs(3)>,
+L<http://libguestfs.org/>,
+L<http://qemu.org/>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones (C<rjones at redhat dot com>)
+
+=head1 COPYRIGHT
+
+Copyright (C) 2009 Red Hat Inc.
+L<http://libguestfs.org/>
+
+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.
--- /dev/null
+/* 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 <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
+
+#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[])
+{
+ static const char *options = "?";
+ static 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 (strcmp (long_options[option_index].name, "helper") == 0)
+ helper = optarg;
+ else if (strcmp (long_options[option_index].name, "qemu") == 0)
+ set_qemu (optarg, 0);
+ else if (strcmp (long_options[option_index].name, "qemudir") == 0)
+ set_qemu (optarg, 1);
+ else {
+ fprintf (stderr,
+ _("libguestfs-test-tool: unknown long option: %s (%d)\n"),
+ long_options[option_index].name, option_index);
+ exit (1);
+ }
+ break;
+
+ case 't':
+ if (sscanf (optarg, "%d", &timeout) != 1 || timeout < 0) {
+ fprintf (stderr,
+ _("libguestfs-test-tool: invalid timeout: %s\n"),
+ optarg);
+ exit (1);
+ }
+ break;
+
+ case '?':
+ usage ();
+ exit (0);
+
+ default:
+ fprintf (stderr,
+ _("libguestfs-test-tool: unexpected command line option 0x%x\n"),
+ c);
+ exit (1);
+ }
+ }
+
+ 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 (strncmp (environ[i], "LIBGUESTFS_", 11) == 0)
+ 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 (1);
+ }
+ if (guestfs_add_drive (g, tmpf) == -1) {
+ fprintf (stderr,
+ _("libguestfs-test-tool: failed to add drive '%s'\n"),
+ tmpf);
+ exit (1);
+ }
+ if (guestfs_add_drive (g, isof) == -1) {
+ fprintf (stderr,
+ _("libguestfs-test-tool: failed to add drive '%s'\n"),
+ isof);
+ exit (1);
+ }
+
+ /* Print any version info etc. */
+ vers = guestfs_version (g);
+ if (vers == NULL) {
+ fprintf (stderr, _("libguestfs-test-tool: guestfs_version failed\n"));
+ exit (1);
+ }
+ 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. */
+ if (guestfs_launch (g) == -1) {
+ fprintf (stderr,
+ _("libguestfs-test-tool: failed to launch appliance\n"));
+ exit (1);
+ }
+
+ printf ("Launching appliance, timeout set to %d seconds.\n", timeout);
+ fflush (stdout);
+
+ alarm (timeout);
+
+ if (guestfs_wait_ready (g) == -1) {
+ fprintf (stderr,
+ _("libguestfs-test-tool: failed or timed out in 'wait_ready'\n"));
+ exit (1);
+ }
+
+ 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 (1);
+ }
+
+ if (guestfs_mkfs (g, "ext2", "/dev/sda1") == -1) {
+ fprintf (stderr,
+ _("libguestfs-test-tool: failed to mkfs.ext2\n"));
+ exit (1);
+ }
+
+ if (guestfs_mount (g, "/dev/sda1", "/") == -1) {
+ fprintf (stderr,
+ _("libguestfs-test-tool: failed to mount /dev/sda1 on /\n"));
+ exit (1);
+ }
+
+ if (guestfs_mkdir (g, "/iso") == -1) {
+ fprintf (stderr,
+ _("libguestfs-test-tool: failed to mkdir /iso\n"));
+ exit (1);
+ }
+
+ if (guestfs_mount (g, "/dev/sdb", "/iso") == -1) {
+ fprintf (stderr,
+ _("libguestfs-test-tool: failed to mount /dev/sdb on /iso\n"));
+ exit (1);
+ }
+
+ /* 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 (1);
+ }
+ free (str);
+
+ printf ("===== TEST FINISHED OK =====\n");
+ exit (0);
+}
+
+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 (1);
+ }
+
+ if (!use_wrapper) {
+ if (access (path, X_OK) == -1) {
+ fprintf (stderr,
+ _("Binary '%s' does not exist or is not executable\n"),
+ path);
+ exit (1);
+ }
+
+ 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 (1);
+ }
+
+ /* Make a wrapper script. */
+ fd = mkstemp (qemuwrapper);
+ if (fd == -1) {
+ perror (qemuwrapper);
+ exit (1);
+ }
+
+ 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 (1);
+ }
+
+ snprintf (cmd, sizeof cmd, "file '%s'", helper);
+ fp = popen (cmd, "r");
+ if (fp == NULL) {
+ perror (cmd);
+ exit (1);
+ }
+ r = fread (buffer, 1, sizeof buffer - 1, fp);
+ if (r == 0) {
+ fprintf (stderr, _("command failed: %s"), cmd);
+ exit (1);
+ }
+ 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 (1);
+ }
+}
+
+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 (1);
+ }
+ 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 (1);
+ }
+
+ /* Allocate the sparse file for /dev/sda. */
+ fd = mkstemp (tmpf);
+ if (fd == -1) {
+ perror (tmpf);
+ unlink (isof);
+ exit (1);
+ }
+
+ if (lseek (fd, 100 * 1024 * 1024 - 1, SEEK_SET) == -1) {
+ perror ("lseek");
+ close (fd);
+ unlink (tmpf);
+ unlink (isof);
+ exit (1);
+ }
+
+ if (write (fd, "\0", 1) == -1) {
+ perror ("write");
+ close (fd);
+ unlink (tmpf);
+ unlink (isof);
+ exit (1);
+ }
+
+ close (fd);
+
+ atexit (cleanup_tmpfiles); /* Removes tmpf and isof. */
+}