virt-v2v/0054-Add-new-virt-v2v-open-tool.patch
Richard W.M. Jones de0be4d153 New tool: virt-v2v-open
resolves: RHEL-88985
2025-05-07 13:15:05 +01:00

1017 lines
30 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From 99e27f9670f7e0e6416bf76074ebd84bd3315c6b Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Wed, 30 Apr 2025 12:47:30 +0100
Subject: [PATCH] Add new virt-v2v-open tool
This can be used to open the virt-v2v input method and run an almost
arbitrary program on it. For example you can run virt-inspector to
inspect the input (eg. to find root devices and other information for
the virt-v2v --root option), or guestfish.
Currently this only opens the input read-only, although read-write
access might be added in future.
Suggested-by: Martin Necas
Fixes: https://issues.redhat.com/browse/RHEL-88985
(cherry picked from commit cb7d1cd1cf1e6e29e9945227aff3162e3cc9fb6a)
---
.gitignore | 3 +
Makefile.am | 4 +-
configure.ac | 1 +
docs/Makefile.am | 15 ++
docs/virt-v2v-open.pod | 183 +++++++++++++++++++++
docs/virt-v2v.pod | 9 +-
open/Makefile.am | 126 +++++++++++++++
open/dummy.c | 2 +
open/open.ml | 298 +++++++++++++++++++++++++++++++++++
open/open.mli | 19 +++
run.in | 1 +
tests/Makefile.am | 4 +
tests/test-open-encrypted.sh | 56 +++++++
tests/test-open.sh | 77 +++++++++
14 files changed, 795 insertions(+), 3 deletions(-)
create mode 100644 docs/virt-v2v-open.pod
create mode 100644 open/Makefile.am
create mode 100644 open/dummy.c
create mode 100644 open/open.ml
create mode 100644 open/open.mli
create mode 100755 tests/test-open-encrypted.sh
create mode 100755 tests/test-open.sh
diff --git a/.gitignore b/.gitignore
index 9dcd4611..f86caaee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,6 +52,7 @@ Makefile.in
/docs/virt-v2v-input-vmware.1
/docs/virt-v2v-input-xen.1
/docs/virt-v2v-inspector.1
+/docs/virt-v2v-open.1
/docs/virt-v2v-output-local.1
/docs/virt-v2v-output-openstack.1
/docs/virt-v2v-output-rhv.1
@@ -84,6 +85,8 @@ Makefile.in
/missing
/ocaml-dep.sh
/ocaml-link.sh
+/open/.depend
+/open/virt-v2v-open
/output/.depend
/output/oUnit-anon.cache
/output/output_rhv_upload_*_source.ml
diff --git a/Makefile.am b/Makefile.am
index 4cc87324..dcbde1b5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -44,6 +44,7 @@ SUBDIRS += convert
SUBDIRS += v2v
SUBDIRS += inspector
SUBDIRS += in-place
+SUBDIRS += open
SUBDIRS += tests
@@ -112,7 +113,8 @@ po/POTFILES: configure.ac
po/POTFILES-ml: configure.ac
rm -f $@ $@-t
cd $(srcdir); \
- find common/ml* lib in-place input inspector output v2v -name '*.ml' | \
+ find common/ml* lib in-place input inspector open output v2v \
+ -name '*.ml' | \
grep -v '^common/mlprogress/' | \
grep -v '^common/mlvisit/' | \
grep -v '^lib/config.ml$$' | \
diff --git a/configure.ac b/configure.ac
index a99bcb75..e1e01742 100644
--- a/configure.ac
+++ b/configure.ac
@@ -145,6 +145,7 @@ AC_CONFIG_FILES([Makefile
inspector/Makefile
lib/Makefile
lib/config.ml
+ open/Makefile
output/Makefile
po-docs/Makefile
po-docs/ja/Makefile
diff --git a/docs/Makefile.am b/docs/Makefile.am
index d86349f4..d3547b4d 100644
--- a/docs/Makefile.am
+++ b/docs/Makefile.am
@@ -25,6 +25,7 @@ EXTRA_DIST = \
virt-v2v-input-vmware.pod \
virt-v2v-input-xen.pod \
virt-v2v-inspector.pod \
+ virt-v2v-open.pod \
virt-v2v-output-local.pod \
virt-v2v-output-openstack.pod \
virt-v2v-output-rhv.pod \
@@ -45,6 +46,7 @@ man_MANS = \
virt-v2v-input-vmware.1 \
virt-v2v-input-xen.1 \
virt-v2v-inspector.1 \
+ virt-v2v-open.1 \
virt-v2v-output-local.1 \
virt-v2v-output-openstack.1 \
virt-v2v-output-rhv.1 \
@@ -62,6 +64,7 @@ noinst_DATA = \
$(top_builddir)/website/virt-v2v-input-vmware.1.html \
$(top_builddir)/website/virt-v2v-input-xen.1.html \
$(top_builddir)/website/virt-v2v-inspector.1.html \
+ $(top_builddir)/website/virt-v2v-open.1.html \
$(top_builddir)/website/virt-v2v-output-local.1.html \
$(top_builddir)/website/virt-v2v-output-openstack.1.html \
$(top_builddir)/website/virt-v2v-output-rhv.1.html \
@@ -142,6 +145,18 @@ stamp-virt-v2v-inspector.pod: virt-v2v-inspector.pod
$<
touch $@
+virt-v2v-open.1 $(top_builddir)/website/virt-v2v-open.1.html: stamp-virt-v2v-open.pod
+
+stamp-virt-v2v-open.pod: virt-v2v-open.pod
+ $(PODWRAPPER) \
+ --man virt-v2v-open.1 \
+ --html $(top_builddir)/website/virt-v2v-open.1.html \
+ --path $(top_srcdir)/common/options \
+ --license GPLv2+ \
+ --warning safe \
+ $<
+ touch $@
+
virt-v2v-output-local.1 $(top_builddir)/website/virt-v2v-output-local.1.html: stamp-virt-v2v-output-local.pod
stamp-virt-v2v-output-local.pod: virt-v2v-output-local.pod
diff --git a/docs/virt-v2v-open.pod b/docs/virt-v2v-open.pod
new file mode 100644
index 00000000..660ac83e
--- /dev/null
+++ b/docs/virt-v2v-open.pod
@@ -0,0 +1,183 @@
+=head1 NAME
+
+virt-v2v-open - Open the virt-v2v input and run a program on it
+
+=head1 SYNOPSIS
+
+ virt-v2v-open [-i* options] guest --run 'program [--options] @@'
+
+ virt-v2v-open -i disk guest.img --run 'virt-inspector --format=raw @@'
+
+ virt-v2v-open -i disk guest.img --run 'guestfish --ro --format=raw @@ -i'
+
+=head1 DESCRIPTION
+
+Virt-v2v-open is a companion tool for L<virt-v2v(1)> and
+L<virt-v2v-inspector(1)> which can be used before conversion to open
+the input side of virt-v2v and run a program.
+
+Some uses for this include running L<virt-inspector(1)> directly on
+the source guest to find source operating systems (to use with the
+virt-v2v I<--root> option). Or running guestfish to take a look
+inside the source guest before conversion. You can follow that by
+running L<virt-v2v-inspector(1)> to estimate how much space would be
+needed to convert that guest, and if conversion of the guest is
+possible.
+
+This manual page only documents the program options, not all of the
+I<-i*> options which are the same as virt-v2v. You should read
+L<virt-v2v(1)> first.
+
+Notes:
+
+=over 4
+
+=item *
+
+The source guest is always opened read-only. You will not be able to
+make persistent changes.
+
+=item *
+
+L<virt-inspector(1)> can be used directly on local disk images.
+
+=back
+
+=head2 Selecting the input guest
+
+You can run virt-v2v-open with the same I<-i*> options as virt-v2v.
+(Don't use any I<-o*> options). This will select the guest that you
+want to open.
+
+=head2 Running the program
+
+On the command line, put the program that you want to run on the
+source guest and any other parameters that program needs into the
+I<--run> parameter. C<@@> in the parameter is substituted with
+I<-a DISK ...> for each source disk, in a way which is compatible with
+L<virt-inspector(1)> and L<guestfish(1)>.
+
+ virt-v2v-open [-i ...] guest \
+ --run 'virt-inspector --format=raw @@ > output.xml'
+
+ virt-v2v-open [-i ...] guest \
+ --run 'guestfish --ro --format=raw @@ -i'
+
+ virt-v2v-open [-i ...] guest \
+ --run 'guestfish --ro --format=raw @@ run : list-filesystems'
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display help.
+
+=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.
+
+=item B<-i> ...
+
+=item B<-ic> ...
+
+=item B<-if> ...
+
+=item B<-io> ...
+
+=item B<-ip> ...
+
+=item B<-it> ...
+
+All of the I<-i*> options supported by virt-v2v are also supported by
+virt-v2v-open.
+
+=item B<--colors>
+
+=item B<--colours>
+
+=item B<--machine-readable>
+
+=item B<--machine-readable>=format
+
+=item B<-q>
+
+=item B<--quiet>
+
+=item B<--wrap>
+
+These options work in the same way as the equivalent virt-v2v options.
+
+=item B<--run> COMMAND
+
+The command that you want to run on the source guest. C<@@> in the
+command is substituted with a list of I<-a DISK> options that is
+compatible with virt-inspector and guestfish.
+
+=back
+
+=head1 FILES
+
+Files used are the same as for virt-v2v. See L<virt-v2v(1)/FILES>.
+
+=head1 ENVIRONMENT VARIABLES
+
+Environment variables used are the same as for virt-v2v. See
+L<virt-v2v(1)/ENVIRONMENT VARIABLES>.
+
+=head1 SEE ALSO
+
+L<virt-v2v(1)>,
+L<virt-p2v(1)>,
+L<virt-inspector(1)>,
+L<virt-v2v-inspector(1)>,
+L<guestfs(3)>,
+L<guestfish(1)>,
+L<qemu-img(1)>,
+L<nbdkit(1)>,
+L<http://libguestfs.org/>.
+
+=head1 AUTHORS
+
+Matthew Booth
+
+Cédric Bosdonnat
+
+Laszlo Ersek
+
+Tomáš Golembiovský
+
+Shahar Havivi
+
+Richard W.M. Jones
+
+Roman Kagan
+
+Mike Latimer
+
+Nir Soffer
+
+Pino Toscano
+
+Xiaodai Wang
+
+Ming Xie
+
+Tingting Zheng
+
+=head1 COPYRIGHT
+
+Copyright (C) 2009-2025 Red Hat Inc.
diff --git a/docs/virt-v2v.pod b/docs/virt-v2v.pod
index d65e13ed..b946758c 100644
--- a/docs/virt-v2v.pod
+++ b/docs/virt-v2v.pod
@@ -784,6 +784,9 @@ Windows Recovery Console, certain attached DVD drives, and bugs in
libguestfs inspection heuristics, can make a guest look like a
multi-boot operating system.
+Either L<virt-inspector(1)> or L<virt-v2v-open(1)> can be used to list
+the roots for a dual-boot or multi-boot VM.
+
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.
@@ -798,8 +801,8 @@ 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, and it may not choose the default option from the guest
bootloader. For predictable results it is better to use
-L<virt-v2v-inspector(1)> to inspect the guest and then specify which
-root you want to convert.
+L<virt-v2v-open(1)> and L<virt-inspector(1)> to inspect the guest and
+then specify which root you want to convert.
=item B<--root> /dev/sdX
@@ -1670,9 +1673,11 @@ L<https://rwmj.wordpress.com/2015/09/18/importing-kvm-guests-to-ovirt-or-rhev/#c
L<virt-p2v(1)>,
L<virt-v2v-inspector(1)>,
+L<virt-v2v-open(1)>,
L<virt-customize(1)>,
L<virt-df(1)>,
L<virt-filesystems(1)>,
+L<virt-inspector(1)>,
L<virt-sparsify(1)>,
L<virt-sysprep(1)>,
L<guestfs(3)>,
diff --git a/open/Makefile.am b/open/Makefile.am
new file mode 100644
index 00000000..e904eec9
--- /dev/null
+++ b/open/Makefile.am
@@ -0,0 +1,126 @@
+# libguestfs virt-v2v-open tool
+# Copyright (C) 2009-2025 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)
+
+SOURCES_MLI = \
+ open.mli
+
+SOURCES_ML = \
+ open.ml
+
+SOURCES_C = \
+ dummy.c
+
+bin_PROGRAMS = virt-v2v-open
+
+virt_v2v_open_SOURCES = $(SOURCES_C)
+virt_v2v_open_CPPFLAGS = \
+ -DCAML_NAME_SPACE \
+ -I. \
+ -I$(top_builddir) \
+ -I$(shell $(OCAMLC) -where) \
+ -I$(top_srcdir)/lib
+virt_v2v_open_CFLAGS = \
+ -pthread \
+ $(WARN_CFLAGS) $(WERROR_CFLAGS) \
+ $(LIBGUESTFS_CFLAGS) \
+ $(LIBVIRT_CFLAGS)
+
+BOBJECTS = $(SOURCES_ML:.ml=.cmo)
+XOBJECTS = $(BOBJECTS:.cmo=.cmx)
+
+OCAMLPACKAGES = \
+ -package str,unix,guestfs,libvirt,nbd \
+ -I $(top_builddir)/common/utils/.libs \
+ -I $(top_builddir)/common/qemuopts/.libs \
+ -I $(top_builddir)/gnulib/lib/.libs \
+ -I $(top_builddir)/lib \
+ -I $(top_builddir)/input \
+ -I $(top_builddir)/common/mlstdutils \
+ -I $(top_builddir)/common/mlutils \
+ -I $(top_builddir)/common/mlgettext \
+ -I $(top_builddir)/common/mlpcre \
+ -I $(top_builddir)/common/mlxml \
+ -I $(top_builddir)/common/mltools \
+ -I $(top_builddir)/common/mlcustomize \
+ -I $(top_builddir)/common/mldrivers
+if HAVE_OCAML_PKG_GETTEXT
+OCAMLPACKAGES += -package gettext-stub
+endif
+
+OCAMLCLIBS = \
+ -pthread \
+ -lqemuopts \
+ $(LIBGUESTFS_LIBS) \
+ $(LIBVIRT_LIBS) \
+ $(LIBCRYPT_LIBS) \
+ $(LIBXML2_LIBS) \
+ $(JSON_C_LIBS) \
+ $(LIBOSINFO_LIBS) \
+ $(LIBINTL) \
+ $(LIBNBD_LIBS) \
+ -lgnu
+
+OCAMLFLAGS = $(OCAML_FLAGS) $(OCAML_WARN_ERROR) -ccopt '$(CFLAGS)'
+
+if !HAVE_OCAMLOPT
+OBJECTS = $(BOBJECTS)
+else
+OBJECTS = $(XOBJECTS)
+endif
+
+OCAMLLINKFLAGS = \
+ mlstdutils.$(MLARCHIVE) \
+ mlgettext.$(MLARCHIVE) \
+ mlpcre.$(MLARCHIVE) \
+ mlxml.$(MLARCHIVE) \
+ mlcutils.$(MLARCHIVE) \
+ mltools.$(MLARCHIVE) \
+ mllibvirt.$(MLARCHIVE) \
+ mlcustomize.$(MLARCHIVE) \
+ mldrivers.$(MLARCHIVE) \
+ mlv2vlib.$(MLARCHIVE) \
+ mlinput.$(MLARCHIVE) \
+ $(LINK_CUSTOM_OCAMLC_ONLY)
+
+virt_v2v_open_DEPENDENCIES = \
+ $(OBJECTS) \
+ $(top_builddir)/input/mlinput.$(MLARCHIVE) \
+ $(top_builddir)/lib/mlv2vlib.$(MLARCHIVE) \
+ $(top_srcdir)/ocaml-link.sh
+virt_v2v_open_LINK = \
+ $(top_srcdir)/ocaml-link.sh -cclib '$(OCAMLCLIBS)' -- \
+ $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) $(OCAMLPACKAGES) $(OCAMLLINKFLAGS) \
+ $(OBJECTS) -o $@
+
+# Data directory.
+
+virttoolsdatadir = $(datadir)/virt-tools
+
+# Dependencies.
+.depend: \
+ $(srcdir)/*.mli \
+ $(srcdir)/*.ml \
+ $(filter %.ml,$(BUILT_SOURCES))
+ $(top_builddir)/ocaml-dep.sh $^
+-include .depend
diff --git a/open/dummy.c b/open/dummy.c
new file mode 100644
index 00000000..ebab6198
--- /dev/null
+++ b/open/dummy.c
@@ -0,0 +1,2 @@
+/* Dummy source, to be used for OCaml-based tools with no C sources. */
+enum { foo = 1 };
diff --git a/open/open.ml b/open/open.ml
new file mode 100644
index 00000000..8a82b13e
--- /dev/null
+++ b/open/open.ml
@@ -0,0 +1,298 @@
+(* virt-v2v-open
+ * Copyright (C) 2009-2025 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 Unix
+
+open Std_utils
+open Tools_utils
+open Unix_utils
+open Common_gettext.Gettext
+open Getopt.OptionName
+
+open Types
+open Utils
+
+let template_rex = PCRE.compile "@@"
+
+module G = Guestfs
+
+let rec main () =
+ let set_string_option_once optname optref arg =
+ match !optref with
+ | Some _ ->
+ error (f_"%s option used more than once on the command line") optname
+ | None ->
+ optref := Some arg
+ in
+
+ let input_conn = ref None in
+ let input_format = ref None in
+ let input_password = ref None in
+ let input_transport = ref None in
+ let input_options = ref [] in
+ let io_query = ref false in
+ let set_input_option_compat k v =
+ List.push_back input_options (k, v)
+ in
+ let set_input_option option =
+ if option = "?" then io_query := true
+ else (
+ let k, v = String.split "=" option in
+ set_input_option_compat k v
+ )
+ in
+
+ let input_mode = ref `Not_set in
+ let set_input_mode mode =
+ if !input_mode <> `Not_set then
+ error (f_"%s option used more than once on the command line") "-i";
+ match mode with
+ | "disk" | "local" -> input_mode := `Disk
+ | "libvirt" -> input_mode := `Libvirt
+ | "libvirtxml" -> input_mode := `LibvirtXML
+ | "ova" -> input_mode := `OVA
+ | "vmx" -> input_mode := `VMX
+ | s ->
+ error (f_"unknown -i option: %s") s
+ in
+
+ let command_template = ref None in
+
+ let argspec = [
+ [ S 'i' ], Getopt.String ("disk|libvirt|libvirtxml|ova|vmx", set_input_mode),
+ s_"Set input mode (default: libvirt)";
+ [ M"ic" ], Getopt.String ("uri", set_string_option_once "-ic" input_conn),
+ s_"Libvirt URI";
+ [ M"if" ], Getopt.String ("format", set_string_option_once "-if" input_format),
+ s_"Input format";
+ [ M"io" ], Getopt.String ("option[=value]", set_input_option),
+ s_"Set option for input mode";
+ [ M"ip" ], Getopt.String ("filename", set_string_option_once "-ip" input_password),
+ s_"Use password from file to connect to input hypervisor";
+ [ M"it" ], Getopt.String ("transport", set_string_option_once "-it" input_transport),
+ s_"Input transport";
+ [ L"run" ], Getopt.String ("COMMAND", set_string_option_once "--run" command_template),
+ s_"External command to run";
+ ] in
+
+ let args = ref [] in
+ let anon_fun s = List.push_front s args in
+ let usage_msg =
+ sprintf (f_"\
+%s: open the virt-v2v input and run a program on it
+
+virt-v2v-open -i disk disk.img --run 'virt-inspector --format=raw @@'
+
+A short summary of the options is given below. For detailed help please
+read the man page virt-v2v-open(1).
+")
+ prog in
+
+ let opthandle = create_standard_options argspec ~anon_fun ~key_opts:false
+ ~machine_readable:true usage_msg in
+ Getopt.parse opthandle.getopt;
+
+ (* Print the version, easier than asking users to tell us. *)
+ debug "info: %s: %s %s (%s)"
+ prog Config.package_name Config.package_version_full
+ Config.host_cpu;
+
+ (* Print the libvirt version if debugging. *)
+ if verbose () then (
+ let major, minor, release = Libvirt_utils.libvirt_get_version () in
+ debug "info: libvirt version: %d.%d.%d" major minor release
+ );
+
+ (* Create the v2v directory to control conversion. *)
+ let v2vdir = create_v2v_directory () in
+
+ (* Dereference the arguments. *)
+ let args = List.rev !args in
+ let input_conn = !input_conn in
+ let input_mode = !input_mode in
+ let input_transport =
+ match !input_transport with
+ | None -> None
+ | Some "ssh" -> Some `SSH
+ | Some "vddk" -> Some `VDDK
+ | Some transport ->
+ error (f_"unknown input transport -it %s") transport in
+
+ let command_template =
+ match !command_template with
+ | None -> error (f_"you must supply the --run parameter")
+ | Some c -> c in
+
+ (* No arguments and machine-readable mode? Print out some facts
+ * about what this binary supports.
+ *)
+ (match args, machine_readable () with
+ | [], Some { pr } ->
+ pr "virt-v2v-open\n";
+ pr "libguestfs-rewrite\n";
+ pr "colours-option\n";
+ pr "io\n";
+ pr "input:disk\n";
+ pr "input:libvirt\n";
+ pr "input:libvirtxml\n";
+ pr "input:ova\n";
+ pr "input:vmx\n";
+ exit 0
+ | _, _ -> ()
+ );
+
+ (* Get the input module. *)
+ let (module Input_module) =
+ match input_mode with
+ | `Disk -> (module Input_disk.Disk : Input.INPUT)
+ | `LibvirtXML -> (module Input_libvirt.LibvirtXML)
+ | `OVA -> (module Input_ova.OVA)
+ | `VMX -> (module Input_vmx.VMX)
+ | `Not_set | `Libvirt ->
+ match input_conn with
+ | None -> (module Input_libvirt.Libvirt_)
+ | Some orig_uri ->
+ let { Xml.uri_server = server; uri_scheme = scheme } =
+ try Xml.parse_uri orig_uri
+ with Invalid_argument msg ->
+ error (f_"could not parse '-ic %s'. \
+ Original error message was: %s")
+ orig_uri msg in
+
+ match server, scheme, input_transport with
+ | None, _, _
+ | Some "", _, _ (* Not a remote URI. *)
+
+ | Some _, None, _ (* No scheme? *)
+ | Some _, Some "", _ ->
+ (module Input_libvirt.Libvirt_)
+
+ (* vCenter over https. *)
+ | Some server, Some ("esx"|"gsx"|"vpx"), None ->
+ (module Input_vcenter_https.VCenterHTTPS)
+
+ (* vCenter or ESXi using nbdkit vddk plugin *)
+ | Some server, Some ("esx"|"gsx"|"vpx"), Some `VDDK ->
+ (module Input_vddk.VDDK)
+
+ (* Xen over SSH *)
+ | Some server, Some "xen+ssh", _ ->
+ (module Input_xen_ssh.XenSSH)
+
+ (* Old virt-v2v also supported qemu+ssh://. However I am
+ * deliberately not supporting this in new virt-v2v. Don't
+ * use virt-v2v if a guest already runs on KVM.
+ *)
+
+ (* Unknown remote scheme. *)
+ | Some _, Some _, _ ->
+ warning (f_"no support for remote libvirt connections \
+ to '-ic %s'. The conversion may fail when it \
+ tries to read the source disks.") orig_uri;
+ (module Input_libvirt.Libvirt_) in
+
+ let input_options = {
+ Input.bandwidth = None;
+ input_conn = input_conn;
+ input_format = !input_format;
+ input_options = !input_options;
+ input_password = !input_password;
+ input_transport = input_transport;
+ (* This must always be true so that we do not modify the
+ * source. This is set to [false] by in-place mode.
+ *)
+ read_only = true;
+ } in
+
+ (* If -io ? then we want to query input options supported in this mode. *)
+ if !io_query then (
+ Input_module.query_input_options ();
+ exit 0
+ );
+
+ (* Before starting the input module, check there is sufficient
+ * free space in the temporary directory on the host.
+ *)
+ check_host_free_space ();
+
+ (* Start the input module (runs an NBD server in the background). *)
+ message (f_"Setting up the source: %s")
+ (Input_module.to_string input_options args);
+ let source = Input_module.setup v2vdir input_options args in
+
+ message (f_"Running external command");
+
+ (* We're not really doing a conversion here, but write the 'convert'
+ * file around this to tune the input module correctly.
+ *)
+ with_open_out (v2vdir // "convert") (fun _ -> ());
+
+ (* Get the list of input sockets (NBD endpoints), formatted as
+ * a shell-quoted list of [-a] options.
+ *)
+ let add_params =
+ List.map (
+ fun { s_disk_id = i } ->
+ let socket = sprintf "nbd+unix://?socket=%s/in%d" v2vdir i in
+ sprintf "-a %s" (quote socket)
+ ) source.s_disks
+ |> String.concat " " in
+
+ (* Run external program. *)
+ debug "command template: %S" command_template;
+ let cmd = PCRE.replace template_rex add_params command_template in
+ let r = shell_command cmd in
+ if r <> 0 then exit r;
+
+ unlink (v2vdir // "convert");
+
+ (* Debug the v2vdir. *)
+ if verbose () then (
+ let cmd = sprintf "ls -alZ %s 1>&2" (quote v2vdir) in
+ ignore (Sys.command cmd)
+ );
+
+ message (f_"Finishing off");
+ (* As the last thing, write a file indicating success before
+ * we exit (so before we kill the helpers). The helpers may
+ * use the presence or absence of the file to determine if
+ * on-success or on-fail cleanup is required.
+ *)
+ with_open_out (v2vdir // "done") (fun _ -> ())
+
+(* Some input modules use large_tmpdir to unpack OVAs or store qcow2
+ * overlays and some output modules use it to store temporary files.
+ * In addition the 500 MB guestfs appliance may be created there.
+ * (RHBZ#1316479, RHBZ#2051394)
+ *)
+and check_host_free_space () =
+ let free_space = StatVFS.free_space (StatVFS.statvfs large_tmpdir) in
+ debug "check_host_free_space: large_tmpdir=%s free_space=%Ld"
+ large_tmpdir free_space;
+ if free_space < 1_073_741_824L then
+ error (f_"insufficient free space in the conversion server \
+ temporary directory %s (%s).\n\nEither free up space \
+ in that directory, or set the LIBGUESTFS_CACHEDIR \
+ environment variable to point to another directory \
+ with more than 1GB of free space.\n\nSee also the \
+ virt-v2v(1) manual, section \
+ \"Minimum free space check in the host\".")
+ large_tmpdir (human_size free_space)
+
+let () = run_main_and_handle_errors main
diff --git a/open/open.mli b/open/open.mli
new file mode 100644
index 00000000..9fd556f6
--- /dev/null
+++ b/open/open.mli
@@ -0,0 +1,19 @@
+(* virt-v2v-open
+ * Copyright (C) 2009-2025 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.
+ *)
+
+(* Nothing is exported. *)
diff --git a/run.in b/run.in
index c75e4e0f..93a0f20f 100755
--- a/run.in
+++ b/run.in
@@ -71,6 +71,7 @@ chcon --reference=/tmp "$b/tmp" 2>/dev/null ||:
prepend PATH "$b/v2v"
prepend PATH "$b/in-place"
prepend PATH "$b/inspector"
+prepend PATH "$b/open"
export PATH
# This is a cheap way to find some use-after-free and uninitialized
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 4d46daf9..97393d02 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -105,6 +105,8 @@ TESTS = \
test-oa-option-raw.sh \
test-of-option.sh \
test-on-option.sh \
+ test-open.sh \
+ test-open-encrypted.sh \
test-print-source.sh \
test-rhbz1232192.sh \
test-sound.sh \
@@ -278,6 +280,8 @@ EXTRA_DIST += \
test-oa-option-raw.sh \
test-of-option.sh \
test-on-option.sh \
+ test-open.sh \
+ test-open-encrypted.sh \
test-print-source.expected \
test-print-source.sh \
test-rhbz1232192.sh \
diff --git a/tests/test-open-encrypted.sh b/tests/test-open-encrypted.sh
new file mode 100755
index 00000000..1ff14c31
--- /dev/null
+++ b/tests/test-open-encrypted.sh
@@ -0,0 +1,56 @@
+#!/bin/bash -
+# libguestfs virt-v2v test script
+# Copyright (C) 2014-2025 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.
+
+# Test virt-v2v-show-roots with an encrypted guest.
+
+unset CDPATH
+export LANG=C
+set -e
+
+source ./functions.sh
+set -e
+set -x
+
+skip_if_skipped
+f=../test-data/phony-guests/fedora-luks-on-lvm.img
+requires test -f $f
+
+requires virt-inspector --version
+
+d=$PWD/test-open-encrypted.d
+rm -rf $d
+cleanup_fn rm -r $d
+mkdir $d
+
+out="$d/out"
+
+keys="--key /dev/Volume-Group/Root:key:FEDORA-Root \
+ --key /dev/Volume-Group/Logical-Volume-1:key:FEDORA-LV1 \
+ --key /dev/Volume-Group/Logical-Volume-2:key:FEDORA-LV2 \
+ --key /dev/Volume-Group/Logical-Volume-3:key:FEDORA-LV3"
+
+$VG virt-v2v-open --debug-gc -i disk $f \
+ --run "virt-inspector --format=raw @@ $keys > $out"
+cat $out
+
+# Expect certain elements to be present.
+grep '^<operatingsystems>' $out
+grep '<operatingsystem>' $out
+grep '<root>/dev/mapper/luks-' $out
+grep '<distro>fedora</distro>' $out
+grep '<osinfo>fedora14</osinfo>' $out
diff --git a/tests/test-open.sh b/tests/test-open.sh
new file mode 100755
index 00000000..9eddfbc7
--- /dev/null
+++ b/tests/test-open.sh
@@ -0,0 +1,77 @@
+#!/bin/bash -
+# libguestfs virt-v2v test script
+# Copyright (C) 2014-2025 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.
+
+# Test virt-v2v-open + virt-inspector.
+
+unset CDPATH
+export LANG=C
+set -e
+
+source ./functions.sh
+set -e
+set -x
+
+skip_if_skipped
+img="$abs_top_builddir/test-data/phony-guests/windows.img"
+requires test -s $img
+
+requires virt-inspector --version
+
+export VIRT_TOOLS_DATA_DIR="$srcdir/../test-data/fake-virt-tools"
+export VIRTIO_WIN="$srcdir/../test-data/fake-virtio-win/drivers"
+
+d=$PWD/test-open.d
+rm -rf $d
+cleanup_fn rm -r $d
+mkdir $d
+
+out="$d/out"
+
+libvirt_xml="$d/test.xml"
+rm -f $libvirt_xml
+n=windows
+cat > $libvirt_xml <<EOF
+<node>
+ <domain type='test'>
+ <name>$n</name>
+ <memory>1048576</memory>
+ <os>
+ <type>hvm</type>
+ <boot dev='hd'/>
+ </os>
+ <devices>
+ <disk type='file' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <source file='$img'/>
+ <target dev='vda' bus='virtio'/>
+ </disk>
+ </devices>
+ </domain>
+</node>
+EOF
+
+$VG virt-v2v-open --debug-gc -i libvirt -ic "test://$libvirt_xml" $n \
+ --run "virt-inspector --format=raw @@ > $out"
+cat $out
+
+# Expect certain elements to be present.
+grep '^<operatingsystems>' $out
+grep '<operatingsystem>' $out
+grep '<root>/dev/sda2</root>' $out
+grep '<distro>windows</distro>' $out
+grep '<osinfo>win2k22</osinfo>' $out