[Libguestfs] [PATCH v7 NOT TO BE APPLIED 2/2] New tool: virt-p2v.

Richard W.M. Jones rjones at redhat.com
Fri May 2 16:32:10 UTC 2014


This is a graphical standalone front-end to virt-v2v which can be run
on physical machines (usually linked into a ISO or PXE boot image) to
convert the physical machine to a virtual machine.
---
 .gitignore         |    4 +
 Makefile.am        |    3 +
 README             |    2 +
 configure.ac       |   16 +
 fish/guestfish.pod |    1 +
 p2v/Makefile.am    |   91 +++++
 p2v/authors.c      |   30 ++
 p2v/config.c       |   89 +++++
 p2v/conversion.c   |  336 +++++++++++++++++
 p2v/copying.c      |   38 ++
 p2v/gui.c          | 1030 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 p2v/kernel.c       |  180 +++++++++
 p2v/main.c         |  457 +++++++++++++++++++++++
 p2v/miniexpect.c   |  364 +++++++++++++++++++
 p2v/miniexpect.h   |   81 +++++
 p2v/p2v.h          |  100 +++++
 p2v/ssh.c          |  427 ++++++++++++++++++++++
 p2v/virt-p2v.pod   |  219 +++++++++++
 po/POTFILES        |    9 +
 src/guestfs.pod    |    5 +
 v2v/virt-v2v.pod   |    5 +-
 21 files changed, 3485 insertions(+), 2 deletions(-)
 create mode 100644 p2v/Makefile.am
 create mode 100644 p2v/authors.c
 create mode 100644 p2v/config.c
 create mode 100644 p2v/conversion.c
 create mode 100644 p2v/copying.c
 create mode 100644 p2v/gui.c
 create mode 100644 p2v/kernel.c
 create mode 100644 p2v/main.c
 create mode 100644 p2v/miniexpect.c
 create mode 100644 p2v/miniexpect.h
 create mode 100644 p2v/p2v.h
 create mode 100644 p2v/ssh.c
 create mode 100644 p2v/virt-p2v.pod

diff --git a/.gitignore b/.gitignore
index 25e9358..f97318b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -245,6 +245,7 @@ Makefile.in
 /html/virt-list-partitions.1.html
 /html/virt-ls.1.html
 /html/virt-make-fs.1.html
+/html/virt-p2v.1.html
 /html/virt-rescue.1.html
 /html/virt-resize.1.html
 /html/virt-sparsify.1.html
