Blame SOURCES/0030-v2v-Implement-i-vmx-to-read-VMware-vmx-files-directl.patch

a30de4
From e43fe85f28ba6fb184e6894db019668f0515c38d Mon Sep 17 00:00:00 2001
90a56e
From: "Richard W.M. Jones" <rjones@redhat.com>
90a56e
Date: Mon, 10 Apr 2017 15:24:26 +0100
90a56e
Subject: [PATCH] v2v: Implement -i vmx to read VMware vmx files directly
90a56e
 (RHBZ#1441197).
90a56e
90a56e
This is a mostly complete implementation of a VMX parser and input
90a56e
class for virt-v2v.  It parses the name, memory size, CPU topology,
90a56e
firmware, video, sound, hard disks, removable disks and network
90a56e
interfaces from the VMX file.  It only omits support for floppies and
90a56e
SCSI CD-ROMs.
90a56e
90a56e
The input class is split into two major parts: a generic VMX file
90a56e
parser (Parse_vmx), and the Input_vmx module which translates the VMX
90a56e
tree into the source device model.
90a56e
90a56e
This also contains tests.  There are simple unit tests of the
90a56e
Parse_vmx module, and also some more complete parsing tests taken from
90a56e
real guests.
90a56e
90a56e
(cherry picked from commit ca40078cdda9167d4658ddfe24c828c7ee76be37)
90a56e
---
90a56e
 v2v/Makefile.am               |  15 ++
90a56e
 v2v/cmdline.ml                |  12 +-
90a56e
 v2v/input_vmx.ml              | 349 ++++++++++++++++++++++++++++++++++++++
90a56e
 v2v/input_vmx.mli             |  22 +++
90a56e
 v2v/name_from_disk.ml         |   2 +-
90a56e
 v2v/parse_vmx.ml              | 381 ++++++++++++++++++++++++++++++++++++++++++
90a56e
 v2v/parse_vmx.mli             |  89 ++++++++++
a30de4
 v2v/test-v2v-i-vmx-1.expected |  39 +++++
90a56e
 v2v/test-v2v-i-vmx-1.vmx      | 172 +++++++++++++++++++
a30de4
 v2v/test-v2v-i-vmx-2.expected |  19 +++
90a56e
 v2v/test-v2v-i-vmx-2.vmx      |  84 ++++++++++
a30de4
 v2v/test-v2v-i-vmx-3.expected |  19 +++
90a56e
 v2v/test-v2v-i-vmx-3.vmx      |  91 ++++++++++
a30de4
 v2v/test-v2v-i-vmx-4.expected |  19 +++
90a56e
 v2v/test-v2v-i-vmx-4.vmx      |  88 ++++++++++
90a56e
 v2v/test-v2v-i-vmx.sh         |  48 ++++++
90a56e
 v2v/v2v_unit_tests.ml         | 143 ++++++++++++++++
90a56e
 v2v/virt-v2v.pod              |  72 +++++++-
a30de4
 18 files changed, 1655 insertions(+), 9 deletions(-)
90a56e
 create mode 100644 v2v/input_vmx.ml
90a56e
 create mode 100644 v2v/input_vmx.mli
90a56e
 create mode 100644 v2v/parse_vmx.ml
90a56e
 create mode 100644 v2v/parse_vmx.mli
90a56e
 create mode 100644 v2v/test-v2v-i-vmx-1.expected
90a56e
 create mode 100644 v2v/test-v2v-i-vmx-1.vmx
90a56e
 create mode 100644 v2v/test-v2v-i-vmx-2.expected
90a56e
 create mode 100644 v2v/test-v2v-i-vmx-2.vmx
90a56e
 create mode 100644 v2v/test-v2v-i-vmx-3.expected
90a56e
 create mode 100644 v2v/test-v2v-i-vmx-3.vmx
90a56e
 create mode 100644 v2v/test-v2v-i-vmx-4.expected
90a56e
 create mode 100644 v2v/test-v2v-i-vmx-4.vmx
90a56e
 create mode 100755 v2v/test-v2v-i-vmx.sh
90a56e
90a56e
diff --git a/v2v/Makefile.am b/v2v/Makefile.am
46ce2f
index 297406496..0df759eca 100644
90a56e
--- a/v2v/Makefile.am
90a56e
+++ b/v2v/Makefile.am
90a56e
@@ -38,6 +38,7 @@ SOURCES_MLI = \
90a56e
 	input_libvirt_xen_ssh.mli \
90a56e
 	input_libvirtxml.mli \
90a56e
 	input_ova.mli \
90a56e
+	input_vmx.mli \
90a56e
 	inspect_source.mli \
90a56e
 	libvirt_utils.mli \
90a56e
 	linux.mli \
90a56e
@@ -55,6 +56,7 @@ SOURCES_MLI = \
90a56e
 	OVF.mli \
90a56e
 	parse_ovf_from_ova.mli \
90a56e
 	parse_libvirt_xml.mli \
90a56e
+	parse_vmx.mli \
90a56e
 	qemu_command.mli \
90a56e
 	target_bus_assignment.mli \
90a56e
 	types.mli \
90a56e
@@ -80,6 +82,7 @@ SOURCES_ML = \
90a56e
 	windows_virtio.ml \
90a56e
 	modules_list.ml \
90a56e
 	input_disk.ml \
90a56e
+	parse_vmx.ml \
90a56e
 	parse_libvirt_xml.ml \
90a56e
 	create_libvirt_xml.ml \
90a56e
 	qemu_command.ml \
90a56e
@@ -89,6 +92,7 @@ SOURCES_ML = \
90a56e
 	input_libvirt_xen_ssh.ml \
90a56e
 	input_libvirt.ml \
90a56e
 	input_ova.ml \
90a56e
+	input_vmx.ml \
90a56e
 	linux_bootloaders.ml \
90a56e
 	linux_kernels.ml \
90a56e
 	convert_linux.ml \
90a56e
@@ -268,6 +272,7 @@ TESTS = \
90a56e
 	test-v2v-i-ova-subfolders.sh \
90a56e
 	test-v2v-i-ova-tar.sh \
90a56e
 	test-v2v-i-ova-two-disks.sh \
90a56e
+	test-v2v-i-vmx.sh \
90a56e
 	test-v2v-bad-networks-and-bridges.sh
90a56e
 
90a56e
 if HAVE_LIBVIRT
90a56e
@@ -411,6 +416,15 @@ EXTRA_DIST += \
90a56e
 	test-v2v-i-ova.ovf \
90a56e
 	test-v2v-i-ova.sh \
90a56e
 	test-v2v-i-ova.xml \
90a56e
+	test-v2v-i-vmx.sh \
90a56e
+	test-v2v-i-vmx-1.expected \
90a56e
+	test-v2v-i-vmx-2.expected \
90a56e
+	test-v2v-i-vmx-3.expected \
90a56e
+	test-v2v-i-vmx-4.expected \
90a56e
+	test-v2v-i-vmx-1.vmx \
90a56e
+	test-v2v-i-vmx-2.vmx \
90a56e
+	test-v2v-i-vmx-3.vmx \
90a56e
+	test-v2v-i-vmx-4.vmx \
90a56e
 	test-v2v-machine-readable.sh \
90a56e
 	test-v2v-networks-and-bridges-expected.xml \
90a56e
 	test-v2v-networks-and-bridges.sh \
90a56e
@@ -450,6 +464,7 @@ v2v_unit_tests_BOBJECTS = \
90a56e
 	windows.cmo \
90a56e
 	windows_virtio.cmo \
90a56e
 	linux.cmo \
90a56e
+	parse_vmx.cmo \
90a56e
 	v2v_unit_tests.cmo
90a56e
 v2v_unit_tests_XOBJECTS = $(v2v_unit_tests_BOBJECTS:.cmo=.cmx)
90a56e
 
90a56e
diff --git a/v2v/cmdline.ml b/v2v/cmdline.ml
a30de4
index 3e3d5c312..db2346a38 100644
90a56e
--- a/v2v/cmdline.ml
90a56e
+++ b/v2v/cmdline.ml
90a56e
@@ -86,6 +86,7 @@ let parse_cmdline () =
90a56e
     | "libvirt" -> input_mode := `Libvirt
90a56e
     | "libvirtxml" -> input_mode := `LibvirtXML
90a56e
     | "ova" -> input_mode := `OVA
90a56e
+    | "vmx" -> input_mode := `VMX
90a56e
     | s ->
90a56e
       error (f_"unknown -i option: %s") s
90a56e
   in
a30de4
@@ -333,7 +334,16 @@ read the man page virt-v2v(1).
90a56e
         | [filename] -> filename
90a56e
         | _ ->
90a56e
           error (f_"expecting an OVA file name on the command line") in
90a56e
-      Input_ova.input_ova filename in
90a56e
+      Input_ova.input_ova filename
90a56e
+
90a56e
+    | `VMX ->
90a56e
+      (* -i vmx: Expecting an vmx filename. *)
90a56e
+      let filename =
90a56e
+        match args with
90a56e
+        | [filename] -> filename
90a56e
+        | _ ->
90a56e
+          error (f_"expecting a VMX file name on the command line") in
90a56e
+      Input_vmx.input_vmx filename in
90a56e
 
90a56e
   (* Prevent use of --in-place option in RHEL. *)
90a56e
   if in_place then
90a56e
diff --git a/v2v/input_vmx.ml b/v2v/input_vmx.ml
90a56e
new file mode 100644
46ce2f
index 000000000..bb09f0bf8
90a56e
--- /dev/null
90a56e
+++ b/v2v/input_vmx.ml
90a56e
@@ -0,0 +1,349 @@
90a56e
+(* virt-v2v
90a56e
+ * Copyright (C) 2017 Red Hat Inc.
90a56e
+ *
90a56e
+ * This program is free software; you can redistribute it and/or modify
90a56e
+ * it under the terms of the GNU General Public License as published by
90a56e
+ * the Free Software Foundation; either version 2 of the License, or
90a56e
+ * (at your option) any later version.
90a56e
+ *
90a56e
+ * This program is distributed in the hope that it will be useful,
90a56e
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
90a56e
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
90a56e
+ * GNU General Public License for more details.
90a56e
+ *
90a56e
+ * You should have received a copy of the GNU General Public License along
90a56e
+ * with this program; if not, write to the Free Software Foundation, Inc.,
90a56e
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
90a56e
+ *)
90a56e
+
90a56e
+open Printf
90a56e
+open Scanf
90a56e
+
90a56e
+open Common_gettext.Gettext
90a56e
+open Common_utils
90a56e
+
90a56e
+open Types
90a56e
+open Utils
90a56e
+open Name_from_disk
90a56e
+
90a56e
+external identity : 'a -> 'a = "%identity"
90a56e
+
90a56e
+let rec find_disks vmx vmx_filename =
90a56e
+  find_scsi_disks vmx vmx_filename @ find_ide_disks vmx vmx_filename
90a56e
+
90a56e
+(* Find all SCSI hard disks.
90a56e
+ *
90a56e
+ * In the VMX file:
90a56e
+ *   scsi0.virtualDev = "pvscsi"  # or may be "lsilogic" etc.
90a56e
+ *   scsi0:0.deviceType = "scsi-hardDisk"
90a56e
+ *   scsi0:0.fileName = "guest.vmdk"
90a56e
+ *)
90a56e
+and find_scsi_disks vmx vmx_filename =
90a56e
+  let get_scsi_controller_target ns =
90a56e
+    sscanf ns "scsi%d:%d" (fun c t -> c, t)
90a56e
+  in
90a56e
+  let is_scsi_controller_target ns =
90a56e
+    try ignore (get_scsi_controller_target ns); true
90a56e
+    with Scanf.Scan_failure _ | End_of_file | Failure _ -> false
90a56e
+  in
90a56e
+  let scsi_device_types = [ "scsi-harddisk" ] in
90a56e
+  let scsi_controller = Source_SCSI in
90a56e
+
90a56e
+  find_hdds vmx vmx_filename
90a56e
+            get_scsi_controller_target is_scsi_controller_target
90a56e
+            scsi_device_types scsi_controller
90a56e
+
90a56e
+(* Find all IDE hard disks.
90a56e
+ *
90a56e
+ * In the VMX file:
90a56e
+ *   ide0:0.deviceType = "ata-hardDisk"
90a56e
+ *   ide0:0.fileName = "guest.vmdk"
90a56e
+ *)
90a56e
+and find_ide_disks vmx vmx_filename =
90a56e
+  let get_ide_controller_target ns =
90a56e
+    sscanf ns "ide%d:%d" (fun c t -> c, t)
90a56e
+  in
90a56e
+  let is_ide_controller_target ns =
90a56e
+    try ignore (get_ide_controller_target ns); true
90a56e
+    with Scanf.Scan_failure _ | End_of_file | Failure _ -> false
90a56e
+  in
90a56e
+  let ide_device_types = [ "ata-harddisk" ] in
90a56e
+  let ide_controller = Source_IDE in
90a56e
+
90a56e
+  find_hdds vmx vmx_filename
90a56e
+            get_ide_controller_target is_ide_controller_target
90a56e
+            ide_device_types ide_controller
90a56e
+
90a56e
+and find_hdds vmx vmx_filename
90a56e
+              get_controller_target is_controller_target
90a56e
+              device_types controller =
90a56e
+  (* Find namespaces matching '(ide|scsi)X:Y' with suitable deviceType. *)
90a56e
+  let hdds =
90a56e
+    Parse_vmx.select_namespaces (
90a56e
+      function
90a56e
+      | [ns] ->
90a56e
+         (* Check the namespace is '(ide|scsi)X:Y' *)
90a56e
+         if not (is_controller_target ns) then false
90a56e
+         else (
90a56e
+           (* Check the deviceType is one we are looking for. *)
90a56e
+           match Parse_vmx.get_string vmx [ns; "deviceType"] with
90a56e
+           | Some str ->
90a56e
+              let str = String.lowercase_ascii str in
90a56e
+              List.mem str device_types
90a56e
+           | None -> false
90a56e
+         )
90a56e
+      | _ -> false
90a56e
+    ) vmx in
90a56e
+
90a56e
+  (* Map the subset to a list of disks. *)
90a56e
+  let hdds =
90a56e
+    Parse_vmx.map (
90a56e
+      fun path v ->
90a56e
+        match path, v with
90a56e
+        | [ns; "filename"], Some filename ->
90a56e
+           let c, t = get_controller_target ns in
90a56e
+           let s = { s_disk_id = (-1);
90a56e
+                     s_qemu_uri = qemu_uri_of_filename vmx_filename filename;
90a56e
+                     s_format = Some "vmdk";
90a56e
+                     s_controller = Some controller } in
90a56e
+           Some (c, t, s)
90a56e
+        | _ -> None
90a56e
+    ) hdds in
90a56e
+  let hdds = filter_map identity hdds in
90a56e
+
90a56e
+  (* We don't have a way to return the controllers and targets, so
90a56e
+   * just make sure the disks are sorted into order, since Parse_vmx
90a56e
+   * won't return them in any particular order.
90a56e
+   *)
90a56e
+  let hdds = List.sort compare hdds in
90a56e
+  let hdds = List.map (fun (_, _, source) -> source) hdds in
90a56e
+
90a56e
+  (* Set the s_disk_id field to an incrementing number. *)
90a56e
+  let hdds = mapi (fun i source -> { source with s_disk_id = i }) hdds in
90a56e
+
90a56e
+  hdds
90a56e
+
90a56e
+(* The filename can be an absolute path, but is more often a
90a56e
+ * path relative to the location of the vmx file.
90a56e
+ *
90a56e
+ * Note that we always end up with an absolute path, which is
90a56e
+ * also useful because it means we won't have any paths that
90a56e
+ * could be misinterpreted by qemu.
90a56e
+ *)
90a56e
+and qemu_uri_of_filename vmx_filename filename =
90a56e
+  if not (Filename.is_relative filename) then
90a56e
+    filename
90a56e
+  else (
90a56e
+    let dir = Filename.dirname (absolute_path vmx_filename) in
90a56e
+    dir // filename
90a56e
+  )
90a56e
+
90a56e
+(* Find all removable disks.
90a56e
+ *
90a56e
+ * In the VMX file:
90a56e
+ *   ide1:0.deviceType = "cdrom-image"
90a56e
+ *   ide1:0.fileName = "boot.iso"
90a56e
+ *
90a56e
+ * XXX This only supports IDE CD-ROMs, but we could support SCSI
90a56e
+ * CD-ROMs and floppies in future.
90a56e
+ *)
90a56e
+and find_removables vmx =
90a56e
+  let get_ide_controller_target ns =
90a56e
+    sscanf ns "ide%d:%d" (fun c t -> c, t)
90a56e
+  in
90a56e
+  let is_ide_controller_target ns =
90a56e
+    try ignore (get_ide_controller_target ns); true
90a56e
+    with Scanf.Scan_failure _ | End_of_file | Failure _ -> false
90a56e
+  in
90a56e
+  let device_types = [ "atapi-cdrom";
90a56e
+                       "cdrom-image"; "cdrom-raw" ] in
90a56e
+
90a56e
+  (* Find namespaces matching 'ideX:Y' with suitable deviceType. *)
90a56e
+  let devs =
90a56e
+    Parse_vmx.select_namespaces (
90a56e
+      function
90a56e
+      | [ns] ->
90a56e
+         (* Check the namespace is 'ideX:Y' *)
90a56e
+         if not (is_ide_controller_target ns) then false
90a56e
+         else (
90a56e
+           (* Check the deviceType is one we are looking for. *)
90a56e
+           match Parse_vmx.get_string vmx [ns; "deviceType"] with
90a56e
+           | Some str ->
90a56e
+              let str = String.lowercase_ascii str in
90a56e
+              List.mem str device_types
90a56e
+           | None -> false
90a56e
+         )
90a56e
+      | _ -> false
90a56e
+    ) vmx in
90a56e
+
90a56e
+  (* Map the subset to a list of CD-ROMs. *)
90a56e
+  let devs =
90a56e
+    Parse_vmx.map (
90a56e
+      fun path v ->
90a56e
+        match path, v with
90a56e
+        | [ns], None ->
90a56e
+           let c, t = get_ide_controller_target ns in
90a56e
+           let s = { s_removable_type = CDROM;
90a56e
+                     s_removable_controller = Some Source_IDE;
90a56e
+                     s_removable_slot = Some (ide_slot c t) } in
90a56e
+           Some s
90a56e
+        | _ -> None
90a56e
+    ) devs in
90a56e
+  let devs = filter_map identity devs in
90a56e
+
90a56e
+  (* Sort by slot. *)
90a56e
+  let devs =
90a56e
+    List.sort
90a56e
+      (fun { s_removable_slot = s1 } { s_removable_slot = s2 } ->
90a56e
+        compare s1 s2)
90a56e
+      devs in
90a56e
+
90a56e
+  devs
90a56e
+
90a56e
+and ide_slot c t =
90a56e
+  (* Assuming the old master/slave arrangement. *)
90a56e
+  c * 2 + t
90a56e
+
90a56e
+(* Find all ethernet cards.
90a56e
+ *
90a56e
+ * In the VMX file:
90a56e
+ *   ethernet0.virtualDev = "vmxnet3"
90a56e
+ *   ethernet0.networkName = "VM Network"
90a56e
+ *   ethernet0.generatedAddress = "00:01:02:03:04:05"
90a56e
+ *   ethernet0.connectionType = "bridged" # also: "custom", "nat" or not present
90a56e
+ *)
90a56e
+and find_nics vmx =
90a56e
+  let get_ethernet_port ns =
90a56e
+    sscanf ns "ethernet%d" (fun p -> p)
90a56e
+  in
90a56e
+  let is_ethernet_port ns =
90a56e
+    try ignore (get_ethernet_port ns); true
90a56e
+    with Scanf.Scan_failure _ | End_of_file | Failure _ -> false
90a56e
+  in
90a56e
+
90a56e
+  (* Find namespaces matching 'ethernetX'. *)
90a56e
+  let nics =
90a56e
+    Parse_vmx.select_namespaces (
90a56e
+      function
90a56e
+      | [ns] -> is_ethernet_port ns
90a56e
+      | _ -> false
90a56e
+    ) vmx in
90a56e
+
90a56e
+  (* Map the subset to a list of NICs. *)
90a56e
+  let nics =
90a56e
+    Parse_vmx.map (
90a56e
+      fun path v ->
90a56e
+        match path, v with
90a56e
+        | [ns], None ->
90a56e
+           let port = get_ethernet_port ns in
90a56e
+           let mac = Parse_vmx.get_string vmx [ns; "generatedAddress"] in
90a56e
+           let model = Parse_vmx.get_string vmx [ns; "virtualDev"] in
90a56e
+           let model =
90a56e
+             match model with
90a56e
+             | Some m when String.lowercase_ascii m = "e1000" ->
90a56e
+                Some Source_e1000
90a56e
+             | Some model ->
90a56e
+                Some (Source_other_nic (String.lowercase_ascii model))
90a56e
+             | None -> None in
90a56e
+           let vnet = Parse_vmx.get_string vmx [ns; "networkName"] in
90a56e
+           let vnet =
90a56e
+             match vnet with
90a56e
+             | Some vnet -> vnet
90a56e
+             | None -> ns (* "ethernetX" *) in
90a56e
+           let vnet_type =
90a56e
+             match Parse_vmx.get_string vmx [ns; "connectionType"] with
90a56e
+             | Some b when String.lowercase_ascii b = "bridged" ->
90a56e
+                Bridge
90a56e
+             | Some _ | None -> Network in
90a56e
+           Some (port,
90a56e
+                 { s_mac = mac; s_nic_model = model;
90a56e
+                   s_vnet = vnet; s_vnet_orig = vnet;
90a56e
+                   s_vnet_type = vnet_type })
90a56e
+        | _ -> None
90a56e
+    ) nics in
90a56e
+  let nics = filter_map identity nics in
90a56e
+
90a56e
+  (* Sort by port. *)
90a56e
+  let nics = List.sort compare nics in
90a56e
+
90a56e
+  let nics = List.map (fun (_, source) -> source) nics in
90a56e
+  nics
90a56e
+
90a56e
+class input_vmx vmx_filename = object
90a56e
+  inherit input
90a56e
+
90a56e
+  method as_options = "-i vmx " ^ vmx_filename
90a56e
+
90a56e
+  method source () =
90a56e
+    (* Parse the VMX file. *)
90a56e
+    let vmx = Parse_vmx.parse_file vmx_filename in
90a56e
+
90a56e
+    let name =
90a56e
+      match Parse_vmx.get_string vmx ["displayName"] with
90a56e
+      | None ->
90a56e
+         warning (f_"no displayName key found in VMX file");
90a56e
+         name_from_disk vmx_filename
90a56e
+      | Some s -> s in
90a56e
+
90a56e
+    let memory_mb =
90a56e
+      match Parse_vmx.get_int64 vmx ["memSize"] with
90a56e
+      | None -> 32_L            (* default is really 32 MB! *)
90a56e
+      | Some i -> i in
90a56e
+    let memory = memory_mb *^ 1024L *^ 1024L in
90a56e
+
90a56e
+    let vcpu =
90a56e
+      match Parse_vmx.get_int vmx ["numvcpus"] with
90a56e
+      | None -> 1
90a56e
+      | Some i -> i in
90a56e
+
90a56e
+    let firmware =
90a56e
+      match Parse_vmx.get_string vmx ["firmware"] with
90a56e
+      | None -> BIOS
90a56e
+      | Some "efi" -> UEFI
90a56e
+      (* Other values are not documented for this field ... *)
90a56e
+      | Some fw ->
90a56e
+         warning (f_"unknown firmware value '%s', assuming BIOS") fw;
90a56e
+         BIOS in
90a56e
+
90a56e
+    let video =
90a56e
+      if Parse_vmx.namespace_present vmx ["svga"] then
90a56e
+        (* We could also parse svga.vramSize. *)
90a56e
+        Some (Source_other_video "vmvga")
90a56e
+      else
90a56e
+        None in
90a56e
+
90a56e
+    let sound =
90a56e
+      match Parse_vmx.get_string vmx ["sound"; "virtualDev"] with
90a56e
+      | Some ("sb16") -> Some { s_sound_model = SB16 }
90a56e
+      | Some ("es1371") -> Some { s_sound_model = ES1370 (* hmmm ... *) }
90a56e
+      | Some "hdaudio" -> Some { s_sound_model = ICH6 (* intel-hda *) }
90a56e
+      | Some model ->
90a56e
+         warning (f_"unknown sound device '%s' ignored") model;
90a56e
+         None
90a56e
+      | None -> None in
90a56e
+
90a56e
+    let disks = find_disks vmx vmx_filename in
90a56e
+    let removables = find_removables vmx in
90a56e
+    let nics = find_nics vmx in
90a56e
+
90a56e
+    let source = {
90a56e
+      s_hypervisor = VMware;
90a56e
+      s_name = name;
90a56e
+      s_orig_name = name;
90a56e
+      s_memory = memory;
90a56e
+      s_vcpu = vcpu;
90a56e
+      s_features = [];
90a56e
+      s_firmware = firmware;
90a56e
+      s_display = None;
90a56e
+      s_video = video;
90a56e
+      s_sound = sound;
90a56e
+      s_disks = disks;
90a56e
+      s_removables = removables;
90a56e
+      s_nics = nics;
90a56e
+    } in
90a56e
+
90a56e
+    source
90a56e
+end
90a56e
+
90a56e
+let input_vmx = new input_vmx
90a56e
+let () = Modules_list.register_input_module "vmx"
90a56e
diff --git a/v2v/input_vmx.mli b/v2v/input_vmx.mli
90a56e
new file mode 100644
46ce2f
index 000000000..f236f8716
90a56e
--- /dev/null
90a56e
+++ b/v2v/input_vmx.mli
90a56e
@@ -0,0 +1,22 @@
90a56e
+(* virt-v2v
90a56e
+ * Copyright (C) 2017 Red Hat Inc.
90a56e
+ *
90a56e
+ * This program is free software; you can redistribute it and/or modify
90a56e
+ * it under the terms of the GNU General Public License as published by
90a56e
+ * the Free Software Foundation; either version 2 of the License, or
90a56e
+ * (at your option) any later version.
90a56e
+ *
90a56e
+ * This program is distributed in the hope that it will be useful,
90a56e
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
90a56e
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
90a56e
+ * GNU General Public License for more details.
90a56e
+ *
90a56e
+ * You should have received a copy of the GNU General Public License along
90a56e
+ * with this program; if not, write to the Free Software Foundation, Inc.,
90a56e
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
90a56e
+ *)
90a56e
+
90a56e
+(** [-i vmx] source. *)
90a56e
+
90a56e
+val input_vmx : string -> Types.input
90a56e
+(** [input_vmx filename] sets up an input from vmware vmx file. *)
90a56e
diff --git a/v2v/name_from_disk.ml b/v2v/name_from_disk.ml
46ce2f
index 82f09250a..452d9462c 100644
90a56e
--- a/v2v/name_from_disk.ml
90a56e
+++ b/v2v/name_from_disk.ml
90a56e
@@ -24,7 +24,7 @@ let name_from_disk disk =
90a56e
   (* Remove the extension (or suffix), only if it's one usually
90a56e
    * used for disk images. *)
90a56e
   let suffixes = [
90a56e
-    ".img"; ".ova"; ".qcow2"; ".raw"; ".vmdk";
90a56e
+    ".img"; ".ova"; ".qcow2"; ".raw"; ".vmdk"; ".vmx";
90a56e
     "-sda";
90a56e
   ] in
90a56e
   let rec loop = function
90a56e
diff --git a/v2v/parse_vmx.ml b/v2v/parse_vmx.ml
90a56e
new file mode 100644
46ce2f
index 000000000..33ec17d3d
90a56e
--- /dev/null
90a56e
+++ b/v2v/parse_vmx.ml
90a56e
@@ -0,0 +1,381 @@
90a56e
+(* virt-v2v
90a56e
+ * Copyright (C) 2017 Red Hat Inc.
90a56e
+ *
90a56e
+ * This program is free software; you can redistribute it and/or modify
90a56e
+ * it under the terms of the GNU General Public License as published by
90a56e
+ * the Free Software Foundation; either version 2 of the License, or
90a56e
+ * (at your option) any later version.
90a56e
+ *
90a56e
+ * This program is distributed in the hope that it will be useful,
90a56e
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
90a56e
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
90a56e
+ * GNU General Public License for more details.
90a56e
+ *
90a56e
+ * You should have received a copy of the GNU General Public License along
90a56e
+ * with this program; if not, write to the Free Software Foundation, Inc.,
90a56e
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
90a56e
+ *)
90a56e
+
90a56e
+open Printf
90a56e
+
90a56e
+open Common_utils
90a56e
+open Common_gettext.Gettext
90a56e
+
90a56e
+(* As far as I can tell the VMX format is totally unspecified.
90a56e
+ * However libvirt has a useful selection of .vmx files in the
90a56e
+ * sources which explore some of the darker regions of this
90a56e
+ * format.
90a56e
+ *
90a56e
+ * So here are some facts about VMX derived from libvirt and
90a56e
+ * other places:
90a56e
+ *
90a56e
+ * - Keys are compared case insensitively.  We assume here
90a56e
+ *   that keys are 7-bit ASCII.
90a56e
+ *
90a56e
+ * - Multiple keys with the same name are not allowed.
90a56e
+ *
90a56e
+ * - Escaping in the value string is possible using a very weird
90a56e
+ *   escape format: "|22" means the character '\x22'.  To write
90a56e
+ *   a pipe character you must use "|7C".
90a56e
+ *
90a56e
+ * - Boolean values are written "TRUE", "FALSE", "True", "true", etc.
90a56e
+ *   Because of the quotes they cannot be distinguished from strings.
90a56e
+ *
90a56e
+ * - Comments (#...) and blank lines are ignored.  Some files start
90a56e
+ *   with a hash-bang path, but we ignore those as comments.  This
90a56e
+ *   parser also ignores any other line which it doesn't understand,
90a56e
+ *   but will print a warning.
90a56e
+ *
90a56e
+ * - Multi-line values are not permitted.
90a56e
+ *
90a56e
+ * - Keys are namespaced using dots, eg. scsi0:0.deviceType has
90a56e
+ *   the namespace "scsi0:0" and the key name "deviceType".
90a56e
+ *
90a56e
+ * - Using namespace.present = "FALSE" means that all other keys
90a56e
+ *   in and under the namespace are ignored.
90a56e
+ *
90a56e
+ * - You cannot have a namespace and a key with the same name, eg.
90a56e
+ *   this is not allowed:
90a56e
+ *     namespace = "some value"
90a56e
+ *     namespace.foo = "another value"
90a56e
+ *
90a56e
+ * - The Hashicorp packer VMX writer considers some special keys
90a56e
+ *   as not requiring any quotes around their values, but I'm
90a56e
+ *   ignoring that for now.
90a56e
+ *)
90a56e
+
90a56e
+(* This VMX file:
90a56e
+ *
90a56e
+ *   foo.a = "abc"
90a56e
+ *   foo.b = "def"
90a56e
+ *   foo.bar.c = "abc"
90a56e
+ *   foo.bar.d = "def"
90a56e
+ *
90a56e
+ * would be represented by this structure:
90a56e
+ *
90a56e
+ *   "foo" => Namespace (             # "foo" is a namespace
90a56e
+ *              "a" => Key "abc";     # "foo.a" is a key with value "abc"
90a56e
+ *              "b" => Key "def";
90a56e
+ *              "bar" => Namespace (  # "foo.bar" is another namespace
90a56e
+ *                         "c" => Key "abc";
90a56e
+ *                         "d" => Key "def";
90a56e
+ *                       )
90a56e
+ *            )
90a56e
+ *   ‘( => )’s represent the StringMap type.
90a56e
+ *)
90a56e
+type t = key StringMap.t
90a56e
+
90a56e
+and key =
90a56e
+  | Key of string
90a56e
+  | Namespace of t
90a56e
+
90a56e
+let empty = StringMap.empty
90a56e
+
90a56e
+(* Compare two trees for equality. *)
90a56e
+let rec equal vmx1 vmx2 =
90a56e
+  let cmp k1 k2 =
90a56e
+    match k1, k2 with
90a56e
+    | Key v1, Key v2 -> v1 = v2
90a56e
+    | Key _, Namespace _ -> false
90a56e
+    | Namespace _, Key _ -> false
90a56e
+    | Namespace vmx1, Namespace vmx2 -> equal vmx1 vmx2
90a56e
+  in
90a56e
+  StringMap.equal cmp vmx1 vmx2
90a56e
+
90a56e
+(* Higher-order functions. *)
90a56e
+let rec select_namespaces pred vmx =
90a56e
+  _select_namespaces [] pred vmx
90a56e
+
90a56e
+and _select_namespaces path pred vmx =
90a56e
+  StringMap.fold (
90a56e
+    fun k v new_vmx ->
90a56e
+      let path = path @ [k] in
90a56e
+      match v with
90a56e
+      | Key _ -> new_vmx
90a56e
+      | Namespace _ when pred path ->
90a56e
+         StringMap.add k v new_vmx
90a56e
+      | Namespace t ->
90a56e
+         let t = _select_namespaces path pred t in
90a56e
+         if not (equal t empty) then
90a56e
+           StringMap.add k (Namespace t) new_vmx
90a56e
+         else
90a56e
+           new_vmx
90a56e
+  ) vmx empty
90a56e
+
90a56e
+let rec map f vmx =
90a56e
+  _map [] f vmx
90a56e
+
90a56e
+and _map path f vmx =
90a56e
+  StringMap.fold (
90a56e
+    fun k v r ->
90a56e
+      let path = path @ [k] in
90a56e
+      match v with
90a56e
+      | Key v -> r @ [ f path (Some v) ]
90a56e
+      | Namespace t -> r @ [ f path None ] @ _map path f t
90a56e
+  ) vmx []
90a56e
+
90a56e
+let rec namespace_present vmx = function
90a56e
+  | [] -> false
90a56e
+  | [ns] ->
90a56e
+     let ns = String.lowercase_ascii ns in
90a56e
+     (try
90a56e
+        let v = StringMap.find ns vmx in
90a56e
+        match v with
90a56e
+        | Key _ -> false
90a56e
+        | Namespace _ -> true
90a56e
+      with
90a56e
+        Not_found -> false
90a56e
+     )
90a56e
+  | ns :: path ->
90a56e
+     let ns = String.lowercase_ascii ns in
90a56e
+     (try
90a56e
+        let v = StringMap.find ns vmx in
90a56e
+        match v with
90a56e
+        | Key _ -> false
90a56e
+        | Namespace vmx -> namespace_present vmx path
90a56e
+      with
90a56e
+        Not_found -> false
90a56e
+     )
90a56e
+
90a56e
+(* Dump the vmx structure to [chan].  Used for debugging. *)
90a56e
+let rec print chan indent vmx =
90a56e
+  StringMap.iter (print_key chan indent) vmx
90a56e
+
90a56e
+and print_key chan indent k = function
90a56e
+  | Key v ->
90a56e
+     output_spaces chan indent;
90a56e
+     fprintf chan "%s = \"%s\"\n" k v
90a56e
+  | Namespace vmx ->
90a56e
+     output_spaces chan indent;
90a56e
+     fprintf chan "namespace '%s':\n" k;
90a56e
+     print chan (indent+4) vmx
90a56e
+
90a56e
+(* As above, but creates a string instead. *)
90a56e
+let rec to_string indent vmx =
90a56e
+  StringMap.fold (fun k v str -> str ^ to_string_key indent k v) vmx ""
90a56e
+
90a56e
+and to_string_key indent k = function
90a56e
+  | Key v ->
90a56e
+     String.spaces indent ^ sprintf "%s = \"%s\"\n" k v
90a56e
+  | Namespace vmx ->
90a56e
+     String.spaces indent ^ sprintf "namespace '%s':\n" k ^
90a56e
+       to_string (indent+4) vmx
90a56e
+
90a56e
+(* Access keys in the tree. *)
90a56e
+let rec get_string vmx = function
90a56e
+  | [] -> None
90a56e
+  | [k] ->
90a56e
+     let k = String.lowercase_ascii k in
90a56e
+     (try
90a56e
+        let v = StringMap.find k vmx in
90a56e
+        match v with
90a56e
+        | Key v -> Some v
90a56e
+        | Namespace _ -> None
90a56e
+      with Not_found -> None
90a56e
+     )
90a56e
+  | ns :: path ->
90a56e
+     let ns = String.lowercase_ascii ns in
90a56e
+     (try
90a56e
+        let v = StringMap.find ns vmx in
90a56e
+        match v with
90a56e
+        | Key v -> None
90a56e
+        | Namespace vmx -> get_string vmx path
90a56e
+      with
90a56e
+        Not_found -> None
90a56e
+     )
90a56e
+
90a56e
+let get_int64 vmx path =
90a56e
+  match get_string vmx path with
90a56e
+  | None -> None
90a56e
+  | Some i -> Some (Int64.of_string i)
90a56e
+
90a56e
+let get_int vmx path =
90a56e
+  match get_string vmx path with
90a56e
+  | None -> None
90a56e
+  | Some i -> Some (int_of_string i)
90a56e
+
90a56e
+let rec get_bool vmx path =
90a56e
+  match get_string vmx path with
90a56e
+  | None -> None
90a56e
+  | Some t -> Some (vmx_bool_of_string t)
90a56e
+
90a56e
+and vmx_bool_of_string t =
90a56e
+  if String.lowercase_ascii t = "true" then true
90a56e
+  else if String.lowercase_ascii t = "false" then false
90a56e
+  else failwith "bool_of_string"
90a56e
+
90a56e
+(* Regular expression used to match key = "value" in VMX file. *)
90a56e
+let rex = Str.regexp "^\\([^ \t=]+\\)[ \t]*=[ \t]*\"\\(.*\\)\"$"
90a56e
+
90a56e
+(* Remove the weird escapes used in value strings.  See description above. *)
90a56e
+let remove_vmx_escapes str =
90a56e
+  let len = String.length str in
90a56e
+  let out = Bytes.make len '\000' in
90a56e
+  let j = ref 0 in
90a56e
+
90a56e
+  let rec loop i =
90a56e
+    if i >= len then ()
90a56e
+    else (
90a56e
+      let c = String.unsafe_get str i in
90a56e
+      if i <= len-3 && c = '|' then (
90a56e
+        let c1 = str.[i+1] and c2 = str.[i+2] in
90a56e
+        if Char.isxdigit c1 && Char.isxdigit c2 then (
90a56e
+          let x = Char.hexdigit c1 * 0x10 + Char.hexdigit c2 in
90a56e
+          Bytes.set out !j (Char.chr x);
90a56e
+          incr j;
90a56e
+          loop (i+3)
90a56e
+        )
90a56e
+        else (
90a56e
+          Bytes.set out !j c;
90a56e
+          incr j;
90a56e
+          loop (i+1)
90a56e
+        )
90a56e
+      )
90a56e
+      else (
90a56e
+        Bytes.set out !j c;
90a56e
+        incr j;
90a56e
+        loop (i+1)
90a56e
+      )
90a56e
+    )
90a56e
+  in
90a56e
+  loop 0;
90a56e
+
90a56e
+  (* Truncate the output string to its real size and return it
90a56e
+   * as an immutable string.
90a56e
+   *)
90a56e
+  Bytes.sub_string out 0 !j
90a56e
+
90a56e
+(* Parsing. *)
90a56e
+let rec parse_file vmx_filename =
90a56e
+  (* Read the whole file as a list of lines. *)
90a56e
+  let str = read_whole_file vmx_filename in
90a56e
+  if verbose () then eprintf "VMX file:\n%s\n" str;
90a56e
+  parse_string str
90a56e
+
90a56e
+and parse_string str =
90a56e
+  let lines = String.nsplit "\n" str in
90a56e
+
90a56e
+  (* I've never seen any VMX file with CR-LF endings, and VMware
90a56e
+   * itself is Linux-based, but to be on the safe side ...
90a56e
+   *)
90a56e
+  let lines = List.map (String.trimr ~test:((=) '\r')) lines in
90a56e
+
90a56e
+  (* Ignore blank lines and comments. *)
90a56e
+  let lines = List.filter (
90a56e
+    fun line ->
90a56e
+      let line = String.triml line in
90a56e
+      let len = String.length line in
90a56e
+      len > 0 && line.[0] != '#'
90a56e
+  ) lines in
90a56e
+
90a56e
+  (* Parse the lines into key = "value". *)
90a56e
+  let lines = filter_map (
90a56e
+    fun line ->
90a56e
+      if Str.string_match rex line 0 then (
90a56e
+        let key = Str.matched_group 1 line in
90a56e
+        let key = String.lowercase_ascii key in
90a56e
+        let value = Str.matched_group 2 line in
90a56e
+        let value = remove_vmx_escapes value in
90a56e
+        Some (key, value)
90a56e
+      )
90a56e
+      else (
90a56e
+        warning (f_"vmx parser: cannot parse this line, ignoring: %s") line;
90a56e
+        None
90a56e
+      )
90a56e
+  ) lines in
90a56e
+
90a56e
+  (* Split the keys into namespace paths. *)
90a56e
+  let lines =
90a56e
+    List.map (fun (key, value) -> String.nsplit "." key, value) lines in
90a56e
+
90a56e
+  (* Build a tree from the flat list and return it.  This is horribly
90a56e
+   * inefficient, at least O(n²), possibly even O(n².log n).  Hope
90a56e
+   * there are no large VMX files!  (XXX)
90a56e
+   *)
90a56e
+  let vmx =
90a56e
+    List.fold_left (
90a56e
+      fun vmx (path, value) -> insert vmx value path
90a56e
+    ) empty lines in
90a56e
+
90a56e
+  (* If we're verbose, dump the parsed VMX for debugging purposes. *)
90a56e
+  if verbose () then (
90a56e
+    eprintf "parsed VMX tree:\n";
90a56e
+    print stderr 0 vmx
90a56e
+  );
90a56e
+
90a56e
+  (* Drop all present = "FALSE" namespaces. *)
90a56e
+  let vmx = drop_not_present vmx in
90a56e
+
90a56e
+  vmx
90a56e
+
90a56e
+and insert vmx value = function
90a56e
+  | [] -> assert false
90a56e
+  | [k] ->
90a56e
+     if StringMap.mem k vmx then (
90a56e
+       warning (f_"vmx parser: duplicate key '%s' ignored") k;
90a56e
+       vmx
90a56e
+     ) else
90a56e
+       StringMap.add k (Key value) vmx
90a56e
+  | ns :: path ->
90a56e
+     let v =
90a56e
+       try
90a56e
+         (match StringMap.find ns vmx with
90a56e
+          | Namespace vmx -> Some vmx
90a56e
+          | Key _ -> None
90a56e
+         )
90a56e
+       with Not_found -> None in
90a56e
+     let v =
90a56e
+       match v with
90a56e
+       | None ->
90a56e
+          (* Completely new namespace. *)
90a56e
+          insert empty value path
90a56e
+       | Some v ->
90a56e
+          (* Insert the subkey into the previously created namespace. *)
90a56e
+          insert v value path in
90a56e
+     StringMap.add ns (Namespace v) vmx
90a56e
+
90a56e
+(* Find any "present" keys.  If we find present = "FALSE", then
90a56e
+ * drop the containing namespace and all subkeys and subnamespaces.
90a56e
+ *)
90a56e
+and drop_not_present vmx =
90a56e
+  StringMap.fold (
90a56e
+    fun k v new_vmx ->
90a56e
+      match v with
90a56e
+      | Key _ ->
90a56e
+         StringMap.add k v new_vmx
90a56e
+      | Namespace vmx when contains_key_present_false vmx ->
90a56e
+         (* drop this namespace and all sub-spaces *)
90a56e
+         new_vmx
90a56e
+      | Namespace v ->
90a56e
+         (* recurse into sub-namespace and do the same check *)
90a56e
+         let v = drop_not_present v in
90a56e
+         StringMap.add k (Namespace v) new_vmx
90a56e
+  ) vmx empty
90a56e
+
90a56e
+and contains_key_present_false vmx =
90a56e
+  try
90a56e
+    match StringMap.find "present" vmx with
90a56e
+    | Key v when vmx_bool_of_string v = false -> true
90a56e
+    | Key _ | Namespace _ -> false
90a56e
+  with
90a56e
+    Failure _ | Not_found -> false
90a56e
diff --git a/v2v/parse_vmx.mli b/v2v/parse_vmx.mli
90a56e
new file mode 100644
46ce2f
index 000000000..0e4f21f07
90a56e
--- /dev/null
90a56e
+++ b/v2v/parse_vmx.mli
90a56e
@@ -0,0 +1,89 @@
90a56e
+(* virt-v2v
90a56e
+ * Copyright (C) 2017 Red Hat Inc.
90a56e
+ *
90a56e
+ * This program is free software; you can redistribute it and/or modify
90a56e
+ * it under the terms of the GNU General Public License as published by
90a56e
+ * the Free Software Foundation; either version 2 of the License, or
90a56e
+ * (at your option) any later version.
90a56e
+ *
90a56e
+ * This program is distributed in the hope that it will be useful,
90a56e
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
90a56e
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
90a56e
+ * GNU General Public License for more details.
90a56e
+ *
90a56e
+ * You should have received a copy of the GNU General Public License along
90a56e
+ * with this program; if not, write to the Free Software Foundation, Inc.,
90a56e
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
90a56e
+ *)
90a56e
+
90a56e
+(** A simple parser for VMware [.vmx] files. *)
90a56e
+
90a56e
+type t
90a56e
+
90a56e
+val parse_file : string -> t
90a56e
+(** [parse_file filename] parses a VMX file. *)
90a56e
+
90a56e
+val parse_string : string -> t
90a56e
+(** [parse_string s] parses VMX from a string. *)
90a56e
+
90a56e
+val get_string : t -> string list -> string option
90a56e
+(** Find a key and return it as a string.  If not present, returns [None].
90a56e
+
90a56e
+    Note that if [namespace.present = "FALSE"] is found in the file
90a56e
+    then all keys in [namespace] and below it are ignored.  This
90a56e
+    applies to all [get_*] functions. *)
90a56e
+
90a56e
+val get_int64 : t -> string list -> int64 option
90a56e
+(** Find a key and return it as an [int64].
90a56e
+    If not present, returns [None].
90a56e
+
90a56e
+    Raises [Failure _] if the key is present but was not parseable
90a56e
+    as an integer. *)
90a56e
+
90a56e
+val get_int : t -> string list -> int option
90a56e
+(** Find a key and return it as an [int].
90a56e
+    If not present, returns [None].
90a56e
+
90a56e
+    Raises [Failure _] if the key is present but was not parseable
90a56e
+    as an integer. *)
90a56e
+
90a56e
+val get_bool : t -> string list -> bool option
90a56e
+(** Find a key and return it as a boolean.
90a56e
+
90a56e
+    You cannot return [namespace.present = "FALSE"] booleans this way.
90a56e
+    They are processed by the parser and the namespace and anything
90a56e
+    below it are removed from the tree.
90a56e
+
90a56e
+    Raises [Failure _] if the key is present but was not parseable
90a56e
+    as a boolean. *)
90a56e
+
90a56e
+val namespace_present : t -> string list -> bool
90a56e
+(** Returns true iff the namespace ({b note:} not key) is present. *)
90a56e
+
90a56e
+val select_namespaces : (string list -> bool) -> t -> t
90a56e
+(** Filter the VMX file, selecting exactly namespaces (and their
90a56e
+    keys) matching the predicate.  The predicate is a function which
90a56e
+    is called on each {i namespace} path ({b note:} not on
90a56e
+    namespace + key paths).  If the predicate matches a
90a56e
+    namespace, then all sub-namespaces under that namespace are
90a56e
+    selected implicitly. *)
90a56e
+
90a56e
+val map : (string list -> string option -> 'a) -> t -> 'a list
90a56e
+(** Map all the entries in the VMX file into a list using the
90a56e
+    map function.  The map function takes two arguments.  The
90a56e
+    first is the path to the namespace or key, and the second
90a56e
+    is the key value (or [None] if the path refers to a namespace). *)
90a56e
+
90a56e
+val equal : t -> t -> bool
90a56e
+(** Compare two VMX files for equality.  This is mainly used for
90a56e
+    testing the parser. *)
90a56e
+
90a56e
+val empty : t
90a56e
+(** An empty VMX file. *)
90a56e
+
90a56e
+val print : out_channel -> int -> t -> unit
90a56e
+(** [print chan indent] prints the VMX file to the output channel.
90a56e
+    [indent] is the indentation applied to each line of output. *)
90a56e
+
90a56e
+val to_string : int -> t -> string
90a56e
+(** Same as {!print} but it creates a printable (multiline) string. *)
90a56e
diff --git a/v2v/test-v2v-i-vmx-1.expected b/v2v/test-v2v-i-vmx-1.expected
90a56e
new file mode 100644
a30de4
index 000000000..c7ef1f5d5
90a56e
--- /dev/null
90a56e
+++ b/v2v/test-v2v-i-vmx-1.expected
a30de4
@@ -0,0 +1,39 @@
90a56e
+[   0.0] Opening the source -i vmx test-v2v-i-vmx-1.vmx
90a56e
+Source guest information (--print-source option):
90a56e
+
90a56e
+    source name: BZ1308535_21disks
90a56e
+hypervisor type: vmware
90a56e
+         memory: 2147483648 (bytes)
90a56e
+       nr vCPUs: 1
90a56e
+   CPU features: 
90a56e
+       firmware: bios
90a56e
+        display: 
90a56e
+          video: vmvga
90a56e
+          sound: 
90a56e
+disks:
90a56e
+	/BZ1308535_21disks.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_1.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_2.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_3.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_4.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_5.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_6.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_7.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_8.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_9.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_10.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_11.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_12.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_13.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_14.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_15.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_16.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_17.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_18.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_19.vmdk (vmdk) [scsi]
90a56e
+	/BZ1308535_21disks_20.vmdk (vmdk) [scsi]
90a56e
+removable media:
90a56e
+	CD-ROM [ide] in slot 2
90a56e
+NICs:
90a56e
+	Network "VM Network" mac: 00:0c:29:36:ef:31 [vmxnet3]
90a56e
+
90a56e
diff --git a/v2v/test-v2v-i-vmx-1.vmx b/v2v/test-v2v-i-vmx-1.vmx
90a56e
new file mode 100644
46ce2f
index 000000000..3f2f060a5
90a56e
--- /dev/null
90a56e
+++ b/v2v/test-v2v-i-vmx-1.vmx
90a56e
@@ -0,0 +1,172 @@
90a56e
+.encoding = "UTF-8"
90a56e
+config.version = "8"
90a56e
+virtualHW.version = "8"
90a56e
+nvram = "BZ1308535_21disks.nvram"
90a56e
+pciBridge0.present = "TRUE"
90a56e
+svga.present = "TRUE"
90a56e
+pciBridge4.present = "TRUE"
90a56e
+pciBridge4.virtualDev = "pcieRootPort"
90a56e
+pciBridge4.functions = "8"
90a56e
+pciBridge5.present = "TRUE"
90a56e
+pciBridge5.virtualDev = "pcieRootPort"
90a56e
+pciBridge5.functions = "8"
90a56e
+pciBridge6.present = "TRUE"
90a56e
+pciBridge6.virtualDev = "pcieRootPort"
90a56e
+pciBridge6.functions = "8"
90a56e
+pciBridge7.present = "TRUE"
90a56e
+pciBridge7.virtualDev = "pcieRootPort"
90a56e
+pciBridge7.functions = "8"
90a56e
+vmci0.present = "TRUE"
90a56e
+hpet0.present = "TRUE"
90a56e
+displayName = "BZ1308535_21disks"
90a56e
+extendedConfigFile = "BZ1308535_21disks.vmxf"
90a56e
+virtualHW.productCompatibility = "hosted"
90a56e
+memSize = "2048"
90a56e
+sched.cpu.units = "mhz"
90a56e
+powerType.powerOff = "soft"
90a56e
+powerType.suspend = "hard"
90a56e
+powerType.reset = "soft"
90a56e
+scsi0.virtualDev = "pvscsi"
90a56e
+scsi0.present = "TRUE"
90a56e
+scsi1.virtualDev = "pvscsi"
90a56e
+scsi1.present = "TRUE"
90a56e
+ide1:0.deviceType = "cdrom-image"
90a56e
+ide1:0.fileName = "/vmfs/volumes/5458b680-34ec3500-9f36-001320f5f6ca/ISOs/RHEL-7.1-20150219.1-Server-x86_64-boot.iso"
90a56e
+ide1:0.present = "TRUE"
90a56e
+floppy0.startConnected = "FALSE"
90a56e
+floppy0.clientDevice = "TRUE"
90a56e
+floppy0.fileName = "vmware-null-remote-floppy"
90a56e
+ethernet0.virtualDev = "vmxnet3"
90a56e
+ethernet0.networkName = "VM Network"
90a56e
+ethernet0.addressType = "generated"
90a56e
+ethernet0.present = "TRUE"
90a56e
+scsi0:0.deviceType = "scsi-hardDisk"
90a56e
+scsi0:0.fileName = "BZ1308535_21disks.vmdk"
90a56e
+scsi0:0.present = "TRUE"
90a56e
+scsi0:1.deviceType = "scsi-hardDisk"
90a56e
+scsi0:1.fileName = "BZ1308535_21disks_1.vmdk"
90a56e
+scsi0:1.present = "TRUE"
90a56e
+scsi0:2.deviceType = "scsi-hardDisk"
90a56e
+scsi0:2.fileName = "BZ1308535_21disks_2.vmdk"
90a56e
+scsi0:2.present = "TRUE"
90a56e
+scsi0:3.deviceType = "scsi-hardDisk"
90a56e
+scsi0:3.fileName = "BZ1308535_21disks_3.vmdk"
90a56e
+scsi0:3.present = "TRUE"
90a56e
+scsi0:4.deviceType = "scsi-hardDisk"
90a56e
+scsi0:4.fileName = "BZ1308535_21disks_4.vmdk"
90a56e
+scsi0:4.present = "TRUE"
90a56e
+scsi0:5.deviceType = "scsi-hardDisk"
90a56e
+scsi0:5.fileName = "BZ1308535_21disks_5.vmdk"
90a56e
+scsi0:5.present = "TRUE"
90a56e
+scsi0:6.deviceType = "scsi-hardDisk"
90a56e
+scsi0:6.fileName = "BZ1308535_21disks_6.vmdk"
90a56e
+scsi0:6.present = "TRUE"
90a56e
+scsi0:8.deviceType = "scsi-hardDisk"
90a56e
+scsi0:8.fileName = "BZ1308535_21disks_7.vmdk"
90a56e
+scsi0:8.present = "TRUE"
90a56e
+scsi0:9.deviceType = "scsi-hardDisk"
90a56e
+scsi0:9.fileName = "BZ1308535_21disks_8.vmdk"
90a56e
+scsi0:9.present = "TRUE"
90a56e
+scsi0:10.deviceType = "scsi-hardDisk"
90a56e
+scsi0:10.fileName = "BZ1308535_21disks_9.vmdk"
90a56e
+scsi0:10.present = "TRUE"
90a56e
+scsi0:11.deviceType = "scsi-hardDisk"
90a56e
+scsi0:11.fileName = "BZ1308535_21disks_10.vmdk"
90a56e
+scsi0:11.present = "TRUE"
90a56e
+scsi0:12.deviceType = "scsi-hardDisk"
90a56e
+scsi0:12.fileName = "BZ1308535_21disks_11.vmdk"
90a56e
+scsi0:12.present = "TRUE"
90a56e
+scsi0:13.deviceType = "scsi-hardDisk"
90a56e
+scsi0:13.fileName = "BZ1308535_21disks_12.vmdk"
90a56e
+scsi0:13.present = "TRUE"
90a56e
+scsi0:14.deviceType = "scsi-hardDisk"
90a56e
+scsi0:14.fileName = "BZ1308535_21disks_13.vmdk"
90a56e
+scsi0:14.present = "TRUE"
90a56e
+scsi0:15.deviceType = "scsi-hardDisk"
90a56e
+scsi0:15.fileName = "BZ1308535_21disks_14.vmdk"
90a56e
+scsi0:15.present = "TRUE"
90a56e
+scsi1:0.deviceType = "scsi-hardDisk"
90a56e
+scsi1:0.fileName = "BZ1308535_21disks_15.vmdk"
90a56e
+scsi1:0.present = "TRUE"
90a56e
+scsi1:1.deviceType = "scsi-hardDisk"
90a56e
+scsi1:1.fileName = "BZ1308535_21disks_16.vmdk"
90a56e
+scsi1:1.present = "TRUE"
90a56e
+scsi1:2.deviceType = "scsi-hardDisk"
90a56e
+scsi1:2.fileName = "BZ1308535_21disks_17.vmdk"
90a56e
+scsi1:2.present = "TRUE"
90a56e
+scsi1:3.deviceType = "scsi-hardDisk"
90a56e
+scsi1:3.fileName = "BZ1308535_21disks_18.vmdk"
90a56e
+scsi1:3.present = "TRUE"
90a56e
+scsi1:4.deviceType = "scsi-hardDisk"
90a56e
+scsi1:4.fileName = "BZ1308535_21disks_19.vmdk"
90a56e
+scsi1:4.present = "TRUE"
90a56e
+scsi1:5.deviceType = "scsi-hardDisk"
90a56e
+scsi1:5.fileName = "BZ1308535_21disks_20.vmdk"
90a56e
+scsi1:5.present = "TRUE"
90a56e
+guestOS = "rhel6-64"
90a56e
+toolScripts.afterPowerOn = "TRUE"
90a56e
+toolScripts.afterResume = "TRUE"
90a56e
+toolScripts.beforeSuspend = "TRUE"
90a56e
+toolScripts.beforePowerOff = "TRUE"
90a56e
+uuid.bios = "56 4d 96 af e6 46 bd 86-5c 4d 65 4e 77 36 ef 31"
90a56e
+uuid.location = "56 4d 96 af e6 46 bd 86-5c 4d 65 4e 77 36 ef 31"
90a56e
+vc.uuid = "52 31 cb fc c1 3f 96 32-83 c0 bb 70 6c 90 5c fd"
90a56e
+chipset.onlineStandby = "FALSE"
90a56e
+sched.cpu.min = "0"
90a56e
+sched.cpu.shares = "normal"
90a56e
+sched.mem.min = "0"
90a56e
+sched.mem.minSize = "0"
90a56e
+sched.mem.shares = "normal"
90a56e
+svga.vramSize = "8388608"
90a56e
+sched.swap.derivedName = "/vmfs/volumes/5458b680-34ec3500-9f36-001320f5f6ca/BZ1308535_21disks/BZ1308535_21disks-6a024f8a.vswp"
90a56e
+replay.supported = "FALSE"
90a56e
+replay.filename = ""
90a56e
+scsi0:0.redo = ""
90a56e
+scsi0:1.redo = ""
90a56e
+scsi0:2.redo = ""
90a56e
+scsi0:3.redo = ""
90a56e
+scsi0:4.redo = ""
90a56e
+scsi0:5.redo = ""
90a56e
+scsi0:6.redo = ""
90a56e
+scsi0:8.redo = ""
90a56e
+scsi0:9.redo = ""
90a56e
+scsi0:10.redo = ""
90a56e
+scsi0:11.redo = ""
90a56e
+scsi0:12.redo = ""
90a56e
+scsi0:13.redo = ""
90a56e
+scsi0:14.redo = ""
90a56e
+scsi0:15.redo = ""
90a56e
+scsi1:0.redo = ""
90a56e
+scsi1:1.redo = ""
90a56e
+scsi1:2.redo = ""
90a56e
+scsi1:3.redo = ""
90a56e
+scsi1:4.redo = ""
90a56e
+scsi1:5.redo = ""
90a56e
+pciBridge0.pciSlotNumber = "17"
90a56e
+pciBridge4.pciSlotNumber = "21"
90a56e
+pciBridge5.pciSlotNumber = "22"
90a56e
+pciBridge6.pciSlotNumber = "23"
90a56e
+pciBridge7.pciSlotNumber = "24"
90a56e
+scsi0.pciSlotNumber = "160"
90a56e
+scsi1.pciSlotNumber = "192"
90a56e
+ethernet0.pciSlotNumber = "224"
90a56e
+vmci0.pciSlotNumber = "32"
90a56e
+scsi0.sasWWID = "50 05 05 6f e6 46 bd 80"
90a56e
+scsi1.sasWWID = "50 05 05 6f e6 46 bc 80"
90a56e
+ethernet0.generatedAddress = "00:0c:29:36:ef:31"
90a56e
+ethernet0.generatedAddressOffset = "0"
90a56e
+vmci0.id = "2000088881"
90a56e
+hostCPUID.0 = "0000000d756e65476c65746e49656e69"
90a56e
+hostCPUID.1 = "000206a700100800179ae3bfbfebfbff"
90a56e
+hostCPUID.80000001 = "00000000000000000000000128100800"
90a56e
+guestCPUID.0 = "0000000d756e65476c65746e49656e69"
90a56e
+guestCPUID.1 = "000206a700010800969822030fabfbff"
90a56e
+guestCPUID.80000001 = "00000000000000000000000128100800"
90a56e
+userCPUID.0 = "0000000d756e65476c65746e49656e69"
90a56e
+userCPUID.1 = "000206a700100800169822030fabfbff"
90a56e
+userCPUID.80000001 = "00000000000000000000000128100800"
90a56e
+evcCompatibilityMode = "FALSE"
90a56e
+vmotion.checkpointFBSize = "8388608"
90a56e
+cleanShutdown = "TRUE"
90a56e
+softPowerOff = "TRUE"
90a56e
+tools.remindInstall = "TRUE"
90a56e
diff --git a/v2v/test-v2v-i-vmx-2.expected b/v2v/test-v2v-i-vmx-2.expected
90a56e
new file mode 100644
a30de4
index 000000000..a04bd0f62
90a56e
--- /dev/null
90a56e
+++ b/v2v/test-v2v-i-vmx-2.expected
a30de4
@@ -0,0 +1,19 @@
90a56e
+[   0.0] Opening the source -i vmx test-v2v-i-vmx-2.vmx
90a56e
+Source guest information (--print-source option):
90a56e
+
90a56e
+    source name: Fedora 20
90a56e
+hypervisor type: vmware
90a56e
+         memory: 2147483648 (bytes)
90a56e
+       nr vCPUs: 1
90a56e
+   CPU features: 
90a56e
+       firmware: bios
90a56e
+        display: 
90a56e
+          video: vmvga
90a56e
+          sound: 
90a56e
+disks:
90a56e
+	/Fedora 20.vmdk (vmdk) [scsi]
90a56e
+removable media:
90a56e
+
90a56e
+NICs:
90a56e
+	Network "VM Network" mac: 00:50:56:9b:5f:0d [vmxnet3]
90a56e
+
90a56e
diff --git a/v2v/test-v2v-i-vmx-2.vmx b/v2v/test-v2v-i-vmx-2.vmx
90a56e
new file mode 100644
46ce2f
index 000000000..d9dcf3a5c
90a56e
--- /dev/null
90a56e
+++ b/v2v/test-v2v-i-vmx-2.vmx
90a56e
@@ -0,0 +1,84 @@
90a56e
+.encoding = "UTF-8"
90a56e
+config.version = "8"
90a56e
+virtualHW.version = "10"
90a56e
+nvram = "Fedora 20.nvram"
90a56e
+pciBridge0.present = "TRUE"
90a56e
+svga.present = "TRUE"
90a56e
+pciBridge4.present = "TRUE"
90a56e
+pciBridge4.virtualDev = "pcieRootPort"
90a56e
+pciBridge4.functions = "8"
90a56e
+pciBridge5.present = "TRUE"
90a56e
+pciBridge5.virtualDev = "pcieRootPort"
90a56e
+pciBridge5.functions = "8"
90a56e
+pciBridge6.present = "TRUE"
90a56e
+pciBridge6.virtualDev = "pcieRootPort"
90a56e
+pciBridge6.functions = "8"
90a56e
+pciBridge7.present = "TRUE"
90a56e
+pciBridge7.virtualDev = "pcieRootPort"
90a56e
+pciBridge7.functions = "8"
90a56e
+vmci0.present = "TRUE"
90a56e
+hpet0.present = "TRUE"
90a56e
+displayName = "Fedora 20"
90a56e
+extendedConfigFile = "Fedora 20.vmxf"
90a56e
+virtualHW.productCompatibility = "hosted"
90a56e
+svga.vramSize = "8388608"
90a56e
+memSize = "2048"
90a56e
+sched.cpu.units = "mhz"
90a56e
+sched.cpu.affinity = "all"
90a56e
+powerType.powerOff = "soft"
90a56e
+powerType.suspend = "hard"
90a56e
+powerType.reset = "soft"
90a56e
+scsi0.virtualDev = "pvscsi"
90a56e
+scsi0.present = "TRUE"
90a56e
+sata0.present = "TRUE"
90a56e
+scsi0:0.deviceType = "scsi-hardDisk"
90a56e
+scsi0:0.fileName = "Fedora 20.vmdk"
90a56e
+sched.scsi0:0.shares = "normal"
90a56e
+sched.scsi0:0.throughputCap = "off"
90a56e
+scsi0:0.present = "TRUE"
90a56e
+ethernet0.virtualDev = "vmxnet3"
90a56e
+ethernet0.networkName = "VM Network"
90a56e
+ethernet0.addressType = "vpx"
90a56e
+ethernet0.generatedAddress = "00:50:56:9b:5f:0d"
90a56e
+ethernet0.present = "TRUE"
90a56e
+sata0:0.startConnected = "FALSE"
90a56e
+sata0:0.deviceType = "cdrom-image"
90a56e
+sata0:0.fileName = "/vmfs/volumes/5458b680-34ec3500-9f36-001320f5f6ca/ISOs/Fedora-20-x86_64-netinst.iso"
90a56e
+sata0:0.present = "TRUE"
90a56e
+floppy0.startConnected = "FALSE"
90a56e
+floppy0.clientDevice = "TRUE"
90a56e
+floppy0.fileName = "vmware-null-remote-floppy"
90a56e
+vmci.filter.enable = "TRUE"
90a56e
+guestOS = "rhel7-64"
90a56e
+toolScripts.afterPowerOn = "TRUE"
90a56e
+toolScripts.afterResume = "TRUE"
90a56e
+toolScripts.beforeSuspend = "TRUE"
90a56e
+toolScripts.beforePowerOff = "TRUE"
90a56e
+uuid.bios = "42 1b 4b 87 e6 b7 d8 81-07 a0 c9 d2 21 cd 3c 6b"
90a56e
+vc.uuid = "50 1b 1f 1b 73 00 32 bf-93 a1 1c b2 b4 e6 17 d6"
90a56e
+sched.cpu.min = "0"
90a56e
+sched.cpu.shares = "normal"
90a56e
+sched.mem.min = "0"
90a56e
+sched.mem.minSize = "0"
90a56e
+sched.mem.shares = "normal"
90a56e
+sched.swap.derivedName = "/vmfs/volumes/5458b680-34ec3500-9f36-001320f5f6ca/Fedora 20/Fedora 20-c71e4118.vswp"
90a56e
+uuid.location = "56 4d 0f 53 00 63 d5 55-41 01 4c f7 55 ce 03 0e"
90a56e
+replay.supported = "TRUE"
90a56e
+replay.filename = ""
90a56e
+scsi0:0.redo = ""
90a56e
+pciBridge0.pciSlotNumber = "17"
90a56e
+pciBridge4.pciSlotNumber = "21"
90a56e
+pciBridge5.pciSlotNumber = "22"
90a56e
+pciBridge6.pciSlotNumber = "23"
90a56e
+pciBridge7.pciSlotNumber = "24"
90a56e
+scsi0.pciSlotNumber = "160"
90a56e
+ethernet0.pciSlotNumber = "192"
90a56e
+vmci0.pciSlotNumber = "32"
90a56e
+sata0.pciSlotNumber = "33"
90a56e
+scsi0.sasWWID = "50 05 05 67 e6 b7 d8 80"
90a56e
+vmci0.id = "567098475"
90a56e
+vmotion.checkpointFBSize = "8388608"
90a56e
+cleanShutdown = "TRUE"
90a56e
+softPowerOff = "TRUE"
90a56e
+sata0:0.allowGuestConnectionControl = "TRUE"
90a56e
+tools.syncTime = "FALSE"
90a56e
diff --git a/v2v/test-v2v-i-vmx-3.expected b/v2v/test-v2v-i-vmx-3.expected
90a56e
new file mode 100644
a30de4
index 000000000..64808a77b
90a56e
--- /dev/null
90a56e
+++ b/v2v/test-v2v-i-vmx-3.expected
a30de4
@@ -0,0 +1,19 @@
90a56e
+[   0.0] Opening the source -i vmx test-v2v-i-vmx-3.vmx
90a56e
+Source guest information (--print-source option):
90a56e
+
90a56e
+    source name: RHEL 7.1 UEFI
90a56e
+hypervisor type: vmware
90a56e
+         memory: 2147483648 (bytes)
90a56e
+       nr vCPUs: 1
90a56e
+   CPU features: 
90a56e
+       firmware: uefi
90a56e
+        display: 
90a56e
+          video: vmvga
90a56e
+          sound: 
90a56e
+disks:
90a56e
+	/RHEL 7.1 UEFI.vmdk (vmdk) [scsi]
90a56e
+removable media:
90a56e
+	CD-ROM [ide] in slot 2
90a56e
+NICs:
90a56e
+	Network "VM Network" mac: 00:0c:29:4b:2b:8c [vmxnet3]
90a56e
+
90a56e
diff --git a/v2v/test-v2v-i-vmx-3.vmx b/v2v/test-v2v-i-vmx-3.vmx
90a56e
new file mode 100644
46ce2f
index 000000000..c39215555
90a56e
--- /dev/null
90a56e
+++ b/v2v/test-v2v-i-vmx-3.vmx
90a56e
@@ -0,0 +1,91 @@
90a56e
+.encoding = "UTF-8"
90a56e
+config.version = "8"
90a56e
+virtualHW.version = "8"
90a56e
+nvram = "RHEL 7.1 UEFI.nvram"
90a56e
+pciBridge0.present = "TRUE"
90a56e
+svga.present = "TRUE"
90a56e
+pciBridge4.present = "TRUE"
90a56e
+pciBridge4.virtualDev = "pcieRootPort"
90a56e
+pciBridge4.functions = "8"
90a56e
+pciBridge5.present = "TRUE"
90a56e
+pciBridge5.virtualDev = "pcieRootPort"
90a56e
+pciBridge5.functions = "8"
90a56e
+pciBridge6.present = "TRUE"
90a56e
+pciBridge6.virtualDev = "pcieRootPort"
90a56e
+pciBridge6.functions = "8"
90a56e
+pciBridge7.present = "TRUE"
90a56e
+pciBridge7.virtualDev = "pcieRootPort"
90a56e
+pciBridge7.functions = "8"
90a56e
+vmci0.present = "TRUE"
90a56e
+hpet0.present = "TRUE"
90a56e
+displayName = "RHEL 7.1 UEFI"
90a56e
+extendedConfigFile = "RHEL 7.1 UEFI.vmxf"
90a56e
+virtualHW.productCompatibility = "hosted"
90a56e
+memSize = "2048"
90a56e
+firmware = "efi"
90a56e
+sched.cpu.units = "mhz"
90a56e
+powerType.powerOff = "soft"
90a56e
+powerType.suspend = "hard"
90a56e
+powerType.reset = "soft"
90a56e
+scsi0.virtualDev = "pvscsi"
90a56e
+scsi0.present = "TRUE"
90a56e
+ide1:0.startConnected = "FALSE"
90a56e
+ide1:0.deviceType = "cdrom-image"
90a56e
+ide1:0.fileName = "/vmfs/volumes/5458b680-34ec3500-9f36-001320f5f6ca/ISOs/RHEL-7.1-20150219.1-Server-x86_64-boot.iso"
90a56e
+ide1:0.present = "TRUE"
90a56e
+floppy0.startConnected = "FALSE"
90a56e
+floppy0.clientDevice = "TRUE"
90a56e
+floppy0.fileName = "vmware-null-remote-floppy"
90a56e
+ethernet0.virtualDev = "vmxnet3"
90a56e
+ethernet0.networkName = "VM Network"
90a56e
+ethernet0.addressType = "generated"
90a56e
+ethernet0.present = "TRUE"
90a56e
+scsi0:0.deviceType = "scsi-hardDisk"
90a56e
+scsi0:0.fileName = "RHEL 7.1 UEFI.vmdk"
90a56e
+scsi0:0.present = "TRUE"
90a56e
+guestOS = "rhel6-64"
90a56e
+toolScripts.afterPowerOn = "TRUE"
90a56e
+toolScripts.afterResume = "TRUE"
90a56e
+toolScripts.beforeSuspend = "TRUE"
90a56e
+toolScripts.beforePowerOff = "TRUE"
90a56e
+uuid.bios = "56 4d 99 89 a7 21 91 0d-cc 28 e2 db d5 4b 2b 8c"
90a56e
+uuid.location = "56 4d 99 89 a7 21 91 0d-cc 28 e2 db d5 4b 2b 8c"
90a56e
+vc.uuid = "52 3f 29 10 d3 81 16 43-fa b0 e3 af 3b ba 36 e5"
90a56e
+chipset.onlineStandby = "FALSE"
90a56e
+sched.cpu.min = "0"
90a56e
+sched.cpu.shares = "normal"
90a56e
+sched.mem.min = "0"
90a56e
+sched.mem.minSize = "0"
90a56e
+sched.mem.shares = "normal"
90a56e
+svga.vramSize = "8388608"
90a56e
+sched.swap.derivedName = "/vmfs/volumes/5458b680-34ec3500-9f36-001320f5f6ca/RHEL 7.1 UEFI/RHEL 7.1 UEFI-58ff6e6f.vswp"
90a56e
+replay.supported = "FALSE"
90a56e
+replay.filename = ""
90a56e
+scsi0:0.redo = ""
90a56e
+pciBridge0.pciSlotNumber = "17"
90a56e
+pciBridge4.pciSlotNumber = "21"
90a56e
+pciBridge5.pciSlotNumber = "22"
90a56e
+pciBridge6.pciSlotNumber = "23"
90a56e
+pciBridge7.pciSlotNumber = "24"
90a56e
+scsi0.pciSlotNumber = "160"
90a56e
+ethernet0.pciSlotNumber = "192"
90a56e
+vmci0.pciSlotNumber = "32"
90a56e
+scsi0.sasWWID = "50 05 05 69 a7 21 91 00"
90a56e
+ethernet0.generatedAddress = "00:0c:29:4b:2b:8c"
90a56e
+ethernet0.generatedAddressOffset = "0"
90a56e
+vmci0.id = "-716493940"
90a56e
+hostCPUID.0 = "0000000d756e65476c65746e49656e69"
90a56e
+hostCPUID.1 = "000206a700100800179ae3bfbfebfbff"
90a56e
+hostCPUID.80000001 = "00000000000000000000000128100800"
90a56e
+guestCPUID.0 = "0000000d756e65476c65746e49656e69"
90a56e
+guestCPUID.1 = "000206a700010800969822030fabfbff"
90a56e
+guestCPUID.80000001 = "00000000000000000000000128100800"
90a56e
+userCPUID.0 = "0000000d756e65476c65746e49656e69"
90a56e
+userCPUID.1 = "000206a700100800169822030fabfbff"
90a56e
+userCPUID.80000001 = "00000000000000000000000128100800"
90a56e
+evcCompatibilityMode = "FALSE"
90a56e
+vmotion.checkpointFBSize = "8388608"
90a56e
+cleanShutdown = "TRUE"
90a56e
+softPowerOff = "TRUE"
90a56e
+ide1:0.allowGuestConnectionControl = "TRUE"
90a56e
+tools.syncTime = "FALSE"
90a56e
diff --git a/v2v/test-v2v-i-vmx-4.expected b/v2v/test-v2v-i-vmx-4.expected
90a56e
new file mode 100644
a30de4
index 000000000..208920b29
90a56e
--- /dev/null
90a56e
+++ b/v2v/test-v2v-i-vmx-4.expected
a30de4
@@ -0,0 +1,19 @@
90a56e
+[   0.0] Opening the source -i vmx test-v2v-i-vmx-4.vmx
90a56e
+Source guest information (--print-source option):
90a56e
+
90a56e
+    source name: Windows 7 x64
90a56e
+hypervisor type: vmware
90a56e
+         memory: 2147483648 (bytes)
90a56e
+       nr vCPUs: 1
90a56e
+   CPU features: 
90a56e
+       firmware: bios
90a56e
+        display: 
90a56e
+          video: vmvga
90a56e
+          sound: 
90a56e
+disks:
90a56e
+	/Windows 7 x64.vmdk (vmdk) [scsi]
90a56e
+removable media:
90a56e
+	CD-ROM [ide] in slot 2
90a56e
+NICs:
90a56e
+	Network "VM Network" mac: 00:0c:29:94:89:23 [e1000]
90a56e
+
90a56e
diff --git a/v2v/test-v2v-i-vmx-4.vmx b/v2v/test-v2v-i-vmx-4.vmx
90a56e
new file mode 100644
46ce2f
index 000000000..7756cf248
90a56e
--- /dev/null
90a56e
+++ b/v2v/test-v2v-i-vmx-4.vmx
90a56e
@@ -0,0 +1,88 @@
90a56e
+.encoding = "UTF-8"
90a56e
+config.version = "8"
90a56e
+virtualHW.version = "8"
90a56e
+nvram = "Windows 7 x64.nvram"
90a56e
+pciBridge0.present = "TRUE"
90a56e
+svga.present = "TRUE"
90a56e
+pciBridge4.present = "TRUE"
90a56e
+pciBridge4.virtualDev = "pcieRootPort"
90a56e
+pciBridge4.functions = "8"
90a56e
+pciBridge5.present = "TRUE"
90a56e
+pciBridge5.virtualDev = "pcieRootPort"
90a56e
+pciBridge5.functions = "8"
90a56e
+pciBridge6.present = "TRUE"
90a56e
+pciBridge6.virtualDev = "pcieRootPort"
90a56e
+pciBridge6.functions = "8"
90a56e
+pciBridge7.present = "TRUE"
90a56e
+pciBridge7.virtualDev = "pcieRootPort"
90a56e
+pciBridge7.functions = "8"
90a56e
+vmci0.present = "TRUE"
90a56e
+hpet0.present = "TRUE"
90a56e
+displayName = "Windows 7 x64"
90a56e
+extendedConfigFile = "Windows 7 x64.vmxf"
90a56e
+virtualHW.productCompatibility = "hosted"
90a56e
+memSize = "2048"
90a56e
+sched.cpu.units = "mhz"
90a56e
+powerType.powerOff = "soft"
90a56e
+powerType.suspend = "hard"
90a56e
+powerType.reset = "soft"
90a56e
+scsi0.virtualDev = "lsisas1068"
90a56e
+scsi0.present = "TRUE"
90a56e
+ide1:0.deviceType = "cdrom-image"
90a56e
+ide1:0.fileName = "/vmfs/volumes/5458b680-34ec3500-9f36-001320f5f6ca/ISOs/en_windows_7_ultimate_with_sp1_x64_dvd_u_677332.iso"
90a56e
+ide1:0.present = "TRUE"
90a56e
+floppy0.startConnected = "FALSE"
90a56e
+floppy0.clientDevice = "TRUE"
90a56e
+floppy0.fileName = "vmware-null-remote-floppy"
90a56e
+ethernet0.virtualDev = "e1000"
90a56e
+ethernet0.networkName = "VM Network"
90a56e
+ethernet0.addressType = "generated"
90a56e
+ethernet0.present = "TRUE"
90a56e
+scsi0:0.deviceType = "scsi-hardDisk"
90a56e
+scsi0:0.fileName = "Windows 7 x64.vmdk"
90a56e
+scsi0:0.present = "TRUE"
90a56e
+guestOS = "windows7-64"
90a56e
+toolScripts.afterPowerOn = "TRUE"
90a56e
+toolScripts.afterResume = "TRUE"
90a56e
+toolScripts.beforeSuspend = "TRUE"
90a56e
+toolScripts.beforePowerOff = "TRUE"
90a56e
+uuid.bios = "56 4d 6f ca 63 a5 a8 3e-13 ec 73 89 1d 94 89 23"
90a56e
+uuid.location = "56 4d 6f ca 63 a5 a8 3e-13 ec 73 89 1d 94 89 23"
90a56e
+vc.uuid = "52 7a 63 e1 2c 2f 50 46-91 66 3a e8 fa f9 c4 65"
90a56e
+chipset.onlineStandby = "FALSE"
90a56e
+sched.cpu.min = "0"
90a56e
+sched.cpu.shares = "normal"
90a56e
+sched.mem.min = "0"
90a56e
+sched.mem.minSize = "0"
90a56e
+sched.mem.shares = "normal"
90a56e
+svga.vramSize = "8388608"
90a56e
+sched.swap.derivedName = "/vmfs/volumes/5458b680-34ec3500-9f36-001320f5f6ca/Windows 7 x64/Windows 7 x64-8e3b0929.vswp"
90a56e
+replay.supported = "FALSE"
90a56e
+replay.filename = ""
90a56e
+scsi0:0.redo = ""
90a56e
+pciBridge0.pciSlotNumber = "17"
90a56e
+pciBridge4.pciSlotNumber = "21"
90a56e
+pciBridge5.pciSlotNumber = "22"
90a56e
+pciBridge6.pciSlotNumber = "23"
90a56e
+pciBridge7.pciSlotNumber = "24"
90a56e
+scsi0.pciSlotNumber = "160"
90a56e
+ethernet0.pciSlotNumber = "32"
90a56e
+vmci0.pciSlotNumber = "33"
90a56e
+scsi0.sasWWID = "50 05 05 6a 63 a5 a8 30"
90a56e
+ethernet0.generatedAddress = "00:0c:29:94:89:23"
90a56e
+ethernet0.generatedAddressOffset = "0"
90a56e
+vmci0.id = "496273699"
90a56e
+hostCPUID.0 = "0000000b756e65476c65746e49656e69"
90a56e
+hostCPUID.1 = "000206c220200800029ee3ffbfebfbff"
90a56e
+hostCPUID.80000001 = "0000000000000000000000012c100800"
90a56e
+guestCPUID.0 = "0000000b756e65476c65746e49656e69"
90a56e
+guestCPUID.1 = "000206c200010800829822030fabfbff"
90a56e
+guestCPUID.80000001 = "00000000000000000000000128100800"
90a56e
+userCPUID.0 = "0000000b756e65476c65746e49656e69"
90a56e
+userCPUID.1 = "000206c220200800029822030fabfbff"
90a56e
+userCPUID.80000001 = "00000000000000000000000128100800"
90a56e
+evcCompatibilityMode = "FALSE"
90a56e
+vmotion.checkpointFBSize = "8388608"
90a56e
+cleanShutdown = "TRUE"
90a56e
+softPowerOff = "TRUE"
90a56e
+tools.remindInstall = "TRUE"
90a56e
diff --git a/v2v/test-v2v-i-vmx.sh b/v2v/test-v2v-i-vmx.sh
90a56e
new file mode 100755
46ce2f
index 000000000..5353e7e2a
90a56e
--- /dev/null
90a56e
+++ b/v2v/test-v2v-i-vmx.sh
90a56e
@@ -0,0 +1,48 @@
90a56e
+#!/bin/bash -
90a56e
+# libguestfs virt-v2v test script
90a56e
+# Copyright (C) 2017 Red Hat Inc.
90a56e
+#
90a56e
+# This program is free software; you can redistribute it and/or modify
90a56e
+# it under the terms of the GNU General Public License as published by
90a56e
+# the Free Software Foundation; either version 2 of the License, or
90a56e
+# (at your option) any later version.
90a56e
+#
90a56e
+# This program is distributed in the hope that it will be useful,
90a56e
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
90a56e
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
90a56e
+# GNU General Public License for more details.
90a56e
+#
90a56e
+# You should have received a copy of the GNU General Public License
90a56e
+# along with this program; if not, write to the Free Software
90a56e
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
90a56e
+
90a56e
+# Test -i ova option.
90a56e
+
90a56e
+set -e
90a56e
+
90a56e
+$TEST_FUNCTIONS
90a56e
+skip_if_skipped
90a56e
+skip_if_backend uml
90a56e
+
90a56e
+export VIRT_TOOLS_DATA_DIR="$top_srcdir/test-data/fake-virt-tools"
90a56e
+export VIRTIO_WIN="$top_srcdir/test-data/fake-virtio-win"
90a56e
+
90a56e
+rm -f test-v2v-i-vmx-*.actual
90a56e
+
90a56e
+for i in 1 2 3 4; do
90a56e
+    $VG virt-v2v --debug-gc \
90a56e
+        -i vmx test-v2v-i-vmx-$i.vmx \
90a56e
+        --print-source > test-v2v-i-vmx-$i.actual
90a56e
+
90a56e
+    # Normalize the print-source output.
90a56e
+    mv test-v2v-i-vmx-$i.actual test-v2v-i-vmx-$i.actual.old
90a56e
+    sed \
90a56e
+        -e "s,$(pwd),," \
90a56e
+        < test-v2v-i-vmx-$i.actual.old > test-v2v-i-vmx-$i.actual
90a56e
+    rm test-v2v-i-vmx-$i.actual.old
90a56e
+
90a56e
+    # Check the output.
90a56e
+    diff -u test-v2v-i-vmx-$i.expected test-v2v-i-vmx-$i.actual
90a56e
+done
90a56e
+
90a56e
+rm test-v2v-i-vmx-*.actual
90a56e
diff --git a/v2v/v2v_unit_tests.ml b/v2v/v2v_unit_tests.ml
a30de4
index 873610a7c..1b4332a9e 100644
90a56e
--- a/v2v/v2v_unit_tests.ml
90a56e
+++ b/v2v/v2v_unit_tests.ml
90a56e
@@ -787,6 +787,148 @@ let test_qemu_img_supports ctx =
90a56e
    *)
90a56e
   ignore (Utils.qemu_img_supports_offset_and_size ())
90a56e
 
90a56e
+(* Test the VMX file parser in the Parse_vmx module. *)
90a56e
+let test_vmx_parse_string ctx =
90a56e
+  let cmp = Parse_vmx.equal in
90a56e
+  let printer = Parse_vmx.to_string 0 in
90a56e
+
90a56e
+  (* This should be identical to the empty file. *)
90a56e
+  let t = Parse_vmx.parse_string "\
90a56e
+test.foo = \"a\"
90a56e
+test.bar = \"b\"
90a56e
+test.present = \"FALSE\"
90a56e
+" in
90a56e
+  assert_equal ~cmp ~printer Parse_vmx.empty t;
90a56e
+
90a56e
+  (* Test weird escapes. *)
90a56e
+  let t1 = Parse_vmx.parse_string "\
90a56e
+foo = \"a|20|21b\"
90a56e
+" in
90a56e
+  let t2 = Parse_vmx.parse_string "\
90a56e
+foo = \"a !b\"
90a56e
+" in
90a56e
+  assert_equal ~cmp ~printer t1 t2;
90a56e
+
90a56e
+  (* Test case insensitivity. *)
90a56e
+  let t1 = Parse_vmx.parse_string "\
90a56e
+foo = \"abc\"
90a56e
+" in
90a56e
+  let t2 = Parse_vmx.parse_string "\
90a56e
+fOO = \"abc\"
90a56e
+" in
90a56e
+  assert_equal ~cmp ~printer t1 t2;
90a56e
+  let t = Parse_vmx.parse_string "\
90a56e
+flag = \"true\"
90a56e
+" in
90a56e
+  assert_bool "parse_vmx: failed case insensitivity test for booleans #1"
90a56e
+              (Parse_vmx.get_bool t ["FLAG"] = Some true);
90a56e
+  let t = Parse_vmx.parse_string "\
90a56e
+flag = \"TRUE\"
90a56e
+" in
90a56e
+  assert_bool "parse_vmx: failed case insensitivity test for booleans #2"
90a56e
+              (Parse_vmx.get_bool t ["Flag"] = Some true);
90a56e
+
90a56e
+  (* Missing keys. *)
90a56e
+  let t = Parse_vmx.parse_string "\
90a56e
+foo = \"a\"
90a56e
+" in
90a56e
+  assert_bool "parse_vmx: failed missing key test"
90a56e
+              (Parse_vmx.get_string t ["bar"] = None);
90a56e
+
90a56e
+  (* namespace_present function *)
90a56e
+  let t = Parse_vmx.parse_string "\
90a56e
+foo.bar.present = \"TRUE\"
90a56e
+foo.baz.present = \"FALSE\"
90a56e
+foo.a.b = \"abc\"
90a56e
+foo.a.c = \"abc\"
90a56e
+foo.b = \"abc\"
90a56e
+foo.c.a = \"abc\"
90a56e
+foo.c.b = \"abc\"
90a56e
+" in
90a56e
+ assert_bool "parse_vmx: namespace_present #1"
90a56e
+             (Parse_vmx.namespace_present t ["foo"] = true);
90a56e
+ assert_bool "parse_vmx: namespace_present #2"
90a56e
+             (Parse_vmx.namespace_present t ["foo"; "bar"] = true);
90a56e
+ assert_bool "parse_vmx: namespace_present #3"
90a56e
+             (* this whole namespace should have been culled *)
90a56e
+             (Parse_vmx.namespace_present t ["foo"; "baz"] = false);
90a56e
+ assert_bool "parse_vmx: namespace_present #4"
90a56e
+             (Parse_vmx.namespace_present t ["foo"; "a"] = true);
90a56e
+ assert_bool "parse_vmx: namespace_present #5"
90a56e
+             (* this is a key, not a namespace *)
90a56e
+             (Parse_vmx.namespace_present t ["foo"; "a"; "b"] = false);
90a56e
+ assert_bool "parse_vmx: namespace_present #6"
90a56e
+             (Parse_vmx.namespace_present t ["foo"; "b"] = false);
90a56e
+ assert_bool "parse_vmx: namespace_present #7"
90a56e
+             (Parse_vmx.namespace_present t ["foo"; "c"] = true);
90a56e
+ assert_bool "parse_vmx: namespace_present #8"
90a56e
+             (Parse_vmx.namespace_present t ["foo"; "d"] = false);
90a56e
+
90a56e
+ (* map function *)
90a56e
+  let t = Parse_vmx.parse_string "\
90a56e
+foo.bar.present = \"TRUE\"
90a56e
+foo.baz.present = \"FALSE\"
90a56e
+foo.a.b = \"abc\"
90a56e
+foo.a.c = \"abc\"
90a56e
+foo.b = \"abc\"
90a56e
+foo.c.a = \"abc\"
90a56e
+foo.c.b = \"abc\"
90a56e
+" in
90a56e
+  let xs =
90a56e
+    Parse_vmx.map (
90a56e
+      fun path ->
90a56e
+        let path = String.concat "." path in
90a56e
+        function
90a56e
+        | None -> sprintf "%s.present = \"true\"\n" path
90a56e
+        | Some v -> sprintf "%s = \"%s\"\n" path v
90a56e
+    ) t in
90a56e
+  let xs = List.sort compare xs in
90a56e
+  let s = String.concat "" xs in
90a56e
+  assert_equal ~printer:identity "\
90a56e
+foo.a.b = \"abc\"
90a56e
+foo.a.c = \"abc\"
90a56e
+foo.a.present = \"true\"
90a56e
+foo.b = \"abc\"
90a56e
+foo.bar.present = \"TRUE\"
90a56e
+foo.bar.present = \"true\"
90a56e
+foo.c.a = \"abc\"
90a56e
+foo.c.b = \"abc\"
90a56e
+foo.c.present = \"true\"
90a56e
+foo.present = \"true\"
90a56e
+" s;
90a56e
+
90a56e
+  (* select_namespaces function *)
90a56e
+  let t1 = Parse_vmx.parse_string "\
90a56e
+foo.bar.present = \"TRUE\"
90a56e
+foo.a.b = \"abc\"
90a56e
+foo.a.c = \"abc\"
90a56e
+foo.b = \"abc\"
90a56e
+foo.c.a = \"abc\"
90a56e
+foo.c.b = \"abc\"
90a56e
+" in
90a56e
+  let t2 =
90a56e
+    Parse_vmx.select_namespaces
90a56e
+      (function ["foo"] -> true | _ -> false) t1 in
90a56e
+  assert_equal ~cmp ~printer t1 t2;
90a56e
+
90a56e
+  let t1 = Parse_vmx.parse_string "\
90a56e
+foo.bar.present = \"TRUE\"
90a56e
+foo.a.b = \"abc\"
90a56e
+foo.a.c = \"abc\"
90a56e
+foo.b = \"abc\"
90a56e
+foo.c.a = \"abc\"
90a56e
+foo.c.b = \"abc\"
90a56e
+foo.c.c.d.e.f = \"abc\"
90a56e
+" in
90a56e
+  let t1 =
90a56e
+    Parse_vmx.select_namespaces
90a56e
+      (function ["foo"; "a"] -> true | _ -> false) t1 in
90a56e
+  let t2 = Parse_vmx.parse_string "\
90a56e
+foo.a.b = \"abc\"
90a56e
+foo.a.c = \"abc\"
90a56e
+" in
90a56e
+  assert_equal ~cmp ~printer t2 t1
90a56e
+
90a56e
 (* Suites declaration. *)
90a56e
 let suite =
90a56e
   "virt-v2v" >:::
90a56e
@@ -798,6 +940,7 @@ let suite =
90a56e
         test_virtio_iso_path_matches_guest_os;
90a56e
       "Utils.shell_unquote" >:: test_shell_unquote;
90a56e
       "Utils.qemu_img_supports" >:: test_qemu_img_supports;
90a56e
+      "Parse_vmx.parse_string" >::test_vmx_parse_string;
90a56e
     ]
90a56e
 
90a56e
 let () =
90a56e
diff --git a/v2v/virt-v2v.pod b/v2v/virt-v2v.pod
a30de4
index 709075fba..7ed5c5d86 100644
90a56e
--- a/v2v/virt-v2v.pod
90a56e
+++ b/v2v/virt-v2v.pod
90a56e
@@ -41,7 +41,8 @@ libguestfs E<ge> 1.28.
90a56e
  ... ───▶│  (default) │   │            │ ──┐ └────────────┘
90a56e
          └────────────┘   │            │ ─┐└──────▶ -o glance
90a56e
  -i libvirtxml ─────────▶ │            │ ┐└─────────▶ -o rhv
90a56e
-                          └────────────┘ └──────────▶ -o vdsm
90a56e
+ -i vmx ────────────────▶ │            │ └──────────▶ -o vdsm
90a56e
+                          └────────────┘
90a56e
 
90a56e
 Virt-v2v has a number of possible input and output modes, selected
90a56e
 using the I<-i> and I<-o> options.  Only one input and output mode can
90a56e
@@ -60,6 +61,8 @@ method used by L<virt-p2v(1)> behind the scenes.
90a56e
 
90a56e
 I<-i ova> is used for reading from a VMware ova source file.
90a56e
 
90a56e
+I<-i vmx> is used for reading from a VMware vmx file.
90a56e
+
90a56e
 I<-o glance> is used for writing to OpenStack Glance.
90a56e
 
90a56e
 I<-o libvirt> is used for writing to any libvirt target.  Libvirt can
a30de4
@@ -228,6 +231,14 @@ ova manifest file and check the vmdk volumes for validity (checksums)
90a56e
 as well as analyzing the ovf file, and then convert the guest.  See
90a56e
 L</INPUT FROM VMWARE OVA> below
90a56e
 
90a56e
+=item B<-i> B<vmx>
90a56e
+
90a56e
+Set the input method to I<vmx>.
90a56e
+
90a56e
+In this mode you can read a VMware vmx file directly.  This is useful
90a56e
+when VMware VMs are stored on an NFS server which you can mount
90a56e
+directly.  See L</INPUT FROM VMWARE VMX> below
90a56e
+
90a56e
 =item B<-ic> libvirtURI
90a56e
 
90a56e
 Specify a libvirt connection URI to use when reading the guest.  This
a30de4
@@ -859,9 +870,10 @@ I<--bridge> option instead.  For example:
90a56e
 
90a56e
 Virt-v2v is able to import guests from VMware vCenter Server.
90a56e
 
90a56e
-vCenter E<ge> 5.0 is required.  If you don't have vCenter, using OVA
90a56e
-is recommended instead (see L</INPUT FROM VMWARE OVA> below), or if
90a56e
-that is not possible then see L</INPUT FROM VMWARE ESXi HYPERVISOR>.
90a56e
+vCenter E<ge> 5.0 is required.  If you don’t have vCenter, using OVA
90a56e
+or VMX is recommended instead (see L</INPUT FROM VMWARE OVA> and/or
90a56e
+L</INPUT FROM VMWARE VMX> below), or if that is not possible then see
90a56e
+L</INPUT FROM VMWARE ESXi HYPERVISOR>.
90a56e
 
90a56e
 Virt-v2v uses libvirt for access to vCenter, and therefore the input
90a56e
 mode should be I<-i libvirt>.  As this is the default, you don't need
a30de4
@@ -1132,12 +1144,58 @@ directory containing the files:
90a56e
 
90a56e
  $ virt-v2v -i ova /path/to/files -o local -os /var/tmp
90a56e
 
90a56e
+=head1 INPUT FROM VMWARE VMX
90a56e
+
90a56e
+Virt-v2v is able to import guests from VMware’s vmx files.  This is
90a56e
+useful where VMware virtual machines are stored on a separate NFS
90a56e
+server and you are able to mount the NFS storage directly.
90a56e
+
90a56e
+If you find a folder of files called F<I<guest>.vmx>,
90a56e
+F<I<guest>.vmxf>, F<I<guest>.nvram> and one or more F<.vmdk> disk
90a56e
+images, then you can use this method.
90a56e
+
90a56e
+=head2 VMX: REMOVE VMWARE TOOLS FROM WINDOWS GUESTS
90a56e
+
90a56e
+For Windows guests, you should remove VMware tools before conversion.
90a56e
+Although this is not strictly necessary, and the guest will still be
90a56e
+able to run, if you don't do this then the converted guest will
90a56e
+complain on every boot.  The tools cannot be removed after conversion
90a56e
+because the uninstaller checks if it is running on VMware and refuses
90a56e
+to start (which is also the reason that virt-v2v cannot remove them).
90a56e
+
90a56e
+This is not necessary for Linux guests, as virt-v2v is able to remove
90a56e
+VMware tools.
90a56e
+
90a56e
+=head2 VMX: GUEST MUST BE SHUT DOWN
90a56e
+
90a56e
+B<The guest must be shut down before conversion starts>.  If you don't
90a56e
+shut it down, you will end up with a corrupted VM disk on the target.
90a56e
+With other methods, virt-v2v tries to prevent concurrent access, but
90a56e
+because the I<-i vmx> method works directly against the storage,
90a56e
+checking for concurrent access is not possible.
90a56e
+
90a56e
+=head2 VMX: MOUNT THE NFS STORAGE ON THE CONVERSION SERVER
90a56e
+
90a56e
+Virt-v2v must be able to access the F<.vmx> file and any local
90a56e
+F<.vmdk> disks.  Normally this means you must mount the NFS storage
90a56e
+containing these files.
90a56e
+
90a56e
+=head2 VMX: IMPORTING A GUEST
90a56e
+
90a56e
+To import a vmx file, do:
90a56e
+
90a56e
+ $ virt-v2v -i vmx guest.vmx -o local -os /var/tmp
90a56e
+
90a56e
+Virt-v2v processes the vmx file and uses it to find the location of
90a56e
+any vmdk disks.
90a56e
+
90a56e
 =head1 INPUT FROM VMWARE ESXi HYPERVISOR
90a56e
 
90a56e
 Virt-v2v cannot access an ESXi hypervisor directly.  You should use
90a56e
-the OVA method above (see L</INPUT FROM VMWARE OVA>) if possible, as
90a56e
-it is much faster and requires much less disk space than the method
90a56e
-described in this section.
90a56e
+the OVA or VMX methods above (see L</INPUT FROM VMWARE OVA> and/or
90a56e
+L</INPUT FROM VMWARE VMX>) if possible, as it is much faster and
90a56e
+requires much less disk space than the method described in this
90a56e
+section.
90a56e
 
90a56e
 You can use the L<virt-v2v-copy-to-local(1)> tool to copy the guest
90a56e
 off the hypervisor into a local file, and then convert it.
90a56e
-- 
a30de4
2.14.3
90a56e