Using the rules compiler added in the previous commit, create an inspection program ("guestfs-inspection"). This will run inside the appliance. It is partly written in the Prolog-inspired language, with C supporting functions. For more details, read the guestfs-inspection(8) man page added in this commit. --- .gitignore | 4 + Makefile.am | 2 + appliance/Makefile.am | 3 +- configure.ac | 1 + docs/guestfs-hacking.pod | 6 + generator/Makefile.am | 2 +- generator/main.ml | 4 + inspection/Makefile.am | 92 ++++++ inspection/detect.c | 130 +++++++++ inspection/facts.c | 318 ++++++++++++++++++++ inspection/guestfs-inspection.pod | 442 ++++++++++++++++++++++++++++ inspection/inspection.c | 104 +++++++ inspection/inspection.h | 79 +++++ inspection/inspection.rules | 472 ++++++++++++++++++++++++++++++ inspection/mount.c | 591 ++++++++++++++++++++++++++++++++++++++ inspection/stringsbuf.c | 254 ++++++++++++++++ inspection/stringsbuf.h | 56 ++++ po/POTFILES | 5 + src/guestfs.pod | 1 + 19 files changed, 2564 insertions(+), 2 deletions(-) create mode 100644 inspection/Makefile.am create mode 100644 inspection/detect.c create mode 100644 inspection/facts.c create mode 100644 inspection/guestfs-inspection.pod create mode 100644 inspection/inspection.c create mode 100644 inspection/inspection.h create mode 100644 inspection/inspection.rules create mode 100644 inspection/mount.c create mode 100644 inspection/stringsbuf.c create mode 100644 inspection/stringsbuf.h 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/appliance/Makefile.am b/appliance/Makefile.am index c83b1ab..7512604 100644 --- a/appliance/Makefile.am +++ b/appliance/Makefile.am @@ -72,11 +72,12 @@ packagelist: packagelist.in Makefile cmp -s $@ $@-t || mv $@-t $@ rm -f $@-t -supermin.d/daemon.tar.gz: ../daemon/guestfsd guestfs_lvm_conf.aug guestfs_shadow.aug +supermin.d/daemon.tar.gz: ../daemon/guestfsd ../inspection/guestfs-inspection guestfs_lvm_conf.aug guestfs_shadow.aug rm -f $@ $@-t rm -rf tmp-d mkdir -p tmp-d$(DAEMON_SUPERMIN_DIR) tmp-d/etc tmp-d/usr/share/guestfs ln ../daemon/guestfsd tmp-d$(DAEMON_SUPERMIN_DIR)/guestfsd + ln ../inspection/guestfs-inspection tmp-d$(DAEMON_SUPERMIN_DIR)/guestfs-inspection ln $(srcdir)/guestfs_lvm_conf.aug tmp-d/usr/share/guestfs/guestfs_lvm_conf.aug ln $(srcdir)/guestfs_shadow.aug tmp-d/usr/share/guestfs/guestfs_shadow.aug ( cd tmp-d && tar zcf - * ) > $@-t diff --git a/configure.ac b/configure.ac index 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/Makefile.am b/generator/Makefile.am index a0fb7bd..401fbda 100644 --- a/generator/Makefile.am +++ b/generator/Makefile.am @@ -170,7 +170,7 @@ noinst_DATA = stamp-generator # Git removes empty directories, so in cases where the # generator is creating the sole file in a directory, we # have to create the directory first. -stamp-generator: generator +stamp-generator: generator $(wildcard $(top_srcdir)/inspection/*.rules) mkdir -p $(top_srcdir)/perl/lib/Sys mkdir -p $(top_srcdir)/ruby/ext/guestfs mkdir -p $(top_srcdir)/java/com/redhat/et/libguestfs diff --git a/generator/main.ml b/generator/main.ml index 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..9227249 --- /dev/null +++ b/inspection/Makefile.am @@ -0,0 +1,92 @@ +# 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 + +SHARED_SOURCE_FILES = \ + ../daemon/cleanups.c \ + ../daemon/cleanups.h \ + ../daemon/command.c \ + ../daemon/command.h + +guestfs_inspection_SOURCES = \ + $(SHARED_SOURCE_FILES) \ + detect.c \ + facts.c \ + inspection.c \ + inspection.h \ + mount.c \ + rules.c \ + stringsbuf.c \ + stringsbuf.h + +guestfs_inspection_LDADD = \ + $(AUGEAS_LIBS) \ + $(HIVEX_LIBS) \ + $(PCRE_LIBS) \ + $(top_builddir)/gnulib/lib/.libs/libgnu.a + +guestfs_inspection_CPPFLAGS = \ + -I$(top_srcdir)/gnulib/lib \ + -I$(top_builddir)/gnulib/lib \ + -I$(top_srcdir)/daemon \ + -I$(top_builddir)/daemon \ + -I$(top_srcdir)/src \ + -I$(top_builddir)/src + +guestfs_inspection_CFLAGS = \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) \ + $(AUGEAS_CFLAGS) \ + $(HIVEX_CFLAGS) \ + $(PCRE_CFLAGS) + +# Manual pages and HTML files for the website. +if INSTALL_DAEMON +man_MANS = guestfs-inspection.8 +else +noinst_MANS = guestfs-inspection.8 +endif +noinst_DATA = $(top_builddir)/website/guestfs-inspection.8.html + +guestfs-inspection.8 $(top_builddir)/website/guestfs-inspection.8.html: stamp-guestfs-inspection.pod + +stamp-guestfs-inspection.pod: guestfs-inspection.pod + $(PODWRAPPER) \ + --section 8 \ + --man guestfs-inspection.8 \ + --html $(top_builddir)/website/guestfs-inspection.8.html \ + --license GPLv2+ \ + $< + touch $@ diff --git a/inspection/detect.c b/inspection/detect.c new file mode 100644 index 0000000..46b7c9e --- /dev/null +++ b/inspection/detect.c @@ -0,0 +1,130 @@ +/* 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_distro_from_os_release (const char *fs, char **distro) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_os_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_product_name_from_os_release (const char *fs, char **product_name) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_distro_from_lsb_release (const char *fs, char **distro) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_lsb_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_product_name_from_lsb_release (const char *fs, char **product_name) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_oracle_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_centos_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_altlinux_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +match_redhat_release_fedora (const char *fs) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +match_redhat_release_rhel (const char *fs) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +match_redhat_release_centos (const char *fs) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +match_redhat_release_scientific_linux (const char *fs) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_version_from_redhat_release (const char *fs, char **major, char **minor) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} + +int +get_product_name_from_release_file (const char *fs, const char *release_file, char **product_name) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return -1; +} diff --git a/inspection/facts.c b/inspection/facts.c new file mode 100644 index 0000000..fc4ca8c --- /dev/null +++ b/inspection/facts.c @@ -0,0 +1,318 @@ +/* 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 (void) __attribute__((constructor)); + +static void +init_facts (void) +{ + true_facts = gl_oset_create_empty (GL_ARRAY_OSET, compare_facts, free_fact); + false_facts = gl_oset_create_empty (GL_ARRAY_OSET, compare_facts, free_fact); +} + +static void free_facts (void) __attribute__((destructor)); + +static void +free_facts (void) +{ + gl_oset_free (true_facts); + gl_oset_free (false_facts); +} + +static void +clear_set (gl_oset_t set) +{ + const void *f; + gl_oset_iterator_t iter = gl_oset_iterator (set); + + while (gl_oset_iterator_next (&iter, &f)) { + gl_oset_remove (set, (void *) f); + } + gl_oset_iterator_free (&iter); +} + +void +clear_true_facts (void) +{ + clear_set (true_facts); +} + +void +clear_false_facts (void) +{ + clear_set (false_facts); +} + +size_t +count_true_facts (void) +{ + return gl_oset_size (true_facts); +} + +/* This is just for debugging facts. */ +void +print_fact (bool is_true, fact *f, FILE *fp) +{ + size_t i; + + if (!is_true) + fputc ('!', fp); + fputs (f->term_name, fp); + if (f->nr_term_args > 0) + fputc ('(', fp); + for (i = 0; i < f->nr_term_args; ++i) { + fputc ('"', fp); + fputs (f->term_arg[i], fp); + fputc ('"', fp); + if (i+1 != f->nr_term_args) + fputs (", ", fp); + } + if (f->nr_term_args > 0) + fputc (')', fp); +} + +static void +print_set (bool is_true, gl_oset_t set) +{ + const void *vf; + gl_oset_iterator_t iter = gl_oset_iterator (set); + + while (gl_oset_iterator_next (&iter, &vf)) { + fact *f = (fact *) vf; + print_fact (is_true, f, stdout); + printf ("\n"); + } + gl_oset_iterator_free (&iter); +} + +void +print_true_facts (void) +{ + print_set (true, true_facts); +} + +void +print_false_facts (void) +{ + print_set (false, false_facts); +} + +/* Look for every string parameter of every fact we know about, and + * add all those strings to the set. + */ +void +add_all_fact_strings (gl_oset_t set) +{ + const void *vf; + fact *f; + gl_oset_iterator_t iter; + size_t i; + + iter = gl_oset_iterator (true_facts); + while (gl_oset_iterator_next (&iter, &vf)) { + f = (fact *) vf; + for (i = 0; i < f->nr_term_args; ++i) + gl_oset_add (set, f->term_arg[i]); + } + gl_oset_iterator_free (&iter); + + iter = gl_oset_iterator (false_facts); + while (gl_oset_iterator_next (&iter, &vf)) { + f = (fact *) vf; + for (i = 0; i < f->nr_term_args; ++i) + gl_oset_add (set, f->term_arg[i]); + } + gl_oset_iterator_free (&iter); +} + +/* Look for every string parameter in the specific argument position + * of the specific term name (both true and false), and add those + * strings only to the set. + */ +void +add_strings_from_facts (gl_oset_t set, const char *term_name, size_t arg_i) +{ + const void *vf; + fact *f; + gl_oset_iterator_t iter; + + iter = gl_oset_iterator (true_facts); + while (gl_oset_iterator_next (&iter, &vf)) { + f = (fact *) vf; + if (strcmp (f->term_name, term_name) == 0) + gl_oset_add (set, f->term_arg[arg_i]); + } + gl_oset_iterator_free (&iter); + + iter = gl_oset_iterator (false_facts); + while (gl_oset_iterator_next (&iter, &vf)) { + f = (fact *) vf; + if (strcmp (f->term_name, term_name) == 0) + gl_oset_add (set, f->term_arg[arg_i]); + } + gl_oset_iterator_free (&iter); +} + +/* NB: This does not make a deep copy of the strings. However before + * we add the fact to the true_facts or false_facts arrays, we do call + * deep_copy to copy the strings. + */ +fact * +create_fact (const char *term_name, size_t n, ...) +{ + fact *f; + size_t i; + va_list args; + + f = malloc (sizeof (*f) + n * sizeof (char *)); + if (f == NULL) + error (EXIT_FAILURE, errno, "malloc"); + va_start (args, term_name); + for (i = 0; i < n; ++i) { + const char *p = va_arg (args, const char *); + f->term_arg[i] = (char *) p; + } + va_end (args); + f->term_name = (char *) term_name; + f->nr_term_args = n; + return f; /* caller must free only the struct */ +} + +static fact * +deep_copy_fact (const fact *f) +{ + fact *ret; + size_t i; + + ret = malloc (sizeof (*ret) + f->nr_term_args * sizeof (char *)); + if (ret == NULL) + error (EXIT_FAILURE, errno, "malloc"); + + ret->term_name = strdup (f->term_name); + if (ret->term_name == NULL) + error (EXIT_FAILURE, errno, "strdup"); + + ret->nr_term_args = f->nr_term_args; + + for (i = 0; i < f->nr_term_args; ++i) { + ret->term_arg[i] = strdup (f->term_arg[i]); + if (ret->term_arg[i] == NULL) + error (EXIT_FAILURE, errno, "strdup"); + } + + return ret; +} + +bool +is_fact (bool is_true, const fact *f) +{ + return gl_oset_search (is_true ? true_facts : false_facts, f); +} + +bool +add_fact (bool is_true, const fact *f) +{ + fact *f2 = deep_copy_fact (f); + bool ret; + + ret = gl_oset_add (is_true ? true_facts : false_facts, f2); + + /* Didn't add it, so we must free the deep copy. */ + if (!ret) + free_fact (f2); + + return ret; +} diff --git a/inspection/guestfs-inspection.pod b/inspection/guestfs-inspection.pod new file mode 100644 index 0000000..790ec58 --- /dev/null +++ b/inspection/guestfs-inspection.pod @@ -0,0 +1,442 @@ +=head1 NAME + +guestfs-inspection - guestfs inspection program + +=head1 SYNOPSIS + + guestfs-inspection + +=head1 NOTE + +This man page documents the guestfs inspection program. If you want +to read about guestfs inspection then this is the wrong place. See +L<guestfs(3)/INSPECTION> instead. + +=head1 DESCRIPTION + +C<guestfs-inspection> is a standalone program that performs inspection +on the local disks, to find out what operating system(s) are +installed. It normally runs inside the libguestfs appliance, started +by L<guestfsd(8)>, when the caller uses the C<guestfs_inspect_os> API +(see L<guestfs-internals(1)> and L<guestfs(3)/guestfs_inspect_os>). +You should never need to run this program by hand. + +The program looks at all disks attached to the appliance, looking for +filesystems that might belong to operating systems. It may mount +these temporarily to examine them for Linux configuration files, +Windows Registries, and so on. It then tries to determine what +operating system(s) are installed on the disks. It is able to detect +many different Linux distributions, Windows, BSD, and others. The +currently mounted root filesystem is ignored, since when running under +libguestfs, that filesystem is part of the libguestfs appliance (this +is the main difference compared to programs like C<facter>). + +Guestfs-inpection is written in C, but most of the C is generated by a +rules compiler from a set of inspection rules written in a more +compact, declarative, Prolog-inspired language. If you want to write +or modify the rules, see L</WRITING RULES> below. + +=head1 OPTIONS + +=over 4 + +=item B<-?> + +=item B<--help> + +Display brief help. + +=item B<-v> + +=item B<--verbose> + +Enable verbose messages for debugging. + +=back + +=head1 WRITING RULES + +Inspection is performed according to a set of rules written in a +compact, declarative, Prolog-inspired language. This section explains +how this language works, so you can write your own rules to detect +other operating systems. + +The rules can be found starting in C<inspection/inspection.rules> (in +the libguestfs sources). The rules are compiled down to C and linked +into the guestfs-inspection program, together with a bit of extra C +code to provide runtime support. + +=head2 Facts + +Facts are what we try to determine about the operating system(s) we +are inspecting. They look like this: + + Filesystem("/dev/sda1") + +which means "F</dev/sda1> is a filesystem". + + File("/dev/sda1", "/etc/fstab") + +which means "there exists a file called F</etc/fstab> on the +F</dev/sda1> filesystem". + +Facts come in three flavours: true facts, false facts, and unknown +facts. False facts are written like this: + + ! File("/dev/sda1", "/etc/fstab") + +which means "either F</dev/sda1> is not a filesystem or there does not +exist a file called F</etc/fstab> on this filesystem". + +Unknown facts are facts that we don't know if they are true or false +yet. + +=head2 Rules + +Rules are used to generate more facts. A simple rule for generating +C<File> facts might look like this: + + File(fs, filename) :- + Filesystem(fs), + {{ + // some C code to mount 'fs' and check for 'filename' + }}. + +You can read this as: "For all C<fs> & C<filename>, if C<fs> is a +filesystem, and running the C code with parameters C<fs> and +C<filename> returns true, then C<File(fs, filename)> is a true fact". + +In the Prolog-inspired language, a comma (C<,>) is the AND operator. +A semicolon (C<;>) is the OR operator. C<:-> is a backwards +if-statement (the condition is on the right, the conclusion is on the +left). Also notice the dot (C<.>) which must follow each rule. + +Uppercase identifiers are facts. Lowercase identifiers are variables. +All identifiers are case-sensitive. + +Everything in C<{{ ... }}> is embedded C code. In this case the C +code returns a true/false/error indication, but embedded C code can +also do more complicated things and return strings and lists as we'll +see later. + +You can use parentheses C<(...)> for grouping expressions on the right +hand side of the C<:-> operator. + +=head2 Program evaluation + +Let's take a simple set of rules which you might use to detect a +Fedora root filesystem: + + File(fs, filename) :- + Filesystem(fs), + {{ + // some C code to mount 'fs' and check for 'filename' + }}. + + Fedora(rootfs) :- + Filesystem(rootfs), + File(rootfs, "/etc/fedora-release"). + +When evaluating this program, there are two sets of facts, the true +facts and the false facts. Let's start with the false facts set being +empty, and let's seed the true facts set with some C<Filesystem> +facts: + + true_facts = { Filesystem("/dev/sda1"), Filesystem("/dev/sda3") } + false_facts = { } // empty set + +Unknown facts are facts which don't appear in either set. + +Evaluating the program works like this: We consider each rule in turn, +and see if we can find new true or false facts from it. These new +facts are added to the true or false facts sets. After looking at +each rule in the program, as long as at least one new fact was added +to the true facts set, we go back to the start of the rules and repeat +over. We do this until we can no longer add any new true facts, and +then we're done. + +In the case of this program, we start with the C<File> rule, and we +substitute (theoretically) every possible string for C<fs> and +C<filename>. + +For example, this substitution: + + File("/dev/sda1", "/etc/fedora-release") :- + Filesystem("/dev/sda1"), + {{ // checks for file and returns false }}. + +turns out to be false (because the C code doesn't find F</etc/fstab> +in F</dev/sda1>), so that yields a new false fact: + + ! File("/dev/sda1", "/etc/fedora-release") + +But this substitution turns out to be true: + + File("/dev/sda3", "/etc/fedora-release") :- + Filesystem("/dev/sda3"), + {{ // checks for file and returns true }}. + +so that yields a new true fact: + + File("/dev/sda3", "/etc/fedora-release") + +In theory every possible string is tried, eg C<File("ardvark", "foo123654")>. +That would take literally forever to run, but luckily the rules +compiler is smarter. + +Looking now at the second rule, we try this substitution: + + Fedora("/dev/sda3") :- + Filesystem("/dev/sda3"), + File("/dev/sda3", "/etc/fedora-release"). + +which yields another new true fact: + + Fedora("/dev/sda3") + +Because we added several new true facts to the set, we go back and +repeat the whole process. But after trying all the rules for a second +time, no more true facts can be added, so now we're done. + +At the end, the set of true facts is: + + true_facts = { Filesystem("/dev/sda1"), Filesystem("/dev/sda3"), + File("/dev/sda3", "/etc/fedora-release"), + Fedora("/dev/sda3") } + +We don't care about the false facts -- they are discarded at the end +of the program. + +The summary of inspection is that F</dev/sda3> contains a Fedora root +filesystem. + +Of course real inspection is much more complicated than this, but the +same program evaluation order is followed. + +=head2 Some caveats with the language + +It's easy to look at an expression like: + + Fedora(rootfs) :- + Filesystem(rootfs), + File(rootfs, "/etc/fedora-release"). /* line 3 */ + +and think that line 3 is "calling" the "File function". This is +B<not> what is happening! Rules are not functions. Rules are +considered in isolation. Rules don't "call" other rules. Instead +when trying to find possible values that can be substituted into a +rule, we only look at the rule and the current sets of true and false +facts. + +When searching for values to subsitute, in theory the compiler would +have to look at every possible string. In practice of course it can't +and doesn't do that. Instead it looks at the current sets of true and +false facts to find strings to substitute. In the following rule: + + File(fs, filename) :- + Filesystem(fs), + {{ // C code }}. + +suitable choices for C<fs> are found by looking at any C<Filesystem> +facts in either the true or false sets. + +In some cases, this doesn't work, as in the example above where we +have no clues for the C<filename> variable. In that case the compiler +tries every string literal from every rule in the program. This can +be inefficient, but by modifying the rule slightly you can avoid this. +In the following program, only the strings F</etc/fstab> and +F</etc/fedora-release> would be tried: + + Filename("/etc/fstab"). + Filename("/etc/fedora-release"). + File(fs, filename) :- + Filesystem(fs), + Filename(filename), + {{ // C code }}. + +=head2 C expressions returning boolean + +Simple C code enclosed in C<{{ ... }}> as shown above should return a +true, false or error status only. It returns true by returning any +integer E<ge> 1. It should return C<0> to indicate false, and it +should return C<-1> to indicate an error (which stops the program and +causes inspection to fail with a user-visible error). + +Here is an example of a simple C expression returning a boolean: + + File(fs, filename) :- + Filesystem(fs), + {{ + int r; + char *relative_filename; + r = get_mount (fs, filename, &relative_filename); + if (r != 1) return r; + r = access (relative_filename, F_OK); + free (relative_filename); + if (r == -1) { + if (errno == ENOENT || errno == ENOTDIR) + return 0; + perror ("access"); + return -1; + } + return 1; + }}. + +Notice that C<fs> and C<filename> are passed into the C code as local +variables. + +You can see that dealing with errors is a bit involved, because we +want to fail hard if some error like C<EIO> is thrown. + +=head2 C expressions returning strings + +C expressions can also return strings or tuples of strings. This is +useful where you want to parse the content of external files. + +The syntax for this sort of C expression is: + + (var1, var2, ...)={{ ... }} + +where C<var1>, C<var2>, etc. are outputs from the C code. + +In the following example, a lot of error checking has been omitted +for clarity: + + ProductName(fs, product_name) :- + Unix_root(fs), + Distro(fs, "RHEL"), + (product_name)={{ + int r; + char *line = NULL; + size_t n; + char *relative_filename; + r = get_mount (fs, "/etc/redhat-release", &relative_filename); + FILE *fp = fopen (relative_filename, "r"); + free (relative_filename); + getline (&line, &n, fp); + fclose (fp); + set_product_name (line); + free (line); + return 0; + }}. + +The C code calls a function C<set_product_name> (that the compiler +generates). + +The return value from the C code should be C<0> if everything was OK, +or C<-1> if there is a error (which stops the whole program). + +=head2 C expressions returning multiple results + +Finally it is possible for C code to return multiple results. + +The syntax is: + + (var1, var2, ...)*={{ ... }} + + (var1, var2, ...)?={{ ... }} + + (var1, var2, ...)+={{ ... }} + +where C<var1>, C<var2>, etc. are outputs. Unlike the previous rules, +these rules may generate zero or multiple facts from a single string +substitution. + +For example, here is how we could populate a list of C<Filesystem> +facts: + + Filesystem(fs) :- + (fs)*={{ + int i; + extern char **fs; + for (i = 0; fs[i] != NULL; ++i) { + set_fs (fs[i]); + } + return 0; + }}. + +In this case, the C code repeatedly calls a function C<set_fs> (that +the compiler generates) for each new filesystem discovered. Multiple +C<Filesystem> facts can be generated as a result of one application of +this rule. + +As with regular expressions, C<*> means we expect zero or more rows of +results, C<?> means zero or one, and C<+> means one or more. The +generated target code checks that you call the C<set_*> function the +correct number of times. + +The return value from the C code should be C<0> if everything was OK, +or C<-1> if there is a error (which stops the whole program). + +=head2 C code memoization + +Although currently B<not implemented>, in future we will implement +memoization of C code. This means that for every input, the C code +will be called only once, making it less important that C code is +efficient: no matter how many times we need to evaluate if +F</etc/fstab> exists on F</dev/sda1>, the C code would only be called +once. + +This means that impure C functions returning different results for the +same input won't work in future, so don't do that. + +=head2 Type checking + +The current language treats every value as a string. Every expression +is a boolean. One possible future enhancement is to handle other +types. There is still some minimal type checking applied: + +=over 4 + +=item * + +A fact name which appears on a right hand side of any rule must also +appear on the left hand side of a rule. This is mainly for catching +typos. + +=item * + +A fact must have the same number of arguments ("arity") each time it +appears in the source. + +=back + +=head2 Debugging + +You can debug the evaluation of inspection programs by calling +C<guestfs_set_verbose> (or setting C<$LIBGUESTFS_DEBUG=1>) before +launching the handle. + +This causes L<guestfsd(8)> to pass the I<--verbose> parameter to this +inspection program, which in turn causes the inspection program to +print information about what rules it is trying and what true/false +facts it has found. These are passed back to libguestfs and printed +on C<stderr> (or sent to the event system if you are using that). + +You can also print debug messages from C code embedded in C<{{...}}> +expressions. These are similarly sent upwards through to libguestfs +and will appear on C<stderr>. + +Remember that C memoization [to be implemented in a future version] +can cause C code to run fewer times than expected. + +=head1 EXIT STATUS + +This program returns 0 if successful, or non-zero if there was an +error. + +=head1 SEE ALSO + +L<guestfsd(8)>, +L<guestfs-hacking(1)>, +L<guestfs-internals(1)>, +L<guestfs(3)/INSPECTION>, +L<http://libguestfs.org/>. + +=head1 AUTHOR + +Richard W.M. Jones L<http://people.redhat.com/~rjones/> + +=head1 COPYRIGHT + +Copyright (C) 2009-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..c0feda0 --- /dev/null +++ b/inspection/inspection.h @@ -0,0 +1,79 @@ +/* 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" + +#include "cleanups.h" +#include "command.h" + +extern int verbose; + +/* detect.c - used by rules */ +extern int get_distro_from_os_release (const char *fs, char **distro); +extern int get_version_from_os_release (const char *fs, char **major, char **minor); +extern int get_product_name_from_os_release (const char *fs, char **product_name); +extern int get_distro_from_lsb_release (const char *fs, char **distro); +extern int get_version_from_lsb_release (const char *fs, char **major, char **minor); +extern int get_product_name_from_lsb_release (const char *fs, char **product_name); +extern int get_version_from_oracle_release (const char *fs, char **major, char **minor); +extern int get_version_from_centos_release (const char *fs, char **major, char **minor); +extern int get_version_from_altlinux_release (const char *fs, char **major, char **minor); +extern int match_redhat_release_fedora (const char *fs); +extern int match_redhat_release_rhel (const char *fs); +extern int match_redhat_release_centos (const char *fs); +extern int match_redhat_release_scientific_linux (const char *fs); +extern int get_version_from_redhat_release (const char *fs, char **major, char **minor); +extern int get_product_name_from_release_file (const char *fs, const char *release_file, char **product_name); + +/* facts.c */ +typedef struct fact fact; +extern void clear_true_facts (void); +extern void clear_false_facts (void); +extern size_t count_true_facts (void); +extern void print_fact (bool is_true, 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, size_t n, ...); +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 rules */ +extern char **get_all_block_devices (void); +extern char **get_all_partitions (void); +extern char **get_all_mddevs (void); +extern char **get_all_lvs (void); +extern char **get_all_ldmvols (void); +extern char **get_all_ldmparts (void); +extern char **get_all_btrfs_subvolumes (const char *fs); +extern char *get_vfs_type (const char *fs); +extern int get_partition_mbr_id (const char *fs); +extern int is_mountable (const char *fs); +extern int get_mount (const char *fs, const char *filename, char **relative_filename); + +#endif /* GUESTFS_INSPECTION_H */ diff --git a/inspection/inspection.rules b/inspection/inspection.rules new file mode 100644 index 0000000..d3fba8f --- /dev/null +++ b/inspection/inspection.rules @@ -0,0 +1,472 @@ +/* Libguestfs inspection rules -*- prolog -*- + * 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. + */ + +/* To understand what's going on here, it's recommended that you read + * guestfs-inspection(8) (inspection/guestfs-inspection.pod) first. + */ + +/* Whole block devices. */ +BlockDevice(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_block_devices (); + if (devs == NULL) return -1; + for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); + return 0; + }}. + +/* Partitions. */ +Partition(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_partitions (); + if (devs == NULL) return -1; + /* Ignore partitions with type byte 0x42 (RHBZ#887520). */ + for (size_t i = 0; devs[i] != NULL; ++i) { + int r = get_partition_mbr_id (devs[i]); + if (r == -1) return -1; + if (r != 0x42) set_dev (devs[i]); + } + return 0; + }}. + +/* LVM2 logical volumes. */ +LV(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_lvs (); + if (devs == NULL) return -1; + for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); + return 0; + }}. + +/* /dev/md* devices. */ +MDDev(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_mddevs (); + if (devs == NULL) return -1; + for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); + return 0; + }}. + +/* Windows LDM voumes. */ +LDMVol(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_ldmvols (); + if (devs == NULL) return -1; + for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); + return 0; + }}. + +/* Windows LDM partitions. */ +LDMPart(dev) :- + (dev)*={{ + CLEANUP_FREE_STRING_LIST char **devs = get_all_ldmparts (); + if (devs == NULL) return -1; + for (size_t i = 0; devs[i] != NULL; ++i) set_dev (devs[i]); + return 0; + }}. + +/* Device(dev) is just a group name for block devices, partitions etc. */ +Device(dev) :- + BlockDevice(dev); Partition(dev); LV(dev); + MDDev(dev); LDMVol(dev); LDMPart(dev). + +/* Map a filesystem to its VFS type (from blkid). */ +VFSType(fs, vfs_type) :- + Device(fs), + (vfs_type)?={{ + CLEANUP_FREE char *vfs_type = get_vfs_type (fs); + if (vfs_type != NULL) set_vfs_type (vfs_type); + return 0; + }}. + +/* A device contains a mountable filesystem (not swap, empty, etc). */ +Mountable(fs) :- + Device(fs), + {{ return is_mountable (fs); }}. + +/* Where a filesystem is btrfs and mountable, get the subvolumes. */ +BtrfsSubvolume(subvol) :- + Device(fs), + Mountable(fs), + VFSType(fs, "btrfs"), + (subvol)*={{ + size_t i; + CLEANUP_FREE_STRING_LIST char **paths = get_all_btrfs_subvolumes (fs); + if (paths == NULL) return -1; + for (i = 0; paths[i] != NULL; ++i) { + CLEANUP_FREE char *subvol; + if (asprintf (&subvol, "btrfsvol:%s/%s", fs, paths[i]) == -1) { + perror ("asprintf"); + exit (EXIT_FAILURE); + } + set_subvol (subvol); + } + return 0; + }}. +VFSType(subvol, "btrfs") :- BtrfsSubvolume(subvol). + +/* Ignore all *_member types. In libblkid these are returned + * for things which are members of some RAID or LVM set, most + * importantly "LVM2_member" which is a PV. Also ignore + * crypto_LUKS (LUKS encrypted partition). + */ +ContainerDevice(dev) :- + VFSType(dev, vfs_type), + {{ return STRSUFFIX (vfs_type, "_member"); }}. +ContainerDevice(dev) :- + VFSType(dev, "crypto_LUKS"). + +/* Ignore all swap devices. */ +SwapDevice(dev) :- VFSType(dev, "swap"). + +/* This rule generates one Filesystem(fs) fact per mountable + * filesystem found in the appliance. A filesystem could be + * a device, partition, LV, btrfs subvolume, etc. + */ +Filesystem(fs) :- + !ContainerDevice(fs), + !SwapDevice(fs), + (Device(fs), Mountable(fs); BtrfsSubvolume(fs)). + +/* File(fs, filename) is true if filename is a regular file in fs. + * It also follows symlinks. + */ +File(fs, filename) :- + Filesystem(fs), + {{ + int r; + CLEANUP_FREE char *relative_filename = NULL; + struct stat statbuf; + + if (filename[0] != '/') return 0; + r = get_mount (fs, filename, &relative_filename); + if (r != 1) return r; + r = stat (relative_filename, &statbuf); + if (r == -1) { + if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP) + return 0; + perror (relative_filename); + return -1; + } + return S_ISREG (statbuf.st_mode); + }}. + +/* Directory(fs, dirname) is true if dirname is a directory in fs. + * It also follows symlinks. + */ +Directory(fs, dirname) :- + Filesystem(fs), + {{ + int r; + CLEANUP_FREE char *relative_dirname = NULL; + struct stat statbuf; + + if (dirname[0] != '/') return 0; + r = get_mount (fs, dirname, &relative_dirname); + if (r != 1) return r; + r = stat (relative_dirname, &statbuf); + if (r == -1) { + if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP) + return 0; + perror (relative_dirname); + return -1; + } + return S_ISDIR (statbuf.st_mode); + }}. + +/* Symlink(fs, filename) is true if filename is a symlink. */ +Symlink(fs, filename) :- + Filesystem(fs), + {{ + int r; + CLEANUP_FREE char *relative_filename = NULL; + struct stat statbuf; + + if (filename[0] != '/') return 0; + r = get_mount (fs, filename, &relative_filename); + if (r != 1) return r; + r = stat (relative_filename, &statbuf); + if (r == -1) { + if (errno == ENOENT || errno == ENOTDIR || errno == ELOOP) + return 0; + perror (relative_filename); + return -1; + } + return S_ISLNK (statbuf.st_mode); + }}. + +/* grub or grub2 /boot */ +GrubBoot(fs) :- + Filesystem(fs), + File(fs, "/grub/menu.lst"), + File(fs, "/grub/grub.conf"), + File(fs, "/grub2/grub.cfg"). + +/* FreeBSD root. */ +FreeBSDRoot(fs) :- + Filesystem(fs), + Directory(fs, "/bin"), + Directory(fs, "/etc"), + File(fs, "/etc/freebsd-update.conf"), + File(fs, "/etc/fstab"). + +/* NetBSD root. */ +NetBSDRoot(fs) :- + Filesystem(fs), + Directory(fs, "/bin"), + Directory(fs, "/etc"), + File(fs, "/netbsd"), + File(fs, "/etc/fstab"), + File(fs, "/etc/release"). + +/* OpenBSD root. */ +OpenBSDRoot(fs) :- + Filesystem(fs), + Directory(fs, "/bin"), + Directory(fs, "/etc"), + File(fs, "/bsd"), + File(fs, "/etc/fstab"), + File(fs, "/etc/motd"). + +/* Hurd root. */ +HurdRoot(fs) :- + Filesystem(fs), + File(fs, "/hurd/console"), + File(fs, "/hurd/hello"), + File(fs, "/hurd/null"). + +/* Minix root. */ +MinixRoot(fs) :- + Filesystem(fs), + File(fs, "/service/vm"), + File(fs, "/etc/fstab"), + File(fs, "/etc/version"). + +/* Linux root (any distro). */ +LinuxRoot(fs) :- + Filesystem(fs), + Directory(fs, "/etc"), + File(fs, "/etc/fstab"), + (Directory(fs, "/bin"); Symlink(fs, "/bin")), + !FreeBSDRoot(fs), + !NetBSDRoot(fs), + !OpenBSDRoot(fs), + !HurdRoot(fs), + !MinixRoot(fs). + +/* Is it Linux using /etc/os-release? (Linux systemd). */ +LinuxRootWithOSRelease(fs) :- + LinuxRoot(fs), + File(fs, "/etc/os-release"). + +Distro(fs, distro) :- + LinuxRootWithOSRelease(fs), + (distro)?={{ + int r; + CLEANUP_FREE char *distro = NULL; + if ((r = get_distro_from_os_release (fs, &distro)) <= 0) + return r; + set_distro (distro); + return 0; + }}. + +Version(fs, major, minor) :- + LinuxRootWithOSRelease(fs), + (major, minor)?={{ + int r; + CLEANUP_FREE char *major = NULL, *minor = NULL; + if ((r = get_version_from_os_release (fs, &major, &minor)) <= 0) + return r; + set_major_minor (major, minor); + return 0; + }}. + +ProductName(fs, product_name) :- + LinuxRootWithOSRelease(fs), + (product_name)?={{ + int r; + CLEANUP_FREE char *product_name = NULL; + if ((r = get_product_name_from_os_release (fs, &product_name)) <= 0) + return r; + set_product_name (product_name); + return 0; + }}. + +/* Is it Linux using /etc/lsb-release? (Linux Standards Base). */ +LinuxRootWithLSBRelease(fs) :- + LinuxRoot(fs), + File(fs, "/etc/lsb-release"), + !LinuxRootWithOSRelease(fs). /* prefer /etc/os-release */ + +Distro(fs, distro) :- + LinuxRootWithLSBRelease(fs), + (distro)?={{ + int r; + CLEANUP_FREE char *distro = NULL; + if ((r = get_distro_from_lsb_release (fs, &distro)) <= 0) + return r; + set_distro (distro); + return 0; + }}. + +Version(fs, major, minor) :- + LinuxRootWithLSBRelease(fs), + (major, minor)?={{ + int r; + CLEANUP_FREE char *major = NULL, *minor = NULL; + if ((r = get_version_from_lsb_release (fs, &major, &minor)) <= 0) + return r; + set_major_minor (major, minor); + return 0; + }}. + +ProductName(fs, product_name) :- + LinuxRootWithLSBRelease(fs), + (product_name)?={{ + int r; + CLEANUP_FREE char *product_name = NULL; + if ((r = get_product_name_from_lsb_release (fs, &product_name)) <= 0) + return r; + set_product_name (product_name); + return 0; + }}. + +/* Fall back on /etc/*-release files to determine the distro, version and + * product name. + */ +LinuxRootWithReleaseFile(fs, "/etc/oracle-release") :- + LinuxRoot(fs), + !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs), + File(fs, "/etc/oracle-release"). +Distro(fs, "oracle-linux") :- + LinuxRootWithReleaseFile(fs, "/etc/oracle-release"). +Version(fs, major, minor) :- + LinuxRootWithReleaseFile(fs, "/etc/oracle-release"), + (major, minor)={{ + CLEANUP_FREE char *major = NULL, *minor = NULL; + if (get_version_from_oracle_release (fs, &major, &minor) == -1) + return -1; + set_major_minor (major, minor); + return 0; + }}. + +LinuxRootWithReleaseFile(fs, "/etc/centos-release") :- + LinuxRoot(fs), + !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs), + File(fs, "/etc/centos-release"). +Distro(fs, "centos") :- + LinuxRootWithReleaseFile(fs, "/etc/centos-release"). +Version(fs, major, minor) :- + LinuxRootWithReleaseFile(fs, "/etc/centos-release"), + (major, minor)={{ + CLEANUP_FREE char *major = NULL, *minor = NULL; + if (get_version_from_centos_release (fs, &major, &minor) == -1) + return -1; + set_major_minor (major, minor); + return 0; + }}. + +LinuxRootWithReleaseFile(fs, "/etc/altlinux-release") :- + LinuxRoot(fs), + !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs), + File(fs, "/etc/altlinux-release"). +Distro(fs, "altlinux") :- + LinuxRootWithReleaseFile(fs, "/etc/altlinux-release"). +Version(fs, major, minor) :- + LinuxRootWithReleaseFile(fs, "/etc/altlinux-release"), + (major, minor)={{ + CLEANUP_FREE char *major = NULL, *minor = NULL; + if (get_version_from_altlinux_release (fs, &major, &minor) == -1) + return -1; + set_major_minor (major, minor); + return 0; + }}. + +/* Handle the fallback case of /etc/redhat-release. */ +LinuxRootWithReleaseFile(fs, "/etc/redhat-release") :- + LinuxRoot(fs), + !LinuxRootWithOSRelease(fs), !LinuxRootWithLSBRelease(fs), + !LinuxRootWithReleaseFile(fs, "/etc/oracle-release"), + !LinuxRootWithReleaseFile(fs, "/etc/centos-release"), + !LinuxRootWithReleaseFile(fs, "/etc/altlinux-release"), + File(fs, "/etc/redhat-release"). +Distro(fs, "fedora") :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + {{ return match_redhat_release_fedora (fs); }}. +Distro(fs, "rhel") :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + {{ return match_redhat_release_rhel (fs); }}. +Distro(fs, "centos") :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + {{ return match_redhat_release_centos (fs); }}. +Distro(fs, "scientificlinux") :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + {{ return match_redhat_release_scientific_linux (fs); }}. +Distro(fs, "redhat-based") :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + !Distro(fs, "fedora"), + !Distro(fs, "rhel"), + !Distro(fs, "centos"), + !Distro(fs, "scientificlinux"). + +Version(fs, major, minor) :- + LinuxRootWithReleaseFile(fs, "/etc/redhat-release"), + (major, minor)={{ + CLEANUP_FREE char *major = NULL, *minor = NULL; + if (get_version_from_redhat_release (fs, &major, &minor) == -1) + return -1; + set_major_minor (major, minor); + return 0; + }}. + +/* Get the product name from a generic Linux release file. */ +ProductName(fs, product_name) :- + LinuxRootWithReleaseFile(fs, release_file), + (product_name)={{ + CLEANUP_FREE char *product_name = NULL; + if (get_product_name_from_release_file (fs, release_file, + &product_name) == -1) + return -1; + set_product_name (product_name); + return 0; + }}. + +/* XXX debian, arch-linux etc release files */ + +/* XXX Linux architecture, fstab, hostname */ + + + +/* XXX CoreOS etc. */ + + + + + + + + +/* +Has_fstab(rootfs) :- + Unix_root(fs), + File(rootfs, "/etc/fstab"). +Mount(rootfs, dev, mountpoint) :- + Has_fstab(rootfs), + [dev, mountpoint]={{ + // code to return (dev, mountpoint) pairs from /etc/fstab + }}. +*/ diff --git a/inspection/mount.c b/inspection/mount.c new file mode 100644 index 0000000..2186bbd --- /dev/null +++ b/inspection/mount.c @@ -0,0 +1,591 @@ +/* guestfs-inspection + * Copyright (C) 2009-2015 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mount.h> +#include <errno.h> +#include <error.h> + +#include "c-ctype.h" + +#include "guestfs-internal-all.h" + +#include "inspection.h" +#include "stringsbuf.h" + +/* If root device is an ext2 filesystem, this is the major and minor. + * This is so we can ignore this device from the point of view of the + * user, eg. in guestfs_list_devices and many other places. + */ +static dev_t root_device = 0; + +/* A temporary directory where we place all the mountpoints. */ +static char mountpoints[] = "/tmp/mp.XXXXXX"; + +static void init_mount (void) __attribute__((constructor)); + +static void +init_mount (void) +{ + struct stat statbuf; + + if (stat ("/", &statbuf) == 0) + root_device = statbuf.st_dev; + + if (mkdtemp (mountpoints) == NULL) + perror ("mkdtemp"); +} + +static void free_mount (void) __attribute__((destructor)); + +static void +free_mount (void) +{ + DIR *dir; + struct dirent *d; + + /* Unmount all mountpoints in the temporary directory, then + * delete those directories and the parent. + */ + dir = opendir (mountpoints); + if (!dir) { + perror (mountpoints); + return; + } + + for (;;) { + errno = 0; + d = readdir (dir); + if (!d) break; + + if (d->d_name[0] != '.') { + CLEANUP_FREE char *mp; + + if (asprintf (&mp, "%s/%s", mountpoints, d->d_name) == -1) { + perror ("asprintf"); + continue; + } + + if (umount2 (mp, MNT_DETACH) == -1) /* lazy umount */ + perror (mp); + + if (rmdir (mp) == -1) + perror (mp); + } + } + + /* Check readdir didn't fail */ + if (errno != 0) { + perror ("readdir"); + return; + } + + /* Close the directory handle */ + if (closedir (dir) == -1) { + perror ("closedir"); + return; + } + + if (rmdir (mountpoints) == -1) + perror (mountpoints); +} + +/* Return true iff device is the root device (and therefore should be + * ignored from the point of view of user calls). + */ +static int +is_root_device_stat (struct stat *statbuf) +{ + if (statbuf->st_rdev == root_device) return 1; + return 0; +} + +static int +is_root_device (const char *device) +{ + struct stat statbuf; + + if (stat (device, &statbuf) == -1) { + perror (device); + return 0; + } + + return is_root_device_stat (&statbuf); +} + +typedef int (*block_dev_func_t) (const char *dev, struct stringsbuf *r); + +/* Execute a given function for each discovered block device */ +static char ** +foreach_block_device (block_dev_func_t func) +{ + DECLARE_STRINGSBUF (r); + DIR *dir; + struct dirent *d; + char dev_path[256]; + int fd; + bool err = false; + + dir = opendir ("/sys/block"); + if (!dir) { + perror ("opendir: /sys/block"); + return NULL; + } + + for (;;) { + errno = 0; + d = readdir (dir); + if (!d) break; + + if (STREQLEN (d->d_name, "sd", 2) || + STREQLEN (d->d_name, "hd", 2) || + STREQLEN (d->d_name, "ubd", 3) || + STREQLEN (d->d_name, "vd", 2) || + STREQLEN (d->d_name, "sr", 2)) { + snprintf (dev_path, sizeof dev_path, "/dev/%s", d->d_name); + + /* Ignore the root device. */ + if (is_root_device (dev_path)) + continue; + + /* RHBZ#514505: Some versions of qemu <= 0.10 add a + * CD-ROM device even though we didn't request it. Try to + * detect this by seeing if the device contains media. + */ + fd = open (dev_path, O_RDONLY|O_CLOEXEC); + if (fd == -1) { + perror (dev_path); + continue; + } + close (fd); + + /* Call the map function for this device */ + if ((*func)(d->d_name, &r) != 0) { + err = true; + break; + } + } + } + + /* Check readdir didn't fail */ + if (errno != 0) { + perror ("readdir: /sys/block"); + free_stringslen (r.argv, r.size); + closedir (dir); + return NULL; + } + + /* Close the directory handle */ + if (closedir (dir) == -1) { + perror ("closedir: /sys/block"); + free_stringslen (r.argv, r.size); + return NULL; + } + + /* Free the result list on error */ + if (err) { + free_stringslen (r.argv, r.size); + return NULL; + } + + /* Sort the devices. */ + if (r.size > 0) + sort_device_names (r.argv, r.size); + + /* NULL terminate the list */ + end_stringsbuf (&r); + + return r.argv; +} + +/* Add a device to the list of devices */ +static int +add_device (const char *device, struct stringsbuf *r) +{ + char dev_path[256]; + snprintf (dev_path, sizeof dev_path, "/dev/%s", device); + + add_string (r, dev_path); + + return 0; +} + +char ** +get_all_block_devices (void) +{ + return foreach_block_device (add_device); +} + +static int +add_partitions (const char *device, struct stringsbuf *r) +{ + char devdir[256]; + + /* Open the device's directory under /sys/block */ + snprintf (devdir, sizeof devdir, "/sys/block/%s", device); + + DIR *dir = opendir (devdir); + if (!dir) { + perror (devdir); + free_stringslen (r->argv, r->size); + return -1; + } + + /* Look in /sys/block/<device>/ for entries starting with <device> + * e.g. /sys/block/sda/sda1 + */ + errno = 0; + struct dirent *d; + while ((d = readdir (dir)) != NULL) { + if (STREQLEN (d->d_name, device, strlen (device))) { + char part[256]; + snprintf (part, sizeof part, "/dev/%s", d->d_name); + + add_string (r, part); + } + } + + /* Check if readdir failed */ + if (0 != errno) { + perror (devdir); + free_stringslen (r->argv, r->size); + closedir (dir); + return -1; + } + + /* Close the directory handle */ + if (closedir (dir) == -1) { + perror (device); + free_stringslen (r->argv, r->size); + return -1; + } + + return 0; +} + +char ** +get_all_partitions (void) +{ + return foreach_block_device (add_partitions); +} + +char ** +get_all_mddevs (void) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return NULL; +} + +char ** +get_all_lvs (void) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return NULL; +} + +char ** +get_all_ldmvols (void) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return NULL; +} + +char ** +get_all_ldmparts (void) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return NULL; +} + +char ** +get_all_btrfs_subvolumes (const char *fs) +{ + error (EXIT_FAILURE, 0, "%s: not implemented", __func__); + return NULL; +} + +static char * +get_blkid_tag (const char *device, const char *tag) +{ + char *out; + CLEANUP_FREE char *err = NULL; + int r; + size_t len; + + r = commandr (&out, &err, + "blkid", + /* Adding -c option kills all caching, even on RHEL 5. */ + "-c", "/dev/null", + "-o", "value", "-s", tag, device, NULL); + if (r != 0 && r != 2) { + if (r >= 0) + fprintf (stderr, "%s: %s (blkid returned %d)\n", device, err, r); + else + fprintf (stderr, "%s: %s\n", device, err); + free (out); + return NULL; + } + + if (r == 2) { /* means UUID etc not found */ + free (out); + out = strdup (""); + if (out == NULL) + perror ("strdup"); + return out; + } + + /* Trim trailing \n if present. */ + len = strlen (out); + if (len > 0 && out[len-1] == '\n') + out[len-1] = '\0'; + + return out; /* caller frees */ +} + +char * +get_vfs_type (const char *fs) +{ + return get_blkid_tag (fs, "TYPE"); +} + +/* Test if sfdisk is recent enough to have --part-type, to be used instead + * of --print-id and --change-id. + */ +static int +test_sfdisk_has_part_type (void) +{ + static int tested = -1; + int r; + CLEANUP_FREE char *out = NULL, *err = NULL; + + if (tested != -1) + return tested; + + r = command (&out, &err, "sfdisk", "--help", NULL); + if (r == -1) { + fprintf (stderr, "%s: %s\n", "sfdisk --help", err); + return -1; + } + + tested = strstr (out, "--part-type") != NULL; + return tested; +} + +static char * +part_to_dev (const char *part) +{ + int err = 1; + size_t n = strlen (part); + char *r; + + while (n >= 1 && c_isdigit (part[n-1])) { + err = 0; + n--; + } + + if (err) { + fprintf (stderr, "device name is not a partition\n"); + return NULL; + } + + r = strndup (part, n); + if (r == NULL) { + perror ("strdup"); + return NULL; + } + + return r; +} + +static int +part_to_partnum (const char *part) +{ + int err = 1; + size_t n = strlen (part); + int r; + + while (n >= 1 && c_isdigit (part[n-1])) { + err = 0; + n--; + } + + if (err) { + fprintf (stderr, "device name is not a partition\n"); + return -1; + } + + if (sscanf (&part[n], "%d", &r) != 1) { + fprintf (stderr, "could not parse number\n"); + return -1; + } + + return r; +} + +int +get_partition_mbr_id (const char *fs) +{ + CLEANUP_FREE char *device; + int partnum; + char partnum_str[16]; + const char *param = + test_sfdisk_has_part_type () ? "--part-type" : "--print-id"; + CLEANUP_FREE char *out = NULL, *err = NULL; + int r; + unsigned id; + + /* Get the block device and partition number from the filesystem + * string. + */ + device = part_to_dev (fs); + if (device == NULL) + return -1; + partnum = part_to_partnum (fs); + if (partnum == -1) + return -1; + snprintf (partnum_str, sizeof partnum_str, "%d", partnum); + + r = command (&out, &err, "sfdisk", param, device, partnum_str, NULL); + if (r == -1) { + fprintf (stderr, "sfdisk %s: %s\n", param, err); + return -1; + } + + /* It's printed in hex ... */ + if (sscanf (out, "%x", &id) != 1) { + fprintf (stderr, "sfdisk --print-id: cannot parse output: %s\n", out); + return -1; + } + + return id; +} + +/* When mounting filesystems, we place them in temporary directories + * under 'mountpoints'. We name the temporary directory after the + * device name, but since device names contain '/' characters, we have + * to mangle the name. + */ +static char * +get_mount_name (const char *fs) +{ + char *ret; + size_t i; + + if (asprintf (&ret, "%s/%s", mountpoints, fs) == -1) { + perror ("asprintf"); + return NULL; + } + + for (i = strlen (mountpoints) + 1; i < strlen (ret); ++i) { + if (ret[i] == '/') + ret[i] = '_'; + } + + return ret; /* caller frees */ +} + +int +is_mountable (const char *fs) +{ + CLEANUP_FREE char *mp = get_mount_name (fs); + struct stat statbuf; + int r; + CLEANUP_FREE char *err = NULL; + + if (stat (mp, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)) + return 1; /* mountable, and mounted already */ + + /* Try to create the mountpoint. */ + if (mkdir (mp, 0700) == -1) { + perror (mp); + return -1; + } + + /* Try to mount the filesystem. */ + r = command (NULL, &err, + "mount", "-o", "ro", fs, mp, NULL); + if (r == -1) { + fprintf (stderr, "mount: %s: %s\n", fs, err); + + /* Now hack things for the *BSDs. */ + /* FreeBSD fs is a variant of ufs called ufs2 ... */ + free (err); err = NULL; + r = command (NULL, &err, + "mount", "-o", "ro,ufstype=ufs2", fs, mp, NULL); + if (r == -1) { + fprintf (stderr, "mount [ufs2]: %s: %s\n", fs, err); + + /* while NetBSD and OpenBSD use another variant labeled 44bsd */ + free (err); err = NULL; + r = command (NULL, &err, + "mount", "-o", "ro,ufstype=44bsd", fs, mp, NULL); + if (r == -1) { + fprintf (stderr, "mount [44bsd]: %s: %s\n", fs, err); + + /* Mount failed, so remove the mountpoint. */ + rmdir (mp); + return 0; + } + } + } + + /* Mount succeeded. */ + return 1; +} + +int +get_mount (const char *fs, const char *filename, char **relative_filename) +{ + CLEANUP_FREE char *mp = NULL; + int r; + + if (filename[0] != '/') { + fprintf (stderr, "get_mount: filename is not an absolute path: %s\n", + filename); + return -1; + } + + r = is_mountable (fs); + if (r == -1) + return -1; + if (r == 0) { + fprintf (stderr, "get_mount: called on non-mountable filesystem: %s\n", + fs); + return -1; + } + + mp = get_mount_name (fs); + + /* Construct the filename relative to the mountpoint. */ + if (asprintf (relative_filename, "%s%s", mp, filename) == -1) { + perror ("asprintf"); + return -1; + } + + return 0; +} diff --git a/inspection/stringsbuf.c b/inspection/stringsbuf.c new file mode 100644 index 0000000..338134e --- /dev/null +++ b/inspection/stringsbuf.c @@ -0,0 +1,254 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009-2015 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <error.h> +#include <assert.h> + +#include "guestfs-internal-all.h" + +#include "stringsbuf.h" + +void +add_string_nodup (struct stringsbuf *sb, char *str) +{ + char **new_argv; + + if (sb->size >= sb->alloc) { + sb->alloc += 64; + new_argv = realloc (sb->argv, sb->alloc * sizeof (char *)); + if (new_argv == NULL) + error (EXIT_FAILURE, errno, "realloc"); + sb->argv = new_argv; + } + + sb->argv[sb->size] = str; + sb->size++; +} + +void +add_string (struct stringsbuf *sb, const char *str) +{ + char *new_str = NULL; + + if (str) { + new_str = strdup (str); + if (new_str == NULL) + error (EXIT_FAILURE, errno, "strdup"); + } + + add_string_nodup (sb, new_str); +} + +void +add_sprintf (struct stringsbuf *sb, const char *fs, ...) +{ + va_list args; + char *str; + int r; + + va_start (args, fs); + r = vasprintf (&str, fs, args); + va_end (args); + if (r == -1) + error (EXIT_FAILURE, errno, "vasprintf"); + + add_string_nodup (sb, str); +} + +void +end_stringsbuf (struct stringsbuf *sb) +{ + add_string_nodup (sb, NULL); +} + +void +free_stringsbuf (struct stringsbuf *sb) +{ + if (sb->argv != NULL) + free_stringslen (sb->argv, sb->size); +} + +size_t +count_strings (char *const *argv) +{ + size_t argc; + + for (argc = 0; argv[argc] != NULL; ++argc) + ; + return argc; +} + +static int +compare (const void *vp1, const void *vp2) +{ + char * const *p1 = (char * const *) vp1; + char * const *p2 = (char * const *) vp2; + return strcmp (*p1, *p2); +} + +void +sort_strings (char **argv, size_t len) +{ + qsort (argv, len, sizeof (char *), compare); +} + +void +free_strings (char **argv) +{ + size_t argc; + + if (!argv) + return; + + for (argc = 0; argv[argc] != NULL; ++argc) + free (argv[argc]); + free (argv); +} + +void +free_stringslen (char **argv, size_t len) +{ + size_t i; + + if (!argv) + return; + + for (i = 0; i < len; ++i) + free (argv[i]); + free (argv); +} + +/* Compare device names (including partition numbers if present). + * https://rwmj.wordpress.com/2011/01/09/how-are-linux-drives-named-beyond-driv... + */ +int +compare_device_names (const char *a, const char *b) +{ + size_t alen, blen; + int r; + int a_partnum, b_partnum; + + /* Skip /dev/ prefix if present. */ + if (STRPREFIX (a, "/dev/")) + a += 5; + if (STRPREFIX (b, "/dev/")) + b += 5; + + /* Skip sd/hd/ubd/vd. */ + alen = strcspn (a, "d"); + blen = strcspn (b, "d"); + assert (alen > 0 && alen <= 2); + assert (blen > 0 && blen <= 2); + a += alen + 1; + b += blen + 1; + + /* Get device name part, that is, just 'a', 'ab' etc. */ + alen = strcspn (a, "0123456789"); + blen = strcspn (b, "0123456789"); + + /* If device name part is longer, it is always greater, eg. + * "/dev/sdz" < "/dev/sdaa". + */ + if (alen != blen) + return alen - blen; + + /* Device name parts are the same length, so do a regular compare. */ + r = strncmp (a, b, alen); + if (r != 0) + return r; + + /* Compare partitions numbers. */ + a += alen; + b += alen; + + /* If no partition numbers, bail -- the devices are the same. This + * can happen in one peculiar case: where you have a mix of devices + * with different interfaces (eg. /dev/sda and /dev/vda). + * (RHBZ#858128). + */ + if (!*a && !*b) + return 0; + + r = sscanf (a, "%d", &a_partnum); + assert (r == 1); + r = sscanf (b, "%d", &b_partnum); + assert (r == 1); + + return a_partnum - b_partnum; +} + +static int +compare_device_names_vp (const void *vp1, const void *vp2) +{ + char * const *p1 = (char * const *) vp1; + char * const *p2 = (char * const *) vp2; + return compare_device_names (*p1, *p2); +} + +void +sort_device_names (char **argv, size_t len) +{ + qsort (argv, len, sizeof (char *), compare_device_names_vp); +} + +char * +concat_strings (char *const *argv) +{ + return join_strings ("", argv); +} + +char * +join_strings (const char *separator, char *const *argv) +{ + size_t i, len, seplen, rlen; + char *r; + + seplen = strlen (separator); + + len = 0; + for (i = 0; argv[i] != NULL; ++i) { + if (i > 0) + len += seplen; + len += strlen (argv[i]); + } + len++; /* for final \0 */ + + r = malloc (len); + if (r == NULL) + error (EXIT_FAILURE, errno, "malloc"); + + rlen = 0; + for (i = 0; argv[i] != NULL; ++i) { + if (i > 0) { + memcpy (&r[rlen], separator, seplen); + rlen += seplen; + } + len = strlen (argv[i]); + memcpy (&r[rlen], argv[i], len); + rlen += len; + } + r[rlen] = '\0'; + + return r; +} diff --git a/inspection/stringsbuf.h b/inspection/stringsbuf.h new file mode 100644 index 0000000..4a26ffb --- /dev/null +++ b/inspection/stringsbuf.h @@ -0,0 +1,56 @@ +/* libguestfs - the guestfsd daemon + * Copyright (C) 2009-2015 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef GUESTFS_INSPECTION_STRINGSBUF_H +#define GUESTFS_INSPECTION_STRINGSBUF_H + +/* Growable strings buffer. */ +struct stringsbuf { + char **argv; + size_t size; + size_t alloc; +}; +#define DECLARE_STRINGSBUF(v) \ + struct stringsbuf (v) = { .argv = NULL, .size = 0, .alloc = 0 } + +/* Append a string to the strings buffer. + * + * add_string_nodup: don't copy the string. + * add_string: copy the string. + * end_stringsbuf: NULL-terminate the buffer. + */ +extern void add_string_nodup (struct stringsbuf *sb, char *str); +extern void add_string (struct stringsbuf *sb, const char *str); +extern void add_sprintf (struct stringsbuf *sb, const char *fs, ...) + __attribute__((format (printf,2,3))); +extern void end_stringsbuf (struct stringsbuf *sb); +extern void free_stringsbuf (struct stringsbuf *sb); + +extern size_t count_strings (char *const *argv); +extern void sort_strings (char **argv, size_t len); +extern void free_strings (char **argv); +extern void free_stringslen (char **argv, size_t len); + +extern void sort_device_names (char **argv, size_t len); +extern int compare_device_names (const char *a, const char *b); + +/* Concatenate strings, optionally with a separator string between each. */ +extern char *concat_strings (char *const *argv); +extern char *join_strings (const char *separator, char *const *argv); + +#endif /* GUESTFS_INSPECTION_STRINGSBUF_H */ diff --git a/po/POTFILES b/po/POTFILES index aca174d..b6332f6 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -258,6 +258,11 @@ gobject/src/struct-version.c gobject/src/struct-xattr.c gobject/src/struct-xfsinfo.c gobject/src/tristate.c +inspection/detect.c +inspection/facts.c +inspection/inspection.c +inspection/mount.c +inspection/stringsbuf.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