diff --git a/mllib/common_utils.ml b/mllib/common_utils.ml index 25136bda0..e1d63292e 100644 --- a/mllib/common_utils.ml +++ b/mllib/common_utils.ml @@ -59,6 +59,17 @@ module Char = struct | 'a'..'f' -> true | 'A'..'F' -> true | _ -> false + + let isalpha = function + | 'a'..'z' -> true + | 'A'..'Z' -> true + | _ -> false + + let isalnum = function + | '0'..'9' -> true + | 'a'..'z' -> true + | 'A'..'Z' -> true + | _ -> false end module String = struct diff --git a/mllib/common_utils.mli b/mllib/common_utils.mli index 2beb3bb2b..1cd38ba83 100644 --- a/mllib/common_utils.mli +++ b/mllib/common_utils.mli @@ -39,6 +39,10 @@ module Char : sig (** Return true if the character is a digit [[0-9]]. *) val isxdigit : char -> bool (** Return true if the character is a hex digit [[0-9a-fA-F]]. *) + val isalpha : char -> bool + (** Return true if the character is a US ASCII 7 bit alphabetic. *) + val isalnum : char -> bool + (** Return true if the character is a US ASCII 7 bit alphanumeric. *) end (** Override the Char module from stdlib. *) diff --git a/v2v/Makefile.am b/v2v/Makefile.am index 43a090ce8..55f7e7012 100644 --- a/v2v/Makefile.am +++ b/v2v/Makefile.am @@ -54,6 +54,7 @@ SOURCES_MLI = \ output_vdsm.mli \ OVF.mli \ parse_libvirt_xml.mli \ + qemu_command.mli \ target_bus_assignment.mli \ types.mli \ uefi.mli \ @@ -79,6 +80,7 @@ SOURCES_ML = \ input_disk.ml \ parse_libvirt_xml.ml \ create_libvirt_xml.ml \ + qemu_command.ml \ input_libvirtxml.ml \ input_libvirt_other.ml \ input_libvirt_vcenter_https.ml \ diff --git a/v2v/output_qemu.ml b/v2v/output_qemu.ml index 25ed28b3b..3e28ad0dc 100644 --- a/v2v/output_qemu.ml +++ b/v2v/output_qemu.ml @@ -69,61 +69,51 @@ object let machine_q35 = secure_boot_required in let smm = secure_boot_required in - let chan = open_out file in + (* Construct the command line. Note that the [Qemu_command] + * module deals with shell and qemu comma quoting. + *) + let cmd = Qemu_command.create ~arch:guestcaps.gcaps_arch () in + let flag = Qemu_command.flag cmd + and arg = Qemu_command.arg cmd + and arg_noquote = Qemu_command.arg_noquote cmd + and commas = Qemu_command.commas cmd in - let fpf fs = fprintf chan fs in - let nl = " \\\n\t" in - fpf "#!/bin/sh -\n"; - fpf "\n"; - - (match uefi_firmware with - | None -> () - | Some { Uefi.vars = vars_template } -> - fpf "# Make a copy of the UEFI variables template\n"; - fpf "uefi_vars=\"$(mktemp)\"\n"; - fpf "cp %s \"$uefi_vars\"\n" (quote vars_template); - fpf "\n" - ); - - fpf "qemu-system-%s" guestcaps.gcaps_arch; - fpf "%s-no-user-config -nodefaults" nl; - fpf "%s-name %s" nl (quote source.s_name); - fpf "%s-machine %s%saccel=kvm:tcg" nl - (if machine_q35 then "q35," else "") - (if smm then "smm=on," else ""); + flag "-no-user-config"; flag "-nodefaults"; + arg "-name" source.s_name; + commas "-machine" (if machine_q35 then ["q35"] else [] @ + if smm then ["smm=on"] else [] @ + ["accel=kvm:tcg"]); (match uefi_firmware with | None -> () | Some { Uefi.code = code } -> if secure_boot_required then - fpf "%s-global driver=cfi.pflash01,property=secure,value=on" nl; - fpf "%s-drive if=pflash,format=raw,file=%s,readonly" nl (quote code); - fpf "%s-drive if=pflash,format=raw,file=\"$uefi_vars\"" nl + commas "-global" + ["driver=cfi.pflash01"; "property=secure"; "value=on"]; + commas "-drive" + ["if=pflash"; "format=raw"; "file=" ^ code; "readonly"]; + arg_noquote "-drive" "if=pflash,format=raw,file=\"$uefi_vars\""; ); - fpf "%s-m %Ld" nl (source.s_memory /^ 1024L /^ 1024L); + arg "-m" (Int64.to_string (source.s_memory /^ 1024L /^ 1024L)); if source.s_vcpu > 1 then - fpf "%s-smp %d" nl source.s_vcpu; + arg "-smp" (string_of_int source.s_vcpu); let make_disk if_name i = function | BusSlotEmpty -> () | BusSlotTarget t -> - let qemu_quoted_filename = String.replace t.target_file "," ",," in - let drive_param = - sprintf "file=%s,format=%s,if=%s,index=%d,media=disk" - qemu_quoted_filename t.target_format if_name i in - fpf "%s-drive %s" nl (quote drive_param) + commas "-drive" ["file=" ^ t.target_file; "format=" ^ t.target_format; + "if=" ^ if_name; "index=" ^ string_of_int i; + "media=disk"] | BusSlotRemovable { s_removable_type = CDROM } -> - let drive_param = - sprintf "format=raw,if=%s,index=%d,media=cdrom" if_name i in - fpf "%s-drive %s" nl (quote drive_param) + commas "-drive" ["format=raw"; "if=" ^ if_name; + "index=" ^ string_of_int i; "media=cdrom"] | BusSlotRemovable { s_removable_type = Floppy } -> - let drive_param = - sprintf "format=raw,if=%s,index=%d,media=floppy" if_name i in - fpf "%s-drive %s" nl (quote drive_param) + commas "-drive" ["format=raw"; "if=" ^ if_name; + "index=" ^ string_of_int i; "media=floppy"] in Array.iteri (make_disk "virtio") target_buses.target_virtio_blk_bus; Array.iteri (make_disk "ide") target_buses.target_ide_bus; @@ -132,21 +122,17 @@ object | BusSlotEmpty -> () | BusSlotTarget t -> - let qemu_quoted_filename = String.replace t.target_file "," ",," in - let drive_param = - sprintf "file=%s,format=%s,if=scsi,bus=0,unit=%d,media=disk" - qemu_quoted_filename t.target_format i in - fpf "%s-drive %s" nl (quote drive_param) + commas "-drive" ["file=" ^ t.target_file; "format=" ^ t.target_format; + "if=scsi"; "bus=0"; "unit=" ^ string_of_int i; + "media=disk"] | BusSlotRemovable { s_removable_type = CDROM } -> - let drive_param = - sprintf "format=raw,if=scsi,bus=0,unit=%d,media=cdrom" i in - fpf "%s-drive %s" nl (quote drive_param) + commas "-drive" ["format=raw"; "if=scsi"; "bus=0"; + "unit=" ^ string_of_int i; "media=cdrom"] | BusSlotRemovable { s_removable_type = Floppy } -> - let drive_param = - sprintf "format=raw,if=scsi,bus=0,unit=%d,media=floppy" i in - fpf "%s-drive %s" nl (quote drive_param) + commas "-drive" ["format=raw"; "if=scsi"; "bus=0"; + "unit=" ^ string_of_int i; "media=floppy"] in Array.iteri make_scsi target_buses.target_scsi_bus; @@ -161,9 +147,12 @@ object | RTL8139 -> "rtl8139" in iteri ( fun i nic -> - fpf "%s-netdev user,id=net%d" nl i; - fpf "%s-device %s,netdev=net%d%s" nl - net_bus i (match nic.s_mac with None -> "" | Some mac -> ",mac=" ^ mac) + commas "-netdev" ["user"; "id=net" ^ string_of_int i]; + commas "-device" [net_bus; + sprintf "netdev=net%d%s" i + (match nic.s_mac with + | None -> "" + | Some mac -> "mac=" ^ mac)] ) source.s_nics; (* Add a display. *) @@ -172,15 +161,18 @@ object | Some display -> (match display.s_display_type with | Window -> - fpf "%s-display gtk" nl + arg "-display" "gtk" | VNC -> - fpf "%s-display vnc=:0" nl + arg "-display" "vnc=:0" | Spice -> - fpf "%s-spice port=%d,addr=127.0.0.1" nl - (match display.s_port with None -> 5900 | Some p -> p) + commas "-spice" [sprintf "port=%d" + (match display.s_port with + | None -> 5900 + | Some p -> p); + "addr=127.0.0.1"] ); - fpf "%s-vga %s" nl - (match guestcaps.gcaps_video with Cirrus -> "cirrus" | QXL -> "qxl") + arg "-vga" + (match guestcaps.gcaps_video with Cirrus -> "cirrus" | QXL -> "qxl") ); (* Add a sound card. *) @@ -189,22 +181,36 @@ object | Some { s_sound_model = model } -> if qemu_supports_sound_card model then ( match model with - | AC97 -> fpf "%s-device AC97" nl - | ES1370 -> fpf "%s-device ES1370" nl - | ICH6 -> fpf "%s-device intel-hda -device hda-duplex" nl - | ICH9 -> fpf "%s-device ich9-intel-hda" nl - | PCSpeaker -> fpf "%s-soundhw pcspk" nl (* not qdev-ified *) - | SB16 -> fpf "%s-device sb16" nl - | USBAudio -> fpf "%s-device usb-audio" nl + | AC97 -> arg "-device" "AC97" + | ES1370 -> arg "-device" "ES1370" + | ICH6 -> arg "-device" "intel-hda -device hda-duplex" + | ICH9 -> arg "-device" "ich9-intel-hda" + | PCSpeaker -> arg "-soundhw" "pcspk" (* not qdev-ified *) + | SB16 -> arg "-device" "sb16" + | USBAudio -> arg "-device" "usb-audio" ) ); (* Add a serial console to Linux guests. *) if inspect.i_type = "linux" then - fpf "%s-serial stdio" nl; + arg "-serial" "stdio"; + (* Write the output file. *) + let chan = open_out file in + let fpf fs = fprintf chan fs in + fpf "#!/bin/sh -\n"; fpf "\n"; + (match uefi_firmware with + | None -> () + | Some { Uefi.vars = vars_template } -> + fpf "# Make a copy of the UEFI variables template\n"; + fpf "uefi_vars=\"$(mktemp)\"\n"; + fpf "cp %s \"$uefi_vars\"\n" (quote vars_template); + fpf "\n" + ); + + Qemu_command.to_chan cmd chan; close_out chan; Unix.chmod file 0o755; diff --git a/v2v/qemu_command.ml b/v2v/qemu_command.ml new file mode 100644 index 000000000..ccdda8ad4 --- /dev/null +++ b/v2v/qemu_command.ml @@ -0,0 +1,101 @@ +(* virt-v2v + * Copyright (C) 2009-2017 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. + *) + +(** Generate a qemu command line, dealing with quoting. *) + +open Printf + +open Common_utils + +type t = { + cmd : string; + mutable args : arg list; (* list constructed in reverse order *) +} +and arg = + | Flag of string + | Arg of string * string * bool + | Commas of string * string list + +let create ?(arch = "x86_64") () = + { cmd = "qemu-system-" ^ arch; args = [] } + +let flag t k = + assert (String.is_prefix k "-"); + t.args <- Flag k :: t.args + +let arg t k v = + assert (String.is_prefix k "-"); + t.args <- Arg (k, v, true) :: t.args + +let arg_noquote t k v = + assert (String.is_prefix k "-"); + t.args <- Arg (k, v, false) :: t.args + +let commas t k vs = + assert (String.is_prefix k "-"); + List.iter (fun v -> assert (v <> "")) vs; + t.args <- Commas (k, vs) :: t.args + +let nl = " \\\n\t" + +(* If the value contains only simple characters then it doesn't + * need quoting. This keeps the output as similar as possible + * to the old code. + *) +let do_quoting str = + let len = String.length str in + let ret = ref false in + for i = 0 to len-1 do + let c = String.unsafe_get str i in + if not (Char.isalnum c) && + c <> '.' && c <> '-' && c <> '_' && + c <> '=' && c <> ',' && c <> ':' && c <> '/' + then + ret := true + done; + !ret + +let print_quoted_param chan k v = + if not (do_quoting v) then + fprintf chan "%s%s %s" nl k v + else + fprintf chan "%s%s %s" nl k (quote v) + +let to_chan t chan = + fprintf chan "%s" t.cmd; + List.iter ( + function + | Flag k -> + fprintf chan "%s%s" nl k + | Arg (k, v, true) -> + print_quoted_param chan k v + | Arg (k, v, false) -> + fprintf chan "%s%s %s" nl k v + | Commas (k, vs) -> + let vs = List.map (fun s -> String.replace s "," ",,") vs in + let v = String.concat "," vs in + print_quoted_param chan k v + ) (List.rev t.args); + fprintf chan "\n" + +let to_script t filename = + let chan = open_out filename in + fprintf chan "#!/bin/sh -\n"; + to_chan t chan; + close_out chan; + Unix.chmod filename 0o755 diff --git a/v2v/qemu_command.mli b/v2v/qemu_command.mli new file mode 100644 index 000000000..d993bd208 --- /dev/null +++ b/v2v/qemu_command.mli @@ -0,0 +1,55 @@ +(* virt-v2v + * Copyright (C) 2009-2017 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. + *) + +(** Generate a qemu command line, dealing with quoting. *) + +type t + +val create : ?arch:string -> unit -> t +(** Create an empty qemu command. If the optional [?arch] parameter + is supplied then the command will be [qemu-system-], + otherwise it will be [qemu-system-x86_64]. *) + +val flag : t -> string -> unit +(** [flag t "-foo"] adds a parameter to the command line with no argument. *) + +val arg : t -> string -> string -> unit +(** [arg t "-m" "1024"] adds [-m 1024] to the command line. + + The value will shell-quoted if required, so you do not need to quote + the string. However if the value is a comma-separated list + (eg. [-drive file=foo,if=ide]) then do {b not} use this function, call + {!commas} instead. *) + +val arg_noquote : t -> string -> string -> unit +(** Like {!arg} except no quoting is done on the value. *) + +val commas : t -> string -> string list -> unit +(** [commas t "-drive" ["file=foo"; "if=ide"]] adds a comma-separated + list of parameters to the command line [-drive file=foo,if=ide]. + + This does both qemu comma-quoting and shell-quoting as required. *) + +val to_script : t -> string -> unit +(** [to_script t "./file.sh"] writes the resulting command line to + a file. The file begins with [#!/bin/sh] and is chmod 0755. *) + +val to_chan : t -> out_channel -> unit +(** [to_chan t chan] appends the resulting command line to + an output channel. The caller must write [!#/bin/sh] and chmod 0755 + the output file, if needed. *)