1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
|
(* helper-v2v-input
* Copyright (C) 2009-2021 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 Common_gettext.Gettext
open Types
open Utils
open Name_from_disk
open Input
module OVA = struct
let to_string options args = String.concat " " ("-i ova" :: args)
let query_input_options () =
printf (f_"No input options can be used in this mode.\n")
(* RHBZ#1570407: VMware-generated OVA files found in the wild can
* contain hrefs referencing snapshots. The href will be something
* like: <File href="disk1.vmdk"/> but the actual disk will be a
* snapshot called something like "disk1.vmdk.000000000".
*)
let re_snapshot = PCRE.compile "\\.(\\d+)$"
let rec setup dir options args =
if options.input_options <> [] then
error (f_"no -io (input options) are allowed here");
let ova =
match args with
| [ova] -> ova
| _ ->
error (f_"-i ova: expecting an OVA file name on the command line") in
(* Check the OVA file is readable. *)
access ova [R_OK];
(* Extract ova file. *)
let ova_t = OVA.parse_ova ova in
(* Extract ovf file from ova. *)
let ovf = OVA.get_ovf_file ova_t in
(* Extract the manifest from *.mf files in the ova. *)
let manifest = OVA.get_manifest ova_t in
(* Verify checksums of files listed in the manifest. *)
List.iter (
fun (file_ref, csum) ->
let filename, r =
match file_ref with
| OVA.LocalFile filename ->
filename, Checksums.verify_checksum csum filename
| OVA.TarFile (tar, filename) ->
filename, Checksums.verify_checksum csum ~tar filename in
match r with
| Checksums.Good_checksum -> ()
| Checksums.Mismatched_checksum (_, actual) ->
error (f_"-i ova: corrupt OVA: checksum of disk %s does not match \
manifest (actual = %s, expected = %s)")
filename actual (Checksums.string_of_csum_t csum)
| Checksums.Missing_file ->
(* RHBZ#1570407: Some OVA files generated by VMware
* reference non-existent components in the *.mf file.
* Generate a warning and ignore it.
*)
warning (f_"manifest has a checksum for non-existent file %s \
(ignored)")
filename
) manifest;
(* Parse the ovf file. *)
let name, memory, vcpu, cpu_topology, firmware, disks, removables, nics =
OVF.parse_ovf_from_ova ovf in
let name =
match name with
| None ->
warning (f_"could not parse ovf:Name from OVF document");
name_from_disk ova
| Some name -> name in
(* Convert the disk hrefs into qemu URIs. *)
let qemu_uris =
List.map (
fun { OVF.href; compressed } ->
let file_ref = find_file_or_snapshot ova_t href manifest in
match compressed, file_ref with
| false, OVA.LocalFile filename ->
filename
| true, OVA.LocalFile filename ->
(* The spec allows the file to be gzip-compressed, in
* which case we must uncompress it into a temporary.
*)
let new_filename =
Filename.temp_file ~temp_dir:Utils.large_tmpdir
"ova" ".vmdk" in
On_exit.unlink new_filename;
let cmd =
sprintf "zcat %s > %s"
(quote filename) (quote new_filename) in
if shell_command cmd <> 0 then
error (f_"error uncompressing %s, see earlier error messages")
filename;
new_filename
| false, OVA.TarFile (tar, filename) ->
(* This is the tar optimization. *)
let offset, size =
try OVA.get_tar_offet_and_size tar filename
with
| Not_found ->
error (f_"file ā%sā not found in the ova") filename
| Failure msg -> error (f_"%s") msg in
(* QEMU requires size aligned to 512 bytes. This is safe because
* tar also works with 512 byte blocks.
*)
let size = roundup64 size 512L in
(* Workaround for libvirt bug RHBZ#1431652. *)
let tar_path = absolute_path tar in
let doc = [
"file", JSON.Dict [
"driver", JSON.String "raw";
"offset", JSON.Int offset;
"size", JSON.Int size;
"file", JSON.Dict [
"driver", JSON.String "file";
"filename", JSON.String tar_path]
]
] in
let uri =
sprintf "json:%s"
(JSON.string_of_doc ~fmt:JSON.Compact doc) in
uri
| true, OVA.TarFile _ ->
(* This should not happen since {!OVA} knows that
* qemu cannot handle compressed files here.
*)
assert false
) disks in
(* Create the source metadata. *)
let s_disks = List.map (fun { OVF.source_disk } -> source_disk) disks in
let source = {
s_hypervisor = VMware;
s_name = name;
s_genid = None; (* XXX *)
s_memory = memory;
s_vcpu = vcpu;
s_cpu_vendor = None;
s_cpu_model = None;
s_cpu_topology = cpu_topology;
s_features = []; (* XXX *)
s_firmware = firmware;
s_display = None; (* XXX *)
s_sound = None;
s_disks = s_disks;
s_removables = removables;
s_nics = nics;
} in
(* Run qemu-nbd for each disk. *)
List.iteri (
fun i qemu_uri ->
let socket = sprintf "%s/in%d" dir i in
On_exit.unlink socket;
let cmd = QemuNBD.create qemu_uri in
QemuNBD.set_snapshot cmd options.read_only; (* protective overlay *)
QemuNBD.set_format cmd None; (* auto-detect format *)
let _, pid = QemuNBD.run_unix socket cmd in
On_exit.kill pid
) qemu_uris;
source
and find_file_or_snapshot ova_t href manifest =
match OVA.resolve_href ova_t href with
| Some f -> f
| None ->
(* Find all files in the OVA called [<href>.\d+] *)
let files = OVA.get_file_list ova_t in
let snapshots =
List.filter_map (
function
| OVA.LocalFile filename ->
get_snapshot_if_matches href filename
| OVA.TarFile (_, filename) ->
get_snapshot_if_matches href filename
) files in
(* Pick highest. *)
let snapshots = List.sort (fun a b -> compare b a) snapshots in
match snapshots with
| [] -> error_missing_href href
| snapshot::_ ->
let href = sprintf "%s.%s" href snapshot in
match OVA.resolve_href ova_t href with
| None -> error_missing_href href
| Some f -> f
(* If [filename] matches [<href>.\d+] then return [Some snapshot]. *)
and get_snapshot_if_matches href filename =
if PCRE.matches re_snapshot filename then (
let snapshot = PCRE.sub 1 in
if String.is_suffix filename (sprintf "%s.%s" href snapshot) then
Some snapshot
else
None
)
else None
and error_missing_href href =
error (f_"-i ova: OVF references file ā%sā which was not found \
in the OVA archive") href
end
|