---
.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