Richard W.M. Jones
2015-Dec-02 22:05 UTC
[Libguestfs] [PATCH 0/3] [FOR COMMENTS ONLY] Rework inspection.
This is something I've been working on: Reworking inspection so it's
not a big mess of ad hoc C code, but instead uses a well-defined
domain-specific language to describe how we inspect guests.
The best introduction to this is the manual page, which I include
below (it's also included in patch 2/3).
Rich.
----------------------------------------------------------------------
NAME
guestfs-inspection - guestfs inspection program
SYNOPSIS
guestfs-inspection
NOTE
This man page documents the guestfs inspection program. If you want to
read about guestfs inspection then this is the wrong place. See
"INSPECTION" in guestfs(3) instead.
DESCRIPTION
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 guestfsd(8),
when the caller uses the guestfs_inspect_os API (see
guestfs-internals(1) and "guestfs_inspect_os" in guestfs(3)). 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 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 "WRITING RULES" below.
OPTIONS
-?
--help
Display brief help.
-v
--verbose
Enable verbose messages for debugging.
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 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.
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 "/dev/sda1 is a filesystem".
File("/dev/sda1", "/etc/fstab")
which means "there exists a file called /etc/fstab on the /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 /dev/sda1 is not a filesystem or there does not
exist a file called /etc/fstab on this filesystem".
Unknown facts are facts that we don't know if they are true or false
yet.
Rules
Rules are used to generate more facts. A simple rule for generating
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 fs & filename, if fs is a
filesystem,
and running the C code with parameters fs and filename returns true,
then File(fs, filename) is a true fact".
In the Prolog-inspired language, a comma (,) is the AND operator. A
semicolon (;) is the OR operator. :- is a backwards if-statement (the
condition is on the right, the conclusion is on the left). Also notice
the dot (.) which must follow each rule.
Uppercase identifiers are facts. Lowercase identifiers are variables.
All identifiers are case-sensitive.
Everything in {{ ... }} 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 (...) for grouping expressions on the right
hand side of the :- operator.
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 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 File rule, and we
substitute (theoretically) every possible string for fs and 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 /etc/fstab in
/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 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 /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.
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 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 fs are found by looking at any 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 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 /etc/fstab and
/etc/fedora-release would be tried:
Filename("/etc/fstab").
Filename("/etc/fedora-release").
File(fs, filename) :-
Filesystem(fs),
Filename(filename),
{{ // C code }}.
C expressions returning boolean
Simple C code enclosed in {{ ... }} as shown above should return a
true, false or error status only. It returns true by returning any
integer ≥ 1. It should return 0 to indicate false, and it should return
-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 fs and 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 EIO is thrown.
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 var1, 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 set_product_name (that the compiler
generates).
The return value from the C code should be 0 if everything was OK, or
-1 if there is a error (which stops the whole program).
C expressions returning multiple results
Finally it is possible for C code to return multiple results.
The syntax is:
[var1, var2, ...]={{ ... }}
where var1, 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 set_fs (that the
compiler generates) for each new filesystem discovered. Multiple
Filesystem facts can be generated as a result of one application of
this rule.
The return value from the C code should be 0 if everything was OK, or
-1 if there is a error (which stops the whole program).
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:
* 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.
* A fact must have the same number of arguments ("arity") each
time
it appears in the source.
Debugging
You can debug the evaluation of inspection programs by calling
guestfs_set_verbose (or setting $LIBGUESTFS_DEBUG=1) before launching
the handle.
This causes guestfsd(8) to pass the --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
stderr (or sent to the event system if you are using that).
You can also print debug messages from C code embedded in {{...}}
expressions. These are similarly sent upwards through to libguestfs and
will appear on stderr.
EXIT STATUS
This program returns 0 if successful, or non-zero if there was an
error.
SEE ALSO
guestfsd(8), guestfs-hacking(1), guestfs-internals(1),
"INSPECTION" in
guestfs(3), http://libguestfs.org/.
AUTHOR
Richard W.M. Jones http://people.redhat.com/~rjones/
COPYRIGHT
Copyright (C) 2009-2015 Red Hat Inc.
LICENSE
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.
----------------------------------------------------------------------
Richard W.M. Jones
2015-Dec-02 22:05 UTC
[Libguestfs] [PATCH 1/3] inspection: Add rules compiler to the generator.
---
.gitignore | 4 +
README | 4 +
bootstrap | 2 +
generator/Makefile.am | 39 ++-
generator/rules_compiler.ml | 757 +++++++++++++++++++++++++++++++++++++++++++
generator/rules_compiler.mli | 21 ++
generator/rules_parser.mly | 111 +++++++
generator/rules_scanner.mll | 112 +++++++
generator/types.ml | 49 +++
generator/utils.ml | 16 +
generator/utils.mli | 6 +
m4/guestfs_ocaml.m4 | 2 +
12 files changed, 1121 insertions(+), 2 deletions(-)
create mode 100644 generator/rules_compiler.ml
create mode 100644 generator/rules_compiler.mli
create mode 100644 generator/rules_parser.mly
create mode 100644 generator/rules_scanner.mll
diff --git a/.gitignore b/.gitignore
index 11557b6..288a853 100644
--- a/.gitignore
+++ b/.gitignore
@@ -221,7 +221,11 @@ Makefile.in
/generator/files-generated.txt
/generator/generator
/generator/.pod2text.data*
+/generator/rules_parser.ml
+/generator/rules_parser.mli
+/generator/rules_scanner.ml
/generator/stamp-generator
+/generator/stamp-rules-parser
/get-kernel/.depend
/get-kernel/stamp-virt-get-kernel.pod
/get-kernel/virt-get-kernel
diff --git a/README b/README
index 2c79c0d..26198fc 100644
--- a/README
+++ b/README
@@ -88,6 +88,10 @@ The full requirements are described below.
| | | | Optional if compiling from tarball. |
| | | | To build generated files and OCaml bindings.
+--------------+-------------+---+-----------------------------------------+
+| ocamllex | 3.11 |R/O| Required if compiling from git. |
+| ocamlyacc | | | Optional if compiling from tarball. |
+| | | | To build generated files and OCaml bindings.
++--------------+-------------+---+-----------------------------------------+
| findlib | |R/O| Required if compiling from git. |
| | | | Optional if compiling from tarball. |
| | | | To build generated files and OCaml bindings.
diff --git a/bootstrap b/bootstrap
index 5df6f0f..f932c0c 100755
--- a/bootstrap
+++ b/bootstrap
@@ -36,6 +36,7 @@ gnulib_tool=$GNULIB_SRCDIR/gnulib-tool
modules='
accept4
+array-oset
areadlink
areadlinkat
arpa_inet
@@ -97,6 +98,7 @@ warnings
xalloc
xalloc-die
xgetcwd
+xoset
xstrtol
xstrtoll
xvasprintf
diff --git a/generator/Makefile.am b/generator/Makefile.am
index 9177e6f..fe6d35d 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -18,6 +18,9 @@
include $(top_srcdir)/subdir-rules.mk
# In alphabetical order.
+#
+# Note we include ocamllex/ocamlyacc-generated files here, since
+# we want to distribute these in the tarball for convenience.
sources = \
actions.ml \
actions.mli \
@@ -48,6 +51,13 @@ sources = \
prepopts.mli \
python.ml \
ruby.ml \
+ rules_compiler.ml \
+ rules_compiler.mli \
+ rules_parser.ml \
+ rules_parser.mli \
+ rules_parser.mly \
+ rules_scanner.ml \
+ rules_scanner.mll \
structs.ml \
structs.mli \
tests_c_api.ml \
@@ -88,6 +98,9 @@ objects = \
bindtests.cmo \
errnostring.cmo \
customize.cmo \
+ rules_scanner.cmo \
+ rules_parser.cmo \
+ rules_compiler.cmo \
main.cmo
EXTRA_DIST = $(sources) files-generated.txt
@@ -101,6 +114,22 @@ if HAVE_OCAML
generator: $(objects)
$(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -linkpkg $^ -o $@
+rules_parser.ml rules_parser.mli: stamp-rules-parser
+
+stamp-rules-parser: rules_parser.mly
+ rm -f $@
+ $(OCAMLYACC) $<
+ touch $@
+
+rules_scanner.ml: rules_scanner.mll
+ $(OCAMLLEX) $<
+
+# Apparently because rules_parser.mli and rules_scanner.ml may not
+# exist before the Makefile is run, the pattern dependencies below
+# don't add these rules automatically, so we have to be explicit.
+rules_parser.cmi: rules_parser.mli
+rules_scanner.cmi: rules_scanner.ml
+
# Dependencies.
%.cmi: %.mli
$(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@
@@ -111,7 +140,7 @@ generator: $(objects)
depend: .depend
-.depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml)
+.depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml)
rules_parser.ml rules_scanner.ml
rm -f $@ $@-t
$(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) $^ | \
$(SED) 's/ *$$//' | \
@@ -154,6 +183,12 @@ stamp-generator: generator
CLEANFILES = $(noinst_DATA) $(noinst_PROGRAM) *.cmi *.cmo *~
-DISTCLEANFILES = .depend .pod2text.data.version.2
+DISTCLEANFILES = \
+ .depend \
+ .pod2text.data.version.2 \
+ rules_parser.ml \
+ rules_parser.mli \
+ rules_scanner.ml \
+ stamp-rules-parser
SUFFIXES = .cmo .cmi .cmx .ml .mli .mll .mly
diff --git a/generator/rules_compiler.ml b/generator/rules_compiler.ml
new file mode 100644
index 0000000..8e0fc6c
--- /dev/null
+++ b/generator/rules_compiler.ml
@@ -0,0 +1,757 @@
+(* libguestfs
+ * 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
+ *)
+
+(* This is the compiler that turns inspection rules into C code. *)
+
+open Printf
+
+open Utils
+open Types
+open Pr
+open Docstrings
+
+module StringSet = Set.Make (String)
+
+let (//) = Filename.concat
+
+type env = {
+ free_vars : string list;
+ assign_vars : string list;
+ list_assign_vars : string list;
+
+ (* Name of the C environment struct. *)
+ env_struct : string;
+}
+
+let rec compile filename () + let rules = parse filename in
+ type_checking filename rules;
+
+ generate_header ~extra_inputs:[filename] CStyle GPLv2plus;
+
+ pr "\
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <error.h>
+
+/* XXX At the moment we have to hard-code any headers needed by
+ * C code snippets from the input here. We could fix this by
+ * allowing the source to define a C prologue.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include \"gl_oset.h\"
+#include \"gl_xoset.h\"
+#include \"gl_array_oset.h\"
+
+#include \"inspection.h\"
+
+#include \"guestfs-internal-all.h\"
+
+/* Disable a few warnings, so we can take a few short-cuts with the
+ * generated code.
+ */
+#pragma GCC diagnostic ignored \"-Wunused-variable\"
+#pragma GCC diagnostic ignored \"-Wunused-macros\"
+#pragma GCC diagnostic ignored \"-Wunused-function\"
+
+static gl_oset_t
+new_string_set (void)
+{
+ /* Note that we don't need a dispose function because all the
+ * strings added to the set will be \"owned\" by other code, either
+ * static (all_strings) or owned by facts.
+ */
+ return gl_oset_create_empty (GL_ARRAY_OSET,
+ (gl_setelement_compar_fn) strcmp, NULL);
+}
+
+static void
+add_all_strings (gl_oset_t set)
+{
+ size_t i;
+
+ for (i = 0; all_strings[i] != NULL; ++i)
+ gl_oset_add (set, all_strings[i]);
+}
+
+";
+
+ get_all_strings filename rules;
+
+ (* Give each rule a unique number. The number is used for rule
+ * function names, environments and so on. eg: 'rule_0 ()'
+ * 'struct rule_0_env'.
+ *)
+ let rules = mapi (fun i rule -> (i, rule)) rules in
+
+ (* Create the environment struct for each rule. This contains all
+ * the variables either consumed or set by the function.
+ *)
+ let rules + List.map (fun (i, rule) ->
+ let env = compile_rule_environment filename i rule in
+ (i, rule, env))
+ rules in
+
+ (* Write all C code snippets to functions. *)
+ iteri (
+ fun j (i, rule, env) ->
+ let rec loop = function
+ | And (e1, e2) | Or (e1, e2) -> loop e1; loop e2
+ | Code code -> compile_code filename i rule env j code
+ | AssignCode _ -> ()
+(*
+ | AssignCode (vs, code) ->
+ compile_assign_code filename i rule env j vs code
+ *)
+ | ListAssignCode (vs, code) ->
+ compile_list_assign_code filename i rule env j vs code
+ | Term _ | Not _ | True | False -> ()
+ in
+ loop rule.body
+ ) rules;
+
+ (* Compile all rules into functions. *)
+ let rules + List.map (
+ fun (i, rule, env) ->
+ let rule_fn = sprintf "rule_%d" i in
+ compile_rule filename rule rule_fn env;
+ (rule, rule_fn)
+ ) rules in
+
+ pr "\
+void
+rules (void)
+{
+ clear_true_facts ();
+ clear_false_facts ();
+
+ /* Loop over all the rules until no more true facts can be added. */
+ for (;;) {
+ size_t nr_true_facts = count_true_facts ();
+
+";
+
+ List.iter (
+ fun (rule, rule_fn) ->
+ pr " if (verbose)\n";
+ pr " printf (\"trying rule %%s\\n\", %S);\n"
+ (string_of_term rule.head);
+ pr " %s ();\n" rule_fn;
+ pr "\n";
+ ) rules;
+
+ pr " /* Added a true fact during this iteration? */
+ if (nr_true_facts == count_true_facts ())
+ break;
+ } /* for (;;) */
+}
+
+/* EOF */\n"
+
+and get_all_strings filename rules + let rec loop = function
+ | True | False | Code _ | AssignCode _ | ListAssignCode _ -> []
+ | And (e1, e2) | Or (e1, e2) -> loop e1 @ loop e2
+ | Term term | Not term -> get_term_strings term
+ and get_term_strings { term_args = args } + filter_map (function Variable
_ -> None | Constant s -> Some s) args
+ in
+ let all_strings + List.map (fun rule -> get_term_strings rule.head @
loop rule.body) rules in
+ let all_strings = List.concat all_strings in
+ let all_strings = sort_uniq all_strings in
+ pr "const char *all_strings[] = {\n";
+ pr " ";
+ let col = ref 0 in
+ List.iter (
+ fun s ->
+ let len = String.length s in
+ if !col + len + 4 >= 72 then (
+ col := 0;
+ pr "\n "
+ );
+ pr "%S, " s;
+ col := !col + len + 4;
+ ) all_strings;
+ if !col > 0 then pr "\n";
+ pr " NULL\n";
+ pr "};\n";
+ pr "\n"
+
+(* Work the environment of a single rule. Also write out the
+ * corresponding struct to the C file.
+ *)
+and compile_rule_environment filename i rule + (* The name of the C struct. *)
+ let env_struct = sprintf "rule_%d_env" i in
+
+ (* Divide all the variables which appear in the rule into:
+ * - ones which we have to search for [free_vars],
+ * - ones which are going to be returned by a C expression within
+ * the body [assign_vars, list_assign_vars].
+ * We can do this statically.
+ * These sets are non-overlapping, so we just need to check which
+ * variables are returned by C expressions, and do an additional
+ * check that no C expressions are returning the same variable.
+ *)
+ (* Get the complete list of vars ... *)
+ let free_vars = Hashtbl.create 13 in
+ (* ... from the head *)
+ List.iter (
+ function
+ | Variable v ->
+ if Hashtbl.mem free_vars v then (
+ eprintf "%s: variable '%s' appears two or more times in a
rule\n"
+ filename v;
+ exit 1
+ );
+ Hashtbl.add free_vars v 1
+ | Constant _ -> ()
+ ) rule.head.term_args;
+ (* ... from the body *)
+ let rec loop = function
+ | And (e1, e2) | Or (e1, e2) -> loop e1; loop e2
+ | Term { term_args = args } | Not { term_args = args } ->
+ List.iter (
+ function
+ | Variable v -> Hashtbl.replace free_vars v 1
+ | Constant _ -> ()
+ ) args
+ | True | False
+ | Code _ | AssignCode _ | ListAssignCode _ -> ()
+ in
+ loop rule.body;
+
+ let assign_vars = Hashtbl.create 13 in
+ let list_assign_vars = Hashtbl.create 13 in
+ let rec loop = function
+ | True | False | Term _ | Not _ | Code _ -> ()
+ | And (e1, e2) | Or (e1, e2) -> loop e1; loop e2
+ | AssignCode (vs, _) ->
+ List.iter (
+ fun v ->
+ Hashtbl.remove free_vars v;
+ if Hashtbl.mem assign_vars v then (
+ eprintf "%s: variable '%s' appears two or more times
in a C assignment expression in a rule\n"
+ filename v;
+ exit 1
+ );
+ Hashtbl.add assign_vars v 1
+ ) vs
+ | ListAssignCode (vs, _) ->
+ List.iter (
+ fun v ->
+ Hashtbl.remove free_vars v;
+ if Hashtbl.mem list_assign_vars v || Hashtbl.mem assign_vars v then
(
+ eprintf "%s: variable '%s' appears two or more times
in a C assignment expression in a rule\n"
+ filename v;
+ exit 1
+ );
+ Hashtbl.add list_assign_vars v 1
+ ) vs
+ in
+ loop rule.body;
+ let free_vars = Hashtbl.fold (fun k _ ks -> k :: ks) free_vars [] in
+ let assign_vars = Hashtbl.fold (fun k _ ks -> k :: ks) assign_vars [] in
+ let list_assign_vars + Hashtbl.fold (fun k _ ks -> k :: ks)
list_assign_vars [] in
+
+ (* Write out the C struct. *)
+ pr "/* Environment struct for rule %s */\n" (string_of_term
rule.head);
+ pr "struct %s {\n" env_struct;
+ if free_vars <> [] then (
+ pr " /* free variables */\n";
+ List.iter (pr " char *%s;\n") free_vars
+ );
+ if assign_vars <> [] then (
+ pr " /* assigned vars */\n";
+ List.iter (pr " char *%s;\n") assign_vars
+ );
+ if list_assign_vars <> [] then (
+ pr " /* assigned lists */\n";
+ pr " size_t nr_list_assign_vars;\n";
+ List.iter (pr " char **%s;\n") list_assign_vars
+ );
+ pr "};\n";
+ pr "\n";
+
+ (* Return the OCaml env. *)
+ { free_vars = free_vars;
+ assign_vars = assign_vars;
+ list_assign_vars = list_assign_vars;
+ env_struct = env_struct; }
+
+(* Compile a single rule to C code. *)
+and compile_rule filename rule rule_fn env + (* For each free variable we need
to find the possible values for that
+ * variable. If they appear within the body in a term like
+ * 'Foo(var)' then we can just look for matching facts and add
+ * them (at runtime). If they don't, then we start with the list
+ * of all strings culled from the source + all strings from all facts.
+ *)
+ let free_vars = List.map (
+ fun v ->
+ let fact_lookups = ref [] in
+ let rec loop = function
+ | True | False | Code _ | AssignCode _ | ListAssignCode _ -> ()
+ | And (e1, e2) | Or (e1, e2) -> loop e1; loop e2
+ | Term { term_name = term_name; term_args = args }
+ | Not { term_name = term_name; term_args = args } ->
+ (* If this term contains this variable at some position,
+ * then save that in the list of 'facts'.
+ *)
+ iteri (
+ fun arg_i ->
+ function
+ | Variable v' when v = v' ->
+ fact_lookups := (term_name, arg_i) :: !fact_lookups
+ | Variable _ | Constant _ -> ()
+ ) args
+ in
+ loop rule.body;
+ let fact_lookups = sort_uniq !fact_lookups in
+
+ v, fact_lookups
+ ) env.free_vars in
+
+ pr "static void\n";
+ pr "%s (void)\n" rule_fn;
+ pr "{\n";
+ pr " struct %s env;\n" env.env_struct;
+ pr " bool added;\n";
+ pr " size_t i;\n";
+ List.iter (
+ fun (v, _) ->
+ pr " gl_oset_t search_%s;\n" v;
+ pr " gl_oset_iterator_t iter_%s;\n" v;
+ ) free_vars;
+ pr "\n";
+
+ if free_vars <> [] then
+ pr " /* Build the sets we will use for searching each free variable.
*/\n";
+ List.iter (
+ function
+ | v, [] ->
+ (* The variable doesn't appear in any expressions, so
+ * add a note to the source. Maybe emit a compiler warning? XXX
+ *)
+ pr " search_%s = new_string_set ();\n" v;
+ pr " /* Warning: variable '%s' is underspecified, so we
will\n" v;
+ pr " * search over all strings from the source and all
facts.\n";
+ pr " */\n";
+ pr " add_all_strings (search_%s);\n" v;
+ pr " add_all_fact_strings (search_%s);\n" v;
+ pr "\n"
+ | v, fact_lookups ->
+ pr " search_%s = new_string_set ();\n" v;
+ List.iter (
+ fun (term_name, arg_i) ->
+ pr " add_strings_from_facts (search_%s, %S, %d);\n"
+ v term_name arg_i
+ ) fact_lookups;
+ pr "\n"
+ ) free_vars;
+
+ (* Do a cartesian search over all [free_vars], substituting each set of
+ * variables, and evaluating the body. If it evaluates to true,
+ * then we will add a new true fact! (Or maybe several if we are
+ * dealing with a list assignment []={{...}}). If it evaluates
+ * to false, we add a false fact. It's also possible that we
+ * cannot evaluate the rule at all, because it contains unknown
+ * facts, in which case we end up adding NO new facts.
+ *)
+ if free_vars <> [] then (
+ pr " /* Perform cartesian search over free variables. */\n";
+
+ List.iter (
+ fun (v, _) ->
+ pr " iter_%s = gl_oset_iterator (search_%s);\n" v v;
+ pr " while (gl_oset_iterator_next (&iter_%s,\n" v;
+ pr " (const void **)&env.%s))
{\n" v;
+ ) free_vars;
+
+ ) else (
+ (* If there are no free_vars, then we have to add a dummy loop
+ * around the next code so that the 'continue' statement can be
used.
+ *)
+ pr " do {\n";
+ );
+
+ (* Initialize any assign_vars and list_assign_vars in the env struct.
+ * Note that the free_vars are initialized by the iterator loops above.
+ *)
+ List.iter (pr " env.%s = NULL;\n") env.assign_vars;
+ List.iter (pr " env.%s = NULL;\n") env.list_assign_vars;
+ if env.list_assign_vars <> [] then pr " env.nr_list_assign_vars =
0;\n";
+
+ (* We can only do this optimization if assign_vars = list_assign_vars = [],
+ * because we don't know what the C code (returning those vars)
+ * may give us yet. XXX Actually we could be looser with this:
+ * we only need to check that the head term contains no assigned
+ * variables.
+ *)
+ if env.assign_vars = [] && env.list_assign_vars = [] then (
+ pr " {\n";
+ pr " /* If the fact already exists, don't bother doing any
work. */\n";
+ pr " CLEANUP_FREE fact *fact = create_fact (%S"
rule.head.term_name;
+ List.iter (function
+ | Variable v -> pr ", env.%s" v
+ | Constant s -> pr ", %S" s)
+ rule.head.term_args;
+ pr ", NULL);\n";
+ pr "\n";
+ pr " if (is_fact (true, fact) || is_fact (false, fact))\n";
+ pr " continue;\n";
+ pr " }\n";
+ pr "\n";
+ );
+
+ (* Evaluate the expression on the right hand side. *)
+ let rec eval result = function
+ | True ->
+ pr " %s = 1;\n" result
+ | False ->
+ pr " %s = 0;\n" result
+ | Code code ->
+ let code_fn = function_of_code code in
+ pr " %s = %s (&env);\n" result code_fn
+ | AssignCode _ ->
+ (* XXX *)
+
+ (* The result of AssignCode is always true (else it would
+ * have exited earlier). Hence:
+ *)
+ pr " %s = 1;\n" result
+ | ListAssignCode (vs, code) ->
+ let code_fn = function_of_code code in
+ pr " %s = %s (&env);\n" result code_fn;
+ pr " if (%s == -1)\n" result;
+ pr " error (EXIT_FAILURE, 0, \"code returned error in
%%s\",\n";
+ pr " \"%s\");\n" (string_of_term
rule.head);
+ (* The result of ListAssignCode is always true (else it would
+ * have exited above). Hence:
+ *)
+ pr " %s = 1;\n" result
+ | And (e1, e2) ->
+ let re1 = sprintf "r_%d" (unique ()) in
+ pr " int %s;\n" re1;
+ eval re1 e1;
+ pr " if (%s != 1)\n" re1;
+ pr " %s = %s;\n" result re1;
+ pr " else {\n";
+ let re2 = sprintf "r_%d" (unique ()) in
+ pr " int %s;\n" re2;
+ eval re2 e2;
+ pr " %s = %s;\n" result re2;
+ pr " }\n";
+ | Or (e1, e2) ->
+ let re1 = sprintf "r_%d" (unique ()) in
+ pr " int %s;\n" re1;
+ eval re1 e1;
+ pr " if (%s == 1)\n" re1;
+ pr " %s = %s;\n" result re1;
+ pr " else {\n";
+ let re2 = sprintf "r_%d" (unique ()) in
+ pr " int %s;\n" re2;
+ eval re2 e2;
+ pr " %s = %s;\n" result re2;
+ pr " }\n";
+ | Term term ->
+ pr " {\n";
+ pr " CLEANUP_FREE fact *fact = create_fact (%S"
term.term_name;
+ List.iter (
+ function
+ | Variable v -> pr ", env.%s" v
+ | Constant s -> pr ", %S" s
+ ) term.term_args;
+ pr ", NULL);\n";
+ pr " %s = is_fact (true, fact);\n" result;
+ pr " }\n";
+ | Not term ->
+ pr " {\n";
+ pr " CLEANUP_FREE fact *fact = create_fact (%S"
term.term_name;
+ List.iter (
+ function
+ | Variable v -> pr ", env.%s" v
+ | Constant s -> pr ", %S" s
+ ) term.term_args;
+ pr ", NULL);\n";
+ pr " %s = is_fact (false, fact);\n" result;
+ pr " }\n";
+ in
+ pr " /* Evaluate the RHS of the rule with this assignment of variables.
*/\n";
+ pr " int result;\n";
+ eval "result" rule.body;
+ pr " if (result == -1) /* not determined */ continue;\n";
+ let make_fact ?i ?(indent = 2) () + let indent = spaces indent in
+ pr "%sCLEANUP_FREE fact *fact = create_fact (%S" indent
rule.head.term_name;
+ List.iter (
+ function
+ | Variable v ->
+ if not (List.mem v env.list_assign_vars) then
+ pr ", env.%s" v
+ else (
+ let i = match i with Some i -> i | None -> assert false in
+ pr ", env.%s[%s]" v i
+ )
+ | Constant s -> pr ", %S" s
+ ) rule.head.term_args;
+ pr ", NULL);\n";
+ in
+ pr " if (result > 0) /* true */ {\n";
+ if env.list_assign_vars = [] then (
+ make_fact ~indent:4 ();
+ pr " added = add_fact (true, fact);\n";
+ pr " if (added && verbose) {\n";
+ pr " printf (\"added new fact \");\n";
+ pr " print_fact (true, fact, stdout);\n";
+ pr " printf (\"\\n\");\n";
+ pr " }\n";
+ ) else (
+ pr " for (i = 0; i < env.nr_list_assign_vars; ++i) {\n";
+ make_fact ~i:"i" ~indent:6 ();
+ pr " added = add_fact (true, fact);\n";
+ pr " if (added && verbose) {\n";
+ pr " printf (\"added new fact \");\n";
+ pr " print_fact (true, fact, stdout);\n";
+ pr " printf (\"\\n\");\n";
+ pr " }\n";
+ pr " }\n";
+ );
+ pr " }\n";
+ pr " if (result == 0) /* false */ {\n";
+ if env.list_assign_vars = [] then (
+ make_fact ~indent:4 ();
+ pr "\n";
+ pr " added = add_fact (false, fact);\n";
+ pr " if (added && verbose) {\n";
+ pr " printf (\"added new fact \");\n";
+ pr " print_fact (false, fact, stdout);\n";
+ pr " printf (\"\\n\");\n";
+ pr " }\n";
+ ) else (
+ pr " for (i = 0; i < env.nr_list_assign_vars; ++i) {\n";
+ make_fact ~i:"i" ~indent:6 ();
+ pr " added = add_fact (false, fact);\n";
+ pr " if (added && verbose) {\n";
+ pr " printf (\"added new fact \");\n";
+ pr " print_fact (false, fact, stdout);\n";
+ pr " printf (\"\\n\");\n";
+ pr " }\n";
+ pr " }\n";
+ );
+ pr " }\n";
+
+ (* Free any assign_vars and list_assign_vars. The free_vars don't
+ * have to be freed because the iterator loop handles them.
+ *)
+ List.iter (pr " free (env.%s);\n") env.assign_vars;
+ List.iter (
+ fun v ->
+ pr " for (size_t i = 0; i < env.nr_list_assign_vars;
++i)\n";
+ pr " free (env.%s[i]);\n" v;
+ pr " free (env.%s);\n" v
+ ) env.list_assign_vars;
+
+ if free_vars <> [] then (
+ List.iter (
+ fun (v, _) ->
+ pr " }\n";
+ pr " gl_oset_iterator_free (&iter_%s);\n" v
+ ) (List.rev free_vars);
+ ) else (
+ pr " } while (0);\n";
+ );
+ pr "\n";
+
+ List.iter (
+ function
+ | v, _ ->
+ pr " gl_oset_free (search_%s);\n" v
+ ) free_vars;
+
+ pr "}\n";
+ pr "\n"
+
+(* Compile a (boolean) Code snippet from a rule into a function. *)
+and compile_code filename i rule env j code + let code_fn = sprintf
"rule_%d_code_%d" i j in
+ Hashtbl.add code_hash code code_fn;
+
+ pr "static int\n";
+ pr "%s (struct %s *env)\n" code_fn env.env_struct;
+ pr "{\n";
+ List.iter (fun v -> pr "#define %s (env->%s)\n" v v)
env.free_vars;
+ (* XXX # lineno *)
+ pr "%s\n" code;
+ List.iter (pr "#undef %s\n") env.free_vars;
+ pr "}\n";
+ pr "\n";
+
+(* Compile a list assignment code (ListAssignCode) snippet
+ * into a function.
+ *)
+and compile_list_assign_code filename i rule env j vs code + (* Create a
function for setting a row in the result. *)
+ let set_vars_fn = sprintf "rule_%d_code_%d_set_row" i j in
+ let set_vars_alias = sprintf "set_%s" (String.concat "_"
vs) in
+ pr "static void\n";
+ pr "%s (struct %s *env, ...)\n" set_vars_fn env.env_struct;
+ pr "{\n";
+ pr " va_list args;\n";
+ pr " size_t i = env->nr_list_assign_vars;\n";
+ pr "\n";
+ pr " va_start (args, env);\n";
+ List.iter (
+ fun v ->
+ pr " env->%s = realloc (env->%s, (i+1) * sizeof (char
*));\n" v v;
+ pr " if (env->%s == NULL)\n" v;
+ pr " error (EXIT_FAILURE, errno, \"realloc\");\n";
+ pr " env->%s[i] = strdup (va_arg (args, char *));\n" v;
+ pr " if (env->%s[i] == NULL)\n" v;
+ pr " error (EXIT_FAILURE, errno, \"strdup\");\n";
+ ) vs;
+ pr " va_end (args);\n";
+ pr " env->nr_list_assign_vars++;\n";
+ pr "}\n";
+ pr "\n";
+
+ (* Create the function itself. *)
+ let code_fn = sprintf "rule_%d_code_%d" i j in
+ Hashtbl.add code_hash code code_fn;
+
+ pr "static int\n";
+ pr "%s (struct %s *env)\n" code_fn env.env_struct;
+ pr "{\n";
+ List.iter (fun v -> pr "#define %s (env->%s)\n" v v)
env.free_vars;
+ pr "#define %s(v1,...) %s (env, (v1), ##__VA_ARGS__)\n"
+ set_vars_alias set_vars_fn;
+ (* XXX # lineno *)
+ pr "%s\n" code;
+ List.iter (pr "#undef %s\n") env.free_vars;
+ pr "#undef %s\n" set_vars_alias;
+ pr "}\n";
+ pr "\n";
+
+(* Map of code to function names. *)
+and code_hash = Hashtbl.create 13
+and function_of_code code = Hashtbl.find code_hash code
+
+(* Parse the input. *)
+and parse filename + let lexbuf = Lexing.from_channel (open_in filename) in
+ let rules = ref [] in
+ (try
+ while true do
+ let rule = Rules_parser.rule Rules_scanner.token lexbuf in
+ (*printf "%s\n" (string_of_rule rule);*)
+ rules := rule :: !rules
+ done
+ with
+ | End_of_file -> ()
+ | Rules_scanner.Error (msg, _, lineno, charno) ->
+ eprintf "%s: %d: %d: %s\n" filename lineno charno msg;
+ exit 1
+ | Parsing.Parse_error ->
+ let p = Lexing.lexeme_start_p lexbuf in
+ eprintf "%s: %d: %d: syntax error\n"
+ filename
+ p.Lexing.pos_lnum
+ (p.Lexing.pos_cnum - p.Lexing.pos_bol);
+ exit 1
+ );
+ let rules = List.rev !rules in
+ rules
+
+(* Minimal type checking. *)
+and type_checking filename rules + check_term_rhs filename rules;
+ check_term_arity filename rules
+
+(* If a term appears on the right hand side in any expression, then
+ * the term must also appear on the left hand side of a rule.
+ *)
+and check_term_rhs filename rules + let names = List.map (fun { head = {
term_name = name } } -> name) rules in
+ let names + List.fold_left (fun set x -> StringSet.add x set)
StringSet.empty names in
+
+ let errors = ref 0 in
+ List.iter (
+ fun { body = body } ->
+ visit_terms (
+ fun { term_name = name } ->
+ if not (StringSet.mem name names) then (
+ eprintf "%s: '%s' appears in a rule expression, but
does not appear on the left hand side of any rule. Maybe there is a
typo?\n"
+ filename name;
+ incr errors
+ )
+ ) body
+ ) rules;
+ if !errors > 0 then exit 1
+
+(* Check the arity of terms is the same wherever they appear. *)
+and check_term_arity filename rules + let hash = Hashtbl.create (List.length
rules) in (* name -> arity *)
+
+ let errors = ref 0 in
+
+ let check_arity { term_name = name; term_args = args } + let arity =
List.length args in
+ try
+ let expected_arity = Hashtbl.find hash name in
+ if arity <> expected_arity then (
+ eprintf "%s: '%s' has different number of parameters (has
%d, expected %d). It must have the same number of parameters throughout the
program.\n"
+ filename name arity expected_arity;
+ incr errors
+ )
+ with
+ (* The first time we've seen this term. *)
+ Not_found -> Hashtbl.add hash name arity
+ in
+
+ List.iter (
+ fun { head = head; body = body } ->
+ check_arity head;
+ visit_terms check_arity body
+ ) rules;
+
+ if !errors > 0 then exit 1
+
+and visit_terms f = function
+ | And (e1, e2)
+ | Or (e1, e2) -> visit_terms f e1; visit_terms f e2
+ | Term t
+ | Not t -> f t
+ | True | False | Code _ | AssignCode _ | ListAssignCode _ -> ()
+
+and unique + let i = ref 0 in
+ fun () -> incr i; !i
diff --git a/generator/rules_compiler.mli b/generator/rules_compiler.mli
new file mode 100644
index 0000000..2bc5274
--- /dev/null
+++ b/generator/rules_compiler.mli
@@ -0,0 +1,21 @@
+(* libguestfs
+ * 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
+ *)
+
+(* This is the compiler that turns inspection rules into C code. *)
+
+val compile : string -> unit -> unit
diff --git a/generator/rules_parser.mly b/generator/rules_parser.mly
new file mode 100644
index 0000000..bdf6159
--- /dev/null
+++ b/generator/rules_parser.mly
@@ -0,0 +1,111 @@
+/* libguestfs -*- text -*-
+ * 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
+ */
+
+%{
+open Types
+%}
+
+%token <string> STRING /* string literal */
+%token <string> UID /* uppercase identifier */
+%token <string> LID /* lowercase identifier */
+
+%token TRUE /* true (keyword) */
+%token FALSE /* false (keyword) */
+
+%token LPAREN RPAREN /* ( ... ) */
+%token LSQUARE RSQUARE /* [ ... ] */
+%token <string> CODE /* {{ .. }} containing C code */
+%token DOT /* . */
+%token IMPLIC /* :- */
+%token COMMA /* , (AND operator) */
+%token SEMI /* , (OR operator) */
+%token NOT /* ! */
+%token EQUALS /* = */
+
+/* These operators are arranged from lowest to highest precedence. */
+%left IMPLIC
+%left SEMI
+%left COMMA
+%nonassoc NOT
+
+%start rule
+%type <Types.rule> rule
+
+%%
+
+rule: head DOT
+ { { head = $1; body = True } }
+ | head IMPLIC body DOT
+ { { head = $1; body = $3 } }
+ ;
+
+head: term
+ { $1 }
+ ;
+
+term: UID
+ { { term_name = $1; term_args = [] } }
+ | UID LPAREN term_args RPAREN
+ { { term_name = $1; term_args = $3 } }
+ ;
+
+term_args:
+ term_arg
+ { [ $1 ] }
+ | term_arg COMMA term_args
+ { $1 :: $3 }
+ ;
+
+term_arg:
+ LID
+ { Variable $1 }
+ | STRING
+ { Constant $1 }
+ ;
+
+body: expr
+ { $1 }
+ ;
+
+expr: TRUE
+ { True }
+ | FALSE
+ { False }
+ | term
+ { Term $1 }
+ | CODE
+ { Code $1 }
+ | LPAREN result_bindings RPAREN EQUALS CODE
+ { AssignCode ($2, $5) }
+ | LSQUARE result_bindings RSQUARE EQUALS CODE
+ { ListAssignCode ($2, $5) }
+ | NOT term
+ { Not $2 }
+ | expr COMMA expr
+ { And ($1, $3) }
+ | expr SEMI expr
+ { Or ($1, $3) }
+ | LPAREN expr RPAREN
+ { $2 }
+ ;
+
+result_bindings:
+ LID
+ { [ $1 ] }
+ | LID COMMA result_bindings
+ { $1 :: $3 }
diff --git a/generator/rules_scanner.mll b/generator/rules_scanner.mll
new file mode 100644
index 0000000..58c959d
--- /dev/null
+++ b/generator/rules_scanner.mll
@@ -0,0 +1,112 @@
+(* libguestfs -*- text -*-
+ * 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
+ *)
+
+{
+open Rules_parser
+
+let string_of_lexbuf = Lexing.lexeme
+
+(* Errors raised by the lexer. *)
+exception Error of string * string * int * int
+
+let raise_error lexbuf msg + let p = Lexing.lexeme_start_p lexbuf in
+ raise (Error (msg, p.Lexing.pos_fname,
+ p.Lexing.pos_lnum,
+ p.Lexing.pos_cnum - p.Lexing.pos_bol))
+
+(* Store "..." strings. *)
+let string_buf = Buffer.create 256
+let reset_string_buffer () = Buffer.clear string_buf
+let store_string_char c = Buffer.add_char string_buf c
+let get_string_buffer () = Buffer.contents string_buf
+
+let char_for_backslash = function
+ | 'n' -> '\010'
+ | 'r' -> '\013'
+ | 'b' -> '\008'
+ | 't' -> '\009'
+ | c -> c
+
+(* Store {{ CODE }} sections. *)
+let code_buf = Buffer.create 256
+let reset_code_buffer () = Buffer.clear code_buf
+let store_code_char c = Buffer.add_char code_buf c
+let get_code_buffer () = Buffer.contents code_buf
+}
+
+(* Characters that can appear within an identifier (after the first
+ * character which is treated specially below).
+ *)
+let id_char = ['a'-'z' 'A'-'Z'
'0'-'9' '_']
+
+(* Whitespace. *)
+let ws = [' ' '\t']
+
+(* Backslash escapes within strings. *)
+let backslash_escapes = ['\\' '\'' '"'
'n' 't' 'b' 'r']
+
+rule token = parse
+ | "/*" { comment lexbuf; token lexbuf }
+ | '(' { LPAREN }
+ | ')' { RPAREN }
+ | '[' { LSQUARE }
+ | ']' { RSQUARE }
+ | '=' { EQUALS }
+ | '.' { DOT }
+ | ":-" { IMPLIC }
+ | ',' { COMMA }
+ | ';' { SEMI }
+ | '!' { NOT }
+ | '"' { reset_string_buffer ();
+ string lexbuf;
+ STRING (get_string_buffer ()) }
+ | "{{" { reset_code_buffer ();
+ code lexbuf;
+ CODE (get_code_buffer ()) }
+ | "true" { TRUE }
+ | "false" { FALSE }
+ | ['A'-'Z'] id_char* { UID (string_of_lexbuf lexbuf) }
+ | ['a'-'z' '_'] id_char* { LID
(string_of_lexbuf lexbuf) }
+ | '\n' { Lexing.new_line lexbuf; token lexbuf }
+ | ws { token lexbuf }
+ | eof { raise End_of_file }
+ | _ { raise_error lexbuf "unexpected character in
input" }
+
+(* Discard C-style comments. *)
+and comment = parse
+ | "*/" { () }
+ | eof { raise_error lexbuf "unterminated comment" }
+ | '\n' { Lexing.new_line lexbuf; comment lexbuf }
+ | _ { comment lexbuf }
+
+(* Store "..." strings. *)
+and string = parse
+ | '"' { () }
+ | eof { raise_error lexbuf "unterminated string" }
+ | '\n' { raise_error lexbuf "strings cannot contain
newline characters" }
+ | '\\' (backslash_escapes as c)
+ { store_string_char (char_for_backslash c); string lexbuf
}
+ | _ as c { store_string_char c; string lexbuf }
+
+(* Store {{ ... }} (CODE) sections containing C code. *)
+and code = parse
+ | "}}" { () }
+ | eof { raise_error lexbuf "unterminated code section"
}
+ | '\n' as c { Lexing.new_line lexbuf; store_code_char c; code
lexbuf }
+ | _ as c { store_code_char c; code lexbuf }
diff --git a/generator/types.ml b/generator/types.ml
index f2d9750..dea94a7 100644
--- a/generator/types.ml
+++ b/generator/types.ml
@@ -18,6 +18,8 @@
(* Please read generator/README first. *)
+open Printf
+
(* Types used to describe the API. *)
type style = ret * args * optargs
@@ -421,3 +423,50 @@ type call_optargt | CallOInt64 of string * int64
| CallOString of string * string
| CallOStringList of string * string list
+
+(* Used by the rules compiler. *)
+
+type rule = { head : term; body : expr }
+(* The type of a parsed rule from the source. *)
+
+and term = { term_name : string; term_args : term_arg list }
+
+and term_arg = Variable of string | Constant of string
+
+and expr + | True (* used for facts *)
+ | False (* false (keyword) *)
+ | Term of term
+ | Not of term (* ! term *)
+ | And of expr * expr (* expr, expr *)
+ | Or of expr * expr (* expr; expr *)
+ | Code of string (* {{ ... }} *)
+ | AssignCode of string list * string (* (a,b)={{ ... }} *)
+ | ListAssignCode of string list * string (* [a,b]={{ ... }} *)
+
+let rec string_of_rule { head = head; body = body } + sprintf "%s
:-\n\t%s." (string_of_term head) (string_of_expr body)
+
+and string_of_term = function
+ | { term_name = term_name; term_args = [] } ->
+ sprintf "%s" term_name
+ | { term_name = term_name; term_args = args } ->
+ sprintf "%s(%s)" term_name
+ (String.concat ", " (List.map string_of_term_arg args))
+
+and string_of_term_arg = function
+ | Variable s -> s
+ | Constant s -> sprintf "%S" s
+
+and string_of_expr = function
+ | True -> "true"
+ | False -> "true"
+ | Term term -> string_of_term term
+ | Not term -> sprintf "!%s" (string_of_term term)
+ | And (e1, e2) -> sprintf "(%s,%s)" (string_of_expr e1)
(string_of_expr e2)
+ | Or (e1, e2) -> sprintf "(%s;%s)" (string_of_expr e1)
(string_of_expr e2)
+ | Code _ -> "{{ // code }}"
+ | AssignCode (bindings, _) ->
+ sprintf "(%s)={{ // code }}" (String.concat ", "
bindings)
+ | ListAssignCode (bindings, _) ->
+ sprintf "[%s]={{ // code }}" (String.concat ", "
bindings)
diff --git a/generator/utils.ml b/generator/utils.ml
index 7d47430..6b4497d 100644
--- a/generator/utils.ml
+++ b/generator/utils.ml
@@ -231,6 +231,22 @@ let mapi f xs in
loop 0 xs
+let uniq ?(cmp = Pervasives.compare) xs + let rec loop acc = function
+ | [] -> acc
+ | [x] -> x :: acc
+ | x :: (y :: _ as xs) when cmp x y = 0 ->
+ loop acc xs
+ | x :: (y :: _ as xs) ->
+ loop (x :: acc) xs
+ in
+ List.rev (loop [] xs)
+
+let sort_uniq ?(cmp = Pervasives.compare) xs + let xs = List.sort cmp xs in
+ let xs = uniq ~cmp xs in
+ xs
+
let count_chars c str let count = ref 0 in
for i = 0 to String.length str - 1 do
diff --git a/generator/utils.mli b/generator/utils.mli
index e0f30c3..392e9d6 100644
--- a/generator/utils.mli
+++ b/generator/utils.mli
@@ -84,6 +84,12 @@ val iteri : (int -> 'a -> unit) -> 'a list
-> unit
val mapi : (int -> 'a -> 'b) -> 'a list -> 'b list
+val uniq : ?cmp:('a -> 'a -> int) -> 'a list -> 'a
list
+ (** Uniquify a list (the list must be sorted first). *)
+
+val sort_uniq : ?cmp:('a -> 'a -> int) -> 'a list ->
'a list
+ (** Sort and uniquify a list. *)
+
val count_chars : char -> string -> int
(** Count number of times the character occurs in string. *)
diff --git a/m4/guestfs_ocaml.m4 b/m4/guestfs_ocaml.m4
index b3e9387..e213f80 100644
--- a/m4/guestfs_ocaml.m4
+++ b/m4/guestfs_ocaml.m4
@@ -29,6 +29,8 @@ AS_IF([test "x$enable_ocaml" != "xno"],[
OCAMLFIND AC_PROG_OCAML
AC_PROG_FINDLIB
+ AC_PROG_OCAMLLEX
+ AC_PROG_OCAMLYACC
dnl OCaml >= 3.11 is required.
AC_MSG_CHECKING([if OCaml version >= 3.11])
--
2.5.0
Richard W.M. Jones
2015-Dec-02 22:05 UTC
[Libguestfs] [PATCH 2/3] inspection: Add inspection directory, rules and supporting code.
---
.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
Richard W.M. Jones
2015-Dec-02 22:05 UTC
[Libguestfs] [PATCH 3/3] inspection: Add unit tests of the rules compiler.
---
.gitignore | 2 ++
generator/main.ml | 4 +++-
inspection/Makefile.am | 25 +++++++++++++++++++++-
inspection/test-harness.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++
inspection/test1.rules | 43 +++++++++++++++++++++++++++++++++++++
po/POTFILES | 2 ++
6 files changed, 128 insertions(+), 2 deletions(-)
create mode 100644 inspection/test-harness.c
create mode 100644 inspection/test1.rules
diff --git a/.gitignore b/.gitignore
index b5e5a77..752dd70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -252,6 +252,8 @@ Makefile.in
/inspection/guestfs-inspection.8
/inspection/rules.c
/inspection/stamp-guestfs-inspection.pod
+/inspection/test1
+/inspection/test1.c
/inspector/actual-*.xml
/inspector/stamp-virt-inspector.pod
/inspector/test-xmllint.sh
diff --git a/generator/main.ml b/generator/main.ml
index 7473e89..4e819a4 100644
--- a/generator/main.ml
+++ b/generator/main.ml
@@ -212,7 +212,9 @@ 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. *)
+ (* Run the rules compiler to generate test cases and inspection rules. *)
+ output_to "inspection/test1.c"
+ (Rules_compiler.compile "inspection/test1.rules");
output_to "inspection/rules.c"
(Rules_compiler.compile "inspection/inspection.rules");
diff --git a/inspection/Makefile.am b/inspection/Makefile.am
index 3020ff4..733529e 100644
--- a/inspection/Makefile.am
+++ b/inspection/Makefile.am
@@ -18,7 +18,8 @@
include $(top_srcdir)/subdir-rules.mk
generator_built = \
- rules.c
+ rules.c \
+ test1.c
BUILT_SOURCES = \
$(generator_built)
@@ -85,3 +86,25 @@ stamp-guestfs-inspection.pod: guestfs-inspection.pod
--license GPLv2+ \
$<
touch $@
+
+# Tests.
+
+TESTS = test1
+check_PROGRAMS = test1
+
+test1_SOURCES = \
+ cleanups.c \
+ facts.c \
+ inspection.h \
+ test-harness.c \
+ test1.c \
+ utils.c
+
+test1_LDADD = $(guestfs_inspection_LDADD)
+test1_CPPFLAGS = $(guestfs_inspection_CPPFLAGS) -DTEST1
+test1_CFLAGS = $(guestfs_inspection_CFLAGS)
+
+# XXX Why is this necessary?
+test1.c: ../generator/stamp-generator
+ rm -f $<
+ $(MAKE) -C ../generator
diff --git a/inspection/test-harness.c b/inspection/test-harness.c
new file mode 100644
index 0000000..350f81f
--- /dev/null
+++ b/inspection/test-harness.c
@@ -0,0 +1,54 @@
+/* guestfs-inspection tests
+ * 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 <assert.h>
+
+#include "inspection.h"
+
+int verbose = 1;
+
+/* Required by the gnulib 'error' module. */
+const char *program_name = "test";
+
+int
+main (int argc, char *argv[])
+{
+ /* Run the rules. */
+ rules ();
+
+ /* Check the true facts are what we expect. */
+#ifdef TEST1
+ CLEANUP_FREE fact *f1 = create_fact ("Distro",
"/dev/sda1", "Debian", NULL);
+ assert (is_fact (true, f1));
+
+ CLEANUP_FREE fact *f2 = create_fact ("Distro",
"/dev/sda2", "Fedora", NULL);
+ assert (is_fact (true, f2));
+
+ CLEANUP_FREE fact *f3 = create_fact ("Distro",
"/dev/sda3", "RHEL", NULL);
+ assert (is_fact (true, f3));
+#else
+#error "unknown test case"
+#endif
+
+ exit (EXIT_SUCCESS);
+}
diff --git a/inspection/test1.rules b/inspection/test1.rules
new file mode 100644
index 0000000..5903b46
--- /dev/null
+++ b/inspection/test1.rules
@@ -0,0 +1,43 @@
+/* -*- prolog -*- */
+
+Filesystem("/dev/sda1").
+Filesystem("/dev/sda2").
+Filesystem("/dev/sda3").
+
+File("/dev/sda1", "/etc/fstab").
+File("/dev/sda1", "/etc/debian_version").
+File("/dev/sda2", "/etc/fstab").
+File("/dev/sda2", "/etc/redhat-release").
+File("/dev/sda2", "/etc/fedora-release").
+File("/dev/sda3", "/etc/fstab").
+File("/dev/sda3", "/etc/fedora-release") :- false.
+File("/dev/sda3", "/etc/redhat-release").
+
+Symlink("/dev/sda1", "/bin").
+Directory("/dev/sda1", "/lib").
+Directory("/dev/sda1", "/etc").
+Directory("/dev/sda2", "/bin").
+Directory("/dev/sda2", "/etc").
+Directory("/dev/sda2", "/lib").
+Directory("/dev/sda3", "/bin").
+Directory("/dev/sda3", "/etc").
+Directory("/dev/sda3", "/lib").
+
+UnixRoot(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") :-
+ UnixRoot(rootfs),
+ File(rootfs, "/etc/redhat-release"),
+ ! File(rootfs, "/etc/fedora-release").
+
+Distro(rootfs, "Fedora") :-
+ UnixRoot(rootfs),
+ File(rootfs, "/etc/fedora-release").
+
+Distro(rootfs, "Debian") :-
+ UnixRoot(rootfs),
+ File(rootfs, "/etc/debian_version").
diff --git a/po/POTFILES b/po/POTFILES
index a88d0d0..8719f55 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -260,6 +260,8 @@ inspection/cleanups.c
inspection/facts.c
inspection/inspection.c
inspection/mount.c
+inspection/test-harness.c
+inspection/test1.c
inspection/utils.c
inspector/inspector.c
java/com_redhat_et_libguestfs_GuestFS.c
--
2.5.0