On 11/18/2015 01:03 AM, Richard W.M. Jones wrote:
This simple parser has (limited) understanding of the Windows
'*.inf'
file format. This is a Windows config file with some peculiarities.
This commit also has a unit test.
---
po/POTFILES-ml | 1 +
v2v/Makefile.am | 5 +-
v2v/v2v_unit_tests.ml | 104 +++++++++++++++++++++++++++++++++++-
v2v/windows_inf.ml | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++
v2v/windows_inf.mli | 58 ++++++++++++++++++++
5 files changed, 308 insertions(+), 3 deletions(-)
create mode 100644 v2v/windows_inf.ml
create mode 100644 v2v/windows_inf.mli
diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index c02ffc0..88db39b 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -127,4 +127,5 @@ v2v/v2v.ml
v2v/v2v_unit_tests.ml
v2v/vCenter.ml
v2v/windows.ml
+v2v/windows_inf.ml
v2v/xml.ml
diff --git a/v2v/Makefile.am b/v2v/Makefile.am
index 5dfef6e..c46594c 100644
--- a/v2v/Makefile.am
+++ b/v2v/Makefile.am
@@ -69,6 +69,7 @@ SOURCES_MLI = \
utils.mli \
vCenter.mli \
windows.mli \
+ windows_inf.mli \
xml.mli
SOURCES_ML = \
@@ -82,8 +83,9 @@ SOURCES_ML = \
DOM.ml \
changeuid.ml \
OVF.ml \
- linux.ml \
+ windows_inf.ml \
windows.ml \
+ linux.ml \
modules_list.ml \
input_disk.ml \
input_libvirtxml.ml \
@@ -309,6 +311,7 @@ v2v_unit_tests_BOBJECTS = \
utils.cmo \
DOM.cmo \
OVF.cmo \
+ windows_inf.cmo \
windows.cmo \
v2v_unit_tests.cmo
v2v_unit_tests_XOBJECTS = $(v2v_unit_tests_BOBJECTS:.cmo=.cmx)
diff --git a/v2v/v2v_unit_tests.ml b/v2v/v2v_unit_tests.ml
index a2dca32..169eea9 100644
--- a/v2v/v2v_unit_tests.ml
+++ b/v2v/v2v_unit_tests.ml
@@ -18,13 +18,21 @@
(* This file tests individual virt-v2v functions. *)
+open Printf
open OUnit2
+
+open Common_utils
+
open Types
-open Printf
-
external identity : 'a -> 'a = "%identity"
+let (//) = Filename.concat
+
+let srcdir =
+ try Sys.getenv "srcdir"
+ with Not_found -> failwith "environment variable $srcdir must be set"
+
let inspect_defaults = {
i_type = ""; i_distro = ""; i_arch = "";
i_major_version = 0; i_minor_version = 0;
@@ -126,6 +134,97 @@ let test_drive_index ctx =
assert_raises exn (fun () -> Utils.drive_index "Z");
assert_raises exn (fun () -> Utils.drive_index "aB")
+(* Test parsing a [*.inf] file. *)
+let test_windows_inf_of_string ctx =
+ let printer = Windows_inf.to_string in
+
+ (* There is nothing special about this choice. It is just a driver
+ * [*.inf] file picked at random.
+ *)
+ let path = srcdir // ".." // "test-data" //
"fake-virtio-win" //
+ "cd" // "Balloon" // "2k12" //
"amd64" // "balloon.inf" in
+
+ let sections = Windows_inf.load path in
+
+ let expected = [
+ "version", [
+ "signature", "\"$WINDOWS NT$\"";
+ "class", "System";
+ "classguid", "{4d36e97d-e325-11ce-bfc1-08002be10318}";
+ "provider", "%RHEL%";
+ "driverver", "12/04/2014,62.71.104.9600";
+ "catalogfile", "Balloon.cat";
+ "driverpackagetype", "PlugAndPlay";
+ "driverpackagedisplayname", "%BALLOON.DeviceDesc%";
+ "pnplockdown", "1";
+ ];
+ "destinationdirs", [
+ "defaultdestdir", "12";
+ ];
+ "sourcedisksnames", [
+ "1", "%DiskId1%,,,\"\"";
+ ];
+ "sourcedisksfiles", [
+ "balloon.sys", "1,,";
+ ];
+ "manufacturer", [
+ "%rhel%", "Standard,NTamd64";
+ ];
+ "standard", [
+ "%balloon.devicedesc%", "BALLOON_Device,
PCI\\VEN_1AF4&DEV_1002&SUBSYS_00051AF4&REV_00";
+ ];
+ "standard.ntamd64", [
+ "%balloon.devicedesc%", "BALLOON_Device,
PCI\\VEN_1AF4&DEV_1002&SUBSYS_00051AF4&REV_00";
+ ];
+ "balloon_device.nt", [
+ "copyfiles", "Drivers_Dir";
+ ];
+ "drivers_dir", [];
+ "balloon_device.nt.services", [
+ "addservice", "BALLOON,%SPSVCINST_ASSOCSERVICE%,
BALLOON_Service_Inst, BALLOON_Logging_Inst";
+ ];
+ "balloon_service_inst", [
+ "displayname", "%BALLOON.SVCDESC%";
+ "servicetype", "1";
+ "starttype", "3";
+ "errorcontrol", "1";
+ "servicebinary", "%12%\\balloon.sys";
+ ];
+ "balloon_logging_inst", [
+ "addreg", "BALLOON_Logging_Inst_AddReg";
+ ];
+ "balloon_logging_inst_addreg", [];
+ "destinationdirs", [
+ "balloon_device_coinstaller_copyfiles", "11";
+ ];
+ "balloon_device.nt.coinstallers", [
+ "addreg", "BALLOON_Device_CoInstaller_AddReg";
+ "copyfiles", "BALLOON_Device_CoInstaller_CopyFiles";
+ ];
+ "balloon_device_coinstaller_addreg", [];
+ "balloon_device_coinstaller_copyfiles", [];
+ "sourcedisksfiles", [
+ "wdfcoinstaller01011.dll", "1";
+ ];
+ "balloon_device.nt.wdf", [
+ "kmdfservice", "BALLOON, BALLOON_wdfsect";
+ ];
+ "balloon_wdfsect", [
+ "kmdflibraryversion", "1.11";
+ ];
+ "strings", [
+ "spsvcinst_assocservice", "0x00000002";
+ "rhel", "\"Red Hat, Inc.\"";
+ "diskid1", "\"VirtIO Balloon Installation Disk
#1\"";
+ "balloon.devicedesc", "\"VirtIO Balloon Driver\"";
+ "balloon.svcdesc", "\"VirtIO Balloon Service\"";
+ "classname", "\"VirtIO Balloon Device\"";
+ ];
+ ] in
+
+ assert_equal ~printer expected sections
+
+(* Test the code which matches [*.inf] files to Windows guests. *)
let test_virtio_iso_path_matches_guest_os ctx =
(* Windows OSes fake inspection data. *)
let make_win name major minor variant arch = {
@@ -772,6 +871,7 @@ let suite =
"OVF.get_ostype" >:: test_get_ostype;
"Utils.drive_name" >:: test_drive_name;
"Utils.drive_index" >:: test_drive_index;
+ "Windows_inf.of_string" >:: test_windows_inf_of_string;
"Windows.virtio_iso_path_matches_guest_os" >::
test_virtio_iso_path_matches_guest_os;
]
diff --git a/v2v/windows_inf.ml b/v2v/windows_inf.ml
new file mode 100644
index 0000000..2066a2e
--- /dev/null
+++ b/v2v/windows_inf.ml
@@ -0,0 +1,143 @@
+(* virt-v2v
+ * Copyright (C) 2015 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 Common_utils
+
+type t = section list
+and section = string * data list
+and data = string * string
+
+let crlf_rex = Str.regexp "\r?\n"
+
+(* Match [[header]] in a Windows [*.inf] file. *)
+let section_header_rex =
+ Str.regexp "^[ \t]*\\[[ \t]*\\(.*\\)[ \t]*\\][ \t]*$"
+
+let match_section_header line = Str.string_match section_header_rex line 0
+let not_section_header line = not (match_section_header line)
+
+(* Match [key = value] in a Windows [*.inf] file. *)
+let key_value_rex =
+ Str.regexp_case_fold
+ "^[ \t]*\\([a-z0-9%_.]+\\)[ \t]*=[ \t]*\\(.*\\)[ \t]*$"
+
+(* Match comment preceeded by whitespace (so comments can be removed). *)
+let comment_rex = Str.regexp "[ \t]*;.*"
+
+(* Parse a Windows [*.inf] file into headers and section lines. *)
+let of_string content =
+ (* Split up the inf file (possibly with DOS line endings) into lines. *)
+ let lines = Str.split crlf_rex content in
+
+ (* Split the file into section headers + section content. *)
+ let rec loop = function
+ | [] -> []
+ | header :: xs when match_section_header header ->
+ let header = Str.matched_group 1 header in
+ let lines = takewhile not_section_header xs in
+ let ys = dropwhile not_section_header xs in
+ (header, lines) :: loop ys
+ | xs ->
+ (* Put all initial lines before the first section into a
+ * section with no name.
+ *)
+ let lines = takewhile not_section_header xs in
+ let ys = dropwhile not_section_header xs in
+ ("", lines) :: loop ys
+ in
+ let sections = loop lines in
+
+ (* Split the lines that match "key = value" into [(key, value)] pairs.
+ * Ignore any other lines.
+ *)
+ let sections = List.map (
+ fun (header, lines) ->
+ let lines = filter_map (
+ fun line ->
+ if Str.string_match key_value_rex line 0 then (
+ let key = Str.matched_group 1 line in
+ let value = Str.matched_group 2 line in
+ Some (key, value)
+ )
+ else None (* ignore the non-matching line *)
+ ) lines in
+ header, lines
+ ) sections in
+
+ (* If the dummy section at the beginning is now completely empty,
+ * remove it.
+ *)
+ let sections =
+ match sections with
+ | ("", []) :: sections -> sections
+ | sections -> sections in
+
+ (* Remove any comments from values, conservatively though because
+ * we don't really understand the value format.
+ *)
+ let sections = List.map (
+ fun (header, lines) ->
+ let lines = List.map (
+ fun (key, value) ->
+ let value =
+ if String.contains value '"' then value
+ else Str.replace_first comment_rex "" value in
+ key, value
+ ) lines in
+ header, lines
+ ) sections in
+
+ (* Normalize (by lowercasing) the section headers and keys (but not
+ * the values).
+ *)
+ let sections = List.map (
+ fun (header, lines) ->
+ let header = String.lowercase_ascii header in
+ let lines = List.map (
+ fun (key, value) ->
+ String.lowercase_ascii key, value
+ ) lines in
+ header, lines
+ ) sections in
+
+ sections
+
+let find_section t section_name =
+ let section_name = String.lowercase_ascii section_name in
+ List.assoc section_name t
+
+let find_key t section_name key_name =
+ let data = find_section t section_name in
+ let key_name = String.lowercase_ascii key_name in
+ List.assoc key_name data
+
+let load filename =
+ of_string (read_whole_file filename)
+
+let rec to_string sections =
+ String.concat "\n" (List.map string_of_section sections)
+
+and string_of_section (header, body) =
+ let header = sprintf "[%s]" header in
+ let body = List.map string_of_key_value body in
+ String.concat "\n" (header :: body)
+
+and string_of_key_value (key, value) =
+ sprintf "%s = %s" key value
diff --git a/v2v/windows_inf.mli b/v2v/windows_inf.mli
new file mode 100644
index 0000000..1cb8040
--- /dev/null
+++ b/v2v/windows_inf.mli
@@ -0,0 +1,58 @@
+(* virt-v2v
+ * Copyright (C) 2009-2015 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.
+ *)
+
+(** Handle Windows driver [*.inf] files. *)
+
+type t = section list
+(** Type of a parsed Windows driver [*.inf] file. *)
+
+and section = string * data list
+(** A single section consists of a header and a list of lines. If
+ the file doesn't start with a section header, then the initial
+ section has a dummy header name [""].
+
+ The section header is always normalized to lowercase ASCII. *)
+
+and data = string * string
+(** A [key = value] pair appearing within a section body. The key
+ (but {i not} the value) is always normalized to lowercase ASCII. *)
+
+val of_string : string -> t
+(** Parse an [*.inf] file from the string. No parse errors are
+ possible since this parser accepts anything as a possible [*.inf]
+ file. *)
+
+val load : string -> t
+(** Same as {!of_string} except we load the content from
+ a host file. *)
+
+val to_string : t -> string
+(** Convert an inf file back to a string. This should probably only
+ be used for debugging, since we don't preserve comments and it's
+ not tested that Windows would be able to parse what we write out. *)
+
+val find_section : t -> string -> data list
+(** [find_section t section_name] finds and returns a section by name.
+
+ Raises [Not_found] if not found. *)
+
+val find_key : t -> string -> string -> string
+(** [find_key t section_name key_name] finds and returns a key within
+ a particular section.
+
+ Raises [Not_found] if not found. *)
adding roman to CC: