New tool: virt-p2v.

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.
This commit is contained in:
Richard W.M. Jones
2014-04-23 21:27:52 +01:00
parent 0131d6f666
commit fd82bb12fd
22 changed files with 3921 additions and 2 deletions

4
.gitignore vendored
View File

@@ -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

View File

@@ -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

2
README
View File

@@ -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. |

View File

@@ -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 .............. "])

View File

@@ -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)>,

91
p2v/Makefile.am Normal file
View File

@@ -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

30
p2v/authors.c Normal file
View File

@@ -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
};

89
p2v/config.c Normal file
View File

@@ -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);
}

475
p2v/conversion.c Normal file
View File

@@ -0,0 +1,475 @@
/* 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 <time.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 "miniexpect.h"
#include "p2v.h"
/* Data per NBD connection / physical disk. */
struct data_conn {
mexp_h *h; /* miniexpect handle to ssh */
pid_t nbd_pid; /* qemu pid */
int nbd_local_port; /* local NBD port on physical machine */
int nbd_remote_port; /* remote NBD port on conversion server */
};
static pid_t start_qemu_nbd (int nbd_local_port, const char *device);
static void cleanup_data_conns (struct data_conn *data_conns, size_t nr);
static char *generate_libvirt_xml (struct config *, struct data_conn *);
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))
{
int ret = -1;
size_t i, len;
size_t nr_disks = guestfs___count_strings (config->disks);
struct data_conn data_conns[nr_disks];
CLEANUP_FREE char *remote_dir = NULL, *libvirt_xml = NULL;
time_t now;
struct tm tm;
mexp_h *control_h = NULL;
debug_parameters (config);
for (i = 0; config->disks[i] != NULL; ++i) {
data_conns[i].h = NULL;
data_conns[i].nbd_pid = 0;
data_conns[i].nbd_local_port = -1;
data_conns[i].nbd_remote_port = -1;
}
/* Start the data connections and qemu-nbd processes, one per disk. */
for (i = 0; config->disks[i] != NULL; ++i) {
CLEANUP_FREE char *device = NULL;
if (notify_ui) {
CLEANUP_FREE char *msg;
if (asprintf (&msg,
_("Opening data connection for %s ..."),
config->disks[i]) == -1) {
perror ("asprintf");
exit (EXIT_FAILURE);
}
notify_ui (NOTIFY_STATUS, msg);
}
data_conns[i].h = open_data_connection (config,
&data_conns[i].nbd_local_port,
&data_conns[i].nbd_remote_port);
if (data_conns[i].h == NULL) {
const char *err = get_ssh_error ();
set_conversion_error ("could not open data connection over SSH to the conversion server: %s", err);
goto out;
}
if (asprintf (&device, "/dev/%s", config->disks[i]) == -1) {
perror ("asprintf");
cleanup_data_conns (data_conns, nr_disks);
exit (EXIT_FAILURE);
}
/* Start qemu-nbd listening on the given port number. */
data_conns[i].nbd_pid =
start_qemu_nbd (data_conns[i].nbd_local_port, device);
if (data_conns[i].nbd_pid == 0)
goto out;
#if DEBUG_STDERR
fprintf (stderr,
"%s: data connection for %s: SSH remote port %d, local port %d\n",
program_name, device,
data_conns[i].nbd_remote_port, data_conns[i].nbd_local_port);
#endif
}
/* 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.
*
* NB: This path MUST NOT require shell quoting.
*/
time (&now);
gmtime_r (&now, &tm);
if (asprintf (&remote_dir,
"/tmp/virt-p2v-%04d%02d%02d-XXXXXXXX",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday) == -1) {
perror ("asprintf");
cleanup_data_conns (data_conns, nr_disks);
exit (EXIT_FAILURE);
}
len = strlen (remote_dir);
guestfs___random_string (&remote_dir[len-8], 8);
if (notify_ui)
notify_ui (NOTIFY_LOG_DIR, remote_dir);
/* Generate the libvirt XML. */
libvirt_xml = generate_libvirt_xml (config, data_conns);
if (libvirt_xml == NULL)
goto out;
#if DEBUG_STDERR
fprintf (stderr, "%s: libvirt XML:\n%s", program_name, libvirt_xml);
#endif
/* Open the control connection and start conversion */
if (notify_ui)
notify_ui (NOTIFY_STATUS, _("Setting up the control connection ..."));
control_h = start_remote_connection (config, remote_dir, libvirt_xml);
if (control_h == NULL) {
const char *err = get_ssh_error ();
set_conversion_error ("could not open control connection over SSH to the conversion server: %s", err);
goto out;
}
/* Do the conversion. This runs until virt-v2v exits. */
if (notify_ui)
notify_ui (NOTIFY_STATUS, _("Doing conversion ..."));
if (mexp_printf (control_h,
"( "
"%s"
"virt-v2v"
"%s"
" -i libvirtxml"
" -o local -os /tmp" /* XXX */
" --root first"
" %s/libvirt.conf"
" </dev/null" /* stdin */
" 2>&1" /* output */
" ;"
" echo $? > %s/status"
" )"
" | tee %s/virt-v2v-conversion-log.txt"
" ;"
" exit"
"\n",
config->sudo ? "sudo " : "",
config->verbose ? " -v -x" : "",
remote_dir,
remote_dir,
remote_dir) == -1) {
set_conversion_error ("mexp_printf: virt-v2v command: %m");
goto out;
}
/* Read output from the virt-v2v process and echo it through the
* notify function, until virt-v2v closes the connection.
*/
for (;;) {
char buf[257];
ssize_t r;
r = read (control_h->fd, buf, sizeof buf - 1);
if (r == -1) {
set_conversion_error ("read: %m");
goto out;
}
if (r == 0)
break;
buf[r] = '\0';
if (notify_ui)
notify_ui (NOTIFY_REMOTE_MESSAGE, buf);
}
ret = 0;
out:
if (control_h)
mexp_close (control_h);
cleanup_data_conns (data_conns, nr_disks);
return ret;
}
/* Note: returns process ID (> 0) or 0 if there is an error. */
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 0;
}
if (pid == 0) { /* Child. */
close (0);
open ("/dev/null", O_RDONLY);
execlp ("qemu-nbd",
"qemu-nbd",
"-r", /* readonly (vital!) */
"-p", port_str, /* listening port */
"-t", /* persistent */
"-f", "raw", /* force raw format */
"-b", "localhost", /* listen only on loopback interface */
"--cache=unsafe", /* use unsafe caching for speed */
device, /* a device like /dev/sda */
NULL);
perror ("qemu-nbd");
_exit (EXIT_FAILURE);
}
/* Parent. */
return pid;
}
static void
cleanup_data_conns (struct data_conn *data_conns, size_t nr)
{
size_t i;
for (i = 0; i < nr; ++i) {
if (data_conns[i].h != NULL) {
/* Because there is no SSH prompt (ssh -N), the only way to kill
* these ssh connections is to send a signal. Just closing the
* pipe doesn't do anything.
*/
kill (data_conns[i].h->pid, SIGTERM);
mexp_close (data_conns[i].h);
}
if (data_conns[i].nbd_pid > 0) {
/* Kill qemu-nbd process and clean up. */
kill (data_conns[i].nbd_pid, SIGTERM);
waitpid (data_conns[i].nbd_pid, 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, struct data_conn *data_conns)
{
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;
fprintf (fp,
"<!--\n"
" NOTE!\n"
"\n"
" This libvirt XML is generated by the virt-p2v front end, in\n"
" order to communicate with the backend virt-v2v process running\n"
" on the conversion server. It is a minimal description of the\n"
" physical machine. If the target of the conversion is libvirt,\n"
" then virt-v2v will generate the real target libvirt XML, which\n"
" has only a little to do with the XML in this file.\n"
"\n"
" For the code that generates this XML, see %s in the virt-p2v\n"
" sources (in the libguestfs package).\n"
"-->\n"
"\n",
__FILE__);
/* 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) {
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",
data_conns[i].nbd_remote_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
}

38
p2v/copying.c Normal file
View File

@@ -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";

1052
p2v/gui.c Normal file

File diff suppressed because it is too large Load Diff

184
p2v/kernel.c Normal file
View File

@@ -0,0 +1,184 @@
/* 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;
case NOTIFY_STATUS:
printf ("%s: %s\n", program_name, data);
break;
default:
printf ("%s: unknown message during conversion: type=%d data=%s\n",
program_name, type, data);
}
}

502
p2v/main.c Normal file
View File

@@ -0,0 +1,502 @@
/* 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>
#include <sys/stat.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 int
device_contains (const char *dev, dev_t root_device)
{
CLEANUP_FREE char *dev_name;
struct stat statbuf;
if (asprintf (&dev_name, "/dev/%s", dev) == -1) {
perror ("asprintf");
exit (EXIT_FAILURE);
}
if (stat (dev_name, &statbuf) == -1)
return 0;
if (statbuf.st_rdev == root_device)
return 1;
/* Could be a partition. XXX Very hacky and incorrect way to get
* the device from its partition.
*/
if (minor (root_device) < 8 /* any major:minor where minor is "small" */ &&
statbuf.st_rdev == makedev (major (root_device), 0))
return 1;
if (major (statbuf.st_rdev) == 8 /* SCSI */ &&
statbuf.st_rdev == makedev (major (root_device), minor (root_device) & 0xf))
return 1;
return 0;
}
static void
find_all_disks (void)
{
DIR *dir;
struct dirent *d;
size_t nr_disks = 0, nr_removable = 0;
dev_t root_device = 0;
struct stat statbuf;
if (stat ("/", &statbuf) == 0)
root_device = statbuf.st_dev;
/* 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;
/* Skip the device containing the root filesystem. This is only
* an approximate test -- for example it doesn't work if the
* root filesystem is on an LV. However it doesn't need to be
* completely accurate, and we only really care that it works on
* the p2v ISO.
*/
if (device_contains (d->d_name, root_device))
continue;
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;
}

379
p2v/miniexpect.c Normal file
View File

@@ -0,0 +1,379 @@
/* 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 <string.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->next_match = -1;
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;
h->next_match = -1;
}
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);
if (h->next_match == -1) {
/* Fully clear the buffer, then read. */
clear_buffer (h);
} else {
/* See the comment in the manual about h->next_match. We have
* some data remaining in the buffer, so begin by matching that.
*/
memmove (&h->buffer[0], &h->buffer[h->next_match], h->len - h->next_match);
h->len -= h->next_match;
h->buffer[h->len] = '\0';
h->next_match = -1;
goto try_match;
}
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
try_match:
/* 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. */
if (ovector != NULL && ovecsize >= 1 && ovector[1] >= 0)
h->next_match = ovector[1];
else
h->next_match = -1;
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;
}

82
p2v/miniexpect.h Normal file
View File

@@ -0,0 +1,82 @@
/* 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;
ssize_t next_match;
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_ */

103
p2v/p2v.h Normal file
View File

@@ -0,0 +1,103 @@
/* 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
#include "miniexpect.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
/* 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 */
#define NOTIFY_STATUS 3 /* stage in conversion process */
extern const char *get_conversion_error (void);
/* ssh.c */
extern int test_connection (struct config *);
extern mexp_h *open_data_connection (struct config *, int *local_port, int *remote_port);
extern mexp_h *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 */

633
p2v/ssh.c Normal file
View File

@@ -0,0 +1,633 @@
/* 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 pcre *portfwd_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);
/* The magic synchronization strings all match this expression. See
* start_ssh function below.
*/
COMPILE (prompt_re, "###([0123456789abcdefghijklmnopqrstuvwxyz]{8})### ", 0);
COMPILE (version_re,
"virt-v2v ([1-9]\\d*)\\.([1-9]\\d*)\\.([1-9]\\d*)", 0);
COMPILE (libguestfs_rewrite_re, "libguestfs-rewrite", 0);
COMPILE (portfwd_re, "Allocated port (\\d+) for remote forward", 0);
}
static void
free_regexps (void)
{
pcre_free (password_re);
pcre_free (prompt_re);
pcre_free (version_re);
pcre_free (libguestfs_rewrite_re);
pcre_free (portfwd_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, int wait_prompt)
{
size_t i, j, nr_args, count;
char port_str[64];
CLEANUP_FREE /* [sic] */ const char **args = NULL;
mexp_h *h;
const int ovecsize = 12;
int ovector[ovecsize];
int saved_timeout;
/* Create the ssh argument array. */
nr_args = 0;
if (extra_args != NULL)
nr_args = guestfs___count_strings (extra_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;
}
}
if (!wait_prompt)
return h;
/* Synchronize with the command prompt and set it to a known string. */
/* Note that we cannot control the initial prompt. It would involve
* changing the remote SSH configuration (AcceptEnv). However what
* we can do is to repeatedly send 'export PS1=<magic>' commands
* until we synchronize with the remote shell.
*/
saved_timeout = h->timeout;
h->timeout = 2000;
for (count = 0; count < 30; ++count) {
char magic[9];
const char *matched;
int r;
if (guestfs___random_string (magic, 8) == -1) {
set_ssh_error ("random_string: %m");
mexp_close (h);
return NULL;
}
/* The purpose of the '' inside the string is to ensure we don't
* mistake the command echo for the prompt.
*/
if (mexp_printf (h, "export PS1='###''%s''### '\n", magic) == -1) {
set_ssh_error ("random_string: %m");
mexp_close (h);
return NULL;
}
/* Wait for the prompt. */
wait_again:
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 a prompt. However it might be an earlier prompt. If it
* doesn't match the PS1 string we sent, then repeat the expect.
*/
r = pcre_get_substring (h->buffer, ovector, h->pcre_error, 1, &matched);
if (r < 0) {
fprintf (stderr, "error: PCRE error reading substring (%d)\n", r);
exit (EXIT_FAILURE);
}
r = STREQ (magic, matched);
pcre_free_substring (matched);
if (!r)
goto wait_again;
goto got_prompt;
case MEXP_EOF:
mexp_close (h);
set_ssh_error ("unexpected end of file waiting for command prompt");
return NULL;
case MEXP_TIMEOUT:
/* Timeout here is not an error, since ssh may "eat" commands that
* we send before the shell at the other end is ready. Just loop.
*/
break;
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;
}
}
mexp_close (h);
set_ssh_error ("failed to synchronize with remote shell after 60 seconds");
return NULL;
got_prompt:
h->timeout = saved_timeout;
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, 1);
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;
}
/* The p2v ISO should allow us to open up just about any port. */
static int nbd_local_port = 50123;
mexp_h *
open_data_connection (struct config *config, int *local_port, int *remote_port)
{
mexp_h *h;
char remote_arg[32];
const char *extra_args[] = {
"-R", remote_arg,
"-N",
NULL
};
CLEANUP_FREE char *port_str = NULL;
const int ovecsize = 12;
int ovector[ovecsize];
snprintf (remote_arg, sizeof remote_arg, "0:localhost:%d", nbd_local_port);
*local_port = nbd_local_port;
nbd_local_port++;
h = start_ssh (config, (char **) extra_args, 0);
if (h == NULL)
return NULL;
switch (mexp_expect (h,
(mexp_regexp[]) {
{ 100, .re = portfwd_re },
{ 0 }
}, ovector, ovecsize)) {
case 100: /* Ephemeral port. */
port_str = strndup (&h->buffer[ovector[2]], ovector[3]-ovector[2]);
sscanf (port_str, "%d", remote_port);
break;
case MEXP_EOF:
mexp_close (h);
set_ssh_error ("unexpected end of file waiting ssh -R output");
return NULL;
case MEXP_TIMEOUT:
mexp_close (h);
set_ssh_error ("timeout waiting for ssh -R output");
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;
}
/* Wait for the prompt. */
static int
wait_for_prompt (mexp_h *h)
{
const int ovecsize = 12;
int ovector[ovecsize];
switch (mexp_expect (h,
(mexp_regexp[]) {
{ 100, .re = prompt_re },
{ 0 }
}, ovector, ovecsize)) {
case 100: /* Got the prompt. */
return 0;
case MEXP_EOF:
set_ssh_error ("unexpected end of file waiting for prompt");
return -1;
case MEXP_TIMEOUT:
set_ssh_error ("timeout waiting for prompt");
return -1;
case MEXP_ERROR:
set_ssh_error ("mexp_expect: %m");
return -1;
case MEXP_PCRE_ERROR:
set_ssh_error ("PCRE error: %d\n", h->pcre_error);
return -1;
}
return 0;
}
mexp_h *
start_remote_connection (struct config *config,
const char *remote_dir, const char *libvirt_xml)
{
mexp_h *h;
char magic[9];
if (guestfs___random_string (magic, 8) == -1) {
perror ("random_string");
return NULL;
}
h = start_ssh (config, NULL, 1);
if (h == NULL)
return NULL;
/* Create the remote directory. */
if (mexp_printf (h, "mkdir %s\n", remote_dir) == -1) {
set_ssh_error ("mexp_printf: %m");
goto error;
}
if (wait_for_prompt (h) == -1)
goto error;
/* Write some useful config information to files in the remote directory. */
if (mexp_printf (h, "echo '%s' > %s/name\n",
config->guestname, remote_dir) == -1) {
set_ssh_error ("mexp_printf: %m");
goto error;
}
if (wait_for_prompt (h) == -1)
goto error;
if (mexp_printf (h, "date > %s/time\n", remote_dir) == -1) {
set_ssh_error ("mexp_printf: %m");
goto error;
}
if (wait_for_prompt (h) == -1)
goto error;
/* Upload the libvirt configuration file to the remote directory. */
if (mexp_printf (h,
"cat > '%s/libvirt.conf' << '__%s__'\n"
"%s"
"__%s__\n",
remote_dir, magic,
libvirt_xml,
magic) == -1) {
set_ssh_error ("mexp_printf: %m");
goto error;
}
if (wait_for_prompt (h) == -1)
goto error;
return h;
error:
mexp_close (h);
return NULL;
}

219
p2v/virt-p2v.pod Normal file
View File

@@ -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.

View File

@@ -249,6 +249,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

View File

@@ -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)>,

View File

@@ -242,6 +242,7 @@ guestfs___random_string (char *ret, size_t len)
errno = saved_errno;
return -1;
}
/* Do not change this! */
ret[i] = "0123456789abcdefghijklmnopqrstuvwxyz"[c % 36];
}
ret[len] = '\0';

View File

@@ -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.
This manual page documents the rewritten virt-v2v included in
libguestfs E<ge> 1.28.
@@ -286,6 +286,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)>,