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.
This commit is contained in:
Richard W.M. Jones
2014-04-01 14:41:30 +01:00
parent 40d9609b29
commit 0131d6f666
32 changed files with 3330 additions and 3 deletions

6
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

@@ -320,3 +320,5 @@ src/test-utils.c
src/tmpdirs.c
src/utils.c
test-tool/test-tool.c
v2v/utils-c.c
v2v/xml-c.c

View File

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

View File

@@ -4420,6 +4420,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>
@@ -4773,6 +4777,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)>,

163
v2v/Makefile.am Normal file
View File

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

23
v2v/README Normal file
View File

@@ -0,0 +1,23 @@
Missing features compared to Perl version:
- 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.

197
v2v/cmdline.ml Normal file
View File

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

236
v2v/convert_linux_common.ml Normal file
View File

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

View File

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

View File

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

View File

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

330
v2v/convert_linux_grub.ml Normal file
View File

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

View File

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

22
v2v/convert_windows.ml Normal file
View File

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

19
v2v/convert_windows.mli Normal file
View File

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

22
v2v/link.sh.in Normal file
View File

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

149
v2v/source_libvirt.ml Normal file
View File

@@ -0,0 +1,149 @@
(* 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 format =
let format = xpath_to_string "driver/@type" "" in
if format <> "" then Some format else None in
(* The <disk type='...'> attribute may be 'block', 'file' or
* 'network'. We ignore any other types.
*)
match xpath_to_string "@type" "" with
| "block" ->
let path = xpath_to_string "source/@dev" "" in
if path <> "" then disks := (path, format) :: !disks
| "file" ->
let path = xpath_to_string "source/@file" "" in
if path <> "" then disks := (path, format) :: !disks
| "network" ->
(* We only handle <source protocol="nbd"> here, and that is
* intended only for virt-p2v. Any other network disk is
* currently ignored.
*)
(match xpath_to_string "source/@protocol" "" with
| "nbd" ->
let host = xpath_to_string "source/host/@name" "" in
let port = xpath_to_int "source/host/@port" 0 in
if host <> "" && port > 0 then (
(* Generate a qemu nbd URL.
* XXX Quoting, although it's not needed for virt-p2v.
*)
let path = sprintf "nbd:%s:%d" host port in
disks := (path, format) :: !disks
)
| "" -> ()
| protocol ->
eprintf (f_"%s: warning: network <disk> with <source protocol='%s'> was ignored")
prog protocol
)
| disk_type ->
eprintf (f_"%s: warning: <disk type='%s'> was ignored") prog disk_type
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

27
v2v/source_libvirt.mli Normal file
View File

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

86
v2v/target_local.ml Normal file
View File

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

21
v2v/target_local.mli Normal file
View File

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

84
v2v/types.ml Normal file
View File

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

77
v2v/types.mli Normal file
View File

@@ -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? *)

43
v2v/utils-c.c Normal file
View File

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

44
v2v/utils.ml Normal file
View File

@@ -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 "&" "&amp;" in
let str = Common_utils.replace_str str "'" "&apos;" in
let str = Common_utils.replace_str str "<" "&lt;" in
let str = Common_utils.replace_str str ">" "&gt;" in
str
external drive_name : int -> string = "v2v_utils_drive_name"

353
v2v/v2v.ml Normal file
View File

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

306
v2v/virt-v2v.pod Normal file
View File

@@ -0,0 +1,306 @@
=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.
This manual page documents the rewritten virt-v2v included in
libguestfs E<ge> 1.28.
=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
Mike Latimer
=head1 COPYRIGHT
Copyright (C) 2009-2014 Red Hat Inc.

244
v2v/xml-c.c Normal file
View File

@@ -0,0 +1,244 @@
/* 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;
/* For security reasons, call xmlReadMemory (not xmlParseMemory) and
* pass XML_PARSE_NONET. See commit 845daded5fddc70f.
*/
doc = xmlReadMemory (String_val (xmlv), caml_string_length (xmlv),
NULL, NULL, XML_PARSE_NONET);
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");
}
}

50
v2v/xml.ml Normal file
View File

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

57
v2v/xml.mli Normal file
View File

@@ -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 (for security reasons it actually calls xmlReadMemory) *)
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 *)