[Libguestfs] [PATCH 2/3] inspection: Add inspection directory, rules and supporting code.

Richard W.M. Jones rjones at redhat.com
Wed Dec 2 22:05:29 UTC 2015


---
 .gitignore                        |   4 +
 Makefile.am                       |   2 +
 configure.ac                      |   1 +
 docs/guestfs-hacking.pod          |   6 +
 generator/main.ml                 |   4 +
 inspection/Makefile.am            |  87 ++++++++
 inspection/cleanups.c             |  38 ++++
 inspection/facts.c                | 321 +++++++++++++++++++++++++++
 inspection/guestfs-inspection.pod | 456 ++++++++++++++++++++++++++++++++++++++
 inspection/inspection.c           | 104 +++++++++
 inspection/inspection.h           |  67 ++++++
 inspection/inspection.rules       | 125 +++++++++++
 inspection/mount.c                |  33 +++
 inspection/utils.c                |  50 +++++
 po/POTFILES                       |   5 +
 src/guestfs.pod                   |   1 +
 16 files changed, 1304 insertions(+)
 create mode 100644 inspection/Makefile.am
 create mode 100644 inspection/cleanups.c
 create mode 100644 inspection/facts.c
 create mode 100644 inspection/guestfs-inspection.pod
 create mode 100644 inspection/inspection.c
 create mode 100644 inspection/inspection.h
 create mode 100644 inspection/inspection.rules
 create mode 100644 inspection/mount.c
 create mode 100644 inspection/utils.c

diff --git a/.gitignore b/.gitignore
index 288a853..b5e5a77 100644
--- a/.gitignore
+++ b/.gitignore
@@ -248,6 +248,10 @@ Makefile.in
 /haskell/Guestfs030Config
 /haskell/Guestfs050LVCreate
 /haskell/Guestfs.hs
+/inspection/guestfs-inspection
+/inspection/guestfs-inspection.8
+/inspection/rules.c
+/inspection/stamp-guestfs-inspection.pod
 /inspector/actual-*.xml
 /inspector/stamp-virt-inspector.pod
 /inspector/test-xmllint.sh
diff --git a/Makefile.am b/Makefile.am
index 951ee43..0363136 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -42,6 +42,7 @@ SUBDIRS += src docs examples po
 
 # The daemon and the appliance.
 if ENABLE_DAEMON
+SUBDIRS += inspection
 SUBDIRS += daemon
 SUBDIRS += tests/daemon
 endif
@@ -289,6 +290,7 @@ all-local:
 	find $(DIST_SUBDIRS) -name '*.c' -o -name '*.pl' -o -name '*.pm' | \
 	grep -v -E '^(examples|gnulib|gobject/docs|perl/(blib|examples)|po-docs|tests|test-data)/' | \
 	grep -v -E '/((guestfs|rc)_protocol\.c)$$' | \
+	grep -v -E '^inspection/rules\.c$$' | \
 	grep -v -E '^python/utils\.c$$' | \
 	LC_ALL=C sort > po/POTFILES
 	cd $(srcdir); \
