[Libguestfs] [PATCH v3 05/10] lib: extract osinfo DB traversing API

Cédric Bosdonnat
Fri, 10 Feb 2017 15:06:00 -0000

Split lib/osinfo.c to provide an API for other pieces of code (namely
mllib) to reuse it. The ISO-related processing is thus moved into a
lib/osinfo-iso.c file.
---
 lib/Makefile.am  |   2 +
 lib/osinfo-iso.c | 464 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/osinfo.c     | 424 +-------------------------------------------------
 lib/osinfo.h     |  27 ++++
 4 files changed, 495 insertions(+), 422 deletions(-)
 create mode 100644 lib/osinfo-iso.c
 create mode 100644 lib/osinfo.h

diff --git a/lib/Makefile.am b/lib/Makefile.am
index e1ab1bff9..7a3580a00 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -112,7 +112,9 @@ libguestfs_la_SOURCES = \
 	lpj.c \
 	match.c \
 	mountable.c \
+	osinfo.h \
 	osinfo.c \
+	osinfo-iso.c \
 	private-data.c \
 	proto.c \
 	qemu.c \
diff --git a/lib/osinfo-iso.c b/lib/osinfo-iso.c
new file mode 100644
index 000000000..0c3c19971
--- /dev/null
+++ b/lib/osinfo-iso.c
@@ -0,0 +1,464 @@
+/* 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) We have to do some translation of the distro names and versions
+ * stored in the libosinfo files and the standard names returned by
+ * libguestfs.
+ *
+ * (2) Media detection is only part of the story.  We may still need
+ * to inspect inside the image.
+ *
+ * (3) 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 <dirent.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <libintl.h>
+#include <sys/stat.h>
+
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+
+#include "ignore-value.h"
+#include "glthread/lock.h"
+#include "c-ctype.h"
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+
+#include "osinfo.h"
+
+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 void free_osinfo_db_entry (struct osinfo *);
+
+#define XMLSTREQ(a,b) (xmlStrEqual((a),(b)) == 1)
+
+static int
+read_osinfo_db_xml (guestfs_h *g, const char *pathname, void *data);
+
+/* 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_int_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, read_osinfo_db_xml, NULL) == -1) {
+      /* 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;
+      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;
+}
+
+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 pathname (which is a full path).
+ * Only memory allocation failures are fatal errors here.
+ */
+static int
+read_osinfo_db_xml (guestfs_h *g, const char *pathname, void *opaque)
+{
+  CLEANUP_XMLFREEDOC xmlDocPtr doc = NULL;
+  CLEANUP_XMLXPATHFREECONTEXT xmlXPathContextPtr xpathCtx = NULL;
+  CLEANUP_XMLXPATHFREEOBJECT xmlXPathObjectPtr xpathObj = NULL;
+  xmlNodeSetPtr nodes;
+  xmlNodePtr iso_node, media_node, os_node;
+  struct osinfo *osinfo;
+  size_t i;
+
+  doc = xmlReadFile (pathname, NULL, XML_PARSE_NONET);
+  if (doc == NULL) {
+    debug (g, "osinfo: unable to parse XML file %s", pathname);
+    return 0;
+  }
+
+  xpathCtx = xmlXPathNewContext (doc);
+  if (xpathCtx == NULL) {
+    error (g, _("osinfo: unable to create new XPath context"));
+    return -1;
+  }
+
+  /* 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);
+    return -1;
+  }
+
+  nodes = xpathObj->nodesetval;
+
+  if (nodes != NULL) {
+    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--;
+        return -1;
+      }
+
+#if 0
+      debug (g, "osinfo: %s: %s%s%s%s=> arch %s live %s installer %s
product %s type %d distro %d version %d.%d",
+             pathname,
+             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->is_installer ? "true" : "false",
+             osinfo->product_name ? osinfo->product_name :
"(none)",
+             (int) osinfo->type, (int) osinfo->distro,
+             osinfo->major_version, osinfo->minor_version);
+#endif
+    }
+  }
+
+  return 0;
+}
+
+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)
+{
+  const char *err;
+  int offset;
+  CLEANUP_FREE char *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);
+  }
+
+  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)
+{
+  osinfo->arch = (char *) xmlGetProp (media_node, BAD_CAST
"arch");
+
+  osinfo->is_live_disk = 0; /* If no 'live' attr, defaults to false.
*/
+  {
+    CLEANUP_XMLFREE xmlChar *content = NULL;
+    content = xmlGetProp (media_node, BAD_CAST "live");
+    if (content)
+      osinfo->is_live_disk = XMLSTREQ (content, BAD_CAST "true");
+  }
+
+  osinfo->is_installer = true; /* If no 'installer' attr, defaults
to true. */
+  {
+    CLEANUP_XMLFREE xmlChar *content = NULL;
+    content = xmlGetProp (media_node, BAD_CAST "installer");
+    if (content)
+      osinfo->is_installer = XMLSTREQ (content, BAD_CAST "true");
+  }
+
+  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)
+{
+  CLEANUP_FREE char *content = NULL;
+
+  content = (char *) xmlNodeGetContent (node);
+  /* We parse either "X.Y" or "X" as version strings, so
try to parse
+   * only if the first character is a digit.
+   */
+  if (content && c_isdigit (content[0])) {
+    struct version version;
+    const int res = guestfs_int_version_from_x_y_or_x (g, &version,
content);
+    if (res < 0)
+      return -1;
+    else if (res > 0) {
+      osinfo->major_version = version.v_major;
+      osinfo->minor_version = version.v_minor;
+    }
+  }
+
+  return 0;
+}
+
+static int
+parse_family (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo)
+{
+  CLEANUP_FREE char *content = NULL;
+
+  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);
+  }
+
+  return 0;
+}
+
+static int
+parse_distro (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo)
+{
+  CLEANUP_FREE char *content = NULL;
+
+  osinfo->distro = OS_DISTRO_UNKNOWN;
+
+  content = (char *) xmlNodeGetContent (node);
+  if (content) {
+    if (STREQ (content, "altlinux"))
+      osinfo->distro = OS_DISTRO_ALTLINUX;
+    else 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, "freebsd"))
+      osinfo->distro = OS_DISTRO_FREEBSD;
+    else if (STREQ (content, "mageia"))
+      osinfo->distro = OS_DISTRO_MAGEIA;
+    else if (STREQ (content, "mandriva"))
+      osinfo->distro = OS_DISTRO_MANDRIVA;
+    else if (STREQ (content, "netbsd"))
+      osinfo->distro = OS_DISTRO_NETBSD;
+    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, "sled") || 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);
+  }
+
+  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);
+}
diff --git a/lib/osinfo.c b/lib/osinfo.c
index b77ff96b3..131616129 100644
--- a/lib/osinfo.c
+++ b/lib/osinfo.c
@@ -29,21 +29,6 @@
  * 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>
