summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard W.M. Jones <rjones@redhat.com>2012-11-27 17:38:31 +0000
committerRichard W.M. Jones <rjones@redhat.com>2012-11-29 18:22:00 +0000
commit90e7981082a2685b235724a6dd9b737cb90fe553 (patch)
tree6bc644091168c32da81994314614d9b15ec0e42a
parent31101db4c706ac0b8a32ee08f2ba19c0f33208f8 (diff)
downloadlibguestfs-90e7981082a2685b235724a6dd9b737cb90fe553.tar.gz
libguestfs-90e7981082a2685b235724a6dd9b737cb90fe553.tar.xz
libguestfs-90e7981082a2685b235724a6dd9b737cb90fe553.zip
inspection: Read libosinfo database in order to inspect OS install CD/DVD/ISOs (RHBZ#803650, RHBZ#805417).
-rw-r--r--po/POTFILES1
-rw-r--r--src/Makefile.am2
-rw-r--r--src/guestfs-internal.h30
-rw-r--r--src/inspect-fs-cd.c46
-rw-r--r--src/inspect-fs.c42
-rw-r--r--src/osinfo.c604
6 files changed, 715 insertions, 10 deletions
diff --git a/po/POTFILES b/po/POTFILES
index 51315b89..675cb8d3 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -260,6 +260,7 @@ src/libvirt-domain.c
src/listfs.c
src/lpj.c
src/match.c
+src/osinfo.c
src/private-data.c
src/proto.c
src/tmpdirs.c
diff --git a/src/Makefile.am b/src/Makefile.am
index db5fb518..198b4f20 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -168,6 +168,7 @@ libguestfs_la_SOURCES = \
listfs.c \
lpj.c \
match.c \
+ osinfo.c \
private-data.c \
proto.c \
tmpdirs.c \
@@ -194,6 +195,7 @@ libguestfs_la_LIBADD += liberrnostring.la libprotocol.la
libguestfs_la_CFLAGS = \
-DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' \
-DGUESTFS_WARN_DEPRECATED=1 \
+ -DLIBOSINFO_DB_PATH='"$(datadir)/libosinfo/db"' \
$(PCRE_CFLAGS) \
$(LIBVIRT_CFLAGS) $(LIBXML2_CFLAGS) \
$(WARN_CFLAGS) $(WERROR_CFLAGS) \
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index 9e9b7084..3e87245e 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -47,6 +47,7 @@
#define STRNEQLEN(a,b,n) (strncmp((a),(b),(n)) != 0)
#define STRCASENEQLEN(a,b,n) (strncasecmp((a),(b),(n)) != 0)
#define STRPREFIX(a,b) (strncmp((a),(b),strlen((b))) == 0)
+#define STRSUFFIX(a,b) (strlen((a)) >= strlen((b)) && STREQ((a)+strlen((a))-strlen((b)),(b)))
#define _(str) dgettext(PACKAGE, (str))
#define N_(str) dgettext(PACKAGE, (str))
@@ -590,6 +591,8 @@ extern int guestfs___parse_unsigned_int_ignore_trailing (guestfs_h *g, const cha
extern int guestfs___parse_major_minor (guestfs_h *g, struct inspect_fs *fs);
extern char *guestfs___first_line_of_file (guestfs_h *g, const char *filename);
extern int guestfs___first_egrep_of_file (guestfs_h *g, const char *filename, const char *eregex, int iflag, char **ret);
+extern void guestfs___check_package_format (guestfs_h *g, struct inspect_fs *fs);
+extern void guestfs___check_package_management (guestfs_h *g, struct inspect_fs *fs);
/* inspect-fs-unix.c */
extern int guestfs___check_linux_root (guestfs_h *g, struct inspect_fs *fs);
@@ -604,6 +607,7 @@ extern int guestfs___check_windows_root (guestfs_h *g, struct inspect_fs *fs);
/* inspect-fs-cd.c */
extern int guestfs___check_installer_root (guestfs_h *g, struct inspect_fs *fs);
+extern int guestfs___check_installer_iso (guestfs_h *g, struct inspect_fs *fs, const char *device);
/* dbdump.c */
typedef int (*guestfs___db_dump_callback) (guestfs_h *g, const unsigned char *key, size_t keylen, const unsigned char *value, size_t valuelen, void *opaque);
@@ -622,6 +626,32 @@ extern void guestfs___free_fuse (guestfs_h *g);
extern virConnectPtr guestfs___open_libvirt_connection (guestfs_h *g, const char *uri, unsigned int flags);
#endif
+/* osinfo.c */
+struct osinfo {
+ /* Data provided by libosinfo database. */
+ enum inspect_os_type type;
+ enum inspect_os_distro distro;
+ char *product_name;
+ int major_version;
+ int minor_version;
+ char *arch;
+ int is_live_disk;
+
+#if 0
+ /* Not yet available in libosinfo database. */
+ char *product_variant;
+ int is_netinst_disk;
+ int is_multipart_disk;
+#endif
+
+ /* The regular expressions used to match ISOs. */
+ pcre *re_system_id;
+ pcre *re_volume_id;
+ pcre *re_publisher_id;
+ pcre *re_application_id;
+};
+extern int guestfs___osinfo_map (guestfs_h *g, const struct guestfs_isoinfo *isoinfo, const struct osinfo **osinfo_ret);
+
/* command.c */
struct command;
typedef void (*cmd_stdout_callback) (guestfs_h *g, void *data, const char *line, size_t len);
diff --git a/src/inspect-fs-cd.c b/src/inspect-fs-cd.c
index aef61d6a..05559b96 100644
--- a/src/inspect-fs-cd.c
+++ b/src/inspect-fs-cd.c
@@ -477,3 +477,49 @@ guestfs___check_installer_root (guestfs_h *g, struct inspect_fs *fs)
return 0;
}
+
+/* This is called for whole block devices. See if the device is an
+ * ISO and we are able to read the ISO info from it. In that case,
+ * try using libosinfo to map from the volume ID and other strings
+ * directly to the operating system type.
+ */
+int
+guestfs___check_installer_iso (guestfs_h *g, struct inspect_fs *fs,
+ const char *device)
+{
+ struct guestfs_isoinfo *isoinfo;
+ const struct osinfo *osinfo;
+ int r;
+
+ guestfs_push_error_handler (g, NULL, NULL);
+ isoinfo = guestfs_isoinfo_device (g, device);
+ guestfs_pop_error_handler (g);
+ if (!isoinfo)
+ return 0;
+
+ r = guestfs___osinfo_map (g, isoinfo, &osinfo);
+ guestfs_free_isoinfo (isoinfo);
+ if (r == -1) /* Fatal error. */
+ return -1;
+ if (r == 0) /* Could not locate any matching ISO. */
+ return 0;
+
+ /* Otherwise we matched an ISO, so fill in the fs fields. */
+ fs->device = safe_strdup (g, device);
+ fs->is_root = 1;
+ fs->content = FS_CONTENT_INSTALLER;
+ fs->format = OS_FORMAT_INSTALLER;
+ fs->type = osinfo->type;
+ fs->distro = osinfo->distro;
+ fs->product_name =
+ osinfo->product_name ? safe_strdup (g, osinfo->product_name) : NULL;
+ fs->major_version = osinfo->major_version;
+ fs->minor_version = osinfo->minor_version;
+ fs->arch = osinfo->arch ? safe_strdup (g, osinfo->arch) : NULL;
+ fs->is_live_disk = osinfo->is_live_disk;
+
+ guestfs___check_package_format (g, fs);
+ guestfs___check_package_management (g, fs);
+
+ return 1;
+}
diff --git a/src/inspect-fs.c b/src/inspect-fs.c
index 0ddeae13..2c81e412 100644
--- a/src/inspect-fs.c
+++ b/src/inspect-fs.c
@@ -80,8 +80,6 @@ free_regexps (void)
}
static int check_filesystem (guestfs_h *g, const char *device, int is_block, int is_partnum);
-static void check_package_format (guestfs_h *g, struct inspect_fs *fs);
-static void check_package_management (guestfs_h *g, struct inspect_fs *fs);
static int extend_fses (guestfs_h *g);
/* Find out if 'device' contains a filesystem. If it does, add
@@ -118,6 +116,24 @@ guestfs___check_for_filesystem_on (guestfs_h *g, const char *device,
return 0;
}
+ /* If it's a whole device, see if it is an install ISO. */
+ if (is_block) {
+ if (extend_fses (g) == -1)
+ return -1;
+ fs = &g->fses[g->nr_fses-1];
+
+ r = guestfs___check_installer_iso (g, fs, device);
+ if (r == -1) { /* Fatal error. */
+ g->nr_fses--;
+ return -1;
+ }
+ if (r > 0) /* Found something. */
+ return 0;
+
+ /* Didn't find anything. Fall through ... */
+ g->nr_fses--;
+ }
+
/* Try mounting the device. As above, ignore errors. */
guestfs_push_error_handler (g, NULL, NULL);
if (vfs_type && STREQ (vfs_type, "ufs")) { /* Hack for the *BSDs. */
@@ -276,8 +292,14 @@ check_filesystem (guestfs_h *g, const char *device,
*/
fs->arch = safe_strdup (g, "i386");
}
- /* Install CD/disk? Skip these checks if it's not a whole device
- * (eg. CD) or the first partition (eg. bootable USB key).
+ /* Install CD/disk?
+ *
+ * Note that we checked (above) for an install ISO, but there are
+ * other types of install image (eg. USB keys) which that check
+ * wouldn't have picked up.
+ *
+ * Skip these checks if it's not a whole device (eg. CD) or the
+ * first partition (eg. bootable USB key).
*/
else if ((is_block || is_partnum == 1) &&
(guestfs_is_file (g, "/isolinux/isolinux.cfg") > 0 ||
@@ -298,8 +320,8 @@ check_filesystem (guestfs_h *g, const char *device,
/* The above code should have set fs->type and fs->distro fields, so
* we can now guess the package management system.
*/
- check_package_format (g, fs);
- check_package_management (g, fs);
+ guestfs___check_package_format (g, fs);
+ guestfs___check_package_management (g, fs);
return 0;
}
@@ -403,8 +425,8 @@ guestfs___parse_major_minor (guestfs_h *g, struct inspect_fs *fs)
* simple function of the distro and major_version fields, so these
* can never return an error. We might be cleverer in future.
*/
-static void
-check_package_format (guestfs_h *g, struct inspect_fs *fs)
+void
+guestfs___check_package_format (guestfs_h *g, struct inspect_fs *fs)
{
switch (fs->distro) {
case OS_DISTRO_FEDORA:
@@ -450,8 +472,8 @@ check_package_format (guestfs_h *g, struct inspect_fs *fs)
}
}
-static void
-check_package_management (guestfs_h *g, struct inspect_fs *fs)
+void
+guestfs___check_package_management (guestfs_h *g, struct inspect_fs *fs)
{
switch (fs->distro) {
case OS_DISTRO_FEDORA:
diff --git a/src/osinfo.c b/src/osinfo.c
new file mode 100644
index 00000000..0be06112
--- /dev/null
+++ b/src/osinfo.c
@@ -0,0 +1,604 @@
+/* libguestfs
+ * Copyright (C) 2012 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* Read libosinfo XML files to parse out just the
+ * os/media/iso/system-id and os/media/iso/volume-id fields, which we
+ * can then use to map install media to operating systems.
+ *
+ * Note some assumptions here:
+ *
+ * (1) Ignore the libosinfo library itself, since we don't care
+ * for GObject nonsense. The XML database contains all we need.
+ *
+ * (2) Ignore os/upgrades and os/derives-from fields. This is
+ * safe(-ish) since the media identifiers always change for every
+ * release of an OS. We can easily add support for this if it becomes
+ * necessary.
+ *
+ * (3) We have to do some translation of the distro names and versions
+ * stored in the libosinfo files and the standard names returned by
+ * libguestfs.
+ *
+ * (4) Media detection is only part of the story. We may still need
+ * to inspect inside the image.
+ *
+ * (5) We only read the XML database files (at most) once per process,
+ * and keep them cached. They are only read at all if someone tries
+ * to inspect a CD/DVD/ISO.
+ *
+ * XXX Currently the database is not freed when the program exits /
+ * library is unloaded, although we should probably do that.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include <assert.h>
+#include <sys/types.h>
+
+#ifdef HAVE_LIBXML2
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#endif
+
+#include "ignore-value.h"
+#include "glthread/lock.h"
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+
+#ifdef HAVE_LIBXML2
+
+static pcre *re_major_minor;
+
+static void compile_regexps (void) __attribute__((constructor));
+static void free_regexps (void) __attribute__((destructor));
+
+static void
+compile_regexps (void)
+{
+ const char *err;
+ int offset;
+
+#define COMPILE(re,pattern,options) \
+ do { \
+ re = pcre_compile ((pattern), (options), &err, &offset, NULL); \
+ if (re == NULL) { \
+ ignore_value (write (2, err, strlen (err))); \
+ abort (); \
+ } \
+ } while (0)
+
+ COMPILE (re_major_minor, "(\\d+)\\.(\\d+)", 0);
+}
+
+static void
+free_regexps (void)
+{
+ pcre_free (re_major_minor);
+}
+
+gl_lock_define_initialized (static, osinfo_db_lock);
+static ssize_t osinfo_db_size = 0; /* 0 = unread, -1 = error, >= 1 = #records */
+static struct osinfo *osinfo_db = NULL;
+
+static int read_osinfo_db (guestfs_h *g);
+static void free_osinfo_db_entry (struct osinfo *);
+
+/* Given one or more fields from the header of a CD/DVD/ISO, look up
+ * the media in the libosinfo database and return our best guess for
+ * the operating system.
+ *
+ * This returns:
+ * -1 => a fatal error ('error' has been called, caller must not ignore it)
+ * 0 => could not locate the OS
+ * 1 => matching OS found, the osinfo_ret pointer has been filled in
+ */
+int
+guestfs___osinfo_map (guestfs_h *g, const struct guestfs_isoinfo *isoinfo,
+ const struct osinfo **osinfo_ret)
+{
+ size_t i;
+
+ /* We only need to lock the database when reading it for the first time. */
+ gl_lock_lock (osinfo_db_lock);
+ if (osinfo_db_size == 0) {
+ if (read_osinfo_db (g) == -1) {
+ gl_lock_unlock (osinfo_db_lock);
+ return -1;
+ }
+ }
+ gl_lock_unlock (osinfo_db_lock);
+
+ if (osinfo_db_size <= 0)
+ return 0;
+
+ /* Look in the database to see if we can find a match. */
+ for (i = 0; i < (size_t) osinfo_db_size; ++i) {
+ if (osinfo_db[i].re_system_id) {
+ if (!isoinfo->iso_system_id ||
+ !match (g, isoinfo->iso_system_id, osinfo_db[i].re_system_id))
+ continue;
+ }
+
+ if (osinfo_db[i].re_volume_id) {
+ if (!isoinfo->iso_volume_id ||
+ !match (g, isoinfo->iso_volume_id, osinfo_db[i].re_volume_id))
+ continue;
+ }
+
+ if (osinfo_db[i].re_publisher_id) {
+ if (!isoinfo->iso_publisher_id ||
+ !match (g, isoinfo->iso_publisher_id, osinfo_db[i].re_publisher_id))
+ continue;
+ }
+
+ if (osinfo_db[i].re_application_id) {
+ if (!isoinfo->iso_application_id ||
+ !match (g, isoinfo->iso_application_id, osinfo_db[i].re_application_id))
+ continue;
+ }
+
+ debug (g, "osinfo: mapped disk to database entry %zu", i);
+
+ if (osinfo_ret)
+ *osinfo_ret = &osinfo_db[i];
+ return 1;
+ }
+
+ debug (g, "osinfo: no mapping found");
+
+ return 0;
+}
+
+/* Read the libosinfo XML database files. The lock is held while
+ * this is called.
+ *
+ * Returns:
+ * -1 => a fatal error ('error' has been called)
+ * 0 => OK
+ *
+ * Note that failure to find or parse the XML files is *not* a fatal
+ * error, since we should fall back silently if these are not
+ * available. Although we'll emit some debug if this happens.
+ */
+#define LIBOSINFO_DB_OS_PATH LIBOSINFO_DB_PATH "/oses"
+
+static int read_osinfo_db_xml (guestfs_h *g, const char *filename);
+
+static int
+read_osinfo_db (guestfs_h *g)
+{
+ DIR *dir = NULL;
+ struct dirent *d;
+ int r;
+ size_t i;
+
+ assert (osinfo_db_size == 0);
+
+ dir = opendir (LIBOSINFO_DB_OS_PATH);
+ if (!dir) {
+ debug (g, "osinfo: %s: %s", LIBOSINFO_DB_OS_PATH, strerror (errno));
+ return -1;
+ }
+
+ debug (g, "osinfo: loading database from %s", LIBOSINFO_DB_OS_PATH);
+
+ for (;;) {
+ errno = 0;
+ d = readdir (dir);
+ if (!d) break;
+
+ if (STRSUFFIX (d->d_name, ".xml")) {
+ r = read_osinfo_db_xml (g, d->d_name);
+ if (r == -1)
+ goto error;
+ }
+ }
+
+ /* Check for failure in readdir. */
+ if (errno != 0) {
+ perrorf (g, "readdir: %s", LIBOSINFO_DB_OS_PATH);
+ goto error;
+ }
+
+ /* Close the directory handle. */
+ r = closedir (dir);
+ dir = NULL;
+ if (r == -1) {
+ perrorf (g, "closedir: %s", LIBOSINFO_DB_OS_PATH);
+ goto error;
+ }
+
+ return 0;
+
+ error:
+ if (dir)
+ closedir (dir);
+
+ /* Fatal error: free any database entries which have been read, and
+ * mark the database as having a permanent error.
+ */
+ if (osinfo_db_size > 0) {
+ for (i = 0; i < (size_t) osinfo_db_size; ++i)
+ free_osinfo_db_entry (&osinfo_db[i]);
+ }
+ free (osinfo_db);
+ osinfo_db = NULL;
+ osinfo_db_size = -1;
+
+ return -1;
+}
+
+static int read_iso_node (guestfs_h *g, xmlNodePtr iso_node, struct osinfo *osinfo);
+static int read_media_node (guestfs_h *g, xmlXPathContextPtr xpathCtx, xmlNodePtr media_node, struct osinfo *osinfo);
+static int read_os_node (guestfs_h *g, xmlXPathContextPtr xpathCtx, xmlNodePtr os_node, struct osinfo *osinfo);
+
+/* Read a single XML file from LIBOSINFO_DB_OS_PATH/filename. Only
+ * memory allocation failures are fatal errors here.
+ */
+static int
+read_osinfo_db_xml (guestfs_h *g, const char *filename)
+{
+ const size_t pathname_len =
+ strlen (LIBOSINFO_DB_OS_PATH) + strlen (filename) + 2;
+ char pathname[pathname_len];
+ xmlDocPtr doc = NULL;
+ xmlXPathContextPtr xpathCtx = NULL;
+ xmlXPathObjectPtr xpathObj = NULL;
+ xmlNodeSetPtr nodes;
+ xmlNodePtr iso_node, media_node, os_node;
+ struct osinfo *osinfo;
+ size_t i;
+ int ret = 0;
+
+ snprintf (pathname, pathname_len, "%s/%s", LIBOSINFO_DB_OS_PATH, filename);
+
+ doc = xmlParseFile (pathname);
+ if (doc == NULL) {
+ debug (g, "osinfo: unable to parse XML file %s", pathname);
+ goto cleanup;
+ }
+
+ xpathCtx = xmlXPathNewContext (doc);
+ if (xpathCtx == NULL) {
+ error (g, _("osinfo: unable to create new XPath context"));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* Get all <iso> nodes at any depth, then use the parent pointers in
+ * order to work back up the tree.
+ */
+ xpathObj = xmlXPathEvalExpression (BAD_CAST "/libosinfo/os/media/iso",
+ xpathCtx);
+ if (xpathObj == NULL) {
+ error (g, _("osinfo: %s: unable to evaluate XPath expression"),
+ pathname);
+ ret = -1;
+ goto cleanup;
+ }
+
+ nodes = xpathObj->nodesetval;
+
+ for (i = 0; i < (size_t) nodes->nodeNr; ++i) {
+ iso_node = nodes->nodeTab[i];
+ assert (iso_node != NULL);
+ assert (STREQ ((const char *) iso_node->name, "iso"));
+ assert (iso_node->type == XML_ELEMENT_NODE);
+
+ media_node = iso_node->parent;
+ assert (media_node != NULL);
+ assert (STREQ ((const char *) media_node->name, "media"));
+ assert (media_node->type == XML_ELEMENT_NODE);
+
+ os_node = media_node->parent;
+ assert (os_node != NULL);
+ assert (STREQ ((const char *) os_node->name, "os"));
+ assert (os_node->type == XML_ELEMENT_NODE);
+
+ /* Allocate an osinfo record. */
+ osinfo_db_size++;
+ osinfo_db = safe_realloc (g, osinfo_db,
+ sizeof (struct osinfo) * osinfo_db_size);
+ osinfo = &osinfo_db[osinfo_db_size-1];
+ memset (osinfo, 0, sizeof *osinfo);
+
+ /* Read XML fields into the new osinfo record. */
+ if (read_iso_node (g, iso_node, osinfo) == -1 ||
+ read_media_node (g, xpathCtx, media_node, osinfo) == -1 ||
+ read_os_node (g, xpathCtx, os_node, osinfo) == -1) {
+ free_osinfo_db_entry (osinfo);
+ osinfo_db_size--;
+ ret = -1;
+ goto cleanup;
+ }
+
+#if 0
+ debug (g, "osinfo: %s: %s%s%s%s=> arch %s live %s product %s type %d distro %d version %d.%d",
+ filename,
+ osinfo->re_system_id ? "<system-id/> " : "",
+ osinfo->re_volume_id ? "<volume-id/> " : "",
+ osinfo->re_publisher_id ? "<publisher-id/> " : "",
+ osinfo->re_application_id ? "<application-id/> " : "",
+ osinfo->arch ? osinfo->arch : "(none)",
+ osinfo->is_live_disk ? "true" : "false",
+ osinfo->product_name ? osinfo->product_name : "(none)",
+ (int) osinfo->type, (int) osinfo->distro,
+ osinfo->major_version, osinfo->minor_version);
+#endif
+ }
+
+ cleanup:
+ if (xpathObj) xmlXPathFreeObject (xpathObj);
+ if (xpathCtx) xmlXPathFreeContext (xpathCtx);
+ if (doc) xmlFreeDoc (doc);
+
+ return ret;
+}
+
+static int compile_re (guestfs_h *g, xmlNodePtr child, pcre **re);
+
+/* Read the regular expressions under the <iso> node. libosinfo
+ * itself uses the glib function 'g_regex_match_simple'. That appears
+ * to implement PCRE, however I have not checked in detail.
+ */
+static int
+read_iso_node (guestfs_h *g, xmlNodePtr iso_node, struct osinfo *osinfo)
+{
+ xmlNodePtr child;
+
+ for (child = iso_node->children; child; child = child->next) {
+ if (STREQ ((const char *) child->name, "system-id")) {
+ if (compile_re (g, child, &osinfo->re_system_id) == -1)
+ return -1;
+ }
+ else if (STREQ ((const char *) child->name, "volume-id")) {
+ if (compile_re (g, child, &osinfo->re_volume_id) == -1)
+ return -1;
+ }
+ else if (STREQ ((const char *) child->name, "publisher-id")) {
+ if (compile_re (g, child, &osinfo->re_publisher_id) == -1)
+ return -1;
+ }
+ else if (STREQ ((const char *) child->name, "application-id")) {
+ if (compile_re (g, child, &osinfo->re_application_id) == -1)
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+compile_re (guestfs_h *g, xmlNodePtr node, pcre **re)
+{
+ char *content;
+ const char *err;
+ int offset;
+
+ content = (char *) xmlNodeGetContent (node);
+ if (content) {
+ *re = pcre_compile (content, 0, &err, &offset, NULL);
+ if (*re == NULL)
+ debug (g, "osinfo: could not parse regular expression '%s': %s (ignored)",
+ content, err);
+ }
+ free (content);
+
+ return 0;
+}
+
+/* Read the attributes of the <media/> node. */
+static int
+read_media_node (guestfs_h *g, xmlXPathContextPtr xpathCtx,
+ xmlNodePtr media_node, struct osinfo *osinfo)
+{
+ xmlXPathObjectPtr xp;
+ xmlNodePtr attr;
+ char *content;
+
+ xpathCtx->node = media_node;
+ xp = xmlXPathEvalExpression (BAD_CAST "./@arch", xpathCtx);
+ if (xp && xp->nodesetval && xp->nodesetval->nodeNr > 0) {
+ attr = xp->nodesetval->nodeTab[0];
+ assert (attr);
+ assert (attr->type == XML_ATTRIBUTE_NODE);
+ osinfo->arch = (char *) xmlNodeGetContent (attr);
+ }
+ xmlXPathFreeObject (xp);
+
+ osinfo->is_live_disk = 0; /* If no 'live' attr, defaults to false. */
+
+ xpathCtx->node = media_node;
+ xp = xmlXPathEvalExpression (BAD_CAST "./@live", xpathCtx);
+ if (xp && xp->nodesetval && xp->nodesetval->nodeNr > 0) {
+ attr = xp->nodesetval->nodeTab[0];
+ assert (attr);
+ assert (attr->type == XML_ATTRIBUTE_NODE);
+ content = (char *) xmlNodeGetContent (attr);
+ osinfo->is_live_disk = STREQ (content, "true");
+ free (content);
+ }
+ xmlXPathFreeObject (xp);
+
+ return 0;
+}
+
+static int parse_version (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo);
+static int parse_family (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo);
+static int parse_distro (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo);
+
+/* Read some fields under the <os/> node. */
+static int
+read_os_node (guestfs_h *g, xmlXPathContextPtr xpathCtx,
+ xmlNodePtr os_node, struct osinfo *osinfo)
+{
+ xmlNodePtr child;
+
+ for (child = os_node->children; child; child = child->next) {
+ if (STREQ ((const char *) child->name, "name"))
+ osinfo->product_name = (char *) xmlNodeGetContent (child);
+ else if (STREQ ((const char *) child->name, "version")) {
+ if (parse_version (g, child, osinfo) == -1)
+ return -1;
+ }
+ else if (STREQ ((const char *) child->name, "family")) {
+ if (parse_family (g, child, osinfo) == -1)
+ return -1;
+ }
+ else if (STREQ ((const char *) child->name, "distro")) {
+ if (parse_distro (g, child, osinfo) == -1)
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+parse_version (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo)
+{
+ char *content;
+ char *major, *minor;
+
+ content = (char *) xmlNodeGetContent (node);
+ if (content) {
+ if (match2 (g, content, re_major_minor, &major, &minor)) {
+ osinfo->major_version = guestfs___parse_unsigned_int (g, major);
+ free (major);
+ if (osinfo->major_version == -1) {
+ free (minor);
+ return -1;
+ }
+ osinfo->minor_version = guestfs___parse_unsigned_int (g, minor);
+ free (minor);
+ if (osinfo->minor_version == -1)
+ return -1;
+ }
+ }
+
+ free (content);
+
+ return 0;
+}
+
+static int
+parse_family (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo)
+{
+ char *content;
+
+ osinfo->type = OS_TYPE_UNKNOWN;
+
+ content = (char *) xmlNodeGetContent (node);
+ if (content) {
+ if (STREQ (content, "linux"))
+ osinfo->type = OS_TYPE_LINUX;
+ else if (STRPREFIX (content, "win"))
+ osinfo->type = OS_TYPE_WINDOWS;
+ else if (STREQ (content, "freebsd"))
+ osinfo->type = OS_TYPE_FREEBSD;
+ else if (STREQ (content, "netbsd"))
+ osinfo->type = OS_TYPE_NETBSD;
+ else if (STREQ (content, "msdos"))
+ osinfo->type = OS_TYPE_DOS;
+ else if (STREQ (content, "openbsd"))
+ osinfo->type = OS_TYPE_OPENBSD;
+ else
+ debug (g, "osinfo: warning: unknown <family> '%s'", content);
+ }
+
+ free (content);
+
+ return 0;
+}
+
+static int
+parse_distro (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo)
+{
+ char *content;
+
+ osinfo->distro = OS_DISTRO_UNKNOWN;
+
+ content = (char *) xmlNodeGetContent (node);
+ if (content) {
+ if (STREQ (content, "centos"))
+ osinfo->distro = OS_DISTRO_CENTOS;
+ else if (STREQ (content, "debian"))
+ osinfo->distro = OS_DISTRO_DEBIAN;
+ else if (STREQ (content, "fedora"))
+ osinfo->distro = OS_DISTRO_FEDORA;
+ else if (STREQ (content, "mandriva"))
+ osinfo->distro = OS_DISTRO_MANDRIVA;
+ else if (STREQ (content, "openbsd"))
+ osinfo->distro = OS_DISTRO_OPENBSD;
+ else if (STREQ (content, "opensuse"))
+ osinfo->distro = OS_DISTRO_OPENSUSE;
+ else if (STREQ (content, "rhel"))
+ osinfo->distro = OS_DISTRO_RHEL;
+ else if (STREQ (content, "sles"))
+ osinfo->distro = OS_DISTRO_SLES;
+ else if (STREQ (content, "ubuntu"))
+ osinfo->distro = OS_DISTRO_UBUNTU;
+ else if (STRPREFIX (content, "win"))
+ osinfo->distro = OS_DISTRO_WINDOWS;
+ else
+ debug (g, "osinfo: warning: unknown <distro> '%s'", content);
+ }
+
+ free (content);
+
+ return 0;
+}
+
+static void
+free_osinfo_db_entry (struct osinfo *osinfo)
+{
+ free (osinfo->product_name);
+ free (osinfo->arch);
+
+ if (osinfo->re_system_id)
+ pcre_free (osinfo->re_system_id);
+ if (osinfo->re_volume_id)
+ pcre_free (osinfo->re_volume_id);
+ if (osinfo->re_publisher_id)
+ pcre_free (osinfo->re_publisher_id);
+ if (osinfo->re_application_id)
+ pcre_free (osinfo->re_application_id);
+}
+
+#else /* !HAVE_LIBXML2 */
+
+int
+guestfs___osinfo_map (guestfs_h *g,
+ const char *system_id,
+ const char *volume_id,
+ const char *publisher_id,
+ const char *application_id,
+ const struct osinfo **osinfo_ret)
+{
+ debug (g, "osinfo: libxml2 not available");
+ return 0;
+}
+
+#endif /* !HAVE_LIBXML2 */