src/osinfo.c has generic code to read the libosinfo DB. Add a wrapper around it to expose it in mllib for use in ocaml code. --- lib/Makefile.am | 2 + lib/osinfo-iso.c | 464 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/osinfo.c | 477 ++++++------------------------------------------------ lib/osinfo.h | 27 ++++ mllib/Makefile.am | 11 +- mllib/osinfo-c.c | 100 ++++++++++++ mllib/osinfo.ml | 26 +++ mllib/osinfo.mli | 31 ++++ 8 files changed, 704 insertions(+), 434 deletions(-) create mode 100644 lib/osinfo-iso.c create mode 100644 lib/osinfo.h create mode 100644 mllib/osinfo-c.c create mode 100644 mllib/osinfo.ml create mode 100644 mllib/osinfo.mli 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 ea2a7659a..6b5ad6de7 100644 --- a/lib/osinfo.c +++ b/lib/osinfo.c @@ -30,19 +30,6 @@ * 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> @@ -52,95 +39,47 @@ #include <string.h> #include <unistd.h> #include <dirent.h> +#include <errno.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" -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 *); +#include "osinfo.h" -#define XMLSTREQ(a,b) (xmlStrEqual((a),(b)) == 1) -/* 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) +#ifndef GUESTFS_PRIVATE +void guestfs_int_debug (guestfs_h *g, const char *fs, ...) { - 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; - } + va_list args; - 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; - } + va_start (args, fs); + vfprintf (stderr, fs, args); + va_end (args); +} - debug (g, "osinfo: mapped disk to database entry %zu", i); +void +guestfs_int_perrorf (guestfs_h *g, const char *fs, ...) +{ + va_list args; + CLEANUP_FREE char *msg = NULL; + int err; - if (osinfo_ret) - *osinfo_ret = &osinfo_db[i]; - return 1; - } + va_start (args, fs); + err = vasprintf (&msg, fs, args); + va_end (args); - debug (g, "osinfo: no mapping found"); + if (err < 0) return; - return 0; + perror(msg); } +#endif /* GUESTFS_PRIVATE */ /* Read the libosinfo XML database files. The lock is held while * this is called. @@ -156,19 +95,18 @@ guestfs_int_osinfo_map (guestfs_h *g, const struct guestfs_isoinfo *isoinfo, * Try to use the shared osinfo database layout (and location) first: * https://gitlab.com/libosinfo/libosinfo/blob/master/docs/database-layout.txt */ -static int read_osinfo_db_xml (guestfs_h *g, const char *filename); +static int read_osinfo_db_flat (guestfs_h *g, const char *directory, + read_osinfo_db_callback callback, void *opaque); +static int read_osinfo_db_three_levels (guestfs_h *g, const char *directory, + read_osinfo_db_callback callback, void *opaque); +static int read_osinfo_db_directory (guestfs_h *g, const char *directory, + read_osinfo_db_callback callback, void *opaque); -static int read_osinfo_db_flat (guestfs_h *g, const char *directory); -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); - -static int -read_osinfo_db (guestfs_h *g) +int +read_osinfo_db (guestfs_h *g, + read_osinfo_db_callback callback, void *opaque) { int r; - size_t i; - - assert (osinfo_db_size == 0); /* (1) Try the shared osinfo directory, using either the * $OSINFO_SYSTEM_DIR envvar or its default value. @@ -181,59 +119,47 @@ read_osinfo_db (guestfs_h *g) if (path == NULL) path = "/usr/share/osinfo"; os_path = safe_asprintf (g, "%s/os", path); - r = read_osinfo_db_three_levels (g, os_path); + r = read_osinfo_db_three_levels (g, os_path, callback, opaque); } if (r == -1) - goto error; + return -1; else if (r == 1) return 0; /* (2) Try the libosinfo directory, using the newer three-directory * layout ($LIBOSINFO_DB_PATH / "os" / $group-ID / [file.xml]). */ - r = read_osinfo_db_three_levels (g, LIBOSINFO_DB_PATH "/os"); + r = read_osinfo_db_three_levels (g, LIBOSINFO_DB_PATH "/os", callback, opaque); if (r == -1) - goto error; + return -1; else if (r == 1) return 0; /* (3) Try the libosinfo directory, using the old flat directory * layout ($LIBOSINFO_DB_PATH / "oses" / [file.xml]). */ - r = read_osinfo_db_flat (g, LIBOSINFO_DB_PATH "/oses"); + r = read_osinfo_db_flat (g, LIBOSINFO_DB_PATH "/oses", callback, opaque); if (r == -1) - goto error; + return -1; else if (r == 1) return 0; /* Nothing found. */ return 0; - - error: - /* 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_osinfo_db_flat (guestfs_h *g, const char *directory) +read_osinfo_db_flat (guestfs_h *g, const char *directory, + read_osinfo_db_callback callback, void *opaque) { debug (g, "osinfo: loading flat database from %s", directory); - return read_osinfo_db_directory (g, directory); + return read_osinfo_db_directory (g, directory, callback, opaque); } static int -read_osinfo_db_three_levels (guestfs_h *g, const char *directory) +read_osinfo_db_three_levels (guestfs_h *g, const char *directory, + read_osinfo_db_callback callback, void *opaque) { DIR *dir; int r; @@ -259,7 +185,7 @@ read_osinfo_db_three_levels (guestfs_h *g, const char *directory) /* Iterate only on directories. */ if (stat (pathname, &sb) == 0 && S_ISDIR (sb.st_mode)) { - r = read_osinfo_db_directory (g, pathname); + r = read_osinfo_db_directory (g, pathname, callback, opaque); if (r == -1) goto error; } @@ -289,7 +215,8 @@ 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_directory (guestfs_h *g, const char *directory, + read_osinfo_db_callback callback, void *opaque) { DIR *dir; int r; @@ -311,7 +238,7 @@ read_osinfo_db_directory (guestfs_h *g, const char *directory) CLEANUP_FREE char *pathname = NULL; pathname = safe_asprintf (g, "%s/%s", directory, d->d_name); - r = read_osinfo_db_xml (g, pathname); + r = callback (g, pathname, opaque); if (r == -1) goto error; } @@ -339,317 +266,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) -{ - 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 */ diff --git a/mllib/Makefile.am b/mllib/Makefile.am index aa5472ade..2541ce280 100644 --- a/mllib/Makefile.am +++ b/mllib/Makefile.am @@ -40,6 +40,7 @@ SOURCES_MLI = \ getopt.mli \ JSON.mli \ mkdtemp.mli \ + osinfo.mli \ planner.mli \ progress.mli \ regedit.mli \ @@ -71,7 +72,8 @@ SOURCES_ML = \ exit.ml \ checksums.ml \ xml.ml \ - xpath_helpers.ml + xpath_helpers.ml \ + osinfo.ml SOURCES_C = \ ../common/visit/visit.c \ @@ -79,6 +81,9 @@ SOURCES_C = \ ../common/options/keys.c \ ../common/options/uri.c \ ../common/progress/progress.c \ + ../lib/alloc.c \ + ../lib/osinfo.c \ + ../lib/osinfo.h \ common_utils-c.c \ dev_t-c.c \ exit-c.c \ @@ -86,6 +91,7 @@ SOURCES_C = \ fsync-c.c \ getopt-c.c \ mkdtemp-c.c \ + osinfo-c.c \ progress-c.c \ statvfs-c.c \ uri-c.c \ @@ -119,7 +125,8 @@ libmllib_a_CPPFLAGS = \ -I$(top_srcdir)/lib \ -I$(top_srcdir)/common/visit \ -I$(top_srcdir)/common/options \ - -I$(top_srcdir)/common/progress + -I$(top_srcdir)/common/progress \ + -DLIBOSINFO_DB_PATH='"$(datadir)/libosinfo/db"' libmllib_a_CFLAGS = \ $(WARN_CFLAGS) $(WERROR_CFLAGS) \ $(LIBVIRT_CFLAGS) $(LIBXML2_CFLAGS) \ diff --git a/mllib/osinfo-c.c b/mllib/osinfo-c.c new file mode 100644 index 000000000..2f03322d3 --- /dev/null +++ b/mllib/osinfo-c.c @@ -0,0 +1,100 @@ +/* Bindings for osinfo db reading function. + * Copyright (C) 2017 SUSE 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 <assert.h> + +#include <caml/alloc.h> +#include <caml/callback.h> +#include <caml/fail.h> +#include <caml/memory.h> +#include <caml/mlvalues.h> + +#include "guestfs.h" +#include "guestfs-internal.h" +#include "osinfo.h" + +#pragma GCC diagnostic ignored "-Wmissing-prototypes" + +struct callback_wrapper_args { + /* In both case we are pointing to local roots, hence why these are + * value* not value. + */ + value *exnp; /* Safe place to store any exception + raised by callback */ + value *fvp; /* callback. */ +}; + +static int read_osinfo_db_callback_wrapper (guestfs_h *g, const char *path, void *opaque); + +value +guestfs_int_mllib_read_osinfo_db (value gv, value fv) +{ + CAMLparam2 (gv, fv); + guestfs_h *g = (guestfs_h *) Int64_val (gv); + struct callback_wrapper_args args; + + /* This stack address is used to point to the exception, if one is + * raised in the visitor_function. Note that the macro initializes + * this to Val_unit, which is how we know if an exception was set. + */ + CAMLlocal1 (exn); + + args.exnp = &exn; + args.fvp = &fv; + + if (read_osinfo_db (g, read_osinfo_db_callback_wrapper, &args) == -1) { + if (exn != Val_unit) { + /* The failure was caused by the callback raising an + * exception. Re-raise it here. + */ + caml_raise (exn); + } + + caml_failwith ("read_osinfo_db"); +} + + CAMLreturn (Val_unit); +} + +static int +read_osinfo_db_callback_wrapper (guestfs_h *g, const char *path, void *opaque) +{ + CAMLparam0 (); + CAMLlocal2 (pathv, v); + struct callback_wrapper_args *args = opaque; + + assert (path != NULL); + assert (args != NULL); + + pathv = caml_copy_string (path); + + v = caml_callback_exn (*args->fvp, pathv); + + if (Is_exception_result (v)) { + *args->exnp = Extract_exception (v); + return -1; + } + + /* No error, return normally. */ + CAMLreturnT (int, 0); +} diff --git a/mllib/osinfo.ml b/mllib/osinfo.ml new file mode 100644 index 000000000..f5afbd889 --- /dev/null +++ b/mllib/osinfo.ml @@ -0,0 +1,26 @@ +(* virt-builder + * Copyright (C) 2016 - SUSE Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + *) +open Common_utils + +type osinfo_db_callback = string -> unit + +external c_read_osinfo_db : int64 -> osinfo_db_callback -> unit = + "guestfs_int_mllib_read_osinfo_db" + +let read_osinfo_db g f = + c_read_osinfo_db (Guestfs.c_pointer g) f diff --git a/mllib/osinfo.mli b/mllib/osinfo.mli new file mode 100644 index 000000000..d106edae2 --- /dev/null +++ b/mllib/osinfo.mli @@ -0,0 +1,31 @@ +(* virt-builder + * Copyright (C) 2016 - SUSE 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. + *) + +(** Bindings for the src/osinfo.h libosinfo database reading API. *) + +type osinfo_db_callback = string -> unit +(** The osinfo_db_callback is a callback called for each data file + in the libosinfo database. The argument of the function is + the absolute path of the data file. + + The callback may raise an exception, which will cause the whole + database read to fail with an error (raising the same exception). *) + +val read_osinfo_db : Guestfs.t -> osinfo_db_callback -> unit +(** [read_osinfo_db g callback] will find all the libosinfo database + files and call the callback on them. *) -- 2.11.0