@@ -314,6 +315,9 @@ Makefile.in
 /ocaml/stamp-mlguestfs
 /ocaml/t/*.bc
 /ocaml/t/*.opt
+/p2v/stamp-virt-p2v.pod
+/p2v/virt-p2v
+/p2v/virt-p2v.1
 /perl/bindtests.pl
 /perl/blib
 /perl/examples/guestfs-perl.3
diff --git a/Makefile.am b/Makefile.am
index 3102e0b..67b1fa8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -81,6 +81,9 @@ SUBDIRS += fish
 
 # virt-tools in C.
 SUBDIRS += align cat diff df edit format inspector make-fs rescue
+if HAVE_P2V
+SUBDIRS += p2v
+endif
 
 # bash-completion
 SUBDIRS += bash
diff --git a/README b/README
index 8fdc041..2d8acfd 100644
--- a/README
+++ b/README
@@ -176,6 +176,8 @@ The full requirements are described below.
 | liblzma      |             | O | Can be used by virt-builder for fast    |
 |              |             |   | uncompression of templates.             |
 +--------------+-------------+---+-----------------------------------------+
+| gtk2         |             | O | Used by virt-p2v user interface.        |
++--------------+-------------+---+-----------------------------------------+
 | findlib      |             | O | For the OCaml bindings.                 |
 +--------------+-------------+---+-----------------------------------------+
 | ocaml-gettext|             | O | For localizing OCaml virt-* tools.      |
diff --git a/configure.ac b/configure.ac
index b4e4dad..d06f1dc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -118,6 +118,9 @@ gl_INIT
 AC_PROG_LIBTOOL
 AC_PROG_LN_S
 
+dnl Define the host CPU architecture (defines 'host_cpu')
+AC_CANONICAL_HOST
+
 # Define $(SED).
 m4_ifdef([AC_PROG_SED],[
     AC_PROG_SED
@@ -933,6 +936,16 @@ PKG_CHECK_MODULES([LIBCONFIG], [libconfig],[
     [AC_MSG_WARN([libconfig not found, some features will be disabled])])
 AM_CONDITIONAL([HAVE_LIBCONFIG],[test "x$LIBCONFIG_LIBS" != "x"])
 
+dnl Check for gtk2 library, used by virt-p2v.
+PKG_CHECK_MODULES([GTK2], [gtk+-2.0], [
+    AC_SUBST([GTK2_CFLAGS])
+    AC_SUBST([GTK2_LIBS])
+],
+    [AC_MSG_WARN([gtk2 not found, virt-p2v will be disabled])])
+
+dnl Can we build virt-p2v?
+AM_CONDITIONAL([HAVE_P2V], [test "x$GTK2_LIBS" != "x"])
+
 dnl hivex library (highly recommended)
 dnl This used to be a part of libguestfs, but was spun off into its
 dnl own separate upstream project in libguestfs 1.0.85.
@@ -1645,6 +1658,7 @@ AC_CONFIG_FILES([Makefile
                  ocaml/META
                  ocaml/Makefile
                  ocaml/examples/Makefile
+                 p2v/Makefile
                  perl/Makefile
                  perl/Makefile.PL
                  perl/examples/Makefile
@@ -1723,6 +1737,8 @@ echo       "guestfish and C-based virt tools .... yes"
 echo       "FUSE filesystem ..................... $enable_fuse"
 AS_ECHO_N(["GNU gettext for i18n ................ "])
 if test "x$HAVE_GNU_GETTEXT_TRUE" = "x"; then echo "yes"; else echo "no"; fi
+AS_ECHO_N(["virt-p2v ............................ "])
+if test "x$HAVE_P2V_TRUE" = "x"; then echo "yes"; else echo "no"; fi
 AS_ECHO_N(["OCaml bindings ...................... "])
 if test "x$HAVE_OCAML_TRUE" = "x"; then echo "yes"; else echo "no"; fi
 AS_ECHO_N(["OCaml-based virt tools .............. "])
diff --git a/fish/guestfish.pod b/fish/guestfish.pod
index 5cf6ebc..cf52f86 100644
--- a/fish/guestfish.pod
+++ b/fish/guestfish.pod
@@ -1617,6 +1617,7 @@ L<virt-list-filesystems(1)>,
 L<virt-list-partitions(1)>,
 L<virt-ls(1)>,
 L<virt-make-fs(1)>,
+L<virt-p2v(1)>,
 L<virt-rescue(1)>,
 L<virt-resize(1)>,
 L<virt-sparsify(1)>,
diff --git a/p2v/Makefile.am b/p2v/Makefile.am
new file mode 100644
index 0000000..e57b571
--- /dev/null
+++ b/p2v/Makefile.am
@@ -0,0 +1,91 @@
+# libguestfs virt-p2v
+# Copyright (C) 2009-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+include $(top_srcdir)/subdir-rules.mk
+
+EXTRA_DIST = \
+	virt-p2v.pod
+
+CLEANFILES = stamp-virt-p2v.pod
+
+# Although virt-p2v is a regular binary, it is not usually installed
+# since it only functions when contained in an ISO or PXE image which
+# is used to boot the physical machine (since otherwise virt-p2v would
+# not be able to get a consistent snapshot of the physical disks).
+noinst_PROGRAMS = virt-p2v
+
+# Note that miniexpect comes from here:
+# http://git.annexia.org/?p=miniexpect.git;a=summary
+virt_p2v_SOURCES = \
+	authors.c \
+	config.c \
+	conversion.c \
+	copying.c \
+	gui.c \
+	kernel.c \
+	main.c \
+	miniexpect.c \
+	miniexpect.h \
+	p2v.h \
+	ssh.c
+
+virt_p2v_CPPFLAGS = \
+	-DLOCALEBASEDIR=\""$(datadir)/locale"\" \
+	-I$(top_srcdir)/src -I$(top_builddir)/src \
+	-I$(srcdir)/../gnulib/lib -I../gnulib/lib
+
+virt_p2v_CFLAGS = \
+	-pthread \
+	$(WARN_CFLAGS) $(WERROR_CFLAGS) \
+	$(PCRE_CFLAGS) \
+	$(LIBXML2_CFLAGS) \
+	$(GTK2_CFLAGS)
+
+virt_p2v_LDADD = \
+	$(PCRE_LIBS) \
+	$(LIBXML2_LIBS) \
+	$(GTK2_LIBS) \
+	$(top_builddir)/src/libutils.la \
+	../gnulib/lib/libgnu.la
+
+# Manual pages and HTML files for the website.
+man_MANS = virt-p2v.1
+
+noinst_DATA = \
+	$(top_builddir)/html/virt-p2v.1.html
+
+virt-p2v.1 $(top_builddir)/html/virt-p2v.1.html: stamp-virt-p2v.pod
+
+stamp-virt-p2v.pod: virt-p2v.pod
+	$(PODWRAPPER) \
+	  --man virt-p2v.1 \
+	  --html $(top_builddir)/html/virt-p2v.1.html \
+	  --license GPLv2+ \
+	  $<
+	touch $@
+
+# Tests.
+
+TESTS_ENVIRONMENT = $(top_builddir)/run --test
+
+#if ENABLE_APPLIANCE
+#TESTS = \
+#	test-virt-p2v.sh
+#endif ENABLE_APPLIANCE
+#
+#check-valgrind:
+#	$(MAKE) VG="$(top_builddir)/run @VG@" check
diff --git a/p2v/authors.c b/p2v/authors.c
new file mode 100644
index 0000000..40f2777
--- /dev/null
+++ b/p2v/authors.c
@@ -0,0 +1,30 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include "p2v.h"
+
+/* The list of authors of virt-p2v and virt-v2v, for the About dialog. */
+
+const char *authors[] = {
+  "Matthew Booth",
+  "Richard W.M. Jones",
+  "Mike Latimer",
+  NULL
+};
diff --git a/p2v/config.c b/p2v/config.c
new file mode 100644
index 0000000..dafb687
--- /dev/null
+++ b/p2v/config.c
@@ -0,0 +1,89 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <locale.h>
+#include <libintl.h>
+
+#include "p2v.h"
+
+struct config *
+new_config (void)
+{
+  struct config *c;
+
+  c = calloc (1, sizeof *c);
+  if (c == NULL) {
+    perror ("calloc");
+    exit (EXIT_FAILURE);
+  }
+
+#if FORCE_REMOTE_DEBUG
+  c->verbose = 1;
+#endif
+  c->port = 22;
+
+  return c;
+}
+
+struct config *
+copy_config (struct config *old)
+{
+  struct config *c = new_config ();
+
+  memcpy (c, old, sizeof *c);
+
+  /* Need to deep copy strings and string lists. */
+  if (c->server)
+    c->server = strdup (c->server);
+  if (c->username)
+    c->username = strdup (c->username);
+  if (c->password)
+    c->password = strdup (c->password);
+  if (c->guestname)
+    c->guestname = strdup (c->guestname);
+  if (c->disks)
+    c->disks = guestfs___copy_string_list (c->disks);
+  if (c->removable)
+    c->removable = guestfs___copy_string_list (c->removable);
+  if (c->interfaces)
+    c->interfaces = guestfs___copy_string_list (c->interfaces);
+
+  return c;
+}
+
+void
+free_config (struct config *c)
+{
+  free (c->server);
+  free (c->username);
+  free (c->password);
+  free (c->guestname);
+  guestfs___free_string_list (c->disks);
+  guestfs___free_string_list (c->removable);
+  guestfs___free_string_list (c->interfaces);
+  free (c);
+}
diff --git a/p2v/conversion.c b/p2v/conversion.c
new file mode 100644
index 0000000..485dd05
--- /dev/null
+++ b/p2v/conversion.c
@@ -0,0 +1,336 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <errno.h>
+#include <locale.h>
+#include <assert.h>
+#include <libintl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <glib.h>
+
+#include "p2v.h"
+
+static pid_t start_qemu_nbd (int port, const char *device);
+static void cleanup_nbd_pids (pid_t *, size_t nr_pids);
+static char *generate_libvirt_xml (struct config *, int *);
+static void debug_parameters (struct config *);
+
+static char *conversion_error;
+
+static void set_conversion_error (const char *fs, ...)
+  __attribute__((format(printf,1,2)));
+
+static void
+set_conversion_error (const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+  int len;
+
+  va_start (args, fs);
+  len = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (len < 0) {
+    perror ("vasprintf");
+    fprintf (stderr, "original error format string: %s\n", fs);
+    exit (EXIT_FAILURE);
+  }
+
+  free (conversion_error);
+  conversion_error = msg;
+}
+
+const char *
+get_conversion_error (void)
+{
+  return conversion_error;
+}
+
+#pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn"
+int
+start_conversion (struct config *config,
+                  void (*notify_ui) (int type, const char *data))
+{
+  size_t i;
+  size_t nr_disks = guestfs___count_strings (config->disks);
+  pid_t nbd_pids[nr_disks];
+  int nbd_ports[nr_disks];
+  CLEANUP_FREE char *remote_dir = NULL, *libvirt_xml = NULL;
+
+  debug_parameters (config);
+
+  /* Start the data connections and qemu-nbd processes, one per disk. */
+  for (i = 0; config->disks[i] != NULL; ++i) {
+    int port;
+    CLEANUP_FREE char *device = NULL;
+
+    port = open_data_connection (config);
+    if (port == -1) {
+      const char *err = get_ssh_error ();
+
+      set_conversion_error ("could not open data connection over SSH to the conversion server: %s", err);
+      cleanup_nbd_pids (nbd_pids, i);
+      return -1;
+    }
+    nbd_ports[i] = port;
+
+    if (asprintf (&device, "/dev/%s", config->disks[i]) == -1) {
+      perror ("asprintf");
+      cleanup_nbd_pids (nbd_pids, i);
+      exit (EXIT_FAILURE);
+    }
+
+    /* Start qemu-nbd listening on the given port number. */
+    nbd_pids[i] = start_qemu_nbd (port, device);
+    if (nbd_pids[i] == -1) {
+      cleanup_nbd_pids (nbd_pids, i);
+      return -1;
+    }
+  }
+
+  /* Create a remote directory name which will be used for libvirt
+   * XML, log files and other stuff.  We don't delete this directory
+   * after the run because (a) it's useful for debugging and (b) it
+   * only contains small files.
+   */
+  remote_dir = strdup("/tmp/virt-p2v-XXXXXXXX");
+  guestfs___random_string (&remote_dir[14], 8);
+  if (notify_ui)
+    notify_ui (NOTIFY_LOG_DIR, remote_dir);
+
+  /* Generate the libvirt XML. */
+  libvirt_xml = generate_libvirt_xml (config, nbd_ports);
+  if (libvirt_xml == NULL) {
+    cleanup_nbd_pids (nbd_pids, nr_disks);
+    return -1;
+  }
+
+  /* Begin the control connection. */
+  if (start_remote_connection (config, remote_dir, libvirt_xml) == -1) {
+    const char *err = get_ssh_error ();
+
+    set_conversion_error ("could not open control connection over SSH to the conversion server: %s", err);
+    cleanup_nbd_pids (nbd_pids, nr_disks);
+    return -1;
+  }
+
+  cleanup_nbd_pids (nbd_pids, nr_disks);
+  return 0;
+}
+
+static pid_t
+start_qemu_nbd (int port, const char *device)
+{
+  pid_t pid;
+  char port_str[64];
+
+  snprintf (port_str, sizeof port_str, "%d", port);
+
+  pid = fork ();
+  if (pid == -1) {
+    set_conversion_error ("fork: %m");
+    return -1;
+  }
+
+  if (pid == 0) {               /* Child. */
+    close (0);
+    open ("/dev/null", O_RDONLY);
+
+    execlp ("qemu-nbd",
+            "qemu-nbd", "-p", port_str, "-r", "-t", "-f", "raw",
+            "-b", "localhost", "-n", "unsafe", device, NULL);
+    perror ("qemu-nbd");
+    _exit (EXIT_FAILURE);
+  }
+
+  /* Parent. */
+  return pid;
+}
+
+static void
+cleanup_nbd_pids (pid_t *pids, size_t nr_pids)
+{
+  size_t i;
+
+  for (i = 0; i < nr_pids; ++i) {
+    assert (pids[i] >= 2);
+
+    kill (pids[i], SIGTERM);
+    waitpid (pids[i], NULL, 0);
+  }
+}
+
+/* Write the libvirt XML for this physical machine.  Note this is not
+ * actually input for libvirt.  It's input for virt-v2v on the
+ * conversion server, and virt-v2v will (if necessary) generate the
+ * final libvirt XML.
+ */
+static char *
+generate_libvirt_xml (struct config *config, int *nbd_ports)
+{
+  uint64_t memkb;
+  FILE *fp;
+  char *ret = NULL;
+  size_t len = 0;
+  size_t i;
+
+  fp = open_memstream (&ret, &len);
+  if (fp == NULL) {
+    set_conversion_error ("open_memstream: %m");
+    return NULL;
+  }
+
+  memkb = config->memory / 1024;
+
+  /* XXX quoting needs to be improved here XXX */
+  fprintf (fp,
+           "<domain>\n"
+           "  <name>%s</name>\n"
+           "  <memory unit='KiB'>%" PRIu64 "</memory>\n"
+           "  <currentMemory unit='KiB'>%" PRIu64 "</currentMemory>\n"
+           "  <vcpu>%d</vcpu>\n"
+           "  <os>\n"
+           "    <type arch='" host_cpu "'>hvm</type>\n"
+           "  </os>\n"
+           "  <devices>\n",
+           config->guestname,
+           memkb, memkb,
+           config->vcpus);
+
+  /* XXX features: acpi, apic, pae */
+
+  for (i = 0; config->disks[i] != NULL; ++i) {
+    int port = nbd_ports[i];
+
+    fprintf (fp,
+             "    <disk type='network' device='disk'>\n"
+             "      <driver name='qemu' type='raw'/>\n"
+             "      <source protocol='nbd'>\n"
+             "        <host name='localhost' port='%d'/>\n"
+             "      </source>\n"
+             "      <target dev='%s'/>\n"
+             "    </disk>\n",
+             port, config->disks[i]);
+  }
+
+  if (config->removable) {
+    for (i = 0; config->removable[i] != NULL; ++i) {
+      fprintf (fp,
+               "    <disk type='network' device='cdrom'>\n"
+               "      <driver name='qemu' type='raw'/>\n"
+               "      <target dev='%s'/>\n"
+               "    </disk>\n",
+               config->removable[i]);
+    }
+  }
+
+  if (config->interfaces) {
+    for (i = 0; config->interfaces[i] != NULL; ++i) {
+      CLEANUP_FREE char *mac_filename = NULL;
+      CLEANUP_FREE char *mac = NULL;
+
+      if (asprintf (&mac_filename, "/sys/class/net/%s/address",
+                    config->interfaces[i]) == -1) {
+        perror ("asprintf");
+        exit (EXIT_FAILURE);
+      }
+      if (g_file_get_contents (mac_filename, &mac, NULL, NULL)) {
+        size_t len = strlen (mac);
+
+        if (len > 0 && mac[len-1] == '\n')
+          mac[len-1] = '\0';
+      }
+
+      fprintf (fp,
+               "    <interface type='network'>\n"
+               "      <source network='default'/>\n"
+               "      <target dev='%s'/>\n",
+               config->interfaces[i]);
+      if (mac)
+        fprintf (fp, "      <mac address='%s'/>\n", mac);
+      fprintf (fp,
+               "    </interface>\n");
+    }
+  }
+
+  fprintf (fp,
+           "  </devices>\n"
+           "</domain>\n");
+  fclose (fp);
+
+  return ret;
+}
+
+static void
+debug_parameters (struct config *config)
+{
+#if DEBUG_STDERR
+  size_t i;
+
+  /* Print the conversion parameters and other important information. */
+  fprintf (stderr, "local version   .  %s\n", PACKAGE_VERSION);
+  fprintf (stderr, "remote version  .  %d.%d.%d\n",
+           v2v_major, v2v_minor, v2v_release);
+  fprintf (stderr, "remote debugging   %s\n",
+           config->verbose ? "true" : "false");
+  fprintf (stderr, "conversion server  %s\n",
+           config->server ? config->server : "none");
+  fprintf (stderr, "port . . . . . .   %d\n", config->port);
+  fprintf (stderr, "username . . . .   %s\n",
+           config->username ? config->username : "none");
+  fprintf (stderr, "password . . . .   %s\n",
+           config->password && strlen (config->password) > 0 ? "***" : "none");
+  fprintf (stderr, "sudo . . . . . .   %s\n",
+           config->sudo ? "true" : "false");
+  fprintf (stderr, "guest name . . .   %s\n",
+           config->guestname ? config->guestname : "none");
+  fprintf (stderr, "vcpus  . . . . .   %d\n", config->vcpus);
+  fprintf (stderr, "memory . . . . .   %" PRIu64 "\n", config->memory);
+  fprintf (stderr, "disks  . . . . .  ");
+  if (config->disks != NULL) {
+    for (i = 0; config->disks[i] != NULL; ++i)
+      fprintf (stderr, " %s", config->disks[i]);
+  }
+  fprintf (stderr, "\n");
+  fprintf (stderr, "removable  . . .  ");
+  if (config->removable != NULL) {
+    for (i = 0; config->removable[i] != NULL; ++i)
+      fprintf (stderr, " %s", config->removable[i]);
+  }
+  fprintf (stderr, "\n");
+  fprintf (stderr, "interfaces . . .  ");
+  if (config->interfaces != NULL) {
+    for (i = 0; config->interfaces[i] != NULL; ++i)
+      fprintf (stderr, " %s", config->interfaces[i]);
+  }
+  fprintf (stderr, "\n");
+  fprintf (stderr, "\n");
+#endif
+}
diff --git a/p2v/copying.c b/p2v/copying.c
new file mode 100644
index 0000000..90e8830
--- /dev/null
+++ b/p2v/copying.c
@@ -0,0 +1,38 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include "p2v.h"
+
+/* The license of virt-p2v, for the About dialog. */
+
+const char *gplv2plus =
+  "This program is free software; you can redistribute it and/or modify\n"
+  "it under the terms of the GNU General Public License as published by\n"
+  "the Free Software Foundation; either version 2 of the License, or\n"
+  "(at your option) any later version.\n"
+  "\n"
+  "This program is distributed in the hope that it will be useful,\n"
+  "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+  "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
+  "GNU General Public License for more details.\n"
+  "\n"
+  "You should have received a copy of the GNU General Public License\n"
+  "along with this program; if not, write to the Free Software\n"
+  "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n";
diff --git a/p2v/gui.c b/p2v/gui.c
new file mode 100644
index 0000000..7a6fe9b
--- /dev/null
+++ b/p2v/gui.c
@@ -0,0 +1,1030 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <locale.h>
+#include <assert.h>
+#include <libintl.h>
+
+#include <pthread.h>
+
+#pragma GCC diagnostic ignored "-Wstrict-prototypes" /* error in <gtk.h> */
+#include <gtk/gtk.h>
+
+#include "p2v.h"
+
+/* Interactive GUI configuration. */
+
+static void create_connection_dialog (struct config *);
+static void create_conversion_dialog (struct config *);
+static void create_running_dialog (void);
+static void show_connection_dialog (void);
+static void show_conversion_dialog (void);
+static void show_running_dialog (void);
+
+/* The connection dialog. */
+static GtkWidget *conn_dlg,
+  *server_entry, *port_entry,
+  *username_entry, *password_entry, *sudo_button,
+  *spinner_hbox, *spinner, *spinner_message, *next_button;
+
+/* The conversion dialog. */
+static GtkWidget *conv_dlg,
+  *guestname_entry, *vcpus_entry, *memory_entry, *debug_button,
+  *disks_list, *removable_list, *interfaces_list,
+  *start_button;
+
+/* The running dialog which is displayed when virt-v2v is running. */
+static GtkWidget *run_dlg,
+  *v2v_output, *log_label,
+  *cancel_button;
+
+/* The entry point from the main program.
+ * Note that gtk_init etc have already been called in main().
+ */
+void
+gui_application (struct config *config)
+{
+  /* Create the dialogs. */
+  create_connection_dialog (config);
+  create_conversion_dialog (config);
+  create_running_dialog ();
+
+  /* Start by displaying the connection dialog. */
+  show_connection_dialog ();
+
+  gtk_main ();
+  gdk_threads_leave ();
+}
+
+/*----------------------------------------------------------------------*/
+/* Connection dialog. */
+
+static void test_connection_clicked (GtkWidget *w, gpointer data);
+static void *test_connection_thread (void *data);
+static void about_button_clicked (GtkWidget *w, gpointer data);
+static void connection_next_clicked (GtkWidget *w, gpointer data);
+
+static void
+create_connection_dialog (struct config *config)
+{
+  GtkWidget *intro, *table;
+  GtkWidget *server_label;
+  GtkWidget *port_label;
+  GtkWidget *username_label;
+  GtkWidget *password_label;
+  GtkWidget *test_hbox, *test;
+  GtkWidget *about;
+  char port_str[64];
+
+  conn_dlg = gtk_dialog_new ();
+  gtk_window_set_title (GTK_WINDOW (conn_dlg), program_name);
+  gtk_window_set_resizable (GTK_WINDOW (conn_dlg), FALSE);
+
+  /* The main dialog area. */
+  intro = gtk_label_new (_("Connect to a virt-v2v conversion server over SSH:"));
+  gtk_label_set_line_wrap (GTK_LABEL (intro), TRUE);
+  gtk_misc_set_padding (GTK_MISC (intro), 10, 10);
+
+  table = gtk_table_new (5, 2, FALSE);
+  server_label = gtk_label_new (_("Conversion server:"));
+  gtk_misc_set_alignment (GTK_MISC (server_label), 1., 0.5);
+  gtk_table_attach (GTK_TABLE (table), server_label,
+                    0, 1, 0, 1, GTK_FILL, GTK_FILL, 4, 4);
+  server_entry = gtk_entry_new ();
+  if (config->server != NULL)
+    gtk_entry_set_text (GTK_ENTRY (server_entry), config->server);
+  gtk_table_attach (GTK_TABLE (table), server_entry,
+                    1, 2, 0, 1, GTK_FILL, GTK_FILL, 4, 4);
+
+  port_label = gtk_label_new (_("SSH port:"));
+  gtk_misc_set_alignment (GTK_MISC (port_label), 1., 0.5);
+  gtk_table_attach (GTK_TABLE (table), port_label,
+                    0, 1, 1, 2, GTK_FILL, GTK_FILL, 4, 4);
+  port_entry = gtk_entry_new ();
+  gtk_entry_set_width_chars (GTK_ENTRY (port_entry), 6);
+  snprintf (port_str, sizeof port_str, "%d", config->port);
+  gtk_entry_set_text (GTK_ENTRY (port_entry), port_str);
+  gtk_table_attach (GTK_TABLE (table), port_entry,
+                    1, 2, 1, 2, GTK_FILL, GTK_FILL, 4, 4);
+
+  username_label = gtk_label_new (_("User name:"));
+  gtk_misc_set_alignment (GTK_MISC (username_label), 1., 0.5);
+  gtk_table_attach (GTK_TABLE (table), username_label,
+                    0, 1, 2, 3, GTK_FILL, GTK_FILL, 4, 4);
+  username_entry = gtk_entry_new ();
+  if (config->username != NULL)
+    gtk_entry_set_text (GTK_ENTRY (username_entry), config->username);
+  else
+    gtk_entry_set_text (GTK_ENTRY (username_entry), "root");
+  gtk_table_attach (GTK_TABLE (table), username_entry,
+                    1, 2, 2, 3, GTK_FILL, GTK_FILL, 4, 4);
+
+  password_label = gtk_label_new (_("Password:"));
+  gtk_misc_set_alignment (GTK_MISC (password_label), 1., 0.5);
+  gtk_table_attach (GTK_TABLE (table), password_label,
+                    0, 1, 3, 4, GTK_FILL, GTK_FILL, 4, 4);
+  password_entry = gtk_entry_new ();
+  gtk_entry_set_visibility (GTK_ENTRY (password_entry), FALSE);
+#ifdef GTK_INPUT_PURPOSE_PASSWORD
+  gtk_entry_set_input_purpose (GTK_ENTRY (password_entry),
+                               GTK_INPUT_PURPOSE_PASSWORD);
+#endif
+  if (config->password != NULL)
+    gtk_entry_set_text (GTK_ENTRY (password_entry), config->password);
+  gtk_table_attach (GTK_TABLE (table), password_entry,
+                    1, 2, 3, 4, GTK_FILL, GTK_FILL, 4, 4);
+
+  sudo_button =
+    gtk_check_button_new_with_label (_("Use sudo when running virt-v2v"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sudo_button),
+                                config->sudo);
+  gtk_table_attach (GTK_TABLE (table), sudo_button,
+                    1, 2, 4, 5, GTK_FILL, GTK_FILL, 4, 4);
+
+  test_hbox = gtk_hbox_new (FALSE, 0);
+  test = gtk_button_new_with_label (_("Test connection"));
+  gtk_box_pack_start (GTK_BOX (test_hbox), test, TRUE, FALSE, 0);
+
+  spinner_hbox = gtk_hbox_new (FALSE, 10);
+  spinner = gtk_spinner_new ();
+  gtk_box_pack_start (GTK_BOX (spinner_hbox), spinner, FALSE, FALSE, 0);
+  spinner_message = gtk_label_new (NULL);
+  gtk_label_set_line_wrap (GTK_LABEL (spinner_message), TRUE);
+  gtk_misc_set_padding (GTK_MISC (spinner_message), 10, 10);
+  gtk_box_pack_start (GTK_BOX (spinner_hbox), spinner_message, TRUE, TRUE, 0);
+
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (conn_dlg)->vbox),
+                      intro, TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (conn_dlg)->vbox),
+                      table, TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (conn_dlg)->vbox),
+                      test_hbox, FALSE, FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (conn_dlg)->vbox),
+                      spinner_hbox, TRUE, TRUE, 0);
+
+  /* Buttons. */
+  gtk_dialog_add_buttons (GTK_DIALOG (conn_dlg),
+                          _("Configure network ..."), 1,
+                          _("About ..."), 2,
+                          _("Next"), 3,
+                          NULL);
+
+  next_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (conn_dlg), 3);
+  gtk_widget_set_sensitive (next_button, FALSE);
+
+  about = gtk_dialog_get_widget_for_response (GTK_DIALOG (conn_dlg), 2);
+
+  /* Signals. */
+  g_signal_connect_swapped (G_OBJECT (conn_dlg), "destroy",
+                            G_CALLBACK (gtk_main_quit), NULL);
+  g_signal_connect (G_OBJECT (test), "clicked",
+                    G_CALLBACK (test_connection_clicked), config);
+  g_signal_connect (G_OBJECT (about), "clicked",
+                    G_CALLBACK (about_button_clicked), NULL);
+  g_signal_connect (G_OBJECT (next_button), "clicked",
+                    G_CALLBACK (connection_next_clicked), NULL);
+}
+
+static void
+show_connection_dialog (void)
+{
+  /* Hide the other dialogs. */
+  gtk_widget_hide (conv_dlg);
+  gtk_widget_hide (run_dlg);
+
+  /* Show everything except the spinner. */
+  gtk_widget_show_all (conn_dlg);
+  gtk_widget_hide_all (spinner_hbox);
+}
+
+static void
+test_connection_clicked (GtkWidget *w, gpointer data)
+{
+  struct config *config = data;
+  const gchar *port_str;
+  size_t errors = 0;
+  struct config *copy;
+  int err;
+  pthread_t tid;
+  pthread_attr_t attr;
+
+  gtk_label_set_text (GTK_LABEL (spinner_message), "");
+  gtk_widget_show_all (spinner_hbox);
+
+  /* Get the fields from the various widgets. */
+  free (config->server);
+  config->server = strdup (gtk_entry_get_text (GTK_ENTRY (server_entry)));
+  if (STREQ (config->server, "")) {
+    gtk_label_set_text (GTK_LABEL (spinner_message),
+                        _("error: No conversion server given."));
+    gtk_widget_grab_focus (server_entry);
+    errors++;
+  }
+  port_str = gtk_entry_get_text (GTK_ENTRY (port_entry));
+  if (sscanf (port_str, "%d", &config->port) != 1 ||
+      config->port <= 0 || config->port >= 65536) {
+    gtk_label_set_text (GTK_LABEL (spinner_message),
+                        _("error: Invalid port number. If in doubt, use \"22\"."));
+    gtk_widget_grab_focus (port_entry);
+    errors++;
+  }
+  free (config->username);
+  config->username = strdup (gtk_entry_get_text (GTK_ENTRY (username_entry)));
+  if (STREQ (config->username, "")) {
+    gtk_label_set_text (GTK_LABEL (spinner_message),
+                        _("error: No user name.  If in doubt, use \"root\"."));
+    gtk_widget_grab_focus (username_entry);
+    errors++;
+  }
+  free (config->password);
+  config->password = strdup (gtk_entry_get_text (GTK_ENTRY (password_entry)));
+
+  config->sudo = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (sudo_button));
+
+  if (errors)
+    return;
+
+  /* Give the testing thread its own copy of the config in case we
+   * update the config in the main thread.
+   */
+  copy = copy_config (config);
+
+  /* No errors so far, so test the connection in a background thread. */
+  pthread_attr_init (&attr);
+  pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+  err = pthread_create (&tid, &attr, test_connection_thread, copy);
+  if (err != 0) {
+    fprintf (stderr, "pthread_create: %s\n", strerror (err));
+    exit (EXIT_FAILURE);
+  }
+  pthread_attr_destroy (&attr);
+}
+
+/* Run test_connection (in a detached background thread).  Once it
+ * finishes stop the spinner and set the spinner message
+ * appropriately.  If the test is successful then we enable the "Next"
+ * button.
+ */
+static void *
+test_connection_thread (void *data)
+{
+  struct config *copy = data;
+  int r;
+
+  gdk_threads_enter ();
+  gtk_label_set_text (GTK_LABEL (spinner_message),
+                      _("Testing the connection to the conversion server ..."));
+  gtk_spinner_start (GTK_SPINNER (spinner));
+  gdk_threads_leave ();
+  r = test_connection (copy);
+  free_config (copy);
+  gdk_threads_enter ();
+  gtk_spinner_stop (GTK_SPINNER (spinner));
+
+  if (r == -1) {
+    /* Error testing the connection. */
+    const char *err = get_ssh_error ();
+
+    gtk_label_set_text (GTK_LABEL (spinner_message), err);
+    /* Disable the Next button. */
+    gtk_widget_set_sensitive (next_button, FALSE);
+  }
+  else {
+    /* Connection is good. */
+    gtk_label_set_text (GTK_LABEL (spinner_message),
+                        _("Connected to the conversion server.\n"
+                          "Press the \"Next\" button to configure the conversion process."));
+    /* Enable the Next button. */
+    gtk_widget_set_sensitive (next_button, TRUE);
+    gtk_widget_grab_focus (next_button);
+  }
+  gdk_threads_leave ();
+
+  /* Thread is detached anyway, so no one is waiting for the status. */
+  return NULL;
+}
+
+static void
+about_button_clicked (GtkWidget *w, gpointer data)
+{
+  gtk_show_about_dialog (GTK_WINDOW (conn_dlg),
+                         "program-name", program_name,
+                         "version", PACKAGE_VERSION,
+                         "copyright", "\u00A9 2009-2014 Red Hat Inc.",
+                         "comments", "Convert a physical machine to use KVM",
+                         "license", gplv2plus,
+                         "website", "http://libguestfs.org/",
+                         "authors", authors,
+                         NULL);
+}
+
+/* The connection dialog Next button has been clicked. */
+static void
+connection_next_clicked (GtkWidget *w, gpointer data)
+{
+  /* Switch to the conversion dialog. */
+  show_conversion_dialog ();
+}
+
+/*----------------------------------------------------------------------*/
+/* Conversion dialog. */
+
+static void populate_disks (GtkTreeView *disks_list);
+static void populate_removable (GtkTreeView *removable_list);
+static void populate_interfaces (GtkTreeView *interfaces_list);
+static void toggled (GtkCellRendererToggle *cell, gchar *path_str, gpointer data);
+static void set_disks_from_ui (struct config *);
+static void set_removable_from_ui (struct config *);
+static void set_interfaces_from_ui (struct config *);
+static void conversion_back_clicked (GtkWidget *w, gpointer data);
+static void start_conversion_clicked (GtkWidget *w, gpointer data);
+static void notify_ui_callback (int type, const char *data);
+
+enum {
+  DISKS_COL_CONVERT = 0,
+  DISKS_COL_DEVICE,
+  DISKS_COL_SIZE,
+  DISKS_COL_MODEL,
+  NUM_DISKS_COLS,
+};
+
+enum {
+  REMOVABLE_COL_CONVERT = 0,
+  REMOVABLE_COL_DEVICE,
+  NUM_REMOVABLE_COLS,
+};
+
+enum {
+  INTERFACES_COL_CONVERT = 0,
+  INTERFACES_COL_DEVICE,
+  NUM_INTERFACES_COLS,
+};
+
+static void
+create_conversion_dialog (struct config *config)
+{
+  GtkWidget *back;
+  GtkWidget *hbox, *right_vbox;
+  GtkWidget *target, *target_vbox, *target_tbl;
+  GtkWidget *guestname_label, *vcpus_label, *memory_label;
+  GtkWidget *disks_frame, *disks_sw;
+  GtkWidget *removable_frame, *removable_sw;
+  GtkWidget *interfaces_frame, *interfaces_sw;
+  char vcpus_str[64];
+  char memory_str[64];
+
+  conv_dlg = gtk_dialog_new ();
+  gtk_window_set_title (GTK_WINDOW (conv_dlg), program_name);
+  gtk_window_set_resizable (GTK_WINDOW (conv_dlg), FALSE);
+  /* XXX It would be nice not to have to set this explicitly, but
+   * if we don't then Gtk chooses a very small window.
+   */
+  gtk_widget_set_size_request (conv_dlg, 800, 500);
+
+  /* The main dialog area. */
+  hbox = gtk_hbox_new (TRUE, 1);
+  right_vbox = gtk_vbox_new (TRUE, 1);
+
+  /* The left column: target properties. */
+  target = gtk_frame_new (_("Target properties"));
+
+  target_vbox = gtk_vbox_new (FALSE, 1);
+
+  target_tbl = gtk_table_new (3, 2, FALSE);
+  guestname_label = gtk_label_new (_("Name:"));
+  gtk_misc_set_alignment (GTK_MISC (guestname_label), 1., 0.5);
+  gtk_table_attach (GTK_TABLE (target_tbl), guestname_label,
+                    0, 1, 0, 1, GTK_FILL, GTK_FILL, 1, 1);
+  guestname_entry = gtk_entry_new ();
+  if (config->guestname != NULL)
+    gtk_entry_set_text (GTK_ENTRY (guestname_entry), config->guestname);
+  gtk_table_attach (GTK_TABLE (target_tbl), guestname_entry,
+                    1, 2, 0, 1, GTK_FILL, GTK_FILL, 1, 1);
+
+  vcpus_label = gtk_label_new (_("# vCPUs:"));
+  gtk_misc_set_alignment (GTK_MISC (vcpus_label), 1., 0.5);
+  gtk_table_attach (GTK_TABLE (target_tbl), vcpus_label,
+                    0, 1, 1, 2, GTK_FILL, GTK_FILL, 1, 1);
+  vcpus_entry = gtk_entry_new ();
+  snprintf (vcpus_str, sizeof vcpus_str, "%d", config->vcpus);
+  gtk_entry_set_text (GTK_ENTRY (vcpus_entry), vcpus_str);
+  gtk_table_attach (GTK_TABLE (target_tbl), vcpus_entry,
+                    1, 2, 1, 2, GTK_FILL, GTK_FILL, 1, 1);
+
+  memory_label = gtk_label_new (_("Memory (MB):"));
+  gtk_misc_set_alignment (GTK_MISC (memory_label), 1., 0.5);
+  gtk_table_attach (GTK_TABLE (target_tbl), memory_label,
+                    0, 1, 2, 3, GTK_FILL, GTK_FILL, 1, 1);
+  memory_entry = gtk_entry_new ();
+  snprintf (memory_str, sizeof memory_str, "%" PRIu64,
+            config->memory / 1024 / 1024);
+  gtk_entry_set_text (GTK_ENTRY (memory_entry), memory_str);
+  gtk_table_attach (GTK_TABLE (target_tbl), memory_entry,
+                    1, 2, 2, 3, GTK_FILL, GTK_FILL, 1, 1);
+
+  debug_button =
+    gtk_check_button_new_with_label (_("Enable server-side debugging\n"
+                                       "(This is saved in /tmp on the conversion server)"));
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (debug_button),
+                                config->verbose);
+
+  gtk_box_pack_start (GTK_BOX (target_vbox), target_tbl, TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (target_vbox), debug_button, TRUE, TRUE, 0);
+  gtk_container_add (GTK_CONTAINER (target), target_vbox);
+
+  /* The right column: select devices to be converted. */
+  disks_frame = gtk_frame_new (_("Fixed hard disks"));
+  disks_sw = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (disks_sw),
+                                  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+  disks_list = gtk_tree_view_new ();
+  populate_disks (GTK_TREE_VIEW (disks_list));
+  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (disks_sw),
+                                         disks_list);
+  gtk_container_add (GTK_CONTAINER (disks_frame), disks_sw);
+
+  removable_frame = gtk_frame_new (_("Removable media"));
+  removable_sw = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (removable_sw),
+                                  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+  removable_list = gtk_tree_view_new ();
+  populate_removable (GTK_TREE_VIEW (removable_list));
+  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (removable_sw),
+                                         removable_list);
+  gtk_container_add (GTK_CONTAINER (removable_frame), removable_sw);
+
+  interfaces_frame = gtk_frame_new (_("Network interfaces"));
+  interfaces_sw = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (interfaces_sw),
+                                  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+  interfaces_list = gtk_tree_view_new ();
+  populate_interfaces (GTK_TREE_VIEW (interfaces_list));
+  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (interfaces_sw),
+                                         interfaces_list);
+  gtk_container_add (GTK_CONTAINER (interfaces_frame), interfaces_sw);
+
+  gtk_box_pack_start (GTK_BOX (right_vbox), disks_frame, TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (right_vbox), removable_frame, TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (right_vbox), interfaces_frame, TRUE, TRUE, 0);
+
+  gtk_box_pack_start (GTK_BOX (hbox), target, TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (hbox), right_vbox, TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (conv_dlg)->vbox),
+                      hbox, TRUE, TRUE, 0);
+
+  /* Buttons. */
+  gtk_dialog_add_buttons (GTK_DIALOG (conv_dlg),
+                          _("Back"), 1,
+                          _("Start conversion"), 2,
+                          NULL);
+  back = gtk_dialog_get_widget_for_response (GTK_DIALOG (conv_dlg), 1);
+  start_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (conv_dlg), 2);
+
+  /* Signals. */
+  g_signal_connect_swapped (G_OBJECT (conv_dlg), "destroy",
+                            G_CALLBACK (gtk_main_quit), NULL);
+  g_signal_connect (G_OBJECT (back), "clicked",
+                    G_CALLBACK (conversion_back_clicked), NULL);
+  g_signal_connect (G_OBJECT (start_button), "clicked",
+                    G_CALLBACK (start_conversion_clicked), config);
+}
+
+static void
+show_conversion_dialog (void)
+{
+  /* Hide the other dialogs. */
+  gtk_widget_hide (conn_dlg);
+  gtk_widget_hide (run_dlg);
+
+  /* Show the conversion dialog. */
+  gtk_widget_show_all (conv_dlg);
+}
+
+static void
+populate_disks (GtkTreeView *disks_list)
+{
+  GtkListStore *disks_store;
+  GtkCellRenderer *disks_col_convert, *disks_col_device,
+    *disks_col_size, *disks_col_model;
+  GtkTreeIter iter;
+  size_t i;
+
+  disks_store = gtk_list_store_new (NUM_DISKS_COLS,
+                                    G_TYPE_BOOLEAN, G_TYPE_STRING,
+                                    G_TYPE_STRING, G_TYPE_STRING);
+  if (all_disks != NULL) {
+    for (i = 0; all_disks[i] != NULL; ++i) {
+      CLEANUP_FREE char *size_filename = NULL;
+      CLEANUP_FREE char *model_filename = NULL;
+      CLEANUP_FREE char *size_str = NULL;
+      CLEANUP_FREE char *size_gb = NULL;
+      CLEANUP_FREE char *model = NULL;
+      uint64_t size;
+
+      if (asprintf (&size_filename, "/sys/block/%s/size",
+                    all_disks[i]) == -1) {
+        perror ("asprintf");
+        exit (EXIT_FAILURE);
+      }
+      if (g_file_get_contents (size_filename, &size_str, NULL, NULL) &&
+          sscanf (size_str, "%" SCNu64, &size) == 1) {
+        size /= 2*1024*1024; /* size from kernel is given in sectors? */
+        if (asprintf (&size_gb, "%" PRIu64, size) == -1) {
+          perror ("asprintf");
+          exit (EXIT_FAILURE);
+        }
+      }
+
+      if (asprintf (&model_filename, "/sys/block/%s/device/model",
+                    all_disks[i]) == -1) {
+        perror ("asprintf");
+        exit (EXIT_FAILURE);
+      }
+      if (g_file_get_contents (model_filename, &model, NULL, NULL)) {
+        /* Need to chomp trailing \n from the content. */
+        size_t len = strlen (model);
+        if (len > 0 && model[len-1] == '\n')
+          model[len-1] = '\0';
+      } else {
+        model = strdup ("");
+      }
+
+      gtk_list_store_append (disks_store, &iter);
+      gtk_list_store_set (disks_store, &iter,
+                          DISKS_COL_CONVERT, TRUE,
+                          DISKS_COL_DEVICE, all_disks[i],
+                          DISKS_COL_SIZE, size_gb,
+                          DISKS_COL_MODEL, model,
+                          -1);
+    }
+  }
+  gtk_tree_view_set_model (disks_list,
+                           GTK_TREE_MODEL (disks_store));
+  gtk_tree_view_set_headers_visible (disks_list, TRUE);
+  disks_col_convert = gtk_cell_renderer_toggle_new ();
+  gtk_tree_view_insert_column_with_attributes (disks_list,
+                                               -1,
+                                               _("Convert"),
+                                               disks_col_convert,
+                                               "active", DISKS_COL_CONVERT,
+                                               NULL);
+  disks_col_device = gtk_cell_renderer_text_new ();
+  gtk_tree_view_insert_column_with_attributes (disks_list,
+                                               -1,
+                                               _("Device"),
+                                               disks_col_device,
+                                               "text", DISKS_COL_DEVICE,
+                                               NULL);
+  disks_col_size = gtk_cell_renderer_text_new ();
+  gtk_tree_view_insert_column_with_attributes (disks_list,
+                                               -1,
+                                               _("Size (GB)"),
+                                               disks_col_size,
+                                               "text", DISKS_COL_SIZE,
+                                               NULL);
+  disks_col_model = gtk_cell_renderer_text_new ();
+  gtk_tree_view_insert_column_with_attributes (disks_list,
+                                               -1,
+                                               _("Model"),
+                                               disks_col_model,
+                                               "text", DISKS_COL_MODEL,
+                                               NULL);
+
+  g_signal_connect (disks_col_convert, "toggled",
+                    G_CALLBACK (toggled), disks_store);
+}
+
+static void
+populate_removable (GtkTreeView *removable_list)
+{
+  GtkListStore *removable_store;
+  GtkCellRenderer *removable_col_convert, *removable_col_device;
+  GtkTreeIter iter;
+  size_t i;
+
+  removable_store = gtk_list_store_new (NUM_REMOVABLE_COLS,
+                                        G_TYPE_BOOLEAN, G_TYPE_STRING);
+  if (all_removable != NULL) {
+    for (i = 0; all_removable[i] != NULL; ++i) {
+      gtk_list_store_append (removable_store, &iter);
+      gtk_list_store_set (removable_store, &iter,
+                          REMOVABLE_COL_CONVERT, TRUE,
+                          REMOVABLE_COL_DEVICE, all_removable[i],
+                          -1);
+    }
+  }
+  gtk_tree_view_set_model (removable_list,
+                           GTK_TREE_MODEL (removable_store));
+  gtk_tree_view_set_headers_visible (removable_list, TRUE);
+  removable_col_convert = gtk_cell_renderer_toggle_new ();
+  gtk_tree_view_insert_column_with_attributes (removable_list,
+                                               -1,
+                                               _("Convert"),
+                                               removable_col_convert,
+                                               "active", REMOVABLE_COL_CONVERT,
+                                               NULL);
+  removable_col_device = gtk_cell_renderer_text_new ();
+  gtk_tree_view_insert_column_with_attributes (removable_list,
+                                               -1,
+                                               _("Device"),
+                                               removable_col_device,
+                                               "text", REMOVABLE_COL_DEVICE,
+                                               NULL);
+
+  g_signal_connect (removable_col_convert, "toggled",
+                    G_CALLBACK (toggled), removable_store);
+}
+
+static void
+populate_interfaces (GtkTreeView *interfaces_list)
+{
+  GtkListStore *interfaces_store;
+  GtkCellRenderer *interfaces_col_convert, *interfaces_col_device;
+  GtkTreeIter iter;
+  size_t i;
+
+  interfaces_store = gtk_list_store_new (NUM_INTERFACES_COLS,
+                                         G_TYPE_BOOLEAN, G_TYPE_STRING);
+  if (all_interfaces) {
+    for (i = 0; all_interfaces[i] != NULL; ++i) {
+      gtk_list_store_append (interfaces_store, &iter);
+      gtk_list_store_set (interfaces_store, &iter,
+                          INTERFACES_COL_CONVERT, TRUE,
+                          INTERFACES_COL_DEVICE, all_interfaces[i],
+                          -1);
+    }
+  }
+  gtk_tree_view_set_model (interfaces_list,
+                           GTK_TREE_MODEL (interfaces_store));
+  gtk_tree_view_set_headers_visible (interfaces_list, TRUE);
+  interfaces_col_convert = gtk_cell_renderer_toggle_new ();
+  gtk_tree_view_insert_column_with_attributes (interfaces_list,
+                                               -1,
+                                               _("Convert"),
+                                               interfaces_col_convert,
+                                               "active", INTERFACES_COL_CONVERT,
+                                               NULL);
+  interfaces_col_device = gtk_cell_renderer_text_new ();
+  gtk_tree_view_insert_column_with_attributes (interfaces_list,
+                                               -1,
+                                               _("Device"),
+                                               interfaces_col_device,
+                                               "text", INTERFACES_COL_DEVICE,
+                                               NULL);
+
+  g_signal_connect (interfaces_col_convert, "toggled",
+                    G_CALLBACK (toggled), interfaces_store);
+}
+
+static void
+toggled (GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
+{
+  GtkTreeModel *model = data;
+  GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
+  GtkTreeIter iter;
+  gboolean v;
+
+  gtk_tree_model_get_iter (model, &iter, path);
+  gtk_tree_model_get (model, &iter, 0 /* CONVERT */, &v, -1);
+  v ^= 1;
+  gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0 /* CONVERT */, v, -1);
+  gtk_tree_path_free (path);
+}
+
+static void
+set_from_ui_generic (char **all, char ***ret, GtkTreeView *list)
+{
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  gboolean b, v;
+  size_t i, j;
+
+  if (all == NULL) {
+    guestfs___free_string_list (*ret);
+    *ret = NULL;
+    return;
+  }
+
+  model = gtk_tree_view_get_model (list);
+
+  guestfs___free_string_list (*ret);
+  *ret = malloc ((1 + guestfs___count_strings (all)) * sizeof (char *));
+  if (*ret == NULL) {
+    perror ("malloc");
+    exit (EXIT_FAILURE);
+  }
+  i = j = 0;
+
+  b = gtk_tree_model_get_iter_first (model, &iter);
+  while (b) {
+    gtk_tree_model_get (model, &iter, 0 /* CONVERT */, &v, -1);
+    if (v) {
+      assert (all[i] != NULL);
+      (*ret)[j++] = strdup (all[i]);
+    }
+    b = gtk_tree_model_iter_next (model, &iter);
+    ++i;
+  }
+
+  (*ret)[j] = NULL;
+}
+
+static void
+set_disks_from_ui (struct config *config)
+{
+  set_from_ui_generic (all_disks, &config->disks,
+                       GTK_TREE_VIEW (disks_list));
+}
+
+static void
+set_removable_from_ui (struct config *config)
+{
+  set_from_ui_generic (all_removable, &config->removable,
+                       GTK_TREE_VIEW (removable_list));
+}
+
+static void
+set_interfaces_from_ui (struct config *config)
+{
+  set_from_ui_generic (all_interfaces, &config->interfaces,
+                       GTK_TREE_VIEW (interfaces_list));
+}
+
+/* The conversion dialog Back button has been clicked. */
+static void
+conversion_back_clicked (GtkWidget *w, gpointer data)
+{
+  /* Switch to the connection dialog. */
+  show_connection_dialog ();
+
+  /* Better disable the Next button so the user is forced to
+   * do "Test connection" again.
+   */
+  gtk_widget_set_sensitive (next_button, FALSE);
+}
+
+/*----------------------------------------------------------------------*/
+/* Running dialog. */
+
+static void set_log_dir (const char *remote_dir);
+static void add_v2v_output (const char *msg);
+static void *start_conversion_thread (void *data);
+
+static void
+create_running_dialog (void)
+{
+  run_dlg = gtk_dialog_new ();
+  gtk_window_set_title (GTK_WINDOW (run_dlg), program_name);
+  gtk_window_set_resizable (GTK_WINDOW (run_dlg), FALSE);
+
+  /* The main dialog area. */
+  v2v_output = gtk_text_view_new ();
+  gtk_text_view_set_editable (GTK_TEXT_VIEW (v2v_output), FALSE);
+  gtk_widget_set_size_request (v2v_output, 700, 400);
+  log_label = gtk_label_new (NULL);
+  gtk_misc_set_alignment (GTK_MISC (log_label), 0., 0.5);
+  gtk_misc_set_padding (GTK_MISC (log_label), 10, 10);
+  set_log_dir (NULL);
+
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (run_dlg)->vbox),
+                      v2v_output, TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (run_dlg)->vbox),
+                      log_label, TRUE, TRUE, 0);
+
+  /* Buttons. */
+  gtk_dialog_add_buttons (GTK_DIALOG (run_dlg),
+                          _("Cancel conversion"), 1,
+                          NULL);
+  cancel_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (conv_dlg), 1);
+  gtk_widget_set_sensitive (cancel_button, FALSE);
+
+  /* Signals. */
+  g_signal_connect_swapped (G_OBJECT (run_dlg), "destroy",
+                            G_CALLBACK (gtk_main_quit), NULL);
+}
+
+static void
+show_running_dialog (void)
+{
+  /* Hide the other dialogs. */
+  gtk_widget_hide (conn_dlg);
+  gtk_widget_hide (conv_dlg);
+
+  /* Show the running dialog. */
+  gtk_widget_show_all (run_dlg);
+  gtk_widget_set_sensitive (cancel_button, FALSE);
+}
+
+static void
+set_log_dir (const char *remote_dir)
+{
+  CLEANUP_FREE char *msg;
+
+  if (asprintf (&msg,
+                _("Log files and debug information "
+                  "is saved to this directory "
+                  "on the conversion server:\n"
+                  "%s"),
+                remote_dir ? remote_dir : "") == -1) {
+    perror ("asprintf");
+    exit (EXIT_FAILURE);
+  }
+
+  gtk_label_set_text (GTK_LABEL (log_label), msg);
+}
+
+/* Append output from the virt-v2v process to the buffer, and scroll
+ * to ensure it is visible.
+ */
+static void
+add_v2v_output (const char *msg)
+{
+  GtkTextBuffer *buf;
+  GtkTextIter iter;
+
+  /* Insert it at the end. */
+  buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (v2v_output));
+  gtk_text_buffer_get_end_iter (buf, &iter);
+  gtk_text_buffer_insert (buf, &iter, msg, -1);
+
+  /* Scroll to the end of the buffer. */
+  gtk_text_buffer_get_end_iter (buf, &iter);
+  gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (v2v_output), &iter,
+                                0, FALSE, 0., 1.);
+}
+
+/* User clicked the Start conversion button. */
+static void
+start_conversion_clicked (GtkWidget *w, gpointer data)
+{
+  struct config *config = data;
+  int i;
+  const char *vcpus_str;
+  const char *memory_str;
+  GtkWidget *dlg;
+  struct config *copy;
+  int err;
+  pthread_t tid;
+  pthread_attr_t attr;
+
+  /* Unpack dialog fields and check them. */
+  free (config->guestname);
+  config->guestname = strdup (gtk_entry_get_text (GTK_ENTRY (guestname_entry)));
+
+  if (STREQ (config->guestname, "")) {
+    dlg = gtk_message_dialog_new (GTK_WINDOW (conv_dlg),
+                                  GTK_DIALOG_DESTROY_WITH_PARENT,
+                                  GTK_MESSAGE_ERROR,
+                                  GTK_BUTTONS_OK,
+                                  _("The guest \"Name\" field is empty."));
+    gtk_window_set_title (GTK_WINDOW (dlg), _("Error"));
+    gtk_dialog_run (GTK_DIALOG (dlg));
+    gtk_widget_destroy (dlg);
+    gtk_widget_grab_focus (guestname_entry);
+    return;
+  }
+
+  vcpus_str = gtk_entry_get_text (GTK_ENTRY (vcpus_entry));
+  if (sscanf (vcpus_str, "%d", &i) == 1 && i > 0)
+    config->vcpus = i;
+  else
+    config->vcpus = 1;
+
+  memory_str = gtk_entry_get_text (GTK_ENTRY (memory_entry));
+  if (sscanf (memory_str, "%d", &i) == 1 && i >= 256)
+    config->memory = (uint64_t) i * 1024 * 1024;
+  else
+    config->memory = 1024 * 1024 * 1024;
+
+  config->verbose =
+    gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (debug_button));
+
+  /* Get the list of disks to be converted. */
+  set_disks_from_ui (config);
+
+  /* The list of disks must be non-empty. */
+  if (config->disks == NULL || guestfs___count_strings (config->disks) == 0) {
+    dlg = gtk_message_dialog_new (GTK_WINDOW (conv_dlg),
+                                  GTK_DIALOG_DESTROY_WITH_PARENT,
+                                  GTK_MESSAGE_ERROR,
+                                  GTK_BUTTONS_OK,
+                                  _("No disks were selected for conversion.\n"
+                                    "At least one fixed hard disk must be selected.\n"));
+    gtk_window_set_title (GTK_WINDOW (dlg), _("Error"));
+    gtk_dialog_run (GTK_DIALOG (dlg));
+    gtk_widget_destroy (dlg);
+    return;
+  }
+
+  /* List of removable media and network interfaces. */
+  set_removable_from_ui (config);
+  set_interfaces_from_ui (config);
+
+  /* Display the UI for conversion. */
+  show_running_dialog ();
+
+  /* Do the conversion, in a background thread. */
+
+  /* Give the conversion (background) thread its own copy of the
+   * config in case we update the config in the main thread.
+   */
+  copy = copy_config (config);
+
+  pthread_attr_init (&attr);
+  pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+  err = pthread_create (&tid, &attr, start_conversion_thread, copy);
+  if (err != 0) {
+    fprintf (stderr, "pthread_create: %s\n", strerror (err));
+    exit (EXIT_FAILURE);
+  }
+  pthread_attr_destroy (&attr);
+}
+
+static void *
+start_conversion_thread (void *data)
+{
+  struct config *copy = data;
+  int r;
+  GtkWidget *dlg;
+
+  r = start_conversion (copy, notify_ui_callback);
+  free_config (copy);
+
+  gdk_threads_enter ();
+
+  if (r == -1) {
+    const char *err = get_conversion_error ();
+
+    dlg = gtk_message_dialog_new (GTK_WINDOW (run_dlg),
+                                  GTK_DIALOG_DESTROY_WITH_PARENT,
+                                  GTK_MESSAGE_ERROR,
+                                  GTK_BUTTONS_OK,
+                                  _("Conversion failed: %s"), err);
+    gtk_window_set_title (GTK_WINDOW (dlg), _("Conversion failed"));
+    gtk_dialog_run (GTK_DIALOG (dlg));
+    gtk_widget_destroy (dlg);
+  }
+  else {
+    dlg = gtk_message_dialog_new (GTK_WINDOW (run_dlg),
+                                  GTK_DIALOG_DESTROY_WITH_PARENT,
+                                  GTK_MESSAGE_ERROR,
+                                  GTK_BUTTONS_OK,
+                                  _("The conversion was successful."));
+    gtk_window_set_title (GTK_WINDOW (dlg), _("Conversion was successful"));
+    gtk_dialog_run (GTK_DIALOG (dlg));
+    gtk_widget_destroy (dlg);
+  }
+
+  gdk_threads_leave ();
+
+  /* Thread is detached anyway, so no one is waiting for the status. */
+  return NULL;
+}
+
+static void
+notify_ui_callback (int type, const char *data)
+{
+  gdk_threads_enter ();
+
+  switch (type) {
+  case NOTIFY_LOG_DIR:
+    set_log_dir (data);
+    break;
+
+  case NOTIFY_REMOTE_MESSAGE:
+    add_v2v_output (data);
+    break;
+
+  default:
+    fprintf (stderr,
+             "%s: unknown message during conversion: type=%d data=%s\n",
+             program_name, type, data);
+  }
+
+  gdk_threads_leave ();
+}
diff --git a/p2v/kernel.c b/p2v/kernel.c
new file mode 100644
index 0000000..0e0f4c4
--- /dev/null
+++ b/p2v/kernel.c
@@ -0,0 +1,180 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* Kernel-driven configuration, non-interactive. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <locale.h>
+#include <libintl.h>
+
+#include "p2v.h"
+
+static void notify_ui_callback (int type, const char *data);
+
+void
+kernel_configuration (struct config *config, const char *cmdline)
+{
+  const char *r;
+  size_t len;
+
+  r = strstr (cmdline, "p2v.server=");
+  assert (r); /* checked by caller */
+  r += 5+6;
+  len = strcspn (r, " ");
+  free (config->server);
+  config->server = strndup (r, len);
+
+  r = strstr (cmdline, "p2v.port=");
+  if (r) {
+    r += 5+4;
+    if (sscanf (r, "%d", &config->port) != 1) {
+      fprintf (stderr, "%s: cannot parse p2v.port from kernel command line",
+               program_name);
+      exit (EXIT_FAILURE);
+    }
+  }
+
+  r = strstr (cmdline, "p2v.username=");
+  if (r) {
+    r += 5+8;
+    len = strcspn (r, " ");
+    free (config->username);
+    config->username = strndup (r, len);
+  }
+
+  r = strstr (cmdline, "p2v.password=");
+  if (r) {
+    r += 5+8;
+    len = strcspn (r, " ");
+    free (config->password);
+    config->password = strndup (r, len);
+  }
+
+  r = strstr (cmdline, "p2v.sudo");
+  if (r)
+    config->sudo = 1;
+
+  /* We should now be able to connect and interrogate virt-v2v
+   * on the conversion server.
+   */
+  if (test_connection (config) == -1) {
+    const char *err = get_ssh_error ();
+
+    fprintf (stderr, "%s: error opening control connection to %s:%d: %s\n",
+             program_name, config->server, config->port, err);
+    exit (EXIT_FAILURE);
+  }
+
+  r = strstr (cmdline, "p2v.name=");
+  if (r) {
+    r += 5+4;
+    len = strcspn (r, " ");
+    free (config->guestname);
+    config->guestname = strndup (r, len);
+  }
+
+  r = strstr (cmdline, "p2v.vcpus=");
+  if (r) {
+    r += 5+5;
+    if (sscanf (r, "%d", &config->vcpus) != 1) {
+      fprintf (stderr, "%s: cannot parse p2v.vcpus from kernel command line\n",
+               program_name);
+      exit (EXIT_FAILURE);
+    }
+  }
+
+  r = strstr (cmdline, "p2v.memory=");
+  if (r) {
+    char mem_code[2];
+
+    r += 5+6;
+    if (sscanf (r, "%" SCNu64 "%c", &config->memory, mem_code) != 1) {
+      fprintf (stderr, "%s: cannot parse p2v.memory from kernel command line\n",
+               program_name);
+      exit (EXIT_FAILURE);
+    }
+    config->memory *= 1024;
+    if (mem_code[0] == 'M' || mem_code[0] == 'G')
+      config->memory *= 1024;
+    if (mem_code[0] == 'G')
+      config->memory *= 1024;
+    if (mem_code[0] != 'M' && mem_code[0] != 'G') {
+      fprintf (stderr, "%s: p2v.memory on kernel command line must be followed by 'G' or 'M'\n",
+               program_name);
+      exit (EXIT_FAILURE);
+    }
+  }
+
+  r = strstr (cmdline, "p2v.disks=");
+  if (r) {
+    r += 5+5;
+    len = strcspn (r, " ");
+    guestfs___free_string_list (config->disks);
+    config->disks = guestfs___split_string (',', r);
+  }
+
+  r = strstr (cmdline, "p2v.removable=");
+  if (r) {
+    r += 5+9;
+    len = strcspn (r, " ");
+    guestfs___free_string_list (config->removable);
+    config->removable = guestfs___split_string (',', r);
+  }
+
+  r = strstr (cmdline, "p2v.interfaces=");
+  if (r) {
+    r += 5+10;
+    len = strcspn (r, " ");
+    guestfs___free_string_list (config->interfaces);
+    config->interfaces = guestfs___split_string (',', r);
+  }
+
+  if (start_conversion (config, notify_ui_callback) == -1) {
+    const char *err = get_conversion_error ();
+
+    fprintf (stderr, "%s: error during conversion: %s\n",
+             program_name, err);
+    exit (EXIT_FAILURE);
+  }
+}
+
+static void
+notify_ui_callback (int type, const char *data)
+{
+  switch (type) {
+  case NOTIFY_LOG_DIR:
+    printf ("%s: remote log directory location: %s\n", program_name, data);
+    break;
+
+  case NOTIFY_REMOTE_MESSAGE:
+    printf ("%s", data);
+    break;
+
+  default:
+    printf ("%s: unknown message during conversion: type=%d data=%s\n",
+            program_name, type, data);
+  }
+}
diff --git a/p2v/main.c b/p2v/main.c
new file mode 100644
index 0000000..eafb8e6
--- /dev/null
+++ b/p2v/main.c
@@ -0,0 +1,457 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <locale.h>
+#include <assert.h>
+#include <libintl.h>
+#include <sys/types.h>
+
+#pragma GCC diagnostic ignored "-Wstrict-prototypes" /* error in <gtk.h> */
+#include <gtk/gtk.h>
+
+#include "p2v.h"
+
+char **all_disks;
+char **all_removable;
+char **all_interfaces;
+
+static void set_config_defaults (struct config *config);
+static void find_all_disks (void);
+static void find_all_interfaces (void);
+static char *read_cmdline (void);
+
+enum { HELP_OPTION = CHAR_MAX + 1 };
+static const char *options = "Vv";
+static const struct option long_options[] = {
+  { "help", 0, 0, HELP_OPTION },
+  { "cmdline", 1, 0, 0 },
+  { "long-options", 0, 0, 0 },
+  { "verbose", 0, 0, 'v' },
+  { "version", 0, 0, 'V' },
+  { 0, 0, 0, 0 }
+};
+
+static void __attribute__((noreturn))
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    fprintf (stderr, _("Try `%s --help' for more information.\n"),
+             program_name);
+  else {
+    fprintf (stdout,
+           _("%s: Convert a physical machine to use KVM\n"
+             "Copyright (C) 2009-2014 Red Hat Inc.\n"
+             "Usage:\n"
+             "  %s [--options]\n"
+             "Options:\n"
+             "  --help                 Display brief help\n"
+             " --cmdline=CMDLINE       Used to debug command line parsing\n"
+              "  -v|--verbose           Verbose messages\n"
+             "  -V|--version           Display version and exit\n"
+             "For more information, see the manpage %s(1).\n"),
+             program_name, program_name, program_name);
+  }
+  exit (status);
+}
+
+/* XXX Copied from fish/options.c. */
+static void
+display_long_options (const struct option *long_options)
+{
+  while (long_options->name) {
+    if (STRNEQ (long_options->name, "long-options"))
+      printf ("--%s\n", long_options->name);
+    long_options++;
+  }
+  exit (EXIT_SUCCESS);
+}
+
+int
+main (int argc, char *argv[])
+{
+  gboolean gui_possible;
+  int c;
+  int option_index;
+  char *cmdline = NULL;
+  struct config *config = new_config ();
+
+  setlocale (LC_ALL, "");
+  bindtextdomain (PACKAGE, LOCALEBASEDIR);
+  textdomain (PACKAGE);
+
+  gdk_threads_init ();
+  gdk_threads_enter ();
+  gui_possible = gtk_init_check (&argc, &argv);
+
+  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, "long-options")) {
+        display_long_options (long_options);
+      }
+      else if (STREQ (long_options[option_index].name, "cmdline")) {
+        cmdline = strdup (optarg);
+      }
+      else {
+        fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
+                 program_name, long_options[option_index].name, option_index);
+        exit (EXIT_FAILURE);
+      }
+      break;
+
+    case 'v':
+      config->verbose = 1;
+      break;
+
+    case 'V':
+      printf ("%s %s\n", program_name, PACKAGE_VERSION);
+      exit (EXIT_SUCCESS);
+
+    case HELP_OPTION:
+      usage (EXIT_SUCCESS);
+
+    default:
+      usage (EXIT_FAILURE);
+    }
+  }
+
+  if (optind != argc) {
+    fprintf (stderr, _("%s: unused arguments on the command line\n"),
+             program_name);
+    usage (EXIT_FAILURE);
+  }
+
+  set_config_defaults (config);
+
+  /* If /proc/cmdline exists and contains "p2v.server=" then we enable
+   * non-interactive configuration.
+   * If /proc/cmdline contains p2v.debug then we enable verbose mode
+   * even for interactive configuration.
+   */
+  if (cmdline == NULL)
+    cmdline = read_cmdline ();
+  if (cmdline == NULL)
+    goto gui;
+
+  if (strstr (cmdline, "p2v.debug"))
+    config->verbose = 1;
+
+  if (strstr (cmdline, "p2v.server="))
+    kernel_configuration (config, cmdline);
+  else {
+  gui:
+    if (!gui_possible)
+      /* Gtk has already printed an error. */
+      exit (EXIT_FAILURE);
+    gui_application (config);
+  }
+
+  free (cmdline);
+
+  exit (EXIT_SUCCESS);
+}
+
+static void
+set_config_defaults (struct config *config)
+{
+  long i;
+  char hostname[257];
+
+  /* Default guest name is derived from the source hostname.  If we
+   * assume that the p2v ISO gets its IP address and hostname from
+   * DHCP, then there is at better than average chance that
+   * gethostname will return the real hostname here.  It's better than
+   * trying to fish around in the guest filesystem anyway.
+   */
+  if (gethostname (hostname, sizeof hostname) == -1) {
+    perror ("gethostname");
+    /* Generate a simple random name. */
+    if (guestfs___random_string (hostname, 8) == -1) {
+      perror ("/dev/urandom");
+      exit (EXIT_FAILURE);
+    }
+  } else {
+    char *p;
+
+    /* If the hostname is an FQDN, truncate before the first dot. */
+    p = strchr (hostname, '.');
+    if (p && p > hostname)
+      *p = '\0';
+  }
+  config->guestname = strdup (hostname);
+
+  /* Defaults for #vcpus and memory are taken from the physical machine. */
+  i = sysconf (_SC_NPROCESSORS_ONLN);
+  if (i == -1) {
+    perror ("sysconf: _SC_NPROCESSORS_ONLN");
+    config->vcpus = 1;
+  }
+  else if (i == 0)
+    config->vcpus = 1;
+  else
+    config->vcpus = i;
+
+  i = sysconf (_SC_PHYS_PAGES);
+  if (i == -1) {
+    perror ("sysconf: _SC_PHYS_PAGES");
+    config->memory = 1024 * 1024 * 1024;
+  }
+  else
+    config->memory = i;
+
+  i  = sysconf (_SC_PAGESIZE);
+  if (i == -1) {
+    perror ("sysconf: _SC_PAGESIZE");
+    config->memory *= 4096;
+  }
+  else
+    config->memory *= i;
+
+  /* Round up the default memory to a power of 2, since the kernel
+   * memory is not included in the total physical pages returned
+   * above.
+   * http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
+   */
+  config->memory--;
+  config->memory |= config->memory >> 1;
+  config->memory |= config->memory >> 2;
+  config->memory |= config->memory >> 4;
+  config->memory |= config->memory >> 8;
+  config->memory |= config->memory >> 16;
+  config->memory |= config->memory >> 32;
+  config->memory++;
+
+  find_all_disks ();
+  config->disks = guestfs___copy_string_list (all_disks);
+  if (all_removable)
+    config->removable = guestfs___copy_string_list (all_removable);
+  find_all_interfaces ();
+  if (all_interfaces)
+    config->interfaces = guestfs___copy_string_list (all_interfaces);
+}
+
+static int
+compare (const void *vp1, const void *vp2)
+{
+  char * const *p1 = (char * const *) vp1;
+  char * const *p2 = (char * const *) vp2;
+  return strcmp (*p1, *p2);
+}
+
+static void
+find_all_disks (void)
+{
+  DIR *dir;
+  struct dirent *d;
+  size_t nr_disks = 0, nr_removable = 0;
+
+  /* The default list of disks is everything in /sys/block which
+   * matches the common patterns for disk names.
+   */
+  dir = opendir ("/sys/block");
+  if (!dir) {
+    perror ("opendir");
+    exit (EXIT_FAILURE);
+  }
+
+  for (;;) {
+    errno = 0;
+    d = readdir (dir);
+    if (!d) break;
+
+    if (STRPREFIX (d->d_name, "cciss!") ||
+        STRPREFIX (d->d_name, "hd") ||
+        STRPREFIX (d->d_name, "sd") ||
+        STRPREFIX (d->d_name, "ubd") ||
+        STRPREFIX (d->d_name, "vd")) {
+      char *p;
+
+      nr_disks++;
+      all_disks = realloc (all_disks, sizeof (char *) * (nr_disks + 1));
+      if (!all_disks) {
+        perror ("realloc");
+        exit (EXIT_FAILURE);
+      }
+
+      all_disks[nr_disks-1] = strdup (d->d_name);
+
+      /* cciss device /dev/cciss/c0d0 will be /sys/block/cciss!c0d0 */
+      p = strchr (all_disks[nr_disks-1], '!');
+      if (p) *p = '/';
+
+      all_disks[nr_disks] = NULL;
+    }
+    else if (STRPREFIX (d->d_name, "sr")) {
+      nr_removable++;
+      all_removable = realloc (all_removable,
+                               sizeof (char *) * (nr_removable + 1));
+      if (!all_removable) {
+        perror ("realloc");
+        exit (EXIT_FAILURE);
+      }
+      all_removable[nr_removable-1] = strdup (d->d_name);
+      all_removable[nr_removable] = NULL;
+    }
+  }
+
+  /* Check readdir didn't fail */
+  if (errno != 0) {
+    perror ("readdir: /sys/block");
+    exit (EXIT_FAILURE);
+  }
+
+  /* Close the directory handle */
+  if (closedir (dir) == -1) {
+    perror ("closedir: /sys/block");
+    exit (EXIT_FAILURE);
+  }
+
+  if (all_disks == NULL) {
+    fprintf (stderr, "%s: error: no non-removable disks were discovered on this machine.\n",
+             program_name);
+    fprintf (stderr, "virt-p2v looked in /sys/block.\n");
+    fprintf (stderr, "This is a fatal error and virt-p2v cannot continue.\n");
+    exit (EXIT_FAILURE);
+  }
+
+  qsort (all_disks, nr_disks, sizeof (char *), compare);
+  if (all_removable)
+    qsort (all_removable, nr_removable, sizeof (char *), compare);
+}
+
+static void
+find_all_interfaces (void)
+{
+  DIR *dir;
+  struct dirent *d;
+  size_t nr_interfaces = 0;
+
+  /* The default list of network interfaces is everything in
+   * /sys/class/net which matches some common patterns.
+   */
+  dir = opendir ("/sys/class/net");
+  if (!dir) {
+    perror ("opendir");
+    exit (EXIT_FAILURE);
+  }
+
+  for (;;) {
+    errno = 0;
+    d = readdir (dir);
+    if (!d) break;
+
+    /* For systemd predictable names, see:
+     * http://cgit.freedesktop.org/systemd/systemd/tree/src/udev/udev-builtin-net_id.c#n20
+     * biosdevname is also a possibility here.
+     * Ignore PPP, SLIP, WWAN, bridges, etc.
+     */
+    if (STRPREFIX (d->d_name, "em") ||
+        STRPREFIX (d->d_name, "en") ||
+        STRPREFIX (d->d_name, "eth") ||
+        STRPREFIX (d->d_name, "wl")) {
+      nr_interfaces++;
+      all_interfaces =
+        realloc (all_interfaces, sizeof (char *) * (nr_interfaces + 1));
+      if (!all_interfaces) {
+        perror ("realloc");
+        exit (EXIT_FAILURE);
+      }
+      all_interfaces[nr_interfaces-1] = strdup (d->d_name);
+      all_interfaces[nr_interfaces] = NULL;
+    }
+  }
+
+  /* Check readdir didn't fail */
+  if (errno != 0) {
+    perror ("readdir: /sys/class/net");
+    exit (EXIT_FAILURE);
+  }
+
+  /* Close the directory handle */
+  if (closedir (dir) == -1) {
+    perror ("closedir: /sys/class/net");
+    exit (EXIT_FAILURE);
+  }
+
+  if (all_interfaces)
+    qsort (all_interfaces, nr_interfaces, sizeof (char *), compare);
+}
+
+/* Read /proc/cmdline. */
+static char *
+read_cmdline (void)
+{
+  int fd;
+  size_t len = 0;
+  ssize_t n;
+  char buf[256];
+  char *r = NULL, *newr;
+
+  fd = open ("/proc/cmdline", O_RDONLY|O_CLOEXEC);
+  if (fd == -1) {
+    perror ("/proc/cmdline");
+    return NULL;
+  }
+
+  for (;;) {
+    n = read (fd, buf, sizeof buf);
+    if (n == -1) {
+      perror ("read");
+      free (r);
+      close (fd);
+      return NULL;
+    }
+    if (n == 0)
+      break;
+    newr = realloc (r, len + n + 1); /* + 1 is for terminating NUL */
+    if (newr == NULL) {
+      perror ("realloc");
+      free (r);
+      close (fd);
+      return NULL;
+    }
+    r = newr;
+    memcpy (&r[len], buf, n);
+    len += n;
+  }
+
+  if (r)
+    r[len] = '\0';
+
+  if (close (fd) == -1) {
+    perror ("close");
+    free (r);
+    return NULL;
+  }
+
+  return r;
+}
diff --git a/p2v/miniexpect.c b/p2v/miniexpect.c
new file mode 100644
index 0000000..7f02584
--- /dev/null
+++ b/p2v/miniexpect.c
@@ -0,0 +1,364 @@
+/* miniexpect
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <poll.h>
+#include <errno.h>
+#include <termios.h>
+#include <time.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+
+#include <pcre.h>
+
+#include "miniexpect.h"
+
+#define DEBUG 0
+
+static mexp_h *
+create_handle (void)
+{
+  mexp_h *h = malloc (sizeof *h);
+  if (h == NULL)
+    return NULL;
+
+  /* Initialize the fields to default values. */
+  h->fd = -1;
+  h->pid = 0;
+  h->timeout = 60000;
+  h->read_size = 1024;
+  h->pcre_error = 0;
+  h->buffer = NULL;
+  h->len = h->alloc = 0;
+  h->user1 = h->user2 = h->user3 = NULL;
+
+  return h;
+}
+
+static void
+clear_buffer (mexp_h *h)
+{
+  free (h->buffer);
+  h->buffer = NULL;
+  h->alloc = h->len = 0;
+}
+
+int
+mexp_close (mexp_h *h)
+{
+  int status = 0;
+
+  free (h->buffer);
+
+  if (h->fd >= 0)
+    close (h->fd);
+  if (h->pid > 0) {
+    if (waitpid (h->pid, &status, 0) == -1)
+      return -1;
+  }
+
+  free (h);
+
+  return status;
+}
+
+mexp_h *
+mexp_spawnl (const char *file, const char *arg, ...)
+{
+  char **argv, **new_argv;
+  size_t i;
+  va_list args;
+  mexp_h *h;
+
+  argv = malloc (sizeof (char *));
+  if (argv == NULL)
+    return NULL;
+  argv[0] = (char *) arg;
+
+  va_start (args, arg);
+  for (i = 1; arg != NULL; ++i) {
+    arg = va_arg (args, const char *);
+    new_argv = realloc (argv, sizeof (char *) * (i+1));
+    if (new_argv == NULL) {
+      free (argv);
+      return NULL;
+    }
+    argv = new_argv;
+    argv[i] = (char *) arg;
+  }
+
+  h = mexp_spawnv (file, argv);
+  free (argv);
+  return h;
+}
+
+mexp_h *
+mexp_spawnv (const char *file, char **argv)
+{
+  mexp_h *h;
+  int fd = -1;
+  int err;
+  char slave[1024];
+  pid_t pid = 0;
+
+  fd = posix_openpt (O_RDWR|O_NOCTTY);
+  if (fd == -1)
+    goto error;
+
+  if (grantpt (fd) == -1)
+    goto error;
+
+  if (unlockpt (fd) == -1)
+    goto error;
+
+  /* Get the slave pty name now, but don't open it in the parent. */
+  if (ptsname_r (fd, slave, sizeof slave) != 0)
+    goto error;
+
+  /* Create the handle last before we fork. */
+  h = create_handle ();
+  if (h == NULL)
+    goto error;
+
+  pid = fork ();
+  if (pid == -1)
+    goto error;
+
+  if (pid == 0) {               /* Child. */
+    struct termios terminal_settings;
+    int slave_fd;
+
+    setsid ();
+
+    /* Open the slave side of the pty.  We must do this in the child
+     * after setsid so it becomes our controlling tty.
+     */
+    slave_fd = open (slave, O_RDWR);
+    if (slave_fd == -1)
+      goto error;
+
+    /* Set raw mode. */
+    tcgetattr (slave_fd, &terminal_settings);
+    cfmakeraw (&terminal_settings);
+    tcsetattr (slave_fd, TCSANOW, &terminal_settings);
+
+    /* Set up stdin, stdout, stderr to point to the pty. */
+    dup2 (slave_fd, 0);
+    dup2 (slave_fd, 1);
+    dup2 (slave_fd, 2);
+    close (slave_fd);
+
+    /* Close the master side of the pty - do this late to avoid a
+     * kernel bug, see sshpass source code.
+     */
+    close (fd);
+
+    /* Run the subprocess. */
+    execvp (file, argv);
+    perror (file);
+    _exit (EXIT_FAILURE);
+  }
+
+  /* Parent. */
+
+  h->fd = fd;
+  h->pid = pid;
+  return h;
+
+ error:
+  err = errno;
+  if (fd >= 0)
+    close (fd);
+  if (pid > 0)
+    waitpid (pid, NULL, 0);
+  errno = err;
+  return NULL;
+}
+
+enum mexp_status
+mexp_expect (mexp_h *h, const mexp_regexp *regexps, int *ovector, int ovecsize)
+{
+  time_t start_t, now_t;
+  int timeout;
+  struct pollfd pfds[1];
+  int r;
+  ssize_t rs;
+
+  time (&start_t);
+
+  /* Clear the read buffer. */
+  /* XXX This is possibly incorrect because it throws away inputs that
+   * may not have been matched yet.  A better idea is to record the
+   * end of the previous match and only throw that away.
+   */
+  clear_buffer (h);
+
+  for (;;) {
+    /* If we've got a timeout then work out how many seconds are left.
+     * Timeout == 0 is not particularly well-defined, but it probably
+     * means "return immediately if there's no data to be read".
+     */
+    if (h->timeout >= 0) {
+      time (&now_t);
+      timeout = h->timeout - ((now_t - start_t) * 1000);
+      if (timeout < 0)
+        timeout = 0;
+    }
+    else
+      timeout = 0;
+
+    pfds[0].fd = h->fd;
+    pfds[0].events = POLLIN;
+    pfds[0].revents = 0;
+    r = poll (pfds, 1, timeout);
+#if DEBUG
+    fprintf (stderr, "DEBUG: poll returned %d\n", r);
+#endif
+    if (r == -1)
+      return MEXP_ERROR;
+
+    if (r == 0)
+      return MEXP_TIMEOUT;
+
+    /* Otherwise we expect there is something to read from the file
+     * descriptor.
+     */
+    if (h->alloc - h->len <= h->read_size) {
+      char *new_buffer;
+      /* +1 here allows us to store \0 after the data read */
+      new_buffer = realloc (h->buffer, h->alloc + h->read_size + 1);
+      if (new_buffer == NULL)
+        return MEXP_ERROR;
+      h->buffer = new_buffer;
+      h->alloc += h->read_size;
+    }
+    rs = read (h->fd, h->buffer + h->len, h->read_size);
+#if DEBUG
+    fprintf (stderr, "DEBUG: read returned %zd\n", rs);
+#endif
+    if (rs == -1) {
+      /* Annoyingly on Linux (I'm fairly sure this is a bug) if the
+       * writer closes the connection, the entire pty is destroyed,
+       * and read returns -1 / EIO.  Handle that special case here.
+       */
+      if (errno == EIO)
+        return MEXP_EOF;
+      return MEXP_ERROR;
+    }
+    if (rs == 0)
+      return MEXP_EOF;
+
+    /* We read something. */
+    h->len += rs;
+    h->buffer[h->len] = '\0';
+#if DEBUG
+    fprintf (stderr, "DEBUG: read %zd bytes from pty\n", rs);
+    fprintf (stderr, "DEBUG: buffer content: %s\n", h->buffer);
+#endif
+
+    /* See if there is a full or partial match against any regexp. */
+    if (regexps) {
+      size_t i;
+      int can_clear_buffer = 1;
+
+      assert (h->buffer != NULL);
+
+      for (i = 0; regexps[i].r > 0; ++i) {
+        int options = regexps[i].options | PCRE_PARTIAL_SOFT;
+
+        r = pcre_exec (regexps[i].re, regexps[i].extra,
+                       h->buffer, (int)h->len, 0,
+                       options,
+                       ovector, ovecsize);
+        h->pcre_error = r;
+
+        if (r >= 0) {
+          /* A full match. */
+          return regexps[i].r;
+        }
+
+        else if (r == PCRE_ERROR_NOMATCH) {
+          /* No match at all. */
+          /* (nothing here) */
+        }
+
+        else if (r == PCRE_ERROR_PARTIAL) {
+          /* Partial match.  Keep the buffer and keep reading. */
+          can_clear_buffer = 0;
+        }
+
+        else {
+          /* An actual PCRE error. */
+          return MEXP_PCRE_ERROR;
+        }
+      }
+
+      /* If none of the regular expressions matched (not partially)
+       * then we can clear the buffer.  This is an optimization.
+       */
+      if (can_clear_buffer)
+        clear_buffer (h);
+
+    } /* if (regexps) */
+  }
+}
+
+int
+mexp_printf (mexp_h *h, const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+  int len;
+  size_t n;
+  ssize_t r;
+  char *p;
+
+  va_start (args, fs);
+  len = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (len < 0)
+    return -1;
+
+#if DEBUG
+  fprintf (stderr, "DEBUG: writing: %s\n", msg);
+#endif
+
+  n = len;
+  p = msg;
+  while (n > 0) {
+    r = write (h->fd, p, n);
+    if (r == -1) {
+      free (msg);
+      return -1;
+    }
+    n -= r;
+    p += r;
+  }
+
+  free (msg);
+  return len;
+}
diff --git a/p2v/miniexpect.h b/p2v/miniexpect.h
new file mode 100644
index 0000000..9a374b7
--- /dev/null
+++ b/p2v/miniexpect.h
@@ -0,0 +1,81 @@
+/* miniexpect
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* ** NOTE ** All API documentation is in the manual page.
+ *
+ * To read the manual page from the source directory, do:
+ *    man ./miniexpect.3
+ * If you have installed miniexpect, do:
+ *    man 3 miniexpect
+ *
+ * The source for the manual page is miniexpect.pod.
+ */
+
+#ifndef MINIEXPECT_H_
+#define MINIEXPECT_H_
+
+#include <unistd.h>
+
+#include <pcre.h>
+
+/* This handle is created per subprocess that is spawned. */
+struct mexp_h {
+  int fd;
+  pid_t pid;
+  int timeout;
+  char *buffer;
+  size_t len;
+  size_t alloc;
+  size_t read_size;
+  int pcre_error;
+  void *user1;
+  void *user2;
+  void *user3;
+};
+typedef struct mexp_h mexp_h;
+
+/* Spawn a subprocess. */
+extern mexp_h *mexp_spawnv (const char *file, char **argv);
+extern mexp_h *mexp_spawnl (const char *file, const char *arg, ...);
+
+/* Close the handle. */
+extern int mexp_close (mexp_h *h);
+
+/* Expect. */
+struct mexp_regexp {
+  int r;
+  const pcre *re;
+  const pcre_extra *extra;
+  int options;
+};
+typedef struct mexp_regexp mexp_regexp;
+
+enum mexp_status {
+  MEXP_EOF        = 0,
+  MEXP_ERROR      = -1,
+  MEXP_PCRE_ERROR = -2,
+  MEXP_TIMEOUT    = -3,
+};
+
+extern int mexp_expect (mexp_h *h, const mexp_regexp *regexps,
+                        int *ovector, int ovecsize);
+
+extern int mexp_printf (mexp_h *h, const char *fs, ...)
+  __attribute__((format(printf,2,3)));
+
+#endif /* MINIEXPECT_H_ */
diff --git a/p2v/p2v.h b/p2v/p2v.h
new file mode 100644
index 0000000..c75efda
--- /dev/null
+++ b/p2v/p2v.h
@@ -0,0 +1,100 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef P2V_H
+#define P2V_H
+
+/* Send various debug information to stderr.  Harmless and useful, so
+ * can be left enabled in production builds.
+ */
+#define DEBUG_STDERR 1
+
+/* Force remote debugging even if user doesn't enable it.  Since
+ * remote debugging is mostly free, we might as well enable this even
+ * in production.
+ */
+#define FORCE_REMOTE_DEBUG 1
+
+/* We don't use libguestfs directly here, and we don't link to it
+ * either (in fact, we don't want libguestfs on the ISO).  However
+ * we include this just so that we can use the convenience macros in
+ * guestfs-internal-frontend.h.
+ */
+#include "guestfs.h"
+#include "guestfs-internal-frontend.h"
+
+/* Ensure we don't use libguestfs. */
+#define guestfs_h DO_NOT_USE
+
+/* All disks / removable media / network interfaces discovered
+ * when the program started.  Do not change these.
+ */
+extern char **all_disks;
+extern char **all_removable;
+extern char **all_interfaces;
+
+/* config.c */
+struct config {
+  int verbose;
+  char *server;
+  int port;
+  char *username;
+  char *password;
+  int sudo;
+  char *guestname;
+  int vcpus;
+  uint64_t memory;
+  char **disks;
+  char **removable;
+  char **interfaces;
+};
+
+extern struct config *new_config (void);
+extern struct config *copy_config (struct config *);
+extern void free_config (struct config *);
+
+/* kernel.c */
+extern void kernel_configuration (struct config *, const char *cmdline);
+
+/* gui.c */
+extern void gui_application (struct config *);
+
+/* conversion.c */
+extern int start_conversion (struct config *, void (*notify_ui) (int type, const char *data));
+#define NOTIFY_LOG_DIR        1  /* location of remote log directory */
+#define NOTIFY_REMOTE_MESSAGE 2  /* log message from remote virt-v2v */
+extern const char *get_conversion_error (void);
+
+/* ssh.c */
+extern int test_connection (struct config *);
+extern int open_data_connection (struct config *);
+extern int start_remote_connection (struct config *, const char *remote_dir, const char *libvirt_xml);
+extern const char *get_ssh_error (void);
+
+/* virt-v2v version and features (read from remote). */
+extern int v2v_major;
+extern int v2v_minor;
+extern int v2v_release;
+
+/* authors.c */
+extern const char *authors[];
+
+/* copying.c */
+extern const char *gplv2plus;
+
+#endif /* P2V_H */
diff --git a/p2v/ssh.c b/p2v/ssh.c
new file mode 100644
index 0000000..2a872dc
--- /dev/null
+++ b/p2v/ssh.c
@@ -0,0 +1,427 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* This file handles the ssh connections to the conversion server.
+ *
+ * virt-p2v will open several connections over the lifetime of
+ * the conversion process.
+ *
+ * In 'test_connection', it will first open a connection (to check it
+ * is possible) and query virt-v2v on the server to ensure it exists,
+ * it is the right version, and so on.  This connection is then
+ * closed, because in the GUI case we don't want to deal with keeping
+ * it alive in case the administrator has set up an autologout.
+ *
+ * Once we start conversion, we will open a control connection to send
+ * the libvirt configuration data and to start up virt-v2v, and we
+ * will open up one data connection per local hard disk.  The data
+ * connection(s) have a reverse port forward to the local qemu-nbd
+ * server which is serving the content of that hard disk.  The remote
+ * port for each data connection is assigned by ssh.  See
+ * 'open_data_connection' and 'start_remote_conversion'.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <errno.h>
+#include <locale.h>
+#include <assert.h>
+#include <libintl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "ignore-value.h"
+
+#include "miniexpect.h"
+#include "p2v.h"
+
+int v2v_major;
+int v2v_minor;
+int v2v_release;
+
+static char *ssh_error;
+
+static void set_ssh_error (const char *fs, ...)
+  __attribute__((format(printf,1,2)));
+
+static void
+set_ssh_error (const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+  int len;
+
+  va_start (args, fs);
+  len = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (len < 0) {
+    perror ("vasprintf");
+    fprintf (stderr, "original error format string: %s\n", fs);
+    exit (EXIT_FAILURE);
+  }
+
+  free (ssh_error);
+  ssh_error = msg;
+}
+
+const char *
+get_ssh_error (void)
+{
+  return ssh_error;
+}
+
+static void compile_regexps (void) __attribute__((constructor));
+static void free_regexps (void) __attribute__((destructor));
+
+static pcre *password_re;
+static pcre *prompt_re;
+static pcre *version_re;
+static pcre *libguestfs_rewrite_re;
+
+static void
+compile_regexps (void)
+{
+  const char *err;
+  int offset;
+
+#define COMPILE(re,pattern,options)                                     \
+  do {                                                                  \
+    re = pcre_compile ((pattern), (options), &err, &offset, NULL);      \
+    if (re == NULL) {                                                   \
+      ignore_value (write (2, err, strlen (err)));                      \
+      abort ();                                                         \
+    }                                                                   \
+  } while (0)
+
+  COMPILE (password_re, "assword", 0);
+  COMPILE (prompt_re, "[$#]", 0);
+  COMPILE (version_re,
+           "virt-v2v ([1-9][0-9]*)\\.([1-9][0-9]*)\\.([1-9][0-9]*)", 0);
+  COMPILE (libguestfs_rewrite_re, "libguestfs-rewrite", 0);
+}
+
+static void
+free_regexps (void)
+{
+  pcre_free (password_re);
+  pcre_free (prompt_re);
+  pcre_free (version_re);
+  pcre_free (libguestfs_rewrite_re);
+}
+
+/* Start ssh subprocess with the standard arguments and possibly some
+ * optional arguments.  Also handles password authentication.
+ */
+static mexp_h *
+start_ssh (struct config *config, char **extra_args)
+{
+  size_t i, j, nr_args;
+  char port_str[64];
+  CLEANUP_FREE /* [sic] */ const char **args = NULL;
+  mexp_h *h;
+  const int ovecsize = 12;
+  int ovector[ovecsize];
+
+  /* Create the ssh argument array. */
+  nr_args = 0;
+  if (extra_args != NULL) {
+    for (i = 0; extra_args[i] != NULL; ++i)
+      nr_args++;
+  }
+
+  nr_args += 11;
+  args = malloc (sizeof (char *) * nr_args);
+  if (args == NULL) {
+    perror ("malloc");
+    exit (EXIT_FAILURE);
+  }
+  j = 0;
+  args[j++] = "ssh";
+  args[j++] = "-p";             /* Port. */
+  snprintf (port_str, sizeof port_str, "%d", config->port);
+  args[j++] = port_str;
+  args[j++] = "-l";             /* Username. */
+  args[j++] = config->username ? config->username : "root";
+  args[j++] = "-o";             /* Host key will always be novel. */
+  args[j++] = "StrictHostKeyChecking=no";
+  args[j++] = "-o";            /* Only use password authentication. */
+  args[j++] = "PreferredAuthentications=keyboard-interactive,password";
+  if (extra_args != NULL) {
+    for (i = 0; extra_args[i] != NULL; ++i)
+      args[j++] = extra_args[i];
+  }
+  args[j++] = config->server;   /* Conversion server. */
+  args[j++] = NULL;
+  assert (j == nr_args);
+
+  h = mexp_spawnv ("ssh", (char **) args);
+  if (h == NULL)
+    return NULL;
+
+  if (config->password && strlen (config->password) > 0) {
+    /* Wait for the password prompt. */
+    switch (mexp_expect (h,
+                         (mexp_regexp[]) {
+                           { 100, .re = password_re },
+                           { 0 }
+                         }, ovector, ovecsize)) {
+    case 100:                   /* Got password prompt. */
+      if (mexp_printf (h, "%s\n", config->password) == -1) {
+        set_ssh_error ("mexp_printf: %m");
+        mexp_close (h);
+        return NULL;
+      }
+      break;
+
+    case MEXP_EOF:
+      mexp_close (h);
+      set_ssh_error ("unexpected end of file waiting for password prompt");
+      return NULL;
+
+    case MEXP_TIMEOUT:
+      mexp_close (h);
+      set_ssh_error ("timeout waiting for password prompt");
+      return NULL;
+
+    case MEXP_ERROR:
+      set_ssh_error ("mexp_expect: %m");
+      mexp_close (h);
+      return NULL;
+
+    case MEXP_PCRE_ERROR:
+      set_ssh_error ("PCRE error: %d\n", h->pcre_error);
+      mexp_close (h);
+      return NULL;
+    }
+  }
+
+  /* Wait for the prompt. */
+  switch (mexp_expect (h,
+                       (mexp_regexp[]) {
+                         { 100, .re = password_re },
+                         { 101, .re = prompt_re },
+                         { 0 }
+                       }, ovector, ovecsize)) {
+  case 100:                    /* Got password prompt unexpectedly. */
+    if (mexp_printf (h, "%s\n", config->password) == -1) {
+      mexp_close (h);
+      set_ssh_error ("unexpected password prompt: probably the password supplied is wrong");
+      return NULL;
+    }
+    break;
+
+  case 101:                    /* Got prompt. */
+    break;
+
+  case MEXP_EOF:
+    mexp_close (h);
+    set_ssh_error ("unexpected end of file waiting for command prompt");
+    return NULL;
+
+  case MEXP_TIMEOUT:
+    mexp_close (h);
+    set_ssh_error ("timeout waiting for command prompt");
+    return NULL;
+
+  case MEXP_ERROR:
+    set_ssh_error ("mexp_expect: %m");
+    mexp_close (h);
+    return NULL;
+
+  case MEXP_PCRE_ERROR:
+    set_ssh_error ("PCRE error: %d\n", h->pcre_error);
+    mexp_close (h);
+    return NULL;
+  }
+
+  return h;
+}
+
+#pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn" /* WTF? */
+int
+test_connection (struct config *config)
+{
+  mexp_h *h;
+  CLEANUP_FREE char *major_str = NULL, *minor_str = NULL, *release_str = NULL;
+  int feature_libguestfs_rewrite = 0;
+  int status;
+  const int ovecsize = 12;
+  int ovector[ovecsize];
+
+  h = start_ssh (config, NULL);
+  if (h == NULL)
+    return -1;
+
+  /* Send 'virt-v2v -V' command and hope we get back a version string. */
+  if (mexp_printf (h, "%svirt-v2v -V\n", config->sudo ? "sudo " : "") == -1) {
+    set_ssh_error ("mexp_printf: %m");
+    mexp_close (h);
+    return -1;
+  }
+
+  switch (mexp_expect (h,
+                       (mexp_regexp[]) {
+                         { 100, .re = version_re },
+                         { 101, .re = prompt_re },
+                         { 0 }
+                       }, ovector, ovecsize)) {
+  case 100:                     /* Got version string. */
+    major_str = strndup (&h->buffer[ovector[2]], ovector[3]-ovector[2]);
+    minor_str = strndup (&h->buffer[ovector[4]], ovector[5]-ovector[4]);
+    release_str = strndup (&h->buffer[ovector[6]], ovector[7]-ovector[6]);
+    sscanf (major_str, "%d", &v2v_major);
+    sscanf (minor_str, "%d", &v2v_minor);
+    sscanf (release_str, "%d", &v2v_release);
+#if DEBUG_STDERR
+    fprintf (stderr, "%s: remote virt-v2v version: %d.%d.%d\n",
+             program_name, v2v_major, v2v_minor, v2v_release);
+#endif
+    if (v2v_major < 1 || v2v_major > 1) {
+      mexp_close (h);
+      set_ssh_error ("invalid version major (%d)", v2v_major);
+      return -1;
+    }
+    break;
+
+  case 101:                     /* Got the prompt, but no version string. */
+    mexp_close (h);
+    set_ssh_error ("virt-v2v is not installed on the conversion server, "
+                   "or it might be a too old version");
+    return -1;
+
+  case MEXP_EOF:
+    mexp_close (h);
+    set_ssh_error ("unexpected end of file waiting virt-v2v -V output");
+    return -1;
+
+  case MEXP_TIMEOUT:
+    mexp_close (h);
+    set_ssh_error ("timeout waiting for virt-v2v -V output");
+    return -1;
+
+  case MEXP_ERROR:
+    set_ssh_error ("mexp_expect: %m");
+    mexp_close (h);
+    return -1;
+
+  case MEXP_PCRE_ERROR:
+    set_ssh_error ("PCRE error: %d\n", h->pcre_error);
+    mexp_close (h);
+    return -1;
+  }
+
+  /* Get virt-v2v features.  See: v2v/cmdline.ml */
+  if (mexp_printf (h, "%svirt-v2v --machine-readable\n",
+                   config->sudo ? "sudo " : "") == -1) {
+    set_ssh_error ("mexp_printf: %m");
+    mexp_close (h);
+    return -1;
+  }
+
+  switch (mexp_expect (h,
+                       (mexp_regexp[]) {
+                         { 100, .re = libguestfs_rewrite_re },
+                         { 0 }
+                       }, ovector, ovecsize)) {
+  case 100:                     /* Got feature: libguestfs-rewrite. */
+    feature_libguestfs_rewrite = 1;
+    break;
+
+  case MEXP_EOF:
+    mexp_close (h);
+    set_ssh_error ("unexpected end of file waiting virt-v2v --machine-readable output");
+    return -1;
+
+  case MEXP_TIMEOUT:
+    mexp_close (h);
+    set_ssh_error ("timeout waiting virt-v2v --machine-readable output");
+    return -1;
+
+  case MEXP_ERROR:
+    set_ssh_error ("mexp_expect: %m");
+    mexp_close (h);
+    return -1;
+
+  case MEXP_PCRE_ERROR:
+    set_ssh_error ("PCRE error: %d\n", h->pcre_error);
+    mexp_close (h);
+    return -1;
+  }
+
+  if (!feature_libguestfs_rewrite) {
+    mexp_close (h);
+    set_ssh_error ("invalid output of virt-v2v --machine-readable command");
+    return -1;
+  }
+
+  /* Test finished, shut down ssh. */
+  if (mexp_printf (h, "exit\n") == -1) {
+    set_ssh_error ("mexp_printf: %m");
+    mexp_close (h);
+    return -1;
+  }
+
+  switch (mexp_expect (h, NULL, NULL, 0)) {
+  case MEXP_EOF:
+    break;
+
+  case MEXP_TIMEOUT:
+    mexp_close (h);
+    set_ssh_error ("timeout waiting for end of ssh session");
+    return -1;
+
+  case MEXP_ERROR:
+    set_ssh_error ("mexp_expect: %m");
+    mexp_close (h);
+    return -1;
+
+  case MEXP_PCRE_ERROR:
+    set_ssh_error ("PCRE error: %d\n", h->pcre_error);
+    mexp_close (h);
+    return -1;
+  }
+
+  status = mexp_close (h);
+  if (!((WIFEXITED (status) && WEXITSTATUS (status) == 0)
+        || (WIFSIGNALED (status) && WTERMSIG (status) == SIGHUP))) {
+    set_ssh_error ("unexpected close status from ssh subprocess (%d)", status);
+    return -1;
+  }
+
+  return 0;
+}
+
+int
+open_data_connection (struct config *config)
+{
+  return -1;
+}
+
+int
+start_remote_connection (struct config *config,
+                         const char *remote_dir, const char *libvirt_xml)
+{
+  return -1;
+}
diff --git a/p2v/virt-p2v.pod b/p2v/virt-p2v.pod
new file mode 100644
index 0000000..0837abd
--- /dev/null
+++ b/p2v/virt-p2v.pod
@@ -0,0 +1,219 @@
+=head1 NAME
+
+virt-p2v - Convert a physical machine to use KVM
+
+=head1 SYNOPSIS
+
+ virt-p2v
+
+ virt-p2v.iso
+
+=head1 DESCRIPTION
+
+Virt-p2v converts a physical machine to run virtualized on KVM,
+managed by libvirt or Red Hat Enterprise Virtualisation (RHEV) version
+2.2 or later.
+
+Normally you don't run the virt-p2v program directly.  Instead you
+have to boot the physical machine using the bootable CD-ROM, ISO or
+PXE image.  This bootable image contains the virt-p2v binary and runs
+it automatically.  This manual page documents both the binary and the
+bootable image.
+
+=head1 NETWORK SETUP
+
+Virt-p2v runs on the physical machine which you want to convert.  It
+has to talk to another server called the "conversion server" which
+must have L<virt-v2v(1)> installed on it.  It always talks to the
+conversion server over SSH:
+
+ +-----------+                +-------------+
+ | virt-p2v  |                | virt-v2v    |
+ | (physical | ssh connection | (conversion |
+ |  server)  ----------------->  server)    |
+ +-----------+                +-------------+
+
+The virt-v2v program on the conversion server does the actual
+conversion (physical to virtual, and virtual to virtual conversions
+are sufficiently similar that we use the same program to do both).
+
+The SSH connection is always initiated from the physical server.  All
+data is transferred over the SSH connection.  In terms of firewall and
+network configuration, you only need to ensure that the physical
+server has access to a port (usually TCP port 22) on the conversion
+server.  (Note that the physical machine may reconnect several times
+during the conversion process.)
+
+The conversion server does not need to be a physical machine.  It
+could be a virtual machine, as long as it has sufficient memory and
+disk space to do the conversion, and as long as the physical machine
+can connect directly to its SSH port.
+
+Because all of the data on the physical server's hard drive(s) has to
+be copied over the network, the speed of conversion is largely
+determined by the speed of the network between the two machines.
+
+=head1 GUI INTERACTIVE CONFIGURATION
+
+When you start virt-p2v, you'll see a graphical configuration dialog
+that walks you through connection to the conversion server, asks for
+the password, which local hard disks you want to convert, and other
+things like the name of the guest to create and the number of virtual
+CPUs to give it.
+
+=head1 KERNEL COMMAND LINE CONFIGURATION
+
+If you don't want to configure things using the graphical UI, an
+alternative is to configure through the kernel command line.  This is
+especially convenient if you are converting a lot of physical machines
+which are booted using PXE.
+
+Where exactly you set command line arguments depends on your PXE
+implementation, but for pxelinux you put them in the C<APPEND> field
+in the C<pxelinux.cfg> file.  For example:
+
+ DEFAULT p2v
+ TIMEOUT 20
+ PROMPT 0
+ LABEL p2v
+   KERNEL virt-p2v-vmlinuz
+   APPEND initrd=virt-p2v-initrd p2v.server=conv.example.com p2v.password=secret
+
+You have to set some or all of the following command line arguments:
+
+=over 4
+
+=item B<p2v.server=SERVER>
+
+The name or IP address of the conversion server.
+
+This is always required if you are using the kernel configuration
+method.  If virt-p2v does not find this on the kernel command line
+then it switches to the GUI (interactive) configuration method.
+
+=item B<p2v.port=NN>
+
+The SSH port number on the conversion server (default: C<22>).
+
+=item B<p2v.username=USERNAME>
+
+The SSH username that we log in as on the conversion server
+(default: C<root>).
+
+=item B<p2v.password=PASSWORD>
+
+The SSH password that we use to log in to the conversion server.
+
+The default is to try with no password.  If this fails then virt-p2v
+will ask the user to type the password (probably several times during
+conversion).
+
+Note that virt-p2v does not support authentication using key
+distribution at this time.
+
+=item B<p2v.sudo>
+
+Use C<p2v.sudo> to tell virt-p2v to use L<sudo(8)> to gain root
+privileges on the conversion server after logging in as a non-root
+user (default: do not use sudo).
+
+=item B<p2v.name=GUESTNAME>
+
+The name of the guest that is created.  The default is to try to
+derive a name from the physical machine's hostname (if possible) else
+use a randomly generated name.
+
+=item B<p2v.vcpus=NN>
+
+The number of virtual CPUs to give to the guest.  The default is to
+use the same as the number of physical CPUs.
+
+=item B<p2v.memory=NN(M|G)>
+
+The size of the guest memory.  You can specify this in megabytes or
+gigabytes by using (eg) C<p2v.memory=1024M> or C<p2v.memory=1G>.  The
+default is to use the same amount of RAM as on the physical machine.
+
+=item B<p2v.debug>
+
+Use this to enable full debugging of virt-v2v.
+
+If asked to diagnose a problem with virt-p2v, you should add
+C<p2v.debug> to the kernel command line, and examine the log file
+which is left in C</tmp> on the conversion server.
+
+=item B<p2v.disks=sdX,sdY,..>
+
+A list of physical hard disks to convert, for example:
+
+ p2v.disks=sda,sdc
+
+The default is to convert all local hard disks that are found.
+
+=item B<p2v.removable=srX,srY,..>
+
+A list of removable media to convert.  The default is to create
+virtual removable devices for every physical removable device found.
+Note that the content of removable media is never copied over.
+
+=item B<p2v.interfaces=em1,..>
+
+A list of network interfaces to convert.  The default is to create
+virtual network interfaces for every physical network interface found.
+
+=item B<ip=dhcp>
+
+Use DHCP for configuring the network interface (this is the default).
+
+=begin comment
+
+=item B<ip=ADDR:GATEWAY:NETMASK>
+
+Set up a static IPv4 network configuration.
+
+=end comment
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display help.
+
+=item B<--cmdline=CMDLINE>
+
+This is used for debugging. Instead of parsing the kernel command line
+from C</proc/cmdline>, parse the string parameter C<CMDLINE>.
+
+=item B<-v>
+
+=item B<--verbose>
+
+Enable debugging (on the conversion server).
+
+=item B<-V>
+
+=item B<--version>
+
+Display version number and exit.
+
+=back
+
+=head1 SEE ALSO
+
+L<virt-v2v(1)>,
+L<qemu-nbd(1)>,
+L<http://libguestfs.org/>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones L<http://people.redhat.com/~rjones/>
+
+Matthew Booth
+
+=head1 COPYRIGHT
+
+Copyright (C) 2009-2014 Red Hat Inc.
diff --git a/po/POTFILES b/po/POTFILES
index b481157..02e1010 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -247,6 +247,15 @@ mllib/tty-c.c
 mllib/uri-c.c
 ocaml/guestfs-c-actions.c
 ocaml/guestfs-c.c
