v2v: -o qemu: Refactor generation of the qemu shell script.

This change refactors how the qemu shell script is generated, making
it a bit less likely that we'll get the quoting wrong.  Parameters are
now added to a list, unquoted, and all quoting is done when printing
out the list.

Checked by running virt-v2v -o qemu before and after the change and
manually comparing the output (which is not identical, but still
correct).
This commit is contained in:
Richard W.M. Jones
2017-02-26 12:17:14 +00:00
parent 5982b9f7de
commit f6ece2c01a
6 changed files with 244 additions and 65 deletions

View File

@@ -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

View File

@@ -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. *)

View File

@@ -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 \

View File

@@ -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;

101
v2v/qemu_command.ml Normal file
View File

@@ -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

55
v2v/qemu_command.mli Normal file
View File

@@ -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-<arch>],
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. *)