mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
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:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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
2
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. |
|
||||
|
||||
16
configure.ac
16
configure.ac
@@ -118,6 +118,9 @@ gl_INIT
|
||||
AC_PROG_LIBTOOL
|
||||
AC_PROG_LN_S
|
||||
|
||||
dnl Define the host CPU architecture (defines 'host_cpu')
|
||||
AC_CANONICAL_HOST
|
||||
|
||||
# Define $(SED).
|
||||
m4_ifdef([AC_PROG_SED],[
|
||||
AC_PROG_SED
|
||||
@@ -933,6 +936,16 @@ PKG_CHECK_MODULES([LIBCONFIG], [libconfig],[
|
||||
[AC_MSG_WARN([libconfig not found, some features will be disabled])])
|
||||
AM_CONDITIONAL([HAVE_LIBCONFIG],[test "x$LIBCONFIG_LIBS" != "x"])
|
||||
|
||||
dnl Check for gtk2 library, used by virt-p2v.
|
||||
PKG_CHECK_MODULES([GTK2], [gtk+-2.0], [
|
||||
AC_SUBST([GTK2_CFLAGS])
|
||||
AC_SUBST([GTK2_LIBS])
|
||||
],
|
||||
[AC_MSG_WARN([gtk2 not found, virt-p2v will be disabled])])
|
||||
|
||||
dnl Can we build virt-p2v?
|
||||
AM_CONDITIONAL([HAVE_P2V], [test "x$GTK2_LIBS" != "x"])
|
||||
|
||||
dnl hivex library (highly recommended)
|
||||
dnl This used to be a part of libguestfs, but was spun off into its
|
||||
dnl own separate upstream project in libguestfs 1.0.85.
|
||||
@@ -1645,6 +1658,7 @@ AC_CONFIG_FILES([Makefile
|
||||
ocaml/META
|
||||
ocaml/Makefile
|
||||
ocaml/examples/Makefile
|
||||
p2v/Makefile
|
||||
perl/Makefile
|
||||
perl/Makefile.PL
|
||||
perl/examples/Makefile
|
||||
@@ -1723,6 +1737,8 @@ echo "guestfish and C-based virt tools .... yes"
|
||||
echo "FUSE filesystem ..................... $enable_fuse"
|
||||
AS_ECHO_N(["GNU gettext for i18n ................ "])
|
||||
if test "x$HAVE_GNU_GETTEXT_TRUE" = "x"; then echo "yes"; else echo "no"; fi
|
||||
AS_ECHO_N(["virt-p2v ............................ "])
|
||||
if test "x$HAVE_P2V_TRUE" = "x"; then echo "yes"; else echo "no"; fi
|
||||
AS_ECHO_N(["OCaml bindings ...................... "])
|
||||
if test "x$HAVE_OCAML_TRUE" = "x"; then echo "yes"; else echo "no"; fi
|
||||
AS_ECHO_N(["OCaml-based virt tools .............. "])
|
||||
|
||||
@@ -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
91
p2v/Makefile.am
Normal 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
30
p2v/authors.c
Normal 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
89
p2v/config.c
Normal 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
475
p2v/conversion.c
Normal 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
38
p2v/copying.c
Normal 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";
|
||||
184
p2v/kernel.c
Normal file
184
p2v/kernel.c
Normal 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
502
p2v/main.c
Normal 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
379
p2v/miniexpect.c
Normal 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
82
p2v/miniexpect.h
Normal 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
103
p2v/p2v.h
Normal 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
633
p2v/ssh.c
Normal 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
219
p2v/virt-p2v.pod
Normal 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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)>,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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)>,
|
||||
|
||||
Reference in New Issue
Block a user