[Libguestfs] [PATCH v9 08/11] daemon: Implement inspection types and utility functions.

Richard W.M. Jones rjones at redhat.com
Mon Jul 17 16:55:28 UTC 2017


Define the types which will be used to communicate between the
different parts of the inspection code.  The main types are:

  fs        corresponds to ‘struct inspect_fs’ in C code

  root      no direct correspondance with the C code, but in the C
            code, ‘inspect_fs’ was overloaded to store roots

  inspection_data
            the inspection data which is incrementally collected about
            each filesystem as we perform inspection steps

Other types have simple and obvious correspondances with the
equivalent C code.

Add some utility function which will be used by inspection.

Note that this commit has no effect on its own, it just links extra
dead code into the daemon.
---
 daemon/Makefile.am       |   4 +
 daemon/inspect_types.ml  | 314 +++++++++++++++++++++++++++++++++++++++++++++++
 daemon/inspect_types.mli | 182 +++++++++++++++++++++++++++
 daemon/inspect_utils.ml  | 193 +++++++++++++++++++++++++++++
 daemon/inspect_utils.mli |  57 +++++++++
 5 files changed, 750 insertions(+)

diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 9b7952879..c92eba9cd 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -263,6 +263,8 @@ SOURCES_MLI = \
 	file.mli \
 	filearch.mli \
 	findfs.mli \
+	inspect_types.mli \
+	inspect_utils.mli \
 	is.mli \
 	ldm.mli \
 	link.mli \
@@ -298,6 +300,8 @@ SOURCES_ML = \
 	parted.ml \
 	listfs.ml \
 	realpath.ml \
+	inspect_types.ml \
+	inspect_utils.ml \
 	callbacks.ml \
 	daemon.ml
 