+p2v/authors.c
+p2v/config.c
+p2v/conversion.c
+p2v/copying.c
+p2v/gui.c
+p2v/kernel.c
+p2v/main.c
+p2v/miniexpect.c
+p2v/ssh.c
 perl/Guestfs.c
 perl/bindtests.pl
 perl/lib/Sys/Guestfs.pm
diff --git a/src/guestfs.pod b/src/guestfs.pod
index eb05404..55e556a 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -4370,6 +4370,10 @@ L<virt-make-fs(1)> command and documentation.
 Various libraries and common code used by L<virt-resize(1)> and
 the other tools which are written in OCaml.
 
+=item C<p2v>
+
+L<virt-p2v(1)> command and documentation.
+
 =item C<po>
 
 Translations of simple gettext strings.
@@ -4770,6 +4774,7 @@ L<virt-list-filesystems(1)>,
 L<virt-list-partitions(1)>,
 L<virt-ls(1)>,
 L<virt-make-fs(1)>,
+L<virt-p2v(1)>,
 L<virt-rescue(1)>,
 L<virt-resize(1)>,
 L<virt-sparsify(1)>,
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
index 138e73b..8faf20b 100644
--- a/v2v/virt-v2v.pod
+++ b/v2v/virt-v2v.pod
@@ -18,8 +18,8 @@ managed by libvirt or Red Hat Enterprise Virtualisation (RHEV) version
 2.2 or later. It can currently convert Red Hat Enterprise Linux and
 Windows guests running on Xen and VMware ESX.
 
-There is also a companion front-end called "virt-p2v" which comes as an
-ISO or CD image that can be booted on physical machines.
+There is also a companion front-end called L<virt-p2v(1)> which comes
+as an ISO or CD image that can be booted on physical machines.
 
 =head1 OPTIONS
 
@@ -283,6 +283,7 @@ For other environment variables, see L<guestfs(3)/ENVIRONMENT VARIABLES>.
 
 =head1 SEE ALSO
 
+L<virt-p2v(1)>,
 L<virt-df(1)>,
 L<virt-filesystems(1)>,
 L<guestfs(3)>,
-- 
1.9.0




More information about the Libguestfs mailing list