Using the rules compiler added in the previous commit, create an
inspection program ("guestfs-inspection"). This will run inside the
appliance. It is partly written in the Prolog-inspired language, with
C supporting functions.
For more details, read the guestfs-inspection(8) man page added in
this commit.
---
.gitignore | 4 +
Makefile.am | 2 +
appliance/Makefile.am | 3 +-
configure.ac | 1 +
docs/guestfs-hacking.pod | 6 +
generator/Makefile.am | 2 +-
generator/main.ml | 4 +
inspection/Makefile.am | 92 ++++++
inspection/detect.c | 152 ++++++++++
inspection/facts.c | 310 ++++++++++++++++++++
inspection/guestfs-inspection.pod | 450 ++++++++++++++++++++++++++++
inspection/inspection.c | 106 +++++++
inspection/inspection.h | 60 ++++
inspection/inspection.rules | 476 ++++++++++++++++++++++++++++++
inspection/match.c | 173 +++++++++++
inspection/mount.c | 597 ++++++++++++++++++++++++++++++++++++++
inspection/rules.h | 69 +++++
inspection/stringsbuf.c | 254 ++++++++++++++++
inspection/stringsbuf.h | 56 ++++
inspection/utils.c | 66 +++++
po/POTFILES | 7 +
src/guestfs.pod | 1 +
22 files changed, 2889 insertions(+), 2 deletions(-)
create mode 100644 inspection/Makefile.am
create mode 100644 inspection/detect.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/match.c
create mode 100644 inspection/mount.c
create mode 100644 inspection/rules.h
create mode 100644 inspection/stringsbuf.c
create mode 100644 inspection/stringsbuf.h
create mode 100644 inspection/utils.c
diff --git a/.gitignore b/.gitignore
index 67d8a2e..8da932c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -252,6 +252,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 ba99feb..11f5e78 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
@@ -290,6 +291,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/appliance/Makefile.am b/appliance/Makefile.am
index d8fb15b..eb1f4d3 100644
--- a/appliance/Makefile.am
+++ b/appliance/Makefile.am
@@ -72,11 +72,12 @@ packagelist: packagelist.in Makefile
cmp -s $@ $@-t || mv $@-t $@
rm -f $@-t
-supermin.d/daemon.tar.gz: ../daemon/guestfsd guestfs_lvm_conf.aug guestfs_shadow.aug
+supermin.d/daemon.tar.gz: ../daemon/guestfsd ../inspection/guestfs-inspection
guestfs_lvm_conf.aug guestfs_shadow.aug
rm -f $@ $@-t
rm -rf tmp-d
mkdir -p tmp-d$(DAEMON_SUPERMIN_DIR) tmp-d/etc tmp-d/usr/share/guestfs
ln ../daemon/guestfsd tmp-d$(DAEMON_SUPERMIN_DIR)/guestfsd
+ ln ../inspection/guestfs-inspection tmp-d$(DAEMON_SUPERMIN_DIR)/guestfs-inspection
ln $(srcdir)/guestfs_lvm_conf.aug tmp-d/usr/share/guestfs/guestfs_lvm_conf.aug
ln $(srcdir)/guestfs_shadow.aug tmp-d/usr/share/guestfs/guestfs_shadow.aug
( cd tmp-d && tar zcf - * ) > $@-t
diff --git a/configure.ac b/configure.ac
index 29b5092..00fccbb 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 419a4c2..4cd76bf 100644
--- a/docs/guestfs-hacking.pod
+++ b/docs/guestfs-hacking.pod
@@ -558,6 +558,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.
@@ -837,6 +842,7 @@ Optionally do a full release of the development branch.
L<guestfs(3)>,
L<guestfs-building(1)>,
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/Makefile.am b/generator/Makefile.am
index c53d3b9..210113a 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -194,7 +194,7 @@ noinst_DATA = stamp-generator
# Git removes empty directories, so in cases where the
# generator is creating the sole file in a directory, we
# have to create the directory first.
-stamp-generator: generator
+stamp-generator: generator $(wildcard $(top_srcdir)/inspection/*.rules)
mkdir -p $(top_srcdir)/perl/lib/Sys
mkdir -p $(top_srcdir)/ruby/ext/guestfs
mkdir -p $(top_srcdir)/java/com/redhat/et/libguestfs
diff --git a/generator/main.ml b/generator/main.ml
index 63a5d25..4137f85 100644
--- a/generator/main.ml
+++ b/generator/main.ml
@@ -213,6 +213,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..5232bc3
--- /dev/null
+++ b/inspection/Makefile.am
@@ -0,0 +1,92 @@
+# libguestfs
+# Copyright (C) 2015-2016 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 = \
+ ../daemon/cleanups.c \
+ ../daemon/cleanups.h \
+ ../daemon/command.c \
+ ../daemon/command.h \
+ detect.c \
+ facts.c \
+ inspection.c \
+ inspection.h \
+ match.c \
+ mount.c \
+ rules.c \
+ rules.h \
+ stringsbuf.c \
+ stringsbuf.h \
+ utils.c
+
+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)/daemon \
+ -I$(top_builddir)/daemon \
+ -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/detect.c b/inspection/detect.c
new file mode 100644
index 0000000..1ebabf0
--- /dev/null
+++ b/inspection/detect.c
@@ -0,0 +1,152 @@
+/* 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 <unistd.h>
+#include <error.h>
+#include <errno.h>
+
+#include "ignore-value.h"
+
+#include <pcre.h>
+
+#include "guestfs-internal-all.h"
+#include "cleanups.h"
+#include "inspection.h"
+
+int
+get_distro_from_os_release (const char *fs, char **distro)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+int
+get_version_from_os_release (const char *fs, char **major, char **minor)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+int
+get_product_name_from_os_release (const char *fs, char **product_name)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+int
+get_distro_from_lsb_release (const char *fs, char **distro)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+int
+get_version_from_lsb_release (const char *fs, char **major, char **minor)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+int
+get_product_name_from_lsb_release (const char *fs, char **product_name)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+int
+get_version_from_oracle_release (const char *fs, char **major, char **minor)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+COMPILE_REGEXP (re_centos_old, "CentOS.*release (\\d+).*Update (\\d+)", 0)
+COMPILE_REGEXP (re_centos, "CentOS.*release (\\d+)\\.(\\d+)", 0)
+COMPILE_REGEXP (re_centos_no_minor, "CentOS.*release (\\d+)", 0)
+
+#define CENTOS_RELEASE_FILE "/etc/centos-release"
+
+int
+get_version_from_centos_release (const char *fs, char **major, char **minor)
+{
+ CLEANUP_FREE char *line = first_line_of_file (fs, CENTOS_RELEASE_FILE);
+
+ if (match2 (line, re_centos_old, major, minor) ||
+ match2 (line, re_centos, major, minor))
+ return 0;
+
+ if ((*major = match1 (line, re_centos_no_minor)) != NULL) {
+ *minor = strdup ("0");
+ if (*minor == NULL)
+ error (EXIT_FAILURE, errno, "strdup");
+ return 0;
+ }
+
+ fprintf (stderr, "%s: cannot parse major/minor version from file",
+ CENTOS_RELEASE_FILE);
+ return -1;
+}
+
+int
+get_version_from_altlinux_release (const char *fs, char **major, char **minor)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+int
+match_redhat_release_fedora (const char *fs)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+int
+match_redhat_release_rhel (const char *fs)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+int
+match_redhat_release_centos (const char *fs)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+int
+match_redhat_release_scientific_linux (const char *fs)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
+
+int
+get_version_from_redhat_release (const char *fs, char **major, char **minor)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return -1;
+}
diff --git a/inspection/facts.c b/inspection/facts.c
new file mode 100644
index 0000000..d4ba7d7
--- /dev/null
+++ b/inspection/facts.c
@@ -0,0 +1,310 @@
+/* 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 "rules.h"
+
+/* True and false facts sets. See guestfs-inspection(8)/WRITING RULES
+ * to understand why these are used. The fact structs themselves are
+ * identical, we only know that a fact is false or true based on which
+ * list it appears on.
+ */
+static gl_oset_t true_facts = NULL;
+static gl_oset_t false_facts = NULL;
+
+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 (void) __attribute__((constructor));
+
+static void
+init_facts (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 (void) __attribute__((destructor));
+
+static void
+free_facts (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, const 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, size_t n, ...)
+{
+ fact *f;
+ size_t i;
+ va_list args;
+
+ f = malloc (sizeof (*f) + n * sizeof (char *));
+ if (f == NULL)
+ error (EXIT_FAILURE, errno, "malloc");
+ va_start (args, term_name);
+ for (i = 0; i < n; ++i) {
+ const char *p = va_arg (args, const char *);
+ f->term_arg[i] = (char *) p;
+ }
+ va_end (args);
+ f->term_name = (char *) term_name;
+ f->nr_term_args = n;
+ 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..2848308
--- /dev/null
+++ b/inspection/guestfs-inspection.pod
@@ -0,0 +1,450 @@
+=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, ...)*={{ ... }}
+
+ (var1, var2, ...)?={{ ... }}
+
+ (var1, var2, ...)+={{ ... }}
+
+where C<var1>, C<var2>, etc. are outputs. Unlike the previous rules,
+these rules may generate zero or multiple facts from a single string
+substitution.
+
+For example, here is how we could populate a list of C<Filesystem>
+facts:
+
+ Filesystem(fs) :-
+ (fs)*={{
+ int i;
+ extern char **fs;
+ for (i = 0; fs[i] != NULL; ++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.
+
+As with regular expressions, C<*> means we expect zero or more rows of
+results, C<?> means zero or one, and C<+> means one or more. The
+generated target code checks that you call the C<set_*> function the
+correct number of times.
+
+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 Prologue and epilogue
+
+If you want to insert arbitrary chunks of C code into the output, for
+example to C<#include> headers, then you can insert literal code
+between C<{{ ... }}> at the top and bottom of the file (ie. before
+all rules and after all rules). You cannot insert these chunks
+between rules.
+
+=head2 C code memoization
+
+Although currently B<only partially implemented>, in future we will
+implement memoization of C code. This means that for every input, the
+C code will be called only once, making it less important that C code
+is efficient: no matter how many times we need to evaluate if
+F</etc/fstab> exists on F</dev/sda1>, the C code would only be called
+once.
+
+This means that impure C functions returning different results for the
+same input won't work in future, so don't do that.
+
+=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>.
+
+Remember that C memoization [to be implemented in a future version]
+can cause C code to run fewer times than expected.
+
+=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-2016 Red Hat Inc.
diff --git a/inspection/inspection.c b/inspection/inspection.c
new file mode 100644
index 0000000..d84b9fe
--- /dev/null
+++ b/inspection/inspection.c
@@ -0,0 +1,106 @@
+/* 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 "rules.h"
+
+int verbose = 0;
+const char *sysroot = NULL;
+const size_t sysroot_len = 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..f3359b1
--- /dev/null
+++ b/inspection/inspection.h
@@ -0,0 +1,60 @@
+/* guestfs-inspection
+ * Copyright (C) 2009-2016 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 <pcre.h>
+
+/* detect.c - used by rules */
+extern int get_distro_from_os_release (const char *fs, char **distro);
+extern int get_version_from_os_release (const char *fs, char **major, char **minor);
+extern int get_product_name_from_os_release (const char *fs, char **product_name);
+extern int get_distro_from_lsb_release (const char *fs, char **distro);
+extern int get_version_from_lsb_release (const char *fs, char **major, char **minor);
+extern int get_product_name_from_lsb_release (const char *fs, char **product_name);
+extern int get_version_from_oracle_release (const char *fs, char **major, char **minor);
+extern int get_version_from_centos_release (const char *fs, char **major, char **minor);
+extern int get_version_from_altlinux_release (const char *fs, char **major, char
**minor);
+extern int match_redhat_release_fedora (const char *fs);
+extern int match_redhat_release_rhel (const char *fs);
+extern int match_redhat_release_centos (const char *fs);
+extern int match_redhat_release_scientific_linux (const char *fs);
+extern int get_version_from_redhat_release (const char *fs, char **major, char **minor);
+
+/* match.c */
+extern char *match1 (const char *str, const pcre *re);
+extern int match2 (const char *str, const pcre *re, char **ret1, char **ret2);
+
+/* mount.c - used by rules */
+extern char **get_all_block_devices (void);
+extern char **get_all_partitions (void);
+extern char **get_all_mddevs (void);
+extern char **get_all_lvs (void);
+extern char **get_all_ldmvols (void);
+extern char **get_all_ldmparts (void);
+extern char **get_all_btrfs_subvolumes (const char *fs);
+extern char *get_vfs_type (const char *fs);
+extern int get_partition_mbr_id (const char *fs);
+extern int is_mountable (const char *fs);
+extern int get_mount (const char *fs, const char *filename, char **relative_filename);
+
+/* utils.c */
+extern char *first_line_of_file (const char *fs, const char *filename);
+
+#endif /* GUESTFS_INSPECTION_H */
diff --git a/inspection/inspection.rules b/inspection/inspection.rules
new file mode 100644
index 0000000..0bdad19
--- /dev/null
+++ b/inspection/inspection.rules
@@ -0,0 +1,476 @@
+/* Libguestfs inspection rules -*- prolog -*-
+ * Copyright (C) 2009-2016 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.
+ */
+
+/* To understand what's going on here, it's recommended that you read
+ * guestfs-inspection(8) (inspection/guestfs-inspection.pod) first.
+ */
+
+{{
+#include "inspection.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+}}
+
+/* Whole block devices. */
+BlockDevice(dev) :-
+ (dev)*={{
+ CLEANUP_FREE_STRING_LIST char **devs = get_all_block_devices ();
+ if (devs == NULL) return -1;
+ for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]);
+ return 0;
+ }}.
+
+/* Partitions. */
+Partition(dev) :-
+ (dev)*={{
+ CLEANUP_FREE_STRING_LIST char **devs = get_all_partitions ();
+ if (devs == NULL) return -1;
+ /* Ignore partitions with type byte 0x42 (RHBZ#887520). */
+ for (size_t i = 0; devs[i] != NULL; ++i) {
+ int r = get_partition_mbr_id (devs[i]);
+ if (r == -1) return -1;
+ if (r != 0x42) set_dev (devs[i]);
+ }
+ return 0;
+ }}.
+
+/* LVM2 logical volumes. */
+LV(dev) :-
+ (dev)*={{
+ CLEANUP_FREE_STRING_LIST char **devs = get_all_lvs ();
+ if (devs == NULL) return -1;
+ for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]);
+ return 0;
+ }}.
+
+/* /dev/md* devices. */
+MDDev(dev) :-
+ (dev)*={{
+ CLEANUP_FREE_STRING_LIST char **devs = get_all_mddevs ();
+ if (devs == NULL) return -1;
+ for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]);
+ return 0;
+ }}.
+
+/* Windows LDM voumes. */
+LDMVol(dev) :-
+ (dev)*={{
+ CLEANUP_FREE_STRING_LIST char **devs = get_all_ldmvols ();
+ if (devs == NULL) return -1;
+ for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]);
+ return 0;
+ }}.
+
+/* Windows LDM partitions. */
+LDMPart(dev) :-
+ (dev)*={{
+ CLEANUP_FREE_STRING_LIST char **devs = get_all_ldmparts ();
+ if (devs == NULL) return -1;
+ for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]);
+ return 0;
+ }}.
+
+/* Device(dev) is just a group name for block devices, partitions etc. */
+Device(dev) :-
+ BlockDevice(dev); Partition(dev); LV(dev);
+ MDDev(dev); LDMVol(dev); LDMPart(dev).
+
+/* Map a filesystem to its VFS type (from blkid). */
+VFSType(fs, vfs_type) :-
+ Device(fs),
+ (vfs_type)?={{
+ CLEANUP_FREE char *vfs_type = get_vfs_type (fs);
+ if (vfs_type != NULL) set_vfs_type (vfs_type);
+ return 0;
+ }}.
+
+/* A device contains a mountable filesystem (not swap, empty, etc). */
+Mountable(fs) :-
+ Device(fs),
+ {{ return is_mountable (fs); }}.
+
+/* Where a filesystem is btrfs and mountable, get the subvolumes. */
+BtrfsSubvolume(subvol) :-
+ Device(fs),
+ Mountable(fs),
+ VFSType(fs, "btrfs"),
+ (subvol)*={{
+ size_t i;
+ CLEANUP_FREE_STRING_LIST char **paths = get_all_btrfs_subvolumes (fs);
+ if (paths == NULL) return -1;
+ for (i = 0; paths[i] != NULL; ++i) {
+ CLEANUP_FREE char *subvol;
+ if (asprintf (&subvol, "btrfsvol:%s/%s", fs, paths[i]) == -1) {
+ perror ("asprintf");
+ exit (EXIT_FAILURE);
+ }
+ set_subvol (subvol);
+ }
+ return 0;
+ }}.
+VFSType(subvol, "btrfs") :- BtrfsSubvolume(subvol).
+
+/* Ignore all *_member types. In libblkid these are returned
+ * for things which are members of some RAID or LVM set, most
+ * importantly "LVM2_member" which is a PV. Also ignore
+ * crypto_LUKS (LUKS encrypted partition).
+ */
+ContainerDevice(dev) :-
+ VFSType(dev, vfs_type),
+ {{ return STRSUFFIX (vfs_type, "_member"); }}.
+ContainerDevice(dev) :-
+ VFSType(dev, "crypto_LUKS").
+
+/* Ignore all swap devices. */
+SwapDevice(dev) :- VFSType(dev, "swap").
+
+/* This rule generates one Filesystem(fs) fact per mountable
+ * filesystem found in the appliance. A filesystem could be
+ * a device, partition, LV, btrfs subvolume, etc.
+ */
+Filesystem(fs) :-
+ !ContainerDevice(fs),
+ !SwapDevice(fs),
+ (Device(fs), Mountable(fs); BtrfsSubvolume(fs)).
+
+/* File(fs, filename) is true if filename is a regular file in fs.
+ * It also follows symlinks.
+ */
+File(fs, filename) :-
+ Filesystem(fs),
+ {{
+ int r;
+ CLEANUP_FREE char *relative_filename = NULL;
+ struct stat statbuf;
+
+ if (filename[0] != '/') return 0;
+ if (get_mount (fs, filename, &relative_filename) == -1)
+ return -1;
+ r = stat (relative_filename, &statbuf);
+ if (r == -1) {
+ if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP)
+ return 0;
+ perror (relative_filename);
+ return -1;
+ }
+ return S_ISREG (statbuf.st_mode);
+ }}.
+
+/* Directory(fs, dirname) is true if dirname is a directory in fs.
+ * It also follows symlinks.
+ */
+Directory(fs, dirname) :-
+ Filesystem(fs),
+ {{
+ int r;
+ CLEANUP_FREE char *relative_dirname = NULL;
+ struct stat statbuf;
+
+ if (dirname[0] != '/') return 0;
+ if (get_mount (fs, dirname, &relative_dirname) == -1)
+ return -1;
+ r = stat (relative_dirname, &statbuf);
+ if (r == -1) {
+ if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP)
+ return 0;
+ perror (relative_dirname);
+ return -1;
+ }
+ return S_ISDIR (statbuf.st_mode);
+ }}.
+
+/* Symlink(fs, filename) is true if filename is a symlink. */
+Symlink(fs, filename) :-
+ Filesystem(fs),
+ {{
+ int r;
+ CLEANUP_FREE char *relative_filename = NULL;
+ struct stat statbuf;
+
+ if (filename[0] != '/') return 0;
+ if (get_mount (fs, filename, &relative_filename) == -1)
+ return -1;
+ r = stat (relative_filename, &statbuf);
+ if (r == -1) {
+ if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP)
+ return 0;
+ perror (relative_filename);
+ return -1;
+ }
+ return S_ISLNK (statbuf.st_mode);
+ }}.
+
+/* grub or grub2 /boot */
+GrubBoot(fs) :-
+ Filesystem(fs),
+ File(fs, "/grub/menu.lst"),
+ File(fs, "/grub/grub.conf"),
+ File(fs, "/grub2/grub.cfg").
+
+/* FreeBSD root. */
+FreeBSDRoot(fs) :-
+ Filesystem(fs),
+ Directory(fs, "/bin"),
+ Directory(fs, "/etc"),
+ File(fs, "/etc/freebsd-update.conf"),
+ File(fs, "/etc/fstab").
+
+/* NetBSD root. */
+NetBSDRoot(fs) :-
+ Filesystem(fs),
+ Directory(fs, "/bin"),
+ Directory(fs, "/etc"),
+ File(fs, "/netbsd"),
+ File(fs, "/etc/fstab"),
+ File(fs, "/etc/release").
+
+/* OpenBSD root. */
+OpenBSDRoot(fs) :-
+ Filesystem(fs),
+ Directory(fs, "/bin"),
+ Directory(fs, "/etc"),
+ File(fs, "/bsd"),
+ File(fs, "/etc/fstab"),
+ File(fs, "/etc/motd").
+
+/* Hurd root. */
+HurdRoot(fs) :-
+ Filesystem(fs),
+ File(fs, "/hurd/console"),
+ File(fs, "/hurd/hello"),
+ File(fs, "/hurd/null").
+
+/* Minix root. */
+MinixRoot(fs) :-
+ Filesystem(fs),
+ File(fs, "/service/vm"),
+ File(fs, "/etc/fstab"),
+ File(fs, "/etc/version").
+
+/* Linux root (any distro). */
+LinuxRoot(fs) :-
+ Filesystem(fs),
+ Directory(fs, "/etc"),
+ File(fs, "/etc/fstab"),
+ (Directory(fs, "/bin"); Symlink(fs, "/bin")),
+ !FreeBSDRoot(fs),
+ !NetBSDRoot(fs),
+ !OpenBSDRoot(fs),
+ !HurdRoot(fs),
+ !MinixRoot(fs).
+
+/* Is it Linux using /etc/os-release? (Linux systemd). */
+LinuxRootWithOSRelease(fs) :-
+ LinuxRoot(fs),
+ File(fs, "/etc/os-release").
+
+Distro(fs, distro) :-
+ LinuxRootWithOSRelease(fs),
+ (distro)?={{
+ int r;
+ CLEANUP_FREE char *distro = NULL;
+ if ((r = get_distro_from_os_release (fs, &distro)) <= 0)
+ return r;
+ set_distro (distro);
+ return 0;
+ }}.
+
+Version(fs, major, minor) :-
+ LinuxRootWithOSRelease(fs),
+ (major, minor)?={{
+ int r;
+ CLEANUP_FREE char *major = NULL, *minor = NULL;
+ if ((r = get_version_from_os_release (fs, &major, &minor)) <= 0)
+ return r;
+ set_major_minor (major, minor);
+ return 0;
+ }}.
+
+ProductName(fs, product_name) :-
+ LinuxRootWithOSRelease(fs),
+ (product_name)?={{
+ int r;
+ CLEANUP_FREE char *product_name = NULL;
+ if ((r = get_product_name_from_os_release (fs, &product_name)) <= 0)
+ return r;
+ set_product_name (product_name);
+ return 0;
+ }}.
+
+/* Is it Linux using /etc/lsb-release? (Linux Standards Base). */
+LinuxRootWithLSBRelease(fs) :-
+ LinuxRoot(fs),
+ File(fs, "/etc/lsb-release"),
+ !LinuxRootWithOSRelease(fs). /* prefer /etc/os-release */
+
+Distro(fs, distro) :-
+ LinuxRootWithLSBRelease(fs),
+ (distro)?={{
+ int r;
+ CLEANUP_FREE char *distro = NULL;
+ if ((r = get_distro_from_lsb_release (fs, &distro)) <= 0)
+ return r;
+ set_distro (distro);
+ return 0;
+ }}.
+
+Version(fs, major, minor) :-
+ LinuxRootWithLSBRelease(fs),
+ (major, minor)?={{
+ int r;
+ CLEANUP_FREE char *major = NULL, *minor = NULL;
+ if ((r = get_version_from_lsb_release (fs, &major, &minor)) <= 0)
+ return r;
+ set_major_minor (major, minor);
+ return 0;
+ }}.
+
+ProductName(fs, product_name) :-
+ LinuxRootWithLSBRelease(fs),
+ (product_name)?={{
+ int r;
+ CLEANUP_FREE char *product_name = NULL;
+ if ((r = get_product_name_from_lsb_release (fs, &product_name)) <= 0)
+ return r;
+ set_product_name (product_name);
+ return 0;
+ }}.
+
+/* Fall back on /etc/*-release files to determine the distro, version and
+ * product name.
+ */
+LinuxRootWithReleaseFile(fs, "/etc/oracle-release") :-
+ LinuxRoot(fs),
+ !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs),
+ File(fs, "/etc/oracle-release").
+Distro(fs, "oracle-linux") :-
+ LinuxRootWithReleaseFile(fs, "/etc/oracle-release").
+Version(fs, major, minor) :-
+ LinuxRootWithReleaseFile(fs, "/etc/oracle-release"),
+ (major, minor)={{
+ CLEANUP_FREE char *major = NULL, *minor = NULL;
+ if (get_version_from_oracle_release (fs, &major, &minor) == -1)
+ return -1;
+ set_major_minor (major, minor);
+ return 0;
+ }}.
+
+LinuxRootWithReleaseFile(fs, "/etc/centos-release") :-
+ LinuxRoot(fs),
+ !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs),
+ File(fs, "/etc/centos-release").
+Distro(fs, "centos") :-
+ LinuxRootWithReleaseFile(fs, "/etc/centos-release").
+Version(fs, major, minor) :-
+ LinuxRootWithReleaseFile(fs, "/etc/centos-release"),
+ (major, minor)={{
+ CLEANUP_FREE char *major = NULL, *minor = NULL;
+ if (get_version_from_centos_release (fs, &major, &minor) == -1)
+ return -1;
+ set_major_minor (major, minor);
+ return 0;
+ }}.
+
+LinuxRootWithReleaseFile(fs, "/etc/altlinux-release") :-
+ LinuxRoot(fs),
+ !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs),
+ File(fs, "/etc/altlinux-release").
+Distro(fs, "altlinux") :-
+ LinuxRootWithReleaseFile(fs, "/etc/altlinux-release").
+Version(fs, major, minor) :-
+ LinuxRootWithReleaseFile(fs, "/etc/altlinux-release"),
+ (major, minor)={{
+ CLEANUP_FREE char *major = NULL, *minor = NULL;
+ if (get_version_from_altlinux_release (fs, &major, &minor) == -1)
+ return -1;
+ set_major_minor (major, minor);
+ return 0;
+ }}.
+
+/* Handle the fallback case of /etc/redhat-release. */
+LinuxRootWithReleaseFile(fs, "/etc/redhat-release") :-
+ LinuxRoot(fs),
+ !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs),
+ !LinuxRootWithReleaseFile(fs, "/etc/oracle-release"),
+ !LinuxRootWithReleaseFile(fs, "/etc/centos-release"),
+ !LinuxRootWithReleaseFile(fs, "/etc/altlinux-release"),
+ File(fs, "/etc/redhat-release").
+Distro(fs, "fedora") :-
+ LinuxRootWithReleaseFile(fs, "/etc/redhat-release"),
+ {{ return match_redhat_release_fedora (fs); }}.
+Distro(fs, "rhel") :-
+ LinuxRootWithReleaseFile(fs, "/etc/redhat-release"),
+ {{ return match_redhat_release_rhel (fs); }}.
+Distro(fs, "centos") :-
+ LinuxRootWithReleaseFile(fs, "/etc/redhat-release"),
+ {{ return match_redhat_release_centos (fs); }}.
+Distro(fs, "scientificlinux") :-
+ LinuxRootWithReleaseFile(fs, "/etc/redhat-release"),
+ {{ return match_redhat_release_scientific_linux (fs); }}.
+Distro(fs, "redhat-based") :-
+ LinuxRootWithReleaseFile(fs, "/etc/redhat-release"),
+ !Distro(fs, "fedora"),
+ !Distro(fs, "rhel"),
+ !Distro(fs, "centos"),
+ !Distro(fs, "scientificlinux").
+
+Version(fs, major, minor) :-
+ LinuxRootWithReleaseFile(fs, "/etc/redhat-release"),
+ (major, minor)={{
+ CLEANUP_FREE char *major = NULL, *minor = NULL;
+ if (get_version_from_redhat_release (fs, &major, &minor) == -1)
+ return -1;
+ set_major_minor (major, minor);
+ return 0;
+ }}.
+
+/* Get the product name from a generic Linux release file. */
+ProductName(fs, product_name) :-
+ LinuxRootWithReleaseFile(fs, release_file),
+ (product_name)={{
+ CLEANUP_FREE char *product_name = first_line_of_file (fs, release_file);
+ set_product_name (product_name);
+ return 0;
+ }}.
+
+/* XXX debian, arch-linux etc release files */
+
+/* XXX Linux architecture, fstab, hostname */
+
+
+
+/* XXX CoreOS etc. */
+
+
+
+
+
+
+
+
+/*
+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/match.c b/inspection/match.c
new file mode 100644
index 0000000..fe12b12
--- /dev/null
+++ b/inspection/match.c
@@ -0,0 +1,173 @@
+/* libguestfs
+ * Copyright (C) 2010-2016 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 <error.h>
+#include <errno.h>
+
+#include <pcre.h>
+
+#include "inspection.h"
+
+static char *
+safe_strndup (const char *str, size_t len)
+{
+ char *ret = strndup (str, len);
+ if (ret == NULL)
+ error (EXIT_FAILURE, errno, "strndup");
+ return ret;
+}
+
+#if 0
+/* Match a regular expression which contains no captures. Returns
+ * true if it matches or false if it doesn't.
+ */
+int
+match (const char *str, const pcre *re)
+{
+ size_t len = strlen (str);
+ int vec[30], r;
+
+ r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof vec / sizeof vec[0]);
+ if (r == PCRE_ERROR_NOMATCH)
+ return 0;
+
+ return 1;
+}
+#endif
+
+/* Match a regular expression which contains exactly one capture. If
+ * the string matches, return the capture, otherwise return NULL. The
+ * caller must free the result.
+ */
+char *
+match1 (const char *str, const pcre *re)
+{
+ size_t len = strlen (str);
+ int vec[30], r;
+
+ r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof vec / sizeof vec[0]);
+ if (r == PCRE_ERROR_NOMATCH)
+ return NULL;
+
+ return r == 2 ? safe_strndup (&str[vec[2]], vec[3]-vec[2]) : NULL;
+}
+
+/* Match a regular expression which contains exactly two captures. */
+int
+match2 (const char *str, const pcre *re, char **ret1, char **ret2)
+{
+ size_t len = strlen (str);
+ int vec[30], r;
+
+ r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30);
+ if (r == PCRE_ERROR_NOMATCH)
+ return 0;
+
+ *ret1 = NULL;
+ *ret2 = NULL;
+
+ if (r > 1) *ret1 = safe_strndup (&str[vec[2]], vec[3]-vec[2]);
+ if (r > 2) *ret2 = safe_strndup (&str[vec[4]], vec[5]-vec[4]);
+
+ return 1;
+}
+
+#if 0
+/* Match a regular expression which contains exactly three captures. */
+int
+match3 (const char *str, const pcre *re,
+ char **ret1, char **ret2, char **ret3)
+{
+ size_t len = strlen (str);
+ int vec[30], r;
+
+ r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30);
+ if (r == PCRE_ERROR_NOMATCH)
+ return 0;
+
+ *ret1 = NULL;
+ *ret2 = NULL;
+ *ret3 = NULL;
+
+ if (r > 1) *ret1 = safe_strndup (&str[vec[2]], vec[3]-vec[2]);
+ if (r > 2) *ret2 = safe_strndup (&str[vec[4]], vec[5]-vec[4]);
+ if (r > 3) *ret3 = safe_strndup (&str[vec[6]], vec[7]-vec[6]);
+
+ return 1;
+}
+
+/* Match a regular expression which contains exactly four captures. */
+int
+match4 (const char *str, const pcre *re,
+ char **ret1, char **ret2, char **ret3, char **ret4)
+{
+ size_t len = strlen (str);
+ int vec[30], r;
+
+ r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30);
+ if (r == PCRE_ERROR_NOMATCH)
+ return 0;
+
+ *ret1 = NULL;
+ *ret2 = NULL;
+ *ret3 = NULL;
+ *ret4 = NULL;
+
+ if (r > 1) *ret1 = safe_strndup (&str[vec[2]], vec[3]-vec[2]);
+ if (r > 2) *ret2 = safe_strndup (&str[vec[4]], vec[5]-vec[4]);
+ if (r > 3) *ret3 = safe_strndup (&str[vec[6]], vec[7]-vec[6]);
+ if (r > 4) *ret4 = safe_strndup (&str[vec[8]], vec[9]-vec[8]);
+
+ return 1;
+}
+
+/* Match a regular expression which contains exactly six captures. */
+int
+match6 (const char *str, const pcre *re,
+ char **ret1, char **ret2, char **ret3, char **ret4,
+ char **ret5, char **ret6)
+{
+ size_t len = strlen (str);
+ int vec[30], r;
+
+ r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30);
+ if (r == PCRE_ERROR_NOMATCH)
+ return 0;
+
+ *ret1 = NULL;
+ *ret2 = NULL;
+ *ret3 = NULL;
+ *ret4 = NULL;
+ *ret5 = NULL;
+ *ret6 = NULL;
+
+ if (r > 1) *ret1 = safe_strndup (&str[vec[2]], vec[3]-vec[2]);
+ if (r > 2) *ret2 = safe_strndup (&str[vec[4]], vec[5]-vec[4]);
+ if (r > 3) *ret3 = safe_strndup (&str[vec[6]], vec[7]-vec[6]);
+ if (r > 4) *ret4 = safe_strndup (&str[vec[8]], vec[9]-vec[8]);
+ if (r > 5) *ret5 = safe_strndup (&str[vec[10]], vec[11]-vec[10]);
+ if (r > 6) *ret6 = safe_strndup (&str[vec[12]], vec[13]-vec[12]);
+
+ return 1;
+}
+#endif
diff --git a/inspection/mount.c b/inspection/mount.c
new file mode 100644
index 0000000..e10dbdd
--- /dev/null
+++ b/inspection/mount.c
@@ -0,0 +1,597 @@
+/* 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 <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <errno.h>
+#include <error.h>
+
+#include "c-ctype.h"
+
+#include "guestfs-internal-all.h"
+
+#include "inspection.h"
+#include "cleanups.h"
+#include "command.h"
+#include "stringsbuf.h"
+
+/* If root device is an ext2 filesystem, this is the major and minor.
+ * This is so we can ignore this device from the point of view of the
+ * user, eg. in guestfs_list_devices and many other places.
+ */
+static dev_t root_device = 0;
+
+/* A temporary directory where we place all the mountpoints. */
+static char mountpoints[] = "/tmp/mp.XXXXXX";
+
+static void init_mount (void) __attribute__((constructor));
+
+static void
+init_mount (void)
+{
+ struct stat statbuf;
+
+ if (stat ("/", &statbuf) == 0)
+ root_device = statbuf.st_dev;
+
+ if (mkdtemp (mountpoints) == NULL)
+ perror ("mkdtemp");
+}
+
+static void free_mount (void) __attribute__((destructor));
+
+static void
+free_mount (void)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ /* Unmount all mountpoints in the temporary directory, then
+ * delete those directories and the parent.
+ */
+ dir = opendir (mountpoints);
+ if (!dir) {
+ perror (mountpoints);
+ return;
+ }
+
+ for (;;) {
+ errno = 0;
+ d = readdir (dir);
+ if (!d) break;
+
+ if (d->d_name[0] != '.') {
+ CLEANUP_FREE char *mp;
+
+ if (asprintf (&mp, "%s/%s", mountpoints, d->d_name) == -1) {
+ perror ("asprintf");
+ continue;
+ }
+
+ if (umount2 (mp, MNT_DETACH) == -1) /* lazy umount */
+ perror (mp);
+
+ if (rmdir (mp) == -1)
+ perror (mp);
+ }
+ }
+
+ /* Check readdir didn't fail */
+ if (errno != 0) {
+ perror ("readdir");
+ return;
+ }
+
+ /* Close the directory handle */
+ if (closedir (dir) == -1) {
+ perror ("closedir");
+ return;
+ }
+
+ if (rmdir (mountpoints) == -1)
+ perror (mountpoints);
+}
+
+/* Return true iff device is the root device (and therefore should be
+ * ignored from the point of view of user calls).
+ */
+static int
+is_root_device_stat (struct stat *statbuf)
+{
+ if (statbuf->st_rdev == root_device) return 1;
+ return 0;
+}
+
+static int
+is_root_device (const char *device)
+{
+ struct stat statbuf;
+
+ if (stat (device, &statbuf) == -1) {
+ perror (device);
+ return 0;
+ }
+
+ return is_root_device_stat (&statbuf);
+}
+
+typedef int (*block_dev_func_t) (const char *dev, struct stringsbuf *r);
+
+/* Execute a given function for each discovered block device */
+static char **
+foreach_block_device (block_dev_func_t func)
+{
+ DECLARE_STRINGSBUF (r);
+ DIR *dir;
+ struct dirent *d;
+ char dev_path[256];
+ int fd;
+ bool err = false;
+
+ dir = opendir ("/sys/block");
+ if (!dir) {
+ perror ("opendir: /sys/block");
+ return NULL;
+ }
+
+ for (;;) {
+ errno = 0;
+ d = readdir (dir);
+ if (!d) break;
+
+ if (STREQLEN (d->d_name, "sd", 2) ||
+ STREQLEN (d->d_name, "hd", 2) ||
+ STREQLEN (d->d_name, "ubd", 3) ||
+ STREQLEN (d->d_name, "vd", 2) ||
+ STREQLEN (d->d_name, "sr", 2)) {
+ snprintf (dev_path, sizeof dev_path, "/dev/%s", d->d_name);
+
+ /* Ignore the root device. */
+ if (is_root_device (dev_path))
+ continue;
+
+ /* RHBZ#514505: Some versions of qemu <= 0.10 add a
+ * CD-ROM device even though we didn't request it. Try to
+ * detect this by seeing if the device contains media.
+ */
+ fd = open (dev_path, O_RDONLY|O_CLOEXEC);
+ if (fd == -1) {
+ perror (dev_path);
+ continue;
+ }
+ close (fd);
+
+ /* Call the map function for this device */
+ if ((*func)(d->d_name, &r) != 0) {
+ err = true;
+ break;
+ }
+ }
+ }
+
+ /* Check readdir didn't fail */
+ if (errno != 0) {
+ perror ("readdir: /sys/block");
+ free_stringslen (r.argv, r.size);
+ closedir (dir);
+ return NULL;
+ }
+
+ /* Close the directory handle */
+ if (closedir (dir) == -1) {
+ perror ("closedir: /sys/block");
+ free_stringslen (r.argv, r.size);
+ return NULL;
+ }
+
+ /* Free the result list on error */
+ if (err) {
+ free_stringslen (r.argv, r.size);
+ return NULL;
+ }
+
+ /* Sort the devices. */
+ if (r.size > 0)
+ sort_device_names (r.argv, r.size);
+
+ /* NULL terminate the list */
+ end_stringsbuf (&r);
+
+ return r.argv;
+}
+
+/* Add a device to the list of devices */
+static int
+add_device (const char *device, struct stringsbuf *r)
+{
+ char dev_path[256];
+ snprintf (dev_path, sizeof dev_path, "/dev/%s", device);
+
+ add_string (r, dev_path);
+
+ return 0;
+}
+
+char **
+get_all_block_devices (void)
+{
+ return foreach_block_device (add_device);
+}
+
+static int
+add_partitions (const char *device, struct stringsbuf *r)
+{
+ char devdir[256];
+
+ /* Open the device's directory under /sys/block */
+ snprintf (devdir, sizeof devdir, "/sys/block/%s", device);
+
+ DIR *dir = opendir (devdir);
+ if (!dir) {
+ perror (devdir);
+ free_stringslen (r->argv, r->size);
+ return -1;
+ }
+
+ /* Look in /sys/block/<device>/ for entries starting with <device>
+ * e.g. /sys/block/sda/sda1
+ */
+ errno = 0;
+ struct dirent *d;
+ while ((d = readdir (dir)) != NULL) {
+ if (STREQLEN (d->d_name, device, strlen (device))) {
+ char part[256];
+ snprintf (part, sizeof part, "/dev/%s", d->d_name);
+
+ add_string (r, part);
+ }
+ }
+
+ /* Check if readdir failed */
+ if (0 != errno) {
+ perror (devdir);
+ free_stringslen (r->argv, r->size);
+ closedir (dir);
+ return -1;
+ }
+
+ /* Close the directory handle */
+ if (closedir (dir) == -1) {
+ perror (device);
+ free_stringslen (r->argv, r->size);
+ return -1;
+ }
+
+ return 0;
+}
+
+char **
+get_all_partitions (void)
+{
+ return foreach_block_device (add_partitions);
+}
+
+char **
+get_all_mddevs (void)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return NULL;
+}
+
+char **
+get_all_lvs (void)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return NULL;
+}
+
+char **
+get_all_ldmvols (void)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return NULL;
+}
+
+char **
+get_all_ldmparts (void)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return NULL;
+}
+
+char **
+get_all_btrfs_subvolumes (const char *fs)
+{
+ error (EXIT_FAILURE, 0, "%s: not implemented", __func__);
+ return NULL;
+}
+
+static char *
+get_blkid_tag (const char *device, const char *tag)
+{
+ char *out;
+ CLEANUP_FREE char *err = NULL;
+ int r;
+ size_t len;
+
+ r = commandr (&out, &err,
+ "blkid",
+ /* Adding -c option kills all caching, even on RHEL 5. */
+ "-c", "/dev/null",
+ "-o", "value", "-s", tag, device, NULL);
+ if (r != 0 && r != 2) {
+ if (r >= 0)
+ fprintf (stderr, "%s: %s (blkid returned %d)\n", device, err, r);
+ else
+ fprintf (stderr, "%s: %s\n", device, err);
+ free (out);
+ return NULL;
+ }
+
+ if (r == 2) { /* means UUID etc not found */
+ free (out);
+ out = strdup ("");
+ if (out == NULL)
+ perror ("strdup");
+ return out;
+ }
+
+ /* Trim trailing \n if present. */
+ len = strlen (out);
+ if (len > 0 && out[len-1] == '\n')
+ out[len-1] = '\0';
+
+ return out; /* caller frees */
+}
+
+char *
+get_vfs_type (const char *fs)
+{
+ return get_blkid_tag (fs, "TYPE");
+}
+
+/* Test if sfdisk is recent enough to have --part-type, to be used instead
+ * of --print-id and --change-id.
+ */
+static int
+test_sfdisk_has_part_type (void)
+{
+ static int tested = -1;
+ int r;
+ CLEANUP_FREE char *out = NULL, *err = NULL;
+
+ if (tested != -1)
+ return tested;
+
+ r = command (&out, &err, "sfdisk", "--help", NULL);
+ if (r == -1) {
+ fprintf (stderr, "%s: %s\n", "sfdisk --help", err);
+ return -1;
+ }
+
+ tested = strstr (out, "--part-type") != NULL;
+ return tested;
+}
+
+static char *
+part_to_dev (const char *part)
+{
+ int err = 1;
+ size_t n = strlen (part);
+ char *r;
+
+ while (n >= 1 && c_isdigit (part[n-1])) {
+ err = 0;
+ n--;
+ }
+
+ if (err) {
+ fprintf (stderr, "device name is not a partition\n");
+ return NULL;
+ }
+
+ r = strndup (part, n);
+ if (r == NULL) {
+ perror ("strdup");
+ return NULL;
+ }
+
+ return r;
+}
+
+static int
+part_to_partnum (const char *part)
+{
+ int err = 1;
+ size_t n = strlen (part);
+ int r;
+
+ while (n >= 1 && c_isdigit (part[n-1])) {
+ err = 0;
+ n--;
+ }
+
+ if (err) {
+ fprintf (stderr, "device name is not a partition\n");
+ return -1;
+ }
+
+ if (sscanf (&part[n], "%d", &r) != 1) {
+ fprintf (stderr, "could not parse number\n");
+ return -1;
+ }
+
+ return r;
+}
+
+int
+get_partition_mbr_id (const char *fs)
+{
+ CLEANUP_FREE char *device;
+ int partnum;
+ char partnum_str[16];
+ const char *param =
+ test_sfdisk_has_part_type () ? "--part-type" : "--print-id";
+ CLEANUP_FREE char *out = NULL, *err = NULL;
+ int r;
+ unsigned id;
+
+ /* Get the block device and partition number from the filesystem
+ * string.
+ */
+ device = part_to_dev (fs);
+ if (device == NULL)
+ return -1;
+ partnum = part_to_partnum (fs);
+ if (partnum == -1)
+ return -1;
+ snprintf (partnum_str, sizeof partnum_str, "%d", partnum);
+
+ r = command (&out, &err, "sfdisk", param, device, partnum_str,
NULL);
+ if (r == -1) {
+ fprintf (stderr, "sfdisk %s: %s\n", param, err);
+ return -1;
+ }
+
+ /* It's printed in hex ... */
+ if (sscanf (out, "%x", &id) != 1) {
+ fprintf (stderr, "sfdisk --print-id: cannot parse output: %s\n", out);
+ return -1;
+ }
+
+ return id;
+}
+
+/* When mounting filesystems, we place them in temporary directories
+ * under 'mountpoints'. We name the temporary directory after the
+ * device name, but since device names contain '/' characters, we have
+ * to mangle the name.
+ */
+static char *
+get_mount_name (const char *fs)
+{
+ char *ret;
+ size_t i;
+
+ if (asprintf (&ret, "%s/%s", mountpoints, fs) == -1) {
+ perror ("asprintf");
+ return NULL;
+ }
+
+ for (i = strlen (mountpoints) + 1; i < strlen (ret); ++i) {
+ if (ret[i] == '/')
+ ret[i] = '_';
+ }
+
+ return ret; /* caller frees */
+}
+
+int
+is_mountable (const char *fs)
+{
+ CLEANUP_FREE char *mp = NULL;
+ struct stat statbuf;
+ int r;
+ CLEANUP_FREE char *err = NULL;
+
+ mp = get_mount_name (fs);
+ if (mp == NULL)
+ return -1;
+
+ if (stat (mp, &statbuf) == 0 && S_ISDIR (statbuf.st_mode))
+ return 1; /* mountable, and mounted already */
+
+ /* Try to create the mountpoint. */
+ if (mkdir (mp, 0700) == -1) {
+ perror (mp);
+ return -1;
+ }
+
+ /* Try to mount the filesystem. */
+ r = command (NULL, &err,
+ "mount", "-o", "ro", fs, mp, NULL);
+ if (r == -1) {
+ fprintf (stderr, "mount: %s: %s\n", fs, err);
+
+ /* Now hack things for the *BSDs. */
+ /* FreeBSD fs is a variant of ufs called ufs2 ... */
+ free (err); err = NULL;
+ r = command (NULL, &err,
+ "mount", "-o", "ro,ufstype=ufs2", fs, mp,
NULL);
+ if (r == -1) {
+ fprintf (stderr, "mount [ufs2]: %s: %s\n", fs, err);
+
+ /* while NetBSD and OpenBSD use another variant labeled 44bsd */
+ free (err); err = NULL;
+ r = command (NULL, &err,
+ "mount", "-o", "ro,ufstype=44bsd", fs,
mp, NULL);
+ if (r == -1) {
+ fprintf (stderr, "mount [44bsd]: %s: %s\n", fs, err);
+
+ /* Mount failed, so remove the mountpoint. */
+ rmdir (mp);
+ return 0;
+ }
+ }
+ }
+
+ /* Mount succeeded. */
+ return 1;
+}
+
+int
+get_mount (const char *fs, const char *filename, char **relative_filename)
+{
+ CLEANUP_FREE char *mp = NULL;
+ int r;
+
+ if (filename[0] != '/') {
+ fprintf (stderr, "get_mount: filename is not an absolute path: %s\n",
+ filename);
+ return -1;
+ }
+
+ r = is_mountable (fs);
+ if (r == -1)
+ return -1;
+ if (r == 0) {
+ fprintf (stderr, "get_mount: called on non-mountable filesystem: %s\n",
+ fs);
+ return -1;
+ }
+
+ mp = get_mount_name (fs);
+
+ /* Construct the filename relative to the mountpoint. */
+ if (asprintf (relative_filename, "%s%s", mp, filename) == -1) {
+ perror ("asprintf");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/inspection/rules.h b/inspection/rules.h
new file mode 100644
index 0000000..cf5b646
--- /dev/null
+++ b/inspection/rules.h
@@ -0,0 +1,69 @@
+/* guestfs-inspection
+ * Copyright (C) 2009-2016 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_RULES_H
+#define GUESTFS_RULES_H
+
+#include <stdbool.h>
+
+#include "gl_oset.h"
+
+extern int verbose;
+
+/* facts.c */
+struct fact {
+ char *term_name;
+ size_t nr_term_args;
+ char *term_arg[];
+};
+typedef struct fact fact;
+
+/* Create a fact on the heap. This doesn't copy the strings, but they
+ * are deep copied when we call add_fact.
+ */
+extern fact *create_fact (const char *term_name, size_t n, ...);
+
+/* Create a fact on the stack and set 'var' to be a const pointer to
+ * it. This doesn't copy the strings, but they are deep copied when
+ * we call add_fact. Because of stupidity in C99, this is way more
+ * complex than it needs to be.
+ */
+#define CREATE_FACT(var,name,n,...) \
+ struct { \
+ const char *term_name; \
+ size_t nr_term_args; \
+ const char *term_arg[n]; \
+ } var##_tmp_fact = { (name), (n), { __VA_ARGS__ } }; \
+ const fact *var = (struct fact *) &var##_tmp_fact
+
+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, const 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 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);
+
+#endif /* GUESTFS_RULES_H */
diff --git a/inspection/stringsbuf.c b/inspection/stringsbuf.c
new file mode 100644
index 0000000..338134e
--- /dev/null
+++ b/inspection/stringsbuf.c
@@ -0,0 +1,254 @@
+/* libguestfs - the guestfsd daemon
+ * 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 <errno.h>
+#include <error.h>
+#include <assert.h>
+
+#include "guestfs-internal-all.h"
+
+#include "stringsbuf.h"
+
+void
+add_string_nodup (struct stringsbuf *sb, char *str)
+{
+ char **new_argv;
+
+ if (sb->size >= sb->alloc) {
+ sb->alloc += 64;
+ new_argv = realloc (sb->argv, sb->alloc * sizeof (char *));
+ if (new_argv == NULL)
+ error (EXIT_FAILURE, errno, "realloc");
+ sb->argv = new_argv;
+ }
+
+ sb->argv[sb->size] = str;
+ sb->size++;
+}
+
+void
+add_string (struct stringsbuf *sb, const char *str)
+{
+ char *new_str = NULL;
+
+ if (str) {
+ new_str = strdup (str);
+ if (new_str == NULL)
+ error (EXIT_FAILURE, errno, "strdup");
+ }
+
+ add_string_nodup (sb, new_str);
+}
+
+void
+add_sprintf (struct stringsbuf *sb, const char *fs, ...)
+{
+ va_list args;
+ char *str;
+ int r;
+
+ va_start (args, fs);
+ r = vasprintf (&str, fs, args);
+ va_end (args);
+ if (r == -1)
+ error (EXIT_FAILURE, errno, "vasprintf");
+
+ add_string_nodup (sb, str);
+}
+
+void
+end_stringsbuf (struct stringsbuf *sb)
+{
+ add_string_nodup (sb, NULL);
+}
+
+void
+free_stringsbuf (struct stringsbuf *sb)
+{
+ if (sb->argv != NULL)
+ free_stringslen (sb->argv, sb->size);
+}
+
+size_t
+count_strings (char *const *argv)
+{
+ size_t argc;
+
+ for (argc = 0; argv[argc] != NULL; ++argc)
+ ;
+ return argc;
+}
+
+static int
+compare (const void *vp1, const void *vp2)
+{
+ char * const *p1 = (char * const *) vp1;
+ char * const *p2 = (char * const *) vp2;
+ return strcmp (*p1, *p2);
+}
+
+void
+sort_strings (char **argv, size_t len)
+{
+ qsort (argv, len, sizeof (char *), compare);
+}
+
+void
+free_strings (char **argv)
+{
+ size_t argc;
+
+ if (!argv)
+ return;
+
+ for (argc = 0; argv[argc] != NULL; ++argc)
+ free (argv[argc]);
+ free (argv);
+}
+
+void
+free_stringslen (char **argv, size_t len)
+{
+ size_t i;
+
+ if (!argv)
+ return;
+
+ for (i = 0; i < len; ++i)
+ free (argv[i]);
+ free (argv);
+}
+
+/* Compare device names (including partition numbers if present).
+ *
https://rwmj.wordpress.com/2011/01/09/how-are-linux-drives-named-beyond-d...
+ */
+int
+compare_device_names (const char *a, const char *b)
+{
+ size_t alen, blen;
+ int r;
+ int a_partnum, b_partnum;
+
+ /* Skip /dev/ prefix if present. */
+ if (STRPREFIX (a, "/dev/"))
+ a += 5;
+ if (STRPREFIX (b, "/dev/"))
+ b += 5;
+
+ /* Skip sd/hd/ubd/vd. */
+ alen = strcspn (a, "d");
+ blen = strcspn (b, "d");
+ assert (alen > 0 && alen <= 2);
+ assert (blen > 0 && blen <= 2);
+ a += alen + 1;
+ b += blen + 1;
+
+ /* Get device name part, that is, just 'a', 'ab' etc. */
+ alen = strcspn (a, "0123456789");
+ blen = strcspn (b, "0123456789");
+
+ /* If device name part is longer, it is always greater, eg.
+ * "/dev/sdz" < "/dev/sdaa".
+ */
+ if (alen != blen)
+ return alen - blen;
+
+ /* Device name parts are the same length, so do a regular compare. */
+ r = strncmp (a, b, alen);
+ if (r != 0)
+ return r;
+
+ /* Compare partitions numbers. */
+ a += alen;
+ b += alen;
+
+ /* If no partition numbers, bail -- the devices are the same. This
+ * can happen in one peculiar case: where you have a mix of devices
+ * with different interfaces (eg. /dev/sda and /dev/vda).
+ * (RHBZ#858128).
+ */
+ if (!*a && !*b)
+ return 0;
+
+ r = sscanf (a, "%d", &a_partnum);
+ assert (r == 1);
+ r = sscanf (b, "%d", &b_partnum);
+ assert (r == 1);
+
+ return a_partnum - b_partnum;
+}
+
+static int
+compare_device_names_vp (const void *vp1, const void *vp2)
+{
+ char * const *p1 = (char * const *) vp1;
+ char * const *p2 = (char * const *) vp2;
+ return compare_device_names (*p1, *p2);
+}
+
+void
+sort_device_names (char **argv, size_t len)
+{
+ qsort (argv, len, sizeof (char *), compare_device_names_vp);
+}
+
+char *
+concat_strings (char *const *argv)
+{
+ return join_strings ("", argv);
+}
+
+char *
+join_strings (const char *separator, char *const *argv)
+{
+ size_t i, len, seplen, rlen;
+ char *r;
+
+ seplen = strlen (separator);
+
+ len = 0;
+ for (i = 0; argv[i] != NULL; ++i) {
+ if (i > 0)
+ len += seplen;
+ len += strlen (argv[i]);
+ }
+ len++; /* for final \0 */
+
+ r = malloc (len);
+ if (r == NULL)
+ error (EXIT_FAILURE, errno, "malloc");
+
+ rlen = 0;
+ for (i = 0; argv[i] != NULL; ++i) {
+ if (i > 0) {
+ memcpy (&r[rlen], separator, seplen);
+ rlen += seplen;
+ }
+ len = strlen (argv[i]);
+ memcpy (&r[rlen], argv[i], len);
+ rlen += len;
+ }
+ r[rlen] = '\0';
+
+ return r;
+}
diff --git a/inspection/stringsbuf.h b/inspection/stringsbuf.h
new file mode 100644
index 0000000..4a26ffb
--- /dev/null
+++ b/inspection/stringsbuf.h
@@ -0,0 +1,56 @@
+/* libguestfs - the guestfsd daemon
+ * 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_STRINGSBUF_H
+#define GUESTFS_INSPECTION_STRINGSBUF_H
+
+/* Growable strings buffer. */
+struct stringsbuf {
+ char **argv;
+ size_t size;
+ size_t alloc;
+};
+#define DECLARE_STRINGSBUF(v) \
+ struct stringsbuf (v) = { .argv = NULL, .size = 0, .alloc = 0 }
+
+/* Append a string to the strings buffer.
+ *
+ * add_string_nodup: don't copy the string.
+ * add_string: copy the string.
+ * end_stringsbuf: NULL-terminate the buffer.
+ */
+extern void add_string_nodup (struct stringsbuf *sb, char *str);
+extern void add_string (struct stringsbuf *sb, const char *str);
+extern void add_sprintf (struct stringsbuf *sb, const char *fs, ...)
+ __attribute__((format (printf,2,3)));
+extern void end_stringsbuf (struct stringsbuf *sb);
+extern void free_stringsbuf (struct stringsbuf *sb);
+
+extern size_t count_strings (char *const *argv);
+extern void sort_strings (char **argv, size_t len);
+extern void free_strings (char **argv);
+extern void free_stringslen (char **argv, size_t len);
+
+extern void sort_device_names (char **argv, size_t len);
+extern int compare_device_names (const char *a, const char *b);
+
+/* Concatenate strings, optionally with a separator string between each. */
+extern char *concat_strings (char *const *argv);
+extern char *join_strings (const char *separator, char *const *argv);
+
+#endif /* GUESTFS_INSPECTION_STRINGSBUF_H */
diff --git a/inspection/utils.c b/inspection/utils.c
new file mode 100644
index 0000000..9e44ab3
--- /dev/null
+++ b/inspection/utils.c
@@ -0,0 +1,66 @@
+/* libguestfs
+ * Copyright (C) 2016 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 <error.h>
+#include <errno.h>
+
+#include <pcre.h>
+
+#include "cleanups.h"
+#include "inspection.h"
+
+/* Get the first line of the file, without any trailing newline
+ * character. The caller must free the returned string.
+ *
+ * If the file is completely empty or begins with '\n' this returns an
+ * empty string.
+ *
+ * This function never returns NULL. If the file does not exist or
+ * there is some other error, it exits with an error.
+ */
+char *
+first_line_of_file (const char *fs, const char *filename)
+{
+ CLEANUP_FREE char *relative_filename = NULL;
+ FILE *fp;
+ ssize_t r;
+ size_t n = 0;
+ char *line = NULL;
+
+ if (get_mount (fs, filename, &relative_filename) == -1)
+ error (EXIT_FAILURE, 0, "%s: failed to get mountpoint: %s %s",
+ __func__, fs, filename);
+
+ fp = fopen (relative_filename, "r");
+ if (fp == NULL)
+ error (EXIT_FAILURE, errno, "%s: open: %s", __func__, relative_filename);
+ r = getline (&line, &n, fp);
+ if (r == -1)
+ error (EXIT_FAILURE, errno, "%s: getline: %s", __func__,
relative_filename);
+ fclose (fp);
+
+ if (r > 0 && line[r-1] == '\n')
+ line[r-1] = '\0';
+
+ return line; /* caller frees */
+}
diff --git a/po/POTFILES b/po/POTFILES
index 2a1e313..c89ca14 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -259,6 +259,13 @@ gobject/src/struct-version.c
gobject/src/struct-xattr.c
gobject/src/struct-xfsinfo.c
gobject/src/tristate.c
+inspection/detect.c
+inspection/facts.c
+inspection/inspection.c
+inspection/match.c
+inspection/mount.c
+inspection/stringsbuf.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 2a199c0..4fb58e6 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -3545,6 +3545,7 @@ Other libguestfs topics:
L<guestfs-building(1)>,
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