diff --git a/configure.ac b/configure.ac
index 5af33ed..893826b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -205,6 +205,7 @@ AC_CONFIG_FILES([Makefile
                  golang/Makefile
                  golang/examples/Makefile
                  haskell/Makefile
+                 inspection/Makefile
                  inspector/Makefile
                  java/Makefile
                  java/examples/Makefile
diff --git a/docs/guestfs-hacking.pod b/docs/guestfs-hacking.pod
index 9f4b72a..5827375 100644
--- a/docs/guestfs-hacking.pod
+++ b/docs/guestfs-hacking.pod
@@ -560,6 +560,11 @@ L<virt-get-kernel(1)> command and documentation.
 Gnulib is used as a portability library.  A copy of gnulib is included
 under here.
 
+=item F<inspection>
+
+Inspection.  See L<guestfs(3)/INSPECTION> and
+L<guestfs-inspection(8)>.
+
 =item F<inspector>
 
 L<virt-inspector(1)>, the virtual machine image inspector.
@@ -739,6 +744,7 @@ Create the branch in git:
 
 L<guestfs(3)>,
 L<guestfs-examples(3)>,
+L<guestfs-inspection(8)>,
 L<guestfs-internals(3)>,
 L<guestfs-performance(1)>,
 L<guestfs-release-notes(1)>,
diff --git a/generator/main.ml b/generator/main.ml
index 35511ce..7473e89 100644
--- a/generator/main.ml
+++ b/generator/main.ml
@@ -212,6 +212,10 @@ Run it from the top source directory using the command
   output_to "customize/customize-synopsis.pod" generate_customize_synopsis_pod;
   output_to "customize/customize-options.pod" generate_customize_options_pod;
 
+  (* Run the rules compiler to generate inspection rules. *)
+  output_to "inspection/rules.c"
+            (Rules_compiler.compile "inspection/inspection.rules");
+
   (* Generate the list of files generated -- last. *)
   printf "generated %d lines of code\n" (get_lines_generated ());
   let files = List.sort compare (get_files_generated ()) in
diff --git a/inspection/Makefile.am b/inspection/Makefile.am
new file mode 100644
index 0000000..3020ff4
--- /dev/null
+++ b/inspection/Makefile.am
@@ -0,0 +1,87 @@
+# libguestfs
+# Copyright (C) 2015 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+include $(top_srcdir)/subdir-rules.mk
+
+generator_built = \
+	rules.c
+
+BUILT_SOURCES = \
+	$(generator_built)
+
+CLEANFILES = \
+	stamp-guestfs-inspection.pod \
+	guestfs-inspection.8
+
+# Build the inspection program.
+
+if INSTALL_DAEMON
+sbin_PROGRAMS = guestfs-inspection
+else
+noinst_PROGRAMS = guestfs-inspection
+endif
+
+guestfs_inspection_SOURCES = \
+	cleanups.c \
+	facts.c \
+	inspection.c \
+	inspection.h \
+	mount.c \
+	rules.c \
+	utils.c
+
+# XXX Why is this necessary?
+rules.c: ../generator/stamp-generator
+	rm -f $<
+	$(MAKE) -C ../generator
+
+guestfs_inspection_LDADD = \
+	$(AUGEAS_LIBS) \
+	$(HIVEX_LIBS) \
+	$(PCRE_LIBS) \
+	$(top_builddir)/gnulib/lib/.libs/libgnu.a
+
+guestfs_inspection_CPPFLAGS = \
+	-I$(top_srcdir)/gnulib/lib \
+	-I$(top_builddir)/gnulib/lib \
+	-I$(top_srcdir)/src \
+	-I$(top_builddir)/src
+
+guestfs_inspection_CFLAGS = \
+	$(WARN_CFLAGS) $(WERROR_CFLAGS) \
+	$(AUGEAS_CFLAGS) \
+	$(HIVEX_CFLAGS) \
+	$(PCRE_CFLAGS)
+
+# Manual pages and HTML files for the website.
+if INSTALL_DAEMON
+man_MANS = guestfs-inspection.8
+else
+noinst_MANS = guestfs-inspection.8
+endif
+noinst_DATA = $(top_builddir)/website/guestfs-inspection.8.html
+
+guestfs-inspection.8 $(top_builddir)/website/guestfs-inspection.8.html: stamp-guestfs-inspection.pod
+
+stamp-guestfs-inspection.pod: guestfs-inspection.pod
+	$(PODWRAPPER) \
+	  --section 8 \
+	  --man guestfs-inspection.8 \
+	  --html $(top_builddir)/website/guestfs-inspection.8.html \
+	  --license GPLv2+ \
+	  $<
+	touch $@
diff --git a/inspection/cleanups.c b/inspection/cleanups.c
new file mode 100644
index 0000000..73d6e92
--- /dev/null
+++ b/inspection/cleanups.c
@@ -0,0 +1,38 @@
+/* guestfs-inspection
+ * Copyright (C) 2009-2015 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "inspection.h"
+
+/* Used by the CLEANUP_* macros.  Do not call these directly. */
+
+void
+cleanup_free (void *ptr)
+{
+  free (* (void **) ptr);
+}
+
+void
+cleanup_free_string_list (void *ptr)
+{
+  free_strings (* (char ***) ptr);
+}
diff --git a/inspection/facts.c b/inspection/facts.c
new file mode 100644
index 0000000..219bcbd
--- /dev/null
+++ b/inspection/facts.c
@@ -0,0 +1,321 @@
+/* guestfs-inspection
+ * Copyright (C) 2009-2015 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/* Handle the true and false facts sets, and other helper functions. */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+#include <assert.h>
+#include <error.h>
+#include <errno.h>
+#include <alloca.h>
+
+#include "gl_oset.h"
+#include "gl_xoset.h"
+#include "gl_array_oset.h"
+
+#include "inspection.h"
+
+/* True and false facts sets.  See guestfs-inspection(8)/WRITING RULES
+ * to understand why these are used.
+ */
+static gl_oset_t true_facts = NULL;
+static gl_oset_t false_facts = NULL;
+
+/* Used to store a single true or false fact.  The only reason we know
+ * it's a false fact is because it would be stored in the false_facts
+ * set.
+ */
+struct fact {
+  char *term_name;
+  size_t nr_term_args;
+  char *term_arg[0];
+};
+
+static int
+compare_facts (const void *vf1, const void *vf2)
+{
+  size_t i;
+  int r;
+  const fact *f1 = vf1;
+  const fact *f2 = vf2;
+
+  r = strcmp (f1->term_name, f2->term_name);
+  if (r != 0) return r;
+
+  /* If term names are equal, they are supposed to have the same
+   * number of arguments.  We type-checked that when reading the
+   * source.  However, better check.
+   */
+  assert (f1->nr_term_args == f2->nr_term_args);
+
+  for (i = 0; i < f1->nr_term_args; ++i) {
+    r = strcmp (f1->term_arg[i], f2->term_arg[i]);
+    if (r != 0) return r;
+  }
+
+  return 0;
+}
+
+static void
+free_fact (const void *vf)
+{
+  /* Why is the parameter const?
+   * See Bruno Haible's explanation here:
+   * https://www.mail-archive.com/bug-gnulib@gnu.org/msg08619.html
+   */
+  fact *f = (fact *) vf;
+  size_t i;
+
+  free (f->term_name);
+  for (i = 0; i < f->nr_term_args; ++i)
+    free (f->term_arg[i]);
+  free (f);
+}
+
+static void init_facts_helper (void) __attribute__((constructor));
+
+static void
+init_facts_helper (void)
+{
+  true_facts = gl_oset_create_empty (GL_ARRAY_OSET, compare_facts, free_fact);
+  false_facts = gl_oset_create_empty (GL_ARRAY_OSET, compare_facts, free_fact);
+}
+
+static void free_facts_helper (void) __attribute__((destructor));
+
+static void
+free_facts_helper (void)
+{
+  gl_oset_free (true_facts);
+  gl_oset_free (false_facts);
+}
+
+static void
+clear_set (gl_oset_t set)
+{
+  const void *f;
+  gl_oset_iterator_t iter = gl_oset_iterator (set);
+
+  while (gl_oset_iterator_next (&iter, &f)) {
+    gl_oset_remove (set, (void *) f);
+  }
+  gl_oset_iterator_free (&iter);
+}
+
+void
+clear_true_facts (void)
+{
+  clear_set (true_facts);
+}
+
+void
+clear_false_facts (void)
+{
+  clear_set (false_facts);
+}
+
+size_t
+count_true_facts (void)
+{
+  return gl_oset_size (true_facts);
+}
+
+/* This is just for debugging facts. */
+void
+print_fact (bool is_true, fact *f, FILE *fp)
+{
+  size_t i;
+
+  if (!is_true)
+    fputc ('!', fp);
+  fputs (f->term_name, fp);
+  if (f->nr_term_args > 0)
+    fputc ('(', fp);
+  for (i = 0; i < f->nr_term_args; ++i) {
+    fputc ('"', fp);
+    fputs (f->term_arg[i], fp);
+    fputc ('"', fp);
+    if (i+1 != f->nr_term_args)
+      fputs (", ", fp);
+  }
+  if (f->nr_term_args > 0)
+    fputc (')', fp);
+}
+
+static void
+print_set (bool is_true, gl_oset_t set)
+{
+  const void *vf;
+  gl_oset_iterator_t iter = gl_oset_iterator (set);
+
+  while (gl_oset_iterator_next (&iter, &vf)) {
+    fact *f = (fact *) vf;
+    print_fact (is_true, f, stdout);
+    printf ("\n");
+  }
+  gl_oset_iterator_free (&iter);
+}
+
+void
+print_true_facts (void)
+{
+  print_set (true, true_facts);
+}
+
+void
+print_false_facts (void)
+{
+  print_set (false, false_facts);
+}
+
+/* Look for every string parameter of every fact we know about, and
+ * add all those strings to the set.
+ */
+void
+add_all_fact_strings (gl_oset_t set)
+{
+  const void *vf;
+  fact *f;
+  gl_oset_iterator_t iter;
+  size_t i;
+
+  iter = gl_oset_iterator (true_facts);
+  while (gl_oset_iterator_next (&iter, &vf)) {
+    f = (fact *) vf;
+    for (i = 0; i < f->nr_term_args; ++i)
+      gl_oset_add (set, f->term_arg[i]);
+  }
+  gl_oset_iterator_free (&iter);
+
+  iter = gl_oset_iterator (false_facts);
+  while (gl_oset_iterator_next (&iter, &vf)) {
+    f = (fact *) vf;
+    for (i = 0; i < f->nr_term_args; ++i)
+      gl_oset_add (set, f->term_arg[i]);
+  }
+  gl_oset_iterator_free (&iter);
+}
+
+/* Look for every string parameter in the specific argument position
+ * of the specific term name (both true and false), and add those
+ * strings only to the set.
+ */
+void
+add_strings_from_facts (gl_oset_t set, const char *term_name, size_t arg_i)
+{
+  const void *vf;
+  fact *f;
+  gl_oset_iterator_t iter;
+
+  iter = gl_oset_iterator (true_facts);
+  while (gl_oset_iterator_next (&iter, &vf)) {
+    f = (fact *) vf;
+    if (strcmp (f->term_name, term_name) == 0)
+      gl_oset_add (set, f->term_arg[arg_i]);
+  }
+  gl_oset_iterator_free (&iter);
+
+  iter = gl_oset_iterator (false_facts);
+  while (gl_oset_iterator_next (&iter, &vf)) {
+    f = (fact *) vf;
+    if (strcmp (f->term_name, term_name) == 0)
+      gl_oset_add (set, f->term_arg[arg_i]);
+  }
+  gl_oset_iterator_free (&iter);
+}
+
+/* NB: This does not make a deep copy of the strings.  However before
+ * we add the fact to the true_facts or false_facts arrays, we do call
+ * deep_copy to copy the strings.
+ */
+fact *
+create_fact (const char *term_name, ...)
+{
+  fact *f;
+  const char *p;
+  size_t i;
+  va_list args;
+
+  f = malloc (sizeof (*f));
+  if (f == NULL)
+    error (EXIT_FAILURE, errno, "malloc");
+  va_start (args, term_name);
+  for (i = 0; (p = va_arg (args, const char *)) != NULL; ++i) {
+    f = realloc (f, sizeof (*f) + (i+1) * sizeof (char *));
+    if (f == NULL)
+      error (EXIT_FAILURE, errno, "realloc");
+    f->term_arg[i] = (char *) p;
+  }
+  va_end (args);
+  f->term_name = (char *) term_name;
+  f->nr_term_args = i;
+  return f;                     /* caller must free only the struct */
+}
+
+static fact *
+deep_copy_fact (const fact *f)
+{
+  fact *ret;
+  size_t i;
+
+  ret = malloc (sizeof (*ret) + f->nr_term_args * sizeof (char *));
+  if (ret == NULL)
+    error (EXIT_FAILURE, errno, "malloc");
+
+  ret->term_name = strdup (f->term_name);
+  if (ret->term_name == NULL)
+    error (EXIT_FAILURE, errno, "strdup");
+
+  ret->nr_term_args = f->nr_term_args;
+
+  for (i = 0; i < f->nr_term_args; ++i) {
+    ret->term_arg[i] = strdup (f->term_arg[i]);
+    if (ret->term_arg[i] == NULL)
+      error (EXIT_FAILURE, errno, "strdup");
+  }
+
+  return ret;
+}
+
+bool
+is_fact (bool is_true, const fact *f)
+{
+  return gl_oset_search (is_true ? true_facts : false_facts, f);
+}
+
+bool
+add_fact (bool is_true, const fact *f)
+{
+  fact *f2 = deep_copy_fact (f);
+  bool ret;
+
+  ret = gl_oset_add (is_true ? true_facts : false_facts, f2);
+
+  /* Didn't add it, so we must free the deep copy. */
+  if (!ret)
+    free_fact (f2);
+
+  return ret;
+}
diff --git a/inspection/guestfs-inspection.pod b/inspection/guestfs-inspection.pod
new file mode 100644
index 0000000..113f903
--- /dev/null
+++ b/inspection/guestfs-inspection.pod
@@ -0,0 +1,456 @@
+=head1 NAME
+
+guestfs-inspection - guestfs inspection program
+
+=head1 SYNOPSIS
+
+ guestfs-inspection
+
+=head1 NOTE
+
+This man page documents the guestfs inspection program.  If you want
+to read about guestfs inspection then this is the wrong place.  See
+L<guestfs(3)/INSPECTION> instead.
+
+=head1 DESCRIPTION
+
+C<guestfs-inspection> is a standalone program that performs inspection
+on the local disks, to find out what operating system(s) are
+installed.  It normally runs inside the libguestfs appliance, started
+by L<guestfsd(8)>, when the caller uses the C<guestfs_inspect_os> API
+(see L<guestfs-internals(1)> and L<guestfs(3)/guestfs_inspect_os>).
+You should never need to run this program by hand.
+
+The program looks at all disks attached to the appliance, looking for
+filesystems that might belong to operating systems.  It may mount
+these temporarily to examine them for Linux configuration files,
+Windows Registries, and so on.  It then tries to determine what
+operating system(s) are installed on the disks.  It is able to detect
+many different Linux distributions, Windows, BSD, and others.  The
+currently mounted root filesystem is ignored, since when running under
+libguestfs, that filesystem is part of the libguestfs appliance (this
+is the main difference compared to programs like C<facter>).
+
+Guestfs-inpection is written in C, but most of the C is generated by a
+rules compiler from a set of inspection rules written in a more
+compact, declarative, Prolog-inspired language.  If you want to write
+or modify the rules, see L</WRITING RULES> below.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-?>
+
+=item B<--help>
+
+Display brief help.
+
+=item B<-v>
+
+=item B<--verbose>
+
+Enable verbose messages for debugging.
+
+=back
+
+=head1 WRITING RULES
+
+Inspection is performed according to a set of rules written in a
+compact, declarative, Prolog-inspired language.  This section explains
+how this language works, so you can write your own rules to detect
+other operating systems.
+
+The rules can be found starting in C<inspection/inspection.rules> (in
+the libguestfs sources).  The rules are compiled down to C and linked
+into the guestfs-inspection program, together with a bit of extra C
+code to provide runtime support.
+
+=head2 Facts
+
+Facts are what we try to determine about the operating system(s) we
+are inspecting.  They look like this:
+
+ Filesystem("/dev/sda1")
+
+which means "F</dev/sda1> is a filesystem".
+
+ File("/dev/sda1", "/etc/fstab")
+
+which means "there exists a file called F</etc/fstab> on the
+F</dev/sda1> filesystem".
+
+Facts come in three flavours: true facts, false facts, and unknown
+facts.  False facts are written like this:
+
+ ! File("/dev/sda1", "/etc/fstab")
+
+which means "either F</dev/sda1> is not a filesystem or there does not
+exist a file called F</etc/fstab> on this filesystem".
+
+Unknown facts are facts that we don't know if they are true or false
+yet.
+
+=head2 Rules
+
+Rules are used to generate more facts.  A simple rule for generating
+C<File> facts might look like this:
+
+ File(fs, filename) :-
+     Filesystem(fs),
+     {{
+       // some C code to mount 'fs' and check for 'filename'
+     }}.
+
+You can read this as: "For all C<fs> & C<filename>, if C<fs> is a
+filesystem, and running the C code with parameters C<fs> and
+C<filename> returns true, then C<File(fs, filename)> is a true fact".
+
+In the Prolog-inspired language, a comma (C<,>) is the AND operator.
+A semicolon (C<;>) is the OR operator.  C<:-> is a backwards
+if-statement (the condition is on the right, the conclusion is on the
+left).  Also notice the dot (C<.>) which must follow each rule.
+
+Uppercase identifiers are facts.  Lowercase identifiers are variables.
+All identifiers are case-sensitive.
+
+Everything in C<{{ ... }}> is embedded C code.  In this case the C
+code returns a true/false/error indication, but embedded C code can
+also do more complicated things and return strings and lists as we'll
+see later.
+
+You can use parentheses C<(...)> for grouping expressions on the right
+hand side of the C<:-> operator.
+
+=head2 Program evaluation
+
+Let's take a simple set of rules which you might use to detect a
+Fedora root filesystem:
+
+ File(fs, filename) :-
+     Filesystem(fs),
+     {{
+       // some C code to mount 'fs' and check for 'filename'
+     }}.
+ 
+ Fedora(rootfs) :-
+     Filesystem(rootfs),
+     File(rootfs, "/etc/fedora-release").
+
+When evaluating this program, there are two sets of facts, the true
+facts and the false facts.  Let's start with the false facts set being
+empty, and let's seed the true facts set with some C<Filesystem>
+facts:
+
+ true_facts = { Filesystem("/dev/sda1"), Filesystem("/dev/sda3") }
+ false_facts = {  } // empty set
+
+Unknown facts are facts which don't appear in either set.
+
+Evaluating the program works like this: We consider each rule in turn,
+and see if we can find new true or false facts from it.  These new
+facts are added to the true or false facts sets.  After looking at
+each rule in the program, as long as at least one new fact was added
+to the true facts set, we go back to the start of the rules and repeat
+over.  We do this until we can no longer add any new true facts, and
+then we're done.
+
+In the case of this program, we start with the C<File> rule, and we
+substitute (theoretically) every possible string for C<fs> and
+C<filename>.
+
+For example, this substitution:
+
+ File("/dev/sda1", "/etc/fedora-release") :-
+     Filesystem("/dev/sda1"),
+     {{ // checks for file and returns false }}.
+
+turns out to be false (because the C code doesn't find F</etc/fstab>
+in F</dev/sda1>), so that yields a new false fact:
+
+ ! File("/dev/sda1", "/etc/fedora-release")
+
+But this substitution turns out to be true:
+
+ File("/dev/sda3", "/etc/fedora-release") :-
+     Filesystem("/dev/sda3"),
+     {{ // checks for file and returns true }}.
+
+so that yields a new true fact:
+
+ File("/dev/sda3", "/etc/fedora-release")
+
+In theory every possible string is tried, eg C<File("ardvark", "foo123654")>.
+That would take literally forever to run, but luckily the rules
+compiler is smarter.
+
+Looking now at the second rule, we try this substitution:
+
+ Fedora("/dev/sda3") :-
+     Filesystem("/dev/sda3"),
+     File("/dev/sda3", "/etc/fedora-release").
+
+which yields another new true fact:
+
+ Fedora("/dev/sda3")
+
+Because we added several new true facts to the set, we go back and
+repeat the whole process.  But after trying all the rules for a second
+time, no more true facts can be added, so now we're done.
+
+At the end, the set of true facts is:
+
+ true_facts = { Filesystem("/dev/sda1"), Filesystem("/dev/sda3"),
+                File("/dev/sda3", "/etc/fedora-release"),
+                Fedora("/dev/sda3") }
+
+We don't care about the false facts -- they are discarded at the end
+of the program.
+
+The summary of inspection is that F</dev/sda3> contains a Fedora root
+filesystem.
+
+Of course real inspection is much more complicated than this, but the
+same program evaluation order is followed.
+
+=head2 Some caveats with the language
+
+It's easy to look at an expression like:
+
+ Fedora(rootfs) :-
+     Filesystem(rootfs),
+     File(rootfs, "/etc/fedora-release"). /* line 3 */
+
+and think that line 3 is "calling" the "File function".  This is
+B<not> what is happening!  Rules are not functions.  Rules are
+considered in isolation.  Rules don't "call" other rules.  Instead
+when trying to find possible values that can be substituted into a
+rule, we only look at the rule and the current sets of true and false
+facts.
+
+When searching for values to subsitute, in theory the compiler would
+have to look at every possible string.  In practice of course it can't
+and doesn't do that.  Instead it looks at the current sets of true and
+false facts to find strings to substitute.  In the following rule:
+
+ File(fs, filename) :-
+     Filesystem(fs),
+     {{ // C code }}.
+
+suitable choices for C<fs> are found by looking at any C<Filesystem>
+facts in either the true or false sets.
+
+In some cases, this doesn't work, as in the example above where we
+have no clues for the C<filename> variable.  In that case the compiler
+tries every string literal from every rule in the program.  This can
+be inefficient, but by modifying the rule slightly you can avoid this.
+In the following program, only the strings F</etc/fstab> and
+F</etc/fedora-release> would be tried:
+
+ Filename("/etc/fstab").
+ Filename("/etc/fedora-release").
+ File(fs, filename) :-
+     Filesystem(fs),
+     Filename(filename),
+     {{ // C code }}.
+
+=head2 C expressions returning boolean
+
+Simple C code enclosed in C<{{ ... }}> as shown above should return a
+true, false or error status only.  It returns true by returning any
+integer E<ge> 1.  It should return C<0> to indicate false, and it
+should return C<-1> to indicate an error (which stops the program and
+causes inspection to fail with a user-visible error).
+
+Here is an example of a simple C expression returning a boolean:
+
+ File(fs, filename) :-
+     Filesystem(fs),
+     {{
+       int r;
+       char *relative_filename;
+       r = get_mount (fs, filename, &relative_filename);
+       if (r != 1) return r;
+       r = access (relative_filename, F_OK);
+       free (relative_filename);
+       if (r == -1) {
+         if (errno == ENOENT || errno == ENOTDIR)
+           return 0;
+         perror ("access");
+         return -1;
+       }
+       return 1;
+     }}.
+
+Notice that C<fs> and C<filename> are passed into the C code as local
+variables.
+
+You can see that dealing with errors is a bit involved, because we
+want to fail hard if some error like C<EIO> is thrown.
+
+=head2 C expressions returning strings
+
+C expressions can also return strings or tuples of strings.  This is
+useful where you want to parse the content of external files.
+
+The syntax for this sort of C expression is:
+
+ (var1, var2, ...)={{ ... }}
+
+where C<var1>, C<var2>, etc. are outputs from the C code.
+
+In the following example, a lot of error checking has been omitted
+for clarity:
+
+ ProductName(fs, product_name) :-
+     Unix_root(fs),
+     Distro(fs, "RHEL"),
+     (product_name)={{
+       int r;
+       char *line = NULL;
+       size_t n;
+       char *relative_filename;
+       r = get_mount (fs, "/etc/redhat-release", &relative_filename);
+       FILE *fp = fopen (relative_filename, "r");
+       free (relative_filename);
+       getline (&line, &n, fp);
+       fclose (fp);
+       set_product_name (line);
+       free (line);
+       return 0;
+     }}.
+
+The C code calls a function C<set_product_name> (that the compiler
+generates).
+
+The return value from the C code should be C<0> if everything was OK,
+or C<-1> if there is a error (which stops the whole program).
+
+=head2 C expressions returning multiple results
+
+Finally it is possible for C code to return multiple results.
+
+The syntax is:
+
+ [var1, var2, ...]={{ ... }}
+
+where C<var1>, C<var2>, etc. are outputs.  Unlike the previous rules,
+these rules may generate multiple facts from a single string
+substitution.
+
+This is how we populate the initial list of true facts about
+filesystems:
+
+ Filesystem(fs) :-
+     [fs]={{
+       int i;
+       for (i = 0; i < nr_filesystems; ++i) {
+         set_fs (fs[i]);
+       }
+       return 0;
+     }}.
+
+In this case, the C code repeatedly calls a function C<set_fs> (that
+the compiler generates) for each new filesystem discovered.  Multiple
+C<Filesystem> facts can be generated as a result of one application of
+this rule.
+
+The return value from the C code should be C<0> if everything was OK,
+or C<-1> if there is a error (which stops the whole program).
+
+=begin comment
+
+NOT IMPLEMENTED YET
+=head2 C code memoization
+
+For efficiency, the C code fragments are called once per input, and
+the result is memoized.
+
+This means you don't have to worry if the C code is particularly
+efficient.  No matter how many times we need to evaluate if
+F</etc/fstab> exists on F</dev/sda1>, the C code to do this will only
+be called once.  But it also means that impure C functions returning
+different results for the same input won't work, but you shouldn't do
+that.
+
+=end comment
+
+=head2 Type checking
+
+The current language treats every value as a string.  Every expression
+is a boolean.  One possible future enhancement is to handle other
+types.  There is still some minimal type checking applied:
+
+=over 4
+
+=item *
+
+A fact name which appears on a right hand side of any rule must also
+appear on the left hand side of a rule.  This is mainly for catching
+typos.
+
+=item *
+
+A fact must have the same number of arguments ("arity") each time it
+appears in the source.
+
+=back
+
+=head2 Debugging
+
+You can debug the evaluation of inspection programs by calling
+C<guestfs_set_verbose> (or setting C<$LIBGUESTFS_DEBUG=1>) before
+launching the handle.
+
+This causes L<guestfsd(8)> to pass the I<--verbose> parameter to this
+inspection program, which in turn causes the inspection program to
+print information about what rules it is trying and what true/false
+facts it has found.  These are passed back to libguestfs and printed
+on C<stderr> (or sent to the event system if you are using that).
+
+You can also print debug messages from C code embedded in C<{{...}}>
+expressions.  These are similarly sent upwards through to libguestfs
+and will appear on C<stderr>.
+
+=begin comment
+
+NOT IMPLEMENTED YET
+Remember that C memoization can cause C code to run fewer times than expected.
+
+=end comment
+
+=head1 EXIT STATUS
+
+This program returns 0 if successful, or non-zero if there was an
+error.
+
+=head1 SEE ALSO
+
+L<guestfsd(8)>,
+L<guestfs-hacking(1)>,
+L<guestfs-internals(1)>,
+L<guestfs(3)/INSPECTION>,
+L<http://libguestfs.org/>.
+
+=head1 AUTHOR
+
+Richard W.M. Jones L<http://people.redhat.com/~rjones/>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2009-2015 Red Hat Inc.
diff --git a/inspection/inspection.c b/inspection/inspection.c
new file mode 100644
index 0000000..ec1758d
--- /dev/null
+++ b/inspection/inspection.c
@@ -0,0 +1,104 @@
+/* guestfs-inspection
+ * Copyright (C) 2009-2015 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <getopt.h>
+#include <unistd.h>
+
+#include "inspection.h"
+
+int verbose = 0;
+
+/* Required by the gnulib 'error' module. */
+const char *program_name = "guestfs-inspection";
+
+static void __attribute__((noreturn))
+usage (int status)
+{
+  if (status != EXIT_SUCCESS)
+    fprintf (stderr, "Try `%s --help' for more information.\n",
+             program_name);
+  else {
+    printf ("%s: guestfs inspection\n"
+            "Copyright (C) 2009-2015 Red Hat Inc.\n"
+            "Usage:\n"
+            "Options:\n"
+            "  --help               Display brief help\n"
+            "  -v|--verbose         Verbose messages\n"
+            "  -V|--version         Display version and exit\n"
+            "For more information, see the manpage %s(8).\n",
+            program_name, program_name);
+  }
+  exit (status);
+}
+
+int
+main (int argc, char *argv[])
+{
+  enum { HELP_OPTION = CHAR_MAX + 1 };
+
+  static const char *options = "vV";
+  static const struct option long_options[] = {
+    { "help", 0, 0, HELP_OPTION },
+    { "verbose", 0, 0, 'v' },
+    { "version", 0, 0, 'V' },
+    { 0, 0, 0, 0 }
+  };
+  int c;
+  int option_index;
+
+  for (;;) {
+    c = getopt_long (argc, argv, options, long_options, &option_index);
+    if (c == -1) break;
+
+    switch (c) {
+    case 0:			/* options which are long only */
+      fprintf (stderr, "%s: unknown long option: %s (%d)\n",
+               program_name,
+               long_options[option_index].name, option_index);
+      exit (EXIT_FAILURE);
+
+    case 'v':
+      verbose++;
+      break;
+
+    case 'V':
+      printf ("%s %s\n", program_name, PACKAGE_VERSION_FULL);
+      exit (EXIT_SUCCESS);
+
+    case HELP_OPTION:
+      usage (EXIT_SUCCESS);
+
+    default:
+      usage (EXIT_FAILURE);
+    }
+  }
+
+  /* Run the rules. */
+  rules ();
+
+  /* Print the true facts.  XXX Output XXX */
+  print_true_facts ();
+
+  exit (EXIT_SUCCESS);
+}
diff --git a/inspection/inspection.h b/inspection/inspection.h
new file mode 100644
index 0000000..8addef9
--- /dev/null
+++ b/inspection/inspection.h
@@ -0,0 +1,67 @@
+/* guestfs-inspection
+ * Copyright (C) 2009-2015 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef GUESTFS_INSPECTION_H
+#define GUESTFS_INSPECTION_H
+
+#include <stdbool.h>
+
+#include "gl_oset.h"
+
+extern int verbose;
+
+typedef struct fact fact;
+
+/* facts.c */
+extern void clear_true_facts (void);
+extern void clear_false_facts (void);
+extern size_t count_true_facts (void);
+extern void print_fact (bool is_true, fact *f, FILE *fp);
+extern void print_true_facts (void);
+extern void print_false_facts (void);
+extern void add_all_fact_strings (gl_oset_t set);
+extern void add_strings_from_facts (gl_oset_t set, const char *term_name, size_t arg_i);
+extern fact *create_fact (const char *term_name, ...);
+extern bool is_fact (bool is_true, const fact *);
+extern bool add_fact (bool is_true, const fact *);
+
+/* rules.c - generated code */
+extern const char *all_strings[];
+extern void rules (void);
+
+/* mount.c - used by generated code */
+extern int get_mount (const char *fs, const char *filename, char **relative_filename);
+
+/* cleanups.c - used by the CLEANUP_* macros. */
+extern void cleanup_free (void *ptr);
+extern void cleanup_free_string_list (void *ptr);
+
+#ifdef HAVE_ATTRIBUTE_CLEANUP
+#define CLEANUP_FREE __attribute__((cleanup(cleanup_free)))
+#define CLEANUP_FREE_STRING_LIST                        \
+    __attribute__((cleanup(cleanup_free_string_list)))
+#else
+#define CLEANUP_FREE
+#define CLEANUP_FREE_STRING_LIST
+#endif
+
+/* utils.c */
+extern size_t count_strings (char *const *argv);
+extern void free_strings (char **argv);
+
+#endif /* GUESTFS_INSPECTION_H */
diff --git a/inspection/inspection.rules b/inspection/inspection.rules
new file mode 100644
index 0000000..9b1ed26
--- /dev/null
+++ b/inspection/inspection.rules
@@ -0,0 +1,125 @@
+/* -*- prolog -*- */
+
+Filesystem(fs) :-
+    [fs]={{
+        const char *filesystems[] = { "/dev/sda1", "/dev/sda2", "/dev/sda3" };
+        int i;
+        for (i = 0; i < 3; ++i) { set_fs (filesystems[i]); }
+        return 0;
+    }}.
+
+File(fs, filename) :-
+    Filesystem(fs),
+    {{
+        int r;
+        char *relative_filename;
+        struct stat statbuf;
+        r = get_mount (fs, filename, &relative_filename);
+        if (r != 1) return r;
+        r = stat (relative_filename, &statbuf);
+        free (relative_filename);
+        if (r == -1) {
+          if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP)
+            return 0;
+          perror ("stat");
+          return -1;
+        }
+        return S_ISREG (statbuf.st_mode);
+    }}.
+
+Directory(fs, dirname) :-
+    Filesystem(fs),
+    {{
+        int r;
+        char *relative_dirname;
+        struct stat statbuf;
+        r = get_mount (fs, dirname, &relative_dirname);
+        if (r != 1) return r;
+        r = stat (relative_dirname, &statbuf);
+        free (relative_dirname);
+        if (r == -1) {
+          if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP)
+            return 0;
+          perror ("stat");
+          return -1;
+        }
+        return S_ISDIR (statbuf.st_mode);
+    }}.
+
+Symlink(fs, filename) :-
+    Filesystem(fs),
+    {{
+        int r;
+        char *relative_filename;
+        struct stat statbuf;
+        r = get_mount (fs, filename, &relative_filename);
+        if (r != 1) return r;
+        r = stat (relative_filename, &statbuf);
+        free (relative_filename);
+        if (r == -1) {
+          if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP)
+            return 0;
+          perror ("stat");
+          return -1;
+        }
+        return S_ISLNK (statbuf.st_mode);
+    }}.
+
+Unix_root(fs) :-
+    Filesystem(fs),
+    (Directory(fs, "/bin"); Symlink(fs, "/bin")),
+    (File(fs, "/etc/fstab"); Symlink(fs, "/etc/fstab")),
+    (Directory(fs, "/lib"); Symlink(fs, "/lib")).
+
+Distro(rootfs, "RHEL") :-
+    Unix_root(rootfs),
+    File(rootfs, "/etc/redhat-release"),
+    ! File(rootfs, "/etc/fedora-release").
+
+/*
+ProductName(rootfs, product_name) :-
+    Unix_root(rootfs),
+    Distro(rootfs, "RHEL"),
+    (product_name)={{
+      // code to read first line from /etc/redhat-release
+    }}.
+
+Version(rootfs, major, minor) :-
+    Unix_root(rootfs),
+    Distro(rootfs, "RHEL"),
+    ProductName(rootfs, product_name),
+    (major, minor)={{
+      // code to parse product_name
+    }}.
+
+Distro(rootfs, "Fedora") :-
+    Unix_root(rootfs),
+    File(rootfs, "/etc/fedora-release").
+
+Version(rootfs, major, minor) :-
+    Unix_root(rootfs),
+    Distro(rootfs, "Fedora"),
+    (major, minor)={{
+      // code to parse /etc/fedora-release
+    }}.
+
+Distro(rootfs, "Debian") :-
+    Unix_root(rootfs),
+    File(rootfs, "/etc/debian_version").
+
+Version(rootfs, major, minor) :-
+    Unix_root(rootfs),
+    Distro(rootfs, "Debian"),
+    (major, minor)={{
+      // code to parse /etc/debian_version
+    }}.
+
+Has_fstab(rootfs) :-
+    Unix_root(fs),
+    File(rootfs, "/etc/fstab").
+Mount(rootfs, dev, mountpoint) :-
+    Has_fstab(rootfs),
+    [dev, mountpoint]={{
+        // code to return (dev, mountpoint) pairs from /etc/fstab
+    }}.
+*/
diff --git a/inspection/mount.c b/inspection/mount.c
new file mode 100644
index 0000000..a849ed2
--- /dev/null
+++ b/inspection/mount.c
@@ -0,0 +1,33 @@
+/* guestfs-inspection
+ * Copyright (C) 2009-2015 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "error.h"
+
+#include "inspection.h"
+
+int
+get_mount (const char *fs, const char *filename, char **relative_filename)
+{
+  error (EXIT_FAILURE, 0, "get_mount: not implemented");
+  return -1;
+}
diff --git a/inspection/utils.c b/inspection/utils.c
new file mode 100644
index 0000000..616e5cd
--- /dev/null
+++ b/inspection/utils.c
@@ -0,0 +1,50 @@
+/* guestfs-inspection
+ * Copyright (C) 2009-2015 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "inspection.h"
+
+size_t
+count_strings (char *const *argv)
+{
+  size_t argc;
+
+  for (argc = 0; argv[argc] != NULL; ++argc)
+    ;
+  return argc;
+}
+
+void
+free_strings (char **argv)
+{
+  size_t argc;
+
+  if (!argv)
+    return;
+
+  for (argc = 0; argv[argc] != NULL; ++argc)
+    free (argv[argc]);
+  free (argv);
+}
diff --git a/po/POTFILES b/po/POTFILES
index c6c277c..a88d0d0 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -256,6 +256,11 @@ gobject/src/struct-version.c
 gobject/src/struct-xattr.c
 gobject/src/struct-xfsinfo.c
 gobject/src/tristate.c
+inspection/cleanups.c
+inspection/facts.c
+inspection/inspection.c
+inspection/mount.c
+inspection/utils.c
 inspector/inspector.c
 java/com_redhat_et_libguestfs_GuestFS.c
 lua/lua-guestfs.c
diff --git a/src/guestfs.pod b/src/guestfs.pod
index f9dea92..299f391 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -3503,6 +3503,7 @@ L<virt-win-reg(1)>.
 Other libguestfs topics:
 L<guestfs-faq(1)>,
 L<guestfs-hacking(1)>,
+L<guestfs-inspection(8)>,
 L<guestfs-internals(1)>,
 L<guestfs-performance(1)>,
 L<guestfs-release-notes(1)>,
-- 
2.5.0




More information about the Libguestfs mailing list