@@ -59,28 +44,13 @@
 #include <libintl.h>
 #include <sys/stat.h>

-#include <libxml/parser.h>
-#include <libxml/xpath.h>
-
 #include "ignore-value.h"
-#include "glthread/lock.h"
 #include "c-ctype.h"

 #include "guestfs.h"
 #include "guestfs-internal.h"

-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;
-
-#define XMLSTREQ(a,b) (xmlStrEqual((a),(b)) == 1)
-typedef int (*read_osinfo_db_callback) (guestfs_h *g, const char *path, void
*opaque);
-
-static int
-read_osinfo_db (guestfs_h *g,
-                read_osinfo_db_callback callback, void *opaque);
-static int read_osinfo_db_xml (guestfs_h *g, const char *pathname, void *data);
-static void free_osinfo_db_entry (struct osinfo *);
+#include "osinfo.h"

 #ifndef GUESTFS_PRIVATE
 void guestfs_int_debug (guestfs_h *g, const char *fs, ...)
@@ -109,82 +79,6 @@ guestfs_int_perrorf (guestfs_h *g, const char *fs, ...)
 }
 #endif /* GUESTFS_PRIVATE */

-/* 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_int_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, read_osinfo_db_xml, NULL) == -1) {
-      /* 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;
-      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.
  *
@@ -206,7 +100,7 @@ static int read_osinfo_db_three_levels (guestfs_h *g, const
char *directory,
 static int read_osinfo_db_directory (guestfs_h *g, const char *directory,
                                      read_osinfo_db_callback callback, void
*opaque);

-static int
+int
 read_osinfo_db (guestfs_h *g,
                 read_osinfo_db_callback callback, void *opaque)
 {
@@ -370,317 +264,3 @@ read_osinfo_db_directory (guestfs_h *g, const char
*directory,

   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 pathname (which is a full path).
- * Only memory allocation failures are fatal errors here.
- */
-static int
-read_osinfo_db_xml (guestfs_h *g, const char *pathname, void *opaque)
-{
-  CLEANUP_XMLFREEDOC xmlDocPtr doc = NULL;
-  CLEANUP_XMLXPATHFREECONTEXT xmlXPathContextPtr xpathCtx = NULL;
-  CLEANUP_XMLXPATHFREEOBJECT xmlXPathObjectPtr xpathObj = NULL;
-  xmlNodeSetPtr nodes;
-  xmlNodePtr iso_node, media_node, os_node;
-  struct osinfo *osinfo;
-  size_t i;
-
-  doc = xmlReadFile (pathname, NULL, XML_PARSE_NONET);
-  if (doc == NULL) {
-    debug (g, "osinfo: unable to parse XML file %s", pathname);
-    return 0;
-  }
-
-  xpathCtx = xmlXPathNewContext (doc);
-  if (xpathCtx == NULL) {
-    error (g, _("osinfo: unable to create new XPath context"));
-    return -1;
-  }
-
-  /* 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);
-    return -1;
-  }
-
-  nodes = xpathObj->nodesetval;
-
-  if (nodes != NULL) {
-    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--;
-        return -1;
-      }
-
-#if 0
-      debug (g, "osinfo: %s: %s%s%s%s=> arch %s live %s installer %s
product %s type %d distro %d version %d.%d",
-             pathname,
-             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->is_installer ? "true" : "false",
-             osinfo->product_name ? osinfo->product_name :
"(none)",
-             (int) osinfo->type, (int) osinfo->distro,
-             osinfo->major_version, osinfo->minor_version);
-#endif
-    }
-  }
-
-  return 0;
-}
-
-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)
-{
-  const char *err;
-  int offset;
-  CLEANUP_FREE char *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);
-  }
-
-  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)
-{
-  osinfo->arch = (char *) xmlGetProp (media_node, BAD_CAST
"arch");
-
-  osinfo->is_live_disk = 0; /* If no 'live' attr, defaults to false.
*/
-  {
-    CLEANUP_XMLFREE xmlChar *content = NULL;
-    content = xmlGetProp (media_node, BAD_CAST "live");
-    if (content)
-      osinfo->is_live_disk = XMLSTREQ (content, BAD_CAST "true");
-  }
-
-  osinfo->is_installer = true; /* If no 'installer' attr, defaults
to true. */
-  {
-    CLEANUP_XMLFREE xmlChar *content = NULL;
-    content = xmlGetProp (media_node, BAD_CAST "installer");
-    if (content)
-      osinfo->is_installer = XMLSTREQ (content, BAD_CAST "true");
-  }
-
-  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)
-{
-  CLEANUP_FREE char *content = NULL;
-
-  content = (char *) xmlNodeGetContent (node);
-  /* We parse either "X.Y" or "X" as version strings, so
try to parse
-   * only if the first character is a digit.
-   */
-  if (content && c_isdigit (content[0])) {
-    struct version version;
-    const int res = guestfs_int_version_from_x_y_or_x (g, &version,
content);
-    if (res < 0)
-      return -1;
-    else if (res > 0) {
-      osinfo->major_version = version.v_major;
-      osinfo->minor_version = version.v_minor;
-    }
-  }
-
-  return 0;
-}
-
-static int
-parse_family (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo)
-{
-  CLEANUP_FREE char *content = NULL;
-
-  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);
-  }
-
-  return 0;
-}
-
-static int
-parse_distro (guestfs_h *g, xmlNodePtr node, struct osinfo *osinfo)
-{
-  CLEANUP_FREE char *content = NULL;
-
-  osinfo->distro = OS_DISTRO_UNKNOWN;
-
-  content = (char *) xmlNodeGetContent (node);
-  if (content) {
-    if (STREQ (content, "altlinux"))
-      osinfo->distro = OS_DISTRO_ALTLINUX;
-    else 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, "freebsd"))
-      osinfo->distro = OS_DISTRO_FREEBSD;
-    else if (STREQ (content, "mageia"))
-      osinfo->distro = OS_DISTRO_MAGEIA;
-    else if (STREQ (content, "mandriva"))
-      osinfo->distro = OS_DISTRO_MANDRIVA;
-    else if (STREQ (content, "netbsd"))
-      osinfo->distro = OS_DISTRO_NETBSD;
-    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, "sled") || 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);
-  }
-
-  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);
-}
diff --git a/lib/osinfo.h b/lib/osinfo.h
new file mode 100644
index 000000000..68846747e
--- /dev/null
+++ b/lib/osinfo.h
@@ -0,0 +1,27 @@
+/* libguestfs
+ * Copyright (C) 2017 Red Hat Inc.
+ * Copyright (C) 2017 SUSE 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
+ */
+
+#ifndef OSINFO_H
+#define OSINFO_H
+
+typedef int (*read_osinfo_db_callback) (guestfs_h *g, const char *path, void
*opaque);
+
+extern int read_osinfo_db (guestfs_h *g, read_osinfo_db_callback callback, void
*opaque);
+
+#endif /* OSINFO_H */
--
2.11.0