/* 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 #include #include #include #include #include #include #include #include #ifdef HAVE_LIBXML2 #include #include #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 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 ? " " : "", osinfo->re_volume_id ? " " : "", osinfo->re_publisher_id ? " " : "", osinfo->re_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 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 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 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 '%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 '%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 */