[Libguestfs] [PATCH] UNFINISHED daemon: Reimplement most inspection APIs in the daemon, in OCaml.

Richard W.M. Jones rjones at redhat.com
Mon Jun 12 22:06:38 UTC 2017


Move the following APIs into the daemon, reimplemented in OCaml:

* inspect_os
* inspect_get_roots
* inspect_get_mountpoints
* inspect_get_filesystems
* inspect_get_format
* inspect_get_type
* inspect_get_distro
* inspect_get_package_format
* inspect_get_package_management
* inspect_get_product_name
* inspect_get_product_variant
* inspect_get_major_version
* inspect_get_minor_version
* inspect_get_arch
* inspect_get_hostname
* inspect_get_windows_systemroot
* inspect_get_windows_software_hive
* inspect_get_windows_system_hive
* inspect_get_windows_current_control_set
* inspect_is_live
* inspect_is_netinst
* inspect_is_multipart

XXX inspect_get_drive_mappings

The following inspection APIs have NOT been reimplemented in this commit:

* inspect_list_applications [deprecated]
* inspect_list_applications2
* inspect_get_icon

This also embeds the ocaml-augeas library (upstream here:
http://git.annexia.org/?p=ocaml-augeas.git;a=summary), but it's
identical to the upstream version and should remain so.
---
 .gitignore                       |   2 +
 daemon/Makefile.am               |  17 +-
 daemon/augeas-c.c                | 288 ++++++++++++++++++
 daemon/augeas.README             |   8 +
 daemon/augeas.ml                 |  59 ++++
 daemon/augeas.mli                |  95 ++++++
 daemon/inspect.ml                | 249 +++++++++++++++
 daemon/inspect.mli               |  40 +++
 daemon/inspect_fs.ml             | 410 +++++++++++++++++++++++++
 daemon/inspect_fs.mli            |  23 ++
 daemon/inspect_fs_cd.ml          |  23 ++
 daemon/inspect_fs_cd.mli         |  25 ++
 daemon/inspect_fs_unix.ml        | 642 +++++++++++++++++++++++++++++++++++++++
 daemon/inspect_fs_unix.mli       |  53 ++++
 daemon/inspect_fs_windows.ml     |  23 ++
 daemon/inspect_fs_windows.mli    |  25 ++
 daemon/inspect_types.ml          | 325 ++++++++++++++++++++
 daemon/inspect_types.mli         | 175 +++++++++++
 daemon/mount.ml                  |  61 ++++
 daemon/mount.mli                 |   2 +
 daemon/utils.ml                  |  75 +++++
 daemon/utils.mli                 |   8 +
 docs/C_SOURCE_FILES              |   1 +
 generator/actions.ml             |   1 +
 generator/actions_inspection.ml  | 481 +++++++++++++++--------------
 generator/actions_inspection.mli |   1 +
 generator/daemon.ml              |  60 +++-
 generator/proc_nr.ml             |  22 ++
 lib/MAX_PROC_NR                  |   2 +-
 lib/guestfs-internal.h           |   2 +-
 lib/inspect.c                    | 632 --------------------------------------
 31 files changed, 2966 insertions(+), 864 deletions(-)
 create mode 100644 daemon/augeas-c.c
 create mode 100644 daemon/augeas.README
 create mode 100644 daemon/augeas.ml
 create mode 100644 daemon/augeas.mli
 create mode 100644 daemon/inspect.ml
 create mode 100644 daemon/inspect.mli
 create mode 100644 daemon/inspect_fs.ml
 create mode 100644 daemon/inspect_fs.mli
 create mode 100644 daemon/inspect_fs_cd.ml
 create mode 100644 daemon/inspect_fs_cd.mli
 create mode 100644 daemon/inspect_fs_unix.ml
 create mode 100644 daemon/inspect_fs_unix.mli
 create mode 100644 daemon/inspect_fs_windows.ml
 create mode 100644 daemon/inspect_fs_windows.mli
 create mode 100644 daemon/inspect_types.ml
 create mode 100644 daemon/inspect_types.mli

diff --git a/.gitignore b/.gitignore
index bca927afc..b645229c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -181,6 +181,8 @@ Makefile.in
 /daemon/optgroups.c
 /daemon/optgroups.h
 /daemon/stamp-guestfsd.pod
+/daemon/stringMap.ml
+/daemon/stringMap.mli
 /daemon/structs-cleanups.c
 /daemon/structs-cleanups.h
 /daemon/structs.ml
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 15b78d6d8..c9ac6be4c 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -75,6 +75,7 @@ guestfsd_SOURCES = \
 	actions.h \
 	available.c \
 	augeas.c \
+	augeas-c.c \
 	base64.c \
 	blkdiscard.c \
 	blkid.c \
@@ -257,6 +258,7 @@ guestfsd_CFLAGS = \
 # library and then linked to the daemon.  See
 # https://caml.inria.fr/pub/docs/manual-ocaml/intfc.html
 SOURCES_MLI = \
+	augeas.mli \
 	blkid.mli \
 	btrfs.mli \
 	chroot.mli \
@@ -265,6 +267,12 @@ SOURCES_MLI = \
 	file.mli \
 	filearch.mli \
 	findfs.mli \
+	inspect.mli \
+	inspect_fs_cd.mli \
+	inspect_fs.mli \
+	inspect_fs_unix.mli \
+	inspect_fs_windows.mli \
+	inspect_types.mli \
 	is.mli \
 	ldm.mli \
 	link.mli \
@@ -278,12 +286,13 @@ SOURCES_MLI = \
 	utils.mli
 
 SOURCES_ML = \
+	augeas.ml \
 	types.ml \
-	utils.ml \
 	structs.ml \
 	sysroot.ml \
 	mountable.ml \
 	chroot.ml \
+	utils.ml \
 	blkid.ml \
 	btrfs.ml \
 	devsparts.ml \
@@ -299,6 +308,12 @@ SOURCES_ML = \
 	parted.ml \
 	listfs.ml \
 	realpath.ml \
+	inspect_types.ml \
+	inspect_fs_cd.ml \
+	inspect_fs_unix.ml \
+	inspect_fs_windows.ml \
+	inspect_fs.ml \
+	inspect.ml \
 	callbacks.ml \
 	daemon.ml
 
diff --git a/daemon/augeas-c.c b/daemon/augeas-c.c
new file mode 100644
index 000000000..c06bf92da
--- /dev/null
+++ b/daemon/augeas-c.c
@@ -0,0 +1,288 @@
+/* Augeas OCaml bindings
+ * Copyright (C) 2008-2012 Red Hat Inc., Richard W.M. Jones
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * $Id: augeas_c.c,v 1.1 2008/05/06 10:48:20 rjones Exp $
+ */
+
+#include "config.h"
+
+#include <augeas.h>
+
+#include <caml/alloc.h>
+#include <caml/memory.h>
+#include <caml/mlvalues.h>
+#include <caml/fail.h>
+#include <caml/callback.h>
+#include <caml/custom.h>
+
+typedef augeas *augeas_t;
+
+/* Raise an Augeas.Error exception. */
+static void
+raise_error (const char *msg)
+{
+  caml_raise_with_string (*caml_named_value ("Augeas.Error"), msg);
+}
+
+/* Map OCaml flags to C flags. */
+static int flag_map[] = {
+  /* AugSaveBackup */  AUG_SAVE_BACKUP,
+  /* AugSaveNewFile */ AUG_SAVE_NEWFILE,
+  /* AugTypeCheck */   AUG_TYPE_CHECK,
+  /* AugNoStdinc */    AUG_NO_STDINC,
+  /* AugSaveNoop */    AUG_SAVE_NOOP,
+  /* AugNoLoad */      AUG_NO_LOAD,
+};
+
+/* Wrap and unwrap augeas_t handles, with a finalizer. */
+#define Augeas_t_val(rv) (*(augeas_t *)Data_custom_val(rv))
+
+static void
+augeas_t_finalize (value tv)
+{
+  augeas_t t = Augeas_t_val (tv);
+  if (t) aug_close (t);
+}
+
+static struct custom_operations custom_operations = {
+  (char *) "augeas_t_custom_operations",
+  augeas_t_finalize,
+  custom_compare_default,
+  custom_hash_default,
+  custom_serialize_default,
+  custom_deserialize_default
+};
+
+static value Val_augeas_t (augeas_t t)
+{
+  CAMLparam0 ();
+  CAMLlocal1 (rv);
+  /* We could choose these so that the GC can make better decisions.
+   * See 18.9.2 of the OCaml manual.
+   */
+  const int used = 0;
+  const int max = 1;
+
+  rv = caml_alloc_custom (&custom_operations,
+			  sizeof (augeas_t), used, max);
+  Augeas_t_val(rv) = t;
+
+  CAMLreturn (rv);
+}
+
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+
+/* val create : string -> string option -> flag list -> t */
+CAMLprim value
+ocaml_augeas_create (value rootv, value loadpathv, value flagsv)
+{
+  CAMLparam1 (rootv);
+  char *root = String_val (rootv);
+  char *loadpath;
+  int flags = 0, i;
+  augeas_t t;
+
+  /* Optional loadpath. */
+  loadpath =
+    loadpathv == Val_int (0)
+    ? NULL
+    : String_val (Field (loadpathv, 0));
+
+  /* Convert list of flags to C. */
+  for (; flagsv != Val_int (0); flagsv = Field (flagsv, 1)) {
+    i = Int_val (Field (flagsv, 0));
+    flags |= flag_map[i];
+  }
+
+  t = aug_init (root, loadpath, flags);
+
+  if (t == NULL)
+    raise_error ("Augeas.create");
+
+  CAMLreturn (Val_augeas_t (t));
+}
+
+/* val close : t -> unit */
+CAMLprim value
+ocaml_augeas_close (value tv)
+{
+  CAMLparam1 (tv);
+  augeas_t t = Augeas_t_val (tv);
+
+  if (t) {
+    aug_close (t);
+    Augeas_t_val(tv) = NULL;	/* So the finalizer doesn't double-free. */
+  }
+
+  CAMLreturn (Val_unit);
+}
+
+/* val get : t -> path -> value option */
+CAMLprim value
+ocaml_augeas_get (value tv, value pathv)
+{
+  CAMLparam2 (tv, pathv);
+  CAMLlocal2 (optv, v);
+  augeas_t t = Augeas_t_val (tv);
+  char *path = String_val (pathv);
+  const char *val;
+  int r;
+
+  r = aug_get (t, path, &val);
+  if (r == 1) {			/* Return Some val */
+    v = caml_copy_string (val);
+    optv = caml_alloc (1, 0);
+    Field (optv, 0) = v;
+  } else if (r == 0)		/* Return None */
+    optv = Val_int (0);
+  else if (r == -1)		/* Error or multiple matches */
+    raise_error ("Augeas.get");
+  else
+    failwith ("Augeas.get: bad return value");
+
+  CAMLreturn (optv);
+}
+
+/* val exists : t -> path -> bool */
+CAMLprim value
+ocaml_augeas_exists (value tv, value pathv)
+{
+  CAMLparam2 (tv, pathv);
+  CAMLlocal1 (v);
+  augeas_t t = Augeas_t_val (tv);
+  char *path = String_val (pathv);
+  int r;
+
+  r = aug_get (t, path, NULL);
+  if (r == 1)			/* Return true. */
+    v = Val_int (1);
+  else if (r == 0)		/* Return false */
+    v = Val_int (0);
+  else if (r == -1)		/* Error or multiple matches */
+    raise_error ("Augeas.exists");
+  else
+    failwith ("Augeas.exists: bad return value");
+
+  CAMLreturn (v);
+}
+
+/* val insert : t -> ?before:bool -> path -> string -> unit */
+CAMLprim value
+ocaml_augeas_insert (value tv, value beforev, value pathv, value labelv)
+{
+  CAMLparam4 (tv, beforev, pathv, labelv);
+  augeas_t t = Augeas_t_val (tv);
+  char *path = String_val (pathv);
+  char *label = String_val (labelv);
+  int before;
+
+  before = beforev == Val_int (0) ? 0 : Int_val (Field (beforev, 0));
+
+  if (aug_insert (t, path, label, before) == -1)
+    raise_error ("Augeas.insert");
+
+  CAMLreturn (Val_unit);
+}
+
+/* val rm : t -> path -> int */
+CAMLprim value
+ocaml_augeas_rm (value tv, value pathv)
+{
+  CAMLparam2 (tv, pathv);
+  augeas_t t = Augeas_t_val (tv);
+  char *path = String_val (pathv);
+  int r;
+
+  r = aug_rm (t, path);
+  if (r == -1)
+    raise_error ("Augeas.rm");
+
+  CAMLreturn (Val_int (r));
+}
+
+/* val matches : t -> path -> path list */
+CAMLprim value
+ocaml_augeas_match (value tv, value pathv)
+{
+  CAMLparam2 (tv, pathv);
+  CAMLlocal3 (rv, v, cons);
+  augeas_t t = Augeas_t_val (tv);
+  char *path = String_val (pathv);
+  char **matches;
+  int r, i;
+
+  r = aug_match (t, path, &matches);
+  if (r == -1)
+    raise_error ("Augeas.matches");
+
+  /* Copy the paths to a list. */
+  rv = Val_int (0);
+  for (i = 0; i < r; ++i) {
+    v = caml_copy_string (matches[i]);
+    free (matches[i]);
+    cons = caml_alloc (2, 0);
+    Field (cons, 1) = rv;
+    Field (cons, 0) = v;
+    rv = cons;
+  }
+
+  free (matches);
+
+  CAMLreturn (rv);
+}
+
+/* val count_matches : t -> path -> int */
+CAMLprim value
+ocaml_augeas_count_matches (value tv, value pathv)
+{
+  CAMLparam2 (tv, pathv);
+  augeas_t t = Augeas_t_val (tv);
+  char *path = String_val (pathv);
+  int r;
+
+  r = aug_match (t, path, NULL);
+  if (r == -1)
+    raise_error ("Augeas.count_matches");
+
+  CAMLreturn (Val_int (r));
+}
+
+/* val save : t -> unit */
+CAMLprim value
+ocaml_augeas_save (value tv)
+{
+  CAMLparam1 (tv);
+  augeas_t t = Augeas_t_val (tv);
+
+  if (aug_save (t) == -1)
+    raise_error ("Augeas.save");
+
+  CAMLreturn (Val_unit);
+}
+
+/* val load : t -> unit */
+CAMLprim value
+ocaml_augeas_load (value tv)
+{
+  CAMLparam1 (tv);
+  augeas_t t = Augeas_t_val (tv);
+
+  if (aug_load (t) == -1)
+    raise_error ("Augeas.load");
+
+  CAMLreturn (Val_unit);
+}
diff --git a/daemon/augeas.README b/daemon/augeas.README
new file mode 100644
index 000000000..938dfd255
--- /dev/null
+++ b/daemon/augeas.README
@@ -0,0 +1,8 @@
+The files augeas-c.c, augeas.ml and augeas.mli come from the
+ocaml-augeas library:
+
+  http://git.annexia.org/?p=ocaml-augeas.git
+
+which is released under a compatible license.  We try to keep them
+identical, so if you make changes to these files then you must also
+submit the changes to ocaml-augeas, and vice versa.
\ No newline at end of file
diff --git a/daemon/augeas.ml b/daemon/augeas.ml
new file mode 100644
index 000000000..f556df0f1
--- /dev/null
+++ b/daemon/augeas.ml
@@ -0,0 +1,59 @@
+(* Augeas OCaml bindings
+ * Copyright (C) 2008 Red Hat Inc., Richard W.M. Jones
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * $Id: augeas.ml,v 1.2 2008/05/06 10:48:20 rjones Exp $
+ *)
+
+type t
+
+exception Error of string
+
+type flag =
+  | AugSaveBackup
+  | AugSaveNewFile
+  | AugTypeCheck
+  | AugNoStdinc
+  | AugSaveNoop
+  | AugNoLoad
+
+type path = string
+
+type value = string
+
+external create : string -> string option -> flag list -> t
+  = "ocaml_augeas_create"
+external close : t -> unit
+  = "ocaml_augeas_close"
+external get : t -> path -> value option
+  = "ocaml_augeas_get"
+external exists : t -> path -> bool
+  = "ocaml_augeas_exists"
+external insert : t -> ?before:bool -> path -> string -> unit
+  = "ocaml_augeas_insert"
+external rm : t -> path -> int
+  = "ocaml_augeas_rm"
+external matches : t -> path -> path list
+  = "ocaml_augeas_match"
+external count_matches : t -> path -> int
+  = "ocaml_augeas_count_matches"
+external save : t -> unit
+  = "ocaml_augeas_save"
+external load : t -> unit
+  = "ocaml_augeas_load"
+
+let () =
+  Callback.register_exception "Augeas.Error" (Error "")
diff --git a/daemon/augeas.mli b/daemon/augeas.mli
new file mode 100644
index 000000000..64e824014
--- /dev/null
+++ b/daemon/augeas.mli
@@ -0,0 +1,95 @@
+(** Augeas OCaml bindings *)
+(* Copyright (C) 2008 Red Hat Inc., Richard W.M. Jones
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * $Id: augeas.mli,v 1.2 2008/05/06 10:48:20 rjones Exp $
+ *)
+
+type t
+  (** Augeas library handle. *)
+
+exception Error of string
+  (** This exception is thrown when the underlying Augeas library
+      returns an error. *)
+
+type flag =
+  | AugSaveBackup			(** Rename original with .augsave *)
+  | AugSaveNewFile			(** Save changes to .augnew *)
+  | AugTypeCheck			(** Type-check lenses *)
+  | AugNoStdinc
+  | AugSaveNoop
+  | AugNoLoad
+  (** Flags passed to the {!create} function. *)
+
+type path = string
+  (** A path expression.
+
+      Note in future we may replace this with a type-safe path constructor. *)
+
+type value = string
+  (** A value. *)
+
+val create : string -> string option -> flag list -> t
+  (** [create root loadpath flags] creates an Augeas handle.
+
+      [root] is a file system path describing the location
+      of the configuration files.
+
+      [loadpath] is an optional colon-separated list of directories
+      which are searched for schema definitions.
+
+      [flags] is a list of flags. *)
+
+val close : t -> unit
+  (** [close handle] closes the handle.
+
+      You don't need to close handles explicitly with this function:
+      they will be finalized eventually by the garbage collector.
+      However calling this function frees up any resources used by the
+      underlying Augeas library immediately.
+
+      Do not use the handle after closing it. *)
+
+val get : t -> path -> value option
+  (** [get t path] returns the value at [path], or [None] if there
+      is no value. *)
+
+val exists : t -> path -> bool
+  (** [exists t path] returns true iff there is a value at [path]. *)
+
+val insert : t -> ?before:bool -> path -> string -> unit
+  (** [insert t ?before path label] inserts [label] as a sibling
+      of [path].  By default it is inserted after [path], unless
+      [~before:true] is specified. *)
+
+val rm : t -> path -> int
+  (** [rm t path] removes all nodes matching [path].
+
+      Returns the number of nodes removed (which may be 0). *)
+
+val matches : t -> path -> path list
+  (** [matches t path] returns a list of path expressions
+      of all nodes matching [path]. *)
+
+val count_matches : t -> path -> int
+  (** [count_matches t path] counts the number of nodes matching
+      [path] but does not return them (see {!matches}). *)
+
+val save : t -> unit
+  (** [save t] saves all pending changes to disk. *)
+
+val load : t -> unit
+  (** [load t] loads files into the tree. *)
diff --git a/daemon/inspect.ml b/daemon/inspect.ml
new file mode 100644
index 000000000..1309b0d83
--- /dev/null
+++ b/daemon/inspect.ml
@@ -0,0 +1,249 @@
+(* 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
+
+let rec inspect_os () =
+  Mount.umount_all ();
+
+  (* Iterate over all detected filesystems.  Inspect each one in turn. *)
+  let fses = Listfs.list_filesystems () in
+
+  let fses =
+    filter_map (
+      fun (mountable, vfs_type) ->
+        Inspect_fs.check_for_filesystem_on mountable vfs_type
+  ) fses in
+  if verbose () then (
+    eprintf "inspect_os: fses:\n";
+    List.iter (fun fs -> eprintf "\t%s\n" (string_of_fs fs)) fses;
+    flush stderr
+  );
+
+(* XXX
+  (* The OS inspection information for CoreOS are gathered by inspecting
+   * multiple filesystems. Gather all the inspected information in the
+   * inspect_fs struct of the root filesystem.
+   *)
+  let fses = collect_coreos_inspection_info fses in
+
+  (* Check if the same filesystem was listed twice as root in fses.
+   * This may happen for the *BSD root partition where an MBR partition
+   * is a shadow of the real root partition probably /dev/sda5
+   *)
+  let fses = check_for_duplicated_bsd_root fses in
+
+  (* For Linux guests with a separate /usr filesystem, merge some of the
+   * inspected information in that partition to the inspect_fs struct
+   * of the root filesystem.
+   *)
+  let fses = collect_linux_inspection_info fses in
+ *)
+
+  (* Save what we found in a global variable. *)
+  Inspect_types.inspect_fses := fses;
+
+  (* At this point we have, in the handle, a list of all filesystems
+   * found and data about each one.  Now we assemble the list of
+   * filesystems which are root devices.
+   *
+   * Fall through to inspect_get_roots to do that.
+   *)
+  inspect_get_roots ()
+
+and inspect_get_roots () =
+  let fses = !Inspect_types.inspect_fses in
+
+  let roots =
+    filter_map (
+      fun fs -> try Some (root_of_fs fs) with Invalid_argument _ -> None
+    ) fses in
+  if verbose () then (
+    eprintf "inspect_get_roots: roots:\n";
+    List.iter (fun root -> eprintf "%s" (string_of_root root)) roots;
+    flush stderr
+  );
+
+  (* Only return the list of mountables, since subsequent calls will
+   * be used to retrieve the other information.
+   *)
+  List.map (fun { root_location = { mountable = m } } -> m) roots
+
+and root_of_fs =
+  function
+  | { fs_location = location; role = RoleRoot data } ->
+     { root_location = location; inspection_data = data }
+  | { role = (RoleUsr _ | RoleSwap | RoleOther) } ->
+     invalid_arg "root_of_fs"
+
+and inspect_get_mountpoints root_mountable =
+  let root = search_for_root root_mountable in
+  let fstab = root.inspection_data.fstab in
+
+  (* If no fstab information (Windows) return just the root. *)
+  if fstab = [] then
+    [ "/", root_mountable ]
+  else (
+    filter_map (
+      fun (mountable, mp) ->
+        if String.length mp > 0 && mp.[0] = '/' then
+          Some (mp, mountable)
+        else
+          None
+    ) fstab
+  )
+
+and inspect_get_filesystems root_mountable =
+  let root = search_for_root root_mountable in
+  let fstab = root.inspection_data.fstab in
+
+  (* If no fstab information (Windows) return just the root. *)
+  if fstab = [] then
+    [ root_mountable ]
+  else
+    List.map fst fstab
+
+and inspect_get_format root =
+  let root = search_for_root root in
+  match root.inspection_data.format with
+  | Some v -> string_of_format v
+  | None -> "unknown"
+
+and inspect_get_type root =
+  let root = search_for_root root in
+  match root.inspection_data.os_type with
+  | Some v -> string_of_os_type v
+  | None -> "unknown"
+
+and inspect_get_distro root =
+  let root = search_for_root root in
+  match root.inspection_data.distro with
+  | Some v -> string_of_distro v
+  | None -> "unknown"
+
+and inspect_get_package_format root =
+  let root = search_for_root root in
+  match root.inspection_data.package_format with
+  | Some v -> string_of_package_format v
+  | None -> "unknown"
+
+and inspect_get_package_management root =
+  let root = search_for_root root in
+  match root.inspection_data.package_management with
+  | Some v -> string_of_package_management v
+  | None -> "unknown"
+
+and inspect_get_product_name root =
+  let root = search_for_root root in
+  match root.inspection_data.product_name with
+  | Some v -> v
+  | None -> "unknown"
+
+and inspect_get_product_variant root =
+  let root = search_for_root root in
+  match root.inspection_data.product_variant with
+  | Some v -> v
+  | None -> "unknown"
+
+and inspect_get_major_version root =
+  let root = search_for_root root in
+  match root.inspection_data.version with
+  | Some (major, _) -> major
+  | None -> 0
+
+and inspect_get_minor_version root =
+  let root = search_for_root root in
+  match root.inspection_data.version with
+  | Some (_, minor) -> minor
+  | None -> 0
+
+and inspect_get_arch root =
+  let root = search_for_root root in
+  match root.inspection_data.arch with
+  | Some v -> v
+  | None -> "unknown"
+
+and inspect_get_hostname root =
+  let root = search_for_root root in
+  match root.inspection_data.hostname with
+  | Some v -> v
+  | None -> "unknown"
+
+and inspect_get_windows_systemroot root =
+  let root = search_for_root root in
+  match root.inspection_data.windows_systemroot with
+  | Some v -> v
+  | None ->
+     failwith "not a Windows guest, or systemroot could not be determined"
+
+and inspect_get_windows_system_hive root =
+  let root = search_for_root root in
+  match root.inspection_data.windows_system_hive with
+  | Some v -> v
+  | None ->
+     failwith "not a Windows guest, or system hive not found"
+
+and inspect_get_windows_software_hive root =
+  let root = search_for_root root in
+  match root.inspection_data.windows_software_hive with
+  | Some v -> v
+  | None ->
+     failwith "not a Windows guest, or software hive not found"
+
+and inspect_get_windows_current_control_set root =
+  let root = search_for_root root in
+  match root.inspection_data.windows_current_control_set with
+  | Some v -> v
+  | None ->
+     failwith "not a Windows guest, or CurrentControlSet could not be determined"
+
+and inspect_is_live root =
+  let root = search_for_root root in
+  root.inspection_data.is_live_disk
+
+and inspect_is_netinst root =
+  let root = search_for_root root in
+  root.inspection_data.is_netinst_disk
+
+and inspect_is_multipart root =
+  let root = search_for_root root in
+  root.inspection_data.is_multipart_disk
+
+and search_for_root root =
+  let fses = !Inspect_types.inspect_fses in
+  if fses = [] then
+    failwith "no inspection data: call guestfs_inspect_os first";
+
+  let root =
+    try
+      List.find (
+        function
+        | { fs_location = { mountable = m }; role = RoleRoot _ } -> root = m
+        | _ -> false
+      ) fses
+    with
+      Not_found ->
+        failwithf "%s: root device not found: only call this function with a root device previously returned by guestfs_inspect_os"
+                  (Mountable.to_string root) in
+
+  root_of_fs root
diff --git a/daemon/inspect.mli b/daemon/inspect.mli
new file mode 100644
index 000000000..5bcdfe259
--- /dev/null
+++ b/daemon/inspect.mli
@@ -0,0 +1,40 @@
+(* 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 inspect_os : unit -> Mountable.t list
+val inspect_get_roots : unit -> Mountable.t list
+val inspect_get_mountpoints : Mountable.t -> (string * Mountable.t) list
+val inspect_get_filesystems : Mountable.t -> Mountable.t list
+val inspect_get_format : Mountable.t -> string
+val inspect_get_type : Mountable.t -> string
+val inspect_get_distro : Mountable.t -> string
+val inspect_get_package_format : Mountable.t -> string
+val inspect_get_package_management : Mountable.t -> string
+val inspect_get_product_name : Mountable.t -> string
+val inspect_get_product_variant : Mountable.t -> string
+val inspect_get_major_version : Mountable.t -> int
+val inspect_get_minor_version : Mountable.t -> int
+val inspect_get_arch : Mountable.t -> string
+val inspect_get_hostname : Mountable.t -> string
+val inspect_get_windows_systemroot : Mountable.t -> string
+val inspect_get_windows_software_hive : Mountable.t -> string
+val inspect_get_windows_system_hive : Mountable.t -> string
+val inspect_get_windows_current_control_set : Mountable.t -> string
+val inspect_is_live : Mountable.t -> bool
+val inspect_is_netinst : Mountable.t -> bool
+val inspect_is_multipart : Mountable.t -> bool
diff --git a/daemon/inspect_fs.ml b/daemon/inspect_fs.ml
new file mode 100644
index 000000000..1e3049aff
--- /dev/null
+++ b/daemon/inspect_fs.ml
@@ -0,0 +1,410 @@
+(* 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 Mountable
+open Inspect_types
+
+let rec check_for_filesystem_on mountable vfs_type =
+  if verbose () then
+    eprintf "check_for_filesystem_on: %s (%s)\n%!"
+            (Mountable.to_string mountable) vfs_type;
+
+  let role =
+    let is_swap = vfs_type = "swap" in
+    if is_swap then
+      Some RoleSwap
+    else (
+      (* If it's a whole device, see if it is an install ISO. *)
+      let is_whole_device = Devsparts.is_whole_device mountable.m_device in
+      let installer_role =
+        if is_whole_device then
+          Inspect_fs_cd.check_installer_iso mountable.m_device
+        else
+          None in
+      match installer_role with
+      | Some _ as role -> role
+      | None ->
+         (* Try mounting the device.  Ignore errors if we can't do this. *)
+         let mounted =
+           if vfs_type = "ufs" then ( (* Hack for the *BSDs. *)
+             (* FreeBSD fs is a variant of ufs called ufs2 ... *)
+             try
+               Mount.mount_vfs (Some "ro,ufstype=ufs2") (Some "ufs")
+                               mountable "/";
+               true
+             with _ ->
+               (* while NetBSD and OpenBSD use another variant labeled 44bsd *)
+               try
+                 Mount.mount_vfs (Some "ro,ufstype=44bsd") (Some "ufs")
+                                 mountable "/";
+                 true
+               with _ -> false
+           ) else (
+             try Mount.mount_ro mountable "/";
+                 true
+             with _ -> false
+           ) in
+         if not mounted then None
+         else (
+           let role = check_filesystem mountable is_whole_device in
+           Mount.umount_all ();
+           role
+         )
+    ) in
+
+  match role with
+  | None -> None
+  | Some role ->
+     Some { fs_location = { mountable = mountable; vfs_type = vfs_type };
+            role = role }
+
+(* When this function is called, the filesystem is mounted on sysroot (). *)
+and check_filesystem mountable is_whole_device =
+  let is_only_partition =
+    if is_whole_device then false
+    else is_only_partition mountable in
+
+  let role = ref `Other in
+  let data = ref null_inspection_data in
+
+  (* Grub /boot? *)
+  if Is.is_file "/grub/menu.lst" ||
+     Is.is_file "/grub/grub.conf" ||
+     Is.is_file "/grub2/grub.cfg" then
+    ()
+  (* FreeBSD root? *)
+  else if Is.is_dir "/etc" &&
+          Is.is_dir "/bin" &&
+          Is.is_file "/etc/freebsd-update.conf" &&
+          Is.is_file "/etc/fstab" then (
+    role := `Root;
+    data := { !data with format = Some FORMAT_INSTALLED };
+    data := Inspect_fs_unix.check_freebsd_root !data
+  )
+  (* NetBSD root? *)
+  else if Is.is_dir "/etc" &&
+          Is.is_dir "/bin" &&
+          Is.is_file "/netbsd" &&
+          Is.is_file "/etc/fstab" &&
+          Is.is_file "/etc/release" then (
+    role := `Root;
+    data := { !data with format = Some FORMAT_INSTALLED };
+    data := Inspect_fs_unix.check_netbsd_root !data;
+  )
+  (* OpenBSD root? *)
+  else if Is.is_dir "/etc" &&
+          Is.is_dir "/bin" &&
+          Is.is_file "/bsd" &&
+          Is.is_file "/etc/fstab" &&
+          Is.is_file "/etc/motd" then (
+    role := `Root;
+    data := { !data with format = Some FORMAT_INSTALLED };
+    data := Inspect_fs_unix.check_openbsd_root !data;
+  )
+  (* Hurd root? *)
+  else if Is.is_file "/hurd/console" &&
+          Is.is_file "/hurd/hello" &&
+          Is.is_file "/hurd/null" then (
+    role := `Root;
+    data := { !data with format = Some FORMAT_INSTALLED };
+    data := Inspect_fs_unix.check_hurd_root !data;
+  )
+  (* Minix root? *)
+  else if Is.is_dir "/etc" &&
+          Is.is_dir "/bin" &&
+          Is.is_file "/service/vm" &&
+          Is.is_file "/etc/fstab" &&
+          Is.is_file "/etc/version" then (
+    role := `Root;
+    data := { !data with format = Some FORMAT_INSTALLED };
+    data := Inspect_fs_unix.check_minix_root !data;
+  )
+  (* Linux root? *)
+  else if Is.is_dir "/etc" &&
+          (Is.is_dir "/bin" ||
+           is_symlink_to "/bin" "usr/bin") &&
+          (Is.is_file "/etc/fstab" ||
+           Is.is_file "/etc/hosts") then (
+    role := `Root;
+    data := { !data with format = Some FORMAT_INSTALLED };
+    data := Inspect_fs_unix.check_linux_root mountable !data;
+  )
+  (* CoreOS root? *)
+  else if Is.is_dir "/etc" &&
+          Is.is_dir "/root" &&
+          Is.is_dir "/home" &&
+          Is.is_dir "/usr" &&
+          Is.is_file "/etc/coreos/update.conf" then (
+    role := `Root;
+    data := { !data with format = Some FORMAT_INSTALLED };
+    data := Inspect_fs_unix.check_coreos_root !data;
+  )
+  (* Linux /usr/local? *)
+  else if Is.is_dir "/etc" &&
+          Is.is_dir "/bin" &&
+          Is.is_dir "/share" &&
+          not (Is.is_dir "/local") &&
+          not (Is.is_file "/etc/fstab") then
+    ()
+  (* Linux /usr? *)
+  else if Is.is_dir "/etc" &&
+          Is.is_dir "/bin" &&
+          Is.is_dir "/share" &&
+          Is.is_dir "/local" &&
+          not (Is.is_file "/etc/fstab") then (
+    data := Inspect_fs_unix.check_linux_usr !data;
+  )
+  (* CoreOS /usr? *)
+  else if Is.is_dir "/bin" &&
+          Is.is_dir "/share" &&
+          Is.is_dir "/local" &&
+          Is.is_dir "/share/coreos" then (
+    data := Inspect_fs_unix.check_coreos_usr !data;
+  )
+  (* Linux /var? *)
+  else if Is.is_dir "/log" &&
+          Is.is_dir "/run" &&
+          Is.is_dir "/spool" then
+    ()
+  (* Windows root? *)
+  else if Inspect_fs_windows.is_windows_systemroot () then (
+    role := `Root;
+    data := { !data with format = Some FORMAT_INSTALLED };
+    data := 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
+    ()
+  (* Windows volume (but not root)? *)
+  else if is_dir_nocase "/System Volume Information" then
+    ()
+  (* FreeDOS? *)
+  else if is_dir_nocase "/FDOS" &&
+          is_file_nocase "/FDOS/FREEDOS.BSS" then (
+    role := `Root;
+    data := { !data with
+              format = Some FORMAT_INSTALLED;
+              os_type = Some OS_TYPE_DOS;
+              distro = Some DISTRO_FREEDOS;
+              (* FreeDOS is a mix of 16 and 32 bit, but
+               * assume it requires a 32 bit i386 processor.
+               *)
+              arch = Some "i386" }
+  )
+  (* Install CD/disk?
+   *
+   * Note that we checked (above) for an install ISO, but there are
+   * other types of install image (eg. USB keys) which that check
+   * wouldn't have picked up.
+   *
+   * Skip these checks if it's not a whole device (eg. CD) or the
+   * first partition (eg. bootable USB key).
+   *)
+  else if (is_whole_device || is_only_partition) &&
+          Is.is_file "/isolinux/isolinux.cfg" ||
+          Is.is_dir "/EFI/BOOT" ||
+          Is.is_file "/images/install.img" ||
+          Is.is_dir "/.disk" ||
+          Is.is_file "/.discinfo" ||
+          Is.is_file "/i386/txtsetup.sif" ||
+          Is.is_file "/amd64/txtsetup.sif" ||
+          Is.is_file "/freedos/freedos.ico" ||
+          Is.is_file "/boot/loader.rc" then (
+    role := `Root;
+    data := { !data with format = Some FORMAT_INSTALLER };
+    data := Inspect_fs_cd.check_installer_root !data;
+  );
+
+  (* The above code should have set [data.os_type] and [data.distro]
+   * fields, so we can now guess the package management system.
+   *)
+  let data = !data in
+  let data = { data with
+               package_format = check_package_format data;
+               package_management = check_package_management data } in
+  match !role with
+  | `Root -> Some (RoleRoot data)
+  | `Usr -> Some (RoleUsr data)
+  | `Other -> Some RoleOther
+
+(* The mountable is the first and only partition on a device
+ * with a single device.
+ *)
+and is_only_partition = function
+  | { m_type = MountablePath | MountableBtrfsVol _ } -> false
+  | { m_type = MountableDevice; m_device = device } ->
+     let partnum, nr_partitions = get_partition_context device in
+     partnum = 1 && nr_partitions = 1
+
+and get_partition_context partition =
+  let partnum = Devsparts.part_to_partnum partition in
+  let device = Devsparts.part_to_dev partition in
+  let nr_partitions = List.length (Parted.part_list device) in
+  partnum, nr_partitions
+
+and is_symlink_to file wanted_target =
+  if not (Is.is_symlink file) then false
+  else Link.readlink file = wanted_target
+
+and 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
+
+(* At the moment, package format and package management are just a
+ * simple function of the [distro] and [version[0]] fields, so these
+ * can never return an error.  We might be cleverer in future.
+ *)
+and check_package_format { distro = distro } =
+  match distro with
+  | None -> None
+  | Some DISTRO_FEDORA
+  | Some DISTRO_MEEGO
+  | Some DISTRO_REDHAT_BASED
+  | Some DISTRO_RHEL
+  | Some DISTRO_MAGEIA
+  | Some DISTRO_MANDRIVA
+  | Some DISTRO_SUSE_BASED
+  | Some DISTRO_OPENSUSE
+  | Some DISTRO_SLES
+  | Some DISTRO_CENTOS
+  | Some DISTRO_SCIENTIFIC_LINUX
+  | Some DISTRO_ORACLE_LINUX
+  | Some DISTRO_ALTLINUX ->
+     Some PACKAGE_FORMAT_RPM
+  | Some DISTRO_DEBIAN
+  | Some DISTRO_UBUNTU
+  | Some DISTRO_LINUX_MINT ->
+     Some PACKAGE_FORMAT_DEB
+  | Some DISTRO_ARCHLINUX ->
+     Some PACKAGE_FORMAT_PACMAN
+  | Some DISTRO_GENTOO ->
+     Some PACKAGE_FORMAT_EBUILD
+  | Some DISTRO_PARDUS ->
+     Some PACKAGE_FORMAT_PISI
+  | Some DISTRO_ALPINE_LINUX ->
+     Some PACKAGE_FORMAT_APK
+  | Some DISTRO_VOID_LINUX ->
+     Some PACKAGE_FORMAT_XBPS
+  | Some DISTRO_SLACKWARE
+  | Some DISTRO_TTYLINUX
+  | Some DISTRO_COREOS
+  | Some DISTRO_WINDOWS
+  | Some DISTRO_BUILDROOT
+  | Some DISTRO_CIRROS
+  | Some DISTRO_FREEDOS
+  | Some DISTRO_FREEBSD
+  | Some DISTRO_NETBSD
+  | Some DISTRO_OPENBSD
+  | Some DISTRO_FRUGALWARE
+  | Some DISTRO_PLD_LINUX ->
+     None
+
+and check_package_management { distro = distro; version = version } =
+  let major = match version with None -> 0 | Some (major, _) -> major in
+  match distro with
+  | None -> None
+
+  | Some DISTRO_MEEGO ->
+     Some PACKAGE_MANAGEMENT_YUM
+
+  | Some DISTRO_FEDORA ->
+    (* If Fedora >= 22 and dnf is installed, say "dnf". *)
+     if major >= 22 && Is.is_file ~followsymlinks:true "/usr/bin/dnf" then
+       Some PACKAGE_MANAGEMENT_DNF
+     else if major >= 1 then
+       Some PACKAGE_MANAGEMENT_YUM
+     else
+       (* Probably parsing the release file failed, see RHBZ#1332025. *)
+       None
+
+  | Some DISTRO_REDHAT_BASED
+  | Some DISTRO_RHEL
+  | Some DISTRO_CENTOS
+  | Some DISTRO_SCIENTIFIC_LINUX
+  | Some DISTRO_ORACLE_LINUX ->
+     if major >= 8 then
+       Some PACKAGE_MANAGEMENT_DNF
+     else if major >= 5 then
+       Some PACKAGE_MANAGEMENT_YUM
+     else if major >= 2 then
+       Some PACKAGE_MANAGEMENT_UP2DATE
+     else
+       (* Probably parsing the release file failed, see RHBZ#1332025. *)
+       None
+
+  | Some DISTRO_DEBIAN
+  | Some DISTRO_UBUNTU
+  | Some DISTRO_LINUX_MINT
+  | Some DISTRO_ALTLINUX ->
+     Some PACKAGE_MANAGEMENT_APT
+
+  | Some DISTRO_ARCHLINUX ->
+     Some PACKAGE_MANAGEMENT_PACMAN
+
+  | Some DISTRO_GENTOO ->
+     Some PACKAGE_MANAGEMENT_PORTAGE
+
+  | Some DISTRO_PARDUS ->
+     Some PACKAGE_MANAGEMENT_PISI
+
+  | Some DISTRO_MAGEIA
+  | Some DISTRO_MANDRIVA ->
+     Some PACKAGE_MANAGEMENT_URPMI
+
+  | Some DISTRO_SUSE_BASED
+  | Some DISTRO_OPENSUSE
+  | Some DISTRO_SLES ->
+     Some PACKAGE_MANAGEMENT_ZYPPER
+
+  | Some DISTRO_ALPINE_LINUX ->
+     Some PACKAGE_MANAGEMENT_APK
+
+  | Some DISTRO_VOID_LINUX ->
+     Some PACKAGE_MANAGEMENT_XBPS;
+
+  | Some DISTRO_SLACKWARE
+  | Some DISTRO_TTYLINUX
+  | Some DISTRO_COREOS
+  | Some DISTRO_WINDOWS
+  | Some DISTRO_BUILDROOT
+  | Some DISTRO_CIRROS
+  | Some DISTRO_FREEDOS
+  | Some DISTRO_FREEBSD
+  | Some DISTRO_NETBSD
+  | Some DISTRO_OPENBSD
+  | Some DISTRO_FRUGALWARE
+  | Some DISTRO_PLD_LINUX ->
+    None
+
diff --git a/daemon/inspect_fs.mli b/daemon/inspect_fs.mli
new file mode 100644
index 000000000..53ea01587
--- /dev/null
+++ b/daemon/inspect_fs.mli
@@ -0,0 +1,23 @@
+(* 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_for_filesystem_on : Mountable.t -> string ->
+                              Inspect_types.fs option
+(** [check_for_filesystem_on cmdline mountable vfs_type] inspects
+    [mountable] looking for a single mountpoint from an operating
+    system. *)
diff --git a/daemon/inspect_fs_cd.ml b/daemon/inspect_fs_cd.ml
new file mode 100644
index 000000000..d16ee8095
--- /dev/null
+++ b/daemon/inspect_fs_cd.ml
@@ -0,0 +1,23 @@
+(* 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.
+ *)
+
+let check_installer_iso device =
+  None (* XXX *)
+
+let check_installer_root data =
+  data (* XXX *)
diff --git a/daemon/inspect_fs_cd.mli b/daemon/inspect_fs_cd.mli
new file mode 100644
index 000000000..052753336
--- /dev/null
+++ b/daemon/inspect_fs_cd.mli
@@ -0,0 +1,25 @@
+(* 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_installer_iso : string -> Inspect_types.role option
+(** Check the named device to see if it could be an install ISO image.
+    If so, returns [Some (RoleRoot ...)]. *)
+
+val check_installer_root : Inspect_types.inspection_data ->
+                           Inspect_types.inspection_data
+(** Inspect the install CD filesystem mounted on sysroot. *)
diff --git a/daemon/inspect_fs_unix.ml b/daemon/inspect_fs_unix.ml
new file mode 100644
index 000000000..87dd4bd9e
--- /dev/null
+++ b/daemon/inspect_fs_unix.ml
@@ -0,0 +1,642 @@
+(* 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 C_utils
+open Std_utils
+
+open Utils
+open Inspect_types
+
+let re_major_minor = Str.regexp "\\([0-9]+\\)\\.\\([0-9]+\\)"
+let re_major_no_minor = Str.regexp "\\([0-9]+\\)"
+
+let re_fedora = Str.regexp "Fedora release \\([0-9]+\\)"
+let re_rhel_old = Str.regexp "Red Hat.*release \\([0-9]+\\).*Update \\([0-9]+\\)"
+let re_rhel = Str.regexp "Red Hat.*release \\([0-9]+\\)\\.\\([0-9]+\\)"
+let re_rhel_no_minor = Str.regexp "Red Hat.*release \\([0-9]+\\)"
+let re_centos_old = Str.regexp "CentOS.*release \\([0-9]+\\).*Update \\([0-9]+\\)"
+let re_centos = Str.regexp "CentOS.*release \\([0-9]+\\)\\.\\([0-9]+\\)"
+let re_centos_no_minor = Str.regexp "CentOS.*release \\([0-9]+\\)"
+let re_scientific_linux_old =
+  Str.regexp "Scientific Linux.*release \\([0-9]+\\).*Update \\([0-9]+\\)"
+let re_scientific_linux =
+  Str.regexp "Scientific Linux.*release \\([0-9]+\\)\\.\\([0-9]+\\)"
+let re_scientific_linux_no_minor =
+  Str.regexp "Scientific Linux.*release \\([0-9]+\\)"
+let re_oracle_linux_old =
+  Str.regexp "Oracle Linux.*release \\([0-9]+\\).*Update \\([0-9]+\\)"
+let re_oracle_linux =
+  Str.regexp "Oracle Linux.*release \\([0-9]+\\)\\.\\([0-9]+\\)"
+let re_oracle_linux_no_minor = Str.regexp "Oracle Linux.*release \\([0-9]+\\)"
+let re_netbsd = Str.regexp "^NetBSD \\([0-9]+\\)\\.\\([0-9]+\\)"
+let re_opensuse = Str.regexp "^\\(openSUSE|SuSE Linux|SUSE LINUX\\) "
+let re_sles = Str.regexp "^SUSE \\(Linux|LINUX\\) Enterprise "
+let re_nld = Str.regexp "^Novell Linux Desktop "
+let re_sles_version = Str.regexp "^VERSION = \\([0-9]+\\)"
+let re_sles_patchlevel = Str.regexp "^PATCHLEVEL = \\([0-9]+\\)"
+let re_minix = Str.regexp "^\\([0-9]+\\)\\.\\([0-9]+\\)\\(\\.\\([0-9]+\\)\\)?"
+let re_openbsd = Str.regexp "^OpenBSD \\([0-9]+|\\?\\)\\.\\([0-9]+|\\?\\)"
+let re_frugalware = Str.regexp "Frugalware \\([0-9]+\\)\\.\\([0-9]+\\)"
+let re_pldlinux = Str.regexp "\\([0-9]+\\)\\.\\([0-9]+\\) PLD Linux"
+
+let re_openbsd_duid = Str.regexp "^[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\\.\\([a-z]\\)"
+
+let rec check_linux_root mountable data =
+  let os_type = OS_TYPE_LINUX in
+  let data = { data with os_type = Some os_type } in
+
+  let tests = [
+    (* systemd distros include /etc/os-release which is reasonably
+     * standardized.  This entry should be first.
+     *)
+    "/etc/os-release",     parse_os_release;
+    (* LSB is also a reasonable standard.  This entry should be second. *)
+    "/etc/lsb-release",    parse_lsb_release;
+
+    (* Now we enter the Wild West ... *)
+
+    (* RHEL-based distros include a [/etc/redhat-release] file, hence their
+     * checks need to be performed before the Red-Hat one.
+     *)
+    "/etc/oracle-release", parse_generic ~rex:re_oracle_linux_old
+                                         DISTRO_ORACLE_LINUX;
+    "/etc/oracle-release", parse_generic ~rex:re_oracle_linux
+                                         DISTRO_ORACLE_LINUX;
+    "/etc/oracle-release", parse_generic ~rex:re_oracle_linux_no_minor
+                                         DISTRO_ORACLE_LINUX;
+    "/etc/centos-release", parse_generic ~rex:re_centos_old
+                                         DISTRO_CENTOS;
+    "/etc/centos-release", parse_generic ~rex:re_centos
+                                         DISTRO_CENTOS;
+    "/etc/centos-release", parse_generic ~rex:re_centos_no_minor
+                                         DISTRO_CENTOS;
+    "/etc/altlinux-release", parse_generic DISTRO_ALTLINUX;
+    "/etc/redhat-release", parse_generic ~rex:re_fedora
+                                         DISTRO_FEDORA;
+    "/etc/redhat-release", parse_generic ~rex:re_rhel_old
+                                         DISTRO_RHEL;
+    "/etc/redhat-release", parse_generic ~rex:re_rhel
+                                         DISTRO_RHEL;
+    "/etc/redhat-release", parse_generic ~rex:re_rhel_no_minor
+                                         DISTRO_RHEL;
+    "/etc/redhat-release", parse_generic ~rex:re_centos_old
+                                         DISTRO_CENTOS;
+    "/etc/redhat-release", parse_generic ~rex:re_centos
+                                         DISTRO_CENTOS;
+    "/etc/redhat-release", parse_generic ~rex:re_centos_no_minor
+                                         DISTRO_CENTOS;
+    "/etc/redhat-release", parse_generic ~rex:re_scientific_linux_old
+                                         DISTRO_SCIENTIFIC_LINUX;
+    "/etc/redhat-release", parse_generic ~rex:re_scientific_linux
+                                         DISTRO_SCIENTIFIC_LINUX;
+    "/etc/redhat-release", parse_generic ~rex:re_scientific_linux_no_minor
+                                         DISTRO_SCIENTIFIC_LINUX;
+
+    (* If there's an /etc/redhat-release file, but nothing above
+     * matches, then it is a generic Red Hat-based distro.
+     *)
+    "/etc/redhat-release", parse_generic DISTRO_REDHAT_BASED;
+    "/etc/redhat-release",
+      (fun _ data -> { data with distro = Some DISTRO_REDHAT_BASED });
+
+    "/etc/debian_version", parse_generic DISTRO_DEBIAN;
+    "/etc/pardus-release", parse_generic DISTRO_PARDUS;
+
+    (* /etc/arch-release file is empty and I can't see a way to
+     * determine the actual release or product string.
+     *)
+    "/etc/arch-release",
+      (fun _ data -> { data with distro = Some DISTRO_ARCHLINUX });
+
+    "/etc/gentoo-release", parse_generic DISTRO_GENTOO;
+    "/etc/meego-release", parse_generic DISTRO_MEEGO;
+    "/etc/slackware-version", parse_generic DISTRO_SLACKWARE;
+    "/etc/ttylinux-target", parse_generic DISTRO_TTYLINUX;
+
+    "/etc/SuSE-release", parse_suse_release;
+    "/etc/SuSE-release",
+      (fun _ data -> { data with distro = Some DISTRO_SUSE_BASED });
+
+    "/etc/cirros/version", parse_generic DISTRO_CIRROS;
+    "/etc/br-version",
+      (fun release_file data ->
+        let distro =
+          if Is.is_file ~followsymlinks:true "/usr/share/cirros/logo" then
+            DISTRO_CIRROS
+          else
+            DISTRO_BUILDROOT in
+        (* /etc/br-version has the format YYYY.MM[-git/hg/svn release] *)
+        parse_generic distro release_file data);
+
+    "/etc/alpine-release", parse_generic DISTRO_ALPINE_LINUX;
+    "/etc/frugalware-release", parse_generic ~rex:re_frugalware
+                                             DISTRO_FRUGALWARE;
+    "/etc/pld-release", parse_generic ~rex:re_pldlinux
+                                      DISTRO_PLD_LINUX;
+  ] in
+
+  let rec loop = function
+    | (release_file, parse_fun) :: tests ->
+       if verbose () then
+         eprintf "check_linux_root: checking %s\n%!" release_file;
+       (try
+          if not (Is.is_file ~followsymlinks:true release_file) then
+            raise Not_found;
+          parse_fun release_file data
+        with
+          Not_found -> loop tests)
+    | [] -> data
+  in
+  let data = loop tests in
+
+  let data = {
+    data with
+      arch = check_architecture ();
+      fstab = check_fstab ~mdadm:true mountable os_type ();
+      hostname = check_hostname_linux ();
+  } in
+
+  data
+
+(* Parse a os-release file.
+ *
+ * Only few fields are parsed, falling back to the usual detection if we
+ * cannot read all of them.
+ *
+ * For the format of os-release, see also:
+ * http://www.freedesktop.org/software/systemd/man/os-release.html
+ *)
+and parse_os_release release_file data =
+  let chroot = Chroot.create (Sysroot.sysroot ()) in
+  let lines =
+    Chroot.f chroot (
+      fun () ->
+        if not (is_small_file release_file) then (
+          eprintf "%s: not a regular file or too large\n" release_file;
+          raise Not_found
+        );
+        read_whole_file release_file
+  ) () in
+  let lines = String.nsplit "\n" lines in
+
+  let data = List.fold_left (
+    fun data line ->
+      let line = String.trim line in
+      if line = "" || line.[0] = '#' then
+        data
+      else (
+        let key, value = String.split "=" line in
+        let value =
+          let n = String.length value in
+          if n >= 2 && value.[0] = '"' && value.[n-1] = '"' then
+            String.sub value 1 (n-2)
+          else
+            value in
+        if key = "ID" then (
+          let distro = distro_of_os_release_id value in
+          match distro with
+          | Some _ as distro -> { data with distro = distro }
+          | None -> data
+        )
+        else if key = "PRETTY_NAME" then
+          { data with product_name = Some value }
+        else if key = "VERSION_ID" then
+          parse_version_from_major_minor value data
+        else
+          data
+      )
+  ) data lines in
+
+  (* os-release in Debian and CentOS does not provide the full
+   * version number (VERSION_ID), just the major part of it.  If
+   * we detect that situation then bail out and use the release
+   * files instead.
+   *)
+  (match data with
+   | { distro = Some (DISTRO_DEBIAN|DISTRO_CENTOS); version = Some (_, 0) } ->
+      raise Not_found
+   | _ -> ()
+  );
+
+  data
+
+(* ID="fedora" => Some DISTRO_FEDORA *)
+and distro_of_os_release_id = function
+  | "alpine" -> Some DISTRO_ALPINE_LINUX
+  | "altlinux" -> Some DISTRO_ALTLINUX
+  | "arch" -> Some DISTRO_ARCHLINUX
+  | "centos" -> Some DISTRO_CENTOS
+  | "coreos" -> Some DISTRO_COREOS
+  | "debian" -> Some DISTRO_DEBIAN
+  | "fedora" -> Some DISTRO_FEDORA
+  | "frugalware" -> Some DISTRO_FRUGALWARE
+  | "mageia" -> Some DISTRO_MAGEIA
+  | "opensuse" -> Some DISTRO_OPENSUSE
+  | "pld" -> Some DISTRO_PLD_LINUX
+  | "rhel" -> Some DISTRO_RHEL
+  | "sles" | "sled" -> Some DISTRO_SLES
+  | "ubuntu" -> Some DISTRO_UBUNTU
+  | "void" -> Some DISTRO_VOID_LINUX
+  | value ->
+     eprintf "/etc/os-release: unknown ID=%s\n" value;
+     None
+
+(* Ubuntu has /etc/lsb-release containing:
+ *   DISTRIB_ID=Ubuntu                                # Distro
+ *   DISTRIB_RELEASE=10.04                            # Version
+ *   DISTRIB_CODENAME=lucid
+ *   DISTRIB_DESCRIPTION="Ubuntu 10.04.1 LTS"         # Product name
+ *
+ * [Ubuntu-derived ...] Linux Mint was found to have this:
+ *   DISTRIB_ID=LinuxMint
+ *   DISTRIB_RELEASE=10
+ *   DISTRIB_CODENAME=julia
+ *   DISTRIB_DESCRIPTION="Linux Mint 10 Julia"
+ * Linux Mint also has /etc/linuxmint/info with more information,
+ * but we can use the LSB file.
+ *
+ * Mandriva has:
+ *   LSB_VERSION=lsb-4.0-amd64:lsb-4.0-noarch
+ *   DISTRIB_ID=MandrivaLinux
+ *   DISTRIB_RELEASE=2010.1
+ *   DISTRIB_CODENAME=Henry_Farman
+ *   DISTRIB_DESCRIPTION="Mandriva Linux 2010.1"
+ * Mandriva also has a normal release file called /etc/mandriva-release.
+ *
+ * CoreOS has a /etc/lsb-release link to /usr/share/coreos/lsb-release containing:
+ *   DISTRIB_ID=CoreOS
+ *   DISTRIB_RELEASE=647.0.0
+ *   DISTRIB_CODENAME="Red Dog"
+ *   DISTRIB_DESCRIPTION="CoreOS 647.0.0"
+ *)
+and parse_lsb_release release_file data =
+  let chroot = Chroot.create (Sysroot.sysroot ()) in
+  let lines =
+    Chroot.f chroot (
+      fun () ->
+        if not (is_small_file release_file) then (
+          eprintf "%s: not a regular file or too large\n" release_file;
+          raise Not_found
+        );
+        read_whole_file release_file
+  ) () in
+  let lines = String.nsplit "\n" lines in
+
+  let data = List.fold_left (
+    fun data line ->
+      if data.distro = None && line = "DISTRIB_ID=Ubuntu" then
+        { data with distro = Some DISTRO_UBUNTU }
+      else if data.distro = None && line = "DISTRIB_ID=LinuxMint" then
+        { data with distro = Some DISTRO_LINUX_MINT }
+      else if data.distro = None && line = "DISTRIB_ID=\"Mageia\"" then
+        { data with distro = Some DISTRO_MAGEIA }
+      else if data.distro = None && line = "DISTRIB_ID=CoreOS" then
+        { data with distro = Some DISTRO_COREOS }
+      else if String.is_prefix line "DISTRIB_RELEASE=" then
+        parse_version_from_major_minor line data
+      else if String.is_prefix line "DISTRIB_DESCRIPTION=\"" ||
+                String.is_prefix line "DISTRIB_DESCRIPTION='" then (
+        let n = String.length line in
+        let product_name = String.sub line 21 (n-20) in
+        { data with product_name = Some product_name }
+      )
+      else if String.is_prefix line "DISTRIB_DESCRIPTION=" then (
+        let n = String.length line in
+        let product_name = String.sub line 20 (n-20) in
+        { data with product_name = Some product_name }
+      )
+      else
+        data
+  ) data lines in
+
+  data
+
+and parse_suse_release release_file data =
+  let chroot = Chroot.create (Sysroot.sysroot ()) in
+  let lines =
+    Chroot.f chroot (
+      fun () ->
+        if not (is_small_file release_file) then (
+          eprintf "%s: not a regular file or too large\n" release_file;
+          raise Not_found
+        );
+        read_whole_file release_file
+  ) () in
+  let lines = String.nsplit "\n" lines in
+
+  if lines = [] then raise Not_found;
+
+  (* First line is dist release name. *)
+  let product_name = List.hd lines in
+  let data = {
+    data with
+      product_name = Some product_name
+  } in
+
+  (* Match SLES first because openSuSE regex overlaps some SLES
+   * release strings.
+   *)
+  if Str.string_match re_sles product_name 0 ||
+     Str.string_match re_nld product_name 0 then (
+    (* Second line contains version string. *)
+    let major =
+      if List.length lines >= 2 then (
+        let line = List.nth lines 1 in
+        if Str.string_match re_sles_version line 0 then
+          Some (int_of_string (Str.matched_group 1 line))
+        else None
+      )
+      else None in
+
+    (* Third line contains service pack string. *)
+    let minor =
+      if List.length lines >= 3 then (
+        let line = List.nth lines 2 in
+        if Str.string_match re_sles_patchlevel line 0 then
+          Some (int_of_string (Str.matched_group 1 line))
+        else None
+      )
+      else None in
+
+    let version =
+      match major, minor with
+      | Some major, Some minor -> Some (major, minor)
+      | Some major, None -> Some (major, 0)
+      | None, Some _ | None, None -> None in
+
+    { data with
+        distro = Some DISTRO_SLES;
+        version = version }
+  )
+  else if Str.string_match re_opensuse product_name 0 then (
+    (* Second line contains version string. *)
+    let data =
+      if List.length lines >= 2 then (
+        let line = List.nth lines 1 in
+        parse_version_from_major_minor line data
+      )
+      else data in
+
+    { data with distro = Some DISTRO_OPENSUSE }
+  )
+  else
+    data
+
+(* Parse any generic /etc/x-release file.
+ *
+ * The optional regular expression which may match 0, 1 or 2
+ * substrings, which are used as the major and minor numbers.
+ *
+ * The fixed distro is always set, and the product name is
+ * set to the first line of the release file.
+ *)
+and parse_generic ?rex distro release_file data =
+  let chroot = Chroot.create (Sysroot.sysroot ()) in
+  let product_name =
+    Chroot.f chroot (
+      fun () ->
+        if not (is_small_file release_file) then (
+          eprintf "%s: not a regular file or too large\n" release_file;
+          raise Not_found
+        );
+        read_first_line_from_file release_file
+  ) () in
+  if product_name = "" then
+    raise Not_found;
+
+  let data =
+    { data with product_name = Some product_name;
+                distro = Some distro } in
+
+  match rex with
+  | Some rex ->
+     (* If ~rex was supplied, then it must match the release file,
+      * else the parsing fails.
+      *)
+     if not (Str.string_match rex product_name 0) then
+       raise Not_found;
+
+    (* Although it's not documented, matched_group raises
+     * Invalid_argument if called with an unknown group number.
+     *)
+    let major =
+      try Some (int_of_string (Str.matched_group 1 product_name))
+      with Not_found | Invalid_argument _ | Failure _ -> None in
+    let minor =
+      try Some (int_of_string (Str.matched_group 2 product_name))
+      with Not_found | Invalid_argument _ | Failure _ -> None in
+    (match major, minor with
+     | None, None -> data
+     | None, Some _ -> data
+     | Some major, None -> { data with version = Some (major, 0) }
+     | Some major, Some minor -> { data with version = Some (major, minor) }
+    )
+
+  | None ->
+     (* However if no ~rex was supplied, then we make a best
+      * effort attempt to parse a version number, but don't
+      * fail if one cannot be found.
+      *)
+     parse_version_from_major_minor product_name data
+
+(* Make a best effort attempt to parse either X or X.Y from a string,
+ * usually the product_name string.
+ *)
+and parse_version_from_major_minor str data =
+  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 -> data
+    | None, Some _ -> data
+    | Some major, None -> { data with version = Some (major, 0) }
+    | Some major, Some minor -> { data with version = Some (major, minor) }
+  )
+  else (
+    eprintf "parse_version_from_major_minor: cannot parse version from ‘%s’\n"
+            str;
+    data
+  )
+
+and check_architecture () =
+  (* XXX *) None
+
+and check_hostname_linux () =
+  (* XXX *) None
+
+and check_fstab ?(mdadm = false) (root_mountable : Mountable.t) os_type () =
+  let configfiles = "/etc/fstab" :: if mdadm then ["/etc/mdadm.conf"] else [] in
+
+  with_augeas configfiles (
+    fun aug ->
+      let is_bsd =
+        os_type = OS_TYPE_FREEBSD ||
+        os_type = OS_TYPE_NETBSD ||
+        os_type = OS_TYPE_OPENBSD in
+
+      (* Generate a map of MD device paths listed in /etc/mdadm.conf
+       * to MD device paths in the guestfs appliance.
+       *)
+      let md_map = if mdadm then map_md_devices () else StringMap.empty in
+
+      let path = "/files/etc/fstab/*[label() != '#comment']" in
+      let entries = Augeas.matches aug path in
+      filter_map (
+        fun entry ->
+          let spec = Augeas.get aug (entry ^ "/spec") in
+          let mp = Augeas.get aug (entry ^ "/file") in
+          let vfstype = Augeas.get aug (entry ^ "/vfstype") in
+
+          match spec, mp, vfstype with
+          | None, _, _ | Some _, None, _ | Some _, Some _, None -> None
+          | Some spec, Some mp, Some vfstype ->
+             (* Ignore /dev/fd (floppy disks) (RHBZ#642929) and
+              * CD-ROM drives.
+              *
+              * /dev/iso9660/FREEBSD_INSTALL can be found in FreeBSD's
+              * installation discs.
+              *)
+             if (String.is_prefix spec "/dev/fd" &&
+                 String.length spec >= 8 && Char.isdigit spec.[7]) ||
+                (String.is_prefix spec "/dev/cd" &&
+                 String.length spec >= 8 && Char.isdigit spec.[7]) ||
+                spec = "/dev/floppy" ||
+                spec = "/dev/cdrom" ||
+                String.is_prefix spec "/dev/iso9660/" then
+               None
+             else (
+               (* Canonicalize the path, so "///usr//local//"
+                * -> "/usr/local"
+                *)
+               let mp = canonical_mountpoint mp in
+
+               (* Ignore certain mountpoints. *)
+               if String.is_prefix mp "/dev/" ||
+                  mp = "/dev" ||
+                  String.is_prefix mp "/media/" ||
+                  String.is_prefix mp "/proc/" ||
+                  mp = "/proc" ||
+                  String.is_prefix mp "/selinux/" ||
+                  mp = "/selinux" ||
+                  String.is_prefix mp "/sys/" ||
+                  mp = "/sys" then
+                 None
+               else (
+                 let mountable =
+                   (* Resolve UUID= and LABEL= to the actual device. *)
+                   if String.is_prefix spec "UUID=" then
+                     Some (Mountable.of_device
+                             (Findfs.findfs_uuid (shell_unquote spec)))
+                   else if String.is_prefix spec "LABEL=" then
+                     Some (Mountable.of_device
+                             (Findfs.findfs_label (shell_unquote spec)))
+                   (* Resolve /dev/root to the current device.
+                    * Do the same for the / partition of the *BSD
+                    * systems, since the BSD -> Linux device
+                    * translation is not straight forward.
+                    *)
+                   else if spec = "/dev/root" || (is_bsd && mp = "/") then
+                     Some root_mountable
+                   (* Resolve guest block device names. *)
+                   else if String.is_prefix spec "/dev/" then
+                     Some (resolve_fstab_device spec md_map os_type)
+                   (* In OpenBSD's fstab you can specify partitions
+                    * on a disk by appending a period and a partition
+                    * letter to a Disklable Unique Identifier. The
+                    * DUID is a 16 hex digit field found in the
+                    * OpenBSD's altered BSD disklabel. For more info
+                    * see here:
+                    * http://www.openbsd.org/faq/faq14.html#intro
+                    *)
+                   else if Str.string_match re_openbsd_duid spec 0 then (
+                     let part = Str.matched_group 1 spec in
+                     (* We cannot peep into disklabels, we can only
+                      * assume that this is the first disk.
+                      *)
+                     let device = sprintf "/dev/sd0%s" part in
+                     Some (resolve_fstab_device device md_map os_type)
+                   )
+                   (* Ignore "/.swap" (Pardus) and pseudo-devices
+                    * like "tmpfs".  If we haven't resolved the device
+                    * successfully by this point, just ignore it.
+                    *)
+                   else
+                     None in
+
+                 match mountable with
+                 | None -> None
+                 | Some mountable ->
+                    let mountable =
+                      if vfstype = "btrfs" then (
+                        (* XXX *) mountable
+                      )
+                      else mountable in
+
+                    Some (mountable, mp)
+               )
+             )
+        ) entries (* filter_map *)
+  ) (* with_augeas *)
+
+and map_md_devices () =
+  (* XXX *) StringMap.empty
+
+and resolve_fstab_device spec md_map os_type =
+  (* XXX *) Mountable.of_device spec
+
+and canonical_mountpoint = (* XXX *) identity
+
+let check_linux_usr data =
+  (* XXX *) data
+
+let check_coreos_root data =
+  (* XXX *) data
+
+let check_coreos_usr data =
+  (* XXX *) data
+
+let check_freebsd_root data =
+  (* XXX *) data
+
+and check_hostname_freebsd () =
+  (* XXX *) None
+
+let check_netbsd_root data =
+  (* XXX *) data
+
+let rec check_openbsd_root data =
+  (* XXX *) data
+
+and check_hostname_openbsd () =
+  (* XXX *) None
+
+let check_hurd_root data =
+  (* XXX *) data
+
+let rec check_minix_root data =
+  (* XXX *) data
+
+and check_hostname_minix () =
+  (* XXX *) None
diff --git a/daemon/inspect_fs_unix.mli b/daemon/inspect_fs_unix.mli
new file mode 100644
index 000000000..7ca1e2fbb
--- /dev/null
+++ b/daemon/inspect_fs_unix.mli
@@ -0,0 +1,53 @@
+(* 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_coreos_usr : Inspect_types.inspection_data ->
+                        Inspect_types.inspection_data
+(** Inspect the CoreOS [/usr] filesystem mounted on sysroot. *)
+
+val check_coreos_root : Inspect_types.inspection_data ->
+                        Inspect_types.inspection_data
+(** Inspect the CoreOS filesystem mounted on sysroot. *)
+
+val check_freebsd_root : Inspect_types.inspection_data ->
+                         Inspect_types.inspection_data
+(** Inspect the FreeBSD filesystem mounted on sysroot. *)
+
+val check_hurd_root : Inspect_types.inspection_data ->
+                      Inspect_types.inspection_data
+(** Inspect the Hurd filesystem mounted on sysroot. *)
+
+val check_linux_usr : Inspect_types.inspection_data ->
+                       Inspect_types.inspection_data
+(** Inspect the Linux [/usr] filesystem mounted on sysroot. *)
+
+val check_linux_root : Mountable.t -> Inspect_types.inspection_data ->
+                       Inspect_types.inspection_data
+(** Inspect the Linux filesystem mounted on sysroot. *)
+
+val check_minix_root : Inspect_types.inspection_data ->
+                       Inspect_types.inspection_data
+(** Inspect the Minix filesystem mounted on sysroot. *)
+
+val check_netbsd_root : Inspect_types.inspection_data ->
+                        Inspect_types.inspection_data
+(** Inspect the NetBSD filesystem mounted on sysroot. *)
+
+val check_openbsd_root : Inspect_types.inspection_data ->
+                         Inspect_types.inspection_data
+(** Inspect the OpenBSD filesystem mounted on sysroot. *)
diff --git a/daemon/inspect_fs_windows.ml b/daemon/inspect_fs_windows.ml
new file mode 100644
index 000000000..9bc2afbc4
--- /dev/null
+++ b/daemon/inspect_fs_windows.ml
@@ -0,0 +1,23 @@
+(* 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.
+ *)
+
+let rec check_windows_root data =
+  (* XXX *) data
+
+and is_windows_systemroot () =
+  (* XXX *) false
diff --git a/daemon/inspect_fs_windows.mli b/daemon/inspect_fs_windows.mli
new file mode 100644
index 000000000..936d695c6
--- /dev/null
+++ b/daemon/inspect_fs_windows.mli
@@ -0,0 +1,25 @@
+(* 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 ->
+                         Inspect_types.inspection_data
+(** 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. *)
diff --git a/daemon/inspect_types.ml b/daemon/inspect_types.ml
new file mode 100644
index 000000000..bdf22e50e
--- /dev/null
+++ b/daemon/inspect_types.ml
@@ -0,0 +1,325 @@
+(* 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 = {
+  format : format option;
+  os_type : os_type option;
+  distro : distro option;
+  package_format : package_format option;
+  package_management : package_management option;
+  product_name : string option;
+  product_variant : string option;
+  version : version option;
+  arch : string option;
+  hostname : string option;
+  fstab : fstab_entry list;
+  windows_systemroot : string option;
+  windows_software_hive : string option;
+  windows_system_hive : string option;
+  windows_current_control_set : string option;
+  drive_mappings : drive_mapping list;
+  is_live_disk : bool;
+  is_netinst_disk : bool;
+  is_multipart_disk : bool;
+}
+and format =
+  | FORMAT_INSTALLED
+  | FORMAT_INSTALLER
+  (* in future: supplemental install disks *)
+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, mountable *)
+
+let rec string_of_fs { fs_location = location; role = role } =
+  sprintf "fs: %s role: %s"
+          (string_of_location location)
+          (match role with
+           | RoleRoot _ -> "root"
+           | RoleUsr _ -> "usr"
+           | 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 "\tformat: %s\n" (string_of_format v))
+      data.format;
+  may (fun v -> bpf "\ttype: %s\n" (string_of_os_type v))
+      data.os_type;
+  may (fun v -> bpf "\tdistro: %s\n" (string_of_distro v))
+      data.distro;
+  may (fun v -> bpf "\tpackage_format: %s\n" (string_of_package_format v))
+      data.package_format;
+  may (fun v -> bpf "\tpackage_management: %s\n" (string_of_package_management v))
+      data.package_management;
+  may (fun v -> bpf "\tproduct_name: %s\n" v)
+      data.product_name;
+  may (fun v -> bpf "\tproduct_variant: %s\n" v)
+      data.product_variant;
+  may (fun (major, minor) -> bpf "\tversion: %d.%d\n" major minor)
+      data.version;
+  may (fun v -> bpf "\tarch: %s\n" v)
+      data.arch;
+  may (fun v -> bpf "\thostname: %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 "\tfstab: [%s]\n" (String.concat ", " v)
+  );
+  may (fun v -> bpf "\twindows_systemroot: %s\n" v)
+      data.windows_systemroot;
+  may (fun v -> bpf "\twindows_software_hive: %s\n" v)
+      data.windows_software_hive;
+  may (fun v -> bpf "\twindows_system_hive: %s\n" v)
+      data.windows_system_hive;
+  may (fun v -> bpf "\twindows_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 "\tdrive_mappings: [%s]\n" (String.concat ", " v)
+  );
+  bpf "\tis_live_disk: %b\n" data.is_live_disk;
+  bpf "\tis_netinst_disk: %b\n" data.is_netinst_disk;
+  bpf "\tis_multipart_disk: %b\n" data.is_multipart_disk;
+  Buffer.contents b
+
+and string_of_format = function
+  | FORMAT_INSTALLED -> "installed"
+  | FORMAT_INSTALLER -> "installer"
+
+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 = {
+  format = None;
+  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 = [];
+  is_live_disk = false;
+  is_netinst_disk = false;
+  is_multipart_disk = false;
+}
+
+let merge_inspection_data child parent =
+  let merge child parent = if parent = None then child else parent in
+
+  { format =          merge child.format parent.format;
+    os_type =         merge child.os_type parent.os_type;
+    distro =          merge child.distro parent.distro;
+    package_format =  merge child.package_format parent.package_format;
+    package_management =
+      merge child.package_management parent.package_management;
+    product_name =    merge child.product_name parent.product_name;
+    product_variant = merge child.product_variant parent.product_variant;
+    version =         merge child.version parent.version;
+    arch =            merge child.arch parent.arch;
+    hostname =        merge child.hostname parent.hostname;
+    fstab =           child.fstab @ parent.fstab;
+    windows_systemroot =
+      merge child.windows_systemroot parent.windows_systemroot;
+    windows_software_hive =
+      merge child.windows_software_hive parent.windows_software_hive;
+    windows_system_hive =
+      merge child.windows_system_hive parent.windows_system_hive;
+    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. *)
+    drive_mappings =  child.drive_mappings @ parent.drive_mappings;
+
+    is_live_disk =    child.is_live_disk || parent.is_live_disk;
+    is_netinst_disk = child.is_netinst_disk || parent.is_netinst_disk;
+    is_multipart_disk = child.is_multipart_disk || parent.is_multipart_disk;
+  }
+
+let inspect_fses = ref []
diff --git a/daemon/inspect_types.mli b/daemon/inspect_types.mli
new file mode 100644
index 000000000..99bffea6f
--- /dev/null
+++ b/daemon/inspect_types.mli
@@ -0,0 +1,175 @@
+(* 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 *)
+}
+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 = {
+  format : format option;
+  os_type : os_type option;
+  distro : distro option;
+  package_format : package_format option;
+  package_management : package_management option;
+  product_name : string option;
+  product_variant : string option;
+  version : version option;
+  arch : string option;
+  hostname : string option;
+  fstab : fstab_entry list;
+  windows_systemroot : string option;
+  windows_software_hive : string option;
+  windows_system_hive : string option;
+  windows_current_control_set : string option;
+  drive_mappings : drive_mapping list;
+  is_live_disk : bool;
+  is_netinst_disk : bool;
+  is_multipart_disk : bool;
+}
+and format =
+  | FORMAT_INSTALLED
+  | FORMAT_INSTALLER
+  (* in future: supplemental install disks *)
+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 -> inspection_data
+(** [merge_inspection_data child parent] merges two sets of inspection
+    data into a single set.  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 string_of_fs : fs -> string
+(** Convert [fs] into a single 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_format : format -> string
+(** Convert [format] to a string.
+    The string is part of the public API. *)
+
+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 : inspection_data
+(** {!inspection_data} structure with all fields set to [None]. *)
+
+val inspect_fses : fs list ref
+(** The global list of filesystems found by the previous call to
+    inspect_os. *)
diff --git a/daemon/mount.ml b/daemon/mount.ml
index 4bb74fb82..40c81be0e 100644
--- a/daemon/mount.ml
+++ b/daemon/mount.ml
@@ -60,3 +60,64 @@ let mount_vfs options vfs mountable mountpoint =
 let mount = mount_vfs None None
 let mount_ro = mount_vfs (Some "ro") None
 let mount_options options = mount_vfs (Some options) None
+
+(* Unmount everything mounted under /sysroot.
+ *
+ * We have to unmount in the correct order, so we sort the paths by
+ * longest first to ensure that child paths are unmounted by parent
+ * paths.
+ *
+ * This call is more important than it appears at first, because it
+ * is widely used by both test and production code in order to
+ * get back to a known state (nothing mounted, everything synchronized).
+ *)
+let rec umount_all () =
+  (* This is called from internal_autosync and generally as a cleanup
+   * function, and since the umount will definitely fail if any
+   * handles are open, we may as well close them.
+   *)
+  (* XXX
+  aug_finalize ();
+  hivex_finalize ();
+  journal_finalize ();
+  *)
+
+  let sysroot = Sysroot.sysroot () in
+  let sysroot_len = String.length sysroot in
+
+  let info = read_whole_file "/proc/self/mountinfo" in
+  let info = String.nsplit "\n" info in
+
+  let mps = ref [] in
+  List.iter (
+    fun line ->
+      let line = String.nsplit " " line in
+      (* The field of interest is the 5th field.  Whitespace is escaped
+       * with octal sequences like \040 (for space).
+       * See fs/seq_file.c:mangle_path.
+       *)
+      if List.length line >= 5 then (
+        let mp = List.nth line 4 in
+        let mp = proc_unmangle_path mp in
+
+        (* Allow a mount directory like "/sysroot" or "/sysroot/..." *)
+        if (sysroot_len > 0 && String.is_prefix mp sysroot) ||
+           (String.is_prefix mp sysroot &&
+            String.length mp > sysroot_len &&
+            mp.[sysroot_len] = '/') then
+          push_front mp mps
+      )
+  ) info;
+
+  let mps = !mps in
+  let mps = List.sort compare_longest_first mps in
+
+  (* Unmount them. *)
+  List.iter (
+    fun mp -> ignore (command "umount" [mp])
+  ) mps
+
+and compare_longest_first s1 s2 =
+  let n1 = String.length s1 in
+  let n2 = String.length s2 in
+  n2 - n1
diff --git a/daemon/mount.mli b/daemon/mount.mli
index e43d97c42..abf538521 100644
--- a/daemon/mount.mli
+++ b/daemon/mount.mli
@@ -20,3 +20,5 @@ val mount : Mountable.t -> string -> unit
 val mount_ro : Mountable.t -> string -> unit
 val mount_options : string -> Mountable.t -> string -> unit
 val mount_vfs : string option -> string option -> Mountable.t -> string -> unit
+
+val umount_all : unit -> unit
diff --git a/daemon/utils.ml b/daemon/utils.ml
index 48f6b9c5c..a8944a8a6 100644
--- a/daemon/utils.ml
+++ b/daemon/utils.ml
@@ -238,3 +238,78 @@ let proc_unmangle_path path =
 let is_small_file path =
   is_regular_file path &&
     (stat path).st_size <= 2 * 1048 * 1024
+
+let max_augeas_file_size = 100 * 1000
+
+let rec with_augeas configfiles f =
+  let chroot = Chroot.create (Sysroot.sysroot ()) in
+
+  (* Security: Refuse to do this if a config file is too large. *)
+  List.iter (
+    fun file ->
+      let size = (Chroot.f chroot Unix.stat file).Unix.st_size in
+      if size >= max_augeas_file_size then
+        failwithf "size of %s is unreasonably large (%d bytes)"
+                  file size
+  ) configfiles;
+
+  let aug = Augeas.create "/" 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 (Augeas.rm 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 = Augeas.matches 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 Augeas.get 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
diff --git a/daemon/utils.mli b/daemon/utils.mli
index a1f956be3..fb79582c4 100644
--- a/daemon/utils.mli
+++ b/daemon/utils.mli
@@ -78,3 +78,11 @@ val commandr : string -> string list -> (int * string * string)
 
 val is_small_file : string -> bool
 (** Return true if the path is a small regular file. *)
+
+val with_augeas : 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. *)
diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES
index 7a3907ce2..b3c208f29 100644
--- a/docs/C_SOURCE_FILES
+++ b/docs/C_SOURCE_FILES
@@ -65,6 +65,7 @@ customize/perl_edit-c.c
 daemon/9p.c
 daemon/acl.c
 daemon/actions.h
+daemon/augeas-c.c
 daemon/augeas.c
 daemon/available.c
 daemon/base64.c
diff --git a/generator/actions.ml b/generator/actions.ml
index 75742397a..a745f6244 100644
--- a/generator/actions.ml
+++ b/generator/actions.ml
@@ -51,6 +51,7 @@ let daemon_functions =
   Actions_core_deprecated.daemon_functions @
   Actions_debug.daemon_functions @
   Actions_hivex.daemon_functions @
+  Actions_inspection.daemon_functions @
   Actions_tsk.daemon_functions @
   Actions_yara.daemon_functions
 
diff --git a/generator/actions_inspection.ml b/generator/actions_inspection.ml
index b7ea5a4de..e7ac6a2c9 100644
--- a/generator/actions_inspection.ml
+++ b/generator/actions_inspection.ml
@@ -22,10 +22,11 @@ open Types
 
 (* Inspection APIs. *)
 
-let non_daemon_functions = [
+let daemon_functions = [
   { defaults with
     name = "inspect_os"; added = (1, 5, 3);
     style = RStringList (RMountable, "roots"), [], [];
+    impl = OCaml "Inspect.inspect_os";
     shortdesc = "inspect disk and return list of operating systems found";
     longdesc = "\
 This function uses other libguestfs functions and certain
@@ -61,8 +62,24 @@ Please read L<guestfs(3)/INSPECTION> for more details.
 See also C<guestfs_list_filesystems>." };
 
   { defaults with
+    name = "inspect_get_roots"; added = (1, 7, 3);
+    style = RStringList (RMountable, "roots"), [], [];
+    impl = OCaml "Inspect.inspect_get_roots";
+    shortdesc = "return list of operating systems found by last inspection";
+    longdesc = "\
+This function is a convenient way to get the list of root
+devices, as returned from a previous call to C<guestfs_inspect_os>,
+but without redoing the whole inspection process.
+
+This returns an empty list if either no root devices were
+found or the caller has not called C<guestfs_inspect_os>.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
     name = "inspect_get_type"; added = (1, 5, 3);
     style = RString (RPlainString, "name"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_type";
     shortdesc = "get type of inspected operating system";
     longdesc = "\
 This returns the type of the inspected operating system.
@@ -116,6 +133,7 @@ Please read L<guestfs(3)/INSPECTION> for more details." };
   { defaults with
     name = "inspect_get_arch"; added = (1, 5, 3);
     style = RString (RPlainString, "arch"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_arch";
     shortdesc = "get architecture of inspected operating system";
     longdesc = "\
 This returns the architecture of the inspected operating system.
@@ -130,6 +148,7 @@ Please read L<guestfs(3)/INSPECTION> for more details." };
   { defaults with
     name = "inspect_get_distro"; added = (1, 5, 3);
     style = RString (RPlainString, "distro"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_distro";
     shortdesc = "get distro of inspected operating system";
     longdesc = "\
 This returns the distro (distribution) of the inspected operating
@@ -286,6 +305,7 @@ Please read L<guestfs(3)/INSPECTION> for more details." };
   { defaults with
     name = "inspect_get_major_version"; added = (1, 5, 3);
     style = RInt "major", [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_major_version";
     shortdesc = "get major version of inspected operating system";
     longdesc = "\
 This returns the major version number of the inspected operating
@@ -305,6 +325,7 @@ Please read L<guestfs(3)/INSPECTION> for more details." };
   { defaults with
     name = "inspect_get_minor_version"; added = (1, 5, 3);
     style = RInt "minor", [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_minor_version";
     shortdesc = "get minor version of inspected operating system";
     longdesc = "\
 This returns the minor version number of the inspected operating
@@ -318,6 +339,7 @@ See also C<guestfs_inspect_get_major_version>." };
   { defaults with
     name = "inspect_get_product_name"; added = (1, 5, 3);
     style = RString (RPlainString, "product"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_product_name";
     shortdesc = "get product name of inspected operating system";
     longdesc = "\
 This returns the product name of the inspected operating
@@ -331,8 +353,235 @@ string C<unknown> is returned.
 Please read L<guestfs(3)/INSPECTION> for more details." };
 
   { defaults with
+    name = "inspect_get_windows_systemroot"; added = (1, 5, 25);
+    style = RString (RPlainString, "systemroot"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_windows_systemroot";
+    shortdesc = "get Windows systemroot of inspected operating system";
+    longdesc = "\
+This returns the Windows systemroot of the inspected guest.
+The systemroot is a directory path such as F</WINDOWS>.
+
+This call assumes that the guest is Windows and that the
+systemroot could be determined by inspection.  If this is not
+the case then an error is returned.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
+    name = "inspect_get_package_format"; added = (1, 7, 5);
+    style = RString (RPlainString, "packageformat"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_package_format";
+    shortdesc = "get package format used by the operating system";
+    longdesc = "\
+This function and C<guestfs_inspect_get_package_management> return
+the package format and package management tool used by the
+inspected operating system.  For example for Fedora these
+functions would return C<rpm> (package format), and
+C<yum> or C<dnf> (package management).
+
+This returns the string C<unknown> if we could not determine the
+package format I<or> if the operating system does not have
+a real packaging system (eg. Windows).
+
+Possible strings include:
+C<rpm>, C<deb>, C<ebuild>, C<pisi>, C<pacman>, C<pkgsrc>, C<apk>,
+C<xbps>.
+Future versions of libguestfs may return other strings.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
+    name = "inspect_get_package_management"; added = (1, 7, 5);
+    style = RString (RPlainString, "packagemanagement"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_package_management";
+    shortdesc = "get package management tool used by the operating system";
+    longdesc = "\
+C<guestfs_inspect_get_package_format> and this function return
+the package format and package management tool used by the
+inspected operating system.  For example for Fedora these
+functions would return C<rpm> (package format), and
+C<yum> or C<dnf> (package management).
+
+This returns the string C<unknown> if we could not determine the
+package management tool I<or> if the operating system does not have
+a real packaging system (eg. Windows).
+
+Possible strings include: C<yum>, C<dnf>, C<up2date>,
+C<apt> (for all Debian derivatives),
+C<portage>, C<pisi>, C<pacman>, C<urpmi>, C<zypper>, C<apk>, C<xbps>.
+Future versions of libguestfs may return other strings.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
+    name = "inspect_get_hostname"; added = (1, 7, 9);
+    style = RString (RPlainString, "hostname"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_hostname";
+    shortdesc = "get hostname of the operating system";
+    longdesc = "\
+This function returns the hostname of the operating system
+as found by inspection of the guest’s configuration files.
+
+If the hostname could not be determined, then the
+string C<unknown> is returned.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
+    name = "inspect_get_format"; added = (1, 9, 4);
+    style = RString (RPlainString, "format"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_format";
+    shortdesc = "get format of inspected operating system";
+    longdesc = "\
+This returns the format of the inspected operating system.  You
+can use it to detect install images, live CDs and similar.
+
+Currently defined formats are:
+
+=over 4
+
+=item \"installed\"
+
+This is an installed operating system.
+
+=item \"installer\"
+
+The disk image being inspected is not an installed operating system,
+but a I<bootable> install disk, live CD, or similar.
+
+=item \"unknown\"
+
+The format of this disk image is not known.
+
+=back
+
+Future versions of libguestfs may return other strings here.
+The caller should be prepared to handle any string.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
+    name = "inspect_is_live"; added = (1, 9, 4);
+    style = RBool "live", [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_is_live";
+    shortdesc = "get live flag for install disk";
+    longdesc = "\
+If C<guestfs_inspect_get_format> returns C<installer> (this
+is an install disk), then this returns true if a live image
+was detected on the disk.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
+    name = "inspect_is_netinst"; added = (1, 9, 4);
+    style = RBool "netinst", [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_is_netinst";
+    shortdesc = "get netinst (network installer) flag for install disk";
+    longdesc = "\
+If C<guestfs_inspect_get_format> returns C<installer> (this
+is an install disk), then this returns true if the disk is
+a network installer, ie. not a self-contained install CD but
+one which is likely to require network access to complete
+the install.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
+    name = "inspect_is_multipart"; added = (1, 9, 4);
+    style = RBool "multipart", [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_is_multipart";
+    shortdesc = "get multipart flag for install disk";
+    longdesc = "\
+If C<guestfs_inspect_get_format> returns C<installer> (this
+is an install disk), then this returns true if the disk is
+part of a set.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
+    name = "inspect_get_product_variant"; added = (1, 9, 13);
+    style = RString (RPlainString, "variant"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_product_variant";
+    shortdesc = "get product variant of inspected operating system";
+    longdesc = "\
+This returns the product variant of the inspected operating
+system.
+
+For Windows guests, this returns the contents of the Registry key
+C<HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion>
+C<InstallationType> which is usually a string such as
+C<Client> or C<Server> (other values are possible).  This
+can be used to distinguish consumer and enterprise versions
+of Windows that have the same version number (for example,
+Windows 7 and Windows 2008 Server are both version 6.1,
+but the former is C<Client> and the latter is C<Server>).
+
+For enterprise Linux guests, in future we intend this to return
+the product variant such as C<Desktop>, C<Server> and so on.  But
+this is not implemented at present.
+
+If the product variant could not be determined, then the
+string C<unknown> is returned.
+
+Please read L<guestfs(3)/INSPECTION> for more details.
+See also C<guestfs_inspect_get_product_name>,
+C<guestfs_inspect_get_major_version>." };
+
+  { defaults with
+    name = "inspect_get_windows_current_control_set"; added = (1, 9, 17);
+    style = RString (RPlainString, "controlset"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_windows_current_control_set";
+    shortdesc = "get Windows CurrentControlSet of inspected operating system";
+    longdesc = "\
+This returns the Windows CurrentControlSet of the inspected guest.
+The CurrentControlSet is a registry key name such as C<ControlSet001>.
+
+This call assumes that the guest is Windows and that the
+Registry could be examined by inspection.  If this is not
+the case then an error is returned.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
+    name = "inspect_get_windows_software_hive"; added = (1, 35, 26);
+    style = RString (RPlainString, "path"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_windows_software_hive";
+    shortdesc = "get the path of the Windows software hive";
+    longdesc = "\
+This returns the path to the hive (binary Windows Registry file)
+corresponding to HKLM\\SOFTWARE.
+
+This call assumes that the guest is Windows and that the guest
+has a software hive file with the right name.  If this is not the
+case then an error is returned.  This call does not check that the
+hive is a valid Windows Registry hive.
+
+You can use C<guestfs_hivex_open> to read or write to the hive.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
+    name = "inspect_get_windows_system_hive"; added = (1, 35, 26);
+    style = RString (RPlainString, "path"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_windows_system_hive";
+    shortdesc = "get the path of the Windows system hive";
+    longdesc = "\
+This returns the path to the hive (binary Windows Registry file)
+corresponding to HKLM\\SYSTEM.
+
+This call assumes that the guest is Windows and that the guest
+has a system hive file with the right name.  If this is not the
+case then an error is returned.  This call does not check that the
+hive is a valid Windows Registry hive.
+
+You can use C<guestfs_hivex_open> to read or write to the hive.
+
+Please read L<guestfs(3)/INSPECTION> for more details." };
+
+  { defaults with
     name = "inspect_get_mountpoints"; added = (1, 5, 3);
     style = RHashtable (RPlainString, RMountable, "mountpoints"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_mountpoints";
     shortdesc = "get mountpoints of inspected operating system";
     longdesc = "\
 This returns a hash of where we think the filesystems
@@ -364,6 +613,7 @@ See also C<guestfs_inspect_get_filesystems>." };
   { defaults with
     name = "inspect_get_filesystems"; added = (1, 5, 3);
     style = RStringList (RMountable, "filesystems"), [String (Mountable, "root")], [];
+    impl = OCaml "Inspect.inspect_get_filesystems";
     shortdesc = "get filesystems associated with inspected operating system";
     longdesc = "\
 This returns a list of all the filesystems that we think
@@ -377,78 +627,9 @@ for a filesystem to be shared between operating systems.
 Please read L<guestfs(3)/INSPECTION> for more details.
 See also C<guestfs_inspect_get_mountpoints>." };
 
-  { defaults with
-    name = "inspect_get_windows_systemroot"; added = (1, 5, 25);
-    style = RString (RPlainString, "systemroot"), [String (Mountable, "root")], [];
-    shortdesc = "get Windows systemroot of inspected operating system";
-    longdesc = "\
-This returns the Windows systemroot of the inspected guest.
-The systemroot is a directory path such as F</WINDOWS>.
-
-This call assumes that the guest is Windows and that the
-systemroot could be determined by inspection.  If this is not
-the case then an error is returned.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
-
-  { defaults with
-    name = "inspect_get_roots"; added = (1, 7, 3);
-    style = RStringList (RMountable, "roots"), [], [];
-    shortdesc = "return list of operating systems found by last inspection";
-    longdesc = "\
-This function is a convenient way to get the list of root
-devices, as returned from a previous call to C<guestfs_inspect_os>,
-but without redoing the whole inspection process.
-
-This returns an empty list if either no root devices were
-found or the caller has not called C<guestfs_inspect_os>.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
-
-  { defaults with
-    name = "inspect_get_package_format"; added = (1, 7, 5);
-    style = RString (RPlainString, "packageformat"), [String (Mountable, "root")], [];
-    shortdesc = "get package format used by the operating system";
-    longdesc = "\
-This function and C<guestfs_inspect_get_package_management> return
-the package format and package management tool used by the
-inspected operating system.  For example for Fedora these
-functions would return C<rpm> (package format), and
-C<yum> or C<dnf> (package management).
-
-This returns the string C<unknown> if we could not determine the
-package format I<or> if the operating system does not have
-a real packaging system (eg. Windows).
-
-Possible strings include:
-C<rpm>, C<deb>, C<ebuild>, C<pisi>, C<pacman>, C<pkgsrc>, C<apk>,
-C<xbps>.
-Future versions of libguestfs may return other strings.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
-
-  { defaults with
-    name = "inspect_get_package_management"; added = (1, 7, 5);
-    style = RString (RPlainString, "packagemanagement"), [String (Mountable, "root")], [];
-    shortdesc = "get package management tool used by the operating system";
-    longdesc = "\
-C<guestfs_inspect_get_package_format> and this function return
-the package format and package management tool used by the
-inspected operating system.  For example for Fedora these
-functions would return C<rpm> (package format), and
-C<yum> or C<dnf> (package management).
-
-This returns the string C<unknown> if we could not determine the
-package management tool I<or> if the operating system does not have
-a real packaging system (eg. Windows).
-
-Possible strings include: C<yum>, C<dnf>, C<up2date>,
-C<apt> (for all Debian derivatives),
-C<portage>, C<pisi>, C<pacman>, C<urpmi>, C<zypper>, C<apk>, C<xbps>.
-Future versions of libguestfs may return other strings.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
+]
 
+let non_daemon_functions = [
   { defaults with
     name = "inspect_list_applications2"; added = (1, 19, 56);
     style = RStructList ("applications2", "application2"), [String (Mountable, "root")], [];
@@ -553,128 +734,6 @@ If unavailable this is returned as an empty string C<\"\">.
 Please read L<guestfs(3)/INSPECTION> for more details." };
 
   { defaults with
-    name = "inspect_get_hostname"; added = (1, 7, 9);
-    style = RString (RPlainString, "hostname"), [String (Mountable, "root")], [];
-    shortdesc = "get hostname of the operating system";
-    longdesc = "\
-This function returns the hostname of the operating system
-as found by inspection of the guest’s configuration files.
-
-If the hostname could not be determined, then the
-string C<unknown> is returned.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
-
-  { defaults with
-    name = "inspect_get_format"; added = (1, 9, 4);
-    style = RString (RPlainString, "format"), [String (Mountable, "root")], [];
-    shortdesc = "get format of inspected operating system";
-    longdesc = "\
-This returns the format of the inspected operating system.  You
-can use it to detect install images, live CDs and similar.
-
-Currently defined formats are:
-
-=over 4
-
-=item \"installed\"
-
-This is an installed operating system.
-
-=item \"installer\"
-
-The disk image being inspected is not an installed operating system,
-but a I<bootable> install disk, live CD, or similar.
-
-=item \"unknown\"
-
-The format of this disk image is not known.
-
-=back
-
-Future versions of libguestfs may return other strings here.
-The caller should be prepared to handle any string.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
-
-  { defaults with
-    name = "inspect_is_live"; added = (1, 9, 4);
-    style = RBool "live", [String (Mountable, "root")], [];
-    shortdesc = "get live flag for install disk";
-    longdesc = "\
-If C<guestfs_inspect_get_format> returns C<installer> (this
-is an install disk), then this returns true if a live image
-was detected on the disk.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
-
-  { defaults with
-    name = "inspect_is_netinst"; added = (1, 9, 4);
-    style = RBool "netinst", [String (Mountable, "root")], [];
-    shortdesc = "get netinst (network installer) flag for install disk";
-    longdesc = "\
-If C<guestfs_inspect_get_format> returns C<installer> (this
-is an install disk), then this returns true if the disk is
-a network installer, ie. not a self-contained install CD but
-one which is likely to require network access to complete
-the install.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
-
-  { defaults with
-    name = "inspect_is_multipart"; added = (1, 9, 4);
-    style = RBool "multipart", [String (Mountable, "root")], [];
-    shortdesc = "get multipart flag for install disk";
-    longdesc = "\
-If C<guestfs_inspect_get_format> returns C<installer> (this
-is an install disk), then this returns true if the disk is
-part of a set.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
-
-  { defaults with
-    name = "inspect_get_product_variant"; added = (1, 9, 13);
-    style = RString (RPlainString, "variant"), [String (Mountable, "root")], [];
-    shortdesc = "get product variant of inspected operating system";
-    longdesc = "\
-This returns the product variant of the inspected operating
-system.
-
-For Windows guests, this returns the contents of the Registry key
-C<HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion>
-C<InstallationType> which is usually a string such as
-C<Client> or C<Server> (other values are possible).  This
-can be used to distinguish consumer and enterprise versions
-of Windows that have the same version number (for example,
-Windows 7 and Windows 2008 Server are both version 6.1,
-but the former is C<Client> and the latter is C<Server>).
-
-For enterprise Linux guests, in future we intend this to return
-the product variant such as C<Desktop>, C<Server> and so on.  But
-this is not implemented at present.
-
-If the product variant could not be determined, then the
-string C<unknown> is returned.
-
-Please read L<guestfs(3)/INSPECTION> for more details.
-See also C<guestfs_inspect_get_product_name>,
-C<guestfs_inspect_get_major_version>." };
-
-  { defaults with
-    name = "inspect_get_windows_current_control_set"; added = (1, 9, 17);
-    style = RString (RPlainString, "controlset"), [String (Mountable, "root")], [];
-    shortdesc = "get Windows CurrentControlSet of inspected operating system";
-    longdesc = "\
-This returns the Windows CurrentControlSet of the inspected guest.
-The CurrentControlSet is a registry key name such as C<ControlSet001>.
-
-This call assumes that the guest is Windows and that the
-Registry could be examined by inspection.  If this is not
-the case then an error is returned.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
-
-  { defaults with
     name = "inspect_get_drive_mappings"; added = (1, 9, 17);
     style = RHashtable (RPlainString, RDevice, "drives"), [String (Mountable, "root")], [];
     shortdesc = "get drive letter mappings";
@@ -773,38 +832,4 @@ advice before using trademarks in applications.
 
 =back" };
 
-  { defaults with
-    name = "inspect_get_windows_software_hive"; added = (1, 35, 26);
-    style = RString (RPlainString, "path"), [String (Mountable, "root")], [];
-    shortdesc = "get the path of the Windows software hive";
-    longdesc = "\
-This returns the path to the hive (binary Windows Registry file)
-corresponding to HKLM\\SOFTWARE.
-
-This call assumes that the guest is Windows and that the guest
-has a software hive file with the right name.  If this is not the
-case then an error is returned.  This call does not check that the
-hive is a valid Windows Registry hive.
-
-You can use C<guestfs_hivex_open> to read or write to the hive.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
-
-  { defaults with
-    name = "inspect_get_windows_system_hive"; added = (1, 35, 26);
-    style = RString (RPlainString, "path"), [String (Mountable, "root")], [];
-    shortdesc = "get the path of the Windows system hive";
-    longdesc = "\
-This returns the path to the hive (binary Windows Registry file)
-corresponding to HKLM\\SYSTEM.
-
-This call assumes that the guest is Windows and that the guest
-has a system hive file with the right name.  If this is not the
-case then an error is returned.  This call does not check that the
-hive is a valid Windows Registry hive.
-
-You can use C<guestfs_hivex_open> to read or write to the hive.
-
-Please read L<guestfs(3)/INSPECTION> for more details." };
-
 ]
diff --git a/generator/actions_inspection.mli b/generator/actions_inspection.mli
index 327f7aa4f..06b8116c4 100644
--- a/generator/actions_inspection.mli
+++ b/generator/actions_inspection.mli
@@ -19,3 +19,4 @@
 (* Please read generator/README first. *)
 
 val non_daemon_functions : Types.action list
+val daemon_functions : Types.action list
diff --git a/generator/daemon.ml b/generator/daemon.ml
index 66b625388..2d1f57aff 100644
--- a/generator/daemon.ml
+++ b/generator/daemon.ml
@@ -597,6 +597,30 @@ return_string_mountable (value retv)
   }
 }
 
+/* Implement RStringList (RMountable, _). */
+static char **
+return_string_mountable_list (value retv)
+{
+  CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret);
+  value v;
+  char *m;
+
+  while (retv != Val_int (0)) {
+    v = Field (retv, 0);
+    m = return_string_mountable (v);
+    if (m == NULL)
+      return NULL;
+    if (add_string_nodup (&ret, m) == -1)
+      return NULL;
+    retv = Field (retv, 1);
+  }
+
+  if (end_stringsbuf (&ret) == -1)
+    return NULL;
+
+  return take_stringsbuf (&ret); /* caller frees */
+}
+
 /* Implement RHashtable (RMountable, RPlainString, _). */
 static char **
 return_hashtable_mountable_string (value retv)
@@ -625,6 +649,34 @@ return_hashtable_mountable_string (value retv)
   return take_stringsbuf (&ret); /* caller frees */
 }
 
+/* Implement RHashtable (RPlainString, RMountable, _). */
+static char **
+return_hashtable_string_mountable (value retv)
+{
+  CLEANUP_FREE_STRINGSBUF DECLARE_STRINGSBUF (ret);
+  value sv, v, mv;
+  char *m;
+
+  while (retv != Val_int (0)) {
+    v = Field (retv, 0);        /* (string, Mountable.t) */
+    sv = Field (v, 0);          /* string */
+    if (add_string (&ret, String_val (sv)) == -1)
+      return NULL;
+    mv = Field (v, 1);          /* Mountable.t */
+    m = return_string_mountable (mv);
+    if (m == NULL)
+      return NULL;
+    if (add_string_nodup (&ret, m) == -1)
+      return NULL;
+    retv = Field (retv, 1);
+  }
+
+  if (end_stringsbuf (&ret) == -1)
+    return NULL;
+
+  return take_stringsbuf (&ret); /* caller frees */
+}
+
 ";
 
   (* Implement code for returning structs and struct lists. *)
@@ -865,9 +917,12 @@ return_hashtable_mountable_string (value retv)
        | RString (RMountable, _) ->
           pr "  char *ret = return_string_mountable (retv);\n";
           pr "  CAMLreturnT (char *, ret); /* caller frees */\n"
-       | RStringList _ ->
+       | RStringList ((RPlainString|RDevice), _) ->
           pr "  char **ret = return_string_list (retv);\n";
           pr "  CAMLreturnT (char **, ret); /* caller frees */\n"
+       | RStringList (RMountable, _) ->
+          pr "  char **ret = return_string_mountable_list (retv);\n";
+          pr "  CAMLreturnT (char **, ret); /* caller frees */\n"
        | RStruct (_, typ) ->
           pr "  guestfs_int_%s *ret =\n" typ;
           pr "    return_%s (retv);\n" typ;
@@ -881,6 +936,9 @@ return_hashtable_mountable_string (value retv)
        | RHashtable (RMountable, RPlainString, _) ->
           pr "  char **ret = return_hashtable_mountable_string (retv);\n";
           pr "  CAMLreturnT (char **, ret); /* caller frees */\n"
+       | RHashtable (RPlainString, RMountable, _) ->
+          pr "  char **ret = return_hashtable_string_mountable (retv);\n";
+          pr "  CAMLreturnT (char **, ret); /* caller frees */\n"
        | RHashtable _ -> assert false
        | RBufferOut _ -> assert false
       );
diff --git a/generator/proc_nr.ml b/generator/proc_nr.ml
index dec02f5fa..d830281a0 100644
--- a/generator/proc_nr.ml
+++ b/generator/proc_nr.ml
@@ -484,6 +484,28 @@ let proc_nr = [
 474, "internal_yara_scan";
 475, "file_architecture";
 476, "list_filesystems";
+477, "inspect_os";
+478, "inspect_get_roots";
+479, "inspect_get_format";
+480, "inspect_get_type";
+481, "inspect_get_distro";
+482, "inspect_get_package_format";
+483, "inspect_get_package_management";
+484, "inspect_get_product_name";
+485, "inspect_get_product_variant";
+486, "inspect_get_major_version";
+487, "inspect_get_minor_version";
+488, "inspect_get_arch";
+489, "inspect_get_hostname";
+490, "inspect_get_windows_systemroot";
+491, "inspect_get_windows_software_hive";
+492, "inspect_get_windows_system_hive";
+493, "inspect_get_windows_current_control_set";
+494, "inspect_is_live";
+495, "inspect_is_netinst";
+496, "inspect_is_multipart";
+497, "inspect_get_mountpoints";
+498, "inspect_get_filesystems";
 ]
 
 (* End of list.  If adding a new entry, add it at the end of the list
diff --git a/lib/MAX_PROC_NR b/lib/MAX_PROC_NR
index b86395733..f9aaa4d56 100644
--- a/lib/MAX_PROC_NR
+++ b/lib/MAX_PROC_NR
@@ -1 +1 @@
-476
+498
diff --git a/lib/guestfs-internal.h b/lib/guestfs-internal.h
index 7a61295b9..e694a8718 100644
--- a/lib/guestfs-internal.h
+++ b/lib/guestfs-internal.h
@@ -129,7 +129,7 @@
  * those.
  */
 #define MAX_SMALL_FILE_SIZE    (2 * 1000 * 1000)
-#define MAX_AUGEAS_FILE_SIZE        (100 * 1000)
+#define MAX_AUGEAS_FILE_SIZE        (100 * 1000) /* XXX REMOVE */
 
 /* Maximum RPM or dpkg database we will download to /tmp.  RPM
  * 'Packages' database can get very large: 70 MB is roughly the
diff --git a/lib/inspect.c b/lib/inspect.c
index 1cc0942f1..2aaf30344 100644
--- a/lib/inspect.c
+++ b/lib/inspect.c
@@ -45,569 +45,6 @@
 
 COMPILE_REGEXP (re_primary_partition, "^/dev/(?:h|s|v)d.[1234]$", 0)
 
-static void check_for_duplicated_bsd_root (guestfs_h *g);
-static void collect_coreos_inspection_info (guestfs_h *g);
-static void collect_linux_inspection_info (guestfs_h *g);
-static void collect_linux_inspection_info_for (guestfs_h *g, struct inspect_fs *root);
-
-/**
- * The main inspection API.
- */
-char **
-guestfs_impl_inspect_os (guestfs_h *g)
-{
-  CLEANUP_FREE_STRING_LIST char **fses = NULL;
-  char **fs, **ret;
-
-  /* Remove any information previously stored in the handle. */
-  guestfs_int_free_inspect_info (g);
-
-  if (guestfs_umount_all (g) == -1)
-    return NULL;
-
-  /* Iterate over all detected filesystems.  Inspect each one in turn
-   * and add that information to the handle.
-   */
-
-  fses = guestfs_list_filesystems (g);
-  if (fses == NULL) return NULL;
-
-  for (fs = fses; *fs; fs += 2) {
-    if (guestfs_int_check_for_filesystem_on (g, *fs)) {
-      guestfs_int_free_inspect_info (g);
-      return NULL;
-    }
-  }
-
-  /* The OS inspection information for CoreOS are gathered by inspecting
-   * multiple filesystems. Gather all the inspected information in the
-   * inspect_fs struct of the root filesystem.
-   */
-  collect_coreos_inspection_info (g);
-
-  /* Check if the same filesystem was listed twice as root in g->fses.
-   * This may happen for the *BSD root partition where an MBR partition
-   * is a shadow of the real root partition probably /dev/sda5
-   */
-  check_for_duplicated_bsd_root (g);
-
-  /* For Linux guests with a separate /usr filesyste, merge some of the
-   * inspected information in that partition to the inspect_fs struct
-   * of the root filesystem.
-   */
-  collect_linux_inspection_info (g);
-
-  /* At this point we have, in the handle, a list of all filesystems
-   * found and data about each one.  Now we assemble the list of
-   * filesystems which are root devices and return that to the user.
-   * Fall through to guestfs_inspect_get_roots to do that.
-   */
-  ret = guestfs_inspect_get_roots (g);
-  if (ret == NULL)
-    guestfs_int_free_inspect_info (g);
-  return ret;
-}
-
-/**
- * Traverse through the filesystem list and find out if it contains
- * the C</> and C</usr> filesystems of a CoreOS image. If this is the
- * case, sum up all the collected information on the root fs.
- */
-static void
-collect_coreos_inspection_info (guestfs_h *g)
-{
-  size_t i;
-  struct inspect_fs *root = NULL, *usr = NULL;
-
-  for (i = 0; i < g->nr_fses; ++i) {
-    struct inspect_fs *fs = &g->fses[i];
-
-    if (fs->distro == OS_DISTRO_COREOS && fs->role == OS_ROLE_ROOT)
-      root = fs;
-  }
-
-  if (root == NULL)
-    return;
-
-  for (i = 0; i < g->nr_fses; ++i) {
-    struct inspect_fs *fs = &g->fses[i];
-
-    if (fs->distro != OS_DISTRO_COREOS || fs->role != OS_ROLE_USR)
-      continue;
-
-    /* CoreOS is designed to contain 2 /usr partitions (USR-A, USR-B):
-     * https://coreos.com/docs/sdk-distributors/sdk/disk-partitions/
-     * One is active and one passive. During the initial boot, the passive
-     * partition is empty and it gets filled up when an update is performed.
-     * Then, when the system reboots, the boot loader is instructed to boot
-     * from the passive partition. If both partitions are valid, we cannot
-     * determine which the active and which the passive is, unless we peep into
-     * the boot loader. As a workaround, we check the OS versions and pick the
-     * one with the higher version as active.
-     */
-    if (usr && guestfs_int_version_cmp_ge (&usr->version, &fs->version))
-      continue;
-
-    usr = fs;
-  }
-
-  if (usr == NULL)
-    return;
-
-  guestfs_int_merge_fs_inspections (g, root, usr);
-}
-
-/**
- * Traverse through the filesystems and find the /usr filesystem for
- * the specified C<root>: if found, merge its basic inspection details
- * to the root when they were set (i.e. because the /usr had os-release
- * or other ways to identify the OS).
- */
-static void
-collect_linux_inspection_info_for (guestfs_h *g, struct inspect_fs *root)
-{
-  size_t i;
-  struct inspect_fs *usr = NULL;
-
-  for (i = 0; i < g->nr_fses; ++i) {
-    struct inspect_fs *fs = &g->fses[i];
-    size_t j;
-
-    if (!(fs->distro == root->distro || fs->distro == OS_DISTRO_UNKNOWN) ||
-        fs->role != OS_ROLE_USR)
-      continue;
-
-    for (j = 0; j < root->nr_fstab; ++j) {
-      if (STREQ (fs->mountable, root->fstab[j].mountable)) {
-        usr = fs;
-        goto got_usr;
-      }
-    }
-  }
-
-  assert (usr == NULL);
-  return;
-
- got_usr:
-  /* If the version information in /usr is not null, then most probably
-   * there was an os-release file there, so reset what is in root
-   * and pick the results from /usr.
-   */
-  if (!version_is_null (&usr->version)) {
-    root->distro = OS_DISTRO_UNKNOWN;
-    free (root->product_name);
-    root->product_name = NULL;
-  }
-
-  guestfs_int_merge_fs_inspections (g, root, usr);
-}
-
-/**
- * Traverse through the filesystem list and find out if it contains
- * the C</> and C</usr> filesystems of a Linux image (but not CoreOS,
- * for which there is a separate C<collect_coreos_inspection_info>).
- * If this is the case, sum up all the collected information on each
- * root fs from the respective /usr filesystems.
- */
-static void
-collect_linux_inspection_info (guestfs_h *g)
-{
-  size_t i;
-
-  for (i = 0; i < g->nr_fses; ++i) {
-    struct inspect_fs *fs = &g->fses[i];
-
-    if (fs->distro != OS_DISTRO_COREOS && fs->role == OS_ROLE_ROOT)
-      collect_linux_inspection_info_for (g, fs);
-  }
-}
-
-/**
- * On *BSD systems, sometimes F</dev/sda[1234]> is a shadow of the
- * real root filesystem that is probably F</dev/sda5> (see:
- * L<http://www.freebsd.org/doc/handbook/disk-organization.html>)
- */
-static void
-check_for_duplicated_bsd_root (guestfs_h *g)
-{
-  size_t i;
-  struct inspect_fs *bsd_primary = NULL;
-
-  for (i = 0; i < g->nr_fses; ++i) {
-    bool is_bsd;
-    struct inspect_fs *fs = &g->fses[i];
-
-    is_bsd =
-      fs->type == OS_TYPE_FREEBSD ||
-      fs->type == OS_TYPE_NETBSD ||
-      fs->type == OS_TYPE_OPENBSD;
-
-    if (fs->role == OS_ROLE_ROOT && is_bsd &&
-        match (g, fs->mountable, re_primary_partition)) {
-      bsd_primary = fs;
-      continue;
-    }
-
-    if (fs->role == OS_ROLE_ROOT && bsd_primary &&
-        bsd_primary->type == fs->type) {
-      /* remove the root role from the bsd_primary */
-      bsd_primary->role = OS_ROLE_UNKNOWN;
-      bsd_primary->format = OS_FORMAT_UNKNOWN;
-      return;
-    }
-  }
-}
-
-static int
-compare_strings (const void *vp1, const void *vp2)
-{
-  const char *s1 = * (char * const *) vp1;
-  const char *s2 = * (char * const *) vp2;
-
-  return strcmp (s1, s2);
-}
-
-char **
-guestfs_impl_inspect_get_roots (guestfs_h *g)
-{
-  size_t i;
-  DECLARE_STRINGSBUF (ret);
-
-  /* NB. Doesn't matter if g->nr_fses == 0.  We just return an empty
-   * list in this case.
-   */
-  for (i = 0; i < g->nr_fses; ++i) {
-    if (g->fses[i].role == OS_ROLE_ROOT)
-      guestfs_int_add_string (g, &ret, g->fses[i].mountable);
-  }
-  guestfs_int_end_stringsbuf (g, &ret);
-
-  qsort (ret.argv, ret.size-1, sizeof (char *), compare_strings);
-
-  return ret.argv;
-}
-
-char *
-guestfs_impl_inspect_get_type (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  char *ret = NULL;
-
-  if (!fs)
-    return NULL;
-
-  switch (fs->type) {
-  case OS_TYPE_DOS: ret = safe_strdup (g, "dos"); break;
-  case OS_TYPE_FREEBSD: ret = safe_strdup (g, "freebsd"); break;
-  case OS_TYPE_HURD: ret = safe_strdup (g, "hurd"); break;
-  case OS_TYPE_LINUX: ret = safe_strdup (g, "linux"); break;
-  case OS_TYPE_MINIX: ret = safe_strdup (g, "minix"); break;
-  case OS_TYPE_NETBSD: ret = safe_strdup (g, "netbsd"); break;
-  case OS_TYPE_OPENBSD: ret = safe_strdup (g, "openbsd"); break;
-  case OS_TYPE_WINDOWS: ret = safe_strdup (g, "windows"); break;
-  case OS_TYPE_UNKNOWN: ret = safe_strdup (g, "unknown"); break;
-  }
-
-  if (ret == NULL)
-    abort ();
-
-  return ret;
-}
-
-char *
-guestfs_impl_inspect_get_arch (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-  return safe_strdup (g, fs->arch ? : "unknown");
-}
-
-char *
-guestfs_impl_inspect_get_distro (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  char *ret = NULL;
-
-  if (!fs)
-    return NULL;
-
-  switch (fs->distro) {
-  case OS_DISTRO_ALPINE_LINUX: ret = safe_strdup (g, "alpinelinux"); break;
-  case OS_DISTRO_ALTLINUX: ret = safe_strdup (g, "altlinux"); break;
-  case OS_DISTRO_ARCHLINUX: ret = safe_strdup (g, "archlinux"); break;
-  case OS_DISTRO_BUILDROOT: ret = safe_strdup (g, "buildroot"); break;
-  case OS_DISTRO_CENTOS: ret = safe_strdup (g, "centos"); break;
-  case OS_DISTRO_CIRROS: ret = safe_strdup (g, "cirros"); break;
-  case OS_DISTRO_COREOS: ret = safe_strdup (g, "coreos"); break;
-  case OS_DISTRO_DEBIAN: ret = safe_strdup (g, "debian"); break;
-  case OS_DISTRO_FEDORA: ret = safe_strdup (g, "fedora"); break;
-  case OS_DISTRO_FREEBSD: ret = safe_strdup (g, "freebsd"); break;
-  case OS_DISTRO_FREEDOS: ret = safe_strdup (g, "freedos"); break;
-  case OS_DISTRO_FRUGALWARE: ret = safe_strdup (g, "frugalware"); break;
-  case OS_DISTRO_GENTOO: ret = safe_strdup (g, "gentoo"); break;
-  case OS_DISTRO_LINUX_MINT: ret = safe_strdup (g, "linuxmint"); break;
-  case OS_DISTRO_MAGEIA: ret = safe_strdup (g, "mageia"); break;
-  case OS_DISTRO_MANDRIVA: ret = safe_strdup (g, "mandriva"); break;
-  case OS_DISTRO_MEEGO: ret = safe_strdup (g, "meego"); break;
-  case OS_DISTRO_NETBSD: ret = safe_strdup (g, "netbsd"); break;
-  case OS_DISTRO_OPENBSD: ret = safe_strdup (g, "openbsd"); break;
-  case OS_DISTRO_OPENSUSE: ret = safe_strdup (g, "opensuse"); break;
-  case OS_DISTRO_ORACLE_LINUX: ret = safe_strdup (g, "oraclelinux"); break;
-  case OS_DISTRO_PARDUS: ret = safe_strdup (g, "pardus"); break;
-  case OS_DISTRO_PLD_LINUX: ret = safe_strdup (g, "pldlinux"); break;
-  case OS_DISTRO_REDHAT_BASED: ret = safe_strdup (g, "redhat-based"); break;
-  case OS_DISTRO_RHEL: ret = safe_strdup (g, "rhel"); break;
-  case OS_DISTRO_SCIENTIFIC_LINUX: ret = safe_strdup (g, "scientificlinux"); break;
-  case OS_DISTRO_SLACKWARE: ret = safe_strdup (g, "slackware"); break;
-  case OS_DISTRO_SLES: ret = safe_strdup (g, "sles"); break;
-  case OS_DISTRO_SUSE_BASED: ret = safe_strdup (g, "suse-based"); break;
-  case OS_DISTRO_TTYLINUX: ret = safe_strdup (g, "ttylinux"); break;
-  case OS_DISTRO_WINDOWS: ret = safe_strdup (g, "windows"); break;
-  case OS_DISTRO_UBUNTU: ret = safe_strdup (g, "ubuntu"); break;
-  case OS_DISTRO_VOID_LINUX: ret = safe_strdup (g, "voidlinux"); break;
-  case OS_DISTRO_UNKNOWN: ret = safe_strdup (g, "unknown"); break;
-  }
-
-  if (ret == NULL)
-    abort ();
-
-  return ret;
-}
-
-int
-guestfs_impl_inspect_get_major_version (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return -1;
-
-  return fs->version.v_major;
-}
-
-int
-guestfs_impl_inspect_get_minor_version (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return -1;
-
-  return fs->version.v_minor;
-}
-
-char *
-guestfs_impl_inspect_get_product_name (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-  return safe_strdup (g, fs->product_name ? : "unknown");
-}
-
-char *
-guestfs_impl_inspect_get_product_variant (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-  return safe_strdup (g, fs->product_variant ? : "unknown");
-}
-
-char *
-guestfs_impl_inspect_get_windows_systemroot (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-  if (!fs->windows_systemroot) {
-    error (g, _("not a Windows guest, or systemroot could not be determined"));
-    return NULL;
-  }
-
-  return safe_strdup (g, fs->windows_systemroot);
-}
-
-char *
-guestfs_impl_inspect_get_windows_software_hive (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-  if (!fs->windows_software_hive) {
-    error (g, _("not a Windows guest, or software hive not found"));
-    return NULL;
-  }
-
-  return safe_strdup (g, fs->windows_software_hive);
-}
-
-char *
-guestfs_impl_inspect_get_windows_system_hive (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-  if (!fs->windows_system_hive) {
-    error (g, _("not a Windows guest, or system hive not found"));
-    return NULL;
-  }
-
-  return safe_strdup (g, fs->windows_system_hive);
-}
-
-char *
-guestfs_impl_inspect_get_windows_current_control_set (guestfs_h *g,
-						      const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-  if (!fs->windows_current_control_set) {
-    error (g, _("not a Windows guest, or CurrentControlSet could not be determined"));
-    return NULL;
-  }
-
-  return safe_strdup (g, fs->windows_current_control_set);
-}
-
-char *
-guestfs_impl_inspect_get_format (guestfs_h *g, const char *root)
-{
-  char *ret = NULL;
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-  switch (fs->format) {
-  case OS_FORMAT_INSTALLED: ret = safe_strdup (g, "installed"); break;
-  case OS_FORMAT_INSTALLER: ret = safe_strdup (g, "installer"); break;
-  case OS_FORMAT_UNKNOWN: ret = safe_strdup (g, "unknown"); break;
-  }
-
-  if (ret == NULL)
-    abort ();
-
-  return ret;
-}
-
-int
-guestfs_impl_inspect_is_live (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return -1;
-
-  return fs->is_live_disk;
-}
-
-int
-guestfs_impl_inspect_is_netinst (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return -1;
-
-  return fs->is_netinst_disk;
-}
-
-int
-guestfs_impl_inspect_is_multipart (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return -1;
-
-  return fs->is_multipart_disk;
-}
-
-char **
-guestfs_impl_inspect_get_mountpoints (guestfs_h *g, const char *root)
-{
-  char **ret;
-  size_t i, count, nr;
-  struct inspect_fs *fs;
-
-  fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-#define CRITERION(fs, i) fs->fstab[i].mountpoint[0] == '/'
-
-  nr = fs->nr_fstab;
-
-  if (nr == 0)
-    count = 1;
-  else {
-    count = 0;
-    for (i = 0; i < nr; ++i)
-      if (CRITERION (fs, i))
-        count++;
-  }
-
-  /* Hashtables have 2N+1 entries. */
-  ret = calloc (2*count+1, sizeof (char *));
-  if (ret == NULL) {
-    perrorf (g, "calloc");
-    return NULL;
-  }
-
-  /* If no fstab information (Windows) return just the root. */
-  if (nr == 0) {
-    ret[0] = safe_strdup (g, "/");
-    ret[1] = safe_strdup (g, root);
-    ret[2] = NULL;
-    return ret;
-  }
-
-  count = 0;
-  for (i = 0; i < nr; ++i)
-    if (CRITERION (fs, i)) {
-      ret[2*count] = safe_strdup (g, fs->fstab[i].mountpoint);
-      ret[2*count+1] = safe_strdup (g, fs->fstab[i].mountable);
-      count++;
-    }
-#undef CRITERION
-
-  return ret;
-}
-
-char **
-guestfs_impl_inspect_get_filesystems (guestfs_h *g, const char *root)
-{
-  char **ret;
-  size_t i, nr;
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-
-  if (!fs)
-    return NULL;
-
-  nr = fs->nr_fstab;
-  ret = calloc (nr == 0 ? 2 : nr+1, sizeof (char *));
-  if (ret == NULL) {
-    perrorf (g, "calloc");
-    return NULL;
-  }
-
-  /* If no fstab information (Windows) return just the root. */
-  if (nr == 0) {
-    ret[0] = safe_strdup (g, root);
-    ret[1] = NULL;
-    return ret;
-  }
-
-  for (i = 0; i < nr; ++i)
-    ret[i] = safe_strdup (g, fs->fstab[i].mountable);
-
-  return ret;
-}
-
 char **
 guestfs_impl_inspect_get_drive_mappings (guestfs_h *g, const char *root)
 {
@@ -628,75 +65,6 @@ guestfs_impl_inspect_get_drive_mappings (guestfs_h *g, const char *root)
   return ret.argv;
 }
 
-char *
-guestfs_impl_inspect_get_package_format (guestfs_h *g, const char *root)
-{
-  char *ret = NULL;
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-  switch (fs->package_format) {
-  case OS_PACKAGE_FORMAT_RPM: ret = safe_strdup (g, "rpm"); break;
-  case OS_PACKAGE_FORMAT_DEB: ret = safe_strdup (g, "deb"); break;
-  case OS_PACKAGE_FORMAT_PACMAN: ret = safe_strdup (g, "pacman"); break;
-  case OS_PACKAGE_FORMAT_EBUILD: ret = safe_strdup (g, "ebuild"); break;
-  case OS_PACKAGE_FORMAT_PISI: ret = safe_strdup (g, "pisi"); break;
-  case OS_PACKAGE_FORMAT_PKGSRC: ret = safe_strdup (g, "pkgsrc"); break;
-  case OS_PACKAGE_FORMAT_APK: ret = safe_strdup (g, "apk"); break;
-  case OS_PACKAGE_FORMAT_XBPS: ret = safe_strdup (g, "xbps"); break;
-  case OS_PACKAGE_FORMAT_UNKNOWN:
-    ret = safe_strdup (g, "unknown");
-    break;
-  }
-
-  if (ret == NULL)
-    abort ();
-
-  return ret;
-}
-
-char *
-guestfs_impl_inspect_get_package_management (guestfs_h *g, const char *root)
-{
-  char *ret = NULL;
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-  switch (fs->package_management) {
-  case OS_PACKAGE_MANAGEMENT_APK: ret = safe_strdup (g, "apk"); break;
-  case OS_PACKAGE_MANAGEMENT_APT: ret = safe_strdup (g, "apt"); break;
-  case OS_PACKAGE_MANAGEMENT_DNF: ret = safe_strdup (g, "dnf"); break;
-  case OS_PACKAGE_MANAGEMENT_PACMAN: ret = safe_strdup (g, "pacman"); break;
-  case OS_PACKAGE_MANAGEMENT_PISI: ret = safe_strdup (g, "pisi"); break;
-  case OS_PACKAGE_MANAGEMENT_PORTAGE: ret = safe_strdup (g, "portage"); break;
-  case OS_PACKAGE_MANAGEMENT_UP2DATE: ret = safe_strdup (g, "up2date"); break;
-  case OS_PACKAGE_MANAGEMENT_URPMI: ret = safe_strdup (g, "urpmi"); break;
-  case OS_PACKAGE_MANAGEMENT_XBPS: ret = safe_strdup (g, "xbps"); break;
-  case OS_PACKAGE_MANAGEMENT_YUM: ret = safe_strdup (g, "yum"); break;
-  case OS_PACKAGE_MANAGEMENT_ZYPPER: ret = safe_strdup (g, "zypper"); break;
-  case OS_PACKAGE_MANAGEMENT_UNKNOWN:
-    ret = safe_strdup (g, "unknown");
-    break;
-  }
-
-  if (ret == NULL)
-    abort ();
-
-  return ret;
-}
-
-char *
-guestfs_impl_inspect_get_hostname (guestfs_h *g, const char *root)
-{
-  struct inspect_fs *fs = guestfs_int_search_for_root (g, root);
-  if (!fs)
-    return NULL;
-
-  return safe_strdup (g, fs->hostname ? : "unknown");
-}
-
 void
 guestfs_int_free_inspect_info (guestfs_h *g)
 {
-- 
2.13.0




More information about the Libguestfs mailing list