Richard W.M. Jones
2014-May-01 18:17 UTC
[Libguestfs] [PATCH v6 NOT TO BE APPLIED 1/2] New tool: virt-v2v.
This is a rewrite of the original virt-v2v tool. The original was
written by Matt Booth et al in Perl between 2009 and 2013.
---
.gitignore | 6 +
Makefile.am | 6 +-
configure.ac | 5 +-
fish/guestfish.pod | 1 +
po/POTFILES | 2 +
po/POTFILES-ml | 11 +
src/guestfs.pod | 5 +
v2v/Makefile.am | 163 ++++++++++
v2v/README | 24 ++
v2v/cmdline.ml | 197 ++++++++++++
v2v/convert_linux_common.ml | 236 +++++++++++++++
v2v/convert_linux_common.mli | 45 +++
v2v/convert_linux_enterprise.ml | 637 +++++++++++++++++++++++++++++++++++++++
v2v/convert_linux_enterprise.mli | 19 ++
v2v/convert_linux_grub.ml | 330 ++++++++++++++++++++
v2v/convert_linux_grub.mli | 43 +++
v2v/convert_windows.ml | 22 ++
v2v/convert_windows.mli | 19 ++
v2v/link.sh.in | 22 ++
v2v/source_libvirt.ml | 118 ++++++++
v2v/source_libvirt.mli | 27 ++
v2v/target_local.ml | 86 ++++++
v2v/target_local.mli | 21 ++
v2v/types.ml | 84 ++++++
v2v/types.mli | 77 +++++
v2v/utils-c.c | 43 +++
v2v/utils.ml | 44 +++
v2v/v2v.ml | 353 ++++++++++++++++++++++
v2v/virt-v2v.pod | 301 ++++++++++++++++++
v2v/xml-c.c | 240 +++++++++++++++
v2v/xml.ml | 50 +++
v2v/xml.mli | 57 ++++
32 files changed, 3291 insertions(+), 3 deletions(-)
create mode 100644 v2v/Makefile.am
create mode 100644 v2v/README
create mode 100644 v2v/cmdline.ml
create mode 100644 v2v/convert_linux_common.ml
create mode 100644 v2v/convert_linux_common.mli
create mode 100644 v2v/convert_linux_enterprise.ml
create mode 100644 v2v/convert_linux_enterprise.mli
create mode 100644 v2v/convert_linux_grub.ml
create mode 100644 v2v/convert_linux_grub.mli
create mode 100644 v2v/convert_windows.ml
create mode 100644 v2v/convert_windows.mli
create mode 100644 v2v/link.sh.in
create mode 100644 v2v/source_libvirt.ml
create mode 100644 v2v/source_libvirt.mli
create mode 100644 v2v/target_local.ml
create mode 100644 v2v/target_local.mli
create mode 100644 v2v/types.ml
create mode 100644 v2v/types.mli
create mode 100644 v2v/utils-c.c
create mode 100644 v2v/utils.ml
create mode 100644 v2v/v2v.ml
create mode 100644 v2v/virt-v2v.pod
create mode 100644 v2v/xml-c.c
create mode 100644 v2v/xml.ml
create mode 100644 v2v/xml.mli
diff --git a/.gitignore b/.gitignore
index 4d47d23..25e9358 100644
--- a/.gitignore
+++ b/.gitignore
@@ -252,6 +252,7 @@ Makefile.in
/html/virt-tar.1.html
/html/virt-tar-in.1.html
/html/virt-tar-out.1.html
+/html/virt-v2v.1.html
/html/virt-win-reg.1.html
/inspector/actual-*.xml
/inspector/stamp-virt-inspector.pod
@@ -523,3 +524,8 @@ Makefile.in
/test-tool/libguestfs-test-tool-helper
/test-tool/stamp-libguestfs-test-tool.pod
/tools/virt-*.1
+/v2v/.depend
+/v2v/link.sh
+/v2v/stamp-virt-v2v.pod
+/v2v/virt-v2v
+/v2v/virt-v2v.1
diff --git a/Makefile.am b/Makefile.am
index b135d65..3102e0b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -132,7 +132,8 @@ SUBDIRS += \
builder builder/website \
resize \
sparsify \
- sysprep
+ sysprep \
+ v2v
endif
# Perl tools.
@@ -257,6 +258,7 @@ HTMLFILES = \
html/virt-tar.1.html \
html/virt-tar-in.1.html \
html/virt-tar-out.1.html \
+ html/virt-v2v.1.html \
html/virt-win-reg.1.html
HTMLSUPPORTFILES = \
@@ -319,7 +321,7 @@ all-local:
grep -v -E '^python/utils.c$$' | \
LC_ALL=C sort > po/POTFILES
cd $(srcdir); \
- find builder customize mllib resize sparsify sysprep -name '*.ml' | \
+ find builder customize mllib resize sparsify sysprep v2v -name '*.ml'
| \
LC_ALL=C sort > po/POTFILES-ml
# Manual pages in top level directory.
diff --git a/configure.ac b/configure.ac
index b80644c..a8cd195 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1599,6 +1599,8 @@ AC_CONFIG_FILES([run],
[chmod +x,-w run])
AC_CONFIG_FILES([sparsify/link.sh],
[chmod +x,-w sparsify/link.sh])
+AC_CONFIG_FILES([v2v/link.sh],
+ [chmod +x,-w v2v/link.sh])
AC_CONFIG_FILES([Makefile
align/Makefile
@@ -1702,7 +1704,8 @@ AC_CONFIG_FILES([Makefile
tests/tmpdirs/Makefile
tests/xfs/Makefile
tests/xml/Makefile
- tools/Makefile])
+ tools/Makefile
+ v2v/Makefile])
AC_OUTPUT
dnl Produce summary.
diff --git a/fish/guestfish.pod b/fish/guestfish.pod
index 25279fb..5cf6ebc 100644
--- a/fish/guestfish.pod
+++ b/fish/guestfish.pod
@@ -1624,6 +1624,7 @@ L<virt-sysprep(1)>,
L<virt-tar(1)>,
L<virt-tar-in(1)>,
L<virt-tar-out(1)>,
+L<virt-v2v(1)>,
L<virt-win-reg(1)>,
L<libguestfs-tools.conf(5)>,
L<display(1)>,
diff --git a/po/POTFILES b/po/POTFILES
index 0fac8fe..b481157 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -318,3 +318,5 @@ src/test-utils.c
src/tmpdirs.c
src/utils.c
test-tool/test-tool.c
+v2v/utils-c.c
+v2v/xml-c.c
diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index 8993136..b47e7db 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -81,3 +81,14 @@ sysprep/sysprep_operation_udev_persistent_net.ml
sysprep/sysprep_operation_user_account.ml
sysprep/sysprep_operation_utmp.ml
sysprep/sysprep_operation_yum_uuid.ml
+v2v/cmdline.ml
+v2v/convert_linux_common.ml
+v2v/convert_linux_enterprise.ml
+v2v/convert_linux_grub.ml
+v2v/convert_windows.ml
+v2v/source_libvirt.ml
+v2v/target_local.ml
+v2v/types.ml
+v2v/utils.ml
+v2v/v2v.ml
+v2v/xml.ml
diff --git a/src/guestfs.pod b/src/guestfs.pod
index 0f54625..f634442 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -4396,6 +4396,10 @@ created by another.
Command line tools written in Perl (L<virt-win-reg(1)> and many others).
+=item C<v2v>
+
+L<virt-v2v(1)> command and documentation.
+
=item C<csharp>
=item C<erlang>
@@ -4749,6 +4753,7 @@ L<virt-sysprep(1)>,
L<virt-tar(1)>,
L<virt-tar-in(1)>,
L<virt-tar-out(1)>,
+L<virt-v2v(1)>,
L<virt-win-reg(1)>,
L<guestfs-faq(1)>,
L<guestfs-performance(1)>,
diff --git a/v2v/Makefile.am b/v2v/Makefile.am
new file mode 100644
index 0000000..3b50a89
--- /dev/null
+++ b/v2v/Makefile.am
@@ -0,0 +1,163 @@
+# libguestfs virt-v2v tool
+# 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 = \
+ $(SOURCES_MLI) $(SOURCES_ML) $(SOURCES_C) \
+ virt-v2v.pod
+
+CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o virt-v2v
+
+SOURCES_MLI = \
+ convert_linux_common.mli \
+ convert_linux_enterprise.mli \
+ convert_linux_grub.mli \
+ convert_windows.mli \
+ source_libvirt.mli \
+ target_local.mli \
+ types.mli \
+ xml.mli
+
+SOURCES_ML = \
+ types.ml \
+ utils.ml \
+ xml.ml \
+ cmdline.ml \
+ source_libvirt.ml \
+ convert_linux_common.ml \
+ convert_linux_grub.ml \
+ convert_linux_enterprise.ml \
+ convert_windows.ml \
+ target_local.ml \
+ v2v.ml
+
+SOURCES_C = \
+ $(top_builddir)/fish/progress.c \
+ $(top_builddir)/mllib/tty-c.c \
+ $(top_builddir)/mllib/progress-c.c \
+ utils-c.c \
+ xml-c.c
+
+if HAVE_OCAML
+
+bin_PROGRAMS = virt-v2v
+
+virt_v2v_SOURCES = $(SOURCES_C)
+virt_v2v_CFLAGS = \
+ -I. \
+ -I$(top_builddir) \
+ -I$(shell $(OCAMLC) -where) \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/fish \
+ $(LIBXML2_CFLAGS)
+
+BOBJECTS = \
+ $(top_builddir)/mllib/common_gettext.cmo \
+ $(top_builddir)/mllib/common_utils.cmo \
+ $(top_builddir)/mllib/tTY.cmo \
+ $(top_builddir)/mllib/progress.cmo \
+ $(top_builddir)/mllib/config.cmo \
+ $(SOURCES_ML:.ml=.cmo)
+XOBJECTS = $(BOBJECTS:.cmo=.cmx)
+
+# -I $(top_builddir)/src/.libs is a hack which forces corresponding -L
+# option to be passed to gcc, so we don't try linking against an
+# installed copy of libguestfs.
+OCAMLPACKAGES = \
+ -package str,unix \
+ -I $(top_builddir)/src/.libs \
+ -I ../gnulib/lib/.libs \
+ -I $(top_builddir)/ocaml \
+ -I $(top_builddir)/mllib
+if HAVE_OCAML_PKG_GETTEXT
+OCAMLPACKAGES += -package gettext-stub
+endif
+
+OCAMLFLAGS = -g -warn-error CDEFLMPSUVYZX
+
+if !HAVE_OCAMLOPT
+OBJECTS = $(BOBJECTS)
+BEST = c
+OCAMLLINKFLAGS = mlguestfs.cma -custom
+else
+OBJECTS = $(XOBJECTS)
+BEST = opt
+OCAMLLINKFLAGS = mlguestfs.cmxa
+endif
+
+virt_v2v_DEPENDENCIES = $(OBJECTS)
+virt_v2v_LINK = \
+ ./link.sh \
+ $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) $(OCAMLPACKAGES) $(OCAMLLINKFLAGS) \
+ $(OBJECTS) -o $@
+
+.mli.cmi:
+ $(OCAMLFIND) ocamlc $(OCAMLFLAGS) $(OCAMLPACKAGES) -c $< -o $@
+.ml.cmo:
+ $(OCAMLFIND) ocamlc $(OCAMLFLAGS) $(OCAMLPACKAGES) -c $< -o $@
+.ml.cmx:
+ $(OCAMLFIND) ocamlopt $(OCAMLFLAGS) $(OCAMLPACKAGES) -c $< -o $@
+
+# Manual pages and HTML files for the website.
+
+man_MANS = virt-v2v.1
+
+noinst_DATA = $(top_builddir)/html/virt-v2v.1.html
+
+virt-v2v.1 $(top_builddir)/html/virt-v2v.1.html: stamp-virt-v2v.pod
+
+stamp-virt-v2v.pod: virt-v2v.pod
+ $(PODWRAPPER) \
+ --man virt-v2v.1 \
+ --html $(top_builddir)/html/virt-v2v.1.html \
+ --license GPLv2+ \
+ $<
+ touch $@
+
+CLEANFILES += stamp-virt-v2v.pod
+
+# Tests.
+
+TESTS_ENVIRONMENT = $(top_builddir)/run --test
+
+if ENABLE_APPLIANCE
+TESTS +endif ENABLE_APPLIANCE
+
+check-valgrind:
+ $(MAKE) VG="$(top_builddir)/run @VG@" check
+
+# Dependencies.
+depend: .depend
+
+.depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml)
+ rm -f $@ $@-t
+ $(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I
$(abs_top_builddir)/mllib $^ | \
+ $(SED) 's/ *$$//' | \
+ $(SED) -e :a -e '/ *\\$$/N; s/ *\\\n */ /; ta' | \
+ $(SED) -e 's,$(abs_srcdir)/,$(builddir)/,g' | \
+ sort > $@-t
+ mv $@-t $@
+
+-include .depend
+
+endif
+
+DISTCLEANFILES = .depend
+
+.PHONY: depend docs
diff --git a/v2v/README b/v2v/README
new file mode 100644
index 0000000..c4b3b0a
--- /dev/null
+++ b/v2v/README
@@ -0,0 +1,24 @@
+Missing features compared to Perl version:
+
+ - virt-p2v
+ - user-custom in virt-v2v.conf to install custom packages (virt-customize?)
+ - Windows support
+ - Fix configure_kernel on SUSE (see Mike Latimer's email)
+ - RHEV-M metadata
+ - testing
+
+Notes on the support matrix for upstream virt-v2v:
+
+- All these in 32- and 64-bit variants.
+
+- All these as Xen HV and PV variants, and ESX guests.
+
+- RHEL 3, 4, 5, 6, 7.
+- RHEL 4.5, 4.8, 5.2, 5.4 - tested particularly because virtio was added
+ between these releases.
+
+- Windows XP, 2000, 2003, 2008.
+
+- SUSE: OpenSUSE 13.1, SLES11 SP3, SLES12.
+
+- VirtualBox: Not tested.
diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml
new file mode 100644
index 0000000..966fe42
--- /dev/null
+++ b/v2v/cmdline.ml
@@ -0,0 +1,197 @@
+(* virt-v2v
+ * 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.
+ *)
+
+(* Command line argument parsing. *)
+
+open Printf
+
+open Common_gettext.Gettext
+open Common_utils
+
+open Types
+open Utils
+
+let parse_cmdline () + let display_version () + printf "virt-v2v
%s\n" Config.package_version;
+ exit 0
+ in
+
+ let debug_gc = ref false in
+ let input_conn = ref "" in
+ let output_conn = ref "" in
+ let output_format = ref "" in
+ let output_name = ref "" in
+ let output_storage = ref "" in
+ let machine_readable = ref false in
+ let quiet = ref false in
+ let verbose = ref false in
+ let trace = ref false in
+
+ let input_mode = ref `Libvirt in
+ let set_input_mode = function
+ | "libvirt" -> input_mode := `Libvirt
+ | "libvirtxml" -> input_mode := `LibvirtXML
+ | s ->
+ error (f_"unknown -i option: %s") s
+ in
+
+ let output_mode = ref `Libvirt in
+ let set_output_mode = function
+ | "libvirt" -> output_mode := `Libvirt
+ | "local" -> output_mode := `Local
+ | "ovirt" | "rhev" -> output_mode := `RHEV
+ | s ->
+ error (f_"unknown -o option: %s") s
+ in
+
+ let output_alloc = ref `Sparse in
+ let set_output_alloc = function
+ | "sparse" -> output_alloc := `Sparse
+ | "preallocated" -> output_alloc := `Preallocated
+ | s ->
+ error (f_"unknown -oa option: %s") s
+ in
+
+ let root_choice = ref `Ask in
+ let set_root_choice = function
+ | "ask" -> root_choice := `Ask
+ | "single" -> root_choice := `Single
+ | "first" -> root_choice := `First
+ | dev when string_prefix dev "/dev/" -> root_choice := `Dev
dev
+ | s ->
+ error (f_"unknown --root option: %s") s
+ in
+
+ let ditto = " -\"-" in
+ let argspec = Arg.align [
+ "--debug-gc",Arg.Set debug_gc, " " ^
s_"Debug GC and memory allocations";
+ "-i", Arg.String set_input_mode, "libvirtxml|libvirt
" ^ s_"Set input mode (default: libvirt)";
+ "-ic", Arg.Set_string input_conn, "uri " ^
s_"Libvirt URI";
+ "--long-options", Arg.Unit display_long_options, " " ^
s_"List long options";
+ "--machine-readable", Arg.Set machine_readable, " " ^
s_"Make output machine readable";
+ "-o", Arg.String set_output_mode, "libvirt|local|rhev
" ^ s_"Set output mode (default: libvirt)";
+ "-oa", Arg.String set_output_alloc,
"sparse|preallocated " ^ s_"Set output allocation mode";
+ "-oc", Arg.Set_string output_conn, "uri " ^
s_"Libvirt URI";
+ "-of", Arg.Set_string output_format, "raw|qcow2 "
^ s_"Set output format";
+ "-on", Arg.Set_string output_name, "name " ^
s_"Rename guest when converting";
+ "-os", Arg.Set_string output_storage, "storage "
^ s_"Set output storage location";
+ "-q", Arg.Set quiet, " " ^
s_"Quiet output";
+ "--quiet", Arg.Set quiet, ditto;
+ "--root", Arg.String set_root_choice,"ask|... " ^
s_"How to choose root filesystem";
+ "-v", Arg.Set verbose, " " ^
s_"Enable debugging messages";
+ "--verbose", Arg.Set verbose, ditto;
+ "-V", Arg.Unit display_version, " " ^
s_"Display version and exit";
+ "--version", Arg.Unit display_version, ditto;
+ "-x", Arg.Set trace, " " ^
s_"Enable tracing of libguestfs calls";
+ ] in
+ long_options := argspec;
+ let args = ref [] in
+ let anon_fun s = args := s :: !args in
+ let usage_msg + sprintf (f_"\
+%s: convert a guest to use KVM
+
+ virt-v2v -ic esx://esx.example.com/ -os imported esx_guest
+
+ virt-v2v -ic esx://esx.example.com/ \
+ -o rhev -os rhev.nfs:/export_domain --network rhevm esx_guest
+
+ virt-v2v -i libvirtxml -o local -os /tmp guest-domain.xml
+
+There is a companion front-end called \"virt-p2v\" which comes as an
+ISO or CD image that can be booted on physical machines.
+
+A short summary of the options is given below. For detailed help please
+read the man page virt-v2v(1).
+")
+ prog in
+ Arg.parse argspec anon_fun usage_msg;
+
+ (* Dereference the arguments. *)
+ let args = List.rev !args in
+ let debug_gc = !debug_gc in
+ let input_conn = match !input_conn with "" -> None | s ->
Some s in
+ let input_mode = !input_mode in
+ let machine_readable = !machine_readable in
+ let output_alloc = !output_alloc in
+ let output_conn = match !output_conn with "" -> None | s ->
Some s in
+ let output_format = match !output_format with "" -> None | s
-> Some s in
+ let output_mode = !output_mode in
+ let output_name = match !output_name with "" -> None | s ->
Some s in
+ let output_storage = !output_storage in
+ let quiet = !quiet in
+ let root_choice = !root_choice in
+ let verbose = !verbose in
+ let trace = !trace in
+
+ (* No arguments and machine-readable mode? Print out some facts
+ * about what this binary supports.
+ *)
+ if args = [] && machine_readable then (
+ printf "virt-v2v\n";
+ printf "libguestfs-rewrite\n";
+ exit 0
+ );
+
+ (* Parsing of the argument(s) depends on the input mode. *)
+ let input + match input_mode with
+ | `Libvirt ->
+ (* -i libvirt: Expecting a single argument which is the name
+ * of the libvirt guest.
+ *)
+ let guest + match args with
+ | [guest] -> guest
+ | _ ->
+ error (f_"expecting a libvirt guest name on the command
line") in
+ InputLibvirt (input_conn, guest)
+ | `LibvirtXML ->
+ (* -i libvirtxml: Expecting a filename (XML file). *)
+ let filename + match args with
+ | [filename] -> filename
+ | _ ->
+ error (f_"expecting a libvirt XML file name on the command
line") in
+ InputLibvirtXML filename in
+
+ (* Parse the output mode. *)
+ let output + match output_mode with
+ | `Libvirt ->
+ if output_storage <> "" then
+ error (f_"-o libvirt: do not use the -os option");
+ OutputLibvirt output_conn
+ | `Local ->
+ if output_storage = "" then
+ error (f_"-o local: output directory was not specified, use
'-os /dir'");
+ let dir_exists + try Sys.is_directory output_storage with
Sys_error _ -> false in
+ if not dir_exists then
+ error (f_"-os %s: output directory does not exist or is not a
directory")
+ output_storage;
+ OutputLocal output_storage
+ | `RHEV ->
+ if output_storage = "" then
+ error (f_"-o local: output storage was not specified, use
'-os'");
+ OutputRHEV output_storage in
+
+ input, output,
+ debug_gc, output_alloc, output_format, output_name,
+ quiet, root_choice, trace, verbose
diff --git a/v2v/convert_linux_common.ml b/v2v/convert_linux_common.ml
new file mode 100644
index 0000000..4922e2f
--- /dev/null
+++ b/v2v/convert_linux_common.ml
@@ -0,0 +1,236 @@
+(* virt-v2v
+ * 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.
+ *)
+
+open Printf
+
+open Common_gettext.Gettext
+open Common_utils
+
+open Types
+open Utils
+
+module StringMap = Map.Make (String)
+let keys map = StringMap.fold (fun k _ ks -> k :: ks) map []
+
+(* Wrappers around aug_init & aug_load which can dump out full Augeas
+ * parsing problems when debugging is enabled.
+ *)
+let rec augeas_init verbose g + g#aug_init "/" 1;
+ if verbose then augeas_debug_errors g
+
+and augeas_reload verbose g + g#aug_load ();
+ if verbose then augeas_debug_errors g
+
+and augeas_debug_errors g + try
+ let errors = g#aug_match "/augeas/files//error" in
+ let errors = Array.to_list errors in
+ let map + List.fold_left (
+ fun map error ->
+ let detail_paths = g#aug_match (error ^ "//*") in
+ let detail_paths = Array.to_list detail_paths in
+ List.fold_left (
+ fun map path ->
+ (* path is
"/augeas/files/<filename>/error/<field>". Put
+ * <filename>, <field> and the value of this Augeas
field
+ * into a map.
+ *)
+ let i = string_find path "/error/" in
+ assert (i >= 0);
+ let filename = String.sub path 13 (i-13) in
+ let field = String.sub path (i+7) (String.length path - (i+7)) in
+
+ let detail = g#aug_get path in
+
+ let fmap : string StringMap.t + try StringMap.find
filename map
+ with Not_found -> StringMap.empty in
+ let fmap = StringMap.add field detail fmap in
+ StringMap.add filename fmap map
+ ) map detail_paths
+ ) StringMap.empty errors in
+
+ let filenames = keys map in
+ let filenames = List.sort compare filenames in
+
+ List.iter (
+ fun filename ->
+ printf "augeas failed to parse %s:\n" filename;
+ let fmap = StringMap.find filename map in
+ (try
+ let msg = StringMap.find "message" fmap in
+ printf " error \"%s\"" msg
+ with Not_found -> ()
+ );
+ (try
+ let line = StringMap.find "line" fmap
+ and char = StringMap.find "char" fmap in
+ printf " at line %s char %s" line char
+ with Not_found -> ()
+ );
+ (try
+ let lens = StringMap.find "lens" fmap in
+ printf " in lens %s" lens
+ with Not_found -> ()
+ );
+ printf "\n"
+ ) filenames;
+
+ flush stdout
+ with
+ G.Error msg -> eprintf "%s: augeas: %s (ignored)\n" prog msg
+
+let install verbose g inspect packages + assert false
+
+let remove verbose g inspect packages + if packages <> [] then (
+ let root = inspect.i_root in
+ let package_format = g#inspect_get_package_format root in
+ match package_format with
+ | "rpm" ->
+ let cmd = [ "rpm"; "-e" ] @ packages in
+ let cmd = Array.of_list cmd in
+ ignore (g#command cmd);
+
+ (* Reload Augeas in case anything changed. *)
+ augeas_reload verbose g
+
+ | format ->
+ error (f_"don't know how to remove packages using %s: packages:
%s")
+ format (String.concat " " packages)
+ )
+
+let file_owned verbose g inspect file + let root = inspect.i_root in
+ let package_format = g#inspect_get_package_format root in
+ match package_format with
+ | "rpm" ->
+ let cmd = [| "rpm"; "-qf"; file |] in
+ (try ignore (g#command cmd); true with G.Error _ -> false)
+
+ | format ->
+ error (f_"don't know how to find package owner using %s")
format
+
+type kernel_info = {
+ base_package : string; (* base package, eg. "kernel-PAE"
*)
+ version : string; (* kernel version *)
+ modules : string list; (* list of kernel modules *)
+ arch : string; (* kernel arch *)
+}
+
+(* There was some crazy SUSE stuff going on in the Perl version
+ * of virt-v2v, which I have dropped from this as I couldn't
+ * understand what on earth it was doing. - RWMJ
+ *)
+let inspect_linux_kernel verbose (g : Guestfs.guestfs) inspect path + let root
= inspect.i_root in
+
+ let base_package + let package_format = g#inspect_get_package_format root
in
+ match package_format with
+ | "rpm" ->
+ let cmd = [| "rpm"; "-qf"; "--qf";
"%{NAME}"; path |] in
+ g#command cmd
+ | format ->
+ error (f_"don't know how to inspect kernel using %s")
format in
+
+ (* Try to get kernel version by examination of the binary.
+ * See supermin.git/src/kernel.ml
+ *)
+ let version + try
+ let hdrS = g#pread path 4 514L in
+ if hdrS <> "HdrS" then raise Not_found;
+ let s = g#pread path 2 518L in
+ let s = (Char.code s.[1] lsl 8) lor Char.code s.[0] in
+ if s < 0x1ff then raise Not_found;
+ let offset = g#pread path 2 526L in
+ let offset = (Char.code offset.[1] lsl 8) lor Char.code offset.[0] in
+ if offset < 0 then raise Not_found;
+ let buf = g#pread path (offset + 0x200) 132L in
+ let rec loop i + if i < 132 then (
+ if buf.[i] = '\000' || buf.[i] = ' ' ||
+ buf.[i] = '\t' || buf.[i] = '\n' then
+ String.sub buf 0 i
+ else
+ loop (i+1)
+ )
+ else raise Not_found
+ in
+ let v = loop 0 in
+ (* There must be a corresponding modules directory. *)
+ let modpath = sprintf "/lib/modules/%s" v in
+ if not (g#is_dir modpath) then
+ raise Not_found;
+ Some (v, modpath)
+ with Not_found -> None in
+
+ (* Apparently Xen PV kernels don't contain a version number,
+ * so try to guess the version from the filename.
+ *)
+ let version + match version with
+ | Some v -> Some v
+ | None ->
+ let rex = Str.regexp "^/boot/vmlinuz-\\(.*\\)" in
+ if Str.string_match rex path 0 then (
+ let v = Str.matched_group 1 path in
+ let modpath = sprintf "/lib/modules/%s" v in
+ if g#is_dir modpath then Some (v, modpath) else None
+ )
+ else None in
+
+ (* If we sill didn't find a version, give up here. *)
+ match version with
+ | None -> None
+ | Some (version, modpath) ->
+
+ (* List modules. *)
+ let modules = g#find modpath in
+ let modules = Array.to_list modules in
+ let rex = Str.regexp ".*\\.k?o$" in
+ let modules = List.filter (fun m -> Str.string_match rex m 0) modules in
+
+ assert (List.length modules > 0);
+
+ (* Determine the kernel architecture by looking at the architecture
+ * of an arbitrary kernel module.
+ *)
+ let arch + let any_module = modpath ^ List.hd modules in
+ g#file_architecture any_module in
+
+ (* Just return the module names, without path or extension. *)
+ let rex = Str.regexp ".*/\\([^/]+\\)\\.k?o$/" in
+ let modules = filter_map (
+ fun m ->
+ if Str.string_match rex m 0 then
+ Some (Str.matched_group 1 m)
+ else
+ None
+ ) modules in
+
+ Some { base_package = base_package;
+ version = version;
+ modules = modules;
+ arch = arch }
diff --git a/v2v/convert_linux_common.mli b/v2v/convert_linux_common.mli
new file mode 100644
index 0000000..4ab621a
--- /dev/null
+++ b/v2v/convert_linux_common.mli
@@ -0,0 +1,45 @@
+(* virt-v2v
+ * 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.
+ *)
+
+(** Common Linux conversion code. *)
+
+val augeas_init : bool -> Guestfs.guestfs -> unit
+val augeas_reload : bool -> Guestfs.guestfs -> unit
+(** Wrappers around [g#aug_init] and [g#aug_load], which (if verbose)
+ provide additional debugging information about parsing problems
+ that augeas found. *)
+
+val install : bool -> Guestfs.guestfs -> Types.inspect -> string list
-> unit
+(** Install package(s) from the list in the guest (or ensure they are
+ installed). *)
+
+val remove : bool -> Guestfs.guestfs -> Types.inspect -> string list
-> unit
+(** Uninstall package(s). *)
+
+val file_owned : bool -> Guestfs.guestfs -> Types.inspect -> string
-> bool
+(** Returns true if the file is owned by an installed package. *)
+
+type kernel_info = {
+ base_package : string; (* base package, eg. "kernel-PAE"
*)
+ version : string; (* kernel version *)
+ modules : string list; (* list of kernel modules *)
+ arch : string; (* kernel arch *)
+}
+
+val inspect_linux_kernel : bool -> Guestfs.guestfs -> Types.inspect ->
string -> kernel_info option
+(** Inspect a Linux kernel (by path) and return various information. *)
diff --git a/v2v/convert_linux_enterprise.ml b/v2v/convert_linux_enterprise.ml
new file mode 100644
index 0000000..9496f1a
--- /dev/null
+++ b/v2v/convert_linux_enterprise.ml
@@ -0,0 +1,637 @@
+(* virt-v2v
+ * 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.
+ *)
+
+(* Convert various RPM-based Linux enterprise distros. This module
+ * handles:
+ *
+ * - RHEL and derivatives like CentOS and ScientificLinux
+ * - SUSE
+ * - OpenSUSE and Fedora (not enterprisey, but similar enough to RHEL/SUSE)
+ *)
+
+open Printf
+
+open Common_gettext.Gettext
+open Common_utils
+
+open Utils
+open Types
+
+let rec convert ?(keep_serial_console = true) verbose (g : Guestfs.guestfs)
+ ({ i_root = root; i_apps = apps }
+ as inspect) source + let typ = g#inspect_get_type root
+ and distro = g#inspect_get_distro root
+ and arch = g#inspect_get_arch root
+ and major_version = g#inspect_get_major_version root
+ and minor_version = g#inspect_get_minor_version root
+ and package_format = g#inspect_get_package_format root
+ and package_management = g#inspect_get_package_management root in
+
+ assert (typ = "linux");
+
+ let is_rhel_family + (distro = "rhel" || distro =
"centos"
+ || distro = "scientificlinux" || distro =
"redhat-based")
+
+ and is_suse_family + (distro = "sles" || distro =
"suse-based" || distro = "opensuse") in
+
+ let rec clean_rpmdb () + (* Clean RPM database. *)
+ assert (package_format = "rpm");
+ let dbfiles = g#glob_expand "/var/lib/rpm/__db.00?" in
+ let dbfiles = Array.to_list dbfiles in
+ List.iter g#rm_f dbfiles
+
+ and autorelabel () + (* Only do autorelabel if load_policy binary exists.
Actually
+ * loading the policy is problematic.
+ *)
+ if g#is_file ~followsymlinks:true "/usr/sbin/load_policy" then
+ g#touch "/.autorelabel";
+
+ and get_grub () + (* Detect if grub2 or grub1 is installed by trying to
create
+ * an object of each sort.
+ *)
+ try Convert_linux_grub.grub2 verbose g inspect
+ with Failure grub2_error ->
+ try Convert_linux_grub.grub1 verbose g inspect
+ with Failure grub1_error ->
+ error (f_"no grub configuration found in this guest.
+Grub2 error was: %s
+Grub1/grub-legacy error was: %s")
+ grub2_error grub1_error
+
+ and unconfigure_xen () + (* Remove kmod-xenpv-* (RHEL 3). *)
+ let xenmods + filter_map (
+ fun { G.app2_name = name } ->
+ if name = "kmod-xenpv" || string_prefix name
"kmod-xenpv-" then
+ Some name
+ else
+ None
+ ) apps in
+ Convert_linux_common.remove verbose g inspect xenmods;
+
+ (* Undo related nastiness if kmod-xenpv was installed. *)
+ if xenmods <> [] then (
+ (* kmod-xenpv modules may have been manually copied to other kernels.
+ * Hunt them down and destroy them.
+ *)
+ let dirs = g#find "/lib/modules" in
+ let dirs = Array.to_list dirs in
+ let dirs = List.filter (fun s -> string_find s "/xenpv"
>= 0) dirs in
+ let dirs = List.map ((^) "/lib/modules/") dirs in
+ let dirs = List.filter g#is_dir dirs in
+
+ (* Check it's not owned by an installed application. *)
+ let dirs = List.filter (
+ fun d -> not (Convert_linux_common.file_owned verbose g inspect d)
+ ) dirs in
+
+ (* Remove any unowned xenpv directories. *)
+ List.iter g#rm_rf dirs;
+
+ (* rc.local may contain an insmod or modprobe of the xen-vbd driver,
+ * added by an installation script.
+ *)
+ (try
+ let lines = g#read_lines "/etc/rc.local" in
+ let lines = Array.to_list lines in
+ let rex = Str.regexp
".*\\b\\(insmod|modprobe\\)\b.*\\bxen-vbd.*" in
+ let lines = List.map (
+ fun s ->
+ if Str.string_match rex s 0 then
+ "#" ^ s
+ else
+ s
+ ) lines in
+ let file = String.concat "\n" lines ^ "\n" in
+ g#write "/etc/rc.local" file
+ with
+ G.Error msg -> eprintf "%s: /etc/rc.local: %s
(ignored)\n" prog msg
+ );
+ );
+
+ if is_suse_family then (
+ (* Remove xen modules from INITRD_MODULES and DOMU_INITRD_MODULES. *)
+ let variables = ["INITRD_MODULES";
"DOMU_INITRD_MODULES"] in
+ let xen_modules = ["xennet"; "xen-vnif";
"xenblk"; "xen-vbd"] in
+ let modified = ref false in
+ List.iter (
+ fun var ->
+ List.iter (
+ fun xen_mod ->
+ let expr + sprintf
"/file/etc/sysconfig/kernel/%s/value[. = '%s']"
+ var xen_mod in
+ let entries = g#aug_match expr in
+ let entries = Array.to_list entries in
+ if entries <> [] then (
+ List.iter (fun e -> ignore (g#aug_rm e)) entries;
+ modified := true
+ )
+ ) xen_modules
+ ) variables;
+ if !modified then g#aug_save ()
+ );
+
+ and unconfigure_vbox () + (* Uninstall VirtualBox Guest Additions. *)
+ let package_name = "virtualbox-guest-additions" in
+ let has_guest_additions + List.exists (
+ fun { G.app2_name = name } -> name = package_name
+ ) apps in
+ if has_guest_additions then
+ Convert_linux_common.remove verbose g inspect [package_name];
+
+ (* Guest Additions might have been installed from a tarball. The
+ * above code won't detect this case. Look for the uninstall tool
+ * and try running it.
+ *
+ * Note that it's important we do this early in the conversion
+ * process, as this uninstallation script naively overwrites
+ * configuration files with versions it cached prior to
+ * installation.
+ *)
+ let vboxconfig = "/var/lib/VBoxGuestAdditions/config" in
+ if g#is_file ~followsymlinks:true vboxconfig then (
+ let lines = g#read_lines vboxconfig in
+ let lines = Array.to_list lines in
+ let rex = Str.regexp "^INSTALL_DIR=\\(.*\\)$" in
+ let lines = filter_map (
+ fun line ->
+ if Str.string_match rex line 0 then (
+ let vboxuninstall = Str.matched_group 1 line ^
"/uninstall.sh" in
+ Some vboxuninstall
+ )
+ else None
+ ) lines in
+ let lines = List.filter (g#is_file ~followsymlinks:true) lines in
+ match lines with
+ | [] -> ()
+ | vboxuninstall :: _ ->
+ try
+ ignore (g#command [| vboxuninstall |]);
+
+ (* Reload Augeas to detect changes made by vbox tools uninst. *)
+ Convert_linux_common.augeas_reload verbose g
+ with
+ G.Error msg ->
+ eprintf (f_"%s: warning: VirtualBox Guest Additions were
detected, but uninstallation failed. The error message was: %s (ignored)")
+ prog msg
+ )
+
+ and unconfigure_vmware () + (* Look for any configured VMware yum repos
and disable them. *)
+ let repos + g#aug_match "/files/etc/yum.repos.d/*/*[baseurl =~
regexp('https?://([^/]+\\.)?vmware\\.com/.*')]" in
+ let repos = Array.to_list repos in
+ List.iter (
+ fun repo ->
+ g#aug_set (repo ^ "/enabled") "0";
+ g#aug_save ()
+ ) repos;
+
+ (* Uninstall VMware Tools. *)
+ let remove = ref [] and libraries = ref [] in
+ List.iter (
+ fun { G.app2_name = name } ->
+ if name = "open-vm-tools" then
+ remove := name :: !remove
+ else if string_prefix name "vmware-tools-libraries-" then
+ libraries := name :: !libraries
+ else if string_prefix name "vmware-tools-" then
+ remove := name :: !remove
+ ) apps;
+ let libraries = !libraries in
+
+ (* VMware tools includes 'libraries' packages which provide custom
+ * versions of core functionality. We need to install non-custom
+ * versions of everything provided by these packages before
+ * attempting to uninstall them, or we'll hit dependency
+ * issues.
+ *)
+ if libraries <> [] then (
+ (* We only support removal of libraries on systems which use yum. *)
+ if package_management = "yum" then (
+ List.iter (
+ fun library ->
+ let provides + g#command_lines [| "rpm";
"-q"; "--provides"; library |] in
+ let provides = Array.to_list provides in
+
+ (* The packages provide themselves, filter this out. *)
+ let provides + List.filter (fun s -> string_find s
library = -1) provides in
+
+ (* Trim whitespace. *)
+ let rex = Str.regexp "^[ \\t]*\\([^ \\t]+\\)[ \\t]*$" in
+ let provides = List.map (Str.replace_first rex "\\1")
provides in
+
+ (* Install the dependencies with yum. Use yum explicitly
+ * because we don't have package names and local install is
+ * impractical. - RWMJ: Not convinced the original Perl code
+ * would work, so I'm just installing the dependencies.
+ *)
+ let cmd = [ "yum"; "install"; "-y" ]
@ provides in
+ let cmd = Array.of_list cmd in
+ (try
+ ignore (g#command cmd);
+ remove := library :: !remove
+ with G.Error msg ->
+ eprintf "%s: could not install replacement for %s. Error
was: %s. %s was not removed.\n"
+ prog library msg library
+ );
+ ) libraries
+ )
+ );
+
+ let remove = !remove in
+ Convert_linux_common.remove verbose g inspect remove;
+
+ (* VMware Tools may have been installed from a tarball, so the
+ * above code won't remove it. Look for the uninstall tool and run
+ * if present.
+ *)
+ let uninstaller = "/usr/bin/vmware-uninstall-tools.pl" in
+ if g#is_file ~followsymlinks:true uninstaller then (
+ try
+ ignore (g#command [| uninstaller |]);
+
+ (* Reload Augeas to detect changes made by vbox tools uninst. *)
+ Convert_linux_common.augeas_reload verbose g
+ with
+ G.Error msg ->
+ eprintf (f_"%s: warning: VMware tools was detected, but
uninstallation failed. The error message was: %s (ignored)")
+ prog msg
+ )
+
+ and unconfigure_citrix () + let pkgs + List.filter (
+ fun { G.app2_name = name } -> string_prefix name
"xe-guest-utilities"
+ ) apps in
+ let pkgs = List.map (fun { G.app2_name = name } -> name) pkgs in
+
+ if pkgs <> [] then (
+ Convert_linux_common.remove verbose g inspect pkgs;
+
+ (* Installing these guest utilities automatically unconfigures
+ * ttys in /etc/inittab if the system uses it. We need to put
+ * them back.
+ *)
+ let rex = Str.regexp
"^\\([1-6]\\):\\([2-5]+\\):respawn:\\(.*\\)" in
+ let updated = ref false in
+ let rec loop () + let comments = g#aug_match
"/files/etc/inittab/#comment" in
+ let comments = Array.to_list comments in
+ match comments with
+ | [] -> ()
+ | commentp :: _ ->
+ let comment = g#aug_get commentp in
+ if Str.string_match rex comment 0 then (
+ let name = Str.matched_group 1 comment in
+ let runlevels = Str.matched_group 2 comment in
+ let process = Str.matched_group 3 comment in
+
+ if string_find process "getty" >= 0 then (
+ updated := true;
+
+ (* Create a new entry immediately after the comment. *)
+ g#aug_insert commentp name false;
+ g#aug_set ("/files/etc/inittab/" ^ name ^
"/runlevels") runlevels;
+ g#aug_set ("/files/etc/inittab/" ^ name ^
"/action") "respawn";
+ g#aug_set ("/files/etc/inittab/" ^ name ^
"/process") process;
+
+ (* Delete the comment node. *)
+ ignore (g#aug_rm commentp);
+
+ (* As the aug_rm invalidates the output of aug_match, we
+ * now have to restart the whole loop.
+ *)
+ loop ()
+ )
+ )
+ in
+ loop ();
+ if !updated then g#aug_save ();
+ )
+
+ and install_virtio () + (* How you install virtio depends on the guest
type. Note that most
+ * modern guests already support virtio, so we do nothing for them.
+ * In Perl virt-v2v this was done via a configuration database
+ * (virt-v2v.db). This function returns true if virtio is supported
+ * already or if we managed to install it.
+ *)
+ match distro, major_version, minor_version with
+ (* RHEL 6+ has always supported virtio. *)
+ |
("rhel"|"centos"|"scientificlinux"|"redhat-based"),
v, _ when v >= 6 ->
+ true
+ |
("rhel"|"centos"|"scientificlinux"|"redhat-based"),
5, _ ->
+ let kernel = upgrade_package "kernel" (0_l, "2.6.18",
"128.el5") in
+ let lvm2 = upgrade_package "lvm2" (0_l, "2.02.40",
"6.el5") in
+ let selinux + upgrade_package ~ifinstalled:true
+ "selinux-policy-targeted" (0_l, "2.4.6",
"203.el5") in
+ kernel && lvm2 && selinux
+ |
("rhel"|"centos"|"scientificlinux"|"redhat-based"),
4, _ ->
+ upgrade_package "kernel" (0_l, "2.6.9",
"89.EL")
+
+ (* All supported Fedora versions support virtio. *)
+ | "fedora", _, _ -> true
+
+ (* SLES 11 supports virtio in the kernel. *)
+ | ("sles"|"suse-based"), v, _ when v >= 11 ->
true
+ | ("sles"|"suse-based"), 10, _ ->
+ upgrade_package "kernel" (0_l, "2.6.16.60",
"0.85.1")
+
+ (* OpenSUSE. *)
+ | "opensuse", v, _ when v >= 11 -> true
+ | "opensuse", 10, _ ->
+ upgrade_package "kernel" (0_l, "2.6.25.5",
"1.1")
+
+ | _ ->
+ eprintf (f_"%s: warning: don't know how to install virtio
drivers for %s %d")
+ prog distro major_version;
+ false
+
+ and configure_kernel virtio grub + let kernels = grub#list_kernels () in
+
+ let bootable_kernel + let rec loop + function
+ | [] -> None
+ | path :: paths ->
+ let kernel + Convert_linux_common.inspect_linux_kernel
verbose g inspect path in
+ match kernel with
+ | None -> loop paths
+ | Some kernel when is_hv_kernel kernel -> loop paths
+ | Some kernel when virtio && not (supports_virtio kernel)
->
+ loop paths
+ | Some kernel -> Some kernel
+ in
+ loop kernels in
+
+ (* If virtio == true, then a virtio kernel should have been
+ * installed. If we didn't find one, it indicates a bug in
+ * virt-v2v.
+ *)
+ if virtio && bootable_kernel = None then
+ error (f_"virtio configured, but no virtio kernel found");
+
+ (* No bootable kernel was found. Install one. *)
+ let bootable_kernel + match bootable_kernel with
+ | Some k -> k
+ | None ->
+ (* Find which kernel is currently used by the guest. *)
+ let current_kernel + let rec loop = function
+ | [] -> "kernel"
+ | path :: paths ->
+ let kernel +
Convert_linux_common.inspect_linux_kernel verbose g inspect
+ path in
+ match kernel with
+ | None -> loop paths
+ | Some kernel -> kernel.Convert_linux_common.base_package
+ in
+ loop kernels in
+
+ (* Replace kernel-xen with a suitable kernel. *)
+ let current_kernel + if string_find current_kernel
"kernel-xen" >= 0 then
+ xen_replacement_kernel ()
+ else
+ current_kernel in
+
+ (* Install the kernel. However we need a way to detect the
+ * version of the kernel that has just been installed. A quick
+ * way is to compare /lib/modules before and after.
+ *)
+ let files1 = g#ls "/lib/modules" in
+ let files1 = Array.to_list files1 in
+ Convert_linux_common.install verbose g inspect [current_kernel];
+ let files2 = g#ls "/lib/modules" in
+ let files2 = Array.to_list files2 in
+
+ (* Note that g#ls is guaranteed to return the strings in order. *)
+ let rec loop files1 files2 + match files1, files2 with
+ | [], [] ->
+ error (f_"tried to install '%s', but no kernel package
was installed") current_kernel
+ | (v1 :: _), [] ->
+ error (f_"tried to install '%s', but there are now
fewer directories under /lib/modules!") current_kernel
+ | [], (v2 :: _) -> v2
+ | (v1 :: _), (v2 :: _) when v1 <> v2 -> v2
+ | (_ :: v1s), (_ :: v2s) -> loop v1s v2s
+ in
+ let version = loop files1 files2 in
+
+ { Convert_linux_common.base_package = current_kernel;
+ version = version; modules = []; arch = "" } in
+
+ (* Set /etc/sysconfig/kernel DEFAULTKERNEL to point to the new
+ * kernel package name.
+ *)
+ if g#is_file ~followsymlinks:true "/etc/sysconfig/kernel" then (
+ let base_package = bootable_kernel.Convert_linux_common.base_package in
+ let paths + g#aug_match
"/files/etc/sysconfig/kernel/DEFAULTKERNEL/value" in
+ let paths = Array.to_list paths in
+ List.iter (fun path -> g#aug_set path base_package) paths;
+ g#aug_save ()
+ );
+
+ (* Return the installed kernel version. *)
+ bootable_kernel.Convert_linux_common.version
+
+ and supports_virtio { Convert_linux_common.modules = modules } + List.mem
"virtio_blk" modules && List.mem "virtio_net"
modules
+
+ (* Is it a hypervisor-specific kernel? *)
+ and is_hv_kernel { Convert_linux_common.modules = modules } + List.mem
"xennet" modules (* Xen PV kernel. *)
+
+ (* Find a suitable replacement for kernel-xen. *)
+ and xen_replacement_kernel () + if is_rhel_family then (
+ match major_version, arch with
+ | 5,
("i386"|"i486"|"i586"|"i686") ->
"kernel-PAE"
+ | 5, _ -> "kernel"
+ | 4,
("i386"|"i486"|"i586"|"i686") ->
+ (* If guest has >= 10GB of RAM, give it a hugemem kernel. *)
+ if source.s_memory >= 10L *^ 1024L *^ 1024L *^ 1024L then
+ "kernel-hugemem"
+ (* SMP kernel for guests with > 1 vCPU. *)
+ else if source.s_vcpu > 1 then
+ "kernel-smp"
+ else
+ "kernel"
+ | 4, _ ->
+ if source.s_vcpu > 8 then "kernel-largesmp"
+ else if source.s_vcpu > 1 then "kernel-smp"
+ else "kernel"
+ | _, _ -> "kernel"
+ )
+ else if is_suse_family then (
+ match distro, major_version, arch with
+ | "opensuse", _, _ -> "kernel-default"
+ | _, v,
("i386"|"i486"|"i586"|"i686") when v
>= 11 ->
+ if source.s_memory >= 10L *^ 1024L *^ 1024L *^ 1024L then
+ "kernel-pae"
+ else
+ "kernel"
+ | _, v, _ when v >= 11 -> "kernel-default"
+ | _, 10,
("i386"|"i486"|"i586"|"i686") ->
+ if source.s_memory >= 10L *^ 1024L *^ 1024L *^ 1024L then
+ "kernel-bigsmp"
+ else if source.s_vcpu > 1 then
+ "kernel-smp"
+ else
+ "kernel-default"
+ | _, 10, _ ->
+ if source.s_vcpu > 1 then
+ "kernel-smp"
+ else
+ "kernel-default"
+ | _ -> "kernel-default"
+ )
+ else
+ "kernel" (* conservative default *)
+
+ (* We configure a console on ttyS0. Make sure existing console
+ * references use it. N.B. Note that the RHEL 6 xen guest kernel
+ * presents a console device called /dev/hvc0, whereas previous xen
+ * guest kernels presented /dev/xvc0. The regular kernel running
+ * under KVM also presents a virtio console device called /dev/hvc0,
+ * so ideally we would just leave it alone. However, RHEL 6 libvirt
+ * doesn't yet support this device so we can't attach to it. We
+ * therefore use /dev/ttyS0 for RHEL 6 anyway.
+ *)
+ and configure_console () + (* Look for gettys using xvc0 or hvc0. RHEL 6
doesn't use inittab
+ * but this still works.
+ *)
+ let paths = g#aug_match "/files/etc/inittab/*/process" in
+ let paths = Array.to_list paths in
+ let rex = Str.regexp "\\(.*\\)\\b\\([xh]vc0\\)\\b\\(.*\\)" in
+ List.iter (
+ fun path ->
+ let proc = g#aug_get path in
+ if Str.string_match rex proc 0 then (
+ let proc = Str.global_replace rex "\\1ttyS0\\3" proc in
+ g#aug_set path proc
+ );
+ ) paths;
+
+ let paths = g#aug_match "/files/etc/securetty/*" in
+ let paths = Array.to_list paths in
+ List.iter (
+ fun path ->
+ let tty = g#aug_get path in
+ if tty = "xvc0" || tty = "hvc0" then
+ g#aug_set path "ttyS0"
+ ) paths;
+
+ g#aug_save ()
+
+ (* If the target doesn't support a serial console, we want to remove
+ * all references to it instead.
+ *)
+ and remove_console () + (* Look for gettys using xvc0 or hvc0. RHEL 6
doesn't use inittab
+ * but this still works.
+ *)
+ let paths = g#aug_match "/files/etc/inittab/*/process" in
+ let paths = Array.to_list paths in
+ let rex = Str.regexp ".*\\b\\([xh]vc0|ttyS0\\)\\b.*" in
+ List.iter (
+ fun path ->
+ let proc = g#aug_get path in
+ if Str.string_match rex proc 0 then
+ ignore (g#aug_rm (path ^ "/.."))
+ ) paths;
+
+ let paths = g#aug_match "/files/etc/securetty/*" in
+ let paths = Array.to_list paths in
+ List.iter (
+ fun path ->
+ let tty = g#aug_get path in
+ if tty = "xvc0" || tty = "hvc0" then
+ ignore (g#aug_rm path)
+ ) paths;
+
+ g#aug_save ()
+
+ (* Upgrade 'pkg' to >= minversion. Returns true if that was
possible. *)
+ and upgrade_package ?(ifinstalled = false) name minversion +
+
+
+
+
+ (* XXX *)
+ true
+
+
+ in
+
+ clean_rpmdb ();
+ autorelabel ();
+ Convert_linux_common.augeas_init verbose g;
+ let grub = get_grub () in
+
+ unconfigure_xen ();
+ unconfigure_vbox ();
+ unconfigure_vmware ();
+ unconfigure_citrix ();
+
+ let virtio = install_virtio () in
+ let kernel_version = configure_kernel virtio grub in (*XXX*) ignore
kernel_version;
+ if keep_serial_console then (
+ configure_console ();
+ grub#configure_console ()
+ ) else (
+ remove_console ();
+ grub#remove_console ()
+ );
+
+
+
+
+
+
+
+
+
+ let guestcaps = {
+ gcaps_block_bus = if virtio then "virtio" else "ide";
+ gcaps_net_bus = if virtio then "virtio" else "e1000";
+ (* XXX display *)
+ } in
+
+ guestcaps
diff --git a/v2v/convert_linux_enterprise.mli b/v2v/convert_linux_enterprise.mli
new file mode 100644
index 0000000..f55ce15
--- /dev/null
+++ b/v2v/convert_linux_enterprise.mli
@@ -0,0 +1,19 @@
+(* virt-v2v
+ * 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.
+ *)
+
+val convert : ?keep_serial_console:bool -> bool -> Guestfs.guestfs ->
Types.inspect -> Types.source -> Types.guestcaps
diff --git a/v2v/convert_linux_grub.ml b/v2v/convert_linux_grub.ml
new file mode 100644
index 0000000..59dd4f2
--- /dev/null
+++ b/v2v/convert_linux_grub.ml
@@ -0,0 +1,330 @@
+(* virt-v2v
+ * 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.
+ *)
+
+module G = Guestfs
+
+open Printf
+
+open Common_gettext.Gettext
+
+open Utils
+open Types
+
+(* Helper function for SUSE: remove (hdX,X) prefix from a path. *)
+let remove_hd_prefix + let rex = Str.regexp "^(hd.*)\\(.*\\)" in
+ Str.replace_first rex "\\1"
+
+(* Helper function to check if guest is EFI. *)
+let check_efi g + if Array.length (g#glob_expand
"/boot/efi/EFI/*/grub.cfg") < 1 then
+ raise Not_found;
+
+ (* Check the first partition of each device looking for an EFI
+ * boot partition. We can't be sure which device is the boot
+ * device, so we just check them all.
+ *)
+ let devs = g#list_devices () in
+ let devs = Array.to_list devs in
+ List.find (
+ fun dev ->
+ try g#part_get_gpt_type dev 1 =
"C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
+ with G.Error _ -> false
+ ) devs
+
+(* Virtual grub superclass. *)
+class virtual grub verbose (g : Guestfs.guestfs) inspect config_file +object
+ method virtual list_kernels : unit -> string list
+
+ method virtual configure_console : unit -> unit
+ method virtual remove_console : unit -> unit
+
+ method private get_default_image () + let cmd + if g#exists
"/sbin/grubby" then
+ [| "grubby"; "--default-kernel" |]
+ else
+ [| "/usr/bin/perl"; "-MBootloader::Tools";
"-e"; "
+ InitLibrary();
+ my $default = Bootloader::Tools::GetDefaultSection();
+ print $default->{image};
+ " |] in
+ match g#command cmd with
+ | "" -> None
+ | k ->
+ let len = String.length k in
+ let k + if len > 0 && k.[len-1] = '\n' then
String.sub k 0 (len-1) else k in
+ Some (remove_hd_prefix k)
+end
+
+(* Concrete implementation for grub1. *)
+class grub1 verbose g inspect config_file grub_fs +object (self)
+ inherit grub verbose g inspect config_file
+
+ method private grub_fs = grub_fs (* grub filesystem prefix *)
+
+ method list_kernels () + let paths + let expr = sprintf
"/files%s/title/kernel" config_file in
+ let paths = g#aug_match expr in
+ let paths = Array.to_list paths in
+
+ (* Get the default kernel from grub if it's set. *)
+ let default + let expr = sprintf "/files%s/default"
config_file in
+ try
+ let idx = g#aug_get expr in
+ let idx = int_of_string idx in
+ (* Grub indices are zero-based, augeas is 1-based. *)
+ let expr = sprintf "/files%s/title[%d]/kernel" config_file
(idx+1) in
+ Some expr
+ with Not_found -> None in
+
+ (* If a default kernel was set, put it at the beginning of the paths
+ * list.
+ *)
+ match default with
+ | None -> paths
+ | Some p -> p :: List.filter ((<>) p) paths in
+
+ (* Remove duplicates. *)
+ let paths + let checked = Hashtbl.create 13 in
+ let rec loop = function
+ | [] -> []
+ | p :: ps when Hashtbl.mem checked p -> ps
+ | p :: ps -> Hashtbl.add checked p true; p :: loop ps
+ in
+ loop paths in
+
+ (* Resolve the Augeas paths to kernel filenames. *)
+ let kernels = List.map g#aug_get paths in
+
+ (* Make sure kernel does not begin with (hdX,X). *)
+ let kernels = List.map remove_hd_prefix kernels in
+
+ (* Prepend grub filesystem. *)
+ let kernels = List.map ((^) grub_fs) kernels in
+
+ (* Check the actual file exists. *)
+ let kernels = List.filter (g#is_file ~followsymlinks:true) kernels in
+
+ kernels
+
+ method configure_console () + let rex = Str.regexp
"\\(.*\\)\\b\\([xh]vc0\\)\\b\\(.*\\)" in
+ let expr = sprintf "/files%s/title/kernel/console" config_file in
+
+ let paths = g#aug_match expr in
+ let paths = Array.to_list paths in
+ List.iter (
+ fun path ->
+ let console = g#aug_get path in
+ if Str.string_match rex console 0 then (
+ let console = Str.global_replace rex "\\1ttyS0\\3" console
in
+ g#aug_set path console
+ )
+ ) paths;
+
+ g#aug_save ()
+
+ method remove_console () + let rex = Str.regexp
"\\(.*\\)\\b\\([xh]vc0\\)\\b\\(.*\\)" in
+ let expr = sprintf "/files%s/title/kernel/console" config_file in
+
+ let rec loop = function
+ | [] -> ()
+ | path :: paths ->
+ let console = g#aug_get path in
+ if Str.string_match rex console 0 then (
+ ignore (g#aug_rm path);
+ (* All the paths are invalid, restart the loop. *)
+ let paths = g#aug_match expr in
+ let paths = Array.to_list paths in
+ loop paths
+ )
+ else
+ loop paths
+ in
+ let paths = g#aug_match expr in
+ let paths = Array.to_list paths in
+ loop paths;
+
+ g#aug_save ()
+
+end
+
+(* Create a grub1 object. *)
+let rec grub1 verbose (g : Guestfs.guestfs) inspect + let root =
inspect.i_root in
+
+ (* Look for a grub configuration file. *)
+ let config_file + try
+ List.find (
+ fun file -> g#is_file ~followsymlinks:true file
+ ) ["/boot/grub/menu.lst"; "/boot/grub/grub.conf"]
+ with
+ Not_found ->
+ failwith (s_"no grub/grub1/grub-legacy configuration file was
found") in
+
+ (* Check for EFI and convert if found. *)
+ (try let dev = check_efi g in grub1_convert_from_efi verbose g dev
+ with Not_found -> ()
+ );
+
+ (* Find the path that has to be prepended to filenames in grub.conf
+ * in order to make them absolute.
+ *)
+ let grub_fs + let mounts = g#inspect_get_mountpoints root in
+ try
+ List.find (
+ fun path -> List.mem_assoc path mounts
+ ) [ "/boot/grub"; "/boot" ]
+ with Not_found -> "" in
+
+ (* Ensure Augeas is reading the grub configuration file, and if not
+ * then add it.
+ *)
+ let () + let incls = g#aug_match "/augeas/load/Grub/incl" in
+ let incls = Array.to_list incls in
+ let incls_contains_conf + List.exists (fun incl -> g#aug_get incl =
config_file) incls in
+ if not incls_contains_conf then (
+ g#aug_set "/augeas/load/Grub/incl[last()+1]" config_file;
+ Convert_linux_common.augeas_reload verbose g;
+ ) in
+
+ new grub1 verbose g inspect config_file grub_fs
+
+(* Reinstall grub. *)
+and grub1_convert_from_efi verbose g dev + g#cp "/etc/grub.conf"
"/boot/grub/grub.conf";
+ g#ln_sf "/boot/grub/grub.conf" "/etc/grub.conf";
+
+ (* Reload Augeas to pick up new location of grub.conf. *)
+ Convert_linux_common.augeas_reload verbose g;
+
+ ignore (g#command [| "grub-install"; dev |])
+
+(* Concrete implementation for grub2. *)
+class grub2 verbose g inspect config_file +object (self)
+ inherit grub verbose g inspect config_file
+
+ method list_kernels () + let files + (match self#get_default_image ()
with
+ | None -> []
+ | Some k -> [k]) @
+ (* This is how the grub2 config generator enumerates kernels. *)
+ Array.to_list (g#glob_expand "/boot/kernel-*") @
+ Array.to_list (g#glob_expand "/boot/vmlinuz-*") @
+ Array.to_list (g#glob_expand "/vmlinuz-*") in
+ let rex = Str.regexp ".*\\.\\(dpkg-.*|rpmsave|rpmnew\\)$" in
+ let files = List.filter (
+ fun file -> not (Str.string_match rex file 0)
+ ) files in
+ files
+
+ method private update_console ~remove + let rex = Str.regexp
"\\(.*\\)\\bconsole=[xh]vc0\\b\\(.*\\)" in
+
+ let grub_cmdline_expr + if g#exists "/etc/sysconfig/grub"
then
+ "/files/etc/sysconfig/grub/GRUB_CMDLINE_LINUX"
+ else
+ "/files/etc/default/grub/GRUB_CMDLINE_LINUX_DEFAULT" in
+
+ (try
+ let grub_cmdline = g#aug_get grub_cmdline_expr in
+ let grub_cmdline + if Str.string_match rex grub_cmdline 0 then (
+ if remove then
+ Str.global_replace rex "\\1\\3" grub_cmdline
+ else
+ Str.global_replace rex "\\1console=ttyS0\\3"
grub_cmdline
+ )
+ else grub_cmdline in
+ g#aug_set grub_cmdline_expr grub_cmdline;
+ g#aug_save ();
+
+ ignore (g#command [| "grub2-mkconfig"; "-o";
config_file |])
+ with
+ G.Error msg ->
+ eprintf (f_"%s: warning: could not update grub2 console: %s
(ignored)\n")
+ prog msg
+ )
+
+ method configure_console () = self#update_console ~remove:false
+ method remove_console () = self#update_console ~remove:true
+end
+
+let rec grub2 verbose (g : Guestfs.guestfs) inspect + (* Look for a grub2
configuration file. *)
+ let config_file = "/boot/grub2/grub.cfg" in
+ if not (g#is_file ~followsymlinks:true config_file) then (
+ let msg + sprintf (f_"no grub2 configuration file was found
(expecting %s)")
+ config_file in
+ failwith msg
+ );
+
+ (* Check for EFI and convert if found. *)
+ (try
+ let dev = check_efi g in
+ grub2_convert_from_efi verbose g inspect dev
+ with Not_found -> ()
+ );
+
+ new grub2 verbose g inspect config_file
+
+(* For grub2:
+ * - Turn the EFI partition into a BIOS Boot Partition
+ * - Remove the former EFI partition from fstab
+ * - Install the non-EFI version of grub
+ * - Install grub2 in the BIOS Boot Partition
+ * - Regenerate grub.cfg
+ *)
+and grub2_convert_from_efi verbose g inspect dev + (* EFI systems boot using
grub2-efi, and probably don't have the
+ * base grub2 package installed.
+ *)
+ Convert_linux_common.install verbose g inspect ["grub2"];
+
+ (* Relabel the EFI boot partition as a BIOS boot partition. *)
+ g#part_set_gpt_type dev 1 "21686148-6449-6E6F-744E-656564454649";
+
+ (* Delete the fstab entry for the EFI boot partition. *)
+ let nodes = g#aug_match "/files/etc/fstab/*[file =
'/boot/efi']" in
+ let nodes = Array.to_list nodes in
+ List.iter (fun node -> ignore (g#aug_rm node)) nodes;
+ g#aug_save ();
+
+ (* Install grub2 in the BIOS boot partition. This overwrites the
+ * previous contents of the EFI boot partition.
+ *)
+ ignore (g#command [| "grub2-install"; dev |]);
+
+ (* Re-generate the grub2 config, and put it in the correct place *)
+ ignore (g#command [| "grub2-mkconfig"; "-o";
"/boot/grub2/grub.cfg" |])
diff --git a/v2v/convert_linux_grub.mli b/v2v/convert_linux_grub.mli
new file mode 100644
index 0000000..324a333
--- /dev/null
+++ b/v2v/convert_linux_grub.mli
@@ -0,0 +1,43 @@
+(* virt-v2v
+ * 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.
+ *)
+
+(** Common code handling grub1 (grub-legacy) and grub2 operations. *)
+
+class type virtual grub = object
+ method virtual list_kernels : unit -> string list
+ (** Return a list of kernels from the grub configuration. The
+ returned list is a list of filenames. *)
+ method virtual configure_console : unit -> unit
+ (** Reconfigure the grub console. *)
+ method virtual remove_console : unit -> unit
+ (** Remove the grub console configuration. *)
+end
+
+val grub1 : bool -> Guestfs.guestfs -> Types.inspect -> grub
+(** Detect if grub1/grub-legacy is used by this guest and return a
+ grub object if so.
+
+ This raises [Failure] if grub1 is not used by this guest or some
+ other problem happens. *)
+
+val grub2 : bool -> Guestfs.guestfs -> Types.inspect -> grub
+(** Detect if grub2 is used by this guest and return a grub object
+ if so.
+
+ This raises [Failure] if grub2 is not used by this guest or some
+ other problem happens. *)
diff --git a/v2v/convert_windows.ml b/v2v/convert_windows.ml
new file mode 100644
index 0000000..e5d1ea8
--- /dev/null
+++ b/v2v/convert_windows.ml
@@ -0,0 +1,22 @@
+(* virt-v2v
+ * 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.
+ *)
+
+(* Convert Windows guests. *)
+
+let convert verbose g inspect + assert false
diff --git a/v2v/convert_windows.mli b/v2v/convert_windows.mli
new file mode 100644
index 0000000..d1e60fe
--- /dev/null
+++ b/v2v/convert_windows.mli
@@ -0,0 +1,19 @@
+(* virt-v2v
+ * 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.
+ *)
+
+val convert : bool -> Guestfs.guestfs -> Types.inspect ->
Types.guestcaps
diff --git a/v2v/link.sh.in b/v2v/link.sh.in
new file mode 100644
index 0000000..9e5684b
--- /dev/null
+++ b/v2v/link.sh.in
@@ -0,0 +1,22 @@
+# libguestfs Makefile.am
+# @configure_input@
+# (C) Copyright 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+# Hack automake to link binary properly. There is no other way to add
+# the -cclib parameter to the end of the command line.
+
+exec "$@" -linkpkg -cclib '-lutils -lncurses @LIBXML2_LIBS@
-lgnu'
diff --git a/v2v/source_libvirt.ml b/v2v/source_libvirt.ml
new file mode 100644
index 0000000..bdea8d4
--- /dev/null
+++ b/v2v/source_libvirt.ml
@@ -0,0 +1,118 @@
+(* virt-v2v
+ * 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.
+ *)
+
+open Printf
+
+open Common_gettext.Gettext
+open Common_utils
+
+open Types
+open Utils
+
+let create_xml xml + let doc = Xml.parse_memory xml in
+ let xpathctx = Xml.xpath_new_context doc in
+
+ let xpath_to_string expr default + let obj = Xml.xpath_eval_expression
xpathctx expr in
+ if Xml.xpathobj_nr_nodes obj < 1 then default
+ else (
+ let node = Xml.xpathobj_node doc obj 0 in
+ Xml.node_as_string node
+ ) in
+ let xpath_to_int expr default + let obj = Xml.xpath_eval_expression
xpathctx expr in
+ if Xml.xpathobj_nr_nodes obj < 1 then default
+ else (
+ let node = Xml.xpathobj_node doc obj 0 in
+ let str = Xml.node_as_string node in
+ try int_of_string str
+ with Failure "int_of_string" ->
+ error (f_"expecting XML expression to return an integer
(expression: %s)")
+ expr
+ ) in
+
+ let dom_type = xpath_to_string "/domain/@type" "" in
+ let name = xpath_to_string "/domain/name/text()" "" in
+ let memory = xpath_to_int "/domain/memory/text()" 0 in
+ let memory = Int64.of_int memory *^ 1024L in
+ let vcpu = xpath_to_int "/domain/vcpu/text()" 0 in
+ let arch = xpath_to_string "/domain/os/type/@arch" "" in
+
+ let features + let features = ref [] in
+ let obj = Xml.xpath_eval_expression xpathctx "/domain/features/*"
in
+ let nr_nodes = Xml.xpathobj_nr_nodes obj in
+ for i = 0 to nr_nodes-1 do
+ let node = Xml.xpathobj_node doc obj i in
+ features := Xml.node_name node :: !features
+ done;
+ !features in
+
+ (* Non-removable disk devices. *)
+ let disks + let disks = ref [] in
+ let obj + Xml.xpath_eval_expression xpathctx
+ "/domain/devices/disk[@device='disk']" in
+ let nr_nodes = Xml.xpathobj_nr_nodes obj in
+ if nr_nodes < 1 then
+ error (f_"this guest has no non-removable disks");
+ for i = 0 to nr_nodes-1 do
+ let node = Xml.xpathobj_node doc obj i in
+ Xml.xpathctx_set_current_context xpathctx node;
+ let path = xpath_to_string "source/@file | source/@dev"
"" in
+ if path <> "" then (
+ let format + let format = xpath_to_string
"driver/@type" "" in
+ if format <> "" then Some format else None in
+ disks := (path, format) :: !disks
+ )
+ done;
+ List.rev !disks in
+
+ (* XXX Much more metadata needs to be collected here:
+ * - graphics
+ * - cdroms
+ * - floppies
+ * - network interfaces
+ * See: lib/Sys/VirtConvert/Connection/LibVirt.pm
+ *)
+
+ {
+ s_dom_type = dom_type;
+ s_name = name;
+ s_memory = memory;
+ s_vcpu = vcpu;
+ s_arch = arch;
+ s_features = features;
+ s_disks = disks;
+ }
+
+let create_from_xml file + let xml = read_whole_file file in
+ create_xml xml
+
+let create libvirt_uri guest + let cmd + match libvirt_uri with
+ | None -> sprintf "virsh dumpxml %s" (quote guest)
+ | Some uri -> sprintf "virsh -c %s dumpxml %s" (quote uri)
(quote guest) in
+ let lines = external_command ~prog cmd in
+ let xml = String.concat "\n" lines in
+ create_xml xml
diff --git a/v2v/source_libvirt.mli b/v2v/source_libvirt.mli
new file mode 100644
index 0000000..1e3b1e1
--- /dev/null
+++ b/v2v/source_libvirt.mli
@@ -0,0 +1,27 @@
+(* virt-v2v
+ * 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.
+ *)
+
+(** [-i libvirt] and [-i libvirtxml] sources. *)
+
+val create : string option -> string -> Types.source
+(** [create libvirt_uri guest] reads the source metadata from the
+ named libvirt guest. *)
+
+val create_from_xml : string -> Types.source
+(** [create_from_xml filename] reads the source metadata from the
+ libvirt XML file. *)
diff --git a/v2v/target_local.ml b/v2v/target_local.ml
new file mode 100644
index 0000000..ed4e5e3
--- /dev/null
+++ b/v2v/target_local.ml
@@ -0,0 +1,86 @@
+(* virt-v2v
+ * 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.
+ *)
+
+open Printf
+
+open Common_gettext.Gettext
+open Common_utils
+
+open Types
+open Utils
+
+let initialize dir overlays + List.map (
+ fun ov ->
+ let target_file = dir // "disk-" ^ ov.ov_sd in
+ { ov with ov_target_file = target_file; ov_target_file_tmp = target_file
}
+ ) overlays
+
+let create_metadata dir source overlays guestcaps + let name = source.s_name
in
+ let file = dir // name ^ ".xml" in
+
+ let chan = open_out file in
+ let p fs = fprintf chan fs in
+
+ p "<domain type='%s'>\n" "kvm"; (* Always
assume target is kvm? *)
+ p " <name>%s</name>\n" name;
+ let memory_k = source.s_memory /^ 1024L in
+ p " <memory unit='KiB'>%Ld</memory>\n"
memory_k;
+ p " <currentMemory
unit='KiB'>%Ld</currentMemory>\n" memory_k;
+ p " <vcpu>%d</vcpu>\n" source.s_vcpu;
+ p " <os>\n";
+ p " <type arch='%s'>hvm</type>\n"
source.s_arch;
+ p " </os>\n";
+ p " <features>\n";
+ List.iter (p " <%s/>\n") source.s_features;
+ p " </features>\n";
+
+ p " <on_poweroff>destroy</on_poweroff>\n";
+ p " <on_reboot>restart</on_reboot>\n";
+ p " <on_crash>restart</on_crash>\n";
+ p " <devices>\n";
+
+ let block_prefix + if guestcaps.gcaps_block_bus = "virtio" then
"vd" else "hd" in
+ iteri (
+ fun i ov ->
+ p " <disk type='file'
device='disk'>\n";
+ p " <driver name='qemu' type='%s'
cache='none'/>\n"
+ ov.ov_target_format;
+ p " <source file='%s'/>\n" (xml_quote_attr
ov.ov_target_file);
+ p " <target dev='%s%s' bus='%s'/>\n"
+ block_prefix (drive_name i) guestcaps.gcaps_block_bus;
+ p " </disk>\n";
+ ) overlays;
+
+ p " <input type='tablet' bus='usb'/>\n";
+ p " <input type='mouse' bus='ps2'/>\n";
+ p " <console type='pty'/>\n";
+
+ (* XXX Missing here from old virt-v2v:
+ <video/>
+ <graphics/>
+ cdroms and floppies
+ network interfaces
+ See: lib/Sys/VirtConvert/Connection/LibVirtTarget.pm
+ *)
+
+ p "</domain>\n";
+
+ close_out chan
diff --git a/v2v/target_local.mli b/v2v/target_local.mli
new file mode 100644
index 0000000..1833ecb
--- /dev/null
+++ b/v2v/target_local.mli
@@ -0,0 +1,21 @@
+(* virt-v2v
+ * 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.
+ *)
+
+val initialize : string -> Types.overlay list -> Types.overlay list
+
+val create_metadata : string -> Types.source -> Types.overlay list ->
Types.guestcaps -> unit
diff --git a/v2v/types.ml b/v2v/types.ml
new file mode 100644
index 0000000..0f5ae86
--- /dev/null
+++ b/v2v/types.ml
@@ -0,0 +1,84 @@
+(* virt-v2v
+ * 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.
+ *)
+
+open Printf
+
+(* Types. See types.mli for documentation. *)
+
+type input +| InputLibvirt of string option * string
+| InputLibvirtXML of string
+
+type output +| OutputLibvirt of string option
+| OutputLocal of string
+| OutputRHEV of string
+
+type source = {
+ s_dom_type : string;
+ s_name : string;
+ s_memory : int64;
+ s_vcpu : int;
+ s_arch : string;
+ s_features : string list;
+ s_disks : source_disk list;
+}
+and source_disk = string * string option
+
+type overlay = {
+ ov_overlay : string;
+ ov_target_file : string;
+ ov_target_file_tmp : string;
+ ov_target_format : string;
+ ov_sd : string;
+ ov_virtual_size : int64;
+ ov_preallocation : string option;
+ ov_source_file : string;
+ ov_source_format : string option;
+}
+
+let string_of_overlay ov + sprintf "\
+ov_overlay = %s
+ov_target_file = %s
+ov_target_file_tmp = %s
+ov_target_format = %s
+ov_sd = %s
+ov_virtual_size = %Ld
+ov_preallocation = %s
+ov_source_file = %s
+ov_source_format = %s
+"
+ ov.ov_overlay
+ ov.ov_target_file ov.ov_target_file_tmp
+ ov.ov_target_format
+ ov.ov_sd
+ ov.ov_virtual_size
+ (match ov.ov_preallocation with None -> "None" | Some s ->
s)
+ ov.ov_source_file
+ (match ov.ov_source_format with None -> "None" | Some s ->
s)
+
+type inspect = {
+ i_root : string;
+ i_apps : Guestfs.application2 list;
+}
+
+type guestcaps = {
+ gcaps_block_bus : string;
+ gcaps_net_bus : string;
+}
diff --git a/v2v/types.mli b/v2v/types.mli
new file mode 100644
index 0000000..e7e72e0
--- /dev/null
+++ b/v2v/types.mli
@@ -0,0 +1,77 @@
+(* virt-v2v
+ * 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.
+ *)
+
+(** Types. *)
+
+type input +| InputLibvirt of string option * string (* -i libvirt: -ic + guest
name *)
+| InputLibvirtXML of string (* -i libvirtxml: XML file name *)
+(** The input arguments as specified on the command line. *)
+
+type output +| OutputLibvirt of string option (* -o libvirt: -oc *)
+| OutputLocal of string (* -o local: directory *)
+| OutputRHEV of string (* -o rhev: output storage *)
+(** The output arguments as specified on the command line. *)
+
+type source = {
+ s_dom_type : string; (** Source domain type, eg
"kvm" *)
+ s_name : string; (** Guest name. *)
+ s_memory : int64; (** Memory size (bytes). *)
+ s_vcpu : int; (** Number of CPUs. *)
+ s_arch : string; (** Architecture. *)
+ s_features : string list; (** Machine features. *)
+ s_disks : source_disk list; (** Disk images. *)
+}
+(** The source: metadata, disk images. *)
+
+and source_disk = string * string option
+(** A source file is a qemu URI and a format. *)
+
+type overlay = {
+ ov_overlay : string; (** Local overlay file (qcow2 format). *)
+ ov_target_file : string; (** Destination file (real). *)
+ ov_target_file_tmp : string; (** Destination file (temporary). *)
+ ov_target_format : string; (** Destination format (eg. -of option). *)
+ ov_sd : string; (** sdX libguestfs name of disk. *)
+ ov_virtual_size : int64; (** Virtual disk size in bytes. *)
+ ov_preallocation : string option; (** ?preallocation option. *)
+
+ (* Note: the next two fields are for information only and must not
+ * be opened/copied/etc.
+ *)
+ ov_source_file : string; (** qemu URI for source file. *)
+ ov_source_format : string option; (** Source file format, if known. *)
+}
+(** Disk overlays and destination disks. *)
+
+val string_of_overlay : overlay -> string
+
+type inspect = {
+ i_root : string; (** Root device. *)
+ i_apps : Guestfs.application2 list; (** Packages installed. *)
+}
+(** Inspection information. Only the applications list is stored here
+ as that is the only one which is slow/inconvenient to fetch. *)
+
+type guestcaps = {
+ gcaps_block_bus : string; (** "virtio", "ide",
possibly others *)
+ gcaps_net_bus : string; (** "virtio", "e1000",
possibly others *)
+ (* XXX acpi, display *)
+}
+(** Guest capabilities after conversion. eg. Was virtio found or installed? *)
diff --git a/v2v/utils-c.c b/v2v/utils-c.c
new file mode 100644
index 0000000..f6a5d74
--- /dev/null
+++ b/v2v/utils-c.c
@@ -0,0 +1,43 @@
+/* virt-v2v
+ * 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 <unistd.h>
+
+#include <caml/alloc.h>
+#include <caml/memory.h>
+#include <caml/mlvalues.h>
+
+#include "guestfs.h"
+#include "guestfs-internal-frontend.h"
+
+value
+v2v_utils_drive_name (value indexv)
+{
+ CAMLparam1 (indexv);
+ CAMLlocal1 (namev);
+ char name[64];
+
+ guestfs___drive_name (Int_val (indexv), name);
+ namev = caml_copy_string (name);
+
+ CAMLreturn (namev);
+}
diff --git a/v2v/utils.ml b/v2v/utils.ml
new file mode 100644
index 0000000..6155f9a
--- /dev/null
+++ b/v2v/utils.ml
@@ -0,0 +1,44 @@
+(* virt-v2v
+ * 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.
+ *)
+
+(* Utilities used in virt-v2v only. *)
+
+open Printf
+
+open Common_gettext.Gettext
+open Common_utils
+
+open Types
+
+let prog = Filename.basename Sys.executable_name
+let error ?exit_code fs = error ~prog ?exit_code fs
+
+let quote = Filename.quote
+
+(* Quote XML <element attr='...'> content. Note you must use
single
+ * quotes around the attribute.
+ *)
+let xml_quote_attr str + let str = Common_utils.replace_str str
"&" "&" in
+ let str = Common_utils.replace_str str "'"
"'" in
+ let str = Common_utils.replace_str str "<" "<"
in
+ let str = Common_utils.replace_str str ">" ">"
in
+ str
+
+external drive_name : int -> string = "v2v_utils_drive_name"
+
diff --git a/v2v/v2v.ml b/v2v/v2v.ml
new file mode 100644
index 0000000..e21cb79
--- /dev/null
+++ b/v2v/v2v.ml
@@ -0,0 +1,353 @@
+(* virt-v2v
+ * 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.
+ *)
+
+open Unix
+open Printf
+
+open Common_gettext.Gettext
+
+module G = Guestfs
+
+open Common_utils
+open Types
+open Utils
+
+let () = Random.self_init ()
+
+let rec main () + (* Handle the command line. *)
+ let input, output,
+ debug_gc, output_alloc, output_format, output_name,
+ quiet, root_choice, trace, verbose + Cmdline.parse_cmdline () in
+
+ let msg fs = make_message_function ~quiet fs in
+
+ let source + match input with
+ | InputLibvirt (libvirt_uri, guest) ->
+ Source_libvirt.create libvirt_uri guest
+ | InputLibvirtXML filename ->
+ Source_libvirt.create_from_xml filename in
+
+ (* Create a qcow2 v3 overlay to protect the source image(s). There
+ * is a specific reason to use the newer qcow2 variant: Because the
+ * L2 table can store zero clusters efficiently, and because
+ * discarded blocks are stored as zero clusters, this should allow us
+ * to fstrim/blkdiscard and avoid copying significant parts of the
+ * data over the wire.
+ *)
+ msg (f_"Creating an overlay to protect the source from being
modified");
+ let overlays + List.map (
+ fun (qemu_uri, format) ->
+ let overlay = Filename.temp_file "v2vovl" ".qcow2"
in
+ unlink_on_exit overlay;
+
+ let options + "compat=1.1,lazy_refcounts=on" ^
+ (match format with None -> ""
+ | Some fmt -> ",backing_fmt=" ^ fmt) in
+ let cmd + sprintf "qemu-img create -q -f qcow2 -b %s -o
%s %s"
+ (quote qemu_uri) (quote options) overlay in
+ if Sys.command cmd <> 0 then
+ error (f_"qemu-img command failed, see earlier errors");
+ overlay, qemu_uri, format
+ ) source.s_disks in
+
+ (* Open the guestfs handle. *)
+ msg (f_"Opening the overlay");
+ let g = new G.guestfs () in
+ g#set_trace trace;
+ g#set_verbose verbose;
+ g#set_network true;
+ List.iter (
+ fun (overlay, _, _) ->
+ g#add_drive_opts overlay
+ ~format:"qcow2" ~cachemode:"unsafe"
~discard:"besteffort"
+ ) overlays;
+
+ g#launch ();
+
+ (* Work out where we will write the final output. Do this early
+ * just so we can display errors to the user before doing too much
+ * work.
+ *)
+ let overlays + initialize_target g output output_alloc output_format
overlays in
+
+ (* Inspection - this also mounts up the filesystems. *)
+ msg (f_"Inspecting the overlay");
+ let inspect = inspect_source g root_choice in
+
+ (* Conversion. *)
+ let guestcaps + let root = inspect.i_root in
+
+ (match g#inspect_get_product_name root with
+ | "unknown" ->
+ msg (f_"Converting the guest to run on KVM")
+ | prod ->
+ msg (f_"Converting %s to run on KVM") prod
+ );
+
+ match g#inspect_get_type root with
+ | "linux" ->
+ (match g#inspect_get_distro root with
+ | "fedora"
+ | "rhel" | "centos" | "scientificlinux" |
"redhat-based"
+ | "sles" | "suse-based" | "opensuse" ->
+
+ (* RHEV doesn't support serial console so remove any on conversion.
*)
+ let keep_serial_console + match output with
+ | OutputRHEV _ -> Some false
+ | OutputLibvirt _ | OutputLocal _ -> None in
+
+ Convert_linux_enterprise.convert ?keep_serial_console
+ verbose g inspect source
+
+ | distro ->
+ error (f_"virt-v2v is unable to convert this guest type
(linux/distro=%s)") distro
+ );
+
+ | "windows" -> Convert_windows.convert verbose g inspect
+
+ | typ ->
+ error (f_"virt-v2v is unable to convert this guest type
(type=%s)") typ in
+
+ (* Trim the filesystems to reduce transfer size. *)
+ msg (f_"Trimming filesystems to reduce amount of data to copy");
+ let () + let mps = g#mountpoints () in
+ List.iter (
+ fun (_, mp) ->
+ try g#fstrim mp
+ with G.Error msg -> eprintf "%s: %s (ignored)\n" mp msg
+ ) mps in
+
+ msg (f_"Closing the overlay");
+ g#umount_all ();
+ g#shutdown ();
+ g#close ();
+
+ (* Copy the source to the output. *)
+ let delete_target_on_exit = ref true in
+ at_exit (fun () ->
+ if !delete_target_on_exit then (
+ List.iter (
+ fun ov -> try Unix.unlink ov.ov_target_file_tmp with _ -> ()
+ ) overlays
+ )
+ );
+ let nr_overlays = List.length overlays in
+ iteri (
+ fun i ov ->
+ msg (f_"Copying disk %d/%d to %s (%s)")
+ (i+1) nr_overlays ov.ov_target_file ov.ov_target_format;
+ if verbose then printf "%s\n%!" (string_of_overlay ov);
+
+ (* It turns out that libguestfs's disk creation code is
+ * considerably more flexible and easier to use than qemu-img, so
+ * create the disk explicitly using libguestfs then pass the
+ * 'qemu-img convert -n' option so qemu reuses the disk.
+ *)
+ let preallocation = ov.ov_preallocation in
+ let compat + match ov.ov_target_format with "qcow2"
-> Some "1.1" | _ -> None in
+ (new G.guestfs ())#disk_create ov.ov_target_file_tmp
+ ov.ov_target_format ov.ov_virtual_size ?preallocation ?compat;
+
+ let cmd + sprintf "qemu-img convert -n -f qcow2 -O %s %s
%s"
+ (quote ov.ov_target_format) (quote ov.ov_overlay)
+ (quote ov.ov_target_file_tmp) in
+ if verbose then printf "%s\n%!" cmd;
+ if Sys.command cmd <> 0 then
+ error (f_"qemu-img command failed, see earlier errors");
+ ) overlays;
+
+ (* Create output metadata. *)
+ msg (f_"Creating output metadata");
+ let () + (* Are we going to rename the guest? *)
+ let renamed_source + match output_name with
+ | None -> source
+ | Some name -> { source with s_name = name } in
+ match output with
+ | OutputLibvirt oc -> assert false
+ | OutputLocal dir ->
+ Target_local.create_metadata dir renamed_source overlays guestcaps
+ | OutputRHEV os -> assert false in
+
+ (* If we wrote to a temporary file, rename to the real file. *)
+ List.iter (
+ fun ov ->
+ if ov.ov_target_file_tmp <> ov.ov_target_file then
+ rename ov.ov_target_file_tmp ov.ov_target_file
+ ) overlays;
+
+ delete_target_on_exit := false;
+
+ msg (f_"Finishing off");
+
+ if debug_gc then
+ Gc.compact ()
+
+and initialize_target g output output_alloc output_format overlays + let
overlays + mapi (
+ fun i (overlay, qemu_uri, backing_format) ->
+ (* Grab the virtual size of each disk. *)
+ let sd = "sd" ^ drive_name i in
+ let dev = "/dev/" ^ sd in
+ let vsize = g#blockdev_getsize64 dev in
+
+ (* What output format should we use? *)
+ let format + match output_format, backing_format with
+ | Some format, _ -> format (* -of overrides everything *)
+ | None, Some format -> format (* same as backing format *)
+ | None, None ->
+ error (f_"disk %s (%s) has no defined format, you have to
either define the original format in the source metadata, or use the
'-of' option to force the output format") sd qemu_uri in
+
+ (* What output preallocation mode should we use? *)
+ let preallocation + match format, output_alloc with
+ | "raw", `Sparse -> Some "sparse"
+ | "raw", `Preallocated -> Some "full"
+ | "qcow2", `Sparse -> Some "off" (* ? *)
+ | "qcow2", `Preallocated -> Some "metadata"
+ | _ -> None (* ignore -oa flag for other formats *) in
+
+ { ov_overlay = overlay;
+ ov_target_file = ""; ov_target_file_tmp = "";
+ ov_target_format = format;
+ ov_sd = sd; ov_virtual_size = vsize; ov_preallocation =
preallocation;
+ ov_source_file = qemu_uri; ov_source_format = backing_format; }
+ ) overlays in
+ let overlays + match output with
+ | OutputLibvirt oc -> assert false
+ | OutputLocal dir -> Target_local.initialize dir overlays
+ | OutputRHEV os -> assert false in
+ overlays
+
+and inspect_source g root_choice + let roots = g#inspect_os () in
+ let roots = Array.to_list roots in
+
+ let root + match roots with
+ | [] ->
+ error (f_"no root device found in this operating system
image.");
+ | [root] -> root
+ | roots ->
+ match root_choice with
+ | `Ask ->
+ (* List out the roots and ask the user to choose. *)
+ printf "\n***\n";
+ printf (f_"dual- or multi-boot operating system detected. Choose
the root filesystem\nthat contains the main operating system from the list
below:\n");
+ printf "\n";
+ iteri (
+ fun i root ->
+ let prod = g#inspect_get_product_name root in
+ match prod with
+ | "unknown" -> printf " [%d] %s\n" i root
+ | prod -> printf " [%d] %s (%s)\n" i root prod
+ ) roots;
+ printf "\n";
+ let i = ref 0 in
+ let n = List.length roots in
+ while !i < 1 || !i > n do
+ printf (f_"Enter number between 1 and %d: ") n;
+ (try i := int_of_string (read_line ())
+ with
+ | End_of_file -> error (f_"connection closed")
+ | Failure "int_of_string" -> ()
+ )
+ done;
+ List.nth roots (!i - 1)
+
+ | `Single ->
+ error (f_"multi-boot operating systems are not supported by
virt-v2v. Use the --root option to change how virt-v2v handles this.")
+
+ | `First ->
+ List.hd roots
+
+ | `Dev dev ->
+ if List.mem dev roots then dev
+ else
+ error (f_"root device %s not found. Roots found were: %s")
+ dev (String.concat " " roots) in
+
+ (* Reject this OS if it doesn't look like an installed image. *)
+ let () + let fmt = g#inspect_get_format root in
+ if fmt <> "installed" then
+ error (f_"libguestfs thinks this is not an installed operating
system (it might be, for example, an installer disk or live CD). If this is
wrong, it is probably a bug in libguestfs. root=%s fmt=%s") root fmt in
+
+ (* Mount up the filesystems. *)
+ let mps = g#inspect_get_mountpoints root in
+ let cmp (a,_) (b,_) = compare (String.length a) (String.length b) in
+ let mps = List.sort cmp mps in
+ List.iter (
+ fun (mp, dev) ->
+ try g#mount dev mp
+ with G.Error msg -> eprintf "%s (ignored)\n" msg
+ ) mps;
+
+ (* Get list of applications/packages installed. *)
+ let apps = g#inspect_list_applications2 root in
+ let apps = Array.to_list apps in
+
+ { i_root = root; i_apps = apps; }
+
+let () + try main ()
+ with
+ | Unix.Unix_error (code, fname, "") -> (* from a syscall *)
+ eprintf (f_"%s: error: %s: %s\n") prog fname (Unix.error_message
code);
+ exit 1
+ | Unix.Unix_error (code, fname, param) -> (* from a syscall *)
+ eprintf (f_"%s: error: %s: %s: %s\n") prog fname
(Unix.error_message code)
+ param;
+ exit 1
+ | Sys_error msg -> (* from a syscall *)
+ eprintf (f_"%s: error: %s\n") prog msg;
+ exit 1
+ | G.Error msg -> (* from libguestfs *)
+ eprintf (f_"%s: libguestfs error: %s\n") prog msg;
+ exit 1
+ | Failure msg -> (* from failwith/failwithf *)
+ eprintf (f_"%s: failure: %s\n") prog msg;
+ exit 1
+ | Invalid_argument msg -> (* probably should never happen *)
+ eprintf (f_"%s: internal error: invalid argument: %s\n") prog
msg;
+ exit 1
+ | Assert_failure (file, line, char) -> (* should never happen *)
+ eprintf (f_"%s: internal error: assertion failed at %s, line %d, char
%d\n") prog file line char;
+ exit 1
+ | Not_found -> (* should never happen *)
+ eprintf (f_"%s: internal error: Not_found exception was
thrown\n") prog;
+ exit 1
+ | exn -> (* something not matched above *)
+ eprintf (f_"%s: exception: %s\n") prog (Printexc.to_string exn);
+ exit 1
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
new file mode 100644
index 0000000..138e73b
--- /dev/null
+++ b/v2v/virt-v2v.pod
@@ -0,0 +1,301 @@
+=head1 NAME
+
+virt-v2v - Convert a guest to use KVM
+
+=head1 SYNOPSIS
+
+ virt-v2v -ic esx://esx.example.com/ -os imported esx_guest
+
+ virt-v2v -ic esx://esx.example.com/ \
+ -o rhev -os rhev.nfs:/export_domain --network rhevm esx_guest
+
+ virt-v2v -i libvirtxml -o local -os /tmp guest-domain.xml
+
+=head1 DESCRIPTION
+
+Virt-v2v converts guests from a foreign hypervisor to run on KVM,
+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.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display help.
+
+=item B<--debug-gc>
+
+Debug garbage collection and memory allocation. This is only useful
+when debugging memory problems in virt-v2v or the OCaml libguestfs
+bindings.
+
+=item B<-i libvirt>
+
+Set the input method to I<libvirt>. This is the default.
+
+In this mode you have to specify a libvirt guest name on the command
+line. You may also specify a libvirt connection URI (see I<-ic>).
+
+=item B<-i libvirtxml>
+
+Set the input method to I<libvirtxml>.
+
+In this mode you have to pass a libvirt XML file on the command line.
+This file is read in order to get metadata about the source guest
+(such as its name, amount of memory), and also to locate the input
+disks.
+
+=item B<-ic> libvirtURI
+
+Specify a libvirt connection URI to use when reading the guest. This
+is only used when S<I<-i libvirt>>.
+
+Only local libvirt connections and ESX connections can be used.
+Remote libvirt connections will not work in general.
+
+=item B<--machine-readable>
+
+This option is used to make the output more machine friendly
+when being parsed by other programs. See
+L</MACHINE READABLE OUTPUT> below.
+
+=item B<-o libvirt>
+
+Set the output method to I<libvirt>. This is the default.
+
+In this mode, the converted guest is created as a libvirt guest. You
+may also specify a libvirt connection URI (see I<-oc>).
+
+=item B<-o local>
+
+Set the output method to I<local>.
+
+In this mode, the converted guest is written to a local directory
+specified by I<-os /dir> (the directory must exist). The converted
+guest's disks are written as:
+
+ /dir/disk-sda
+ /dir/disk-sdb
+ [etc]
+
+and a libvirt XML file is created containing guest metadata
+(C</dir/name.xml>, where C<name> is the guest name).
+
+=item B<-o rhev>
+
+Set the output method to I<rhev>.
+
+The converted guest is written to a RHEV Export Storage Domain. The
+I<-os> parameter must also be used to specify the location of the
+Export Storage Domain. Note this does not actually import the guest
+into RHEV. You have to do that manually later using the UI.
+
+=item B<-oa sparse>
+
+=item B<-oa preallocated>
+
+Set the output file allocation mode. The default is C<sparse>.
+
+=item B<-oc> libvirtURI
+
+Specify a libvirt connection to use when writing the converted guest.
+This is only used when S<I<-o libvirt>>.
+
+Only local libvirt connections can be used. Remote libvirt
+connections will not work.
+
+=item B<-of> format
+
+When converting the guest, convert the disks to the given format.
+
+If not specified, then the input format is used.
+
+=item B<-on> name
+
+Rename the guest when converting it. If this option is not used then
+the output name is the same as the input name.
+
+=item B<-os> storage
+
+The location of the storage for the converted guest.
+
+For I<-o libvirt>, this is a libvirt pool (see S<C<virsh
pool-list>>).
+
+For I<-o local>, this is a directory name. The directory must exist.
+
+For I<-o rhev>, this is an NFS path of the form
+C<E<lt>hostE<gt>:E<lt>pathE<gt>>, eg:
+
+ rhev-storage.example.com:/rhev/export
+
+The NFS export must be mountable and writable by the user and host
+running virt-v2v, since the virt-v2v program has to actually mount it
+when it runs.
+
+=item B<-q>
+
+=item B<--quiet>
+
+This disables progress bars and other unnecessary output.
+
+=item B<--root ask>
+
+=item B<--root single>
+
+=item B<--root first>
+
+=item B<--root> /dev/sdX
+
+=item B<--root> /dev/VG/LV
+
+Choose the root filesystem to be converted.
+
+In the case where the virtual machine is dual-boot or multi-boot, or
+where the VM has other filesystems that look like operating systems,
+this option can be used to select the root filesystem (a.k.a. C<C:>
+drive or C</>) of the operating system that is to be converted. The
+Windows Recovery Console, certain attached DVD drives, and bugs in
+libguestfs inspection heuristics, can make a guest look like a
+multi-boot operating system.
+
+The default in virt-v2v E<le> 0.7.1 was S<I<--root single>>,
which
+causes virt-v2v to die if a multi-boot operating system is found.
+
+Since virt-v2v E<ge> 0.7.2 the default is now S<I<--root
ask>>: If the
+VM is found to be multi-boot, then virt-v2v will stop and list the
+possible root filesystems and ask the user which to use. This
+requires that virt-v2v is run interactively.
+
+S<I<--root first>> means to choose the first root device in the
case
+of a multi-boot operating system. Since this is a heuristic, it may
+sometimes choose the wrong one.
+
+You can also name a specific root device, eg. S<I<--root
/dev/sda2>>
+would mean to use the second partition on the first hard drive. If
+the named root device does not exist or was not detected as a root
+device, then virt-v2v will fail.
+
+Note that there is a bug in grub which prevents it from successfully
+booting a multiboot system if VirtIO is enabled. Grub is only able to
+boot an operating system from the first VirtIO disk. Specifically,
+C</boot> must be on the first VirtIO disk, and it cannot chainload an
+OS which is not in the first VirtIO disk.
+
+=item B<-v>
+
+=item B<--verbose>
+
+Enable verbose messages for debugging.
+
+=item B<-V>
+
+=item B<--version>
+
+Display version number and exit.
+
+=item B<-x>
+
+Enable tracing of libguestfs API calls.
+
+=back
+
+=head1 MACHINE READABLE OUTPUT
+
+The I<--machine-readable> option can be used to make the output more
+machine friendly, which is useful when calling virt-v2v from
+other programs, GUIs etc.
+
+There are two ways to use this option.
+
+Firstly use the option on its own to query the capabilities of the
+virt-v2v binary. Typical output looks like this:
+
+ $ virt-v2v --machine-readable
+ virt-v2v
+ libguestfs-rewrite
+
+A list of features is printed, one per line, and the program exits
+with status 0.
+
+Secondly use the option in conjunction with other options to make the
+regular program output more machine friendly.
+
+At the moment this means:
+
+=over 4
+
+=item 1.
+
+Progress bar messages can be parsed from stdout by looking for this
+regular expression:
+
+ ^[0-9]+/[0-9]+$
+
+=item 2.
+
+The calling program should treat messages sent to stdout (except for
+progress bar messages) as status messages. They can be logged and/or
+displayed to the user.
+
+=item 3.
+
+The calling program should treat messages sent to stderr as error
+messages. In addition, virt-v2v exits with a non-zero status
+code if there was a fatal error.
+
+=back
+
+Virt-v2v E<le> 0.9.1 did not support the I<--machine-readable>
+option at all. The option was added when virt-v2v was rewritten in 2014.
+
+=head1 ENVIRONMENT VARIABLES
+
+=over 4
+
+=item TMPDIR
+
+Location of the temporary directory used for the potentially large
+temporary overlay file.
+
+You should ensure there is enough free space in the worst case for a
+full copy of the source disk (I<virtual> size), or else set
C<$TMPDIR>
+to point to another directory that has enough space.
+
+This defaults to C</tmp>.
+
+Note that if C<$TMPDIR> is a tmpfs (eg. if C</tmp> is on tmpfs, or
if
+you use C<TMPDIR=/dev/shm>), tmpfs defaults to a maximum size of
+I<half> of physical RAM. If virt-v2v exceeds this, it will hang.
+The solution is either to use a real disk, or to increase the maximum
+size of the tmpfs mountpoint, eg:
+
+ mount -o remount,size=10G /tmp
+
+=back
+
+For other environment variables, see L<guestfs(3)/ENVIRONMENT VARIABLES>.
+
+=head1 SEE ALSO
+
+L<virt-df(1)>,
+L<virt-filesystems(1)>,
+L<guestfs(3)>,
+L<guestfish(1)>,
+L<qemu-img(1)>,
+L<http://libguestfs.org/>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones L<http://people.redhat.com/~rjones/>
+
+Matthew Booth
+
+=head1 COPYRIGHT
+
+Copyright (C) 2009-2014 Red Hat Inc.
diff --git a/v2v/xml-c.c b/v2v/xml-c.c
new file mode 100644
index 0000000..9b79c6b
--- /dev/null
+++ b/v2v/xml-c.c
@@ -0,0 +1,240 @@
+/* virt-v2v
+ * 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.
+ */
+
+/* Mini interface to libxml2 for parsing libvirt XML. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <caml/alloc.h>
+#include <caml/custom.h>
+#include <caml/fail.h>
+#include <caml/memory.h>
+#include <caml/mlvalues.h>
+
+#include <libxml/xpath.h>
+
+#include "guestfs.h"
+#include "guestfs-internal-frontend.h"
+
+/* xmlDocPtr type */
+#define Doc_val(v) (*((xmlDocPtr *)Data_custom_val(v)))
+
+static void
+doc_finalize (value docv)
+{
+ xmlDocPtr doc = Doc_val (docv);
+
+ if (doc)
+ xmlFreeDoc (doc);
+}
+
+static struct custom_operations doc_custom_operations = {
+ (char *) "doc_custom_operations",
+ doc_finalize,
+ custom_compare_default,
+ custom_hash_default,
+ custom_serialize_default,
+ custom_deserialize_default
+};
+
+/* xmlXPathContextPtr type */
+#define Xpathctx_val(v) (*((xmlXPathContextPtr *)Data_custom_val(v)))
+
+static void
+xpathctx_finalize (value xpathctxv)
+{
+ xmlXPathContextPtr xpathctx = Xpathctx_val (xpathctxv);
+
+ if (xpathctx)
+ xmlXPathFreeContext (xpathctx);
+}
+
+static struct custom_operations xpathctx_custom_operations = {
+ (char *) "xpathctx_custom_operations",
+ xpathctx_finalize,
+ custom_compare_default,
+ custom_hash_default,
+ custom_serialize_default,
+ custom_deserialize_default
+};
+
+/* xmlXPathObjectPtr type */
+#define Xpathobj_val(v) (*((xmlXPathObjectPtr *)Data_custom_val(v)))
+
+static void
+xpathobj_finalize (value xpathobjv)
+{
+ xmlXPathObjectPtr xpathobj = Xpathobj_val (xpathobjv);
+
+ if (xpathobj)
+ xmlXPathFreeObject (xpathobj);
+}
+
+static struct custom_operations xpathobj_custom_operations = {
+ (char *) "xpathobj_custom_operations",
+ xpathobj_finalize,
+ custom_compare_default,
+ custom_hash_default,
+ custom_serialize_default,
+ custom_deserialize_default
+};
+
+value
+v2v_xml_parse_memory (value xmlv)
+{
+ CAMLparam1 (xmlv);
+ CAMLlocal1 (docv);
+ xmlDocPtr doc;
+
+ doc = xmlParseMemory (String_val (xmlv), caml_string_length (xmlv));
+ if (doc == NULL)
+ caml_invalid_argument ("parse_memory: unable to parse XML from
libvirt");
+
+ docv = caml_alloc_custom (&doc_custom_operations, sizeof (xmlDocPtr), 0,
1);
+ Doc_val (docv) = doc;
+
+ CAMLreturn (docv);
+}
+
+value
+v2v_xml_xpath_new_context (value docv)
+{
+ CAMLparam1 (docv);
+ CAMLlocal1 (xpathctxv);
+ xmlDocPtr doc;
+ xmlXPathContextPtr xpathctx;
+
+ doc = Doc_val (docv);
+ xpathctx = xmlXPathNewContext (doc);
+ if (xpathctx == NULL)
+ caml_invalid_argument ("xpath_new_context: unable to create
xmlXPathNewContext");
+
+ xpathctxv = caml_alloc_custom (&xpathctx_custom_operations,
+ sizeof (xmlXPathContextPtr), 0, 1);
+ Xpathctx_val (xpathctxv) = xpathctx;
+
+ CAMLreturn (xpathctxv);
+}
+
+value
+v2v_xml_xpath_eval_expression (value xpathctxv, value exprv)
+{
+ CAMLparam2 (xpathctxv, exprv);
+ CAMLlocal1 (xpathobjv);
+ xmlXPathContextPtr xpathctx;
+ xmlXPathObjectPtr xpathobj;
+
+ xpathctx = Xpathctx_val (xpathctxv);
+ xpathobj = xmlXPathEvalExpression (BAD_CAST String_val (exprv), xpathctx);
+ if (xpathobj == NULL)
+ caml_invalid_argument ("xpath_eval_expression: unable to evaluate
XPath expression");
+
+ xpathobjv = caml_alloc_custom (&xpathobj_custom_operations,
+ sizeof (xmlXPathObjectPtr), 0, 1);
+ Xpathobj_val (xpathobjv) = xpathobj;
+
+ CAMLreturn (xpathobjv);
+}
+
+value
+v2v_xml_xpathobj_nr_nodes (value xpathobjv)
+{
+ CAMLparam1 (xpathobjv);
+ xmlXPathObjectPtr xpathobj = Xpathobj_val (xpathobjv);
+
+ CAMLreturn (Val_int (xpathobj->nodesetval->nodeNr));
+}
+
+value
+v2v_xml_xpathobj_get_node_ptr (value xpathobjv, value iv)
+{
+ CAMLparam2 (xpathobjv, iv);
+ xmlXPathObjectPtr xpathobj = Xpathobj_val (xpathobjv);
+ int i = Int_val (iv);
+
+ /* Because xmlNodePtrs are owned by the document, we don't want to
+ * wrap this up with a finalizer, so just pass the pointer straight
+ * back to OCaml as a value. OCaml will ignore it because it's
+ * outside the heap, and just pass it back to us when needed. This
+ * relies on the xmlDocPtr not being freed, but we pair the node
+ * pointer with the doc in the OCaml layer so the GC will not free
+ * one without freeing the other.
+ */
+ CAMLreturn ((value) xpathobj->nodesetval->nodeTab[i]);
+}
+
+value
+v2v_xml_xpathctx_set_node_ptr (value xpathctxv, value nodev)
+{
+ CAMLparam2 (xpathctxv, nodev);
+ xmlXPathContextPtr xpathctx = Xpathctx_val (xpathctxv);
+ xmlNodePtr node = (xmlNodePtr) nodev;
+
+ xpathctx->node = node;
+
+ CAMLreturn (Val_unit);
+}
+
+value
+v2v_xml_node_ptr_name (value nodev)
+{
+ CAMLparam1 (nodev);
+ xmlNodePtr node = (xmlNodePtr) nodev;
+
+ switch (node->type) {
+ case XML_ATTRIBUTE_NODE:
+ case XML_ELEMENT_NODE:
+ CAMLreturn (caml_copy_string ((char *) node->name));
+
+ default:
+ caml_invalid_argument ("node_name: don't know how to get the name
of this node");
+ }
+}
+
+value
+v2v_xml_node_ptr_as_string (value docv, value nodev)
+{
+ CAMLparam2 (docv, nodev);
+ xmlDocPtr doc = Doc_val (docv);
+ xmlNodePtr node = (xmlNodePtr) nodev;
+ CLEANUP_FREE char *str = NULL;
+
+ switch (node->type) {
+ case XML_TEXT_NODE:
+ case XML_COMMENT_NODE:
+ case XML_CDATA_SECTION_NODE:
+ case XML_PI_NODE:
+ CAMLreturn (caml_copy_string ((char *) node->content));
+
+ case XML_ATTRIBUTE_NODE:
+ case XML_ELEMENT_NODE:
+ str = (char *) xmlNodeListGetString (doc, node->children, 1);
+
+ if (str == NULL)
+ caml_invalid_argument ("node_as_string: xmlNodeListGetString cannot
convert node to string");
+
+ CAMLreturn (caml_copy_string (str));
+
+ default:
+ caml_invalid_argument ("node_as_string: don't know how to convert
this node to a string");
+ }
+}
diff --git a/v2v/xml.ml b/v2v/xml.ml
new file mode 100644
index 0000000..5cd75c1
--- /dev/null
+++ b/v2v/xml.ml
@@ -0,0 +1,50 @@
+(* virt-v2v
+ * 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.
+ *)
+
+(* Mini interface to libxml2 for parsing libvirt XML. *)
+
+type doc
+type node_ptr
+type xpathctx
+type xpathobj
+
+(* Since node is owned by doc, we have to make that explicit to the
+ * garbage collector.
+ *)
+type node = doc * node_ptr
+
+external parse_memory : string -> doc = "v2v_xml_parse_memory"
+external xpath_new_context : doc -> xpathctx =
"v2v_xml_xpath_new_context"
+external xpath_eval_expression : xpathctx -> string -> xpathobj =
"v2v_xml_xpath_eval_expression"
+
+external xpathobj_nr_nodes : xpathobj -> int =
"v2v_xml_xpathobj_nr_nodes"
+external xpathobj_get_node_ptr : xpathobj -> int -> node_ptr =
"v2v_xml_xpathobj_get_node_ptr"
+let xpathobj_node doc xpathobj i + let n = xpathobj_get_node_ptr xpathobj i in
+ (doc, n)
+
+external xpathctx_set_node_ptr : xpathctx -> node_ptr -> unit =
"v2v_xml_xpathctx_set_node_ptr"
+let xpathctx_set_current_context xpathctx (_, node) + xpathctx_set_node_ptr
xpathctx node
+
+external node_ptr_name : node_ptr -> string =
"v2v_xml_node_ptr_name"
+let node_name (_, node) = node_ptr_name node
+
+external node_ptr_as_string : doc -> node_ptr -> string =
"v2v_xml_node_ptr_as_string"
+let node_as_string (doc, node) + node_ptr_as_string doc node
diff --git a/v2v/xml.mli b/v2v/xml.mli
new file mode 100644
index 0000000..c4363ad
--- /dev/null
+++ b/v2v/xml.mli
@@ -0,0 +1,57 @@
+(* virt-v2v
+ * 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.
+ *)
+
+(** Mini interface to libxml2 for parsing libvirt XML. *)
+
+type doc (** xmlDocPtr *)
+type node (** xmlNodePtr *)
+type xpathctx (** xmlXPathContextPtr *)
+type xpathobj (** xmlXPathObjectPtr *)
+
+val parse_memory : string -> doc
+(** xmlParseMemory *)
+val xpath_new_context : doc -> xpathctx
+(** xmlXPathNewContext *)
+val xpath_eval_expression : xpathctx -> string -> xpathobj
+(** xmlXPathEvalExpression *)
+
+val xpathobj_nr_nodes : xpathobj -> int
+(** Get the number of nodes in the node set of the xmlXPathObjectPtr. *)
+val xpathobj_node : doc -> xpathobj -> int -> node
+(** Get the number of nodes in the node set of the xmlXPathObjectPtr. *)
+
+val xpathctx_set_current_context : xpathctx -> node -> unit
+(** Set the current context of an xmlXPathContextPtr to the node.
+ Basically the same as the following C code:
+
+ {v
+ xpathctx->node = node
+ v}
+
+ It means the next expression you evaluate within this context will
+ start at this node, when evaluating relative paths
+ (eg. [./@attr]).
+*)
+
+val node_name : node -> string
+(** Get the name of the node. Note that only things like elements and
+ attributes have names. Other types of nodes will return an
+ error. *)
+
+val node_as_string : node -> string
+(** Converter to turn a node into a string *)
--
1.9.0
Richard W.M. Jones
2014-May-01 18:17 UTC
[Libguestfs] [PATCH v6 NOT TO BE APPLIED 2/2] 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.
---
.gitignore | 4 +
Makefile.am | 3 +
README | 2 +
configure.ac | 13 +
fish/guestfish.pod | 1 +
p2v/Makefile.am | 88 +++++++
p2v/authors.c | 30 +++
p2v/copying.c | 38 +++
p2v/gui.c | 693 +++++++++++++++++++++++++++++++++++++++++++++++++++++
p2v/main.c | 579 ++++++++++++++++++++++++++++++++++++++++++++
p2v/miniexpect.c | 364 ++++++++++++++++++++++++++++
p2v/miniexpect.h | 81 +++++++
p2v/p2v.h | 64 +++++
p2v/ssh.c | 408 +++++++++++++++++++++++++++++++
p2v/virt-p2v.pod | 219 +++++++++++++++++
po/POTFILES | 6 +
src/guestfs.pod | 5 +
v2v/virt-v2v.pod | 5 +-
18 files changed, 2601 insertions(+), 2 deletions(-)
create mode 100644 p2v/Makefile.am
create mode 100644 p2v/authors.c
create mode 100644 p2v/copying.c
create mode 100644 p2v/gui.c
create mode 100644 p2v/main.c
create mode 100644 p2v/miniexpect.c
create mode 100644 p2v/miniexpect.h
create mode 100644 p2v/p2v.h
create mode 100644 p2v/ssh.c
create mode 100644 p2v/virt-p2v.pod
diff --git a/.gitignore b/.gitignore
index 25e9358..f97318b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -245,6 +245,7 @@ Makefile.in
/html/virt-list-partitions.1.html
/html/virt-ls.1.html
/html/virt-make-fs.1.html
+/html/virt-p2v.1.html
/html/virt-rescue.1.html
/html/virt-resize.1.html
/html/virt-sparsify.1.html
@@ -314,6 +315,9 @@ Makefile.in
/ocaml/stamp-mlguestfs
/ocaml/t/*.bc
/ocaml/t/*.opt
+/p2v/stamp-virt-p2v.pod
+/p2v/virt-p2v
+/p2v/virt-p2v.1
/perl/bindtests.pl
/perl/blib
/perl/examples/guestfs-perl.3
diff --git a/Makefile.am b/Makefile.am
index 3102e0b..67b1fa8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -81,6 +81,9 @@ SUBDIRS += fish
# virt-tools in C.
SUBDIRS += align cat diff df edit format inspector make-fs rescue
+if HAVE_P2V
+SUBDIRS += p2v
+endif
# bash-completion
SUBDIRS += bash
diff --git a/README b/README
index 8fdc041..2d8acfd 100644
--- a/README
+++ b/README
@@ -176,6 +176,8 @@ The full requirements are described below.
| liblzma | | O | Can be used by virt-builder for fast |
| | | | uncompression of templates. |
+--------------+-------------+---+-----------------------------------------+
+| gtk2 | | O | Used by virt-p2v user interface. |
++--------------+-------------+---+-----------------------------------------+
| findlib | | O | For the OCaml bindings. |
+--------------+-------------+---+-----------------------------------------+
| ocaml-gettext| | O | For localizing OCaml virt-* tools. |
diff --git a/configure.ac b/configure.ac
index a8cd195..a27239f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -933,6 +933,16 @@ PKG_CHECK_MODULES([LIBCONFIG], [libconfig],[
[AC_MSG_WARN([libconfig not found, some features will be disabled])])
AM_CONDITIONAL([HAVE_LIBCONFIG],[test "x$LIBCONFIG_LIBS" !=
"x"])
+dnl Check for gtk2 library, used by virt-p2v.
+PKG_CHECK_MODULES([GTK2], [gtk+-2.0], [
+ AC_SUBST([GTK2_CFLAGS])
+ AC_SUBST([GTK2_LIBS])
+],
+ [AC_MSG_WARN([gtk2 not found, virt-p2v will be disabled])])
+
+dnl Can we build virt-p2v?
+AM_CONDITIONAL([HAVE_P2V], [test "x$GTK2_LIBS" != "x"])
+
dnl hivex library (highly recommended)
dnl This used to be a part of libguestfs, but was spun off into its
dnl own separate upstream project in libguestfs 1.0.85.
@@ -1645,6 +1655,7 @@ AC_CONFIG_FILES([Makefile
ocaml/META
ocaml/Makefile
ocaml/examples/Makefile
+ p2v/Makefile
perl/Makefile
perl/Makefile.PL
perl/examples/Makefile
@@ -1723,6 +1734,8 @@ echo "guestfish and C-based virt tools ....
yes"
echo "FUSE filesystem ..................... $enable_fuse"
AS_ECHO_N(["GNU gettext for i18n ................ "])
if test "x$HAVE_GNU_GETTEXT_TRUE" = "x"; then echo
"yes"; else echo "no"; fi
+AS_ECHO_N(["virt-p2v ............................ "])
+if test "x$HAVE_P2V_TRUE" = "x"; then echo "yes";
else echo "no"; fi
AS_ECHO_N(["OCaml bindings ...................... "])
if test "x$HAVE_OCAML_TRUE" = "x"; then echo
"yes"; else echo "no"; fi
AS_ECHO_N(["OCaml-based virt tools .............. "])
diff --git a/fish/guestfish.pod b/fish/guestfish.pod
index 5cf6ebc..cf52f86 100644
--- a/fish/guestfish.pod
+++ b/fish/guestfish.pod
@@ -1617,6 +1617,7 @@ L<virt-list-filesystems(1)>,
L<virt-list-partitions(1)>,
L<virt-ls(1)>,
L<virt-make-fs(1)>,
+L<virt-p2v(1)>,
L<virt-rescue(1)>,
L<virt-resize(1)>,
L<virt-sparsify(1)>,
diff --git a/p2v/Makefile.am b/p2v/Makefile.am
new file mode 100644
index 0000000..f88de4d
--- /dev/null
+++ b/p2v/Makefile.am
@@ -0,0 +1,88 @@
+# 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 \
+ copying.c \
+ gui.c \
+ main.c \
+ miniexpect.c \
+ miniexpect.h \
+ p2v.h \
+ ssh.c
+
+virt_p2v_CPPFLAGS = \
+ -DLOCALEBASEDIR=\""$(datadir)/locale"\" \
+ -I$(top_srcdir)/src -I$(top_builddir)/src \
+ -I$(srcdir)/../gnulib/lib -I../gnulib/lib
+
+virt_p2v_CFLAGS = \
+ -pthread \
+ $(WARN_CFLAGS) $(WERROR_CFLAGS) \
+ $(PCRE_CFLAGS) \
+ $(LIBXML2_CFLAGS) \
+ $(GTK2_CFLAGS)
+
+virt_p2v_LDADD = \
+ $(PCRE_LIBS) \
+ $(LIBXML2_LIBS) \
+ $(GTK2_LIBS) \
+ $(top_builddir)/src/libutils.la \
+ ../gnulib/lib/libgnu.la
+
+# Manual pages and HTML files for the website.
+man_MANS = virt-p2v.1
+
+noinst_DATA = \
+ $(top_builddir)/html/virt-p2v.1.html
+
+virt-p2v.1 $(top_builddir)/html/virt-p2v.1.html: stamp-virt-p2v.pod
+
+stamp-virt-p2v.pod: virt-p2v.pod
+ $(PODWRAPPER) \
+ --man virt-p2v.1 \
+ --html $(top_builddir)/html/virt-p2v.1.html \
+ --license GPLv2+ \
+ $<
+ touch $@
+
+# Tests.
+
+TESTS_ENVIRONMENT = $(top_builddir)/run --test
+
+#if ENABLE_APPLIANCE
+#TESTS = \
+# test-virt-p2v.sh
+#endif ENABLE_APPLIANCE
+#
+#check-valgrind:
+# $(MAKE) VG="$(top_builddir)/run @VG@" check
diff --git a/p2v/authors.c b/p2v/authors.c
new file mode 100644
index 0000000..40f2777
--- /dev/null
+++ b/p2v/authors.c
@@ -0,0 +1,30 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA.
+ */
+
+#include <config.h>
+
+#include "p2v.h"
+
+/* The list of authors of virt-p2v and virt-v2v, for the About dialog. */
+
+const char *authors[] = {
+ "Matthew Booth",
+ "Richard W.M. Jones",
+ "Mike Latimer",
+ NULL
+};
diff --git a/p2v/copying.c b/p2v/copying.c
new file mode 100644
index 0000000..90e8830
--- /dev/null
+++ b/p2v/copying.c
@@ -0,0 +1,38 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA.
+ */
+
+#include <config.h>
+
+#include "p2v.h"
+
+/* The license of virt-p2v, for the About dialog. */
+
+const char *gplv2plus + "This program is free software; you can
redistribute it and/or modify\n"
+ "it under the terms of the GNU General Public License as published
by\n"
+ "the Free Software Foundation; either version 2 of the License,
or\n"
+ "(at your option) any later version.\n"
+ "\n"
+ "This program is distributed in the hope that it will be useful,\n"
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
+ "GNU General Public License for more details.\n"
+ "\n"
+ "You should have received a copy of the GNU General Public
License\n"
+ "along with this program; if not, write to the Free Software\n"
+ "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.\n";
diff --git a/p2v/gui.c b/p2v/gui.c
new file mode 100644
index 0000000..134ad91
--- /dev/null
+++ b/p2v/gui.c
@@ -0,0 +1,693 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <locale.h>
+#include <assert.h>
+#include <libintl.h>
+
+#include <pthread.h>
+
+#pragma GCC diagnostic ignored "-Wstrict-prototypes" /* error in
<gtk.h> */
+#include <gtk/gtk.h>
+
+#include "p2v.h"
+
+/* Interactive GUI configuration. */
+
+static void create_connection_dialog (void);
+static void create_conversion_dialog (void);
+static void show_connection_dialog (void);
+static void show_conversion_dialog (void);
+
+/* The connection dialog. */
+static GtkWidget *conn_dlg,
+ *server_entry, *port_entry,
+ *username_entry, *password_entry, *sudo_button,
+ *spinner_hbox, *spinner, *spinner_message, *next_button;
+
+/* The conversion dialog. */
+static GtkWidget *conv_dlg,
+ *guestname_entry, *vcpus_entry, *memory_entry, *debug_button,
+ *start_button;
+
+/* The entry point from the main program.
+ * Note that gtk_init etc have already been called in main().
+ */
+void
+gui_application (void)
+{
+ /* Create the dialogs. */
+ create_connection_dialog ();
+ create_conversion_dialog ();
+
+ /* Start by displaying the connection dialog. */
+ show_connection_dialog ();
+
+ gtk_main ();
+ gdk_threads_leave ();
+}
+
+/*----------------------------------------------------------------------*/
+/* Connection dialog. */
+
+static void test_connection_clicked (GtkWidget *w, gpointer data);
+static void *test_connection_thread (void *data);
+static void about_button_clicked (GtkWidget *w, gpointer data);
+static void connection_next_clicked (GtkWidget *w, gpointer data);
+
+static void
+create_connection_dialog (void)
+{
+ GtkWidget *intro, *table;
+ GtkWidget *server_label;
+ GtkWidget *port_label;
+ GtkWidget *username_label;
+ GtkWidget *password_label;
+ GtkWidget *test_hbox, *test;
+ GtkWidget *about;
+ char port_str[64];
+
+ conn_dlg = gtk_dialog_new ();
+ gtk_window_set_title (GTK_WINDOW (conn_dlg), program_name);
+ gtk_window_set_resizable (GTK_WINDOW (conn_dlg), FALSE);
+
+ /* The main dialog area. */
+ intro = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (intro), TRUE);
+ gtk_label_set_text (GTK_LABEL (intro),
+ _("Connect to a virt-v2v conversion server over
SSH:"));
+ gtk_misc_set_padding (GTK_MISC (intro), 10, 10);
+
+ table = gtk_table_new (5, 2, FALSE);
+ server_label = gtk_label_new (_("Conversion server:"));
+ gtk_misc_set_alignment (GTK_MISC (server_label), 1., 0.5);
+ gtk_table_attach (GTK_TABLE (table), server_label,
+ 0, 1, 0, 1, GTK_FILL, GTK_FILL, 4, 4);
+ server_entry = gtk_entry_new ();
+ if (server != NULL)
+ gtk_entry_set_text (GTK_ENTRY (server_entry), server);
+ gtk_table_attach (GTK_TABLE (table), server_entry,
+ 1, 2, 0, 1, GTK_FILL, GTK_FILL, 4, 4);
+
+ port_label = gtk_label_new (_("SSH port:"));
+ gtk_misc_set_alignment (GTK_MISC (port_label), 1., 0.5);
+ gtk_table_attach (GTK_TABLE (table), port_label,
+ 0, 1, 1, 2, GTK_FILL, GTK_FILL, 4, 4);
+ port_entry = gtk_entry_new ();
+ gtk_entry_set_width_chars (GTK_ENTRY (port_entry), 6);
+ snprintf (port_str, sizeof port_str, "%d", port);
+ gtk_entry_set_text (GTK_ENTRY (port_entry), port_str);
+ gtk_table_attach (GTK_TABLE (table), port_entry,
+ 1, 2, 1, 2, GTK_FILL, GTK_FILL, 4, 4);
+
+ username_label = gtk_label_new (_("User name:"));
+ gtk_misc_set_alignment (GTK_MISC (username_label), 1., 0.5);
+ gtk_table_attach (GTK_TABLE (table), username_label,
+ 0, 1, 2, 3, GTK_FILL, GTK_FILL, 4, 4);
+ username_entry = gtk_entry_new ();
+ if (username != NULL)
+ gtk_entry_set_text (GTK_ENTRY (username_entry), username);
+ else
+ gtk_entry_set_text (GTK_ENTRY (username_entry), "root");
+ gtk_table_attach (GTK_TABLE (table), username_entry,
+ 1, 2, 2, 3, GTK_FILL, GTK_FILL, 4, 4);
+
+ password_label = gtk_label_new (_("Password:"));
+ gtk_misc_set_alignment (GTK_MISC (password_label), 1., 0.5);
+ gtk_table_attach (GTK_TABLE (table), password_label,
+ 0, 1, 3, 4, GTK_FILL, GTK_FILL, 4, 4);
+ password_entry = gtk_entry_new ();
+ gtk_entry_set_visibility (GTK_ENTRY (password_entry), FALSE);
+#ifdef GTK_INPUT_PURPOSE_PASSWORD
+ gtk_entry_set_input_purpose (GTK_ENTRY (password_entry),
+ GTK_INPUT_PURPOSE_PASSWORD);
+#endif
+ if (password != NULL)
+ gtk_entry_set_text (GTK_ENTRY (password_entry), password);
+ gtk_table_attach (GTK_TABLE (table), password_entry,
+ 1, 2, 3, 4, GTK_FILL, GTK_FILL, 4, 4);
+
+ sudo_button + gtk_check_button_new_with_label (_("Use sudo when
running virt-v2v"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sudo_button), sudo);
+ gtk_table_attach (GTK_TABLE (table), sudo_button,
+ 1, 2, 4, 5, GTK_FILL, GTK_FILL, 4, 4);
+
+ test_hbox = gtk_hbox_new (FALSE, 0);
+ test = gtk_button_new_with_label (_("Test connection"));
+ gtk_box_pack_start (GTK_BOX (test_hbox), test, TRUE, FALSE, 0);
+
+ spinner_hbox = gtk_hbox_new (FALSE, 10);
+ spinner = gtk_spinner_new ();
+ gtk_box_pack_start (GTK_BOX (spinner_hbox), spinner, FALSE, FALSE, 0);
+ spinner_message = gtk_label_new (NULL);
+ gtk_label_set_line_wrap (GTK_LABEL (spinner_message), TRUE);
+ gtk_misc_set_padding (GTK_MISC (spinner_message), 10, 10);
+ gtk_box_pack_start (GTK_BOX (spinner_hbox), spinner_message, TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (conn_dlg)->vbox),
+ intro, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (conn_dlg)->vbox),
+ table, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (conn_dlg)->vbox),
+ test_hbox, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (conn_dlg)->vbox),
+ spinner_hbox, TRUE, TRUE, 0);
+
+ /* Buttons. */
+ gtk_dialog_add_buttons (GTK_DIALOG (conn_dlg),
+ _("Configure network ..."), 1,
+ _("About ..."), 2,
+ _("Next"), 3,
+ NULL);
+
+ next_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (conn_dlg), 3);
+ gtk_widget_set_sensitive (next_button, FALSE);
+
+ about = gtk_dialog_get_widget_for_response (GTK_DIALOG (conn_dlg), 2);
+
+ /* Signals. */
+ g_signal_connect_swapped (G_OBJECT (conn_dlg), "destroy",
+ G_CALLBACK (gtk_main_quit), NULL);
+ g_signal_connect (G_OBJECT (test), "clicked",
+ G_CALLBACK (test_connection_clicked), NULL);
+ g_signal_connect (G_OBJECT (about), "clicked",
+ G_CALLBACK (about_button_clicked), NULL);
+ g_signal_connect (G_OBJECT (next_button), "clicked",
+ G_CALLBACK (connection_next_clicked), NULL);
+}
+
+static void
+show_connection_dialog (void)
+{
+ /* Hide the conversion dialog. */
+ gtk_widget_hide (conv_dlg);
+
+ /* Show everything except the spinner. */
+ gtk_widget_show_all (conn_dlg);
+ gtk_widget_hide_all (spinner_hbox);
+}
+
+static void
+test_connection_clicked (GtkWidget *w, gpointer data)
+{
+ const gchar *port_str;
+ size_t errors = 0;
+ int err;
+ pthread_t tid;
+ pthread_attr_t attr;
+
+ gtk_label_set_text (GTK_LABEL (spinner_message), "");
+ gtk_widget_show_all (spinner_hbox);
+
+ /* Get the fields from the various widgets. */
+ free (server);
+ server = strdup (gtk_entry_get_text (GTK_ENTRY (server_entry)));
+ if (STREQ (server, "")) {
+ gtk_label_set_text (GTK_LABEL (spinner_message),
+ _("error: No conversion server given."));
+ gtk_widget_grab_focus (server_entry);
+ errors++;
+ }
+ port_str = gtk_entry_get_text (GTK_ENTRY (port_entry));
+ if (sscanf (port_str, "%d", &port) != 1 || port <= 0 || port
>= 65536) {
+ gtk_label_set_text (GTK_LABEL (spinner_message),
+ _("error: Invalid port number. If in doubt, use
\"22\"."));
+ gtk_widget_grab_focus (port_entry);
+ errors++;
+ }
+ free (username);
+ username = strdup (gtk_entry_get_text (GTK_ENTRY (username_entry)));
+ if (STREQ (username, "")) {
+ gtk_label_set_text (GTK_LABEL (spinner_message),
+ _("error: No user name. If in doubt, use
\"root\"."));
+ gtk_widget_grab_focus (username_entry);
+ errors++;
+ }
+ free (password);
+ password = strdup (gtk_entry_get_text (GTK_ENTRY (password_entry)));
+
+ if (errors)
+ return;
+
+ /* No errors so far, so test the connection in a background thread. */
+ pthread_attr_init (&attr);
+ pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+ err = pthread_create (&tid, &attr, test_connection_thread, NULL);
+ if (err != 0) {
+ fprintf (stderr, "pthread_create: %s\n", strerror (err));
+ exit (EXIT_FAILURE);
+ }
+ pthread_attr_destroy (&attr);
+}
+
+/* Run test_connection (in a detached background thread). Once it
+ * finishes stop the spinner and set the spinner message
+ * appropriately. If the test is successful then we enable the
"Next"
+ * button.
+ */
+static void *
+test_connection_thread (void *data)
+{
+ int r;
+
+ gdk_threads_enter ();
+ gtk_label_set_text (GTK_LABEL (spinner_message),
+ _("Testing the connection to the conversion server
..."));
+ gtk_spinner_start (GTK_SPINNER (spinner));
+ gdk_threads_leave ();
+ r = test_connection ();
+ gdk_threads_enter ();
+ gtk_spinner_stop (GTK_SPINNER (spinner));
+
+ if (r == -1) {
+ /* Error testing the connection. */
+ const char *err = get_ssh_error ();
+
+ gtk_label_set_text (GTK_LABEL (spinner_message), err);
+ /* Disable the Next button. */
+ gtk_widget_set_sensitive (next_button, FALSE);
+ }
+ else {
+ /* Connection is good. */
+ gtk_label_set_text (GTK_LABEL (spinner_message),
+ _("Connected to the conversion server.\n"
+ "Press the \"Next\" button to
configure the conversion process."));
+ /* Enable the Next button. */
+ gtk_widget_set_sensitive (next_button, TRUE);
+ gtk_widget_grab_focus (next_button);
+ }
+ gdk_threads_leave ();
+
+ /* Thread is detached anyway, so no one is waiting for the status. */
+ return NULL;
+}
+
+static void
+about_button_clicked (GtkWidget *w, gpointer data)
+{
+ gtk_show_about_dialog (GTK_WINDOW (conn_dlg),
+ "program-name", program_name,
+ "version", PACKAGE_VERSION,
+ "copyright", "\u00A9 2009-2014 Red Hat
Inc.",
+ "comments", "Convert a physical machine
to use KVM",
+ "license", gplv2plus,
+ "website",
"http://libguestfs.org/",
+ "authors", authors,
+ NULL);
+}
+
+/* The connection dialog Next button has been clicked. */
+static void
+connection_next_clicked (GtkWidget *w, gpointer data)
+{
+ /* Switch to the conversion dialog. */
+ show_conversion_dialog ();
+}
+
+/*----------------------------------------------------------------------*/
+/* Conversion dialog. */
+
+static void populate_disks (GtkTreeView *disks_list);
+static void populate_removable (GtkTreeView *removable_list);
+static void populate_interfaces (GtkTreeView *interfaces_list);
+static void toggled (GtkCellRendererToggle *cell, gchar *path_str, gpointer
data);
+static void conversion_back_clicked (GtkWidget *w, gpointer data);
+
+enum {
+ DISKS_COL_CONVERT = 0,
+ DISKS_COL_DEVICE,
+ DISKS_COL_SIZE,
+ DISKS_COL_MODEL,
+ NUM_DISKS_COLS,
+};
+
+enum {
+ REMOVABLE_COL_CONVERT = 0,
+ REMOVABLE_COL_DEVICE,
+ NUM_REMOVABLE_COLS,
+};
+
+enum {
+ INTERFACES_COL_CONVERT = 0,
+ INTERFACES_COL_DEVICE,
+ NUM_INTERFACES_COLS,
+};
+
+static void
+create_conversion_dialog (void)
+{
+ GtkWidget *back;
+ GtkWidget *hbox, *right_vbox;
+ GtkWidget *target, *target_tbl;
+ GtkWidget *guestname_label, *vcpus_label, *memory_label;
+ GtkWidget *disks_frame, *disks_sw;
+ GtkWidget *disks_list;
+ GtkWidget *removable_frame, *removable_sw;
+ GtkWidget *removable_list;
+ GtkWidget *interfaces_frame, *interfaces_sw;
+ GtkWidget *interfaces_list;
+ char vcpus_str[64];
+ char memory_str[64];
+
+ conv_dlg = gtk_dialog_new ();
+ gtk_window_set_title (GTK_WINDOW (conv_dlg), program_name);
+ gtk_window_set_resizable (GTK_WINDOW (conv_dlg), FALSE);
+ /* XXX It would be nice not to have to set this explicitly, but
+ * if we don't then Gtk chooses a very small window.
+ */
+ gtk_widget_set_size_request (conv_dlg, 800, 500);
+
+ /* The main dialog area. */
+ hbox = gtk_hbox_new (TRUE, 1);
+ right_vbox = gtk_vbox_new (TRUE, 1);
+
+ /* The left column: target properties. */
+ target = gtk_frame_new (_("Target properties"));
+
+ target_tbl = gtk_table_new (4, 2, FALSE);
+ guestname_label = gtk_label_new (_("Name:"));
+ gtk_misc_set_alignment (GTK_MISC (guestname_label), 1., 0.5);
+ gtk_table_attach (GTK_TABLE (target_tbl), guestname_label,
+ 0, 1, 0, 1, GTK_FILL, GTK_FILL, 1, 1);
+ guestname_entry = gtk_entry_new ();
+ if (guestname != NULL)
+ gtk_entry_set_text (GTK_ENTRY (guestname_entry), guestname);
+ gtk_table_attach (GTK_TABLE (target_tbl), guestname_entry,
+ 1, 2, 0, 1, GTK_FILL, GTK_FILL, 1, 1);
+
+ vcpus_label = gtk_label_new (_("# vCPUs:"));
+ gtk_misc_set_alignment (GTK_MISC (vcpus_label), 1., 0.5);
+ gtk_table_attach (GTK_TABLE (target_tbl), vcpus_label,
+ 0, 1, 1, 2, GTK_FILL, GTK_FILL, 1, 1);
+ vcpus_entry = gtk_entry_new ();
+ snprintf (vcpus_str, sizeof vcpus_str, "%d", vcpus);
+ gtk_entry_set_text (GTK_ENTRY (vcpus_entry), vcpus_str);
+ gtk_table_attach (GTK_TABLE (target_tbl), vcpus_entry,
+ 1, 2, 1, 2, GTK_FILL, GTK_FILL, 1, 1);
+
+ memory_label = gtk_label_new (_("Memory (MB):"));
+ gtk_misc_set_alignment (GTK_MISC (memory_label), 1., 0.5);
+ gtk_table_attach (GTK_TABLE (target_tbl), memory_label,
+ 0, 1, 2, 3, GTK_FILL, GTK_FILL, 1, 1);
+ memory_entry = gtk_entry_new ();
+ snprintf (memory_str, sizeof memory_str, "%" PRIu64, memory / 1024
/ 1024);
+ gtk_entry_set_text (GTK_ENTRY (memory_entry), memory_str);
+ gtk_table_attach (GTK_TABLE (target_tbl), memory_entry,
+ 1, 2, 2, 3, GTK_FILL, GTK_FILL, 1, 1);
+
+ debug_button + gtk_check_button_new_with_label (_("Enable server-side
debugging"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (debug_button), verbose);
+ gtk_table_attach (GTK_TABLE (target_tbl), debug_button,
+ 1, 2, 3, 4, GTK_FILL, GTK_FILL, 1, 1);
+
+ gtk_container_add (GTK_CONTAINER (target), target_tbl);
+
+ /* The right column: select devices to be converted. */
+ disks_frame = gtk_frame_new (_("Fixed hard disks"));
+ disks_sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (disks_sw),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ disks_list = gtk_tree_view_new ();
+ populate_disks (GTK_TREE_VIEW (disks_list));
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (disks_sw),
+ disks_list);
+ gtk_container_add (GTK_CONTAINER (disks_frame), disks_sw);
+
+ removable_frame = gtk_frame_new (_("Removable media"));
+ removable_sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (removable_sw),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ removable_list = gtk_tree_view_new ();
+ populate_removable (GTK_TREE_VIEW (removable_list));
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (removable_sw),
+ removable_list);
+ gtk_container_add (GTK_CONTAINER (removable_frame), removable_sw);
+
+ interfaces_frame = gtk_frame_new (_("Network interfaces"));
+ interfaces_sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (interfaces_sw),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ interfaces_list = gtk_tree_view_new ();
+ populate_interfaces (GTK_TREE_VIEW (interfaces_list));
+ gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (interfaces_sw),
+ interfaces_list);
+ gtk_container_add (GTK_CONTAINER (interfaces_frame), interfaces_sw);
+
+ gtk_box_pack_start (GTK_BOX (right_vbox), disks_frame, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (right_vbox), removable_frame, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (right_vbox), interfaces_frame, TRUE, TRUE, 0);
+
+ gtk_box_pack_start (GTK_BOX (hbox), target, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (hbox), right_vbox, TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (GTK_DIALOG (conv_dlg)->vbox),
+ hbox, TRUE, TRUE, 0);
+
+ /* Buttons. */
+ gtk_dialog_add_buttons (GTK_DIALOG (conv_dlg),
+ _("Back"), 1,
+ _("Start conversion"), 2,
+ NULL);
+ back = gtk_dialog_get_widget_for_response (GTK_DIALOG (conv_dlg), 1);
+ start_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (conv_dlg), 2);
+
+ /* Signals. */
+ g_signal_connect_swapped (G_OBJECT (conv_dlg), "destroy",
+ G_CALLBACK (gtk_main_quit), NULL);
+ g_signal_connect (G_OBJECT (back), "clicked",
+ G_CALLBACK (conversion_back_clicked), NULL);
+}
+
+static void
+show_conversion_dialog (void)
+{
+ /* Hide the connection dialog. */
+ gtk_widget_hide (conn_dlg);
+
+ /* Show the conversion dialog. */
+ gtk_widget_show_all (conv_dlg);
+}
+
+static void
+populate_disks (GtkTreeView *disks_list)
+{
+ GtkListStore *disks_store;
+ GtkCellRenderer *disks_col_convert, *disks_col_device,
+ *disks_col_size, *disks_col_model;
+ GtkTreeIter iter;
+ size_t i;
+
+ disks_store = gtk_list_store_new (NUM_DISKS_COLS,
+ G_TYPE_BOOLEAN, G_TYPE_STRING,
+ G_TYPE_STRING, G_TYPE_STRING);
+ if (disks != NULL) {
+ for (i = 0; disks[i] != NULL; ++i) {
+ CLEANUP_FREE char *size_filename = NULL;
+ CLEANUP_FREE char *model_filename = NULL;
+ CLEANUP_FREE char *size_str = NULL;
+ CLEANUP_FREE char *size_gb = NULL;
+ CLEANUP_FREE char *model = NULL;
+ uint64_t size;
+
+ if (asprintf (&size_filename, "/sys/block/%s/size",
+ disks[i]) == -1) {
+ perror ("asprintf");
+ exit (EXIT_FAILURE);
+ }
+ if (g_file_get_contents (size_filename, &size_str, NULL, NULL)
&&
+ sscanf (size_str, "%" SCNu64, &size) == 1) {
+ size /= 2*1024*1024; /* size from kernel is given in sectors? */
+ if (asprintf (&size_gb, "%" PRIu64, size) == -1) {
+ perror ("asprintf");
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ if (asprintf (&model_filename,
"/sys/block/%s/device/model",
+ disks[i]) == -1) {
+ perror ("asprintf");
+ exit (EXIT_FAILURE);
+ }
+ if (!g_file_get_contents (model_filename, &model, NULL, NULL))
+ model = strdup ("");
+
+ gtk_list_store_append (disks_store, &iter);
+ gtk_list_store_set (disks_store, &iter,
+ DISKS_COL_CONVERT, TRUE,
+ DISKS_COL_DEVICE, disks[i],
+ DISKS_COL_SIZE, size_gb,
+ DISKS_COL_MODEL, model,
+ -1);
+ }
+ }
+ gtk_tree_view_set_model (disks_list,
+ GTK_TREE_MODEL (disks_store));
+ gtk_tree_view_set_headers_visible (disks_list, TRUE);
+ disks_col_convert = gtk_cell_renderer_toggle_new ();
+ gtk_tree_view_insert_column_with_attributes (disks_list,
+ -1,
+ _("Convert"),
+ disks_col_convert,
+ "active",
DISKS_COL_CONVERT,
+ NULL);
+ disks_col_device = gtk_cell_renderer_text_new ();
+ gtk_tree_view_insert_column_with_attributes (disks_list,
+ -1,
+ _("Device"),
+ disks_col_device,
+ "text",
DISKS_COL_DEVICE,
+ NULL);
+ disks_col_size = gtk_cell_renderer_text_new ();
+ gtk_tree_view_insert_column_with_attributes (disks_list,
+ -1,
+ _("Size (GB)"),
+ disks_col_size,
+ "text",
DISKS_COL_SIZE,
+ NULL);
+ disks_col_model = gtk_cell_renderer_text_new ();
+ gtk_tree_view_insert_column_with_attributes (disks_list,
+ -1,
+ _("Model"),
+ disks_col_model,
+ "text",
DISKS_COL_MODEL,
+ NULL);
+
+ g_signal_connect (disks_col_convert, "toggled",
+ G_CALLBACK (toggled), disks_store);
+}
+
+static void
+populate_removable (GtkTreeView *removable_list)
+{
+ GtkListStore *removable_store;
+ GtkCellRenderer *removable_col_convert, *removable_col_device;
+ GtkTreeIter iter;
+ size_t i;
+
+ removable_store = gtk_list_store_new (NUM_REMOVABLE_COLS,
+ G_TYPE_BOOLEAN, G_TYPE_STRING);
+ if (removable != NULL) {
+ for (i = 0; removable[i] != NULL; ++i) {
+ gtk_list_store_append (removable_store, &iter);
+ gtk_list_store_set (removable_store, &iter,
+ REMOVABLE_COL_CONVERT, TRUE,
+ REMOVABLE_COL_DEVICE, removable[i],
+ -1);
+ }
+ }
+ gtk_tree_view_set_model (removable_list,
+ GTK_TREE_MODEL (removable_store));
+ gtk_tree_view_set_headers_visible (removable_list, TRUE);
+ removable_col_convert = gtk_cell_renderer_toggle_new ();
+ gtk_tree_view_insert_column_with_attributes (removable_list,
+ -1,
+ _("Convert"),
+ removable_col_convert,
+ "active",
REMOVABLE_COL_CONVERT,
+ NULL);
+ removable_col_device = gtk_cell_renderer_text_new ();
+ gtk_tree_view_insert_column_with_attributes (removable_list,
+ -1,
+ _("Device"),
+ removable_col_device,
+ "text",
REMOVABLE_COL_DEVICE,
+ NULL);
+
+ g_signal_connect (removable_col_convert, "toggled",
+ G_CALLBACK (toggled), removable_store);
+}
+
+static void
+populate_interfaces (GtkTreeView *interfaces_list)
+{
+ GtkListStore *interfaces_store;
+ GtkCellRenderer *interfaces_col_convert, *interfaces_col_device;
+ GtkTreeIter iter;
+ size_t i;
+
+ interfaces_store = gtk_list_store_new (NUM_INTERFACES_COLS,
+ G_TYPE_BOOLEAN, G_TYPE_STRING);
+ if (interfaces) {
+ for (i = 0; interfaces[i] != NULL; ++i) {
+ gtk_list_store_append (interfaces_store, &iter);
+ gtk_list_store_set (interfaces_store, &iter,
+ INTERFACES_COL_CONVERT, TRUE,
+ INTERFACES_COL_DEVICE, interfaces[i],
+ -1);
+ }
+ }
+ gtk_tree_view_set_model (interfaces_list,
+ GTK_TREE_MODEL (interfaces_store));
+ gtk_tree_view_set_headers_visible (interfaces_list, TRUE);
+ interfaces_col_convert = gtk_cell_renderer_toggle_new ();
+ gtk_tree_view_insert_column_with_attributes (interfaces_list,
+ -1,
+ _("Convert"),
+ interfaces_col_convert,
+ "active",
INTERFACES_COL_CONVERT,
+ NULL);
+ interfaces_col_device = gtk_cell_renderer_text_new ();
+ gtk_tree_view_insert_column_with_attributes (interfaces_list,
+ -1,
+ _("Device"),
+ interfaces_col_device,
+ "text",
INTERFACES_COL_DEVICE,
+ NULL);
+
+ g_signal_connect (interfaces_col_convert, "toggled",
+ G_CALLBACK (toggled), interfaces_store);
+}
+
+static void
+toggled (GtkCellRendererToggle *cell, gchar *path_str, gpointer data)
+{
+ GtkTreeModel *model = data;
+ GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
+ GtkTreeIter iter;
+ gboolean v;
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter, 0 /* CONVERT */, &v, -1);
+ v ^= 1;
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0 /* CONVERT */, v,
-1);
+ gtk_tree_path_free (path);
+}
+
+/* The conversion dialog Back button has been clicked. */
+static void
+conversion_back_clicked (GtkWidget *w, gpointer data)
+{
+ /* Switch to the connection dialog. */
+ show_connection_dialog ();
+
+ /* Better disable the Next button so the user is forced to
+ * do "Test connection" again.
+ */
+ gtk_widget_set_sensitive (next_button, FALSE);
+}
diff --git a/p2v/main.c b/p2v/main.c
new file mode 100644
index 0000000..6dcac2a
--- /dev/null
+++ b/p2v/main.c
@@ -0,0 +1,579 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dirent.h>
+#include <locale.h>
+#include <assert.h>
+#include <libintl.h>
+#include <sys/types.h>
+
+#pragma GCC diagnostic ignored "-Wstrict-prototypes" /* error in
<gtk.h> */
+#include <gtk/gtk.h>
+
+#include "p2v.h"
+
+int verbose;
+char *server;
+int port = 22;
+char *username;
+char *password;
+int sudo;
+char *guestname;
+int vcpus;
+uint64_t memory;
+char **disks;
+char **removable;
+char **interfaces;
+
+static void set_target_defaults (void);
+static void find_default_disks (void);
+static void find_default_interfaces (void);
+static char *read_cmdline (void);
+static void kernel_configuration (const char *cmdline);
+
+enum { HELP_OPTION = CHAR_MAX + 1 };
+static const char *options = "Vv";
+static const struct option long_options[] = {
+ { "help", 0, 0, HELP_OPTION },
+ { "cmdline", 1, 0, 0 },
+ { "long-options", 0, 0, 0 },
+ { "verbose", 0, 0, 'v' },
+ { "version", 0, 0, 'V' },
+ { 0, 0, 0, 0 }
+};
+
+static void __attribute__((noreturn))
+usage (int status)
+{
+ if (status != EXIT_SUCCESS)
+ fprintf (stderr, _("Try `%s --help' for more
information.\n"),
+ program_name);
+ else {
+ fprintf (stdout,
+ _("%s: Convert a physical machine to use KVM\n"
+ "Copyright (C) 2009-2014 Red Hat Inc.\n"
+ "Usage:\n"
+ " %s [--options]\n"
+ "Options:\n"
+ " --help Display brief help\n"
+ " --cmdline=CMDLINE Used to debug command line
parsing\n"
+ " -v|--verbose Verbose messages\n"
+ " -V|--version Display version and exit\n"
+ "For more information, see the manpage %s(1).\n"),
+ program_name, program_name, program_name);
+ }
+ exit (status);
+}
+
+/* XXX Copied from fish/options.c. */
+static void
+display_long_options (const struct option *long_options)
+{
+ while (long_options->name) {
+ if (STRNEQ (long_options->name, "long-options"))
+ printf ("--%s\n", long_options->name);
+ long_options++;
+ }
+ exit (EXIT_SUCCESS);
+}
+
+int
+main (int argc, char *argv[])
+{
+ gboolean gui_possible;
+ int c;
+ int option_index;
+ char *cmdline = NULL;
+
+ setlocale (LC_ALL, "");
+ bindtextdomain (PACKAGE, LOCALEBASEDIR);
+ textdomain (PACKAGE);
+
+ gdk_threads_init ();
+ gdk_threads_enter ();
+ gui_possible = gtk_init_check (&argc, &argv);
+
+ for (;;) {
+ c = getopt_long (argc, argv, options, long_options, &option_index);
+ if (c == -1) break;
+
+ switch (c) {
+ case 0: /* options which are long only */
+ if (STREQ (long_options[option_index].name, "long-options")) {
+ display_long_options (long_options);
+ }
+ else if (STREQ (long_options[option_index].name, "cmdline")) {
+ cmdline = strdup (optarg);
+ }
+ else {
+ fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
+ program_name, long_options[option_index].name, option_index);
+ exit (EXIT_FAILURE);
+ }
+ break;
+
+ case 'v':
+ verbose = 1;
+ break;
+
+ case 'V':
+ printf ("%s %s\n", program_name, PACKAGE_VERSION);
+ exit (EXIT_SUCCESS);
+
+ case HELP_OPTION:
+ usage (EXIT_SUCCESS);
+
+ default:
+ usage (EXIT_FAILURE);
+ }
+ }
+
+ if (optind != argc) {
+ fprintf (stderr, _("%s: unused arguments on the command line\n"),
+ program_name);
+ usage (EXIT_FAILURE);
+ }
+
+ set_target_defaults ();
+
+ /* If /proc/cmdline exists and contains "p2v.server=" then we
enable
+ * non-interactive configuration.
+ * If /proc/cmdline contains p2v.debug then we enable verbose mode
+ * even for interactive configuration.
+ */
+ if (cmdline == NULL)
+ cmdline = read_cmdline ();
+ if (cmdline == NULL)
+ goto gui;
+
+ if (strstr (cmdline, "p2v.debug"))
+ verbose = 1;
+
+ if (strstr (cmdline, "p2v.server="))
+ kernel_configuration (cmdline);
+ else {
+ gui:
+ if (!gui_possible)
+ /* Gtk has already printed an error. */
+ exit (EXIT_FAILURE);
+ gui_application ();
+ }
+
+ free (cmdline);
+
+ exit (EXIT_SUCCESS);
+}
+
+static void
+set_target_defaults (void)
+{
+ 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';
+ }
+ 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");
+ vcpus = 1;
+ }
+ else if (i == 0)
+ vcpus = 1;
+ else
+ vcpus = i;
+
+ i = sysconf (_SC_PHYS_PAGES);
+ if (i == -1) {
+ perror ("sysconf: _SC_PHYS_PAGES");
+ memory = 1024 * 1024 * 1024;
+ }
+ else
+ memory = i;
+
+ i = sysconf (_SC_PAGESIZE);
+ if (i == -1) {
+ perror ("sysconf: _SC_PAGESIZE");
+ memory *= 4096;
+ }
+ else
+ 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
+ */
+ memory--;
+ memory |= memory >> 1;
+ memory |= memory >> 2;
+ memory |= memory >> 4;
+ memory |= memory >> 8;
+ memory |= memory >> 16;
+ memory |= memory >> 32;
+ memory++;
+
+ find_default_disks ();
+ find_default_interfaces ();
+}
+
+static int
+compare (const void *vp1, const void *vp2)
+{
+ char * const *p1 = (char * const *) vp1;
+ char * const *p2 = (char * const *) vp2;
+ return strcmp (*p1, *p2);
+}
+
+static void
+find_default_disks (void)
+{
+ DIR *dir;
+ struct dirent *d;
+ size_t nr_disks = 0, nr_removable = 0;
+
+ /* The default list of disks is everything in /sys/block which
+ * matches the common patterns for disk names.
+ */
+ dir = opendir ("/sys/block");
+ if (!dir) {
+ perror ("opendir");
+ exit (EXIT_FAILURE);
+ }
+
+ for (;;) {
+ errno = 0;
+ d = readdir (dir);
+ if (!d) break;
+
+ if (STRPREFIX (d->d_name, "cciss!") ||
+ STRPREFIX (d->d_name, "hd") ||
+ STRPREFIX (d->d_name, "sd") ||
+ STRPREFIX (d->d_name, "ubd") ||
+ STRPREFIX (d->d_name, "vd")) {
+ char *p;
+
+ nr_disks++;
+ disks = realloc (disks, sizeof (char *) * (nr_disks + 1));
+ if (!disks) {
+ perror ("realloc");
+ exit (EXIT_FAILURE);
+ }
+
+ disks[nr_disks-1] = strdup (d->d_name);
+
+ /* cciss device /dev/cciss/c0d0 will be /sys/block/cciss!c0d0 */
+ p = strchr (disks[nr_disks-1], '!');
+ if (p) *p = '/';
+
+ disks[nr_disks] = NULL;
+ }
+ else if (STRPREFIX (d->d_name, "sr")) {
+ nr_removable++;
+ removable = realloc (removable, sizeof (char *) * (nr_removable + 1));
+ if (!removable) {
+ perror ("realloc");
+ exit (EXIT_FAILURE);
+ }
+ removable[nr_removable-1] = strdup (d->d_name);
+ 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 (disks)
+ qsort (disks, nr_disks, sizeof (char *), compare);
+ if (removable)
+ qsort (removable, nr_removable, sizeof (char *), compare);
+}
+
+static void
+find_default_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++;
+ interfaces = realloc (interfaces, sizeof (char *) * (nr_interfaces + 1));
+ if (!interfaces) {
+ perror ("realloc");
+ exit (EXIT_FAILURE);
+ }
+ interfaces[nr_interfaces-1] = strdup (d->d_name);
+ 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 (interfaces)
+ qsort (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;
+}
+
+/* Kernel-driven configuration, non-interactive. */
+static void
+kernel_configuration (const char *cmdline)
+{
+ const char *r;
+ size_t len;
+
+ r = strstr (cmdline, "p2v.server=");
+ assert (r); /* checked by caller */
+ r += 5+6;
+ len = strcspn (r, " ");
+ free (server);
+ server = strndup (r, len);
+
+ r = strstr (cmdline, "p2v.port=");
+ if (r) {
+ r += 5+4;
+ if (sscanf (r, "%d", &port) != 1) {
+ fprintf (stderr, "%s: cannot parse p2v.port from kernel command
line",
+ program_name);
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ r = strstr (cmdline, "p2v.username=");
+ if (r) {
+ r += 5+8;
+ len = strcspn (r, " ");
+ free (username);
+ username = strndup (r, len);
+ }
+
+ r = strstr (cmdline, "p2v.password=");
+ if (r) {
+ r += 5+8;
+ len = strcspn (r, " ");
+ free (password);
+ password = strndup (r, len);
+ }
+
+ r = strstr (cmdline, "p2v.sudo");
+ if (r)
+ sudo = 1;
+
+ /* We should now be able to connect and interrogate virt-v2v
+ * on the conversion server.
+ */
+ if (test_connection () == -1) {
+ const char *err = get_ssh_error ();
+
+ fprintf (stderr, "%s: error opening control connection to %s:%d:
%s\n",
+ program_name, server, port, err);
+ exit (EXIT_FAILURE);
+ }
+
+ r = strstr (cmdline, "p2v.name=");
+ if (r) {
+ r += 5+4;
+ len = strcspn (r, " ");
+ free (guestname);
+ guestname = strndup (r, len);
+ }
+
+ r = strstr (cmdline, "p2v.vcpus=");
+ if (r) {
+ r += 5+5;
+ if (sscanf (r, "%d", &vcpus) != 1) {
+ fprintf (stderr, "%s: cannot parse p2v.vcpus from kernel command
line\n",
+ program_name);
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ r = strstr (cmdline, "p2v.memory=");
+ if (r) {
+ char mem_code[2];
+
+ r += 5+6;
+ if (sscanf (r, "%" SCNu64 "%c", &memory, mem_code)
!= 1) {
+ fprintf (stderr, "%s: cannot parse p2v.memory from kernel command
line\n",
+ program_name);
+ exit (EXIT_FAILURE);
+ }
+ memory *= 1024;
+ if (mem_code[0] == 'M' || mem_code[0] == 'G')
+ memory *= 1024;
+ if (mem_code[0] == 'G')
+ memory *= 1024;
+ if (mem_code[0] != 'M' && mem_code[0] != 'G') {
+ fprintf (stderr, "%s: p2v.memory on kernel command line must be
followed by 'G' or 'M'\n",
+ program_name);
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ r = strstr (cmdline, "p2v.disks=");
+ if (r) {
+ r += 5+5;
+ len = strcspn (r, " ");
+ guestfs___free_string_list (disks);
+ disks = guestfs___split_string (',', r);
+ }
+
+ r = strstr (cmdline, "p2v.removable=");
+ if (r) {
+ r += 5+9;
+ len = strcspn (r, " ");
+ guestfs___free_string_list (removable);
+ removable = guestfs___split_string (',', r);
+ }
+
+ r = strstr (cmdline, "p2v.interfaces=");
+ if (r) {
+ r += 5+10;
+ len = strcspn (r, " ");
+ guestfs___free_string_list (interfaces);
+ interfaces = guestfs___split_string (',', r);
+ }
+
+
+
+
+
+
+
+}
diff --git a/p2v/miniexpect.c b/p2v/miniexpect.c
new file mode 100644
index 0000000..7f02584
--- /dev/null
+++ b/p2v/miniexpect.c
@@ -0,0 +1,364 @@
+/* miniexpect
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <poll.h>
+#include <errno.h>
+#include <termios.h>
+#include <time.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+
+#include <pcre.h>
+
+#include "miniexpect.h"
+
+#define DEBUG 0
+
+static mexp_h *
+create_handle (void)
+{
+ mexp_h *h = malloc (sizeof *h);
+ if (h == NULL)
+ return NULL;
+
+ /* Initialize the fields to default values. */
+ h->fd = -1;
+ h->pid = 0;
+ h->timeout = 60000;
+ h->read_size = 1024;
+ h->pcre_error = 0;
+ h->buffer = NULL;
+ h->len = h->alloc = 0;
+ h->user1 = h->user2 = h->user3 = NULL;
+
+ return h;
+}
+
+static void
+clear_buffer (mexp_h *h)
+{
+ free (h->buffer);
+ h->buffer = NULL;
+ h->alloc = h->len = 0;
+}
+
+int
+mexp_close (mexp_h *h)
+{
+ int status = 0;
+
+ free (h->buffer);
+
+ if (h->fd >= 0)
+ close (h->fd);
+ if (h->pid > 0) {
+ if (waitpid (h->pid, &status, 0) == -1)
+ return -1;
+ }
+
+ free (h);
+
+ return status;
+}
+
+mexp_h *
+mexp_spawnl (const char *file, const char *arg, ...)
+{
+ char **argv, **new_argv;
+ size_t i;
+ va_list args;
+ mexp_h *h;
+
+ argv = malloc (sizeof (char *));
+ if (argv == NULL)
+ return NULL;
+ argv[0] = (char *) arg;
+
+ va_start (args, arg);
+ for (i = 1; arg != NULL; ++i) {
+ arg = va_arg (args, const char *);
+ new_argv = realloc (argv, sizeof (char *) * (i+1));
+ if (new_argv == NULL) {
+ free (argv);
+ return NULL;
+ }
+ argv = new_argv;
+ argv[i] = (char *) arg;
+ }
+
+ h = mexp_spawnv (file, argv);
+ free (argv);
+ return h;
+}
+
+mexp_h *
+mexp_spawnv (const char *file, char **argv)
+{
+ mexp_h *h;
+ int fd = -1;
+ int err;
+ char slave[1024];
+ pid_t pid = 0;
+
+ fd = posix_openpt (O_RDWR|O_NOCTTY);
+ if (fd == -1)
+ goto error;
+
+ if (grantpt (fd) == -1)
+ goto error;
+
+ if (unlockpt (fd) == -1)
+ goto error;
+
+ /* Get the slave pty name now, but don't open it in the parent. */
+ if (ptsname_r (fd, slave, sizeof slave) != 0)
+ goto error;
+
+ /* Create the handle last before we fork. */
+ h = create_handle ();
+ if (h == NULL)
+ goto error;
+
+ pid = fork ();
+ if (pid == -1)
+ goto error;
+
+ if (pid == 0) { /* Child. */
+ struct termios terminal_settings;
+ int slave_fd;
+
+ setsid ();
+
+ /* Open the slave side of the pty. We must do this in the child
+ * after setsid so it becomes our controlling tty.
+ */
+ slave_fd = open (slave, O_RDWR);
+ if (slave_fd == -1)
+ goto error;
+
+ /* Set raw mode. */
+ tcgetattr (slave_fd, &terminal_settings);
+ cfmakeraw (&terminal_settings);
+ tcsetattr (slave_fd, TCSANOW, &terminal_settings);
+
+ /* Set up stdin, stdout, stderr to point to the pty. */
+ dup2 (slave_fd, 0);
+ dup2 (slave_fd, 1);
+ dup2 (slave_fd, 2);
+ close (slave_fd);
+
+ /* Close the master side of the pty - do this late to avoid a
+ * kernel bug, see sshpass source code.
+ */
+ close (fd);
+
+ /* Run the subprocess. */
+ execvp (file, argv);
+ perror (file);
+ _exit (EXIT_FAILURE);
+ }
+
+ /* Parent. */
+
+ h->fd = fd;
+ h->pid = pid;
+ return h;
+
+ error:
+ err = errno;
+ if (fd >= 0)
+ close (fd);
+ if (pid > 0)
+ waitpid (pid, NULL, 0);
+ errno = err;
+ return NULL;
+}
+
+enum mexp_status
+mexp_expect (mexp_h *h, const mexp_regexp *regexps, int *ovector, int ovecsize)
+{
+ time_t start_t, now_t;
+ int timeout;
+ struct pollfd pfds[1];
+ int r;
+ ssize_t rs;
+
+ time (&start_t);
+
+ /* Clear the read buffer. */
+ /* XXX This is possibly incorrect because it throws away inputs that
+ * may not have been matched yet. A better idea is to record the
+ * end of the previous match and only throw that away.
+ */
+ clear_buffer (h);
+
+ for (;;) {
+ /* If we've got a timeout then work out how many seconds are left.
+ * Timeout == 0 is not particularly well-defined, but it probably
+ * means "return immediately if there's no data to be read".
+ */
+ if (h->timeout >= 0) {
+ time (&now_t);
+ timeout = h->timeout - ((now_t - start_t) * 1000);
+ if (timeout < 0)
+ timeout = 0;
+ }
+ else
+ timeout = 0;
+
+ pfds[0].fd = h->fd;
+ pfds[0].events = POLLIN;
+ pfds[0].revents = 0;
+ r = poll (pfds, 1, timeout);
+#if DEBUG
+ fprintf (stderr, "DEBUG: poll returned %d\n", r);
+#endif
+ if (r == -1)
+ return MEXP_ERROR;
+
+ if (r == 0)
+ return MEXP_TIMEOUT;
+
+ /* Otherwise we expect there is something to read from the file
+ * descriptor.
+ */
+ if (h->alloc - h->len <= h->read_size) {
+ char *new_buffer;
+ /* +1 here allows us to store \0 after the data read */
+ new_buffer = realloc (h->buffer, h->alloc + h->read_size + 1);
+ if (new_buffer == NULL)
+ return MEXP_ERROR;
+ h->buffer = new_buffer;
+ h->alloc += h->read_size;
+ }
+ rs = read (h->fd, h->buffer + h->len, h->read_size);
+#if DEBUG
+ fprintf (stderr, "DEBUG: read returned %zd\n", rs);
+#endif
+ if (rs == -1) {
+ /* Annoyingly on Linux (I'm fairly sure this is a bug) if the
+ * writer closes the connection, the entire pty is destroyed,
+ * and read returns -1 / EIO. Handle that special case here.
+ */
+ if (errno == EIO)
+ return MEXP_EOF;
+ return MEXP_ERROR;
+ }
+ if (rs == 0)
+ return MEXP_EOF;
+
+ /* We read something. */
+ h->len += rs;
+ h->buffer[h->len] = '\0';
+#if DEBUG
+ fprintf (stderr, "DEBUG: read %zd bytes from pty\n", rs);
+ fprintf (stderr, "DEBUG: buffer content: %s\n", h->buffer);
+#endif
+
+ /* See if there is a full or partial match against any regexp. */
+ if (regexps) {
+ size_t i;
+ int can_clear_buffer = 1;
+
+ assert (h->buffer != NULL);
+
+ for (i = 0; regexps[i].r > 0; ++i) {
+ int options = regexps[i].options | PCRE_PARTIAL_SOFT;
+
+ r = pcre_exec (regexps[i].re, regexps[i].extra,
+ h->buffer, (int)h->len, 0,
+ options,
+ ovector, ovecsize);
+ h->pcre_error = r;
+
+ if (r >= 0) {
+ /* A full match. */
+ return regexps[i].r;
+ }
+
+ else if (r == PCRE_ERROR_NOMATCH) {
+ /* No match at all. */
+ /* (nothing here) */
+ }
+
+ else if (r == PCRE_ERROR_PARTIAL) {
+ /* Partial match. Keep the buffer and keep reading. */
+ can_clear_buffer = 0;
+ }
+
+ else {
+ /* An actual PCRE error. */
+ return MEXP_PCRE_ERROR;
+ }
+ }
+
+ /* If none of the regular expressions matched (not partially)
+ * then we can clear the buffer. This is an optimization.
+ */
+ if (can_clear_buffer)
+ clear_buffer (h);
+
+ } /* if (regexps) */
+ }
+}
+
+int
+mexp_printf (mexp_h *h, const char *fs, ...)
+{
+ va_list args;
+ char *msg;
+ int len;
+ size_t n;
+ ssize_t r;
+ char *p;
+
+ va_start (args, fs);
+ len = vasprintf (&msg, fs, args);
+ va_end (args);
+
+ if (len < 0)
+ return -1;
+
+#if DEBUG
+ fprintf (stderr, "DEBUG: writing: %s\n", msg);
+#endif
+
+ n = len;
+ p = msg;
+ while (n > 0) {
+ r = write (h->fd, p, n);
+ if (r == -1) {
+ free (msg);
+ return -1;
+ }
+ n -= r;
+ p += r;
+ }
+
+ free (msg);
+ return len;
+}
diff --git a/p2v/miniexpect.h b/p2v/miniexpect.h
new file mode 100644
index 0000000..9a374b7
--- /dev/null
+++ b/p2v/miniexpect.h
@@ -0,0 +1,81 @@
+/* miniexpect
+ * Copyright (C) 2014 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* ** NOTE ** All API documentation is in the manual page.
+ *
+ * To read the manual page from the source directory, do:
+ * man ./miniexpect.3
+ * If you have installed miniexpect, do:
+ * man 3 miniexpect
+ *
+ * The source for the manual page is miniexpect.pod.
+ */
+
+#ifndef MINIEXPECT_H_
+#define MINIEXPECT_H_
+
+#include <unistd.h>
+
+#include <pcre.h>
+
+/* This handle is created per subprocess that is spawned. */
+struct mexp_h {
+ int fd;
+ pid_t pid;
+ int timeout;
+ char *buffer;
+ size_t len;
+ size_t alloc;
+ size_t read_size;
+ int pcre_error;
+ void *user1;
+ void *user2;
+ void *user3;
+};
+typedef struct mexp_h mexp_h;
+
+/* Spawn a subprocess. */
+extern mexp_h *mexp_spawnv (const char *file, char **argv);
+extern mexp_h *mexp_spawnl (const char *file, const char *arg, ...);
+
+/* Close the handle. */
+extern int mexp_close (mexp_h *h);
+
+/* Expect. */
+struct mexp_regexp {
+ int r;
+ const pcre *re;
+ const pcre_extra *extra;
+ int options;
+};
+typedef struct mexp_regexp mexp_regexp;
+
+enum mexp_status {
+ MEXP_EOF = 0,
+ MEXP_ERROR = -1,
+ MEXP_PCRE_ERROR = -2,
+ MEXP_TIMEOUT = -3,
+};
+
+extern int mexp_expect (mexp_h *h, const mexp_regexp *regexps,
+ int *ovector, int ovecsize);
+
+extern int mexp_printf (mexp_h *h, const char *fs, ...)
+ __attribute__((format(printf,2,3)));
+
+#endif /* MINIEXPECT_H_ */
diff --git a/p2v/p2v.h b/p2v/p2v.h
new file mode 100644
index 0000000..cd5b2cf
--- /dev/null
+++ b/p2v/p2v.h
@@ -0,0 +1,64 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA.
+ */
+
+#ifndef P2V_H
+#define P2V_H
+
+/* We don't use libguestfs directly here, and we don't link to it
+ * either (in fact, we don't want libguestfs on the ISO). However
+ * we include this just so that we can use the convenience macros in
+ * guestfs-internal-frontend.h.
+ */
+#include "guestfs.h"
+#include "guestfs-internal-frontend.h"
+
+/* Ensure we don't use libguestfs. */
+#define guestfs_h DO_NOT_USE
+
+/* Configuration happens through these global variables. */
+extern int verbose;
+extern char *server;
+extern int port;
+extern char *username;
+extern char *password;
+extern int sudo;
+extern char *guestname;
+extern int vcpus;
+extern uint64_t memory;
+extern char **disks;
+extern char **removable;
+extern char **interfaces;
+
+/* gui.c */
+extern void gui_application (void);
+
+/* ssh.c */
+extern int test_connection (void);
+extern const char *get_ssh_error (void);
+
+/* virt-v2v version and features (read from remote). */
+extern int v2v_major;
+extern int v2v_minor;
+
+/* authors.c */
+extern const char *authors[];
+
+/* copying.c */
+extern const char *gplv2plus;
+
+#endif /* P2V_H */
diff --git a/p2v/ssh.c b/p2v/ssh.c
new file mode 100644
index 0000000..2640dc2
--- /dev/null
+++ b/p2v/ssh.c
@@ -0,0 +1,408 @@
+/* virt-p2v
+ * Copyright (C) 2009-2014 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
USA.
+ */
+
+/* This file handles the ssh connections to the conversion server.
+ *
+ * virt-p2v will open several connections over the lifetime of
+ * the conversion process.
+ *
+ * In 'test_connection', it will first open a connection (to check it
+ * is possible) and query virt-v2v on the server to ensure it exists,
+ * it is the right version, and so on. This connection is then
+ * closed, because in the GUI case we don't want to deal with keeping
+ * it alive in case the administrator has set up an autologout.
+ *
+ * Once we start conversion, we will open a control connection to send
+ * the libvirt configuration data and to start up virt-v2v, and we
+ * will open up one data connection per local hard disk. The data
+ * connection(s) have a reverse port forward to the local qemu-nbd
+ * server which is serving the content of that hard disk. The remote
+ * port for each data connection is assigned by ssh. See
+ * 'open_data_connection' and 'start_remote_conversion'.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <errno.h>
+#include <locale.h>
+#include <assert.h>
+#include <libintl.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "ignore-value.h"
+
+#include "miniexpect.h"
+#include "p2v.h"
+
+int v2v_major;
+int v2v_minor;
+
+static char *ssh_error;
+
+static void set_ssh_error (const char *fs, ...)
+ __attribute__((format(printf,1,2)));
+
+static void
+set_ssh_error (const char *fs, ...)
+{
+ va_list args;
+ char *msg;
+ int len;
+
+ va_start (args, fs);
+ len = vasprintf (&msg, fs, args);
+ va_end (args);
+
+ if (len < 0) {
+ perror ("vasprintf");
+ fprintf (stderr, "original error format string: %s\n", fs);
+ exit (EXIT_FAILURE);
+ }
+
+ free (ssh_error);
+ ssh_error = msg;
+}
+
+const char *
+get_ssh_error (void)
+{
+ return ssh_error;
+}
+
+static void compile_regexps (void) __attribute__((constructor));
+static void free_regexps (void) __attribute__((destructor));
+
+static pcre *password_re;
+static pcre *prompt_re;
+static pcre *version_re;
+static pcre *libguestfs_rewrite_re;
+
+static void
+compile_regexps (void)
+{
+ const char *err;
+ int offset;
+
+#define COMPILE(re,pattern,options) \
+ do { \
+ re = pcre_compile ((pattern), (options), &err, &offset, NULL);
\
+ if (re == NULL) { \
+ ignore_value (write (2, err, strlen (err))); \
+ abort (); \
+ } \
+ } while (0)
+
+ COMPILE (password_re, "assword", 0);
+ COMPILE (prompt_re, "[$#]", 0);
+ COMPILE (version_re, "virt-v2v ([1-9][0-9]*)\\.([1-9][0-9]*)\\.",
0);
+ COMPILE (libguestfs_rewrite_re, "libguestfs-rewrite", 0);
+}
+
+static void
+free_regexps (void)
+{
+ pcre_free (password_re);
+ pcre_free (prompt_re);
+ pcre_free (version_re);
+ pcre_free (libguestfs_rewrite_re);
+}
+
+/* Start ssh subprocess with the standard arguments and possibly some
+ * optional arguments. Also handles password authentication.
+ */
+static mexp_h *
+start_ssh (char **extra_args)
+{
+ size_t i, j, nr_args;
+ char port_str[64];
+ CLEANUP_FREE /* [sic] */ const char **args = NULL;
+ mexp_h *h;
+ const int ovecsize = 12;
+ int ovector[ovecsize];
+
+ /* Create the ssh argument array. */
+ nr_args = 0;
+ if (extra_args != NULL) {
+ for (i = 0; extra_args[i] != NULL; ++i)
+ nr_args++;
+ }
+
+ nr_args += 11;
+ args = malloc (sizeof (char *) * nr_args);
+ if (args == NULL) {
+ perror ("malloc");
+ exit (EXIT_FAILURE);
+ }
+ j = 0;
+ args[j++] = "ssh";
+ args[j++] = "-p"; /* Port. */
+ snprintf (port_str, sizeof port_str, "%d", port);
+ args[j++] = port_str;
+ args[j++] = "-l"; /* Username. */
+ args[j++] = username ? username : "root";
+ args[j++] = "-o"; /* Host key will always be novel. */
+ args[j++] = "StrictHostKeyChecking=no";
+ args[j++] = "-o"; /* Only use password authentication.
*/
+ args[j++] =
"PreferredAuthentications=keyboard-interactive,password";
+ if (extra_args != NULL) {
+ for (i = 0; extra_args[i] != NULL; ++i)
+ args[j++] = extra_args[i];
+ }
+ args[j++] = server; /* Conversion server. */
+ args[j++] = NULL;
+ assert (j == nr_args);
+
+ h = mexp_spawnv ("ssh", (char **) args);
+ if (h == NULL)
+ return NULL;
+
+ if (password && strlen (password) > 0) {
+ /* Wait for the password prompt. */
+ switch (mexp_expect (h,
+ (mexp_regexp[]) {
+ { 100, .re = password_re },
+ { 0 }
+ }, ovector, ovecsize)) {
+ case 100: /* Got password prompt. */
+ if (mexp_printf (h, "%s\n", password) == -1) {
+ set_ssh_error ("mexp_printf: %m");
+ mexp_close (h);
+ return NULL;
+ }
+ break;
+
+ case MEXP_EOF:
+ mexp_close (h);
+ set_ssh_error ("unexpected end of file waiting for password
prompt");
+ return NULL;
+
+ case MEXP_TIMEOUT:
+ mexp_close (h);
+ set_ssh_error ("timeout waiting for password prompt");
+ return NULL;
+
+ case MEXP_ERROR:
+ set_ssh_error ("mexp_expect: %m");
+ mexp_close (h);
+ return NULL;
+
+ case MEXP_PCRE_ERROR:
+ set_ssh_error ("PCRE error: %d\n", h->pcre_error);
+ mexp_close (h);
+ return NULL;
+ }
+ }
+
+ /* Wait for the prompt. */
+ switch (mexp_expect (h,
+ (mexp_regexp[]) {
+ { 100, .re = password_re },
+ { 101, .re = prompt_re },
+ { 0 }
+ }, ovector, ovecsize)) {
+ case 100: /* Got password prompt unexpectedly. */
+ if (mexp_printf (h, "%s\n", password) == -1) {
+ mexp_close (h);
+ set_ssh_error ("unexpected password prompt: probably the password
supplied is wrong");
+ return NULL;
+ }
+ break;
+
+ case 101: /* Got prompt. */
+ break;
+
+ case MEXP_EOF:
+ mexp_close (h);
+ set_ssh_error ("unexpected end of file waiting for command
prompt");
+ return NULL;
+
+ case MEXP_TIMEOUT:
+ mexp_close (h);
+ set_ssh_error ("timeout waiting for command prompt");
+ return NULL;
+
+ case MEXP_ERROR:
+ set_ssh_error ("mexp_expect: %m");
+ mexp_close (h);
+ return NULL;
+
+ case MEXP_PCRE_ERROR:
+ set_ssh_error ("PCRE error: %d\n", h->pcre_error);
+ mexp_close (h);
+ return NULL;
+ }
+
+ return h;
+}
+
+#pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn" /* WTF?
*/
+int
+test_connection (void)
+{
+ mexp_h *h;
+ CLEANUP_FREE char *major_str = NULL, *minor_str = NULL;
+ int feature_libguestfs_rewrite = 0;
+ int status;
+ const int ovecsize = 12;
+ int ovector[ovecsize];
+
+ h = start_ssh (NULL);
+ if (h == NULL)
+ return -1;
+
+ /* Send 'virt-v2v -V' command and hope we get back a version string.
*/
+ if (mexp_printf (h, "%svirt-v2v -V\n", sudo ? "sudo " :
"") == -1) {
+ set_ssh_error ("mexp_printf: %m");
+ mexp_close (h);
+ return -1;
+ }
+
+ switch (mexp_expect (h,
+ (mexp_regexp[]) {
+ { 100, .re = version_re },
+ { 101, .re = prompt_re },
+ { 0 }
+ }, ovector, ovecsize)) {
+ case 100: /* Got version string. */
+ major_str = strndup (&h->buffer[ovector[2]], ovector[3]-ovector[2]);
+ minor_str = strndup (&h->buffer[ovector[4]], ovector[5]-ovector[4]);
+ sscanf (major_str, "%d", &v2v_major);
+ sscanf (minor_str, "%d", &v2v_minor);
+ fprintf (stderr, "%s: remote virt-v2v version: %d.%d\n",
+ program_name, v2v_major, v2v_minor);
+ if (v2v_major < 1 || v2v_major > 1) {
+ mexp_close (h);
+ set_ssh_error ("invalid version major (%d)", v2v_major);
+ return -1;
+ }
+ break;
+
+ case 101: /* Got the prompt, but no version string. */
+ mexp_close (h);
+ set_ssh_error ("virt-v2v is not installed on the conversion server,
"
+ "or it might be a too old version");
+ return -1;
+
+ case MEXP_EOF:
+ mexp_close (h);
+ set_ssh_error ("unexpected end of file waiting virt-v2v -V
output");
+ return -1;
+
+ case MEXP_TIMEOUT:
+ mexp_close (h);
+ set_ssh_error ("timeout waiting for virt-v2v -V output");
+ return -1;
+
+ case MEXP_ERROR:
+ set_ssh_error ("mexp_expect: %m");
+ mexp_close (h);
+ return -1;
+
+ case MEXP_PCRE_ERROR:
+ set_ssh_error ("PCRE error: %d\n", h->pcre_error);
+ mexp_close (h);
+ return -1;
+ }
+
+ /* Get virt-v2v features. See: v2v/cmdline.ml */
+ if (mexp_printf (h, "%svirt-v2v --machine-readable\n",
+ sudo ? "sudo " : "") == -1) {
+ set_ssh_error ("mexp_printf: %m");
+ mexp_close (h);
+ return -1;
+ }
+
+ switch (mexp_expect (h,
+ (mexp_regexp[]) {
+ { 100, .re = libguestfs_rewrite_re },
+ { 0 }
+ }, ovector, ovecsize)) {
+ case 100: /* Got feature: libguestfs-rewrite. */
+ feature_libguestfs_rewrite = 1;
+ break;
+
+ case MEXP_EOF:
+ mexp_close (h);
+ set_ssh_error ("unexpected end of file waiting virt-v2v
--machine-readable output");
+ return -1;
+
+ case MEXP_TIMEOUT:
+ mexp_close (h);
+ set_ssh_error ("timeout waiting virt-v2v --machine-readable
output");
+ return -1;
+
+ case MEXP_ERROR:
+ set_ssh_error ("mexp_expect: %m");
+ mexp_close (h);
+ return -1;
+
+ case MEXP_PCRE_ERROR:
+ set_ssh_error ("PCRE error: %d\n", h->pcre_error);
+ mexp_close (h);
+ return -1;
+ }
+
+ if (!feature_libguestfs_rewrite) {
+ mexp_close (h);
+ set_ssh_error ("invalid output of virt-v2v --machine-readable
command");
+ return -1;
+ }
+
+ /* Test finished, shut down ssh. */
+ if (mexp_printf (h, "exit\n") == -1) {
+ set_ssh_error ("mexp_printf: %m");
+ mexp_close (h);
+ return -1;
+ }
+
+ switch (mexp_expect (h, NULL, NULL, 0)) {
+ case MEXP_EOF:
+ break;
+
+ case MEXP_TIMEOUT:
+ mexp_close (h);
+ set_ssh_error ("timeout waiting for end of ssh session");
+ return -1;
+
+ case MEXP_ERROR:
+ set_ssh_error ("mexp_expect: %m");
+ mexp_close (h);
+ return -1;
+
+ case MEXP_PCRE_ERROR:
+ set_ssh_error ("PCRE error: %d\n", h->pcre_error);
+ mexp_close (h);
+ return -1;
+ }
+
+ status = mexp_close (h);
+ if (!((WIFEXITED (status) && WEXITSTATUS (status) == 0)
+ || (WIFSIGNALED (status) && WTERMSIG (status) == SIGHUP))) {
+ set_ssh_error ("unexpected close status from ssh subprocess
(%d)", status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/p2v/virt-p2v.pod b/p2v/virt-p2v.pod
new file mode 100644
index 0000000..0837abd
--- /dev/null
+++ b/p2v/virt-p2v.pod
@@ -0,0 +1,219 @@
+=head1 NAME
+
+virt-p2v - Convert a physical machine to use KVM
+
+=head1 SYNOPSIS
+
+ virt-p2v
+
+ virt-p2v.iso
+
+=head1 DESCRIPTION
+
+Virt-p2v converts a physical machine to run virtualized on KVM,
+managed by libvirt or Red Hat Enterprise Virtualisation (RHEV) version
+2.2 or later.
+
+Normally you don't run the virt-p2v program directly. Instead you
+have to boot the physical machine using the bootable CD-ROM, ISO or
+PXE image. This bootable image contains the virt-p2v binary and runs
+it automatically. This manual page documents both the binary and the
+bootable image.
+
+=head1 NETWORK SETUP
+
+Virt-p2v runs on the physical machine which you want to convert. It
+has to talk to another server called the "conversion server" which
+must have L<virt-v2v(1)> installed on it. It always talks to the
+conversion server over SSH:
+
+ +-----------+ +-------------+
+ | virt-p2v | | virt-v2v |
+ | (physical | ssh connection | (conversion |
+ | server) -----------------> server) |
+ +-----------+ +-------------+
+
+The virt-v2v program on the conversion server does the actual
+conversion (physical to virtual, and virtual to virtual conversions
+are sufficiently similar that we use the same program to do both).
+
+The SSH connection is always initiated from the physical server. All
+data is transferred over the SSH connection. In terms of firewall and
+network configuration, you only need to ensure that the physical
+server has access to a port (usually TCP port 22) on the conversion
+server. (Note that the physical machine may reconnect several times
+during the conversion process.)
+
+The conversion server does not need to be a physical machine. It
+could be a virtual machine, as long as it has sufficient memory and
+disk space to do the conversion, and as long as the physical machine
+can connect directly to its SSH port.
+
+Because all of the data on the physical server's hard drive(s) has to
+be copied over the network, the speed of conversion is largely
+determined by the speed of the network between the two machines.
+
+=head1 GUI INTERACTIVE CONFIGURATION
+
+When you start virt-p2v, you'll see a graphical configuration dialog
+that walks you through connection to the conversion server, asks for
+the password, which local hard disks you want to convert, and other
+things like the name of the guest to create and the number of virtual
+CPUs to give it.
+
+=head1 KERNEL COMMAND LINE CONFIGURATION
+
+If you don't want to configure things using the graphical UI, an
+alternative is to configure through the kernel command line. This is
+especially convenient if you are converting a lot of physical machines
+which are booted using PXE.
+
+Where exactly you set command line arguments depends on your PXE
+implementation, but for pxelinux you put them in the C<APPEND> field
+in the C<pxelinux.cfg> file. For example:
+
+ DEFAULT p2v
+ TIMEOUT 20
+ PROMPT 0
+ LABEL p2v
+ KERNEL virt-p2v-vmlinuz
+ APPEND initrd=virt-p2v-initrd p2v.server=conv.example.com
p2v.password=secret
+
+You have to set some or all of the following command line arguments:
+
+=over 4
+
+=item B<p2v.server=SERVER>
+
+The name or IP address of the conversion server.
+
+This is always required if you are using the kernel configuration
+method. If virt-p2v does not find this on the kernel command line
+then it switches to the GUI (interactive) configuration method.
+
+=item B<p2v.port=NN>
+
+The SSH port number on the conversion server (default: C<22>).
+
+=item B<p2v.username=USERNAME>
+
+The SSH username that we log in as on the conversion server
+(default: C<root>).
+
+=item B<p2v.password=PASSWORD>
+
+The SSH password that we use to log in to the conversion server.
+
+The default is to try with no password. If this fails then virt-p2v
+will ask the user to type the password (probably several times during
+conversion).
+
+Note that virt-p2v does not support authentication using key
+distribution at this time.
+
+=item B<p2v.sudo>
+
+Use C<p2v.sudo> to tell virt-p2v to use L<sudo(8)> to gain root
+privileges on the conversion server after logging in as a non-root
+user (default: do not use sudo).
+
+=item B<p2v.name=GUESTNAME>
+
+The name of the guest that is created. The default is to try to
+derive a name from the physical machine's hostname (if possible) else
+use a randomly generated name.
+
+=item B<p2v.vcpus=NN>
+
+The number of virtual CPUs to give to the guest. The default is to
+use the same as the number of physical CPUs.
+
+=item B<p2v.memory=NN(M|G)>
+
+The size of the guest memory. You can specify this in megabytes or
+gigabytes by using (eg) C<p2v.memory=1024M> or C<p2v.memory=1G>.
The
+default is to use the same amount of RAM as on the physical machine.
+
+=item B<p2v.debug>
+
+Use this to enable full debugging of virt-v2v.
+
+If asked to diagnose a problem with virt-p2v, you should add
+C<p2v.debug> to the kernel command line, and examine the log file
+which is left in C</tmp> on the conversion server.
+
+=item B<p2v.disks=sdX,sdY,..>
+
+A list of physical hard disks to convert, for example:
+
+ p2v.disks=sda,sdc
+
+The default is to convert all local hard disks that are found.
+
+=item B<p2v.removable=srX,srY,..>
+
+A list of removable media to convert. The default is to create
+virtual removable devices for every physical removable device found.
+Note that the content of removable media is never copied over.
+
+=item B<p2v.interfaces=em1,..>
+
+A list of network interfaces to convert. The default is to create
+virtual network interfaces for every physical network interface found.
+
+=item B<ip=dhcp>
+
+Use DHCP for configuring the network interface (this is the default).
+
+=begin comment
+
+=item B<ip=ADDR:GATEWAY:NETMASK>
+
+Set up a static IPv4 network configuration.
+
+=end comment
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display help.
+
+=item B<--cmdline=CMDLINE>
+
+This is used for debugging. Instead of parsing the kernel command line
+from C</proc/cmdline>, parse the string parameter C<CMDLINE>.
+
+=item B<-v>
+
+=item B<--verbose>
+
+Enable debugging (on the conversion server).
+
+=item B<-V>
+
+=item B<--version>
+
+Display version number and exit.
+
+=back
+
+=head1 SEE ALSO
+
+L<virt-v2v(1)>,
+L<qemu-nbd(1)>,
+L<http://libguestfs.org/>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones L<http://people.redhat.com/~rjones/>
+
+Matthew Booth
+
+=head1 COPYRIGHT
+
+Copyright (C) 2009-2014 Red Hat Inc.
diff --git a/po/POTFILES b/po/POTFILES
index b481157..00f68ff 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -247,6 +247,12 @@ mllib/tty-c.c
mllib/uri-c.c
ocaml/guestfs-c-actions.c
ocaml/guestfs-c.c
+p2v/authors.c
+p2v/copying.c
+p2v/gui.c
+p2v/main.c
+p2v/miniexpect.c
+p2v/ssh.c
perl/Guestfs.c
perl/bindtests.pl
perl/lib/Sys/Guestfs.pm
diff --git a/src/guestfs.pod b/src/guestfs.pod
index f634442..cbee273 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -4346,6 +4346,10 @@ L<virt-make-fs(1)> command and documentation.
Various libraries and common code used by L<virt-resize(1)> and
the other tools which are written in OCaml.
+=item C<p2v>
+
+L<virt-p2v(1)> command and documentation.
+
=item C<po>
Translations of simple gettext strings.
@@ -4746,6 +4750,7 @@ L<virt-list-filesystems(1)>,
L<virt-list-partitions(1)>,
L<virt-ls(1)>,
L<virt-make-fs(1)>,
+L<virt-p2v(1)>,
L<virt-rescue(1)>,
L<virt-resize(1)>,
L<virt-sparsify(1)>,
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
index 138e73b..8faf20b 100644
--- a/v2v/virt-v2v.pod
+++ b/v2v/virt-v2v.pod
@@ -18,8 +18,8 @@ managed by libvirt or Red Hat Enterprise Virtualisation (RHEV)
version
2.2 or later. It can currently convert Red Hat Enterprise Linux and
Windows guests running on Xen and VMware ESX.
-There is also a companion front-end called "virt-p2v" which comes as
an
-ISO or CD image that can be booted on physical machines.
+There is also a companion front-end called L<virt-p2v(1)> which comes
+as an ISO or CD image that can be booted on physical machines.
=head1 OPTIONS
@@ -283,6 +283,7 @@ For other environment variables, see
L<guestfs(3)/ENVIRONMENT VARIABLES>.
=head1 SEE ALSO
+L<virt-p2v(1)>,
L<virt-df(1)>,
L<virt-filesystems(1)>,
L<guestfs(3)>,
--
1.9.0