diff --git a/daemon/inspect_types.ml b/daemon/inspect_types.ml
new file mode 100644
index 000000000..4570349ba
--- /dev/null
+++ b/daemon/inspect_types.ml
@@ -0,0 +1,314 @@
+(* 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
+
+type fs = {
+  fs_location : location;
+  role : role;             (** Special cases: root filesystem or /usr *)
+}
+and root = {
+  root_location : location;
+  inspection_data : inspection_data;
+}
+and location = {
+  mountable : Mountable.t; (** The device name or other mountable object.*)
+  vfs_type : string;       (** Returned from [vfs_type] API. *)
+}
+
+and role =
+  | RoleRoot of inspection_data
+  | RoleUsr of inspection_data
+  | RoleSwap
+  | RoleOther
+and inspection_data = {
+  mutable os_type : os_type option;
+  mutable distro : distro option;
+  mutable package_format : package_format option;
+  mutable package_management : package_management option;
+  mutable product_name : string option;
+  mutable product_variant : string option;
+  mutable version : version option;
+  mutable arch : string option;
+  mutable hostname : string option;
+  mutable fstab : fstab_entry list;
+  mutable windows_systemroot : string option;
+  mutable windows_software_hive : string option;
+  mutable windows_system_hive : string option;
+  mutable windows_current_control_set : string option;
+  mutable drive_mappings : drive_mapping list;
+}
+and os_type =
+  | OS_TYPE_DOS
+  | OS_TYPE_FREEBSD
+  | OS_TYPE_HURD
+  | OS_TYPE_LINUX
+  | OS_TYPE_MINIX
+  | OS_TYPE_NETBSD
+  | OS_TYPE_OPENBSD
+  | OS_TYPE_WINDOWS
+and distro =
+  | DISTRO_ALPINE_LINUX
+  | DISTRO_ALTLINUX
+  | DISTRO_ARCHLINUX
+  | DISTRO_BUILDROOT
+  | DISTRO_CENTOS
+  | DISTRO_CIRROS
+  | DISTRO_COREOS
+  | DISTRO_DEBIAN
+  | DISTRO_FEDORA
+  | DISTRO_FREEBSD
+  | DISTRO_FREEDOS
+  | DISTRO_FRUGALWARE
+  | DISTRO_GENTOO
+  | DISTRO_LINUX_MINT
+  | DISTRO_MAGEIA
+  | DISTRO_MANDRIVA
+  | DISTRO_MEEGO
+  | DISTRO_NETBSD
+  | DISTRO_OPENBSD
+  | DISTRO_OPENSUSE
+  | DISTRO_ORACLE_LINUX
+  | DISTRO_PARDUS
+  | DISTRO_PLD_LINUX
+  | DISTRO_REDHAT_BASED
+  | DISTRO_RHEL
+  | DISTRO_SCIENTIFIC_LINUX
+  | DISTRO_SLACKWARE
+  | DISTRO_SLES
+  | DISTRO_SUSE_BASED
+  | DISTRO_TTYLINUX
+  | DISTRO_UBUNTU
+  | DISTRO_VOID_LINUX
+  | DISTRO_WINDOWS
+and package_format =
+  | PACKAGE_FORMAT_APK
+  | PACKAGE_FORMAT_DEB
+  | PACKAGE_FORMAT_EBUILD
+  | PACKAGE_FORMAT_PACMAN
+  | PACKAGE_FORMAT_PISI
+  | PACKAGE_FORMAT_PKGSRC
+  | PACKAGE_FORMAT_RPM
+  | PACKAGE_FORMAT_XBPS
+and package_management =
+  | PACKAGE_MANAGEMENT_APK
+  | PACKAGE_MANAGEMENT_APT
+  | PACKAGE_MANAGEMENT_DNF
+  | PACKAGE_MANAGEMENT_PACMAN
+  | PACKAGE_MANAGEMENT_PISI
+  | PACKAGE_MANAGEMENT_PORTAGE
+  | PACKAGE_MANAGEMENT_UP2DATE
+  | PACKAGE_MANAGEMENT_URPMI
+  | PACKAGE_MANAGEMENT_XBPS
+  | PACKAGE_MANAGEMENT_YUM
+  | PACKAGE_MANAGEMENT_ZYPPER
+and version = int * int
+and fstab_entry = Mountable.t * string (* mountable, mountpoint *)
+and drive_mapping = string * string (* drive name, device *)
+
+let rec string_of_fs { fs_location = location; role = role } =
+  sprintf "fs: %s role: %s\n"
+          (string_of_location location)
+          (match role with
+           | RoleRoot data -> "root\n" ^ string_of_inspection_data data
+           | RoleUsr data -> "usr\n" ^ string_of_inspection_data data
+           | RoleSwap -> "swap"
+           | RoleOther -> "other")
+
+and string_of_location { mountable = mountable; vfs_type = vfs_type } =
+  sprintf "%s (%s)" (Mountable.to_string mountable) vfs_type
+
+and string_of_root { root_location = location;
+                     inspection_data = inspection_data } =
+  sprintf "%s:\n%s"
+          (string_of_location location)
+          (string_of_inspection_data inspection_data)
+
+and string_of_inspection_data data =
+  let b = Buffer.create 1024 in
+  let bpf fs = bprintf b fs in
+  may (fun v -> bpf "    type: %s\n" (string_of_os_type v))
+      data.os_type;
+  may (fun v -> bpf "    distro: %s\n" (string_of_distro v))
+      data.distro;
+  may (fun v -> bpf "    package_format: %s\n" (string_of_package_format v))
+      data.package_format;
+  may (fun v -> bpf "    package_management: %s\n" (string_of_package_management v))
+      data.package_management;
+  may (fun v -> bpf "    product_name: %s\n" v)
+      data.product_name;
+  may (fun v -> bpf "    product_variant: %s\n" v)
+      data.product_variant;
+  may (fun (major, minor) -> bpf "    version: %d.%d\n" major minor)
+      data.version;
+  may (fun v -> bpf "    arch: %s\n" v)
+      data.arch;
+  may (fun v -> bpf "    hostname: %s\n" v)
+      data.hostname;
+  if data.fstab <> [] then (
+    let v = List.map (
+      fun (a, b) -> sprintf "(%s, %s)" (Mountable.to_string a) b
+    ) data.fstab in
+    bpf "    fstab: [%s]\n" (String.concat ", " v)
+  );
+  may (fun v -> bpf "    windows_systemroot: %s\n" v)
+      data.windows_systemroot;
+  may (fun v -> bpf "    windows_software_hive: %s\n" v)
+      data.windows_software_hive;
+  may (fun v -> bpf "    windows_system_hive: %s\n" v)
+      data.windows_system_hive;
+  may (fun v -> bpf "    windows_current_control_set: %s\n" v)
+      data.windows_current_control_set;
+  if data.drive_mappings <> [] then (
+    let v =
+      List.map (fun (a, b) -> sprintf "(%s, %s)" a b) data.drive_mappings in
+    bpf "    drive_mappings: [%s]\n" (String.concat ", " v)
+  );
+  Buffer.contents b
+
+and string_of_os_type = function
+  | OS_TYPE_DOS -> "dos"
+  | OS_TYPE_FREEBSD -> "freebsd"
+  | OS_TYPE_HURD -> "hurd"
+  | OS_TYPE_LINUX -> "linux"
+  | OS_TYPE_MINIX -> "minix"
+  | OS_TYPE_NETBSD -> "netbsd"
+  | OS_TYPE_OPENBSD -> "openbsd"
+  | OS_TYPE_WINDOWS -> "windows"
+
+and string_of_distro = function
+  | DISTRO_ALPINE_LINUX -> "alpinelinux"
+  | DISTRO_ALTLINUX -> "altlinux"
+  | DISTRO_ARCHLINUX -> "archlinux"
+  | DISTRO_BUILDROOT -> "buildroot"
+  | DISTRO_CENTOS -> "centos"
+  | DISTRO_CIRROS -> "cirros"
+  | DISTRO_COREOS -> "coreos"
+  | DISTRO_DEBIAN -> "debian"
+  | DISTRO_FEDORA -> "fedora"
+  | DISTRO_FREEBSD -> "freebsd"
+  | DISTRO_FREEDOS -> "freedos"
+  | DISTRO_FRUGALWARE -> "frugalware"
+  | DISTRO_GENTOO -> "gentoo"
+  | DISTRO_LINUX_MINT -> "linuxmint"
+  | DISTRO_MAGEIA -> "mageia"
+  | DISTRO_MANDRIVA -> "mandriva"
+  | DISTRO_MEEGO -> "meego"
+  | DISTRO_NETBSD -> "netbsd"
+  | DISTRO_OPENBSD -> "openbsd"
+  | DISTRO_OPENSUSE -> "opensuse"
+  | DISTRO_ORACLE_LINUX -> "oraclelinux"
+  | DISTRO_PARDUS -> "pardus"
+  | DISTRO_PLD_LINUX -> "pldlinux"
+  | DISTRO_REDHAT_BASED -> "redhat-based"
+  | DISTRO_RHEL -> "rhel"
+  | DISTRO_SCIENTIFIC_LINUX -> "scientificlinux"
+  | DISTRO_SLACKWARE -> "slackware"
+  | DISTRO_SLES -> "sles"
+  | DISTRO_SUSE_BASED -> "suse-based"
+  | DISTRO_TTYLINUX -> "ttylinux"
+  | DISTRO_UBUNTU -> "ubuntu"
+  | DISTRO_VOID_LINUX -> "voidlinux"
+  | DISTRO_WINDOWS -> "windows"
+
+and string_of_package_format = function
+  | PACKAGE_FORMAT_APK -> "apk"
+  | PACKAGE_FORMAT_DEB -> "deb"
+  | PACKAGE_FORMAT_EBUILD -> "ebuild"
+  | PACKAGE_FORMAT_PACMAN -> "pacman"
+  | PACKAGE_FORMAT_PISI -> "pisi"
+  | PACKAGE_FORMAT_PKGSRC -> "pkgsrc"
+  | PACKAGE_FORMAT_RPM -> "rpm"
+  | PACKAGE_FORMAT_XBPS -> "xbps"
+
+and string_of_package_management = function
+  | PACKAGE_MANAGEMENT_APK -> "apk"
+  | PACKAGE_MANAGEMENT_APT -> "apt"
+  | PACKAGE_MANAGEMENT_DNF -> "dnf"
+  | PACKAGE_MANAGEMENT_PACMAN -> "pacman"
+  | PACKAGE_MANAGEMENT_PISI -> "pisi"
+  | PACKAGE_MANAGEMENT_PORTAGE -> "portage"
+  | PACKAGE_MANAGEMENT_UP2DATE -> "up2date"
+  | PACKAGE_MANAGEMENT_URPMI -> "urpmi"
+  | PACKAGE_MANAGEMENT_XBPS -> "xbps"
+  | PACKAGE_MANAGEMENT_YUM -> "yum"
+  | PACKAGE_MANAGEMENT_ZYPPER -> "zypper"
+
+let null_inspection_data = {
+  os_type = None;
+  distro = None;
+  package_format = None;
+  package_management = None;
+  product_name = None;
+  product_variant = None;
+  version = None;
+  arch = None;
+  hostname = None;
+  fstab = [];
+  windows_systemroot = None;
+  windows_software_hive = None;
+  windows_system_hive = None;
+  windows_current_control_set = None;
+  drive_mappings = [];
+}
+let null_inspection_data () = { null_inspection_data with os_type = None }
+
+let merge_inspection_data child parent =
+  let merge child parent = if parent = None then child else parent in
+
+  parent.os_type <-         merge child.os_type parent.os_type;
+  parent.distro <-          merge child.distro parent.distro;
+  parent.package_format <-  merge child.package_format parent.package_format;
+  parent.package_management <-
+    merge child.package_management parent.package_management;
+  parent.product_name <-    merge child.product_name parent.product_name;
+  parent.product_variant <- merge child.product_variant parent.product_variant;
+  parent.version <-         merge child.version parent.version;
+  parent.arch <-            merge child.arch parent.arch;
+  parent.hostname <-        merge child.hostname parent.hostname;
+  parent.fstab <-           child.fstab @ parent.fstab;
+  parent.windows_systemroot <-
+    merge child.windows_systemroot parent.windows_systemroot;
+  parent.windows_software_hive <-
+    merge child.windows_software_hive parent.windows_software_hive;
+  parent.windows_system_hive <-
+    merge child.windows_system_hive parent.windows_system_hive;
+  parent.windows_current_control_set <-
+    merge child.windows_current_control_set parent.windows_current_control_set;
+
+  (* This is what the old C code did, but I doubt that it's correct. *)
+  parent.drive_mappings <-  child.drive_mappings @ parent.drive_mappings
+
+let merge child_fs parent_fs =
+  let inspection_data_of_fs = function
+    | { role = RoleRoot data }
+    | { role = RoleUsr data } -> data
+    | { role = (RoleSwap|RoleOther) } -> null_inspection_data ()
+  in
+
+  match parent_fs with
+  | { role = RoleRoot parent_data } ->
+     merge_inspection_data (inspection_data_of_fs child_fs) parent_data
+  | { role = RoleUsr parent_data } ->
+     merge_inspection_data (inspection_data_of_fs child_fs) parent_data
+  | { role = (RoleSwap|RoleOther) } ->
+     ()
+
+let inspect_fses = ref []
diff --git a/daemon/inspect_types.mli b/daemon/inspect_types.mli
new file mode 100644
index 000000000..5c2151e14
--- /dev/null
+++ b/daemon/inspect_types.mli
@@ -0,0 +1,182 @@
+(* 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.
+ *)
+
+type fs = {
+  fs_location : location;
+  role : role;             (** Special cases: root filesystem or /usr *)
+}
+(** A single filesystem. *)
+
+and root = {
+  root_location : location;
+  inspection_data : inspection_data;
+}
+(** A root (as in "inspect_get_roots"). *)
+
+and location = {
+  mountable : Mountable.t; (** The device name or other mountable object.*)
+  vfs_type : string;       (** Returned from [vfs_type] API. *)
+}
+
+and role =
+  | RoleRoot of inspection_data
+  | RoleUsr of inspection_data
+  | RoleSwap
+  | RoleOther
+(** During inspection, single filesystems are assigned a role which is
+    one of root, /usr, swap or other. *)
+
+and inspection_data = {
+  mutable os_type : os_type option;
+  mutable distro : distro option;
+  mutable package_format : package_format option;
+  mutable package_management : package_management option;
+  mutable product_name : string option;
+  mutable product_variant : string option;
+  mutable version : version option;
+  mutable arch : string option;
+  mutable hostname : string option;
+  mutable fstab : fstab_entry list;
+  mutable windows_systemroot : string option;
+  mutable windows_software_hive : string option;
+  mutable windows_system_hive : string option;
+  mutable windows_current_control_set : string option;
+  mutable drive_mappings : drive_mapping list;
+}
+(** During inspection, this data is collected incrementally for each
+    filesystem.  At the end of inspection, inspection data is merged
+    into the root. *)
+
+and os_type =
+  | OS_TYPE_DOS
+  | OS_TYPE_FREEBSD
+  | OS_TYPE_HURD
+  | OS_TYPE_LINUX
+  | OS_TYPE_MINIX
+  | OS_TYPE_NETBSD
+  | OS_TYPE_OPENBSD
+  | OS_TYPE_WINDOWS
+and distro =
+  | DISTRO_ALPINE_LINUX
+  | DISTRO_ALTLINUX
+  | DISTRO_ARCHLINUX
+  | DISTRO_BUILDROOT
+  | DISTRO_CENTOS
+  | DISTRO_CIRROS
+  | DISTRO_COREOS
+  | DISTRO_DEBIAN
+  | DISTRO_FEDORA
+  | DISTRO_FREEBSD
+  | DISTRO_FREEDOS
+  | DISTRO_FRUGALWARE
+  | DISTRO_GENTOO
+  | DISTRO_LINUX_MINT
+  | DISTRO_MAGEIA
+  | DISTRO_MANDRIVA
+  | DISTRO_MEEGO
+  | DISTRO_NETBSD
+  | DISTRO_OPENBSD
+  | DISTRO_OPENSUSE
+  | DISTRO_ORACLE_LINUX
+  | DISTRO_PARDUS
+  | DISTRO_PLD_LINUX
+  | DISTRO_REDHAT_BASED
+  | DISTRO_RHEL
+  | DISTRO_SCIENTIFIC_LINUX
+  | DISTRO_SLACKWARE
+  | DISTRO_SLES
+  | DISTRO_SUSE_BASED
+  | DISTRO_TTYLINUX
+  | DISTRO_UBUNTU
+  | DISTRO_VOID_LINUX
+  | DISTRO_WINDOWS
+and package_format =
+  | PACKAGE_FORMAT_APK
+  | PACKAGE_FORMAT_DEB
+  | PACKAGE_FORMAT_EBUILD
+  | PACKAGE_FORMAT_PACMAN
+  | PACKAGE_FORMAT_PISI
+  | PACKAGE_FORMAT_PKGSRC
+  | PACKAGE_FORMAT_RPM
+  | PACKAGE_FORMAT_XBPS
+and package_management =
+  | PACKAGE_MANAGEMENT_APK
+  | PACKAGE_MANAGEMENT_APT
+  | PACKAGE_MANAGEMENT_DNF
+  | PACKAGE_MANAGEMENT_PACMAN
+  | PACKAGE_MANAGEMENT_PISI
+  | PACKAGE_MANAGEMENT_PORTAGE
+  | PACKAGE_MANAGEMENT_UP2DATE
+  | PACKAGE_MANAGEMENT_URPMI
+  | PACKAGE_MANAGEMENT_XBPS
+  | PACKAGE_MANAGEMENT_YUM
+  | PACKAGE_MANAGEMENT_ZYPPER
+and version = int * int
+and fstab_entry = Mountable.t * string (* mountable, mountpoint *)
+and drive_mapping = string * string (* drive name, device *)
+
+val merge_inspection_data : inspection_data -> inspection_data -> unit
+(** [merge_inspection_data child parent] merges two sets of inspection
+    data into the parent.  The parent inspection data fields, if
+    present, take precedence over the child inspection data fields.
+
+    It's intended that you merge upwards, ie.
+    [merge_inspection_data usr root] *)
+
+val merge : fs -> fs -> unit
+(** [merge child_fs parent_fs] merges two filesystems,
+    using [merge_inspection_data] to merge the inspection data of
+    the child into the parent.  (Nothing else is merged, only
+    the inspection data). *)
+
+val string_of_fs : fs -> string
+(** Convert [fs] into a multi-line string, for debugging only. *)
+
+val string_of_root : root -> string
+(** Convert [root] into a multi-line string, for debugging only. *)
+
+val string_of_location : location -> string
+(** Convert [location] into a string, for debugging only. *)
+
+val string_of_inspection_data : inspection_data -> string
+(** Convert [inspection_data] into a multi-line string, for debugging only. *)
+
+val string_of_os_type : os_type -> string
+(** Convert [os_type] to a string.
+    The string is part of the public API. *)
+
+val string_of_distro : distro -> string
+(** Convert [distro] to a string.
+    The string is part of the public API. *)
+
+val string_of_package_format : package_format -> string
+(** Convert [package_format] to a string.
+    The string is part of the public API. *)
+
+val string_of_package_management : package_management -> string
+(** Convert [package_management] to a string.
+    The string is part of the public API. *)
+
+val null_inspection_data : unit -> inspection_data
+(** {!inspection_data} structure with all fields set to [None].
+    This is a function: since we mutate this structure, we want
+    a fresh structure each time (so we're not mutating a common copy). *)
+
+val inspect_fses : fs list ref
+(** The global list of filesystems found by the previous call to
+    inspect_os. *)
diff --git a/daemon/inspect_utils.ml b/daemon/inspect_utils.ml
new file mode 100644
index 000000000..9f66bbfea
--- /dev/null
+++ b/daemon/inspect_utils.ml
@@ -0,0 +1,193 @@
+(* 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 Unix
+open Printf
+
+open Std_utils
+
+open Utils
+open Inspect_types
+
+let max_augeas_file_size = 100 * 1000
+
+let rec with_augeas ?name configfiles f =
+  let sysroot = Sysroot.sysroot () in
+  let name =
+    match name with
+    | None -> sprintf "with_augeas: %s" (String.concat " " configfiles)
+    | Some name -> name in
+  let chroot = Chroot.create ~name sysroot in
+
+  (* Security:
+   *
+   * The old C code had a few problems: It ignored non-regular-file
+   * objects (eg. devices), passing them to Augeas, so relying on
+   * Augeas to do the right thing.  Also too-large regular files
+   * caused the whole inspection operation to fail.
+   *
+   * I have tried to improve this so that non-regular files and
+   * too large files are ignored (dropped from the configfiles list),
+   * so that Augeas won't touch them, but they also won't stop
+   * inspection.
+   *)
+  let safe_file file =
+    Is.is_file ~followsymlinks:true file && (
+      let size = (Chroot.f chroot Unix.stat file).Unix.st_size in
+      size <= max_augeas_file_size
+    )
+  in
+  let configfiles = List.filter safe_file configfiles in
+
+  let aug =
+    Augeas.create sysroot None [Augeas.AugSaveNoop; Augeas.AugNoLoad] in
+
+  protect
+    ~f:(fun () ->
+      (* Tell Augeas to only load configfiles and no other files.  This
+       * prevents a rogue guest from performing a denial of service attack
+       * by having large, over-complicated configuration files which are
+       * unrelated to the task at hand.  (Thanks Dominic Cleal).
+       * Note this requires Augeas >= 1.0.0 because of RHBZ#975412.
+       *)
+      let pathexpr = make_augeas_path_expression configfiles in
+      ignore (aug_rm_noerrors aug pathexpr);
+      Augeas.load aug;
+
+      (* Check that augeas did not get a parse error for any of the
+       * configfiles, otherwise we are silently missing information.
+       *)
+      let matches = aug_matches_noerrors aug "/augeas/files//error" in
+      List.iter (
+        fun match_ ->
+          List.iter (
+            fun file ->
+              let errorpath = sprintf "/augeas/files%s/error" file in
+              if match_ = errorpath then (
+                (* There's been an error - get the error details. *)
+                let get path =
+                  match aug_get_noerrors aug (errorpath ^ path) with
+                  | None -> "<missing>"
+                  | Some v -> v
+                in
+                let message = get "message" in
+                let line = get "line" in
+                let charp = get "char" in
+                failwithf "%s:%s:%s: augeas parse failure: %s"
+                          file line charp message
+              )
+          ) configfiles
+      ) matches;
+
+      f aug
+    )
+    ~finally:(
+      fun () -> Augeas.close aug
+    )
+
+(* Explained here: https://bugzilla.redhat.com/show_bug.cgi?id=975412#c0 *)
+and make_augeas_path_expression files =
+  let subexprs =
+    List.map (
+      fun file ->
+        (*           v NB trailing '/' after filename *)
+        sprintf "\"%s/\" !~ regexp('^') + glob(incl) + regexp('/.*')" file
+    ) files in
+  let subexprs = String.concat " and " subexprs in
+
+  let ret = sprintf "/augeas/load/*[ %s ]" subexprs in
+  if verbose () then
+    eprintf "augeas pathexpr = %s\n%!" ret;
+
+  ret
+
+and aug_get_noerrors aug path =
+  try Augeas.get aug path
+  with Augeas.Error _ -> None
+
+and aug_matches_noerrors aug path =
+  try Augeas.matches aug path
+  with Augeas.Error _ -> []
+
+and aug_rm_noerrors aug path =
+  try Augeas.rm aug path
+  with Augeas.Error _ -> 0
+
+let is_file_nocase path =
+  let path =
+    try Some (Realpath.case_sensitive_path path)
+    with _ -> None in
+  match path with
+  | None -> false
+  | Some path -> Is.is_file path
+
+and is_dir_nocase path =
+  let path =
+    try Some (Realpath.case_sensitive_path path)
+    with _ -> None in
+  match path with
+  | None -> false
+  | Some path -> Is.is_dir path
+
+(* Rather hairy test for "is a partition", taken directly from
+ * the old C inspection code.  XXX fix function and callers
+ *)
+let is_partition partition =
+  try
+    let device = Devsparts.part_to_dev partition in
+    ignore (Devsparts.device_index device);
+    true
+  with _ -> false
+
+(* Without non-greedy matching, it's difficult to write these regular
+ * expressions properly.  We should really switch to using PCRE. XXX
+ *)
+let re_major_minor = Str.regexp "[^0-9]*\\([0-9]+\\)\\.\\([0-9]+\\)"
+let re_major_no_minor = Str.regexp "[^0-9]*\\([0-9]+\\)"
+
+let parse_version_from_major_minor str data =
+  if verbose () then
+    eprintf "parse_version_from_major_minor: parsing '%s'\n%!" str;
+
+  if Str.string_match re_major_minor str 0 ||
+       Str.string_match re_major_no_minor str 0 then (
+    let major =
+      try Some (int_of_string (Str.matched_group 1 str))
+      with Not_found | Invalid_argument _ | Failure _ -> None in
+    let minor =
+      try Some (int_of_string (Str.matched_group 2 str))
+      with Not_found | Invalid_argument _ | Failure _ -> None in
+    match major, minor with
+    | None, None
+    | None, Some _ -> ()
+    | Some major, None -> data.version <- Some (major, 0)
+    | Some major, Some minor -> data.version <- Some (major, minor)
+  )
+  else (
+    eprintf "parse_version_from_major_minor: cannot parse version from '%s'\n"
+            str
+  )
+
+let with_hive hive_filename f =
+  let flags = [ Hivex.OPEN_UNSAFE ] in
+  let flags = if verbose () then Hivex.OPEN_VERBOSE :: flags else flags in
+  let h = Hivex.open_file hive_filename flags in
+  protect ~f:(fun () -> f h (Hivex.root h)) ~finally:(fun () -> Hivex.close h)
+
+let hivex_value_as_utf8 h value =
+  utf16le_to_utf8 (snd (Hivex.value_value h value))
diff --git a/daemon/inspect_utils.mli b/daemon/inspect_utils.mli
new file mode 100644
index 000000000..74e196b52
--- /dev/null
+++ b/daemon/inspect_utils.mli
@@ -0,0 +1,57 @@
+(* 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 with_augeas : ?name:string -> string list -> (Augeas.t -> 'a) -> 'a
+(** Open an Augeas handle, parse only 'configfiles' (these
+    files must exist), and then call 'f' with the Augeas handle.
+
+    As a security measure, this bails if any file is too large for
+    a reasonable configuration file.  After the call to 'f' the
+    Augeas handle is closed. *)
+
+val aug_get_noerrors : Augeas.t -> string -> string option
+val aug_matches_noerrors : Augeas.t -> string -> string list
+val aug_rm_noerrors : Augeas.t -> string -> int
+(** When inspecting a guest, we don't particularly care if Augeas
+    calls fail.  These functions wrap {!Augeas.get}, {!Augeas.matches}
+    and {!Augeas.rm} returning null content if there is an error. *)
+
+val is_file_nocase : string -> bool
+val is_dir_nocase : string -> bool
+(** With a filesystem mounted under sysroot, check if [path] is
+    a file or directory under that sysroot.  The [path] is
+    checked case-insensitively. *)
+
+val is_partition : string -> bool
+(** Return true if the device is a partition. *)
+
+val parse_version_from_major_minor : string -> Inspect_types.inspection_data -> unit
+(** Make a best effort attempt to parse either X or X.Y from a string,
+    usually the product_name string. *)
+
+val with_hive : string -> (Hivex.t -> Hivex.node -> 'a) -> 'a
+(** Open a Windows registry "hive", and call the function on the
+    handle and root node.
+
+    After the call to the function, the hive is always closed.
+
+    The hive is opened readonly. *)
+
+val hivex_value_as_utf8 : Hivex.t -> Hivex.value -> string
+(** Convert a Hivex value which we interpret as UTF-16LE to UTF-8.
+    The type field stored in the registry is ignored. *)
-- 
2.13.2




More information about the Libguestfs mailing list