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 | 13 ++
fish/guestfish.pod | 1 +
p2v/Makefile.am | 86 +++++++++++
p2v/gui.c | 265 ++++++++++++++++++++++++++++++++++
p2v/main.c | 324 ++++++++++++++++++++++++++++++++++++++++++
p2v/miniexpect.c | 364 +++++++++++++++++++++++++++++++++++++++++++++++
p2v/miniexpect.h | 81 +++++++++++
p2v/p2v.h | 55 ++++++++
p2v/ssh.c | 408 +++++++++++++++++++++++++++++++++++++++++++++++++++++
p2v/virt-p2v.pod | 219 ++++++++++++++++++++++++++++
po/POTFILES | 4 +
src/guestfs.pod | 5 +
v2v/virt-v2v.pod | 5 +-
16 files changed, 1837 insertions(+), 2 deletions(-)
create mode 100644 p2v/Makefile.am
create mode 100644 p2v/gui.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 a8cd195..a27239f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -933,6 +933,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 +1655,7 @@ AC_CONFIG_FILES([Makefile
ocaml/META
ocaml/Makefile
ocaml/examples/Makefile
+ p2v/Makefile
perl/Makefile
perl/Makefile.PL
perl/examples/Makefile
@@ -1723,6 +1734,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..3f25e8c
--- /dev/null
+++ b/p2v/Makefile.am
@@ -0,0 +1,86 @@
+# 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 = \
+ gui.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/gui.c b/p2v/gui.c
new file mode 100644
index 0000000..44deb7a
--- /dev/null
+++ b/p2v/gui.c
@@ -0,0 +1,265 @@
+/* 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 test_connection_clicked (GtkWidget *w, gpointer data);
+static void *test_connection_thread (void *data);
+
+static GtkWidget *server_entry, *port_entry,
+ *username_entry, *password_entry, *sudo_button,
+ *spinner_hbox, *spinner, *spinner_message, *next_button;
+
+void
+gui_application (void)
+{
+ GtkWidget *dlg;
+ GtkWidget *intro, *table;
+ GtkWidget *server_label;
+ GtkWidget *port_label;
+ GtkWidget *username_label;
+ GtkWidget *password_label;
+ GtkWidget *test_hbox, *test;
+ char port_str[64];
+
+ /* Note that gtk_init etc have already been called in main(). */
+
+ dlg = gtk_dialog_new ();
+ gtk_window_set_title (GTK_WINDOW (dlg), program_name);
+
+ /* The main dialog area. */
+ intro = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (intro), TRUE);
+ gtk_label_set_text (GTK_LABEL (intro),
+ _("Connect to a virt-v2v conversion server over SSH:"));
+ 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 (server != NULL)
+ gtk_entry_set_text (GTK_ENTRY (server_entry), 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", 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 (username != NULL)
+ gtk_entry_set_text (GTK_ENTRY (username_entry), 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 (password != NULL)
+ gtk_entry_set_text (GTK_ENTRY (password_entry), 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), 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 (dlg)->vbox),
+ intro, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox),
+ table, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox),
+ test_hbox, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox),
+ spinner_hbox, TRUE, TRUE, 0);
+
+ /* Buttons. */
+ gtk_dialog_add_buttons (GTK_DIALOG (dlg),
+ _("Configure network ..."), 1,
+ _("About ..."), 2,
+ _("Next ..."), 3,
+ NULL);
+
+ next_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (dlg), 3);
+ gtk_widget_set_sensitive (next_button, FALSE);
+
+ /* Signals. */
+ g_signal_connect_swapped (G_OBJECT (dlg), "destroy",
+ G_CALLBACK (gtk_main_quit), NULL);
+ g_signal_connect (G_OBJECT (test), "clicked",
+ G_CALLBACK (test_connection_clicked), NULL);
+
+ /* Show everything except the spinner. */
+ gtk_widget_show_all (dlg);
+ gtk_widget_hide_all (spinner_hbox);
+
+ gtk_window_set_resizable (GTK_WINDOW (dlg), FALSE);
+
+ gtk_main ();
+ gdk_threads_leave ();
+}
+
+static void
+test_connection_clicked (GtkWidget *w, gpointer data)
+{
+ const gchar *port_str;
+ size_t errors = 0;
+ 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 (server);
+ server = strdup (gtk_entry_get_text (GTK_ENTRY (server_entry)));
+ if (STREQ (server, "")) {
+ gtk_label_set_text (GTK_LABEL (spinner_message),
+ _("error: No conversion server given."));
+ errors++;
+ }
+ port_str = gtk_entry_get_text (GTK_ENTRY (port_entry));
+ if (sscanf (port_str, "%d", &port) != 1 || port <= 0 || port >=
65536) {
+ gtk_label_set_text (GTK_LABEL (spinner_message),
+ _("error: Invalid port number. If in doubt, use
\"22\"."));
+ errors++;
+ }
+ free (username);
+ username = strdup (gtk_entry_get_text (GTK_ENTRY (username_entry)));
+ if (STREQ (username, "")) {
+ gtk_label_set_text (GTK_LABEL (spinner_message),
+ _("error: No user name. If in doubt, use
\"root\"."));
+ errors++;
+ }
+ free (password);
+ password = strdup (gtk_entry_get_text (GTK_ENTRY (password_entry)));
+
+ if (errors)
+ return;
+
+ /* 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, NULL);
+ 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)
+{
+ 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 ();
+ 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 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;
+}
diff --git a/p2v/main.c b/p2v/main.c
new file mode 100644
index 0000000..f157995
--- /dev/null
+++ b/p2v/main.c
@@ -0,0 +1,324 @@
+/* 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>
+
+#pragma GCC diagnostic ignored "-Wstrict-prototypes" /* error in <gtk.h>
*/
+#include <gtk/gtk.h>
+
+#include "p2v.h"
+
+int verbose;
+char *server;
+int port = 22;
+char *username;
+char *password;
+int sudo;
+char *guestname;
+int vcpus;
+uint64_t memory;
+
+static char *read_cmdline (void);
+static void kernel_configuration (const char *cmdline);
+
+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;
+
+ 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':
+ 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);
+ }
+
+ /* 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"))
+ verbose = 1;
+
+ if (strstr (cmdline, "p2v.server="))
+ kernel_configuration (cmdline);
+ else {
+ gui:
+ if (!gui_possible)
+ /* Gtk has already printed an error. */
+ exit (EXIT_FAILURE);
+ gui_application ();
+ }
+
+ free (cmdline);
+
+ exit (EXIT_SUCCESS);
+}
+
+/* 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;
+}
+
+/* Kernel-driven configuration, non-interactive. */
+static void
+kernel_configuration (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, " ");
+ server = strndup (r, len);
+
+ r = strstr (cmdline, "p2v.port=");
+ if (r) {
+ r += 5+4;
+ if (sscanf (r, "%d", &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, " ");
+ username = strndup (r, len);
+ }
+
+ r = strstr (cmdline, "p2v.password=");
+ if (r) {
+ r += 5+8;
+ len = strcspn (r, " ");
+ password = strndup (r, len);
+ }
+
+ r = strstr (cmdline, "p2v.sudo");
+ if (r)
+ sudo = 1;
+
+ /* We should now be able to connect and interrogate virt-v2v
+ * on the conversion server.
+ */
+ if (test_connection () == -1) {
+ const char *err = get_ssh_error ();
+
+ fprintf (stderr, "%s: error opening control connection to %s:%d: %s\n",
+ program_name, server, port, err);
+ exit (EXIT_FAILURE);
+ }
+
+ r = strstr (cmdline, "p2v.name");
+ if (r) {
+ r += 5+4;
+ len = strcspn (r, " ");
+ guestname = strndup (r, len);
+ }
+
+ r = strstr (cmdline, "p2v.vcpus");
+ if (r) {
+ r += 5+5;
+ if (sscanf (r, "%d", &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", &memory, mem_code) != 1) {
+ fprintf (stderr, "%s: cannot parse p2v.memory from kernel command
line\n",
+ program_name);
+ exit (EXIT_FAILURE);
+ }
+ memory *= 1024;
+ if (mem_code[0] == 'M' || mem_code[0] == 'G')
+ memory *= 1024;
+ if (mem_code[0] == 'G')
+ 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);
+ }
+ }
+
+
+}
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..5c314df
--- /dev/null
+++ b/p2v/p2v.h
@@ -0,0 +1,55 @@
+/* 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
+
+/* 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
+
+/* Configuration happens through these global variables. */
+extern int verbose;
+extern char *server;
+extern int port;
+extern char *username;
+extern char *password;
+extern int sudo;
+extern char *guestname;
+extern int vcpus;
+extern uint64_t memory;
+
+/* gui.c */
+extern void gui_application (void);
+
+/* ssh.c */
+extern int test_connection (void);
+extern const char *get_ssh_error (void);
+
+/* virt-v2v version and features (read from remote). */
+extern int v2v_major;
+extern int v2v_minor;
+
+#endif /* P2V_H */
diff --git a/p2v/ssh.c b/p2v/ssh.c
new file mode 100644
index 0000000..2640dc2
--- /dev/null
+++ b/p2v/ssh.c
@@ -0,0 +1,408 @@
+/* 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;
+
+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]*)\\.", 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 (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", port);
+ args[j++] = port_str;
+ args[j++] = "-l"; /* Username. */
+ args[j++] = username ? 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++] = server; /* Conversion server. */
+ args[j++] = NULL;
+ assert (j == nr_args);
+
+ h = mexp_spawnv ("ssh", (char **) args);
+ if (h == NULL)
+ return NULL;
+
+ if (password && strlen (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", 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", 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 (void)
+{
+ mexp_h *h;
+ CLEANUP_FREE char *major_str = NULL, *minor_str = NULL;
+ int feature_libguestfs_rewrite = 0;
+ int status;
+ const int ovecsize = 12;
+ int ovector[ovecsize];
+
+ h = start_ssh (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", 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]);
+ sscanf (major_str, "%d", &v2v_major);
+ sscanf (minor_str, "%d", &v2v_minor);
+ fprintf (stderr, "%s: remote virt-v2v version: %d.%d\n",
+ program_name, v2v_major, v2v_minor);
+ 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",
+ 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;
+}
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..9acedfb 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -247,6 +247,10 @@ mllib/tty-c.c
mllib/uri-c.c
ocaml/guestfs-c-actions.c
ocaml/guestfs-c.c
+p2v/gui.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 f634442..cbee273 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -4346,6 +4346,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.
@@ -4746,6 +4750,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.8.5.3