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