[Libguestfs] [PATCH v11 09/10] daemon: Implement inspection of Windows.
Pino Toscano
ptoscano at redhat.com
Tue Aug 8 16:57:50 UTC 2017
On Monday, 31 July 2017 17:40:58 CEST Richard W.M. Jones wrote:
> Mostly a line-for-line translation of the C inspection code.
> ---
> daemon/Makefile.am | 2 +
> daemon/inspect_fs.ml | 6 +
> daemon/inspect_fs_windows.ml | 491 ++++++++++++++++++++++++++++++++++++++++++
> daemon/inspect_fs_windows.mli | 24 +++
> 4 files changed, 523 insertions(+)
>
> diff --git a/daemon/Makefile.am b/daemon/Makefile.am
> index a4657ed86..80314a524 100644
> --- a/daemon/Makefile.am
> +++ b/daemon/Makefile.am
> @@ -254,6 +254,7 @@ SOURCES_MLI = \
> inspect_fs.mli \
> inspect_fs_unix.mli \
> inspect_fs_unix_fstab.mli \
> + inspect_fs_windows.mli \
> inspect_types.mli \
> inspect_utils.mli \
> is.mli \
> @@ -296,6 +297,7 @@ SOURCES_ML = \
> inspect_utils.ml \
> inspect_fs_unix_fstab.ml \
> inspect_fs_unix.ml \
> + inspect_fs_windows.ml \
> inspect_fs.ml \
> inspect.ml \
> callbacks.ml \
> diff --git a/daemon/inspect_fs.ml b/daemon/inspect_fs.ml
> index 9153e68a5..10a15827b 100644
> --- a/daemon/inspect_fs.ml
> +++ b/daemon/inspect_fs.ml
> @@ -192,6 +192,12 @@ and check_filesystem mountable =
> debug_matching "Linux /var";
> ()
> )
> + (* Windows root? *)
> + else if Inspect_fs_windows.is_windows_systemroot () then (
> + debug_matching "Windows root";
> + role := `Root;
> + Inspect_fs_windows.check_windows_root data;
> + )
> (* Windows volume with installed applications (but not root)? *)
> else if is_dir_nocase "/System Volume Information" &&
> is_dir_nocase "/Program Files" then (
> diff --git a/daemon/inspect_fs_windows.ml b/daemon/inspect_fs_windows.ml
> new file mode 100644
> index 000000000..8cece319f
> --- /dev/null
> +++ b/daemon/inspect_fs_windows.ml
> @@ -0,0 +1,491 @@
> +(* guestfs-inspection
> + * 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.
> + *)
> +
> +open Printf
> +
> +open Std_utils
> +
> +open Utils
> +open Inspect_types
> +open Inspect_utils
> +
> +(* Check a predefined list of common windows system root locations. *)
> +let systemroot_paths =
> + [ "/windows"; "/winnt"; "/win32"; "/win"; "/reactos" ]
> +
> +let re_boot_ini_os =
> + Str.regexp "^\\(multi\\|scsi\\)(\\([0-9]+\\))disk(\\([0-9]+\\))rdisk(\\([0-9]+\\))partition(\\([0-9]+\\))\\([^=]+\\)="
> +
> +let rec check_windows_root data =
> + let systemroot =
> + match get_windows_systemroot () with
> + | None -> assert false (* Should never happen - see caller. *)
> + | Some systemroot -> systemroot in
> +
> + data.os_type <- Some OS_TYPE_WINDOWS;
> + data.distro <- Some DISTRO_WINDOWS;
> + data.windows_systemroot <- Some systemroot;
> + data.arch <- Some (check_windows_arch systemroot);
> +
> + (* Load further fields from the Windows registry. *)
> + check_windows_registry systemroot data
> +
> +and is_windows_systemroot () =
> + get_windows_systemroot () <> None
> +
> +and get_windows_systemroot () =
> + let rec loop = function
> + | [] -> None
> + | path :: paths ->
> + let path = case_sensitive_path_silently path in
> + match path with
> + | None -> loop paths
> + | Some path ->
> + if is_systemroot path then Some path
> + else loop paths
> + in
> + let systemroot = loop systemroot_paths in
> +
> + let systemroot =
> + match systemroot with
> + | Some systemroot -> Some systemroot
> + | None ->
> + (* If the fs contains boot.ini, check it for non-standard
> + * systemroot locations.
> + *)
> + let boot_ini_path = case_sensitive_path_silently "/boot.ini" in
> + match boot_ini_path with
> + | None -> None
> + | Some boot_ini_path ->
> + get_windows_systemroot_from_boot_ini boot_ini_path in
> +
> + match systemroot with
> + | None -> None
> + | Some systemroot ->
> + if verbose () then
> + eprintf "get_windows_systemroot: windows %%SYSTEMROOT%% = %s\n%!"
> + systemroot;
> + Some systemroot
> +
> +and get_windows_systemroot_from_boot_ini boot_ini_path =
> + let chroot = Chroot.create ~name:"get_windows_systemroot_from_boot_ini" () in
> + let lines =
> + Chroot.f chroot (
> + fun () ->
> + if not (is_small_file boot_ini_path) then (
> + eprintf "%s: not a regular file or too large\n" boot_ini_path;
> + None
> + )
> + else
> + Some (read_whole_file boot_ini_path)
> + ) () in
> + match lines with
> + | None -> None
> + | Some lines ->
> + let lines = String.nsplit "\n" lines in
> +
> + (* Find:
> + * [operating systems]
> + * followed by multiple lines starting with "multi" or "scsi".
> + *)
> + let rec loop = function
> + | [] -> None
> + | str :: rest when String.is_prefix str "[operating systems]" ->
> + let rec loop2 = function
> + | [] -> []
> + | str :: rest when String.is_prefix str "multi(" ||
> + String.is_prefix str "scsi(" ->
> + str :: loop2 rest
> + | _ -> []
> + in
> + Some (loop2 rest)
> + | _ :: rest -> loop rest
> + in
> + match loop lines with
> + | None -> None
> + | Some oses ->
> + (* Rewrite multi|scsi lines, removing any which we cannot parse. *)
> + let oses =
> + filter_map (
> + fun line ->
> + if Str.string_match re_boot_ini_os line 0 then (
> + let ctrlr_type = Str.matched_group 1 line
> + and ctrlr = int_of_string (Str.matched_group 2 line)
> + and disk = int_of_string (Str.matched_group 3 line)
> + and rdisk = int_of_string (Str.matched_group 4 line)
> + and part = int_of_string (Str.matched_group 5 line)
> + and path = Str.matched_group 6 line in
> +
> + (* Swap backslashes for forward slashes in the
> + * system root path.
> + *)
> + let path = String.replace_char path '\\' '/' in
> +
> + Some (ctrlr_type, ctrlr, disk, rdisk, part, path)
> + )
> + else None
> + ) oses in
> +
> + (* The Windows system root may be on any disk. However, there
> + * are currently (at least) 2 practical problems preventing us
> + * from locating it on another disk:
> + *
> + * 1. We don't have enough metadata about the disks we were
> + * given to know if what controller they were on and what
> + * index they had.
> + *
> + * 2. The way inspection of filesystems currently works, we
> + * can't mark another filesystem, which we may have already
> + * inspected, to be inspected for a specific Windows system
> + * root.
> + *
> + * Solving 1 properly would require a new API at a minimum. We
> + * might be able to fudge something practical without this,
> + * though, e.g. by looking at the <partition>th partition of
> + * every disk for the specific windows root.
> + *
> + * Solving 2 would probably require a significant refactoring
> + * of the way filesystems are inspected. We should probably do
> + * this some time.
> + *
> + * For the moment, we ignore all partition information and
> + * assume the system root is on the current partition. In
> + * practice, this will normally be correct.
> + *)
> +
> + let rec loop = function
> + | [] -> None
> + | (_, _, _, _, _, path) :: rest ->
> + if is_systemroot path then Some path
> + else loop rest
> + in
> + loop oses
> +
> +(* Try to find Windows systemroot using some common locations.
> + *
> + * Notes:
> + *
> + * (1) We check for some directories inside to see if it is a real
> + * systemroot, and not just a directory that happens to have the same
> + * name.
> + *
> + * (2) If a Windows guest has multiple disks and applications are
> + * installed on those other disks, then those other disks will contain
> + * "/Program Files" and "/System Volume Information". Those would
> + * *not* be Windows root disks. (RHBZ#674130)
> + *)
> +and is_systemroot systemroot =
> + is_dir_nocase (systemroot ^ "/system32") &&
> + is_dir_nocase (systemroot ^ "/system32/config") &&
> + is_file_nocase (systemroot ^ "/system32/cmd.exe")
> +
> +(* Return the architecture of the guest from cmd.exe. *)
> +and check_windows_arch systemroot =
> + let cmd_exe = sprintf "%s/system32/cmd.exe" systemroot in
> +
> + (* Should exist because of previous check above in is_systemroot. *)
> + let cmd_exe = Realpath.case_sensitive_path cmd_exe in
> +
> + Filearch.file_architecture cmd_exe
> +
> +(* Read further fields from the Windows registry. *)
> +and check_windows_registry systemroot data =
> + (* We know (from is_systemroot) that the config directory exists. *)
> + let software_hive = sprintf "%s/system32/config/software" systemroot in
> + let software_hive = Realpath.case_sensitive_path software_hive in
> + let software_hive =
> + if Is.is_file software_hive then Some software_hive else None in
> + data.windows_software_hive <- software_hive;
> +
> + let system_hive = sprintf "%s/system32/config/system" systemroot in
> + let system_hive = Realpath.case_sensitive_path system_hive in
> + let system_hive =
> + if Is.is_file system_hive then Some system_hive else None in
> + data.windows_system_hive <- system_hive;
> +
> + match software_hive, system_hive with
> + | None, _ | Some _, None -> ()
> + | Some software_hive, Some system_hive ->
> + (* Check software hive. *)
> + check_windows_software_registry software_hive data;
> +
> + (* Check system hive. *)
> + check_windows_system_registry system_hive data
> +
> +(* At the moment, pull just the ProductName and version numbers from
> + * the registry. In future there is a case for making many more
> + * registry fields available to callers.
> + *)
> +and check_windows_software_registry software_hive data =
> + with_hive (Sysroot.sysroot () // software_hive) (
sysroot_path here (and in the functions below too).
> + fun h root ->
> + try
> + let path = [ "Microsoft"; "Windows NT"; "CurrentVersion" ] in
> + let node = get_node h root path in
> + let values = Hivex.node_values h node in
> + let values = Array.to_list values in
> + (* Convert to a list of (key, value) to make the following easier. *)
> + let values = List.map (fun v -> Hivex.value_key h v, v) values in
> +
> + (* Look for ProductName key. *)
> + (try
> + let v = List.assoc "ProductName" values in
> + data.product_name <- Some (hivex_value_as_utf8 h v)
> + with
> + Not_found -> ()
> + );
> +
> + (* Version is complicated. Use CurrentMajorVersionNumber and
> + * CurrentMinorVersionNumber if present. If they are not
> + * found, fall back on CurrentVersion.
> + *)
> + (try
> + let major_v = List.assoc "CurrentMajorVersionNumber" values
> + and minor_v = List.assoc "CurrentMinorVersionNumber" values in
> + let major = Int32.to_int (Hivex.value_dword h major_v)
> + and minor = Int32.to_int (Hivex.value_dword h minor_v) in
> + data.version <- Some (major, minor)
> + with
> + Not_found ->
> + let v = List.assoc "CurrentVersion" values in
> + let v = hivex_value_as_utf8 h v in
> + parse_version_from_major_minor v data
> + );
> +
> + (* InstallationType (product_variant). *)
> + (try
> + let v = List.assoc "InstallationType" values in
> + data.product_variant <- Some (hivex_value_as_utf8 h v)
> + with
> + Not_found -> ()
> + );
> + with
> + | Not_found ->
> + if verbose () then
> + eprintf "check_windows_software_registry: cannot locate HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\n%!"
> + ) (* with_hive *)
> +
> +and check_windows_system_registry system_hive data =
> + with_hive (Sysroot.sysroot () // system_hive) (
> + fun h root ->
> + get_drive_mappings h root data;
> +
> + let current_control_set = get_current_control_set h root in
> + data.windows_current_control_set <- current_control_set;
> +
> + match current_control_set with
> + | None -> ()
> + | Some current_control_set ->
> + let hostname = get_hostname h root current_control_set in
> + data.hostname <- hostname
> + ) (* with_hive *)
> +
> +(* Get the CurrentControlSet. *)
> +and get_current_control_set h root =
> + try
> + let path = [ "Select" ] in
> + let node = get_node h root path in
> + let current_v = Hivex.node_get_value h node "Current" in
> + let current_control_set =
> + sprintf "ControlSet%03ld" (Hivex.value_dword h current_v) in
> + Some current_control_set
> + with
> + | Not_found ->
> + if verbose () then
> + eprintf "check_windows_system_registry: cannot locate HKLM\\SYSTEM\\Select\n%!";
> + None
> +
> +(* Get the drive mappings.
> + * This page explains the contents of HKLM\System\MountedDevices:
> + * http://www.goodells.net/multiboot/partsigs.shtml
> + *)
> +and get_drive_mappings h root data =
> + let devices = lazy (Devsparts.list_devices ()) in
> + let partitions = lazy (Devsparts.list_partitions ()) in
> + try
> + let path = [ "MountedDevices" ] in
> + let node = get_node h root path in
> + let values = Hivex.node_values h node in
> + let values = Array.to_list values in
> + let values =
> + filter_map (
> + fun value ->
> + let key = Hivex.value_key h value in
> + let keylen = String.length key in
> + if keylen >= 14 &&
> + String.lowercase_ascii (String.sub key 0 12) = "\\dosdevices\\" &&
> + Char.isalpha key.[12] && key.[13] = ':' then (
> + let drive_letter = String.sub key 12 1 in
> +
> + (* Get the binary value. Is it a fixed disk? *)
> + let (typ, blob) = Hivex.value_value h value in
> + let device =
> + if typ = Hivex.REG_BINARY then (
> + if String.length blob >= 24 &&
> + String.is_prefix blob "DMIO:ID:" (* GPT *) then
> + map_registry_disk_blob_gpt (Lazy.force partitions) blob
> + else if String.length blob = 12 then
> + map_registry_disk_blob (Lazy.force devices) blob
> + else
> + None
> + )
> + else None in
> +
> + match device with
> + | None -> None
> + | Some device -> Some (drive_letter, device)
> + )
> + else
> + None
> + ) values in
> +
> + data.drive_mappings <- values
> +
> + with
> + | Not_found ->
> + if verbose () then
> + eprintf "check_windows_system_registry: cannot find drive mappings\n%!"
> +
> +(* Windows Registry HKLM\SYSTEM\MountedDevices uses a blob of data
> + * to store partitions. This blob is described here:
> + * http://www.goodells.net/multiboot/partsigs.shtml
> + * The following function maps this blob to a libguestfs partition
> + * name, if possible.
> + *)
> +and map_registry_disk_blob devices blob =
> + try
> + (* First 4 bytes are the disk ID. Search all devices to find the
> + * disk with this disk ID.
> + *)
> + let diskid = String.sub blob 0 4 in
> + let device = List.find (fun dev -> pread dev 4 0x01b8 = diskid) devices in
> +
> + (* Next 8 bytes are the offset of the partition in bytes(!) given as
> + * a 64 bit little endian number. Luckily it's easy to get the
> + * partition byte offset from Parted.part_list.
> + *)
> + let offset = String.sub blob 4 8 in
> + let offset = int_of_le64 offset in
> + let partitions = Parted.part_list device in
> + let partition =
> + List.find (fun { Parted.part_start = s } -> s = offset) partitions in
> +
> + (* Construct the full device name. *)
> + Some (sprintf "%s%ld" device partition.Parted.part_num)
> + with
> + | Not_found -> None
> +
> +(* Matches Windows registry HKLM\SYSYTEM\MountedDevices\DosDevices blob to
> + * to libguestfs GPT partition device. For GPT disks, the blob is made of
> + * "DMIO:ID:" prefix followed by the GPT partition GUID.
> + *)
> +and map_registry_disk_blob_gpt partitions blob =
> + (* The blob_guid is returned as a lowercase hex string. *)
> + let blob_guid = extract_guid_from_registry_blob blob in
> +
> + if verbose () then
> + eprintf "map_registry_disk_blob_gpt: searching for GUID %s\n%!"
> + blob_guid;
> +
> + try
> + let partition =
> + List.find (
> + fun part ->
> + let partnum = Devsparts.part_to_partnum part in
> + let device = Devsparts.part_to_dev part in
> + let typ = Parted.part_get_parttype device in
> + if typ <> "gpt" then false
> + else (
> + let guid = Parted.part_get_gpt_guid device partnum in
> + String.lowercase_ascii guid = blob_guid
> + )
> + ) partitions in
> + Some partition
> + with
> + | Not_found -> None
> +
> +(* Extracts the binary GUID stored in blob from Windows registry
> + * HKLM\SYSTYEM\MountedDevices\DosDevices value and converts it to a
> + * GUID string so that it can be matched against libguestfs partition
> + * device GPT GUID.
> + *)
> +and extract_guid_from_registry_blob blob =
> + (* Copy relevant sections from blob to respective ints.
> + * Note we have to skip 8 byte "DMIO:ID:" prefix.
> + *)
> + let data1 = int_of_le32 (String.sub blob 8 4)
> + and data2 = int_of_le16 (String.sub blob 12 2)
> + and data3 = int_of_le16 (String.sub blob 14 2)
> + and data4 = int_of_be64 (String.sub blob 16 8) (* really big endian! *) in
> +
> + (* Must be lowercase hex. *)
> + sprintf "%08Lx-%04Lx-%04Lx-%04Lx-%012Lx"
> + data1 data2 data3
> + (Int64.shift_right_logical data4 48)
> + (data4 &^ 0xffffffffffff_L)
> +
> +and pread device size offset =
> + let fd = Unix.openfile device [Unix.O_RDONLY; Unix.O_CLOEXEC] 0 in
> + let ret =
> + protect ~f:(
> + fun () ->
> + ignore (Unix.lseek fd offset Unix.SEEK_SET);
> + let ret = Bytes.create size in
> + if Unix.read fd ret 0 size < size then
> + failwithf "pread: %s: short read" device;
> + ret
> + ) ~finally:(fun () -> Unix.close fd) in
> + Bytes.to_string ret
> +
> +(* Get the hostname. *)
> +and get_hostname h root current_control_set =
> + try
> + let path = [ current_control_set; "Services"; "Tcpip"; "Parameters" ] in
> + let node = get_node h root path in
> + let values = Hivex.node_values h node in
> + let values = Array.to_list values in
> + (* Convert to a list of (key, value) to make the following easier. *)
> + let values = List.map (fun v -> Hivex.value_key h v, v) values in
> + let hostname_v = List.assoc "Hostname" values in
> + Some (hivex_value_as_utf8 h hostname_v)
> + with
> + | Not_found ->
> + if verbose () then
> + eprintf "check_windows_system_registry: cannot locate HKLM\\SYSTEM\\%s\\Services\\Tcpip\\Parameters and/or Hostname key\n%!" current_control_set;
> + None
> +
> +(* Raises [Not_found] if the node is not found. *)
> +and get_node h node = function
> + | [] -> node
> + | x :: xs ->
> + let node = Hivex.node_get_child h node x in
> + get_node h node xs
> +
> +(* NB: This function DOES NOT test for the existence of the file. It
> + * will return non-NULL even if the file/directory does not exist.
> + * You have to call guestfs_is_file{,_opts} etc.
> + *)
> +and case_sensitive_path_silently path =
> + try
> + Some (Realpath.case_sensitive_path path)
> + with
> + | exn ->
> + if verbose () then
> + eprintf "case_sensitive_path_silently: %s: %s\n%!" path
> + (Printexc.to_string exn);
> + None
> diff --git a/daemon/inspect_fs_windows.mli b/daemon/inspect_fs_windows.mli
> new file mode 100644
> index 000000000..a7dcd76ba
> --- /dev/null
> +++ b/daemon/inspect_fs_windows.mli
> @@ -0,0 +1,24 @@
> +(* guestfs-inspection
> + * 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.
> + *)
> +
> +val check_windows_root : Inspect_types.inspection_data -> unit
> +(** Inspect the Windows [C:] filesystem mounted on sysroot. *)
> +
> +val is_windows_systemroot : unit -> bool
> +(** Decide if the filesystem mounted on sysroot looks like a
> + Windows [C:] filesystem. *)
>
--
Pino Toscano
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: This is a digitally signed message part.
URL: <http://listman.redhat.com/archives/libguestfs/attachments/20170808/c6b6f750/attachment.sig>
More information about the Libguestfs
mailing list